From 1e809fa919a724c5e5f8742d3ce4ee31a0872b32 Mon Sep 17 00:00:00 2001 From: AtomicGlance Date: Sat, 26 Sep 2026 20:31:13 +0330 Subject: [PATCH 1/2] Preserve NWB subject age reference in metadata --- dandi/consts.py | 1 + dandi/metadata/util.py | 2 ++ dandi/tests/test_metadata.py | 54 ++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/dandi/consts.py b/dandi/consts.py index baca5465c..ea1a5fb55 100644 --- a/dandi/consts.py +++ b/dandi/consts.py @@ -32,6 +32,7 @@ metadata_nwb_subject_fields = ( "age", + "age__reference", "date_of_birth", "genotype", "sex", diff --git a/dandi/metadata/util.py b/dandi/metadata/util.py index 1bd41bf1f..ab294a572 100644 --- a/dandi/metadata/util.py +++ b/dandi/metadata/util.py @@ -247,6 +247,8 @@ def extract_age(metadata: dict) -> models.PropertyValue | None: except (KeyError, TypeError, ValueError): if metadata.get("age") is not None: duration, ref = parse_age(metadata["age"]) + if metadata.get("age__reference") == "gestational": + ref = "Gestational" else: return None else: diff --git a/dandi/tests/test_metadata.py b/dandi/tests/test_metadata.py index 26f29acbb..c8a8042a9 100644 --- a/dandi/tests/test_metadata.py +++ b/dandi/tests/test_metadata.py @@ -34,6 +34,7 @@ import numpy as np from pydantic import ByteSize from pynwb import NWBHDF5IO, NWBFile, TimeSeries +from pynwb.file import Subject import pytest import requests from semantic_version import Version @@ -475,6 +476,59 @@ def test_time_extract() -> None: ) +@pytest.mark.ai_generated +@pytest.mark.parametrize("reference", ["birth", "gestational"]) +def test_age_reference_roundtrip(tmp_path: Path, reference: str) -> None: + path = tmp_path / "age.nwb" + nwb = NWBFile("age reference", "age", datetime(2020, 1, 1, tzinfo=tzutc())) + nwb.subject = Subject(age="P3W", age__reference=reference) + with NWBHDF5IO(str(path), "w") as io: + io.write(nwb) + metadata = get_metadata(path) + assert metadata["age__reference"] == reference + age = extract_age(metadata) + assert age is not None + assert age.value == "P3W" + expected = ( + AgeReferenceType.GestationalReference + if reference == "gestational" + else AgeReferenceType.BirthReference + ) + assert age.valueReference == PropertyValue(value=expected) + + +@pytest.mark.ai_generated +@pytest.mark.parametrize("reference", ["birth", None]) +@pytest.mark.parametrize("legacy", [False, True]) +def test_age_reference_legacy(reference: str | None, legacy: bool) -> None: + age = extract_age( + {"age": "Gestational week 3" if legacy else "P3W", "age__reference": reference} + ) + assert age is not None + assert age.value == "P3W" + assert age.valueReference == PropertyValue( + value=AgeReferenceType.GestationalReference + if legacy + else AgeReferenceType.BirthReference + ) + + +@pytest.mark.ai_generated +@pytest.mark.parametrize("reference", ["birth", "gestational", None]) +def test_age_reference_preserves_birth_dates(reference: str | None) -> None: + age = extract_age( + { + "age": "P3W", + "age__reference": reference, + "date_of_birth": "2020-01-01T00:00:00Z", + "session_start_time": "2020-01-03T00:00:00Z", + } + ) + assert age is not None + assert age.value == "P2D" + assert age.valueReference == PropertyValue(value=AgeReferenceType.BirthReference) + + def test_time_extract_gest() -> None: """extract age with Gestational ref""" meta_birth = { From 17c1cc1977381a864a28443540beed1ba8030c24 Mon Sep 17 00:00:00 2001 From: AtomicGlance Date: Sat, 26 Sep 2026 22:46:22 +0330 Subject: [PATCH 2/2] Centralize expected age references in metadata tests --- dandi/tests/test_metadata.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/dandi/tests/test_metadata.py b/dandi/tests/test_metadata.py index c8a8042a9..f6b40ddaa 100644 --- a/dandi/tests/test_metadata.py +++ b/dandi/tests/test_metadata.py @@ -63,6 +63,12 @@ METADATA_DIR = Path(__file__).with_name("data") / "metadata" +AGE_REFERENCE_TYPES = { + "birth": AgeReferenceType.BirthReference, + "gestational": AgeReferenceType.GestationalReference, + None: AgeReferenceType.BirthReference, +} + mark_xfail_ontobee = pytest.mark.xfail( condition="not config.getoption('--scheduled')", reason="Flaky ontobee site", @@ -477,7 +483,9 @@ def test_time_extract() -> None: @pytest.mark.ai_generated -@pytest.mark.parametrize("reference", ["birth", "gestational"]) +@pytest.mark.parametrize( + "reference", [ref for ref in AGE_REFERENCE_TYPES.keys() if ref is not None] +) def test_age_reference_roundtrip(tmp_path: Path, reference: str) -> None: path = tmp_path / "age.nwb" nwb = NWBFile("age reference", "age", datetime(2020, 1, 1, tzinfo=tzutc())) @@ -489,16 +497,11 @@ def test_age_reference_roundtrip(tmp_path: Path, reference: str) -> None: age = extract_age(metadata) assert age is not None assert age.value == "P3W" - expected = ( - AgeReferenceType.GestationalReference - if reference == "gestational" - else AgeReferenceType.BirthReference - ) - assert age.valueReference == PropertyValue(value=expected) + assert age.valueReference == PropertyValue(value=AGE_REFERENCE_TYPES[reference]) @pytest.mark.ai_generated -@pytest.mark.parametrize("reference", ["birth", None]) +@pytest.mark.parametrize("reference", AGE_REFERENCE_TYPES.keys()) @pytest.mark.parametrize("legacy", [False, True]) def test_age_reference_legacy(reference: str | None, legacy: bool) -> None: age = extract_age( @@ -507,14 +510,12 @@ def test_age_reference_legacy(reference: str | None, legacy: bool) -> None: assert age is not None assert age.value == "P3W" assert age.valueReference == PropertyValue( - value=AgeReferenceType.GestationalReference - if legacy - else AgeReferenceType.BirthReference + value=AGE_REFERENCE_TYPES["gestational" if legacy else reference] ) @pytest.mark.ai_generated -@pytest.mark.parametrize("reference", ["birth", "gestational", None]) +@pytest.mark.parametrize("reference", AGE_REFERENCE_TYPES.keys()) def test_age_reference_preserves_birth_dates(reference: str | None) -> None: age = extract_age( { @@ -526,7 +527,7 @@ def test_age_reference_preserves_birth_dates(reference: str | None) -> None: ) assert age is not None assert age.value == "P2D" - assert age.valueReference == PropertyValue(value=AgeReferenceType.BirthReference) + assert age.valueReference == PropertyValue(value=AGE_REFERENCE_TYPES["birth"]) def test_time_extract_gest() -> None: