Joao P Dubas c81e4fda67
All checks were successful
continuous-integration/drone/pr Build is passing
chore(growth): add log in loader
2024-06-11 12:02:14 +00:00

190 lines
4.4 KiB
Elixir

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), 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