feat(growth): add weight-for-height calculation
All checks were successful
continuous-integration/drone/pr Build is passing
All checks were successful
continuous-integration/drone/pr Build is passing
This commit is contained in:
parent
49e990935c
commit
c481c42e5b
@ -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
|
||||
|
142
test/growth/score/weight_for_height_test.exs
Normal file
142
test/growth/score/weight_for_height_test.exs
Normal file
@ -0,0 +1,142 @@
|
||||
# test/growth/score/weight_for_height_test.exs
|
||||
defmodule Growth.Score.WeightForHeightTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Growth
|
||||
alias Growth.Score.WeightForHeight
|
||||
|
||||
describe "measure_name/0" do
|
||||
test "returns the correct measure name atom" do
|
||||
assert WeightForHeight.measure_name() == :weight_for_height
|
||||
end
|
||||
end
|
||||
|
||||
describe "lms/2" do
|
||||
test "returns LMS values for exact height match" do
|
||||
growth = %Growth{
|
||||
gender: :male,
|
||||
weight: 7.3,
|
||||
height: 65.0,
|
||||
name: "Joe Doe",
|
||||
date_of_measurement: Date.utc_today(),
|
||||
date_of_birth: Date.shift(Date.utc_today(), day: -91)
|
||||
}
|
||||
|
||||
assert WeightForHeight.lms(growth, WeightForHeight) == [
|
||||
{"height", {-0.35210000000000002, 7.4326999999999996, 0.082170000000000007}}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns LMS values for closest height match when exact is not found" do
|
||||
growth = %Growth{
|
||||
gender: :male,
|
||||
weight: 7.3,
|
||||
height: 64.9,
|
||||
name: "Joe Doe",
|
||||
date_of_measurement: Date.utc_today(),
|
||||
date_of_birth: Date.shift(Date.utc_today(), day: -91)
|
||||
}
|
||||
|
||||
assert WeightForHeight.lms(growth, WeightForHeight) == [
|
||||
{"height", {-0.35210000000000002, 7.4326999999999996, 0.082170000000000007}}
|
||||
]
|
||||
|
||||
growth_closer_to_higher = %Growth{
|
||||
gender: :male,
|
||||
weight: 7.4,
|
||||
height: 65.09,
|
||||
name: "Joe Doe",
|
||||
date_of_measurement: Date.utc_today(),
|
||||
date_of_birth: Date.shift(Date.utc_today(), day: -91)
|
||||
}
|
||||
|
||||
assert WeightForHeight.lms(growth_closer_to_higher, WeightForHeight) == [
|
||||
{"height", {-0.35210000000000002, 7.4562999999999997, 0.082159999999999997}}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns empty list if weight or height is missing" do
|
||||
growth_no_weight = %Growth{
|
||||
gender: :male,
|
||||
height: 60.0,
|
||||
name: "Joe Doe",
|
||||
date_of_measurement: Date.utc_today(),
|
||||
date_of_birth: Date.shift(Date.utc_today(), day: -91)
|
||||
}
|
||||
|
||||
assert WeightForHeight.lms(growth_no_weight, WeightForHeight) == []
|
||||
|
||||
growth_no_height = %Growth{
|
||||
gender: :male,
|
||||
weight: 5.0,
|
||||
name: "Joe Doe",
|
||||
date_of_measurement: Date.utc_today(),
|
||||
date_of_birth: Date.shift(Date.utc_today(), day: -91)
|
||||
}
|
||||
|
||||
assert WeightForHeight.lms(growth_no_height, WeightForHeight) == []
|
||||
|
||||
growth_nil_values = %Growth{
|
||||
gender: :male,
|
||||
weight: nil,
|
||||
height: nil,
|
||||
name: "Joe Doe",
|
||||
date_of_measurement: Date.utc_today(),
|
||||
date_of_birth: Date.shift(Date.utc_today(), day: -91)
|
||||
}
|
||||
|
||||
assert WeightForHeight.lms(growth_nil_values, WeightForHeight) == []
|
||||
end
|
||||
|
||||
test "returns empty list if no matching gender data exists" do
|
||||
# Assuming no :unknown gender data was inserted
|
||||
growth = %Growth{
|
||||
gender: :unknown,
|
||||
weight: 5.0,
|
||||
height: 60.0,
|
||||
name: "Joe Doe",
|
||||
date_of_measurement: Date.utc_today(),
|
||||
date_of_birth: Date.shift(Date.utc_today(), day: -91)
|
||||
}
|
||||
|
||||
assert WeightForHeight.lms(growth, WeightForHeight) == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "find_closest_height/2" do
|
||||
test "finds the lower closest height" do
|
||||
assert WeightForHeight.find_closest_height(:male, 64.99) == [
|
||||
{"height", {-0.35210000000000002, 7.4326999999999996, 0.082170000000000007}}
|
||||
]
|
||||
end
|
||||
|
||||
test "finds the higher closest height" do
|
||||
assert WeightForHeight.find_closest_height(:male, 65.61) == [
|
||||
{"height", {-0.35210000000000002, 7.5738000000000003, 0.082140000000000005}}
|
||||
]
|
||||
end
|
||||
|
||||
test "finds the lower closest height when exactly midway" do
|
||||
expected_lms =
|
||||
[
|
||||
[{"height", {-0.35210000000000002, 7.5034000000000001, 0.082150000000000001}}],
|
||||
[{"height", {-0.35210000000000002, 7.4798999999999998, 0.082159999999999997}}]
|
||||
]
|
||||
|
||||
lms = WeightForHeight.find_closest_height(:male, 65.25)
|
||||
assert Enum.any?(expected_lms, fn entity_lms -> entity_lms == lms end)
|
||||
end
|
||||
|
||||
test "do not match when below minimum height" do
|
||||
assert WeightForHeight.find_closest_height(:male, 44.0) == []
|
||||
end
|
||||
|
||||
test "do not match when above maximum height" do
|
||||
assert WeightForHeight.find_closest_height(:male, 121.0) == []
|
||||
end
|
||||
|
||||
test "returns empty list when no data exists for the gender" do
|
||||
assert WeightForHeight.find_closest_height(:unknown, 60.0) == []
|
||||
end
|
||||
end
|
||||
end
|
@ -61,6 +61,14 @@ defmodule Growth.Data do
|
||||
weight-for-age,male,day,28,0.2331,4.3670999999999998,0.13497000000000001,2.8540000000000001,3.3050000000000002,3.8069999999999999,4.367,4.9880000000000004,5.6740000000000004,6.43
|
||||
weight-for-age,male,week,4,0.2331,4.3670999999999998,0.13497000000000001,2.9,3.3,3.8,4.4000000000000004,5,5.7,6.4
|
||||
weight-for-age,male,month,1,0.22969999999999999,4.4709000000000003,0.13395000000000001,2.9,3.4,3.9,4.5,5.0999999999999996,5.8,6.6
|
||||
weight-for-height,female,length,65,-0.38329999999999997,7.0811999999999999,0.091189999999999993,5.4589999999999996,5.9370000000000003,6.4740000000000002,7.0810000000000004,7.77,8.5549999999999997,9.4540000000000006
|
||||
weight-for-height,female,length,65.099999999999994,-0.38329999999999997,7.1040999999999999,0.091179999999999997,5.4770000000000003,5.9560000000000004,6.4950000000000001,7.1040000000000001,7.7949999999999999,8.5820000000000007,9.484
|
||||
weight-for-height,female,height,65,-0.38329999999999997,7.2401999999999997,0.091130000000000003,5.5830000000000002,6.0709999999999997,6.62,7.24,7.944,8.7460000000000004,9.6639999999999997
|
||||
weight-for-height,female,height,65.099999999999994,-0.38329999999999997,7.2626999999999997,0.091120000000000007,5.6,6.09,6.64,7.2629999999999999,7.9690000000000003,8.7729999999999997,9.6940000000000008
|
||||
weight-for-height,male,length,65,-0.35210000000000002,7.2666000000000004,0.082229999999999998,5.7359999999999998,6.1929999999999996,6.7009999999999996,7.2670000000000003,7.899,8.6080000000000005,9.4060000000000006
|
||||
weight-for-height,male,length,65.099999999999994,-0.35210000000000002,7.2904999999999998,0.082220000000000001,5.7549999999999999,6.2130000000000001,6.7229999999999999,7.29,7.9249999999999998,8.6359999999999992,9.4369999999999994
|
||||
weight-for-height,male,height,65,-0.35210000000000002,7.4326999999999996,0.082170000000000007,5.8680000000000003,6.335,6.8540000000000001,7.4329999999999998,8.0790000000000006,8.8040000000000003,9.6189999999999998
|
||||
weight-for-height,male,height,65.099999999999994,-0.35210000000000002,7.4562999999999997,0.082159999999999997,5.8869999999999996,6.3550000000000004,6.8760000000000003,7.4560000000000004,8.1050000000000004,8.8309999999999995,9.6489999999999991
|
||||
"""
|
||||
|
||||
@doc """
|
||||
|
Loading…
x
Reference in New Issue
Block a user