[WIP] Implement growth assessment using WHO indicators #80

Draft
joao.dubas wants to merge 76 commits from jpd-feat-add-bmi-module-with-live-view into main
Showing only changes of commit 52c1d0c028 - Show all commits

View File

@ -0,0 +1,145 @@
defmodule Growth.Indicators.Load do
@moduledoc """
Load local indicators csv files into ets.
For each measurement an ets table is created, so the system has the following tables:
* `:arm_circumference_for_age`
* `:bmi_for_age`
* `:head_circumference_for_age`
* `:height_for_age`
* `:subscapular_skinfold_for_age`
* `:telemetry_handler_table`
* `:triceps_skinfold_for_age`
* `:weight_for_age`
* `:weight_for_height`
The rows in the csv files are converted to two tuple, representing the a key and value, with the following format:
* **key**: `{gender, unit, t-value}`
* **value**: map with the keys:
* l
* m
* s
* sd3neg
* sd2neg
* sd1neg
* sd0
* sd1
* sd2
* sd3
* source
* category
"""
@spec all :: [[boolean()]]
@doc """
Load indicators csv files into their own ets tables.
"""
def all do
"priv/growth/indicators/*.csv"
|> Path.wildcard()
|> Enum.map(&create_ets/1)
|> Enum.map(&Task.async(__MODULE__, :load_measure, [&1]))
|> Task.await_many()
end
@spec create_ets(String.t()) :: {:atom, String.t()}
@doc """
Create a public ets table for the csv filename, using the file name as the table name.
Returns a tuple with table name as an atom and the filename.
"""
def create_ets(filename) do
measure = filename |> Path.basename() |> Path.rootname() |> String.to_atom()
try do
:ets.new(measure, [:set, :public, :named_table])
rescue
_ ->
nil
end
{measure, filename}
end
@spec load_measure({:atom, String.t()}) :: [boolean()]
@doc """
Read, convert, and load a measure/filename into the proper ets table.
"""
def load_measure({measure, filename}) do
filename
|> read()
|> convert()
|> load(measure)
end
@spec read(String.t()) :: Enumerable.t()
@doc false
def read(filename) do
filename
|> File.stream!()
|> IndicatorParser.parse_stream()
end
@spec convert(Enumerable.t()) :: Enumerable.t()
@doc false
def convert(data) do
Stream.map(data, fn [
source,
category,
gender,
unit,
t,
l,
m,
s,
sd3neg,
sd2neg,
sd1neg,
sd0,
sd1,
sd2,
sd3
] ->
converted_t =
if unit in ~w(day week month) do
as_integer(t)
else
as_float(t)
end
key = {gender, unit, converted_t}
value = %{
l: as_float(l),
m: as_float(m),
s: as_float(s),
sd3neg: as_float(sd3neg),
sd2neg: as_float(sd2neg),
sd1neg: as_float(sd1neg),
sd0: as_float(sd0),
sd1: as_float(sd1),
sd2: as_float(sd2),
sd3: as_float(sd3),
source: source,
category: category
}
{key, value}
end)
end
@spec load(Enumerable.t(), atom()) :: [boolean()]
@doc false
def load(data, ets_table) do
Enum.map(data, fn {key, value} ->
:ets.insert_new(ets_table, {key, value})
end)
end
defp as_integer(value), do: value |> Integer.parse() |> elem(0)
defp as_float(value), do: value |> Float.parse() |> elem(0)
end