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