Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions rcpchgrowth/constants/reference_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
Reference constants
"""

from .age_constants import (
FORTY_TWO_WEEKS_GESTATION,
TWENTY_FIVE_WEEKS_GESTATION,
TWENTY_THREE_WEEKS_GESTATION,
)

# Trisomy 21 constants
TRISOMY_21 = "trisomy-21"
TRISOMY_21_AAP = "trisomy-21-aap"
Expand Down Expand Up @@ -34,15 +40,13 @@
WHO_2007_CHILD = "who_2007_child" # WHO 2007 child is the reference name for children 5-19 years
WHO_REFERENCES = [WHO_2006_INFANT, WHO_2006_CHILD, WHO_2007_CHILD] # WHO references

WHO_2006_REFERENCE_LOWER_THRESHOLD = ((42 * 7) - (40 * 7)) / 365.25 # 42 weeks as decimal age # 2 weeks as decimal age
WHO_2006_REFERENCE_LOWER_THRESHOLD = FORTY_TWO_WEEKS_GESTATION
WHO_2006_REFERENCE_UPPER_THRESHOLD = 1856/365.25 # 5 years and one month just under as decimal age
WHO_2007_REFERENCE_LOWER_THRESHOLD = (61/30.4375)/365.25 # 5 years and one month as decimal age (61 mths)
WHO_2007_REFERENCE_UPPER_THRESHOLD = 19.0 # 19 years as decimal age

# 23 weeks is the lowest decimal age available on the UK90 charts
UK90_REFERENCE_LOWER_THRESHOLD = (
(23 * 7) - (40 * 7)
) / 365.25 # 23 weeks as decimal age
UK90_REFERENCE_LOWER_THRESHOLD = TWENTY_THREE_WEEKS_GESTATION

# The WHO references change from measuring infants in the lying position to measuring children in the standing position at 2.0 years.
WHO_CHILD_LOWER_THRESHOLD = 2.0 # 2 years as decimal age
Expand All @@ -56,9 +60,7 @@
# 3 weeks of life

WHO_CHILDREN_UPPER_THRESHOLD = 4.0
UK_WHO_INFANT_LOWER_THRESHOLD = (
(42 * 7) - (40 * 7)
) / 365.25 # 42 weeks as decimal age
UK_WHO_INFANT_LOWER_THRESHOLD = FORTY_TWO_WEEKS_GESTATION
UK90_UPPER_THRESHOLD = 20

WHO_NEWBORN_LOWER_THRESHOLD = 0.0
Expand Down Expand Up @@ -111,8 +113,8 @@
]
EXTENDED_WHO_CENTILES_COLLECTION = [1, 3, 5, 10, 15, 50, 85, 90, 95, 97, 99]

UK_90_PRETERM_AGES = [-0.325804244, -0.306639288, -0.287474333, -0.268309377, -0.249144422, -0.229979466, -0.210814511, -0.191649555, -0.1724846, -0.153319644, -0.134154689, -0.114989733, -0.095824778, -0.076659822, -0.057494867, -0.038329911, -0.019164956, 0, 0.019164956, 0.038329911]
WHO_2006_UNDER_TWOS_AGES = [0.038329911, 0.057494867, 0.076659822, 0.083333333, 0.095824778, 0.114989733, 0.134154689, 0.153319644, 0.166666667, 0.1724846, 0.191649555, 0.210814511, 0.229979466, 0.249144422, 0.25, 0.333333333, 0.416666667, 0.5, 0.583333333, 0.666666667, 0.75, 0.833333333, 0.916666667, 1, 1.083333333, 1.166666667, 1.25, 1.333333333, 1.416666667, 1.5, 1.583333333, 1.666666667, 1.75, 1.833333333, 1.916666667, 2]
UK_90_PRETERM_AGES = [TWENTY_THREE_WEEKS_GESTATION, -0.306639288, TWENTY_FIVE_WEEKS_GESTATION, -0.268309377, -0.249144422, -0.229979466, -0.210814511, -0.191649555, -0.1724846, -0.153319644, -0.134154689, -0.114989733, -0.095824778, -0.076659822, -0.057494867, -0.038329911, -0.019164956, 0, 0.019164956, FORTY_TWO_WEEKS_GESTATION]
WHO_2006_UNDER_TWOS_AGES = [FORTY_TWO_WEEKS_GESTATION, 0.057494867, 0.076659822, 0.083333333, 0.095824778, 0.114989733, 0.134154689, 0.153319644, 0.166666667, 0.1724846, 0.191649555, 0.210814511, 0.229979466, 0.249144422, 0.25, 0.333333333, 0.416666667, 0.5, 0.583333333, 0.666666667, 0.75, 0.833333333, 0.916666667, 1, 1.083333333, 1.166666667, 1.25, 1.333333333, 1.416666667, 1.5, 1.583333333, 1.666666667, 1.75, 1.833333333, 1.916666667, 2]
UK_WHO_2006_OVER_TWOS_AGES = [2, 2.083333333, 2.166666667, 2.25, 2.333333333, 2.416666667, 2.5, 2.583333333, 2.666666667, 2.75, 2.833333333, 2.916666667, 3, 3.083333333, 3.166666667, 3.25, 3.333333333, 3.416666667, 3.5, 3.583333333, 3.666666667, 3.75, 3.833333333, 3.916666667, 4]
WHO_2006_OVER_TWOS_AGES = [2, 2.083333333, 2.166666667, 2.25, 2.333333333, 2.416666667, 2.5, 2.583333333, 2.666666667, 2.75, 2.833333333, 2.916666667, 3, 3.083333333, 3.166666667, 3.25, 3.333333333, 3.416666667, 3.5, 3.583333333, 3.666666667, 3.75, 3.833333333, 3.916666667, 4, 4.083333333, 4.166666667, 4.25, 4.333333333, 4.416666667, 4.5, 4.583333333, 4.666666667, 4.75, 4.833333333, 4.916666667, 5.0]
WHO_2007_AGES = [5.0, 5.08,5.17,5.25,5.33,5.42, 5.5,5.58,5.67,5.75,5.83,5.92,6,6.08,6.17,6.25,6.33,6.42, 6.5,6.58,6.67,6.75,6.83,6.92,7,7.08,7.17,7.25,7.33,7.42, 7.5,7.58,7.67,7.75,7.83,7.92,8,8.08,8.17,8.25,8.33,8.42, 8.5,8.58,8.67,8.75,8.83,8.92,9,9.08,9.17,9.25,9.33,9.42, 9.5,9.58,9.67,9.75,9.83,9.92,10,10.08,10.17,10.25,10.33,10.42, 10.5,10.58,10.67,10.75,10.83,10.92,11,11.08,11.17,11.25,11.33,11.42, 11.5,11.58,11.67,11.75,11.83,11.92,12,12.08,12.17,12.25,12.33,12.42, 12.5,12.58,12.67,12.75,12.83,12.92,13,13.08,13.17,13.25,13.33,13.42, 13.5,13.58,13.67,13.75,13.83,13.92,14,14.08,14.17,14.25,14.33,14.42, 14.5,14.58,14.67,14.75,14.83,14.92,15,15.08,15.17,15.25,15.33,15.42, 15.5,15.58,15.67,15.75,15.83,15.92,16,16.08,16.17,16.25,16.33,16.42, 16.5,16.58,16.67,16.75,16.83,16.92,17,17.08,17.17,17.25,17.33,17.42, 17.5,17.58,17.67,17.75,17.83,17.92, 18,18.08,18.17,18.25,18.33,18.42, 18.5,18.58,18.67,18.75,18.83,18.92,19]
Expand All @@ -123,4 +125,4 @@
TURNER_AGES=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
TRISOMY_21_AGES=[0, 0.08, 0.17, 0.25, 0.33, 0.42, 0.5, 0.58, 0.67, 0.75, 0.83, 0.92, 1, 1.08, 1.17, 1.25, 1.33, 1.42, 1.5, 1.58, 1.67, 1.75, 1.83, 1.92, 2, 2.08, 2.17, 2.25, 2.33, 2.42, 2.5, 2.58, 2.67, 2.75, 2.83, 2.92, 3, 3.08, 3.17, 3.25, 3.33, 3.42, 3.5, 3.58, 3.67, 3.75, 3.83, 3.92, 4, 4.08, 4.17, 4.25, 4.33, 4.42, 4.5, 4.58, 4.67, 4.75, 4.83, 4.92, 5, 5.08, 5.17, 5.25, 5.33, 5.42, 5.5, 5.58, 5.67, 5.75, 5.83, 5.92, 6, 6.08, 6.17, 6.25, 6.33, 6.42, 6.5, 6.58, 6.67, 6.75, 6.83, 6.92, 7, 7.08, 7.17, 7.25, 7.33, 7.42, 7.5, 7.58, 7.67, 7.75, 7.83, 7.92, 8, 8.08, 8.17, 8.25, 8.33, 8.42, 8.5, 8.58, 8.67, 8.75, 8.83, 8.92, 9, 9.08, 9.17, 9.25, 9.33, 9.42, 9.5, 9.58, 9.67, 9.75, 9.83, 9.92, 10, 10.08, 10.17, 10.25, 10.33, 10.42, 10.5, 10.58, 10.67, 10.75, 10.83, 10.92, 11, 11.08, 11.17, 11.25, 11.33, 11.42, 11.5, 11.58, 11.67, 11.75, 11.83, 11.92, 12, 12.08, 12.17, 12.25, 12.33, 12.42, 12.5, 12.58, 12.67, 12.75, 12.83, 12.92, 13, 13.08, 13.17, 13.25, 13.33, 13.42, 13.5, 13.58, 13.67, 13.75, 13.83, 13.92, 14, 14.08, 14.17, 14.25, 14.33, 14.42, 14.5, 14.58, 14.67, 14.75, 14.83, 14.92, 15, 15.08, 15.17, 15.25, 15.33, 15.42, 15.5, 15.58, 15.67, 15.75, 15.83, 15.92, 16, 16.08, 16.17, 16.25, 16.33, 16.42, 16.5, 16.58, 16.67, 16.75, 16.83, 16.92, 17, 17.08, 17.17, 17.25, 17.33, 17.42, 17.5, 17.58, 17.67, 17.75, 17.83, 17.92, 18, 18.08, 18.17, 18.25, 18.33, 18.42, 18.5, 18.58, 18.67, 18.75, 18.83, 18.92, 19, 19.08, 19.17, 19.25, 19.33, 19.42, 19.5, 19.58, 19.67, 19.75, 19.83, 19.92, 20]
TRISOMY_21_AAP_INFANT_AGES=[0.083333333, 0.166666667, 0.25, 0.333333333, 0.416666667, 0.5, 0.583333333, 0.666666667, 0.75, 0.833333333, 0.916666667, 1, 1.083333333, 1.166666667, 1.25, 1.333333333, 1.416666667, 1.5, 1.583333333, 1.666666667, 1.75, 1.833333333, 1.916666667, 2, 2.083333333, 2.166666667, 2.25, 2.333333333, 2.416666667, 2.5, 2.583333333, 2.666666667, 2.75, 2.833333333, 2.916666667, 3]
TRISOMY_21_AAP_CHILD_AGES=[3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5, 16, 16.5, 17, 17.5, 18, 18.5, 19, 19.5, 20]
TRISOMY_21_AAP_CHILD_AGES=[3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5, 16, 16.5, 17, 17.5, 18, 18.5, 19, 19.5, 20]
2 changes: 1 addition & 1 deletion rcpchgrowth/global_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ def generate_centile(

def should_default_to_youngest_reference(age: float, reference_name: str):
if reference_name == UK90_PRETERM:
if age == 0.038329911:
if age == FORTY_TWO_WEEKS_GESTATION:
return True
if reference_name == UK_WHO_INFANT:
if age == 2:
Expand Down
93 changes: 93 additions & 0 deletions rcpchgrowth/tests/test_chart_boundary_ages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Regression tests for valid gestational boundary points in UK-WHO charts."""

from datetime import date

import pytest

from rcpchgrowth import Measurement
from rcpchgrowth.chart_functions import create_chart
from rcpchgrowth.constants import (
BMI,
FORTY_TWO_WEEKS_GESTATION,
HEAD_CIRCUMFERENCE,
HEIGHT,
TWENTY_FIVE_WEEKS_GESTATION,
TWENTY_THREE_WEEKS_GESTATION,
TWENTY_TWO_WEEKS_GESTATION,
UK90_CHILD,
UK90_PRETERM,
UK_WHO,
UK_WHO_CHILD,
UK_WHO_INFANT,
WEIGHT,
)


@pytest.mark.parametrize(
("measurement_method", "component", "expected_first_age"),
[
(WEIGHT, UK90_PRETERM, TWENTY_THREE_WEEKS_GESTATION),
(HEAD_CIRCUMFERENCE, UK90_PRETERM, TWENTY_THREE_WEEKS_GESTATION),
(HEIGHT, UK90_PRETERM, TWENTY_FIVE_WEEKS_GESTATION),
# UK90 has no preterm BMI values; this seam point is sourced from WHO.
(BMI, UK_WHO_INFANT, FORTY_TWO_WEEKS_GESTATION),
],
)
def test_chart_includes_first_valid_gestational_boundary(
measurement_method,
component,
expected_first_age,
):
chart = create_chart(
reference=UK_WHO,
measurement_method=measurement_method,
sex="female",
)
component_data = next(item[component] for item in chart if component in item)
first_centile = component_data["female"][measurement_method][0]["data"]

assert first_centile[0]["x"] == round(expected_first_age, 4)


@pytest.mark.parametrize("sex", ["female", "male"])
@pytest.mark.parametrize("measurement_method", [HEIGHT, WEIGHT, HEAD_CIRCUMFERENCE, BMI])
def test_both_uk_who_chart_components_include_age_four(measurement_method, sex):
chart = create_chart(
reference=UK_WHO,
measurement_method=measurement_method,
sex=sex,
)
younger_data = next(
item[UK_WHO_CHILD] for item in chart if UK_WHO_CHILD in item
)[sex][measurement_method][0]["data"]
older_data = next(item[UK90_CHILD] for item in chart if UK90_CHILD in item)[
sex
][measurement_method][0]["data"]

assert younger_data[-1]["x"] == 4
assert younger_data[-1]["y"] is not None
assert older_data[0]["x"] == 4
assert older_data[0]["y"] is not None


def test_observation_below_reference_floor_remains_plottable_without_sds():
measurement = Measurement(
birth_date=date(2026, 1, 1),
observation_date=date(2026, 1, 1),
measurement_method=WEIGHT,
observation_value=0.5,
reference=UK_WHO,
sex="female",
gestation_weeks=22,
).measurement
point = measurement["plottable_data"]["centile_data"][
"corrected_decimal_age_data"
]

assert point["x"] == TWENTY_TWO_WEEKS_GESTATION
assert point["y"] == 0.5
assert point["sds"] is None
assert point["centile"] is None
assert point["observation_value_error"] == (
"There is no UK90 reference data below 23 weeks gestation"
)
5 changes: 2 additions & 3 deletions rcpchgrowth/tests/test_supported_chart_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
REFERENCES,
TRISOMY_21,
TRISOMY_21_AAP,
TWENTY_FIVE_WEEKS_GESTATION,
TURNERS,
UK_WHO,
WHO,
Expand All @@ -23,9 +24,7 @@
HEIGHT,
MALE,
[
# The nominal 25-week grid value is fractionally below the exact
# validation threshold, so the first emitted height point is 26 weeks.
("uk90_preterm", -0.2683, 0.0383),
("uk90_preterm", round(TWENTY_FIVE_WEEKS_GESTATION, 4), 0.0383),
("uk_who_infant", 0.0383, 2),
("uk_who_child", 2, 4),
("uk90_child", 4, 20),
Expand Down
1 change: 1 addition & 0 deletions spec/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Complete these items before broad formatting, import cleanup, packaging reductio
## Medium priority

- [ ] **R25 - Derive accepted age domains from reference metadata and table extents.** Replace duplicated age-limit literals with a central domain model for every reference segment, measurement, and sex. Distinguish observed table extent from clinically supported applicability, including endpoint inclusivity and overlap precedence for hybrid references such as UK-WHO and WHO. Add build-time checks against canonical source material in `rcpch/growth-references`, prohibit extrapolation beyond selected series, and test every first/last accepted endpoint plus `math.nextafter()` immediately outside it. Track implementation in issue #109.
- [ ] **R26 - Ask whether the unused WHO 2007 lower-threshold constant can be removed.** Open a small PR for Simon to review `WHO_2007_REFERENCE_LOWER_THRESHOLD`: it has no repository consumers and its expression `(61 / 30.4375) / 365.25` evaluates to about `0.00549` years rather than the documented 61 months (`61 / 12`, about `5.08333` years). Confirm that no external consumer relies on this wildcard-exported name before removing it; do not silently correct an unused public constant and create a new behavior without an identified consumer.
- [ ] **R3 - Decide the future of the unusable `BoneAge` class.** `rcpchgrowth/bone_age.py:12-48` returns a dictionary from `__init__`, so construction raises `TypeError: __init__() should return None`. The class has no internal consumers and is not exported from the package root, although related fields are supported by `Measurement`. Either implement and test a coherent object API or remove the module after checking external compatibility.
- [ ] **R4 - Define an explicit runtime package-data allowlist.** The published installation occupies roughly 15 MB and ships spreadsheets, a PDF, VBA source, validation CSVs, old WHO tables, experimental LMS tables, and other provenance material because `pyproject.toml:68-69` broadly includes file extensions beneath `data_tables`. Identify the JSON and correlation resources required at runtime, package only those files, and verify wheel and sdist contents in CI.
- [ ] **R5 - Exclude the test package from release wheels.** `pyproject.toml:60-62` discovers `rcpchgrowth*`, which includes 19 modules beneath `rcpchgrowth/tests` in the published wheel. Exclude `rcpchgrowth.tests` and its subpackages while retaining all tests in the source repository and sdist if deliberately required there.
Expand Down