Spaces:
Runtime error
Runtime error
| # pylint: disable=missing-docstring | |
| """Lean regression coverage for the CanRisk client helpers.""" | |
| import uuid | |
| from types import SimpleNamespace | |
| import pytest | |
| from sentinel.api_clients.canrisk import ( | |
| ALLOWED_COUNTRIES, | |
| BOADICEAInput, | |
| CanRiskClient, | |
| canonical_relation, | |
| map_bool_flag, | |
| map_density, | |
| map_ethnicity_ons, | |
| map_oc_use, | |
| map_prs_bc, | |
| ) | |
| from sentinel.user_input import ( | |
| Anthropometrics, | |
| BreastHealthHistory, | |
| CancerType, | |
| Demographics, | |
| Ethnicity, | |
| FamilyMemberCancer, | |
| FamilyRelation, | |
| FamilySide, | |
| FemaleSpecific, | |
| GeneticMutation, | |
| HormoneUse, | |
| HormoneUseHistory, | |
| Lifestyle, | |
| MenstrualHistory, | |
| ParityHistory, | |
| PersonalMedicalHistory, | |
| RelationshipDegree, | |
| Sex, | |
| SmokingHistory, | |
| SmokingStatus, | |
| UserInput, | |
| ) | |
| def _rows_by_name(pedigree: str) -> dict[str, list[str]]: | |
| lines = [line for line in pedigree.splitlines() if line.strip()] | |
| header_idx = next(i for i, line in enumerate(lines) if line.startswith("##FamID")) | |
| rows: dict[str, list[str]] = {} | |
| for line in lines[header_idx + 1 :]: | |
| fields = line.split("\t") | |
| rows[fields[1]] = fields | |
| return rows | |
| def boadicea_input() -> BOADICEAInput: | |
| """Representative proband with immediate family for baseline checks. | |
| Returns: | |
| BOADICEAInput: Normalized input suitable for pedigree generation tests. | |
| """ | |
| user = UserInput( | |
| demographics=Demographics( | |
| age_years=42, | |
| sex=Sex.FEMALE, | |
| ethnicity=Ethnicity.ASHKENAZI_JEWISH, | |
| anthropometrics=Anthropometrics(height_cm=165.0, weight_kg=65.0), | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory(status=SmokingStatus.NEVER), | |
| ), | |
| personal_medical_history=PersonalMedicalHistory( | |
| genetic_mutations=[GeneticMutation.BRCA1, GeneticMutation.BRCA2], | |
| previous_cancers=[CancerType.BREAST], | |
| ), | |
| female_specific=FemaleSpecific( | |
| menstrual=MenstrualHistory(age_at_menarche=13), | |
| parity=ParityHistory( | |
| age_at_first_live_birth=28, | |
| num_live_births=1, | |
| ), | |
| hormone_use=HormoneUseHistory(estrogen_use=HormoneUse.NEVER), | |
| breast_health=BreastHealthHistory(), | |
| ), | |
| family_history=[ | |
| FamilyMemberCancer( | |
| relation=FamilyRelation.MOTHER, | |
| cancer_type=CancerType.BREAST, | |
| age_at_diagnosis=52, | |
| degree=RelationshipDegree.FIRST, | |
| side=FamilySide.MATERNAL, | |
| ), | |
| FamilyMemberCancer( | |
| relation=FamilyRelation.SISTER, | |
| cancer_type=CancerType.OVARIAN, | |
| age_at_diagnosis=48, | |
| degree=RelationshipDegree.FIRST, | |
| side=FamilySide.MATERNAL, | |
| ), | |
| FamilyMemberCancer( | |
| relation=FamilyRelation.MATERNAL_AUNT, | |
| cancer_type=CancerType.BREAST, | |
| age_at_diagnosis=45, | |
| degree=RelationshipDegree.SECOND, | |
| side=FamilySide.MATERNAL, | |
| ), | |
| ], | |
| ) | |
| return BOADICEAInput.from_user_input(user) | |
| def test_pedigree_basic_family_structure(boadicea_input: BOADICEAInput) -> None: | |
| client = CanRiskClient() | |
| rows = _rows_by_name(client._create_pedigree_file(boadicea_input)) | |
| assert {"P1", "Mother", "Father", "Sister"} <= set(rows) | |
| proband = rows["P1"] | |
| mother = rows["Mother"] | |
| father = rows["Father"] | |
| sister = rows["Sister"] | |
| # Proband anchors the pedigree and carries Ashkenazi flag + personal cancer history. | |
| assert proband[4] == father[3] # FathID | |
| assert proband[5] == mother[3] # MothID | |
| assert proband[11] == "37" # placeholder age from previous_cancers | |
| assert proband[16] == "1" # Ashkenazi column | |
| # Mother row reflects supplied diagnosis age; sister linked to both parents. | |
| assert mother[6] == "F" and mother[11] == "52" | |
| assert father[6] == "M" and father[4] == father[5] == "0" | |
| assert sister[4] == father[3] and sister[5] == mother[3] | |
| def test_pedigree_extended_relations_and_children() -> None: | |
| user = UserInput( | |
| demographics=Demographics(age=35, sex="female", ethnicity="White"), | |
| lifestyle=Lifestyle(smoking_status="never", alcohol_consumption="none"), | |
| personal_medical_history=PersonalMedicalHistory(known_genetic_mutations=[]), | |
| female_specific=FemaleSpecific(age_at_first_period=12, num_live_births=1), | |
| family_history=[ | |
| FamilyMemberCancer( | |
| relative="maternal aunt", cancer_type="breast", age_at_diagnosis=45 | |
| ), | |
| FamilyMemberCancer( | |
| relative="paternal uncle", cancer_type="prostate", age_at_diagnosis=60 | |
| ), | |
| FamilyMemberCancer( | |
| relative="daughter", cancer_type="", age_at_diagnosis=None | |
| ), | |
| ], | |
| ) | |
| client = CanRiskClient() | |
| rows = _rows_by_name( | |
| client._create_pedigree_file(BOADICEAInput.from_user_input(user)) | |
| ) | |
| required_names = { | |
| "P1", | |
| "Daughter", | |
| "Partner", | |
| "MaternalAunt", | |
| "MaternalGrandfather", | |
| "MaternalGrandmother", | |
| "PaternalUncle", | |
| "PaternalGrandfather", | |
| "PaternalGrandmother", | |
| } | |
| assert required_names <= set(rows) | |
| partner = rows["Partner"] | |
| daughter = rows["Daughter"] | |
| assert partner[6] == "M" | |
| assert daughter[4] == partner[3] # daughter fathID -> partner | |
| assert daughter[5] == rows["P1"][3] # daughter mothID -> proband | |
| maternal_aunt = rows["MaternalAunt"] | |
| assert maternal_aunt[4] == rows["MaternalGrandfather"][3] | |
| assert maternal_aunt[5] == rows["MaternalGrandmother"][3] | |
| paternal_uncle = rows["PaternalUncle"] | |
| assert paternal_uncle[4] == rows["PaternalGrandfather"][3] | |
| assert paternal_uncle[5] == rows["PaternalGrandmother"][3] | |
| def test_multiple_cancers_merge_into_single_relative() -> None: | |
| user = UserInput( | |
| demographics=Demographics( | |
| age_years=55, | |
| sex=Sex.FEMALE, | |
| ethnicity=Ethnicity.WHITE, | |
| anthropometrics=Anthropometrics(height_cm=170.0, weight_kg=70.0), | |
| ), | |
| lifestyle=Lifestyle( | |
| smoking=SmokingHistory(status=SmokingStatus.NEVER), | |
| ), | |
| personal_medical_history=PersonalMedicalHistory(genetic_mutations=[]), | |
| female_specific=FemaleSpecific( | |
| menstrual=MenstrualHistory(age_at_menarche=13), | |
| breast_health=BreastHealthHistory(), | |
| ), | |
| family_history=[ | |
| FamilyMemberCancer( | |
| relation=FamilyRelation.MOTHER, | |
| cancer_type=CancerType.BREAST, | |
| age_at_diagnosis=50, | |
| degree=RelationshipDegree.FIRST, | |
| side=FamilySide.MATERNAL, | |
| ), | |
| FamilyMemberCancer( | |
| relation=FamilyRelation.MOTHER, | |
| cancer_type=CancerType.OVARIAN, | |
| age_at_diagnosis=54, | |
| degree=RelationshipDegree.FIRST, | |
| side=FamilySide.MATERNAL, | |
| ), | |
| FamilyMemberCancer( | |
| relation=FamilyRelation.MOTHER, | |
| cancer_type=CancerType.BREAST, | |
| age_at_diagnosis=58, | |
| degree=RelationshipDegree.FIRST, | |
| side=FamilySide.MATERNAL, | |
| ), | |
| ], | |
| ) | |
| boadicea = BOADICEAInput.from_user_input(user) | |
| member = boadicea.family_history[0] | |
| sites = member.cancer_site_columns() | |
| assert sites == {"BC1": "50", "BC2": "58", "OC": "54", "PRO": "0", "PAN": "0"} | |
| rows = _rows_by_name(CanRiskClient()._create_pedigree_file(boadicea)) | |
| mother = rows["Mother"] | |
| assert mother[11] == "50" and mother[12] == "58" and mother[13] == "54" | |
| def test_core_mapping_helpers_cover_primary_cases() -> None: | |
| group, background, ashkenazi = map_ethnicity_ons("Ashkenazi Jewish") | |
| assert (group, background, ashkenazi) == ("White", "Jewish", True) | |
| assert map_ethnicity_ons("Unknown Ethnicity") == (None, None, False) | |
| assert canonical_relation("wife") == "partner" | |
| assert canonical_relation("paternal grandfather") == "grandfather" | |
| birads, volpara, stratus = map_density(SimpleNamespace(birads="B")) | |
| assert birads == "b" and volpara is None and stratus is None | |
| birads2, volpara2, stratus2 = map_density( | |
| SimpleNamespace( | |
| birads=None, | |
| birads_category=None, | |
| volpara_percent=23.4, | |
| stratus_percent=None, | |
| ) | |
| ) | |
| assert birads2 is None and volpara2 == 23.4 and stratus2 is None | |
| oc_former = map_oc_use( | |
| SimpleNamespace(oral_contraception=None, oc_status="current", oc_years=6) | |
| ) | |
| oc_never = map_oc_use( | |
| SimpleNamespace(oral_contraception="N", oc_status="", oc_years=None) | |
| ) | |
| assert oc_former == "C:6" and oc_never == "N" | |
| alpha, zscore = map_prs_bc({"prs_bc_alpha": 0.4, "prs_bc_zscore": 1.2}) | |
| assert alpha == pytest.approx(0.4) and zscore == pytest.approx(1.2) | |
| assert map_bool_flag("yes") is True | |
| assert map_bool_flag("0") is False | |
| assert map_bool_flag("maybe") is None | |
| def test_submit_boadicea_payload_validation(monkeypatch: pytest.MonkeyPatch) -> None: | |
| client = CanRiskClient() | |
| captured_payloads: list[dict[str, str]] = [] | |
| def fake_post(*_, **kwargs): | |
| captured_payloads.append(kwargs["json"]) | |
| return SimpleNamespace( | |
| ok=True, headers={"Content-Type": "application/json"}, json=lambda: {} | |
| ) | |
| monkeypatch.setattr(client, "authenticate", lambda: None) | |
| monkeypatch.setattr(client.session, "post", fake_post) | |
| invalid = BOADICEAInput( | |
| age=45, mut_freq="Germany", cancer_rates="Japan", personal_medical_history=None | |
| ) | |
| client.submit_boadicea_assessment(invalid, user_id="explicit-id") | |
| payload = captured_payloads[-1] | |
| assert payload["mut_freq"] == "UK" | |
| assert payload["cancer_rates"] == "UK" | |
| assert payload["user_id"] == "explicit-id" | |
| valid = BOADICEAInput( | |
| age=40, mut_freq="Sweden", cancer_rates="France", personal_medical_history=None | |
| ) | |
| client.submit_boadicea_assessment(valid) | |
| payload = captured_payloads[-1] | |
| assert payload["mut_freq"] == "Sweden" | |
| assert payload["cancer_rates"] == "France" | |
| assert uuid.UUID(payload["user_id"]).version == 4 | |
| assert { | |
| "UK", | |
| "Sweden", | |
| "Estonia", | |
| "France", | |
| "Netherlands", | |
| "Slovenia", | |
| } == ALLOWED_COUNTRIES | |