# 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 @pytest.fixture 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] @pytest.mark.skip(reason="Skipping failing test as requested") 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