"""Behaviour of the EPC Prediction corpus anonymiser (ADR-0030): de-identify a cert payload for the committed fixture without disturbing the component data the scorer reads. Pure dict-in / dict-out. """ import json from pathlib import Path from domain.geospatial.coordinates import Coordinates from harness.epc_prediction_corpus import load_coordinates, anonymise_payload def _payload() -> dict[str, object]: return { "address_line_1": "12 Acacia Avenue", "address_line_2": "Hyde Park", "post_town": "LEEDS", "certificate_number": "1234-5678-9012-3456-7890", "postcode": "LS6 1AA", "sap_version": 10.2, "energy_rating_current": 72, "sap_building_parts": [{"wall_construction": 1}], } def test_hashes_identifiers_and_blanks_address_lines() -> None: # Arrange raw = _payload() # Act anon = anonymise_payload(raw) # Assert — the street address + cert number are replaced by opaque tokens, # the secondary address lines blanked. assert anon["address_line_1"] != "12 Acacia Avenue" assert str(anon["address_line_1"]).startswith("addr-") assert str(anon["certificate_number"]).startswith("cert-") assert anon["address_line_2"] == "" assert anon["post_town"] == "" def test_keeps_postcode_and_component_fields() -> None: # Arrange raw = _payload() # Act anon = anonymise_payload(raw) # Assert — postcode (coarse, the cohort key) and all component / lodged data # survive untouched. assert anon["postcode"] == "LS6 1AA" assert anon["sap_version"] == 10.2 assert anon["energy_rating_current"] == 72 assert anon["sap_building_parts"] == [{"wall_construction": 1}] def test_address_hash_is_stable_for_dedup() -> None: # Arrange — the same address re-lodged with trivial whitespace/case noise. a = anonymise_payload({"address_line_1": "Flat 3"}) b = anonymise_payload({"address_line_1": " FLAT 3 "}) # Act / Assert — both normalise to the same token, so the dedup-by-address # leave-one-out still collapses re-lodgements in the fixture. assert a["address_line_1"] == b["address_line_1"] def test_does_not_mutate_the_input() -> None: # Arrange raw = _payload() # Act anonymise_payload(raw) # Assert — the caller's payload is left intact. assert raw["address_line_1"] == "12 Acacia Avenue" def test_loads_the_coordinates_sidecar(tmp_path: Path) -> None: # Arrange — a `_coordinates.json` sidecar mapping UPRN -> [lon, lat]. (tmp_path / "_coordinates.json").write_text( json.dumps({"100024": [-1.5, 53.4]}) ) # Act coordinates = load_coordinates(tmp_path) # Assert — parsed into UPRN-keyed Coordinates. assert coordinates == {100024: Coordinates(longitude=-1.5, latitude=53.4)} def test_coordinates_sidecar_absent_yields_empty(tmp_path: Path) -> None: # Arrange / Act — no sidecar present (a corpus without geo data). coordinates = load_coordinates(tmp_path) # Assert — geo-weighting simply stays off. assert coordinates == {}