feat(growth): add weight-for-height calculation
All checks were successful
continuous-integration/drone/pr Build is passing

This commit is contained in:
2025-04-30 22:40:45 +00:00
parent 49e990935c
commit c481c42e5b
3 changed files with 240 additions and 0 deletions

View File

@@ -1,5 +1,95 @@
defmodule Growth.Score.WeightForHeight do
@moduledoc """
Calculate z-score for weight for height.
This module calculates the z-score for weight relative to height, which is an important
indicator for assessing whether a child's weight is appropriate for their height,
regardless of age.
**Limitation**: the memasurements do not differentiate between length and height, and
always assume height.
"""
@behaviour Growth.Score.Scorer
alias Growth.Score.Scorer
@impl Scorer
@spec measure_name() :: atom()
@doc """
Name of the measurement used in weight for height indicator.
"""
def measure_name do
:weight_for_height
end
@doc """
Custom implementation for weight-for-height lookup, as it requires both weight and height.
This overrides the default implementation in the Scorer module.
"""
@spec lms(Growth.t(), module()) :: [{String.t(), {number(), number(), number()}}]
def lms(%Growth{gender: gender, weight: weight, height: height}, _indicator)
when is_number(weight) and is_number(height) do
# For weight-for-height, we use height as the lookup value instead of age
key = {{gender, :height, height}, :_}
case :ets.match_object(__MODULE__, key) do
[{{^gender, _, ^height}, %{l: l, m: m, s: s}} | _] ->
[{"height", {l, m, s}}]
_ ->
# Try to find the closest height value
find_closest_height(gender, height)
end
end
def lms(_growth, _indicator) do
[]
end
@doc """
Find the closest height value in the ETS table when an exact match isn't found.
"""
@spec find_closest_height(Growth.gender(), number()) :: [
{String.t(), {number(), number(), number()}}
]
def find_closest_height(gender, height) do
# Fetch all entries for the given gender and height measurement
matcher = [
{
{{:"$1", :"$2", :"$3"}, :_},
[
{:andalso,
{:andalso, {:andalso, {:==, :"$1", gender}, {:==, :"$2", :height}},
{:>, :"$3", height - 1.0}}, {:<, :"$3", height + 1.0}}
],
[:"$_"]
}
]
gender_height_entries = :ets.select(__MODULE__, matcher)
case gender_height_entries do
[] ->
# No entries found for this gender
[]
entries ->
# Find the entry with the height closest to the target height
closest_entry =
entries
|> Enum.filter(fn {{_, _, entry_height}, _lms_data} ->
abs(entry_height - height) <= 0.5
end)
|> Enum.min_by(fn {{_, _, entry_height}, _lms_data} ->
abs(entry_height - height)
end)
# Extract the LMS data from the closest entry
{_, %{l: l, m: m, s: s}} = closest_entry
[{"height", {l, m, s}}]
end
end
end