diff --git a/tests/domain/epc_prediction/test_component_accuracy_gate.py b/tests/domain/epc_prediction/test_component_accuracy_gate.py index cc262c17..ce7105ef 100644 --- a/tests/domain/epc_prediction/test_component_accuracy_gate.py +++ b/tests/domain/epc_prediction/test_component_accuracy_gate.py @@ -56,6 +56,15 @@ _FIXTURE = Path(__file__).parents[3] / "tests" / "fixtures" / "epc_prediction" # new-build-vs-old-stock service mismatch on 1-2 targets each (heating_main_fuel # 0.9722->0.9394, water_heating_fuel ->0.9495, cylinder_insulation_type 0.6667-> # 0.3333) plus floor_area (+0.31 MAE). Tighten-only resumes from these values. +# +# has_pv re-baselined 0.9798->0.9697 when full-SAP lodged PV mapping landed +# (datatypes/epc/domain/mapper.py `_sap_17_1_pv_arrays`): full-SAP certs lodge +# their measured array under `sap_energy_source.pv_arrays`, which the schema +# dropped at parse, so the leave-one-out scorer's *actual* has_pv read False for +# every full-SAP PV dwelling. Carrying the array now reads the true has_pv=True, +# and one full-SAP target the similarity-weighted donors don't predict as PV +# tips the agreement 32/33 (the held-out actual is now correct — a ground-truth- +# method change, not a prediction-logic loosening). Tighten-only resumes here. _RATE_FLOORS: dict[str, float] = { "wall_construction": 0.9091, "wall_insulation_type": 0.8687, @@ -76,7 +85,7 @@ _RATE_FLOORS: dict[str, float] = { "floor_insulation": 0.9375, "has_room_in_roof": 0.9495, "modal_glazing_type": 0.8384, - "has_pv": 0.9798, + "has_pv": 0.9697, "solar_water_heating": 1.0000, } 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 5575384f..6988818d 100644 --- a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py +++ b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py @@ -122,12 +122,18 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = ( # (engine uses the cert's measured 0.19/0.11/0.11 U-values; Elmhurst uses # age-band L proxies + party-wall default) plus FGHRS (cert idx 60031) omitted # on BOTH sides (the engine can't yet model full-SAP FGHRS). PINNED TO THE - # OBSERVED 82, not lodged 84 — mapping deliberately untuned. + # OBSERVED 83 (was 82), not lodged 84 — mapping deliberately untuned. + # WAS 82 until the full-SAP electricity-tariff → RdSAP meter_type fix: this + # cert lodges energy_tariff=1 (standard), which the mapper previously passed + # through untranslated as RdSAP meter_type "1" — wrongly read as dual/Economy 7 + # and priced on the off-peak high/low split. Translating it to "single" (the + # correct standard tariff) re-prices its electricity at the flat rate, lifting + # this gas semi 82→83. No PV (sap_energy_source.pv_arrays absent). RealCertExpectation( schema="SAP-Schema-17.1", sample="uprn_10093116528", cert_num="8000-8495-2839-2607-9683", - sap_score=82, + sap_score=83, ), # UPRN 10093116543 → cert 8358-7436-5620-6889-0906. SAP-Schema-17.1 — a # FULL-SAP cert (2017 mains-gas COMBI semi, Emsworth), forced through the @@ -290,13 +296,18 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = ( # 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. + # + MEV priced as extract loss not heat recovery. PINNED to the observed 84 + # (was 82), still −1 vs lodged 85 — mapping untuned. + # WAS 82 until full-SAP lodged PV mapping landed: this cert lodges a 0.38 kWp + # array under sap_energy_source.pv_arrays (SE-facing, pitch 30°, unshaded) that + # the schema dropped at parse, so the Appendix-M generation credit was lost. + # Carrying it (mapper `_sap_17_1_pv_arrays`) credits the generation and lifts + # this flat 82→84, closing most of the gap to the lodged 85 the array explains. RealCertExpectation( schema="SAP-Schema-19.1.0", sample="uprn_10096028301", cert_num="0390-3321-6060-2405-7985", - sap_score=82, + sap_score=84, ), # 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 @@ -326,14 +337,20 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = ( # 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. + # (combi); MEV on; AP50 Blower Door 3.2; party wall 6.43 m entered. + # WAS 80 (engine == Elmhurst, both built WITHOUT PV) until full-SAP lodged PV + # mapping landed: this cert lodges a 0.48 kWp array under + # sap_energy_source.pv_arrays (SE-facing, pitch 30°, unshaded) the schema + # dropped at parse. Crediting it (mapper `_sap_17_1_pv_arrays`) closes the + # −2 gap exactly — the engine now reproduces the accredited lodged 82. The + # Elmhurst worksheet (80) omitted the PV (not entered in the RdSAP build), so + # the +2 over Elmhurst is the now-credited array, not a calculator drift. + # PINNED to the observed 82 == lodged 82 — mapping untuned. RealCertExpectation( schema="SAP-Schema-17.0", sample="uprn_10023444324", cert_num="8501-5064-6739-1407-0163", - sap_score=80, + sap_score=82, ), # 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), @@ -342,12 +359,24 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = ( # 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. + # another dwelling below). + # WAS 81 until full-SAP lodged PV mapping landed: this cert lodges the SAME + # 0.48 kWp array as its ground-floor sibling 10023444324 under + # sap_energy_source.pv_arrays (the block's roof PV apportioned to the flat on + # the lodged cert). Crediting it faithfully (mapper `_sap_17_1_pv_arrays`) + # lifts this flat 81→83. NOTE this lands +2 OVER the lodged 81 (and +1 over the + # Elmhurst worksheet 82) — unlike the ground-floor sibling whose pre-PV engine + # was 2 UNDER lodged so the same array closed the gap exactly. The mid-floor's + # pre-PV engine already matched lodged, so the credited array now overshoots: + # the lodged 81 does not appear to carry the array's full generation credit + # that SAP Appendix-M awards it. This is the documented full-SAP→RdSAP residual + # (faithful to the cert's lodged PV, not tuned to the lodged integer). PINNED + # to the observed 83 — mapping untuned. RealCertExpectation( schema="SAP-Schema-17.0", sample="uprn_10023444320", cert_num="0868-6045-7331-4376-0914", - sap_score=81, + sap_score=83, ), # 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