Compare commits
1 Commits
jpd-feat-a
...
a7063ff2b9
Author | SHA1 | Date | |
---|---|---|---|
a7063ff2b9 |
@@ -9,7 +9,7 @@ trigger:
|
||||
|
||||
steps:
|
||||
- name: database healthcheck
|
||||
image: &postgres 'postgres:17.5-alpine'
|
||||
image: &postgres 'postgres:17.4-alpine'
|
||||
environment:
|
||||
PGUSER: &db_user postgres
|
||||
PGPASSWORD: &db_pass postgres
|
||||
@@ -29,7 +29,7 @@ steps:
|
||||
settings:
|
||||
archive_format: gzip
|
||||
bucket: trainlog-cache
|
||||
cache_key: '{{ .Repo.Name }}-{{ checksum ".tool-versions" }}-{{ checksum "mix.lock" }}-{{ checksum "Dockerfile" }}'
|
||||
cache_key: '{{ .Repo.Name }}-{{ checksum ".tool-versions" }}-{{ checksum "mix.lock" }}'
|
||||
endpoint: minio:9000
|
||||
mount:
|
||||
- _build
|
||||
@@ -44,7 +44,7 @@ steps:
|
||||
path: /drone/src/deps
|
||||
|
||||
- name: dependencies and compile
|
||||
image: &elixir 'hexpm/elixir:1.18.4-erlang-27.3.4-debian-bookworm-20250520-slim'
|
||||
image: &elixir 'hexpm/elixir:1.18.3-erlang-27.3.2-debian-bookworm-20250407-slim'
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -y git make
|
||||
@@ -139,7 +139,7 @@ steps:
|
||||
settings:
|
||||
archive_format: gzip
|
||||
bucket: trainlog-cache
|
||||
cache_key: '{{ .Repo.Name }}-{{ checksum ".tool-versions" }}-{{ checksum "mix.lock" }}-{{ checksum "Dockerfile" }}'
|
||||
cache_key: '{{ .Repo.Name }}-{{ checksum ".tool-versions" }}-{{ checksum "mix.lock" }}'
|
||||
endpoint: minio:9000
|
||||
exit_code: true
|
||||
mount:
|
||||
|
@@ -1,3 +1,3 @@
|
||||
erlang 27.3.4
|
||||
elixir 1.18.4
|
||||
lefthook 1.11.14
|
||||
erlang 27.3.3
|
||||
elixir 1.18.3
|
||||
lefthook 1.11.8
|
||||
|
@@ -1,6 +1,6 @@
|
||||
ARG ELIXIR_VERSION=1.18.4
|
||||
ARG OTP_VERSION=27.3.4
|
||||
ARG DEBIAN_VERSION=bookworm-20250520-slim
|
||||
ARG ELIXIR_VERSION=1.18.3
|
||||
ARG OTP_VERSION=27.3.2
|
||||
ARG DEBIAN_VERSION=bookworm-20250407-slim
|
||||
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
|
||||
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
|
||||
|
||||
|
8
Makefile
8
Makefile
@@ -1,6 +1,6 @@
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
COMPOSE = docker compose
|
||||
COMPOSE = docker compose -f docker-compose.yml -f docker-compose.override.yml
|
||||
|
||||
.PHONY: system_setup
|
||||
system_setup: ## setup system deps
|
||||
@@ -27,15 +27,11 @@ credo: ## run credo
|
||||
|
||||
.PHONY: dialyzer
|
||||
dialyzer: ## run dialyzer
|
||||
@mix dialyzer --format short
|
||||
@mix dialyzer --no-check --quiet --ignore-exit-status --format short
|
||||
|
||||
.PHONY: static_code_analysis
|
||||
static_code_analysis: check_format check_compile credo dialyzer ## run static code analysis
|
||||
|
||||
.PHONY: docs
|
||||
docs: ## create documentation files
|
||||
@mix docs
|
||||
|
||||
.PHONY: test
|
||||
test: ## run tests
|
||||
@mix test --cover --trace --slowest 10
|
||||
|
@@ -1,8 +1,5 @@
|
||||
import Config
|
||||
|
||||
config :wabanex, Growth.Indicators.Download,
|
||||
who_req_options: [plug: {Req.Test, Growth.Indicators.Download.WHO}]
|
||||
|
||||
config :wabanex, Wabanex.Repo,
|
||||
database: "wabanex_test#{System.get_env("MIX_TEST_PARTITION")}",
|
||||
pool: Ecto.Adapters.SQL.Sandbox
|
||||
|
@@ -1,9 +1,7 @@
|
||||
---
|
||||
name: ${EX_TRAINER_PROJECT_NAME:-ex_trainer}
|
||||
|
||||
services:
|
||||
db:
|
||||
image: 'postgres:17.5-alpine'
|
||||
image: 'postgres:17.4-alpine'
|
||||
hostname: &db_host db
|
||||
init: true
|
||||
environment:
|
||||
@@ -14,7 +12,6 @@ services:
|
||||
- './priv/docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql'
|
||||
- 'db_data:/var/lib/postgresql/data'
|
||||
restart: unless-stopped
|
||||
|
||||
app:
|
||||
image: 'joaodubas/ex_trainer:${EX_TRAINER_TAG:-dev}'
|
||||
build:
|
||||
@@ -29,17 +26,23 @@ services:
|
||||
init: true
|
||||
develop:
|
||||
watch:
|
||||
- path: ./mix.lock
|
||||
action: rebuild
|
||||
- path: ./
|
||||
ignore:
|
||||
- ./build/
|
||||
- ./deps/
|
||||
- ./.elixir_ls/
|
||||
- ./cover/
|
||||
- ./report/
|
||||
target: /opt/app
|
||||
action: sync
|
||||
- path: ./config/
|
||||
target: /opt/app
|
||||
action: sync+restart
|
||||
- path: ./lib/wabanex/application.ex
|
||||
target: /opt/app
|
||||
action: sync+restart
|
||||
- path: ./mix.exs
|
||||
target: /opt/app
|
||||
action: sync+restart
|
||||
- path: ./mix.lock
|
||||
action: rebuild
|
||||
environment:
|
||||
DNS_CLUSTER_QUERY: *app_host
|
||||
POSTGRES_HOST: *db_host
|
||||
@@ -51,7 +54,6 @@ services:
|
||||
scale: 3
|
||||
entrypoint: ./priv/docker/service/docker-entrypoint.sh
|
||||
command: local-cookie
|
||||
|
||||
test:
|
||||
image: 'joaodubas/ex_trainer:test'
|
||||
build:
|
||||
|
@@ -1,48 +0,0 @@
|
||||
defmodule Growth.Calc.Age do
|
||||
@moduledoc """
|
||||
Calculate the age in months.
|
||||
"""
|
||||
|
||||
@type precision :: :day | :week | :month | :year
|
||||
|
||||
# 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`, considering the measurement date as today.
|
||||
|
||||
For age in weeks or months, considers completed ones, removing decimal value.
|
||||
"""
|
||||
@spec calculate(precision(), Date.t(), Date.t()) :: pos_integer()
|
||||
|
||||
def calculate(precision, date_of_birth) do
|
||||
calculate(precision, date_of_birth, Date.utc_today())
|
||||
end
|
||||
|
||||
@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.
|
||||
"""
|
||||
|
||||
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
|
@@ -1,29 +0,0 @@
|
||||
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
|
@@ -1,45 +0,0 @@
|
||||
defmodule Growth.Calc.Centile do
|
||||
@moduledoc """
|
||||
Calculate the value of measurement at a given z-score and fitted values of Box-Cox by age/height:
|
||||
|
||||
* **power** `t:Growth.Calc.ZScore.l/0`
|
||||
* **median** `t:Growth.Calc.ZScore.m/0`
|
||||
* **coefficientof variation** `t:Growth.Calc.ZScore.s/0`
|
||||
|
||||
This calculation is described in the [instructions provided by the World Health Organization](https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/computation.pdf).
|
||||
|
||||
## Examples:
|
||||
|
||||
iex> measures =
|
||||
...> [
|
||||
...> [-3.0, -1.7862, 16.9392, 0.1107],
|
||||
...> [2, -1.3592, 20.4951, 0.12579],
|
||||
...> [-1.2, -1.6318, 16.049, 0.10038]
|
||||
...> ]
|
||||
iex> Enum.map(measures, &apply(Growth.Calc.Centile, :compute, &1))
|
||||
[13.05127032828574, 27.884359024082663, 14.37765739914362]
|
||||
|
||||
"""
|
||||
|
||||
alias Growth.Calc.ZScore
|
||||
|
||||
@spec compute(number(), ZScore.l(), ZScore.m(), ZScore.s()) :: number() | :na
|
||||
@doc """
|
||||
Compute the measurement value for a given z-score and the Box-Cox values: power (`l`), median (`m`), and coefficient of variation (`s`).
|
||||
|
||||
Returns the measurement based on the z-score when -3 <= z-score <= 3; otherwise, `:na.`
|
||||
|
||||
## Examples:
|
||||
|
||||
iex> Growth.Calc.Centile.compute(0.0, -1.6318, 16.049, 0.10038)
|
||||
16.049
|
||||
|
||||
"""
|
||||
def compute(z_score, l, m, s) when -3 <= z_score and z_score <= 3 do
|
||||
m * :math.pow(1 + l * s * z_score, 1 / l)
|
||||
end
|
||||
|
||||
def compute(_z_score, _l, _m, _s) do
|
||||
:na
|
||||
end
|
||||
end
|
@@ -1,30 +0,0 @@
|
||||
defmodule Growth.Calc.Percentile do
|
||||
@moduledoc """
|
||||
Convert the z-score of a given measurement into a cumulative percentile.
|
||||
|
||||
This calculation is described in the [cumulative distribution function][https://en.wikipedia.org/wiki/Error_function#Cumulative_distribution_function].
|
||||
|
||||
## Examples
|
||||
|
||||
iex> z_scores = [-1.0, 0.0, 1.0]
|
||||
iex> Enum.map(z_scores, &apply(Growth.Calc.Percentile, :compute, [&1]))
|
||||
[0.15865525393145707, 0.5, 0.8413447460685429]
|
||||
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Convert the z-score of a given measurement into a percentile representation, ranging from 0 to 1.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Growth.Calc.Percentile.compute(2.0)
|
||||
0.9772498680518208
|
||||
iex> Growth.Calc.Percentile.compute(-2.0)
|
||||
0.02275013194817921
|
||||
|
||||
"""
|
||||
@spec compute(number()) :: number()
|
||||
def compute(z_score) do
|
||||
0.5 * (:math.erf(z_score / :math.sqrt(2)) + 1)
|
||||
end
|
||||
end
|
@@ -1,116 +0,0 @@
|
||||
defmodule Growth.Calc.ZScore do
|
||||
@moduledoc """
|
||||
Calculate z-score for a given measurement and the fitted values of Box-Cox by age/height:
|
||||
|
||||
* **power** `t:l/0`
|
||||
* **median** `t:m/0`
|
||||
* **coefficientof variation** `t:s/0`
|
||||
|
||||
This calculation is described in the [instructions provided by the World Health Organization](https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/computation.pdf).
|
||||
|
||||
## Examples:
|
||||
|
||||
iex> measures =
|
||||
...> [
|
||||
...> [30, -1.7862, 16.9392, 0.1107],
|
||||
...> [14, -1.3592, 20.4951, 0.12579],
|
||||
...> [19, -1.6318, 16.049, 0.10038]
|
||||
...> ]
|
||||
iex> Enum.map(measures, &apply(Growth.Calc.ZScore, :compute, &1))
|
||||
[3.35390255606726, -3.7985108865993493, 1.4698319520484722]
|
||||
|
||||
"""
|
||||
|
||||
@typedoc """
|
||||
The **power** value in Box-Cox transformation.
|
||||
"""
|
||||
@type l :: number()
|
||||
@typedoc """
|
||||
The **median** value in Box-Cox transformation.
|
||||
"""
|
||||
@type m :: number()
|
||||
@typedoc """
|
||||
The **coefficient of variation** in Box-Cox transformation.
|
||||
"""
|
||||
@type s :: number()
|
||||
|
||||
@spec compute(number(), l(), m(), s()) :: number()
|
||||
@doc """
|
||||
Compute the adjusted z-score for a given measurement (`y`) and the Box-Cox values: power (`l`), median (`m`), and
|
||||
coefficient of variation (`s`).
|
||||
|
||||
## Examples:
|
||||
|
||||
iex> Growth.Calc.ZScore.compute(30, -1.7862, 16.9392, 0.1107)
|
||||
3.35390255606726
|
||||
iex> Growth.Calc.ZScore.compute(14, -1.3592, 20.4951, 0.12579)
|
||||
-3.7985108865993493
|
||||
iex> Growth.Calc.ZScore.compute(19, -1.6318, 16.049, 0.10038)
|
||||
1.4698319520484722
|
||||
|
||||
"""
|
||||
def compute(y, l, m, s) do
|
||||
y
|
||||
|> raw(l, m, s)
|
||||
|> adjust(y, l, m, s)
|
||||
end
|
||||
|
||||
@spec raw(number(), l(), m(), s()) :: number()
|
||||
@doc """
|
||||
Compute the raw z-score for a given measurement (`y`) and the Box-Cox values, power (`l`), median (`m`), and
|
||||
coefficient of variation (`s`).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Growth.Calc.ZScore.raw(30, -1.7862, 16.9392, 0.1107)
|
||||
3.2353902101095855
|
||||
iex> Growth.Calc.ZScore.raw(14, -1.3592, 20.4951, 0.12579)
|
||||
-3.969714730727475
|
||||
iex> Growth.Calc.ZScore.compute(19, -1.6318, 16.049, 0.10038)
|
||||
1.4698319520484722
|
||||
|
||||
"""
|
||||
def raw(y, l, m, s) do
|
||||
(:math.pow(y / m, l) - 1) / (s * l)
|
||||
end
|
||||
|
||||
@spec adjust(number(), number(), l(), m(), s()) :: number()
|
||||
@doc """
|
||||
Adjust the raw z-score considering that extreme values, beyond -3 and 3 standard deviation, must be handled differently.
|
||||
"""
|
||||
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
|
||||
|
||||
@spec cutoffs(number(), number(), number()) :: [number()]
|
||||
@doc """
|
||||
Calculate the standard deviations cutoffs (2, 3, -2, and -3) for a given set of fitted Box-Cox values: power (`l`),
|
||||
median (`m`), and coefficient of variation (`s`).
|
||||
|
||||
These cutoffs are used to calculate the adjusted z-score.
|
||||
"""
|
||||
def cutoffs(l, m, s) do
|
||||
Enum.map([2, 3, -2, -3], &measure_at_standard_deviation(&1, l, m, s))
|
||||
end
|
||||
|
||||
@spec measure_at_standard_deviation(integer(), number(), number(), number()) :: number()
|
||||
@doc """
|
||||
Calculate the measure value at a given standard deviation (`sd`), considering the fitted Box-Cox values: power (`l`),
|
||||
median (`m`), and coefficient of variation (`s`).
|
||||
"""
|
||||
def measure_at_standard_deviation(sd, l, m, s) do
|
||||
m * :math.pow(1 + l * s * sd, 1 / l)
|
||||
end
|
||||
end
|
@@ -1,225 +0,0 @@
|
||||
defmodule Growth do
|
||||
@moduledoc """
|
||||
Follow up child growth from 0 to 19 years, calculating z-scores for the following measurements:
|
||||
|
||||
* weight between ages 0 and 10 years
|
||||
* height between ages 0 and 19 years
|
||||
* body mass index (bmi) between ages 0 and 19 years
|
||||
* head circumference between ages 0 and 5 years
|
||||
* arm circumference between ages 3 months and 5 years
|
||||
* subscapular skinfold between ages 3 months and 5 years
|
||||
* triceps skinfold between ages 3 months and 5 years
|
||||
"""
|
||||
|
||||
alias __MODULE__.Calc.Age
|
||||
alias __MODULE__.Calc.BMI
|
||||
alias __MODULE__.Score
|
||||
|
||||
@type gender :: :male | :female
|
||||
|
||||
@type measure :: number() | nil
|
||||
|
||||
@type opts :: [
|
||||
date_of_measurement: Date.t(),
|
||||
weight: measure(),
|
||||
height: measure(),
|
||||
arm_circumference: measure(),
|
||||
head_circumference: measure(),
|
||||
subscapular_skinfold: measure(),
|
||||
triceps_skinfold: measure()
|
||||
]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
name: String.t(),
|
||||
gender: gender(),
|
||||
date_of_birth: Date.t(),
|
||||
date_of_measurement: Date.t(),
|
||||
age_in_days: pos_integer(),
|
||||
age_in_weeks: pos_integer(),
|
||||
age_in_months: pos_integer(),
|
||||
weight: measure(),
|
||||
height: measure(),
|
||||
arm_circumference: measure(),
|
||||
head_circumference: measure(),
|
||||
subscapular_skinfold: measure(),
|
||||
triceps_skinfold: measure(),
|
||||
bmi: measure(),
|
||||
results: list()
|
||||
}
|
||||
|
||||
@enforce_keys [:name, :gender, :date_of_birth]
|
||||
defstruct [
|
||||
:name,
|
||||
:gender,
|
||||
:date_of_birth,
|
||||
:date_of_measurement,
|
||||
:age_in_days,
|
||||
:age_in_weeks,
|
||||
:age_in_months,
|
||||
:weight,
|
||||
:height,
|
||||
:arm_circumference,
|
||||
:head_circumference,
|
||||
:subscapular_skinfold,
|
||||
:triceps_skinfold,
|
||||
:bmi,
|
||||
:results
|
||||
]
|
||||
|
||||
@valid_measures [
|
||||
:date_of_measurement,
|
||||
:weight,
|
||||
:height,
|
||||
:arm_circumference,
|
||||
:head_circumference,
|
||||
:subscapular_skinfold,
|
||||
:triceps_skinfold
|
||||
]
|
||||
|
||||
@doc """
|
||||
Create a new growth measurement for a children with name, gender, date of birth, and the following optional arguments:
|
||||
|
||||
* `:date_of_measurement`: date when the measures were collected, defaults to today.
|
||||
* `:weight`: weight in kilograms, defaults to `nil`.
|
||||
* `:height`: height in centimeters, defaults to `nil`.
|
||||
* `:arm_circumference`: arm circumference in centimeters, defaults to `nil`.
|
||||
* `:head_circumference`: head circumference in centimeters, defaults to `nil`.
|
||||
* `:subscapular_skinfold`: subscapular skinfold in milimeters, defaults to `nil`.
|
||||
* `:triceps_skinfold`: triceps skinfold in milimeters, defaults to `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Growth.new(
|
||||
...> "child a",
|
||||
...> :male,
|
||||
...> ~D[2024-01-01],
|
||||
...> date_of_measurement: ~D[2024-04-01],
|
||||
...> weight: 8,
|
||||
...> height: 65.4,
|
||||
...> arm_circumference: 15.5,
|
||||
...> head_circumference: 42.8,
|
||||
...> subscapular_skinfold: 10.9,
|
||||
...> triceps_skinfold: 13.5
|
||||
...> )
|
||||
%Growth{
|
||||
name: "child a",
|
||||
gender: :male,
|
||||
date_of_birth: ~D[2024-01-01],
|
||||
date_of_measurement: ~D[2024-04-01],
|
||||
age_in_days: 91,
|
||||
age_in_weeks: 13,
|
||||
age_in_months: 2,
|
||||
weight: 8,
|
||||
height: 65.4,
|
||||
arm_circumference: 15.5,
|
||||
head_circumference: 42.8,
|
||||
subscapular_skinfold: 10.9,
|
||||
triceps_skinfold: 13.5,
|
||||
bmi: 18.703999850368,
|
||||
results: [
|
||||
head_circumference: [
|
||||
day: {1.945484886994137, 0.9741416765426315},
|
||||
week: {1.945484886994137, 0.9741416765426315},
|
||||
month: {3.130859582465616, 0.9991285226182205}
|
||||
],
|
||||
arm_circumference: [day: {1.9227031505630465, 0.9727413295221268}],
|
||||
subscapular_skinfold: [day: {1.9437372448689536, 0.9740364275897885}],
|
||||
triceps_skinfold: [day: {1.950277062993091, 0.974428447506235}],
|
||||
weight: [
|
||||
day: {1.982458622036091, 0.9762860329545557},
|
||||
week: {1.982458622036091, 0.9762860329545557},
|
||||
month: {3.0355951313091745, 0.9987996926038037}
|
||||
],
|
||||
height: [
|
||||
day: {1.956263992749136, 0.9747829682259178},
|
||||
week: {1.956263992749136, 0.9747829682259178},
|
||||
month: {3.4867331002754054, 0.9997555204670452}
|
||||
],
|
||||
bmi: [
|
||||
day: {1.1977344927294398, 0.8844898016950435},
|
||||
week: {1.1977344927294398, 0.8844898016950435},
|
||||
month: {1.5837461190318038, 0.9433742474306444}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
"""
|
||||
@spec new(String.t(), gender(), Date.t(), opts()) :: t()
|
||||
def new(name, gender, date_of_birth, opts \\ []) do
|
||||
default_opts()
|
||||
|> Enum.reduce(
|
||||
%__MODULE__{
|
||||
name: name,
|
||||
gender: gender,
|
||||
date_of_birth: date_of_birth,
|
||||
results: []
|
||||
},
|
||||
fn {key, default_value}, measurement ->
|
||||
opts
|
||||
|> Keyword.get(key, default_value)
|
||||
|> then(&Map.put(measurement, key, &1))
|
||||
end
|
||||
)
|
||||
|> with_age_in_days()
|
||||
|> with_age_in_weeks()
|
||||
|> with_age_in_months()
|
||||
|> with_bmi()
|
||||
|> with_results()
|
||||
end
|
||||
|
||||
for precision <- [:day, :week, :month] do
|
||||
@doc """
|
||||
Add age with given precision in growth measurement.
|
||||
"""
|
||||
@spec unquote(:"with_age_in_#{precision}s")(t()) :: t()
|
||||
|
||||
def unquote(:"with_age_in_#{precision}s")(
|
||||
%__MODULE__{date_of_birth: date_of_birth, date_of_measurement: date_of_measurement} =
|
||||
growth
|
||||
)
|
||||
when not is_nil(date_of_birth) and not is_nil(date_of_measurement) do
|
||||
age = Age.calculate(unquote(precision), date_of_birth, date_of_measurement)
|
||||
Map.put(growth, unquote(:"age_in_#{precision}s"), age)
|
||||
end
|
||||
|
||||
def unquote(:"with_age_in_#{precision}s")(%__MODULE__{date_of_birth: date_of_birth} = growth)
|
||||
when not is_nil(date_of_birth) do
|
||||
unquote(:"with_age_in_#{precision}s")(%{growth | date_of_measurement: Date.utc_today()})
|
||||
end
|
||||
|
||||
def unquote(:"with_age_in_#{precision}s")(growth) do
|
||||
growth
|
||||
end
|
||||
end
|
||||
|
||||
def with_bmi(%__MODULE__{weight: weight, height: height} = growth)
|
||||
when is_number(weight) and is_number(height) do
|
||||
%{growth | bmi: BMI.calculate(:metric, weight, height)}
|
||||
end
|
||||
|
||||
def with_bmi(growth) do
|
||||
growth
|
||||
end
|
||||
|
||||
def with_results(growth) do
|
||||
Score.Scorer.results(growth, [
|
||||
Score.BMI,
|
||||
Score.Height,
|
||||
Score.Weight,
|
||||
Score.TricepsSkinfold,
|
||||
Score.SubscapularSkinfold,
|
||||
Score.ArmCircumference,
|
||||
Score.HeadCircumference
|
||||
])
|
||||
end
|
||||
|
||||
defp default_opts do
|
||||
Enum.map(@valid_measures, fn
|
||||
:date_of_measurement = key ->
|
||||
{key, Date.utc_today()}
|
||||
|
||||
key ->
|
||||
{key, nil}
|
||||
end)
|
||||
end
|
||||
end
|
@@ -1,328 +0,0 @@
|
||||
defmodule Growth.Indicators.Download do
|
||||
@moduledoc """
|
||||
To calculate z-scores for the different growth measurements, the system must:
|
||||
|
||||
1. Fetch indicators from World Health Organization
|
||||
2. Extract data from excel sheets
|
||||
3. Convert the data into proper format, specially, handle with decimal values
|
||||
3. Add metadata to make search for the parameters possible
|
||||
|
||||
The following indicators to construct z-scores are fetched:
|
||||
|
||||
* [height for age 0 to 5 years](https://www.who.int/tools/child-growth-standards/standards/length-height-for-age)
|
||||
* [height for age 5 to 19 years](https://www.who.int/tools/growth-reference-data-for-5to19-years/indicators/height-for-age)
|
||||
* [weight for age 0 to 5 years](https://www.who.int/tools/child-growth-standards/standards/weight-for-age)
|
||||
* [weight for age 5 to 10 years](https://www.who.int/tools/growth-reference-data-for-5to19-years/indicators/weight-for-age-5to10-years)
|
||||
* [weight for height](https://www.who.int/tools/child-growth-standards/standards/weight-for-length-height)
|
||||
* [body mass index (bmi) for age 0 to 5 years](https://www.who.int/toolkits/child-growth-standards/standards/body-mass-index-for-age-bmi-for-age)
|
||||
* [body mass index (bmi) for age 5 to 19 years](https://www.who.int/tools/growth-reference-data-for-5to19-years/indicators/bmi-for-age)
|
||||
* [head circumference for age 0 to 5 years](https://www.who.int/tools/child-growth-standards/standards/head-circumference-for-age)
|
||||
* [arm circumference for age 3 months to 5 years](https://www.who.int/tools/child-growth-standards/standards/arm-circumference-for-age)
|
||||
* [subscapular skinfold for age 3 months to 5 years](https://www.who.int/tools/child-growth-standards/standards/subscapular-skinfold-for-age)
|
||||
* [triceps skinfold for age 3 months to 5 years](https://www.who.int/tools/child-growth-standards/standards/triceps-skinfold-for-age)
|
||||
"""
|
||||
|
||||
NimbleCSV.define(IndicatorParser, separator: ",", escape: "\"")
|
||||
|
||||
@urls %{
|
||||
height_for_age: %{
|
||||
female: %{
|
||||
age_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_girls_0-to-13-weeks_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_girls_0-to-2-years_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_girls_2-to-5-years_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/height-for-age-(5-19-years)/hfa-girls-z-who-2007-exp.xlsx
|
||||
],
|
||||
expanded_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/expandable-tables/lhfa-girls-zscore-expanded-tables.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/height-for-age-(5-19-years)/hfa-girls-z-who-2007-exp.xlsx
|
||||
]
|
||||
},
|
||||
male: %{
|
||||
age_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_boys_0-to-13-weeks_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_boys_0-to-2-years_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_boys_2-to-5-years_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/height-for-age-(5-19-years)/hfa-boys-z-who-2007-exp.xlsx
|
||||
],
|
||||
expanded_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/expandable-tables/lhfa-boys-zscore-expanded-tables.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/height-for-age-(5-19-years)/hfa-boys-z-who-2007-exp.xlsx
|
||||
]
|
||||
}
|
||||
},
|
||||
weight_for_age: %{
|
||||
female: %{
|
||||
age_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/wfa_girls_0-to-13-weeks_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/wfa_girls_0-to-5-years_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/weight-for-age-(5-10-years)/hfa-girls-z-who-2007-exp_7ea58763-36a2-436d-bef0-7fcfbadd2820.xlsx
|
||||
],
|
||||
expanded_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/expanded-tables/wfa-girls-zscore-expanded-tables.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/weight-for-age-(5-10-years)/hfa-girls-z-who-2007-exp_7ea58763-36a2-436d-bef0-7fcfbadd2820.xlsx
|
||||
]
|
||||
},
|
||||
male: %{
|
||||
age_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/wfa_boys_0-to-13-weeks_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/wfa_boys_0-to-5-years_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/weight-for-age-(5-10-years)/hfa-boys-z-who-2007-exp_0ff9c43c-8cc0-4c23-9fc6-81290675e08b.xlsx
|
||||
],
|
||||
expanded_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/expanded-tables/wfa-boys-zscore-expanded-tables.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/weight-for-age-(5-10-years)/hfa-boys-z-who-2007-exp_0ff9c43c-8cc0-4c23-9fc6-81290675e08b.xlsx
|
||||
]
|
||||
}
|
||||
},
|
||||
weight_for_height: %{
|
||||
female: %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/wfl_girls_0-to-2-years_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/wfh_girls_2-to-5-years_zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/expanded-tables/wfl-girls-zscore-expanded-table.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/expanded-tables/wfh-girls-zscore-expanded-tables.xlsx
|
||||
)
|
||||
},
|
||||
male: %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/wfl_boys_0-to-2-years_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/wfh_boys_2-to-5-years_zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/expanded-tables/wfl-boys-zscore-expanded-table.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/expanded-tables/wfh-boys-zscore-expanded-tables.xlsx
|
||||
)
|
||||
}
|
||||
},
|
||||
bmi_for_age: %{
|
||||
female: %{
|
||||
age_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_girls_0-to-13-weeks_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_girls_0-to-2-years_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_girls_2-to-5-years_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/bmi-for-age-(5-19-years)/bmi-girls-z-who-2007-exp.xlsx
|
||||
],
|
||||
expanded_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/expanded-tables/bfa-girls-zscore-expanded-tables.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/bmi-for-age-(5-19-years)/bmi-girls-z-who-2007-exp.xlsx
|
||||
]
|
||||
},
|
||||
male: %{
|
||||
age_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_boys_0-to-13-weeks_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_boys_0-to-2-years_zcores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_boys_2-to-5-years_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/bmi-for-age-(5-19-years)/bmi-boys-z-who-2007-exp.xlsx
|
||||
],
|
||||
expanded_tables: ~w[
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/expanded-tables/bfa-boys-zscore-expanded-tables.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/bmi-for-age-(5-19-years)/bmi-boys-z-who-2007-exp.xlsx
|
||||
]
|
||||
}
|
||||
},
|
||||
head_circumference_for_age: %{
|
||||
female: %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/hcfa-girls-0-13-zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/hcfa-girls-0-5-zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/expanded-tables/hcfa-girls-zscore-expanded-tables.xlsx
|
||||
)
|
||||
},
|
||||
male: %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/hcfa-boys-0-13-zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/hcfa-boys-0-5-zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/expanded-tables/hcfa-boys-zscore-expanded-tables.xlsx
|
||||
)
|
||||
}
|
||||
},
|
||||
arm_circumference_for_age: %{
|
||||
female: %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/arm-circumference-for-age/acfa-girls-3-5-zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/arm-circumference-for-age/expanded-tables/acfa-girls-zscore-expanded-tables.xlsx
|
||||
)
|
||||
},
|
||||
male: %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/arm-circumference-for-age/acfa-boys-3-5-zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/arm-circumference-for-age/expanded-tables/acfa-boys-zscore-expanded-tables.xlsx
|
||||
)
|
||||
}
|
||||
},
|
||||
subscapular_skinfold_for_age: %{
|
||||
female: %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/subscapular-skinfold-for-age/ssfa-girls-3-5-zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/subscapular-skinfold-for-age/expanded-tables/ssfa-girls-zscore-expanded-table.xlsx
|
||||
)
|
||||
},
|
||||
male: %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/subscapular-skinfold-for-age/ssfa-boys-3-5-zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/subscapular-skinfold-for-age/expanded-tables/ssfa-boys-zscore-expanded-table.xlsx
|
||||
)
|
||||
}
|
||||
},
|
||||
triceps_skinfold_for_age: %{
|
||||
female: %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/triceps-skinfold-for-age/tsfa-girls-3-5-zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/triceps-skinfold-for-age/expanded-tables/tsfa-girls-zscore-expanded-tables.xlsx
|
||||
)
|
||||
},
|
||||
male: %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/triceps-skinfold-for-age/tsfa-boys-3-5-zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/triceps-skinfold-for-age/expanded-tables/tsfa-boys-zscore-expanded-tables.xlsx
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def process_all do
|
||||
@urls
|
||||
|> Enum.map(&Task.async(__MODULE__, :process_measure, [&1]))
|
||||
|> Task.await_many()
|
||||
end
|
||||
|
||||
def process_measure({measure, urls}) do
|
||||
urls
|
||||
|> process_genders()
|
||||
|> as_csv()
|
||||
|> save(measure)
|
||||
end
|
||||
|
||||
def process_genders(%{
|
||||
female: %{age_tables: female_urls, expanded_tables: e_female_urls},
|
||||
male: %{age_tables: male_urls, expanded_tables: e_male_urls}
|
||||
}) do
|
||||
[
|
||||
{:female, :age, female_urls},
|
||||
{:male, :age, male_urls},
|
||||
{:female, :expanded, e_female_urls},
|
||||
{:male, :expanded, e_male_urls}
|
||||
]
|
||||
|> Enum.map(&Task.async(__MODULE__, :process_gender, [&1]))
|
||||
|> Task.await_many()
|
||||
|> merge()
|
||||
end
|
||||
|
||||
def process_gender({gender, category, urls}) do
|
||||
urls
|
||||
|> Enum.map(&Task.async(__MODULE__, :process, [gender, category, &1]))
|
||||
|> Task.await_many()
|
||||
|> merge()
|
||||
end
|
||||
|
||||
def process(gender, category, url) do
|
||||
url
|
||||
|> fetch!()
|
||||
|> extract!(url)
|
||||
|> convert(gender, category, url)
|
||||
end
|
||||
|
||||
def fetch!(url) do
|
||||
req =
|
||||
[url: url]
|
||||
|> Keyword.merge(
|
||||
:wabanex
|
||||
|> Application.get_env(__MODULE__, [])
|
||||
|> Keyword.get(:who_req_options, [])
|
||||
)
|
||||
|> Req.new()
|
||||
|
||||
case Req.get(req) do
|
||||
{:ok, %{status: 200, body: body}} ->
|
||||
body
|
||||
|
||||
_ ->
|
||||
raise("fetch failed for url #{url}")
|
||||
end
|
||||
end
|
||||
|
||||
def extract!(content, url) do
|
||||
with {:ok, package} <- XlsxReader.open(content, source: :binary),
|
||||
[sheet_name | _] <- XlsxReader.sheet_names(package),
|
||||
{:ok, data} <- XlsxReader.sheet(package, sheet_name) do
|
||||
data
|
||||
else
|
||||
_ -> raise("failed to extract excel for #{url}")
|
||||
end
|
||||
end
|
||||
|
||||
@common_header ~w(source category gender age_unit age l m s sd3neg sd2neg sd1neg sd0 sd1 sd2 sd3)
|
||||
|
||||
# FIX: (jpd) weight for lenght/height does not have an age in the header row
|
||||
def convert([header | rows], gender, category, url) do
|
||||
age_unit = header |> hd() |> String.downcase()
|
||||
|
||||
fixed_header = header |> tl() |> Enum.map(&String.downcase/1) |> Enum.map(&String.trim/1)
|
||||
|
||||
parsed_header = ["source" | ["category" | ["gender" | ["age_unit" | ["age" | fixed_header]]]]]
|
||||
|
||||
# NOTE: (jpd): parsing the rows consist in:
|
||||
# 1. convert row values to decimal
|
||||
# 2. prepend the values url source, gender, and age unit
|
||||
# 3. convert row to keyword list using the parsed header
|
||||
# 4. convert from keyword list to map
|
||||
# 5. fetch common values based on common headers
|
||||
# 6. sort row values based on common headers
|
||||
parsed_rows =
|
||||
rows
|
||||
|> Stream.map(fn row -> Enum.map(row, &Decimal.new/1) end)
|
||||
|> Stream.map(&[url | [category | [gender | [age_unit | &1]]]])
|
||||
|> Stream.map(&Enum.zip(parsed_header, &1))
|
||||
|> Stream.map(&Map.new/1)
|
||||
|> Stream.map(&Map.take(&1, @common_header))
|
||||
|> Enum.map(fn row ->
|
||||
Enum.map(@common_header, fn key -> Map.get(row, key) end)
|
||||
end)
|
||||
|
||||
[@common_header | parsed_rows]
|
||||
end
|
||||
|
||||
def merge(datum) do
|
||||
datum
|
||||
|> Stream.with_index()
|
||||
|> Stream.map(fn
|
||||
{data, 0} ->
|
||||
data
|
||||
|
||||
{[_ | data], _} ->
|
||||
data
|
||||
end)
|
||||
|> Enum.reduce([], fn data, accum ->
|
||||
Enum.concat(accum, data)
|
||||
end)
|
||||
end
|
||||
|
||||
def as_csv(data) do
|
||||
IndicatorParser.dump_to_iodata(data)
|
||||
end
|
||||
|
||||
def save(data, measurement) do
|
||||
:wabanex
|
||||
|> Application.app_dir(["priv", "growth", "indicators", "#{measurement}.csv"])
|
||||
|> File.write(data)
|
||||
end
|
||||
end
|
@@ -1,190 +0,0 @@
|
||||
defmodule Growth.Indicators.Load do
|
||||
@moduledoc """
|
||||
Load local indicators csv files into ets.
|
||||
|
||||
For each measurement an ets table is created, so the system has the following tables:
|
||||
|
||||
* `Growth.Score.ArmCircumference`
|
||||
* `Growth.Score.BMI`
|
||||
* `Growth.Score.HeadCircumference`
|
||||
* `Growth.Score.Height`
|
||||
* `Growth.Score.SubscapularSkinfold`
|
||||
* `Growth.Score.TricepsSkinfold`
|
||||
* `Growth.Score.Weight`
|
||||
* `Growth.Score.WeightForHeight`
|
||||
|
||||
The rows in the csv files are converted to two tuple, representing the a key and value, with the following format:
|
||||
|
||||
* **key**: `{gender, unit, t-value}`
|
||||
* **value**: map with the keys:
|
||||
* l
|
||||
* m
|
||||
* s
|
||||
* sd3neg
|
||||
* sd2neg
|
||||
* sd1neg
|
||||
* sd0
|
||||
* sd1
|
||||
* sd2
|
||||
* sd3
|
||||
* source
|
||||
* category
|
||||
|
||||
"""
|
||||
|
||||
use Task
|
||||
|
||||
require Logger
|
||||
|
||||
alias Growth.Score
|
||||
|
||||
@measure_to_score [
|
||||
arm_circumference_for_age: Score.ArmCircumference,
|
||||
bmi_for_age: Score.BMI,
|
||||
head_circumference_for_age: Score.HeadCircumference,
|
||||
height_for_age: Score.Height,
|
||||
subscapular_skinfold_for_age: Score.SubscapularSkinfold,
|
||||
triceps_skinfold_for_age: Score.TricepsSkinfold,
|
||||
weight_for_age: Score.Weight,
|
||||
weight_for_height: Score.WeightForHeight
|
||||
]
|
||||
|
||||
@doc false
|
||||
def start_link(_) do
|
||||
_ =
|
||||
Enum.map(@measure_to_score, fn {_, measure} ->
|
||||
create_ets(measure)
|
||||
end)
|
||||
|
||||
Task.start_link(__MODULE__, :all, [])
|
||||
end
|
||||
|
||||
@spec all :: [[boolean()]]
|
||||
@doc """
|
||||
Load indicators csv files into their own ets tables.
|
||||
"""
|
||||
def all do
|
||||
Logger.debug("load growth indicators")
|
||||
|
||||
:wabanex
|
||||
|> Application.app_dir(["priv", "growth", "indicators", "*.csv"])
|
||||
|> Path.wildcard()
|
||||
|> Enum.map(&create_ets_from_filename/1)
|
||||
|> Enum.map(&Task.async(__MODULE__, :load_measure, [&1]))
|
||||
|> Task.await_many()
|
||||
end
|
||||
|
||||
@spec create_ets(module()) :: module()
|
||||
@doc """
|
||||
Create a public ets table for the growth module, using it as the table name.
|
||||
|
||||
Returns the given module.
|
||||
"""
|
||||
def create_ets(measure) do
|
||||
try do
|
||||
:ets.new(measure, [:set, :public, :named_table])
|
||||
rescue
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
|
||||
measure
|
||||
end
|
||||
|
||||
@spec create_ets_from_filename(String.t()) :: {atom(), String.t()}
|
||||
@doc """
|
||||
Create ets table based on filename and return a tuple with the ets table name and filename.
|
||||
"""
|
||||
def create_ets_from_filename(filename) do
|
||||
measure =
|
||||
filename
|
||||
|> Path.basename()
|
||||
|> Path.rootname()
|
||||
|> String.to_atom()
|
||||
|> then(&Keyword.get(@measure_to_score, &1, &1))
|
||||
|> create_ets()
|
||||
|
||||
{measure, filename}
|
||||
end
|
||||
|
||||
@spec load_measure({atom(), String.t()}) :: [boolean()]
|
||||
@doc """
|
||||
Read, convert, and load a measure/filename into the proper ets table.
|
||||
"""
|
||||
def load_measure({measure, filename}) do
|
||||
Logger.debug("load data from #{filename} into #{measure}")
|
||||
|
||||
filename
|
||||
|> read()
|
||||
|> convert()
|
||||
|> load(measure)
|
||||
end
|
||||
|
||||
@spec read(String.t()) :: Enumerable.t()
|
||||
@doc false
|
||||
def read(filename) do
|
||||
filename
|
||||
|> File.stream!()
|
||||
|> IndicatorParser.parse_stream()
|
||||
end
|
||||
|
||||
@spec convert(Enumerable.t()) :: Enumerable.t()
|
||||
@doc false
|
||||
def convert(data) do
|
||||
Stream.map(data, fn [
|
||||
source,
|
||||
category,
|
||||
gender,
|
||||
unit,
|
||||
t,
|
||||
l,
|
||||
m,
|
||||
s,
|
||||
sd3neg,
|
||||
sd2neg,
|
||||
sd1neg,
|
||||
sd0,
|
||||
sd1,
|
||||
sd2,
|
||||
sd3
|
||||
] ->
|
||||
converted_t =
|
||||
if unit in ~w(day week month) do
|
||||
as_integer(t)
|
||||
else
|
||||
as_float(t)
|
||||
end
|
||||
|
||||
key = {String.to_atom(gender), String.to_atom(unit), converted_t}
|
||||
|
||||
value = %{
|
||||
l: as_float(l),
|
||||
m: as_float(m),
|
||||
s: as_float(s),
|
||||
sd3neg: as_float(sd3neg),
|
||||
sd2neg: as_float(sd2neg),
|
||||
sd1neg: as_float(sd1neg),
|
||||
sd0: as_float(sd0),
|
||||
sd1: as_float(sd1),
|
||||
sd2: as_float(sd2),
|
||||
sd3: as_float(sd3),
|
||||
source: source,
|
||||
category: category
|
||||
}
|
||||
|
||||
{key, value}
|
||||
end)
|
||||
end
|
||||
|
||||
@spec load(Enumerable.t(), atom()) :: [boolean()]
|
||||
@doc false
|
||||
def load(data, ets_table) do
|
||||
Enum.map(data, fn {key, value} ->
|
||||
:ets.insert_new(ets_table, {key, value})
|
||||
end)
|
||||
end
|
||||
|
||||
defp as_integer(value), do: value |> Integer.parse() |> elem(0)
|
||||
|
||||
defp as_float(value), do: value |> Float.parse() |> elem(0)
|
||||
end
|
@@ -1,18 +0,0 @@
|
||||
defmodule Growth.Score.ArmCircumference do
|
||||
@moduledoc """
|
||||
Calculate z-score for arm circumference for age.
|
||||
"""
|
||||
|
||||
@behaviour Growth.Score.Scorer
|
||||
|
||||
alias Growth.Score.Scorer
|
||||
|
||||
@impl Scorer
|
||||
@spec measure_name() :: atom()
|
||||
@doc """
|
||||
Name of the measurement used in arm circumference indicator.
|
||||
"""
|
||||
def measure_name do
|
||||
:arm_circumference
|
||||
end
|
||||
end
|
@@ -1,18 +0,0 @@
|
||||
defmodule Growth.Score.BMI do
|
||||
@moduledoc """
|
||||
Calculate z-score for body mass index for age.
|
||||
"""
|
||||
|
||||
@behaviour Growth.Score.Scorer
|
||||
|
||||
alias Growth.Score.Scorer
|
||||
|
||||
@impl Scorer
|
||||
@spec measure_name() :: atom()
|
||||
@doc """
|
||||
Name of the measurement used in BMI indicator.
|
||||
"""
|
||||
def measure_name do
|
||||
:bmi
|
||||
end
|
||||
end
|
@@ -1,18 +0,0 @@
|
||||
defmodule Growth.Score.HeadCircumference do
|
||||
@moduledoc """
|
||||
Calculate z-score for head circumference for age.
|
||||
"""
|
||||
|
||||
@behaviour Growth.Score.Scorer
|
||||
|
||||
alias Growth.Score.Scorer
|
||||
|
||||
@impl Scorer
|
||||
@spec measure_name() :: atom()
|
||||
@doc """
|
||||
Name of the measurement used in head circumference indicator.
|
||||
"""
|
||||
def measure_name do
|
||||
:head_circumference
|
||||
end
|
||||
end
|
@@ -1,18 +0,0 @@
|
||||
defmodule Growth.Score.Height do
|
||||
@moduledoc """
|
||||
Calculate z-score for height for age.
|
||||
"""
|
||||
|
||||
@behaviour Growth.Score.Scorer
|
||||
|
||||
alias Growth.Score.Scorer
|
||||
|
||||
@impl Scorer
|
||||
@spec measure_name() :: atom()
|
||||
@doc """
|
||||
Name of the measurement used in height indicator.
|
||||
"""
|
||||
def measure_name do
|
||||
:height
|
||||
end
|
||||
end
|
@@ -1,96 +0,0 @@
|
||||
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
|
@@ -1,18 +0,0 @@
|
||||
defmodule Growth.Score.SubscapularSkinfold do
|
||||
@moduledoc """
|
||||
Calculate z-score for subscapular skinfold for age.
|
||||
"""
|
||||
|
||||
@behaviour Growth.Score.Scorer
|
||||
|
||||
alias Growth.Score.Scorer
|
||||
|
||||
@impl Scorer
|
||||
@spec measure_name() :: atom()
|
||||
@doc """
|
||||
Name of the measurement used in subscapular skinfold indicator.
|
||||
"""
|
||||
def measure_name do
|
||||
:subscapular_skinfold
|
||||
end
|
||||
end
|
@@ -1,18 +0,0 @@
|
||||
defmodule Growth.Score.TricepsSkinfold do
|
||||
@moduledoc """
|
||||
Calculate z-score for triceps skinfold for age.
|
||||
"""
|
||||
|
||||
@behaviour Growth.Score.Scorer
|
||||
|
||||
alias Growth.Score.Scorer
|
||||
|
||||
@impl Scorer
|
||||
@spec measure_name() :: atom()
|
||||
@doc """
|
||||
Name of the measurement used in triceps skinfold indicator.
|
||||
"""
|
||||
def measure_name do
|
||||
:triceps_skinfold
|
||||
end
|
||||
end
|
@@ -1,18 +0,0 @@
|
||||
defmodule Growth.Score.Weight do
|
||||
@moduledoc """
|
||||
Calculate z-score for weight for age.
|
||||
"""
|
||||
|
||||
@behaviour Growth.Score.Scorer
|
||||
|
||||
alias Growth.Score.Scorer
|
||||
|
||||
@impl Scorer
|
||||
@spec measure_name() :: atom()
|
||||
@doc """
|
||||
Name of the measurement used in weight indicator.
|
||||
"""
|
||||
def measure_name do
|
||||
:weight
|
||||
end
|
||||
end
|
@@ -1,95 +0,0 @@
|
||||
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
|
@@ -9,7 +9,6 @@ defmodule Wabanex.Application do
|
||||
Wabanex.Repo,
|
||||
WabanexWeb.Telemetry,
|
||||
{Phoenix.PubSub, name: Wabanex.PubSub},
|
||||
{Growth.Indicators.Load, []},
|
||||
{DNSCluster,
|
||||
query: Application.get_env(:wabanex, :dns_cluster_query) || :ignore,
|
||||
log: :info,
|
||||
|
16
mix.exs
16
mix.exs
@@ -32,34 +32,28 @@ defmodule Wabanex.MixProject do
|
||||
{:absinthe, "~> 1.7.0"},
|
||||
{:absinthe_plug,
|
||||
git: "https://github.com/absinthe-graphql/absinthe_plug.git",
|
||||
ref: "24ec7aa3b513c7c1aa79e5cad1197cb138603972"},
|
||||
ref: "5de21da279938d1a10642f94d5e9c9fbdc846149"},
|
||||
{:credo, "~> 1.7.0", only: [:dev, :test], runtime: false},
|
||||
{:crudry, "~> 2.4.0"},
|
||||
{:decimal, "~> 2.3.0"},
|
||||
{:dialyxir, "~> 1.4.0", only: [:dev, :test], runtime: false},
|
||||
{:dns_cluster, "~> 0.2.0"},
|
||||
{:ecto_sql, "~> 3.13.0"},
|
||||
{:elixlsx, "~> 0.6.0", only: :test},
|
||||
{:ex_doc, "~> 0.38.0", only: :dev, runtime: false},
|
||||
{:ecto_sql, "~> 3.12.0"},
|
||||
{:gettext, "~> 0.26.0"},
|
||||
{:jason, "~> 1.4.0"},
|
||||
{:junit_formatter, "~> 3.4.0", only: [:test]},
|
||||
{:lcov_ex, "~> 0.3.0", only: [:dev, :test], runtime: false},
|
||||
{:mix_audit, "~> 2.1.0", only: [:dev, :test], runtime: false},
|
||||
{:nimble_csv, "~> 1.2.0"},
|
||||
{:pg_ranges, "~> 1.1.0"},
|
||||
{:phoenix, "~> 1.7.0"},
|
||||
{:phoenix_ecto, "~> 4.6.0"},
|
||||
{:phoenix_live_dashboard, "~> 0.8.0"},
|
||||
{:phoenix_view, "~> 2.0.0"},
|
||||
{:phoenix_live_dashboard, "~> 0.8.0"},
|
||||
{:plug_cowboy, "~> 2.7.0"},
|
||||
{:postgrex, "~> 0.20.0"},
|
||||
{:prom_ex, "~> 1.11.0"},
|
||||
{:req, "~> 0.5.0"},
|
||||
{:sobelow, "~> 0.14", only: [:dev, :test], runtime: false},
|
||||
{:sobelow, "~> 0.13", only: [:dev, :test], runtime: false},
|
||||
{:telemetry_metrics, "~> 1.1.0"},
|
||||
{:telemetry_poller, "~> 1.2.0"},
|
||||
{:xlsx_reader, "~> 0.8.0"}
|
||||
{:telemetry_poller, "~> 1.2.0"}
|
||||
]
|
||||
end
|
||||
|
||||
|
38
mix.lock
38
mix.lock
@@ -1,23 +1,20 @@
|
||||
%{
|
||||
"absinthe": {:hex, :absinthe, "1.7.10", "b33471b593260f148d05e4d771d1857e07b70a680f89cfa75184098bef4ec893", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1 or ~> 0.3", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ffda95735364c041a65a4b0e02ffb04eabb1e52ab664fa7eeecefb341449e8c2"},
|
||||
"absinthe_plug": {:git, "https://github.com/absinthe-graphql/absinthe_plug.git", "24ec7aa3b513c7c1aa79e5cad1197cb138603972", [ref: "24ec7aa3b513c7c1aa79e5cad1197cb138603972"]},
|
||||
"absinthe": {:hex, :absinthe, "1.7.9", "85c5bc9dfcb984fadf632d6e9a9f6daa7770c68d4f751f20cd5f9e77c57be4a1", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1 or ~> 0.3", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "08ae6339b05252b98e859ccd76614c94325c991f85aa325edde30027b7b044fc"},
|
||||
"absinthe_plug": {:git, "https://github.com/absinthe-graphql/absinthe_plug.git", "5de21da279938d1a10642f94d5e9c9fbdc846149", [ref: "5de21da279938d1a10642f94d5e9c9fbdc846149"]},
|
||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||
"castore": {:hex, :castore, "1.0.14", "4582dd7d630b48cf5e1ca8d3d42494db51e406b7ba704e81fbd401866366896a", [:mix], [], "hexpm", "7bc1b65249d31701393edaaac18ec8398d8974d52c647b7904d01b964137b9f4"},
|
||||
"castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"},
|
||||
"cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"},
|
||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||
"cowlib": {:hex, :cowlib, "2.15.0", "3c97a318a933962d1c12b96ab7c1d728267d2c523c25a5b57b0f93392b6e9e25", [:make, :rebar3], [], "hexpm", "4f00c879a64b4fe7c8fcb42a4281925e9ffdb928820b03c3ad325a617e857532"},
|
||||
"cowlib": {:hex, :cowlib, "2.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"},
|
||||
"credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"},
|
||||
"crudry": {:hex, :crudry, "2.4.0", "d175f1a8ee44456e852ae6b4d75997642a3e2a9f62ac28e4736656856c80dff5", [:mix], [{:absinthe, ">= 1.4.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:gettext, ">= 0.0.0", [hex: :gettext, repo: "hexpm", optional: false]}], "hexpm", "a14150b3f82e060e602d67f2c52c6195f0fefb49aee7d226d68dc3b83b8f58dc"},
|
||||
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
|
||||
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
|
||||
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
|
||||
"ecto": {:hex, :ecto, "3.13.1", "ebb11c2f0307ff62e8aaba57def59ad920a3cbd89d002b1118944cbf598c13c7", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d9ea5075a6f3af9cd2cdbabe8a0759eb73b485e981fd7c03014f79479ac85340"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.13.1", "d3d76d78afd2757644b5c4f7ca37f90bcf1e05d05a06cca8526e30cefb0034a1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fba0174284fd339f69376b0405942036ce5f0ff7d59402a6ccf3b7ce2903198"},
|
||||
"elixlsx": {:hex, :elixlsx, "0.6.0", "858c2c821ab52f4ca0988adce188d19f3b239a4fff8b36b26cd81ec8af9b2ab3", [:mix], [], "hexpm", "c4766f47afea075a85950a5c6fe981e98b8b8a30cc076382aaacf2bb8dbcd25d"},
|
||||
"ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
|
||||
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"},
|
||||
"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},
|
||||
"file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
|
||||
"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
|
||||
@@ -26,13 +23,9 @@
|
||||
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
|
||||
"junit_formatter": {:hex, :junit_formatter, "3.4.0", "d0e8db6c34dab6d3c4154c3b46b21540db1109ae709d6cf99ba7e7a2ce4b1ac2", [:mix], [], "hexpm", "bb36e2ae83f1ced6ab931c4ce51dd3dbef1ef61bb4932412e173b0cfa259dacd"},
|
||||
"lcov_ex": {:hex, :lcov_ex, "0.3.4", "f48aed787db0d1cff1409db391f442cdbc0af0f860dbc326430030027e6ded36", [:mix], [], "hexpm", "c3987b6aeadd78d4b7933fa9cc4de3bd69d34f0fae39bad908f79a7ceea957e5"},
|
||||
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
|
||||
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
|
||||
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
|
||||
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
|
||||
"mix_audit": {:hex, :mix_audit, "2.1.5", "c0f77cee6b4ef9d97e37772359a187a166c7a1e0e08b50edf5bf6959dfe5a016", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "87f9298e21da32f697af535475860dc1d3617a010e0b418d2ec6142bc8b42d69"},
|
||||
"nimble_csv": {:hex, :nimble_csv, "1.2.0", "4e26385d260c61eba9d4412c71cea34421f296d5353f914afe3f2e71cce97722", [:mix], [], "hexpm", "d0628117fcc2148178b034044c55359b26966c6eaa8e2ce15777be3bbc91b12a"},
|
||||
"mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"},
|
||||
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
|
||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
||||
@@ -40,22 +33,20 @@
|
||||
"peep": {:hex, :peep, "3.5.0", "9f6ead7b0f2c684494200c8fc02e7e62e8c459afe861b29bd859e4c96f402ed8", [:mix], [{:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:plug, "~> 1.16", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry_metrics, "~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5a73a99c6e60062415efeb7e536a663387146463a3d3df1417da31fd665ac210"},
|
||||
"pg_ranges": {:hex, :pg_ranges, "1.1.1", "304da41118282eb81ffaefc3195e379afe139867fdc5171626aec9c83d360537", [:mix], [{:decimal, "~> 2.1", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.11", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "d49c28628c2276d4964f0ced6cc8c140a62af6eeb05c1b86a23afed8b3a5db8c"},
|
||||
"phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.4", "dcf3483ab45bab4c15e3a47c34451392f64e433846b08469f5d16c2a4cd70052", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f5b8584c36ccc9b903948a696fc9b8b81102c79c7c0c751a9f00cdec55d5f2d7"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "1.0.17", "beeb16d83a7d3760f7ad463df94e83b087577665d2acc0bf2987cd7d9778068f", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a4ca05c1eb6922c4d07a508a75bfa12c45e5f4d8f77ae83283465f02c53741e1"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "1.0.9", "4dc5e535832733df68df22f9de168b11c0c74bca65b27b088a10ac36dfb75d04", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1dccb04ec8544340e01608e108f32724458d0ac4b07e551406b3b920c40ba2e5"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
|
||||
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
|
||||
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
|
||||
"plug": {:hex, :plug, "1.18.0", "d78df36c41f7e798f2edf1f33e1727eae438e9dd5d809a9997c463a108244042", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "819f9e176d51e44dc38132e132fe0accaf6767eab7f0303431e404da8476cfa2"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.7.4", "729c752d17cf364e2b8da5bdb34fb5804f56251e88bb602aff48ae0bd8673d11", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9b85632bd7012615bae0a5d70084deb1b25d2bcbb32cab82d1e9a1e023168aa3"},
|
||||
"plug": {:hex, :plug, "1.17.0", "a0832e7af4ae0f4819e0c08dd2e7482364937aea6a8a997a679f2cbb7e026b2e", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6692046652a69a00a5a21d0b7e11fcf401064839d59d6b8787f23af55b1e6bc"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.7.3", "1304d36752e8bdde213cea59ef424ca932910a91a07ef9f3874be709c4ddb94b", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "77c95524b2aa5364b247fa17089029e73b951ebc1adeef429361eab0bb55819d"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
|
||||
"postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"},
|
||||
"prom_ex": {:hex, :prom_ex, "1.11.0", "1f6d67f2dead92224cb4f59beb3e4d319257c5728d9638b4a5e8ceb51a4f9c7e", [:mix], [{:absinthe, ">= 1.7.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.1.0", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.11.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.18", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.10.0", [hex: :oban, repo: "hexpm", optional: true]}, {:octo_fetch, "~> 0.4", [hex: :octo_fetch, repo: "hexpm", optional: false]}, {:peep, "~> 3.0", [hex: :peep, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.7.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.20.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.16.0", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 2.6.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, ">= 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.2", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.1", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "76b074bc3730f0802978a7eb5c7091a65473eaaf07e99ec9e933138dcc327805"},
|
||||
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
|
||||
"req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"},
|
||||
"saxy": {:hex, :saxy, "1.6.0", "02cb4e9bd045f25ac0c70fae8164754878327ee393c338a090288210b02317ee", [:mix], [], "hexpm", "ef42eb4ac983ca77d650fbdb68368b26570f6cc5895f0faa04d34a6f384abad3"},
|
||||
"sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"},
|
||||
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
|
||||
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"},
|
||||
@@ -63,7 +54,6 @@
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "1.2.0", "ba82e333215aed9dd2096f93bd1d13ae89d249f82760fcada0850ba33bac154b", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7216e21a6c326eb9aa44328028c34e9fd348fb53667ca837be59d0aa2a0156e8"},
|
||||
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||
"websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},
|
||||
"xlsx_reader": {:hex, :xlsx_reader, "0.8.8", "fbb29049548ff687f03a2873f2eb0d9057e47eb69cafb07f44988f030fb620b7", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:saxy, "~> 1.5", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "642d979a3a156b150bb76a89998a130483e1c399fa32e8d3a66abc1d9799dbd7"},
|
||||
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
|
||||
"yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"},
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,46 +0,0 @@
|
||||
defmodule Growth.Calc.AgeTest do
|
||||
@moduledoc false
|
||||
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Growth.Calc.Age
|
||||
|
||||
describe "calculate/2" do
|
||||
test "consider today as date of measurement" do
|
||||
date_of_birth = Date.add(Date.utc_today(), -7)
|
||||
assert 7 == Age.calculate(:day, date_of_birth)
|
||||
end
|
||||
end
|
||||
|
||||
describe "calculate/3" do
|
||||
test "calculate age in days" do
|
||||
date_of_measurement = ~D[2024-06-09]
|
||||
days_of_birth = [{-1, 1}, {-7, 7}, {-30, 30}]
|
||||
|
||||
Enum.map(days_of_birth, fn {days_ago, expected_age} ->
|
||||
date_of_birth = Date.add(date_of_measurement, days_ago)
|
||||
assert expected_age == Age.calculate(:day, date_of_birth, date_of_measurement)
|
||||
end)
|
||||
end
|
||||
|
||||
test "calculate age in completed weeks" do
|
||||
date_of_measurement = ~D[2024-06-09]
|
||||
days_of_birth = [{-1, 0}, {-7, 1}, {-16, 2}, {-30, 4}]
|
||||
|
||||
Enum.map(days_of_birth, fn {days_ago, expected_age} ->
|
||||
date_of_birth = Date.add(date_of_measurement, days_ago)
|
||||
assert expected_age == Age.calculate(:week, date_of_birth, date_of_measurement)
|
||||
end)
|
||||
end
|
||||
|
||||
test "calculate age in completed months" do
|
||||
date_of_measurement = ~D[2024-06-09]
|
||||
days_of_birth = [{-1, 0}, {-16, 0}, {-31, 1}, {-70, 2}, {-93, 3}]
|
||||
|
||||
Enum.map(days_of_birth, fn {days_ago, expected_age} ->
|
||||
date_of_birth = Date.add(date_of_measurement, days_ago)
|
||||
assert expected_age == Age.calculate(:month, date_of_birth, date_of_measurement)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
@@ -1,23 +0,0 @@
|
||||
defmodule Growth.Calc.BMITest do
|
||||
@moduledoc false
|
||||
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Growth.Calc.BMI
|
||||
|
||||
describe "calculate/3" do
|
||||
test "in metric system" do
|
||||
weight_kg = 78.5
|
||||
height_cm = 168.2
|
||||
|
||||
assert 27.74710475751505 == BMI.calculate(:metric, weight_kg, height_cm)
|
||||
end
|
||||
|
||||
test "in english system" do
|
||||
weight_lb = 173.1
|
||||
height_in = 66.2
|
||||
|
||||
assert 27.770202207557876 == BMI.calculate(:english, weight_lb, height_in)
|
||||
end
|
||||
end
|
||||
end
|
@@ -1,24 +0,0 @@
|
||||
defmodule Growth.Calc.CentileTest do
|
||||
@moduledoc false
|
||||
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
import Growth.Data, only: [sample: 0]
|
||||
|
||||
doctest Growth.Calc.Centile
|
||||
|
||||
alias Growth.Calc.Centile
|
||||
|
||||
describe "compute/4" do
|
||||
for %{key: key} = params <- sample() do
|
||||
@tag params: params
|
||||
test "returns the measure given a z-score and box-cox fitted values #{key}", %{
|
||||
params: params
|
||||
} do
|
||||
%{zscore: zscore, measure: measure, l: l, m: m, s: s} = params
|
||||
|
||||
assert_in_delta Centile.compute(zscore, l, m, s), measure, 0.05
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@@ -1,26 +0,0 @@
|
||||
defmodule Growth.Calc.PercentileTest do
|
||||
@moduledoc false
|
||||
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
doctest Growth.Calc.Percentile
|
||||
|
||||
alias Growth.Calc.Percentile
|
||||
|
||||
describe "compute/1" do
|
||||
for {zscore, _} = params <- [
|
||||
{-3, 0.0013498125},
|
||||
{-2, 0.0227502617},
|
||||
{-1, 0.1586553192},
|
||||
{0, 0.5000000000},
|
||||
{1, 0.8413446808},
|
||||
{2, 0.9772497383},
|
||||
{3, 0.9986501875}
|
||||
] do
|
||||
@tag params: params
|
||||
test "returns the percentile for z-score #{zscore}", %{params: {zscore, percentile}} do
|
||||
assert_in_delta Percentile.compute(zscore), percentile, 0.0000005
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@@ -1,24 +0,0 @@
|
||||
defmodule Growth.Calc.ZScoreTest do
|
||||
@moduledoc false
|
||||
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
import Growth.Data, only: [sample: 0]
|
||||
|
||||
doctest Growth.Calc.ZScore
|
||||
|
||||
alias Growth.Calc.ZScore
|
||||
|
||||
describe "compute/4" do
|
||||
for %{key: key} = params <- sample() do
|
||||
@tag params: params
|
||||
test "returns a z-score given a measurement and box-cox fitted values #{key}", %{
|
||||
params: params
|
||||
} do
|
||||
%{zscore: zscore, measure: measure, l: l, m: m, s: s} = params
|
||||
|
||||
assert_in_delta ZScore.compute(measure, l, m, s), zscore, 0.12
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@@ -1,131 +0,0 @@
|
||||
defmodule GrowthTest do
|
||||
@moduledoc false
|
||||
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
doctest Growth
|
||||
|
||||
@child %{
|
||||
name: "Jane Doe",
|
||||
gender: :female,
|
||||
date_of_measurement: Date.utc_today(),
|
||||
age_in_days: 91,
|
||||
weight: 5.84,
|
||||
height: 59.78,
|
||||
head_circumference: 39.51,
|
||||
arm_circumference: 13.02,
|
||||
subscapular_skinfold: 7.79,
|
||||
triceps_skinfold: 9.75
|
||||
}
|
||||
@child_date_of_birth Date.shift(@child.date_of_measurement, day: @child.age_in_days * -1)
|
||||
|
||||
describe "new/4" do
|
||||
test "create a new growth measurement" do
|
||||
growth =
|
||||
Growth.new(@child.name, @child.gender, @child_date_of_birth,
|
||||
date_of_measurement: @child.date_of_measurement,
|
||||
weight: @child.weight,
|
||||
height: @child.height,
|
||||
head_circumference: @child.head_circumference,
|
||||
arm_circumference: @child.arm_circumference,
|
||||
subscapular_skinfold: @child.subscapular_skinfold,
|
||||
triceps_skinfold: @child.triceps_skinfold
|
||||
)
|
||||
|
||||
assert @child.name == growth.name
|
||||
assert @child.gender == growth.gender
|
||||
assert @child_date_of_birth == growth.date_of_birth
|
||||
assert @child.date_of_measurement == growth.date_of_measurement
|
||||
assert @child.age_in_days == growth.age_in_days
|
||||
assert 13 == growth.age_in_weeks
|
||||
assert 2 == growth.age_in_months
|
||||
assert @child.weight == growth.weight
|
||||
assert @child.height == growth.height
|
||||
assert @child.head_circumference == growth.head_circumference
|
||||
assert @child.arm_circumference == growth.arm_circumference
|
||||
assert @child.subscapular_skinfold == growth.subscapular_skinfold
|
||||
assert @child.triceps_skinfold == growth.triceps_skinfold
|
||||
end
|
||||
end
|
||||
|
||||
describe "with_bmi/1" do
|
||||
test "calculate bmi from measurement" do
|
||||
growth =
|
||||
%Growth{
|
||||
name: @child.name,
|
||||
gender: @child.gender,
|
||||
date_of_birth: @child_date_of_birth,
|
||||
date_of_measurement: @child.date_of_measurement,
|
||||
weight: @child.weight,
|
||||
height: @child.height,
|
||||
head_circumference: @child.head_circumference,
|
||||
arm_circumference: @child.arm_circumference,
|
||||
subscapular_skinfold: @child.subscapular_skinfold,
|
||||
triceps_skinfold: @child.triceps_skinfold
|
||||
}
|
||||
|> Growth.with_age_in_days()
|
||||
|> Growth.with_age_in_weeks()
|
||||
|> Growth.with_age_in_months()
|
||||
|> Growth.with_bmi()
|
||||
|
||||
assert 16.341842694989243 == growth.bmi
|
||||
end
|
||||
end
|
||||
|
||||
describe "with_results/1" do
|
||||
test "calculate z-score and percentiles from measurement" do
|
||||
growth =
|
||||
%Growth{
|
||||
name: @child.name,
|
||||
gender: @child.gender,
|
||||
date_of_birth: @child_date_of_birth,
|
||||
date_of_measurement: @child.date_of_measurement,
|
||||
weight: @child.weight,
|
||||
height: @child.height,
|
||||
head_circumference: @child.head_circumference,
|
||||
arm_circumference: @child.arm_circumference,
|
||||
subscapular_skinfold: @child.subscapular_skinfold,
|
||||
triceps_skinfold: @child.triceps_skinfold,
|
||||
results: []
|
||||
}
|
||||
|> Growth.with_age_in_days()
|
||||
|> Growth.with_age_in_weeks()
|
||||
|> Growth.with_age_in_months()
|
||||
|> Growth.with_bmi()
|
||||
|> Growth.with_results()
|
||||
|
||||
assert [
|
||||
day: {9.496948997971584e-4, 0.5003788733920584},
|
||||
week: {9.496948997971584e-4, 0.5003788733920584},
|
||||
month: {1.0060928051683196, 0.8428145353253619}
|
||||
] == Keyword.get(growth.results, :weight)
|
||||
|
||||
assert [
|
||||
day: {0.0012831717968983271, 0.5005119113423219},
|
||||
week: {0.0012831717968983271, 0.5005119113423219},
|
||||
month: {1.3322618635180914, 0.9086129228760054}
|
||||
] == Keyword.get(growth.results, :height)
|
||||
|
||||
assert [
|
||||
day: {-0.007440424136462911, 0.4970317276150869},
|
||||
week: {-0.007440424136462911, 0.4970317276150869},
|
||||
month: {0.3827194919327071, 0.6490361198066796}
|
||||
] == Keyword.get(growth.results, :bmi)
|
||||
|
||||
assert [
|
||||
day: {-0.008864109494641385, 0.4964637782528006},
|
||||
week: {-0.008864109494641385, 0.4964637782528006},
|
||||
month: {1.0380198575748647, 0.8503695948597997}
|
||||
] == Keyword.get(growth.results, :head_circumference)
|
||||
|
||||
assert [day: {-0.004182676756535293, 0.49833135826198854}] ==
|
||||
Keyword.get(growth.results, :arm_circumference)
|
||||
|
||||
assert [day: {0.001811404894950268, 0.5007226456043324}] ==
|
||||
Keyword.get(growth.results, :subscapular_skinfold)
|
||||
|
||||
assert [day: {-0.0019309186728254878, 0.49922967538007906}] ==
|
||||
Keyword.get(growth.results, :triceps_skinfold)
|
||||
end
|
||||
end
|
||||
end
|
@@ -1,297 +0,0 @@
|
||||
defmodule Growth.Indicators.DownloadTest do
|
||||
@moduledoc false
|
||||
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Elixlsx.Sheet
|
||||
alias Elixlsx.Workbook
|
||||
|
||||
alias Growth.Indicators.Download
|
||||
|
||||
setup do
|
||||
mock_who_request()
|
||||
end
|
||||
|
||||
describe "process/3" do
|
||||
test "fetch excel from url and convert it to a list of lists" do
|
||||
gender = :female
|
||||
category = :age_tables
|
||||
|
||||
url =
|
||||
"https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_girls_0-to-13-weeks_zscores.xlsx"
|
||||
|
||||
content = Download.process(gender, category, url)
|
||||
|
||||
expected_content =
|
||||
[
|
||||
~w(source category gender age_unit age l m s sd3neg sd2neg sd1neg sd0 sd1 sd2 sd3),
|
||||
[
|
||||
url,
|
||||
category,
|
||||
gender,
|
||||
"week",
|
||||
Decimal.new("0"),
|
||||
Decimal.new("1"),
|
||||
Decimal.new("49.1477"),
|
||||
Decimal.new("0.0379"),
|
||||
Decimal.new("43.6"),
|
||||
Decimal.new("45.4"),
|
||||
Decimal.new("47.3"),
|
||||
Decimal.new("49.1"),
|
||||
Decimal.new("51"),
|
||||
Decimal.new("52.9"),
|
||||
Decimal.new("54.7")
|
||||
]
|
||||
]
|
||||
|
||||
assert expected_content == content
|
||||
end
|
||||
end
|
||||
|
||||
describe "process_gender/1" do
|
||||
test "merge multiple contents into a single one" do
|
||||
category = :age_tables
|
||||
gender = :female
|
||||
|
||||
urls =
|
||||
[
|
||||
"https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_girls_0-to-13-weeks_zscores.xlsx",
|
||||
"https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_girls_0-to-2-years_zscores.xlsx"
|
||||
]
|
||||
|
||||
content = Download.process_gender({gender, category, urls})
|
||||
|
||||
expected_content =
|
||||
[
|
||||
~w(source category gender age_unit age l m s sd3neg sd2neg sd1neg sd0 sd1 sd2 sd3),
|
||||
[
|
||||
List.first(urls),
|
||||
category,
|
||||
gender,
|
||||
"week",
|
||||
Decimal.new("0"),
|
||||
Decimal.new("1"),
|
||||
Decimal.new("49.1477"),
|
||||
Decimal.new("0.0379"),
|
||||
Decimal.new("43.6"),
|
||||
Decimal.new("45.4"),
|
||||
Decimal.new("47.3"),
|
||||
Decimal.new("49.1"),
|
||||
Decimal.new("51"),
|
||||
Decimal.new("52.9"),
|
||||
Decimal.new("54.7")
|
||||
],
|
||||
[
|
||||
List.last(urls),
|
||||
category,
|
||||
gender,
|
||||
"month",
|
||||
Decimal.new("0"),
|
||||
Decimal.new("1"),
|
||||
Decimal.new("49.1477"),
|
||||
Decimal.new("0.0379"),
|
||||
Decimal.new("43.6"),
|
||||
Decimal.new("45.4"),
|
||||
Decimal.new("47.3"),
|
||||
Decimal.new("49.1"),
|
||||
Decimal.new("51"),
|
||||
Decimal.new("52.9"),
|
||||
Decimal.new("54.7")
|
||||
]
|
||||
]
|
||||
|
||||
assert expected_content == content
|
||||
end
|
||||
end
|
||||
|
||||
describe "process_genders/1" do
|
||||
test "merge multiple genders and contents into a single one" do
|
||||
female_urls = %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_girls_0-to-13-weeks_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_girls_0-to-2-years_zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/expandable-tables/lhfa-girls-zscore-expanded-tables.xlsx
|
||||
)
|
||||
}
|
||||
|
||||
male_urls = %{
|
||||
age_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_boys_0-to-13-weeks_zscores.xlsx
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_boys_0-to-2-years_zscores.xlsx
|
||||
),
|
||||
expanded_tables: ~w(
|
||||
https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/expandable-tables/lhfa-boys-zscore-expanded-tables.xlsx
|
||||
)
|
||||
}
|
||||
|
||||
content = Download.process_genders(%{female: female_urls, male: male_urls})
|
||||
|
||||
expected_content =
|
||||
[
|
||||
~w(source category gender age_unit age l m s sd3neg sd2neg sd1neg sd0 sd1 sd2 sd3),
|
||||
[
|
||||
female_urls |> Map.get(:age_tables) |> List.first(),
|
||||
:age,
|
||||
:female,
|
||||
"week",
|
||||
Decimal.new("0"),
|
||||
Decimal.new("1"),
|
||||
Decimal.new("49.1477"),
|
||||
Decimal.new("0.0379"),
|
||||
Decimal.new("43.6"),
|
||||
Decimal.new("45.4"),
|
||||
Decimal.new("47.3"),
|
||||
Decimal.new("49.1"),
|
||||
Decimal.new("51"),
|
||||
Decimal.new("52.9"),
|
||||
Decimal.new("54.7")
|
||||
],
|
||||
[
|
||||
female_urls |> Map.get(:age_tables) |> List.last(),
|
||||
:age,
|
||||
:female,
|
||||
"month",
|
||||
Decimal.new("0"),
|
||||
Decimal.new("1"),
|
||||
Decimal.new("49.1477"),
|
||||
Decimal.new("0.0379"),
|
||||
Decimal.new("43.6"),
|
||||
Decimal.new("45.4"),
|
||||
Decimal.new("47.3"),
|
||||
Decimal.new("49.1"),
|
||||
Decimal.new("51"),
|
||||
Decimal.new("52.9"),
|
||||
Decimal.new("54.7")
|
||||
],
|
||||
[
|
||||
male_urls |> Map.get(:age_tables) |> List.first(),
|
||||
:age,
|
||||
:male,
|
||||
"week",
|
||||
Decimal.new("0"),
|
||||
Decimal.new("1"),
|
||||
Decimal.new("49.8842"),
|
||||
Decimal.new("0.03795"),
|
||||
Decimal.new("44.2"),
|
||||
Decimal.new("46.1"),
|
||||
Decimal.new("48"),
|
||||
Decimal.new("49.9"),
|
||||
Decimal.new("51.8"),
|
||||
Decimal.new("53.7"),
|
||||
Decimal.new("55.6")
|
||||
],
|
||||
[
|
||||
male_urls |> Map.get(:age_tables) |> List.last(),
|
||||
:age,
|
||||
:male,
|
||||
"month",
|
||||
Decimal.new("0"),
|
||||
Decimal.new("1"),
|
||||
Decimal.new("49.8842"),
|
||||
Decimal.new("0.03795"),
|
||||
Decimal.new("44.2"),
|
||||
Decimal.new("46.1"),
|
||||
Decimal.new("48"),
|
||||
Decimal.new("49.9"),
|
||||
Decimal.new("51.8"),
|
||||
Decimal.new("53.7"),
|
||||
Decimal.new("55.6")
|
||||
],
|
||||
[
|
||||
female_urls |> Map.get(:expanded_tables) |> List.first(),
|
||||
:expanded,
|
||||
:female,
|
||||
"day",
|
||||
Decimal.new("0"),
|
||||
Decimal.new("1"),
|
||||
Decimal.new("49.1477"),
|
||||
Decimal.new("0.0379"),
|
||||
Decimal.new("43.56"),
|
||||
Decimal.new("45.422"),
|
||||
Decimal.new("47.285"),
|
||||
Decimal.new("49.148"),
|
||||
Decimal.new("51.01"),
|
||||
Decimal.new("52.873"),
|
||||
Decimal.new("54.736")
|
||||
],
|
||||
[
|
||||
male_urls |> Map.get(:expanded_tables) |> List.first(),
|
||||
:expanded,
|
||||
:male,
|
||||
"day",
|
||||
Decimal.new("0"),
|
||||
Decimal.new("1"),
|
||||
Decimal.new("49.8842"),
|
||||
Decimal.new("0.03795"),
|
||||
Decimal.new("44.205"),
|
||||
Decimal.new("46.098"),
|
||||
Decimal.new("47.991"),
|
||||
Decimal.new("49.884"),
|
||||
Decimal.new("51.777"),
|
||||
Decimal.new("53.67"),
|
||||
Decimal.new("55.564")
|
||||
]
|
||||
]
|
||||
|
||||
assert expected_content == content
|
||||
end
|
||||
end
|
||||
|
||||
defp mock_who_request do
|
||||
Req.Test.stub(Growth.Indicators.Download.WHO, fn %Plug.Conn{path_info: path_info} = conn ->
|
||||
filename = List.last(path_info)
|
||||
|
||||
rows =
|
||||
case filename do
|
||||
"lhfa_girls_0-to-13-weeks_zscores.xlsx" ->
|
||||
[
|
||||
~w(Week L M S SD SD3neg SD2neg SD1neg SD0 SD1 SD2 SD3),
|
||||
~w(0 1 49.1477 0.0379 1.8627 43.6 45.4 47.3 49.1 51 52.9 54.7)
|
||||
]
|
||||
|
||||
"lhfa_girls_0-to-2-years_zscores.xlsx" ->
|
||||
[
|
||||
~w(Month L M S SD SD3neg SD2neg SD1neg SD0 SD1 SD2 SD3),
|
||||
~w(0 1 49.1477 0.0379 1.8627 43.6 45.4 47.3 49.1 51 52.9 54.7)
|
||||
]
|
||||
|
||||
"lhfa-girls-zscore-expanded-tables.xlsx" ->
|
||||
[
|
||||
~w(Day L M S SD4neg SD3neg SD2neg SD1neg SD0 SD1 SD2 SD3 SD4),
|
||||
~w(0 1 49.1477 0.0379 41.697 43.56 45.422 47.285 49.148 51.01 52.873 54.736 56.598)
|
||||
]
|
||||
|
||||
"lhfa_boys_0-to-13-weeks_zscores.xlsx" ->
|
||||
[
|
||||
~w(Week L M S SD SD3neg SD2neg SD1neg SD0 SD1 SD2 SD3),
|
||||
~w(0 1 49.8842 0.03795 1.8931 44.2 46.1 48 49.9 51.8 53.7 55.6)
|
||||
]
|
||||
|
||||
"lhfa_boys_0-to-2-years_zscores.xlsx" ->
|
||||
[
|
||||
~w(Month L M S SD SD3neg SD2neg SD1neg SD0 SD1 SD2 SD3),
|
||||
~w(0 1 49.8842 0.03795 1.8931 44.2 46.1 48 49.9 51.8 53.7 55.6)
|
||||
]
|
||||
|
||||
"lhfa-boys-zscore-expanded-tables.xlsx" ->
|
||||
[
|
||||
~w(Day L M S SD4neg SD3neg SD2neg SD1neg SD0 SD1 SD2 SD3 SD4),
|
||||
~w(0 1 49.8842 0.03795 42.312 44.205 46.098 47.991 49.884 51.777 53.67 55.564 57.457)
|
||||
]
|
||||
end
|
||||
|
||||
{:ok, {_charlist_content, content}} =
|
||||
%Sheet{name: "zscore", rows: rows}
|
||||
|> then(&%Workbook{sheets: [&1]})
|
||||
|> Elixlsx.write_to_memory(filename)
|
||||
|
||||
conn
|
||||
|> Plug.Conn.put_resp_content_type(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
)
|
||||
|> Plug.Conn.send_resp(200, content)
|
||||
end)
|
||||
end
|
||||
end
|
@@ -1,142 +0,0 @@
|
||||
# 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
|
@@ -1,123 +0,0 @@
|
||||
defmodule Growth.Data do
|
||||
@moduledoc """
|
||||
Sample data for growth tests based on WHO csv files containing:
|
||||
|
||||
* source measure
|
||||
* gender
|
||||
* age_unit
|
||||
* box-cox fitted params:
|
||||
* l
|
||||
* m
|
||||
* s
|
||||
* expeceted measure values at given z-scores:
|
||||
* -3
|
||||
* -2
|
||||
* -1
|
||||
* 0
|
||||
* 1
|
||||
* 2
|
||||
* 3
|
||||
"""
|
||||
|
||||
NimbleCSV.define(Growth.DataCase, separator: ",", escape: "\"")
|
||||
|
||||
# NOTE: (jpd) this combined table will be used on zscore and centile test to cross-validate the results using
|
||||
# different genders, ages units, and measures.
|
||||
@sample_combined_csv """
|
||||
source,gender,age_unit,age,l,m,s,sd3neg,sd2neg,sd1neg,sd0,sd1,sd2,sd3
|
||||
arm-circumference-for-age,female,month,3,-0.17330000000000001,13.0284,0.082629999999999995,10.199999999999999,11.1,12,13,14.2,15.4,16.8
|
||||
arm-circumference-for-age,female,day,91,-0.17330000000000001,13.0245,0.082619999999999999,10.218,11.066000000000001,11.999000000000001,13.023999999999999,14.154999999999999,15.401999999999999,16.78
|
||||
arm-circumference-for-age,male,month,3,0.39279999999999998,13.4817,0.074749999999999997,10.7,11.6,12.5,13.5,14.5,15.6,16.7
|
||||
arm-circumference-for-age,male,day,91,0.39329999999999998,13.4779,0.074740000000000001,10.657999999999999,11.554,12.493,13.478,14.507999999999999,15.585000000000001,16.709
|
||||
body-mass-index-for-age,female,day,28,0.36370000000000002,14.4208,0.095769999999999994,10.646000000000001,11.824,13.081,14.420999999999999,15.843999999999999,17.353999999999999,18.952999999999999
|
||||
body-mass-index-for-age,female,week,4,0.36370000000000002,14.4208,0.095769999999999994,10.6,11.8,13.1,14.4,15.8,17.399999999999999,19
|
||||
body-mass-index-for-age,female,month,1,0.3448,14.5679,0.095560000000000006,10.8,12,13.2,14.6,16,17.5,19.100000000000001
|
||||
body-mass-index-for-age,male,day,28,0.28810000000000002,14.7714,0.090719999999999995,11.125999999999999,12.26,13.474,14.771000000000001,16.155000000000001,17.629000000000001,19.196000000000002
|
||||
body-mass-index-for-age,male,week,4,0.28810000000000002,14.7714,0.090719999999999995,11.1,12.3,13.5,14.8,16.2,17.600000000000001,19.2
|
||||
body-mass-index-for-age,male,month,1,0.27079999999999999,14.944100000000001,0.090270000000000003,11.3,12.4,13.6,14.9,16.3,17.8,19.399999999999999
|
||||
head-circumference-for-age,female,day,28,1,36.376100000000001,0.032149999999999998,32.868000000000002,34.036999999999999,35.207000000000001,36.375999999999998,37.545999999999999,38.715000000000003,39.884999999999998
|
||||
head-circumference-for-age,female,week,4,1,36.376100000000001,0.032149999999999998,32.9,34,35.200000000000003,36.4,37.5,38.700000000000003,39.9
|
||||
head-circumference-for-age,female,month,1,1,36.546300000000002,0.032099999999999997,33,34.200000000000003,35.4,36.5,37.700000000000003,38.9,40.1
|
||||
head-circumference-for-age,male,day,28,1,37.092599999999997,0.031480000000000001,33.590000000000003,34.756999999999998,35.924999999999997,37.093000000000004,38.26,39.427999999999997,40.595999999999997
|
||||
head-circumference-for-age,male,week,4,1,37.092599999999997,0.031480000000000001,33.6,34.799999999999997,35.9,37.1,38.299999999999997,39.4,40.6
|
||||
head-circumference-for-age,male,month,1,1,37.2759,0.031329999999999997,33.799999999999997,34.9,36.1,37.299999999999997,38.4,39.6,40.799999999999997
|
||||
length-height-for-age,female,day,28,1,53.380899999999997,0.036470000000000002,47.54,49.487000000000002,51.433999999999997,53.381,55.328000000000003,57.274999999999999,59.220999999999997
|
||||
length-height-for-age,female,week,4,1,53.380899999999997,0.036470000000000002,47.5,49.5,51.4,53.4,55.3,57.3,59.2
|
||||
length-height-for-age,female,month,1,1,53.687199999999997,0.036400000000000002,47.8,49.8,51.7,53.7,55.6,57.6,59.5
|
||||
length-height-for-age,male,day,28,1,54.388100000000001,0.035700000000000003,48.563000000000002,50.505000000000003,52.445999999999998,54.387999999999998,56.33,58.271000000000001,60.213000000000001
|
||||
length-height-for-age,male,week,4,1,54.388100000000001,0.035700000000000003,48.6,50.5,52.4,54.4,56.3,58.3,60.2
|
||||
length-height-for-age,male,month,1,1,54.724400000000003,0.035569999999999997,48.9,50.8,52.8,54.7,56.7,58.6,60.6
|
||||
subscapular-skinfold-for-age,female,day,91,-0.2019,7.7873999999999999,0.18428,4.6109999999999998,5.4580000000000002,6.4989999999999997,7.7869999999999999,9.3960000000000008,11.422000000000001,13.994999999999999
|
||||
subscapular-skinfold-for-age,female,month,3,-0.2026,7.7846000000000002,0.18428,4.5999999999999996,5.5,6.5,7.8,9.4,11.4,14
|
||||
subscapular-skinfold-for-age,male,day,91,-0.30299999999999999,7.6920000000000002,0.17019000000000001,4.7850000000000001,5.5640000000000001,6.516,7.6920000000000002,9.1609999999999996,11.016999999999999,13.395
|
||||
subscapular-skinfold-for-age,male,month,3,-0.30330000000000001,7.6898999999999997,0.17019999999999999,4.8,5.6,6.5,7.7,9.1999999999999993,11,13.4
|
||||
triceps-skinfold-for-age,female,day,91,0.18820000000000001,9.7532999999999994,0.17524999999999999,5.6070000000000002,6.7869999999999999,8.1609999999999996,9.7530000000000001,11.589,13.695,16.102
|
||||
triceps-skinfold-for-age,female,month,3,0.1875,9.7515999999999998,0.17535000000000001,5.6,6.8,8.1999999999999993,9.8000000000000007,11.6,13.7,16.100000000000001
|
||||
triceps-skinfold-for-age,male,day,91,0.0030000000000000001,9.7658000000000005,0.16611000000000001,5.931,7.0039999999999996,8.2710000000000008,9.766,11.53,13.612,16.068000000000001
|
||||
triceps-skinfold-for-age,male,month,3,0.0027000000000000001,9.7638999999999996,0.16617999999999999,5.9,7,8.3000000000000007,9.8000000000000007,11.5,13.6,16.100000000000001
|
||||
weight-for-age,female,day,28,0.1789,4.0987,0.13805000000000001,2.665,3.0880000000000001,3.5640000000000001,4.0990000000000002,4.6980000000000004,5.3659999999999997,6.1120000000000001
|
||||
weight-for-age,female,week,4,0.1789,4.0987,0.13805000000000001,2.7,3.1,3.6,4.0999999999999996,4.7,5.4,6.1
|
||||
weight-for-age,female,month,1,0.1714,4.1872999999999996,0.13724,2.7,3.2,3.6,4.2,4.8,5.5,6.2
|
||||
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 """
|
||||
Sample data to validate calculation made to convert measure into z-score and vice-versa.
|
||||
|
||||
This data is based on values extracted from WHO csv files.
|
||||
"""
|
||||
@spec sample :: [
|
||||
%{
|
||||
key: String.t(),
|
||||
zscore: number(),
|
||||
measure: number(),
|
||||
l: number(),
|
||||
m: number(),
|
||||
s: number()
|
||||
}
|
||||
]
|
||||
def sample do
|
||||
@sample_combined_csv
|
||||
|> Growth.DataCase.parse_string()
|
||||
|> Enum.flat_map(fn [
|
||||
source,
|
||||
gender,
|
||||
age_unit,
|
||||
age,
|
||||
l,
|
||||
m,
|
||||
s,
|
||||
sd3n,
|
||||
sd2n,
|
||||
sd1n,
|
||||
sd0,
|
||||
sd1,
|
||||
sd2,
|
||||
sd3
|
||||
] ->
|
||||
key = "#{source}:#{gender}:#{age_unit}:#{age}"
|
||||
|
||||
[l, m, s, sd3n, sd2n, sd1n, sd0, sd1, sd2, sd3] =
|
||||
Enum.map(
|
||||
[l, m, s, sd3n, sd2n, sd1n, sd0, sd1, sd2, sd3],
|
||||
&(&1 |> Float.parse() |> elem(0))
|
||||
)
|
||||
|
||||
[-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]
|
||||
|> Enum.zip([sd3n, sd2n, sd1n, sd0, sd1, sd2, sd3])
|
||||
|> Enum.map(fn {zscore, measure} ->
|
||||
%{key: "#{key}:#{zscore}", zscore: zscore, measure: measure, l: l, m: m, s: s}
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user