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: * `Growth.Score.ArmCircumference` * `Growth.Score.BMI` * `Growth.Score.HeadCircumference` * `Growth.Score.Height` * `Growth.Score.SubscapularSkinfold` * `Growth.Score.TricepsSkinfold` * `Growth.Score.Weight` * `Growth.Score.WeightForHeight` 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 """ use Task require Logger alias Growth.Score @measure_to_score [ arm_circumference_for_age: Score.ArmCircumference, bmi_for_age: Score.BMI, head_circumference_for_age: Score.HeadCircumference, height_for_age: Score.Height, subscapular_skinfold_for_age: Score.SubscapularSkinfold, triceps_skinfold_for_age: Score.TricepsSkinfold, weight_for_age: Score.Weight, weight_for_height: Score.WeightForHeight ] @doc false def start_link(_) do _ = Enum.map(@measure_to_score, fn {_, measure} -> create_ets(measure) end) Task.start_link(__MODULE__, :all, []) end @spec all :: [[boolean()]] @doc """ Load indicators csv files into their own ets tables. """ def all do Logger.debug("load growth indicators") "priv/growth/indicators/*.csv" |> Path.wildcard() |> Enum.map(&create_ets_from_filename/1) |> Enum.map(&Task.async(__MODULE__, :load_measure, [&1])) |> Task.await_many() end @spec create_ets(module()) :: module() @doc """ Create a public ets table for the growth module, using it as the table name. Returns the given module. """ def create_ets(measure) do try do :ets.new(measure, [:set, :public, :named_table]) rescue _ -> nil end measure end @spec create_ets_from_filename(String.t()) :: {atom(), String.t()} @doc """ Create ets table based on filename and return a tuple with the ets table name and filename. """ def create_ets_from_filename(filename) do measure = filename |> Path.basename() |> Path.rootname() |> String.to_atom() |> then(&Keyword.get(@measure_to_score, &1, &1)) |> create_ets() {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 Logger.debug("load data from #{filename} into #{measure}") 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 = {String.to_atom(gender), String.to_atom(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