97 lines
2.5 KiB
Elixir
97 lines
2.5 KiB
Elixir
defmodule Growth.Score.Scorer do
|
|
@moduledoc """
|
|
Behaviour defining common interface to calculate z-score and percentile for a given measurement.
|
|
"""
|
|
|
|
alias Growth.Calc.Percentile
|
|
alias Growth.Calc.ZScore
|
|
|
|
@callback measure_name() :: atom()
|
|
|
|
@spec results(Growth.t(), [module()]) :: Growth.t()
|
|
@doc """
|
|
Add z-score and centile values in growth measurements `results` for each indicator.
|
|
"""
|
|
def results(growth, indicators) do
|
|
Enum.reduce(indicators, growth, &result/2)
|
|
end
|
|
|
|
@spec result(module(), Growth.t()) :: Growth.t()
|
|
@doc """
|
|
Calculate z-score and percentile values for the given indicator and add them to the growth measurement `results`.
|
|
"""
|
|
def result(indicator, growth) do
|
|
result =
|
|
growth
|
|
|> lms(indicator)
|
|
|> Enum.map(fn {precision, {l, m, s}} ->
|
|
{precision, scores(indicator, growth, l, m, s)}
|
|
end)
|
|
|
|
%{growth | results: Keyword.put(growth.results, indicator.measure_name(), result)}
|
|
end
|
|
|
|
@spec lms(Growth.t(), module()) :: [{String.t(), {number(), number(), number()}}]
|
|
@doc """
|
|
Get the indicaator fitted values of Box-Cox transformation:
|
|
|
|
* power (`l`)
|
|
* median (`m`)
|
|
* coefficient of variation (`s`)
|
|
|
|
"""
|
|
def lms(growth, indicator) do
|
|
[
|
|
{growth.gender, :day, growth.age_in_days},
|
|
{growth.gender, :week, growth.age_in_weeks},
|
|
{growth.gender, :month, growth.age_in_months}
|
|
]
|
|
|> Enum.map(fn {_, precision, _} = key ->
|
|
case :ets.lookup(indicator, key) do
|
|
[{^key, %{l: l, m: m, s: s}} | _] ->
|
|
{precision, {l, m, s}}
|
|
|
|
_ ->
|
|
nil
|
|
end
|
|
end)
|
|
|> Enum.reject(&is_nil/1)
|
|
end
|
|
|
|
@doc """
|
|
Calculate the z-score and percentile of an indicator measurement.
|
|
"""
|
|
@spec scores(module(), Growth.t(), ZScore.l(), ZScore.m(), ZScore.s()) ::
|
|
{Growth.measure(), Growth.measure()}
|
|
def scores(indicator, growth, l, m, s) do
|
|
growth
|
|
|> Map.get(indicator.measure_name())
|
|
|> z_score(l, m, s)
|
|
|> then(fn score -> {score, percentile(score)} end)
|
|
end
|
|
|
|
@doc """
|
|
Check `Growth.Calc.ZScore.compute/4`.
|
|
"""
|
|
@spec z_score(Growth.measure(), ZScore.l(), ZScore.m(), ZScore.s()) :: Growth.measure()
|
|
def z_score(nil, _, _, _) do
|
|
nil
|
|
end
|
|
|
|
def z_score(value, l, m, s) do
|
|
ZScore.compute(value, l, m, s)
|
|
end
|
|
|
|
@doc """
|
|
Check `Growth.Calc.Percentile.compute/1`.
|
|
"""
|
|
@spec percentile(Growth.measure()) :: Growth.measure()
|
|
def percentile(nil) do
|
|
nil
|
|
end
|
|
|
|
def percentile(score) do
|
|
Percentile.compute(score)
|
|
end
|
|
end
|