diff --git a/src/igh_data_transform/transformations/diseases.py b/src/igh_data_transform/transformations/diseases.py index 1745eb7..7ce1571 100644 --- a/src/igh_data_transform/transformations/diseases.py +++ b/src/igh_data_transform/transformations/diseases.py @@ -181,6 +181,36 @@ def transform_diseases( mask = has_dash & suffix_matches_secondary df.loc[mask, "disease_filter"] = parent_candidate[mask] + # ========================================================= + # Display label (single source of truth) + # ========================================================= + # + # The portal prints ONE disease label in tables, slide-ins and the + # per-column table filter. The agreed rule: + # * default -> the secondary disease (COVID-19, Lassa fever, + # Gonorrhea, …); + # * no secondary -> the primary disease group (Tuberculosis, + # Buruli ulcer); + # * Malaria (the -> "", because the strains + # only outlier) (P. falciparum, P. vivax) never stand alone. + # Computing it here keeps the rule in one tested place; downstream + # (Gold, GraphQL, the React tables/slide-ins) just passes it through. + # Both inputs are already normalized above, so "no secondary" is + # exactly `secondary_disease_name IS NULL` here. + if "disease_filter" in df.columns and "secondary_disease_name" in df.columns: + primary = df["disease_filter"] + secondary = df["secondary_disease_name"] + has_secondary = secondary.notna() + + # Default branch: the secondary when present, else the primary group. + label = secondary.where(has_secondary, primary) + + # Malaria outlier: prefix the primary so the strain never stands alone. + malaria = has_secondary & primary.eq("Malaria") + label = label.mask(malaria, primary.str.cat(secondary, sep=" – ")) + + df["disease_label"] = label + cleaned_option_sets: dict[str, pd.DataFrame] = {} if option_sets and "_optionset_new_globalhealtharea" in option_sets: diff --git a/src/igh_data_transform/transformations/silver_to_gold/config/schema_map.py b/src/igh_data_transform/transformations/silver_to_gold/config/schema_map.py index fda4589..971a4ef 100644 --- a/src/igh_data_transform/transformations/silver_to_gold/config/schema_map.py +++ b/src/igh_data_transform/transformations/silver_to_gold/config/schema_map.py @@ -83,6 +83,10 @@ # any new joins to filter by either. "disease_filter": "disease_filter", "secondary_disease_name": "secondary_disease_name", + # Pre-computed canonical display label (primary/secondary/Malaria + # rule). Tables, slide-ins and the per-column table filter all read + # this so there is one definition of how a disease is named. + "disease_label": "disease_label", }, "dim_phase": { "_source_table": "vin_rdstages", diff --git a/tests/e2e/test_silver_to_gold_e2e.py b/tests/e2e/test_silver_to_gold_e2e.py index 584eb56..f8b2537 100644 --- a/tests/e2e/test_silver_to_gold_e2e.py +++ b/tests/e2e/test_silver_to_gold_e2e.py @@ -191,6 +191,7 @@ def test_dim_disease_has_expected_columns(self, gold_conn): "disease_name", "disease_group_name", "global_health_area", + "disease_label", } missing = expected - cols assert not missing, f"Missing columns: {missing}" diff --git a/tests/unit/test_diseases.py b/tests/unit/test_diseases.py index 92e3492..42f811b 100644 --- a/tests/unit/test_diseases.py +++ b/tests/unit/test_diseases.py @@ -309,3 +309,76 @@ def test_normalizes_sti_primary_when_suffix_matches_secondary(self): # Secondary unchanged. assert result["secondary_disease_name"].iloc[0] == "Gonorrhea" assert result["secondary_disease_name"].iloc[1] == "Different" + + def test_disease_label_prefers_secondary(self): + # Default rule: when a secondary disease exists, it is the label + # (the primary group is implied by context). + df = self._make_input_df( + overrides={ + "new_diseasefilter": ["Coronaviral diseases", "Filoviral diseases"], + "new_secondary_diseae_choice_text": ["COVID-19", "Ebola"], + } + ) + result, _ = transform_diseases(df) + assert result["disease_label"].iloc[0] == "COVID-19" + assert result["disease_label"].iloc[1] == "Ebola" + + def test_disease_label_falls_back_to_primary_without_secondary(self): + # No secondary -> show the primary disease group. The sentinel + # "No secondary disease" has already been collapsed to NULL. + df = self._make_input_df( + overrides={ + "new_diseasefilter": ["Tuberculosis", "Buruli ulcer"], + "new_secondary_diseae_choice_text": [None, "No secondary disease"], + } + ) + result, _ = transform_diseases(df) + assert result["disease_label"].iloc[0] == "Tuberculosis" + assert result["disease_label"].iloc[1] == "Buruli ulcer" + + def test_disease_label_combines_for_malaria(self): + # Malaria is the only outlier: its strains never stand alone, so + # the label is "" with a spaced en dash. + df = self._make_input_df( + overrides={ + "new_diseasefilter": ["Malaria", "Malaria"], + "new_secondary_diseae_choice_text": ["P. falciparum", "P. vivax"], + } + ) + result, _ = transform_diseases(df) + assert result["disease_label"].iloc[0] == "Malaria – P. falciparum" + assert result["disease_label"].iloc[1] == "Malaria – P. vivax" + + def test_disease_label_for_sti_uses_secondary(self): + # STIs arrive parent-collapsed with the specific infection in the + # secondary field, so the default branch already prints it. + df = self._make_input_df( + overrides={ + "new_diseasefilter": [ + "Sexually transmitted infections (STIs) - Gonorrhea", + "Sexually transmitted infections (STIs)", + ], + "new_secondary_diseae_choice_text": [ + "Gonorrhea", + "Trichomoniasis", + ], + } + ) + result, _ = transform_diseases(df) + assert result["disease_label"].iloc[0] == "Gonorrhea" + assert result["disease_label"].iloc[1] == "Trichomoniasis" + + def test_disease_label_is_null_when_both_inputs_missing(self): + # Cross-cutting "R&D for all global health areas" rows carry + # neither a primary filter nor a secondary -> no label. The + # second row keeps a secondary populated so the column survives + # `drop_empty_columns` (in production it always has values). + df = self._make_input_df( + overrides={ + "new_diseasefilter": [None, "Dengue"], + "new_secondary_diseae_choice_text": [None, "Severe dengue"], + } + ) + result, _ = transform_diseases(df) + assert pd.isna(result["disease_label"].iloc[0]) + assert result["disease_label"].iloc[1] == "Severe dengue"