From a955a09e9cb9f274d0cd16078d3735f381e82324 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Wed, 17 Jun 2026 10:26:11 +0000 Subject: [PATCH 001/340] Pin uprn_10093116330 (full-SAP gas-combi 2-storey semi): engine 82 vs Elmhurst 78 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 7th sibling full-SAP cert; documented full-SAP→RdSAP +4 residual. Build clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../expand-sap-accuracy-corpus/worklist.md | 2 +- .../uprn_10093116330/elmhurst_summary.pdf | Bin 0 -> 63536 bytes .../uprn_10093116330/elmhurst_worksheet.pdf | Bin 0 -> 43031 bytes .../test_real_cert_sap_accuracy.py | 10 ++++++++++ 4 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093116330/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093116330/elmhurst_worksheet.pdf diff --git a/.claude/skills/expand-sap-accuracy-corpus/worklist.md b/.claude/skills/expand-sap-accuracy-corpus/worklist.md index c062be227..2890163c8 100644 --- a/.claude/skills/expand-sap-accuracy-corpus/worklist.md +++ b/.claude/skills/expand-sap-accuracy-corpus/worklist.md @@ -86,7 +86,7 @@ Skip the 🚩 MVHR / 🚩 heat-pump-fuel and ⛔ sparse certs. - [⚠] 10091636116 — SAP-17.0 · eng 80 / lodged 88 · 🚩 MVHR idx 500249 not credited (flagged) - [ ] 10093049853 — SAP-17.0 · eng 82 / lodged 87 - [ ] 10093390790 — SAP-17.1 · eng 79 / lodged 82 -- [ ] 10093116330 — SAP-17.1 · eng 82 / lodged 83 +- [x] 10093116330 — SAP-17.1 (2017 gas-combi 2-storey semi HOUSE, TFA 73) · eng 82 / elm 78 (lodged 83) · PINNED engine 82. +4 = documented full-SAP→RdSAP residual. Build clean. No mapper change. - [ ] 10093116326 — SAP-17.1 · eng 82 / lodged 82 - [ ] 10090317693 — SAP-17.0 · eng 81 / lodged 88 - [ ] 10090034872 — SAP-17.0 · eng 83 / lodged 85 diff --git a/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093116330/elmhurst_summary.pdf b/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093116330/elmhurst_summary.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c830b3f9df45321b08a43c49143548641097f63c GIT binary patch literal 63536 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 literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093116330/elmhurst_worksheet.pdf b/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093116330/elmhurst_worksheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b1bbad345f894318f2d4bba29446a8b5e286ea81 GIT binary patch literal 43031 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 dY8WCS7_M$6E^dGICT Date: Thu, 18 Jun 2026 13:16:44 +0000 Subject: [PATCH 002/340] =?UTF-8?q?fix(heat-transmission):=20bill=20a=20gr?= =?UTF-8?q?ound-floor=20flat's=20ground=20floor=20(RdSAP=2010=20=C2=A73.12?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flat floor-exposure heuristic keys on dwelling_type: a flat defaults to has_exposed_floor=False (assuming a heated dwelling below). The Elmhurst Summary path lodges a ground-floor flat's vertical position as a "Ground floor" floor_type rather than the API floor_heat_loss=1 exposed code, and the mapper can label such a flat "Top-floor flat" — so the cascade dropped the ground floor entirely (a ground floor is in contact with the ground and carries heat loss). Treat a "ground floor" floor_type as a heat-loss floor, overriding the dwelling-level suppression upward — mirroring the existing "another dwelling below" party override downward. Worksheet-validated to 1e-4 on simulated case 45 (a ground-floor flat the mapper labelled "Top-floor flat"): floor (28a) 0 -> 25.38 W/K, fabric (33) 75.63 -> 101.0104, HTC (39) 112.93 -> 145.3579, all matching the P960 exactly; SAP 67.81 -> 62.52. RdSAP-21.0.1 corpus within-0.5 69.5% -> 69.7% (MAE 0.859 -> 0.854). Floors ratcheted. Pinned in test_heat_transmission (ground-floor billed + party-floor suppressed). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../worksheet/heat_transmission.py | 11 ++++ .../worksheet/test_heat_transmission.py | 64 +++++++++++++++++++ .../epc_client/test_sap_accuracy_corpus.py | 10 ++- 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/domain/sap10_calculator/worksheet/heat_transmission.py b/domain/sap10_calculator/worksheet/heat_transmission.py index d12b7da95..8140cec24 100644 --- a/domain/sap10_calculator/worksheet/heat_transmission.py +++ b/domain/sap10_calculator/worksheet/heat_transmission.py @@ -1105,6 +1105,16 @@ def heat_transmission_from_cert( # lodgement is authoritative. Mirrors the roof's "another dwelling # above" override above. Cert 2115-4121-4711-9361-3686. part_floor_is_party = "another dwelling below" in (part.floor_type or "").lower() + # A part whose floor_type is a GROUND floor sits in contact with the + # ground (RdSAP 10 §3.12) and is therefore a heat-loss floor, even when + # the dwelling-level flat heuristic (`_dwelling_exposure`) defaults a + # flat to has_exposed_floor=False. The Elmhurst Summary path lodges a + # ground-floor flat's position as a "Ground floor" floor_type (not the + # API floor_heat_loss=1 exposed code), so without this signal the + # cascade dropped its ground floor entirely — simulated case 45 (a + # ground-floor flat the mapper labelled "Top-floor flat"): worksheet + # (28a) = 47.0 × 0.54 = 25.38 W/K billed as 0, over-rating by +7 SAP. + part_floor_is_ground = "ground floor" in (part.floor_type or "").lower() # A floor lodged as a heat-loss floor — *exposed* (API # floor_heat_loss=1 → `is_exposed_floor`, "an exposed floor if there # is an open space below") or *above a partially heated space* (API @@ -1117,6 +1127,7 @@ def heat_transmission_from_cert( # the "another dwelling below" party signal overrides it downward. part_has_exposed_floor = ( exposure.has_exposed_floor or is_exposed_floor or is_above_partial + or part_floor_is_ground ) and not part_floor_is_party floor_area_total = _round_half_up( geom["ground_floor_area_m2"] if part_has_exposed_floor else 0.0, diff --git a/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py b/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py index d841746ea..0d53867b0 100644 --- a/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py +++ b/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py @@ -157,6 +157,70 @@ def test_mixed_flat_pitched_roof_does_not_contaminate_pitched_u_value() -> None: assert abs(result.roof_w_per_k - 44.6) <= 2.0 +def test_ground_floor_flat_bills_floor_despite_flat_dwelling_type() -> None: + # Arrange — a ground-floor flat whose dwelling_type the mapper labelled + # "Top-floor flat" (so the dwelling-level exposure heuristic + # `_dwelling_exposure` suppresses the floor on the assumption a heated + # dwelling sits below), but whose building part lodges a "Ground floor" + # floor_type. A ground floor is in contact with the ground (RdSAP 10 + # §3.12) -> heat-loss floor. The Elmhurst Summary path lodges this as a + # "Ground floor" floor_type (not the API floor_heat_loss=1 exposed code), + # so without the per-part ground signal the cascade dropped the floor. + # Worksheet-validated by simulated case 45: (28a) = 47.0 × U=0.54 = 25.38 + # W/K, billed as 0 before this fix (+7 SAP). + ground = make_building_part( + identifier=BuildingPartIdentifier.MAIN, + construction_age_band="C", + floor_type="Ground floor", + floor_dimensions=[ + make_floor_dimension( + total_floor_area_m2=47.0, room_height_m=2.4, + heat_loss_perimeter_m=15.8, party_wall_length_m=0.0, floor=0, + ), + ], + ) + epc = make_minimal_sap10_epc( + total_floor_area_m2=47.0, country_code="ENG", + dwelling_type="Top-floor flat", property_type="Flat", + sap_building_parts=[ground], + ) + + # Act + result = heat_transmission_from_cert(epc) + + # Assert — the ground floor carries heat loss (≈ 47 × 0.54), not 0. + assert result.floor_w_per_k > 20.0 + + +def test_top_floor_flat_with_party_floor_stays_suppressed() -> None: + # Arrange — the contrast: a flat lodging "(another dwelling below)" sits + # over a heated dwelling, so its floor is a party floor with no heat loss + # (RdSAP 10 §3). The ground-floor override must NOT fire — proving the + # discriminator is the floor_type, not the flat label. + party = make_building_part( + identifier=BuildingPartIdentifier.MAIN, + construction_age_band="C", + floor_type="To another dwelling below", + floor_dimensions=[ + make_floor_dimension( + total_floor_area_m2=47.0, room_height_m=2.4, + heat_loss_perimeter_m=15.8, party_wall_length_m=0.0, floor=0, + ), + ], + ) + epc = make_minimal_sap10_epc( + total_floor_area_m2=47.0, country_code="ENG", + dwelling_type="Top-floor flat", property_type="Flat", + sap_building_parts=[party], + ) + + # Act + result = heat_transmission_from_cert(epc) + + # Assert — party floor, no heat loss. + assert result.floor_w_per_k == 0.0 + + def test_part_geometry_floorless_part_honours_full_key_contract() -> None: # Arrange — a building part lodged with NO sap_floor_dimensions (e.g. # a party-wall-only or RR-only extension; observed on 5 certs in a diff --git a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py index 9aed0f34b..7b510ab3e 100644 --- a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py +++ b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py @@ -119,7 +119,15 @@ _CORPUS = Path( # 100010129331 (roof 110.5 -> 31.3 W/K, +13.1 -> -0.05 SAP). within-0.5 # 68.8% -> 69.5% (MAE 0.888 -> 0.859; PE 13.9 -> 13.6); 3-part cohort 56% -> # 61%. Pinned in test_heat_transmission (by_kind split + no-contamination). -_MIN_WITHIN_HALF_SAP = 0.69 +# GROUND-FLOOR FLAT FLOOR EXPOSURE (RdSAP 10 §3.12): a ground-floor flat whose +# dwelling_type the mapper labelled "Top-floor flat" had its ground floor (in +# contact with the ground -> heat loss) dropped, because the flat exposure +# heuristic keys on dwelling_type and the Summary path lodges the position as a +# "Ground floor" floor_type (not the API floor_heat_loss=1 code). Treating a +# "ground floor" floor_type as exposed (worksheet-validated to 1e-4 on simulated +# case 45: floor (28a) 0 -> 25.38 W/K, fabric (33) 75.6 -> 101.01) -> 69.5% -> +# 69.7% (MAE 0.859 -> 0.854). Pinned in test_heat_transmission. +_MIN_WITHIN_HALF_SAP = 0.695 _MAX_SAP_MAE = 0.86 _MAX_CO2_MAE_TONNES = 0.30 # t CO2 / yr vs co2_emissions_current _MAX_PE_PER_M2_MAE = 14.0 # kWh / m2 / yr vs energy_consumption_current From 72ef0f0e7b60a9b60edbe5a8a199df359abcd4f3 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 18 Jun 2026 13:44:51 +0000 Subject: [PATCH 003/340] fix(water): don't apply heat-pump water SCOP to a separate immersion (SAP N3.7a) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a heat-pump cert lodges a PCDB Table 362 record, the APM override set BOTH the space efficiency (N3.6) and the water efficiency (N3.7a) from the heat pump unconditionally. But the PCDB η_water applies only when the DHW is heated BY the heat pump (water-heating code "from main": 901/902/914). A separate electric immersion (WHC 903) heats the water at 100% regardless of the space system, so applying the HP's water SCOP (187.5% × 0.6 in-use = 112.5%) under-counted the immersion's hot-water fuel. Gate the η_water override on the DHW-from-main codes; a separate immersion keeps its own 100% efficiency. Space η_space still always uses the APM value (the heat pump is the space main). Worksheet-validated to 1e-4 on simulated case 45 (HP space + WHC-903 immersion): water fuel (62) 1893.57 -> 2130.2639, total cost (255) 619.7433, CO2 692.13 — all matching the P960 exactly; SAP 60.53 -> rounds to the worksheet's 61. RdSAP-21.0.1 corpus unchanged (no HP+WHC903 certs in it). Pinned in test_cert_to_inputs (immersion fuel is main-independent). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../sap10_calculator/rdsap/cert_to_inputs.py | 16 +++++- .../rdsap/test_cert_to_inputs.py | 53 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/domain/sap10_calculator/rdsap/cert_to_inputs.py b/domain/sap10_calculator/rdsap/cert_to_inputs.py index c8a59fcfa..a271f8f5e 100644 --- a/domain/sap10_calculator/rdsap/cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/cert_to_inputs.py @@ -7216,7 +7216,21 @@ def cert_to_inputs( epc=epc, ) if apm_efficiencies is not None: - eff, water_eff = apm_efficiencies + # η_space (N3.6) always replaces the Table 4a default — the heat + # pump is the space main. η_water (N3.7a) applies ONLY when the DHW + # is actually heated by that main (WHC "from main": 901/902/914). A + # separate electric immersion (WHC 903) or other independent DHW + # source keeps its own water efficiency (immersion = 100%), not the + # HP's water SCOP — otherwise a HP-space + immersion-DHW dwelling + # under-counts its hot-water fuel (case 45: water 2130 -> 1894 kWh, + # +1.5 SAP, because 187.5% × 0.6 in-use = 112.5% was applied where + # the worksheet (216) uses 100%). + eff, apm_water_eff = apm_efficiencies + if ( + epc.sap_heating.water_heating_code + in _WATER_INHERIT_FROM_MAIN_CODES + ): + water_eff = apm_water_eff if ( _is_heat_network_main(main) and epc.sap_heating.water_heating_code in _WATER_INHERIT_FROM_MAIN_CODES diff --git a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py index 4531c08d6..e3e99e9ea 100644 --- a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py +++ b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py @@ -38,6 +38,7 @@ from datatypes.epc.domain.epc_property_data import ( from domain.sap10_ml.tests._fixtures import ( make_building_part, make_floor_dimension, + make_main_heating_detail, make_minimal_sap10_epc, make_sap_heating, make_window, @@ -7581,3 +7582,55 @@ def test_index_less_mev_applies_table_4g_note_3_default_data_iuf() -> None: # 2.5x the raw-0.8 value, not the raw default. assert fan_kwh > 0.0 assert abs(fan_kwh - expected) <= 1e-9 + + +def test_heat_pump_water_scop_not_applied_to_separate_immersion_dhw() -> None: + # Arrange — SAP 10.2 Appendix N3.7(a): a heat pump's PCDB water + # efficiency (η_water) applies to the DHW ONLY when the cylinder is + # heated BY the heat pump. A separate electric immersion (WHC 903) heats + # the water at 100% regardless of the space-heating system, so the HP's + # water SCOP must NOT leak onto it. Invariant: a WHC-903 immersion's + # hot-water fuel is INDEPENDENT of the main — a heat-pump main and a gas- + # boiler main yield the SAME immersion fuel (both 100%, no primary loss). + # Before the fix the APM override set η_water = 187.5% × 0.6 in-use = + # 112.5% on the HP cert, under-counting its immersion fuel. Worksheet- + # validated on simulated case 45: water (62) = 2130.26 kWh at η_water=100%, + # not 2130.26 / 1.125 = 1893.57. + hp_main = make_main_heating_detail( + main_fuel_type=29, # electricity + heat_emitter_type=1, + main_heating_category=4, # heat pump + main_heating_index_number=100053, # PCDB Table 362 ASHP (ECODAN 5 kW) + main_heating_data_source=1, + ) + boiler_main = make_main_heating_detail( + main_fuel_type=26, # mains gas + heat_emitter_type=1, + main_heating_category=2, # gas boiler + sap_main_heating_code=102, + ) + + def _immersion_epc(main: MainHeatingDetail) -> EpcPropertyData: + return make_minimal_sap10_epc( + total_floor_area_m2=_TYPICAL_TFA_M2, + habitable_rooms_count=4, + country_code="ENG", + has_hot_water_cylinder=True, + sap_heating=make_sap_heating( + main_heating_details=[main], + water_heating_code=903, # separate electric immersion + water_heating_fuel=30, # standard electricity + cylinder_size=2, + cylinder_insulation_type=1, + cylinder_insulation_thickness_mm=25, + ), + ) + + # Act + hp_fuel = cert_to_inputs(_immersion_epc(hp_main)).hot_water_kwh_per_yr + boiler_fuel = cert_to_inputs(_immersion_epc(boiler_main)).hot_water_kwh_per_yr + + # Assert — the immersion DHW fuel is identical whether the space main is a + # heat pump or a gas boiler (the HP water SCOP does not apply to it). + assert hp_fuel > 0.0 + assert abs(hp_fuel - boiler_fuel) <= 1e-6 From 26106505bee7fa4c3ee22176657f1b1167b26f31 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 18 Jun 2026 13:48:12 +0000 Subject: [PATCH 004/340] chore(scripts): add corpus SAP-accuracy profiler + per-cert dive tools profile_corpus_error.py buckets signed SAP error by raw-API feature and lists worst over/under-raters with the PE/CO2-vs-cost split (COST-side vs DEMAND-side triage). dive_cert.py dumps one cert's lodged-vs-ours SAP/CO2/PE + full intermediate line refs + mapped inputs. Both run on the committed RdSAP-21.0.1 corpus (no /tmp sample needed). Used to find the stone-wall, per-part-roof, ground-floor-flat and HP-water fixes this session. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/dive_cert.py | 103 +++++++++++++++ scripts/profile_corpus_error.py | 217 ++++++++++++++++++++++++++++++++ 2 files changed, 320 insertions(+) create mode 100644 scripts/dive_cert.py create mode 100644 scripts/profile_corpus_error.py diff --git a/scripts/dive_cert.py b/scripts/dive_cert.py new file mode 100644 index 000000000..6fa83efb7 --- /dev/null +++ b/scripts/dive_cert.py @@ -0,0 +1,103 @@ +"""Deep-dive a single corpus cert: lodged vs computed SAP/CO2/PE + the full +intermediate line-ref dump + the mapped fabric/heat-loss inputs, so the +diverging line is visible WITHOUT an Elmhurst worksheet. + +USAGE + PYTHONPATH=/workspaces/model python scripts/dive_cert.py + PYTHONPATH=/workspaces/model python scripts/dive_cert.py --filter wall_insulation_type=3 [--n 8] +""" +from __future__ import annotations + +import json +import sys +from pathlib import Path +from typing import Any + +from datatypes.epc.domain.mapper import EpcPropertyDataMapper +from domain.sap10_calculator.calculator import calculate_sap_from_inputs +from domain.sap10_calculator.rdsap.cert_to_inputs import ( + SAP_10_2_SPEC_PRICES, + cert_to_inputs, +) +from scripts.profile_api_error import features + +_CORPUS = Path("backend/epc_api/json_samples/RdSAP-Schema-21.0.1/corpus.jsonl") + + +def _cert_id(doc: dict[str, Any]) -> str: + return str( + doc.get("certificate_number") + or doc.get("lmk_key") + or doc.get("uprn") + or "?" + ) + + +def _dump(doc: dict[str, Any]) -> None: + cert = _cert_id(doc) + lodged_sap = doc.get("energy_rating_current") + lodged_co2 = doc.get("co2_emissions_current") + lodged_pe = doc.get("energy_consumption_current") + epc = EpcPropertyDataMapper.from_api_response(doc) + r = calculate_sap_from_inputs(cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)) + print("=" * 90) + print(f"CERT {cert}") + print( + f" SAP lodged={lodged_sap} ours={r.sap_score_continuous:.2f} " + f"d={r.sap_score_continuous - (lodged_sap or 0):+.2f}" + ) + if lodged_co2 is not None: + print( + f" CO2 lodged={lodged_co2:.3f} ours={r.co2_kg_per_yr / 1000:.3f} t " + f"d={r.co2_kg_per_yr / 1000 - lodged_co2:+.3f}" + ) + if lodged_pe is not None: + print( + f" PE lodged={lodged_pe:.1f} ours={r.primary_energy_kwh_per_m2:.1f} " + f"d={r.primary_energy_kwh_per_m2 - lodged_pe:+.1f} kWh/m2" + ) + print( + f" energy kWh/yr: spaceheat={r.space_heating_kwh_per_yr:.0f} " + f"main={r.main_heating_fuel_kwh_per_yr:.0f} " + f"sec={r.secondary_heating_fuel_kwh_per_yr:.0f} " + f"hw={r.hot_water_kwh_per_yr:.0f} light={r.lighting_kwh_per_yr:.0f} " + f"pumpfan={r.pumps_fans_kwh_per_yr:.0f}" + ) + d = epc.__dict__ + print(" --- key mapped inputs ---") + f = features(doc) + for k in ( + "property_type", "built_form", "age_band", "main_sap_code", + "main_heat_cat", "main_fuel", "has_pcdb_main", "main_data_source", + "wall_construction", "wall_insulation_type", "roof_codes", + "roof_insulation_thickness", "whc", "water_fuel", "immersion_type", + "has_cylinder", "has_secondary", "has_pv", "mains_gas", "n_building_parts", + ): + print(f" {k:26s}= {f.get(k)}") + print(" --- intermediate line refs ---") + inter = r.intermediate or {} + for k in sorted(inter): + print(f" {k:34s}= {inter[k]:.4f}") + + +def main() -> None: + docs = [json.loads(l) for l in _CORPUS.read_text().splitlines() if l.strip()] + if "--filter" in sys.argv: + spec = sys.argv[sys.argv.index("--filter") + 1] + key, _, val = spec.partition("=") + n = int(sys.argv[sys.argv.index("--n") + 1]) if "--n" in sys.argv else 6 + hits = [d for d in docs if str(features(d).get(key)) == val] + print(f"{len(hits)} certs match {spec}; dumping first {n}") + for d in hits[:n]: + _dump(d) + return + target = sys.argv[1] + for d in docs: + if target in _cert_id(d): + _dump(d) + return + print(f"no cert matching {target}") + + +if __name__ == "__main__": + main() diff --git a/scripts/profile_corpus_error.py b/scripts/profile_corpus_error.py new file mode 100644 index 000000000..851086a92 --- /dev/null +++ b/scripts/profile_corpus_error.py @@ -0,0 +1,217 @@ +"""Profile API-path SAP/CO2/PE error over the COMMITTED corpus (no /tmp cache). + +WHAT THIS IS FOR +---------------- +The accuracy thesis: the gov-API response carries the full SAP input set and our +calculator is deterministic, so EVERY cert should reproduce the lodged +SAP/CO2/PE. Any divergence is an input-handling bug, not irreducible noise. + +This is the per-cert microscope for that loop. It runs the in-repo corpus +(``backend/epc_api/json_samples/RdSAP-Schema-21.0.1/corpus.jsonl``) through the +real ``from_api_response`` -> ``cert_to_inputs`` -> ``calculate_sap_from_inputs`` +path, then: + 1. buckets the signed SAP error by raw-API feature (reusing + ``profile_api_error.features``) ranked by wasted accuracy, so a + dropped/mis-mapped field surfaces as a biased bucket; + 2. for the worst over- and under-raters, prints the PE/CO2-vs-cost split so + each can be triaged WITHOUT a worksheet: + - PE & CO2 both ~match lodged but SAP off -> COST-side bug + (tariff / PV export / standing charge / secondary fuel); + - PE/CO2 also off -> DEMAND-side bug + (fabric / ventilation / gains / heating demand). + +USAGE +----- + PYTHONPATH=/workspaces/model python scripts/profile_corpus_error.py + PYTHONPATH=/workspaces/model python scripts/profile_corpus_error.py --min-n 15 --worst 40 +""" +from __future__ import annotations + +import json +import statistics as stats +import sys +from collections import defaultdict +from pathlib import Path +from typing import Any, Optional + +from datatypes.epc.domain.mapper import EpcPropertyDataMapper +from domain.sap10_calculator.calculator import calculate_sap_from_inputs +from domain.sap10_calculator.rdsap.cert_to_inputs import ( + SAP_10_2_SPEC_PRICES, + cert_to_inputs, +) +from scripts.profile_api_error import features + +_CORPUS = Path("backend/epc_api/json_samples/RdSAP-Schema-21.0.1/corpus.jsonl") + + +class Row: + __slots__ = ( + "cert", "sap_err", "co2_err_t", "pe_err", "lodged_sap", + "our_sap", "lodged_pe", "our_pe", "feats", + ) + + def __init__( + self, + cert: str, + sap_err: float, + co2_err_t: Optional[float], + pe_err: Optional[float], + lodged_sap: float, + our_sap: float, + lodged_pe: Optional[float], + our_pe: float, + feats: dict[str, Any], + ) -> None: + self.cert = cert + self.sap_err = sap_err + self.co2_err_t = co2_err_t + self.pe_err = pe_err + self.lodged_sap = lodged_sap + self.our_sap = our_sap + self.lodged_pe = lodged_pe + self.our_pe = our_pe + self.feats = feats + + +def _load() -> list[dict[str, Any]]: + return [ + json.loads(line) + for line in _CORPUS.read_text().splitlines() + if line.strip() + ] + + +def _compute(corpus: list[dict[str, Any]]) -> tuple[list[Row], int, int]: + rows: list[Row] = [] + skipped = 0 + raised = 0 + for doc in corpus: + lodged_sap = doc.get("energy_rating_current") + if lodged_sap is None: + skipped += 1 + continue + try: + epc = EpcPropertyDataMapper.from_api_response(doc) + result = calculate_sap_from_inputs( + cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES) + ) + except Exception: + raised += 1 + continue + cert = str( + doc.get("certificate_number") + or doc.get("lmk_key") + or doc.get("uprn") + or len(rows) + ) + lodged_co2_t = doc.get("co2_emissions_current") + lodged_pe = doc.get("energy_consumption_current") + rows.append(Row( + cert=cert, + sap_err=result.sap_score_continuous - lodged_sap, + co2_err_t=(result.co2_kg_per_yr / 1000.0 - lodged_co2_t) + if lodged_co2_t is not None else None, + pe_err=(result.primary_energy_kwh_per_m2 - lodged_pe) + if lodged_pe is not None else None, + lodged_sap=lodged_sap, + our_sap=result.sap_score_continuous, + lodged_pe=lodged_pe, + our_pe=result.primary_energy_kwh_per_m2, + feats=features(doc), + )) + return rows, skipped, raised + + +def _triage(r: Row) -> str: + """Cost vs demand label from the PE/CO2 split (~tolerant).""" + if r.pe_err is None or r.co2_err_t is None: + return "?" + pe_ok = abs(r.pe_err) < 5.0 # kWh/m2/yr + co2_ok = abs(r.co2_err_t) < 0.10 # t/yr + if pe_ok and co2_ok: + return "COST" # demand reproduces, cost-side off + return "DEMAND" + + +def main() -> None: + min_n = 12 + n_worst = 30 + if "--min-n" in sys.argv: + min_n = int(sys.argv[sys.argv.index("--min-n") + 1]) + if "--worst" in sys.argv: + n_worst = int(sys.argv[sys.argv.index("--worst") + 1]) + + rows, skipped, raised = _compute(_load()) + n = len(rows) + within = sum(1 for r in rows if abs(r.sap_err) < 0.5) / n * 100 + print( + f"profiled {n} certs ({skipped} no-lodged-SAP, {raised} raised) | " + f"within-0.5 = {within:.1f}% | " + f"signed {stats.mean(r.sap_err for r in rows):+.3f} | " + f"MAE {stats.mean(abs(r.sap_err) for r in rows):.3f}" + ) + out = [r for r in rows if abs(r.sap_err) >= 0.5] + cost_n = sum(1 for r in out if _triage(r) == "COST") + dem_n = sum(1 for r in out if _triage(r) == "DEMAND") + print( + f"of {len(out)} outside-0.5: {dem_n} DEMAND-side (PE/CO2 also off), " + f"{cost_n} COST-side (PE/CO2 match), {len(out) - cost_n - dem_n} unknown" + ) + print("=" * 104) + + feat_names = list(rows[0].feats.keys()) + bucket_lines: list[tuple[float, str]] = [] + for fn in feat_names: + groups: dict[str, list[float]] = defaultdict(list) + for r in rows: + groups[str(r.feats.get(fn))].append(r.sap_err) + for val, es in groups.items(): + cnt = len(es) + if cnt < min_n: + continue + w05 = sum(1 for e in es if abs(e) < 0.5) + mabs = stats.mean(abs(e) for e in es) + waste = (cnt - w05) * mabs + bucket_lines.append((waste, ( + f" {fn:22s}={val:<20.20s} n={cnt:4d} " + f"within0.5={w05 / cnt * 100:4.0f}% " + f"signed={stats.mean(es):+6.2f} mean|err|={mabs:5.2f} " + f"[waste={waste:6.0f}]" + ))) + print(f"TOP ERROR-CARRYING BUCKETS (n_out x mean|err|; min-n={min_n}):") + for _, line in sorted(bucket_lines, key=lambda x: -x[0])[:40]: + print(line) + + print("=" * 104) + print(f"WORST {n_worst} OVER-RATERS (our SAP too high -> we under-count loss/cost):") + _dump_worst(sorted(rows, key=lambda r: -r.sap_err)[:n_worst]) + print("-" * 104) + print(f"WORST {n_worst} UNDER-RATERS (our SAP too low -> we over-count loss/cost):") + _dump_worst(sorted(rows, key=lambda r: r.sap_err)[:n_worst]) + + +def _dump_worst(rows: list[Row]) -> None: + print( + f" {'cert':>16s} {'lodgSAP':>7s} {'ourSAP':>7s} {'dSAP':>6s} " + f"{'dPE':>6s} {'dCO2t':>6s} {'split':>6s} " + f"heat/prop/wall/roof/fuel" + ) + for r in rows: + f = r.feats + sig = ( + f"{f.get('main_sap_code')}/{f.get('property_type')}/" + f"{f.get('wall_construction')}/{f.get('roof_codes')}/" + f"{f.get('main_fuel')} pcdb={f.get('has_pcdb_main')} " + f"2nd={f.get('has_secondary')} pv={f.get('has_pv')}" + ) + pe = f"{r.pe_err:+6.1f}" if r.pe_err is not None else " ?" + co2 = f"{r.co2_err_t:+6.2f}" if r.co2_err_t is not None else " ?" + print( + f" {r.cert:>16.16s} {r.lodged_sap:7.1f} {r.our_sap:7.2f} " + f"{r.sap_err:+6.2f} {pe} {co2} {_triage(r):>6s} {sig}" + ) + + +if __name__ == "__main__": + main() From fc7c4d2d3b79e37cdfa459208d68564d812d016f Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 18 Jun 2026 14:15:34 +0000 Subject: [PATCH 005/340] fix(climate): compute EPC CO2/PE on the postcode demand cascade (SAP 10.2 Appendix U p.124) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SAP/EI rating is computed on UK-average weather (Appendix U Tables U1-U3 region 0) so ratings are nationally comparable, but Appendix U paragraph 1 (PDF p.124) requires that "other calculations (such as for energy use and costs on EPCs) are done using local weather. Weather data for each postcode district are taken from the PCDB". `Sap10Calculator. calculate` ran ONE cascade (UK-average) and fed it to SAP, CO2 AND primary energy, so every cert's EPC-displayed CO2/PE were computed on the wrong climate. Because most of England is warmer than the UK-average, this systematically OVER-counted heating demand on the emissions/PE outputs. The two cascades (`cert_to_inputs` rating, `cert_to_demand_inputs` postcode) already existed; this wires the demand cascade into the production entry point and grafts its CO2/PE onto the rating result (SAP unchanged). The corpus gauge's longstanding +5% CO2/PE over-estimate was mostly this climate bug, NOT (as previously diagnosed) per-cert mapper fidelity: CO2 MAE 0.26 -> 0.12 t/yr (bias +0.18 -> +0.04) PE MAE 13.6 -> 3.8 kWh/m2 (bias +9.0 -> +0.24) SAP within-0.5 = 69.7% (rating cascade, unchanged) Worksheet-validated to 1e-4 on simulated case 45 (heat-pump ground-floor flat, postcode W6): the P960 prints the current dwelling twice — Block 1 on UK-average weather (SAP 60.5318, CO2 692.13) and Block 2 on postcode weather (CO2 626.78, PE 6581.59). Both reproduce exactly. Added a tracked case-45 Summary fixture + two-cascade cascade pin as a permanent guard, and ratcheted the corpus CO2/PE ceilings to 0.13 / 4.2. The e2e Elmhurst suite (Block-1 line refs) now pins the rating cascade directly; the two Vaillant overlay snapshots refreshed to demand-cascade CO2/PE. pyright not installed in this codespace (strict gate not run locally); change is type-trivial (dataclasses.replace over SapResult). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../tests/fixtures/Summary_001431_case45.pdf | Bin 0 -> 80737 bytes domain/sap10_calculator/calculator.py | 25 +++- .../modelling/test_elmhurst_cascade_pins.py | 13 ++- .../_elmhurst_worksheet_001431_case45.py | 107 ++++++++++++++++++ .../worksheet/test_e2e_elmhurst_sap_score.py | 14 ++- .../worksheet/test_section_cascade_pins.py | 62 ++++++++++ .../epc_client/test_sap_accuracy_corpus.py | 33 ++++-- 7 files changed, 234 insertions(+), 20 deletions(-) create mode 100644 backend/documents_parser/tests/fixtures/Summary_001431_case45.pdf create mode 100644 tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case45.py diff --git a/backend/documents_parser/tests/fixtures/Summary_001431_case45.pdf b/backend/documents_parser/tests/fixtures/Summary_001431_case45.pdf new file mode 100644 index 0000000000000000000000000000000000000000..48a5273e27cf1e15948bb78faf95e9efa4463d31 GIT binary patch literal 80737 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/*P!T$Yhw9I*3Xp^JK@{l5Hq$ucBv8wCJy*NBVwjEn7S)uehLb zdgNlnw-u6A8mRhgd52e7sZ_~n(T}1>f%IT_xuhg5@QmCI`Y{e6ESp@6i z4d2vtC*t@ip@>?8>gGt(hiLZ(*~JG7xo+Y3ScmSZD`6dce0|27iuyWI7#&@xkr;If z5t)IdN{KUP_*5EvDNPsgS81(81~z`=)IF-c_A%zDyP`aBHwTs$S2v;0-OT=joZVx4 z|3=RKXfOYh-ZPZq4`1+f9p1-&zZlP++Yq*hP z%bT?2-JLtdlE9%n-C))G99=RgZJWcQ7hF$Prto}sX|~m?GO65t@h1EW;1yvmj|h-^ z^&L8`J7EZp`s=Tq#pDLOAFsuljzq$9L0s$9K6VeZ40~0FyJSHgv(f{|pyI+S^zog5 z3bKlhq^Z@y1>#7KId5x?QPV17DVS6pe*C4;DGV}bEPKE)Sp(?A7Iv0Sx8-m{AO!g( zC(aW07l@@KBW3*p!~6}LO?Gq8?+Xun_c{MT6n?_pf3fhuKWWB4f*u?cKmYvU1(P@@ z1A5g)I#=C%Il>KqC<#;u_uha+6EgGju#gEPC{g!)-*UM}-+i(4S*Nr`xq z#CWyZS*dD#jK@X5j;-=0L+nQ-CucF4R^`3iaxJo(!wHMUo|E^7o_>weY82pqQtW}P zw<5KM!m5(eD4V9WC-9buly6U@ft;$IgAB1Df}OCjuZJhs98lT*BJUj$V?@N6iwPIs z&J}|3+d_(c4}ERs@Hr070mkB0xGenhadE?=rWc9p1*(dHjU!VN=1xSQY!(()*6?c^XL8Wl2cr)QH z+%q`^fetU}`bvwL85N`G7vE|p@UyeM_FV041bf~*EHjdz@!`st*sM1#T(5BKBRlT{&s29S3VqpX2V z!aFv#YrG|@6;Ym%9QjdGf4qP1`hcr{fmz*7(nv23kI2@vIT$DgTu6s2>EO31*E>r* zv*UJ?t?K(Yu}59ql#YbTOytti&|_MxPpU>`#Sc(6^(fDDKPhpjW=MpoU#9KP6Co3z z+;`88E3h{bU$T_>3Q(wEoj5ru5zDt0wgKWt!lNTkvlJA3mPZ{nkBDhz< zpmCAobk31{UGy#Xki1L8#J)Ao+k@Xxu_Xl+CzYROhqkSpfkj3Y^&G3!EKDFK*&tOX z2)(A;Rzvn-pvF@A=sKJCYV4JNA=qp+0x$RgZ=y_B0Lv2bb29cKvn6x3`wAj4%9^vo z@->sHEkglt=KV6qRI__##}p$FDc+Imrebnqe#jlUV7B!&=J5*(OTTm`bW9tG&j%4T zl+0K1uwiKnQ}N$N-K9BP_E7{+HXdesQ45-=Z@4_TNy(LH8SlT^?1E328)`@7!>otZ zu-@o68aD4QWb=^4r#(;FRut2Ea;!YnMIqKf+D8~>ZIM+j%*9mUn|_Xpiqu9Bo%PH- z*|E4t%S{4=V~$0t7~ZXVgfv2u5VAG@mW5v!N-Q`Z{O$1gQ~9j?)AnWoJr`<4jh0UY zv*_QhXG^3syHY|SdSBvgjgdd%O3nkAz2vVZ0UvJw3Na?xBS<+%PuTLcS&6>84p`Zd zO@)6U4dQELOyDuvGik_PYe}sK}GpWyDr60l$eR zOI5NvOCWnJ*V`MIg*e38-_xe6y^%56EvjWjWB#9m0?Nh)W}`5s_{(;yn0E(-^QuTW_PB80jHvHu}RwyU?~~eeWC0ecl7@kUnmFkE`$GSo7-(Dkl)5*f&MmMc2+R+{kgHTLT>jE+FcW-tpH4UMp@$bSJUT0njP literal 0 HcmV?d00001 diff --git a/domain/sap10_calculator/calculator.py b/domain/sap10_calculator/calculator.py index f7099f182..11cecf731 100644 --- a/domain/sap10_calculator/calculator.py +++ b/domain/sap10_calculator/calculator.py @@ -42,7 +42,7 @@ Appendix L + U. RdSAP10 Table 32 (p.95) for fuel prices/CO2/PE factors. from __future__ import annotations from abc import ABC, abstractmethod -from dataclasses import dataclass, field +from dataclasses import dataclass, field, replace from typing import Final, Optional, TYPE_CHECKING from domain.sap10_calculator.climate.appendix_u import external_temperature_c @@ -863,6 +863,25 @@ class Sap10Calculator(SapCalculator): """ def calculate(self, epc: "EpcPropertyData") -> SapResult: - from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_inputs + # SAP 10.2 Appendix U paragraph 1 (p.124): the SAP and EI ratings are + # computed on UK-average climate (so ratings are nationally + # comparable), but "other calculations (such as for energy use and + # costs on EPCs) are done using local weather" — the EPC-displayed + # CO2 emissions and primary energy use postcode-district weather from + # the PCDB. So we run two climate cascades and graft the demand + # cascade's CO2/PE onto the rating cascade's SAP result. (Worked + # example: simulated case 45 — rating SAP 60.53/CO2 692.13 on + # UK-average; demand CO2 626.78/PE 6581.59 on the W6 postcode.) + from domain.sap10_calculator.rdsap.cert_to_inputs import ( + cert_to_demand_inputs, + cert_to_inputs, + ) - return calculate_sap_from_inputs(cert_to_inputs(epc)) + rating = calculate_sap_from_inputs(cert_to_inputs(epc)) + demand = calculate_sap_from_inputs(cert_to_demand_inputs(epc)) + return replace( + rating, + co2_kg_per_yr=demand.co2_kg_per_yr, + primary_energy_kwh_per_yr=demand.primary_energy_kwh_per_yr, + primary_energy_kwh_per_m2=demand.primary_energy_kwh_per_m2, + ) diff --git a/tests/domain/modelling/test_elmhurst_cascade_pins.py b/tests/domain/modelling/test_elmhurst_cascade_pins.py index 0e6832b74..19186eaee 100644 --- a/tests/domain/modelling/test_elmhurst_cascade_pins.py +++ b/tests/domain/modelling/test_elmhurst_cascade_pins.py @@ -686,12 +686,15 @@ def test_ashp_overlay_scores_the_vaillant_end_state_from_a_gas_boiler() -> None: # dwelling's baseline fabric and so the ASHP end-state SAP. Still a snapshot # of the Vaillant overlay's own output, validated transitively by the # system-boiler pin below (which reproduces a real Vaillant cert at delta 0). + # CO2/PE are the postcode DEMAND cascade now that `Sap10Calculator. + # calculate` computes EPC emissions/PE on local weather (SAP 10.2 + # Appendix U p.124); SAP is unchanged (UK-average rating cascade). _assert_overlay_scores( before, option.overlay, sap=51.99820176096402, - co2=1268.4645083243888, - pe=13080.20756425629, + co2=1065.7593506066496, + pe=10995.781557709413, ) @@ -715,12 +718,14 @@ def test_ashp_overlay_scores_the_vaillant_end_state_from_a_gas_boiler_instant_hw # boiler-1 pin above); the same merge also resolved this cert's main-fuel # mapper gap (§14.2 mains-gas derivation), so its raw before now baselines — # see `test_gas_boiler_instant_hw_before_baselines`. + # CO2/PE are the postcode DEMAND cascade now (see the boiler-1 pin above); + # SAP is unchanged (UK-average rating cascade). _assert_overlay_scores( before, option.overlay, sap=39.00740809309464, - co2=2248.6089062232704, - pe=23094.10189037302, + co2=1845.8588018295509, + pe=18944.42568846759, ) diff --git a/tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case45.py b/tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case45.py new file mode 100644 index 000000000..7d9ab95f2 --- /dev/null +++ b/tests/domain/sap10_calculator/worksheet/_elmhurst_worksheet_001431_case45.py @@ -0,0 +1,107 @@ +"""Mapper-driven cascade pin against the Elmhurst P960-0001-001431 +"simulated case 45" worksheet — a ~47 m² GROUND-FLOOR FLAT heated by an +air-source HEAT PUMP (PCDB 100053 ECODAN, radiators, MCS=No) with a +WHC-903 electric-immersion DHW and a 110 L cylinder, postcode W6 9BF +(SAP Region "Thames Valley"). + +Case 45 is the 1e-4 oracle for the SAP 10.2 Appendix U (PDF p.124) TWO- +CLIMATE-CASCADE split. The P960 prints the current dwelling TWICE: + + * Block 1 — "11a. SAP rating / 12a. CO2" — computed on UK-AVERAGE + weather (Appendix U Tables U1-U3 region 0). Drives the SAP/EI rating. + Space-heat demand (98c) = 7333.79; SAP value (258) = 60.5318 (-> 61); + total CO2 (272) = 692.13. + * Block 2 — "CALCULATION OF EPC COSTS, EMISSIONS AND PRIMARY ENERGY" — + computed on POSTCODE-DISTRICT weather (PCDB Table 172, W6). Drives the + EPC-displayed figures. Space-heat demand (98c) = 5921.05; total CO2 + (272) = 626.78; total primary energy (286) = 6581.59. + +Per Appendix U paragraph 1: "Other calculations (such as for energy use +and costs on EPCs) are done using local weather." `Sap10Calculator. +calculate` therefore runs both cascades and grafts the demand cascade's +CO2/PE onto the rating cascade's SAP — this fixture pins BOTH. + +Like the other `_elmhurst_worksheet_001431_case*` fixtures it does NOT +hand-build the EpcPropertyData: it routes the Summary PDF through +ElmhurstSiteNotesExtractor + from_elmhurst_site_notes so the pin exercises +the WHOLE extractor + mapper + calculator pipeline. + +Source: user-simulated PDFs at `sap worksheets/golden fixture debugging/ +simulated case 45/`. The Summary is mirrored into the tracked +`backend/documents_parser/tests/fixtures/Summary_001431_case45.pdf` so the +test runs without depending on the unstaged workspace. + +Per [[feedback-zero-error-strict]]: pins are abs <= 1e-4 against the PDF. +""" + +from __future__ import annotations + +import re +import subprocess +from pathlib import Path +from typing import Final + +from backend.documents_parser.elmhurst_extractor import ElmhurstSiteNotesExtractor +from datatypes.epc.domain.epc_property_data import EpcPropertyData +from datatypes.epc.domain.mapper import EpcPropertyDataMapper + +# parents[0]=worksheet/, [1]=sap10_calculator/, [2]=domain/, [3]=tests/, +# [4]=repo root. +_SUMMARY_PDF: Final[Path] = ( + Path(__file__).resolve().parents[4] + / "backend" / "documents_parser" / "tests" / "fixtures" + / "Summary_001431_case45.pdf" +) + +# Block 1 — UK-average RATING cascade (`cert_to_inputs`). +RATING_SPACE_HEATING_KWH: Final[float] = 7333.7892 # (98c) +RATING_SAP_CONTINUOUS: Final[float] = 60.5318 # (258) un-rounded +RATING_SAP_INTEGER: Final[int] = 61 # (258) +RATING_CO2_KG_PER_YR: Final[float] = 692.1287 # (272) + +# Block 2 — POSTCODE-district DEMAND cascade (`cert_to_demand_inputs`). +DEMAND_SPACE_HEATING_KWH: Final[float] = 5921.0486 # (98c) +DEMAND_CO2_KG_PER_YR: Final[float] = 626.7797 # (272) +DEMAND_PRIMARY_ENERGY_KWH: Final[float] = 6581.5936 # (286) + + +def _summary_pdf_to_textract_style_pages(pdf_path: Path) -> list[str]: + """Convert a Summary PDF into the per-page text format the + ElmhurstSiteNotesExtractor expects (label/value token sequences). + Mirror of the helper in the other `_elmhurst_worksheet_*` fixtures. + """ + info = subprocess.run( + ["pdfinfo", str(pdf_path)], capture_output=True, text=True, check=True, + ).stdout + m = re.search(r"Pages:\s+(\d+)", info) + if m is None: + raise RuntimeError(f"Could not parse page count from {pdf_path}") + page_count = int(m.group(1)) + pages: list[str] = [] + for i in range(1, page_count + 1): + layout = subprocess.run( + [ + "pdftotext", "-layout", "-f", str(i), "-l", str(i), + str(pdf_path), "-", + ], + capture_output=True, text=True, check=True, + ).stdout + tokens: list[str] = [] + for line in layout.splitlines(): + if not line.strip(): + tokens.append("") + continue + parts = [p for p in re.split(r"\s{2,}", line.strip()) if p] + tokens.extend(parts) + pages.append("\n".join(tokens)) + return pages + + +def build_epc() -> EpcPropertyData: + """Route the simulated case-45 Summary through extractor + mapper. + No hand-built EpcPropertyData — the extractor and mapper are part of + the test target. This module is a pin PROVIDER (build_epc + constants); + the collected assertions live in `test_section_cascade_pins`.""" + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_PDF) + site_notes = ElmhurstSiteNotesExtractor(pages).extract() + return EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes) diff --git a/tests/domain/sap10_calculator/worksheet/test_e2e_elmhurst_sap_score.py b/tests/domain/sap10_calculator/worksheet/test_e2e_elmhurst_sap_score.py index 1637f281d..1625bcbcf 100644 --- a/tests/domain/sap10_calculator/worksheet/test_e2e_elmhurst_sap_score.py +++ b/tests/domain/sap10_calculator/worksheet/test_e2e_elmhurst_sap_score.py @@ -24,7 +24,10 @@ from typing import Final import pytest -from domain.sap10_calculator.calculator import Sap10Calculator +from domain.sap10_calculator.calculator import ( + Sap10Calculator, + calculate_sap_from_inputs, +) from domain.sap10_calculator.rdsap.cert_to_inputs import ( cert_to_inputs, water_heating_section_from_cert, @@ -338,8 +341,13 @@ def test_sap_result_pin(fixture_name: str, field_name: str) -> None: epc = _FIXTURE_MODULES[fixture_name].build_epc() expected = getattr(pin, field_name) - # Act - result = Sap10Calculator().calculate(epc) + # Act — these pins are the worksheet's Block-1 (energy-rating) line refs, + # i.e. the UK-average RATING cascade. `Sap10Calculator.calculate` now + # grafts the postcode DEMAND cascade's CO2/PE onto the result (SAP 10.2 + # Appendix U p.124), so the rating-cascade fields are pinned via + # `cert_to_inputs` directly; the demand cascade is pinned separately + # (corpus gauge + simulated case 45 Block-2 pins). + result = calculate_sap_from_inputs(cert_to_inputs(epc)) actual = getattr(result, field_name) # Assert diff --git a/tests/domain/sap10_calculator/worksheet/test_section_cascade_pins.py b/tests/domain/sap10_calculator/worksheet/test_section_cascade_pins.py index 6547a585e..ce93e21c2 100644 --- a/tests/domain/sap10_calculator/worksheet/test_section_cascade_pins.py +++ b/tests/domain/sap10_calculator/worksheet/test_section_cascade_pins.py @@ -46,6 +46,7 @@ from tests.domain.sap10_calculator.worksheet import ( _elmhurst_worksheet_001431_case21 as _w001431_case21, _elmhurst_worksheet_001431_case43 as _w001431_case43, _elmhurst_worksheet_001431_case44 as _w001431_case44, + _elmhurst_worksheet_001431_case45 as _w001431_case45, ) @@ -491,6 +492,67 @@ def test_case44_blower_door_pressure_test_matches_pdf() -> None: _pin(vent.effective_monthly_ach[0], 0.5812, "§2 (25) Jan case44") +def test_case45_heat_pump_two_climate_cascade_matches_pdf() -> None: + """Simulated case 45 (heat-pump ground-floor flat, postcode W6) is the + 1e-4 oracle for the SAP 10.2 Appendix U (p.124) two-climate-cascade + split. The P960 prints the current dwelling twice: + + * Block 1 ("11a SAP rating / 12a CO2") on UK-AVERAGE weather (region + 0): space heat (98c) 7333.79, SAP (258) 60.5318, CO2 (272) 692.13. + * Block 2 ("EPC COSTS, EMISSIONS AND PRIMARY ENERGY") on POSTCODE + weather (PCDB Table 172, W6): space heat (98c) 5921.05, CO2 (272) + 626.78, primary energy (286) 6581.59. + + The SAP/EI rating reads the rating cascade; the EPC-displayed CO2/PE + read the demand cascade. Pins both ends at abs=1e-4.""" + # Arrange + from domain.sap10_calculator.calculator import calculate_sap_from_inputs + from domain.sap10_calculator.rdsap.cert_to_inputs import cert_to_demand_inputs + + epc = _w001431_case45.build_epc() + # The split only exists because the postcode resolves to local weather. + assert local_climate_for_cert(epc) is not None + + # Act — both climate cascades from the one cert. + rating = calculate_sap_from_inputs(cert_to_inputs(epc)) + demand = calculate_sap_from_inputs(cert_to_demand_inputs(epc)) + + # Assert — Block 1 (UK-average rating cascade). + _pin( + rating.space_heating_kwh_per_yr, + _w001431_case45.RATING_SPACE_HEATING_KWH, + "(98c) rating case45", + ) + _pin( + rating.sap_score_continuous, + _w001431_case45.RATING_SAP_CONTINUOUS, + "(258) rating case45", + ) + assert rating.sap_score == _w001431_case45.RATING_SAP_INTEGER + _pin( + rating.co2_kg_per_yr, + _w001431_case45.RATING_CO2_KG_PER_YR, + "(272) rating case45", + ) + + # Assert — Block 2 (postcode demand cascade). + _pin( + demand.space_heating_kwh_per_yr, + _w001431_case45.DEMAND_SPACE_HEATING_KWH, + "(98c) demand case45", + ) + _pin( + demand.co2_kg_per_yr, + _w001431_case45.DEMAND_CO2_KG_PER_YR, + "(272) demand case45", + ) + _pin( + demand.primary_energy_kwh_per_yr, + _w001431_case45.DEMAND_PRIMARY_ENERGY_KWH, + "(286) demand case45", + ) + + def test_case6_main_2_emitter_and_control_extracted() -> None: """Simulated case 6's §14.1 Main Heating2 lodges its OWN emitter ("Underfloor Heating") and control ("SAP code 2110, ...") — the two diff --git a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py index 7b510ab3e..ca4681e44 100644 --- a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py +++ b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py @@ -30,11 +30,7 @@ from typing import Any import pytest from datatypes.epc.domain.mapper import EpcPropertyDataMapper -from domain.sap10_calculator.calculator import calculate_sap_from_inputs -from domain.sap10_calculator.rdsap.cert_to_inputs import ( - SAP_10_2_SPEC_PRICES, - cert_to_inputs, -) +from domain.sap10_calculator.calculator import Sap10Calculator _CORPUS = Path( "backend/epc_api/json_samples/RdSAP-Schema-21.0.1/corpus.jsonl" @@ -127,10 +123,25 @@ _CORPUS = Path( # "ground floor" floor_type as exposed (worksheet-validated to 1e-4 on simulated # case 45: floor (28a) 0 -> 25.38 W/K, fabric (33) 75.6 -> 101.01) -> 69.5% -> # 69.7% (MAE 0.859 -> 0.854). Pinned in test_heat_transmission. +# POSTCODE DEMAND CASCADE (SAP 10.2 Appendix U paragraph 1, p.124): the +# CO2/PE over-estimate diagnosed above as "per-cert mapper/demand fidelity" +# was largely a CLIMATE-cascade bug. The SAP/EI rating is computed on +# UK-average weather (Tables U1-U3 region 0), but EPC-displayed energy use, +# CO2 emissions and primary energy use POSTCODE-DISTRICT weather from PCDB +# Table 172 — "other calculations (such as for energy use and costs on EPCs) +# are done using local weather". We were feeding the UK-average demand to all +# three outputs, so warm-region certs (most of England, warmer than the +# UK-average) over-counted heating demand → CO2/PE high. `Sap10Calculator. +# calculate` now grafts the demand cascade's CO2/PE onto the rating cascade's +# SAP. Across the corpus this moved CO2 MAE 0.26 -> 0.12 t/yr (bias +0.18 -> +# +0.04) and PE MAE 13.6 -> 3.8 kWh/m2/yr (bias +9.0 -> +0.24); SAP unchanged +# (rating cascade). Worksheet-validated to 1e-4 on simulated case 45 (rating +# CO2 692.13; demand CO2 626.78, PE 6581.59). The residual PE/CO2 spread is +# now the genuine per-cert mapper-fidelity tail. _MIN_WITHIN_HALF_SAP = 0.695 _MAX_SAP_MAE = 0.86 -_MAX_CO2_MAE_TONNES = 0.30 # t CO2 / yr vs co2_emissions_current -_MAX_PE_PER_M2_MAE = 14.0 # kWh / m2 / yr vs energy_consumption_current +_MAX_CO2_MAE_TONNES = 0.13 # t CO2 / yr vs co2_emissions_current +_MAX_PE_PER_M2_MAE = 4.2 # kWh / m2 / yr vs energy_consumption_current def _load_corpus() -> list[dict[str, Any]]: @@ -155,8 +166,12 @@ def test_api_path_sap_accuracy_on_rdsap_21_0_1_corpus( co2_signed_errs_t: list[float] = [] # our − lodged, tonnes/yr pe_signed_errs: list[float] = [] # our − lodged, kWh/m²/yr skipped = 0 + _calculator = Sap10Calculator() # Act — run the API → EpcPropertyData → calculator pipeline per cert. + # `Sap10Calculator.calculate` runs both climate cascades (SAP 10.2 + # Appendix U p.124): the SAP rating on UK-average weather, CO2/PE on + # postcode-district weather — exactly the two figures the EPC lodges. for doc in corpus: lodged_sap = doc.get("energy_rating_current") if lodged_sap is None: @@ -164,9 +179,7 @@ def test_api_path_sap_accuracy_on_rdsap_21_0_1_corpus( continue try: epc = EpcPropertyDataMapper.from_api_response(doc) - result = calculate_sap_from_inputs( - cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES) - ) + result = _calculator.calculate(epc) except Exception: # A mapper / calculator raise is a coverage gap tracked elsewhere # (eval_api_sap_accuracy.py); here we gauge the certs that compute. From 6950deae06c80c738c38a5502943ef7ab393c853 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 18 Jun 2026 14:21:00 +0000 Subject: [PATCH 006/340] chore(scripts): triage PE/CO2 on the demand cascade in the corpus profilers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `profile_corpus_error.py` and `dive_cert.py` compared our PE/CO2 against the lodged EPC figures using the UK-average RATING cascade, but the EPC lodges CO2/PE on the postcode DEMAND cascade (SAP 10.2 Appendix U p.124, now wired into Sap10Calculator.calculate in fc7c4d2d). That confounded the DEMAND-vs-COST triage: a cert whose demand actually reproduced on local weather looked "PE off" purely from the climate difference and was mislabelled DEMAND-side. Switching the PE/CO2 lens to `cert_to_demand_ inputs` (SAP still from the rating cascade) re-classifies the corpus outside-0.5 set 261/42 -> 211/92 DEMAND/COST — ~50 certs are genuinely cost-side (e.g. 10091578598: SAP +7.81 but PE +1.6 / CO2 -0.04). Sharpens the hunt for the subtle widespread SAP term. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/dive_cert.py | 13 +++++++++---- scripts/profile_corpus_error.py | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/scripts/dive_cert.py b/scripts/dive_cert.py index 6fa83efb7..8d6da1b46 100644 --- a/scripts/dive_cert.py +++ b/scripts/dive_cert.py @@ -17,6 +17,7 @@ from datatypes.epc.domain.mapper import EpcPropertyDataMapper from domain.sap10_calculator.calculator import calculate_sap_from_inputs from domain.sap10_calculator.rdsap.cert_to_inputs import ( SAP_10_2_SPEC_PRICES, + cert_to_demand_inputs, cert_to_inputs, ) from scripts.profile_api_error import features @@ -40,6 +41,10 @@ def _dump(doc: dict[str, Any]) -> None: lodged_pe = doc.get("energy_consumption_current") epc = EpcPropertyDataMapper.from_api_response(doc) r = calculate_sap_from_inputs(cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)) + # SAP/EI rating is the UK-average rating cascade (`r`); EPC CO2/PE use the + # postcode demand cascade (SAP 10.2 Appendix U p.124). Display CO2/PE from + # the demand cascade so they compare like-for-like with the lodged EPC. + d = calculate_sap_from_inputs(cert_to_demand_inputs(epc, prices=SAP_10_2_SPEC_PRICES)) print("=" * 90) print(f"CERT {cert}") print( @@ -48,13 +53,13 @@ def _dump(doc: dict[str, Any]) -> None: ) if lodged_co2 is not None: print( - f" CO2 lodged={lodged_co2:.3f} ours={r.co2_kg_per_yr / 1000:.3f} t " - f"d={r.co2_kg_per_yr / 1000 - lodged_co2:+.3f}" + f" CO2 lodged={lodged_co2:.3f} ours={d.co2_kg_per_yr / 1000:.3f} t " + f"d={d.co2_kg_per_yr / 1000 - lodged_co2:+.3f} (demand cascade)" ) if lodged_pe is not None: print( - f" PE lodged={lodged_pe:.1f} ours={r.primary_energy_kwh_per_m2:.1f} " - f"d={r.primary_energy_kwh_per_m2 - lodged_pe:+.1f} kWh/m2" + f" PE lodged={lodged_pe:.1f} ours={d.primary_energy_kwh_per_m2:.1f} " + f"d={d.primary_energy_kwh_per_m2 - lodged_pe:+.1f} kWh/m2 (demand cascade)" ) print( f" energy kWh/yr: spaceheat={r.space_heating_kwh_per_yr:.0f} " diff --git a/scripts/profile_corpus_error.py b/scripts/profile_corpus_error.py index 851086a92..6ef2c4e6c 100644 --- a/scripts/profile_corpus_error.py +++ b/scripts/profile_corpus_error.py @@ -37,6 +37,7 @@ from typing import Any, Optional from datatypes.epc.domain.mapper import EpcPropertyDataMapper from domain.sap10_calculator.calculator import calculate_sap_from_inputs from domain.sap10_calculator.rdsap.cert_to_inputs import ( + cert_to_demand_inputs, SAP_10_2_SPEC_PRICES, cert_to_inputs, ) @@ -96,6 +97,14 @@ def _compute(corpus: list[dict[str, Any]]) -> tuple[list[Row], int, int]: result = calculate_sap_from_inputs( cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES) ) + # SAP/EI rating is the UK-average rating cascade (`result`); + # the EPC-displayed CO2/PE use the postcode demand cascade + # (SAP 10.2 Appendix U p.124). Use the demand cascade for the + # PE/CO2-vs-cost triage so it is not confounded by the climate + # difference (UK-average vs local weather). + demand = calculate_sap_from_inputs( + cert_to_demand_inputs(epc, prices=SAP_10_2_SPEC_PRICES) + ) except Exception: raised += 1 continue @@ -110,14 +119,14 @@ def _compute(corpus: list[dict[str, Any]]) -> tuple[list[Row], int, int]: rows.append(Row( cert=cert, sap_err=result.sap_score_continuous - lodged_sap, - co2_err_t=(result.co2_kg_per_yr / 1000.0 - lodged_co2_t) + co2_err_t=(demand.co2_kg_per_yr / 1000.0 - lodged_co2_t) if lodged_co2_t is not None else None, - pe_err=(result.primary_energy_kwh_per_m2 - lodged_pe) + pe_err=(demand.primary_energy_kwh_per_m2 - lodged_pe) if lodged_pe is not None else None, lodged_sap=lodged_sap, our_sap=result.sap_score_continuous, lodged_pe=lodged_pe, - our_pe=result.primary_energy_kwh_per_m2, + our_pe=demand.primary_energy_kwh_per_m2, feats=features(doc), )) return rows, skipped, raised From 8942d457723a29ebf0ca62d170182b95c03ebf33 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 18 Jun 2026 14:52:58 +0000 Subject: [PATCH 007/340] fix(fuel): price secondary dual-fuel/anthracite at their own rate, not the colliding LPG code (RdSAP 10 Table 32) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gov-API lodges secondary fuel as an enum whose value can COLLIDE with a different same-valued RdSAP 10 Table 32 / SAP 10.2 Table 12 fuel code: - enum 9 = "dual fuel (mineral and wood)" vs Table code 9 = LPG SC11F - enum 5 = "anthracite" vs Table code 5 = LPG (bulk) The main-fuel boundary already canonicalises these (`_GOV_API_COLLISION_ FUELS`), but the SECONDARY-heating cost + CO2/PE paths never did — they took the bare same-value lookup, so a dual-fuel room heater was priced as LPG (3.48 vs dual-fuel 3.99 p/kWh) and emitted as LPG (CO2 0.241 vs 0.087), and an anthracite secondary as bulk LPG (12.19 vs 3.64 p/kWh). The price under-count over-rates SAP; the CO2 over-count inflates emissions. Fix: add enum 9 to `_GOV_API_COLLISION_FUELS` (5 and 33 were already there) and canonicalise the secondary fuel code on both the cost (`_secondary_fuel_cost_gbp_per_kwh`) and factor (`_secondary_fuel_code`) paths, mirroring the main-fuel boundary. canonical_fuel_code only touches {5,9,33}, so genuinely Table-coded secondaries (House coal 11, wood logs 20, community fuels 30-32) are left unchanged — confirmed by a full-map audit. Corpus: within-0.5 69.7% -> 70.2% (MAE 0.854 -> 0.845; dual-fuel-secondary cohort 42.9% -> 49.0%, signed +0.55 -> +0.41) and CO2 MAE 0.12 -> 0.08 t/yr (bias +0.04 -> 0.00). Ratcheted the corpus floors (within 0.70, MAE 0.85, CO2 0.09, PE 4.0). A prior session deferred enum 9 ("direction not understood") while the EPC PE/CO2 lens was confounded by the climate-cascade bug (fc7c4d2d); on the corrected lens the over-rate direction is clear. pyright not installed in this codespace (strict gate not run locally). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../sap10_calculator/rdsap/cert_to_inputs.py | 10 ++++- domain/sap10_calculator/tables/table_32.py | 18 +++++--- .../rdsap/test_cert_to_inputs.py | 42 +++++++++++++++++++ .../epc_client/test_sap_accuracy_corpus.py | 20 +++++++-- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/domain/sap10_calculator/rdsap/cert_to_inputs.py b/domain/sap10_calculator/rdsap/cert_to_inputs.py index a271f8f5e..49576723d 100644 --- a/domain/sap10_calculator/rdsap/cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/cert_to_inputs.py @@ -2772,7 +2772,11 @@ def _secondary_fuel_cost_gbp_per_kwh( meter_type, fuel_is_electric=True ): return _secondary_off_peak_rate_gbp_per_kwh(meter_type) - return prices.unit_price_p_per_kwh(sec_fuel) * _PENCE_TO_GBP + # Normalise colliding gov-API enum codes (e.g. 9 dual fuel, whose + # value collides with Table-32 9 = LPG SC11F) before the price lookup, + # exactly as the main-fuel boundary does — otherwise the same-value + # Table lookup mis-prices the secondary at the colliding fuel's rate. + return prices.unit_price_p_per_kwh(canonical_fuel_code(sec_fuel)) * _PENCE_TO_GBP def _pv_array_generation_kwh_per_yr( @@ -3927,6 +3931,10 @@ def _secondary_fuel_code(epc: EpcPropertyData) -> int: code = _int_or_none(epc.sap_heating.secondary_fuel_type) if code is None: return _STANDARD_ELECTRICITY_FUEL_CODE + # Normalise colliding gov-API enum codes (e.g. 9 dual fuel, whose value + # collides with the LPG Table code) so the CO2/PE factor lookups resolve + # to the lodged fuel — mirrors the main-fuel boundary + the cost side. + code = canonical_fuel_code(code) or code if code in CO2_KG_PER_KWH: return code return _table_12_factor_fuel_code(code) diff --git a/domain/sap10_calculator/tables/table_32.py b/domain/sap10_calculator/tables/table_32.py index 8377fe862..14544aea1 100644 --- a/domain/sap10_calculator/tables/table_32.py +++ b/domain/sap10_calculator/tables/table_32.py @@ -121,11 +121,17 @@ API_FUEL_TO_TABLE_32: Final[dict[int, int]] = { # 33 = coal — Table-32 code 33 is the electricity 10-hour low rate # 7.5 p vs house coal 3.67 p (and `is_electric_fuel_code(33)` # wrongly classified the coal main as electric). -# DEFERRED (not included): API 9 = dual fuel (mineral + wood) is also a -# collision (Table-32 9 = LPG SC11F 3.48 p vs dual fuel 3.99 p) but the -# 0.45 p delta nets neutral-to-negative on the (outlier-dominated) -# dual-fuel certs and shifts them in a direction not yet understood — -# investigate separately. +# 9 = dual fuel (mineral + wood) — Table-32 code 9 is LPG SC11F +# 3.48 p vs dual fuel 3.99 p. The gov-API lodges API enum 9 for a +# dual-fuel appliance (description "Room heaters, dual fuel +# (mineral and wood)"), but the same-value Table-32 lookup returns +# LPG 3.48 p, under-pricing the (mostly secondary) dual-fuel heat. +# A prior session deferred this as "direction not understood" +# while the EPC PE/CO2 lens was confounded by the climate-cascade +# bug (fixed in fc7c4d2d); on the corrected lens the dual-fuel +# secondary cohort over-rates (SAP too high = cost too low) by +# +0.55 signed, and pricing UP to the dual-fuel 3.99 p row reduces +# that over-rate — the correct direction. # # COMMUNITY FUELS (handled elsewhere, NOT here): API 30 (waste # combustion), 31 (biomass) and 32 (biogas) — all "(community)" in the @@ -140,7 +146,7 @@ API_FUEL_TO_TABLE_32: Final[dict[int, int]] = { # cert_to_inputs), where the community meaning is unambiguous. Community # fuels 20/25 do not collide with an electricity code, so they resolve # correctly through the heat-network path without any special handling. -_GOV_API_COLLISION_FUELS: Final[frozenset[int]] = frozenset({5, 33}) +_GOV_API_COLLISION_FUELS: Final[frozenset[int]] = frozenset({5, 9, 33}) def canonical_fuel_code(fuel_code: Optional[int]) -> Optional[int]: diff --git a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py index e3e99e9ea..2ead1beff 100644 --- a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py +++ b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py @@ -80,6 +80,7 @@ from domain.sap10_calculator.rdsap.cert_to_inputs import ( _pv_overshading_factor, # pyright: ignore[reportPrivateUsage] _pv_pitch_deg, # pyright: ignore[reportPrivateUsage] _responsiveness, # pyright: ignore[reportPrivateUsage] + _secondary_fuel_code, # pyright: ignore[reportPrivateUsage] _secondary_fuel_cost_gbp_per_kwh, # pyright: ignore[reportPrivateUsage] _secondary_heating_fraction_for_category, # pyright: ignore[reportPrivateUsage] _section_12_4_4_summer_immersion_applies, # pyright: ignore[reportPrivateUsage] @@ -2162,6 +2163,47 @@ def test_is_electric_main_dual_fuel_table_32_code_10_is_not_electric() -> None: assert _is_electric_main(community_electric_main) is False +def test_dual_fuel_secondary_api_enum_9_prices_as_dual_fuel_not_lpg() -> None: + # Arrange — the gov-API lodges secondary fuel enum 9 = "dual fuel (mineral + # and wood)", but enum value 9 COLLIDES with the same-valued RdSAP 10 + # Table 32 / SAP 10.2 Table 12 code 9 = "LPG (bulk, SC11F)". The secondary + # cost + CO2/PE paths previously took the same-value lookup (LPG 3.48 + # p/kWh, CO2 0.241 kg/kWh) instead of translating the enum to the dual- + # fuel row (3.99 p/kWh, CO2 0.087) — under-costing the secondary (SAP + # over-rate) AND over-counting its CO2 (LPG is fossil; dual fuel is part + # wood). Enum 9 is now in `_GOV_API_COLLISION_FUELS`, and both secondary + # paths canonicalise (mirroring the main-fuel boundary). SAP 10.2 Table + # 12 (p.189) / RdSAP 10 Table 32 (p.95). + gas_boiler_main = MainHeatingDetail( + has_fghrs=False, main_fuel_type=26, heat_emitter_type=1, + emitter_temperature=1, main_heating_control=2106, + main_heating_category=2, sap_main_heating_code=102, + ) + dual_fuel_secondary_epc = make_minimal_sap10_epc( + total_floor_area_m2=_TYPICAL_TFA_M2, + habitable_rooms_count=4, + country_code="ENG", + sap_heating=make_sap_heating( + main_heating_details=[gas_boiler_main], + secondary_fuel_type=9, # gov-API enum: dual fuel (mineral + wood) + secondary_heating_type=631, + ), + ) + + # Act — the rating-cascade secondary price + the CO2/PE fuel code. + secondary_rate_gbp_per_kwh = _secondary_fuel_cost_gbp_per_kwh( + dual_fuel_secondary_epc.sap_heating, + gas_boiler_main, + 2, # standard (single-rate) meter + SAP_10_2_SPEC_PRICES, + ) + secondary_factor_code = _secondary_fuel_code(dual_fuel_secondary_epc) + + # Assert — dual fuel 3.99 p/kWh (NOT LPG 3.48) + Table code 10 (NOT 9). + assert abs(secondary_rate_gbp_per_kwh - 0.0399) <= 1e-6 + assert secondary_factor_code == 10 + + def test_is_electric_water_dual_fuel_table_32_code_10_is_not_electric() -> None: # Arrange — same API/Table 32 collision as `_is_electric_main` per # S0380.136 docstring. diff --git a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py index ca4681e44..ef933adb0 100644 --- a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py +++ b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py @@ -138,10 +138,22 @@ _CORPUS = Path( # (rating cascade). Worksheet-validated to 1e-4 on simulated case 45 (rating # CO2 692.13; demand CO2 626.78, PE 6581.59). The residual PE/CO2 spread is # now the genuine per-cert mapper-fidelity tail. -_MIN_WITHIN_HALF_SAP = 0.695 -_MAX_SAP_MAE = 0.86 -_MAX_CO2_MAE_TONNES = 0.13 # t CO2 / yr vs co2_emissions_current -_MAX_PE_PER_M2_MAE = 4.2 # kWh / m2 / yr vs energy_consumption_current +# DUAL-FUEL SECONDARY COLLISION (RdSAP 10 Table 32 / SAP 10.2 Table 12): the +# gov-API lodges fuel enum 9 ("dual fuel, mineral and wood") for a dual-fuel +# room heater, but enum 9 collides with the same-valued Table-32/12 code 9 +# (LPG SC11F), so the price (3.48 vs dual-fuel 3.99 p/kWh) AND the CO2/PE +# factors (LPG 0.241 / 1.163 vs dual fuel 0.087 / 1.049) resolved to LPG — +# the secondary was under-costed (→ SAP over-rate) and over-counted on CO2. +# Canonicalising enum 9 (now in `_GOV_API_COLLISION_FUELS`) on the secondary +# cost + factor paths took within-0.5 69.7% -> 70.2% (MAE 0.854 -> 0.845; +# dual-fuel-secondary cohort 42.9% -> 49.0%, signed +0.55 -> +0.41) and CO2 +# MAE 0.12 -> 0.08 t/yr (bias +0.04 -> 0.00). A prior session deferred enum 9 +# ("direction not understood") while the PE/CO2 lens was confounded by the +# climate-cascade bug (fc7c4d2d); the corrected lens shows the over-rate. +_MIN_WITHIN_HALF_SAP = 0.70 +_MAX_SAP_MAE = 0.85 +_MAX_CO2_MAE_TONNES = 0.09 # t CO2 / yr vs co2_emissions_current +_MAX_PE_PER_M2_MAE = 4.0 # kWh / m2 / yr vs energy_consumption_current def _load_corpus() -> list[dict[str, Any]]: From 2e416a02217f1bab4bc05dec990e142d74fa0017 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 18 Jun 2026 15:01:26 +0000 Subject: [PATCH 008/340] chore: untrack .claude/settings.json (machine-specific personal config) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `.claude/settings.json` was committed to main by mistake — it holds per-developer permission allow-lists (npx cache paths, /tmp script paths, and even hardcoded credentials), not shared project config. Mirror the existing `.claude/settings.local.json` treatment: remove it from the index and add it to .gitignore so each developer keeps their own local copy. Claude Code merges settings.json + settings.local.json at runtime, so no permissions are lost. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/settings.json | 168 ------------------------------------------ .gitignore | 1 + 2 files changed, 1 insertion(+), 168 deletions(-) delete mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index f9ab31566..000000000 --- a/.claude/settings.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(python -c ' *)", - "Bash(python -m pytest tests/ -v --no-cov)", - "Bash(git -C /workspaces/model diff --stat)", - "Bash(python -c \"import moto; print\\('moto installed:', moto.__version__\\)\")", - "Bash(grep -E \"\\\\.\\(py|sql\\)$\")", - "Bash(xargs basename -a)", - "Bash(ls -la /workspaces/home 2>/dev/null | head -20)", - "Read(//workspaces/home/**)", - "Bash(command -v uv)", - "Bash(uv --version)", - "Bash(echo \"uv: $\\(uv --version\\)\")", - "Bash(python -m pyright --version)", - "Bash(npx --no-install pyright --version)", - "Bash(node /home/vscode/.npm/_npx/110e52990071af13/node_modules/pyright/dist/pyright.js --outputjson etl/hubspot/hubspotClient.py etl/hubspot/hubspotDataTodB.py etl/hubspot/project_data.py etl/hubspot/scripts/scraper/main.py backend/app/db/models/hubspot_project_data.py backend/app/db/models/hubspot_deal_data.py etl/hubspot/tests/test_scraper_handler.py etl/hubspot/tests/test_hubspot_data_to_db.py etl/hubspot/tests/test_hubspot_client_integration.py)", - "Bash(python -c \"import sys,json; d=json.load\\(sys.stdin\\); s=d['summary']; print\\('errors',s['errorCount'],'warnings',s['warningCount']\\); [print\\(f\\\\\"{x['severity']}: {x['file'].split\\('/'\\)[-1]}:{x['range']['start']['line']+1} {x['rule'] if 'rule' in x else ''} -- {x['message'].splitlines\\(\\)[0]}\\\\\"\\) for x in d['generalDiagnostics'] if x['severity']=='error']\")", - "Bash(node /home/vscode/.npm/_npx/110e52990071af13/node_modules/pyright/dist/pyright.js etl/hubspot/project_data.py backend/app/db/models/hubspot_project_data.py)", - "Bash(python -c \"import sqlmodel, sqlalchemy, hubspot; print\\('deps importable'\\)\")", - "Bash(python -m pytest --version)", - "Bash(python -m pytest etl/hubspot/tests/ -m \"not integration\" -p no:cacheprovider -o addopts=\"\" -q)", - "Bash(node /home/vscode/.npm/_npx/110e52990071af13/node_modules/pyright/dist/pyright.js --pythonpath /usr/local/bin/python etl/hubspot/project_data.py backend/app/db/models/hubspot_project_data.py)", - "Bash(node /home/vscode/.npm/_npx/110e52990071af13/node_modules/pyright/dist/pyright.js --pythonpath /usr/local/bin/python etl/hubspot/company_data.py)", - "Bash(python /tmp/inspect_project.py)", - "Read(//home/vscode/github/**)", - "Bash(find / -maxdepth 6 -type d -name assessment-model)", - "Bash(terraform fmt *)", - "Bash(terraform init *)", - "Bash(terraform validate *)", - "Bash(python -m pytest tests/orchestration/test_landlord_description_overrides_orchestrator.py -q)", - "Bash(python -m pytest tests/orchestration/test_landlord_description_overrides_orchestrator.py tests/repositories/landlord_overrides -q --no-cov)", - "Bash(python -m py_compile tests/infrastructure/chatgpt/test_chatgpt_column_classifier.py)", - "Bash(git add *)", - "Bash(git status *)", - "Bash(cp pyrightconfig.json /tmp/pyrightconfig.bak.json)", - "Bash(python3 -)", - "Bash(npx --yes pyright infrastructure/postgres/landlord_overrides_postgres_repository.py applications/landlord_description_overrides/handler.py tests/repositories/landlord_overrides/postgres/test_landlord_overrides_postgres_repository.py orchestration/classifiable_column.py)", - "Bash(git stash *)", - "Bash(npx --yes pyright applications/landlord_description_overrides/handler.py)", - "Bash(python3 -m pytest tests/repositories/landlord_overrides/postgres/test_landlord_overrides_postgres_repository.py -q)", - "Bash(python3 -m pytest tests/orchestration/test_landlord_description_overrides_orchestrator.py tests/repositories/landlord_overrides/ -q -p no:cov)", - "Bash(python3 -m pytest tests/orchestration/test_landlord_description_overrides_orchestrator.py tests/repositories/landlord_overrides/ -q)", - "Bash(cp pyrightconfig.json /tmp/pyrightconfig.bak2.json)", - "Bash(npx --yes pyright infrastructure/landlord_overrides/landlord_overrides_postgres_repository.py tests/repositories/landlord_overrides/postgres/test_landlord_overrides_postgres_repository.py)", - "Bash(GIT_EDITOR=true git rebase --continue)", - "Bash(git worktree *)", - "Bash(git branch *)", - "Bash(echo \"exit: $?\")", - "Bash(git reset *)", - "Bash(echo \"fetch-exit: $?\")", - "Bash(sed -n *)", - "Bash(set -e)", - "Bash(git rm *)", - "Bash(git ls-tree *)", - "Bash(command -v pyright)", - "Bash(git merge *)", - "Bash(git rev-list *)", - "Bash(git remote *)", - "Bash(git ls-remote *)", - "Bash(grep -v '\\\\.sample$')", - "Bash([ -f \".git/hooks/$h\" ])", - "Bash(python3 -m pytest tests/test_lambda_packaging.py -p no:cacheprovider --no-header -q)", - "Bash(python3 -m pytest tests/test_lambda_packaging.py -p no:cov --no-header -q)", - "Bash(python3 -m pytest tests/test_lambda_packaging.py --no-cov -p no:cacheprovider -q)", - "Bash(python3 -m pytest tests/test_lambda_packaging.py --no-cov -q)", - "Bash(python3 -m pyright tests/test_lambda_packaging.py)", - "Bash(python3 -m pytest tests/ --collect-only --no-cov -q)", - "Bash(python3 -c \"import yaml; [print\\(f, 'OK'\\) for f in ['.github/workflows/ddd_tests.yml','.github/workflows/unit_tests.yml'] if yaml.safe_load\\(open\\(f\\)\\)]\")", - "Bash(python3 -m pytest tests/ -q --no-cov)", - "Bash(python3 -c ' *)", - "Bash(python3 -m pyright --outputjson scripts/hyde/main.py)", - "Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); s=d['summary']; print\\('errors:',s['errorCount'],'warnings:',s['warningCount']\\); [print\\(f\\\\\" L{e['range']['start']['line']+1}: {e['message'].splitlines\\(\\)[0]}\\\\\"\\) for e in d['generalDiagnostics'] if e['severity']=='error']\")", - "Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); s=d['summary']; print\\('pyright errors:',s['errorCount'],'warnings:',s['warningCount']\\); [print\\(f\\\\\" L{e['range']['start']['line']+1}: {e['message'].splitlines\\(\\)[0]}\\\\\"\\) for e in d['generalDiagnostics'] if e['severity']=='error']\")", - "Bash(python3 main.py)", - "Bash(netstat -ltnp)", - "Bash(fuser 8000/tcp)", - "Bash(kill 351610 685390 351625)", - "Read(//proc/531213/net/**)", - "Bash(ps -p 351610 -o pid=)", - "Bash(python -)", - "Bash(python -m pytest \"datatypes/epc/domain/tests/test_from_rdsap_schema.py::TestRdSap20_0_0ReducedFieldSynthesis::test_cert_omitting_sap_windows_maps_without_missing_required_field\")", - "Bash(python -m pytest datatypes/epc/domain/tests/test_from_rdsap_schema.py infrastructure/epc_client/tests/test_mapper_corpus.py)", - "Bash(git -C /workspaces/model stash push -u -- datatypes/epc/schema/rdsap_schema_20_0_0.py datatypes/epc/domain/mapper.py datatypes/epc/domain/tests/test_from_rdsap_schema.py)", - "Bash(python -m pytest \"datatypes/epc/domain/tests/test_from_rdsap_schema.py::TestFromRdSapSchema21_0_1::test_total_floor_area\")", - "Bash(git -C /workspaces/model stash pop)", - "Bash(python -m pytest \"datatypes/epc/domain/tests/test_from_rdsap_schema.py::TestRdSap20_0_0ReducedFieldSynthesis::test_band_more_than_typical_scales_glazing_by_1_25\")", - "Bash(python -m pytest \"datatypes/epc/domain/tests/test_from_rdsap_schema.py::TestRdSap20_0_0ReducedFieldSynthesis\")", - "Bash(python -m pytest \"datatypes/epc/domain/tests/test_from_rdsap_schema.py::TestRdSap20_0_0ReducedFieldSynthesis::test_synthesised_glazing_type_routed_through_cascade\")", - "Bash(python -m pytest infrastructure/epc_client/tests/test_mapper_corpus.py -k \"wip_schema_20\" -q -p no:cov --no-header -rN)", - "Bash(python -m pytest infrastructure/epc_client/tests/test_mapper_corpus.py -k \"wip_schema_20\" -q --no-header)", - "Bash(timeout 150 python scripts/eon/find_epc_data.py)", - "Bash(python -m scripts.run_modelling_e2e --scenario-id 1266 --portfolio-id 785 --measures high_heat_retention_storage_heaters,solar_pv 709634 709635 709636 709638 709639 709640 709641 709642 709644)", - "Read(//home/vscode/.claude/skills/grill-me/**)", - "Bash(awk '{print $9, $5}')", - "Bash(grep -E \"\\\\.py$\")", - "Bash(python3 scripts/eon/harvest_certs.py)", - "Bash(python scripts/eon/harvest_certs.py)", - "Bash(git commit -q -m ' *)", - "Bash(timeout 250 npx -y pyright@1.1.410 datatypes/epc/domain/mapper.py)", - "Bash(git check-ignore *)", - "Bash(timeout 250 npx -y pyright@1.1.410 datatypes/epc/schema/rdsap_schema_18_0.py datatypes/epc/domain/mapper.py)", - "Bash(timeout 250 npx -y pyright@1.1.410 datatypes/epc/schema/rdsap_schema_17_1.py datatypes/epc/domain/mapper.py)", - "Bash(python -m scripts.run_modelling_e2e --scenario-id 1266 --portfolio-id 785 --measures high_heat_retention_storage_heaters,solar_pv 709634 709635 709636 709638 709639 709640 709641 709642 709643 709644 709645 709637)", - "Read(//workspaces/**)", - "Bash(python -m scripts.run_modelling_e2e --scenario-id 1266 --portfolio-id 785 --measures high_heat_retention_storage_heaters,solar_pv 709634)", - "Bash(python 2_export_data.py)", - "Bash(git commit -q -m 'Map RdSAP-Schema-17.0 certs to EpcPropertyData 🟥 *)", - "Bash(python -m py_compile tests/utilities/floats.py backend/app/db/functions/tests/test_portfolio_functions.py backend/documents_parser/tests/test_summary_pdf_mapper_chain.py backend/documents_parser/tests/test_heating_systems_corpus.py)", - "Bash(PYTHONPATH=/workspaces/model python -)", - "Bash(npx --no-install pyright datatypes/epc/schema/sap_schema_17_1.py datatypes/epc/domain/mapper.py datatypes/epc/domain/tests/test_from_sap_schema.py)", - "Bash(git commit -q -m 'Map full-SAP cert identity and scalar fields to EpcPropertyData 🟩 *)", - "Bash(npx --no-install pyright --outputjson datatypes/epc/domain/mapper.py)", - "Bash(git commit -q -m 'Carry full-SAP measured fabric U-value descriptions into the domain model 🟩 *)", - "Bash(python scripts/hyde/elmhurst_session.py --help)", - "Bash(python scripts/hyde/elmhurst_session.py status)", - "Bash(DISPLAY=:99 import -window root /tmp/hyde-viewer/elm_now2.png)", - "Bash(DISPLAY=:99 timeout 90 python scripts/hyde/elmhurst_explore.py \"https://rdsap10online.elmhurstenergy.co.uk/Processing/WebFormAddress.aspx?Guid=B44A0DB4-4C08-4241-B818-86F060172105&Referrer=https%3a%2f%2fmembers.elmhurstenergy.co.uk%2fRdsapZone%2fHome.aspx\")", - "Bash(DISPLAY=:99 timeout 90 python scripts/hyde/elmhurst_explore.py \"https://rdsap10online.elmhurstenergy.co.uk/Processing/WebFormPropertyDescription.aspx?Guid=B44A0DB4-4C08-4241-B818-86F060172105\")", - "Bash(DISPLAY=:99 timeout 90 python scripts/hyde/elmhurst_explore.py \"https://rdsap10online.elmhurstenergy.co.uk/Processing/WebFormAddress.aspx?Guid=B44A0DB4-4C08-4241-B818-86F060172105\")", - "Bash(DISPLAY=:99 timeout 150 python scripts/hyde/elmhurst_fill.py)", - "Bash(pkill -f \"rdsap10\\\\|for-testing\")", - "Bash(rm -f scripts/hyde/.elmhurst-session/Singleton*)", - "Bash(DISPLAY=:99 timeout 280 python -u scripts/hyde/elmhurst_fill.py --page property_description --commit)", - "Bash(npx --no-install pyright scripts/hyde/elmhurst_download.py)", - "Edit(/.claude/skills/expand-sap-accuracy-corpus/**)", - "Bash(PYTHONPATH=/workspaces/model python scripts/fetch_real_life_epc_sample.py 10093116528)", - "Bash(PYTHONPATH=/workspaces/model python -c ' *)", - "Bash(bash scripts/hyde/start_viewer.sh)", - "Bash(DISPLAY=:99 ELMHURST_URL=\"https://rdsap10online.elmhurstenergy.co.uk/Processing/WebFormAddress.aspx?Guid=B44A0DB4-4C08-4241-B818-86F060172105\" python scripts/hyde/elmhurst_session.py login)", - "Bash(break)", - "Bash(DISPLAY=:99 timeout 90 python /tmp/check_session.py)", - "Bash(pkill -9 -f \"elm_login_hold.py\")", - "Bash(pkill -9 -f \"elmhurst-session\")", - "Bash(ELM_ACCESS=\"P960-0001\" ELM_PWD=\"y22bseGUzr\" DISPLAY=:99 timeout 150 python /tmp/elm_build_dims.py)", - "Bash(ELM_ACCESS=\"P960-0001\" ELM_PWD=\"y22bseGUzr\" DISPLAY=:99 timeout 120 python /tmp/elm_dump_full.py Walls WebFormWalls.aspx)", - "Bash(ELM_ACCESS=\"P960-0001\" ELM_PWD=\"y22bseGUzr\" DISPLAY=:99 timeout 120 python /tmp/elm_walls_disc.py)", - "Bash(ELM_ACCESS=\"P960-0001\" ELM_PWD=\"y22bseGUzr\" DISPLAY=:99 timeout 120 python /tmp/elm_roof_disc.py)", - "Bash(ELM_ACCESS=\"P960-0001\" ELM_PWD=\"y22bseGUzr\" DISPLAY=:99 timeout 120 python /tmp/elm_dump_full.py Openings WebFormOpenings.aspx)", - "Bash(ELM_ACCESS=\"P960-0001\" ELM_PWD=\"y22bseGUzr\" DISPLAY=:99 timeout 120 python /tmp/elm_win_probe.py)", - "Bash(ELM_ACCESS=\"P960-0001\" ELM_PWD=\"y22bseGUzr\" DISPLAY=:99 timeout 220 python /tmp/elm_build_openings.py)", - "Bash(ELM_ACCESS=\"P960-0001\" ELM_PWD=\"y22bseGUzr\" DISPLAY=:99 timeout 120 python /tmp/elm_tab_probe.py)", - "Bash(ELM_ACCESS=\"P960-0001\" ELM_PWD=\"y22bseGUzr\" DISPLAY=:99 timeout 120 python /tmp/elm_del_probe.py)", - "Bash(npx --no-install pyright datatypes/epc/domain/epc_property_data.py datatypes/epc/domain/mapper.py domain/sap10_calculator/rdsap/cert_to_inputs.py)", - "Bash(PYTHONPATH=/workspaces/model python -m pytest domain/sap10_calculator/tests/test_sap_accuracy_corpus.py -q --no-cov)", - "Bash(PYTHONPATH=/workspaces/model python -m pytest tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py -q --no-cov)", - "Edit(/.claude/skills/epc-to-elmhurst-rdsap-inputs/**)", - "Bash(python3 -c \"import sys; sys.path.insert\\(0,'scripts/hyde'\\); import elmhurst_lib; print\\('elmhurst_lib imports OK:', [f for f in dir\\(elmhurst_lib\\) if not f.startswith\\('_'\\)][:12]\\)\")", - "Bash(PYTHONPATH=/workspaces/model python -m pytest tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py tests/infrastructure/epc_client/test_sap_accuracy_corpus.py -q --no-cov)", - "Bash(PYTHONPATH=/workspaces/model python scripts/fetch_real_life_epc_sample.py 10093116543)", - "Bash(echo \"---EXIT $?---\")", - "Bash(sudo apt-get update -o Dir::Etc::sourceparts=-)", - "Bash(apt-cache policy *)", - "Bash(sudo apt-get update)", - "Bash(bash scripts/hyde/start_viewer.sh restart)", - "Bash(echo \"=== listen sockets \\(port 6080 / 5900\\) ===\"; ss -ltnp 2>/dev/null | grep -E ':6080|:5900' || netstat -ltnp 2>/dev/null | grep -E ':6080|:5900' || echo \"ss/netstat unavailable\" *)", - "Read(//proc/5476/**)", - "Bash(sudo python -m playwright install-deps chromium)", - "Bash(python -m playwright install chromium)", - "Bash(python -m playwright --version)", - "Bash(pip show *)" - ], - "additionalDirectories": [ - "/tmp", - "/workspaces/model/scripts/hyde/.elmhurst-session" - ] - } -} diff --git a/.gitignore b/.gitignore index 9cb34cb3d..9c77b3117 100644 --- a/.gitignore +++ b/.gitignore @@ -304,6 +304,7 @@ backlog/* # Local Claude config files .claude/*modelling_cohort.csv .claude/settings.local.json +.claude/settings.json # Local EPC debug cache (scripts/eon) scripts/eon/epc_cache.pkl From e6a829aaeafbef9ce501ef63caac08cb5f03e885 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 09:51:49 +0000 Subject: [PATCH 009/340] more examples --- .../expand-sap-accuracy-corpus/SKILL.md | 53 +- .../expand-sap-accuracy-corpus/worklist.md | 19 +- .../uprn_100021943298/elmhurst_inputs.md | 98 ++++ .../uprn_100021943298/elmhurst_summary.pdf | Bin 0 -> 63418 bytes .../uprn_100021943298/elmhurst_worksheet.pdf | Bin 0 -> 43041 bytes .../uprn_100021943298/epc.json | 232 +++++++++ .../uprn_100020933699/elmhurst_summary.pdf | Bin 0 -> 80690 bytes .../uprn_100020933699/elmhurst_worksheet.pdf | Bin 0 -> 43088 bytes .../uprn_44012843/elmhurst_summary.pdf | Bin 0 -> 62686 bytes .../uprn_44012843/elmhurst_worksheet.pdf | Bin 0 -> 43994 bytes .../uprn_10023444320/elmhurst_summary.pdf | Bin 0 -> 62601 bytes .../uprn_10023444320/elmhurst_worksheet.pdf | Bin 0 -> 23393 bytes .../uprn_10023444324/elmhurst_summary.pdf | Bin 0 -> 62607 bytes .../uprn_10023444324/elmhurst_worksheet.pdf | Bin 0 -> 23365 bytes .../uprn_10096028301/elmhurst_inputs.md | 96 ++++ .../uprn_10096028301/elmhurst_summary.pdf | Bin 0 -> 62575 bytes .../uprn_10096028301/elmhurst_worksheet.pdf | Bin 0 -> 23200 bytes .../uprn_10096028301/epc.json | 456 ++++++++++++++++++ datatypes/epc/domain/mapper.py | 111 ++++- domain/elmhurst/README.md | 28 ++ scripts/hyde/elmhurst_dom/conservatories.json | 6 - scripts/hyde/elmhurst_dom/dimensions.json | 54 ++- scripts/hyde/elmhurst_dom/floors.json | 6 - .../hyde/elmhurst_dom/newtechnologies.json | 6 - scripts/hyde/elmhurst_dom/openings.json | 172 +------ .../elmhurst_dom/propertydescription.json | 71 ++- .../hyde/elmhurst_dom/propertydetails.json | 6 - scripts/hyde/elmhurst_dom/roofs.json | 12 +- scripts/hyde/elmhurst_dom/spaceheating.json | 36 +- .../elmhurst_dom/ventilationandcooling.json | 42 +- scripts/hyde/elmhurst_dom/walls.json | 48 +- scripts/hyde/elmhurst_dom/waterheating.json | 155 +----- scripts/hyde/elmhurst_download.py | 8 +- scripts/hyde/elmhurst_lib.py | 110 +++++ .../test_real_cert_sap_accuracy.py | 93 ++++ 35 files changed, 1467 insertions(+), 451 deletions(-) create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.1/uprn_100021943298/elmhurst_inputs.md create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.1/uprn_100021943298/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.1/uprn_100021943298/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.1/uprn_100021943298/epc.json create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100020933699/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100020933699/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.3/uprn_44012843/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.3/uprn_44012843/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.0/uprn_10023444320/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.0/uprn_10023444320/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.0/uprn_10023444324/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.0/uprn_10023444324/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-19.1.0/uprn_10096028301/elmhurst_inputs.md create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-19.1.0/uprn_10096028301/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-19.1.0/uprn_10096028301/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-19.1.0/uprn_10096028301/epc.json create mode 100644 domain/elmhurst/README.md diff --git a/.claude/skills/expand-sap-accuracy-corpus/SKILL.md b/.claude/skills/expand-sap-accuracy-corpus/SKILL.md index 9c2d40626..437c305e8 100644 --- a/.claude/skills/expand-sap-accuracy-corpus/SKILL.md +++ b/.claude/skills/expand-sap-accuracy-corpus/SKILL.md @@ -105,13 +105,52 @@ Pattern: `with E.session() as (ctx,page): E.goto(...); E.set_text/set_select(... default. Pin the engine's observed value; document the Elmhurst delta (don't tune). - **FGHRS** (full-SAP `has_fghrs`/`fghrs_index_number`) is dropped by the mapper and not yet modelled — omit it on BOTH sides so the comparison stays clean. +- **Validation errors live on the Recommendations page as LINKS** (red ✗ anchors), + not coloured spans — parse `[id*=ContentPlaceHolder1] a` text, not CSS colour. + Until Recommendations shows ZERO errors the **Energy Report Summary nav silently + redirects to the Address page** and the worksheet/Results PDFs won't generate + (Results renders empty). Two errors that bit the 16.1/19.1.0 builds: *"Walls + (Main): Insulation Thickness must be entered"* (set the insulation-thickness + dropdown — "Unknown" is fine for a reduced cert) and *"Incorrect Controls (NNNN + ctrl-group-X) for Heating System (idx ctrl-group-Y)"* (the heating control's + system-type must match the boiler's — see the controls dialog below). +- **All Elmhurst modal dialogs sit above a `modalBackground` that defeats element + clicks** (Playwright sees it "intercepting pointer events", even with force). + The cracked pattern is now in `elmhurst_lib.py`: open via JS click, set cascade + ` values by JS (set .value + dispatch 'change') — this drives +# the AutoPostBack cascade without a real click, +# * commit by a raw `page.mouse.click(x, y)` at the OK/Select control's centre +# (coordinate clicks land on the topmost element = the dialog, not the +# background). +# Typing into the boiler search box needs force-click-then-keyboard (the box is +# overlapped); result cells are clicked by coordinate (the model column is +# display:none at >=1024px, so match the row by its Ref-No cell and click the +# first VISIBLE cell). Direct-writing the PCDF reference number is COSMETIC — it +# does NOT re-resolve the boiler type/efficiency; only the search dialog does. +MH1 = ( + "ContentBody_ContentPlaceHolder1_TabContainer_TabPanelMainHeating1_" + "WebUserControlMainHeating1_" +) +HEATING_DIALOG = "ContentBody_OutsideUpdatePanel_SelectHeatingDialog_" + + +def dialog_commit(page: Page, label: str = "OK") -> bool: + """Coordinate-click a dialog's commit control (a styled , e.g. 'OK' or + 'Select') — bypasses the modalBackground that defeats element clicks.""" + pt = page.evaluate( + """(lbl)=>{const e=[...document.querySelectorAll('[id*=SelectHeatingDialog] span,[id*=SelectHeatingDialog] a,[id*=SelectHeatingDialog] input,[id*=SelectBoilerDialog] span')] + .find(x=>new RegExp('^'+lbl+'$','i').test((x.value||x.innerText||'').trim()) && x.offsetParent); + if(!e)return null;const c=e.getBoundingClientRect();return {x:c.x+c.width/2,y:c.y+c.height/2};}""", + label, + ) + if not pt: + return False + page.mouse.click(pt["x"], pt["y"]) + page.wait_for_timeout(SETTLE_MS * 3) + return True + + +def set_heating_dialog(page: Page, open_button_suffix: str, *level_regexes: str) -> None: + """Open a SelectHeatingDialog cascade (water-heating method or main-heating + controls) via the button at FP+`open_button_suffix`, set DropDownList1..N to + the options matching each regex (JS, fires the cascade), then coordinate-OK. + e.g. water (combi): set_heating_dialog(page, MH..ButtonWaterHeatingCode, + 'From Space Heating', 'From the primary heating system'); + control 2106: set_heating_dialog(page, ..ButtonMainHeatingControls, + '^Boilers', '^Standard', 'CBE Programmer, room thermostat and TRVs').""" + page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", f"{FP}{open_button_suffix}") + page.wait_for_timeout(SETTLE_MS * 3) + for i, rx in enumerate(level_regexes, start=1): + page.evaluate( + """(a)=>{const s=document.getElementById(a[0]);if(!s)return; + const o=[...s.options].find(o=>new RegExp(a[1],'i').test(o.text)); + if(o){s.value=o.value;s.dispatchEvent(new Event('change',{bubbles:true}));}}""", + [f"{HEATING_DIALOG}DropDownList{i}", rx], + ) + page.wait_for_timeout(SETTLE_MS * 4) # AutoPostBack re-renders the next level + dialog_commit(page, "OK") + + +def select_boiler(page: Page, query: str, ref: str) -> str: + """Set the main-heating boiler to PCDB `ref` via the search dialog. `query` + is a full-text search (brand/model) that surfaces `ref` on page 1. TWO-STEP + commit: click the result ROW (highlights it) then the top 'Select' span.""" + page.evaluate("(id)=>{const e=document.getElementById(id); if(e)e.click();}", f"{MH1}ButtonBoilerReference") + page.wait_for_selector("[id*='SelectBoilerDialog_TextBoxFullSearch']", state="visible", timeout=10_000) + page.locator("[id*='SelectBoilerDialog_TextBoxFullSearch']").click(force=True) + page.keyboard.type(query) + page.locator("[id*='SelectBoilerDialog_HyperLinkActionBoilerSearch']").click(force=True) + page.wait_for_timeout(SETTLE_MS * 4) + pt = page.evaluate( + """(ref)=>{const t=document.querySelector("[id*=SelectBoilerDialog] [id*=GridView]");if(!t)return null; + for(const r of t.querySelectorAll('tr')){const tds=[...r.querySelectorAll('td')]; + if(tds.length>2 && tds[2].innerText.replace(/\\s+/g,' ').trim()===ref){ + const v=tds.find(td=>td.offsetParent&&td.getClientRects().length); + if(v){const c=v.getBoundingClientRect();return {x:c.x+c.width/2,y:c.y+c.height/2};}}}return null;}""", + ref, + ) + if not pt: + return "row-not-found" + page.mouse.click(pt["x"], pt["y"]) # highlight the row + page.wait_for_timeout(SETTLE_MS) + page.locator("[id*=SelectBoilerDialog] span", has_text="Select").filter( + visible=True + ).first.click(force=True, timeout=8_000) + page.wait_for_timeout(SETTLE_MS * 3) + return "ok" + + +def clear_hot_water_cylinder(page: Page, panel: str = "TabContainer_TabPanelWaterHeating_") -> None: + """Uncheck the HW cylinder (combi). The checkbox won't uncheck via + .uncheck()/commit (a regular-boiler default re-asserts it); JS-set + checked=false + fire click + change drives the AutoPostBack.""" + cid = f"{FP}{panel}CheckBoxHotWaterCylinder" + if not page.locator(f"#{cid}").is_checked(): + return + try: + with page.expect_navigation(wait_until="load", timeout=8_000): + page.evaluate( + """(id)=>{const c=document.getElementById(id);if(c){c.checked=false; + c.dispatchEvent(new Event('click',{bubbles:true})); + c.dispatchEvent(new Event('change',{bubbles:true}));}}""", + cid, + ) + except PlaywrightTimeoutError: + page.wait_for_timeout(2_000) diff --git a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py index 715fb053d..aa6575437 100644 --- a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py +++ b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py @@ -256,6 +256,99 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = ( cert_num="0215-2818-7357-9703-2145", sap_score=61, ), + # UPRN 100021943298 → cert 0498-6963-7270-0822-1980. SAP-Schema-16.1 — a + # reduced-field (RdSAP-shaped) ground-floor FLAT, band B (1900-1929), solid + # brick with internal insulation, mains-gas COMBI (PCDB 10328 Vaillant Ecotec + # Pro 28), double glazed, TFA 78 m². New-schema coverage: mapped via the + # dedicated `from_sap_schema_16_1` → `_normalize_sap_schema_16_x` → + # `from_rdsap_schema_17_1`. Lodged 72 (old 2012 assessment); engine 76. + # Built in Elmhurst RdSAP10 on the mapped inputs (evidence saved: + # elmhurst_summary.pdf / elmhurst_worksheet.pdf): Elmhurst worksheet SAP 75 — + # engine within ~1 (continuous 75.70 vs 75), the tightest agreement in the + # cohort because this is reduced-field (both sides use RdSAP age-band/ + # description U-values, not measured). Boiler set to the cert's exact PCDB + # 10328 via the search dialog; control 2106 (CBE programmer + room stat + + # TRVs); water from primary (combi, no cylinder). The ~0.7 residual is minor + # RdSAP defaults (wall thickness 280 vs 300 mm, internal-insulation thickness + # Unknown vs lodged 50 mm). PINNED to the observed 76 — mapping untuned. + RealCertExpectation( + schema="SAP-Schema-16.1", + sample="uprn_100021943298", + cert_num="0498-6963-7270-0822-1980", + sap_score=76, + ), + # UPRN 10096028301 → cert 0390-3321-6060-2405-7985. SAP-Schema-19.1.0 — a + # FULL-SAP ground-floor FLAT (band M 2023+, mains-gas COMBI Ideal Logic Combi + # PCDB 17929, decentralised MEV, measured AP50 3.5, TFA 73 m²). New-schema + # coverage: mapped via the dedicated `from_sap_schema_19_1_0` (parses with the + # 17.1 dataclass, delegates to from_sap_schema_17_1). Lodged 85; engine 82. + # Built in Elmhurst RdSAP10 on the mapped inputs (evidence saved: + # elmhurst_summary.pdf / elmhurst_worksheet.pdf): Elmhurst worksheet SAP 82 — + # engine EXACTLY matches (82.11 vs 82), and engine-on-Elmhurst's-own-parsed- + # inputs is 82.16 ≈ 82, so the calculator is confirmed faithful on identical + # inputs. Boiler set to the cert's exact PCDB 17929 via the search dialog; + # control 2106 (CBE); water from primary (combi); MEV on; AP50 Blower Door 3.5. + # The −3 vs lodged 85 is the documented full-SAP→RdSAP gap: the engine uses the + # cert's MEASURED U (wall 0.24 / floor 0.13, WORSE than RdSAP band-M defaults) + # + MEV priced as extract loss not heat recovery. PINNED to the observed 82 — + # mapping untuned; engine == Elmhurst. + RealCertExpectation( + schema="SAP-Schema-19.1.0", + sample="uprn_10096028301", + cert_num="0390-3321-6060-2405-7985", + sap_score=82, + ), + # UPRN 44012843 → cert 0775-2898-6628-9594-8005. SAP-Schema-16.3 — a + # reduced-field (RdSAP-shaped) ground-floor FLAT, band K (2007-2011), cavity + # insulated, mains-gas REGULAR boiler (PCDB 9895 Ideal icos HE15) + cylinder, + # double glazed, TFA 55 m². New-schema coverage via the dedicated + # `from_sap_schema_16_3` → `_normalize_sap_schema_16_x` → from_rdsap_schema_17_1. + # Lodged 81; engine 79. Built in Elmhurst RdSAP10 on the mapped inputs + # (evidence saved: elmhurst_summary.pdf / elmhurst_worksheet.pdf): Elmhurst + # worksheet SAP 78 — engine within ~1 (continuous 78.82 vs 78), and engine-on- + # Elmhurst's-own-parsed-inputs is 78.48 ≈ 78 → calculator confirmed faithful. + # Boiler set to the cert's exact PCDB 9895 via the search dialog; control 2106 + # (CBE); regular boiler + cylinder (Large, foam, 50 mm), water from primary. + # The −2 vs lodged 81 is reduced-field RdSAP defaults. PINNED to the observed + # 79 — mapping untuned. + RealCertExpectation( + schema="SAP-Schema-16.3", + sample="uprn_44012843", + cert_num="0775-2898-6628-9594-8005", + sap_score=79, + ), + # UPRN 10023444324 → cert 8501-5064-6739-1407-0163. SAP-Schema-17.0 — a + # FULL-SAP ground-floor FLAT (band M, mains-gas COMBI Ideal Logic+ combi 35 + # PCDB 16211, decentralised MEV, measured AP50 3.2, lodged party wall 6.43 m, + # TFA 50 m²). Full-SAP shape ≡ 17.1, dispatched via from_sap_schema_17_1. + # Lodged 82; engine 80. Built in Elmhurst RdSAP10 on the mapped inputs + # (evidence saved: elmhurst_summary.pdf / elmhurst_worksheet.pdf): Elmhurst + # worksheet SAP 80 — engine EXACTLY matches (80.13 vs 80); engine-on-Elmhurst's- + # own-parsed-inputs 81.03 ≈ 80 → calculator faithful. Boiler set to the cert's + # exact PCDB 16211 via the search dialog; control 2106 (CBE); water from primary + # (combi); MEV on; AP50 Blower Door 3.2; party wall 6.43 m entered. The −2 vs + # lodged 82 is the documented full-SAP→RdSAP gap (measured U 0.2/0.1 + MEV + # extract loss). PINNED to the observed 80 — mapping untuned; engine == Elmhurst. + RealCertExpectation( + schema="SAP-Schema-17.0", + sample="uprn_10023444324", + cert_num="8501-5064-6739-1407-0163", + sap_score=80, + ), + # UPRN 10023444320 → cert 0868-6045-7331-4376-0914. SAP-Schema-17.0 — FULL-SAP + # MID-FLOOR FLAT (sibling of 10023444324, same block / combi PCDB 16211 / MEV), + # band M, no party wall, floor + roof both to other dwellings, TFA 50.68 m². + # Lodged 81; engine 81. Built in Elmhurst RdSAP10 (evidence saved): Elmhurst + # worksheet 82 — engine within ~1 (81.38 vs 82); engine-on-Elmhurst-inputs 82.46 + # ≈ 82 → calculator faithful. Boiler PCDB 16211 via search; control 2106 (CBE); + # water from primary (combi); MEV on; AP50 Blower Door 3.09; mid-floor (floor = + # another dwelling below). PINNED to the observed 81 — mapping untuned. + RealCertExpectation( + schema="SAP-Schema-17.0", + sample="uprn_10023444320", + cert_num="0868-6045-7331-4376-0914", + sap_score=81, + ), ) From 37b0a384257a1d408353d2d6130831c229ac6e04 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 10:59:51 +0000 Subject: [PATCH 010/340] add more test cases --- .../expand-sap-accuracy-corpus/worklist.md | 2 +- .../uprn_10090844932/elmhurst_summary.pdf | Bin 0 -> 63489 bytes .../uprn_10090844932/elmhurst_worksheet.pdf | Bin 0 -> 44321 bytes scripts/hyde/elmhurst_download.py | 2 +- .../test_real_cert_sap_accuracy.py | 15 +++++++++++++++ 5 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-20.0.0/uprn_10090844932/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-20.0.0/uprn_10090844932/elmhurst_worksheet.pdf diff --git a/.claude/skills/expand-sap-accuracy-corpus/worklist.md b/.claude/skills/expand-sap-accuracy-corpus/worklist.md index 0c3318044..e4db5aa55 100644 --- a/.claude/skills/expand-sap-accuracy-corpus/worklist.md +++ b/.claude/skills/expand-sap-accuracy-corpus/worklist.md @@ -58,7 +58,7 @@ Skip the 🚩 MVHR / 🚩 heat-pump-fuel and ⛔ sparse certs. - [x] 🔧 10023444324 — SAP-17.0 (full-SAP g/f FLAT, band M, combi PCDB 16211, MEV, AP50 3.2, party wall 6.43) · eng 80 / elm 80 (lodged 82) · PINNED engine 80. Built in Elmhurst (boiler 16211 via search, control CBE/2106, water from primary, MEV on, AP50 Blower Door 3.2, party wall 6.43). Engine EXACTLY matches Elmhurst worksheet (80.13 vs 80); engine-on-Elmhurst-inputs 81.03 ≈ 80 → calculator faithful. −2 vs lodged = full-SAP→RdSAP residual (measured U + MEV). No mapper change beyond coverage. - [⚠] 10092970673 — SAP-17.0 · eng 77 / lodged 86 · 🚩 MVHR idx 500418 not credited (flagged) - [⚠] 10094601287 — SAP-18.0.0 · eng 80 / lodged 84 · 🚩 MVHR idx 500230 not credited (flagged) -- [ ] 10090844932 — RdSAP-20.0.0 · eng 78 / lodged 78 +- [x] 10090844932 — RdSAP-20.0.0 (end-terrace HOUSE 2-storey, band L, combi PCDB 10327, party wall 4.93, 250mm loft) · eng 78 / elm 77 (lodged 78) · PINNED engine 78. Built in Elmhurst (House, boiler 10327 via search, control CBE/2106, water from primary, party wall 4.93 filled). Within ~1 (78.13 vs 77); engine-on-Elmhurst-inputs 77.24 ≈ 77 → faithful. - [⛔] 10090844948 — SAP-16.3 · NOT MAPPABLE (ValueError: RdSapSchema17_1: missing required field ) - [ ] 100090182288 — SAP-16.2 · eng 71 / lodged 71 - [⚠] 10093114053 — SAP-17.0 · eng 93 / lodged 79 · 🚩 heat-pump fuel-39 (flagged) diff --git a/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-20.0.0/uprn_10090844932/elmhurst_summary.pdf b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-20.0.0/uprn_10090844932/elmhurst_summary.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e6335fd0fea43d1f74a843783a29432ddfa6b2bc GIT binary patch literal 63489 zcmeFa1yo$mw%{GX-3cziJGcjz;1;ZdHSX>ZEO>BtCuoocf@|>L7Tn!kgM?}R@9Mkr z-S6EyYi7-w*PV4zb7Zf=Vw-Rw=-rI(|6Lh zvNdH^(l<4BBxi%%R2CF8wlRVfL3%p-o1%XdVHUG>ws9ioV3svEa@1nwda6Lq%JWCU z#_}J@AFb&8r5X0WZ{~xdfrYW56SIo5fz#6nq^%)cGXu?yoXp7CSXh{)jLl8WoXA<( zIUs#m**d7$=^Gj|iyOO`8yYK1iZY9uJ2@&DJBZm@+u7O}L&n0xte|fVX@`Yb!raQq z*nwHXO5e#?+}P08$e3Bq*v1snFeeuqkD#EVlY_CoHPVN`Lmfm%Rms@jUr?_GE4aQL zxCJ?5SYfQ&AB2+(_H-S%^?uM378R{RGa?~2aC#<+7fgjrlKqlor&aSs&eWtu@nlOD zNOMzJZEF9c*E6=ucY!a3CQHh{UaP5_kfikG`TxzQbG1i-|F`?*rKt+LZ4)J08*zAW zlIS+m`$kVNXN*+yAt5iZ%_mX(<7x!u5FAiQ^Wj%sV)el`P)vi-$~ukne!HWI()B4OZFDJQSm)vY4FU$R*>8aJl-o34@(}8X_t&g+? zUT2bq^M!49pkW`d?McMs)T&bUsd?LAw2=DGE|*uV`Cp9nq`Xg6rKxWc_vhX=-+hSc z{;i}+lZ{M}v0O@P?x(XrLfEb2WQ4T>tK&`g zdiyzM3gTsp7Xe;=k~il9hVD6OVTN zoz*9V(S=9;ux=acV;)e;2u8C7fnBr155jcFf3U=0Vu$CwfMD(Kr~MT8PuKqsUSbeC zputM3oY~tlF=B75#0@(>YCR-u!FGGuj3lW}^zn|bw@0NQGo<+UZo}!xc!!LUr73OL zC9A%j3tHQql|_*9-OelT#jWnk4+rh%F>R`gDCVnJ3Q6iqZsY6?7Z%+9(Zu9m$IWke zeRRPz*|J&T@}IeG3tM+*@mKScEG-Fou-#uazn!)N9($%a6_g@*#dd4b3->8h%ss!gkU4^e;~M@PlVov;3)fjFn~(lv54d|4I=;M5 z=(6TZ>)Kv29(0nK*G6Xv~-rmmm3Orn4lk_YdoQBa%D+&7&cVdT}ia&`!_@iz`xI*FMlYcOH(lM=zmPRU_Nd%I4b>Pw)Ai504jb z4#Z;`X=(U+X}Ljo%#Xn$MyCY1&(ZP1w*GuGWM%OEkcUk{U2YVM3tGsgLFws-CygGawv=ZWfPMQ zz{<^L$?dU;YfZ3b#`FyFMR&D(cRAsr=eS{il;C|;kIOdN?p^KhBf|s8z39AOTvZl# z0IT9#q};kgg)M<( zi2l$SgTa!DSqxkk*V<~lbi|+B$7Ne&r)9THC$G1p<#d2?l#vKj z>XyDM7#0E))*ld4@xEZ+kk}%5SoDVBi0LP*86cP`X30-YLIfA+GVQk^AXECQn_%yr z%8+GJ*aCcq_!}rxlo#<-MCR{trUMlg$>Q||WbL~~FPuWymt>0Ow9oTlXs)NNb zcfQzEVpPq?)iZ_{ZAc;TH|+z;-7xG(fk_X(F52*L7zckF3ky3uk%o{MK>66{Eqsn+ zK6S^3%1c-b7Baba{J9%($kr{uuwnY@ZAYH!mY8 zypg_{i@`MUpyu1s+&M|I$e!faO@%UUmmm=%_EhO(U!A(9tT~QV$RwKO@Vh((OVGpa zK(lg$yq^-hG$@bfVblSbut<1TqOCFa=AatONmW$+`^j9WNaBPpo& z3%yA{Yx-|91+%zJ0)f70VXXv49VN)}*AK{Ksam7URYE9WvJ}Ydt=Oi?HT#0XRx2W7 zZbv&m79u9x7gy6xPP@gY=8v}LYG`-q7am&IG)vDufO3}gSbcHh^~_#p(3gxm-X3>V znvxz^uPR$}vWYQvZ1;D@RU!(sIfODFR##x@@5dp6PgdnhMvq?{OoMx_ZBOaU=bI_= z0WG#k!0$ck2cPv<0KEFECV{FHosHt--%HTX&h+28gdZ(BC#dYNoOI^IK;}WnBzXM0 z`%rlQ@E}34sJEa4-emjYe#CTpqFhV8G}F!~;o-JFz@W`2hRv7ZfJrOpZupq(#cAlG zH)@I^-#63hBSE5y;#W-)F^ra}Ee2riOTWcbMo{3@5y^=xC=w|63prJ5cCb_+;f#sj zw?#dj5mZ@NYg7`tC@J_}=}hC@G2>4M=1kqwv7$grLrtp80vDrS6rh!sB*N+r_l=yPu~rLFEdsy)+!dh6ZJVVpV%( za`uQP(R5SXaHD$Z&vn7eSFDsUa#d2E@-93dn6|M#be_-t438axRNNhAE)i{J`4IEF zmtKP_)h+)38S)#tBYqcl*rkD~Nt01%FexQl6J62gJO*@@m*MIKz#jZ65yDB>CF#gq z$?wYC4uCy$M3UWBd*m>52x>Oe`8F^xHgly|$AX1;Pc+q7Y~+5qPFM#d{c-x@B&t!O z1*4n8u37o}(OEExYq0$F7qO*UtGgesHePNOoSrDFc@)yq4wP&cUiNjL1iVKh zXM|(&fof6ug=6!0r6JG5Kqij?I@e9%w`p5^juOp8L3Ad&!rd47kz@7^!c+LVqtemO z_(YjyTJ;azx^t~JVDIvC=jT6e)zJB$aEFnT(T`+mP zup9T5jnF75ll^c2;Xsux3(w>s>^GYm#FjzjLtD=npT2Y*j#rz_+V%~4-KIdDz3*Ah z6ZpEsr%7YP+w#8JDye_<8!yk6Zyq)&r@4J+NEXd@#PAP>Zr-bV=ur@rf<6+A z>umJ<5-AhEmcI+UQy7U0I;6o`U(GKld>DH0X9p)b55d)3o<^bpgn13yDsi##Bs)sN z*XW%%39SiUiJ~4~qV8?IEcV%W!_nGgc|7~3v1Njn3p9RP53zZzr8?L@ceTix*~iH; z9}5x*bM_>GZt0v6kNY3X%0W&y>oNGH`9#8KH)Mu;h5WaX_F&PJq#nb($;A=0Y#@N;+AOfu04&9=~nWTe}vp)p-7*Z2)i@P zUlAiG%Q@fLH8SJd)01|}xD8@prf-7#&i3ToCnb|Yyx$FXwi?cXZ(Mw-0{I z&&5-xL_B6k6!Nb6nY$+rM|o~ z0|`nLeSRy_j%>rfE3!@7d}rq5{JtI!$Myd4{WOB_0@oR3KDIYz4SWD*0#4-C#;Rg7 zVS2`wQIF{Dm?1jObf$#;x|>}%;yW^u(t zw^&%-v+afndb{UL^1x*LCEL6pO!Ok>vdMQNT3t6GH!bh6wC#0fMKfiCFAV+Hc)O}1 z;$?h)9oML3!M8!clZYBCV3n!6bjESxktE)y? zbS;~olHQceBhv4DkDXJ8L430tdu{RzGTvW-yZ)I@9|RtQg{BHA<)VpA(7 z6#1I$Sng509;2Yj@^Q^q<Lb9=Ne$JhD5~{tIw)Imp|oGnWOB``F~9H4Bde z*si3=#D8a|xeR<%zY?RX$^?$h=lI)X{GcbEw`&A5BI9AMDip#5_L!JD-i|)LKgnJFm6*Bg%P!l_7!^3FbQv*?maz5(wzu-HxQf zdx_YHGuOIQk_OdCDF6JdjxjFlDo+`uJ zxmDO(D)IBdrzGOhGDkt|&%nex&>QfN2j;Z4`E^{&tW>v2!w>lR(GqN6&nW`4dN0c2 z43GdgWv}=i!+qbcI>tFxct5vGf8fWDF50a)|zGmJvR21`+(}I4?Zc$JEky-tDJXAin%m6oQM`dJ6gBz zJi(Jg@~14+;0~8&B>0VlnMjKF=_f7>=B&?C#Qruw@|2yA_KVLzO;D6L@8GylStLNfYt@*`hM!@`Pr?!q0(0ZQBx~^+F@Fg>rm-7$5p+G-0v*O ztNxA7_aYJVHe!leuT;c-9Ecm6xkoMco{ZTPCR{^v9EcT=StZEKZxuE>DrsAYzS;?J z7aDlf&KfS+Xl*H`n&W+d%m=FpGh{z~?F*CqnRB<}TP_PO1cPlOij|muiF=1E(Ee`j z;Lkrt`c5}0i{`$nZS2vW1(aq6g|7ugT48H)8tXz_3kE_1v;yr2VqXUN9G;de9R6Z` z!KajdHez`r1frh}R-%HF+~S7fW~ykFt4R!Jt3@qu0fJ0C{rQ3q%|GS;vJ6! zP7$3`rGzodc6a>XU;%xSn@b_R@3HAl5}#9#A!3j_MZc{6>FOfv-#*JbLCNrOWE+eO zh`C)7j7Nzhs~KXJ6B5;Gxll3YT16gm3hZq-;u=xr{H)a<>|H;4!URZs6E``?g z3<=`f{*xQ{PhM`0f6qM)@l^k7?rGM4;-2Q_;AHtb_cXi{(R9LOruA&Re3eHtZ;0!3 z3!g%|QB>TF@&S)c>;m;bBBMrN%QcdEh3k8qCZuA~O705Z7Hp~y-{G)e_(Uo`=gl-# zB)vj>{u~8i950OH`rcm*?f2j#yW^wB^#feY=NGU@ICM(c!oL5Q>F^Ip$=3=iVdv99 zMnWg#d6?A@P)7!=^XXtBp`)oMiNAa)R%vY^y-3?cE9=tu7KapLA+uS4lZxRuq_O>x zBh$x^6Q`=Vv0@@2w_=->jg3u!a_s_dVdth%Y9;==P|BnwSLHEw~au;aPF)(nO$M&;M zP6S8Ic%xJ7+Yz)Ou4AVJ`U96z$S0*ZhXN5SA=yJCiQmC1X?W8c3ODs0P`4a8R+jZc zh%vc%aW_V|D~U@z0D!ZImtWg>!_}(EyOB0w$|ze#+A03KSR_;v=Z8HwSf9kC zSA59J_^1ZVa#u#ZrxU8lAyLxi7{1X8pd1_;>LV&?dY?0cdpq5_ zzI@|@D@x`Lbep!g98+%mDGRey;_7m1LU%xjf&}B$(z3q3e%9;%b^ z^sV`0n`xC~&LiOXTL} zX5!YCCRbBydF06Ua-%bH{it0nw1E73-#_*=+`>{xPJNpTyqoFdt~(1BL zO{Ixjc-Ug=$wb^ot|H5?Pc(!8;EC zpjh&ls9k0SzElYK2;US6>*OY!l$w;?s#l5t8nXnk0qz=J?>97Th8#A)h64NF50ndd z>^UhLjJx0=HMB+oud#|I7k*M(cH5%h}3!^@$lX zk1|$v2+WCeMSUh;yNyQ&qK8KbY%M2!&FIC4e7Jj*c;!EiD2Q3yRDvwyZL_A(7HzBc zyGXaw(zv6BHKU)RZU0bLEB>*g_?FjPIbMv2*||N@glf(3I9G3!Oq?isAH@B0Pfv|zt*i}af;lPT=A42Sou4^0%A6?X^Hp7zaYG#tt5^xjPl<|PW zBLh*bSh$#Y-s^Lh+9phd@p2kx&3k)${dz{)5uvR(?yOt45!y5=ze|sfj>0ccJQ*5h zuWGLb!{t`9l^pW?r+oqFNN^!Ow6wIcvN9N*%x;N0ON-X9Zl;|#xC zJgrd1fchf7`3>(DhGxcMVp^A$6a;m7c#869mXYV?=e`rOfuubtgC{Cj-WXgiD*={V zzdVRfMw5ldI(Pes$@j&DN?oZbrY8RO7*V~PVu0(oySpvUde6?vadvtI=XhXcHIn|; z+Elg#T#_^_-2-q1a?3lT?ugw-a2piaW)~NTxH>tw*!`RwADftp2>+a%)y8)f;L+77 zAoGYi5yylUjqS5TNlrL1&lgKGONzd}3l; z=&civPI0$z$*+qqQ3C`0)RMx#6$S5$noF(9783DH;b|kjS&)&weY3kMY(gXps9`s? zE-Mw#UOX^&(cB{}`X2b5{D+B=)Mv$;gD;9Chs2}tCma{f7vZSktq-?(;G~f)P!COI zW8>W1_zTm*bRdQ%>gA<1st&(*iOSG9sWUj;l1ESNv$E17!c(p04XKp~|>f1@R0Lwd8j zvEsHlz8P9|w$_n*F!-kS=R_0P2MrJn&zE%IwWoohX{VjhA!290PgR+xHg9os`UmDl zj^E0`r*a-(J(U{25EgbWU!A=JRhEZ>FJC%)`sdn*NxdU@M0)@jFMC&qN2OMgjmlr5 zeag*#9^CodXGPqIO-^0`4mPv5K`+|5|u0u4>O!E^S<_X^qaAQcrA{9*XjlB#jG zzIMj*{=qIBNhTQB9N!-ZpGH?O3}1E<@&(wp_b-qvAccSO| z+54)iYAUY_+uT#%lbQA0#yDP{_eifSQu+ZvAe})4I3jn@^}UoQ${ZdJ3c@dBdC4A(D43UDAQSlsXm-b9P&>uN zHmu;sZ^E?n#VPAP(S6hG7~>Vj&hC~VYpV^y%bwAZUi1j$)#g}BtRj=j+7zIy0jSsv z9DO72XQ2=ycByhTsc2N!uwZPI+@*8{J6o37R((|qOfsw50EMh5O~C9jmRv_n)h z#HY^=HuO^)R8o*}~jvlN%MLQi#>`!RfW( zWxDCaMAk3w`sIUx<&-1S^L_5}h*$0n?wW+Dvl_FMl`NoGf2rn>fZ#MfD@>d~DSEJ6 zM0mmld*?gK&h8&lRVdm_(?4fmLNy_y@$;A14Pa46_{`y`@(qv#mWs4W0g|(*W_cpK z!J1dru_ob%sgj^*KibAUL*p2so@#gndfUUwC0iF$&mDo?4Y^ zSTwUp{wy>mlW7F-+N(2-+u(SVWBhM^B8Vx^p^FLzLk2JI8-cbtWy>MfPriHBf3#@X!z&q%IdxS!`FP6&7z-_ zlvU*teZztMck^{UC6Eaz=p!6kbBBgnLW0aN<+BkYGNNFKJnyYiz#^>oyW9E0ZQN>m z53RI4?~_bGdPK>WcHFlBhrpwBQ|*}%GKXIW)@fj2hP>+x!N;1_EM!7y-7F3a7;{St z)^CogZD9i6OYUFb=ITCC`$ zlTn%T-p%ZlcvEOy1-zS@(z76PDhj^9iSNGGb%nED_#n(5BSY+5EJ&zmF~r5i#bXm= zi(9`?=s4oqi;BzPz~7lpJjFovMDAq~hk~5&r5caaoq(vGLXooTjL7?;D_K8P@sG1B zid@s_YQ^YMSS#C6JUY^k2`G4Yc*C$Tqa+!wj9`X-z{&#l8ukx`20OveCWiYPD>sYo z)Hp~w>AR%mN7(DGUw%v_s=s2DQfJfm5qkEPnwn@(%_I4*S(i5a?Kzz3MAmAz?`2HoanTwa#K3%nk zTcF-h#~|_H=GEc+*)Pmf3Yf?y0!wTC;%G((o1 zu6bM|nPW9qrf4xaz2g2&SmLU#R*Kv8DKUsf`=w@MxLA5}@+TMp=e4{G7A8OO&r8+u zs=Mh;!9cg})+Ip}LX7QkiS?-+Gb6XI-MHm7^YzMsUVo!zKZYRjV5+#3*R1Y$TnDkW zK_+{*k};X71%+k%o2wzyl1@C!$DA}QX}jj;zueu$`;FP!%bOP#LeCsV)53KQBXlAn zP>(p<>Wm*`48ZqpQ9d5KhYt=tNW0D@*MisltaUtf_JKz^^+hw|3!)FDscB)B2SaJR zv%Cc1C?~z+gZBr;6VF?{&5~mWi@v5*B3ivz%INZigM(SqcX4%&!)=!`^mIJnnR%D^ z{LMJe7g7Q|3Z3Y=V)i7;1oL80suqkL3!x?|I0)06KnE7$Y8kzDwK%OV>cU?X01ARfp_E41rS_0S6Em=_g` zWpo6|ZtLpkyxadiA-wN@cy@jHy1;!wh#bo|Uo=)pwu8wtF(FY@%27)zW=f?5aZjM? zDvy4D83XriCFsP)Jy!h;$q4r#0i`&4slxlwunt_XRludjonuGG$T`$IG0?xzA+`Ly zYZL(wMZG9d2LRr~EMAjONlQ*xCOT;$aGaRvR3RrPxnyFb&z~LtpbS=q)5*|ba>8%s z?879D+CJC_*Q77z8g_8Bj{~Z>1w~KTxmUPJp}AZ^tWUHHbc|&@9GsbngS7m$`i>6K z)QE#Ta`S5Yc{^AHxN3Sjct>m%GG^Q6*6zS}zXr3_8PiY!dq;agDo5PlPyL~xC~#t_ zKyklG${96HWzjd2?QsqGT;=ni&|d?lO8%*4fB%v?1z@J_z$JM7)-pHqLulG7s4qOvAZbR3zoyX*Vg zTN|6v%wB(e=n9jdo!PciQ??pY>Ww={dInYv-CtctuY0O7tp z*%9^>Y}-T01 zBt&N<8Nd1*ES~*L!WZPxyNVDs47@NcagB1zNY~Bdf^+{|QC%Itg27}fWTTw zyyUAxwtM8*hUNS{y2LKH#r*X0PC*hk)`F3g!{=^;cZiSDLMu8L85I?^cx?$zt%zD5 zsOGH#GWL_4XLgExJi!kG_elj_eC>OlQDLol^-lj<&&Y^MfDu($<{fA;0mKU%IBUGU zzsv!6#Qkwz-eOgkK-H4>D0%m)E~8ZziSN_dnM07OWMXJBLm^|8=uqvhZd4}Ued4Bl zU>;?Go=$&TtvxjzzunI9F%Szk_AIn7g7M6L^+DZBHCKi^BOJ0L&n`=m#r-{d(c z$jLc5u{bd~%gN90DLF>hd3IJ68uR<>D~2gk*WA)-9N(Nh2IOL@r0zk(n_7OuVF3P zw6L^nvJ*5*HS+!qdakOjDI>!a2hr0=XGjCdHTV|G?BI_Z0 zHu^sL#<{oCV9@^FK|r7A=v$Y4b$k7ooJ?;wvt+rg73B6Vkz`I}6~~I)@^Tv|CmdRZ za79%$_1)baP-~Ii32YK73XiFQS$sx<0Ntx}-DKUnO8#~|ZL8A6MmW+yr>$SV++`iZ zG_U3oFZC&MB;bNl!ox#DLw$XlytXDwEv!+?KA#{6W})NB%PXLwq2b}}eCl;z4uNg_02ZVubAI5mtewb&M z(6)W-PB`+bZqCaNYb~zC{7Lyi{8_xe@0zPq_8X5>68>zb-&{;U6?q-+!*D5n=IMdX ziIHAz4j$DiWm0~4YnEA?6-hO|kqQuy&i|lLgKCu;1wOPjdN=xA$1V1G1%U zvp)B!-SgHjs|4R6?pf}SCgukx7>|yR8|2x}%%5LtaM)McPnUztSAD1fSY>h?D8K&0&1o5l~N+e=k%nHVeeN>^Yw#lpGNo^#^^ z2^^`=`R#TQqcu`eAj?I1s(nSoyn?*Uq$>1Iu}+ho0@RH!jg+S&)H9kIj)X^=yswHr zSJ*jXv;W$?hAd%u%00!>Hu8)h%NRuPGcxAmn|hwWl+|eSsoL&$d6sXlC`aoth}MjY zCCct)OdQ3Bl1xgoNF5%uKb}oe+~O-*wh;o$51a?GzkS0#{Mb2mg#!0d26Sd?;Sd^_ z(s1TQ9)=Y5;{Klcam#4?d$IKgGRREll=OY{@WeR#A^qnPGR&Gy&l+Ge$zZyojPXsr zZCP_EaVRoQ?1;9ya_Bbsx&_&@?+xqjUnR;GzNTNzV7c~Ak$Dr`x8|)sWS_?fX|y_i z_I6U4FdP-#jU4_w+T_v5C-qDOZ6e@A{%l5nnY=AQm!MxV6*%^Oi~`?}qVI={@0EmI zFvc_95EUijt`An>evC|*b)wh|;Ab6L0l}ihux0h+k@57Qb}jrZYh{2ou0b!i2nS*Z|h!i4fO-aDzrUs&k}Mz7*p0SoF?!b;;k z0v8kIEc|TXSuMWe9_$1HY$Jd?2uRMQ%c4|!XPd~rp>>q*dXU*ib;y94hKf$@%(4Y_ z>D;$&ej62J}vmIQmP-(83A#>apug7RQ{sv^;_~8 zI7^#;v*bN}I?*>PuNYElJUyp5OSiBqidg%-B%t2StWZ87Hvo8AX-~uz3{irXvVdRF zc@W3no(`KAM^axv-J)vu6$iP=T>Zj9Y3W9n_>OzS^9%+P($1{A!IZSN$FN@^v>du2 z^b|Twg)r6BlA>J}1WF+OdI29FTz1AbtosA@R-k9{xkM2>e*_Vkn$=!)z zpQFOQ)mA9mS0sPObEC$x6=rB*aucIlO^{kREU+v3{Qc(!jzM3|yV+2;goGHxw;^5o zY%4Imy>*wtdM@Zk3l09Y8oI!wq%>rgd@5Q~mhl00EwDNvf$F_p+hJ1;iM3Yi&EDSE zj0o#bSy{3CFCQMp=g}a)Pz-*Edn;u_Y}{2H2yqwTP3^m|fqbzDE%uBOx!zFWM6yx7 z@l+bSCC5PYHTBW;(Y89s{}I-2yP^Lyr$sCM*mMB&4-8Zh8GQ3AofUd}@L1T$HJ;!9 z_1Or4a##B>s0u}fuz^>O(RrwGt}SZeolZ#X5}6`}jO^5{;0y#AU=UFE88i+F3fbZ-3pymhpu}G$5|Q z-rUyI2_d}nTYjyM_ikbx8GlY!!0rg1U~~D>bq{|FAA>rDAZVNXxM6rWICvc;ZxItTZ1Du4Nc_XYUD!>3;E0sz#`dNu3!5IOl=02`>Wi7{ z`}^kNn2?L0>a(5QT{r=&vQqmYhy#3yH1ot#yokqTKQWA5hb221C?+P>0~bWJ)GZd) zSiHh5dfkmK?LVAftX`jay8m>}DNsR(f(sVP=tG+Tf;S}(A z5KO16j!8m7vVyCDk5uxtzpvfi#symomETJ0*dg24vvs0o#`#D8px*)Q9o`Z<#qV}v zjQds=4@nG7eO6ZLk^nN3H;jz4v!mnETrMlM4ny)owX3eY*Kc|Jsr^DSGs?Sb4In!^ zeYUZpJ+Cdu(?*Yu_V7px_7ca?*%Q_Al7OV;WYeUigf3OC&4jp%?d$Ri3d6&L5Em12 zUR)Dy*#C)p8p;+y*&--g1Z9h$Y!Q?#g0e+Wwg}1=LD?cGTLfi`pllJ8ErPN| zP__ul7D3q}C|d+&i=b=~lr4g?MNqZ~$`(P{A}Ct~WsCmXvqjwh-sbJU%ocI{lg(Qw zTLfi`pllH|ZV@zYks~y25j1WQG;R?zZV@zY5j1WQG;R?zZqa{w!~fGMG;R?zZV@zY z5j1WQG;R?zZV@zY5j1Ym|I=}cc>X>2^uNp&asCtcG?XoZvPDp~2+9^g*&--g1Z9h$ zY!Q?#g0e+Wwg}1=LD?cGTLfi`pllJ8ErPN|P__ul7D3q}C|d+&i=b=~lr4g?MNqZ~ z$`<{%XN!3Mz0KQynJwb_C!4oWwg}1=LD?cGTLfi`plnetlr4g?MNqZ~$`(P{A}Ct~ zWs9I}5#n+GV_7-K>1I6!zjR-dKi*>($`(P{A}Ct~Ws9I}5tJ>0vPJ(-vqh{d|DJ#P zUxtgg|A~JZ3Kv1)A}Cx0g^QqY5fmmF#ZKSQm|5J|#oW+XSyGf))ZEEY$=E^6*4ob2#@NP*oQGLK-`bd2n&+SF-EwmA z3JN+pIT-6(BYg;b7dxVeErQegNPTPBWPPbe&T1hLoq1VwYQ!&fG5+!+hL*W+b$o4g zpgLm!8K=Z+YPFvjKgnex5@0w%7+5cJ$1_s6&rQ7Mdw5a~Y^t)ZxA>NiCp7051C@`t z4;5isK9XdCrb^y>lvt5jna3|aHh$Z&{`S)2b@^5Y<@)w(o6L()Rx+cPJY-`-)r{Vc z!LzNPFwvggHKs(n^LVZBJM!%$UZFgc6z;=1d#!tWCDVM zvd>Ge-cq}(*qU%MEX)UZ=mVz2nlg4OrNsKhYJMsS@pE4KaAWk^-csi z5BQd;{vf<`(gUaOb+!~t9O`KeWnsoqOln5U0%wboP#cUM3g+BL1~C0{oUqyiT;D{0 zDtoJb2UD!sKw*i0lbnPsE|_yfHUWb#Z-&ybPb`;w9~s<@;>KF+%lGU49<9c*>Q$Ev z24W^+jLEFFQWKf@DF?xT0TY)4v3gbg1lG4CJxX#}kvH(g?CLKJ8p(V-bJka8SqK2* zf`DBEX|atC1v+e!EC7WLD&j3@AdX#uX^MiMFd!Te*5A|<2Pr0+TQy~cJUp!`ro#Ph z*=lC)7B+%vBrD%tDUiH4ff|XGoaS!TSRw4okrlZ?U01{JqeSg2-{AMGK7pAIhLKJN z*B5BN@Ix!E7m$o?jQ%7;kesc71rjUU-%GZHt&Nkov7@1bxt)`(1M}1AAF?UpV6JcV zM%32Ih*`o)-_((uo$ZgCqN28LT8tdryyT4B+#KYr92_j^ zHgIycGiFwGa5h$b%KXuZ1PimMzN7I|r_6uT;X4~cTO)HDQ)ZyKjfjn-`CrZ@%pDw^ z#LV;^o<<|5|ED}F8ylpgk&~ID78^GY`P0G9$w|)1$@RyLDxldYFnOTArVs$*_ z1O=HtC>mH88$R8ahWya}xGl{}{-@yyLOy?1(0>ifC}D2pWDL>zR{BoH;>HlIYs@TX zY-8$V2GN!r?0;i_WM~dK%=2Ki?$g^1sz#VUgL&LyGv*ua;&bS+1f+Ei6JsF8))z>@ zQyq?-ADAyY5amkMT1i_q#D48UX#GvNxI@NqZiwTsxel6kk_!5-{ z3t{@`_Y~Mn+*ra1pN|vlcpK9{MTZ>T3=7%Q;G_*oj^k&>V;14R%Vg$#Gc3sevm~L` z(j{&LxoKQYb3;h}H+y5|0WJ<7Z=~P}(QO3Goz+y}!zQ@`2TzWRudDQr5uR;7(ITVZ zyIf$9M3C_Hu2b=0_~PDI%~X6BaEW>1@V5U#tVc)G8i{^fb$^iB$t3Ac*<}zNF1`ny zd{67ol4FyZVVQ2*Q~uBuF2E)`E)9-ZB$13b$)uXK$XKX=_5v$%WjwQ4 zK?f!I+h7!GFCPy%m}US&wt}+@QWNu)RHIR4-tv2xW>CH{B4VDAk9X1Vw8XrS51}f> z46U@=`=SqO8x8dINeRKWo_1c2vB(uLKSrGJ$@Pw3ymdeJ)j@@07CC+nbFV6vS}*tQ z0Os548=u#kWTBWxZ4ogphf3iHt|cV-M3CY$WYm#>246GsOV4s_oR45o?_ zT!(BpzE8r4{%M0DW;`fZUnxi zV_x0w?{^d_ELANDnO`Wo-3toIh2pQ3o!W!?P4ir3$nQ4n)PC|_@*_y-w_AVETBM-? zGoG4I-jgvmRGM}>X$s{B#rC~WZKGA+&d+R=Bu*>M`g|buy{tB>fQ?;!ZxG{$+IV3^ zTC*n`>b0cKSUoEUZkwM!NgEDl?Sp=tqP#{GK?<(#yDDQ6t6#*HKMSKv@>2xa+&I|X z6xq(X1Lg}v=;&e!Is_?p(nVl%=_7BX1Jz*ZC~%_9h^i!X3IK(C@51-i?~0{z$mq7L zzLx2Tg$;zx^-0r5G{$;i5Y_L5aZVT`4m--j4Z-Y)b6_JZXzgH?Zx3LZoxd2i(f9)U z4(-^#dOBsZXHdqixZ)T`>{go0(pk`7JckUG@p09$THvj)7o7$I%01-EXlTq)XIHN7cSPRJ^I}}=}80X4`BCR;sNy;JAp)p zBJXQ4wGZC}&$>E;#gLRg1T4>L`6vP&1lSK+>D0w*Nz3W1v|X$unvi}mjf48h>Feca zau<*M2ej3UlblmY`r=!95IjpJnvwBCGZFYqVv*h9V5^*E{C8+yvRP0Pp~B7Ow>Nx3 zY07?o6hOc6a|k9R7Tsv{oftf^6r5JYXO~fKcVnw^cBDe(LX8^JfCCZG;16NSx$6X@k1czM+^jkAiwaTJOuuBwVh9oAl_H;qjUyw3iMzX{VniO^qrF}Wygi0Z$3WqTR#$MAO0iOT#oqK<;(DZd1=T(p-2&eIrcxse< zYf%t9Ui%$K&=0K9pYYN%)zhOUHs!zUe9_{1Bwrngk@E5*TC*oI3(%`J`^SUaZb-&n ztP8})dfcDD$GTV$9ptEj)>H;xf}`-*m_UPkZvv>&%_sr_4(!}eZhJ&uxuphToD~Jm z+46{TreM8nQh0Pe#T6xpVw2B+sNJPr;GJXk&S0n{^yktPrFP$<8Bg+J;k~|K zRCPAGcdaJ|aIdX^I zqzY|yBZrV65_pd^GOP7HS4JXuF_8RiQm=B!Q(%`xcO=tUo#=}1(hfoPEipgV{DW75 z;m;EPQQdafv@K%L7CSHpVm+e4sAn&AsWokM>|MItM7VS)-We)dy)B73$?!>g%zFOu zAxg)z#Mqi*SC{)dD%e>gMXj%Am%;fMiCZcd7un~*P*AZDP9au3`m47II_{lh<=`SL zp`STqBr_-TJt&i)^j%f;jvUmT=9Rhy8BNdE&f-m zkEa)`{Cn01WSQ_E)(1QJlQjgfP5##U`0I;X{+ac`&GUEGN1wxMZmgDbz}XripJ;iH za*5$qcA%=ORw6HD6AAVzvMNk>YB@uNrFqT0Wa|*ELT){d3=X1Hjz1Lwvo0k!RqX9s zB*Y!&EH@M$UpmBQ9t8ubFjuly+QnmSlDd+oHrO}scPi-+f{A{ateXHYS=NYiS?2Bq zRH?xF#k%PiLPKaa?_NlTNFx}sGbnh5Mif&D29xnzwyVP$b zW>paAp;ToKIvNF0FyVI&C*FhkDy zkR(WwoRJ_oBgiN)s=Vvw| zKPT+j71E`X*abZsp@8DMMuc&z!c)W4$sDnL8WW?f@)5VI+VJIy5S0a3=1TG_Txgb@ z`52d4t9&9~N(j|uyg^|%D^^Qjqac&dB(35y9;b{*}lg8F|OLvP-eme3^ zv+~MB%PS=AGi6qwH^~}ox>j#DuAO0n)|)vgyB9Rx@c~$lg}&KKONDsZD4MFgH)s#I z<3cuh2D7ulQ%B`z-7-JxX01NzY7rw{E4nQ>{l2}vMB*X8RGO*{zA0y}B>j}=4BV&U z>n9*6s1w?Qd-2xN)6S#kv>wAE+j9|{KzOvS1;5!>AA^VNxG;n5LA2J-c@3sYnU5U8@`nIrTF2Ps-ue6iI-HjxlUhi%Ka z{e?Fk$4)Ir7Gq`(vNF!4~lFgVB`rkVlFhqpY7{iJwAjIJ2mRO;Q;k#^h%E> z*tGTYj%XK-j67HPY+{n6rj0mB{y>24o2=;GXlcamcq=cRNpiAHvR7HrfaC%X6C%8* zzUU;_&}&B~z`$RW4qKZK{a;1KR?l`~iBSod`!xwCoG7ylrh8az3$ZL9Fn!t%Lkhl< zqvX-SMx7q*ad$v6l%m*+x7nqV>UHAQQRo4-)FFeyW3i+PZFlUVAlWDB$}-ZpaqTB} zH#r4cqUKzO3l!^%+QLgxJT_*f-HL5tLbMVcbAy`9vy0J;{iWkJSqr5_DG9_wE1wF@ zCO9i6-zSI|J@Xs@AMR0;lN#!huj zg2&gF`%84x1vx3?9;fU`(S}q=u*Cxv7T>Zi|FW-Kofkzhd~E`QcTUsb?E9gz!DEow zx&vQLQ{>BcEHfv>-kd)T#DO6{?bOF6_zC7>=8!|L7uu+qxn3EUp`A&ycUxv7$H|su z?j5(=#reyR^^Gznj22mH&@di<+mBC^bbB=VZ20_qe?Pj*Do{K@>N{OBfN%n*^FrI( zXu*4)UhCvMrKZI-GnG7}BKvP z;CQ;Qvg>6~1thOAS+(}--O8-AU(UAgGf69w%j52yIwdmm`w~xoZ#Y{q^WG^R_wFQ_ zUE{kNXr-rFEOK?2YIHuLE5>z(!j6no6~f&d@u-Y`?nr>SU@`m zd&v3n#i0K>DOMT*@%$?Zy!>PJs8!6FNOI_%zRJ?#qnCb*Cwd{QO{LlzvyIVZ7U`nv zs&q>7_rq&SUOwLtz6~Oi6yHrthrAaDm!EMl-0m=Z0dJWLWn;KZyxfNJaR|8 zDdL*@F=L(7q>wT4N$C}q`sm_t?#!csKw&}VUAsXo?bFq!plmLn0|TX$bW@>Tw7d>KpDen7Rsk+;hgazhONlVMPZ$2-zPi%96_JZ+t4$d&U z-wV@l>HB+g#`b*|0o*fAtN#=zJiknj{~vpV=T}Da-w6~x@L$%7A`JXXF#Rr2{v7wO z690oh;o;%?cY&hY>$n6byRt`CHL`F#XyZ=B$yF9hG`I5{mt`N_6=i`t6vxVNF_(`; z57%xFpqSWDjEtQO(B2;nKQc`l^ysjTmSPC7Qv>Ng#)I*(b**&1lS~7))V5N|hm)H2 z>j^N1<-UWZoZ7X^9VmxA(Zc z-eAr7A-&xAOg7R^!6DwBqs{H|ehqdX;qcD`8&-pnohbi|*XT(_olSm`y#w%a z_=_1M6aLVeOiFZmY_$6XUMvPHzCQ3!*|D!fz@HKvYJc*L+aMYa2f{I8NdXWDD8@2@tUIS z<&+|-^miwH&cu;XCn{DZbvYx8{#a^iRcL&V$8y-J1ui}NIpfZHbOEq^1jz|_9?-DL zRVT2FP3O!xTx)JH7iBajK5&1`x$YsjPlwE*z1f7}5$8ad3q!ZSs}TR=2+sD0-(Ff< zyvH>|)e5VAI%&*q4eNEiH{oX~Q-T%gSlw+YKrK2&JaYcT=xR+s-X&Tq_dI0hwvVi- zkN@Zx=X?j@Y&_)P7fC6|OM?VHS(AT0>ystG+)}`PMkXCzOna;TwL_nr=IHU1VH9pM z%5x(FF&R6n*Zh8L*D5ckb0>`Wn0>11EN|3$DQ`1+icBT_nJjBVcy$Dn6C6`ps(n3b%mVu&4 zWl}%#&70hra!P|gjv&yPMYoCa13@|mhE8;K$dwC$(x}N0$N|dT-U1_@TQcnYs1XDm zu@g=Xm}0lqn4yyhRD*>}2J8JW1fxMVYFcvS3KaPpXd7yCiry%8!P6KGI*?bXqgNj@ z$p!h(^MmSop#$9u^JNiYQlOO$ySZZk2fuZ z;_w~CVj5=Uu(_+#w8o_LIyTxbejeE-eyLj?&y22NWPRa74`y5NtZbKWJ8EdsVguL{ z9jsvc%_gxpHGQ2~#EaqqWQ{}OTGv>Qn%@}ej9!ush@UXj~=d;6Ee!Ys~$3^7tQ^n;XjiZ_Hh))8jD(C!4{rTiIqVvG|E6 zCYBEGIs?Zv;v)IE36KhsQn(VloAgIq!eChiA!q$p2Mbb+_|tW~X~MjI9%htZ$Ug$< zZ&Jo_q~zKrN*UERMlR?J1PJx33fTv7BOg4!(;fnfe{SBWRF(z~WKum{Q?C$RSh~N- zQTvJRxvPi`t>>Kww)neD?V=}3)@5@H;Dk1Ra??#^N_a8518 zC{Fq2TzmVX{j3&wEa*dNl9aX#uX-j|gJ4zr)@Zaz@E|}VK>iCMBsMiy+&YQI1?wx| ze3uF3lT}y1rcX)BP-A(xWLhpBQK|ORNx<(xB8_e-&1`xwx=M+Yl@)HK4(zkt_j*pm z#4{)LT!JM=ii6Zy96)SQzsm7IEsYLub2sg7whwyK^H~tcdN$Bk)+l*aB0Qr(mTGaB zHfAlTYH=rU$)3`fVVNOs)Ba&E0*%>+XS-E6BP1P=d3O$ot=%P8HY7>ovCj45tR6qr zO|9+P9P#QX0Ht<597yRN!4;6+i#Iv%Tt&|}a2A%_%3hD%_0@Lef9-bYqHf3f;bsYB zK!aGY9jE2(wBsYtJ6qT+@z~P?j1RM-Ix2vhPp2Nbu?}?PjPQ?+%Wr7Jv&no zcAL^KGhqeiQyDk(cOr@tH$HD`c*rQ^3{Tl+zfEVAzFBO|X;i?+Nj)`}BJ}NmqBP;r zkO$f3RrU^k*@2z+i^0TN_EmnQD z5#*{A`-$D4$qkeCuDQF#6swDqQG6V7zcH!q z4cXV{QYUI#&;3q^4d? z?>*eAq^7oNrW*wgPUe0QC>Sm}-ICOw(mx`s*d>9#uSmLQYIn>3?cP?r5|v(8e}kvQ z&k%~KLXmH0r-dr)M5W{T%33mohC5(kwGQoyfm)wS#EM4KNbmW6=lzhrf%X@|1ogKy zt=A#vgO1zVmi}r?xTh(FX@nyRrvZ-l5#U~qFY+&}d~<6gg?ekjavNx8?v`4x>RAp% zF}%fM1zILkAj&tunwM+Xs7{0zTn0?M`zUs5aw30W&rcV2cC4jTQ9(A&MKI%3QQpoV zb!IFcj3i6|wm+%ej}pe{6rzJaQktA(UufbNUSjhaB}8h`Se=u>z6nJI32Q?!E_CZ* z3t?@InTbk?nkNRl#72HtuEdSuC~a943~UAd8@pnnUk&W|H+B^aUdN!n z*zWg!FhS^56!#kj<%RKF3DMs%L0;&UCjAZLfnR~wFkYTtas2Q7__%qms=bE6p*&Y6 z+;8K-c=@kxZCu0n_^v|F>lh4vy%rdZTi~A$Gr{lm z2|xv|&mWwdhv)jcaBg0~>vI5y!hc1@zl{fn3c&uYW;Yj8TYF0vfQSfyOVifJ^4FSk lX*_#&b%E>G<^m8C1Gu`Gy14!MN5FZmf=~c6v#grjzW@xE?GOL} literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-20.0.0/uprn_10090844932/elmhurst_worksheet.pdf b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-20.0.0/uprn_10090844932/elmhurst_worksheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0effc7b72e51ac47cb31c5dbcf9c73e867b90655 GIT binary patch literal 44321 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 literal 0 HcmV?d00001 diff --git a/scripts/hyde/elmhurst_download.py b/scripts/hyde/elmhurst_download.py index 418d9dbe0..3ef550111 100644 --- a/scripts/hyde/elmhurst_download.py +++ b/scripts/hyde/elmhurst_download.py @@ -28,7 +28,7 @@ SESSION_DIR = HERE / ".elmhurst-session" SAMPLE_DIR = ( HERE.parent.parent / "backend/epc_api/json_samples/real_life_examples" - / "SAP-Schema-17.0/uprn_10023444320" + / "RdSAP-Schema-20.0.0/uprn_10090844932" ) ASSESSMENT_GUID = "B44A0DB4-4C08-4241-B818-86F060172105" diff --git a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py index aa6575437..7deb44494 100644 --- a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py +++ b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py @@ -349,6 +349,21 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = ( cert_num="0868-6045-7331-4376-0914", sap_score=81, ), + # UPRN 10090844932 → cert 0646-3008-6208-0619-6204. RdSAP-Schema-20.0.0 — + # END-TERRACE HOUSE, 2-storey, band L (2012-2022), cavity insulated, pitched + # roof 250 mm loft insulation, mains-gas COMBI (PCDB 10327 Vaillant Ecotec Plus + # 831), double glazed, lodged party wall 4.93 m, TFA 74.7 m². Native RdSAP + # (from_rdsap_schema_20_0_0). Lodged 78; engine 78. Built in Elmhurst RdSAP10 + # (evidence saved): Elmhurst worksheet 77 — engine within ~1 (78.13 vs 77); + # engine-on-Elmhurst-inputs 77.24 ≈ 77 → calculator faithful. Boiler PCDB 10327 + # via search; control 2106 (CBE); water from primary (combi); party wall 4.93 m + # (filled cavity). PINNED to the observed 78 — mapping untuned. + RealCertExpectation( + schema="RdSAP-Schema-20.0.0", + sample="uprn_10090844932", + cert_num="0646-3008-6208-0619-6204", + sap_score=78, + ), ) From 360e069386e0205499e63d0bdfc4c858ec183fa0 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 11:07:30 +0000 Subject: [PATCH 011/340] hubspot etl 20 in parrell --- deployment/terraform/lambda/hubspot_deal_etl/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/terraform/lambda/hubspot_deal_etl/variables.tf b/deployment/terraform/lambda/hubspot_deal_etl/variables.tf index 84f0e5673..189822f04 100644 --- a/deployment/terraform/lambda/hubspot_deal_etl/variables.tf +++ b/deployment/terraform/lambda/hubspot_deal_etl/variables.tf @@ -19,7 +19,7 @@ variable "image_digest" { variable "maximum_concurrency" { type = number - default = 2 + default = 20 description = "Maximum number of concurrent Lambda invocations from SQS (2-1000). null = no limit." } From 240d7b102513e8d9430d760b0add12d24184a20e Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:07:32 +0000 Subject: [PATCH 012/340] =?UTF-8?q?Resolve=20a=20landlord=20mains-gas=20ov?= =?UTF-8?q?erride=20to=20the=20primary=20fuel=20code=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../property_overlays/main_fuel_overlay.py | 22 +++++++++++++++++++ tests/domain/epc/test_main_fuel_overlay.py | 19 ++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 domain/epc/property_overlays/main_fuel_overlay.py create mode 100644 tests/domain/epc/test_main_fuel_overlay.py diff --git a/domain/epc/property_overlays/main_fuel_overlay.py b/domain/epc/property_overlays/main_fuel_overlay.py new file mode 100644 index 000000000..4d721865b --- /dev/null +++ b/domain/epc/property_overlays/main_fuel_overlay.py @@ -0,0 +1,22 @@ +"""Map a Landlord-Override main-fuel value to a heating Simulation Overlay. + +A main-fuel value is one canonical gov-EPC `main_fuel` description ("mains gas", +"electricity", …). The calculator reads the dwelling's primary fuel from +`main_heating_details[0].main_fuel_type` as the RdSAP **int code**, so the +overlay decomposes the value into that code and emits a whole-dwelling +`HeatingOverlay` (fuel is not a per-building-part attribute, so `building_part` +is ignored). Codes follow the modern RdSAP-20/21 `(not community)` family the +gov-EPC API baseline uses. Unresolvable values produce no overlay. +""" + +from __future__ import annotations + +from typing import Optional + +from domain.modelling.simulation import EpcSimulation + + +def fuel_overlay_for( + main_fuel_value: str, building_part: int +) -> Optional[EpcSimulation]: + raise NotImplementedError diff --git a/tests/domain/epc/test_main_fuel_overlay.py b/tests/domain/epc/test_main_fuel_overlay.py new file mode 100644 index 000000000..96072c1de --- /dev/null +++ b/tests/domain/epc/test_main_fuel_overlay.py @@ -0,0 +1,19 @@ +"""The Landlord-Override main-fuel → heating Simulation Overlay mapping. + +A main-fuel value resolves to the RdSAP `main_fuel_type` int code the calculator +reads from the dwelling's primary heating system; the overlay is whole-dwelling. +""" + +from __future__ import annotations + +from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for + + +def test_mains_gas_overlays_the_primary_fuel() -> None: + # Act + simulation = fuel_overlay_for("mains gas", 0) + + # Assert — mains gas (not community) is RdSAP main_fuel code 26. + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.main_fuel_type == 26 From 0e85da15079e9f3d9f99c8e35a9d3eb64593ed5b Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:15:54 +0000 Subject: [PATCH 013/340] =?UTF-8?q?Resolve=20a=20landlord=20mains-gas=20ov?= =?UTF-8?q?erride=20to=20the=20primary=20fuel=20code=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../property_overlays/main_fuel_overlay.py | 12 +- scripts/fill_domna_addresses.py | 353 ++++++++++++++++++ scripts/finalise_to_property_table.py | 331 ++++++++++++++++ scripts/hyde_epc_schema_versions.py | 159 ++++++++ scripts/lisasrequest/compare_to_ara.py | 169 +++++++++ .../lisasrequest/durkan_805_schema_check.py | 142 +++++++ scripts/lisasrequest/fill_domna_address.py | 200 ++++++++++ .../finalise_to_property_table.py | 111 ++++++ .../resolve_uprns_for_finaliser.py | 212 +++++++++++ scripts/lisasrequest/review_flags.py | 135 +++++++ scripts/resolve_uprns_for_finaliser.py | 328 ++++++++++++++++ 11 files changed, 2150 insertions(+), 2 deletions(-) create mode 100644 scripts/fill_domna_addresses.py create mode 100644 scripts/finalise_to_property_table.py create mode 100644 scripts/hyde_epc_schema_versions.py create mode 100644 scripts/lisasrequest/compare_to_ara.py create mode 100644 scripts/lisasrequest/durkan_805_schema_check.py create mode 100644 scripts/lisasrequest/fill_domna_address.py create mode 100644 scripts/lisasrequest/finalise_to_property_table.py create mode 100644 scripts/lisasrequest/resolve_uprns_for_finaliser.py create mode 100644 scripts/lisasrequest/review_flags.py create mode 100644 scripts/resolve_uprns_for_finaliser.py diff --git a/domain/epc/property_overlays/main_fuel_overlay.py b/domain/epc/property_overlays/main_fuel_overlay.py index 4d721865b..9118c0c9e 100644 --- a/domain/epc/property_overlays/main_fuel_overlay.py +++ b/domain/epc/property_overlays/main_fuel_overlay.py @@ -13,10 +13,18 @@ from __future__ import annotations from typing import Optional -from domain.modelling.simulation import EpcSimulation +from domain.modelling.simulation import EpcSimulation, HeatingOverlay + +# RdSAP-20/21 `main_fuel` `(not community)` codes (epc_codes.csv `main_fuel`). +_FUEL_CODES: dict[str, int] = { + "mains gas": 26, +} def fuel_overlay_for( main_fuel_value: str, building_part: int ) -> Optional[EpcSimulation]: - raise NotImplementedError + code = _FUEL_CODES.get(main_fuel_value) + if code is None: + return None + return EpcSimulation(heating=HeatingOverlay(main_fuel_type=code)) diff --git a/scripts/fill_domna_addresses.py b/scripts/fill_domna_addresses.py new file mode 100644 index 000000000..e4a7e18b8 --- /dev/null +++ b/scripts/fill_domna_addresses.py @@ -0,0 +1,353 @@ +"""Fill the DOMNA columns in the AddressProfilingResults spreadsheet. + +Input: scripts/manipulation(2).xlsx, sheet "AddressProfilingResults", columns + Organisation Reference | UPRN | DOMNA FOUND UPRN | DOMNA FOUND ADDRESS | Address | Postcode + +Per-row rule ("if there's a UPRN in the UPRN column we're done"): + + * UPRN present AND Address present -> nothing to do (already sorted). + * UPRN present AND Address missing -> reverse-lookup the address from the UPRN + via the EPC API -> DOMNA FOUND ADDRESS. + * UPRN missing AND Address present -> resolve a UPRN from address + postcode + (EPC API, then Ordnance Survey) -> writes + DOMNA FOUND UPRN + DOMNA FOUND ADDRESS. + * not resolvable -> marked "NOT FOUND" and listed in the + unresolved report. + +Relaxed matching (this batch only — production AddressMatch is untouched): the +landlord writes flats as "3 GLADYS COURT" while EPC stores "Flat 3 Gladys +Court", which the production matcher hard-rejects. So per address we try several +query variants — the full string, just the first comma-segment, and a +"Flat ..." form — and keep the best-scoring, unambiguous match. The unit +number must still match exactly (AddressMatch zeroes mismatched numbers), so a +wrong-unit match stays unlikely. Each fill carries its score + source so you can +spot-check (DOMNA SCORE / DOMNA SOURCE). + +Rows that already have a DOMNA FOUND UPRN are skipped (idempotent / resumable). + + python -m scripts.fill_domna_addresses + python -m scripts.fill_domna_addresses --limit 200 # smoke test first N + +Keys come from backend/.env (OPEN_EPC_API_TOKEN, ORDNANCE_SURVEY_API_KEY). Run +from the worktree root (import trap). +""" + +from __future__ import annotations + +import argparse +import os +import re +import sys +from pathlib import Path +from typing import Optional + +import pandas as pd + +_REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(_REPO_ROOT)) # worktree root first — avoid the import trap + +from backend.address2UPRN.main import get_epc_data_with_postcode # noqa: E402 +from backend.address2UPRN.scoring import all_uprns_match, rank_address_similarity # noqa: E402 +from backend.ordnanceSurvey.helpers import ( # noqa: E402 + lookup_os_places, + os_places_results_to_dataframe, +) +from backend.utils.addressMatch import AddressMatch # noqa: E402 +from datatypes.epc.search import EpcSearchResult # noqa: E402 +from infrastructure.epc_client.epc_client_service import EpcClientService # noqa: E402 +from scripts.resolve_uprns_for_finaliser import clean_postcode, load_keys # noqa: E402 + +SHEET = "AddressProfilingResults" +UPRN_COL = "UPRN" +ADDRESS_COL = "Address" +POSTCODE_COL = "Postcode" +REF_COL = "Organisation Reference" +FOUND_UPRN_COL = "DOMNA FOUND UPRN" +FOUND_ADDRESS_COL = "DOMNA FOUND ADDRESS" +SCORE_COL = "DOMNA SCORE" +SOURCE_COL = "DOMNA SOURCE" +NOT_FOUND = "NOT FOUND" + +# EPC matches are tight (short addresses) so we hold the production 0.7 bar; OS +# addresses carry more trailing tokens, so a slightly lower bar is appropriate. +EPC_THRESHOLD = 0.7 +OS_THRESHOLD = 0.6 + +_DEFAULT_IN = _REPO_ROOT / "scripts" / "manipulation(2).xlsx" +_DEFAULT_OUT = _REPO_ROOT / "scripts" / "manipulation_filled.xlsx" +_DEFAULT_UNRESOLVED = _REPO_ROOT / "scripts" / "manipulation_unresolved.csv" + +# A resolved hit: (uprn, matched_address, score, source). +Hit = tuple[str, str, float, str] + + +def cell_str(value: object) -> str: + """Coerce a spreadsheet cell to a trimmed string ("" for NaN/None).""" + if value is None: + return "" + text = str(value).strip() + return "" if text.lower() == "nan" else text + + +def parse_uprn_cell(value: object) -> Optional[int]: + """Read a UPRN cell that pandas loaded as float64 back into an int.""" + text = cell_str(value) + if not text: + return None + try: + return int(float(text)) + except ValueError: + return None + + +def address_variants(address: str) -> list[str]: + """Query forms to try for one input address, best-discriminating first. + + Landlord flats read "3 GLADYS COURT, 260 REIGATE ROAD" but EPC stores + "Flat 3 Gladys Court"; the full string scores low (extra tokens) and the + bare "3 ..." trips the flat guard. So we also try the first comma-segment + and a "Flat " form. + """ + address = address.strip() + first = address.split(",")[0].strip() + variants = [address, first] + if re.match(r"^\d", first): # starts with a unit/house number + variants.append("Flat " + first) + variants.append("Flat " + address) + seen: set[str] = set() + out: list[str] = [] + for v in variants: + key = v.lower() + if v and key not in seen: + seen.add(key) + out.append(v) + return out + + +def resolve_epc_relaxed( + address: str, + postcode_clean: str, + epc_cache: dict[str, pd.DataFrame], + threshold: float = EPC_THRESHOLD, +) -> Optional[Hit]: + """Best unambiguous EPC match across the address variants (cached per postcode).""" + epc_df = epc_cache.get(postcode_clean) + if epc_df is None: + epc_df = get_epc_data_with_postcode(postcode=postcode_clean) + epc_cache[postcode_clean] = epc_df + if epc_df.empty: + return None + + best: Optional[Hit] = None + for variant in address_variants(address): + scored = rank_address_similarity(epc_df, user_address=variant) + if scored.empty: + continue + score = float(scored.iloc[0]["lexiscore"]) + if best is not None and score <= best[2]: + continue + top_rank = scored[scored["lexirank"] == 1] + # rank-1 rows must agree on one UPRN, else it's ambiguous — skip. + if not all_uprns_match(top_rank, top_rank.iloc[0]["uprn"]): + continue + uprn = str(top_rank.iloc[0]["uprn"]) + if uprn in ("", "nan"): + continue + best = (uprn, str(scored.iloc[0]["address"]), score, "epc") + + return best if best is not None and best[2] >= threshold else None + + +def resolve_os_relaxed( + address: str, + postcode_clean: str, + os_api_key: str, + os_cache: dict[str, pd.DataFrame], + threshold: float = OS_THRESHOLD, +) -> Optional[Hit]: + """Best OS Places match across the address variants (cached per postcode).""" + places_df = os_cache.get(postcode_clean) + if places_df is None: + response = lookup_os_places(postcode_clean, os_api_key) + if response.get("status") == 200 and "data" in response: + places_df = os_places_results_to_dataframe(response["data"]) + else: + places_df = pd.DataFrame() + os_cache[postcode_clean] = places_df + if places_df.empty or "ADDRESS" not in places_df.columns: + return None + + records: list[dict[str, object]] = places_df.to_dict(orient="records") + best: Optional[Hit] = None + for variant in address_variants(address): + for rec in records: + candidate = str(rec.get("ADDRESS", "")) + score = AddressMatch.score(variant, candidate) + if best is None or score > best[2]: + best = (str(rec.get("UPRN", "")), candidate, score, "ordnance_survey") + return best if best is not None and best[2] >= threshold else None + + +def _address_from_search(result: EpcSearchResult) -> str: + parts = [ + result.address_line_1, + result.address_line_2, + result.address_line_3, + result.address_line_4, + result.post_town, + ] + return ", ".join(p.strip() for p in parts if p and p.strip()) + + +def reverse_address_from_uprn( + uprn: int, + postcode_clean: str, + service: EpcClientService, + search_cache: dict[str, list[EpcSearchResult]], +) -> Optional[str]: + """Find the EPC address for a known UPRN by searching its postcode (cached).""" + results = search_cache.get(postcode_clean) + if results is None: + results = service.search_by_postcode(postcode_clean) + search_cache[postcode_clean] = results + for result in results: + if result.uprn is not None and int(result.uprn) == uprn: + return _address_from_search(result) + return None + + +def fill(df: pd.DataFrame, *, os_api_key: Optional[str]) -> list[dict[str, str]]: + """Fill the DOMNA columns in place. Returns the unresolved rows.""" + for col in (FOUND_UPRN_COL, FOUND_ADDRESS_COL, SCORE_COL, SOURCE_COL): + if col not in df.columns: + df[col] = "" + df[FOUND_UPRN_COL] = df[FOUND_UPRN_COL].astype("object") + df[FOUND_ADDRESS_COL] = df[FOUND_ADDRESS_COL].astype("object") + + token = os.environ.get("OPEN_EPC_API_TOKEN") + service = EpcClientService(auth_token=token) if token else None + epc_cache: dict[str, pd.DataFrame] = {} + os_cache: dict[str, pd.DataFrame] = {} + search_cache: dict[str, list[EpcSearchResult]] = {} + + unresolved: list[dict[str, str]] = [] + resolved_uprn = resolved_addr = skipped = 0 + total = len(df) + + for n, idx in enumerate(df.index, start=1): + ref = cell_str(df.at[idx, REF_COL]) + given_uprn = parse_uprn_cell(df.at[idx, UPRN_COL]) + address = cell_str(df.at[idx, ADDRESS_COL]) + postcode_raw = cell_str(df.at[idx, POSTCODE_COL]) + postcode_clean = clean_postcode(postcode_raw) + + # Already sorted (UPRN + address) or already filled by a prior run. + if given_uprn is not None and address: + skipped += 1 + continue + if cell_str(df.at[idx, FOUND_UPRN_COL]) and cell_str(df.at[idx, FOUND_UPRN_COL]) != NOT_FOUND: + skipped += 1 + continue + + def mark_not_found(reason: str) -> None: + df.at[idx, FOUND_UPRN_COL] = NOT_FOUND if given_uprn is None else "" + df.at[idx, FOUND_ADDRESS_COL] = NOT_FOUND + df.at[idx, SOURCE_COL] = "not_found" + unresolved.append( + { + "Organisation Reference": ref, + "reason": reason, + "Address": address, + "Postcode": postcode_raw, + } + ) + + # Case B — UPRN present, address missing: reverse-lookup the address. + if given_uprn is not None and not address: + found: Optional[str] = None + if service is not None and postcode_clean: + try: + found = reverse_address_from_uprn( + given_uprn, postcode_clean, service, search_cache + ) + except Exception as exc: + print(f" reverse failed {ref} {given_uprn}: {exc}") + if found: + df.at[idx, FOUND_ADDRESS_COL] = found + df.at[idx, SOURCE_COL] = "epc_reverse" + resolved_addr += 1 + else: + mark_not_found("no address for UPRN") + continue + + # Case A — no UPRN, has address: resolve a UPRN. + if given_uprn is None and address: + if not postcode_clean: + mark_not_found("no postcode") + continue + hit: Optional[Hit] = None + if token: + try: + hit = resolve_epc_relaxed(address, postcode_clean, epc_cache) + except Exception as exc: + print(f" EPC failed {ref} {postcode_clean}: {exc}") + if hit is None and os_api_key: + try: + hit = resolve_os_relaxed(address, postcode_clean, os_api_key, os_cache) + except Exception as exc: + print(f" OS failed {ref} {postcode_clean}: {exc}") + if hit is not None: + uprn, matched, score, source = hit + df.at[idx, FOUND_UPRN_COL] = uprn + df.at[idx, FOUND_ADDRESS_COL] = matched + df.at[idx, SCORE_COL] = round(score, 4) + df.at[idx, SOURCE_COL] = source + resolved_uprn += 1 + else: + mark_not_found("no UPRN match") + if n % 100 == 0: + print( + f"[{n}/{total}] resolved={resolved_uprn} not_found={len(unresolved)}" + ) + continue + + # Case C — neither a UPRN nor an address. + mark_not_found("no UPRN and no address") + + print( + f"\nResolved {resolved_uprn} UPRNs, {resolved_addr} addresses; " + f"{skipped} already sorted/done; {len(unresolved)} not found." + ) + return unresolved + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--in", dest="inp", type=Path, default=_DEFAULT_IN) + parser.add_argument("--out", type=Path, default=_DEFAULT_OUT) + parser.add_argument("--unresolved", type=Path, default=_DEFAULT_UNRESOLVED) + parser.add_argument("--limit", type=int, default=None, help="process first N rows") + return parser.parse_args() + + +def main() -> int: + args = _parse_args() + _epc_token, os_api_key = load_keys() + + df = pd.read_excel(args.inp, sheet_name=SHEET) + if args.limit is not None: + df = df.head(args.limit).copy() + print(f"Loaded {len(df)} rows from {args.inp} [{SHEET}]") + + unresolved = fill(df, os_api_key=os_api_key) + + df.to_excel(args.out, sheet_name=SHEET, index=False) + print(f"Wrote filled sheet -> {args.out}") + if unresolved: + pd.DataFrame(unresolved).to_csv(args.unresolved, index=False) + print(f"Wrote {len(unresolved)} unresolved rows -> {args.unresolved}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/finalise_to_property_table.py b/scripts/finalise_to_property_table.py new file mode 100644 index 000000000..751e8c59a --- /dev/null +++ b/scripts/finalise_to_property_table.py @@ -0,0 +1,331 @@ +"""Insert resolved manipulation_filled rows into the FE-owned ``property`` table. + +Reuses the bulk_upload_finaliser's own row->PropertyIdentityInsert mapping +(``BulkUploadFinaliserOrchestrator._row_to_insert``) and the same +``PropertyPostgresRepository.insert_all`` the Lambda uses, so a row inserted here +is identical to one the real finaliser would write. The status-writer / +property_overrides path is skipped — this only populates ``property`` (no +BulkUpload task needed). + +Insert is ON CONFLICT (portfolio_id, uprn) DO NOTHING, so re-running is safe. + + # one random resolved row into portfolio 796, then read it back + python -m scripts.finalise_to_property_table --portfolio 796 --one + + # a specific Organisation Reference + python -m scripts.finalise_to_property_table --portfolio 796 --ref 56100000101 + + # the whole sheet (resolved rows only by default; --include-unmatched to add + # null-UPRN rows too) + python -m scripts.finalise_to_property_table --portfolio 796 --all + +Postgres target comes from the root .env (POSTGRES_*). Run from the worktree root. +""" + +from __future__ import annotations + +import argparse +import os +import sys +from pathlib import Path +from typing import Optional + +import pandas as pd +from dotenv import load_dotenv +from sqlmodel import select + +_REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(_REPO_ROOT)) # worktree root first — avoid the import trap + +from infrastructure.postgres.config import PostgresConfig # noqa: E402 +from infrastructure.postgres.engine import commit_scope, make_engine, make_session # noqa: E402 +from infrastructure.postgres.property_table import PropertyRow # noqa: E402 +from orchestration.bulk_upload_finaliser_orchestrator import ( # noqa: E402 + BulkUploadFinaliserOrchestrator, +) +from repositories.property.property_postgres_repository import ( # noqa: E402 + PropertyPostgresRepository, +) +from repositories.property.property_repository import PropertyIdentityInsert # noqa: E402 +from scripts.fill_domna_addresses import ( # noqa: E402 + ADDRESS_COL, + FOUND_ADDRESS_COL, + FOUND_UPRN_COL, + POSTCODE_COL, + REF_COL, + SCORE_COL, + SHEET, + UPRN_COL, + NOT_FOUND, + cell_str, + parse_uprn_cell, +) + +_DEFAULT_IN = _REPO_ROOT / "scripts" / "manipulation_filled.xlsx" + + +def _final_uprn(row: pd.Series) -> Optional[int]: + """The authoritative UPRN: the given one, else the DOMNA-found one.""" + given = parse_uprn_cell(row.get(UPRN_COL)) + if given is not None: + return given + found = cell_str(row.get(FOUND_UPRN_COL)) + if found and found != NOT_FOUND: + return parse_uprn_cell(found) + return None + + +def to_combiner_row(row: pd.Series) -> dict[str, str]: + """Map one spreadsheet row to the combiner-output shape the finaliser reads.""" + given_uprn = parse_uprn_cell(row.get(UPRN_COL)) + address = cell_str(row.get(ADDRESS_COL)) + uprn = _final_uprn(row) + + domna_addr = cell_str(row.get(FOUND_ADDRESS_COL)) + if domna_addr == NOT_FOUND: + domna_addr = "" + # Matched address: the resolved one when we found it, else the given address + # (for rows that already had a UPRN + address). + matched = domna_addr or (address if given_uprn is not None else "") + score = cell_str(row.get(SCORE_COL)) + + return { + "Address 1": address, + "Address 2": "", + "Address 3": "", + "postcode": cell_str(row.get(POSTCODE_COL)), + "Internal Reference": cell_str(row.get(REF_COL)), + "address2uprn_uprn": "" if uprn is None else str(uprn), + "address2uprn_address": matched, + "address2uprn_lexiscore": score, + } + + +def load_rows( + path: Path, *, include_unmatched: bool +) -> tuple[pd.DataFrame, list[dict[str, str]]]: + """Load the sheet and the combiner rows. By default drop rows with no UPRN.""" + df = pd.read_excel(path, sheet_name=SHEET) + df = df.reset_index(drop=True) + if not include_unmatched: + keep = df.apply(lambda r: _final_uprn(r) is not None, axis=1) + df = df[keep].reset_index(drop=True) + rows = [to_combiner_row(r) for _, r in df.iterrows()] + return df, rows + + +def dedupe_by_uprn( + rows: list[dict[str, str]], +) -> tuple[list[dict[str, str]], list[dict[str, str]]]: + """Keep the first row per UPRN; return (kept, dropped collisions). + + The DB INSERT collapses duplicate (portfolio, uprn) via ON CONFLICT DO + NOTHING anyway, so this just makes the collision explicit (the dropped rows + are written out for review) rather than letting an arbitrary ref win silently. + """ + seen: set[str] = set() + kept: list[dict[str, str]] = [] + dropped: list[dict[str, str]] = [] + for row in rows: + uprn = row["address2uprn_uprn"] + if uprn in seen: + dropped.append(row) + else: + seen.add(uprn) + kept.append(row) + return kept, dropped + + +# Force-reload teardown order (bottom-up). property_overrides is ON DELETE +# CASCADE so it clears itself when the property goes; everything below is NO +# ACTION and must be deleted first, deepest child first. +# property -> epc_property -> {these children} +_EPC_CHILD_TABLES = ( + "epc_energy_element", + "epc_window", + "epc_main_heating_detail", + "epc_renewable_heat_incentive", + "epc_building_part", + "epc_flat_details", +) +# property -> {these direct dependents}, deleted after the epc children +_PROPERTY_DEPENDENTS = ("epc_property", "plan") +_INSERT_CHUNK = 4000 # 9 cols/row -> well under psycopg2's 65535-param limit + + +def _reset_portfolio(session: object, portfolio_id: int) -> int: + """Delete a portfolio's properties and their NO ACTION dependency tree. + + Returns the number of property rows deleted (property_overrides cascade). + """ + from sqlalchemy import text + + pids = "SELECT id FROM property WHERE portfolio_id = :pid" + epc_ids = f"SELECT id FROM epc_property WHERE property_id IN ({pids})" + for table in _EPC_CHILD_TABLES: + session.execute( # type: ignore[attr-defined] + text(f"DELETE FROM {table} WHERE epc_property_id IN ({epc_ids})"), + {"pid": portfolio_id}, + ) + for table in _PROPERTY_DEPENDENTS: + session.execute( # type: ignore[attr-defined] + text(f"DELETE FROM {table} WHERE property_id IN ({pids})"), + {"pid": portfolio_id}, + ) + result = session.execute( # type: ignore[attr-defined] + text("DELETE FROM property WHERE portfolio_id = :pid"), {"pid": portfolio_id} + ) + return result.rowcount + + +def clean_reload( + rows: list[dict[str, str]], portfolio_id: int, *, reset: bool +) -> tuple[int, int]: + """Optionally wipe the portfolio, then chunk-insert rows. One transaction. + + Returns (properties_deleted, properties_inserted). + """ + inserts: list[PropertyIdentityInsert] = [ + BulkUploadFinaliserOrchestrator._row_to_insert(r, portfolio_id) for r in rows + ] + engine = _engine() + session = make_session(engine) + deleted = 0 + inserted = 0 + try: + repo = PropertyPostgresRepository(session) + with commit_scope(session): + if reset: + deleted = _reset_portfolio(session, portfolio_id) + for start in range(0, len(inserts), _INSERT_CHUNK): + inserted += repo.insert_all(inserts[start : start + _INSERT_CHUNK]) + finally: + session.close() + return deleted, inserted + + +def _engine(): + load_dotenv(_REPO_ROOT / ".env") + return make_engine(PostgresConfig.from_env(os.environ)) + + +def insert_rows(rows: list[dict[str, str]], portfolio_id: int) -> int: + """Insert via the finaliser's mapper + repository. Returns rows inserted.""" + inserts: list[PropertyIdentityInsert] = [ + BulkUploadFinaliserOrchestrator._row_to_insert(r, portfolio_id) for r in rows + ] + engine = _engine() + session = make_session(engine) + try: + repo = PropertyPostgresRepository(session) + with commit_scope(session): + inserted = repo.insert_all(inserts) + finally: + session.close() + return inserted + + +def fetch_by_ref(portfolio_id: int, ref: str) -> list[PropertyRow]: + """Read back inserted rows for one Organisation Reference (for verification).""" + engine = _engine() + session = make_session(engine) + try: + stmt = select(PropertyRow).where( + PropertyRow.portfolio_id == portfolio_id, + PropertyRow.landlord_property_id == ref, + ) + return list(session.exec(stmt).all()) + finally: + session.close() + + +def _show(row: dict[str, str], insert: PropertyIdentityInsert) -> None: + print("\nSource (combiner) row:") + for k, v in row.items(): + print(f" {k}: {v!r}") + print("\nMapped PropertyIdentityInsert:") + for k, v in insert.__dict__.items(): + print(f" {k}: {v!r}") + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--in", dest="inp", type=Path, default=_DEFAULT_IN) + parser.add_argument("--portfolio", type=int, required=True) + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("--one", action="store_true", help="one random resolved row") + group.add_argument("--ref", help="a specific Organisation Reference") + group.add_argument("--all", action="store_true", help="every row") + parser.add_argument( + "--include-unmatched", + action="store_true", + help="also insert rows with no UPRN (null-UPRN property rows)", + ) + parser.add_argument( + "--reset", + action="store_true", + help="(with --all) DELETE all properties in the portfolio first " + "(cascades property_overrides; clears plan/epc_property)", + ) + parser.add_argument( + "--collisions", + type=Path, + default=_REPO_ROOT / "scripts" / "manipulation_collisions.csv", + help="where to write rows dropped as duplicate-UPRN collisions", + ) + parser.add_argument("--seed", type=int, default=0, help="random seed for --one") + return parser.parse_args() + + +def main() -> int: + args = _parse_args() + df, rows = load_rows(args.inp, include_unmatched=args.include_unmatched) + print(f"Loaded {len(rows)} candidate rows from {args.inp}") + + if args.all: + kept, dropped = dedupe_by_uprn(rows) + if dropped: + pd.DataFrame(dropped).to_csv(args.collisions, index=False) + print( + f"{len(dropped)} duplicate-UPRN rows dropped -> {args.collisions} " + f"({len(kept)} unique to insert)" + ) + deleted, inserted = clean_reload(kept, args.portfolio, reset=args.reset) + if args.reset: + print(f"Deleted {deleted} existing properties in portfolio {args.portfolio}.") + print(f"Inserted {inserted} properties into portfolio {args.portfolio}.") + return 0 + + # Single-row paths: pick the row, show the mapping, insert, read back. + if args.ref: + match = [r for r in rows if r["Internal Reference"] == args.ref] + if not match: + print(f"No resolved row with Organisation Reference {args.ref!r}.") + return 1 + row = match[0] + else: # --one: deterministic "random" pick via seed + idx = (args.seed * 7919) % len(rows) + row = rows[idx] + + ref = row["Internal Reference"] + insert = BulkUploadFinaliserOrchestrator._row_to_insert(row, args.portfolio) + _show(row, insert) + + inserted = insert_rows([row], args.portfolio) + print( + f"\ninsert_all -> {inserted} new row(s) " + f"(0 means it already existed; ON CONFLICT DO NOTHING)." + ) + + print(f"\nproperty rows for portfolio {args.portfolio}, ref {ref!r}:") + for pr in fetch_by_ref(args.portfolio, ref): + print( + f" id={pr.id} uprn={pr.uprn} address={pr.address!r} " + f"postcode={pr.postcode!r} status={pr.creation_status} " + f"lexiscore={pr.lexiscore}" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/hyde_epc_schema_versions.py b/scripts/hyde_epc_schema_versions.py new file mode 100644 index 000000000..7bb882aca --- /dev/null +++ b/scripts/hyde_epc_schema_versions.py @@ -0,0 +1,159 @@ +"""Tally the EPC schema versions across the hyde list (manipulation_filled UPRNs). + +For every resolved UPRN we look up its EPC certificate's ``schemaType`` (e.g. +``RdSAP-Schema-21.0.1``, ``RdSAP-Schema-17.1``, ``SAP-Schema-16.2``). The +gov EPC ``/api/domestic/search`` endpoint returns ``schemaType`` per row, so one +search-per-postcode covers every UPRN in that postcode — far cheaper than a +certificate fetch per UPRN. The latest cert (max registrationDate) wins per UPRN. + +Outputs: a per-schema-version tally with one example UPRN each, plus a CSV +mapping every UPRN -> schema version. + + python -m scripts.hyde_epc_schema_versions + python -m scripts.hyde_epc_schema_versions --workers 8 --out scripts/hyde_schema_versions.csv + +Reads OPEN_EPC_API_TOKEN from backend/.env. Run from the worktree root. +""" + +from __future__ import annotations + +import argparse +import os +import sys +import time +from collections import Counter, defaultdict +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from typing import Any, Optional + +import httpx +from dotenv import load_dotenv + +_REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(_REPO_ROOT)) # worktree root first — avoid the import trap + +from scripts.fill_domna_addresses import clean_postcode # noqa: E402 +from scripts.finalise_to_property_table import load_rows # noqa: E402 + +_BASE = "https://api.get-energy-performance-data.communities.gov.uk" +_SEARCH = f"{_BASE}/api/domestic/search" +NOT_IN_EPC = "NOT_IN_EPC" + +_DEFAULT_IN = _REPO_ROOT / "scripts" / "manipulation_filled.xlsx" +_DEFAULT_OUT = _REPO_ROOT / "scripts" / "hyde_schema_versions.csv" + + +def search_postcode( + client: httpx.Client, postcode: str, headers: dict[str, str] +) -> list[dict[str, Any]]: + """Return the search rows for a postcode, retrying on rate-limit (429).""" + for attempt in range(5): + resp = client.get(_SEARCH, params={"postcode": postcode}, headers=headers, timeout=30) + if resp.status_code == 429: + retry_after = float(resp.headers.get("Retry-After", "2")) + time.sleep(min(retry_after, 10) * (attempt + 1)) + continue + # 400 = malformed postcode (data-entry typo), 404 = no certs — skip both. + if resp.status_code in (400, 404): + return [] + resp.raise_for_status() + return resp.json().get("data", []) + return [] + + +def build_uprn_schema_map( + postcodes: list[str], token: str, workers: int +) -> dict[int, tuple[str, str]]: + """Map UPRN -> (schemaType, registrationDate) for the latest cert per UPRN. + + One search per postcode (concurrent); later we look our UPRNs up in here. + """ + headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"} + by_uprn: dict[int, tuple[str, str]] = {} + done = 0 + total = len(postcodes) + + def fetch(pc: str) -> list[dict[str, Any]]: + with httpx.Client() as client: + return search_postcode(client, pc, headers) + + with ThreadPoolExecutor(max_workers=workers) as pool: + for rows in pool.map(fetch, postcodes): + for row in rows: + uprn = row.get("uprn") + schema = row.get("schemaType") + reg = row.get("registrationDate") or "" + if uprn is None or not schema: + continue + prev = by_uprn.get(int(uprn)) + # Keep the latest-registered cert's schema for this UPRN. + if prev is None or reg > prev[1]: + by_uprn[int(uprn)] = (str(schema), str(reg)) + done += 1 + if done % 250 == 0: + print(f" searched {done}/{total} postcodes, {len(by_uprn)} uprns seen") + return by_uprn + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--in", dest="inp", type=Path, default=_DEFAULT_IN) + parser.add_argument("--out", type=Path, default=_DEFAULT_OUT) + parser.add_argument("--workers", type=int, default=8) + return parser.parse_args() + + +def main() -> int: + args = _parse_args() + load_dotenv(_REPO_ROOT / "backend" / ".env") + token = os.environ.get("OPEN_EPC_API_TOKEN") + if not token: + print("OPEN_EPC_API_TOKEN not set (backend/.env)") + return 2 + + _, rows = load_rows(args.inp, include_unmatched=False) + pairs: list[tuple[int, str, str]] = [] # (uprn, postcode_clean, address) + for r in rows: + uprn = r["address2uprn_uprn"] + if uprn: + pairs.append((int(uprn), clean_postcode(r["postcode"]), r["address2uprn_address"])) + postcodes = sorted({pc for _, pc, _ in pairs if pc}) + print(f"{len(pairs)} UPRNs across {len(postcodes)} unique postcodes") + + by_uprn = build_uprn_schema_map(postcodes, token, args.workers) + print(f"EPC search returned schema for {len(by_uprn)} distinct UPRNs") + + # Resolve each hyde UPRN to its schema version. + tally: Counter[str] = Counter() + example: dict[str, tuple[int, str]] = {} + out_lines: list[tuple[int, str, str, str]] = [] # uprn, schema, postcode, address + seen: set[int] = set() + for uprn, pc, address in pairs: + if uprn in seen: + continue + seen.add(uprn) + schema = by_uprn.get(uprn, (NOT_IN_EPC, ""))[0] + tally[schema] += 1 + example.setdefault(schema, (uprn, address)) + out_lines.append((uprn, schema, pc, address)) + + # Write the full per-UPRN mapping. + import csv + + with args.out.open("w", newline="", encoding="utf-8") as fh: + w = csv.writer(fh) + w.writerow(["uprn", "schema_version", "postcode", "matched_address"]) + w.writerows(out_lines) + + print(f"\nSchema versions across {len(seen)} distinct UPRNs:\n") + print(f" {'schema version':<26} {'count':>7} example UPRN") + print(f" {'-'*26} {'-'*7} {'-'*12}") + for schema, count in tally.most_common(): + ex_uprn, ex_addr = example[schema] + print(f" {schema:<26} {count:>7} {ex_uprn} ({ex_addr})") + print(f"\nFull mapping -> {args.out}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/lisasrequest/compare_to_ara.py b/scripts/lisasrequest/compare_to_ara.py new file mode 100644 index 000000000..c30b5f29f --- /dev/null +++ b/scripts/lisasrequest/compare_to_ara.py @@ -0,0 +1,169 @@ +"""Compare our step-1 UPRN resolution against the old "Ara output" data. + +The Ara data lives in scripts/lisasrequest/Durkan data.xlsx, sheet "Ara output", +and carries UPRNs from our previous dataset. It is NOT treated as ground truth — +this just lines it up against what we found / didn't find so a human can eyeball +the differences. (We read the xlsx, not the CSV export: the CSV mangled half the +UPRNs to Excel scientific notation, e.g. ``1.00023E+11``; the xlsx keeps them +intact, so every comparison below is exact.) + +Join key is (postcode, leading number, first street word), since the UPRN is the +thing under comparison and Ara's address strings differ from the landlord input. + +Each of our rows lands in one comparison bucket: + match both found a UPRN and they are equal. + differ both found a UPRN and they differ. + we_only we resolved a UPRN, Ara had none for this address. + ara_only we did NOT resolve, but Ara had a UPRN <- recovery candidates. + both_missing neither resolved a UPRN. + no_ara_record the Ara sheet had no row matching this address at all. + + python -m scripts.lisasrequest.compare_to_ara +""" + +from __future__ import annotations + +import argparse +import csv +import re +import sys +from collections import Counter, OrderedDict +from pathlib import Path +from typing import Optional + +import pandas as pd + +_REPO_ROOT = Path(__file__).resolve().parents[2] + +ADDRESS_COL = "address" +POSTCODE_COL = "postcode" +OUR_UPRN_COL = "domna_address_uprn" +OUR_SOURCE_COL = "domna_source" + +ARA_UPRN_COL = "EPC_B.uprn" +ARA_ADDRESS_COL = "EPC_B.address" +ARA_POSTCODE_COL = "EPC_B.postcode" +ARA_SHEET = "Ara output" + +_OUR_IN = _REPO_ROOT / "scripts" / "lisasrequest" / "durkan_domna_filled.csv" +_ARA_IN = _REPO_ROOT / "scripts" / "lisasrequest" / "Durkan data.xlsx" +_DEFAULT_OUT = _REPO_ROOT / "scripts" / "lisasrequest" / "durkan_vs_ara.csv" + +Key = tuple[str, str, str] + + +def norm_key(address: str, postcode: str) -> Key: + """(postcode-no-space, leading number, first street word) — the join key.""" + pc = postcode.upper().replace(" ", "") + upper = address.upper() + nums = re.findall(r"\d+[A-Z]?", upper) + words = [w for w in re.findall(r"[A-Z]+", upper) if w != "FLAT"] + return (pc, nums[0] if nums else "", words[0] if words else "") + + +def load_ara(path: Path) -> tuple[dict[Key, dict[str, str]], int]: + """Index the Ara-output xlsx sheet by join key (first row wins). + + Returns (index, duplicates). Read as strings so UPRNs keep their full value. + """ + df = pd.read_excel(path, sheet_name=ARA_SHEET, dtype=str) + rows: list[dict[str, str]] = df.fillna("").to_dict(orient="records") + index: dict[Key, dict[str, str]] = OrderedDict() + dupes = 0 + for row in rows: + address = str(row.get(ARA_ADDRESS_COL) or "").strip() + postcode = str(row.get(ARA_POSTCODE_COL) or row.get(POSTCODE_COL) or "").strip() + if not address: + continue + key = norm_key(address, postcode) + if key in index: + dupes += 1 + continue + index[key] = row + return index, dupes + + +def classify( + our_uprn: str, our_found: bool, ara: Optional[dict[str, str]] +) -> tuple[str, str, str]: + """Return (comparison, ara_uprn, ara_address) for one of our rows.""" + if ara is None: + return ("no_ara_record", "", "") + ara_uprn = (ara.get(ARA_UPRN_COL) or "").strip() + ara_address = (ara.get(ARA_ADDRESS_COL) or "").strip() + ara_found = bool(ara_uprn) + + if our_found and ara_found: + comparison = "match" if our_uprn == ara_uprn else "differ" + elif our_found and not ara_found: + comparison = "we_only" + elif not our_found and ara_found: + comparison = "ara_only" + else: + comparison = "both_missing" + return (comparison, ara_uprn, ara_address) + + +def compare( + our_rows: list[dict[str, str]], ara_index: dict[Key, dict[str, str]] +) -> list[dict[str, str]]: + out: list[dict[str, str]] = [] + for row in our_rows: + address = (row.get(ADDRESS_COL) or "").strip() + postcode = (row.get(POSTCODE_COL) or "").strip() + our_uprn = (row.get(OUR_UPRN_COL) or "").strip() + our_source = (row.get(OUR_SOURCE_COL) or "").strip() + our_found = bool(our_uprn) and our_source != "not_found" + + ara = ara_index.get(norm_key(address, postcode)) + comparison, ara_uprn, ara_address = classify(our_uprn, our_found, ara) + out.append( + { + "address": address, + "postcode": postcode, + "our_uprn": our_uprn, + "our_source": our_source, + "ara_uprn": ara_uprn, + "ara_address": ara_address, + "comparison": comparison, + } + ) + return out + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--ours", type=Path, default=_OUR_IN) + parser.add_argument("--ara", type=Path, default=_ARA_IN) + parser.add_argument("--out", type=Path, default=_DEFAULT_OUT) + args = parser.parse_args() + + with args.ours.open(newline="", encoding="utf-8-sig") as fh: + our_rows = [dict(r) for r in csv.DictReader(fh)] + ara_index, dupes = load_ara(args.ara) + print(f"Loaded {len(our_rows)} of our rows; {len(ara_index)} Ara keys " + f"({dupes} duplicate Ara rows ignored).") + + result = compare(our_rows, ara_index) + fieldnames = list(result[0].keys()) + with args.out.open("w", newline="", encoding="utf-8") as fh: + writer = csv.DictWriter(fh, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(result) + + counts = Counter(r["comparison"] for r in result) + print(f"\nComparison of {len(result)} rows -> {args.out}") + for name in ( + "match", + "differ", + "we_only", + "ara_only", + "both_missing", + "no_ara_record", + ): + print(f" {name}: {counts.get(name, 0)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/lisasrequest/durkan_805_schema_check.py b/scripts/lisasrequest/durkan_805_schema_check.py new file mode 100644 index 000000000..776a7813d --- /dev/null +++ b/scripts/lisasrequest/durkan_805_schema_check.py @@ -0,0 +1,142 @@ +"""EPC SAP-schema check for portfolio 805, and whether each is mapper-supported. + +For every UPRN currently in the ``property`` table for portfolio 805, look up its +latest EPC certificate's ``schemaType`` (one /api/domestic/search per postcode, +reusing scripts.hyde_epc_schema_versions) and check it against the schemas the +EpcPropertyData mapper actually handles +(``EpcPropertyDataMapper.from_api_response``, datatypes/epc/domain/mapper.py). + +Prints a per-schema tally with a supported? flag and an example UPRN, and writes +the full per-UPRN mapping to durkan_805_schema_check.csv. + + python -m scripts.lisasrequest.durkan_805_schema_check + python -m scripts.lisasrequest.durkan_805_schema_check --portfolio 805 --workers 8 + +Reads OPEN_EPC_API_TOKEN from backend/.env and POSTGRES_* from the root .env. +Run from the worktree root. +""" + +from __future__ import annotations + +import argparse +import csv +import os +import sys +from collections import Counter +from pathlib import Path + +from dotenv import load_dotenv +from sqlmodel import select + +_REPO_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(_REPO_ROOT)) # worktree root first — avoid the import trap + +from infrastructure.postgres.config import PostgresConfig # noqa: E402 +from infrastructure.postgres.engine import make_engine, make_session # noqa: E402 +from infrastructure.postgres.property_table import PropertyRow # noqa: E402 +from scripts.fill_domna_addresses import clean_postcode # noqa: E402 +from scripts.hyde_epc_schema_versions import ( # noqa: E402 + NOT_IN_EPC, + build_uprn_schema_map, +) + +# Schemas EpcPropertyDataMapper.from_api_response dispatches on (everything else +# raises "Unsupported EPC schema"). Keep in sync with mapper.py:2539-2603. +SUPPORTED_SCHEMAS = frozenset( + { + "RdSAP-Schema-17.0", + "RdSAP-Schema-17.1", + "RdSAP-Schema-18.0", + "RdSAP-Schema-19.0", + "RdSAP-Schema-20.0.0", + "RdSAP-Schema-21.0.0", + "RdSAP-Schema-21.0.1", + "SAP-Schema-16.0", + "SAP-Schema-16.2", + "SAP-Schema-16.3", + "SAP-Schema-17.0", + "SAP-Schema-17.1", + "SAP-Schema-18.0.0", + } +) + +_DEFAULT_OUT = _REPO_ROOT / "scripts" / "lisasrequest" / "durkan_805_schema_check.csv" + + +def load_portfolio_uprns(portfolio_id: int) -> list[tuple[int, str]]: + """Return (uprn, postcode) for every property in the portfolio with a UPRN.""" + load_dotenv(_REPO_ROOT / ".env") + engine = make_engine(PostgresConfig.from_env(os.environ)) + session = make_session(engine) + try: + stmt = select(PropertyRow.uprn, PropertyRow.postcode).where( + PropertyRow.portfolio_id == portfolio_id + ) + out: list[tuple[int, str]] = [] + for uprn, postcode in session.exec(stmt).all(): + if uprn is not None: + out.append((int(uprn), str(postcode or ""))) + return out + finally: + session.close() + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--portfolio", type=int, default=805) + parser.add_argument("--out", type=Path, default=_DEFAULT_OUT) + parser.add_argument("--workers", type=int, default=8) + args = parser.parse_args() + + load_dotenv(_REPO_ROOT / "backend" / ".env") + token = os.environ.get("OPEN_EPC_API_TOKEN") + if not token: + print("OPEN_EPC_API_TOKEN not set (backend/.env)") + return 2 + + pairs = load_portfolio_uprns(args.portfolio) + postcodes = sorted({clean_postcode(pc) for _, pc in pairs if pc}) + print( + f"Portfolio {args.portfolio}: {len(pairs)} UPRNs across " + f"{len(postcodes)} unique postcodes" + ) + + by_uprn = build_uprn_schema_map(postcodes, token, args.workers) + print(f"EPC search returned a schema for {len(by_uprn)} distinct UPRNs") + + tally: Counter[str] = Counter() + example: dict[str, int] = {} + rows_out: list[tuple[int, str, str, str]] = [] # uprn, schema, supported, postcode + seen: set[int] = set() + for uprn, pc in pairs: + if uprn in seen: + continue + seen.add(uprn) + schema = by_uprn.get(uprn, (NOT_IN_EPC, ""))[0] + supported = "yes" if schema in SUPPORTED_SCHEMAS else "no" + tally[schema] += 1 + example.setdefault(schema, uprn) + rows_out.append((uprn, schema, supported, clean_postcode(pc))) + + with args.out.open("w", newline="", encoding="utf-8") as fh: + writer = csv.writer(fh) + writer.writerow(["uprn", "schema_version", "mapper_supported", "postcode"]) + writer.writerows(rows_out) + + supported_count = sum(c for s, c in tally.items() if s in SUPPORTED_SCHEMAS) + print(f"\nSchema versions across {len(seen)} distinct UPRNs in portfolio " + f"{args.portfolio}:\n") + print(f" {'schema version':<26} {'count':>5} {'supported?':<10} example UPRN") + print(f" {'-' * 26} {'-' * 5} {'-' * 10} {'-' * 12}") + for schema, count in tally.most_common(): + supported = "yes" if schema in SUPPORTED_SCHEMAS else "NO" + print(f" {schema:<26} {count:>5} {supported:<10} {example[schema]}") + print( + f"\nMapper-supported: {supported_count}/{len(seen)} UPRNs. " + f"Full mapping -> {args.out}" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/lisasrequest/fill_domna_address.py b/scripts/lisasrequest/fill_domna_address.py new file mode 100644 index 000000000..98d700710 --- /dev/null +++ b/scripts/lisasrequest/fill_domna_address.py @@ -0,0 +1,200 @@ +"""Step 1 (Durkan portfolio): resolve a UPRN per CSV row via EPC then OS. + +Input: scripts/lisasrequest/260611_Sample_Seed_Portfolio_Durkan_split_addresses(Split Addresses).csv + columns include ``address`` and ``postcode``. + +Every row carries an address and none carry a UPRN, so there is a single case: + + * resolve a UPRN from ``address`` + ``postcode`` via the EPC API (relaxed + address variants, threshold 0.7), then Ordnance Survey Places as a fallback + (threshold 0.6). + * not resolvable -> domna_source = "not_found"; uprn/address/score left empty. + +Writes a NEW CSV = every original column, in order, plus four DOMNA columns: + + domna_address_found the canonical address EPC/OS returned (matched string) + domna_address_uprn the resolved UPRN ("" when unresolved) + domna_lexiscore the match score in [0, 1] ("" when unresolved) + domna_source epc / ordnance_survey / not_found + +This is the human-review file; step 2 (resolve_uprns_for_finaliser) reshapes it +into the finaliser columns without re-hitting the APIs. + + python -m scripts.lisasrequest.fill_domna_address + python -m scripts.lisasrequest.fill_domna_address --limit 20 # smoke test + +Resolution reuses the relaxed matchers from scripts.fill_domna_addresses. Keys +come from backend/.env (OPEN_EPC_API_TOKEN, ORDNANCE_SURVEY_API_KEY). Run from +the worktree root (import trap). +""" + +from __future__ import annotations + +import argparse +import csv +import sys +from pathlib import Path +from typing import Optional + +import pandas as pd + +_REPO_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(_REPO_ROOT)) # worktree root first — avoid the import trap + +from scripts.fill_domna_addresses import ( # noqa: E402 + Hit, + resolve_epc_relaxed, + resolve_os_relaxed, +) +from scripts.resolve_uprns_for_finaliser import clean_postcode, load_keys # noqa: E402 + +ADDRESS_COL = "address" +POSTCODE_COL = "postcode" +FOUND_ADDRESS_COL = "domna_address_found" +FOUND_UPRN_COL = "domna_address_uprn" +LEXISCORE_COL = "domna_lexiscore" +SOURCE_COL = "domna_source" +NOT_FOUND = "not_found" +_RESULT_COLS = (FOUND_ADDRESS_COL, FOUND_UPRN_COL, LEXISCORE_COL, SOURCE_COL) + +_CSV_NAME = "260611_Sample_Seed_Portfolio_Durkan_split_addresses(Split Addresses).csv" +_DEFAULT_IN = _REPO_ROOT / "scripts" / "lisasrequest" / _CSV_NAME +_DEFAULT_OUT = _REPO_ROOT / "scripts" / "lisasrequest" / "durkan_domna_filled.csv" + + +def read_rows(path: Path) -> tuple[list[dict[str, str]], list[str]]: + """Read a CSV into (rows, fieldnames), preserving column order.""" + with path.open(newline="", encoding="utf-8-sig") as fh: + reader = csv.DictReader(fh) + fieldnames = list(reader.fieldnames or []) + rows = [dict(row) for row in reader] + return rows, fieldnames + + +def resolve_one( + address: str, + postcode_raw: str, + *, + epc_token: Optional[str], + os_api_key: Optional[str], + epc_cache: dict[str, pd.DataFrame], + os_cache: dict[str, pd.DataFrame], +) -> Optional[Hit]: + """Resolve one row's UPRN: EPC (relaxed) first, then OS Places fallback.""" + postcode_clean = clean_postcode(postcode_raw) + if not address or not postcode_clean: + return None + + hit: Optional[Hit] = None + if epc_token: + try: + hit = resolve_epc_relaxed(address, postcode_clean, epc_cache) + except Exception as exc: + print(f" EPC failed {address!r} / {postcode_clean}: {exc}") + if hit is None and os_api_key: + try: + hit = resolve_os_relaxed(address, postcode_clean, os_api_key, os_cache) + except Exception as exc: + print(f" OS failed {address!r} / {postcode_clean}: {exc}") + return hit + + +def fill( + rows: list[dict[str, str]], + *, + epc_token: Optional[str], + os_api_key: Optional[str], +) -> tuple[int, int, int]: + """Fill the DOMNA columns on each row in place. + + Returns (epc_hits, os_hits, not_found) counts. + """ + epc_cache: dict[str, pd.DataFrame] = {} + os_cache: dict[str, pd.DataFrame] = {} + epc_hits = os_hits = not_found = 0 + total = len(rows) + + for n, row in enumerate(rows, start=1): + address = str(row.get(ADDRESS_COL, "") or "").strip() + postcode_raw = str(row.get(POSTCODE_COL, "") or "").strip() + hit = resolve_one( + address, + postcode_raw, + epc_token=epc_token, + os_api_key=os_api_key, + epc_cache=epc_cache, + os_cache=os_cache, + ) + if hit is None: + row[FOUND_ADDRESS_COL] = "" + row[FOUND_UPRN_COL] = "" + row[LEXISCORE_COL] = "" + row[SOURCE_COL] = NOT_FOUND + not_found += 1 + else: + uprn, matched, score, source = hit + row[FOUND_ADDRESS_COL] = matched + row[FOUND_UPRN_COL] = uprn + row[LEXISCORE_COL] = str(round(score, 4)) + row[SOURCE_COL] = source + if source == "epc": + epc_hits += 1 + else: + os_hits += 1 + print( + f"[{n}/{total}] {address!r} -> " + f"{row[FOUND_UPRN_COL] or '(no match)'} ({row[SOURCE_COL]})" + ) + + return epc_hits, os_hits, not_found + + +def write_rows(rows: list[dict[str, str]], path: Path, fieldnames: list[str]) -> None: + """Write rows to CSV, preserving input columns and appending DOMNA columns.""" + out_fields = list(fieldnames) + for col in _RESULT_COLS: + if col not in out_fields: + out_fields.append(col) + with path.open("w", newline="", encoding="utf-8") as fh: + writer = csv.DictWriter(fh, fieldnames=out_fields, extrasaction="ignore") + writer.writeheader() + writer.writerows(rows) + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--in", dest="inp", type=Path, default=_DEFAULT_IN) + parser.add_argument("--out", type=Path, default=_DEFAULT_OUT) + parser.add_argument("--limit", type=int, default=None, help="process first N rows") + return parser.parse_args() + + +def main() -> int: + args = _parse_args() + epc_token, os_api_key = load_keys() + if not epc_token: + print("OPEN_EPC_API_TOKEN not set (backend/.env) — EPC resolution disabled") + if not os_api_key: + print("ORDNANCE_SURVEY_API_KEY not set (backend/.env) — OS fallback disabled") + + rows, fieldnames = read_rows(args.inp) + if args.limit is not None: + rows = rows[: args.limit] + print(f"Loaded {len(rows)} rows from {args.inp}") + + epc_hits, os_hits, not_found = fill( + rows, epc_token=epc_token, os_api_key=os_api_key + ) + + write_rows(rows, args.out, fieldnames) + resolved = epc_hits + os_hits + print( + f"\nResolved {resolved}/{len(rows)} " + f"(epc={epc_hits}, ordnance_survey={os_hits}); {not_found} not found." + ) + print(f"Wrote filled CSV -> {args.out}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/lisasrequest/finalise_to_property_table.py b/scripts/lisasrequest/finalise_to_property_table.py new file mode 100644 index 000000000..eee66a0f7 --- /dev/null +++ b/scripts/lisasrequest/finalise_to_property_table.py @@ -0,0 +1,111 @@ +"""Step 3 (Durkan portfolio): insert the reshaped rows into the ``property`` table. + +Reads durkan_finaliser_input.csv (step 2) and, per row, maps it with the real +finaliser mapper (``BulkUploadFinaliserOrchestrator._row_to_insert``) and inserts +via the same ``PropertyPostgresRepository.insert_all`` the Lambda uses — so a row +written here is identical to one the production finaliser would write. Insert is +ON CONFLICT (portfolio_id, uprn) DO NOTHING, so re-running is safe. + +DRY RUN BY DEFAULT — it dedupes, reports, and writes the collisions file but does +NOT touch the database. Add --commit to actually insert. + + # preview only (no DB writes): dedupe + mapping report + python -m scripts.lisasrequest.finalise_to_property_table --portfolio 805 + + # actually insert + python -m scripts.lisasrequest.finalise_to_property_table --portfolio 805 --commit + +Postgres target comes from the root .env (POSTGRES_*). Run from the worktree root. +""" + +from __future__ import annotations + +import argparse +import csv +import sys +from pathlib import Path + +_REPO_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(_REPO_ROOT)) # worktree root first — avoid the import trap + +from scripts.finalise_to_property_table import ( # noqa: E402 + dedupe_by_uprn, + insert_rows, +) + +_DEFAULT_IN = _REPO_ROOT / "scripts" / "lisasrequest" / "durkan_finaliser_input.csv" +_DEFAULT_COLLISIONS = ( + _REPO_ROOT / "scripts" / "lisasrequest" / "durkan_finaliser_collisions.csv" +) +UPRN_COL = "address2uprn_uprn" +MATCHED_ADDRESS_COL = "address2uprn_address" +POSTCODE_COL = "postcode" +LEXISCORE_COL = "address2uprn_lexiscore" + + +def read_rows(path: Path) -> list[dict[str, str]]: + with path.open(newline="", encoding="utf-8-sig") as fh: + return [dict(row) for row in csv.DictReader(fh)] + + +def _preview(rows: list[dict[str, str]]) -> None: + """Show the first few rows as they will be inserted (no DB, no mapper call). + + The finalise step applies the standard finaliser mapper + (BulkUploadFinaliserOrchestrator) on insert; the fields below are its inputs. + """ + print("\nSample rows to insert (uprn | matched address | postcode | lexiscore):") + for row in rows[:3]: + print( + f" {row.get(UPRN_COL)} | {row.get(MATCHED_ADDRESS_COL)!r} | " + f"{row.get(POSTCODE_COL)!r} | {row.get(LEXISCORE_COL)}" + ) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--in", dest="inp", type=Path, default=_DEFAULT_IN) + parser.add_argument("--portfolio", type=int, required=True) + parser.add_argument( + "--commit", + action="store_true", + help="actually insert into property (default is a dry-run preview)", + ) + parser.add_argument("--collisions", type=Path, default=_DEFAULT_COLLISIONS) + args = parser.parse_args() + + rows = read_rows(args.inp) + print(f"Loaded {len(rows)} finaliser rows from {args.inp}") + + kept, dropped = dedupe_by_uprn(rows) + if dropped: + with args.collisions.open("w", newline="", encoding="utf-8") as fh: + writer = csv.DictWriter(fh, fieldnames=list(dropped[0].keys())) + writer.writeheader() + writer.writerows(dropped) + print( + f"{len(dropped)} duplicate-UPRN rows dropped -> {args.collisions} " + f"({len(kept)} unique to insert)" + ) + else: + print(f"No duplicate-UPRN collisions; {len(kept)} unique rows to insert.") + + _preview(kept) + + if not args.commit: + print( + f"\nDRY RUN — nothing written. {len(kept)} rows would be inserted into " + f"portfolio {args.portfolio}. Re-run with --commit to write." + ) + return 0 + + inserted = insert_rows(kept, args.portfolio) + print( + f"\nInserted {inserted} new properties into portfolio {args.portfolio} " + f"({len(kept) - inserted} already existed; ON CONFLICT DO NOTHING)." + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/lisasrequest/resolve_uprns_for_finaliser.py b/scripts/lisasrequest/resolve_uprns_for_finaliser.py new file mode 100644 index 000000000..6107b8378 --- /dev/null +++ b/scripts/lisasrequest/resolve_uprns_for_finaliser.py @@ -0,0 +1,212 @@ +"""Step 2 (Durkan portfolio): split step-1 matches, reshape the confident ones. + +Reads durkan_domna_filled.csv (step 1) and SPLITS it in two — no re-resolution, +just column work: + + * Rows we cannot confidently insert are held back to a client-clarification CSV + (durkan_client_clarification.csv) for Khalim to take to the client. Reasons: + not_found_no_match no UPRN was resolved. + no_flat_level_uprn a block of flats all collapsed onto one building + UPRN — OS/EPC carry no flat-level records, so we + can't tell the flats apart. + unit_number_mismatch the matched house number differs from the input + (e.g. "9 ..." matched "9A ..."), so the property is + ambiguous. + * Every remaining row is reshaped into the columns the finaliser reads + (bulk_upload_finaliser_orchestrator), written to durkan_finaliser_input.csv + ready for step 3: + Address 1/2/3 | postcode | Internal Reference | address2uprn_uprn + | address2uprn_address | address2uprn_lexiscore + Internal Reference is left blank (landlord_property_id null, by decision). + + python -m scripts.lisasrequest.resolve_uprns_for_finaliser + +This stage hits no APIs. The held rows are not lost — once the client confirms +them they can be appended to the finaliser input by hand. +""" + +from __future__ import annotations + +import argparse +import csv +import sys +from collections import Counter +from pathlib import Path +from typing import Optional + +_REPO_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(_REPO_ROOT)) # worktree root first — avoid the import trap + +from scripts.lisasrequest.fill_domna_address import ( # noqa: E402 + ADDRESS_COL, + FOUND_ADDRESS_COL, + FOUND_UPRN_COL, + LEXISCORE_COL, + POSTCODE_COL, + SOURCE_COL, +) +from scripts.lisasrequest.review_flags import address_numbers, input_unit # noqa: E402 + +# Finaliser input columns — must match bulk_upload_finaliser_orchestrator +# (ADDRESS_COLS / POSTCODE_COL / INTERNAL_REF_COL / UPRN_COL / +# MATCHED_ADDRESS_COL / LEXISCORE_COL). Hard-coded to keep this a light, +# stdlib-only reshape; step 3 imports the real orchestrator and will fail loudly +# if these ever drift. +FIN_ADDRESS_1, FIN_ADDRESS_2, FIN_ADDRESS_3 = "Address 1", "Address 2", "Address 3" +FIN_POSTCODE = "postcode" +FIN_INTERNAL_REF = "Internal Reference" +FIN_UPRN = "address2uprn_uprn" +FIN_MATCHED_ADDRESS = "address2uprn_address" +FIN_LEXISCORE = "address2uprn_lexiscore" +_FINALISER_COLS = [ + FIN_ADDRESS_1, + FIN_ADDRESS_2, + FIN_ADDRESS_3, + FIN_POSTCODE, + FIN_INTERNAL_REF, + FIN_UPRN, + FIN_MATCHED_ADDRESS, + FIN_LEXISCORE, +] + +# Client-clarification report columns (kept human-readable for the client). +CONTEXT_COLS = ["address", "postcode", "No.", "Address Block"] +DOMNA_COLS = [FOUND_ADDRESS_COL, FOUND_UPRN_COL, LEXISCORE_COL, SOURCE_COL] +REASON_COL = "clarification_reason" +ACTION_COL = "action_needed" +_CLARIFY_COLS = CONTEXT_COLS + DOMNA_COLS + [REASON_COL, ACTION_COL] + +_REASON_ORDER = { + "not_found_no_match": 0, + "no_flat_level_uprn": 1, + "unit_number_mismatch": 2, +} +_REASON_ACTION = { + "not_found_no_match": "No UPRN found for this address — please confirm the " + "exact address or provide the UPRN.", + "no_flat_level_uprn": "Address registers hold only the building, not the " + "individual flats — please provide a UPRN per flat, or confirm a " + "building-level record is acceptable.", + "unit_number_mismatch": "Closest match has a different unit number (see " + "domna_address_found) — please confirm the correct property / UPRN.", +} + +_DEFAULT_IN = _REPO_ROOT / "scripts" / "lisasrequest" / "durkan_domna_filled.csv" +_DEFAULT_FINALISER = _REPO_ROOT / "scripts" / "lisasrequest" / "durkan_finaliser_input.csv" +_DEFAULT_CLARIFY = ( + _REPO_ROOT / "scripts" / "lisasrequest" / "durkan_client_clarification.csv" +) + + +def read_rows(path: Path) -> list[dict[str, str]]: + with path.open(newline="", encoding="utf-8-sig") as fh: + return [dict(row) for row in csv.DictReader(fh)] + + +def clarification_reason( + row: dict[str, str], uprn_counts: Counter[str] +) -> Optional[str]: + """Why this row can't be inserted yet, or None if it's safe to finalise.""" + uprn = row.get(FOUND_UPRN_COL, "") + if row.get(SOURCE_COL) == "not_found" or not uprn: + return "not_found_no_match" + + unit = input_unit(row.get(ADDRESS_COL, "")) + unit_missing = bool(unit) and unit not in address_numbers( + row.get(FOUND_ADDRESS_COL, "") + ) + duplicate = uprn_counts[uprn] > 1 + if unit_missing: + return "no_flat_level_uprn" if duplicate else "unit_number_mismatch" + if duplicate: + # A shared UPRN with the right unit number still collides at finalise. + return "no_flat_level_uprn" + return None + + +def to_finaliser_row(row: dict[str, str]) -> dict[str, str]: + """Rename a confident step-1 row into the finaliser's input columns.""" + return { + FIN_ADDRESS_1: row.get(ADDRESS_COL, ""), + FIN_ADDRESS_2: "", + FIN_ADDRESS_3: "", + FIN_POSTCODE: row.get(POSTCODE_COL, ""), + FIN_INTERNAL_REF: "", # landlord_property_id null, by decision + FIN_UPRN: row.get(FOUND_UPRN_COL, ""), + FIN_MATCHED_ADDRESS: row.get(FOUND_ADDRESS_COL, ""), + FIN_LEXISCORE: row.get(LEXISCORE_COL, ""), + } + + +def to_clarify_row(row: dict[str, str], reason: str) -> dict[str, str]: + out = {col: row.get(col, "") for col in CONTEXT_COLS + DOMNA_COLS} + out[REASON_COL] = reason + out[ACTION_COL] = _REASON_ACTION[reason] + return out + + +def split( + rows: list[dict[str, str]], + *, + accept_unit_mismatch: bool = False, +) -> tuple[list[dict[str, str]], list[dict[str, str]]]: + """Return (finaliser_rows, clarification_rows). + + ``accept_unit_mismatch`` reshapes the ``unit_number_mismatch`` rows (a + near-miss like 9 -> 9A the client has already confirmed) into the finaliser + input instead of holding them back. + """ + uprn_counts: Counter[str] = Counter( + r.get(FOUND_UPRN_COL, "") for r in rows if r.get(FOUND_UPRN_COL) + ) + finaliser: list[dict[str, str]] = [] + clarify: list[dict[str, str]] = [] + for row in rows: + reason = clarification_reason(row, uprn_counts) + if reason is None or ( + accept_unit_mismatch and reason == "unit_number_mismatch" + ): + finaliser.append(to_finaliser_row(row)) + else: + clarify.append(to_clarify_row(row, reason)) + clarify.sort(key=lambda r: _REASON_ORDER.get(r[REASON_COL], 9)) + return finaliser, clarify + + +def write_csv(rows: list[dict[str, str]], path: Path, fieldnames: list[str]) -> None: + with path.open("w", newline="", encoding="utf-8") as fh: + writer = csv.DictWriter(fh, fieldnames=fieldnames, extrasaction="ignore") + writer.writeheader() + writer.writerows(rows) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--in", dest="inp", type=Path, default=_DEFAULT_IN) + parser.add_argument("--finaliser-out", type=Path, default=_DEFAULT_FINALISER) + parser.add_argument("--clarify-out", type=Path, default=_DEFAULT_CLARIFY) + parser.add_argument( + "--accept-unit-mismatch", + action="store_true", + help="reshape unit_number_mismatch rows (e.g. 9->9A) into the finaliser " + "input instead of holding them for the client", + ) + args = parser.parse_args() + + rows = read_rows(args.inp) + finaliser, clarify = split(rows, accept_unit_mismatch=args.accept_unit_mismatch) + + write_csv(finaliser, args.finaliser_out, _FINALISER_COLS) + write_csv(clarify, args.clarify_out, _CLARIFY_COLS) + + counts = Counter(r[REASON_COL] for r in clarify) + print(f"Read {len(rows)} step-1 rows.") + print(f" -> {len(finaliser)} confident rows reshaped -> {args.finaliser_out}") + print(f" -> {len(clarify)} held for client -> {args.clarify_out}") + for reason in _REASON_ORDER: + print(f" {reason}: {counts.get(reason, 0)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/lisasrequest/review_flags.py b/scripts/lisasrequest/review_flags.py new file mode 100644 index 000000000..4bf710e29 --- /dev/null +++ b/scripts/lisasrequest/review_flags.py @@ -0,0 +1,135 @@ +"""Flag step-1 matches that need a human eye, for review before finalising. + +Reads durkan_domna_filled.csv (the step-1 output) and writes a review CSV of +only the rows carrying at least one flag, newest-doubt-first: + + not_found no UPRN resolved at all. + unit_not_in_match the input flat/house number does NOT appear in the matched + address — the high-precision "wrong property" signal. Two + shapes: a near-miss ("9 VANBRUGH" matched "9A, VANBRUGH") + or a flat collapsing onto its building ("FLAT 1, 20 WARWICK" + matched "20, WARWICK ROAD"). + dup_uprn the same UPRN was resolved for >1 input row — typically a + block of flats all collapsing onto the building UPRN; all + but one will be dropped at finalise. + low_score lexiscore < 0.70 (a weak match, just over the OS bar). NOTE: + on its own this is noisy — truncated EPC addresses and extra + locality tokens push correct matches below 0.70. Treat it as + informational unless paired with one of the flags above. + + python -m scripts.lisasrequest.review_flags +""" + +from __future__ import annotations + +import argparse +import csv +import re +import sys +from collections import Counter +from pathlib import Path + +_REPO_ROOT = Path(__file__).resolve().parents[2] + +ADDRESS_COL = "address" +POSTCODE_COL = "postcode" +FOUND_ADDRESS_COL = "domna_address_found" +FOUND_UPRN_COL = "domna_address_uprn" +LEXISCORE_COL = "domna_lexiscore" +SOURCE_COL = "domna_source" +LOW_SCORE = 0.70 + +_DEFAULT_IN = _REPO_ROOT / "scripts" / "lisasrequest" / "durkan_domna_filled.csv" +_DEFAULT_OUT = _REPO_ROOT / "scripts" / "lisasrequest" / "durkan_review_flags.csv" + +_REVIEW_COLS = [ + ADDRESS_COL, + POSTCODE_COL, + FOUND_ADDRESS_COL, + FOUND_UPRN_COL, + LEXISCORE_COL, + SOURCE_COL, + "flags", +] + + +def input_unit(address: str) -> str: + """The salient unit number of an input address: the FLAT number if present, + else the leading house number ("" if neither). Upper-cased.""" + upper = address.upper() + flat = re.search(r"\bFLAT\s+(\d+[A-Z]?)", upper) + if flat: + return flat.group(1) + lead = re.match(r"\s*(\d+[A-Z]?)\b", upper) + return lead.group(1) if lead else "" + + +def address_numbers(address: str) -> set[str]: + """All standalone number tokens in an address (e.g. {"3", "20"}). Upper-cased.""" + return set(re.findall(r"\b\d+[A-Z]?\b", address.upper())) + + +def _score(value: str) -> float: + try: + return float(value) + except (TypeError, ValueError): + return 0.0 + + +def flag_rows(rows: list[dict[str, str]]) -> list[dict[str, str]]: + """Return the flagged subset, each with a ';'-joined ``flags`` field.""" + uprn_counts = Counter( + r.get(FOUND_UPRN_COL, "") for r in rows if r.get(FOUND_UPRN_COL) + ) + + flagged: list[dict[str, str]] = [] + for row in rows: + uprn = row.get(FOUND_UPRN_COL, "") + source = row.get(SOURCE_COL, "") + flags: list[str] = [] + + if source == "not_found" or not uprn: + flags.append("not_found") + else: + unit = input_unit(row.get(ADDRESS_COL, "")) + if unit and unit not in address_numbers(row.get(FOUND_ADDRESS_COL, "")): + flags.append("unit_not_in_match") + if uprn_counts[uprn] > 1: + flags.append("dup_uprn") + if _score(row.get(LEXISCORE_COL, "")) < LOW_SCORE: + flags.append("low_score") + + if flags: + flagged.append({**{c: row.get(c, "") for c in _REVIEW_COLS[:-1]}, + "flags": ";".join(flags)}) + + # not_found first, then mismatches, then dup/low. + order = {"not_found": 0, "unit_not_in_match": 1, "dup_uprn": 2, "low_score": 3} + flagged.sort(key=lambda r: order.get(r["flags"].split(";")[0], 9)) + return flagged + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--in", dest="inp", type=Path, default=_DEFAULT_IN) + parser.add_argument("--out", type=Path, default=_DEFAULT_OUT) + args = parser.parse_args() + + with args.inp.open(newline="", encoding="utf-8-sig") as fh: + rows = [dict(r) for r in csv.DictReader(fh)] + + flagged = flag_rows(rows) + with args.out.open("w", newline="", encoding="utf-8") as fh: + writer = csv.DictWriter(fh, fieldnames=_REVIEW_COLS, extrasaction="ignore") + writer.writeheader() + writer.writerows(flagged) + + counts = Counter(f for r in flagged for f in r["flags"].split(";")) + print(f"{len(flagged)}/{len(rows)} rows flagged for review -> {args.out}") + for name in ("not_found", "unit_not_in_match", "dup_uprn", "low_score"): + print(f" {name}: {counts.get(name, 0)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/resolve_uprns_for_finaliser.py b/scripts/resolve_uprns_for_finaliser.py new file mode 100644 index 000000000..c01a55ed4 --- /dev/null +++ b/scripts/resolve_uprns_for_finaliser.py @@ -0,0 +1,328 @@ +"""Resolve a CSV of addresses to UPRNs, ready to feed the bulk-upload finaliser. + +Takes a CSV with `Address 1/2/3` + `postcode` columns and, per row, resolves a +UPRN by trying — in order — the new EPC API (address2uprn), the historic EPC S3 +dataset, then the Ordnance Survey Places API as a fallback. Whichever source +wins, the result is written into the SAME three columns the finaliser reads +(`bulk_upload_finaliser_orchestrator`): + + address2uprn_uprn UPRN integer (empty when unresolved) + address2uprn_address the matched address + address2uprn_lexiscore the match score in [0, 1] + +A `resolution_source` diagnostic column (epc / epc_historic / ordnance_survey / +none) is appended too — the finaliser ignores unknown columns. All original +columns are preserved in their original order, so the output CSV drops straight +into the finaliser. + + python -m scripts.resolve_uprns_for_finaliser input.csv -o resolved.csv + + # OS-only / EPC-only, custom postcode column, custom OS score threshold + python -m scripts.resolve_uprns_for_finaliser in.csv -o out.csv --no-epc + python -m scripts.resolve_uprns_for_finaliser in.csv -o out.csv --postcode-col Postcode --os-threshold 0.6 + +Keys are read from backend/.env: OPEN_EPC_API_TOKEN (EPC) and +ORDNANCE_SURVEY_API_KEY (OS Places). Run from the worktree root (import trap). + +The module-level functions (`load_keys`, `read_rows`, `resolve_row`, `process`, +`write_rows`) are written to be driven line-by-line from a REPL as well as via +the CLI. +""" + +from __future__ import annotations + +import argparse +import csv +import os +import sys +from pathlib import Path +from typing import Optional + +import pandas as pd +from dotenv import load_dotenv + +_REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(_REPO_ROOT)) # worktree root first — avoid the import trap + +from backend.address2UPRN.main import ( # noqa: E402 + get_epc_data_with_postcode, + get_uprn_from_historic_epc, + get_uprn_with_epc_df, +) +from backend.ordnanceSurvey.helpers import ( # noqa: E402 + lookup_os_places, + os_places_results_to_dataframe, +) +from backend.utils.addressMatch import AddressMatch # noqa: E402 + +# Columns the finaliser reads (bulk_upload_finaliser_orchestrator). +UPRN_COL = "address2uprn_uprn" +MATCHED_ADDRESS_COL = "address2uprn_address" +LEXISCORE_COL = "address2uprn_lexiscore" +SOURCE_COL = "resolution_source" +_RESULT_COLS = (UPRN_COL, MATCHED_ADDRESS_COL, LEXISCORE_COL, SOURCE_COL) + +# A resolved hit: (uprn, matched_address, lexiscore, source). +Resolution = tuple[str, str, float, str] + + +def load_keys() -> tuple[Optional[str], Optional[str]]: + """Load (epc_token, os_api_key) from backend/.env (and the process env).""" + load_dotenv(_REPO_ROOT / "backend" / ".env") + epc_token = os.environ.get("OPEN_EPC_API_TOKEN") + os_api_key = os.environ.get("ORDNANCE_SURVEY_API_KEY") + return epc_token, os_api_key + + +def read_rows(path: Path) -> tuple[list[dict[str, str]], list[str]]: + """Read a CSV into (rows, fieldnames). Preserves column order.""" + with path.open(newline="", encoding="utf-8-sig") as fh: + reader = csv.DictReader(fh) + fieldnames = list(reader.fieldnames or []) + rows = [dict(row) for row in reader] + return rows, fieldnames + + +def clean_postcode(postcode: str) -> str: + """Sanitise to the no-space upper form the EPC/OS lookups expect (e.g. E84SQ).""" + return postcode.upper().replace(" ", "").strip() + + +def build_address(row: dict[str, str]) -> str: + """Concatenate Address 1/2/3 the same way the address2uprn lambda does.""" + return " ".join( + str(row.get(col, "") or "").strip() for col in ("Address 1", "Address 2", "Address 3") + ).strip() + + +def resolve_epc( + address: str, postcode_clean: str, epc_cache: dict[str, pd.DataFrame] +) -> Optional[Resolution]: + """Resolve via the new EPC API (cached per postcode), then historic EPC S3. + + `epc_cache` is mutated to memoise one EPC API call per postcode — pass the + same dict across rows so a postcode is only fetched once. + """ + epc_df = epc_cache.get(postcode_clean) + if epc_df is None: + epc_df = get_epc_data_with_postcode(postcode=postcode_clean) + epc_cache[postcode_clean] = epc_df + + result = get_uprn_with_epc_df( + user_inputed_address=address, epc_df=epc_df, verbose=True + ) + if isinstance(result, tuple): + uprn, matched, score = result + return str(uprn), str(matched), float(score), "epc" + + historic = get_uprn_from_historic_epc( + user_inputed_address=address, postcode=postcode_clean + ) + if historic is not None: + uprn, matched, score = historic + return str(uprn), str(matched), float(score), "epc_historic" + + return None + + +def resolve_os( + address: str, + postcode_clean: str, + os_api_key: str, + os_cache: dict[str, pd.DataFrame], + threshold: float, +) -> Optional[Resolution]: + """Resolve via the OS Places API: best-scoring address above `threshold`. + + `os_cache` memoises one OS Places call per postcode. + """ + places_df = os_cache.get(postcode_clean) + if places_df is None: + response = lookup_os_places(postcode_clean, os_api_key) + if response.get("status") != 200 or "data" not in response: + places_df = pd.DataFrame() + else: + places_df = os_places_results_to_dataframe(response["data"]) + os_cache[postcode_clean] = places_df + + if places_df.empty or "ADDRESS" not in places_df.columns: + return None + + # Iterate plain records — avoids pandas' partially-unknown indexing types. + records: list[dict[str, object]] = places_df.to_dict(orient="records") + best: Optional[Resolution] = None + for rec in records: + candidate = str(rec.get("ADDRESS", "")) + score = AddressMatch.score(address, candidate) + if score >= threshold and (best is None or score > best[2]): + best = (str(rec.get("UPRN", "")), candidate, score, "ordnance_survey") + return best + + +def resolve_row( + row: dict[str, str], + *, + epc_token: Optional[str], + os_api_key: Optional[str], + epc_cache: dict[str, pd.DataFrame], + os_cache: dict[str, pd.DataFrame], + postcode_col: str, + use_epc: bool, + use_os: bool, + os_threshold: float, + validate_postcode: bool, +) -> dict[str, str]: + """Resolve one row in place and return it with the finaliser columns filled. + + Tries EPC (new + historic) first, then OS Places. On no match the three + result columns are written empty and `resolution_source` is "none". + """ + address = build_address(row) + postcode_clean = clean_postcode(str(row.get(postcode_col, "") or "")) + + def write(res: Optional[Resolution]) -> dict[str, str]: + if res is None: + row[UPRN_COL] = "" + row[MATCHED_ADDRESS_COL] = "" + row[LEXISCORE_COL] = "" + row[SOURCE_COL] = "none" + else: + uprn, matched, score, source = res + row[UPRN_COL] = uprn + row[MATCHED_ADDRESS_COL] = matched + row[LEXISCORE_COL] = str(score) + row[SOURCE_COL] = source + return row + + if not address or not postcode_clean: + return write(None) + + if validate_postcode and not AddressMatch.is_valid_postcode(postcode_clean): + return write(None) + + if use_epc and epc_token: + try: + res = resolve_epc(address, postcode_clean, epc_cache) + if res is not None: + return write(res) + except Exception as exc: # keep going on a per-row API/lookup failure + print(f" EPC lookup failed for {address!r} / {postcode_clean}: {exc}") + + if use_os and os_api_key: + try: + res = resolve_os(address, postcode_clean, os_api_key, os_cache, os_threshold) + if res is not None: + return write(res) + except Exception as exc: + print(f" OS lookup failed for {address!r} / {postcode_clean}: {exc}") + + return write(None) + + +def process( + rows: list[dict[str, str]], + *, + epc_token: Optional[str], + os_api_key: Optional[str], + postcode_col: str = "postcode", + use_epc: bool = True, + use_os: bool = True, + os_threshold: float = 0.5, + validate_postcode: bool = True, +) -> list[dict[str, str]]: + """Resolve every row, printing a per-row line so REPL/CLI progress is visible.""" + epc_cache: dict[str, pd.DataFrame] = {} + os_cache: dict[str, pd.DataFrame] = {} + for i, row in enumerate(rows, start=1): + resolve_row( + row, + epc_token=epc_token, + os_api_key=os_api_key, + epc_cache=epc_cache, + os_cache=os_cache, + postcode_col=postcode_col, + use_epc=use_epc, + use_os=use_os, + os_threshold=os_threshold, + validate_postcode=validate_postcode, + ) + print( + f"[{i}/{len(rows)}] {build_address(row)!r} -> " + f"{row[UPRN_COL] or '(no match)'} ({row[SOURCE_COL]})" + ) + return rows + + +def write_rows(rows: list[dict[str, str]], path: Path, fieldnames: list[str]) -> None: + """Write rows to CSV, preserving input columns and appending the result columns.""" + out_fields = list(fieldnames) + for col in _RESULT_COLS: + if col not in out_fields: + out_fields.append(col) + with path.open("w", newline="", encoding="utf-8") as fh: + writer = csv.DictWriter(fh, fieldnames=out_fields, extrasaction="ignore") + writer.writeheader() + writer.writerows(rows) + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("input", type=Path, help="input CSV (Address 1/2/3 + postcode)") + parser.add_argument( + "-o", "--out", type=Path, required=True, help="output CSV for the finaliser" + ) + parser.add_argument("--postcode-col", default="postcode", help="postcode column name") + parser.add_argument("--no-epc", action="store_true", help="skip EPC resolution") + parser.add_argument("--no-os", action="store_true", help="skip Ordnance Survey fallback") + parser.add_argument( + "--os-threshold", type=float, default=0.5, help="min OS match score (default 0.5)" + ) + parser.add_argument( + "--no-validate-postcode", + action="store_true", + help="skip the postcodes.io validity check (one HTTP call per postcode)", + ) + parser.add_argument("--limit", type=int, default=None, help="process only the first N rows") + return parser.parse_args() + + +def main() -> int: + args = _parse_args() + epc_token, os_api_key = load_keys() + + use_epc = not args.no_epc + use_os = not args.no_os + if use_epc and not epc_token: + print("OPEN_EPC_API_TOKEN not set (backend/.env) — EPC resolution disabled") + use_epc = False + if use_os and not os_api_key: + print("ORDNANCE_SURVEY_API_KEY not set (backend/.env) — OS fallback disabled") + use_os = False + if not use_epc and not use_os: + print("No resolver enabled (missing keys or both --no-* flags). Nothing to do.") + return 2 + + rows, fieldnames = read_rows(args.input) + if args.limit is not None: + rows = rows[: args.limit] + print(f"Loaded {len(rows)} rows from {args.input}") + + process( + rows, + epc_token=epc_token, + os_api_key=os_api_key, + postcode_col=args.postcode_col, + use_epc=use_epc, + use_os=use_os, + os_threshold=args.os_threshold, + validate_postcode=not args.no_validate_postcode, + ) + + write_rows(rows, args.out, fieldnames) + matched = sum(1 for r in rows if r.get(UPRN_COL)) + print(f"\nResolved {matched}/{len(rows)} rows. Wrote {args.out}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 7f8e2762d0487986df9ebdbfeac6e491d681eaaf Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:22:32 +0000 Subject: [PATCH 014/340] =?UTF-8?q?Decode=20landlord=20electricity/LPG/oil?= =?UTF-8?q?/coal=20overrides=20to=20fuel=20codes=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/domain/epc/test_main_fuel_overlay.py | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/domain/epc/test_main_fuel_overlay.py b/tests/domain/epc/test_main_fuel_overlay.py index 96072c1de..f3cf0007c 100644 --- a/tests/domain/epc/test_main_fuel_overlay.py +++ b/tests/domain/epc/test_main_fuel_overlay.py @@ -6,6 +6,8 @@ reads from the dwelling's primary heating system; the overlay is whole-dwelling. from __future__ import annotations +import pytest + from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for @@ -17,3 +19,24 @@ def test_mains_gas_overlays_the_primary_fuel() -> None: assert simulation is not None assert simulation.heating is not None assert simulation.heating.main_fuel_type == 26 + + +@pytest.mark.parametrize( + ("main_fuel_value", "code"), + [ + ("electricity", 29), + ("LPG (bulk)", 27), + ("oil", 28), + ("house coal", 33), + ], +) +def test_fuels_decode_to_their_modern_not_community_codes( + main_fuel_value: str, code: int +) -> None: + # Act + simulation = fuel_overlay_for(main_fuel_value, 0) + + # Assert + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.main_fuel_type == code From a402a3858e868b67b4725a3b567916fa0c4d1dc6 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:22:44 +0000 Subject: [PATCH 015/340] =?UTF-8?q?Decode=20landlord=20electricity/LPG/oil?= =?UTF-8?q?/coal=20overrides=20to=20fuel=20codes=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/epc/property_overlays/main_fuel_overlay.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/domain/epc/property_overlays/main_fuel_overlay.py b/domain/epc/property_overlays/main_fuel_overlay.py index 9118c0c9e..9b580860f 100644 --- a/domain/epc/property_overlays/main_fuel_overlay.py +++ b/domain/epc/property_overlays/main_fuel_overlay.py @@ -18,6 +18,10 @@ from domain.modelling.simulation import EpcSimulation, HeatingOverlay # RdSAP-20/21 `main_fuel` `(not community)` codes (epc_codes.csv `main_fuel`). _FUEL_CODES: dict[str, int] = { "mains gas": 26, + "LPG (bulk)": 27, + "oil": 28, + "electricity": 29, + "house coal": 33, } From 39950d410c0aaba18f814f622ec2e52a59dfb4df Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:22:56 +0000 Subject: [PATCH 016/340] =?UTF-8?q?Map=20community=20mains=20gas=20to=20it?= =?UTF-8?q?s=20distinct=20fuel=20code=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/domain/epc/test_main_fuel_overlay.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/domain/epc/test_main_fuel_overlay.py b/tests/domain/epc/test_main_fuel_overlay.py index f3cf0007c..3f6bfdebd 100644 --- a/tests/domain/epc/test_main_fuel_overlay.py +++ b/tests/domain/epc/test_main_fuel_overlay.py @@ -40,3 +40,13 @@ def test_fuels_decode_to_their_modern_not_community_codes( assert simulation is not None assert simulation.heating is not None assert simulation.heating.main_fuel_type == code + + +def test_community_mains_gas_is_a_distinct_fuel_code() -> None: + # Act + simulation = fuel_overlay_for("mains gas (community)", 0) + + # Assert — community mains gas is code 20, distinct from 26 (not community). + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.main_fuel_type == 20 From 07aea7ef9bb993b5abb653478b762a90f28eade1 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:23:06 +0000 Subject: [PATCH 017/340] =?UTF-8?q?Map=20community=20mains=20gas=20to=20it?= =?UTF-8?q?s=20distinct=20fuel=20code=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/epc/property_overlays/main_fuel_overlay.py | 1 + 1 file changed, 1 insertion(+) diff --git a/domain/epc/property_overlays/main_fuel_overlay.py b/domain/epc/property_overlays/main_fuel_overlay.py index 9b580860f..c31457055 100644 --- a/domain/epc/property_overlays/main_fuel_overlay.py +++ b/domain/epc/property_overlays/main_fuel_overlay.py @@ -18,6 +18,7 @@ from domain.modelling.simulation import EpcSimulation, HeatingOverlay # RdSAP-20/21 `main_fuel` `(not community)` codes (epc_codes.csv `main_fuel`). _FUEL_CODES: dict[str, int] = { "mains gas": 26, + "mains gas (community)": 20, "LPG (bulk)": 27, "oil": 28, "electricity": 29, From 35b48cc8fcde6181969b8d84f3c734e588fdf438 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:23:25 +0000 Subject: [PATCH 018/340] =?UTF-8?q?Produce=20no=20overlay=20for=20an=20unr?= =?UTF-8?q?esolvable=20landlord=20fuel=20value=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/domain/epc/test_main_fuel_overlay.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/domain/epc/test_main_fuel_overlay.py b/tests/domain/epc/test_main_fuel_overlay.py index 3f6bfdebd..1b554f5bd 100644 --- a/tests/domain/epc/test_main_fuel_overlay.py +++ b/tests/domain/epc/test_main_fuel_overlay.py @@ -50,3 +50,12 @@ def test_community_mains_gas_is_a_distinct_fuel_code() -> None: assert simulation is not None assert simulation.heating is not None assert simulation.heating.main_fuel_type == 20 + + +@pytest.mark.parametrize("main_fuel_value", ["Unknown", "", "no heating or hot water"]) +def test_unresolvable_fuel_produces_no_overlay(main_fuel_value: str) -> None: + # Act + simulation = fuel_overlay_for(main_fuel_value, 0) + + # Assert + assert simulation is None From 991eb741325cb3c2bd5a5a19f694c77e510a7548 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:23:37 +0000 Subject: [PATCH 019/340] =?UTF-8?q?Route=20a=20main=5Ffuel=20override=20ro?= =?UTF-8?q?w=20through=20the=20fuel=20overlay=20mapper=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../property/test_landlord_override_overlays.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/repositories/property/test_landlord_override_overlays.py b/tests/repositories/property/test_landlord_override_overlays.py index 20c79c1f6..4c4a34034 100644 --- a/tests/repositories/property/test_landlord_override_overlays.py +++ b/tests/repositories/property/test_landlord_override_overlays.py @@ -47,6 +47,21 @@ def test_each_resolvable_component_produces_an_overlay() -> None: assert len(overlays) == 4 +def test_main_fuel_row_produces_a_heating_fuel_overlay() -> None: + # Arrange + overrides = ResolvedPropertyOverrides( + rows=(ResolvedPropertyOverride("main_fuel", 0, "mains gas"),) + ) + + # Act + overlays = overlays_from(overrides) + + # Assert + assert len(overlays) == 1 + assert overlays[0].heating is not None + assert overlays[0].heating.main_fuel_type == 26 + + def test_unresolvable_rows_are_skipped() -> None: # Arrange — an "Unknown" property type and an unmapped wall material. overrides = ResolvedPropertyOverrides( From b85db926edeb3644f431b2b44fdb6b46922acd97 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:23:52 +0000 Subject: [PATCH 020/340] =?UTF-8?q?Route=20a=20main=5Ffuel=20override=20ro?= =?UTF-8?q?w=20through=20the=20fuel=20overlay=20mapper=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- repositories/property/landlord_override_overlays.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/repositories/property/landlord_override_overlays.py b/repositories/property/landlord_override_overlays.py index ab8d7ca34..9d0f2fc7b 100644 --- a/repositories/property/landlord_override_overlays.py +++ b/repositories/property/landlord_override_overlays.py @@ -29,6 +29,7 @@ from domain.epc.property_overlays.attribute_overlay import ( built_form_overlay_for, property_type_overlay_for, ) +from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for from domain.epc.property_overlays.roof_type_overlay import roof_overlay_for from domain.epc.property_overlays.wall_type_overlay import wall_overlay_for from domain.modelling.simulation import EpcSimulation @@ -43,6 +44,7 @@ _COMPONENT_OVERLAYS: dict[str, Callable[[str, int], Optional[EpcSimulation]]] = "roof_type": roof_overlay_for, "property_type": property_type_overlay_for, "built_form_type": built_form_overlay_for, + "main_fuel": fuel_overlay_for, } From 3dab6fc4251fa37e4367be844bd4f72a75806569 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:24:31 +0000 Subject: [PATCH 021/340] =?UTF-8?q?Remap=20the=20EPC=20primary=20fuel=20fr?= =?UTF-8?q?om=20a=20landlord=20fuel=20override=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/domain/epc/test_main_fuel_overlay.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/domain/epc/test_main_fuel_overlay.py b/tests/domain/epc/test_main_fuel_overlay.py index 1b554f5bd..2ffd9e298 100644 --- a/tests/domain/epc/test_main_fuel_overlay.py +++ b/tests/domain/epc/test_main_fuel_overlay.py @@ -9,6 +9,10 @@ from __future__ import annotations import pytest from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for +from domain.modelling.scoring.overlay_applicator import apply_simulations +from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( + build_epc, +) def test_mains_gas_overlays_the_primary_fuel() -> None: @@ -59,3 +63,16 @@ def test_unresolvable_fuel_produces_no_overlay(main_fuel_value: str) -> None: # Assert assert simulation is None + + +def test_fuel_override_remaps_the_primary_systems_fuel_on_the_epc() -> None: + # Arrange — a landlord correction that the dwelling runs on electricity. + baseline = build_epc() + overlay = fuel_overlay_for("electricity", 0) + assert overlay is not None + + # Act + result = apply_simulations(baseline, [overlay]) + + # Assert — the calculator reads the primary fuel from main_heating_details[0]. + assert result.sap_heating.main_heating_details[0].main_fuel_type == 29 From 04dd2dd222ccdc717a2795eef06d0a702db19002 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:43:54 +0000 Subject: [PATCH 022/340] =?UTF-8?q?Classify=20the=20landlord=20Main=20Fuel?= =?UTF-8?q?=20column=20into=20a=20fuel=20category=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../__init__.py | 0 .../test_build_columns.py | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/applications/landlord_description_overrides/__init__.py create mode 100644 tests/applications/landlord_description_overrides/test_build_columns.py diff --git a/tests/applications/landlord_description_overrides/__init__.py b/tests/applications/landlord_description_overrides/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/applications/landlord_description_overrides/test_build_columns.py b/tests/applications/landlord_description_overrides/test_build_columns.py new file mode 100644 index 000000000..aff2f290f --- /dev/null +++ b/tests/applications/landlord_description_overrides/test_build_columns.py @@ -0,0 +1,27 @@ +"""The landlord-description-overrides handler's column wiring (`_build_columns`). + +A `column_mapping` entry of ``{category -> source header}`` must produce a +ClassifiableColumn that reads the named header and classifies into the +category's enum. This pins the main_fuel category onto the wiring. +""" + +from __future__ import annotations + +from typing import cast + +from applications.landlord_description_overrides.handler import _build_columns +from infrastructure.chatgpt.chatgpt import ChatGPT + + +def test_build_columns_wires_a_main_fuel_classifier_column() -> None: + # Arrange — the factory only stores the injected collaborators, so a bare + # object stands in for the (I/O-bound) ChatGPT client and the DB session. + chat_gpt = cast(ChatGPT, object()) + + # Act + columns = _build_columns({"main_fuel": "Main Fuel"}, chat_gpt, None) + + # Assert — one column, named main_fuel, reading the "Main Fuel" header. + assert len(columns) == 1 + assert columns[0].name == "main_fuel" + assert columns[0].source_column == "Main Fuel" From dbfe9820a5a06a8d1b33d2457098096cef6e1491 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:44:56 +0000 Subject: [PATCH 023/340] =?UTF-8?q?Classify=20the=20landlord=20Main=20Fuel?= =?UTF-8?q?=20column=20into=20a=20fuel=20category=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../landlord_description_overrides/handler.py | 14 ++++ domain/epc/main_fuel_type.py | 23 +++++++ .../landlord_main_fuel_override_table.py | 69 +++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 domain/epc/main_fuel_type.py create mode 100644 infrastructure/postgres/landlord_main_fuel_override_table.py diff --git a/applications/landlord_description_overrides/handler.py b/applications/landlord_description_overrides/handler.py index 5e689a45d..7be4dba12 100644 --- a/applications/landlord_description_overrides/handler.py +++ b/applications/landlord_description_overrides/handler.py @@ -8,6 +8,7 @@ from applications.landlord_description_overrides.landlord_description_overrides_ LandlordDescriptionOverridesTriggerBody, ) from domain.epc.built_form_type import BuiltFormType +from domain.epc.main_fuel_type import MainFuelType from domain.epc.property_type import PropertyType from domain.epc.roof_type import RoofType from domain.epc.wall_type import WallType @@ -24,6 +25,9 @@ from infrastructure.postgres.engine import commit_scope, make_engine, make_sessi from infrastructure.postgres.landlord_built_form_type_override_table import ( LandlordBuiltFormTypeOverrideRow, ) +from infrastructure.postgres.landlord_main_fuel_override_table import ( + LandlordMainFuelOverrideRow, +) from infrastructure.postgres.landlord_property_type_override_table import ( LandlordPropertyTypeOverrideRow, ) @@ -102,6 +106,16 @@ def _build_columns( session, LandlordRoofTypeOverrideRow ), ), + "main_fuel": lambda src: ClassifiableColumn( + name="main_fuel", + source_column=src, + classifier=ChatGptColumnClassifier( + chat_gpt, MainFuelType, MainFuelType.UNKNOWN + ), + repo=LandlordOverridesRepository[MainFuelType]( + session, LandlordMainFuelOverrideRow + ), + ), } columns: list[ClassifiableColumn[Any]] = [] diff --git a/domain/epc/main_fuel_type.py b/domain/epc/main_fuel_type.py new file mode 100644 index 000000000..0ad575c38 --- /dev/null +++ b/domain/epc/main_fuel_type.py @@ -0,0 +1,23 @@ +from enum import Enum + + +class MainFuelType(Enum): + """A landlord-supplied main-fuel description, as resolved by the + landlord-description-overrides context. + + Each member's value is the canonical fuel description that the main-fuel + Simulation Overlay (``domain/epc/property_overlays/main_fuel_overlay.py``) + decomposes into the RdSAP ``main_fuel`` int code the calculator reads — so + the member values here MUST stay in lock-step with that overlay's + ``_FUEL_CODES`` keys. ``UNKNOWN`` covers values the classifier cannot + resolve, and also any fuel not yet given a verified overlay code (it leaves + the lodged cert's fuel untouched rather than guessing). + """ + + MAINS_GAS = "mains gas" + MAINS_GAS_COMMUNITY = "mains gas (community)" + ELECTRICITY = "electricity" + LPG_BULK = "LPG (bulk)" + OIL = "oil" + HOUSE_COAL = "house coal" + UNKNOWN = "Unknown" diff --git a/infrastructure/postgres/landlord_main_fuel_override_table.py b/infrastructure/postgres/landlord_main_fuel_override_table.py new file mode 100644 index 000000000..c046453bb --- /dev/null +++ b/infrastructure/postgres/landlord_main_fuel_override_table.py @@ -0,0 +1,69 @@ +"""SQLModel mirror of the ``landlord_main_fuel_overrides`` Drizzle table. + +The schema source of truth lives in the ``assessment-model`` TS repo +(`src/app/db/schema/landlord_overrides.ts`). The migrations are owned there; +this row class only mirrors the columns so the Python lambda can read/write. +See ADR-0003. Shape mirrors ``LandlordWallTypeOverrideRow`` -- the only +differences are the table name, the ``main_fuel`` pgEnum on ``value``, and +the unique-constraint name. +""" + +from datetime import datetime, timezone +from typing import ClassVar +from uuid import UUID, uuid4 + +from sqlalchemy import BigInteger, Column, UniqueConstraint +from sqlalchemy import Enum as SAEnum +from sqlmodel import Field, SQLModel + +from domain.epc.main_fuel_type import MainFuelType +from infrastructure.postgres.landlord_override_enums import override_source_sa_enum + + +class LandlordMainFuelOverrideRow(SQLModel, table=True): + __tablename__: ClassVar[str] = "landlord_main_fuel_overrides" # pyright: ignore[reportIncompatibleVariableOverride] + __table_args__: ClassVar[tuple[UniqueConstraint, ...]] = ( # pyright: ignore[reportIncompatibleVariableOverride] + UniqueConstraint( + "portfolio_id", + "description", + name="landlord_main_fuel_overrides_portfolio_description_unique", + ), + ) + + id: UUID = Field(default_factory=uuid4, primary_key=True) + + # bigint to match the Drizzle ``portfolio_id`` FK; SQLModel's default int + # mapping is 32-bit Integer and would overflow once portfolio IDs exceed + # 2^31. The FK to ``portfolio.id`` is enforced by the Drizzle migration, + # not declared here -- the ``portfolio`` table is not modelled in Python. + portfolio_id: int = Field( + sa_column=Column(BigInteger, nullable=False, index=True), + ) + + description: str = Field(nullable=False) + + value: MainFuelType = Field( + sa_column=Column( + SAEnum( + MainFuelType, + name="main_fuel", + values_callable=lambda cls: [m.value for m in cls], # pyright: ignore[reportUnknownLambdaType, reportUnknownMemberType, reportUnknownVariableType] + ), + nullable=False, + ), + ) + + # Shared SAEnum -- see ``landlord_override_enums`` for why this single + # instance is reused by every ``landlord_*_overrides`` row class. + source: str = Field( + sa_column=Column(override_source_sa_enum, nullable=False), + ) + + created_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc), + nullable=False, + ) + updated_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc), + nullable=False, + ) From cab69bbca9209e582db110a93cf7f62d390e9f8b Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 12:45:22 +0000 Subject: [PATCH 024/340] =?UTF-8?q?Guarantee=20every=20classifier=20fuel?= =?UTF-8?q?=20value=20maps=20to=20an=20overlay=20code=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/domain/epc/test_main_fuel_overlay.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/domain/epc/test_main_fuel_overlay.py b/tests/domain/epc/test_main_fuel_overlay.py index 2ffd9e298..6001ddc36 100644 --- a/tests/domain/epc/test_main_fuel_overlay.py +++ b/tests/domain/epc/test_main_fuel_overlay.py @@ -8,6 +8,7 @@ from __future__ import annotations import pytest +from domain.epc.main_fuel_type import MainFuelType from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for from domain.modelling.scoring.overlay_applicator import apply_simulations from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( @@ -76,3 +77,18 @@ def test_fuel_override_remaps_the_primary_systems_fuel_on_the_epc() -> None: # Assert — the calculator reads the primary fuel from main_heating_details[0]. assert result.sap_heating.main_heating_details[0].main_fuel_type == 29 + + +@pytest.mark.parametrize( + "member", [m for m in MainFuelType if m is not MainFuelType.UNKNOWN] +) +def test_every_resolvable_fuel_value_decodes_to_a_code(member: MainFuelType) -> None: + # A classifier emits a MainFuelType value; if the overlay can't decode it the + # override silently no-ops. Every non-UNKNOWN member must resolve. + + # Act + simulation = fuel_overlay_for(member.value, 0) + + # Assert + assert simulation is not None + From fd922a26c293dca75c375d7c72d24754400b3d36 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:01:07 +0000 Subject: [PATCH 025/340] =?UTF-8?q?Satisfy=20strict=20type-checking=20for?= =?UTF-8?q?=20the=20main=5Ffuel=20classifier=20wiring=20=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../landlord_description_overrides/test_build_columns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/applications/landlord_description_overrides/test_build_columns.py b/tests/applications/landlord_description_overrides/test_build_columns.py index aff2f290f..02feaab6a 100644 --- a/tests/applications/landlord_description_overrides/test_build_columns.py +++ b/tests/applications/landlord_description_overrides/test_build_columns.py @@ -9,7 +9,7 @@ from __future__ import annotations from typing import cast -from applications.landlord_description_overrides.handler import _build_columns +from applications.landlord_description_overrides.handler import _build_columns # pyright: ignore[reportPrivateUsage] from infrastructure.chatgpt.chatgpt import ChatGPT From f1c6825cae95bdd38b921ae0586602bfc724e19e Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:18:45 +0000 Subject: [PATCH 026/340] =?UTF-8?q?Resolve=20a=20landlord=20double-glazing?= =?UTF-8?q?=20override=20to=20its=20glazing=20code=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc/property_overlays/glazing_overlay.py | 23 +++++++++++++++++++ domain/modelling/simulation.py | 23 +++++++++++++++++++ tests/domain/epc/test_glazing_overlay.py | 20 ++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 domain/epc/property_overlays/glazing_overlay.py create mode 100644 tests/domain/epc/test_glazing_overlay.py diff --git a/domain/epc/property_overlays/glazing_overlay.py b/domain/epc/property_overlays/glazing_overlay.py new file mode 100644 index 000000000..9da7582d7 --- /dev/null +++ b/domain/epc/property_overlays/glazing_overlay.py @@ -0,0 +1,23 @@ +"""Map a Landlord-Override glazing value to a glazing Simulation Overlay. + +A glazing value is one canonical glazing description carrying type + era +("Double glazing, 2002 or later", "Single glazing", "Triple glazing, 2002 or +later"). The calculator derives each window's U-value from its SAP10 +`glazing_type` code via the RdSAP Table 24 cascade, so the overlay decomposes +the value into that code and emits a whole-dwelling `GlazingOverlay` (a landlord +describes the dwelling's glazing as a whole, with no per-window geometry, so +`building_part` is ignored). `_fold_glazing` expands it across every window. +Unresolvable values produce no overlay. +""" + +from __future__ import annotations + +from typing import Optional + +from domain.modelling.simulation import EpcSimulation + + +def glazing_overlay_for( + glazing_value: str, building_part: int +) -> Optional[EpcSimulation]: + raise NotImplementedError diff --git a/domain/modelling/simulation.py b/domain/modelling/simulation.py index 624826f68..217d446b5 100644 --- a/domain/modelling/simulation.py +++ b/domain/modelling/simulation.py @@ -73,6 +73,28 @@ class WindowOverlay: solar_transmittance: Optional[float] = None +@dataclass(frozen=True) +class GlazingOverlay: + """All-optional partial of the dwelling's whole-glazing state — the + correction a Landlord Override makes when the lodged glazing is wrong. + + Unlike a per-window `WindowOverlay` (keyed by `sap_windows` index), this + targets no single window: a landlord describes the dwelling's glazing as a + whole ("Double glazing, 2002 or later") with no per-window geometry, so the + overlay builder (which never sees the baseline window list) emits one of + these and `_fold_glazing` expands it across every `sap_windows` entry. + + `glazing_type` is the SAP10 glazing-type code (Table 24 / `u_window` + cascade: 1=single, 2=double 2002-2021, 3=double pre-2002, 9=triple 2002+, + …). The fold sets it on every window AND clears each window's lodged + transmission U-value, so the Table-24 cascade re-derives the corrected U + from the new type (the lodged U was for the OLD, mis-recorded glazing). + A `None` field means "leave the baseline value unchanged". + """ + + glazing_type: Optional[int] = None + + @dataclass(frozen=True) class LightingOverlay: """All-optional partial of the dwelling's fixed-lighting bulb counts — the @@ -220,6 +242,7 @@ class EpcSimulation: windows: Mapping[int, WindowOverlay] = field(default_factory=_no_windows) ventilation: Optional[VentilationOverlay] = None lighting: Optional[LightingOverlay] = None + glazing: Optional[GlazingOverlay] = None heating: Optional[HeatingOverlay] = None secondary_heating: Optional[SecondaryHeatingOverlay] = None solar: Optional[SolarOverlay] = None diff --git a/tests/domain/epc/test_glazing_overlay.py b/tests/domain/epc/test_glazing_overlay.py new file mode 100644 index 000000000..b54e134e3 --- /dev/null +++ b/tests/domain/epc/test_glazing_overlay.py @@ -0,0 +1,20 @@ +"""The Landlord-Override glazing → glazing Simulation Overlay mapping. + +A glazing value resolves to the SAP10 `glazing_type` code the calculator's +Table-24 cascade reads; the overlay is whole-dwelling (expanded across every +window by `_fold_glazing`). +""" + +from __future__ import annotations + +from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for + + +def test_double_glazing_post_2002_overlays_its_glazing_code() -> None: + # Act + simulation = glazing_overlay_for("Double glazing, 2002 or later", 0) + + # Assert — double glazing 2002-2021 is SAP10 glazing_type code 2. + assert simulation is not None + assert simulation.glazing is not None + assert simulation.glazing.glazing_type == 2 From 0fb86771dd0607b4d43e776ced5d807f0bf3d02a Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:19:02 +0000 Subject: [PATCH 027/340] =?UTF-8?q?Resolve=20a=20landlord=20double-glazing?= =?UTF-8?q?=20override=20to=20its=20glazing=20code=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/epc/property_overlays/glazing_overlay.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/domain/epc/property_overlays/glazing_overlay.py b/domain/epc/property_overlays/glazing_overlay.py index 9da7582d7..6ee050304 100644 --- a/domain/epc/property_overlays/glazing_overlay.py +++ b/domain/epc/property_overlays/glazing_overlay.py @@ -14,10 +14,19 @@ from __future__ import annotations from typing import Optional -from domain.modelling.simulation import EpcSimulation +from domain.modelling.simulation import EpcSimulation, GlazingOverlay + +# Canonical glazing description → SAP10 glazing-type code (the Table 24 / +# `u_window` cascade enum, `_GLAZING_CODE_TO_UWINDOW` in heat_transmission). +_GLAZING_CODES: dict[str, int] = { + "Double glazing, 2002 or later": 2, +} def glazing_overlay_for( glazing_value: str, building_part: int ) -> Optional[EpcSimulation]: - raise NotImplementedError + code = _GLAZING_CODES.get(glazing_value) + if code is None: + return None + return EpcSimulation(glazing=GlazingOverlay(glazing_type=code)) From 917627c8336151bb8525ca32f9aba5b57810d90c Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:19:33 +0000 Subject: [PATCH 028/340] =?UTF-8?q?Decode=20single/double/triple=20glazing?= =?UTF-8?q?=20overrides=20to=20SAP=20codes=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/domain/epc/test_glazing_overlay.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/domain/epc/test_glazing_overlay.py b/tests/domain/epc/test_glazing_overlay.py index b54e134e3..a5a6699e8 100644 --- a/tests/domain/epc/test_glazing_overlay.py +++ b/tests/domain/epc/test_glazing_overlay.py @@ -7,6 +7,8 @@ window by `_fold_glazing`). from __future__ import annotations +import pytest + from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for @@ -18,3 +20,24 @@ def test_double_glazing_post_2002_overlays_its_glazing_code() -> None: assert simulation is not None assert simulation.glazing is not None assert simulation.glazing.glazing_type == 2 + + +@pytest.mark.parametrize( + ("glazing_value", "code"), + [ + ("Single glazing", 1), + ("Double glazing, pre-2002", 3), + ("Triple glazing, 2002 or later", 9), + ("Triple glazing, pre-2002", 6), + ], +) +def test_glazing_types_decode_to_their_sap_codes( + glazing_value: str, code: int +) -> None: + # Act + simulation = glazing_overlay_for(glazing_value, 0) + + # Assert + assert simulation is not None + assert simulation.glazing is not None + assert simulation.glazing.glazing_type == code From 88a4badbf7873d90ae609cddc60d677ff176a0f9 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:19:44 +0000 Subject: [PATCH 029/340] =?UTF-8?q?Decode=20single/double/triple=20glazing?= =?UTF-8?q?=20overrides=20to=20SAP=20codes=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/epc/property_overlays/glazing_overlay.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/domain/epc/property_overlays/glazing_overlay.py b/domain/epc/property_overlays/glazing_overlay.py index 6ee050304..d692181d8 100644 --- a/domain/epc/property_overlays/glazing_overlay.py +++ b/domain/epc/property_overlays/glazing_overlay.py @@ -19,7 +19,11 @@ from domain.modelling.simulation import EpcSimulation, GlazingOverlay # Canonical glazing description → SAP10 glazing-type code (the Table 24 / # `u_window` cascade enum, `_GLAZING_CODE_TO_UWINDOW` in heat_transmission). _GLAZING_CODES: dict[str, int] = { + "Single glazing": 1, "Double glazing, 2002 or later": 2, + "Double glazing, pre-2002": 3, + "Triple glazing, pre-2002": 6, + "Triple glazing, 2002 or later": 9, } From 71bdcabb73a92e257f0704dafda136161d07d0db Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:19:56 +0000 Subject: [PATCH 030/340] =?UTF-8?q?Produce=20no=20overlay=20for=20an=20unr?= =?UTF-8?q?esolvable=20landlord=20glazing=20value=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/domain/epc/test_glazing_overlay.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/domain/epc/test_glazing_overlay.py b/tests/domain/epc/test_glazing_overlay.py index a5a6699e8..9668e3aa5 100644 --- a/tests/domain/epc/test_glazing_overlay.py +++ b/tests/domain/epc/test_glazing_overlay.py @@ -41,3 +41,12 @@ def test_glazing_types_decode_to_their_sap_codes( assert simulation is not None assert simulation.glazing is not None assert simulation.glazing.glazing_type == code + + +@pytest.mark.parametrize("glazing_value", ["Unknown", ""]) +def test_unresolvable_glazing_produces_no_overlay(glazing_value: str) -> None: + # Act + simulation = glazing_overlay_for(glazing_value, 0) + + # Assert + assert simulation is None From 1810f09240457c5b312a4a020d9669d403b6f48c Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:20:09 +0000 Subject: [PATCH 031/340] =?UTF-8?q?Route=20a=20glazing=20override=20row=20?= =?UTF-8?q?through=20the=20glazing=20overlay=20mapper=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../property/test_landlord_override_overlays.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/repositories/property/test_landlord_override_overlays.py b/tests/repositories/property/test_landlord_override_overlays.py index 4c4a34034..ce86e78aa 100644 --- a/tests/repositories/property/test_landlord_override_overlays.py +++ b/tests/repositories/property/test_landlord_override_overlays.py @@ -62,6 +62,21 @@ def test_main_fuel_row_produces_a_heating_fuel_overlay() -> None: assert overlays[0].heating.main_fuel_type == 26 +def test_glazing_row_produces_a_glazing_overlay() -> None: + # Arrange + overrides = ResolvedPropertyOverrides( + rows=(ResolvedPropertyOverride("glazing", 0, "Double glazing, 2002 or later"),) + ) + + # Act + overlays = overlays_from(overrides) + + # Assert + assert len(overlays) == 1 + assert overlays[0].glazing is not None + assert overlays[0].glazing.glazing_type == 2 + + def test_unresolvable_rows_are_skipped() -> None: # Arrange — an "Unknown" property type and an unmapped wall material. overrides = ResolvedPropertyOverrides( From 5a1cf288927e63c3e6f60a08d2d8fcd8a20aaec9 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:20:25 +0000 Subject: [PATCH 032/340] =?UTF-8?q?Route=20a=20glazing=20override=20row=20?= =?UTF-8?q?through=20the=20glazing=20overlay=20mapper=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- repositories/property/landlord_override_overlays.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/repositories/property/landlord_override_overlays.py b/repositories/property/landlord_override_overlays.py index 9d0f2fc7b..624487eb2 100644 --- a/repositories/property/landlord_override_overlays.py +++ b/repositories/property/landlord_override_overlays.py @@ -29,6 +29,7 @@ from domain.epc.property_overlays.attribute_overlay import ( built_form_overlay_for, property_type_overlay_for, ) +from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for from domain.epc.property_overlays.roof_type_overlay import roof_overlay_for from domain.epc.property_overlays.wall_type_overlay import wall_overlay_for @@ -45,6 +46,7 @@ _COMPONENT_OVERLAYS: dict[str, Callable[[str, int], Optional[EpcSimulation]]] = "property_type": property_type_overlay_for, "built_form_type": built_form_overlay_for, "main_fuel": fuel_overlay_for, + "glazing": glazing_overlay_for, } From 8ce22a5ccb35fecbf1249d4922b62a8052cf3a9e Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:21:06 +0000 Subject: [PATCH 033/340] =?UTF-8?q?Remap=20every=20window's=20glazing=20fr?= =?UTF-8?q?om=20a=20landlord=20glazing=20override=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/domain/epc/test_glazing_overlay.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/domain/epc/test_glazing_overlay.py b/tests/domain/epc/test_glazing_overlay.py index 9668e3aa5..d65220e71 100644 --- a/tests/domain/epc/test_glazing_overlay.py +++ b/tests/domain/epc/test_glazing_overlay.py @@ -10,6 +10,10 @@ from __future__ import annotations import pytest from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for +from domain.modelling.scoring.overlay_applicator import apply_simulations +from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( + build_epc, +) def test_double_glazing_post_2002_overlays_its_glazing_code() -> None: @@ -50,3 +54,20 @@ def test_unresolvable_glazing_produces_no_overlay(glazing_value: str) -> None: # Assert assert simulation is None + + +def test_glazing_override_remaps_every_window_and_clears_lodged_u() -> None: + # Arrange — baseline windows are double glazed (code 2, lodged U 2.8); the + # landlord corrects the whole dwelling to single glazing. + baseline = build_epc() + assert len(baseline.sap_windows) > 1 + overlay = glazing_overlay_for("Single glazing", 0) + assert overlay is not None + + # Act + result = apply_simulations(baseline, [overlay]) + + # Assert — every window flips to single (code 1) and its lodged transmission + # U is cleared so the Table-24 cascade re-derives U from the new type. + assert all(w.glazing_type == 1 for w in result.sap_windows) + assert all(w.window_transmission_details is None for w in result.sap_windows) From 608e96bb09b4c81204be04a4b8fba5622fa1b09f Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:21:42 +0000 Subject: [PATCH 034/340] =?UTF-8?q?Remap=20every=20window's=20glazing=20fr?= =?UTF-8?q?om=20a=20landlord=20glazing=20override=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/modelling/scoring/overlay_applicator.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/domain/modelling/scoring/overlay_applicator.py b/domain/modelling/scoring/overlay_applicator.py index 9c83724a2..961f4e024 100644 --- a/domain/modelling/scoring/overlay_applicator.py +++ b/domain/modelling/scoring/overlay_applicator.py @@ -19,6 +19,7 @@ from datatypes.epc.domain.epc_property_data import ( ) from domain.modelling.simulation import ( EpcSimulation, + GlazingOverlay, HeatingOverlay, LightingOverlay, SecondaryHeatingOverlay, @@ -53,6 +54,8 @@ def apply_simulations( ) if simulation.lighting is not None: _fold_lighting(result, simulation.lighting) + if simulation.glazing is not None: + _fold_glazing(result, simulation.glazing) if simulation.heating is not None: _fold_heating(result, simulation.heating) if simulation.secondary_heating is not None: @@ -202,6 +205,21 @@ def _fold_window(window: SapWindow, overlay: WindowOverlay) -> None: details.solar_transmittance = overlay.solar_transmittance +def _fold_glazing(epc: EpcPropertyData, overlay: GlazingOverlay) -> None: + """Expand a whole-dwelling `GlazingOverlay` across every window: set each + window's `glazing_type` to the corrected SAP10 code AND clear its lodged + transmission U, so `heat_transmission`'s Table-24 cascade re-derives U from + the new type (the lodged U was for the old, mis-recorded glazing). A landlord + glazing override carries no per-window geometry, so it applies uniformly — + the expansion lives here because the baseline window list is known only at + fold time.""" + if overlay.glazing_type is None: + return + for window in epc.sap_windows: + window.glazing_type = overlay.glazing_type + window.window_transmission_details = None + + def _fold_ventilation( baseline: Optional[SapVentilation], overlay: VentilationOverlay ) -> SapVentilation: From 0b782bd1a60d1ce0029af3faba7399602f5cbbbf Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:35:39 +0000 Subject: [PATCH 035/340] =?UTF-8?q?Classify=20the=20landlord=20Glazing=20c?= =?UTF-8?q?olumn=20into=20a=20glazing=20category=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../test_build_columns.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/applications/landlord_description_overrides/test_build_columns.py b/tests/applications/landlord_description_overrides/test_build_columns.py index 02feaab6a..c67487281 100644 --- a/tests/applications/landlord_description_overrides/test_build_columns.py +++ b/tests/applications/landlord_description_overrides/test_build_columns.py @@ -25,3 +25,16 @@ def test_build_columns_wires_a_main_fuel_classifier_column() -> None: assert len(columns) == 1 assert columns[0].name == "main_fuel" assert columns[0].source_column == "Main Fuel" + + +def test_build_columns_wires_a_glazing_classifier_column() -> None: + # Arrange + chat_gpt = cast(ChatGPT, object()) + + # Act + columns = _build_columns({"glazing": "Glazing"}, chat_gpt, None) + + # Assert — one column, named glazing, reading the "Glazing" header. + assert len(columns) == 1 + assert columns[0].name == "glazing" + assert columns[0].source_column == "Glazing" From db95205fc53f85890b0d8318cb08f7f82a5cb187 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:36:34 +0000 Subject: [PATCH 036/340] =?UTF-8?q?Classify=20the=20landlord=20Glazing=20c?= =?UTF-8?q?olumn=20into=20a=20glazing=20category=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../landlord_description_overrides/handler.py | 14 ++++ domain/epc/glazing_type.py | 24 +++++++ .../landlord_glazing_override_table.py | 69 +++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 domain/epc/glazing_type.py create mode 100644 infrastructure/postgres/landlord_glazing_override_table.py diff --git a/applications/landlord_description_overrides/handler.py b/applications/landlord_description_overrides/handler.py index 7be4dba12..16dbb000c 100644 --- a/applications/landlord_description_overrides/handler.py +++ b/applications/landlord_description_overrides/handler.py @@ -8,6 +8,7 @@ from applications.landlord_description_overrides.landlord_description_overrides_ LandlordDescriptionOverridesTriggerBody, ) from domain.epc.built_form_type import BuiltFormType +from domain.epc.glazing_type import GlazingType from domain.epc.main_fuel_type import MainFuelType from domain.epc.property_type import PropertyType from domain.epc.roof_type import RoofType @@ -25,6 +26,9 @@ from infrastructure.postgres.engine import commit_scope, make_engine, make_sessi from infrastructure.postgres.landlord_built_form_type_override_table import ( LandlordBuiltFormTypeOverrideRow, ) +from infrastructure.postgres.landlord_glazing_override_table import ( + LandlordGlazingOverrideRow, +) from infrastructure.postgres.landlord_main_fuel_override_table import ( LandlordMainFuelOverrideRow, ) @@ -116,6 +120,16 @@ def _build_columns( session, LandlordMainFuelOverrideRow ), ), + "glazing": lambda src: ClassifiableColumn( + name="glazing", + source_column=src, + classifier=ChatGptColumnClassifier( + chat_gpt, GlazingType, GlazingType.UNKNOWN + ), + repo=LandlordOverridesRepository[GlazingType]( + session, LandlordGlazingOverrideRow + ), + ), } columns: list[ClassifiableColumn[Any]] = [] diff --git a/domain/epc/glazing_type.py b/domain/epc/glazing_type.py new file mode 100644 index 000000000..ee3ad267b --- /dev/null +++ b/domain/epc/glazing_type.py @@ -0,0 +1,24 @@ +from enum import Enum + + +class GlazingType(Enum): + """A landlord-supplied glazing description, as resolved by the + landlord-description-overrides context. + + Each member's value is the canonical glazing description (type + era) that + the glazing Simulation Overlay + (``domain/epc/property_overlays/glazing_overlay.py``) decomposes into the + SAP10 ``glazing_type`` code the calculator's Table-24 cascade reads — so the + member values here MUST stay in lock-step with that overlay's + ``_GLAZING_CODES`` keys. The era matters: double-glazing pre-2002 and + 2002-onward resolve to different codes (and U-values). ``UNKNOWN`` covers + values the classifier cannot resolve, and any glazing not yet given a + verified overlay code (it leaves the lodged cert's glazing untouched). + """ + + SINGLE = "Single glazing" + DOUBLE_POST_2002 = "Double glazing, 2002 or later" + DOUBLE_PRE_2002 = "Double glazing, pre-2002" + TRIPLE_PRE_2002 = "Triple glazing, pre-2002" + TRIPLE_POST_2002 = "Triple glazing, 2002 or later" + UNKNOWN = "Unknown" diff --git a/infrastructure/postgres/landlord_glazing_override_table.py b/infrastructure/postgres/landlord_glazing_override_table.py new file mode 100644 index 000000000..f42a48d2f --- /dev/null +++ b/infrastructure/postgres/landlord_glazing_override_table.py @@ -0,0 +1,69 @@ +"""SQLModel mirror of the ``landlord_glazing_overrides`` Drizzle table. + +The schema source of truth lives in the ``assessment-model`` TS repo +(`src/app/db/schema/landlord_overrides.ts`). The migrations are owned there; +this row class only mirrors the columns so the Python lambda can read/write. +See ADR-0003. Shape mirrors ``LandlordWallTypeOverrideRow`` -- the only +differences are the table name, the ``glazing`` pgEnum on ``value``, and the +unique-constraint name. +""" + +from datetime import datetime, timezone +from typing import ClassVar +from uuid import UUID, uuid4 + +from sqlalchemy import BigInteger, Column, UniqueConstraint +from sqlalchemy import Enum as SAEnum +from sqlmodel import Field, SQLModel + +from domain.epc.glazing_type import GlazingType +from infrastructure.postgres.landlord_override_enums import override_source_sa_enum + + +class LandlordGlazingOverrideRow(SQLModel, table=True): + __tablename__: ClassVar[str] = "landlord_glazing_overrides" # pyright: ignore[reportIncompatibleVariableOverride] + __table_args__: ClassVar[tuple[UniqueConstraint, ...]] = ( # pyright: ignore[reportIncompatibleVariableOverride] + UniqueConstraint( + "portfolio_id", + "description", + name="landlord_glazing_overrides_portfolio_description_unique", + ), + ) + + id: UUID = Field(default_factory=uuid4, primary_key=True) + + # bigint to match the Drizzle ``portfolio_id`` FK; SQLModel's default int + # mapping is 32-bit Integer and would overflow once portfolio IDs exceed + # 2^31. The FK to ``portfolio.id`` is enforced by the Drizzle migration, + # not declared here -- the ``portfolio`` table is not modelled in Python. + portfolio_id: int = Field( + sa_column=Column(BigInteger, nullable=False, index=True), + ) + + description: str = Field(nullable=False) + + value: GlazingType = Field( + sa_column=Column( + SAEnum( + GlazingType, + name="glazing", + values_callable=lambda cls: [m.value for m in cls], # pyright: ignore[reportUnknownLambdaType, reportUnknownMemberType, reportUnknownVariableType] + ), + nullable=False, + ), + ) + + # Shared SAEnum -- see ``landlord_override_enums`` for why this single + # instance is reused by every ``landlord_*_overrides`` row class. + source: str = Field( + sa_column=Column(override_source_sa_enum, nullable=False), + ) + + created_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc), + nullable=False, + ) + updated_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc), + nullable=False, + ) From 5a6d9a4e5d0d8a0fb6441bf1cb1736cfe2567294 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:38:07 +0000 Subject: [PATCH 037/340] =?UTF-8?q?Guarantee=20every=20classifier=20glazin?= =?UTF-8?q?g=20value=20maps=20to=20an=20overlay=20code=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/domain/epc/test_glazing_overlay.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/domain/epc/test_glazing_overlay.py b/tests/domain/epc/test_glazing_overlay.py index d65220e71..c7ba72a8c 100644 --- a/tests/domain/epc/test_glazing_overlay.py +++ b/tests/domain/epc/test_glazing_overlay.py @@ -9,6 +9,7 @@ from __future__ import annotations import pytest +from domain.epc.glazing_type import GlazingType from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for from domain.modelling.scoring.overlay_applicator import apply_simulations from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( @@ -71,3 +72,19 @@ def test_glazing_override_remaps_every_window_and_clears_lodged_u() -> None: # U is cleared so the Table-24 cascade re-derives U from the new type. assert all(w.glazing_type == 1 for w in result.sap_windows) assert all(w.window_transmission_details is None for w in result.sap_windows) + + +@pytest.mark.parametrize( + "member", [m for m in GlazingType if m is not GlazingType.UNKNOWN] +) +def test_every_resolvable_glazing_value_decodes_to_a_code( + member: GlazingType, +) -> None: + # A classifier emits a GlazingType value; if the overlay can't decode it the + # override silently no-ops. Every non-UNKNOWN member must resolve. + + # Act + simulation = glazing_overlay_for(member.value, 0) + + # Assert + assert simulation is not None From 0135f0f27b33a1d0f9e871d5c6fa4c36a27155df Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:41:20 +0000 Subject: [PATCH 038/340] =?UTF-8?q?Resolve=20a=20landlord=20age-band=20ove?= =?UTF-8?q?rride=20onto=20the=20main=20building=20part=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../construction_age_band_overlay.py | 22 ++++++++++++++++++ domain/modelling/simulation.py | 6 +++++ .../epc/test_construction_age_band_overlay.py | 23 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 domain/epc/property_overlays/construction_age_band_overlay.py create mode 100644 tests/domain/epc/test_construction_age_band_overlay.py diff --git a/domain/epc/property_overlays/construction_age_band_overlay.py b/domain/epc/property_overlays/construction_age_band_overlay.py new file mode 100644 index 000000000..d4cc6eae7 --- /dev/null +++ b/domain/epc/property_overlays/construction_age_band_overlay.py @@ -0,0 +1,22 @@ +"""Map a Landlord-Override construction-age-band value to a fabric Simulation +Overlay. + +A construction-age-band value is the RdSAP England-&-Wales letter code (A..M) +the calculator's U-value cascades key on (`SapBuildingPart.construction_age_band`, +read via `.strip().upper()` against the letter-code bands). The overlay targets +the override's building part and sets the band; an unrecognised code produces no +overlay. Re-dating a part re-derives its construction-default U-values, so this +is the highest-leverage fabric override. +""" + +from __future__ import annotations + +from typing import Optional + +from domain.modelling.simulation import EpcSimulation + + +def age_band_overlay_for( + age_band_value: str, building_part: int +) -> Optional[EpcSimulation]: + raise NotImplementedError diff --git a/domain/modelling/simulation.py b/domain/modelling/simulation.py index 217d446b5..caee5fb51 100644 --- a/domain/modelling/simulation.py +++ b/domain/modelling/simulation.py @@ -28,6 +28,12 @@ class BuildingPartOverlay: # The wall material (RdSAP `wall_construction` code). Left `None` by Measures # — insulating a wall doesn't change its material — but set by a Landlord # Override that corrects the construction itself (ADR-0032). + # RdSAP England-&-Wales construction age band — the letter code A..M the + # calculator's U-value cascades key on (`SapBuildingPart.construction_age_band`). + # Left `None` by Measures (retrofits don't change build era); set by a Landlord + # Override that corrects the lodged age band, which re-derives this part's + # fabric U-value defaults. Folds onto the part via the generic field loop. + construction_age_band: Optional[str] = None wall_construction: Optional[int] = None wall_insulation_type: Optional[int] = None # Added solid-wall insulation depth (mm) — drives the calculator's Table 6 diff --git a/tests/domain/epc/test_construction_age_band_overlay.py b/tests/domain/epc/test_construction_age_band_overlay.py new file mode 100644 index 000000000..066244a61 --- /dev/null +++ b/tests/domain/epc/test_construction_age_band_overlay.py @@ -0,0 +1,23 @@ +"""The Landlord-Override construction-age-band → fabric Simulation Overlay. + +An age-band value resolves to the RdSAP letter code the calculator's U-value +cascades read from `SapBuildingPart.construction_age_band`; the overlay targets +the override's building part. +""" + +from __future__ import annotations + +from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier +from domain.epc.property_overlays.construction_age_band_overlay import ( + age_band_overlay_for, +) + + +def test_age_band_overlays_the_main_building_part() -> None: + # Act — band B (1900-1929) on the main building part. + simulation = age_band_overlay_for("B", 0) + + # Assert + assert simulation is not None + overlay = simulation.building_parts[BuildingPartIdentifier.MAIN] + assert overlay.construction_age_band == "B" From 406365753bcdf2b75a4ec66787aa375746896cb4 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:41:35 +0000 Subject: [PATCH 039/340] =?UTF-8?q?Resolve=20a=20landlord=20age-band=20ove?= =?UTF-8?q?rride=20onto=20the=20main=20building=20part=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../construction_age_band_overlay.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/domain/epc/property_overlays/construction_age_band_overlay.py b/domain/epc/property_overlays/construction_age_band_overlay.py index d4cc6eae7..55054168a 100644 --- a/domain/epc/property_overlays/construction_age_band_overlay.py +++ b/domain/epc/property_overlays/construction_age_band_overlay.py @@ -13,10 +13,27 @@ from __future__ import annotations from typing import Optional -from domain.modelling.simulation import EpcSimulation +from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier +from domain.modelling.simulation import BuildingPartOverlay, EpcSimulation + +# RdSAP England-&-Wales construction age bands (letter codes A..M). +_VALID_AGE_BANDS: frozenset[str] = frozenset("ABCDEFGHIJKLM") def age_band_overlay_for( age_band_value: str, building_part: int ) -> Optional[EpcSimulation]: - raise NotImplementedError + band = age_band_value.strip().upper() + if band not in _VALID_AGE_BANDS: + return None + + identifier = ( + BuildingPartIdentifier.MAIN + if building_part == 0 + else BuildingPartIdentifier.extension(building_part) + ) + return EpcSimulation( + building_parts={ + identifier: BuildingPartOverlay(construction_age_band=band) + } + ) From cd14751fdb5a27c1cc50001f70e42986ee8de43d Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:42:02 +0000 Subject: [PATCH 040/340] =?UTF-8?q?Route=20age-band=20overrides=20to=20the?= =?UTF-8?q?=20right=20part,=20normalise,=20reject=20unknown=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc/test_construction_age_band_overlay.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/domain/epc/test_construction_age_band_overlay.py b/tests/domain/epc/test_construction_age_band_overlay.py index 066244a61..bb66933eb 100644 --- a/tests/domain/epc/test_construction_age_band_overlay.py +++ b/tests/domain/epc/test_construction_age_band_overlay.py @@ -7,6 +7,8 @@ the override's building part. from __future__ import annotations +import pytest + from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier from domain.epc.property_overlays.construction_age_band_overlay import ( age_band_overlay_for, @@ -21,3 +23,38 @@ def test_age_band_overlays_the_main_building_part() -> None: assert simulation is not None overlay = simulation.building_parts[BuildingPartIdentifier.MAIN] assert overlay.construction_age_band == "B" + + +def test_age_band_overlay_targets_the_extension_building_part() -> None: + # Act — building_part 1 is the first extension. + simulation = age_band_overlay_for("L", 1) + + # Assert + assert simulation is not None + assert BuildingPartIdentifier.EXTENSION_1 in simulation.building_parts + assert ( + simulation.building_parts[BuildingPartIdentifier.EXTENSION_1] + .construction_age_band + == "L" + ) + + +def test_lowercase_age_band_is_normalised_to_its_letter_code() -> None: + # Act + simulation = age_band_overlay_for("d", 0) + + # Assert — the calculator upper-cases the band; the overlay stores it upper. + assert simulation is not None + assert ( + simulation.building_parts[BuildingPartIdentifier.MAIN].construction_age_band + == "D" + ) + + +@pytest.mark.parametrize("age_band_value", ["Z", "", "1900-1929", "Unknown"]) +def test_unrecognised_age_band_produces_no_overlay(age_band_value: str) -> None: + # Act + simulation = age_band_overlay_for(age_band_value, 0) + + # Assert + assert simulation is None From 55cdc1c7775ac45e79cc181c4a999356864d2fdd Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:42:19 +0000 Subject: [PATCH 041/340] =?UTF-8?q?Route=20a=20construction=5Fage=5Fband?= =?UTF-8?q?=20override=20row=20through=20its=20overlay=20mapper=20?= =?UTF-8?q?=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../property/test_landlord_override_overlays.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/repositories/property/test_landlord_override_overlays.py b/tests/repositories/property/test_landlord_override_overlays.py index ce86e78aa..2b687656e 100644 --- a/tests/repositories/property/test_landlord_override_overlays.py +++ b/tests/repositories/property/test_landlord_override_overlays.py @@ -77,6 +77,21 @@ def test_glazing_row_produces_a_glazing_overlay() -> None: assert overlays[0].glazing.glazing_type == 2 +def test_construction_age_band_row_produces_a_building_part_overlay() -> None: + # Arrange + overrides = ResolvedPropertyOverrides( + rows=(ResolvedPropertyOverride("construction_age_band", 0, "B"),) + ) + + # Act + overlays = overlays_from(overrides) + + # Assert + assert len(overlays) == 1 + main = overlays[0].building_parts[BuildingPartIdentifier.MAIN] + assert main.construction_age_band == "B" + + def test_unresolvable_rows_are_skipped() -> None: # Arrange — an "Unknown" property type and an unmapped wall material. overrides = ResolvedPropertyOverrides( From b13166742dd0849b1b1acccae650eeefac52b14e Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:42:43 +0000 Subject: [PATCH 042/340] =?UTF-8?q?Route=20a=20construction=5Fage=5Fband?= =?UTF-8?q?=20override=20row=20through=20its=20overlay=20mapper=20?= =?UTF-8?q?=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- repositories/property/landlord_override_overlays.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/repositories/property/landlord_override_overlays.py b/repositories/property/landlord_override_overlays.py index 624487eb2..fd6d5f686 100644 --- a/repositories/property/landlord_override_overlays.py +++ b/repositories/property/landlord_override_overlays.py @@ -29,6 +29,9 @@ from domain.epc.property_overlays.attribute_overlay import ( built_form_overlay_for, property_type_overlay_for, ) +from domain.epc.property_overlays.construction_age_band_overlay import ( + age_band_overlay_for, +) from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for from domain.epc.property_overlays.roof_type_overlay import roof_overlay_for @@ -47,6 +50,7 @@ _COMPONENT_OVERLAYS: dict[str, Callable[[str, int], Optional[EpcSimulation]]] = "built_form_type": built_form_overlay_for, "main_fuel": fuel_overlay_for, "glazing": glazing_overlay_for, + "construction_age_band": age_band_overlay_for, } From e71df5df99f38773db56c2bd5c18c32f92b8fbcb Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:43:28 +0000 Subject: [PATCH 043/340] =?UTF-8?q?Re-date=20a=20building=20part's=20EPC?= =?UTF-8?q?=20age=20band=20from=20a=20landlord=20override=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc/test_construction_age_band_overlay.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/domain/epc/test_construction_age_band_overlay.py b/tests/domain/epc/test_construction_age_band_overlay.py index bb66933eb..7527b9ea3 100644 --- a/tests/domain/epc/test_construction_age_band_overlay.py +++ b/tests/domain/epc/test_construction_age_band_overlay.py @@ -13,6 +13,14 @@ from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier from domain.epc.property_overlays.construction_age_band_overlay import ( age_band_overlay_for, ) +from domain.modelling.scoring.overlay_applicator import apply_simulations +from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( + build_epc, +) + + +def _part(epc, identifier): # type: ignore[no-untyped-def] + return next(p for p in epc.sap_building_parts if p.identifier is identifier) def test_age_band_overlays_the_main_building_part() -> None: @@ -58,3 +66,21 @@ def test_unrecognised_age_band_produces_no_overlay(age_band_value: str) -> None: # Assert assert simulation is None + + +def test_age_band_override_re_dates_the_main_part_only() -> None: + # Arrange — baseline main + extension are both band B; the landlord corrects + # the main building's age band to F (1976-1982). + baseline = build_epc() + overlay = age_band_overlay_for("F", 0) + assert overlay is not None + + # Act + result = apply_simulations(baseline, [overlay]) + + # Assert — the main part is re-dated (its U-value cascade now keys on F); the + # extension is left untouched. + assert _part(result, BuildingPartIdentifier.MAIN).construction_age_band == "F" + assert ( + _part(result, BuildingPartIdentifier.EXTENSION_1).construction_age_band == "B" + ) From fc591c65502c73a4464a0d35bc002b3da09245a2 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:43:47 +0000 Subject: [PATCH 044/340] =?UTF-8?q?Classify=20the=20landlord=20Age=20colum?= =?UTF-8?q?n=20into=20a=20construction-age-band=20category=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../test_build_columns.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/applications/landlord_description_overrides/test_build_columns.py b/tests/applications/landlord_description_overrides/test_build_columns.py index c67487281..8f50afce8 100644 --- a/tests/applications/landlord_description_overrides/test_build_columns.py +++ b/tests/applications/landlord_description_overrides/test_build_columns.py @@ -38,3 +38,16 @@ def test_build_columns_wires_a_glazing_classifier_column() -> None: assert len(columns) == 1 assert columns[0].name == "glazing" assert columns[0].source_column == "Glazing" + + +def test_build_columns_wires_a_construction_age_band_classifier_column() -> None: + # Arrange + chat_gpt = cast(ChatGPT, object()) + + # Act + columns = _build_columns({"construction_age_band": "Age"}, chat_gpt, None) + + # Assert — one column, named construction_age_band, reading the "Age" header. + assert len(columns) == 1 + assert columns[0].name == "construction_age_band" + assert columns[0].source_column == "Age" From cb6a335382985ebd2fda9df31ecc1e8e378cdcf1 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:44:40 +0000 Subject: [PATCH 045/340] =?UTF-8?q?Classify=20the=20landlord=20Age=20colum?= =?UTF-8?q?n=20into=20a=20construction-age-band=20category=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../landlord_description_overrides/handler.py | 14 ++++ domain/epc/construction_age_band.py | 31 +++++++++ ...rd_construction_age_band_override_table.py | 69 +++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 domain/epc/construction_age_band.py create mode 100644 infrastructure/postgres/landlord_construction_age_band_override_table.py diff --git a/applications/landlord_description_overrides/handler.py b/applications/landlord_description_overrides/handler.py index 16dbb000c..2f83b81f5 100644 --- a/applications/landlord_description_overrides/handler.py +++ b/applications/landlord_description_overrides/handler.py @@ -8,6 +8,7 @@ from applications.landlord_description_overrides.landlord_description_overrides_ LandlordDescriptionOverridesTriggerBody, ) from domain.epc.built_form_type import BuiltFormType +from domain.epc.construction_age_band import ConstructionAgeBand from domain.epc.glazing_type import GlazingType from domain.epc.main_fuel_type import MainFuelType from domain.epc.property_type import PropertyType @@ -26,6 +27,9 @@ from infrastructure.postgres.engine import commit_scope, make_engine, make_sessi from infrastructure.postgres.landlord_built_form_type_override_table import ( LandlordBuiltFormTypeOverrideRow, ) +from infrastructure.postgres.landlord_construction_age_band_override_table import ( + LandlordConstructionAgeBandOverrideRow, +) from infrastructure.postgres.landlord_glazing_override_table import ( LandlordGlazingOverrideRow, ) @@ -130,6 +134,16 @@ def _build_columns( session, LandlordGlazingOverrideRow ), ), + "construction_age_band": lambda src: ClassifiableColumn( + name="construction_age_band", + source_column=src, + classifier=ChatGptColumnClassifier( + chat_gpt, ConstructionAgeBand, ConstructionAgeBand.UNKNOWN + ), + repo=LandlordOverridesRepository[ConstructionAgeBand]( + session, LandlordConstructionAgeBandOverrideRow + ), + ), } columns: list[ClassifiableColumn[Any]] = [] diff --git a/domain/epc/construction_age_band.py b/domain/epc/construction_age_band.py new file mode 100644 index 000000000..83f6e9a85 --- /dev/null +++ b/domain/epc/construction_age_band.py @@ -0,0 +1,31 @@ +from enum import Enum + + +class ConstructionAgeBand(Enum): + """A landlord-supplied construction age band, as resolved by the + landlord-description-overrides context. + + Each member's value is the RdSAP England-&-Wales age-band **letter code** + (A..M) the calculator's U-value cascades read from + `SapBuildingPart.construction_age_band` — the same representation the gov-EPC + API lodges. The construction-age-band Simulation Overlay + (``domain/epc/property_overlays/construction_age_band_overlay.py``) sets the + letter directly, so these values MUST stay the bare letter codes. Member + names carry the year ranges for readability. ``UNKNOWN`` covers values the + classifier cannot resolve (it leaves the lodged cert's age band untouched). + """ + + A_BEFORE_1900 = "A" + B_1900_1929 = "B" + C_1930_1949 = "C" + D_1950_1966 = "D" + E_1967_1975 = "E" + F_1976_1982 = "F" + G_1983_1990 = "G" + H_1991_1995 = "H" + I_1996_2002 = "I" + J_2003_2006 = "J" + K_2007_2011 = "K" + L_2012_2022 = "L" + M_2023_ONWARDS = "M" + UNKNOWN = "Unknown" diff --git a/infrastructure/postgres/landlord_construction_age_band_override_table.py b/infrastructure/postgres/landlord_construction_age_band_override_table.py new file mode 100644 index 000000000..598bbf569 --- /dev/null +++ b/infrastructure/postgres/landlord_construction_age_band_override_table.py @@ -0,0 +1,69 @@ +"""SQLModel mirror of the ``landlord_construction_age_band_overrides`` Drizzle table. + +The schema source of truth lives in the ``assessment-model`` TS repo +(`src/app/db/schema/landlord_overrides.ts`). The migrations are owned there; +this row class only mirrors the columns so the Python lambda can read/write. +See ADR-0003. Shape mirrors ``LandlordWallTypeOverrideRow`` -- the only +differences are the table name, the ``construction_age_band`` pgEnum on +``value``, and the unique-constraint name. +""" + +from datetime import datetime, timezone +from typing import ClassVar +from uuid import UUID, uuid4 + +from sqlalchemy import BigInteger, Column, UniqueConstraint +from sqlalchemy import Enum as SAEnum +from sqlmodel import Field, SQLModel + +from domain.epc.construction_age_band import ConstructionAgeBand +from infrastructure.postgres.landlord_override_enums import override_source_sa_enum + + +class LandlordConstructionAgeBandOverrideRow(SQLModel, table=True): + __tablename__: ClassVar[str] = "landlord_construction_age_band_overrides" # pyright: ignore[reportIncompatibleVariableOverride] + __table_args__: ClassVar[tuple[UniqueConstraint, ...]] = ( # pyright: ignore[reportIncompatibleVariableOverride] + UniqueConstraint( + "portfolio_id", + "description", + name="landlord_construction_age_band_overrides_portfolio_description_unique", + ), + ) + + id: UUID = Field(default_factory=uuid4, primary_key=True) + + # bigint to match the Drizzle ``portfolio_id`` FK; SQLModel's default int + # mapping is 32-bit Integer and would overflow once portfolio IDs exceed + # 2^31. The FK to ``portfolio.id`` is enforced by the Drizzle migration, + # not declared here -- the ``portfolio`` table is not modelled in Python. + portfolio_id: int = Field( + sa_column=Column(BigInteger, nullable=False, index=True), + ) + + description: str = Field(nullable=False) + + value: ConstructionAgeBand = Field( + sa_column=Column( + SAEnum( + ConstructionAgeBand, + name="construction_age_band", + values_callable=lambda cls: [m.value for m in cls], # pyright: ignore[reportUnknownLambdaType, reportUnknownMemberType, reportUnknownVariableType] + ), + nullable=False, + ), + ) + + # Shared SAEnum -- see ``landlord_override_enums`` for why this single + # instance is reused by every ``landlord_*_overrides`` row class. + source: str = Field( + sa_column=Column(override_source_sa_enum, nullable=False), + ) + + created_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc), + nullable=False, + ) + updated_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc), + nullable=False, + ) From 46d1f8bbb22f9da3796db19e56556c2e8dbd275c Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:45:31 +0000 Subject: [PATCH 046/340] =?UTF-8?q?Guarantee=20every=20classifier=20age-ba?= =?UTF-8?q?nd=20value=20maps=20to=20an=20overlay=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc/test_construction_age_band_overlay.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/tests/domain/epc/test_construction_age_band_overlay.py b/tests/domain/epc/test_construction_age_band_overlay.py index 7527b9ea3..3408900b4 100644 --- a/tests/domain/epc/test_construction_age_band_overlay.py +++ b/tests/domain/epc/test_construction_age_band_overlay.py @@ -9,7 +9,12 @@ from __future__ import annotations import pytest -from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier +from datatypes.epc.domain.epc_property_data import ( + BuildingPartIdentifier, + EpcPropertyData, + SapBuildingPart, +) +from domain.epc.construction_age_band import ConstructionAgeBand from domain.epc.property_overlays.construction_age_band_overlay import ( age_band_overlay_for, ) @@ -19,7 +24,9 @@ from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( ) -def _part(epc, identifier): # type: ignore[no-untyped-def] +def _part( + epc: EpcPropertyData, identifier: BuildingPartIdentifier +) -> SapBuildingPart: return next(p for p in epc.sap_building_parts if p.identifier is identifier) @@ -84,3 +91,19 @@ def test_age_band_override_re_dates_the_main_part_only() -> None: assert ( _part(result, BuildingPartIdentifier.EXTENSION_1).construction_age_band == "B" ) + + +@pytest.mark.parametrize( + "member", [m for m in ConstructionAgeBand if m is not ConstructionAgeBand.UNKNOWN] +) +def test_every_resolvable_age_band_value_decodes_to_an_overlay( + member: ConstructionAgeBand, +) -> None: + # A classifier emits a ConstructionAgeBand value; if the overlay can't decode + # it the override silently no-ops. Every non-UNKNOWN member must resolve. + + # Act + simulation = age_band_overlay_for(member.value, 0) + + # Assert + assert simulation is not None From 89bb075ce6866ea1a0f9ec28f472d042fdc74a0b Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 13:52:28 +0000 Subject: [PATCH 047/340] =?UTF-8?q?Keep=20the=20age-band=20unique=20constr?= =?UTF-8?q?aint=20within=20Postgres's=2063-char=20limit=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../landlord_construction_age_band_override_table.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/infrastructure/postgres/landlord_construction_age_band_override_table.py b/infrastructure/postgres/landlord_construction_age_band_override_table.py index 598bbf569..91eec66eb 100644 --- a/infrastructure/postgres/landlord_construction_age_band_override_table.py +++ b/infrastructure/postgres/landlord_construction_age_band_override_table.py @@ -23,10 +23,14 @@ from infrastructure.postgres.landlord_override_enums import override_source_sa_e class LandlordConstructionAgeBandOverrideRow(SQLModel, table=True): __tablename__: ClassVar[str] = "landlord_construction_age_band_overrides" # pyright: ignore[reportIncompatibleVariableOverride] __table_args__: ClassVar[tuple[UniqueConstraint, ...]] = ( # pyright: ignore[reportIncompatibleVariableOverride] + # NB: shortened (drop the redundant ``_overrides``) to stay within + # PostgreSQL's 63-char identifier limit -- the full + # ``landlord_construction_age_band_overrides_portfolio_description_unique`` + # is 68 chars and would be silently truncated, diverging from Drizzle. UniqueConstraint( "portfolio_id", "description", - name="landlord_construction_age_band_overrides_portfolio_description_unique", + name="landlord_construction_age_band_portfolio_description_unique", ), ) From 8f7a34470767ae4383abc941924959f79d1f7149 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:02:48 +0000 Subject: [PATCH 048/340] =?UTF-8?q?Resolve=20a=20landlord=20from-main-gas?= =?UTF-8?q?=20water-heating=20override=20to=20its=20codes=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../water_heating_overlay.py | 22 +++++++++++++++++++ .../domain/epc/test_water_heating_overlay.py | 22 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 domain/epc/property_overlays/water_heating_overlay.py create mode 100644 tests/domain/epc/test_water_heating_overlay.py diff --git a/domain/epc/property_overlays/water_heating_overlay.py b/domain/epc/property_overlays/water_heating_overlay.py new file mode 100644 index 000000000..b65db1b15 --- /dev/null +++ b/domain/epc/property_overlays/water_heating_overlay.py @@ -0,0 +1,22 @@ +"""Map a Landlord-Override water-heating value to a heating Simulation Overlay. + +A water-heating value is one canonical ", " description ("From main +system, mains gas", "Electric immersion, electricity"). The calculator reads the +hot-water arrangement from `sap_heating.water_heating_code` (the SAP Table 4a +system code) and `water_heating_fuel`, so the overlay decomposes the value into +those two int codes and emits a whole-dwelling `HeatingOverlay` (water heating is +not per-building-part, so `building_part` is ignored). It composes field-wise with +the main_fuel / main_heating overlays. Unresolvable values produce no overlay. +""" + +from __future__ import annotations + +from typing import Optional + +from domain.modelling.simulation import EpcSimulation + + +def water_heating_overlay_for( + water_heating_value: str, building_part: int +) -> Optional[EpcSimulation]: + raise NotImplementedError diff --git a/tests/domain/epc/test_water_heating_overlay.py b/tests/domain/epc/test_water_heating_overlay.py new file mode 100644 index 000000000..f93791843 --- /dev/null +++ b/tests/domain/epc/test_water_heating_overlay.py @@ -0,0 +1,22 @@ +"""The Landlord-Override water-heating → heating Simulation Overlay mapping. + +A water-heating value resolves to the SAP `water_heating_code` (system) and +`water_heating_fuel` the calculator reads; the overlay is whole-dwelling. +""" + +from __future__ import annotations + +from domain.epc.property_overlays.water_heating_overlay import ( + water_heating_overlay_for, +) + + +def test_from_main_system_mains_gas_overlays_water_heating() -> None: + # Act + simulation = water_heating_overlay_for("From main system, mains gas", 0) + + # Assert — "from main system" is water_heating_code 901, mains gas fuel 26. + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.water_heating_code == 901 + assert simulation.heating.water_heating_fuel == 26 From 7d70c1d298bbdeb16d554e274251360a06f762e3 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:03:03 +0000 Subject: [PATCH 049/340] =?UTF-8?q?Resolve=20a=20landlord=20from-main-gas?= =?UTF-8?q?=20water-heating=20override=20to=20its=20codes=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../water_heating_overlay.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/domain/epc/property_overlays/water_heating_overlay.py b/domain/epc/property_overlays/water_heating_overlay.py index b65db1b15..d75ea81c0 100644 --- a/domain/epc/property_overlays/water_heating_overlay.py +++ b/domain/epc/property_overlays/water_heating_overlay.py @@ -13,10 +13,27 @@ from __future__ import annotations from typing import Optional -from domain.modelling.simulation import EpcSimulation +from domain.modelling.simulation import EpcSimulation, HeatingOverlay + +# Canonical ", " description → (water_heating_code, water_heating_fuel). +# water_heating_code: 901 "from main system" (SAP Table 4a inherit-from-main), +# 903 "electric immersion". Fuel codes are the modern RdSAP "(not community)" +# family (26 mains gas, 29 electricity), matching the main_fuel overlay. +_WATER_HEATING_CODES: dict[str, tuple[int, int]] = { + "From main system, mains gas": (901, 26), +} def water_heating_overlay_for( water_heating_value: str, building_part: int ) -> Optional[EpcSimulation]: - raise NotImplementedError + codes = _WATER_HEATING_CODES.get(water_heating_value) + if codes is None: + return None + water_heating_code, water_heating_fuel = codes + return EpcSimulation( + heating=HeatingOverlay( + water_heating_code=water_heating_code, + water_heating_fuel=water_heating_fuel, + ) + ) From fc5f10ea92e12de78f327ed186a547961326bfb4 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 19 Jun 2026 14:03:13 +0000 Subject: [PATCH 050/340] =?UTF-8?q?fix(rating):=20floor=20the=20continuous?= =?UTF-8?q?=20SAP=20score=20at=201=20(SAP=2010.2=20=C2=A713=20/=20RdSAP=20?= =?UTF-8?q?10=20=C2=A713)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SAP rating is spec-floored at 1 ("if the result of the calculation is less than 1, the rating is 1"). `sap_rating_integer` already clamps, but the continuous `sap_score_continuous` did not — so a degenerate dwelling could emit a physically impossible negative SAP. Apply the same max(1, …) floor to the continuous value (the un-rounded part is for sensitivity near real ratings, not for negative ratings). Removes a -12.3 accuracy outlier on the committed corpus (cert 422000111926, lodged at the floor of 1, was computing -11.3): within-0.5 70.2% -> 70.3%, MAE 0.845 -> 0.833. Ratcheted the corpus MAE ceiling to 0.84. Unit-pinned in test_calculator. pyright not installed in this codespace (strict gate not run locally). Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/sap10_calculator/calculator.py | 8 ++++++- .../sap10_calculator/test_calculator.py | 22 +++++++++++++++++++ .../epc_client/test_sap_accuracy_corpus.py | 8 ++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/domain/sap10_calculator/calculator.py b/domain/sap10_calculator/calculator.py index 11cecf731..fb859bf6d 100644 --- a/domain/sap10_calculator/calculator.py +++ b/domain/sap10_calculator/calculator.py @@ -617,7 +617,13 @@ def calculate_sap_from_inputs(inputs: CalculatorInputs) -> SapResult: ) ecf = energy_cost_factor(total_cost_gbp=total_cost, total_floor_area_m2=tfa) sap_int = sap_rating_integer(ecf=ecf) - sap_cont = sap_rating(ecf=ecf) + # SAP 10.2 §13 / RdSAP 10 §13: the SAP rating is floored at 1 ("if the + # result of the calculation is less than 1, the rating is 1"). Apply the + # same floor to the continuous value so it stays a valid rating — the + # un-rounded part is for sensitivity NEAR real ratings, not for emitting + # a physically impossible negative SAP on a degenerate dwelling (e.g. a + # cert lodged at the floor of 1). Mirrors `sap_rating_integer`'s max(1,…). + sap_cont = max(1.0, sap_rating(ecf=ecf)) co2_factor = inputs.co2_factor_kg_per_kwh # Per-end-use effective CO2 factors (Table 12d monthly cascade for # electricity, annual for gas). cert_to_inputs supplies these from diff --git a/tests/domain/sap10_calculator/test_calculator.py b/tests/domain/sap10_calculator/test_calculator.py index dba25409c..5444a1409 100644 --- a/tests/domain/sap10_calculator/test_calculator.py +++ b/tests/domain/sap10_calculator/test_calculator.py @@ -270,6 +270,28 @@ def test_calculator_returns_twelve_month_breakdown_and_plausible_sap_score() -> ) +def test_sap_score_continuous_floored_at_1_for_degenerate_high_cost() -> None: + # Arrange — SAP 10.2 §13 / RdSAP 10 §13: the SAP rating is floored at 1 + # ("if the result of the calculation is less than 1, the rating is 1"). + # Drive the cost so high that the raw ECF formula returns a negative SAP + # (a degenerate dwelling, e.g. a cert lodged at the floor of 1); both the + # integer AND the continuous score must clamp to 1 rather than emit a + # physically impossible negative rating. + inputs = replace( + _baseline_inputs(), + space_heating_fuel_cost_gbp_per_kwh=5.0, + hot_water_fuel_cost_gbp_per_kwh=5.0, + other_fuel_cost_gbp_per_kwh=5.0, + ) + + # Act + result = calculate_sap_from_inputs(inputs) + + # Assert — raw SAP would be < 1 here; the floor holds on both outputs. + assert result.sap_score == 1 + assert abs(result.sap_score_continuous - 1.0) <= 1e-9 + + def test_calculate_exposes_dimensions_intermediates() -> None: # Arrange — P5 trace mode: `result.intermediate` must surface the # worksheet-named dimensions variables for per-section diffing diff --git a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py index ef933adb0..7b3524f5c 100644 --- a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py +++ b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py @@ -150,8 +150,14 @@ _CORPUS = Path( # MAE 0.12 -> 0.08 t/yr (bias +0.04 -> 0.00). A prior session deferred enum 9 # ("direction not understood") while the PE/CO2 lens was confounded by the # climate-cascade bug (fc7c4d2d); the corrected lens shows the over-rate. +# SAP RATING FLOOR (SAP 10.2 §13 / RdSAP 10 §13): the rating is floored at 1 +# ("if the result is less than 1, the rating is 1"). `calculate_sap_from_inputs` +# now applies that floor to the CONTINUOUS score too (was integer-only), so a +# degenerate dwelling no longer emits a negative SAP. Removed a -12.3 outlier +# (cert 422000111926, lodged at the floor of 1, was computing -11.3): within-0.5 +# 70.2% -> 70.3%, MAE 0.845 -> 0.833. _MIN_WITHIN_HALF_SAP = 0.70 -_MAX_SAP_MAE = 0.85 +_MAX_SAP_MAE = 0.84 _MAX_CO2_MAE_TONNES = 0.09 # t CO2 / yr vs co2_emissions_current _MAX_PE_PER_M2_MAE = 4.0 # kWh / m2 / yr vs energy_consumption_current From a117e61b31d665a85c66fa80152b4eda2f1cba61 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:03:25 +0000 Subject: [PATCH 051/340] =?UTF-8?q?Decode=20more=20water-heating=20system/?= =?UTF-8?q?fuel=20overrides=20to=20their=20codes=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../domain/epc/test_water_heating_overlay.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/domain/epc/test_water_heating_overlay.py b/tests/domain/epc/test_water_heating_overlay.py index f93791843..49cfffbf2 100644 --- a/tests/domain/epc/test_water_heating_overlay.py +++ b/tests/domain/epc/test_water_heating_overlay.py @@ -6,6 +6,8 @@ A water-heating value resolves to the SAP `water_heating_code` (system) and from __future__ import annotations +import pytest + from domain.epc.property_overlays.water_heating_overlay import ( water_heating_overlay_for, ) @@ -20,3 +22,34 @@ def test_from_main_system_mains_gas_overlays_water_heating() -> None: assert simulation.heating is not None assert simulation.heating.water_heating_code == 901 assert simulation.heating.water_heating_fuel == 26 + + +@pytest.mark.parametrize( + ("water_heating_value", "code", "fuel"), + [ + ("From main system, electricity", 901, 29), + ("Electric immersion, electricity", 903, 29), + ], +) +def test_water_heating_systems_decode_to_their_codes( + water_heating_value: str, code: int, fuel: int +) -> None: + # Act + simulation = water_heating_overlay_for(water_heating_value, 0) + + # Assert + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.water_heating_code == code + assert simulation.heating.water_heating_fuel == fuel + + +@pytest.mark.parametrize("water_heating_value", ["Unknown", ""]) +def test_unresolvable_water_heating_produces_no_overlay( + water_heating_value: str, +) -> None: + # Act + simulation = water_heating_overlay_for(water_heating_value, 0) + + # Assert + assert simulation is None From c6ab9ba38359379e22dc2dcee0476398a29423cd Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:03:36 +0000 Subject: [PATCH 052/340] =?UTF-8?q?Decode=20more=20water-heating=20system/?= =?UTF-8?q?fuel=20overrides=20to=20their=20codes=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/epc/property_overlays/water_heating_overlay.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain/epc/property_overlays/water_heating_overlay.py b/domain/epc/property_overlays/water_heating_overlay.py index d75ea81c0..e55a578ed 100644 --- a/domain/epc/property_overlays/water_heating_overlay.py +++ b/domain/epc/property_overlays/water_heating_overlay.py @@ -21,6 +21,8 @@ from domain.modelling.simulation import EpcSimulation, HeatingOverlay # family (26 mains gas, 29 electricity), matching the main_fuel overlay. _WATER_HEATING_CODES: dict[str, tuple[int, int]] = { "From main system, mains gas": (901, 26), + "From main system, electricity": (901, 29), + "Electric immersion, electricity": (903, 29), } From 55e83c7f9f0ca104cf3d38a33070b5015fa30d2a Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:03:51 +0000 Subject: [PATCH 053/340] =?UTF-8?q?Route=20a=20water=5Fheating=20override?= =?UTF-8?q?=20row=20through=20its=20overlay=20mapper=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../test_landlord_override_overlays.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/repositories/property/test_landlord_override_overlays.py b/tests/repositories/property/test_landlord_override_overlays.py index 2b687656e..b2b49d55b 100644 --- a/tests/repositories/property/test_landlord_override_overlays.py +++ b/tests/repositories/property/test_landlord_override_overlays.py @@ -92,6 +92,26 @@ def test_construction_age_band_row_produces_a_building_part_overlay() -> None: assert main.construction_age_band == "B" +def test_water_heating_row_produces_a_heating_overlay() -> None: + # Arrange + overrides = ResolvedPropertyOverrides( + rows=( + ResolvedPropertyOverride( + "water_heating", 0, "From main system, mains gas" + ), + ) + ) + + # Act + overlays = overlays_from(overrides) + + # Assert + assert len(overlays) == 1 + assert overlays[0].heating is not None + assert overlays[0].heating.water_heating_code == 901 + assert overlays[0].heating.water_heating_fuel == 26 + + def test_unresolvable_rows_are_skipped() -> None: # Arrange — an "Unknown" property type and an unmapped wall material. overrides = ResolvedPropertyOverrides( From 805dfc70a28e4c2fd7f3101503347e38d7160b24 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:04:11 +0000 Subject: [PATCH 054/340] =?UTF-8?q?Route=20a=20water=5Fheating=20override?= =?UTF-8?q?=20row=20through=20its=20overlay=20mapper=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- repositories/property/landlord_override_overlays.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/repositories/property/landlord_override_overlays.py b/repositories/property/landlord_override_overlays.py index fd6d5f686..ebceb6cf0 100644 --- a/repositories/property/landlord_override_overlays.py +++ b/repositories/property/landlord_override_overlays.py @@ -34,6 +34,9 @@ from domain.epc.property_overlays.construction_age_band_overlay import ( ) from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for +from domain.epc.property_overlays.water_heating_overlay import ( + water_heating_overlay_for, +) from domain.epc.property_overlays.roof_type_overlay import roof_overlay_for from domain.epc.property_overlays.wall_type_overlay import wall_overlay_for from domain.modelling.simulation import EpcSimulation @@ -51,6 +54,7 @@ _COMPONENT_OVERLAYS: dict[str, Callable[[str, int], Optional[EpcSimulation]]] = "main_fuel": fuel_overlay_for, "glazing": glazing_overlay_for, "construction_age_band": age_band_overlay_for, + "water_heating": water_heating_overlay_for, } From b5ef41ea62c242a56d90b682f9d4cd51e8f764d3 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:04:33 +0000 Subject: [PATCH 055/340] =?UTF-8?q?Remap=20the=20EPC=20hot-water=20arrange?= =?UTF-8?q?ment=20from=20a=20landlord=20override=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/domain/epc/test_water_heating_overlay.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/domain/epc/test_water_heating_overlay.py b/tests/domain/epc/test_water_heating_overlay.py index 49cfffbf2..7dd8c2d0a 100644 --- a/tests/domain/epc/test_water_heating_overlay.py +++ b/tests/domain/epc/test_water_heating_overlay.py @@ -11,6 +11,10 @@ import pytest from domain.epc.property_overlays.water_heating_overlay import ( water_heating_overlay_for, ) +from domain.modelling.scoring.overlay_applicator import apply_simulations +from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( + build_epc, +) def test_from_main_system_mains_gas_overlays_water_heating() -> None: @@ -53,3 +57,17 @@ def test_unresolvable_water_heating_produces_no_overlay( # Assert assert simulation is None + + +def test_water_heating_override_remaps_the_hot_water_arrangement() -> None: + # Arrange — landlord correction: HW is a separate electric immersion. + baseline = build_epc() + overlay = water_heating_overlay_for("Electric immersion, electricity", 0) + assert overlay is not None + + # Act + result = apply_simulations(baseline, [overlay]) + + # Assert — the calculator reads these off sap_heating. + assert result.sap_heating.water_heating_code == 903 + assert result.sap_heating.water_heating_fuel == 29 From 21afbefa9dbe3c597a8ec5559705f795c049092e Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:05:00 +0000 Subject: [PATCH 056/340] =?UTF-8?q?Resolve=20a=20landlord=20gas-combi=20he?= =?UTF-8?q?ating=20override=20to=20its=20SAP=20code=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../main_heating_system_overlay.py | 28 +++++++++++++++++++ .../epc/test_main_heating_system_overlay.py | 21 ++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 domain/epc/property_overlays/main_heating_system_overlay.py create mode 100644 tests/domain/epc/test_main_heating_system_overlay.py diff --git a/domain/epc/property_overlays/main_heating_system_overlay.py b/domain/epc/property_overlays/main_heating_system_overlay.py new file mode 100644 index 000000000..6b7aca804 --- /dev/null +++ b/domain/epc/property_overlays/main_heating_system_overlay.py @@ -0,0 +1,28 @@ +"""Map a Landlord-Override main-heating-system value to a heating Simulation Overlay. + +A main-heating-system value is one canonical system archetype ("Gas boiler, +combi", "Electric storage heaters, fan"). The calculator reads the primary +system's `sap_main_heating_code` (SAP Table 4a/4b), so the overlay maps the +archetype to a representative code and emits a whole-dwelling `HeatingOverlay` +targeting `main_heating_details[0]` (`building_part` is ignored). It composes +field-wise with the main_fuel / water_heating overlays. + +The SEDBUK A-G efficiency band the Hyde "Heating" column carries is NOT honoured +yet (no efficiency slot on the overlay/MainHeatingDetail) -- archetypes map to +their modern/condensing Table 4b code, so an old low-rated boiler is currently +modelled at the condensing efficiency. Heat pumps and community heating (which +resolve via main_heating_index_number / community codes, not a Table 4b code) +are left UNKNOWN until modelled. Unresolvable values produce no overlay. +""" + +from __future__ import annotations + +from typing import Optional + +from domain.modelling.simulation import EpcSimulation + + +def main_heating_overlay_for( + main_heating_value: str, building_part: int +) -> Optional[EpcSimulation]: + raise NotImplementedError diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py new file mode 100644 index 000000000..e1b7a90d6 --- /dev/null +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -0,0 +1,21 @@ +"""The Landlord-Override main-heating-system → heating Simulation Overlay mapping. + +A main-heating-system value resolves to the SAP `sap_main_heating_code` the +calculator reads from the primary system; the overlay is whole-dwelling. +""" + +from __future__ import annotations + +from domain.epc.property_overlays.main_heating_system_overlay import ( + main_heating_overlay_for, +) + + +def test_gas_combi_overlays_the_primary_heating_code() -> None: + # Act + simulation = main_heating_overlay_for("Gas boiler, combi", 0) + + # Assert — condensing combi is SAP Table 4b code 104. + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.sap_main_heating_code == 104 From d6bde062eda53b3288c6d1a531daec0aa16b3047 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:05:14 +0000 Subject: [PATCH 057/340] =?UTF-8?q?Resolve=20a=20landlord=20gas-combi=20he?= =?UTF-8?q?ating=20override=20to=20its=20SAP=20code=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../main_heating_system_overlay.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/domain/epc/property_overlays/main_heating_system_overlay.py b/domain/epc/property_overlays/main_heating_system_overlay.py index 6b7aca804..630c247a4 100644 --- a/domain/epc/property_overlays/main_heating_system_overlay.py +++ b/domain/epc/property_overlays/main_heating_system_overlay.py @@ -19,10 +19,21 @@ from __future__ import annotations from typing import Optional -from domain.modelling.simulation import EpcSimulation +from domain.modelling.simulation import EpcSimulation, HeatingOverlay + +# Canonical system archetype → representative `sap_main_heating_code` (SAP Table +# 4b boiler rows / Table 4a). Codes map to the modern/condensing variant (A-G +# efficiency deferred): 102 regular condensing, 104 condensing combi, 120 CPSU, +# 404 fan storage heaters, 191 direct-acting electric boiler. +_MAIN_HEATING_CODES: dict[str, int] = { + "Gas boiler, combi": 104, +} def main_heating_overlay_for( main_heating_value: str, building_part: int ) -> Optional[EpcSimulation]: - raise NotImplementedError + code = _MAIN_HEATING_CODES.get(main_heating_value) + if code is None: + return None + return EpcSimulation(heating=HeatingOverlay(sap_main_heating_code=code)) From 2ede8591584725d167780025e8481d173a2be558 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:05:39 +0000 Subject: [PATCH 058/340] =?UTF-8?q?Decode=20regular/CPSU/storage/direct-el?= =?UTF-8?q?ectric=20heating=20overrides=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc/test_main_heating_system_overlay.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index e1b7a90d6..1a55de61d 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -6,6 +6,8 @@ calculator reads from the primary system; the overlay is whole-dwelling. from __future__ import annotations +import pytest + from domain.epc.property_overlays.main_heating_system_overlay import ( main_heating_overlay_for, ) @@ -19,3 +21,41 @@ def test_gas_combi_overlays_the_primary_heating_code() -> None: assert simulation is not None assert simulation.heating is not None assert simulation.heating.sap_main_heating_code == 104 + + +@pytest.mark.parametrize( + ("main_heating_value", "code"), + [ + ("Gas boiler, regular", 102), + ("Gas CPSU", 120), + ("Electric storage heaters, fan", 404), + ("Direct-acting electric", 191), + ], +) +def test_heating_archetypes_decode_to_their_sap_codes( + main_heating_value: str, code: int +) -> None: + # Act + simulation = main_heating_overlay_for(main_heating_value, 0) + + # Assert + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.sap_main_heating_code == code + + +@pytest.mark.parametrize( + "main_heating_value", + ["Unknown", "", "Air source heat pump", "Community heating"], +) +def test_unresolvable_or_unmodelled_heating_produces_no_overlay( + main_heating_value: str, +) -> None: + # Heat pumps (main_heating_index_number) and community heating (community + # codes) don't map to a Table 4b sap_main_heating_code yet — no overlay. + + # Act + simulation = main_heating_overlay_for(main_heating_value, 0) + + # Assert + assert simulation is None From 10c670036670ea5a93250fed0b60a225011d3722 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:05:50 +0000 Subject: [PATCH 059/340] =?UTF-8?q?Decode=20regular/CPSU/storage/direct-el?= =?UTF-8?q?ectric=20heating=20overrides=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/epc/property_overlays/main_heating_system_overlay.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/domain/epc/property_overlays/main_heating_system_overlay.py b/domain/epc/property_overlays/main_heating_system_overlay.py index 630c247a4..5d61cb5a8 100644 --- a/domain/epc/property_overlays/main_heating_system_overlay.py +++ b/domain/epc/property_overlays/main_heating_system_overlay.py @@ -27,6 +27,10 @@ from domain.modelling.simulation import EpcSimulation, HeatingOverlay # 404 fan storage heaters, 191 direct-acting electric boiler. _MAIN_HEATING_CODES: dict[str, int] = { "Gas boiler, combi": 104, + "Gas boiler, regular": 102, + "Gas CPSU": 120, + "Electric storage heaters, fan": 404, + "Direct-acting electric": 191, } From 6c2d1dce349ff07659e4c7810ff05d4e4abba611 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:06:04 +0000 Subject: [PATCH 060/340] =?UTF-8?q?Route=20a=20main=5Fheating=5Fsystem=20o?= =?UTF-8?q?verride=20row=20through=20its=20overlay=20mapper=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../property/test_landlord_override_overlays.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/repositories/property/test_landlord_override_overlays.py b/tests/repositories/property/test_landlord_override_overlays.py index b2b49d55b..1f790aa88 100644 --- a/tests/repositories/property/test_landlord_override_overlays.py +++ b/tests/repositories/property/test_landlord_override_overlays.py @@ -112,6 +112,23 @@ def test_water_heating_row_produces_a_heating_overlay() -> None: assert overlays[0].heating.water_heating_fuel == 26 +def test_main_heating_system_row_produces_a_heating_overlay() -> None: + # Arrange + overrides = ResolvedPropertyOverrides( + rows=( + ResolvedPropertyOverride("main_heating_system", 0, "Gas boiler, combi"), + ) + ) + + # Act + overlays = overlays_from(overrides) + + # Assert + assert len(overlays) == 1 + assert overlays[0].heating is not None + assert overlays[0].heating.sap_main_heating_code == 104 + + def test_unresolvable_rows_are_skipped() -> None: # Arrange — an "Unknown" property type and an unmapped wall material. overrides = ResolvedPropertyOverrides( From 66bb29a8340d3c5102da92ea9e12a1f3ed0b5622 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:06:24 +0000 Subject: [PATCH 061/340] =?UTF-8?q?Route=20a=20main=5Fheating=5Fsystem=20o?= =?UTF-8?q?verride=20row=20through=20its=20overlay=20mapper=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- repositories/property/landlord_override_overlays.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/repositories/property/landlord_override_overlays.py b/repositories/property/landlord_override_overlays.py index ebceb6cf0..6c40d009c 100644 --- a/repositories/property/landlord_override_overlays.py +++ b/repositories/property/landlord_override_overlays.py @@ -34,6 +34,9 @@ from domain.epc.property_overlays.construction_age_band_overlay import ( ) from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for +from domain.epc.property_overlays.main_heating_system_overlay import ( + main_heating_overlay_for, +) from domain.epc.property_overlays.water_heating_overlay import ( water_heating_overlay_for, ) @@ -55,6 +58,7 @@ _COMPONENT_OVERLAYS: dict[str, Callable[[str, int], Optional[EpcSimulation]]] = "glazing": glazing_overlay_for, "construction_age_band": age_band_overlay_for, "water_heating": water_heating_overlay_for, + "main_heating_system": main_heating_overlay_for, } From a6f2ae99df61a6475784a50b2f2c0602b1c2e390 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:06:52 +0000 Subject: [PATCH 062/340] =?UTF-8?q?Remap=20the=20primary=20heating=20syste?= =?UTF-8?q?m=20and=20compose=20the=20heating=20override=20trio=20?= =?UTF-8?q?=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc/test_main_heating_system_overlay.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index 1a55de61d..0710837e8 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -8,9 +8,17 @@ from __future__ import annotations import pytest +from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for from domain.epc.property_overlays.main_heating_system_overlay import ( main_heating_overlay_for, ) +from domain.epc.property_overlays.water_heating_overlay import ( + water_heating_overlay_for, +) +from domain.modelling.scoring.overlay_applicator import apply_simulations +from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( + build_epc, +) def test_gas_combi_overlays_the_primary_heating_code() -> None: @@ -59,3 +67,39 @@ def test_unresolvable_or_unmodelled_heating_produces_no_overlay( # Assert assert simulation is None + + +def test_main_heating_override_remaps_the_primary_system_code() -> None: + # Arrange + baseline = build_epc() + overlay = main_heating_overlay_for("Gas boiler, regular", 0) + assert overlay is not None + + # Act + result = apply_simulations(baseline, [overlay]) + + # Assert — the calculator reads the code off main_heating_details[0]. + assert result.sap_heating.main_heating_details[0].sap_main_heating_code == 102 + + +def test_the_three_heating_overrides_compose_without_conflict() -> None: + # Arrange — main_fuel, water_heating and main_heating_system all fold onto one + # HeatingOverlay surface but set DISJOINT fields, so they compose (the + # field-disjoint design that makes precedence moot for these three). + baseline = build_epc() + overlays = [ + fuel_overlay_for("electricity", 0), + water_heating_overlay_for("Electric immersion, electricity", 0), + main_heating_overlay_for("Electric storage heaters, fan", 0), + ] + assert all(o is not None for o in overlays) + + # Act + result = apply_simulations(baseline, [o for o in overlays if o is not None]) + + # Assert — each override landed on its own field. + main = result.sap_heating.main_heating_details[0] + assert main.main_fuel_type == 29 + assert main.sap_main_heating_code == 404 + assert result.sap_heating.water_heating_code == 903 + assert result.sap_heating.water_heating_fuel == 29 From ad3b1f15a80f3192fc95f87242ed99a7afa58de9 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:07:14 +0000 Subject: [PATCH 063/340] =?UTF-8?q?Classify=20the=20landlord=20Hot=20Water?= =?UTF-8?q?=20and=20Heating=20columns=20into=20categories=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../test_build_columns.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/applications/landlord_description_overrides/test_build_columns.py b/tests/applications/landlord_description_overrides/test_build_columns.py index 8f50afce8..0e47ed8d9 100644 --- a/tests/applications/landlord_description_overrides/test_build_columns.py +++ b/tests/applications/landlord_description_overrides/test_build_columns.py @@ -51,3 +51,29 @@ def test_build_columns_wires_a_construction_age_band_classifier_column() -> None assert len(columns) == 1 assert columns[0].name == "construction_age_band" assert columns[0].source_column == "Age" + + +def test_build_columns_wires_a_water_heating_classifier_column() -> None: + # Arrange + chat_gpt = cast(ChatGPT, object()) + + # Act + columns = _build_columns({"water_heating": "Hot Water"}, chat_gpt, None) + + # Assert + assert len(columns) == 1 + assert columns[0].name == "water_heating" + assert columns[0].source_column == "Hot Water" + + +def test_build_columns_wires_a_main_heating_system_classifier_column() -> None: + # Arrange + chat_gpt = cast(ChatGPT, object()) + + # Act + columns = _build_columns({"main_heating_system": "Heating"}, chat_gpt, None) + + # Assert + assert len(columns) == 1 + assert columns[0].name == "main_heating_system" + assert columns[0].source_column == "Heating" From cc228b3da5b87e5dffaaa676f99a0305f10426f6 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:09:05 +0000 Subject: [PATCH 064/340] =?UTF-8?q?Classify=20the=20landlord=20Hot=20Water?= =?UTF-8?q?=20and=20Heating=20columns=20into=20categories=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../landlord_description_overrides/handler.py | 28 ++++++++ domain/epc/main_heating_system_type.py | 24 +++++++ domain/epc/water_heating_type.py | 21 ++++++ ...ord_override_reader_postgres_repository.py | 20 ++++++ ...lord_main_heating_system_override_table.py | 71 +++++++++++++++++++ .../landlord_water_heating_override_table.py | 69 ++++++++++++++++++ 6 files changed, 233 insertions(+) create mode 100644 domain/epc/main_heating_system_type.py create mode 100644 domain/epc/water_heating_type.py create mode 100644 infrastructure/postgres/landlord_main_heating_system_override_table.py create mode 100644 infrastructure/postgres/landlord_water_heating_override_table.py diff --git a/applications/landlord_description_overrides/handler.py b/applications/landlord_description_overrides/handler.py index 2f83b81f5..1a99aea69 100644 --- a/applications/landlord_description_overrides/handler.py +++ b/applications/landlord_description_overrides/handler.py @@ -11,8 +11,10 @@ from domain.epc.built_form_type import BuiltFormType from domain.epc.construction_age_band import ConstructionAgeBand from domain.epc.glazing_type import GlazingType from domain.epc.main_fuel_type import MainFuelType +from domain.epc.main_heating_system_type import MainHeatingSystemType from domain.epc.property_type import PropertyType from domain.epc.roof_type import RoofType +from domain.epc.water_heating_type import WaterHeatingType from domain.epc.wall_type import WallType from domain.epc.wall_type_construction_dates import ( wall_type_construction_date_prompt_hint, @@ -36,6 +38,12 @@ from infrastructure.postgres.landlord_glazing_override_table import ( from infrastructure.postgres.landlord_main_fuel_override_table import ( LandlordMainFuelOverrideRow, ) +from infrastructure.postgres.landlord_main_heating_system_override_table import ( + LandlordMainHeatingSystemOverrideRow, +) +from infrastructure.postgres.landlord_water_heating_override_table import ( + LandlordWaterHeatingOverrideRow, +) from infrastructure.postgres.landlord_property_type_override_table import ( LandlordPropertyTypeOverrideRow, ) @@ -144,6 +152,26 @@ def _build_columns( session, LandlordConstructionAgeBandOverrideRow ), ), + "water_heating": lambda src: ClassifiableColumn( + name="water_heating", + source_column=src, + classifier=ChatGptColumnClassifier( + chat_gpt, WaterHeatingType, WaterHeatingType.UNKNOWN + ), + repo=LandlordOverridesRepository[WaterHeatingType]( + session, LandlordWaterHeatingOverrideRow + ), + ), + "main_heating_system": lambda src: ClassifiableColumn( + name="main_heating_system", + source_column=src, + classifier=ChatGptColumnClassifier( + chat_gpt, MainHeatingSystemType, MainHeatingSystemType.UNKNOWN + ), + repo=LandlordOverridesRepository[MainHeatingSystemType]( + session, LandlordMainHeatingSystemOverrideRow + ), + ), } columns: list[ClassifiableColumn[Any]] = [] diff --git a/domain/epc/main_heating_system_type.py b/domain/epc/main_heating_system_type.py new file mode 100644 index 000000000..ff3c4233a --- /dev/null +++ b/domain/epc/main_heating_system_type.py @@ -0,0 +1,24 @@ +from enum import Enum + + +class MainHeatingSystemType(Enum): + """A landlord-supplied main-heating-system description, as resolved by the + landlord-description-overrides context. + + Each member's value is the canonical system archetype that the main-heating + Simulation Overlay + (``domain/epc/property_overlays/main_heating_system_overlay.py``) maps to a + representative SAP ``sap_main_heating_code`` — so the member values MUST stay + in lock-step with that overlay's ``_MAIN_HEATING_CODES`` keys. The SEDBUK A-G + efficiency band the Hyde "Heating" column carries is NOT modelled yet + (deferred), so archetypes map to their modern/condensing code. ``UNKNOWN`` + covers values the classifier cannot resolve and the not-yet-modelled systems + (heat pumps, community heating). + """ + + GAS_COMBI = "Gas boiler, combi" + GAS_REGULAR = "Gas boiler, regular" + GAS_CPSU = "Gas CPSU" + ELECTRIC_STORAGE_FAN = "Electric storage heaters, fan" + DIRECT_ELECTRIC = "Direct-acting electric" + UNKNOWN = "Unknown" diff --git a/domain/epc/water_heating_type.py b/domain/epc/water_heating_type.py new file mode 100644 index 000000000..025c669a6 --- /dev/null +++ b/domain/epc/water_heating_type.py @@ -0,0 +1,21 @@ +from enum import Enum + + +class WaterHeatingType(Enum): + """A landlord-supplied water-heating description, as resolved by the + landlord-description-overrides context. + + Each member's value is the canonical ", " description that the + water-heating Simulation Overlay + (``domain/epc/property_overlays/water_heating_overlay.py``) decomposes into + the SAP ``water_heating_code`` + ``water_heating_fuel`` int codes the + calculator reads — so the member values MUST stay in lock-step with that + overlay's ``_WATER_HEATING_CODES`` keys. ``UNKNOWN`` covers values the + classifier cannot resolve, and any combination not yet given verified codes + (it leaves the lodged cert's hot-water arrangement untouched). + """ + + FROM_MAIN_MAINS_GAS = "From main system, mains gas" + FROM_MAIN_ELECTRICITY = "From main system, electricity" + ELECTRIC_IMMERSION = "Electric immersion, electricity" + UNKNOWN = "Unknown" diff --git a/infrastructure/landlord_overrides/landlord_override_reader_postgres_repository.py b/infrastructure/landlord_overrides/landlord_override_reader_postgres_repository.py index 79b851f33..4d02b0d59 100644 --- a/infrastructure/landlord_overrides/landlord_override_reader_postgres_repository.py +++ b/infrastructure/landlord_overrides/landlord_override_reader_postgres_repository.py @@ -25,9 +25,24 @@ from infrastructure.postgres.landlord_property_type_override_table import ( from infrastructure.postgres.landlord_roof_type_override_table import ( LandlordRoofTypeOverrideRow, ) +from infrastructure.postgres.landlord_construction_age_band_override_table import ( + LandlordConstructionAgeBandOverrideRow, +) +from infrastructure.postgres.landlord_glazing_override_table import ( + LandlordGlazingOverrideRow, +) +from infrastructure.postgres.landlord_main_fuel_override_table import ( + LandlordMainFuelOverrideRow, +) +from infrastructure.postgres.landlord_main_heating_system_override_table import ( + LandlordMainHeatingSystemOverrideRow, +) from infrastructure.postgres.landlord_wall_type_override_table import ( LandlordWallTypeOverrideRow, ) +from infrastructure.postgres.landlord_water_heating_override_table import ( + LandlordWaterHeatingOverrideRow, +) from repositories.landlord_overrides.landlord_override_reader import ( LandlordOverrideReader, ) @@ -38,6 +53,11 @@ _ROW_TYPES: dict[str, type] = { "built_form_type": LandlordBuiltFormTypeOverrideRow, "wall_type": LandlordWallTypeOverrideRow, "roof_type": LandlordRoofTypeOverrideRow, + "main_fuel": LandlordMainFuelOverrideRow, + "glazing": LandlordGlazingOverrideRow, + "construction_age_band": LandlordConstructionAgeBandOverrideRow, + "water_heating": LandlordWaterHeatingOverrideRow, + "main_heating_system": LandlordMainHeatingSystemOverrideRow, } diff --git a/infrastructure/postgres/landlord_main_heating_system_override_table.py b/infrastructure/postgres/landlord_main_heating_system_override_table.py new file mode 100644 index 000000000..a06c35c48 --- /dev/null +++ b/infrastructure/postgres/landlord_main_heating_system_override_table.py @@ -0,0 +1,71 @@ +"""SQLModel mirror of the ``landlord_main_heating_system_overrides`` Drizzle table. + +The schema source of truth lives in the ``assessment-model`` TS repo +(`src/app/db/schema/landlord_overrides.ts`). The migrations are owned there; +this row class only mirrors the columns so the Python lambda can read/write. +See ADR-0003. Shape mirrors ``LandlordWallTypeOverrideRow`` -- the only +differences are the table name, the ``main_heating_system`` pgEnum on ``value``, +and the unique-constraint name. +""" + +from datetime import datetime, timezone +from typing import ClassVar +from uuid import UUID, uuid4 + +from sqlalchemy import BigInteger, Column, UniqueConstraint +from sqlalchemy import Enum as SAEnum +from sqlmodel import Field, SQLModel + +from domain.epc.main_heating_system_type import MainHeatingSystemType +from infrastructure.postgres.landlord_override_enums import override_source_sa_enum + + +class LandlordMainHeatingSystemOverrideRow(SQLModel, table=True): + __tablename__: ClassVar[str] = "landlord_main_heating_system_overrides" # pyright: ignore[reportIncompatibleVariableOverride] + __table_args__: ClassVar[tuple[UniqueConstraint, ...]] = ( # pyright: ignore[reportIncompatibleVariableOverride] + # Shortened (drop the redundant ``_overrides``) to stay within + # PostgreSQL's 63-char identifier limit; mirrors the Drizzle name. + UniqueConstraint( + "portfolio_id", + "description", + name="landlord_main_heating_system_portfolio_description_unique", + ), + ) + + id: UUID = Field(default_factory=uuid4, primary_key=True) + + # bigint to match the Drizzle ``portfolio_id`` FK; SQLModel's default int + # mapping is 32-bit Integer and would overflow once portfolio IDs exceed + # 2^31. The FK to ``portfolio.id`` is enforced by the Drizzle migration, + # not declared here -- the ``portfolio`` table is not modelled in Python. + portfolio_id: int = Field( + sa_column=Column(BigInteger, nullable=False, index=True), + ) + + description: str = Field(nullable=False) + + value: MainHeatingSystemType = Field( + sa_column=Column( + SAEnum( + MainHeatingSystemType, + name="main_heating_system", + values_callable=lambda cls: [m.value for m in cls], # pyright: ignore[reportUnknownLambdaType, reportUnknownMemberType, reportUnknownVariableType] + ), + nullable=False, + ), + ) + + # Shared SAEnum -- see ``landlord_override_enums`` for why this single + # instance is reused by every ``landlord_*_overrides`` row class. + source: str = Field( + sa_column=Column(override_source_sa_enum, nullable=False), + ) + + created_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc), + nullable=False, + ) + updated_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc), + nullable=False, + ) diff --git a/infrastructure/postgres/landlord_water_heating_override_table.py b/infrastructure/postgres/landlord_water_heating_override_table.py new file mode 100644 index 000000000..c69a41d26 --- /dev/null +++ b/infrastructure/postgres/landlord_water_heating_override_table.py @@ -0,0 +1,69 @@ +"""SQLModel mirror of the ``landlord_water_heating_overrides`` Drizzle table. + +The schema source of truth lives in the ``assessment-model`` TS repo +(`src/app/db/schema/landlord_overrides.ts`). The migrations are owned there; +this row class only mirrors the columns so the Python lambda can read/write. +See ADR-0003. Shape mirrors ``LandlordWallTypeOverrideRow`` -- the only +differences are the table name, the ``water_heating`` pgEnum on ``value``, and +the unique-constraint name. +""" + +from datetime import datetime, timezone +from typing import ClassVar +from uuid import UUID, uuid4 + +from sqlalchemy import BigInteger, Column, UniqueConstraint +from sqlalchemy import Enum as SAEnum +from sqlmodel import Field, SQLModel + +from domain.epc.water_heating_type import WaterHeatingType +from infrastructure.postgres.landlord_override_enums import override_source_sa_enum + + +class LandlordWaterHeatingOverrideRow(SQLModel, table=True): + __tablename__: ClassVar[str] = "landlord_water_heating_overrides" # pyright: ignore[reportIncompatibleVariableOverride] + __table_args__: ClassVar[tuple[UniqueConstraint, ...]] = ( # pyright: ignore[reportIncompatibleVariableOverride] + UniqueConstraint( + "portfolio_id", + "description", + name="landlord_water_heating_overrides_portfolio_description_unique", + ), + ) + + id: UUID = Field(default_factory=uuid4, primary_key=True) + + # bigint to match the Drizzle ``portfolio_id`` FK; SQLModel's default int + # mapping is 32-bit Integer and would overflow once portfolio IDs exceed + # 2^31. The FK to ``portfolio.id`` is enforced by the Drizzle migration, + # not declared here -- the ``portfolio`` table is not modelled in Python. + portfolio_id: int = Field( + sa_column=Column(BigInteger, nullable=False, index=True), + ) + + description: str = Field(nullable=False) + + value: WaterHeatingType = Field( + sa_column=Column( + SAEnum( + WaterHeatingType, + name="water_heating", + values_callable=lambda cls: [m.value for m in cls], # pyright: ignore[reportUnknownLambdaType, reportUnknownMemberType, reportUnknownVariableType] + ), + nullable=False, + ), + ) + + # Shared SAEnum -- see ``landlord_override_enums`` for why this single + # instance is reused by every ``landlord_*_overrides`` row class. + source: str = Field( + sa_column=Column(override_source_sa_enum, nullable=False), + ) + + created_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc), + nullable=False, + ) + updated_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc), + nullable=False, + ) From 9d0d12c278ccd8b100591f6eeb50b0262b26d2db Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:10:02 +0000 Subject: [PATCH 065/340] =?UTF-8?q?Lock=20heating-override=20value=20cover?= =?UTF-8?q?age=20and=20reader/overlay=20registry=20parity=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc/test_main_heating_system_overlay.py | 15 ++++++++++++ .../domain/epc/test_water_heating_overlay.py | 14 +++++++++++ .../test_override_component_consistency.py | 24 +++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 tests/repositories/property/test_override_component_consistency.py diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index 0710837e8..e161dbf5b 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -8,6 +8,7 @@ from __future__ import annotations import pytest +from domain.epc.main_heating_system_type import MainHeatingSystemType from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for from domain.epc.property_overlays.main_heating_system_overlay import ( main_heating_overlay_for, @@ -103,3 +104,17 @@ def test_the_three_heating_overrides_compose_without_conflict() -> None: assert main.sap_main_heating_code == 404 assert result.sap_heating.water_heating_code == 903 assert result.sap_heating.water_heating_fuel == 29 + + +@pytest.mark.parametrize( + "member", + [m for m in MainHeatingSystemType if m is not MainHeatingSystemType.UNKNOWN], +) +def test_every_resolvable_main_heating_value_decodes( + member: MainHeatingSystemType, +) -> None: + # Act + simulation = main_heating_overlay_for(member.value, 0) + + # Assert + assert simulation is not None diff --git a/tests/domain/epc/test_water_heating_overlay.py b/tests/domain/epc/test_water_heating_overlay.py index 7dd8c2d0a..6e0b1fdbb 100644 --- a/tests/domain/epc/test_water_heating_overlay.py +++ b/tests/domain/epc/test_water_heating_overlay.py @@ -11,6 +11,7 @@ import pytest from domain.epc.property_overlays.water_heating_overlay import ( water_heating_overlay_for, ) +from domain.epc.water_heating_type import WaterHeatingType from domain.modelling.scoring.overlay_applicator import apply_simulations from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( build_epc, @@ -71,3 +72,16 @@ def test_water_heating_override_remaps_the_hot_water_arrangement() -> None: # Assert — the calculator reads these off sap_heating. assert result.sap_heating.water_heating_code == 903 assert result.sap_heating.water_heating_fuel == 29 + + +@pytest.mark.parametrize( + "member", [m for m in WaterHeatingType if m is not WaterHeatingType.UNKNOWN] +) +def test_every_resolvable_water_heating_value_decodes( + member: WaterHeatingType, +) -> None: + # Act + simulation = water_heating_overlay_for(member.value, 0) + + # Assert + assert simulation is not None diff --git a/tests/repositories/property/test_override_component_consistency.py b/tests/repositories/property/test_override_component_consistency.py new file mode 100644 index 000000000..df830cd02 --- /dev/null +++ b/tests/repositories/property/test_override_component_consistency.py @@ -0,0 +1,24 @@ +"""Every override component must be wired through the WHOLE chain. + +The finaliser reader (`_ROW_TYPES`, component -> landlord table) and the overlay +registry (`_COMPONENT_OVERLAYS`, component -> overlay mapper) must cover exactly +the same set of components. If a component is classified + stored but has no +reader entry, the finaliser silently never writes its `property_overrides` rows; +if it has no overlay entry, the row never reaches the calculator. This guard +keeps the two registries in lock-step (it would have caught the missing +main_fuel / glazing / construction_age_band reader entries). +""" + +from __future__ import annotations + +from infrastructure.landlord_overrides.landlord_override_reader_postgres_repository import ( # pyright: ignore[reportPrivateUsage] + _ROW_TYPES, +) +from repositories.property.landlord_override_overlays import ( # pyright: ignore[reportPrivateUsage] + _COMPONENT_OVERLAYS, +) + + +def test_reader_and_overlay_registries_cover_the_same_components() -> None: + # Assert + assert set(_ROW_TYPES) == set(_COMPONENT_OVERLAYS) From b7d2cff220b5b30689f19dfa4e51ddca2335cdc0 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:18:33 +0000 Subject: [PATCH 066/340] =?UTF-8?q?Decode=20bottled/special=20LPG,=20commu?= =?UTF-8?q?nity=20electric/biomass,=20dual-fuel,=20smokeless=20coal=20?= =?UTF-8?q?=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/domain/epc/test_main_fuel_overlay.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/domain/epc/test_main_fuel_overlay.py b/tests/domain/epc/test_main_fuel_overlay.py index 6001ddc36..161078fb1 100644 --- a/tests/domain/epc/test_main_fuel_overlay.py +++ b/tests/domain/epc/test_main_fuel_overlay.py @@ -47,6 +47,27 @@ def test_fuels_decode_to_their_modern_not_community_codes( assert simulation.heating.main_fuel_type == code +@pytest.mark.parametrize( + ("main_fuel_value", "code"), + [ + ("bottled LPG", 3), + ("LPG special condition", 17), + ("electricity (community)", 25), + ("biomass (community)", 31), + ("dual fuel (mineral and wood)", 10), + ("smokeless coal", 15), + ], +) +def test_more_fuels_decode_to_their_codes(main_fuel_value: str, code: int) -> None: + # Act + simulation = fuel_overlay_for(main_fuel_value, 0) + + # Assert + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.main_fuel_type == code + + def test_community_mains_gas_is_a_distinct_fuel_code() -> None: # Act simulation = fuel_overlay_for("mains gas (community)", 0) From 21bb55f4144c2cddaf05a2120c11baeb9bbaab84 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:18:55 +0000 Subject: [PATCH 067/340] =?UTF-8?q?Decode=20bottled/special=20LPG,=20commu?= =?UTF-8?q?nity=20electric/biomass,=20dual-fuel,=20smokeless=20coal=20?= =?UTF-8?q?=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/epc/main_fuel_type.py | 6 ++++++ domain/epc/property_overlays/main_fuel_overlay.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/domain/epc/main_fuel_type.py b/domain/epc/main_fuel_type.py index 0ad575c38..8427b9cf0 100644 --- a/domain/epc/main_fuel_type.py +++ b/domain/epc/main_fuel_type.py @@ -17,7 +17,13 @@ class MainFuelType(Enum): MAINS_GAS = "mains gas" MAINS_GAS_COMMUNITY = "mains gas (community)" ELECTRICITY = "electricity" + ELECTRICITY_COMMUNITY = "electricity (community)" LPG_BULK = "LPG (bulk)" + LPG_BOTTLED = "bottled LPG" + LPG_SPECIAL_CONDITION = "LPG special condition" OIL = "oil" HOUSE_COAL = "house coal" + SMOKELESS_COAL = "smokeless coal" + DUAL_FUEL_MINERAL_WOOD = "dual fuel (mineral and wood)" + BIOMASS_COMMUNITY = "biomass (community)" UNKNOWN = "Unknown" diff --git a/domain/epc/property_overlays/main_fuel_overlay.py b/domain/epc/property_overlays/main_fuel_overlay.py index c31457055..cc482122d 100644 --- a/domain/epc/property_overlays/main_fuel_overlay.py +++ b/domain/epc/property_overlays/main_fuel_overlay.py @@ -20,9 +20,15 @@ _FUEL_CODES: dict[str, int] = { "mains gas": 26, "mains gas (community)": 20, "LPG (bulk)": 27, + "bottled LPG": 3, + "LPG special condition": 17, "oil": 28, "electricity": 29, + "electricity (community)": 25, "house coal": 33, + "smokeless coal": 15, + "dual fuel (mineral and wood)": 10, + "biomass (community)": 31, } From 49f783de45fa89602ba29efc6efcba5f1dc0d62b Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:20:02 +0000 Subject: [PATCH 068/340] =?UTF-8?q?Decode=20from-main=20oil/LPG/coal=20and?= =?UTF-8?q?=20gas=20boiler-circulator=20water=20heating=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../domain/epc/test_water_heating_overlay.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/domain/epc/test_water_heating_overlay.py b/tests/domain/epc/test_water_heating_overlay.py index 6e0b1fdbb..12e6e8bee 100644 --- a/tests/domain/epc/test_water_heating_overlay.py +++ b/tests/domain/epc/test_water_heating_overlay.py @@ -49,6 +49,30 @@ def test_water_heating_systems_decode_to_their_codes( assert simulation.heating.water_heating_fuel == fuel +@pytest.mark.parametrize( + ("water_heating_value", "code", "fuel"), + [ + ("From main system, oil", 901, 28), + ("From main system, LPG (bulk)", 901, 27), + ("From main system, bottled LPG", 901, 3), + ("From main system, house coal", 901, 33), + # "boiler/circulator for water heating only" is SAP Table 4a code 911. + ("Gas boiler/circulator, mains gas", 911, 26), + ], +) +def test_more_water_heating_combos_decode_to_their_codes( + water_heating_value: str, code: int, fuel: int +) -> None: + # Act + simulation = water_heating_overlay_for(water_heating_value, 0) + + # Assert + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.water_heating_code == code + assert simulation.heating.water_heating_fuel == fuel + + @pytest.mark.parametrize("water_heating_value", ["Unknown", ""]) def test_unresolvable_water_heating_produces_no_overlay( water_heating_value: str, From d8ab3f6ec7dea4b5d738e782ac4020181c23d58b Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:20:24 +0000 Subject: [PATCH 069/340] =?UTF-8?q?Decode=20from-main=20oil/LPG/coal=20and?= =?UTF-8?q?=20gas=20boiler-circulator=20water=20heating=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/epc/property_overlays/water_heating_overlay.py | 6 ++++++ domain/epc/water_heating_type.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/domain/epc/property_overlays/water_heating_overlay.py b/domain/epc/property_overlays/water_heating_overlay.py index e55a578ed..3ec952de2 100644 --- a/domain/epc/property_overlays/water_heating_overlay.py +++ b/domain/epc/property_overlays/water_heating_overlay.py @@ -22,7 +22,13 @@ from domain.modelling.simulation import EpcSimulation, HeatingOverlay _WATER_HEATING_CODES: dict[str, tuple[int, int]] = { "From main system, mains gas": (901, 26), "From main system, electricity": (901, 29), + "From main system, oil": (901, 28), + "From main system, LPG (bulk)": (901, 27), + "From main system, bottled LPG": (901, 3), + "From main system, house coal": (901, 33), "Electric immersion, electricity": (903, 29), + # "boiler/circulator for water heating only" — SAP Table 4a code 911 (gas). + "Gas boiler/circulator, mains gas": (911, 26), } diff --git a/domain/epc/water_heating_type.py b/domain/epc/water_heating_type.py index 025c669a6..e85a195d8 100644 --- a/domain/epc/water_heating_type.py +++ b/domain/epc/water_heating_type.py @@ -17,5 +17,10 @@ class WaterHeatingType(Enum): FROM_MAIN_MAINS_GAS = "From main system, mains gas" FROM_MAIN_ELECTRICITY = "From main system, electricity" + FROM_MAIN_OIL = "From main system, oil" + FROM_MAIN_LPG_BULK = "From main system, LPG (bulk)" + FROM_MAIN_BOTTLED_LPG = "From main system, bottled LPG" + FROM_MAIN_HOUSE_COAL = "From main system, house coal" ELECTRIC_IMMERSION = "Electric immersion, electricity" + GAS_BOILER_CIRCULATOR_MAINS_GAS = "Gas boiler/circulator, mains gas" UNKNOWN = "Unknown" From 4fbd8d5999da6568d3997edcd362d522b4103249 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:20:41 +0000 Subject: [PATCH 070/340] =?UTF-8?q?Decode=20old/slimline/convector=20stora?= =?UTF-8?q?ge-heater=20heating=20overrides=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc/test_main_heating_system_overlay.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index e161dbf5b..8b901616d 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -53,6 +53,26 @@ def test_heating_archetypes_decode_to_their_sap_codes( assert simulation.heating.sap_main_heating_code == code +@pytest.mark.parametrize( + ("main_heating_value", "code"), + [ + ("Electric storage heaters, old", 401), + ("Electric storage heaters, slimline", 402), + ("Electric storage heaters, convector", 403), + ], +) +def test_storage_heater_subtypes_decode_to_their_codes( + main_heating_value: str, code: int +) -> None: + # Act + simulation = main_heating_overlay_for(main_heating_value, 0) + + # Assert + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.sap_main_heating_code == code + + @pytest.mark.parametrize( "main_heating_value", ["Unknown", "", "Air source heat pump", "Community heating"], From 2dbbcf0c7bbd727333d2204324bb690d22a8b508 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:21:01 +0000 Subject: [PATCH 071/340] =?UTF-8?q?Decode=20old/slimline/convector=20stora?= =?UTF-8?q?ge-heater=20heating=20overrides=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/epc/main_heating_system_type.py | 3 +++ domain/epc/property_overlays/main_heating_system_overlay.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/domain/epc/main_heating_system_type.py b/domain/epc/main_heating_system_type.py index ff3c4233a..bea14e6af 100644 --- a/domain/epc/main_heating_system_type.py +++ b/domain/epc/main_heating_system_type.py @@ -19,6 +19,9 @@ class MainHeatingSystemType(Enum): GAS_COMBI = "Gas boiler, combi" GAS_REGULAR = "Gas boiler, regular" GAS_CPSU = "Gas CPSU" + ELECTRIC_STORAGE_OLD = "Electric storage heaters, old" + ELECTRIC_STORAGE_SLIMLINE = "Electric storage heaters, slimline" + ELECTRIC_STORAGE_CONVECTOR = "Electric storage heaters, convector" ELECTRIC_STORAGE_FAN = "Electric storage heaters, fan" DIRECT_ELECTRIC = "Direct-acting electric" UNKNOWN = "Unknown" diff --git a/domain/epc/property_overlays/main_heating_system_overlay.py b/domain/epc/property_overlays/main_heating_system_overlay.py index 5d61cb5a8..1f06eabc7 100644 --- a/domain/epc/property_overlays/main_heating_system_overlay.py +++ b/domain/epc/property_overlays/main_heating_system_overlay.py @@ -29,6 +29,9 @@ _MAIN_HEATING_CODES: dict[str, int] = { "Gas boiler, combi": 104, "Gas boiler, regular": 102, "Gas CPSU": 120, + "Electric storage heaters, old": 401, + "Electric storage heaters, slimline": 402, + "Electric storage heaters, convector": 403, "Electric storage heaters, fan": 404, "Direct-acting electric": 191, } From 034d4b7cd53af4e5d15700020046ebd339141288 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 19 Jun 2026 14:32:31 +0000 Subject: [PATCH 072/340] =?UTF-8?q?fix(uvalues):=20bill=20known-insulation?= =?UTF-8?q?=20stone=20walls=20by=20the=20=C2=A75.6=20thickness=20formula,?= =?UTF-8?q?=20uncapped=20(RdSAP=2010=20=C2=A75.6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An uninsulated stone wall of lodged thickness, age bands A-E, is billed by the RdSAP 10 §5.6 Table-12 formula on its measured thickness — sandstone / limestone U = 54.876·W^-0.561, granite / whinstone U = 45.315·W^-0.513. Two bugs suppressed it: 1. CAP: the as-built branch capped the formula result at the Table-6 typical-thickness default (`if u0 >= 1.7: return 1.7`). But the formula only dips below 1.7 past ~488 mm (sandstone) / ~640 mm (granite), so the cap nullified §5.6 for essentially every real-thickness stone wall, under-counting fabric loss and over-rating. A measured 400 mm sandstone age-B wall is 1.90 (Elmhurst-confirmed), not 1.70. 2. NO INSULATION-STATE GATE: the branch fired for any stone wall with a lodged thickness, including ones whose insulation is "Unknown". RdSAP treats an unknown-insulation wall via the Table-6 typical-thickness default, NOT as bare stone of the lodged thickness — so a 50 mm granite wall with Unknown insulation must read 1.70 (worksheet), not the formula's 6.09. Gated on `wall_insulation_type is not None`. Fixes the 2 long-standing stone-U unit tests (granite 120 mm → 3.8871, sandstone 120 mm → 3.7408 — they were correct red tests, not "known fails"). Corpus within-0.5 70.3% -> 71.6% (MAE 0.833 -> 0.822); ratcheted floors to 0.71 / 0.83. Worksheet fixture 000565 (granite 50 mm Unknown → 1.70) still passes via the insulation gate. The two stone groups (sandstone/limestone vs granite/whinstone) keep their distinct §5.6 coefficients. pyright not installed in this codespace (strict gate not run locally). Co-Authored-By: Claude Opus 4.8 (1M context) --- domain/sap10_ml/rdsap_uvalues.py | 43 +++++++++++-------- .../epc_client/test_sap_accuracy_corpus.py | 17 +++++++- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/domain/sap10_ml/rdsap_uvalues.py b/domain/sap10_ml/rdsap_uvalues.py index 33b9741c9..26e4f611b 100644 --- a/domain/sap10_ml/rdsap_uvalues.py +++ b/domain/sap10_ml/rdsap_uvalues.py @@ -567,23 +567,25 @@ def u_wall( ctry = country if country is not None else Country.ENG age_idx = _age_index(age_band) band = _AGE_BANDS[age_idx] - # RdSAP 10 §5.6 (PDF p.40) — uninsulated stone wall thin-wall - # formula, age bands A-E. Fires only when a documentary wall - # thickness is lodged (per §5.3 documentary-evidence rule). - # §5.8 + Table 14 dry-line adjustment applies on top. - # - # Table 6 footnote (a) (PDF p.34): "Or from equations in 5.6 if - # the calculated U-value is less than 1.7." The cap applies only - # to the AS-BUILT (no insulation, no dry-line) Table 6 row — for - # thin walls where §5.6 gives U ≥ 1.7 (e.g. granite at W=50 mm - # yields 6.09 → use Table 6 default 1.7 instead). When the wall - # is dry-lined or insulated, the raw §5.6 result feeds the §5.8 - # chain as the input U₀ — the Table 6 footnote doesn't cap that - # path (verified empirically against cert 000565 Main alt_wall_1: - # granite W=120 mm dry-lined → U₀=3.88 raw + dry-line → 2.34 - # matches worksheet, NOT 1.7 + dry-line → 1.32). + # RdSAP 10 §5.6 (PDF p.40) — uninsulated stone wall formula, age + # bands A-E. Fires only when (a) a documentary wall thickness is lodged + # (per §5.3 documentary-evidence rule) AND (b) the insulation STATE is + # known (`wall_insulation_type` not None — As Built / external / internal). + # When either is absent the cascade falls through to the Table-6 + # typical-thickness default (1.7) below: an "insulation Unknown" lodgement + # is NOT treated as bare stone of the lodged thickness (cert 000565 Ext1: + # granite 50 mm + insulation Unknown → Table-6 1.70 in the worksheet, NOT + # the §5.6 formula's 6.09), and the footnote (a) "use §5.6 if the + # calculated U-value is less than 1.7" clause governs the unknown path. When the thickness IS + # lodged the raw §5.6 U is the spec target — it is NOT capped at 1.7, + # because a thin/standard solid stone wall genuinely loses more than the + # typical-thickness default (sandstone 400 mm → 1.90, granite 120 mm → + # 3.89). §5.8 + Table 14 insulation / dry-line adjustments apply on top + # of the raw §5.6 U₀ (cert 000565 Main alt_wall_1: granite W=120 mm + # dry-lined → U₀=3.88 + dry-line → 2.34, matches worksheet). if ( wall_thickness_mm is not None + and wall_insulation_type is not None and band in _STONE_AGE_A_TO_E and construction in (WALL_STONE_GRANITE, WALL_STONE_SANDSTONE) ): @@ -628,8 +630,15 @@ def u_wall( Decimal("0.01"), rounding=ROUND_HALF_UP ) ) - if u0 >= 1.7: - return 1.7 # Table-6 row cap per footnote (a) + # As-built (uninsulated, not dry-lined) stone wall of KNOWN + # thickness, age A-E: return the raw §5.6 result. The Table-6 + # footnote (a) "< 1.7" clause governs the UNKNOWN-thickness path + # (which falls through to the Table-6 typical 1.7 default below) — + # NOT a documentary-thickness lodgement. A thin solid stone wall + # genuinely has U > 1.7 (e.g. sandstone 400 mm = 1.90, granite + # 120 mm = 3.89); capping it to 1.7 under-counts fabric loss and + # over-rates. Confirmed against Elmhurst (age-B sandstone 400 mm + # → 1.9) and the §5.6 Table-12 formula tests. return u0 known_types = { WALL_STONE_GRANITE, WALL_STONE_SANDSTONE, WALL_SOLID_BRICK, WALL_CAVITY, diff --git a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py index 7b3524f5c..2f454359f 100644 --- a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py +++ b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py @@ -150,14 +150,27 @@ _CORPUS = Path( # MAE 0.12 -> 0.08 t/yr (bias +0.04 -> 0.00). A prior session deferred enum 9 # ("direction not understood") while the PE/CO2 lens was confounded by the # climate-cascade bug (fc7c4d2d); the corrected lens shows the over-rate. +# UNINSULATED STONE WALL §5.6 FORMULA (RdSAP 10 §5.6 Table 12, PDF p.40): a +# stone wall of KNOWN thickness whose insulation STATE is known (As Built / +# external / internal) is billed by the §5.6 formula on its lodged thickness +# (sandstone/limestone U = 54.876·W^-0.561, granite/whinstone 45.315·W^-0.513), +# NOT capped at the Table-6 typical-thickness 1.7. The old `if u0>=1.7: 1.7` +# cap nullified the formula for every real-thickness stone wall (it only dips +# below 1.7 past ~488 mm sandstone / ~640 mm granite) and under-counted fabric +# loss → over-rate. Gated on `wall_insulation_type is not None` so an +# "insulation Unknown" wall still falls to the Table-6 default (cert 000565 +# Ext1: granite 50 mm + Unknown → worksheet 1.70, not the formula's 6.09). +# Took within-0.5 70.3% -> 71.6% (MAE 0.833 -> 0.822); fixed the 2 stone-U +# unit tests; worksheet-validated (Elmhurst age-B sandstone 400 mm → 1.90). +# # SAP RATING FLOOR (SAP 10.2 §13 / RdSAP 10 §13): the rating is floored at 1 # ("if the result is less than 1, the rating is 1"). `calculate_sap_from_inputs` # now applies that floor to the CONTINUOUS score too (was integer-only), so a # degenerate dwelling no longer emits a negative SAP. Removed a -12.3 outlier # (cert 422000111926, lodged at the floor of 1, was computing -11.3): within-0.5 # 70.2% -> 70.3%, MAE 0.845 -> 0.833. -_MIN_WITHIN_HALF_SAP = 0.70 -_MAX_SAP_MAE = 0.84 +_MIN_WITHIN_HALF_SAP = 0.71 +_MAX_SAP_MAE = 0.83 _MAX_CO2_MAE_TONNES = 0.09 # t CO2 / yr vs co2_emissions_current _MAX_PE_PER_M2_MAE = 4.0 # kWh / m2 / yr vs energy_consumption_current From 11393e54dc5e60d18b08e0367ea47c0bc1d9a564 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 19 Jun 2026 14:49:51 +0000 Subject: [PATCH 073/340] feat(modelling): price secondary-heating-removal from an off-catalogue overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The FE-owned `material.type` pgEnum cannot carry `secondary_heating_removal`, so pricing it through the DB catalogue raises a DataError that poisons the session — the modelling pipeline crashed on any property with a lodged secondary heater unless the measure was excluded on the Scenario. Realise the `ProductRepository` docstring's intent (DB catalogue today, a JSON file for costs the ETL does not yet supply, behind the same port): add a `CompositeProductRepository` that resolves an override source first, then the catalogue. Checking the override first keeps that Measure Type away from the DB entirely; every other type misses the override and falls through unchanged. - off_catalogue_costs.json prices it at £270 flat per-dwelling — the legacy `Costs.heater_removal` ported to the new flat model (ADR-0028): (£25 + £200 baseline) x 1.2 VAT, for the single fixed secondary a cert lodges. Contingency (0.25) is joined from config, not the file. - Wire the composite into PostgresUnitOfWork.product and run_modelling_e2e, so the first-run pipeline and the local runner both honour the overlay. - Integration test: drop the unrealistic seeded secondary_heating_removal DB rows (the pgEnum can't hold the type) and assert it is JSON-sourced (material_id is None, cost £270) end-to-end through a real Unit of Work. Co-Authored-By: Claude Opus 4.8 (1M context) --- repositories/postgres_unit_of_work.py | 6 +- .../product/composite_product_repository.py | 51 +++++++++ repositories/product/off_catalogue_costs.json | 3 + scripts/run_modelling_e2e.py | 6 +- ...test_ara_first_run_pipeline_integration.py | 40 +++---- .../test_composite_product_repository.py | 101 ++++++++++++++++++ 6 files changed, 175 insertions(+), 32 deletions(-) create mode 100644 repositories/product/composite_product_repository.py create mode 100644 repositories/product/off_catalogue_costs.json create mode 100644 tests/repositories/product/test_composite_product_repository.py diff --git a/repositories/postgres_unit_of_work.py b/repositories/postgres_unit_of_work.py index fd4489bfb..a828d8c63 100644 --- a/repositories/postgres_unit_of_work.py +++ b/repositories/postgres_unit_of_work.py @@ -11,8 +11,8 @@ from repositories.property_baseline.property_baseline_postgres_repository import ) from repositories.epc.epc_postgres_repository import EpcPostgresRepository from repositories.plan.plan_postgres_repository import PlanPostgresRepository -from repositories.product.product_postgres_repository import ( - ProductPostgresRepository, +from repositories.product.composite_product_repository import ( + catalogue_with_off_catalogue_overrides, ) from repositories.property.property_postgres_repository import ( PropertyPostgresRepository, @@ -49,7 +49,7 @@ class PostgresUnitOfWork(UnitOfWork): self.spatial = spatial_repo self.property_baseline = PropertyBaselinePostgresRepository(self._session) self.scenario = ScenarioPostgresRepository(self._session) - self.product = ProductPostgresRepository(self._session) + self.product = catalogue_with_off_catalogue_overrides(self._session) self.plan = PlanPostgresRepository(self._session) return self diff --git a/repositories/product/composite_product_repository.py b/repositories/product/composite_product_repository.py new file mode 100644 index 000000000..8c03c68ea --- /dev/null +++ b/repositories/product/composite_product_repository.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from pathlib import Path + +from sqlmodel import Session + +from domain.modelling.product import Product +from repositories.product.product_json_repository import ProductJsonRepository +from repositories.product.product_postgres_repository import ( + ProductPostgresRepository, +) +from repositories.product.product_repository import ProductRepository + +# The committed off-catalogue cost overlay — Measure Types the live ``material`` +# catalogue cannot supply, priced from a static JSON until the ETL stocks them. +OFF_CATALOGUE_COSTS_PATH: Path = Path(__file__).resolve().parent / "off_catalogue_costs.json" + + +class CompositeProductRepository(ProductRepository): + """Resolves a Product from an ``override`` source first, then a ``fallback``. + + The override holds off-catalogue costs the live ``material`` catalogue cannot + supply — today ``secondary_heating_removal``, whose Measure Type the FE-owned + ``material.type`` pgEnum does not carry. Querying that value against the DB + raises a ``DataError`` that poisons the session's transaction (it is not a + benign ``ProductNotFound``), so checking the override **first** keeps that + Measure Type away from the catalogue entirely. Every other Measure Type + misses the override and falls through to the catalogue unchanged. + """ + + def __init__( + self, override: ProductRepository, fallback: ProductRepository + ) -> None: + self._override = override + self._fallback = fallback + + def get(self, measure_type: str) -> Product: + product = self._override.get_optional(measure_type) + if product is not None: + return product + return self._fallback.get(measure_type) + + +def catalogue_with_off_catalogue_overrides(session: Session) -> ProductRepository: + """The live ``material`` catalogue with the committed off-catalogue JSON costs + layered on top — the single composition point both the pipeline's Unit of + Work and the local e2e runner price through.""" + return CompositeProductRepository( + override=ProductJsonRepository(OFF_CATALOGUE_COSTS_PATH), + fallback=ProductPostgresRepository(session), + ) diff --git a/repositories/product/off_catalogue_costs.json b/repositories/product/off_catalogue_costs.json new file mode 100644 index 000000000..299da01d2 --- /dev/null +++ b/repositories/product/off_catalogue_costs.json @@ -0,0 +1,3 @@ +{ + "secondary_heating_removal": { "unit_cost_per_m2": 270.0 } +} diff --git a/scripts/run_modelling_e2e.py b/scripts/run_modelling_e2e.py index fb919f82f..8156db036 100644 --- a/scripts/run_modelling_e2e.py +++ b/scripts/run_modelling_e2e.py @@ -81,8 +81,8 @@ from infrastructure.solar.google_solar_api_client import ( # noqa: E402 from repositories.geospatial.geospatial_s3_repository import ( # noqa: E402 GeospatialS3Repository, ) -from repositories.product.product_postgres_repository import ( # noqa: E402 - ProductPostgresRepository, +from repositories.product.composite_product_repository import ( # noqa: E402 + catalogue_with_off_catalogue_overrides, ) from repositories.postgres_unit_of_work import PostgresUnitOfWork # noqa: E402 from repositories.scenario.scenario_postgres_repository import ( # noqa: E402 @@ -361,7 +361,7 @@ def main() -> None: # One read-only session for the live `material` catalogue, reused across the # batch so both store and no-store runs price against the same DB rows. catalogue_session = Session(engine) - products = ProductPostgresRepository(catalogue_session) + products = catalogue_with_off_catalogue_overrides(catalogue_session) scenario: Optional[Scenario] = ( _scenario_for(catalogue_session, args.scenario_id) if args.scenario_id is not None diff --git a/tests/orchestration/test_ara_first_run_pipeline_integration.py b/tests/orchestration/test_ara_first_run_pipeline_integration.py index 192f07a55..5bc089015 100644 --- a/tests/orchestration/test_ara_first_run_pipeline_integration.py +++ b/tests/orchestration/test_ara_first_run_pipeline_integration.py @@ -192,14 +192,8 @@ def test_first_run_baselines_through_repos_and_is_idempotent_on_rerun( is_active=True, description="Zoned heating controls + cylinder tune-up", ), - MaterialRow( - id=9, - type="secondary_heating_removal", - total_cost=250.0, - cost_unit="gbp_per_unit", - is_active=True, - description="Secondary heating removal", - ), + # secondary_heating_removal is off-catalogue (priced from the + # JSON overlay, not the pgEnum-constrained material table). ] ) session.commit() @@ -324,14 +318,10 @@ def test_modelling_optimises_and_persists_a_multi_measure_plan( is_active=True, description="LED bulb", ), - MaterialRow( - id=9, - type="secondary_heating_removal", - total_cost=250.0, - cost_unit="gbp_per_unit", - is_active=True, - description="Secondary heating removal", - ), + # No secondary_heating_removal row: the FE-owned ``material.type`` + # pgEnum cannot carry that Measure Type, so it is priced from the + # committed off-catalogue JSON overlay (£270, no material_id) that + # the Unit of Work layers over the catalogue, not from the DB. ] ) session.commit() @@ -402,13 +392,17 @@ def test_modelling_optimises_and_persists_a_multi_measure_plan( "air_source_heat_pump", "secondary_heating_removal", } - # Each persisted measure carries the catalogue id of the Product it installs + # Each catalogue-sourced measure carries the id of the Product it installs # (the MaterialRow ids seeded above), replacing the retired # recommendation_materials BOM with a single material_id on the row. assert by_type["air_source_heat_pump"].material_id == 5 assert by_type["suspended_floor_insulation"].material_id == 2 assert by_type["low_energy_lighting"].material_id == 4 - assert by_type["secondary_heating_removal"].material_id == 9 + # Secondary heating removal is priced from the off-catalogue JSON overlay + # (£270 flat per-dwelling, ADR-0028), so it carries no catalogue material_id. + assert by_type["secondary_heating_removal"].material_id is None + assert by_type["secondary_heating_removal"].estimated_cost is not None + assert abs(by_type["secondary_heating_removal"].estimated_cost - 270.0) <= 1e-6 for rec in rec_rows: assert rec.default is True assert rec.already_installed is False @@ -498,14 +492,8 @@ def test_modelling_recommends_nothing_when_already_at_the_target_band( is_active=True, description="LED bulb", ), - MaterialRow( - id=9, - type="secondary_heating_removal", - total_cost=250.0, - cost_unit="gbp_per_unit", - is_active=True, - description="Secondary heating removal", - ), + # secondary_heating_removal is off-catalogue (priced from the + # JSON overlay, not the pgEnum-constrained material table). ] ) session.commit() diff --git a/tests/repositories/product/test_composite_product_repository.py b/tests/repositories/product/test_composite_product_repository.py new file mode 100644 index 000000000..527999fed --- /dev/null +++ b/tests/repositories/product/test_composite_product_repository.py @@ -0,0 +1,101 @@ +"""Behaviour of the composite ProductRepository: an off-catalogue override +source checked first, the live catalogue as fallback. The override holds costs +the ``material`` catalogue cannot supply (today ``secondary_heating_removal``, +whose Measure Type the ``material.type`` pgEnum does not carry). Resolving it +from the override keeps that Measure Type away from the DB entirely, while every +other Measure Type falls through to the catalogue unchanged.""" + +import pytest + +from domain.modelling.product import Product +from repositories.product.composite_product_repository import ( + OFF_CATALOGUE_COSTS_PATH, + CompositeProductRepository, +) +from repositories.product.product_json_repository import ProductJsonRepository +from repositories.product.product_repository import ProductNotFound, ProductRepository + + +class _StaticRepo(ProductRepository): + """A ProductRepository over a fixed measure-type → Product map.""" + + def __init__(self, products: dict[str, Product]) -> None: + self._products = products + + def get(self, measure_type: str) -> Product: + product = self._products.get(measure_type) + if product is None: + raise ProductNotFound(f"no product for measure type {measure_type!r}") + return product + + +class _ExplodingRepo(ProductRepository): + """A fallback that must never be reached — any read fails the test loudly, + standing in for the catalogue whose pgEnum query would poison the session.""" + + def get(self, measure_type: str) -> Product: + raise AssertionError( + f"fallback was queried for {measure_type!r}; the override should have " + "resolved it without touching the catalogue" + ) + + +def _product(measure_type: str, unit_cost: float) -> Product: + return Product( + measure_type=measure_type, unit_cost_per_m2=unit_cost, contingency_rate=0.25 + ) + + +def test_get_returns_the_override_without_touching_the_fallback() -> None: + # Arrange — the override supplies the off-catalogue cost; the fallback would + # raise if consulted (mirroring the live pgEnum DataError on this type). + override = _StaticRepo( + {"secondary_heating_removal": _product("secondary_heating_removal", 270.0)} + ) + repo = CompositeProductRepository(override=override, fallback=_ExplodingRepo()) + + # Act + product: Product = repo.get("secondary_heating_removal") + + # Assert + assert product.measure_type == "secondary_heating_removal" + assert abs(product.unit_cost_per_m2 - 270.0) <= 1e-9 + + +def test_get_falls_through_to_the_fallback_when_the_override_misses() -> None: + # Arrange — the override has nothing for this type; the catalogue does. + override = _StaticRepo({}) + fallback = _StaticRepo( + {"cavity_wall_insulation": _product("cavity_wall_insulation", 18.5)} + ) + repo = CompositeProductRepository(override=override, fallback=fallback) + + # Act + product: Product = repo.get("cavity_wall_insulation") + + # Assert + assert product.measure_type == "cavity_wall_insulation" + assert abs(product.unit_cost_per_m2 - 18.5) <= 1e-9 + + +def test_get_raises_product_not_found_when_neither_source_has_it() -> None: + # Arrange + repo = CompositeProductRepository( + override=_StaticRepo({}), fallback=_StaticRepo({}) + ) + + # Act / Assert + with pytest.raises(ProductNotFound): + repo.get("loft_insulation") + + +def test_committed_override_file_prices_secondary_heating_removal() -> None: + # The committed overlay carries the flat per-dwelling decommission cost + # (ADR-0028, ported from the legacy heater_removal: (£25 + £200) × 1.2 VAT); + # contingency 0.25 is joined from config. + product: Product = ProductJsonRepository(OFF_CATALOGUE_COSTS_PATH).get( + "secondary_heating_removal" + ) + + assert abs(product.unit_cost_per_m2 - 270.0) <= 1e-9 + assert abs(product.contingency_rate - 0.25) <= 1e-9 From 3a0a122b7f4e405037871d6e32778c895acea131 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 14:49:58 +0000 Subject: [PATCH 074/340] =?UTF-8?q?Group=20landlord=20property-override=20?= =?UTF-8?q?enums=20under=20domain/epc/property=5Foverrides=20=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../landlord_description_overrides/handler.py | 20 +++++++++---------- domain/epc/property_overrides/__init__.py | 4 ++++ .../built_form_type.py | 0 .../construction_age_band.py | 0 .../{ => property_overrides}/glazing_type.py | 0 .../main_fuel_type.py | 0 .../main_heating_system_type.py | 0 .../override_code_mapping.py | 0 .../{ => property_overrides}/property_type.py | 0 .../epc/{ => property_overrides}/roof_type.py | 0 .../epc/{ => property_overrides}/wall_type.py | 0 .../wall_type_construction_dates.py | 2 +- .../water_heating_type.py | 0 ...landlord_built_form_type_override_table.py | 2 +- ...rd_construction_age_band_override_table.py | 2 +- .../landlord_glazing_override_table.py | 2 +- .../landlord_main_fuel_override_table.py | 2 +- ...lord_main_heating_system_override_table.py | 2 +- .../landlord_property_type_override_table.py | 2 +- .../landlord_roof_type_override_table.py | 2 +- .../landlord_wall_type_override_table.py | 2 +- .../landlord_water_heating_override_table.py | 2 +- .../bulk_upload_finaliser_orchestrator.py | 8 ++++---- ...ide_backed_prediction_attributes_reader.py | 2 +- .../epc/test_construction_age_band_overlay.py | 2 +- tests/domain/epc/test_glazing_overlay.py | 2 +- tests/domain/epc/test_main_fuel_overlay.py | 2 +- .../epc/test_main_heating_system_overlay.py | 2 +- .../domain/epc/test_override_code_mapping.py | 6 +++--- .../domain/epc/test_water_heating_overlay.py | 2 +- .../chatgpt/test_chatgpt_column_classifier.py | 4 ++-- ...lord_description_overrides_orchestrator.py | 6 +++--- ..._landlord_overrides_postgres_repository.py | 4 ++-- 33 files changed, 44 insertions(+), 40 deletions(-) create mode 100644 domain/epc/property_overrides/__init__.py rename domain/epc/{ => property_overrides}/built_form_type.py (100%) rename domain/epc/{ => property_overrides}/construction_age_band.py (100%) rename domain/epc/{ => property_overrides}/glazing_type.py (100%) rename domain/epc/{ => property_overrides}/main_fuel_type.py (100%) rename domain/epc/{ => property_overrides}/main_heating_system_type.py (100%) rename domain/epc/{ => property_overrides}/override_code_mapping.py (100%) rename domain/epc/{ => property_overrides}/property_type.py (100%) rename domain/epc/{ => property_overrides}/roof_type.py (100%) rename domain/epc/{ => property_overrides}/wall_type.py (100%) rename domain/epc/{ => property_overrides}/wall_type_construction_dates.py (97%) rename domain/epc/{ => property_overrides}/water_heating_type.py (100%) diff --git a/applications/landlord_description_overrides/handler.py b/applications/landlord_description_overrides/handler.py index 1a99aea69..4020bc512 100644 --- a/applications/landlord_description_overrides/handler.py +++ b/applications/landlord_description_overrides/handler.py @@ -7,16 +7,16 @@ import boto3 from applications.landlord_description_overrides.landlord_description_overrides_trigger_body import ( LandlordDescriptionOverridesTriggerBody, ) -from domain.epc.built_form_type import BuiltFormType -from domain.epc.construction_age_band import ConstructionAgeBand -from domain.epc.glazing_type import GlazingType -from domain.epc.main_fuel_type import MainFuelType -from domain.epc.main_heating_system_type import MainHeatingSystemType -from domain.epc.property_type import PropertyType -from domain.epc.roof_type import RoofType -from domain.epc.water_heating_type import WaterHeatingType -from domain.epc.wall_type import WallType -from domain.epc.wall_type_construction_dates import ( +from domain.epc.property_overrides.built_form_type import BuiltFormType +from domain.epc.property_overrides.construction_age_band import ConstructionAgeBand +from domain.epc.property_overrides.glazing_type import GlazingType +from domain.epc.property_overrides.main_fuel_type import MainFuelType +from domain.epc.property_overrides.main_heating_system_type import MainHeatingSystemType +from domain.epc.property_overrides.property_type import PropertyType +from domain.epc.property_overrides.roof_type import RoofType +from domain.epc.property_overrides.water_heating_type import WaterHeatingType +from domain.epc.property_overrides.wall_type import WallType +from domain.epc.property_overrides.wall_type_construction_dates import ( wall_type_construction_date_prompt_hint, ) from infrastructure.chatgpt.chatgpt import ChatGPT diff --git a/domain/epc/property_overrides/__init__.py b/domain/epc/property_overrides/__init__.py new file mode 100644 index 000000000..e11710a45 --- /dev/null +++ b/domain/epc/property_overrides/__init__.py @@ -0,0 +1,4 @@ +"""Landlord property-override classifier vocabulary — the category enums a +landlord description resolves into, plus their value→code helpers. The classifier +target for the property_overrides chain (mirrors the property_overrides table / +override_component pgEnum). Distinct from the EPC-context types of the same name.""" diff --git a/domain/epc/built_form_type.py b/domain/epc/property_overrides/built_form_type.py similarity index 100% rename from domain/epc/built_form_type.py rename to domain/epc/property_overrides/built_form_type.py diff --git a/domain/epc/construction_age_band.py b/domain/epc/property_overrides/construction_age_band.py similarity index 100% rename from domain/epc/construction_age_band.py rename to domain/epc/property_overrides/construction_age_band.py diff --git a/domain/epc/glazing_type.py b/domain/epc/property_overrides/glazing_type.py similarity index 100% rename from domain/epc/glazing_type.py rename to domain/epc/property_overrides/glazing_type.py diff --git a/domain/epc/main_fuel_type.py b/domain/epc/property_overrides/main_fuel_type.py similarity index 100% rename from domain/epc/main_fuel_type.py rename to domain/epc/property_overrides/main_fuel_type.py diff --git a/domain/epc/main_heating_system_type.py b/domain/epc/property_overrides/main_heating_system_type.py similarity index 100% rename from domain/epc/main_heating_system_type.py rename to domain/epc/property_overrides/main_heating_system_type.py diff --git a/domain/epc/override_code_mapping.py b/domain/epc/property_overrides/override_code_mapping.py similarity index 100% rename from domain/epc/override_code_mapping.py rename to domain/epc/property_overrides/override_code_mapping.py diff --git a/domain/epc/property_type.py b/domain/epc/property_overrides/property_type.py similarity index 100% rename from domain/epc/property_type.py rename to domain/epc/property_overrides/property_type.py diff --git a/domain/epc/roof_type.py b/domain/epc/property_overrides/roof_type.py similarity index 100% rename from domain/epc/roof_type.py rename to domain/epc/property_overrides/roof_type.py diff --git a/domain/epc/wall_type.py b/domain/epc/property_overrides/wall_type.py similarity index 100% rename from domain/epc/wall_type.py rename to domain/epc/property_overrides/wall_type.py diff --git a/domain/epc/wall_type_construction_dates.py b/domain/epc/property_overrides/wall_type_construction_dates.py similarity index 97% rename from domain/epc/wall_type_construction_dates.py rename to domain/epc/property_overrides/wall_type_construction_dates.py index 0eccc44cd..04deda230 100644 --- a/domain/epc/wall_type_construction_dates.py +++ b/domain/epc/property_overrides/wall_type_construction_dates.py @@ -27,7 +27,7 @@ from __future__ import annotations from dataclasses import dataclass from typing import Mapping, Optional -from domain.epc.wall_type import WallType +from domain.epc.property_overrides.wall_type import WallType @dataclass(frozen=True) diff --git a/domain/epc/water_heating_type.py b/domain/epc/property_overrides/water_heating_type.py similarity index 100% rename from domain/epc/water_heating_type.py rename to domain/epc/property_overrides/water_heating_type.py diff --git a/infrastructure/postgres/landlord_built_form_type_override_table.py b/infrastructure/postgres/landlord_built_form_type_override_table.py index ec93ba274..5f343613e 100644 --- a/infrastructure/postgres/landlord_built_form_type_override_table.py +++ b/infrastructure/postgres/landlord_built_form_type_override_table.py @@ -16,7 +16,7 @@ from sqlalchemy import BigInteger, Column, UniqueConstraint from sqlalchemy import Enum as SAEnum from sqlmodel import Field, SQLModel -from domain.epc.built_form_type import BuiltFormType +from domain.epc.property_overrides.built_form_type import BuiltFormType from infrastructure.postgres.landlord_override_enums import override_source_sa_enum diff --git a/infrastructure/postgres/landlord_construction_age_band_override_table.py b/infrastructure/postgres/landlord_construction_age_band_override_table.py index 91eec66eb..0c8f9119a 100644 --- a/infrastructure/postgres/landlord_construction_age_band_override_table.py +++ b/infrastructure/postgres/landlord_construction_age_band_override_table.py @@ -16,7 +16,7 @@ from sqlalchemy import BigInteger, Column, UniqueConstraint from sqlalchemy import Enum as SAEnum from sqlmodel import Field, SQLModel -from domain.epc.construction_age_band import ConstructionAgeBand +from domain.epc.property_overrides.construction_age_band import ConstructionAgeBand from infrastructure.postgres.landlord_override_enums import override_source_sa_enum diff --git a/infrastructure/postgres/landlord_glazing_override_table.py b/infrastructure/postgres/landlord_glazing_override_table.py index f42a48d2f..b25dea54f 100644 --- a/infrastructure/postgres/landlord_glazing_override_table.py +++ b/infrastructure/postgres/landlord_glazing_override_table.py @@ -16,7 +16,7 @@ from sqlalchemy import BigInteger, Column, UniqueConstraint from sqlalchemy import Enum as SAEnum from sqlmodel import Field, SQLModel -from domain.epc.glazing_type import GlazingType +from domain.epc.property_overrides.glazing_type import GlazingType from infrastructure.postgres.landlord_override_enums import override_source_sa_enum diff --git a/infrastructure/postgres/landlord_main_fuel_override_table.py b/infrastructure/postgres/landlord_main_fuel_override_table.py index c046453bb..3012a2e9a 100644 --- a/infrastructure/postgres/landlord_main_fuel_override_table.py +++ b/infrastructure/postgres/landlord_main_fuel_override_table.py @@ -16,7 +16,7 @@ from sqlalchemy import BigInteger, Column, UniqueConstraint from sqlalchemy import Enum as SAEnum from sqlmodel import Field, SQLModel -from domain.epc.main_fuel_type import MainFuelType +from domain.epc.property_overrides.main_fuel_type import MainFuelType from infrastructure.postgres.landlord_override_enums import override_source_sa_enum diff --git a/infrastructure/postgres/landlord_main_heating_system_override_table.py b/infrastructure/postgres/landlord_main_heating_system_override_table.py index a06c35c48..d9862089a 100644 --- a/infrastructure/postgres/landlord_main_heating_system_override_table.py +++ b/infrastructure/postgres/landlord_main_heating_system_override_table.py @@ -16,7 +16,7 @@ from sqlalchemy import BigInteger, Column, UniqueConstraint from sqlalchemy import Enum as SAEnum from sqlmodel import Field, SQLModel -from domain.epc.main_heating_system_type import MainHeatingSystemType +from domain.epc.property_overrides.main_heating_system_type import MainHeatingSystemType from infrastructure.postgres.landlord_override_enums import override_source_sa_enum diff --git a/infrastructure/postgres/landlord_property_type_override_table.py b/infrastructure/postgres/landlord_property_type_override_table.py index ae9377cd2..2718fb344 100644 --- a/infrastructure/postgres/landlord_property_type_override_table.py +++ b/infrastructure/postgres/landlord_property_type_override_table.py @@ -14,7 +14,7 @@ from sqlalchemy import BigInteger, Column, UniqueConstraint from sqlalchemy import Enum as SAEnum from sqlmodel import Field, SQLModel -from domain.epc.property_type import PropertyType +from domain.epc.property_overrides.property_type import PropertyType from infrastructure.postgres.landlord_override_enums import override_source_sa_enum diff --git a/infrastructure/postgres/landlord_roof_type_override_table.py b/infrastructure/postgres/landlord_roof_type_override_table.py index 58bd61fff..659c5c64e 100644 --- a/infrastructure/postgres/landlord_roof_type_override_table.py +++ b/infrastructure/postgres/landlord_roof_type_override_table.py @@ -16,7 +16,7 @@ from sqlalchemy import BigInteger, Column, UniqueConstraint from sqlalchemy import Enum as SAEnum from sqlmodel import Field, SQLModel -from domain.epc.roof_type import RoofType +from domain.epc.property_overrides.roof_type import RoofType from infrastructure.postgres.landlord_override_enums import override_source_sa_enum diff --git a/infrastructure/postgres/landlord_wall_type_override_table.py b/infrastructure/postgres/landlord_wall_type_override_table.py index b5097164a..7a3c70ae2 100644 --- a/infrastructure/postgres/landlord_wall_type_override_table.py +++ b/infrastructure/postgres/landlord_wall_type_override_table.py @@ -16,7 +16,7 @@ from sqlalchemy import BigInteger, Column, UniqueConstraint from sqlalchemy import Enum as SAEnum from sqlmodel import Field, SQLModel -from domain.epc.wall_type import WallType +from domain.epc.property_overrides.wall_type import WallType from infrastructure.postgres.landlord_override_enums import override_source_sa_enum diff --git a/infrastructure/postgres/landlord_water_heating_override_table.py b/infrastructure/postgres/landlord_water_heating_override_table.py index c69a41d26..039b5a62a 100644 --- a/infrastructure/postgres/landlord_water_heating_override_table.py +++ b/infrastructure/postgres/landlord_water_heating_override_table.py @@ -16,7 +16,7 @@ from sqlalchemy import BigInteger, Column, UniqueConstraint from sqlalchemy import Enum as SAEnum from sqlmodel import Field, SQLModel -from domain.epc.water_heating_type import WaterHeatingType +from domain.epc.property_overrides.water_heating_type import WaterHeatingType from infrastructure.postgres.landlord_override_enums import override_source_sa_enum diff --git a/orchestration/bulk_upload_finaliser_orchestrator.py b/orchestration/bulk_upload_finaliser_orchestrator.py index 4aa49ab85..1d707a8d7 100644 --- a/orchestration/bulk_upload_finaliser_orchestrator.py +++ b/orchestration/bulk_upload_finaliser_orchestrator.py @@ -14,10 +14,10 @@ from typing import Any, Optional from uuid import UUID -from domain.epc.built_form_type import BuiltFormType -from domain.epc.property_type import PropertyType -from domain.epc.roof_type import RoofType -from domain.epc.wall_type import WallType +from domain.epc.property_overrides.built_form_type import BuiltFormType +from domain.epc.property_overrides.property_type import PropertyType +from domain.epc.property_overrides.roof_type import RoofType +from domain.epc.property_overrides.wall_type import WallType from repositories.bulk_upload.bulk_upload_status_writer import BulkUploadStatusWriter from repositories.landlord_overrides.landlord_override_reader import ( LandlordOverrideReader, diff --git a/repositories/property/override_backed_prediction_attributes_reader.py b/repositories/property/override_backed_prediction_attributes_reader.py index 5befd3b34..1c5a4b71f 100644 --- a/repositories/property/override_backed_prediction_attributes_reader.py +++ b/repositories/property/override_backed_prediction_attributes_reader.py @@ -13,7 +13,7 @@ from __future__ import annotations from typing import Optional -from domain.epc.override_code_mapping import ( +from domain.epc.property_overrides.override_code_mapping import ( built_form_to_code, property_type_to_code, ) diff --git a/tests/domain/epc/test_construction_age_band_overlay.py b/tests/domain/epc/test_construction_age_band_overlay.py index 3408900b4..5ae822e68 100644 --- a/tests/domain/epc/test_construction_age_band_overlay.py +++ b/tests/domain/epc/test_construction_age_band_overlay.py @@ -14,7 +14,7 @@ from datatypes.epc.domain.epc_property_data import ( EpcPropertyData, SapBuildingPart, ) -from domain.epc.construction_age_band import ConstructionAgeBand +from domain.epc.property_overrides.construction_age_band import ConstructionAgeBand from domain.epc.property_overlays.construction_age_band_overlay import ( age_band_overlay_for, ) diff --git a/tests/domain/epc/test_glazing_overlay.py b/tests/domain/epc/test_glazing_overlay.py index c7ba72a8c..3182d79cf 100644 --- a/tests/domain/epc/test_glazing_overlay.py +++ b/tests/domain/epc/test_glazing_overlay.py @@ -9,7 +9,7 @@ from __future__ import annotations import pytest -from domain.epc.glazing_type import GlazingType +from domain.epc.property_overrides.glazing_type import GlazingType from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for from domain.modelling.scoring.overlay_applicator import apply_simulations from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( diff --git a/tests/domain/epc/test_main_fuel_overlay.py b/tests/domain/epc/test_main_fuel_overlay.py index 161078fb1..9a1b13355 100644 --- a/tests/domain/epc/test_main_fuel_overlay.py +++ b/tests/domain/epc/test_main_fuel_overlay.py @@ -8,7 +8,7 @@ from __future__ import annotations import pytest -from domain.epc.main_fuel_type import MainFuelType +from domain.epc.property_overrides.main_fuel_type import MainFuelType from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for from domain.modelling.scoring.overlay_applicator import apply_simulations from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index 8b901616d..8ccde2b97 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -8,7 +8,7 @@ from __future__ import annotations import pytest -from domain.epc.main_heating_system_type import MainHeatingSystemType +from domain.epc.property_overrides.main_heating_system_type import MainHeatingSystemType from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for from domain.epc.property_overlays.main_heating_system_overlay import ( main_heating_overlay_for, diff --git a/tests/domain/epc/test_override_code_mapping.py b/tests/domain/epc/test_override_code_mapping.py index b22ad0a5d..5e462e08a 100644 --- a/tests/domain/epc/test_override_code_mapping.py +++ b/tests/domain/epc/test_override_code_mapping.py @@ -12,12 +12,12 @@ from typing import Optional import pytest -from domain.epc.built_form_type import BuiltFormType -from domain.epc.override_code_mapping import ( +from domain.epc.property_overrides.built_form_type import BuiltFormType +from domain.epc.property_overrides.override_code_mapping import ( built_form_to_code, property_type_to_code, ) -from domain.epc.property_type import PropertyType +from domain.epc.property_overrides.property_type import PropertyType def test_house_maps_to_gov_code_zero() -> None: diff --git a/tests/domain/epc/test_water_heating_overlay.py b/tests/domain/epc/test_water_heating_overlay.py index 12e6e8bee..a1955aa73 100644 --- a/tests/domain/epc/test_water_heating_overlay.py +++ b/tests/domain/epc/test_water_heating_overlay.py @@ -11,7 +11,7 @@ import pytest from domain.epc.property_overlays.water_heating_overlay import ( water_heating_overlay_for, ) -from domain.epc.water_heating_type import WaterHeatingType +from domain.epc.property_overrides.water_heating_type import WaterHeatingType from domain.modelling.scoring.overlay_applicator import apply_simulations from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( build_epc, diff --git a/tests/infrastructure/chatgpt/test_chatgpt_column_classifier.py b/tests/infrastructure/chatgpt/test_chatgpt_column_classifier.py index 425d26251..0070af494 100644 --- a/tests/infrastructure/chatgpt/test_chatgpt_column_classifier.py +++ b/tests/infrastructure/chatgpt/test_chatgpt_column_classifier.py @@ -5,8 +5,8 @@ from typing import Optional import pytest from domain.data_transformation.column_classifier import ClassificationError -from domain.epc.property_type import PropertyType -from domain.epc.wall_type import WallType +from domain.epc.property_overrides.property_type import PropertyType +from domain.epc.property_overrides.wall_type import WallType from infrastructure.chatgpt.chatgpt import ChatGPT from infrastructure.chatgpt.chatgpt_column_classifier import ( ChatGptColumnClassifier, diff --git a/tests/orchestration/test_landlord_description_overrides_orchestrator.py b/tests/orchestration/test_landlord_description_overrides_orchestrator.py index bf5b13ce9..2339c615b 100644 --- a/tests/orchestration/test_landlord_description_overrides_orchestrator.py +++ b/tests/orchestration/test_landlord_description_overrides_orchestrator.py @@ -4,9 +4,9 @@ from enum import Enum from typing import Any, Optional from domain.addresses.unstandardised_address import AddressList, UnstandardisedAddress -from domain.epc.built_form_type import BuiltFormType -from domain.epc.property_type import PropertyType -from domain.epc.wall_type import WallType +from domain.epc.property_overrides.built_form_type import BuiltFormType +from domain.epc.property_overrides.property_type import PropertyType +from domain.epc.property_overrides.wall_type import WallType from domain.postcode import Postcode from domain.data_transformation.column_classifier import ColumnClassifier from orchestration.classifiable_column import ClassifiableColumn diff --git a/tests/repositories/landlord_overrides/postgres/test_landlord_overrides_postgres_repository.py b/tests/repositories/landlord_overrides/postgres/test_landlord_overrides_postgres_repository.py index 1ce4e9972..31ca2b175 100644 --- a/tests/repositories/landlord_overrides/postgres/test_landlord_overrides_postgres_repository.py +++ b/tests/repositories/landlord_overrides/postgres/test_landlord_overrides_postgres_repository.py @@ -25,8 +25,8 @@ import pytest from sqlalchemy import Engine, Table from sqlmodel import Session, SQLModel, select -from domain.epc.property_type import PropertyType -from domain.epc.wall_type import WallType +from domain.epc.property_overrides.property_type import PropertyType +from domain.epc.property_overrides.wall_type import WallType from infrastructure.landlord_overrides.landlord_overrides_postgres_repository import ( LandlordOverridesRepository, ) From f7ee47118b6a99be043de0d19a303d3b727753ce Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 18:18:12 +0000 Subject: [PATCH 075/340] =?UTF-8?q?Add=20the=205=20new=20components=20to?= =?UTF-8?q?=20the=20override=5Fcomponent=20pgEnum=20mirror=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caught live writing property_overrides on portfolio 796: the Python override_component SAEnum lagged the DB enum, so reading a new-component row back threw LookupError. Guard it with a consistency test. Co-Authored-By: Claude Opus 4.8 (1M context) --- infrastructure/postgres/property_override_table.py | 5 +++++ .../property/test_override_component_consistency.py | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/infrastructure/postgres/property_override_table.py b/infrastructure/postgres/property_override_table.py index 3132ddf58..404901443 100644 --- a/infrastructure/postgres/property_override_table.py +++ b/infrastructure/postgres/property_override_table.py @@ -27,6 +27,11 @@ override_component_sa_enum = SAEnum( "roof_type", "property_type", "built_form_type", + "main_fuel", + "glazing", + "construction_age_band", + "water_heating", + "main_heating_system", name="override_component", ) diff --git a/tests/repositories/property/test_override_component_consistency.py b/tests/repositories/property/test_override_component_consistency.py index df830cd02..3f7f4d31d 100644 --- a/tests/repositories/property/test_override_component_consistency.py +++ b/tests/repositories/property/test_override_component_consistency.py @@ -14,6 +14,7 @@ from __future__ import annotations from infrastructure.landlord_overrides.landlord_override_reader_postgres_repository import ( # pyright: ignore[reportPrivateUsage] _ROW_TYPES, ) +from infrastructure.postgres.property_override_table import override_component_sa_enum from repositories.property.landlord_override_overlays import ( # pyright: ignore[reportPrivateUsage] _COMPONENT_OVERLAYS, ) @@ -22,3 +23,10 @@ from repositories.property.landlord_override_overlays import ( # pyright: ignor def test_reader_and_overlay_registries_cover_the_same_components() -> None: # Assert assert set(_ROW_TYPES) == set(_COMPONENT_OVERLAYS) + + +def test_override_component_pgenum_covers_every_component() -> None: + # The property_overrides.override_component pgEnum mirror must list every + # component, or writing/reading a new-component row through it throws a + # LookupError against Postgres (caught live on the Hyde portfolio-796 run). + assert set(override_component_sa_enum.enums) == set(_COMPONENT_OVERLAYS) From e03fd27357578f941cfef30422a5c388d113a8e6 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 18:19:07 +0000 Subject: [PATCH 076/340] =?UTF-8?q?Type-clean=20the=20override=5Fcomponent?= =?UTF-8?q?=20consistency=20guard=20=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/hyde/build_property_overrides.py | 437 ++++++++++++++++++ .../test_override_component_consistency.py | 13 +- .../test_build_property_overrides_smoke.py | 88 ++++ 3 files changed, 533 insertions(+), 5 deletions(-) create mode 100644 scripts/hyde/build_property_overrides.py create mode 100644 tests/scripts/test_build_property_overrides_smoke.py diff --git a/scripts/hyde/build_property_overrides.py b/scripts/hyde/build_property_overrides.py new file mode 100644 index 000000000..05d70d2dd --- /dev/null +++ b/scripts/hyde/build_property_overrides.py @@ -0,0 +1,437 @@ +"""Build ``property_overrides`` for a portfolio from the Hyde Excel, bypassing the +frontend + lambdas, using the ``landlord_*_overrides`` tables as the durable +classification ledger. + +Why the ledger (not a throwaway cache): ``landlord_*_overrides`` stores +``(portfolio_id, description) -> value`` with a ``source`` (classifier|user). + * Re-runs classify only descriptions NOT already stored -> saves ChatGPT calls. + * Human corrections are stored as ``source=user`` and the classifier is + forbidden from overwriting them (ADR-0003) -> edits are permanent. +Then we resolve the vocab + match each row to a ``property.id`` by **org_ref** +(Excel "Organisation Reference" -> property.landlord_property_id) and upsert +``property_overrides`` (the fact layer the SAP overlay reads). + +Subcommands: + list-values print each component's valid override values (reference) + classify --excel f --portfolio-id 795 + PASS 1: classify cache-misses via ChatGPT, + upsert to landlord tables, write + overrides_unknowns.csv (with allowed_values) + validate --edits overrides_edits.csv + check a hand-edited file: every corrected_value + must be a valid enum value (suggests fixes) + apply-edits --edits overrides_edits.csv --portfolio-id 795 [--apply] + upsert validated corrections as source=user + write --excel f --portfolio-id 795 [--apply] + PASS 2: build + upsert property_overrides from vocab + +Env: POSTGRES_* (PostgresConfig.from_env) and OPENAI_API_KEY (ChatGPT). +""" + +from __future__ import annotations + +import argparse +import csv +import difflib +import logging +import os +from collections import Counter +from dataclasses import dataclass +from datetime import datetime, timezone +from enum import Enum +from typing import Any, Optional + +import pandas as pd +from sqlalchemy import Table, text +from sqlalchemy.dialects.postgresql import insert as pg_insert +from sqlmodel import SQLModel + +from domain.epc.property_overrides.built_form_type import BuiltFormType +from domain.epc.property_overrides.construction_age_band import ConstructionAgeBand +from domain.epc.property_overrides.glazing_type import GlazingType +from domain.epc.property_overrides.main_fuel_type import MainFuelType +from domain.epc.property_overrides.main_heating_system_type import MainHeatingSystemType +from domain.epc.property_overrides.property_type import PropertyType +from domain.epc.property_overrides.roof_type import RoofType +from domain.epc.property_overrides.wall_type import WallType +from domain.epc.property_overrides.wall_type_construction_dates import ( + wall_type_construction_date_prompt_hint, +) +from domain.epc.property_overrides.water_heating_type import WaterHeatingType +from infrastructure.chatgpt.chatgpt import ChatGPT +from infrastructure.chatgpt.chatgpt_column_classifier import ChatGptColumnClassifier +from infrastructure.landlord_overrides.landlord_override_reader_postgres_repository import ( + LandlordOverrideReaderPostgresRepository, +) +from infrastructure.landlord_overrides.landlord_overrides_postgres_repository import ( + LandlordOverridesRepository, +) +from infrastructure.postgres.config import PostgresConfig +from infrastructure.postgres.engine import commit_scope, make_engine, make_session +from infrastructure.postgres.landlord_built_form_type_override_table import ( + LandlordBuiltFormTypeOverrideRow, +) +from infrastructure.postgres.landlord_construction_age_band_override_table import ( + LandlordConstructionAgeBandOverrideRow, +) +from infrastructure.postgres.landlord_glazing_override_table import ( + LandlordGlazingOverrideRow, +) +from infrastructure.postgres.landlord_main_fuel_override_table import ( + LandlordMainFuelOverrideRow, +) +from infrastructure.postgres.landlord_main_heating_system_override_table import ( + LandlordMainHeatingSystemOverrideRow, +) +from infrastructure.postgres.landlord_override_enums import OverrideSource +from infrastructure.postgres.landlord_property_type_override_table import ( + LandlordPropertyTypeOverrideRow, +) +from infrastructure.postgres.landlord_roof_type_override_table import ( + LandlordRoofTypeOverrideRow, +) +from infrastructure.postgres.landlord_wall_type_override_table import ( + LandlordWallTypeOverrideRow, +) +from infrastructure.postgres.landlord_water_heating_override_table import ( + LandlordWaterHeatingOverrideRow, +) +from repositories.property.landlord_override_overlays import overlays_from +from repositories.property.property_override_postgres_repository import ( + PropertyOverridePostgresRepository, +) +from repositories.property.property_override_repository import PropertyOverrideInsert +from repositories.property.property_overrides_postgres_reader import ( + PropertyOverridesPostgresReader, +) + +logging.basicConfig(level=logging.INFO, format="%(message)s") +logger = logging.getLogger("build_property_overrides") + +ORG_REF_COLUMN = "Organisation Reference" +UNKNOWNS_PATH = "overrides_unknowns.csv" + + +@dataclass(frozen=True) +class ComponentSpec: + component: str + enum_cls: type[Enum] + unknown: Enum + row_type: type[SQLModel] + excel_header: str + per_building_part: bool # comma = building parts (wall/roof/age) vs whole-dwelling + extra_instructions: Optional[str] = None + + def allowed_values(self) -> list[str]: + """Valid override values a human may pick (excludes UNKNOWN).""" + return sorted(m.value for m in self.enum_cls if m is not self.unknown) + + +def _component_specs() -> list[ComponentSpec]: + return [ + ComponentSpec("property_type", PropertyType, PropertyType.UNKNOWN, LandlordPropertyTypeOverrideRow, "Property Type", False), + ComponentSpec("built_form_type", BuiltFormType, BuiltFormType.UNKNOWN, LandlordBuiltFormTypeOverrideRow, "Property Type", False), + ComponentSpec("wall_type", WallType, WallType.UNKNOWN, LandlordWallTypeOverrideRow, "Walls", True, wall_type_construction_date_prompt_hint()), + ComponentSpec("roof_type", RoofType, RoofType.UNKNOWN, LandlordRoofTypeOverrideRow, "Roofs", True), + ComponentSpec("construction_age_band", ConstructionAgeBand, ConstructionAgeBand.UNKNOWN, LandlordConstructionAgeBandOverrideRow, "Age", True), + ComponentSpec("main_fuel", MainFuelType, MainFuelType.UNKNOWN, LandlordMainFuelOverrideRow, "Main Fuel", False), + ComponentSpec("glazing", GlazingType, GlazingType.UNKNOWN, LandlordGlazingOverrideRow, "Glazing", False), + ComponentSpec("water_heating", WaterHeatingType, WaterHeatingType.UNKNOWN, LandlordWaterHeatingOverrideRow, "Hot Water", False), + ComponentSpec("main_heating_system", MainHeatingSystemType, MainHeatingSystemType.UNKNOWN, LandlordMainHeatingSystemOverrideRow, "Heating", False), + ] + + +def _specs_by_component() -> dict[str, ComponentSpec]: + return {s.component: s for s in _component_specs()} + + +def _norm(s: Any) -> str: + """Vocab key normalisation — mirrors the orchestrator (strip + lower).""" + return str(s or "").strip().lower() + + +def _split_entries(cell: Any, per_building_part: bool) -> list[str]: + raw = "" if cell is None else str(cell) + if not raw.strip(): + return [] + if not per_building_part: + return [raw.strip()] + return [part.strip() for part in raw.split(",") if part.strip()] + + +def _load_rows(excel: str, sheet: str) -> list[dict[str, Any]]: + return pd.read_excel(excel, sheet_name=sheet).to_dict(orient="records") # type: ignore[return-value] + + +def _filter_rows(rows: list[dict[str, Any]], org_ref: Optional[str], + limit: Optional[int]) -> list[dict[str, Any]]: + """Narrow to one property (--org-ref) or the first N rows (--limit) for a + cheap smoke test before the full run.""" + if org_ref: + rows = [r for r in rows if str(r.get(ORG_REF_COLUMN, "")).strip() == org_ref] + if limit: + rows = rows[:limit] + return rows + + +def _distinct_entries(rows: list[dict[str, Any]], spec: ComponentSpec) -> Counter[str]: + counts: Counter[str] = Counter() + for row in rows: + for entry in _split_entries(row.get(spec.excel_header), spec.per_building_part): + counts[entry] += 1 + return counts + + +# --------------------------------------------------------------------------- # +def list_values(_: argparse.Namespace) -> None: + """Print the valid override values per component (the reference for edits).""" + for spec in _component_specs(): + print(f"\n## {spec.component} (Excel: {spec.excel_header})") + for v in spec.allowed_values(): + print(f" {v}") + + +def validate(args: argparse.Namespace) -> None: + """Check a hand-edited CSV: every corrected_value must be a valid enum value.""" + specs = _specs_by_component() + bad = 0 + with open(args.edits, newline="") as f: + for i, r in enumerate(csv.DictReader(f), start=2): + val = (r.get("corrected_value") or "").strip() + if not val: + continue + comp = (r.get("component") or "").strip() + spec = specs.get(comp) + if spec is None: + logger.error("row %d: unknown component %r", i, comp) + bad += 1 + continue + if val not in spec.allowed_values(): + hint = difflib.get_close_matches(val, spec.allowed_values(), n=2) + logger.error("row %d [%s]: %r is not a valid value.%s", + i, comp, val, + f" Did you mean: {hint}?" if hint else + " Run 'list-values' for the allowed set.") + bad += 1 + if bad: + raise SystemExit(f"{bad} invalid corrected_value(s) — fix them before apply-edits.") + logger.info("All corrected values are valid enum values. ✓") + + +def _db_session() -> Any: + return make_session(make_engine(PostgresConfig.from_env(os.environ))) + + +def classify(args: argparse.Namespace) -> None: + rows = _filter_rows(_load_rows(args.excel, args.sheet), args.org_ref, args.limit) + logger.info("Classifying over %d row(s).", len(rows)) + chat_gpt = ChatGPT() + session = _db_session() + reader = LandlordOverrideReaderPostgresRepository(session) + try: + vocab = reader.load_for_portfolio(args.portfolio_id) # {component: {desc: value}} + unknown_rows: list[tuple[str, str, int, str]] = [] + + for spec in _component_specs(): + counts = _distinct_entries(rows, spec) + known = vocab.get(spec.component, {}) # already-classified (cache) + to_classify = {d for d in counts if _norm(d) not in known} + logger.info("%-22s %4d distinct | %4d cached | %4d to classify", + spec.component, len(counts), len(counts) - len(to_classify), len(to_classify)) + + resolved: dict[str, Enum] = {} + if to_classify: + classifier: ChatGptColumnClassifier[Any] = ChatGptColumnClassifier( + chat_gpt, spec.enum_cls, spec.unknown, extra_instructions=spec.extra_instructions) + resolved = classifier.classify(to_classify) + repo: LandlordOverridesRepository[Any] = LandlordOverridesRepository(session, spec.row_type) + with commit_scope(session): + # store keyed on the normalised description (matches the reader/finaliser lookup) + repo.upsert_all(args.portfolio_id, {_norm(d): m for d, m in resolved.items()}) + + # collect UNKNOWNs (freshly classified + anything cached as UNKNOWN) for review + unk = spec.unknown.value + for desc, n in counts.items(): + v = resolved.get(desc).value if desc in resolved and resolved[desc] else known.get(_norm(desc)) # type: ignore[union-attr] + if v is None or v == unk: + allowed = " | ".join(spec.allowed_values()) + unknown_rows.append((spec.component, desc, n, allowed)) + + with open(UNKNOWNS_PATH, "w", newline="") as f: + w = csv.writer(f) + w.writerow(["component", "description", "count", "corrected_value", "allowed_values"]) + for comp, desc, n, allowed in sorted(unknown_rows, key=lambda r: (-r[2])): + w.writerow([comp, desc, n, "", allowed]) + logger.info("\nWrote %s — fill 'corrected_value' (must match 'allowed_values'), " + "then: validate -> apply-edits -> write.", UNKNOWNS_PATH) + finally: + session.close() + + +def _upsert_user_corrections(session: Any, portfolio_id: int, + by_component: dict[str, dict[str, str]]) -> int: + """Upsert validated human corrections as source=user (always wins on conflict).""" + specs = _specs_by_component() + n = 0 + now = datetime.now(timezone.utc) + for comp, mapping in by_component.items(): + spec = specs[comp] + table: Table = getattr(spec.row_type, "__table__") + rows = [{"portfolio_id": portfolio_id, "description": _norm(d), "value": v, + "source": OverrideSource.USER, "created_at": now, "updated_at": now} + for d, v in mapping.items()] + if not rows: + continue + stmt = pg_insert(table).values(rows) + stmt = stmt.on_conflict_do_update( + index_elements=["portfolio_id", "description"], + set_={"value": stmt.excluded.value, "source": stmt.excluded.source, + "updated_at": stmt.excluded.updated_at}) + session.execute(stmt) + n += len(rows) + return n + + +def apply_edits(args: argparse.Namespace) -> None: + validate(args) # fail before touching the DB + specs = _specs_by_component() + by_component: dict[str, dict[str, str]] = {} + with open(args.edits, newline="") as f: + for r in csv.DictReader(f): + val = (r.get("corrected_value") or "").strip() + if val and r["component"] in specs: + by_component.setdefault(r["component"], {})[r["description"]] = val + session = _db_session() + try: + if not args.apply: + total = sum(len(m) for m in by_component.values()) + logger.info("DRY RUN — %d user corrections ready. Re-run with --apply.", total) + return + with commit_scope(session): + n = _upsert_user_corrections(session, args.portfolio_id, by_component) + logger.info("Upserted %d user corrections (source=user).", n) + finally: + session.close() + + +def _org_ref_to_property_id(session: Any, portfolio_id: int) -> dict[str, int]: + stmt = text("SELECT landlord_property_id, id FROM property " + "WHERE portfolio_id = :pid AND landlord_property_id IS NOT NULL") + return {str(ref).strip(): int(pid) for ref, pid in session.execute(stmt, {"pid": portfolio_id})} + + +def write(args: argparse.Namespace) -> None: + rows = _filter_rows(_load_rows(args.excel, args.sheet), args.org_ref, args.limit) + logger.info("Writing over %d row(s).", len(rows)) + session = _db_session() + reader = LandlordOverrideReaderPostgresRepository(session) + try: + vocab = reader.load_for_portfolio(args.portfolio_id) + org_ref_map = _org_ref_to_property_id(session, args.portfolio_id) + logger.info("Portfolio %d: %d properties with org_ref.", args.portfolio_id, len(org_ref_map)) + + inserts: list[PropertyOverrideInsert] = [] + unmatched: Counter[str] = Counter() + unresolved: Counter[str] = Counter() + for row in rows: + org_ref = str(row.get(ORG_REF_COLUMN, "")).strip() + property_id = org_ref_map.get(org_ref) + if property_id is None: + unmatched[org_ref] += 1 + continue + for spec in _component_specs(): + comp_vocab = vocab.get(spec.component, {}) + for building_part, entry in enumerate( + _split_entries(row.get(spec.excel_header), spec.per_building_part)): + value = comp_vocab.get(_norm(entry)) + if not value or value == spec.unknown.value: + unresolved[f"{spec.component}: {entry}"] += 1 + continue + inserts.append(PropertyOverrideInsert( + property_id=property_id, portfolio_id=args.portfolio_id, + building_part=building_part, override_component=spec.component, + override_value=value, original_spreadsheet_description=entry)) + + logger.info("Built %d rows | %d unmatched org_refs | %d unresolved", + len(inserts), sum(unmatched.values()), sum(unresolved.values())) + if unresolved: + logger.info("Top unresolved (need apply-edits): %s", unresolved.most_common(10)) + if not args.apply: + logger.info("DRY RUN — not writing. Re-run with --apply.") + for ins in inserts[:10]: + logger.info(" %s", ins) + return + with commit_scope(session): + affected = PropertyOverridePostgresRepository(session).upsert_all(inserts) + logger.info("Upserted %d property_overrides.", affected) + finally: + session.close() + + +def verify(args: argparse.Namespace) -> None: + """For one property (by org_ref): show the persisted property_overrides rows + and the EpcSimulation overlays they produce — the end-to-end proof that the + chain reaches the SAP overlay surface.""" + session = _db_session() + try: + org_ref_map = _org_ref_to_property_id(session, args.portfolio_id) + property_id = org_ref_map.get(args.org_ref) + if property_id is None: + raise SystemExit(f"org_ref {args.org_ref!r} not found in portfolio {args.portfolio_id}.") + reader = PropertyOverridesPostgresReader(lambda: session) + resolved = reader.overrides_for(property_id) + logger.info("property_id %d — %d property_overrides rows:", property_id, len(resolved.rows)) + for r in resolved.rows: + logger.info(" part %d | %-22s = %s", r.building_part, r.override_component, r.override_value) + overlays = overlays_from(resolved) + logger.info("\n-> %d EpcSimulation overlay(s) produced (what the SAP calc applies):", len(overlays)) + for o in overlays: + logger.info(" %s", o) + finally: + session.close() + + +def main() -> None: + p = argparse.ArgumentParser(description=__doc__) + sub = p.add_subparsers(dest="cmd", required=True) + + sub.add_parser("list-values").set_defaults(func=list_values) + + v = sub.add_parser("validate") + v.add_argument("--edits", required=True) + v.set_defaults(func=validate) + + c = sub.add_parser("classify") + c.add_argument("--excel", required=True) + c.add_argument("--sheet", default="AddressProfilingResults") + c.add_argument("--portfolio-id", type=int, required=True) + c.add_argument("--org-ref", default=None, help="smoke test: only this property's org_ref") + c.add_argument("--limit", type=int, default=None, help="smoke test: first N rows") + c.set_defaults(func=classify) + + a = sub.add_parser("apply-edits") + a.add_argument("--edits", required=True) + a.add_argument("--portfolio-id", type=int, required=True) + a.add_argument("--apply", action="store_true") + a.set_defaults(func=apply_edits) + + w = sub.add_parser("write") + w.add_argument("--excel", required=True) + w.add_argument("--sheet", default="AddressProfilingResults") + w.add_argument("--portfolio-id", type=int, required=True) + w.add_argument("--org-ref", default=None, help="smoke test: only this property's org_ref") + w.add_argument("--limit", type=int, default=None, help="smoke test: first N rows") + w.add_argument("--apply", action="store_true") + w.set_defaults(func=write) + + vf = sub.add_parser("verify") + vf.add_argument("--portfolio-id", type=int, required=True) + vf.add_argument("--org-ref", required=True) + vf.set_defaults(func=verify) + + args = p.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/tests/repositories/property/test_override_component_consistency.py b/tests/repositories/property/test_override_component_consistency.py index 3f7f4d31d..64957a2b2 100644 --- a/tests/repositories/property/test_override_component_consistency.py +++ b/tests/repositories/property/test_override_component_consistency.py @@ -11,12 +11,14 @@ main_fuel / glazing / construction_age_band reader entries). from __future__ import annotations -from infrastructure.landlord_overrides.landlord_override_reader_postgres_repository import ( # pyright: ignore[reportPrivateUsage] - _ROW_TYPES, +from typing import cast + +from infrastructure.landlord_overrides.landlord_override_reader_postgres_repository import ( + _ROW_TYPES, # pyright: ignore[reportPrivateUsage] ) from infrastructure.postgres.property_override_table import override_component_sa_enum -from repositories.property.landlord_override_overlays import ( # pyright: ignore[reportPrivateUsage] - _COMPONENT_OVERLAYS, +from repositories.property.landlord_override_overlays import ( + _COMPONENT_OVERLAYS, # pyright: ignore[reportPrivateUsage] ) @@ -29,4 +31,5 @@ def test_override_component_pgenum_covers_every_component() -> None: # The property_overrides.override_component pgEnum mirror must list every # component, or writing/reading a new-component row through it throws a # LookupError against Postgres (caught live on the Hyde portfolio-796 run). - assert set(override_component_sa_enum.enums) == set(_COMPONENT_OVERLAYS) + pgenum_values = cast(list[str], override_component_sa_enum.enums) + assert set(pgenum_values) == set(_COMPONENT_OVERLAYS) diff --git a/tests/scripts/test_build_property_overrides_smoke.py b/tests/scripts/test_build_property_overrides_smoke.py new file mode 100644 index 000000000..77a458aad --- /dev/null +++ b/tests/scripts/test_build_property_overrides_smoke.py @@ -0,0 +1,88 @@ +"""End-to-end smoke of the Hyde override script for ONE property, against a real +(ephemeral) Postgres. Seeds the landlord vocab (simulating post-classify, so no +ChatGPT) + a minimal ``property`` row, then runs the script's real +``write`` + ``verify`` paths and asserts property_overrides + overlays land. +""" + +from __future__ import annotations + +import argparse +from typing import Any + +from sqlalchemy import Engine, text +from sqlmodel import Session + +import scripts.hyde.build_property_overrides as b +from domain.epc.property_overrides.built_form_type import BuiltFormType +from domain.epc.property_overrides.construction_age_band import ConstructionAgeBand +from domain.epc.property_overrides.glazing_type import GlazingType +from domain.epc.property_overrides.main_fuel_type import MainFuelType +from domain.epc.property_overrides.main_heating_system_type import MainHeatingSystemType +from domain.epc.property_overrides.property_type import PropertyType +from domain.epc.property_overrides.roof_type import RoofType +from domain.epc.property_overrides.wall_type import WallType +from domain.epc.property_overrides.water_heating_type import WaterHeatingType +from infrastructure.landlord_overrides.landlord_overrides_postgres_repository import ( + LandlordOverridesRepository, +) + +PORTFOLIO = 795 +ORG_REF = "55180004001" +EXCEL = "scripts/hyde/hyde_property_overrides.xlsx" + +# What ChatGPT WOULD resolve this property's 9 descriptions to (component -> +# (raw Excel entry, enum member)). Seeded into the landlord ledger. +SEED = { + "property_type": ("House: MidTerrace", PropertyType.HOUSE), + "built_form_type": ("House: MidTerrace", BuiltFormType.MID_TERRACE), + "wall_type": ("TimberFrame: AsBuilt", WallType.TIMBER_FRAME_AS_BUILT_NO_INSULATION_ASSUMED), + "roof_type": ("PitchedNormalLoftAccess: 300mm", RoofType.PITCHED_300_MM), + "construction_age_band": ("L: 2012-2022", ConstructionAgeBand.L_2012_2022), + "main_fuel": ("Gas: Mains Gas", MainFuelType.MAINS_GAS), + "glazing": ("100% Double glazing 2002 or later", GlazingType.DOUBLE_POST_2002), + "water_heating": ("From main heating system: Mains Gas", WaterHeatingType.FROM_MAIN_MAINS_GAS), + "main_heating_system": ("Boiler: C rated Combi", MainHeatingSystemType.GAS_COMBI), +} + + +def test_one_property_end_to_end(db_engine: Engine, monkeypatch: Any, capsys: Any) -> None: + specs = b._specs_by_component() + + # minimal FE-owned `property` table + the one row we'll match by org_ref + with Session(db_engine) as s: + s.execute(text( + "CREATE TABLE property (id bigint PRIMARY KEY, portfolio_id bigint, " + "landlord_property_id text)")) + s.execute(text("INSERT INTO property VALUES (1, :p, :ref)"), + {"p": PORTFOLIO, "ref": ORG_REF}) + # seed the classifier ledger (keyed on normalised description) + for comp, (raw, member) in SEED.items(): + LandlordOverridesRepository(s, specs[comp].row_type).upsert_all( + PORTFOLIO, {b._norm(raw): member}) + s.commit() + + # point the script at the ephemeral engine + monkeypatch.setattr(b, "_db_session", lambda: Session(db_engine)) + + # --- run the real write() for this one property --- + b.write(argparse.Namespace(excel=EXCEL, sheet="AddressProfilingResults", + portfolio_id=PORTFOLIO, org_ref=ORG_REF, limit=None, apply=True)) + + with Session(db_engine) as s: + rows = list(s.execute(text( + "SELECT override_component, building_part, override_value " + "FROM property_overrides WHERE property_id = 1 ORDER BY override_component"))) + got = {c: v for c, _, v in rows} + # every seeded component produced a property_overrides row with the resolved value + assert got["main_fuel"] == "mains gas" + assert got["glazing"] == "Double glazing, 2002 or later" + assert got["construction_age_band"] == "L" + assert got["main_heating_system"] == "Gas boiler, combi" + assert got["water_heating"] == "From main system, mains gas" + assert len(rows) == 9 # all 9 components + + # --- run the real verify() and confirm overlays are produced --- + b.verify(argparse.Namespace(portfolio_id=PORTFOLIO, org_ref=ORG_REF)) + out = capsys.readouterr().out + print(out) # surfaced with -s so we can eyeball the one-property result + assert "EpcSimulation overlay" in out From 85e43b373736e04a12e98db01363cb450efd9b08 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 18:19:32 +0000 Subject: [PATCH 077/340] =?UTF-8?q?Silence=20untyped=20SAEnum.enums=20acce?= =?UTF-8?q?ss=20in=20the=20consistency=20guard=20=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../property/test_override_component_consistency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/repositories/property/test_override_component_consistency.py b/tests/repositories/property/test_override_component_consistency.py index 64957a2b2..d3801b0da 100644 --- a/tests/repositories/property/test_override_component_consistency.py +++ b/tests/repositories/property/test_override_component_consistency.py @@ -31,5 +31,5 @@ def test_override_component_pgenum_covers_every_component() -> None: # The property_overrides.override_component pgEnum mirror must list every # component, or writing/reading a new-component row through it throws a # LookupError against Postgres (caught live on the Hyde portfolio-796 run). - pgenum_values = cast(list[str], override_component_sa_enum.enums) + pgenum_values = cast(list[str], getattr(override_component_sa_enum, "enums")) assert set(pgenum_values) == set(_COMPONENT_OVERLAYS) From c1ab179d5109f8f3fd086c86bdeff4ce545661c9 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 19:02:42 +0000 Subject: [PATCH 078/340] =?UTF-8?q?Add=20Hyde=20property=5Foverrides=20bui?= =?UTF-8?q?ld=20script=20+=20creds-free=20smoke=20test=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two-pass org_ref-matched builder for property_overrides (classify via ChatGPT into the landlord ledger, validate+apply user edits, write idempotently); ephemeral-Postgres smoke proves the one-property chain without creds. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 5 +++ scripts/hyde/build_property_overrides.py | 2 +- .../test_build_property_overrides_smoke.py | 33 +++++++++++-------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 9c77b3117..0bf9e0e90 100644 --- a/.gitignore +++ b/.gitignore @@ -313,3 +313,8 @@ scripts/eon/epc_cache.pkl scripts/hyde/.elmhurst-session/ scripts/hyde/elmhurst_downloads/ scripts/hyde/.elmhurst-creds.json + +# Hyde property-overrides script artifacts +overrides_cache.json +overrides_unknowns.csv +overrides_edits.csv diff --git a/scripts/hyde/build_property_overrides.py b/scripts/hyde/build_property_overrides.py index 05d70d2dd..dc77b520f 100644 --- a/scripts/hyde/build_property_overrides.py +++ b/scripts/hyde/build_property_overrides.py @@ -41,7 +41,7 @@ from datetime import datetime, timezone from enum import Enum from typing import Any, Optional -import pandas as pd +import pandas as pd # pyright: ignore[reportMissingTypeStubs] from sqlalchemy import Table, text from sqlalchemy.dialects.postgresql import insert as pg_insert from sqlmodel import SQLModel diff --git a/tests/scripts/test_build_property_overrides_smoke.py b/tests/scripts/test_build_property_overrides_smoke.py index 77a458aad..59db45e54 100644 --- a/tests/scripts/test_build_property_overrides_smoke.py +++ b/tests/scripts/test_build_property_overrides_smoke.py @@ -25,6 +25,10 @@ from domain.epc.property_overrides.water_heating_type import WaterHeatingType from infrastructure.landlord_overrides.landlord_overrides_postgres_repository import ( LandlordOverridesRepository, ) +from repositories.property.landlord_override_overlays import overlays_from +from repositories.property.property_overrides_postgres_reader import ( + PropertyOverridesPostgresReader, +) PORTFOLIO = 795 ORG_REF = "55180004001" @@ -36,7 +40,7 @@ SEED = { "property_type": ("House: MidTerrace", PropertyType.HOUSE), "built_form_type": ("House: MidTerrace", BuiltFormType.MID_TERRACE), "wall_type": ("TimberFrame: AsBuilt", WallType.TIMBER_FRAME_AS_BUILT_NO_INSULATION_ASSUMED), - "roof_type": ("PitchedNormalLoftAccess: 300mm", RoofType.PITCHED_300_MM), + "roof_type": ("PitchedNormalLoftAccess: 300mm", RoofType.PITCHED_LOFT_300MM), "construction_age_band": ("L: 2012-2022", ConstructionAgeBand.L_2012_2022), "main_fuel": ("Gas: Mains Gas", MainFuelType.MAINS_GAS), "glazing": ("100% Double glazing 2002 or later", GlazingType.DOUBLE_POST_2002), @@ -45,20 +49,21 @@ SEED = { } -def test_one_property_end_to_end(db_engine: Engine, monkeypatch: Any, capsys: Any) -> None: - specs = b._specs_by_component() +def test_one_property_end_to_end(db_engine: Engine, monkeypatch: Any) -> None: + specs = b._specs_by_component() # pyright: ignore[reportPrivateUsage] # minimal FE-owned `property` table + the one row we'll match by org_ref with Session(db_engine) as s: - s.execute(text( + s.execute(text( # pyright: ignore[reportDeprecated] "CREATE TABLE property (id bigint PRIMARY KEY, portfolio_id bigint, " "landlord_property_id text)")) - s.execute(text("INSERT INTO property VALUES (1, :p, :ref)"), + s.execute(text("INSERT INTO property VALUES (1, :p, :ref)"), # pyright: ignore[reportDeprecated] {"p": PORTFOLIO, "ref": ORG_REF}) # seed the classifier ledger (keyed on normalised description) for comp, (raw, member) in SEED.items(): - LandlordOverridesRepository(s, specs[comp].row_type).upsert_all( - PORTFOLIO, {b._norm(raw): member}) + repo: LandlordOverridesRepository[Any] = LandlordOverridesRepository( + s, specs[comp].row_type) + repo.upsert_all(PORTFOLIO, {b._norm(raw): member}) # pyright: ignore[reportPrivateUsage] s.commit() # point the script at the ephemeral engine @@ -69,7 +74,7 @@ def test_one_property_end_to_end(db_engine: Engine, monkeypatch: Any, capsys: An portfolio_id=PORTFOLIO, org_ref=ORG_REF, limit=None, apply=True)) with Session(db_engine) as s: - rows = list(s.execute(text( + rows = list(s.execute(text( # pyright: ignore[reportDeprecated] "SELECT override_component, building_part, override_value " "FROM property_overrides WHERE property_id = 1 ORDER BY override_component"))) got = {c: v for c, _, v in rows} @@ -81,8 +86,10 @@ def test_one_property_end_to_end(db_engine: Engine, monkeypatch: Any, capsys: An assert got["water_heating"] == "From main system, mains gas" assert len(rows) == 9 # all 9 components - # --- run the real verify() and confirm overlays are produced --- - b.verify(argparse.Namespace(portfolio_id=PORTFOLIO, org_ref=ORG_REF)) - out = capsys.readouterr().out - print(out) # surfaced with -s so we can eyeball the one-property result - assert "EpcSimulation overlay" in out + # --- the overrides reach the SAP overlay surface --- + b.verify(argparse.Namespace(portfolio_id=PORTFOLIO, org_ref=ORG_REF)) # exercises verify() + overlays = overlays_from( + PropertyOverridesPostgresReader(lambda: Session(db_engine)).overrides_for(1)) + assert len(overlays) == 9 + assert any(o.heating is not None and o.heating.main_fuel_type == 26 for o in overlays) + assert any(o.glazing is not None and o.glazing.glazing_type == 2 for o in overlays) From 36b8b7a9390f0b993a9fb0877f139cfe20d4ec98 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 19:57:34 +0000 Subject: [PATCH 079/340] =?UTF-8?q?Add=20Hyde=20portfolio-796=20unknowns?= =?UTF-8?q?=20review=20doc=20for=20Khalim=20sign-off=20=F0=9F=93=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/hyde/unknowns_review.md | 53 +++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 scripts/hyde/unknowns_review.md diff --git a/scripts/hyde/unknowns_review.md b/scripts/hyde/unknowns_review.md new file mode 100644 index 000000000..445142211 --- /dev/null +++ b/scripts/hyde/unknowns_review.md @@ -0,0 +1,53 @@ +# Hyde portfolio 796 — UNKNOWN overrides for review + +After ChatGPT classification, **19 distinct descriptions** did not auto-resolve (out of ~440 distinct across all components). Grouped below with a **proposed value** (must be one of the allowed enum values) + the row count it affects. Nothing is written to the DB until these are confirmed. + +## 1. construction_age_band — 29,829 rows (DETERMINISTIC, no judgement) + +The classifier didn't extract the band letter in batch, but the band IS the leading letter, so these are mapped mechanically (`"D: 1950-1966"` → `D`). Just confirm the approach. + +| description | → band | rows | +|---|---|---| +| D: 1950-1966 | `D` | 4,978 | +| K: 2007-2011 | `K` | 4,201 | +| I: 1996-2002 | `I` | 3,708 | +| B: 1900-1929 | `B` | 3,222 | +| H: 1991-1995 | `H` | 2,747 | +| E: 1967-1975 | `E` | 2,479 | +| J: 2003-2006 | `J` | 2,221 | +| F: 1976-1982 | `F` | 2,071 | +| C: 1930-1949 | `C` | 1,840 | +| G: 1983-1990 | `G` | 1,615 | +| A: pre-1900 | `A` | 615 | +| M: 2023 onwards | `M` | 132 | + +## 2. roof_type (flat roofs) — 1,473 rows (NEEDS KHALIM'S CALL) + +Flat-roof insulation drives the SAP roof U-value. **`Flat: As Built` (1,172) + `Flat: Unknown` (194) are the load-bearing decision** — proposed conservatively as *no insulation (assumed)*. + +| description | proposed value | rows | alt options | +|---|---|---|---| +| Flat: As Built | `Flat, no insulation (assumed)` | 1,172 | Flat, insulated (assumed) / Flat, limited insulation (assumed) / Flat, no insulation (assumed) | +| Flat: Unknown | `Flat, no insulation (assumed)` | 194 | Flat, insulated (assumed) / Flat, limited insulation (assumed) / Flat, no insulation (assumed) | +| Flat: 150mm | `Flat, insulated` | 59 | Flat, insulated (assumed) / Flat, limited insulation (assumed) / Flat, no insulation (assumed) | +| Flat: 100mm | `Flat, insulated` | 32 | Flat, insulated (assumed) / Flat, limited insulation (assumed) / Flat, no insulation (assumed) | +| Flat: 50mm | `Flat, limited insulation` | 13 | Flat, insulated (assumed) / Flat, limited insulation (assumed) / Flat, no insulation (assumed) | +| SameDwellingAbove | `(same dwelling above)` | 3 | Flat, insulated (assumed) / Flat, limited insulation (assumed) / Flat, no insulation (assumed) | + +## 3. wall_type — 7 rows + +| description | proposed value | rows | +|---|---|---| +| TimberFrame: Internal | `Timber frame, with additional insulation` | 7 | + +## How to apply after review + +Edit the `corrected_value` column of `overrides_edits.csv`, then: + +``` +python scripts/hyde/build_property_overrides.py validate --edits overrides_edits.csv +python scripts/hyde/build_property_overrides.py apply-edits --edits overrides_edits.csv --portfolio-id 796 --apply +python scripts/hyde/build_property_overrides.py write --excel scripts/hyde/hyde_property_overrides.xlsx --portfolio-id 796 --apply +``` + +> Note: a proper fix for the age classifier (a prompt hint so the production lambda extracts the band letter) is a separate follow-up; these script edits handle this run. \ No newline at end of file From 6c0545a8ee9c1e7e14883ffe63b29074189a44b0 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 19 Jun 2026 19:58:53 +0000 Subject: [PATCH 080/340] =?UTF-8?q?Add=20resume=20prompt=20to=20finish=20p?= =?UTF-8?q?ortfolio-796=20run=20after=20Khalim=20review=20=F0=9F=93=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/hyde/RESUME_AFTER_KHALIM.md | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 scripts/hyde/RESUME_AFTER_KHALIM.md diff --git a/scripts/hyde/RESUME_AFTER_KHALIM.md b/scripts/hyde/RESUME_AFTER_KHALIM.md new file mode 100644 index 000000000..f29275774 --- /dev/null +++ b/scripts/hyde/RESUME_AFTER_KHALIM.md @@ -0,0 +1,44 @@ +# Resume prompt — finish the Hyde portfolio-796 property_overrides run (after Khalim review) + +Paste the block below to continue. It tells the assistant to review the unknown-override +decisions with me, verify them, confirm before writing, then run the remaining steps. + +--- + +We paused the Hyde property-overrides bulk load to review the UNKNOWN classifications with +Khalim. Pick it back up. + +**Context (already done):** +- Target is **portfolio 796** in DevAssessmentModelDB (NOT 795 — 795 is empty). +- Script: `scripts/hyde/build_property_overrides.py`. Pass 1 (`classify`) is DONE — the + `landlord_*_overrides` ledger is populated; re-running classify is free (cache hits). +- The 19 unresolved descriptions are documented in `scripts/hyde/unknowns_review.md`, with + proposed values already written to `overrides_edits.csv` (gitignored). +- Env (DB creds + `OPENAI_API_KEY`) is in `/workspaces/home/github/Model/.env`; load it with + python-dotenv and set `POSTGRES_DRIVER=psycopg2`. Writes are idempotent upserts (unique on + `property_id, override_component, building_part`) — safe to re-run, never duplicates. + +**Do this, in order:** +1. **Ask me what Khalim decided** for the unknowns. The one real judgement call is the + flat-roof reading: `Flat: As Built` (1,172 rows) + `Flat: Unknown` (194) → which of + `Flat, no insulation (assumed)` / `Flat, insulated (assumed)` / `Flat, limited insulation + (assumed)`. The `construction_age_band` bands (29,829 rows) are deterministic (band = first + letter) — keep as-is unless I say otherwise. Confirm the other roof/wall proposals too. +2. **Update `overrides_edits.csv`** (`corrected_value` column) to match Khalim's decisions. +3. Run `validate --edits overrides_edits.csv` and fix anything it rejects. +4. **Show me the final edits + the planned write counts, and WAIT for my explicit go-ahead + before any `--apply`.** Do not write to the DB before I confirm. +5. On my go-ahead: + - `apply-edits --edits overrides_edits.csv --portfolio-id 796 --apply` (user corrections → ledger) + - `write --excel scripts/hyde/hyde_property_overrides.xlsx --portfolio-id 796` (DRY RUN — + report unmatched org_refs + unresolved across all 31,773 first) + - then the same `write ... --apply` +6. `verify --portfolio-id 796 --org-ref ` to confirm property_overrides + + overlays landed. +7. Remind me about the deferred **age-classifier prompt-hint fix** for the production lambda + (the live frontend will hit the same `"D: 1950-1966"` → UNKNOWN until that lands). + +Every DB command loads env from `/workspaces/home/github/Model/.env`. Read-only checks +(`verify`, dry-run `write`) are fine to run unprompted; anything `--apply` needs my confirm. + +--- From 3044c70202c991a11caab7cc37e484cf7743a232 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Sat, 20 Jun 2026 07:25:42 +0000 Subject: [PATCH 081/340] sap score and elmhirst mapper optimsaiation --- .../reference/mapping.md | 23 ++- .../expand-sap-accuracy-corpus/SKILL.md | 162 ++++++++++++++++++ .../expand-sap-accuracy-corpus/worklist.md | 23 +-- .../uprn_68151071/elmhurst_summary.pdf | Bin 0 -> 64329 bytes .../uprn_68151071/elmhurst_worksheet.pdf | Bin 0 -> 45769 bytes .../uprn_10022893721/elmhurst_summary.pdf | Bin 0 -> 63214 bytes .../uprn_10022893721/elmhurst_worksheet.pdf | Bin 0 -> 44109 bytes .../uprn_10023443426/elmhurst_summary.pdf | Bin 0 -> 63493 bytes .../uprn_10023443426/elmhurst_worksheet.pdf | Bin 0 -> 44372 bytes .../uprn_100021985993/elmhurst_summary.pdf | Bin 0 -> 65075 bytes .../uprn_100021985993/elmhurst_worksheet.pdf | Bin 0 -> 43902 bytes .../uprn_100090182288/elmhurst_summary.pdf | Bin 0 -> 64262 bytes .../uprn_100090182288/elmhurst_worksheet.pdf | Bin 0 -> 44204 bytes .../uprn_10091568921/elmhurst_summary.pdf | Bin 0 -> 63519 bytes .../uprn_10091568921/elmhurst_worksheet.pdf | Bin 0 -> 43148 bytes .../uprn_10093115480/elmhurst_summary.pdf | Bin 0 -> 63491 bytes .../uprn_10093115480/elmhurst_worksheet.pdf | Bin 0 -> 42906 bytes .../uprn_10093412452/elmhurst_summary.pdf | Bin 0 -> 63525 bytes .../uprn_10093412452/elmhurst_worksheet.pdf | Bin 0 -> 43131 bytes .../uprn_10093718424/elmhurst_summary.pdf | Bin 0 -> 63520 bytes .../uprn_10093718424/elmhurst_worksheet.pdf | Bin 0 -> 43160 bytes .../sap10_calculator/rdsap/cert_to_inputs.py | 23 ++- scripts/hyde/elmhurst_download.py | 2 +- .../rdsap/test_cert_to_inputs.py | 49 ++++++ .../test_real_cert_sap_accuracy.py | 146 ++++++++++++++++ 25 files changed, 405 insertions(+), 23 deletions(-) create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-17.0/uprn_68151071/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-17.0/uprn_68151071/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-18.0/uprn_10022893721/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-18.0/uprn_10022893721/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_10023443426/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_10023443426/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100021985993/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100021985993/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100090182288/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100090182288/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10091568921/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10091568921/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093115480/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093115480/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093412452/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093412452/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093718424/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093718424/elmhurst_worksheet.pdf diff --git a/.claude/skills/epc-to-elmhurst-rdsap-inputs/reference/mapping.md b/.claude/skills/epc-to-elmhurst-rdsap-inputs/reference/mapping.md index 1aab02980..245b357a2 100644 --- a/.claude/skills/epc-to-elmhurst-rdsap-inputs/reference/mapping.md +++ b/.claude/skills/epc-to-elmhurst-rdsap-inputs/reference/mapping.md @@ -180,12 +180,23 @@ Table 32 unit costs, p/kWh (`domain/sap10_calculator/tables/table_32.py`): **`main_fuel_type` / `water_heating_fuel` 29 = off-peak (7-hour) electricity** → Elmhurst Electricity meter type = **Dual-rate / Economy 7 (7-hour)**, NOT Single. -⚠️ **Known over-rating bug:** the engine prices **100% of off-peak space heating -AND hot water at the 5.50p low rate** (`inputs.space_heating_fuel_cost_gbp_per_kwh` -= 0.055), instead of the SAP **Table 12a high/low split** (a portion at the 15.29p -high rate). This under-costs all-electric Economy-7 dwellings and inflates the SAP -score. Always surface this in the output's "Known divergences". Canonical case: -UPRN 10002468137 — lodged 55, engine 62. +✅ **Economy-7 high/low split — FIXED (PR #1217).** The engine now applies the SAP +**Table 12a Grid 1** (space) + **Table 13** (immersion DHW) high/low split rather +than pricing 100% at the 5.50p low rate. Electric STORAGE heaters legitimately get +a 0.00 SH high-rate fraction (100% low — spec value, not a bug); immersion HW takes +the cylinder-volume/occupancy/single-dual Table 13 blend (applied in +`cert_to_inputs._hot_water_fuel_cost_gbp_per_kwh` when volume + occupancy + +single/dual are all resolved; absent any of them it still falls back to 100% low — +a rarer edge). Verified: canonical UPRN 10002468137 engine 60.92 = Elmhurst 61 to +the penny (its lodged 55 is the OLD SAP-2012 schema, not comparable); UPRN +10022893721 engine 79 = lodged 79, Elmhurst (Dual meter) 81. + +⚠️ **When building in Elmhurst you MUST set the Economy-7 meter** (`main_fuel_type` +/ `water_heating_fuel` 29 = off-peak 7-hour → Electricity meter type **Dual**, NOT +Single). Elmhurst silently defaults to Single/Standard and prices at the 13.19p +standard rate, collapsing the worksheet SAP ~13 points — which can masquerade as an +engine "over-rating". The control is a hidden Meters sub-tab on the SpaceHeating +page (`TabPanelMeters_RadioButtonListElectricityType`). ## Water Heating diff --git a/.claude/skills/expand-sap-accuracy-corpus/SKILL.md b/.claude/skills/expand-sap-accuracy-corpus/SKILL.md index 437c305e8..6d73c47f4 100644 --- a/.claude/skills/expand-sap-accuracy-corpus/SKILL.md +++ b/.claude/skills/expand-sap-accuracy-corpus/SKILL.md @@ -19,6 +19,74 @@ score the property would get unchanged. Elmhurst is the accredited ground truth; its Input Summary (parsed back to `EpcPropertyData`) exposes mapper holes, and its worksheet exposes calculator holes. +## ⏩ Resume in a fresh context (autonomous run) + +If the user says "continue" / "next" / "keep going through the worklist", run this +loop **continuously without asking between certs** — report only `eng X / elm Y` +per cert and tick the worklist line as each is pinned. The build automation is +fully working (see `scripts/hyde/elmhurst_lib.py` helpers + the `build_.py` +templates); most certs build unattended end-to-end. + +**State (2026-06-19):** pinned this campaign — SAP-17.1 cohort + RdSAP-17.1/18.0 +(older) plus: **16.1** `100021943298` (76/75), **19.1.0** `10096028301` (82/82), +**16.3** `44012843` (79/78), **17.0** `10023444324` (80/80) + `10023444320` +(81/82), **RdSAP-20.0.0** `10090844932` (78/77), **16.2** `100090182288` (69/71, +semi house). Latest run (2026-06-19): **16.2** `100021985993` (74/72, end-terrace +bungalow), **17.1** `10091568921` (82/80, full-SAP end-terrace house combi 17615) ++ `10093718424` (81/80, semi sibling), **RdSAP-18.0** `10022893721` (79/81, first +NON-BOILER cert — electric storage heaters + immersion; storage-heater automation +now SOLVED incl. the Economy-7 Dual-meter step, see banked findings; engine 79 = +lodged, NO bug), **RdSAP-21.0.1** `10023443426` (76/79, native schema, combi house; +engine 76 = lodged EXACTLY; Elmhurst +3 = omitted-secondary build gap). Next `[ ]`: +`10093412452` (SAP-17.1), then `10090343335` (17.0) / `10093115480` (17.1) / +`68151071` (RdSAP-17.0). Skip `100020933699` (user said skip), `[⛔]` (NOT +MAPPABLE), `[⚠]` (flagged engine bugs: MVHR / heat-pump fuel-39). + +**Per-UPRN recipe** (all commands `DISPLAY=:99`, cwd `scripts/hyde`; run +`bash scripts/hyde/start_viewer.sh` once; creds in `.elmhurst-creds.json`; shared +assessment GUID `B44A0DB4-4C08-4241-B818-86F060172105`): +1. `PYTHONPATH=/workspaces/model python scripts/fetch_real_life_epc_sample.py ` + then scope: dwelling_type/built_form, age band, walls/roof/floor descriptions, + heating `main_heating_index_number`/category/`has_hot_water_cylinder`, window + total area (`sum(sap_windows w*h)`), party_wall_length, lighting %, MEV/AP50. +2. **Copy the closest `build_.py` template** and adjust values: + - combi flat → `build_100021943298.py`; regular-boiler+cylinder flat → + `build_44012843.py`; full-SAP combi flat (MEV+AP50) → `build_10096028301.py` + / `build_10023444324.py` (+party wall) / `build_10023444320.py` (mid-floor); + - combi house → `build_10090844932.py` (end-terrace, party wall) / + `build_100090182288.py` (semi, no party wall). + Adjust: property type/built-form, band (`_pick` by year, e.g. "1950"/"2012"/ + "2023"), two-floor dims + party wall, wall insulation, roof, floor, window m², + doors, lighting, boiler PCDB ref + search query, MEV/AP50 if present. +3. Run pages: `for p in property_description [flats] dimensions walls roofs floors + openings ventilation; do … build_.py $p; done` (one Save&Close each, + ~1 min/page; flats only for Flat property type). Then a window-verify/fix + snippet (re-add the combined window if the grid shows 0.00), then + `build_.py space_heating` and `water_heating`. +4. Heating uses the `elmhurst_lib` helpers: `E.select_boiler(page, "", + "")` (look up id/type in `domain/elmhurst/pcdb_gas_oil_boiler_codes.csv`; + the lodged `main_heating_index_number` IS the id); control + `E.set_heating_dialog(page, "…ButtonMainHeatingControls", "^Boilers", + "^Standard", "CBE Programmer, room thermostat and TRVs")` (=2106); water + `E.set_heating_dialog(page, "…ButtonWaterHeatingCode", "From Space Heating", + "From the primary heating system")`; combi → `E.clear_hot_water_cylinder(page)`. +5. Download: edit `elmhurst_download.py` `SAMPLE_DIR` to the cert's + `/uprn_` dir; first confirm Recommendations is clean (parse + `[id*=ContentPlaceHolder1] a` link text — Summary silently redirects to Address + until zero errors); `python scripts/hyde/elmhurst_download.py` (retry once; the + nav goes Address→Recommendations→Summary). +6. `PYTHONPATH=/workspaces/model python scripts/compare_epc_paths.py ` → + read **"gov-API inputs → SAP"** (engine) and **"Elmhurst's OWN engine + (worksheet …)"** (Elmhurst ground truth). Target ≤0.5–1. +7. **Pin** the engine value: add a `RealCertExpectation(schema, sample=uprn_, + cert_num, sap_score=)` in `tests/.../test_real_cert_sap_accuracy.py`; + run `…::test_real_cert_sap_score`. +8. Tick the worklist line `[x] … · eng X / elm Y (lodged Z) · PINNED …`. Next cert. + +See **Banked findings** below for the modal-dialog mechanics (all already encoded +in the helpers). New schema not mappable → add a dedicated `from_*_schema_*` +mapper first (per-schema convention) + guard with the RdSAP-21.0.1 corpus gauge. + ## The loop (one UPRN) 1. **Pick** the first `[ ]` UPRN in [worklist.md](worklist.md). @@ -93,6 +161,91 @@ Pattern: `with E.session() as (ctx,page): E.goto(...); E.set_text/set_select(... lines 17/18). This drove the first campaign mapper fix — see Banked findings. ## Banked findings (fold new ones in here as the corpus grows) +- **MAPPER GAP — cylinder insulation thickness dropped (RdSAP-17.0+):** the mapper + carries `cylinder_size` + `cylinder_insulation_type` but NOT + `cylinder_insulation_thickness` → `EpcPropertyData.sap_heating.cylinder_ + insulation_thickness_mm` stays None even when the cert lodges it (e.g. 50 mm). + The engine then assumes a poorly-insulated cylinder → over-counts HW cylinder + loss → under-rates. Confirmed uprn_68151071 (raw 50 mm → mapped None; engine HW + 3446 vs Elmhurst 2911 kWh; engine 68 vs lodged 70 / Elmhurst 71). FIX: map the + thickness in the RdSAP per-schema mapper; check blast radius (any pinned cylinder + cert may shift) + regress the RdSAP-21.0.1 corpus gauge. Leverage point — likely + improves every cylinder-with-lodged-thickness cert. Flagged, not yet fixed. +- **Party-wall type `_pick` gotcha:** matching `"filled"` ALSO matches "Cavity + masonry **UN**filled" (CU, U≈0.5) — the wrong type for a cert whose party wall is + U≈0. Match `"masonry filled"` to hit CF (Cavity masonry filled, U≈0). Affects + cavity builds (10090844932 / 10091568921 / 10093718424 / 10093412452 used the + loose `"filled"` and may have got CU in Elmhurst — only the Elmhurst cross-check, + not the pinned engine value which is validated against lodged). Fixed for + uprn_10093115480. +- **Shared-assessment reset: storage/electric → boiler cert.** The shared + assessment carries the PRIOR cert's heating system. Going storage→combi, the + boiler search dialog won't open while a SAP-table `MainHeatingCode` (e.g. SEB) is + set. Fix: JS-clear `MH1.TextBoxMainHeatingCode` to `''` + dispatch change, Save & + Close, then `select_boiler` works. Also RESET the electricity meter on the Meters + sub-tab (a prior off-peak cert leaves it Dual; gas certs want Single) and the + SECONDARY heating (a prior cert's secondary persists even when the calc shows + presence=No — set it explicitly or it pollutes the worksheet). +- **Secondary heating must be built in Elmhurst when lodged.** Certs lodge a + secondary (`sap_heating.secondary_heating_type`, e.g. 612 = mains-gas room heater + @ Table 4a seasonal efficiency 0.20 — a low-eff decorative/old gas fire). The + engine models it (fraction 0.1 from Table 11 ÷ the secondary efficiency → e.g. + 3065 kWh for code 612); omitting it in Elmhurst inflates the worksheet (uprn_ + 10023443426: omitted → 79 vs engine/lodged 76). Build via Secondary present=Yes → + `ButtonSecondaryHeatingCode` cascade (title "Select secondary heating"): fuel → + sub-fuel → appliance → type, e.g. Gas → Mains gas → Room Heaters → RGx (pick the + RGx whose efficiency matches the lodged code, ~0.20 = decorative/old gas fire). + ⚠ For a NATIVE RdSAP-21.0.1 cert, engine = lodged (exact, all components) is the + authoritative validation — if the Elmhurst rebuild diverges, suspect an omitted + lodged feature (secondary / meter), confirm via engine-on-Elmhurst-inputs ≈ + worksheet, and pin the engine = lodged value. +- **Non-boiler (storage-heater) main heating — SOLVED** (uprn_10022893721, RdSAP- + 18.0 GF flat, electric storage heaters + immersion). The SpaceHeating page has NO + inline system-type selector and a `ButtonMainHeatingCode` button only APPEARS + once the bound PCDF boiler is cleared. Two-pass recipe (one Save & Close each): + 1. **Clear the leftover PCDB boiler**: set `MH1.TextBoxPCDFBoilerReference` to + `"0"` via JS + dispatch `change`, then `save_close`. (It doesn't AutoPostBack; + the Save commits it. After reload, boilerRef="0", the boiler fuel/flue fields + vanish, and `MH1.ButtonMainHeatingCode` is now present.) + 2. **Select the SAP-table system** via `set_heating_dialog(..ButtonMainHeating + Code, "^Electric","^Electric","Storage","SEB Modern slimline")` (title "Select + heating code"; L1 Gas/Oil/Solid Fuel/Electric/Community/No heating; storage L4 + = SEA old large-volume / SEB modern slimline / SED fan / SEJ integrated / SEK + high-heat-retention). **Match the cert's `sap_main_heating_code`**: 402 = SEB. + Then the CONTROL via `set_heating_dialog(..ButtonMainHeatingControls, + "Storage Radiator","CSA Manual charge control")` (= SAP 2401). Secondary No. + Water: `set_heating_dialog(..ButtonWaterHeatingCode, "Water Heater","^Electric", + "Immersion")` (→ code HEI) — then the immersion code REQUIRES a cylinder: CHECK + `WH.CheckBoxHotWaterCylinder` (JS click+change, AutoPostBacks) or Recommendations + errors "must have a Hot Water Cylinder"; set `WH.DropDownListCylinderSize` + (size 2→Normal/110L, 3→Medium/160L, 4→Large/210L), `WH.DropDownListInsulated` + (Foam/Jacket), `WH.DropDownListInsulationThickness`, and `WH.RadioButton + ListImmersionHeater` (off-peak meter → Dual). See `build_10022893721.py` as the + template. ⚠ **CRITICAL — set the electricity meter type.** All-electric off-peak + certs MUST set the Economy-7 meter or Elmhurst silently defaults to Single / + Standard and prices everything at the 13.19p standard rate (worksheet collapses + ~13 SAP). The control is a HIDDEN sub-tab on the SpaceHeating page: + `E.click_tab(page,"TabContainer_TabPanelMeters")` then `E.set_select(page, + "TabContainer_TabPanelMeters_RadioButtonListElectricityType","Dual")` (options + Single/Dual/18 Hour/24 Hour/Unknown; cert `meter_type` 1→Dual=7-hour off-peak). + Verify in Elmhurst summary §14.2 "Electricity meter type" / worksheet + "Electricity Tariff" before trusting the worksheet SAP. +- **Economy-7 off-peak pricing is CORRECT — do NOT "fix" it** (was a real bug, FIXED + in PR #1217 via the Table 13 off-peak water-heating split + window-U fix). The + engine applies SAP Table 12a Grid 1 (space) + Table 13 (immersion DHW) high/low + splits properly: storage heaters' SH high-rate fraction is legitimately 0.00 + (100% low rate), immersion HW takes the volume/occupancy/single-dual blend. Proof: + uprn_10002468137 (canonical) engine 60.92 = Elmhurst 61 to the penny; + uprn_10022893721 engine 79 = lodged 79, Elmhurst (Dual meter) 81. ⚠ If you see a + big engine-over-Elmhurst gap on an all-electric off-peak cert, SUSPECT THE BUILD + (Elmhurst meter left on Single — see meter step above), not the calculator. The + `reference/mapping.md` "known over-rating bug (engine 62)" note is STALE (pre-PR + #1217). `cert_to_inputs.py` `_hot_water_fuel_cost_gbp_per_kwh` applies the immersion + Table 13 blend when cylinder volume + occupancy resolve; when `immersion_heating_ + type` is UNLODGED on an off-peak meter it now defaults to DUAL per RdSAP §10.5 + (was a 100%-low-rate fallback that under-costs — fixed, +regression test + `test_off_peak_immersion_unlodged_type_defaults_to_dual_table13_blend`). Only an + unresolvable cylinder volume / occupancy still falls back to 100% low. - **Air-permeability AP50 fix** (uprn_10093116528, the first campaign cert): the full-SAP mapper was routing the lodged q50 to the engine's AP4/Pulse formula (`0.263×AP4^0.924`) instead of the AP50 `/20` path — a big infiltration @@ -134,6 +287,15 @@ Pattern: `with E.session() as (ctx,page): E.goto(...); E.set_text/set_select(... cylinder()`, then set water method "From Space Heating → primary" via `set_heating_dialog(..ButtonWaterHeatingCode, 'From Space Heating', 'From the primary heating system')`. +- **Reduced-field (16.x) semi/terraced party wall.** 16.x lodges no + `party_wall_length`, so the engine models NO party wall (defaults 0). But + Elmhurst REJECTS a semi/terraced with a zero party wall ("At least one building + part must have a party wall length of non zero"). Enter the geometry-derived + length (solve `2w+d=heat_loss_perimeter`, `w*d=floor_area`; party wall = the + joined side `d`) on every floor + a filled-cavity party-wall type (CF), and + EXPECT engine to over-read Elmhurst by ~1–2 SAP (engine's no-party-wall vs + Elmhurst's forced one). That delta is the documented reduced-field gap, not a + bug — confirm via engine-on-Elmhurst-inputs ≈ worksheet. Pin the engine value. - **Main-heating control must match the boiler's system type.** Heat-pump leftover control (e.g. CHJ/2210, ctrl-group-2) is invalid for a gas boiler (ctrl-group-1) and blocks the report. For a boiler programmer+room-stat+TRVs (SAP 2106): diff --git a/.claude/skills/expand-sap-accuracy-corpus/worklist.md b/.claude/skills/expand-sap-accuracy-corpus/worklist.md index e4db5aa55..e40f28538 100644 --- a/.claude/skills/expand-sap-accuracy-corpus/worklist.md +++ b/.claude/skills/expand-sap-accuracy-corpus/worklist.md @@ -4,7 +4,8 @@ Process **one at a time**, top to bottom (see [SKILL.md](SKILL.md)). After each UPRN, tick it and annotate: `— · eng / elm · `. **Legend:** `[ ]` todo · `[x]` pinned (≤0.5) · `🔧` mapper extended · `⚠` xfail -(engine bug) · `⛔` blocked. +(engine bug) · `⛔` blocked · `[🔍]` flagged — engine vs Elmhurst-worksheet gap too +large, or a new/complex build pattern needed; NEEDS INVESTIGATION. **Worked reference (not in the 100):** `uprn_10092973954` (SAP-Schema-17.1, 2020 new-build flat) — full loop proven: eng 77 / elm 78, engine-on-Elmhurst- @@ -60,19 +61,19 @@ Skip the 🚩 MVHR / 🚩 heat-pump-fuel and ⛔ sparse certs. - [⚠] 10094601287 — SAP-18.0.0 · eng 80 / lodged 84 · 🚩 MVHR idx 500230 not credited (flagged) - [x] 10090844932 — RdSAP-20.0.0 (end-terrace HOUSE 2-storey, band L, combi PCDB 10327, party wall 4.93, 250mm loft) · eng 78 / elm 77 (lodged 78) · PINNED engine 78. Built in Elmhurst (House, boiler 10327 via search, control CBE/2106, water from primary, party wall 4.93 filled). Within ~1 (78.13 vs 77); engine-on-Elmhurst-inputs 77.24 ≈ 77 → faithful. - [⛔] 10090844948 — SAP-16.3 · NOT MAPPABLE (ValueError: RdSapSchema17_1: missing required field ) -- [ ] 100090182288 — SAP-16.2 · eng 71 / lodged 71 +- [x] 100090182288 — SAP-16.2 (semi-detached HOUSE 2-storey, band D, filled cavity, combi PCDB 10327, double glazed) · eng 71 / elm 69 (lodged 71) · PINNED engine 71. Built in Elmhurst (semi, boiler 10327, control CBE/2106, water from primary, party wall 4.28 filled — Elmhurst requires non-zero for a semi). +2 vs Elmhurst = documented 16.2 reduced-field party-wall gap (gov-API lodges no party_wall_length → engine models none); engine-on-Elmhurst-inputs 69.35 ≈ worksheet 69 → calculator faithful. - [⚠] 10093114053 — SAP-17.0 · eng 93 / lodged 79 · 🚩 heat-pump fuel-39 (flagged) -- [ ] 10091568921 — SAP-17.1 · eng 82 / lodged 85 -- [ ] 10093718424 — SAP-17.1 · eng 81 / lodged 84 -- [ ] 10022893721 — RdSAP-18.0 · eng 79 / lodged 79 -- [ ] 10023443426 — RdSAP-21.0.1 · eng 76 / lodged 76 -- [ ] 10093412452 — SAP-17.1 · eng 81 / lodged 84 +- [x] 10091568921 — SAP-17.1 (full-SAP END-TERRACE HOUSE 2-storey, band L 2018, combi PCDB 17615 Potterton Promax Ultra, party wall 40.56m², natural vent + 3 extract fans, AP50 4.45, 20 LED, measured U 0.18/0.10/0.15) · eng 82 / elm 80 (lodged 85) · PINNED engine 82. Built in Elmhurst (end-terrace house, boiler 17615, CBE/2106, water from primary, party wall 8.45 filled, AP50 Blower Door 4.45+cert). +2 = documented full-SAP→RdSAP residual (measured U beats band-L defaults); engine-on-Elmhurst-inputs 80.16 ≈ worksheet 80 → faithful. has_hot_water_cylinder lodged true but combi PCDB + no cylinder detail → built combi. No mapper change. +- [x] 10093718424 — SAP-17.1 (full-SAP SEMI-DETACHED HOUSE 2-storey, band L, combi PCDB 17615, party wall 40.89m², natural vent + 3 extract fans, AP50 4.18, 20 LED, measured U 0.25/0.13/0.16; sibling of 10091568921) · eng 81 / elm 80 (lodged 84) · PINNED engine 81. +1 = documented full-SAP→RdSAP residual; engine-on-Elmhurst-inputs 80.12 ≈ worksheet 80 → faithful. Built combi (cylinder lodged true but combi PCDB). No mapper change. +- [x] 10022893721 — RdSAP-18.0 (GF FLAT, band I, cavity insulated, ELECTRIC STORAGE HEATERS SAP 402 SEB Modern slimline + manual charge CSA/2401, immersion off-peak dual + cylinder Normal/110L foam 50mm, party wall 21.48) · eng 79 / elm 81 (lodged 79) · PINNED engine 79 = lodged. Built in Elmhurst (storage SEB, CSA control, immersion dual + cylinder, **Dual electricity meter / Economy 7**). engine -2 vs Elmhurst, within tolerance; engine-on-Elmhurst-inputs 78.76 ≈ 79 → faithful. Economy-7 off-peak pricing is CORRECT (Table 12a/13, fixed PR #1217) — NOT a bug. ⚠ Earlier mis-build left Elmhurst meter on Single/Standard → bogus worksheet 66; fixed by setting Dual meter on the Meters sub-tab. 🔧 First non-boiler cert — SOLVED storage-heater automation (see banked findings: clear PCDB boiler → ButtonMainHeatingCode Electric→Electric→Storage→SEB; set Dual meter). build_10022893721.py complete. +- [x] 10023443426 — RdSAP-21.0.1 (END-TERRACE HOUSE 2-storey, band L, cavity insulated, combi PCDB 17045 Ideal Logic ES35, 300mm loft, party wall 9.2 lodged, 11 dbl-glazed windows ~10.3m², 9 LED, mains-gas room-heater SECONDARY code 612 eff 0.20) · eng 76 / elm 79 (lodged 76) · PINNED engine 76 = lodged EXACTLY (native RdSAP-21.0.1 schema; reproduces every component: space 6129/main 6247/HW 2752/CO2 2232). Elmhurst +3 is a BUILD gap — my build omitted the lodged secondary gas fire (engine models it 3065 kWh = 0.1÷0.20); engine-on-Elmhurst-inputs 79.29 ≈ worksheet 79 → calculator faithful. NB: shared-assessment storage→combi reset needed (clear leftover MainHeatingCode) + meter reset to Single. build_10023443426.py. +- [x] 10093412452 — SAP-17.1 (full-SAP END-TERRACE HOUSE 2-storey, band L, combi PCDB 17615, party wall 41.01m², natural vent + 3 extract fans, AP50 4.62, 20 LED, measured U 0.25/0.13/0.16; sibling of 10093718424/10091568921) · eng 81 / elm 80 (lodged 84) · PINNED engine 81. +1 = documented full-SAP→RdSAP residual; engine-on-Elmhurst-inputs 79.91 ≈ worksheet 80 → faithful. Built combi (cylinder lodged true but combi PCDB). No mapper change. - [⛔] 10014314798 — SAP-16.2 · NOT MAPPABLE (ValueError: RdSapSchema17_1: missing required field ) - [⚠] 10094601294 — SAP-18.0.0 · eng 81 / lodged 84 · 🚩 MVHR idx 500230 not credited (flagged) -- [ ] 10090343335 — SAP-17.0 · eng 86 / lodged 88 -- [ ] 10093115480 — SAP-17.1 · eng 81 / lodged 81 -- [ ] 68151071 — RdSAP-17.0 · eng 68 / lodged 70 -- [ ] 100021985993 — SAP-16.2 · eng 74 / lodged 70 +- [🔍] 10090343335 — SAP-17.0 (full-SAP SEMI-DETACHED HOUSE, 3-storey incl. ROOM-IN-ROOF top floor h1.95, combi PCDB 16841 Vaillant ecoTEC plus 824, party wall 48.51m², 10 windows 18.46m² + 1 ROOF WINDOW type-12 2.31m², 3 doors, AP50 3.98, 10 LED, measured U walls 0.18/roof 0.15/floor 0.13, TFA 122) · eng 86 / lodged 88 · 🔍 FLAGGED — NEEDS NEW BUILD PATTERN: room-in-roof + roof-window not covered by templates (Elmhurst DIM panel only exposes 2 floors; room-in-roof entered via RoomInRoofMain age-band + separate RiR dimensions TBD; roof window type-12 entry TBD). Elmhurst build + worksheet comparison pending. Skipped in autonomous sweep to keep momentum; revisit with a dedicated room-in-roof build. +- [x] 10093115480 — SAP-17.1 (full-SAP END-TERRACE BUNGALOW single-storey, band L 2016, combi PCDB 16841, party wall 10.99m² CF filled, natural vent + 2 fans, AP50 3.85, 9 LED, measured U 0.19/0.11/0.12, TFA 56) · eng 81 / elm 78 (lodged 81) · PINNED engine 81 = lodged EXACT. +3 = documented full-SAP→RdSAP residual (measured U beats band-L defaults); engine-on-Elmhurst-inputs 78.29 ≈ worksheet 78 → faithful. Built combi. No mapper change. +- [🔍] 68151071 — RdSAP-17.0 (semi-detached BUNGALOW single-storey, band H 1991-1995, cavity insulated, REGULAR boiler PCDB 17550 Worcester Greenstar 18Ri + cylinder size2/foam/50mm, pitched 200mm loft, suspended uninsulated floor, party wall 9.48 lodged, TFA 50) · eng 68 / elm 71 (lodged 70) · 🔍 FLAGGED — MAPPER GAP (cylinder insulation thickness dropped). engine -3 vs Elmhurst, -2 vs lodged; engine HW 3446 kWh vs Elmhurst 2911. ROOT CAUSE confirmed: raw cert lodges sap_heating.cylinder_insulation_thickness=50 but the RdSAP-17.0 mapper maps cylinder_size + cylinder_insulation_type only, leaving EpcPropertyData.sap_heating.cylinder_insulation_thickness_mm=None → engine assumes a poorly-insulated cylinder → over-counts HW cylinder loss → under-rates. FIX: carry cylinder_insulation_thickness → cylinder_insulation_thickness_mm in the RdSAP mapper (check blast radius across schemas + regression). engine-on-Elmhurst-inputs 71.34 ≈ worksheet 71 → calculator faithful (gap is purely the dropped input). build_68151071.py + evidence saved. +- [x] 100021985993 — SAP-16.2 (END-TERRACE BUNGALOW single-storey, band C, solid-brick internal insulation, combi PCDB 10328, double glazed, 100mm loft, suspended uninsulated floor) · eng 74 / elm 72 (lodged 70) · PINNED engine 74. Built in Elmhurst (end-terrace, boiler 10328, control CBE/2106, water from primary, party wall 6.89 solid masonry — Elmhurst requires non-zero for an end-terrace). +2 vs Elmhurst = documented 16.2 reduced-field party-wall gap (gov-API lodges no party_wall_length → engine models none; worksheet's only extra element is party wall 16.19m²×U0.25=4.05 W/K ≈ 2 SAP). engine-on-Elmhurst-PDF-inputs 67 is PDF-parser noise (HW over-parsed), not a calc bug. - [ ] 100020665611 — RdSAP-20.0.0 · eng 36 / lodged 37 - [⚠] 10093388044 — SAP-17.1 · eng 87 / lodged 93 · 🚩 heat-pump fuel-39 (flagged) - [ ] 10090944225 — SAP-17.0 · eng 81 / lodged 82 diff --git a/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-17.0/uprn_68151071/elmhurst_summary.pdf b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-17.0/uprn_68151071/elmhurst_summary.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f30539c7f30876e126f1ec1db11790853a27332d GIT binary patch literal 64329 zcmeFa1yo#5w&1q--oK%+ki@ zre+{gR(1|ZT~@XZDt7vY#?0cz&gO>3%2J}tqUIn+C1VFMTWdR88)HaYc$gLSts&*G zFiVZ_U>(Moep%f zX}Qznd!0!c&KI=Z0f&9SwkHvjQ>#kZr{-;g(L(A!yPRLM=6yBRllDGUm7%&x+@E{b zeD^V``?r!RwIht9b~$EkEem=-6(}-dJGTri7`D{ElZ`}>u}oTP?x&MLLfEb2WQ4UM ztK&`gdi(jS6vWFGFMQnk=31sVvAxW?L$U%yXm|mNP6zWH2@yjj@hQ^Oa#wh*N>=Lq zARg`dJF8DGM;9LX!@6y(k9mMCBN)vV_;$?>KM2wx|KJG0gbvSp8G^OHpZ1gCJ>CB| z-l7*fpvFwAnAzJhF=B75zzI7(YCR-q!E%LZMv_v0<9^52+oO`78B+9nx8d|;yhGN= z(v&9bl2zZ%8LjQk$|6YlZs)c4;#T+N$Ak9sm^Rf#6!TR~#U%A5*KziS3kz=lXhPC& zivIbf2mWtghUae-)RPP$XLi3_K z{+D3-dGEpU-=lks49>N+!v%!2LeJ-tseojqTGJ$E?{=ydsUL0({FYFq$m!TkV*t{- zyYxMRbz74+?a{*Kxry!i1DfsRC(L{9rY@hjOrikgQU?-LD5%Y6ZktYzunKBjIXeVd z^2Fh-po4h^C6~uLkIPalII83f4dCMb8x*j(p8IDGj=E6+R<-%rQrPBbEJTi6FzYh&^b_?bufiuo!oHkCE%B##BVc@B$&8*JH+vXo<{kk{^ zPP5KLO&^m(PRqW>0vrbztc?S2@FA@RC z;`e-~-j9y8edRfoGmo%iA09Jx$$cVg%tAlP@q?8E6Vo{41O~`DWBlNH z4jNy_IZ@Mz`@QxxXUwYF1U<3YVCefV90vDZ7Z;dro{1saLIl?`z<1?I`82?H?uen= zWAz%BIqqI~B(P$FcGW_abQ++fL1$vjSa-WLgUHN&Jxorixid!182jVd-noAaF5RX9 z-?K|z4b`%H490*xT=G*!rxg4X3+G=oOLm1rDK*N0gA9c`SCs4tw%<2=tczz+&KGwOuJZx`MTDkx(Fu+R;T!Wm+-67o&Q%#! zHZkd7Sh?9OzCAW^sR`E1n4TfL=&p9_E+bg<95?Kb61=bMao$GTy{jF5q<;Xq6`uEt ztIFXFV3t=!ns;Df-WB9i#YI>5>)z#?s^H!oog?|-QQHwP=MOPBGk?dP!XOHqoy}Aw zv&EMR(H}aaH&{|Js{tO$hwv)$3=kYd&O+`luN6{Pt@?F3aSM;c;j9BV z-BugtGfRrql-8Z(6e)*2{4y3;4vYsn>QwapJl1uXYu?MsG=B4$jIi2?_r*YQe=P~C zmtOq`3le;P7IXa9XS2CnhaRFnCg*ntBL=YoY7UGT;MgULWOTAkP-UTZ;Q|+J{i875 z^NGex#^w&Ii@U4~xhTLj${mlHccP0un{e$gdx_*%t6wE1PPJFHKBZH2wSH%zc3|7l z$_^ICFY`sFlA~%qE}k*GXhVvDziA#&?uOw;@=bd1bkT-~!#MccSXkH*-e?GkF(@A! zy+g=x3@ACVV6E)O9WMJoF)J_0&5OfC1&;ODFFReE>P_S%NmsBZe-+ns)*s`anC+86 z=IUi6jXTmeb1|4E9@KnW@?}nnB(f)&qNzaE^%5vz#GWd1?5k7Plr_h(3YkQ+9DbLj zUDqEy*;SL1gVOuf1kN8{WbWSYJxNCl5q<= zYa|8rexWz%XHEZYreGGANg%)%EvyyasG}HJ;raoYBvos4xl#xPOp*ebz2CQKa?QRV zv(<{onA_3Li-m{@_r=vT2((*tYW`?@u7-A(e&L~YO}+H&BQR%KkJT3^UeAmogRXeo z@%Ff@!j$;HdR5t)lTD1VW4pgIt^!e@%^{Teu(}*me?JZpe6lKEJbL`%U>e+WZF@>< zKHp51$IxP%1o+;ge(+g;g@IRp)g(}rtg}&k{ChF_*_pnCbNJDsQ-aF=%1LKV3}haJ zOoGS1yAOr;4-XRF7xw0NAed}l+>e-UPn2n?mt@)*B|O~r2N<*&#jyF(A24YJ-3=eJ zy*LeB^hQm2&sSwyeI)p%yy$h4WDKKaYKs9_`_gYQl@SE0qrel6yVX1rNqWHMo({?fKB=Pf9HmFRI!b`(3Y-mt6 zC|0#sHfN7;5=}S76(_2f?pznVe8ox*D_<$?so>1>k!c(AW9Rwo&+ynGNXFe^=3>!i zmX9&Nd+9W|QeE>7kRhMY9ic?nVVA}$P3nvSgGp(*n&|RAr!jz&f-F}r1Kh!{Vj=8= zU80W6mAtOZ?Ett#Mv6-v)bu5^O_iv^ei;UbZ*9qzX#6M17 zoJ2KBwqSH~*flGEKROFWaS2wq{wlUqYjyYI^#;sV{^^ObnnwX0%|P*{)8&`K%t&uX zd|g?g@wYU;US6ZQv|bB^4fuOo)=u{C6|X0tU3v0d5PE5m#Y}Z@-ehMXesJy1te2zc zlSJ@n|#tMb8Haf0$olsoW=66d^5W*1T|?6ktsV>tIJ%OdT}%&W7N7 zoeIbVhh)FnCbC*_H6}rv6Cov-w;{o2u<`P2sq(N%IsMXihGfxfM~LuX=<2#KSB1rI|H{_NmHry=;7%hO0S24P;qwhA09 zT&a%Y@HILRCxJEoYf;qWOVqtBm?EEzw;ZiamdCSi8(SuLxq#!h^$?xcTDpV%b61O; znSGob^ReI?Va}c;;4Q5a;&K0DX&Df7vmS$2vaiV>@39M6O>K-b{}SE|aLc2lKChOTIdB$X=&hi2z$23Dp?X#U4&p%R`y*&+W zjuMUdz(&IBf;g0Po>jWoe=RVn+0t{7U8R+De*7 z5Y@PQ6fdS({t1T}gl%r)rA>-B^)UR>g{Drs2_QO67Pmx$d+lj_OuLe+{3GNZ6Gi6C zMA(gf{)!MeS>EZ^u8|qfo{qRv)^!l$Rr)5l?`%)rZBi;J)cZ9dlAB0L_VSm`4~`Ce z{`SF-d0%kV$q|p)5rw=fe}35$ksQ+UWP~-Y(pS7mW@cjW~-_p_ zmrz~anE?eQ3qQXTX-Br<-xb-WX}&W9Ien?NgUq?Nnw_}EAIn$XE_UmqT;R)Y~D`9g=MFr*-euiC}%v3QO$vJ`yKbAm!=kl$7-%e=)WawbCAn_jS9$3uGav@a9eTZ z=G%=G$?NxdRkh??o6<8KH9Z&+%?qZbA`*cDvEu<7E5;+cL+!s9jxGmzJ9Xx=806iX zKw7g1*bLhhWSMvpW}3@@NA)W)+R9A8*nEz^O~wy8!g;$!Fe5VVt5wAU*uWkWQ^(uU z#}6kNuA(*!pzG=A09V26z{4B4%b{pq&F2a-1{f91`Jl^kyct(UbuG8rRB=qG;JVtF~q*PH6ssaX6ow{P z^Xju+{2}s?F0A}TbRDNes5X!C-q;06#7*Ly^RARNv9Cm!StP+=@>IC_OGVEWy%$jf zAOtXtq8d|kyWuUQv@cKf6=%%}2`4wo-6B%vORQ`Oh!kJtOwGQKp+A>Gn6v2cAZR!$ zzYmf8!ueWdXRsr{$jzw{G@`(uC4d=Qvi|`KEPuy?jVX}{esFU#T6X*VY_?R8I^8Nj z6rkz=zlL*(*oZyXx>THuFt(~ROvldJTfO$}B=mr8M(R?)OSTUOD7xxE63Z^;B}l$G z`*WT$!`rD<*jqaB^TDSi!qHMkL9EY!#5>?y@Q(-Pw6^(m9Lua!*Ga>VczMy1Y+%nR ze6xBl@}dl&061l@=pMsu->*8xDOPy@OPBt@j~`uxaT3`lp|>KZr5iAn2u=k?99gLb z*9Y!IH);Mb>^y(PyC*4Y>%;{iQ^Y1f^Lc;<}lc$K%#ErS%pQ&x~ zDtI1Jc+B0Mc=8xU5{oV)W6tNtifp&1pM6`AF(6PX=ts6cEZd`YZ3C9;g?b(8x!qX#-I-#F_8pQ7}-^hgwp#j)YzPe z7C}2&w-TP<$svVP7AkOub2AdcM#4-a*@yHKXL@ti=P6@ZQfO2CUB&pezB}?7lM81^ zGBg+LGW^}+WYk|sf-hbL*T9fZk={Cnv zy^7rLEY7X|jn4NX@zrg_6qR0yi2XPKCpL4BO8x^0vng!2hUPc`Ga$24keS~qY<5)2 zwg7##lfg}B;88nkxOk(rrHFEl_W?2=tR~Em{PeXiNb+a?vK`-YnSUV|Y#ULe#QaO# zJ7j_8cXJ1S-Z|2D+EF<)w^eOpkM=Bp3^Oo%Ehy3oOOw-B7h+m45E`HnXn!g8b&${D zY01LjFV+`)O73SPmOFwk`q^M5DmckCZYXZ1l190j$Z)n=)bf@=kSQ0bhi(sLDrA?q zrsG+>c z>D@=R!N?3Tw@ZTYC~+h;<4x`(4TRhS`)!p35l4rIM?&rVPV!XIyB3UZr}{%W#GE-B*OzZk{ap#h*tp;o@-A ziPmAWdYx#iPiD7v)CM|<;g)&3x3`PH4?a)UD}NuzdSg7qxQK+sS1`IUy9oRn1t)l6 z_3^nhTGKNmh;92%X5c?rxjFtd^EAX#{m+@FS^t4~nwx`@<-eGx5kPOI6DBjQXX6zr zJ(_t#T&7$26w{5O;%1Z&cx+-9s0I=lH3D0%k<`mwK43Q?6^T}Gm;1J0QGWamj|s~s zQvNx2rl~yXHRAK5oGi+aEbHef&7FE1Mh3ClbDtZ?m$ou?di`UEnV4+%!tB#D5n`nY83;9j%$T{8AoV zQC`l%!^88AO^}Tvvx|LrXJTN#PVR7Ab|Y);{njDqkoG`LUQrPV)^Bw+C(r%^*9dp& zwSCN|%z3GS1J;N`o?+sK`U#xeavIim@943Sf`fy%d&E%%;PAAB=!rolR~p+_vuo^S z>uFz}Pj};dL-y;SF0ZeDNyGCNUVC-sL{)X5Z-dyWUeqKDRoO;}vErO#W9njjR1uKa zk=cH6Q9XlLrf=qI<|Nu>cK*`x!E!bmp`C%!?rol&K&jUDlxMHvbW+FC_nDdGF5seL zVBj{7?Pr~w2#%WZMo{d#5wsz$V^9L!fpZDuNGZx8Lj+4o^-xRUb?{0V-t>mTPkmsh zTaFwn&H5q4m|V2D8>%uhnVglC#HAj~6ROD}S=XK_mj0EH9IiY16#!N;$aW|y#BWb9e4`b> zwbQ371wbhwU!(QA;qhHR?&*kLg zuA8!3A~!cT6SuZBxtdzbB1g8D8=a6VR&NXKK=#_$A3Z(;@33zu-qX)i_&t}JPSu^D z&thS`D}vcCn%%U{=tNNtgb#LY^azg(UplU=*DkBcQxny`e_%2L8#OwrzAc40UyX`@-*s?k*>R(mtMHS zDQjqa{IYf3^p>9rmGhDziD~)RuT=^nH^QQ}RS|H3E3jza6yZ1EQ!cj_1Rm}z7&2)0t9pZj3{{G2v zI8u?Klj6FmBykHDOKd&)4fm0Y$TG!==1{mo6&rh9)hJm`d48j2M{#~W^$Ai?*z^)w zDJM1Vc>n{7C69^PWmX`JVt_kBQz#tBRX8a%DZ5p#y_ z=>ITKCg8CLQZ^WO#zksqjRag{7EUhwq{MjHC20S;tUUL;QgW;nww3xPpER6vyE7bT zE92EC=2yAov2sITPNXZUGlkl1Tv{L<0!mf2WLa;U zHO01QTeaVXx}BEB9W|^O{bX(Xhq_wvj~zv~yynUw6Jgc;y?Qn9COq?Y$k^a`Ln+t5 z*BI^DqCm1&2CWeTf$Z1{VNBfK#k#jqO2?d?+Y?Qw*7T3lWqUFq2#^i~oT8hu_^9t8{eeP1*goz+tUgNBJZ*Q+(&qzBWv=!Tpb?Y`ln_A^}$3=zUTA_>q^hJF08YC8mX2xP-T9=j-1$B9N3UjHKk>}>;z7w(mWjx7)C(2pg z8eA?bF)X=!eGs3FCJB#q>h=*+=!*-LzEXRin)useMD=ot9=_x5?zSlF13N3n+36L$ zjX4#3bvy}YzW)#3LpRv9`cb^@nc^606Z7Gf&DqLwSR)74J8 z#ifVG3Ux6uFd7`}6Zt4hn4XbQ{H-K+c4p7aVp?@tnl0{>o}VN9XoH`RPfPXV&EYi7 z7be}^!(EB42nvM=h3DSC8#U+pWcq$+d;r?-b;$^kEOXh+4_=`Bn3`CGL4GFd;o-iD z-N%Ut%*)SRv9*0~TQgyLny7ElV-uke{{F<3A3M~-!_D;zcAo}IS|Ph&!h(alW@>As zjgEE3#>+vxA5m9uA;qOR=GNwc$!JqW#g5ijFPnm_vx|e9;7|4Xl}tUqP?x9wKp`ea zf2$>8LwvKlvEsTpz8PA1w$}0GVDN42&xt0Ij~YN~p0DYEYfl41(@s01L&VO0pUP5C zZQi2j^pDJq9KV%=Pvt$pdMY)3AuQ}%zB+pcsw@xrU%z(t^v|^q6MIMSi1sjG!1S&T zk4mp18l4sjIQR1sa-mgXipz@8xo3K`JUL zc*6**#g*f1eeI0r{exZDQcSRLIlez$ei~iDFofwO;0^lTaD8eRTJ=r8>-8=Q zRi01V_czb=v-ee3)l?`7+T2n1Pz9xL{#nZ^76(uDg9u0Nj!tf;E3Eo+XtiYCUbZ=Ck`7VP5T8C*xDd4A8~3|G`S%RXXsWCnZ-(X)=lj)?pC7$$rDoz%bbxIZ6|s|% z1!C1d_kd$$z5A(mQ#*Q&-b~@B3`>SxM>ok&T7n(SFiwM{iT~j0)o@{ ztX^RUO4EVmBf=9d*gGZ2JG*~KSE6V$P5+#M4b_CS#?N1JH-JU`f_Gc=A7s;P$8fQPV-Be;-?vuy#J=-b{06B@L4 zBwp&<*CHva^y+j}yl_1vG1*PUd_WS7jsb}m?c%Zzq)WDP?7J5pCrBNy!BKC;_`(Z9 zicr9I3RJ3;!=jmm3TL4)nM@-L6keTa+y*}sWfbMdC#G0OCxXSKD9KBUax3m0NRs9dj);2o9d4x9-ZtY9^Pk(o2UL46m9Ihc3jf$yXm`oyM0-rKf0>F)Zf4= z!2r(I?3lscO&B0Z^)T?LRJ(lsSXt>X9T3eWN?D2W5=x*H(&JGZMI@7`0!Cj)kVgn@-{$W5i$!rE$`(+Qz`xyI-L zp{QcdRi5v61h>4Rx3JKe?|BTjHG0eXN#9DnY#b^k7Un4n0|PzRusga@b7=VI>Wb>U z{X+`ASIwfIl$2HF6Me%0{deGv6>8X7=a^xGQ#H-(* zOY}$CYGVMxj*gBkuDSUQMKTf=mmCo|oLw3f#P;93)nh8%kCz3_mqevAyUbFmM0N@z0Qcd zAG(tBQx$igU3t$novv1dE{(ae9mS&~^O%5wi;Fu92RllX;lc=}?`K$9z*@ulq1a$2 z_}Rp8e`Dol(TxflNhf`mxa-ceCeZyA|>RcL?Z&qZHu zS?YgbQQB&y`oq0F>Ut(+r!F@FC&t_K3~va-5T`i6;`QF{L`Hgov<;BY_8tgWB>>;U z5SlrAdF|6yd$9&TP8&Y%5ybxH;s*@SOttzYyG3{gfqik+OCe7|@vwZ`VW zH=@{@a3COvSG~J~Yx_z;#fxtb%T7bX$AIi3OiSMXtVvT#^M~u?JczIEcPQMxsZSPV zr+a(VuKC?r(sIq?8qpl9xiVRc$>|mMZ-QbMb+rqbjp1VH$;qE!1)SD$ zFIbrT#6K@p$E)t9Hw6P+yIYq8SqLz;$0gUNcFc@iyLRK2*UZ-|273LCn*Hd5#Dgj0 zQYcv6?zj$OYlBSoZlz)}Q}YW-_cvEVrlmkU%g3D5ENQ#u=D*zB#QTle+RK_37DCS) zM$^J|4kL6TB2bSw+v zC8=p)mIp&=ytBOc;V37)f`| z?ne123yhAJH7cVYFP#h>O_Yy{PIqj2YM$Vz$ ziGluw4(a9ZU865?QPc|)br`^VuZq?bQqqzWmfxJT;5$xCbgGb&5?wMe(&f#Le^dr5 z!|P<|FoE!zIs0A_M{OT$glp0jaSb~-+Q$J@T!W$~?A*#-rO}+PAlfI|1vWpcq40}g=K`KYw z;7|Rbp(yZTsQ_`mN%9#rO=Z!ylkIU0cwA*NC4)g2>{C&QhMV(OH{7eoN8AErwk6S5 z^PYAyn_(5^vxv6e^z8LTcxVrHk9?(?O3lQ@Ud&uIH1JNs^d0u@_0OrlX31$0Xi-@c zDLjr$+1>U1?X8W)XlAd!K6Ld8zn$5(Q&X<`mGoOTpv(-M8oIx_vi4wEk#8=|JF0gC zceCkiI}8N-_9RDGQ*dn$#gnJkgxIecI)Gkl&Ytdyr#H-j2$+yB`k5CM88ydJmLnGx zmPhZzn{A&pYDtRDNHKo%IaoaVnS>|Eqjwb{Y8ZH7TI>?#nvt%X%LVWDxxBhMfCYoe zR?LFzmA;|A$n)ZF4%u#zV;h$9_vn(l;1=`K%R5CWoLCD+Vh*3X4c;L>atp2KU}RKO z)S|T|c(p<*eSn&`3eeb3YMvPs`*?yE2JVv%y!h7lJfqxN^GZVhTF=OcQh*UvSylqL zm;mI33!F9H-e2Zmc*OZ}Ue;n&mq6K)`zUqyx-O$t4vFv6*_lI-s#IcV5q$w;rRY%Y zu5MH&-+khyePAwmzMf8hTdh46Ex+B)@i71sC-y9~FM{#Re)U1=@YWZ`ga-R;PoUsw zTT_zP#c^F5b8v9*0_5Uy@-8T#V^U4c;~*(15f%m}!_(VGci9hVtWJ7i_Vc~;x&!jF zzE65Y`c0mLf}EU_6N?j*vz+|=o>F79oo8p2p)tR|y{4Zsb@@_KjqRJWM~_@YnbbXK zcvH)7nEbG&TiT{fyLb%|ifC_;mHR{*o|B79a8Ll|KK8xW!;a#xbRJb@T#Lk7Ut$VA ze%G>NRaFRLh~iZwlj1Ot7|7E-HAn3qlaov8tPlaNi>msHB^X0^aBxc3av%47d~L1! z$B(J0CS)Oo{(gZ)feR;42$eT1>fQxQjdyIZSf$17{{G(n-%)YRk@|pl|(uW^>&PbF~ z43gmepcI>0Ho88##<_RXVBr4VK|r7A=sV|qb$k7ooJ?<5vt;?L73B6Vkz`I}702=~ zWo0%X5H^is_Y1-Vo6OR0N z_8u#EK(3^1*5^L8d)^wRQcwbM&vJh>F)uj5cyxT+AlG(g{`^{l!@k0Px(p~WQ5LNk z%;bN*{US!{_D94%_X>H!psXmCV|i8FJu(nt4*E1+kG;bfhyVNNmUDatRuuVYaxz7+ zeF;?i6tiW=L zg>$Jr=f(rzJ5r+a+wCGoYow$=mWyUjcFR-?_QYP%(JE#F;{ zkJe+nSu-w@EWMXCaTFg)GAYR-c6iWsKbs`G#d~krMgS-~a2m+2s=_*S?;N{AfrpU= zp4nPBga)QGoOzLkA%(rTzo&ZKGTQ!LWc`r@GSh*QzKy=~pwDF1=GE-uU;ex$6(v z=P^PWt&X3)K`Il5qoTW!!=Fc+JR13=pNXJN1e_?G&FC+awk7D|_e-S$#y*UZ;rWsE z{gCy&lC%rPc*Yx|qD0vB(JI`Jk?B>PC>A~VS%+3YuxJrnX+3FVJYA?=3xCTR$>^f} zDJ*IA3Kn-Ru4qTNAJ6fX)X2iky7xpA=6?AA-g}D&uIN{dMWV=O$OqQunKnl@AUqd5 zKp-N0cXuDYev*+a3dkdVp@)d=893LFK|B>iW2&tnBXZNfG{wYlel)zA{alfb{%w4l z>nGi3@`Yze!WErUeS-*CVYJLC>0L|I;~x3~di4UV?Sp;3tD5`Dq^1CUpuN3~vE78X zz}C7?jmCcWyB*I9h+eZ)$;Z#u(9i<@fUC;7v;rAfDG_bLgz_>zfK=lztn>q;S8=QW z`SmJcCGj4Ci;40Uel`fK7T<6Vb^;k}BLF-vk(^4FMJe~rHj#Zp>&V^oAhVC^kO37n zC9T?-Wee)qTcY#SEYF>**<~|bVrSl~+%4M|0rS# zR<-v4eKN8)Jp=**0*YvnXX-om4d>%=tL?_(MW}C_tOZ_wTJT#XS3jaP0^n3}=FE&# z{G#dgTk;t=OPhYXEKPi2{5V_gH!is~7gMjo{Fg6$y$mzh7-$U&$dwnSX zUapQqYkGwAxhRgzvY@yQ^)Gcwboy*hh_ws}8A&-VPDMpouk{FZXCsN7HzvK)-7Ts$ z`!dlcxjQlJb5vMWZMm|2dGdE$S1L?fVfq#(S25bv1nGste7nNW-+yjk8}!vk%!axq zB*Y-T3+dWtTY>HEt-B1?b4EW}Xz;Jq&;=wVr6D`#QPP;Qj1RDDfz=7{RqyrM4x4I- zthHKi_V&JIL|A{y%8KQOd3YF~M}vHz82k|PR?3FhxQjXfVlKj++IMCH`eG7T>=`9; zy`{vCWFvp;sWf&=ih<~B>Z9wUZFP|MBdp(cL;q<`i&pxv>A=uGFi`o%pvtdgR_NWq zV?iU=cwYavXCwH^UG2lbN)%aw23~nar=iBVwx|UOosifilJ{h?a#NSOuU0th?21lC z%V+Ut%Z6uXHAcU8{M4>2dEM+cq(4o&g`=dVkyn&&&&uW?E-QX{dD)KJ&eE}c`|BpQ zlrJoz0dWoP=C-Cz=p~HbGKD&>#Kbx>-kh$0-4O!*=JKV>9^Mun22~1v+KLV(4fW<` z8RNp|R?JN^Q``4dal~lw!wB&32s%pMA|_~9;t4{Lc!!6(aGU;s5oyzn?M+h_Ha%bo zKZVi>y)OLj6qOiZi? zKIqL-w^&$X(F(WdbvL$+!B;XW;m+WNz{QtH{86T8KD}i{F9?jAr*h??`yZWamLa@<+qYPcE~pNY@Mi?ar)6e=yyPK zhr7g1_Pd=BzTVq!uL?10#{tYY3Q8Y^3TjT({nVO)o_8=@X<_+&sG& zBCdbUJpE6zMeP5;JPl=wpllJ8ErPN|P__ul7D3q}C|d+&i=b=~lr4g?MNqZ~$`(P{ zA}Ct~Ws9I}5tJ>0vPDp~2+9^g*&--g1Z9h$Y!Q?#g0e;b?b)Iy-$wrqzeODXVDlEr z7D3q}C|d;eTLkr6TLkr61oc}4^;-n>Tl6=-@c(oP^;-n>TLkr6 z1oc}4^;-n>TLkr61od0=|FqvCo`20e{ZF$+od3W)4P}d0 zvPJ*x*&^P5ZS(e@_FKgD4>oV1Y!Q?#g0e+Wwg}1=LD`}&P__ul7D3q}C|d+&i=b=~ zlr4g?MTp1!kELZm(9L=bUdg^Df4s*olr4g?MNqZ~$`(P{A}Ct~WsClwW{X%^{x$pb zKMfag{{#Cp6fT0oMNqg13Kv1)A}Cx0g^QqY5fmz}H zt(C2Vik-fpF|)X_v$>(MvXm&Zs5!_{$=E^6*4ob2#@Gf#%EPRvZ*9yh!}AaJZaKMl z1qB^J4#xV{NFM_wVn^O%iD36WQr%iMSzqdrvRVj4XI>Vb8u3eCjKjENXqo#~$JbT| zsxt+Q z_#UpsohS=9Rs6xD*owr;Jbv-9v1-S<3dVz?Y^#HOeVf82^CFa$#0Z9mWNfIK(fcuY zwiOsA+S9wnlxTMzuN8hrx}C%;l#7zWeOPC&b#Jc(cSFt8gX%nQjM_9)m{!p8bJKih7HKw+S>oL!C*h5l??-DZlUVXE7i)$^#(gOTDik#p*l|%J=XzI>4GPm~%uj0gI<#hSIT5D4%>E8QhKH%39>h_v`*1t;VwQb(bs# zVkTmY$*i_g6N&gK2mXKo6PE*_dS(3tW>u0NIjNk;TZAHZ^%n+>BtD)w>npP?_zdHM z47&z0VjCTbv{*!03}iZ}h_}FjICe#*DKdV7fN(@Oe^XCvq?l-K)sz*|@U+U9a<{u> ztC_i5xCqLTtUNcRK+>WFDkN4?>bq5A#jvkOR-^`XT@AmF61B5@gFmqP1ZFxIMuH5k zFVKGBg_d70AQ{^j{Yi!(F^DTJBv!V6i)8acB^xT)|7OW%|F4oQX=?)#H+D31Ft-EQ zIxs(7{xRx`IGF2Oy%n{!GGdmr(l>P^WoP^2p{S^>s}>^%H!mq8H#Y|Dy*RiT89p{;Eav%EkssX#_HJ)MDf2A$>a8IXOu=Il2Bgd3bn9 zxwyDVS$SDW+1c6u_w2*s$&iCg$|9|0M(nHF4D(8mE4eqJ>7fC%*n#?N4lqqcpweDJ? zH{vPe;DWp$NLf!W@Gp&r)bZ!|A9eoe4Pkp~1LS;a!&CTF|DV(SIsBuXr{_;?eaZ)k zKeh2G{L`uPhdlhJ?2xiPiI(zz$^qx!%fWx9{u@dAr-h8`?+gF`5;E4m5i;h#$kYF# zd~tI@JCd?K{qnjpv$CqRq_bPBPJ*o*C^aJEWB5bGd&A^$ z?XW2M_JYHY5`&iG)PA$Uam}L2Qhpe(h_V)~cS5H9DS@+-755qQPHy}YmWWJxkVtGU zVqgD+rL5p|oR7S>V(cL$`8w^2{p&+TqlYfQhV%cA@pwe_Ejl&w&lUb$N7Mm zBt+CNJTU?Ah=aauuCl-8UesewwBQcYY^$)aal$O`;>jjcPW%c$#9jlATccJXyF7k^Ki zhayJ%>(kvg*$4_p;cK^ARTD`YwRxsX^ka` z53>DNN_&2uhy5gTZ6nImwx=ACs~mE4pTm_0gF5wRBUl=y9j!qLWZcWF&sY;XNVfEg ziFOV>TzfMd-`>}%N&qvf8_l_IGAOGoH)VvH(gVi00_zOBH&$;XD_pZ1cTjldVuP#r z3`mo{!PyjRW1aNXebD|q#74BDv-rZPEcCh@xwdy4PKNe(_`B}|Y_jnxf7rYHgwJ=4- z=(K%5UpCvs*RovHb9GI3U>6mf{u=hKB#?Vo9~L*-9zjHFKoa>>_IuJO@f zdBenr97C|TGG878Kw5Y&IjJX%IFS6ly<8CH=_T_1*-bAdg<*j)ms#SHR5*1T$x6|C z@BPiO5MhtEFP@cw)?Q{&gO4{~Daljb02RTjg8Z8~scY?z@7BIm<)pFjpUXwk}$9z(u z#1s@H5<$jPHiJWJCAmD3`h+t5BL}IiEVJ?I<4Q1^aKI_<9>+5Ule2;=v(*)=%^2&RoC>q$7gGw)gy`dRr5LyfBN&SfR zwz(ePh8O|H6uGTfSap(hEEu>?-hN0+kYbkjzg1KFii5 z0G?{zJ%3ReV+J`|-8vITdqB#4^~2FeN`!kS!#lXIA>=8BTi~U_o+f8B=E+vwX5gOl zV4Lb4Qbo>Z{UR@V&B}aV75zH}@6NK}_Aj`2RGs%9ejr|*@a))P_&Yry9&Qf;MU8>GFhw|lwrrMs zMp*lopqspaW^a3N$--p;vUN_G8V9pD2%Q4%B{3;IG~|zO1a2s_vUcK;_y!1vpI9Jaa8lXbM0lV3`uG8Wf4L zVbYM_m~?BqdW0E!0YCRYu}S>RZ~6RhXclZws>i>kSwNNm|Djp1lm1EXcv3C?q*?q? z?ti#4|3{hy59@!?Ean~NxiGw@0V{bK&OOFx|S) z*ecPDB$m7u@``LgR*fBJeX4Iv%Da;md_!?h4lOiV3gWz1^>`A*Utbe5wWe`Nv`EV;a!xX20TmG#a*!-Zau{-!3?iV!0g)U8L;=Y;2#n;YNQS|O-&gP5PkdFk>fZfx z)v0s3_t~{q_gekoS)Mj&`$KPog2+Us{7q#h+9OCo2d3n+xriikWN3VST!v|ZszI?` zwM>AY;Yu^@c;@Xs#ly*Cx`^V^lAC(!$$gePD3b4FsS7FkNx2JBqrBLstSxEA`i4Rw z>38HKthR2U^@8@a#==OVL~at{qktPh=%sLBDJPt>qGU3yXi$fzYxsH*^ob%p?`&F> zEcq_w*|%G48iTx?dXWhT1zrms_K$+uDbhIwh#-qzw3wvjR4DNI1=||zW-%54_R%I5 zfw5crm)Fq$8BKyIGcggBM2GjBM0c=58z- zYX0P!7DVboH=1v>&r>-pG@tE7BEjdm*iO&2eHdyPq)lrOzgT!nTB^p%o17b;D2V-Y zxR!B+A*}~DGzl~~Y*xq_x9n=yWfK}kfs$i%v-vW-gTzl9%$@>~M(Z+K_d0z0@|S7&0zx zKmQzy!Su}j9gRizjpteS^w#j++K!OQ5&GfkL~llXkSgDaQZ)|HJV!gfcYgx$wNO;> zdnG{L7)qr-R`|l%a$H%d;6ZYE&mb$^FzYTPB@%hGJw6vs;{7=wy;rGd;@&y?QP7uG z-zvulvd~N*92sK<6~1`(h}nnhKs9cV6@xA0P~R7m$GNr({e<=Am$HesDWLW-^#hjo zh?XE$)mz)5a*Mq(#&Mn;iy!XEN$m6w1(NssY-LwuHqv^?PDEXa<}U@Umrtun@&%AF#_8Gy*G7$bN40!x ztgD-njM#;in`rsO4;x145?Y^N4?YzDZ+d(2>bKOH&)7oDZe?=A9SR3(qn^RZeVlWn z&MA(|n@~N=1o%xhBr+I&hpkQ-_XB6Wy6ZT+W9N^E~*Gl?QdU?A@j> z7^t)tdyhXyhJBbk7DVR^4>9K$Gci6~=s3DX&G}kqrp#YbwWinf>sv(os)d zMFwg2X&6^>m7CzjB$@OFY)({@(I`-w!WCjyMf-NRXM7$TdF+QEM=3TMIJZCtr*@bu zt(m-Gmb}^oew&}9(l&S>1?Z?TT4ljSywx&Os=7FW<*BqxzrQsAB)~tZL;O2)Zb*-& zb>00LW~hIpm?zx6*JU%~j6x)?jR;RZZJ?=~srBRp6GJJ&v%O+!D#>Ry!iwL&zRy!+ zj&f#_?qg6;M*0h^{TO`;NcxNsz}K*QCy$!8Kr_CxbYE}Dc#)0&Vktv^DPvG0ly}`Q zp~5?(Xo#Nv@gCA`V*LG$nC;`hqw=h*0HBX=2}{m&nubp}qheY{Pz~Ka#eFJqs+2yX z$R!~IMyzv#%9V}SS&2r?_3Sc=pYP3s0W-x0(Yb^`4-%jz0z*vVy_1-V+B@CM4`#r+ zA36KLzzoRgoXz%Q#z<2YPI*o;w|9l?s1+=;IH&n=n^&bzcd}$zQB=zfU%>BhnZQ}J z+q{F$o1v|NqX8LU7;P?MyWCf%5LS|+UwhxGGjY_`#w`&lpc_$L0!ynY;V$kGTpHca zKdAR59o$}#N9=#k_nq0eG=Xd6g_wRwbN(qwpKeOl({w~MA6;~>KuYWsIB7%Au0#Il zK<72}6XlW{ZiOLyZ(elvNQBw-Lr5PaynI94LN9J>LT*=Clr2(y?3?iYYz|{$5#f^i zbd;K@%TP=5Q4l|m#>0<4YUcB0nLWBiCRi%A79=(DVxP@c)sy2Xt>yRp90&*DLWI(*~i9VRx(%al&>MV{mkchAE75@gCQN>;*F$36sgmb#~4pMJVw zy>4>Z` z3)*RW7RdIqK<7aj6Rz3H5wp^$GT&N3&=-;?jGJc<%G{m{oSe+MW^@#-%4dw{8PdV; z7aqHkz}(06GA{A4#FsWlaT^`hkYqm2_|g&f&wTXk>iuK`9LeRli`CU4#1 zUxnr2daxv(Nz-Ve>|syl#rHY*qFAXz>&8_I(h?`PT6GqX@_mUVd{I*X{GR01bBUi~ zm$u$GW4ZEH#P(SJ>jOamV!)?CN9KC!iMlMzU?%>^m2D6==Pj7NgFt;!bC(ymF%a(^InPDE1Mc1Kl_qacda5V6 z3W5Z?6q-rOH8Xy9bAUjqYcsJp528vll2V;h=Att~QNgxLEHMvYUP7SC`O8TuDQ)Gp z@HuM@C36_)2w0SgWJ^~%)Ylp>tAKUZNdxY8ls$z)?R2u4eO*3|pd!_&-kO!ulVTvg zLf1JNxR5B1M$CLm#7ICtaXF#zm{DQ|aK=n-j(SB%-XRw;CEg)GvBqPHXkLEbL718; z?k(g6HDeCG8>S9`ptzTYUiXotHSh`4v{uNwRWOGLe-B%l&C(33q-yjz5pAi2xWMzu zsoade?K0@#vvbbe#$;Xk`&>@Y#6m3sI5^jQ!Pn1pZx8cvmaYWU4>TQsM6b| z6Mg%JGE!-#X<2Mj0*PSZ?x*ZW-RX+khI39#O%MAl&R7IpSq(EOQ7FQKm?Tt0ad24HRGgQRtsQ_zGM&W(y^YIaWv{b%0y2}OsU{U~$jH;A>JY9O&p@COUnKs zm3-C(kNqn1urdeFoh&?hq~UaI@C(m{j1KS?W8H?)(owh4R?2)?Yvb$B)3vW>7^CHI zcEJUd3kn9^>uF8(i##>BH<}bz12EtYD#}9_XFN7}J8}!}u;>!FzI=#dsPw$cIkT98 zyIvG(TytVe>o~|G*ncq)X+%`bxoCvdm+hW)j^7#!+n#)?gw$96G(tT)R%G<;|G6zpK=;MpzJQT@0tiCVIgW_4Yw6&mUZnBnLx z!Rj!-eNN9UqBMNCq^c#<-TS!KuJ{brx{8}-+Z!gG%LcZ_XYFf4Ac~jF+Yy?U2+~>C zXRrDpCDbnsIX_0k@O$-SmgIh)J4@4T{@4Z_-2beVO4F}X?yiegtJnOY$L~Ej$)BTg zar|!lrlv$JjScSDIff2yTNH^<$Xb)A{tXOTyweLzM^74}Z4r0KNoXMgiw{z|>?~P) zS7LJQ?a@!n;92{;|A!E+*^U1VA^fd)|0je12K?%suL$AF1^m19Rh0nzHNA3$K>>fc z{Z|V5ul2v3?5~ggTb2Nd{gWli4f{N_rR&NEoRi zD~Bf|w#jxVEV+oCs6D6j%C9z_0Lycic$Y;cPHQ{pG-S6wygigF+REldQM@5rrA0mU z0NX%eQfv}H1C^1h@cl=ms&sWOH$;nEhLRV3&PQ)>auRiW%Y0&9I2OPB>i~+2q$9gX z0>AB||44zaHGfu2$HSKXK0+>7+?xaFF3&^BEZW*S&4*Jct~whI?}9W|k-tU`kD zH{Cvru0D!ni;Z^bx{v)vzq4uFIly&*o4b2}*!DeCa92C-xxy;;GH(bLf7cmw*E;{X z{kyD{<>#YcW#;`7<@DH$&`V~{U;CL{gg;V^O2S1(kNM=Y8ael2&IZjyC&Q7}O6(^a z{&_MeiLGx4$jBF_pG^MgUv`4X_r0Htmk9QT&{lzk_q1L5qt>Qy3f&OSp`lKZuPFCf ztRnymMM37Gc=gFIr;{1$tVWGnGGZf-kUmb&C-fXW+8Kh{2q)so5Fd!v6MNMFQ8}br z#-0y!Mnw7*%ZnC?s`g~mLCe(m1mUjfc^+z`wmvG8jmLc+oT{=*vOujB20oaM5hPg~ zFHUp|6`eT7WNU0^E-C;o?;i~->FmkDP}&K4le*Eo%_Z6KO=ok*Z+J*3nfo zMY6`WlV-N(W;dLBtaiR8oe~7K$5SA6WTudcnA-Jcb$lD_gSFhGWMGd1)7FBmbptp+)lFz zT^a_f4$KMc;TaVxMm<|I^kGJsL#0nP*W4Xf`-DL#$7IC0!qVYn77$tS(04EJglx^l z`k=WDr}^+cMcBmNQ=XdzW37JiemuIFM(ZYhKR>wgFQPV&yp|&&V?*7u-jqI66(#wZ z2kK3dwtL$q9MwZ<8Y`wHN+Y>NF~mXQHNaflu$_cDybXs2T#-iU*kHm2T$dSP5QiX9 zVXp-&a>$=PPfSy_iBnmm8SEBru8OG%^(snRI`L-){UAlCSG^;b*e04sFAl|8gp&Te z$-pC|r$tR>^OedoRTvgW;x632uZaH1^YW7HmX|&lW@B_>O=Svrox|hpqHzq715QI4k7~n-vWR^ATbcY3h*!N>Wccc0FM8{t}Msvn26}r zjr#j@Am}eM@Hb3E3?zD01OJXe#eO-8zhR=_EASd7Ci=_h|NS{}px9Nk*D$b%=#>Qc z+xtLbkgIu+YnZtBm7RJG1BqT``UeKR9v=uK@kh*{t9-A=3<857f5ZZYh+W@PkObt9 zy#h%{K(6l<7zq6%H?RosYHH;7n8BjICSk5)VDLZwk(;ZzjlHESk(3mXu&#}d<*#!f l40Ce28if4CNkn(=5~(n8;f?Sg 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

f2_on5RI=Nsz9}n4}LSa%YetD2G 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 literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-18.0/uprn_10022893721/elmhurst_worksheet.pdf b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-18.0/uprn_10022893721/elmhurst_worksheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2a7cab925f5673daefe91230eb4c1a6abba2d84c GIT binary patch literal 44109 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 z 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 ZR3Ct~H{jo=iJwn^n-7(NL0(hg{{TYFT&Dm4 literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_10023443426/elmhurst_summary.pdf b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_10023443426/elmhurst_summary.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5989896d74d62c73635308a025c617affc39c0cb GIT binary patch literal 63493 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 T%ip_+lY<)wM53maRFwJ;-q!v( literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100021985993/elmhurst_summary.pdf b/backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100021985993/elmhurst_summary.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b9f25bb4051d5fc67c94f0145d2e110ad0a21917 GIT binary patch literal 65075 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 IEq%5B1EQ(iy8r+H literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100021985993/elmhurst_worksheet.pdf b/backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100021985993/elmhurst_worksheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..53e01bf96c2fa17c514061a8d80e4d059de7ac5c GIT binary patch literal 43902 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

UfI(sh_@N4aF}2O}Pe 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 O2n+!Ov9ijj%Ki_VL|@SW literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100090182288/elmhurst_worksheet.pdf b/backend/epc_api/json_samples/real_life_examples/SAP-Schema-16.2/uprn_100090182288/elmhurst_worksheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..65951fc43a30f3760deb8961daff7eec43a35b38 GIT binary patch literal 44204 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 Qi5tkn4aA|RmwqerA9t*llK=n! literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093115480/elmhurst_summary.pdf b/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093115480/elmhurst_summary.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f1e38755115ef314207dc8625ebf1e519ea7f9d3 GIT binary patch literal 63491 zcmeFa1yo$mw&0xv3+@^`!Gn8nCwQ<9*0{Sn1PBn^Ew~1PG){1Lhv4q+?$i9=)puv^ zynAPTv%WR2JL{zCoKx1Y&#qm&_?=CmASzDJ%)o}gOv*%Rt!Ku|%c$&XW5_6?>!534 zZOo{kYiwvw$^yQr$j@(RWdJUM@O<_+MgJ(mC}QnsyLzm z=|7S`TG9GTGpv8#%x8H$Gedm`MkPl*hvyM|v;=p}2rxBpFd=1OVq%muG&MGHAZ2D{ z1NUWNZKq_Tt8d6CYUpIDZ>T6C%qVQ?V6R|kCt_`BV{K&!9t#(voUSFf9VSL`Qws+} zJ4SH}T?a!^Lw#!lLq-`xD`RlO>>Mmy{QUL~c80o^2%r5AwBYTPC88g*kuL{IIV$#D z0vypS(AI4CLx~2uI`&<A@&qA?w) zzM-fxx##Bjg5^TYA4*`Nu;k~JimDM&Qg4pm-+bCvy5x8tT{kX_l~}Fo$x)gKLjx0p zw-`Rvd4SlXBpVJ0xCyPk2;&}AzE%vv0tPi4{NyH79cTtd)jFIf_ehW9aI+W(k;Fv02`jorQC~Vet;wYF`D-om{8Wnr}2O z?(TYqR5@~CFS@&;KShfPebC}fyI#K>eaUH)m`=R0Pb42H( zf-2}&Szv0pn22|s;_q{i%TN4~g_Tl&~^- zGxQ|*i$+g;oL>#q3~!>l8MOzc_=r$&zsfo8&$Y#c4Hm^FNm5E*;x;K*sP;Kh7d$(_p+NVP_yNvj|}(u`v1jCv?BYI z7|CVRyPHM^taWABAxDQz2Lz3nE>H~!5~^?9?s&Spl=9Mo3LbZAPmaghqzufBsX{K8 zb#0tbn(r*k0u=AI@w^r`J1;)(x1L2cD=#3Ku3*R|s4lvUvDTiOar#9PlKvbsz2^4T z22o~6r-#aZ_W z>Dc~Md{JD?_|saABQq~M#E~Ku!{Q5SP6mIUUG>dFuoZx8?gErEDYj&-cY%^?humyZcR6E9KQ#wv*1S(IEWYtBNq+wBU~SHHdc3 zYoPRTWS5@Ksk(aT2O+h<%h^NdfaJ~&Z5Myd z=EQYtq@Za|e5-E1dMo)cr^A`@I2tcXCz8D1(a>J?XhT{{otV&1bHbJ@!acGmn z{v4fx^V6OCMKLA}MPjNNaAEHa5=d0X?He0g%?KZ}%G^vbbVDSjxQ}N--l4rYFjIQw z*2AIfb=~;bt~y(FBYwyV3DeB^B{8>iFHp>ti*5DEQ=m!Nz`Cfc;r7_WYi|4V)A{>- z(Wp8qN}g`YpFDMs2YQ)1Kjy@N(@rJqR*n~nD~xR+pvlRN^!BIQhMv;($(EQ&{m13|&U7(jn>zAAv#s7U2iJRni@p6A5t z$-cU`G_!pA33~L?Q<^rpcX*Xa@MmdWkfMKlGMfzF83<55K`sqsKWF*bwq!N=<2?xu z@^1@+YFtJjICiG zt7yeMu4ZSBT2vaL#y98I0w%F5rc0FM!V zie=c1Z^mJ$e^<2K(|@XoWm3!&wG*uIbN~toOlhMM91XzM<$b!%2z$&?8d5YeYNK1e z-YC31GIFj8R8O6nCOq$~bnPr5SnwFr?~CBSFYj{NLfN^i9(tmE0J`R%^@%D=WA|f} zR)m|jVPf3<$fJmftnAai%QIHOxjQ^V@WrLHAz;iKq;q0y!J0%P@}HSWQzo;@f7%W?G+?1xeD^=VsLj1>+0k$O9A32is-6l>*l7dD>s(~r=<0T#KW z?C^&K0R(mXMU=eGS=Yrki5?cbpxL7ONUHkrrwf>JzbC+h^0XQDnqMQ5`>7gX?wm-G zq>)+E`3&;bp7#aPDqOfz=D`|meGu<{swp8u23mHn(ZJ7KR|;6ZMYsFNs4#;HBf{UL zoU{J1_C*>z=<&!Y21u7LvQx#p_quK|T#lm`ZoIWxKvlWo=4AX97JR$?s1=?$sb^kunwwrC(O;0m?^NozK(vkaBe_>xW3A3lpuTN$q z_!rn$_cl*P7(R?a)^k|^yMYBXyvJHpwe9ioM4(eY|f;2D3>(b~O z+APlR($A$M09QzNTqa)e&bBOq)kCaB;@K8AMMjR*m(||IlQq@8r@=NL>yh#{Ci|?p z0%P$J6>n#cC~lNNIsZqh2c)|pnBhF5E?jMtp`j2q-ex8yR=7860wQ#ZM+P6@GVQ+> z9h)(i_u&kce0n=0BhJZ<%|HQ)cHb*GS)J^TXD3OKwIxT7sXy(D@{7;#P9<~kG?2s@ z?wvj#NEQufxGl<>l^_Z4N_<=YL(1g>C}hC;{o|33R!x2SEZYir5>2;zEJ?!PcemMB zFB>N9BL^)G$l|ygw9$>5#l0xhRGWRjUy0$MEUelxeP4VtfJZUTo_;~UiIqN_gnU2W zo$$M=?>3D;ox{i<;DZv~JBr{Ki&$@)wMGy?91j#yru1TT0Y zC&}|v7*`(hzbP%is~3-=H~-$K2hzOoUHDE9^uIhLI+g~81Nd(cziZ446!FEKGVuB| zs;1Bb%YIZF6b3Iy@PAS`RTI0|8I!vAlf52KI;D+|EDBq5p~%p@M5h@j$lF)8 z_KEw-`6=i24P#~cTo3(eTzaS+*FP$ztZ~^2)#Le58Ek&+yvhw9wXGGL#MK`882N%n zm{F=p_rRqy%W@s&E;nm#&TX@b+FLn7R-rd~zM9<{_3$w7Kv|ZX2e}wHY=3WO>z1I& zw6~MnTY|g93B0M#K>s55I4agveOtG}kSstPF{eq^VIXv;eY+r-?l>A3CacPyIQF9x z`<8{kAR&$Qp#Sy0GIctR(L=~1iwoG6LFPeOOC6iKa2|?PnMvRB33%HqN0G7TQNrc_ zw$Qs?ZP?5FzS1J0b<(dXY#>$+CoX}gK`R#u7ABPc(ymbw_xpEQI3zQkdiey^O?Hl_ z-|+$UBh&P`oQR3>`Jvl{p?b$^4%TfNpSU$Ns8~Ecc@~y#=*|jky-=&cQ-iVB;nZb$ zm1@zj1WikVfyA*{QIK>PM1w?f@6Jx}0Q2n?+&}-|)4fU7o}5R(QtBcG+q5&Yb!_+E z9|)aBTu+Mv#VI1^z6yStTVyKVBUlh3{n4O)?lQs092e5Y3b*imK(B=b&gbeoAPp3h zfxbm#vFu_<0zWH6N-$?df=_4V>Csr>Zk2SB)q9Fy)?h;j_n`0MwX%yE0ahuf!-3e2 z29FmAX}HzA9iZ(W;n=_fN{qFY+`JzTgAabJpm@hY*s6<@a1=U0ZvEymY)l-9w!+X= z8V7a)OMEI5y#Va5sk4# ztmYoXy9_GIc4U*fO1cyRHreU>Z(#e!NZJ8|!nNMdpAgz+IM$tgrZ9Pb65J3W9QKKY zgxeW@FySn{c%j}?|F$WgR|^O8y7HHB!H?q$pC>OZ?`{W1c<+g93NM!?(p-Xwx}C#V z5%tn9*o+QXrdFPsr0|muL$95wYBcKs!c$~1i&QvQ9)?HM%Q=cYLH8I)A5V=0U1{eo z2@w-z9B*yv7;$ZBh})%H2GGz`HbA|nyE3j55(&Xxc!UV9LPZ%%S?! zN^16qd*oKsAT@gmL)>1?^$slI2T=tqc8Li8ocwRls}pHTCc~MBU`77%BP`Iw?2>_c z;YUf=mJ2%Y!>%LI1A}39hG|}i@OkD%z0Z22s&-sfa?Vpx^V_u22J%`TXqum~Hs$$* zOSpbo&Jj!e9|8c!VI7#O*x$Xs)@#dL5pG274bz>6w$VlIPS^OSW5v^sGEf&`bW|w{ zuV(O)(-@Puhx?xGva)N@if(jbu1*{wN{=4T7^*l=G!tl@7>SHW3*e}gMjEGqZEE?r zd>^B2^IfvHqh#;Wy`6KFd2<#g7X(MEu7+uU#%-~Y%VbA<%}M;F28`mg;K<3d87+{} z?e(l^%)BzDWjL&VFd&-aPyP;1|mt*n6@+ZWkTm|x{*tIK7Dz!G^4|u1i`^qFO)@U-*KaRoiuoy1EWQc@lXxl8pbh|0RKNq}ZMx^BW-k4)`9_^T3$gJhz5zp8nlsLjN;vZlpL1$YT=UD|Ax44pk24_``rL{kz`Pz8leX zvL6&{Eyy|sTV8tv>n4G)LDZw25djTkRJ4J860FiF)4)<(v)6F@qh_^3JdkX-GB`@F zm+sZzayhuQPMPt_S7QU7Z9{#en~Co-+vryPdi5hvFhP>!Ny1byqi*-7DjVFg9tUsT zXYYHRu()6Ny?#4>S3I z7l42l9c;U(n4svUb?<#wj=P1pqjQ1Q4F2y45prZd1o=bSmE}1GaKrfYa4I=(i`@89Vv04XTsziczC%CsqUnqn(ohVQi( z=2SkS^1O;izYUwD&?yqK9RpxTr|nY6d?H~qh7MIz9|K@~O)KYTsS=jtU zdIL|$eXT@thVg~J=`BYDCb+~5#!Qz}DOM8c&r}MV-_r3j>>^DD?GRVBy@<6x zVot@1wTrq2@yZy%< z=ny{<((U@PS8V2Ya2n1o!!Px<1(t*_zkASr>zU@D`f&b*?X8ZTt(_l~azviB1{wMBfPBwO?zjIH+IlP&Qn@F>qiIpvPZ{QAc zo@(TgOEHLunO5BAvWlLk=#QsY^KZODP%U--gjJ7FAY8^->eGn%?sE$)1~iXQ>9?He z`qBhE_?It{UXS5~uwC8ziJ&|VJh9q8xnDiNMtyq)gMdY?kRj;vkC`^Vprl-lkU~}- zEkp!V0({X@87GDJ{Xf)PhXYaJa9Om-dB;4lS6>^U0KP@wf)2~%=!JwHtI{- zoJ9XVbJzjb5OM9VaqOH@D&`L#Xt59i0|U3ZM3MPma5V&Ii5-kC)wV8YR#{8dlCxe; zbz=WS^lhUo{q^fL71w)M&6Vk6W##_fbz;X~!ba)HidF*jWoK;bljmb2a)9`@wAS^t#t2f-sej56>DrwdUVT8CA2NJOiw3v02l22{kOQR zziDNLu~m)LIYfULMj7Nda)_hZcPavZk_s}(;6dUNU6c~IZQK(2*WJOelb`5nmcmDi z(|ZKy6AKo0f|aHx64TQYI8?vVVJ+a~R@Yr~G^um1CyyJ`OBWNji#`?z1*>EA*n$Fe ziHy2M2R#iBt3XV5#T2_*!Rl<{HLc%8QnCrjVLBtx0np+B)`Jm2zPtRP>rDWT?Osjm z^4EoNlf?;Rxy;ogwd2l$x>LH2jw<4s#rFQ1xKx+=8^VnEiXZdiW^?04^|>n88pf}L zbe)CjwxnQLYYva$2=>+Fa~#ZZ;kOYAU~rS>3jze<0b92O!B!LNv->yT9oF?*dD`hR-5>VwmF}mrx(^uKE=JJ?dbB5_CM7)i0w2hybJczwAzXDhExvMrQB+g= zoV9sX|DKlunf-z;fnn*$w@CslH-f^}6=5*`%h1ULZ-Su1Z3zh6I_(gvLq^VQ{5{)H z&dMd)H2A-`C7d^IovXL{QaGDw0C;y>+`4LSVctnhR?Pa}O}BH_9N@GRwtTT43YUA^ z{`RWAD1H+MQ)Dgi4dVsh%Tm%!oCuB6~6ukzHngdzr?rPue)z)qV9n``E1NuJo zm+-moIwgWn@1AHsnrRFy@~$v z3nO|Cd9?H(h#ldQ;#9VJ3x^s=1Bc|_R6_ig-jfIMVCOLY(r*l&AHATy5K+p@YE`Z| z(pu#)U%TDhu&s(YwU4ZM??784_NlGlmfKV@Xgs8{uUn@I)`)A)1`!Jst1sc=kB8Qp zAq*r#*J}#v_h-eD4PoH)D%8G>P&i_5-x{w+wxoTUD%t%Q1P6X#fRi+{xC@sD&INCv zsc0ipVzKav(e22tZzs(CVf-;M%Mas10i{np0<8AVt8DXbPO6g|Aqh5B(}~G(*l{e1 zIDnzy{)i?FY;+v2wb=_zBL;$48MV`f-QC?j9Rtm<;3h0r=FQtMO-iN5qQk?(&~qdY z+S-}R>dS#pnUxF$yBxnMA39V7*dT8zDk^DdDYW3zS=VUZ*M)ZWiBnmitf74Fp7Gf+ z+8ZYi3#3tiu8>b|t=RnF^k`I6)8e8Wzcv?Feh%dl;_TdP3n2^eqX&85cq!9+y^AFU zx<%*g2hoX0lF(?!PHz#}-k4y?OBMO=@sIAq$`_Ngux)pDw*~2+See;QPcC8Y_bn`j zQ$AQ4OBaF)6NWx^(K!P+WgU^XMef5m_42JV3i5=U9qgQJeou^zj!%Y#eoIVm=DGap z-q9}Lej5rkI20!*VMIi4$yrCY=zGqd>sr;&fJDAyVwQ>Zy$V_Gt6EE()(*pcxZ|1W z`1qK>2L~>#f=rfB8%ercpPIms;~+(65?3+5N?-Jy<__vVu7#T=@lfT~2mM1zO9Em+O-6bW~3AF%;1$r3-DeH4|=eXkjsf zoelI11_pYCK1&g%q^1`BEXtXg-Ze3sQl64zi8-O=WeYuA=jGwiQ2u;V$~TCKmBUACcVC(5Aul_-y{m7wb%@w2j7zwS4h^b%WoSfl1<{}c z3gt^y#>>F=m)^^w1}rkNa!dD*x@IUSnzbG?JuRiuB>_rGO1MLCD~07_ zEWNGtXMF=5SP~4-Fqu9*ufL2eqv=Dn6L1H#)Lva)3JXhyD_8u~?ZDeXqR91bZh7-k zH)BtEMMdfDk7n2JpGZu)Zlmlk&iX!98G+2wuS{JxIpc?KsDMk##Vv5xq^c^|Y5@bG zNMRMb+}zyJ^$I<7uZgFT>Fg2PsC%Jg-=qx<1q9&!V1i-|gy+x6&65iM0x-E_(yN~2 zU>TD0k9XZLIYPrj+uqsaXKu234b?R=+>IKBxY7`9j*)LvR-FWp)&mxp zfFiGDea+;eL@tyrC*&IE@-EeY@baZ&Oqnr5Y}s@CrlFqoiQ(mUD{ei`s;R zgS>lPV1iJFuHEhiWaQ}@P?VY3-VDyh&-JM!zC6ThqGaHZvx9CB7P9#$1;qUI(jA7L zp1YhKXy^lQLQ(KE%*|y(|nL|5DPS@!1|{Nf@4Mt*bi0_ZhVr@-il1w4Y={(AU6Z9t(6Ve@PmU zOju~#IcvKZd3$G%WI2*1!_@C-=wNm5Xng&|cfK;IzW&B$uT(*o0Fn&1NCJ>DDW`k9 zevdJysAWmS3tcWw)_S;weTu?1OfgxD2Y3i+JA?@;JKf^5ioET9Frq^FK;o&obtROv zLaRzc!417?yYBgGN)dT+($W?vGGafk?}wgiFf411vzDR52T8KaY-7giGT~~ z?%f}~E*q*(MegmaB<^0P@f#<8=Wm-?xotQkZVX&=vgOu#YiK)?;6>O83 z^aRlQ`|H-_48&iEvh0qZt9|c1K6I0tIvX0a zsks?*g}rif2wzL#{VS}TuYhkyzORNgzN=BMVw-Bd_d7YUEkz7};~0PW_^n8HgrzzP zAZTxI-{_K)S6lE=%*G@#5)KZ|5DfGPQK~aNh_;Vzc^-2Wvq!GhhX0$9{@(iX z^@1w}7J^pF4sppL=9+VM&t$wR9i>|l8iw_hOl$!>|*|M$ZejK!==EZ*JX2new z$~|ta5m(bm+ci01*il}_r?`V?`q+hE&G2@2##2+`B&~ov*7rcb3IXUIiqOQ#({qoy z(%r>hXRvL6@L=QeVD9t={e%oUydK}&Qn%m(2yBeD6*@XP`eb=6HbrO38@$~dw<92k zRld7}Y0iF2!HsVV%}PbZLx<=sNKM}NqF!A?y~kx@&Vi@qF&Jjg*gO4QyIX6;j_KWL z!cx`KD$y*nsUlgU(a9y}BSE3Fs!9=d$Cvm3N=+#Bx=@jn#KbSqe2%L*=S&Q~qTd!P zW0iMO>H`5TolT4UOay3KW8!O*+a?As9Xl~gtEOvZ{oQ^B4ZgGiqJi&XlHM}A-f`?l zR|gpF-bzHJeb4(*ytlCuG$rA{wRFTz$&|cfYI@`9D%xkr(pu6mKOcN*Hq6LZEWF~s;$^Pks0!du-r-zhL}jlb1Sz3gLJ8 z$}e+h_Lk7FKa>HFtz4s3PZ12T_v4TXA{R@&p7d)#d7F70Dx8@%)b#9w-Q)dz^KFt# zEgd7TagbE==iNr^5P=xK6i#y%^86k)YewHO?5 z8`yi%i6gf5*F)853OI)B>}_KJN-hDB<2J6PE|MrtmtgA?MAJ{AoGps`Ly!0T_!U0!pp9367I20Gg1ea%mTfwM zJzJ7P%t@H$hr)@ID?%(hx;CKas*{JC+{rbgKO6@55B*FF3Je;e-<2X3gSChm8M>~% zuF%WEpLQ9p;iK#3bN8s?JD^6>lZ!h!3G8SydSW*3yLIkC9&$5{$UsD7WaNU?MOc-5 z3SEGTmlDv>S7MIQA^PbUHw4rx>3{yS_ho9SrTV3q?v;*#!8<;BWJM`4;6fab8^(Xe zaBFXgjqVA%=d7gBq9*QLW6qPr9bQdplQaU)m(x?b0A-2z-~!qo^yR{X)jQe|X*~Dw z8@B#A!K$En};zojpC1ll6!K zbbWn%3w-B}4nY)N(9o}hByC;~c1?yI?(S@^m>eyy%1{LaQ&Lirzad z;J2B%dA$w4$#(;m0)*AR}=Z>zTx+#_>_(rD!K=-<9#U>S$UN#n-_S`#Wsj+_*~H zhp1o9#$V`?Ws1WFB!z|s2M7E3)O&7D6q#8f7k@i`&7Y2nBP%P1jDmuLi=9l6+@aY_ zhKKgWx?}m50`>3l$pX-l&aAh>@{RrD%VbZ7dbE^)fSitw!Nej>RXKO%qynw}W@5w- z*Lk@X%CDtiY#noT?4=v6x;nP^wrMmwpmFt$^n$0}c^;c7v%RG!V(b?LG=CoDV(Br> zD5Pq3>x?_}t!&822x%%PL;p?wS@cD$pUsB(TW_+9h8k@(!eIKz>#F})n?>AABjH8$Ha+o=+u*my~#dLV<}+19Hl ziQAsAJ=)KHeGLBN;Vt{vG_)|{;lxCeT+s#ELh>c) zFXO#+bP`-tj%d&NEhg}+M+Q*NQMI)a0}``lY#2TiZ7r66V4yG4DO!eR7YXH1dC7?j zz_)*g%4@R&AE}m<1YR!EyxWt9&&kV4ODIQe7il-@%0phyt|LDgrkGY&vnM!I=f=zb zR%&C9$$GPM1zy5(m3W9Gujd$mmof057bJ{F*T1;@lU5>4C#yTfa?C$ml8^jCd$Vd- zAYObgWn?cpm|#?tPHgv}>2^9nc8e=--b?@}*>~*EsHng^aBCmEM1qBq0-jo%*#-M2 z)t-8ih9HEzy1%D*+BDc|DX{!Z0-os{5?V$Mjtw&&QhqNYLa$nNt+O;9xfHhv zM0># z930`cP+zX2ONrt6>ou?OdW^l&eq4F82aZVex&mRuQ^b8s(=@9?D+gR>T!243ZD(gM zzHWkn6q18`?0gpy%L`DBKAq@y2P$Jt*^ffkeT$O}bZ3V{D;Y24XlUQZHoJV$ej$^8 ziXd3lKG{0}hZ#c6n3U47NIB-N%ct{;kGXZA*Jnk2Pm$CZpbNCMwKB9B_u|`J^R80c z>-@0oaSpa?7Rz~fS!!z=L7#AxnHQJAn)MyLRfs@t>L&-~*mDbA|Hu_=3qam4rI4an zcmIWW88criIA*h-*!$c5bk<=2uGa{TMN7i(c274DeS&MqU3I{-kMf`%1?4+xl~eOZ zQDeGkwj zBYV?Dz{kfYhZ26Ox_w`JHWsteYA9NO{KnCe5AVyo?+UriweE)X8}7jI4Z6k>S*=`V95_jxc<6OX1gFE+#)@niK`?Y34x$}~Z)M)nC{C-iUosTX0+`Fsub>8yQ?gs8`}7=MVC1^1rqGUdPhwtrD9Fc8QCNg8vZI zvB$Cu-Q8Vt5vb#YdN^O}SFNTENJvOVbjp24Wz00z&#D1ZCBRp{*J(beuOhP4Xu96r z{h1nO`6WF)niuNfVQdZs{0l|n1-rMB)gTQRpAfDs>JFIXP z^7kGJqqn4J@IJ=g+TNNL`?)Nv)7 z`+mL{##iiU9RijkNfFd?%g{Rx*3C9Y%!_FSMK6-blSxTWUTC8)v)R}b9FLUF;Lnr{ z&CIBcw6y)!EHA=q@Ez2hqTa+-P*KY*$g^c;u@jXNy|}n&#c5?~+q%8EjxOd239E%) zg}J`1su6e%<-7D&6-R7*4H0)%o6qJD4u50m!g&{W6Bmsl2|syR>m3#4#zqPK{I@2I z4HIK)`HC1~6xbm+SXekM1ur2Z6im@LfpFY|gB_R+Kfti0@%q+=F%ydpu!#Qsr^@r` ztNZ(gf~cVLfXdVDogG*{i{c{NL9hdSkvQ$xTr{7{X)iv6Rf{Pj5g;NW(ghpvX0cNw zq^@9@Q~0VA%Stbsj6$$IaNd96H3DyhF^YG0NkLS4HMC#t%CDykozl!S2ftT99>znX z_xUivtC-&X*3$Lm9CfA;mZ+FChbY9RAkNFLc`7nFOGfQhe?WIp*smEB3DDtsEef}2Pn|c`eFwr8LD(V)TLfW?AZ!tYErPH`|4*|;%uN5D zfBIjBi#Y#@e;NW8LEs_?Tm*rOAaD@`E`q>C5V!~e7eU}62wVh#iy&|j1TKQWMG&|M z0vAExA_!arfr}t;5dVV>TV{4fNkda(69-ao zPQ=>6+D^$vSKp9P)X>RP-%wFPm{Hi&!Ct}8PQ=>M#@foz%7K)NQBK#=kntneKiRuw z=iuh&w|B5J)U`zT>@OBQERQLK)%`?qYhG`8p+m}S#uu4(k$+;qD|tQ!<%XtV>Qfn8 zUFok%|CNMY{4IsbZ?xZ}Qekn>>_9XOsI0NnB+fG<&$%wHq`eKgPBPTptwjz2P^5l zo&sl@fFZ(N-Kz}oHfOOKp?9QP3EToXNJ*RrHMSb}whA!Ulnh-0Q>y++owbNKHi&bKa#w^^owGtYV;SLH!RKj(3Q89xN{+;6(en z1MbZ{(Q`)>^A8N6^Wv2InN#>P4@t(Mab-=A+V%)#67RzUJCR(N3w(HP?(b2m%**jQ zq|o5g;G>LYG!^PeL{Hf8`}G(&>xdiEA3?A>sBuB(DSX+azX0y8P)-HM@HNcN!xsa{P-?c-s*X1Y({CuVQ|G&}up z2feFvlpEaO(yMs{Lo0(n$q+bat!IY7%<}h=EpBb)AZlo@Z)a-bU~R|veENrM3fY%)mF*l2m7gT6 zDm$cqh?e4C<$&!U<>0TS|C6Ns%R}_=lcT&^0z>RM541p7x(ruOg$Yp@FIHvle~+F6`Xw3@n`B zuDRHl8Nix~OABm5SUG@8+mkXshfh>wR5Y}=cC^zsv_*q`h%uxUN{zvc+?T_0ZnMwaNJbv)!&kFjlVHw3uEgTHNTHiv~!BEr?taS|; zWely19ZbO5l8NhY?2lA+S^GH-w8lNaK67e;smDoSLiho7mETO%B$>7?A873BkvOe*yb*&Q@b-m9;_~4__IvjcGqFUD5x#*@ zqB*Q7Ec$>P%@(VaE7S>h%u2ULicvg8gs^jADbT}D=Ne$fwp3L2U@E#|D(Xuu2OUY; z%6=^nk1?q(2+%8^YP;F?@D-=5+v0f;{DP1;AQ3wLr3`N&WbF&9FNyKd;r9utU3N{J zGewX=YFwvtOtZvVFHj_?On+NsakK{KJX<~CHqei2b|rrrW-$kyo9wFsiEyjiJXNit zmjTW;M>0chT5DC&Uap(3hMN4D1;4W$D~19w6;BtXT57%4vL5QWIvj(P&^EDDT>B5B z3zgcn_oAm5@~e3A6=;N_vA58KsQp#}E2@KWs-3@Cuj5}1lr0~!U1e5hOo3L2$c@#m z4Qb9S92E93o0X;CI#uP~Y$+#M){DU^Ryh*d8K^~2@vu{Qv!22OWN3q^dhI}}foxR)cOlCwAC z=5bAS2S6*KaL79pJpexq^?pI`sZVy&$PvZtpK0-|!}Mw`jOaOAB)roK^!t@JMgsw|@zVn;$MpmDRhOAE zuw*x%Ik0hZtT|0Y)LtD`{1nerp1$*H9U579o#mg%N5%M%PfuzOJPM#GuLG&msob4> zF0I*Te5bl2|A~FKiUb66zOv%SFVjagtkiC)MQa4Km+FDF{Y9PJ<}83S5ZGzeUR^UM ziXC>zn}qW?^+R|k#_oe9G0R4_L8ha(eC2yuZgvqunMu^N{dgbqf+Lk;BZXc~*T@iu z$gut_HwB_AceOj)ITxI?=73_fqHF_pl&Y~z(b6&}wPDRsVjC+&6}&l9Kh@WnyN`r!p}M896{ zRTSu6Xla>M@w2SXB)`!aI_W7@SnSsD8Z^6M2iR!D)5%xAJoMOSviN#oA@%*usceoP zHWf`?Xl|u;I1OtJZ3?HFmnaQf1pRTEMoE6RmbNAWw+s;^4jqD{48{=k9pO#>6yXMjpHOdqI%7etk!Roy zURF^uN*{=iy|~+h#+#H7Rl76u&Wm$+Ro0D`Kiy)2r$9~)b+2!zWozxP^!Z+(^N!t9Yw~V5L)j(my*Q5l0_Cv;{9hA=;B}*?V9v?Dt#qOEoAj zeC20RiYH?G9ZPuawCy#bT#-#S8!<00uN>yi9O0HPxg)g`VRO{xBrD3V6Cu{YZ6LAEL@FRvqCI|{xz`s?f{i0e>|F{sFII0FbN)AC(y`I}vkMhtzWUA>Jh3*nZ+z&&vOPe~oHxDURq0 zYpgOQdaZyl3#HMT{JvtxQ#asXxYP3!|IMg|>ipX`V|nW@3Q?0`jNpCZ&iGjb9`NN_ zwqhibI1#)~7E=8$W;J8a_u$CjmfH0*1@uv7A29=mlWM=Js%4_Ys$wG9DH1)n1t#-xxWF@@#fC+uRCxEwAXqf%!vU}m1cTI^|)z4*G4Z}P~4(rZqu{CS& z)ZvPPwp0W5$=fR%K>P4UIg>hGNP~8)8$#;U6^0_4m*Kq!L`Wfm*`4Rwq0>5jQ z#e)ZL+eDbf|H;zrKgFx~KQS$y-=6aCnHJzRz<-z)tfbGT4%qPcThrpNZ)N#kn-(nK zMbh7y7ISuUTo|5Hfb)I}3Ke2lT;LBU5({a&=o5p=_~?S-KE!uIIGpb?S17`-A}%fY zZ3%w+XBfhl(xFC~rtyb~UkPPz2*8oOd>E!8hLR(ddc_zXgNJidkBSsvc6`*D?&tD^ zas6q#jQaH*WJG?qG?q&?7zNeMya_jyK_62);Gt;TqbHIQJh<{v=qy&ruAEQG|DYdmP|h0-2to#fHyM>UTl<98I!0N6+zACE+W zg7x@^rCo9|CGp^jd#(yrt2~8lx!>Np+zcpFyX(I-xsKt>e^!W&jd8t+j{VZDZN4Pf zwIjKKwt-z!ofA##s}-ddMN7lB;s9B}6NMyhMSc$p`DBzrLz&5ZUa|stXx7wX1R3)RTOHAGSuvedUqB$qz)rTh&DNj6hIB(3T)d~_ zE3UeMdiGI*iAbWZI!}DtyneLSH^;yN&aN%kg9+l8VJIcwbrQu-CoKk)*!05}W-&kDU~Xyd zcARcE2T&>T4j{5xo4Bqh+M1K1&JalbqXNM|#A+bx&IHwx8nl_s>9Wv^WE!?b6(qPwQ!Fi_*l~$E!&8y>&l!yF{jaZN^8;|#4}2X<@!UxE;8>}ig2%^jd~e2 zAKqmP512t%^}Gurnb-f}HaA{Zi&w2BO5@2m6Jhaa8lZQCA0`_{kZtiJm|5?HO2*EL}bM^Eu|YlOGY;O8II zT!V|rb@}_p6Sl)0mHB6Z{cm<13U2&ugVS%Q{i0SkV^KpRtg9RJgcZ^cI42QCC|TP) z-Pw9)VN!f$#hi6P?V>)1S0QMuaB;y8C?p*$tchZ$C`YRjr4gK|g+MeX)H+b}D?2#f z6zlhjX`B;cmC9Ixv=Np(qm!ha)$y#6ne(w3A|*y{kz75Ae&|3$@Y;UKbM}&**S;siKf>YU^nu_Dae> zd`DqqZO(u(S?}V4-tlEW+ug&`n0JTH2A_M3WY}mx)=tR;$dS}e!a8S!!0stGDjkE> zJG-Thlq$lqMhNC){@~mo%**I>V&#B+JwIP_A$e2bXgCXs+J7hDOJke3m#f2&p=&ny zB3Ef~`sr+e4v7N{BSP4DW+?S_=RNCrbJxPE7zDF3BLP9M=*V1j2;en~L5$;E=*xm) z*TFoAT~<~UA04#*0WCC*Vqn=y@EC5~hM0Czwd~1>7x5~kH_<>H%s)xysxj?D)bWe< zrBUF`n(1;GD0d{L(ftbVI_*8iHY!UWeS_jU(te)bwA>K$IjU1%g z?0%M5Ey(v0Jc*#mHjQLx3FH4HrE2VNQ1sF4!X{xi(3W_>Dr5L3cd@>BQs5|A*AT*^_U_gey7z~_#> zGklH2=Z2<{$4KI#SD1Uxz5#i*zn8pxhOb?01c)U)cK25LLXuQ)a080;xNm-=B8Biyyv?A;X3LF^NF7 z3`C=p{+2*;N}CDG#%z91^zdlFX~qP)t;oY#`CK79P_6?HcHO`uo$=`a{aL=t2(ryU z6EayW=52*EHu(J5H^*ZNcPzW8s}bC~n2Cn`8ds#A6V7@3CBP*ohLjFK3GZxclo;|o z9la3yAR-g3Xa3nXx-YyY1U`MeBCW}HLA3a`;}Zms&2o@)C&SnK(G-7pD$>(b=8_tt*A^rA^@_&OHh8+m`^9wi=* z2xHb0vBoebdqW;#b4J>hQW5m=fbOp^py{BsT+bHqD(d#miQ!SPPcL4+y71;Qr$`D% z`RQKTDp4i{grTcla+jVy3=}5CHC|#4rIz*g?wo&$O?L0RA8CyNYHe%|vWm?!=wLE- z?K@;)=*l9;BxnNMSJbipjQyRa(~YJ*N+-F>1^`Us|2RN>1r=Bye;S6vj-&U}rE%pY zwWDCzarj)Asw7}%(1-_lJF|u#}ih|;Zig6vU4~Ag*xmnl|AFt80~4dP&5K8 zbfzc#0^i(%oc#G-BMWMU<>ZQR${FxS=67D&%mUn~G%x9_IEg^M{qq%jH$Q>STxOFVnco(tK$IX7|vhO~+8yW33|pL37Ac46!n3=Z`Ut6752vj;ov#O7AARw*kSarqa;WXM5Tu695KLU@8{O^+?aMNa)kp5q z3){z5)F;n+rn&Qh+^t8P0-`BJ1!+Kk{dHO2dCx3CmX0Eh3u4KLQo8%i3HC!W>XRok zhS8XBtC1TSL0@C%b=%)h?OGP(qIO;roN&z4Tog=NEf;LY%n&Q3J4&;+MASxtxeo=` zi^F}+@unUQ(-B&=iT>ckM4l20kng0dJeG{e&50MtQw`FJUgF zbl*VUq$+7RJUKaUu7c9w*)b42x8(Mv;y{Sro)Ohs8@ee!QLR>&A(%@l^Kc6g6dn8#Q`b zk8%f(Ar``G^2@7(+GgPcoolldQ34`TOKT1@2R}{$tA3wmQD8bg5JRlsTROjL{-V~6 zy`=PY5F*9?XEmGYYfJmhUG27YW`%^<81MLR;a%c4Iu-FOy>*Y-13Vc3PZp6?-LhTB zEo~Z4aP9C9Rx!Nilh~ZvKF=*-Mf2ijO#|aP*V&KTlMS^eZ-_?3FP{H5?fQ;i_a=(?f=MFh6 z5t++u-r|Xg<>R}~q*H2fQ34>Ga5)J$=2N~+hGQNPfV7;jvtIDQBK$G-Y$IQq2%oq6 z3(5iVCQ=4;$~exHJVYYgsJS(2QBTlMcvxB3E)W#X>PylcN*nKM)~fI>4dBnLe7>$) z30qud+T^VNK=11+YE6eEVMSo?GIzsHm#y9{FarMU@*y|fRHVeNelt0LLB#FB$e9?; z;6b{e^2R7m5q+V%d&zEIgFKeDp*#t$CB>(j$CR}r<`U!J*2lrft#HjYN<*( zgxL4+#b@SSfcrjWdjsbrau&xWFmGPtWK~I{$J*RYBg^sZ-9$PD(pt^?dr2F;oqrmU z(IQQ?G*0(sJ+NkJ$A8(5(wK3Dv0&4VYcQzya37lER_PohVIN*VazJ49hs5fJ1W`QN zm0p~srj1T&ec$GUM^6!LDvE0)rGElbP;xKcln$xa@w;_t;%8EmwgAw<8x- zTlNO@GT?|Bfl&8@j_g^7=d}3<$UMQ6&B383i@e05JV<%<0h^kt4m2SKGlsWxurzL* z!$#NERFuQId|>W%(bY`GJ-waC(!`CA+iLDoa=GI(h@9+nc1iS7D{iAAes1cSg%sf* z2NdOhJ|A=MeGxffZoDMr@CUEQjR&;#qim-Trlu->$URL{otVz(>MQ0iLe+!!B}vfp(i|FKDY z8pza|)R;{C*%yAQvgPZ28ZwzbvLx-Kq(f+{rtaV|-SVQ>)@SECNw+ZH}5%$VmX#c6mGa_4>yOhJG_&H>o~ORu~-3E{zdfXqg(GpM5; zpnQ=Fl#l2zUj@IDDuVHkuovVRwyNS|g}ft8Bzqn^^Yu&N;-0q-}a^NE4;WY0kAecJU1cpV6OD8Z?$y#E>7t(Sagb z;6oSs@2?kMceQ3FDkQ3(8t@Sqd84@!v_@2Gf$S@L)yJ{dx{N8Erl>OY82<5l~(seh4lLi?oW&e*v=I2n_%L literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093115480/elmhurst_worksheet.pdf b/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093115480/elmhurst_worksheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dd214b03ce47b61fc8ab0a8d028ea63445b622fb GIT binary patch literal 42906 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 literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093412452/elmhurst_summary.pdf b/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093412452/elmhurst_summary.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0cdfe5af5866e7e2c89755efef008505bf7d36d5 GIT binary patch literal 63525 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 W)7IVduP*`$k(H69=I2+}z5Xx81sA>m literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093412452/elmhurst_worksheet.pdf b/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093412452/elmhurst_worksheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..818e3b5079ba056d99629892982b882f19dce03e GIT binary patch literal 43131 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 O=>h;zXlbQYW&Q)UQ>|hE literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093718424/elmhurst_summary.pdf b/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093718424/elmhurst_summary.pdf new file mode 100644 index 0000000000000000000000000000000000000000..01f488b6a339b14b852710262af6198a78dcb41e GIT binary patch literal 63520 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 T|N0}~!V(Bk02h~vw(5TYQ#%A~ literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093718424/elmhurst_worksheet.pdf b/backend/epc_api/json_samples/real_life_examples/SAP-Schema-17.1/uprn_10093718424/elmhurst_worksheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9e212df0ac058e799711ef5875bb8202f97b77f2 GIT binary patch literal 43160 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 None: + # Arrange — electric immersion DHW (WHC 903) on a Dual / Economy-7 (7-hour) + # meter with a cylinder present (Normal / 110 L), but the cert does NOT lodge + # `immersion_heating_type`. RdSAP 10 §10.5 (PDF p.54) assumes a DUAL immersion + # on a dual / off-peak meter, so the hot-water scalar rate must be the SAP + # 10.2 Table 13 DUAL high/low blend (a small high-rate fraction at 15.29 p + + # the remainder at 5.50 p), NOT the 100%-low-rate (5.50 p) fallback that an + # unlodged type previously triggered. The fallback under-costs the DHW and + # over-rates the dwelling — cf. uprn_10022893721, whose Elmhurst worksheet + # bills the immersion at a 0.1386 high-rate fraction, not 100% low. + from dataclasses import replace + + main = MainHeatingDetail( + has_fghrs=False, + main_fuel_type=29, # electricity + heat_emitter_type=0, + emitter_temperature=1, + main_heating_control=2401, + main_heating_category=7, # electric storage heaters + sap_main_heating_code=401, + ) + base = make_minimal_sap10_epc( + total_floor_area_m2=_TYPICAL_TFA_M2, + habitable_rooms_count=4, + country_code="ENG", + has_hot_water_cylinder=True, + sap_building_parts=[make_building_part(construction_age_band="E")], + sap_heating=make_sap_heating( + main_heating_details=[main], + water_heating_code=903, # electric immersion + water_heating_fuel=29, # off-peak electricity + cylinder_size=2, # Normal / 110 L + immersion_heating_type=None, # UNLODGED -> assume dual on off-peak + ), + ) + epc = replace( + base, + sap_energy_source=replace(base.sap_energy_source, meter_type="1"), # Dual + ) + + # Act + inputs = cert_to_inputs(epc) + + # Assert — the Table 13 DUAL blend applied, not the 100%-low-rate fallback. + rate = inputs.hot_water_fuel_cost_gbp_per_kwh + assert rate > 0.0550 + 1e-6 # NOT the 100%-low-rate bug value (5.50 p/kWh) + assert rate < 0.0900 # a small DUAL high-rate fraction, not single (~11 p) + + def test_non_integrated_storage_heater_bills_100_percent_low_rate() -> None: # Arrange — same off-peak storage cert but SAP code 401 ("other storage # heaters"): Table 12a Grid 1 gives a 0.00 high-rate fraction → the heat diff --git a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py index 7deb44494..935715a89 100644 --- a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py +++ b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py @@ -364,6 +364,152 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = ( cert_num="0646-3008-6208-0619-6204", sap_score=78, ), + # UPRN 100090182288 → cert 0068-1092-7237-0157-9954. SAP-Schema-16.2 — + # reduced-field SEMI-DETACHED HOUSE, 2-storey, band D (1950-1966), filled + # cavity, pitched roof 250 mm, solid uninsulated floor, mains-gas COMBI (PCDB + # 10327 Vaillant Ecotec Plus 831), double glazed, TFA 73.4 m². Lodged 71; + # engine 71. Built in Elmhurst RdSAP10 (evidence saved): Elmhurst worksheet 69. + # The +2 (engine 71 vs Elmhurst 69) is the documented 16.2 reduced-field + # PARTY-WALL gap: gov-API 16.2 lodges no party_wall_length → the engine models + # NO party wall, but Elmhurst requires one for a semi (entered the geometry- + # derived 4.28 m, filled cavity). Calculator confirmed faithful: engine on + # Elmhurst's own parsed inputs (which include the party wall) = 69.35 ≈ worksheet + # 69. PINNED to the observed 71 (= lodged) — mapping untuned; the Elmhurst delta + # is the reduced-field missing-party-wall-data gap, not a calculator bug. + RealCertExpectation( + schema="SAP-Schema-16.2", + sample="uprn_100090182288", + cert_num="0068-1092-7237-0157-9954", + sap_score=71, + ), + # UPRN 100021985993 → cert 9658-2087-7277-0667-1910. SAP-Schema-16.2 — + # reduced-field END-TERRACE BUNGALOW, single-storey, band C (1930-1949), + # solid-brick internal insulation, pitched roof 100 mm loft, suspended + # uninsulated floor, mains-gas COMBI (PCDB 10328 Vaillant Ecotec Pro 28), + # double glazed, TFA 76.54 m². Lodged 70; engine 74. Built in Elmhurst RdSAP10 + # (evidence saved): Elmhurst worksheet 72. The +2 (engine 74 vs Elmhurst 72) is + # the documented 16.2 reduced-field PARTY-WALL gap: gov-API 16.2 lodges no + # party_wall_length → the engine models NO party wall, but Elmhurst requires one + # for an end-terrace (entered the geometry-derived 6.89 m, solid masonry; the + # worksheet's only extra heat-loss element is the party wall at 16.19 m² × U0.25 + # = 4.05 W/K ≈ the 2-SAP gap). Calculator faithful: engine-on-gov-API 74 minus + # that omitted party-wall heat loss ≈ 72 = worksheet. (Engine on Elmhurst's + # PDF-parsed inputs = 67 here — lower than the worksheet — is PDF-parser noise: + # hot water over-parsed to 2998 kWh/£684; not a calculator divergence.) PINNED + # to the observed 74 — mapping untuned; the Elmhurst delta is the reduced-field + # missing-party-wall-data gap, not a calculator bug. + RealCertExpectation( + schema="SAP-Schema-16.2", + sample="uprn_100021985993", + cert_num="9658-2087-7277-0667-1910", + sap_score=74, + ), + # UPRN 10091568921 → cert 8806-5635-1239-5807-8283. SAP-Schema-17.1 — full-SAP + # END-TERRACE HOUSE, 2-storey, 2018 (band L), measured U walls 0.18 / roof 0.10 + # / floor 0.15, mains-gas COMBI (PCDB 17615 Potterton Promax Ultra Combi 28, + # 88.5%), double glazed, party wall 40.56 m², natural vent + 3 extract fans, + # AP50 4.45 (tested), 20 LED outlets, TFA 78.7 m². has_hot_water_cylinder lodged + # true but cylinder detail all None + PCDB combi → built as combi (water from + # primary). Lodged 85; engine 82. Built in Elmhurst RdSAP10 (evidence saved): + # worksheet 80. The +2 (engine 82 vs Elmhurst 80) is the documented full-SAP→ + # RdSAP residual (cert's measured U-values beat RdSAP band-L age defaults). + # Calculator confirmed faithful: engine on Elmhurst's own parsed inputs = 80.16 + # ≈ worksheet 80. PINNED to the observed 82 — mapping untuned. (Control: cert + # 2110 time+temp zone vs Elmhurst CBE 2106; sub-0.5 SAP, in the noise.) + RealCertExpectation( + schema="SAP-Schema-17.1", + sample="uprn_10091568921", + cert_num="8806-5635-1239-5807-8283", + sap_score=82, + ), + # UPRN 10093718424 → cert 0369-3892-7678-2690-7475. SAP-Schema-17.1 — full-SAP + # SEMI-DETACHED HOUSE, 2-storey, band L, measured U walls 0.25 / roof 0.13 / + # floor 0.16, mains-gas COMBI (PCDB 17615 Potterton Promax Ultra Combi 28), + # double glazed, party wall 40.89 m², natural vent + 3 extract fans, AP50 4.18 + # (tested), 20 LED outlets, TFA 79.9 m². Sibling of uprn_10091568921 (same + # development/boiler); built combi (cylinder lodged true but combi PCDB). + # Lodged 84; engine 81. Built in Elmhurst RdSAP10 (evidence saved): worksheet + # 80. The +1 (engine 81 vs Elmhurst 80) is the documented full-SAP→RdSAP + # residual. Calculator faithful: engine on Elmhurst's own parsed inputs = 80.12 + # ≈ worksheet 80. PINNED to the observed 81 — mapping untuned. + RealCertExpectation( + schema="SAP-Schema-17.1", + sample="uprn_10093718424", + cert_num="0369-3892-7678-2690-7475", + sap_score=81, + ), + # UPRN 10022893721 → cert 8078-7422-5930-5662-8922. RdSAP-Schema-18.0 — GROUND- + # FLOOR FLAT, band I (1996-2002), cavity insulated, ELECTRIC STORAGE HEATERS + # (SAP code 402 = SEB Modern slimline, manual charge control 2401) + electric + # immersion off-peak (dual-rate) hot water with cylinder (Normal/110 L, foam + # 50 mm), party wall 21.48 m, TFA 54.29. First non-boiler corpus cert. Lodged + # 79; engine 79. Built in Elmhurst RdSAP10 (evidence saved): worksheet 81 — + # engine -2, within tolerance; engine on Elmhurst's own parsed inputs = 78.76 ≈ + # engine 79 → calculator faithful. The Economy-7 off-peak pricing is CORRECT + # (Table 12a/13 split, fixed in PR #1217): storage SH high-rate fraction 0.00 → + # 100% low rate is the spec value, and immersion HW takes the Table 13 blend. + # (An earlier build mistakenly left Elmhurst's meter on Single/Standard, pricing + # at 13.19p → bogus worksheet 66; corrected by setting the Dual meter. NOT an + # engine bug.) PINNED to the observed engine 79 = lodged. + RealCertExpectation( + schema="RdSAP-Schema-18.0", + sample="uprn_10022893721", + cert_num="8078-7422-5930-5662-8922", + sap_score=79, + ), + # UPRN 10023443426 → cert 4106-3336-4002-1402-2202. RdSAP-Schema-21.0.1 (the + # engine's NATIVE schema) — END-TERRACE HOUSE, 2-storey, band L, cavity + # insulated, mains-gas COMBI (PCDB 17045 Ideal Logic Combi ES35), 300 mm loft, + # solid insulated floor, party wall 9.2 m (lodged), 11 double-glazed windows + # (~10.3 m²), 9 LED, mains-gas room-heater SECONDARY (SAP code 612, seasonal + # efficiency 0.20 per Table 4a — a low-efficiency decorative/old gas fire), + # TFA 98. Engine 76 = lodged 76 EXACTLY (and reproduces every component: space + # 6129, main fuel 6247, HW 2752, CO2 2232 kWh/kg) — the authoritative validation + # for a native-schema cert. Built in Elmhurst RdSAP10 (evidence saved): + # worksheet 79. The +3 is a BUILD gap, not a calculator error — my Elmhurst + # build omitted the lodged secondary gas fire (engine models it at 3065 kWh = + # 0.1 fraction ÷ 0.20 eff; Elmhurst secondary 0). engine on Elmhurst's own + # parsed inputs (also no secondary) = 79.29 ≈ worksheet 79 → calculator faithful + # to its inputs. PINNED to the observed engine 76 = lodged; mapping untuned. + RealCertExpectation( + schema="RdSAP-Schema-21.0.1", + sample="uprn_10023443426", + cert_num="4106-3336-4002-1402-2202", + sap_score=76, + ), + # UPRN 10093412452 → cert 8306-7575-5832-6507-9803. SAP-Schema-17.1 — full-SAP + # END-TERRACE HOUSE, 2-storey, band L, measured U walls 0.25 / roof 0.13 / + # floor 0.16, mains-gas COMBI (PCDB 17615 Potterton Promax Ultra Combi 28), + # double glazed (~12.3 m²), party wall 41.01 m², natural vent + 3 extract fans, + # AP50 4.62, 20 LED, no secondary, TFA 79.8 m². Same Emsworth development as the + # 10091568921 / 10093718424 siblings (cylinder lodged true but combi PCDB → + # built combi). Lodged 84; engine 81. Built in Elmhurst RdSAP10 (evidence + # saved): worksheet 80. The +1 (engine 81 vs Elmhurst 80) is the documented + # full-SAP→RdSAP residual; engine on Elmhurst's own parsed inputs = 79.91 ≈ + # worksheet 80 → calculator faithful. PINNED to the observed 81 — mapping + # untuned. + RealCertExpectation( + schema="SAP-Schema-17.1", + sample="uprn_10093412452", + cert_num="8306-7575-5832-6507-9803", + sap_score=81, + ), + # UPRN 10093115480 → cert 8393-7438-5230-3319-1996. SAP-Schema-17.1 — full-SAP + # END-TERRACE BUNGALOW (single-storey), 2016 (band L), measured U walls 0.19 / + # roof 0.11 / floor 0.12, mains-gas COMBI (PCDB 16841 Vaillant ecoTEC plus 824), + # double glazed (~11.9 m²), party wall 10.99 m² (CF filled, U≈0), natural vent + + # 2 extract fans, AP50 3.85, 9 LED, TFA 56 m². Lodged 81; engine 81 (EXACT + # match). Built in Elmhurst RdSAP10 (evidence saved): worksheet 78. The +3 + # (engine 81 vs Elmhurst 78) is the documented full-SAP→RdSAP residual — the + # cert's measured U-values beat Elmhurst's RdSAP band-L age defaults; engine on + # Elmhurst's own parsed inputs = 78.29 ≈ worksheet 78 → calculator faithful. + # PINNED to the observed engine 81 = lodged — mapping untuned. + RealCertExpectation( + schema="SAP-Schema-17.1", + sample="uprn_10093115480", + cert_num="8393-7438-5230-3319-1996", + sap_score=81, + ), ) From a3e25663789b4f56e959aefc7a618dcb827a64c8 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Sat, 20 Jun 2026 12:57:54 +0000 Subject: [PATCH 082/340] landlord override data added --- .../property_overlays/roof_type_overlay.py | 61 +++++++++---- .../property_overlays/wall_type_overlay.py | 8 ++ domain/modelling/simulation.py | 6 ++ .../property/landlord_override_overlays.py | 21 +++-- scripts/hyde/build_property_overrides.py | 46 +++++++++- scripts/hyde/elmhurst_lib.py | 85 +++++++++++++++++++ tests/domain/epc/test_roof_type_overlay.py | 69 ++++++++++++++- tests/domain/epc/test_wall_type_overlay.py | 14 ++- .../modelling/test_overlay_applicator.py | 21 +++++ 9 files changed, 299 insertions(+), 32 deletions(-) diff --git a/domain/epc/property_overlays/roof_type_overlay.py b/domain/epc/property_overlays/roof_type_overlay.py index 888379889..aadcc3878 100644 --- a/domain/epc/property_overlays/roof_type_overlay.py +++ b/domain/epc/property_overlays/roof_type_overlay.py @@ -1,12 +1,21 @@ -"""Map a Landlord-Override `RoofType` value to a roof Simulation Overlay (ADR-0032). +"""Map a Landlord-Override `RoofType` value to a roof Simulation Overlay (ADR-0032/0033). -The calculator derives the roof U-value from the building part's loft-insulation -depth, so a `roof_type` override moves the score only via -`BuildingPartOverlay.roof_insulation_thickness` (mm). The resolvable family is -the explicit `"Pitched, N mm loft insulation"` values — N is parsed out. -Everything else (flat roofs, room-in-roof, "Unknown loft insulation", -"Another Premises Above" — a flat with a dwelling above, no roof to insulate) has -no clean loft depth, so it produces no overlay. +Two resolvable families: + +* `"Pitched, N mm loft insulation"` — the loft depth N maps to + `BuildingPartOverlay.roof_insulation_thickness` (mm); the calculator scores the + roof from that depth. +* `"Flat, …"` — a flat roof carries no loft depth, so its U-value comes from the + age-band default (`_FLAT_ROOF_BY_AGE`, ADR-0033). The calculator's flat path + keys on the `roof_construction_type` *string* (`"flat" in …`), so the overlay + sets that to `"Flat"` and leaves thickness `None` for the (separately overlaid) + construction age band to drive the U-value. No flat `RoofType` value carries an + explicit mm depth, confirmed by the Elmhurst sweep (As Built / Unknown ≡ + age-band default). + +Everything else (room-in-roof, "Unknown loft insulation", party-ceiling adjacency +markers like "Another Premises Above") has no clean depth or shape correction, so +it produces no overlay. """ from __future__ import annotations @@ -23,8 +32,8 @@ _LOFT_MM = re.compile(r"(\d+)\+?\s*mm loft insulation") def roof_overlay_for( roof_type_value: str, building_part: int ) -> Optional[EpcSimulation]: - match = _LOFT_MM.search(roof_type_value) - if match is None: + overlay = _overlay_for(roof_type_value) + if overlay is None: return None identifier = ( @@ -32,10 +41,28 @@ def roof_overlay_for( if building_part == 0 else BuildingPartIdentifier.extension(building_part) ) - return EpcSimulation( - building_parts={ - identifier: BuildingPartOverlay( - roof_insulation_thickness=int(match.group(1)) - ) - } - ) + return EpcSimulation(building_parts={identifier: overlay}) + + +def _overlay_for(roof_type_value: str) -> Optional[BuildingPartOverlay]: + match = _LOFT_MM.search(roof_type_value) + if match is not None: + return BuildingPartOverlay(roof_insulation_thickness=int(match.group(1))) + if roof_type_value.startswith("Flat,"): + # Flat roof: U-value is the age-band default; flag the shape and let the + # age-band overlay drive `_FLAT_ROOF_BY_AGE` (ADR-0033). + return BuildingPartOverlay(roof_construction_type="Flat") + if roof_type_value == "Pitched, Unknown loft insulation": + # Unknown loft depth: U-value is the pitched age-band default. Assert the + # pitched shape (keeps `is_flat_roof`/`is_sloping_ceiling` False) and leave + # thickness None so the age-band overlay drives `_ROOF_BY_AGE` (ADR-0033). + return BuildingPartOverlay(roof_construction_type="Pitched") + if roof_type_value.startswith("Pitched, no insulation"): + # Genuinely uninsulated pitched roof → 2.30 (Table 16 row 0). Thickness 0 + # drives the calculator's thickness path and, unlike "Unknown", overrides + # any lodged numeric thickness (ADR-0033). Pitched text is load-bearing + # here — it does NOT take the age-band default (rdsap_uvalues §5.11 note). + return BuildingPartOverlay( + roof_construction_type="Pitched", roof_insulation_thickness=0 + ) + return None diff --git a/domain/epc/property_overlays/wall_type_overlay.py b/domain/epc/property_overlays/wall_type_overlay.py index 63ac6aa0c..0620819cf 100644 --- a/domain/epc/property_overlays/wall_type_overlay.py +++ b/domain/epc/property_overlays/wall_type_overlay.py @@ -34,8 +34,16 @@ _MATERIAL_CONSTRUCTION: dict[str, int] = { # RdSAP `wall_insulation_type` codes by insulation-state suffix # (domain/sap10_ml/rdsap_uvalues.py): external 1, filled-cavity 2, internal 3, # as-built/uninsulated 4, cavity+external 6, cavity+internal 7. +# All three "as built (assumed)" variants resolve to the same as-built code (4); +# the construction-age-band overlay supplies the U-value, so "partial" / "insulated" +# need no distinct code (ADR-0033, confirmed by the full A–M Elmhurst sweep in +# scripts/hyde/uvalue_probe_walls.csv — cavity As Built ≡ (CAVITY,0) by age band, +# e.g. band F → 1.0 = "partial"). The bare "as built" covers Cob / Park home. _STATE_INSULATION: dict[str, int] = { + "as built": 4, "as built, no insulation (assumed)": 4, + "as built, partial insulation (assumed)": 4, + "as built, insulated (assumed)": 4, "with internal insulation": 3, "with external insulation": 1, "filled cavity": 2, diff --git a/domain/modelling/simulation.py b/domain/modelling/simulation.py index caee5fb51..77ce25378 100644 --- a/domain/modelling/simulation.py +++ b/domain/modelling/simulation.py @@ -41,6 +41,12 @@ class BuildingPartOverlay: # IWI (`wall_insulation_type=3`); λ defaults to 0.04 W/m·K in the calculator. wall_insulation_thickness: Optional[int] = None roof_insulation_thickness: Optional[int] = None + # The roof shape string the calculator's flat-roof path keys on + # (`heat_transmission.py`: `"flat" in roof_construction_type`). Left `None` + # by Measures (insulating a roof doesn't change its shape); set by a Landlord + # Override that corrects a roof to flat so the `_FLAT_ROOF_BY_AGE` age-band + # default applies (ADR-0033). Folds onto the part via the generic field loop. + roof_construction_type: Optional[str] = None floor_insulation_thickness: Optional[int] = None floor_insulation_type_str: Optional[str] = None diff --git a/repositories/property/landlord_override_overlays.py b/repositories/property/landlord_override_overlays.py index 6c40d009c..b65cf5d29 100644 --- a/repositories/property/landlord_override_overlays.py +++ b/repositories/property/landlord_override_overlays.py @@ -7,18 +7,23 @@ type — `domain/` never imports `repositories/`. Per-component and partial — an override produces an overlay only where a component mapping exists and the value resolves; anything else is left to the -lodged EPC. All four `override_component`s are mapped: +lodged EPC. Every `override_component` is mapped: * `wall_type` → fabric overlay (`wall_construction` + `wall_insulation_type`) -* `roof_type` → fabric overlay (`roof_insulation_thickness`, loft-depth family) +* `roof_type` → fabric overlay (`roof_insulation_thickness` / `roof_construction_type`) +* `construction_age_band` → per-part `construction_age_band` — the U-value lever + for the "as built / assumed / Unknown" fabric states (ADR-0033) * `property_type` / `built_form_type` → whole-dwelling categorical correction +* `main_fuel` / `glazing` / `water_heating` / `main_heating_system` → whole-dwelling -Two value families deliberately resolve to *no* overlay rather than a guess: the -`"(assumed) insulated"` / `"partial insulation (assumed)"` wall states (RdSAP -infers their U-value from the build-era age band, so there is no single -`wall_insulation_type` code for them — they need Elmhurst validation, ADR-0032), -and `"Unknown"` categorical values. Roofs with no clean loft depth (flat, -room-in-roof, "another premises above") likewise produce no overlay. +Since ADR-0033 the "as built (assumed)" wall states resolve to the as-built code +(4) and flat / pitched-Unknown / pitched-no-insulation roofs produce an overlay — +their U-value comes from the per-part `construction_age_band`, not a per-state +code. Values that still resolve to *no* overlay (left to the lodged EPC): bare +`"Unknown"` categoricals, party-ceiling roofs ("another/same dwelling above" — a +party ceiling has ≈0 heat loss), room-in-roof, and the residue logged in ADR-0033. +The `audit_override_coverage.py` / `audit_hyde_rows.py` pair reports exactly which +values are live vs no-op before any write. """ from __future__ import annotations diff --git a/scripts/hyde/build_property_overrides.py b/scripts/hyde/build_property_overrides.py index dc77b520f..4a554586b 100644 --- a/scripts/hyde/build_property_overrides.py +++ b/scripts/hyde/build_property_overrides.py @@ -111,6 +111,21 @@ logger = logging.getLogger("build_property_overrides") ORG_REF_COLUMN = "Organisation Reference" UNKNOWNS_PATH = "overrides_unknowns.csv" +# A "party ceiling" roof (another/same dwelling or premises above) means the +# dwelling has no exposed roof, which is only physically valid for a Flat or +# Maisonette. When the property type says House/Bungalow the two landlord fields +# contradict (a house has nothing above it). Per Khalim these may be houses split +# into flats — leave them entirely as-is for joint review, so we SKIP the whole +# property (write NO overrides for it) and record the org_refs for that review +# (ADR-0033; surfaced by scripts/hyde/audit_hyde_rows.py). +_PARTY_CEILING_ROOF_VALUES: frozenset[str] = frozenset({ + "(another dwelling above)", "(same dwelling above)", + "(other premises above)", "(another premises above)", + "Another Premises Above", +}) +_FLAT_PTYPE_PREFIXES: tuple[str, ...] = ("flat", "maisonette") +SKIPPED_PATH = "skipped_contradictory_properties.csv" + @dataclass(frozen=True) class ComponentSpec: @@ -330,15 +345,32 @@ def write(args: argparse.Namespace) -> None: org_ref_map = _org_ref_to_property_id(session, args.portfolio_id) logger.info("Portfolio %d: %d properties with org_ref.", args.portfolio_id, len(org_ref_map)) + ptype_header = _specs_by_component()["property_type"].excel_header + roof_header = _specs_by_component()["roof_type"].excel_header + roof_vocab = vocab.get("roof_type", {}) inserts: list[PropertyOverrideInsert] = [] unmatched: Counter[str] = Counter() unresolved: Counter[str] = Counter() + skipped: list[tuple[str, str, str]] = [] # (org_ref, property_type, roof) for row in rows: org_ref = str(row.get(ORG_REF_COLUMN, "")).strip() property_id = org_ref_map.get(org_ref) if property_id is None: unmatched[org_ref] += 1 continue + ptype_raw = str(row.get(ptype_header) or "").strip() + roof_raw = str(row.get(roof_header) or "").strip() + row_is_flat = ptype_raw.lower().startswith(_FLAT_PTYPE_PREFIXES) + roof_resolved = { + roof_vocab.get(_norm(e)) + for e in _split_entries(row.get(roof_header), True) + } + if not row_is_flat and roof_resolved & _PARTY_CEILING_ROOF_VALUES: + # Party-ceiling roof on a non-flat dwelling — contradictory source + # data (maybe a house split into flats). Leave the property fully + # as-is for joint review: write NO overrides, record it (see above). + skipped.append((org_ref, ptype_raw, roof_raw)) + continue for spec in _component_specs(): comp_vocab = vocab.get(spec.component, {}) for building_part, entry in enumerate( @@ -352,8 +384,18 @@ def write(args: argparse.Namespace) -> None: building_part=building_part, override_component=spec.component, override_value=value, original_spreadsheet_description=entry)) - logger.info("Built %d rows | %d unmatched org_refs | %d unresolved", - len(inserts), sum(unmatched.values()), sum(unresolved.values())) + logger.info("Built %d rows | %d unmatched org_refs | %d unresolved | " + "%d contradictory properties skipped (left as-is)", + len(inserts), sum(unmatched.values()), sum(unresolved.values()), + len(skipped)) + if skipped: + out = os.path.join(os.path.dirname(args.excel) or ".", SKIPPED_PATH) + with open(out, "w", newline="") as f: + w = csv.writer(f) + w.writerow(["org_ref", "property_type", "roof"]) + w.writerows(sorted(skipped)) + logger.info("Recorded %d skipped properties -> %s (for joint review)", + len(skipped), out) if unresolved: logger.info("Top unresolved (need apply-edits): %s", unresolved.most_common(10)) if not args.apply: diff --git a/scripts/hyde/elmhurst_lib.py b/scripts/hyde/elmhurst_lib.py index 088b7258e..0336cca3c 100644 --- a/scripts/hyde/elmhurst_lib.py +++ b/scripts/hyde/elmhurst_lib.py @@ -49,6 +49,7 @@ from __future__ import annotations import json import os +import re from contextlib import contextmanager from pathlib import Path from typing import Callable, Generator, Optional @@ -162,6 +163,30 @@ def set_select(page: Page, suffix: str, value: str, autopostback: bool = True) - loc.select_option(value) +def select_options(page: Page, suffix: str) -> list[tuple[str, str]]: + """Return [(value, visible_text), …] for the