157 Commits

Author SHA1 Message Date
a90cdefa1e merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
Conflicts:
    mix.lock
2025-06-20 18:53:18 -03:00
10d7f15598 chore(deps): update dependency ecto_sql to v3.13.1 (#160)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ecto_sql](https://hex.pm/packages/ecto_sql) ([source](https://github.com/elixir-ecto/ecto_sql)) | prod | patch | `3.13.0` -> `3.13.1` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #160
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-06-20 21:31:27 +00:00
b00c96fa5c merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
Conflicts:
    mix.exs
    mix.lock
2025-06-20 11:59:45 -03:00
77c51b176d chore(deps): update dependency ecto_sql to ~> 3.13.0 (#159)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ecto_sql](https://hex.pm/packages/ecto_sql) ([source](https://github.com/elixir-ecto/ecto_sql)) | prod | minor | `~> 3.12.0` -> `~> 3.13.0` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #159
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-06-20 14:54:09 +00:00
5941f7fe6c chore(deps): update dependency plug_cowboy to v2.7.4 (#158)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [plug_cowboy](https://hex.pm/packages/plug_cowboy) ([source](https://github.com/elixir-plug/plug_cowboy)) | prod | patch | `2.7.3` -> `2.7.4` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #158
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-06-19 00:19:41 +00:00
5074a28507 feat: upgrade dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
* `ex_doc`: from 0.38.1 to 0.38.2
2025-06-16 20:37:38 -03:00
f23bbcb557 merge: updates from upstream
Conflicts:
    mix.lock
2025-06-16 20:36:16 -03:00
d687c8f467 feat: upgrade runtimes and dependencies (#157)
All checks were successful
continuous-integration/drone/push Build is passing
Upgrade runtimes:

* `lefthook`: from 1.11.13 to 1.11.14

Upgrade transitive dependencies:

* `ecto`: from 3.12.5 to 3.12.6
* `phoenix_live_view`: from 1.0.12 to 1.0.17
* `plug`: from 1.17.0 to 1.18.0

Reviewed-on: #157
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2025-06-16 23:33:38 +00:00
9294c274dc merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
Conflicts:
    mix.lock
2025-06-16 20:21:46 -03:00
f111cc1d2d chore(deps): update dependency absinthe to v1.7.10 (#156)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [absinthe](https://hex.pm/packages/absinthe) ([source](https://github.com/absinthe-graphql/absinthe)) | prod | patch | `1.7.9` -> `1.7.10` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #156
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-06-16 23:05:43 +00:00
05a1336477 chore(deps): update dependency mix_audit to v2.1.5 (#155)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [mix_audit](https://hex.pm/packages/mix_audit) ([source](https://github.com/mirego/mix_audit)) | dev | patch | `2.1.4` -> `2.1.5` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #155
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-06-16 16:48:13 +00:00
d7b3a3b87e feat(deps): upgrade ex_doc from 0.37.3 to 0.38.1
All checks were successful
continuous-integration/drone/pr Build is passing
2025-05-22 19:12:35 +00:00
01ff79373d merge: updates from upstream
Conflicts:
    mix.lock
2025-05-22 19:05:12 +00:00
3797c0592f chore(deps): update absinthe_plug digest to 24ec7aa (#153)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| absinthe_plug | prod | digest | `916d818` -> `24ec7aa` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #153
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-05-22 18:50:05 +00:00
541d29ae77 feat: upgrade runtimes (#154)
All checks were successful
continuous-integration/drone/push Build is passing
The following changes were made:

* Upgrade `lefthook` from 1.11.2 to 1.11.3
* Sync `elixir` version in `Dockerfile` and `drone.yml` (from 1.18.3 to 1.18.4)
* Upgrade `debian` version in `Dockerfile` and `drone.yml` (from 20250428 to 20250520)
* Upgrade transitive dependencies:
   * `castore` from 1.0.12 to 1.0.14
   * `mime` from 2.0.6 to 2.0.7
   * `phoenix_live_view` from 1.0.11 to 1.0.12
* Add `Dockerfile` to the CI cache key.
  This is needed because the `.tool-versions` runtime versions aren't in sync with the `Dockerfile` and `drone.yml` versions.

Reviewed-on: #154
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2025-05-22 17:23:37 +00:00
0ee1a5b76c chore(deps): update dependency elixir to v1.18.4 (#152)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [elixir](https://elixir-lang.org/) ([source](https://github.com/elixir-lang/elixir)) | patch | `1.18.3` -> `1.18.4` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #152
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-05-22 13:32:02 +00:00
99685d5a83 merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
Conflicts:
    mix.exs
    mix.lock
2025-05-13 18:21:48 +00:00
68d8b986ac feat(ci/docker): upgrade runtimes (#150)
All checks were successful
continuous-integration/drone/push Build is passing
* `erlang` from `27.3.3` to `27.3.4`
* `postgres` from `17.4` to `17.5`

Reviewed-on: #150
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2025-05-13 17:51:45 +00:00
0385353b54 chore(deps): update dependency erlang to v27.3.4 (#149)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | patch | `27.3.3` -> `27.3.4` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #149
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-05-13 17:33:24 +00:00
f978b5bdfb chore(deps): update postgres docker tag to v17.5 (#147)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| postgres | minor | `17.4-alpine` -> `17.5-alpine` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #147
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-05-13 14:55:04 +00:00
0e9ca8330c chore(deps): update dependency sobelow to ~> 0.14 (#148)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [sobelow](https://hex.pm/packages/sobelow) ([source](https://github.com/sobelow/sobelow)) | dev | minor | `~> 0.13` -> `~> 0.14` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #148
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-05-13 14:54:33 +00:00
b340092dc1 merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2025-05-06 22:59:23 +00:00
3cb77cdf46 feat: upgrade runtimes and dependencies (#145)
All checks were successful
continuous-integration/drone/push Build is passing
* Upgrade `lefthook` from 1.11.8 to 1.11.12
* Upgrade `debian bookworm` from 20250407 to 20250428
* Upgrade transient dependencies:
  * `phoenix_live_view` from 1.0.10 to 1.0.11

Reviewed-on: #145
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2025-05-06 22:58:31 +00:00
37e05d2c0a chore(deps): update dependency phoenix_ecto to v4.6.4 (#144)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [phoenix_ecto](https://hex.pm/packages/phoenix_ecto) ([source](https://github.com/phoenixframework/phoenix_ecto)) | prod | patch | `4.6.3` -> `4.6.4` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #144
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-05-06 18:09:17 +00:00
64143b3537 chore(deps): update absinthe_plug digest to 916d818 (#143)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| absinthe_plug | prod | digest | `5de21da` -> `916d818` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #143
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-05-06 17:41:28 +00:00
380a1dc974 chore(hex): update dependency lock
All checks were successful
continuous-integration/drone/pr Build is passing
2025-05-02 20:23:46 +00:00
c481c42e5b feat(growth): add weight-for-height calculation
All checks were successful
continuous-integration/drone/pr Build is passing
2025-04-30 22:48:29 +00:00
49e990935c merge: updates from upstream 2025-04-30 17:20:07 +00:00
3a0bc3621f chore: improve compose for git worktree (#142)
All checks were successful
continuous-integration/drone/push Build is passing
* Avoid multiple project names in `docker compose` by setting a common name.
* Simplify `develop` watch rules:
  * When `mix.lock` is changed, rebuild the image.
  * When any other file is changed, sync and restart the service.
    * Except for directories that are related to deps/build/test/lsp.
* Remove `file` argument from `docker compose` in `Makefile`

Reviewed-on: #142
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2025-04-30 17:19:18 +00:00
d4299d4d7a chore(deps): update dependency phoenix_live_dashboard to v0.8.7 (#141)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [phoenix_live_dashboard](https://hex.pm/packages/phoenix_live_dashboard) ([source](https://github.com/phoenixframework/phoenix_live_dashboard)) | prod | patch | `0.8.6` -> `0.8.7` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #141
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-04-30 12:57:37 +00:00
f54755df10 merge: updates from upstream 2025-04-29 14:39:05 +00:00
b9bb80fac7 chore(deps): update dependency credo to v1.7.12 (#139)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [credo](https://hex.pm/packages/credo) ([source](https://github.com/rrrene/credo)) | dev | patch | `1.7.11` -> `1.7.12` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #139
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-04-23 17:59:31 +00:00
613c028a67 chore(docker): upgrade erlang to 27.3.3 (#140)
All checks were successful
continuous-integration/drone/push Build is passing
Upgrade docker image for [`elixir:1.18.3-erlang-27.3.3-debian-bookworm-20250407-slim`][0].

Reviewed-on: #140

[0]: https://hub.docker.com/layers/hexpm/elixir/1.18.3-erlang-27.3.3-debian-bookworm-20250407-slim/images/sha256-88627ea2786f2e9ad7520d0f12bc7a9266ea0d6e7f89f2e0e278404263d470a4
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2025-04-23 13:48:55 +00:00
4c0aeaed3a chore(deps): update dependency erlang to v27.3.3 (#138)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | patch | `27.3.2` -> `27.3.3` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #138
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-04-23 13:21:18 +00:00
1873dcbd2e merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2025-04-08 23:46:46 +00:00
70914b9c2f merge: updates from upstream
Conflicts:
    mix.exs
2025-04-08 23:42:25 +00:00
068bccf7d9 feat(deps): upgrade runtimes and transient dependencies (#137)
All checks were successful
continuous-integration/drone/push Build is passing
1. Upgrade runtimes for `docker` and `ci`:
    1. `erlang` from 27.3 to 27.3.2
    2. `debian` from 20250224 to 20250407
2. Upgrade `lefthook` from 1.11.3 to 1.11.8
3. Upgrade transient dependencies:
    1. `hpax`: from 1.0.2 to 1.0.3
    2. `phoenix_live_view`: from 1.0.5 to 1.0.9
    3. `plug_crypto`: from 2.1.0 to 2.1.1

Reviewed-on: #137
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2025-04-08 21:56:06 +00:00
801624232a chore(deps): update dependency erlang to v27.3.2 (#136)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | patch | `27.3.1` -> `27.3.2` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #136
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-04-08 13:27:25 +00:00
eba513c929 chore(deps): update dependency telemetry_poller to ~> 1.2.0 (#135)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [telemetry_poller](https://hex.pm/packages/telemetry_poller) ([source](https://github.com/beam-telemetry/telemetry_poller)) | prod | minor | `~> 1.1.0` -> `~> 1.2.0` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #135
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-04-02 11:27:43 +00:00
f0c78f6dff chore(deps): update dependency erlang to v27.3.1 (#134)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | patch | `27.3` -> `27.3.1` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #134
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-04-01 00:59:28 +00:00
a196b77864 chore(deps): update dependency phoenix to v1.7.21 (#133)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [phoenix](https://hex.pm/packages/phoenix) ([source](https://github.com/phoenixframework/phoenix)) | prod | patch | `1.7.20` -> `1.7.21` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #133
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-04-01 00:22:48 +00:00
33a65e77ac merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2025-03-19 15:40:57 +00:00
1ed2d89cd4 chore(deps): update dependency absinthe to v1.7.9 (#132)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [absinthe](https://hex.pm/packages/absinthe) ([source](https://github.com/absinthe-graphql/absinthe)) | prod | patch | `1.7.8` -> `1.7.9` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #132
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-03-19 15:40:09 +00:00
0bf11bb809 feat(deps): upgrade transitive dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
* `earmark_parser` from 1.4.43 to 1.4.44
* `makeup` from 1.1.2 to 1.2.1
* `makeup_elixir` from 0.16.2 to 1.0.1
* `makeup_erlang` from 1.0.1 to 1.0.2
* `plug` from 1.16.1 to 1.17.0
* `saxy` from 1.5.0 to 1.6.0
2025-03-14 17:47:57 +00:00
706f065dc8 feat(deps): upgrade dependencies
* `ex_doc` from 0.36.1 to 0.37.3
* `xlsx_reader` from 0.8.7 to 0.8.8
2025-03-14 17:45:52 +00:00
8d07a04b95 merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
Conflicts:
    mix.lock
2025-03-14 17:38:28 +00:00
acf2143177 feat(deps): upgrade runtime and dependencies (#131)
All checks were successful
continuous-integration/drone/push Build is passing
Ensure that the `docker` and `ci` environments use the same runtime versions from `mise`.

Also, update transitive dependencies:

* `mint`: from 1.6.2 to 1.7.1
* `peep`: from 3.4.1 to 3.5.0
* `phoenix_live_view`: from 1.0.2 to 1.0.5

Reviewed-on: #131
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2025-03-11 00:54:52 +00:00
1d9dd6b3ad chore(deps): update absinthe_plug digest to 5de21da (#125)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| absinthe_plug | prod | digest | `307c8bb` -> `5de21da` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzkuMTg4LjMiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: #125
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-03-11 00:28:23 +00:00
9e01c57dc2 chore(deps): update dependency lcov_ex to v0.3.4 (#128)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [lcov_ex](https://hex.pm/packages/lcov_ex) ([source](https://github.com/dariodf/lcov_ex)) | dev | patch | `0.3.3` -> `0.3.4` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #128
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-03-09 21:52:49 +00:00
50e51715dc chore(deps): update dependency phoenix to v1.7.20 (#129)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [phoenix](https://hex.pm/packages/phoenix) ([source](https://github.com/phoenixframework/phoenix)) | prod | patch | `1.7.18` -> `1.7.20` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODguMyIsInVwZGF0ZWRJblZlciI6IjM5LjE4OC4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #129
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-03-09 18:42:47 +00:00
aa94db5590 chore(deps): update dependency dns_cluster to ~> 0.2.0 (#126)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [dns_cluster](https://hex.pm/packages/dns_cluster) ([source](https://github.com/phoenixframework/dns_cluster)) | prod | minor | `~> 0.1.1` -> `~> 0.2.0` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzkuMTg4LjMiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: #126
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-03-07 18:33:32 +00:00
7478829cbd chore(deps): update dependency elixir to v1.18.3 (#127)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [elixir](https://elixir-lang.org/) ([source](https://github.com/elixir-lang/elixir)) | patch | `1.18.2` -> `1.18.3` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzkuMTg4LjMiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: #127
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-03-07 01:52:41 +00:00
04c80e314c chore(deps): update postgres docker tag to v17.4 (#124)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| postgres | minor | `17.3-alpine` -> `17.4-alpine` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzkuOTguMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #124
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-03-06 22:04:04 +00:00
ab2ade294e chore(deps): update dependency erlang to v27.3 (#123)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | minor | `27.2.2` -> `27.3` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzkuOTguMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #123
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-03-06 22:03:29 +00:00
81ea8bc06b merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2025-02-14 22:07:52 +00:00
8a6c0d73df chore(deps): update postgres docker tag to v17.3 (#122)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| postgres | minor | `17.2-alpine` -> `17.3-alpine` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzkuOTguMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Reviewed-on: #122
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-02-14 22:06:25 +00:00
30a913bca5 chore(deps): update dependency erlang to v27.2.2 (#121)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | patch | `27.2.1` -> `27.2.2` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzkuOTguMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Reviewed-on: #121
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-02-14 21:48:33 +00:00
d2b7ac73aa chore(deps): update dependency postgrex to ~> 0.20.0 (#119)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [postgrex](https://hex.pm/packages/postgrex) ([source](https://github.com/elixir-ecto/postgrex)) | prod | minor | `~> 0.19.0` -> `~> 0.20.0` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzkuOTguMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #119
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-02-06 17:56:19 +00:00
18759bf234 feat(deps): upgrade dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
* `decimal`: from 2.1.1 to 2.3.0
* `earmark_parser`: from 1.4.41 to 1.4.43
* `ex_doc`: from 0.34.2 to 0.36.1
* `req`: from 0.5.7 to 0.5.8

https://diff.hex.pm/diffs?diffs[]=decimal:2.1.1:2.3.0&diffs[]=earmark_parser:1.4.41:1.4.43&diffs[]=ex_doc:0.34.2:0.36.1&diffs[]=req:0.5.7:0.5.8&
2025-01-26 15:10:24 +00:00
d385451a5e merge: updates from upstream
Conflicts:
    mix.exs
    mix.lock
2025-01-26 15:07:39 +00:00
eadd2f5988 feat(deps): upgrade dependencies (#118)
All checks were successful
continuous-integration/drone/push Build is passing
[Upgrade dependencies][0]:

* `credo`: from 1.7.8 to 1.7.11
* `dialyxir`: from 1.4.4 to 1.4.5
* `gettext`: from 0.26.1 to 0.26.2
* `phoenix`: from 1.7.14 to 1.7.18
* `phoenix_live_dashboard`: from 0.8.4 to 0.8.6
* `postgrex`: from 0.19.2 to 0.19.3

[Upgrade transient dependencies][1]:

* `castore`: from 1.0.9 to 1.0.11
* `decimal`: from 2.1.1 to 2.3.0
* `ecto`: from 3.12.4 to 3.12.5
* `file_system`: from 1.0.1 to 1.1.0
* `hpax`: from 1.0.0 to 1.0.2
* `nimble_parsec`: from 1.4.0 to 1.4.2
* `peep`: from 3.3.0 to 3.4.1
* `phoenix_html`: from 4.1.1 to 4.2.0
* `phoenix_live_view`: from 0.20.17 to 1.0.2
* `websock_adapter`: from 0.5.7 to 0.5.8

Reviewed-on: #118

[0]: https://diff.hex.pm/diffs?diffs[]=credo:1.7.8:1.7.11&diffs[]=dialyxir:1.4.4:1.4.5&diffs[]=gettext:0.26.1:0.26.2&diffs[]=phoenix:1.7.14:1.7.18&diffs[]=phoenix_live_dashboard:0.8.4:0.8.6&diffs[]=postgrex:0.19.2:0.19.3
[1]: https://diff.hex.pm/diffs?diffs[]=castore:1.0.9:1.0.11&diffs[]=decimal:2.1.1:2.3.0&diffs[]=ecto:3.12.4:3.12.5&diffs[]=file_system:1.0.1:1.1.0&diffs[]=hpax:1.0.0:1.0.2&diffs[]=nimble_parsec:1.4.0:1.4.2&diffs[]=peep:3.3.0:3.4.1&diffs[]=phoenix_html:4.1.1:4.2.0&diffs[]=phoenix_live_view:0.20.17:1.0.2&diffs[]=websock_adapter:0.5.7:0.5.8
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2025-01-26 10:50:44 +00:00
395ee2eca7 chore(deps): update absinthe_plug digest to 307c8bb (#116)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| absinthe_plug | prod | digest | `3a984cc` -> `307c8bb` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzkuOTguMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #116
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-01-25 15:17:15 +00:00
27ff265d47 chore(deps): update dependency telemetry_metrics to ~> 1.1.0 (#115)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [telemetry_metrics](https://hex.pm/packages/telemetry_metrics) ([source](https://github.com/beam-telemetry/telemetry_metrics)) | prod | minor | `~> 1.0.0` -> `~> 1.1.0` |

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzkuOTguMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #115
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-01-25 14:38:43 +00:00
bd894e4aac chore(deps): upgrade lefthook from 1.8.2 to 1.10.10 (#117)
All checks were successful
continuous-integration/drone/push Build is passing
Upgrade [lefthook](https://lefthook.dev/) from [1.8.2 to 1.10.10](https://github.com/evilmartians/lefthook/compare/v1.8.2...v1.10.10).

Reviewed-on: #117
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2025-01-25 12:45:40 +00:00
dec73629e9 chore(deps): update dependency erlang to v27.2.1 (#114)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | patch | `27.2` -> `27.2.1` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzkuOTguMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Reviewed-on: #114
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-01-25 12:29:32 +00:00
1967275b7e chore(deps): update dependency elixir to v1.18.2 (#113)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [elixir](https://elixir-lang.org/) ([source](https://github.com/elixir-lang/elixir)) | patch | `1.18.0` -> `1.18.2` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS43NC4xIiwidXBkYXRlZEluVmVyIjoiMzkuOTguMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Reviewed-on: #113
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2025-01-25 12:14:28 +00:00
d916795d1d chore(deps): update dependency elixir to v1.18.0 (#112)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [elixir](https://elixir-lang.org/) ([source](https://github.com/elixir-lang/elixir)) | minor | `1.17.3` -> `1.18.0` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS43NC4xIiwidXBkYXRlZEluVmVyIjoiMzkuNzQuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #112
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-12-21 21:07:31 +00:00
2521072af1 chore(deps): update dependency erlang to v27.2 (#111)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | minor | `27.1.3` -> `27.2` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMzMuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEzMy4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Reviewed-on: #111
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-12-11 19:34:35 +00:00
97b1ef9a6b merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2024-12-07 15:50:18 +00:00
c43d1774ec chore(deps): update dependency erlang to v27.1.3 (#110)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | patch | `27.1.2` -> `27.1.3` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMzMuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEzMy4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: Joao P Dubas <joao.dubas@gmail.com>
Reviewed-on: #110
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-12-07 10:31:59 +00:00
42a5712d38 chore(deps): update postgres docker tag to v17.2 (#109)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| postgres | minor | `17.1-alpine` -> `17.2-alpine` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMzMuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEzMy4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #109
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-11-23 17:01:40 +00:00
0ca674f76c chore(deps): update postgres docker tag to v17.1 (#108)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| postgres | minor | `17.0-alpine` -> `17.1-alpine` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMzMuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEzMy4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #108
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-11-22 18:51:58 +00:00
4b643202c3 feat: update deps
All checks were successful
continuous-integration/drone/pr Build is passing
* `req` from 0.5.6 to 0.5.7
2024-11-02 14:24:09 +00:00
adfb85533b merge: updates from upstream
Conflicts:
    mix.lock
2024-11-02 14:22:34 +00:00
d0114d9c46 feat: always build image when running tests (#107)
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #107
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2024-11-02 14:19:04 +00:00
fb001fc8b5 chore: update deps (#106)
All checks were successful
continuous-integration/drone/push Build is passing
Update deps:

* `ecto_sql` from 3.12.0 to 3.12.1
* `phoenix_ecto` from 4.6.2 to 4.6.3
* `postgrex` from 0.19.1 to 0.19.2

Update deps of deps:

* `castore` from 1.0.9 to 1.0.9
* `ecto` from 3.12.4 to 3.12.4
* `file_system` from 1.0.0 to 1.0.1
* `peep` from 3.2.0 to 3.3.0

Reviewed-on: #106
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2024-11-02 14:10:33 +00:00
f5d001d419 merge: updates from upstream 2024-11-02 13:30:15 +00:00
70582003de feat: check code quality through git hooks (#105)
All checks were successful
continuous-integration/drone/push Build is passing
Add `git hooks` that execute common and repetitive tasks before committing and pushing to make developer life easier.

Reviewed-on: #105
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2024-11-02 13:29:32 +00:00
aef062dcf5 fix(growth): remove unused match
All checks were successful
continuous-integration/drone/pr Build is passing
2024-11-02 12:56:38 +00:00
02540e1046 merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
Conflicts:
  mix.exs
  mix.lock
2024-10-30 19:56:46 +00:00
ab97a6c14f chore(deps): update dependency prom_ex to ~> 1.11.0 (#104)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [prom_ex](https://hex.pm/packages/prom_ex) | minor | `~> 1.10.0` -> `~> 1.11.0` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4zOS42IiwidXBkYXRlZEluVmVyIjoiMzguMzkuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #104
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-10-30 19:25:50 +00:00
69cd36d7e1 merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2024-10-21 02:16:01 +00:00
f6cfff14e7 merge: update from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2024-10-21 02:14:48 +00:00
216015fb82 chore(docker/ci): bump erlang to 27.1.2 (#103)
All checks were successful
continuous-integration/drone/push Build is passing
Also, use debian bookworm 20241016.

Reviewed-on: #103
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2024-10-21 02:13:56 +00:00
ffa2a68b7a fix(growth): use Application.app_dir to write/read priv dir
All checks were successful
continuous-integration/drone/pr Build is passing
2024-10-21 01:53:14 +00:00
26b72afd5a chore(deps): update dependency erlang to v27.1.2 (#102)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | patch | `27.1.1` -> `27.1.2` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4zOS42IiwidXBkYXRlZEluVmVyIjoiMzguMzkuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #102
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-10-21 01:51:44 +00:00
fb38910137 test(growth): add test for happy download paths
All checks were successful
continuous-integration/drone/pr Build is passing
2024-10-07 19:27:27 +00:00
6ba171343d test(growth): add dep to write in-memory excel data
This will be used to mock data from WHO site.
2024-10-07 19:23:26 +00:00
0fc8c56c41 chore(growth): add separated method to merge genders data
This makes easier to test aggregation logic for multiple gender
configuration.
2024-10-07 19:22:14 +00:00
c77d4eb97d chore(growth): allow one to customize Req options
With this change it's possible to change the way the application make
requests to WHO website.
2024-10-07 19:21:27 +00:00
df16f2ce1c merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2024-10-07 12:17:42 +00:00
5e029670eb feat: execute test with docker compose (#101)
All checks were successful
continuous-integration/drone/push Build is passing
When we changed the application to be executed as distributed nodes (PR #96), performing tests with `docker-compose` became impossible.

To fix this situation in this PR, two new targets are added to `Makefile`:

* `compose_test`
* `compose_test_shell`

These new targets allow one to execute tests and enter the container shell responsible for running these tests.

Reviewed-on: #101
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2024-10-07 12:16:07 +00:00
25cb7508bb test(growth): add main module tests
All checks were successful
continuous-integration/drone/pr Build is passing
2024-10-05 15:11:33 +00:00
84cf6758bb fix(growth): results is now a keyword list 2024-10-05 15:10:40 +00:00
7e041ae6c9 chore(growth): adjust indicator lookup to use atoms for unit 2024-10-05 15:09:44 +00:00
82d7009b34 chore(growth): adjust key in ets tables to contain atoms
The `ets` tables containing growth indicator has a key in the form of
`{gender :: atom(), unit :: atom(), value :: number()}`.
2024-10-05 15:07:10 +00:00
fa65a36852 test(growth): fix doctest for centile and percentile
All checks were successful
continuous-integration/drone/pr Build is passing
2024-10-05 12:58:36 +00:00
1e36c29180 test(growth): add percentile test 2024-10-05 12:58:04 +00:00
8c89b55487 test(growth): add centile compute test 2024-10-05 12:57:20 +00:00
a427e11958 test(growth): add zscore compute test 2024-10-05 12:56:28 +00:00
c9e3bf68ae test(growth): add bmi test 2024-10-05 12:55:44 +00:00
7670dce630 test(growth): add sample data to be used in tests 2024-10-05 12:53:56 +00:00
f3846cad24 chore(growth): update doc to reflect percentile values
All checks were successful
continuous-integration/drone/pr Build is passing
This fixes an issue were the returned value represented the measurment
at a given z-score (the centile value).
2024-10-03 22:16:15 +00:00
27a7a05584 fix(growth): improve doc for centile calculation
This module converts a given z-score and Box-Cox values into the related
measurement.
2024-10-03 22:14:56 +00:00
fc0d9db7a5 merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
Conflicts:
  mix.exs
  mix.lock
2024-10-02 12:31:04 +00:00
0ae8bf53a2 chore(deps): update dependency gettext to ~> 0.26.0 (#94)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [gettext](https://hex.pm/packages/gettext) | minor | `~> 0.25.0` -> `~> 0.26.0` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4zOS42IiwidXBkYXRlZEluVmVyIjoiMzguMzkuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Reviewed-on: #94
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-10-02 11:51:23 +00:00
d9dcfa3846 merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2024-10-02 10:40:04 +00:00
235396f041 chore: upgrade deps and match ci services to the ones used locally (#100)
All checks were successful
continuous-integration/drone/push Build is passing
Upgrade deps:

* `credo` from 1.7.7 to 1.7.8
* `dialyxir` from 1.4.3 to 1.4.4
* `plug_cowboy` from 2.7.1 to 2.7.2
* `postgrex` from 0.19.0 to 0.19.1

In `CI` match image versions:

* `postgres` from 16.4 to 17.0
* `erlang` from 27.0.1 to 27.1.1
* `elixir` from 1.17.2 to 1.17.3

Reviewed-on: #100
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2024-10-02 10:39:16 +00:00
dbb9fed9f8 chore(deps): update dependency erlang to v27.1.1 (#99)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | patch | `27.1` -> `27.1.1` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4zOS42IiwidXBkYXRlZEluVmVyIjoiMzguMzkuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #99
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-10-01 20:38:54 +00:00
3a69b8d4ff merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2024-10-01 14:29:26 +00:00
bb149d1137 chore(deps): update postgres docker tag to v17 (#98)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| postgres | major | `16.4-alpine` -> `17.0-alpine` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4zOS42IiwidXBkYXRlZEluVmVyIjoiMzguMzkuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #98
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-10-01 14:28:44 +00:00
7c15e1fae6 chore: execute dialyzer with short format
All checks were successful
continuous-integration/drone/pr Build is passing
2024-09-26 00:32:44 +00:00
8328bbe9fb merge: updates from tracking
All checks were successful
continuous-integration/drone/pr Build is passing
2024-09-25 23:48:32 +00:00
90613635fb merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
Conflicts:
    .gitignore
    lib/wabanex/application.ex
    mix.lock
2024-09-22 19:06:02 +00:00
9be7566cbb feat: execute service as distributed erlang (#96)
All checks were successful
continuous-integration/drone/push Build is passing
To create a cluster with distributed nodes, the following changes were made:

* Use [`dns_cluster`][0] to execute distributed nodes
* Add _script_ to execute distributed nodes locally

Also, improve local execution by:

* Use [`mix release`][1] to make easier [deploy `phoenix`][2]
* Use [`compose watch`][3] to synchronize code from the host with the service

Some minor improvements:

* Ignore files and folders in git and docker
* Remove boilerplate comments

[0]: https://github.com/phoenixframework/dns_cluster
[1]: https://hexdocs.pm/mix/Mix.Tasks.Release.html
[2]: https://hexdocs.pm/phoenix/releases.html
[3]: https://docs.docker.com/compose/how-tos/file-watch/

Co-authored-by: Joao P Dubas <joao.dubas@gmail.com>
Reviewed-on: #96
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2024-09-22 18:56:27 +00:00
0a4e262b91 chore(deps): update dependency erlang to v27.1 (#95)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [erlang](https://github.com/erlang/otp) | minor | `27.0.1` -> `27.1` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4zOS42IiwidXBkYXRlZEluVmVyIjoiMzguMzkuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #95
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-09-20 13:07:08 +00:00
bcdabe41c3 chore(deps): update dependency elixir to v1.17.3 (#81)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [elixir](https://elixir-lang.org/) ([source](https://github.com/elixir-lang/elixir)) | patch | `1.17.2-otp-27` -> `1.17.3` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4xNjIuMSIsInVwZGF0ZWRJblZlciI6IjM4LjM5LjYiLCJ0YXJnZXRCcmFuY2giOiJtYWluIn0=-->

Reviewed-on: #81
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-09-19 18:07:20 +00:00
dc6e00d54b chore: ignore elixir tools folder
All checks were successful
continuous-integration/drone/pr Build is passing
2024-09-18 11:49:23 +00:00
5c044e69d9 fix(growth): use centile and percentile correctly
1. The calculation made in `Grownth.Calc.Centile.compute/4` converts a
   given z-score into the expected measurement value.
   This can be used to get specific values for key z-scores, such as,
   -3, -2, 2 and 3.
2. To calculate the percentile of a given z-score the system uses the
   cumulative distribution function.
2024-08-28 13:03:45 +00:00
824a70452f merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2024-08-13 14:17:32 +00:00
4d545074f5 chore(deps): update postgres docker tag to v16.4 (#90)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| postgres | minor | `16.3-alpine` -> `16.4-alpine` |

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40MjEuMiIsInVwZGF0ZWRJblZlciI6IjM3LjQyMS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Reviewed-on: #90
Co-authored-by: renovate-bot <renovate-bot@dubas.dev>
Co-committed-by: renovate-bot <renovate-bot@dubas.dev>
2024-08-13 14:09:16 +00:00
b696cc129d merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
Conflicts:
    mix.exs
    mix.lock
2024-08-12 20:19:36 +00:00
4195fc615f feat: upgrade application dependencies (#93)
All checks were successful
continuous-integration/drone/push Build is passing
[Upgrade deps][0]:

* `absinthe` from 1.7.7 to 1.7.8
* `ecto` from 3.11.2 to 3.12.0
* `ecto_sql` from 3.11.3 to 3.12.0
* `gettext` from 0.24.0 to 0.25.0
* `jason` from 1.4.3 to 1.4.4
* `mix_audit` from 2.1.3 to 2.1.4
* `postgrex` from 0.18.0 to 0.19.0
* `prom_ex` from 1.9.0 to 1.10.0
* `telemetry_metrics` from 0.6.2 to 1.0.0

Install transient deps:

* `peep` to 3.2.0

Reviewed-on: #93

[0]: https://diff.hex.pm/diffs?diffs[]=absinthe:1.7.7:1.7.8&diffs[]=ecto:3.11.2:3.12.0&diffs[]=ecto_sql:3.11.3:3.12.0&diffs[]=gettext:0.24.0:0.25.0&diffs[]=jason:1.4.3:1.4.4&diffs[]=mix_audit:2.1.3:2.1.4&diffs[]=postgrex:0.18.0:0.19.0&diffs[]=prom_ex:1.9.0:1.10.0&diffs[]=telemetry_metrics:0.6.2:1.0.0
Co-authored-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
Co-committed-by: Joao P Dubas <joao.dubas+gitea@gmail.com>
2024-08-12 20:08:18 +00:00
c923b2efb7 test(growth): work on age calculation
All checks were successful
continuous-integration/drone/pr Build is passing
2024-07-12 11:18:40 +00:00
8e9907a3e8 merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2024-07-12 11:15:30 +00:00
3914bd5240 merge: updates from upstream
All checks were successful
continuous-integration/drone/pr Build is passing
2024-07-08 21:55:02 +00:00
8dffb86d01 chore(deps): update deps of deps
All checks were successful
continuous-integration/drone/pr Build is passing
2024-07-08 21:36:37 +00:00
866730238e feat(deps): upgrade req from 0.5.0 to 0.5.2 2024-07-08 21:28:35 +00:00
eeb6964348 merge: updates from upstream
Conflicts:
    mix.lock
2024-07-08 21:25:21 +00:00
0d7a4cc9c3 chore(growth): move nil logic to scorer behaviour
All checks were successful
continuous-integration/drone/pr Build is passing
Handle `nil` measurement in `Growth.Score.Scorer`.
2024-06-11 14:53:19 +00:00
7fab746ea8 feat: add ex_doc to generate documentation
All checks were successful
continuous-integration/drone/pr Build is passing
2024-06-11 12:18:18 +00:00
c81e4fda67 chore(growth): add log in loader
All checks were successful
continuous-integration/drone/pr Build is passing
2024-06-11 12:02:14 +00:00
bc9c2124f1 chore(growth): add example for Growth.new/4 2024-06-11 12:01:50 +00:00
c14a26a184 fix(growth): measure for subscapular skinfold 2024-06-11 12:01:25 +00:00
79aec9f7e5 feat(growth): calculate z-score/centile for all indicators
All checks were successful
continuous-integration/drone/pr Build is passing
2024-06-11 11:42:57 +00:00
2545a796c3 chore(growth): return nil when measure isn't set 2024-06-11 11:42:17 +00:00
88b8e811a8 chore(growth): apply scorer behaviour in indicators 2024-06-11 11:41:31 +00:00
fa022b9592 feat(growth): calculate z-score/centile for all indicators
The `Growth.Score.Scorer` behaviour implements the methods needed to
calculate z-score and centile for any indicator.
2024-06-11 11:38:18 +00:00
0ac434c61d fix(growth): ignore enum result explicitly
All checks were successful
continuous-integration/drone/pr Build is passing
2024-06-10 19:24:41 +00:00
06b35cbceb wip(growth): implement results for bmi
Some checks failed
continuous-integration/drone/pr Build is failing
This implementation should be made generic for all other scores.
2024-06-10 19:09:35 +00:00
28f0546bcb chore(growth): add default value for results 2024-06-10 18:35:12 +00:00
c175c3edfd fix(growth): typespec and doc for centile calc 2024-06-10 18:34:24 +00:00
4234b2e917 chore(growth): load is independent from task
Some checks failed
continuous-integration/drone/pr Build is failing
Ensure that `Growth.Indicators.Load.all/0` can be run independently from
the task.

So, the `ets` table is create based on the filename processed.
2024-06-10 13:01:44 +00:00
04fff60541 wip(growth): add score module
Some checks failed
continuous-integration/drone/pr Build is failing
This module is responsible for calculate the z score/centile for each
measurement.
2024-06-10 12:15:16 +00:00
b415b38184 feat(growth): load data on application start
To make this possible, the following changes were made:

* Use `Task` to start the load process
* Create `ets` table before start the async task
  * This is needed to keep the tables alive after the task process exits
* Adjust the application to start the `Growth.Indicators.Load` task
2024-06-10 12:11:18 +00:00
412250d7ef feat(growth): add struct to represent measurement
All checks were successful
continuous-integration/drone/pr Build is passing
2024-06-09 20:00:49 +00:00
d3c1aa9b6b chore(growth): improve age documentation and typing 2024-06-09 19:59:33 +00:00
025ab1537d chore(growth): name ets tables with score modules 2024-06-09 19:58:34 +00:00
52c1d0c028 feat(growth): load csv indicators into ets tables
All checks were successful
continuous-integration/drone/pr Build is passing
2024-06-07 13:28:13 +00:00
902a22c947 chore(growth): fetch extended data for indicators
All checks were successful
continuous-integration/drone/pr Build is passing
Also, improve module doc.
2024-06-06 22:49:52 +00:00
a8466f0c1d chore(growth): add documentation and examples
All checks were successful
continuous-integration/drone/pr Build is passing
2024-06-06 20:39:10 +00:00
Joao P Dubas
bb79aa2f3a fix(growth): alias z-score and use if
Some checks failed
continuous-integration/drone/pr Build is failing
2024-06-06 11:34:02 +00:00
500d0e89bb wip(growth): calculate key metrics for growth
Some checks failed
continuous-integration/drone/pr Build is failing
1. calculate age in days, weeks, and months
2. calculate body mass index for metric and english unities
3. calculate z-score for a given measurement and box-cox parameters (l,
   m, s)
4. calculate centile score for a given measurement and box-cox
   parameters
2024-06-06 01:36:11 +00:00
8be9fa38bb wip(growth): add fix note on convert/3 method
All checks were successful
continuous-integration/drone/pr Build is passing
2024-06-04 23:56:27 +00:00
9c8e1d20ee wip(growth): improve data parsing
1. Fetch common data for all indicators
2. Remove headers during merge

Also, re-generate files.
2024-06-04 23:55:03 +00:00
2fedb4ecf0 wip(growth): change from with to case
All checks were successful
continuous-integration/drone/pr Build is passing
2024-06-04 14:36:20 +00:00
58f14c0346 wip(growth): fetch|extract|save indicators from WHO
Initial implementation to fetch growth indicators from WHO.

For now, the system merges the measurements for each gender and age
group in a single `csv` file.

The final solution should do this, saving a `dets` file.
2024-06-04 13:57:32 +00:00
60 changed files with 33460 additions and 230 deletions

33
.dockerignore Normal file
View File

@@ -0,0 +1,33 @@
.dockerignore
docker-compose.yml
docker-compose.*.yml
# Ignore git, but keep git HEAD and refs to access current commit hash if needed:
#
# $ cat .git/HEAD | awk '{print ".git/"$2}' | xargs cat
# d0b8727759e1e0e7aa3d41707d12376e373d5ecc
.git
!.git/HEAD
!.git/refs
# Common development/test artifacts
/cover/
/doc/
/test/
/tmp/
/.elixir_ls
/.elixir-tools/
# Mix artifacts
/_build/
/deps/
*.ez
# Generated on crash by the VM
erl_crash.dump
# Static artifacts - These should be fetched and built inside the Docker image
/assets/node_modules/
/priv/plts/
/priv/static/assets/
/priv/static/cache_manifest.json

View File

@@ -9,7 +9,7 @@ trigger:
steps:
- name: database healthcheck
image: &postgres 'postgres:16.3-alpine'
image: &postgres 'postgres:17.5-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" }}'
cache_key: '{{ .Repo.Name }}-{{ checksum ".tool-versions" }}-{{ checksum "mix.lock" }}-{{ checksum "Dockerfile" }}'
endpoint: minio:9000
mount:
- _build
@@ -44,7 +44,7 @@ steps:
path: /drone/src/deps
- name: dependencies and compile
image: &elixir 'hexpm/elixir:1.17.2-erlang-27.0.1-debian-bookworm-20240701-slim'
image: &elixir 'hexpm/elixir:1.18.4-erlang-27.3.4-debian-bookworm-20250520-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" }}'
cache_key: '{{ .Repo.Name }}-{{ checksum ".tool-versions" }}-{{ checksum "mix.lock" }}-{{ checksum "Dockerfile" }}'
endpoint: minio:9000
exit_code: true
mount:

6
.gitignore vendored
View File

@@ -1,4 +1,6 @@
# paths
/.elixir_ls/
/.elixir-tools/
/_build/
/cover/
/report/
@@ -13,5 +15,5 @@ erl_crash.dump
*.ez
wabanex-*.tar
docker-compose.*.yml
priv/docker/pgcli/history
priv/docker/pgcli/log
/priv/docker/pgcli/history
/priv/docker/pgcli/log

View File

@@ -1,2 +1,3 @@
erlang 27.0.1
elixir 1.17.2-otp-27
erlang 27.3.4
elixir 1.18.4
lefthook 1.11.14

View File

@@ -1,10 +1,70 @@
FROM hexpm/elixir:1.17.2-erlang-27.0.1-debian-bookworm-20240701-slim AS builder
ARG ELIXIR_VERSION=1.18.4
ARG OTP_VERSION=27.3.4
ARG DEBIAN_VERSION=bookworm-20250520-slim
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
FROM ${BUILDER_IMAGE} AS builder
RUN apt-get update \
&& apt-get -y install git make
&& apt-get -y install \
build-essential \
git \
make \
&& apt-get clean \
&& rm -rf /var/lib/ap/lists/*_*
WORKDIR /opt/app
# install hex + rebar
RUN mix do local.hex --force, local.rebar --force
# set build ENV
ARG BUILD_MIX_ENV=prod
ENV MIX_ENV=${BUILD_MIX_ENV}
# install mix dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get --only ${MIX_ENV}
RUN mkdir config
# copy compile-time config files before we compile dependencies
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${BUILD_MIX_ENV}.exs config/
RUN mix deps.compile
COPY priv priv
COPY lib lib
# Compile the release
RUN mix compile
# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/
COPY rel rel
RUN mix release
COPY ./mix.exs ./
COPY ./mix.lock ./
RUN mix do local.hex --force, local.rebar --force \
&& mix do deps.get, deps.compile
EXPOSE 4000
ENTRYPOINT ["./priv/docker/service/docker-entrypoint.sh"]
CMD ["sample-cookie"]
# start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE}
RUN apt-get update -y \
&& apt-get install -y \
ca-certificates \
libncurses5 \
libstdc++6 \
locales \
openssl \
tini \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
WORKDIR /opt/app
RUN chown nobody /opt/app
# set runner ENV
ARG BUILD_MIX_ENV=prod
ENV MIX_ENV=${BUILD_MIX_ENV}
# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/${BUILD_MIX_ENV}/rel/wabanex ./
USER nobody
EXPOSE 4000
ENTRYPOINT ["tini", "--"]
CMD ["/app/bin/server"]

View File

@@ -1,9 +1,13 @@
.DEFAULT_GOAL := help
COMPOSE = docker compose -f docker-compose.yml -f docker-compose.override.yml
COMPOSE = docker compose
.PHONY: system_setup
system_setup: ## setup system deps
@lefthook install
.PHONY: setup
setup: ## setup project
setup: system_setup ## setup project
@mkdir -p priv/plts
@mix do local.rebar --force, local.hex --force
@mix do deps.get, deps.compile
@@ -13,36 +17,36 @@ setup: ## setup project
check_format: ## run format checker
@mix format --check-formatted
.PHONY: check_compile
check_compile: ## run compile
@mix compile --warnings-as-errors --force
.PHONY: credo
credo: ## run credo
@mix credo suggest --strict --format=flycheck
.PHONY: dialyzer
dialyzer: ## run dialyzer
@mix dialyzer --no-check --quiet --ignore-exit-status --format short
@mix dialyzer --format short
.PHONY: static_code_analysis
static_code_analysis: check_format credo dialyzer ## run 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
.PHONY: compose_up
compose_up: ## start containers for this service
@$(COMPOSE) up -d
.PHONY: compose_test
compose_test: ## run tests in containers
@$(COMPOSE) run -e MIX_ENV=test --entrypoint make app test
.PHONY: compose_database_create
compose_database_create:
@$(COMPOSE) --profile setup run db_setup
@$(COMPOSE) run --rm --entrypoint mix app ecto.create
.PHONY: compose_database_migrate
compose_database_migrate: ## apply migrations to our database
@$(COMPOSE) --profile migrate run db_migrate
@$(COMPOSE) run --rm --entrypoint mix app ecto.migrate
.PHONY: compose_database_setup
compose_database_setup: compose_database_create compose_database_migrate ## create and apply migrations
@@ -51,6 +55,26 @@ compose_database_setup: compose_database_create compose_database_migrate ## cre
compose_ps: ## status of containers
@$(COMPOSE) ps
.PHONY: compose_remote
compose_remote: ## connect to remote node
@$(COMPOSE) exec app ./priv/docker/service/docker-remote.sh
.PHONY: compose_test
compose_test: ## execute test on docker environment
@$(COMPOSE) --profile test run --build --rm test
.PHONY: compose_test_shell
compose_test_shell: ## enter test environment shell
@$(COMPOSE) --profile test run --rm --entrypoint bash test -c bash
.PHONY: compose_up
compose_up: ## start containers for this service
@$(COMPOSE) up -d
.PHONY: compose_watch
compose_watch: ## start containers for this service watching for updates in filesystem
@$(COMPOSE) up -w
.PHONY: help
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

View File

@@ -1,10 +1,3 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
# General application configuration
import Config
config :wabanex,
@@ -21,7 +14,6 @@ config :wabanex, Wabanex.PromEx,
grafana: :disabled,
metrics_server: :disabled
# Configures the endpoint
config :wabanex, WabanexWeb.Endpoint,
url: [host: "localhost"],
secret_key_base: "wkyuhU+mCGwXUSBYVKZSRGoFmDYCbOFzdokbVmBBI9JgCWOqGPfuA/3JI5/b4Wdl",
@@ -29,14 +21,10 @@ config :wabanex, WabanexWeb.Endpoint,
pubsub_server: Wabanex.PubSub,
live_view: [signing_salt: "SXtw7DzV"]
# Configures Elixir's Logger
config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"

View File

@@ -1,17 +1,14 @@
import Config
# Configure your database
config :wabanex,
dns_cluster_query: :ignore,
dns_cluster_resolver: Wabanex.DevDNSClusterResolver
config :wabanex, Wabanex.Repo,
database: "wabanex_dev",
show_sensitive_data_on_connection_error: true,
pool_size: 10
# For development, we disable any cache and enable
# debugging and code reloading.
#
# The watchers configuration can be used to run external
# watchers to your application. For example, we use it
# with webpack to recompile .js and .css sources.
config :wabanex, WabanexWeb.Endpoint,
http: [port: 4000],
debug_errors: true,
@@ -19,36 +16,8 @@ config :wabanex, WabanexWeb.Endpoint,
check_origin: false,
watchers: []
# ## SSL Support
#
# In order to use HTTPS in development, a self-signed
# certificate can be generated by running the following
# Mix task:
#
# mix phx.gen.cert
#
# Note that this task requires Erlang/OTP 20 or later.
# Run `mix help phx.gen.cert` for more information.
#
# The `http:` config above can be replaced with:
#
# https: [
# port: 4001,
# cipher_suite: :strong,
# keyfile: "priv/cert/selfsigned_key.pem",
# certfile: "priv/cert/selfsigned.pem"
# ],
#
# If desired, both `http:` and `https:` keys can be
# configured to run both http and https servers on
# different ports.
# Do not include metadata nor timestamps in development logs
config :logger, :console, format: "[$level] $message\n"
# Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive.
config :phoenix, :stacktrace_depth, 20
# Initialize plugs at runtime for faster development compilation
config :phoenix, :plug_init_mode, :runtime

View File

@@ -1,55 +1,7 @@
import Config
# For production, don't forget to configure the url host
# to something meaningful, Phoenix uses this information
# when generating URLs.
#
# Note we also include the path to a cache manifest
# containing the digested version of static files. This
# manifest is generated by the `mix phx.digest` task,
# which you should run after static files are built and
# before starting your production server.
config :wabanex, WabanexWeb.Endpoint,
url: [host: "example.com", port: 80],
cache_static_manifest: "priv/static/cache_manifest.json"
# Do not print debug messages in production
config :logger, level: :info
# ## SSL Support
#
# To get SSL working, you will need to add the `https` key
# to the previous section and set your `:url` port to 443:
#
# config :wabanex, WabanexWeb.Endpoint,
# ...
# url: [host: "example.com", port: 443],
# https: [
# port: 443,
# cipher_suite: :strong,
# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
# certfile: System.get_env("SOME_APP_SSL_CERT_PATH"),
# transport_options: [socket_opts: [:inet6]]
# ]
#
# The `cipher_suite` is set to `:strong` to support only the
# latest and more secure SSL ciphers. This means old browsers
# and clients may not be supported. You can set it to
# `:compatible` for wider support.
#
# `:keyfile` and `:certfile` expect an absolute path to the key
# and cert in disk or a relative path inside priv, for example
# "priv/ssl/server.key". For all supported SSL configuration
# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
#
# We also recommend setting `force_ssl` in your endpoint, ensuring
# no data is ever sent via http, always redirecting to https:
#
# config :wabanex, WabanexWeb.Endpoint,
# force_ssl: [hsts: true]
#
# Check `Plug.SSL` for all available options in `force_ssl`.
# Finally import the config/prod.secret.exs which loads secrets
# and configuration from environment variables.
import_config "prod.secret.exs"

View File

@@ -1,5 +1,35 @@
import Config
# [warn] Conditional IPv6 support missing from runtime configuration.
#
# Add the following to your config/runtime.exs:
#
# maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
#
# config :wabanex, Wabanex.Repo,
# ...,
# socket_options: maybe_ipv6
#
# [warn] Conditional server startup is missing from runtime configuration.
#
# Add the following to the top of your config/runtime.exs:
#
# if System.get_env("PHX_SERVER") do
# config :wabanex, WabanexWeb.Endpoint, server: true
# end
#
# [warn] Environment based URL export is missing from runtime configuration.
#
# Add the following to your config/runtime.exs:
#
# host = System.get_env("PHX_HOST") || "example.com"
#
# config :wabanex, WabanexWeb.Endpoint,
# ...,
# url: [host: host, port: 443]
config :wabanex, dns_cluster_query: System.get_env("DNS_CLUSTER_QUERY") || :ignore
config :wabanex, Wabanex.Repo,
username: System.get_env("POSTGRES_USER") || "postgres",
password: System.get_env("POSTGRES_PASS") || "postgres",

View File

@@ -1,21 +1,16 @@
import Config
# Configure your database
#
# The MIX_TEST_PARTITION environment variable can be used
# to provide built-in test partitioning in CI environment.
# Run `mix help test` for more information.
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
# We don't run a server during test. If one is required,
# you can enable the server option below.
config :wabanex, WabanexWeb.Endpoint,
http: [port: 4002],
server: false
# Print only warnings and errors during test
config :logger, level: :warning
config :junit_formatter,

View File

@@ -1,65 +1,90 @@
---
name: ${EX_TRAINER_PROJECT_NAME:-ex_trainer}
services:
db:
image: 'postgres:16.4-alpine'
image: 'postgres:17.5-alpine'
hostname: &db_host db
init: true
environment:
POSTGRES_USER: &db_user postgres
POSTGRES_PASSWORD: &db_pass postgres
POSTGRES_DB: &db_name postgres
POSTGRES_DB: postgres
volumes:
- './priv/docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql'
- 'db_data:/var/lib/postgresql/data'
restart: unless-stopped
app:
image: &app_image 'joaodubas/ex_trainer:${EX_TRAINER_TAG:-dev}'
image: 'joaodubas/ex_trainer:${EX_TRAINER_TAG:-dev}'
build:
target: builder
context: .
hostname: app
args:
BUILD_MIX_ENV: dev
pull_policy: never
hostname: &app_host ex_trainer
depends_on:
- db
init: true
environment: &app_environment
develop:
watch:
- path: ./mix.lock
action: rebuild
- path: ./
ignore:
- ./build/
- ./deps/
- ./.elixir_ls/
- ./cover/
- ./report/
target: /opt/app
action: sync+restart
environment:
DNS_CLUSTER_QUERY: *app_host
POSTGRES_HOST: *db_host
POSTGRES_USER: *db_user
POSTGRES_PASS: *db_pass
POSTGRES_NAME: wabanex_dev
volumes: &app_volumes
- '.:/opt/app'
- 'app_build:/opt/app/_build'
- 'app_deps:/opt/app/deps'
working_dir: /opt/app
restart: unless-stopped
entrypoint: sleep
command: infinity
db_setup:
image: *app_image
scale: 3
entrypoint: ./priv/docker/service/docker-entrypoint.sh
command: local-cookie
test:
image: 'joaodubas/ex_trainer:test'
build:
target: builder
context: .
args:
BUILD_MIX_ENV: test
pull_policy: never
profiles:
- test
hostname: ex_trainer_test
depends_on:
- db
profiles:
- setup
environment: *app_environment
volumes: *app_volumes
restart: 'no'
command: |
mix ecto.setup \
&& MIX_ENV=test mix ecto.setup
db_migrate:
image: *app_image
depends_on:
- db
profiles:
- migrate
environment: *app_environment
volumes: *app_volumes
restart: 'no'
command: |
mix ecto.migrate \
&& MIX_ENV=test mix.ecto.migrate
init: true
volumes:
- './mix.exs:/opt/app/mix.exs:ro'
- './mix.lock:/opt/app/mix.lock:ro'
- './students.csv:/opt/app/students.csv:ro'
- './config:/opt/app/config'
- './cover:/opt/app/cover'
- './lib:/opt/app/lib'
- './priv:/opt/app/priv'
- './report:/opt/app/report'
- './test:/opt/app/test'
environment:
POSTGRES_HOST: *db_host
POSTGRES_USER: *db_user
POSTGRES_PASS: *db_pass
POSTGRES_NAME: wabanex_test
working_dir: /opt/app
restart: never
entrypoint: mix test
command: --trace --cover --slowest 10
volumes:
db_data: {}
app_build: {}
app_deps: {}

20
lefthook.yml Normal file
View File

@@ -0,0 +1,20 @@
---
pre-commit:
parallel: true
commands:
compiling:
glob: &elixir_files '*.{ex,exs}'
run: make check_compile
formating:
glob: *elixir_files
run: make check_format
linting:
glob: *elixir_files
run: make credo
typing:
glob: *elixir_files
run: make dialyzer
pre-push:
commands:
testing:
run: make compose_test

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

@@ -0,0 +1,48 @@
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

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

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

View File

@@ -0,0 +1,45 @@
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

View File

@@ -0,0 +1,30 @@
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

116
lib/growth/calc/z_score.ex Normal file
View File

@@ -0,0 +1,116 @@
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

225
lib/growth/growth.ex Normal file
View File

@@ -0,0 +1,225 @@
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

View File

@@ -0,0 +1,328 @@
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

View File

@@ -0,0 +1,190 @@
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

View File

@@ -0,0 +1,18 @@
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

18
lib/growth/score/bmi.ex Normal file
View File

@@ -0,0 +1,18 @@
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

View File

@@ -0,0 +1,18 @@
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

View File

@@ -0,0 +1,18 @@
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

View File

@@ -0,0 +1,96 @@
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

View File

@@ -0,0 +1,18 @@
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

View File

@@ -0,0 +1,18 @@
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

View File

@@ -0,0 +1,18 @@
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

View File

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

View File

@@ -1,34 +1,26 @@
defmodule Wabanex.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
def start(_type, _args) do
children = [
# Start the PromEx supervisor
Wabanex.PromEx,
# Start the Ecto repository
Wabanex.Repo,
# Start the Telemetry supervisor
WabanexWeb.Telemetry,
# Start the PubSub system
{Phoenix.PubSub, name: Wabanex.PubSub},
# Start the Endpoint (http/https)
{Growth.Indicators.Load, []},
{DNSCluster,
query: Application.get_env(:wabanex, :dns_cluster_query) || :ignore,
log: :info,
resolver: Application.get_env(:wabanex, :dns_cluster_resolver) || DNSCluster.Resolver},
WabanexWeb.Endpoint
# Start a worker by calling: Wabanex.Worker.start_link(arg)
# {Wabanex.Worker, arg}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Wabanex.Supervisor]
Supervisor.start_link(children, opts)
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
def config_change(changed, _new, removed) do
WabanexWeb.Endpoint.config_change(changed, removed)
:ok

View File

@@ -0,0 +1,26 @@
defmodule Wabanex.DevDNSClusterResolver do
@moduledoc """
Local DNS resolver for cluster query.
"""
require Record
Record.defrecord(:hostent, Record.extract(:hostent, from_lib: "kernel/include/inet.hrl"))
def basename(node_name) when is_atom(node_name) do
[basename, _] = node_name |> to_string() |> String.split("@")
basename
end
def connect_node(node_name) when is_atom(node_name) do
Node.connect(node_name)
end
def list_nodes, do: Node.list(:visible)
def lookup(query, type) when is_binary(query) and type in [:a, :aaaa] do
case :inet_res.getbyname(~c"#{query}", type) do
{:ok, hostent(h_addr_list: addr_list)} -> addr_list
{:error, _} -> []
end
end
end

28
lib/wabanex/release.ex Normal file
View File

@@ -0,0 +1,28 @@
defmodule Wabanex.Release do
@moduledoc """
Used for executing DB release tasks when run in production without Mix
installed.
"""
@app :wabanex
def migrate do
load_app()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
def rollback(repo, version) do
load_app()
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end
defp repos do
Application.fetch_env!(@app, :ecto_repos)
end
defp load_app do
Application.load(@app)
end
end

View File

@@ -19,10 +19,10 @@ defmodule WabanexWeb do
def controller do
quote do
use Gettext, backend: WabanexWeb.Gettext
use Phoenix.Controller, namespace: WabanexWeb
import Plug.Conn
import WabanexWeb.Gettext
alias WabanexWeb.Router.Helpers, as: Routes
end
end
@@ -54,17 +54,18 @@ defmodule WabanexWeb do
def channel do
quote do
use Phoenix.Channel
import WabanexWeb.Gettext
use Gettext, backend: WabanexWeb.Gettext
end
end
defp view_helpers do
quote do
use Gettext, backend: WabanexWeb.Gettext
# Import basic rendering functionality (render, render_layout, etc)
import Phoenix.View
import WabanexWeb.ErrorHelpers
import WabanexWeb.Gettext
alias WabanexWeb.Router.Helpers, as: Routes
end
end

View File

@@ -5,7 +5,7 @@ defmodule WabanexWeb.Gettext do
By using [Gettext](https://hexdocs.pm/gettext),
your module gains a set of macros for translations, for example:
import WabanexWeb.Gettext
use Gettext, backend: WabanexWeb.Gettext
# Simple translation
gettext("Here is the string to translate")
@@ -20,5 +20,5 @@ defmodule WabanexWeb.Gettext do
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
"""
use Gettext, otp_app: :wabanex
use Gettext.Backend, otp_app: :wabanex
end

25
mix.exs
View File

@@ -32,27 +32,34 @@ defmodule Wabanex.MixProject do
{:absinthe, "~> 1.7.0"},
{:absinthe_plug,
git: "https://github.com/absinthe-graphql/absinthe_plug.git",
ref: "3a984cc341ebb32c79e7ae58b4ebd116d5c62f9e"},
ref: "24ec7aa3b513c7c1aa79e5cad1197cb138603972"},
{: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},
{:ecto_sql, "~> 3.11.0"},
{:gettext, "~> 0.24.0"},
{: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},
{: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_view, "~> 2.0.0"},
{:phoenix_live_dashboard, "~> 0.8.0"},
{:phoenix_view, "~> 2.0.0"},
{:plug_cowboy, "~> 2.7.0"},
{:postgrex, "~> 0.18.0"},
{:prom_ex, "~> 1.9.0"},
{:sobelow, "~> 0.13", only: [:dev, :test], runtime: false},
{:telemetry_metrics, "~> 0.6.0"},
{:telemetry_poller, "~> 1.1.0"}
{:postgrex, "~> 0.20.0"},
{:prom_ex, "~> 1.11.0"},
{:req, "~> 0.5.0"},
{:sobelow, "~> 0.14", only: [:dev, :test], runtime: false},
{:telemetry_metrics, "~> 1.1.0"},
{:telemetry_poller, "~> 1.2.0"},
{:xlsx_reader, "~> 0.8.0"}
]
end

View File

@@ -1,57 +1,69 @@
%{
"absinthe": {:hex, :absinthe, "1.7.7", "ecbf4e9b21372dda271c79bb43dded3583b4f080348c5e68d9b5445e790ff17e", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 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", "2145519828bcb7c8621b72d7af2bcff88b01cba2774583c40ebd867e1d336ff6"},
"absinthe_plug": {:git, "https://github.com/absinthe-graphql/absinthe_plug.git", "3a984cc341ebb32c79e7ae58b4ebd116d5c62f9e", [ref: "3a984cc341ebb32c79e7ae58b4ebd116d5c62f9e"]},
"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"]},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
"castore": {:hex, :castore, "1.0.14", "4582dd7d630b48cf5e1ca8d3d42494db51e406b7ba704e81fbd401866366896a", [:mix], [], "hexpm", "7bc1b65249d31701393edaaac18ec8398d8974d52c647b7904d01b964137b9f4"},
"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.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [: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", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
"cowlib": {:hex, :cowlib, "2.15.0", "3c97a318a933962d1c12b96ab7c1d728267d2c523c25a5b57b0f93392b6e9e25", [:make, :rebar3], [], "hexpm", "4f00c879a64b4fe7c8fcb42a4281925e9ffdb928820b03c3ad325a617e857532"},
"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.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
"ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [: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", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"},
"ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 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", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"},
"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"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"expo": {:hex, :expo, "0.5.2", "beba786aab8e3c5431813d7a44b828e7b922bfa431d6bfbada0904535342efe2", [:mix], [], "hexpm", "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"},
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"},
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"},
"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"},
"gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"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.3", "1745a88e46606c4f86408299f54878b7d0cd22ea3e9c54b0018b6ed631a9ce87", [:mix], [], "hexpm", "ea373ec4d2df213357c5a464be16ab08d1e58e61ea2de784a483780c22a1e74a"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [: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", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"},
"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"},
"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"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"octo_fetch": {:hex, :octo_fetch, "0.4.0", "074b5ecbc08be10b05b27e9db08bc20a3060142769436242702931c418695b19", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "cf8be6f40cd519d7000bb4e84adcf661c32e59369ca2827c4e20042eda7a7fc6"},
"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.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [: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", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.2", "3b83b24ab5a2eb071a20372f740d7118767c272db386831b2e77638c4dcc606d", [: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", "3f94d025f59de86be00f5f8c5dd7b5965a3298458d21ab1c328488be3b5fcd59"},
"phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.4", "4508e481f791ce62ec6a096e13b061387158cbeefacca68c6c1928e1305e23ed", [: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", "2984aae96994fbc5c61795a73b8fb58153b41ff934019cfb522343d2d3817d59"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.20.17", "f396bbdaf4ba227b82251eb75ac0afa6b3da5e509bc0d030206374237dfc9450", [: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", "a61d741ffb78c85fdbca0de084da6a48f8ceb5261a79165b5a0b59e5f65ce98b"},
"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_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_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.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [: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", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [: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", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [: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", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"},
"prom_ex": {:hex, :prom_ex, "1.9.0", "63e6dda6c05cdeec1f26c48443dcc38ffd2118b3665ae8d2bd0e5b79f2aea03e", [:mix], [{:absinthe, ">= 1.6.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.0.2", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.5.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.4.0", [hex: :oban, repo: "hexpm", optional: true]}, {:octo_fetch, "~> 0.3", [hex: :octo_fetch, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.14.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.12.1", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.5", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry, ">= 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "01f3d4f69ec93068219e686cc65e58a29c42bea5429a8ff4e2121f19db178ee6"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
"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_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"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
"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"},
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
"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.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [: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", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
"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"},
}

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
elixir --name ${HOSTNAME}@$(hostname -i | cut -d ' ' -f1) --cookie ${1} -S mix phx.server

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
iex --name ex_trainer_remote@127.0.0.1 --hidden --cookie local-cookie --remsh ${HOSTNAME}@$(hostname -i | cut -d ' ' -f1)

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

5
rel/overlays/bin/migrate Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
set -eu
cd -P -- "$(dirname -- "$0")"
exec ./wabanex eval Wabanex.Release.migrate

1
rel/overlays/bin/migrate.bat Executable file
View File

@@ -0,0 +1 @@
call "%~dp0\wabanex" eval Wabanex.Release.migrate

5
rel/overlays/bin/server Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
set -eu
cd -P -- "$(dirname -- "$0")"
PHX_SERVER=true exec ./wabanex start

2
rel/overlays/bin/server.bat Executable file
View File

@@ -0,0 +1,2 @@
set PHX_SERVER=true
call "%~dp0\wabanex" start

View File

@@ -0,0 +1,46 @@
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

View File

@@ -0,0 +1,23 @@
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

View File

@@ -0,0 +1,24 @@
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

View File

@@ -0,0 +1,26 @@
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

View File

@@ -0,0 +1,24 @@
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

131
test/growth/growth_test.exs Normal file
View File

@@ -0,0 +1,131 @@
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

View File

@@ -0,0 +1,297 @@
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

View File

@@ -0,0 +1,142 @@
# test/growth/score/weight_for_height_test.exs
defmodule Growth.Score.WeightForHeightTest do
use ExUnit.Case, async: true
alias Growth
alias Growth.Score.WeightForHeight
describe "measure_name/0" do
test "returns the correct measure name atom" do
assert WeightForHeight.measure_name() == :weight_for_height
end
end
describe "lms/2" do
test "returns LMS values for exact height match" do
growth = %Growth{
gender: :male,
weight: 7.3,
height: 65.0,
name: "Joe Doe",
date_of_measurement: Date.utc_today(),
date_of_birth: Date.shift(Date.utc_today(), day: -91)
}
assert WeightForHeight.lms(growth, WeightForHeight) == [
{"height", {-0.35210000000000002, 7.4326999999999996, 0.082170000000000007}}
]
end
test "returns LMS values for closest height match when exact is not found" do
growth = %Growth{
gender: :male,
weight: 7.3,
height: 64.9,
name: "Joe Doe",
date_of_measurement: Date.utc_today(),
date_of_birth: Date.shift(Date.utc_today(), day: -91)
}
assert WeightForHeight.lms(growth, WeightForHeight) == [
{"height", {-0.35210000000000002, 7.4326999999999996, 0.082170000000000007}}
]
growth_closer_to_higher = %Growth{
gender: :male,
weight: 7.4,
height: 65.09,
name: "Joe Doe",
date_of_measurement: Date.utc_today(),
date_of_birth: Date.shift(Date.utc_today(), day: -91)
}
assert WeightForHeight.lms(growth_closer_to_higher, WeightForHeight) == [
{"height", {-0.35210000000000002, 7.4562999999999997, 0.082159999999999997}}
]
end
test "returns empty list if weight or height is missing" do
growth_no_weight = %Growth{
gender: :male,
height: 60.0,
name: "Joe Doe",
date_of_measurement: Date.utc_today(),
date_of_birth: Date.shift(Date.utc_today(), day: -91)
}
assert WeightForHeight.lms(growth_no_weight, WeightForHeight) == []
growth_no_height = %Growth{
gender: :male,
weight: 5.0,
name: "Joe Doe",
date_of_measurement: Date.utc_today(),
date_of_birth: Date.shift(Date.utc_today(), day: -91)
}
assert WeightForHeight.lms(growth_no_height, WeightForHeight) == []
growth_nil_values = %Growth{
gender: :male,
weight: nil,
height: nil,
name: "Joe Doe",
date_of_measurement: Date.utc_today(),
date_of_birth: Date.shift(Date.utc_today(), day: -91)
}
assert WeightForHeight.lms(growth_nil_values, WeightForHeight) == []
end
test "returns empty list if no matching gender data exists" do
# Assuming no :unknown gender data was inserted
growth = %Growth{
gender: :unknown,
weight: 5.0,
height: 60.0,
name: "Joe Doe",
date_of_measurement: Date.utc_today(),
date_of_birth: Date.shift(Date.utc_today(), day: -91)
}
assert WeightForHeight.lms(growth, WeightForHeight) == []
end
end
describe "find_closest_height/2" do
test "finds the lower closest height" do
assert WeightForHeight.find_closest_height(:male, 64.99) == [
{"height", {-0.35210000000000002, 7.4326999999999996, 0.082170000000000007}}
]
end
test "finds the higher closest height" do
assert WeightForHeight.find_closest_height(:male, 65.61) == [
{"height", {-0.35210000000000002, 7.5738000000000003, 0.082140000000000005}}
]
end
test "finds the lower closest height when exactly midway" do
expected_lms =
[
[{"height", {-0.35210000000000002, 7.5034000000000001, 0.082150000000000001}}],
[{"height", {-0.35210000000000002, 7.4798999999999998, 0.082159999999999997}}]
]
lms = WeightForHeight.find_closest_height(:male, 65.25)
assert Enum.any?(expected_lms, fn entity_lms -> entity_lms == lms end)
end
test "do not match when below minimum height" do
assert WeightForHeight.find_closest_height(:male, 44.0) == []
end
test "do not match when above maximum height" do
assert WeightForHeight.find_closest_height(:male, 121.0) == []
end
test "returns empty list when no data exists for the gender" do
assert WeightForHeight.find_closest_height(:unknown, 60.0) == []
end
end
end

123
test/support/growth_data.ex Normal file
View File

@@ -0,0 +1,123 @@
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