wip(growth): calculate key metrics for growth
Some checks failed
continuous-integration/drone/pr Build is failing

1. calculate age in days, weeks, and months
2. calculate body mass index for metric and english unities
3. calculate z-score for a given measurement and box-cox parameters (l,
   m, s)
4. calculate centile score for a given measurement and box-cox
   parameters
This commit is contained in:
João Paulo Dubas 2024-06-06 01:33:06 +00:00
parent 8be9fa38bb
commit 500d0e89bb
Signed by: joao.dubas
SSH Key Fingerprint: SHA256:V1mixgOGRc/YMhGx/DNkOSmJxgA2vHNrDZEk3wt/kOA
4 changed files with 141 additions and 0 deletions

40
lib/growth/calc/age.ex Normal file
View File

@ -0,0 +1,40 @@
defmodule Growth.Calc.Age do
@moduledoc """
Calculate the age in months.
"""
# NOTE: (jpd): based on [WHO instructions][0]
# [0]: https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/instructions-en.pdf
@day_in_month 30.4375
@day_in_week 7.0
@doc """
Calculate the age with the precision of day, or week, or month. For age in weeks or months, considers completed ones, removing decimal value.
If only date of birth is available, assumes that measurement date is today.
"""
@spec calculate(:day | :week | :month, Date.t(), Date.t()) :: pos_integer()
def calculate(precision, date_of_birth) do
calculate(precision, date_of_birth, Date.utc_today())
end
def calculate(:month, date_of_birth, date_of_measurement) do
:day
|> calculate(date_of_birth, date_of_measurement)
|> Kernel./(@day_in_month)
|> floor()
end
def calculate(:week, date_of_birth, date_of_measurement) do
:day
|> calculate(date_of_birth, date_of_measurement)
|> Kernel./(@day_in_week)
|> floor()
end
def calculate(:day, date_of_birth, date_of_measurement) do
Date.diff(date_of_measurement, date_of_birth)
end
end

29
lib/growth/calc/bmi.ex Normal file
View File

@ -0,0 +1,29 @@
defmodule Growth.Calc.BMI do
@moduledoc """
Calculate body mass index
"""
@inch_to_centimeter 2.54
@pound_to_kilogram 0.453592
@doc """
Calculate the body mass index for a given weight and height.
Measurements taken in english unit (pounds and inches) are converted to their metric equivalents (kilograms and centimeters).
"""
@spec calculate(:english | :metric, number, number) :: number
def calculate(:english, weight, height) do
metric_weight = pound_to_kilogram(weight)
metric_height = inches_to_centimeters(height)
calculate(:metric, metric_weight, metric_height)
end
def calculate(:metric, weight, height) do
weight / :math.pow(height / 100.0, 2)
end
defp inches_to_centimeters(height), do: height * @inch_to_centimeter
defp pound_to_kilogram(weight), do: weight * @pound_to_kilogram
end

View File

@ -0,0 +1,25 @@
defmodule Growth.Calc.Centile do
@moduledoc """
measures =
[
[30, -1.7862, 16.9392, 0.1107],
[14, -1.3592, 20.4951, 0.12579],
[19, -1.6318, 16.049, 0.10038]
]
Enum.map(measures, &apply(Growth.Calc.Centile, :compute, &1))
"""
# TODO: (jpd) add documentation and typespecs
def compute(y, l, m, s) do
zscore = Growth.Calc.ZScore.raw(y, l, m, s)
cond do
-3 <= zscore and zscore <= 3 ->
m * :math.pow(1 + l * s * zscore, 1 / l)
true ->
:na
end
end
end

View File

@ -0,0 +1,47 @@
defmodule Growth.Calc.ZScore do
@moduledoc """
measures =
[
[30, -1.7862, 16.9392, 0.1107],
[14, -1.3592, 20.4951, 0.12579],
[19, -1.6318, 16.049, 0.10038]
]
Enum.map(measures, &apply(Growth.Calc.ZScore, :compute, &1))
"""
# TODO: (jpd) add documentation and typespecs
def compute(y, l, m, s) do
y
|> raw(l, m, s)
|> adjust(y, l, m, s)
end
def raw(y, l, m, s) do
(:math.pow(y / m, l) - 1) / (s * l)
end
def adjust(zscore, y, l, m, s) when zscore > 3 do
[sd2, sd3, _, _] = cutoffs(l, m, s)
sd_delta = sd3 - sd2
3 + (y - sd3) / sd_delta
end
def adjust(zscore, y, l, m, s) when zscore < -3 do
[_, _, sd2, sd3] = cutoffs(l, m, s)
sd_delta = sd2 - sd3
-3 + (y - sd3) / sd_delta
end
def adjust(zscore, _, _, _, _) do
zscore
end
def cutoffs(l, m, s) do
Enum.map([2, 3, -2, -3], &cutoff_for_standard_deviation(&1, l, m, s))
end
def cutoff_for_standard_deviation(sd, l, m, s) do
m * :math.pow(1 + l * s * sd, 1 / l)
end
end