diff --git a/lib/growth/calc/age.ex b/lib/growth/calc/age.ex new file mode 100644 index 0000000..d5fe58d --- /dev/null +++ b/lib/growth/calc/age.ex @@ -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 diff --git a/lib/growth/calc/bmi.ex b/lib/growth/calc/bmi.ex new file mode 100644 index 0000000..d6d3081 --- /dev/null +++ b/lib/growth/calc/bmi.ex @@ -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 diff --git a/lib/growth/calc/centile.ex b/lib/growth/calc/centile.ex new file mode 100644 index 0000000..ea48020 --- /dev/null +++ b/lib/growth/calc/centile.ex @@ -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 diff --git a/lib/growth/calc/z_score.ex b/lib/growth/calc/z_score.ex new file mode 100644 index 0000000..540214f --- /dev/null +++ b/lib/growth/calc/z_score.ex @@ -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