diff --git a/lib/growth/indicators/load.ex b/lib/growth/indicators/load.ex new file mode 100644 index 0000000..d8b2206 --- /dev/null +++ b/lib/growth/indicators/load.ex @@ -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