Model/tests/harness/test_epc_prediction_corpus.py
Khalim Conn-Kowlessar 008c1922c4 feat(epc-prediction): anonymised Tier-1 fixture + builder (ADR-0030)
The committed gate needs frozen, reproducible data without dumping real UK
addresses into the repo. Add:
- harness anonymise_payload + stable_hash: hash street address + cert number
  into opaque, dedup-stable tokens; blank secondary address lines + post_town;
  keep postcode + all component/lodged fields (gov data is OGL). Unit-tested.
- scripts/build_epc_prediction_fixture.py: curate qualifying postcodes (>=1
  SAP 10.2 target + >=2 distinct addresses) from the local scratch corpus,
  anonymise, freeze under tests/fixtures/epc_prediction/.
- The frozen fixture: 15 postcodes / 280 certs / 36 SAP-10.2 targets.
  Verified no plaintext address_line_1 and post_town all blank.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:17:27 +00:00

71 lines
2.2 KiB
Python

"""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.
"""
from harness.epc_prediction_corpus import 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"