From ee950d353d44c25eecf62cc2f50f33bdd77425ad Mon Sep 17 00:00:00 2001 From: SkyeAv Date: Tue, 25 Aug 2026 10:19:46 -0700 Subject: [PATCH] fix: leave number_of_cases alone instead of coercing it to study_size number_of_cases is a legitimate Biolink Association slot (cases carrying the phenotype/disease), but STUDY_SIZE_PREFIX_PATTERN matched it via 'number of cases' and coerce_study_size_columns renamed it to study_size, destroying the edge field. Add STUDY_SIZE_EXEMPT_PATTERN so study_size_target returns None for the exact slot. --- src/tablassert/coerce.py | 14 ++++++++++++++ tests/test_lib.py | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/tablassert/coerce.py b/src/tablassert/coerce.py index f25e8a2..44212f9 100644 --- a/src/tablassert/coerce.py +++ b/src/tablassert/coerce.py @@ -415,6 +415,18 @@ def coerce_pvalue_columns(lf: pl.LazyFrame) -> pl.LazyFrame: """, re.IGNORECASE | re.VERBOSE, ) +# The Biolink ``Association`` slot ``number_of_cases`` counts cases carrying the +# phenotype/disease, not the study population. It would otherwise match +# PREFIX ("number of cases") and be destroyed by the rename to ``study_size``, +# so the exact slot (separator-tolerant, like the patterns above) is exempt. +STUDY_SIZE_EXEMPT_PATTERN: re.Pattern[str] = re.compile( + rf""" + ^ + number {_SEP} of {_SEP} cases + $ + """, + re.IGNORECASE | re.VERBOSE, +) def study_size_target(name: str) -> str | None: @@ -430,6 +442,8 @@ def study_size_target(name: str) -> str | None: ``"study_size"`` when the name matches any of the study-size patterns, else ``None``. """ + if STUDY_SIZE_EXEMPT_PATTERN.search(name): + return None if STUDY_SIZE_EXACT_PATTERN.search(name): return "study_size" if STUDY_SIZE_COUNT_PATTERN.search(name): diff --git a/tests/test_lib.py b/tests/test_lib.py index f32bfd2..73c1bdc 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -2105,6 +2105,22 @@ def test_study_size_target_excludes_expanded_near_misses() -> None: assert study_size_target(n) is None, n +def test_study_size_target_leaves_number_of_cases_alone() -> None: + """study_size_target never touches the Biolink ``number_of_cases`` slot. + + ``number_of_cases`` counts cases carrying the phenotype/disease, not the + study population; coercing it to ``study_size`` destroys a legitimate edge + field. + """ + names: list[str] = ["number_of_cases", "Number of Cases", "number-of-cases", "numberofcases"] + for n in names: + assert study_size_target(n) is None, n + + lf: pl.LazyFrame = pl.DataFrame({"number_of_cases": [42, 7]}).lazy() + result: pl.DataFrame = coerce_study_size_columns(lf).collect() + assert result.columns == ["number_of_cases"] + + def test_coerce_study_size_columns_renames_n_column() -> None: """coerce_study_size_columns renames bare N to study_size.""" lf: pl.LazyFrame = pl.DataFrame({"n": [120, 450]}).lazy()