From 15d6b78149fc92c8ab168f21bb6466d530ba6f2e Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 21 May 2026 13:45:33 +0000 Subject: [PATCH] pcdb followup: e2e mapper-chain regression test for main_heating_index_number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pins the API JSON → EpcPropertyDataMapper → CalculatorInputs chain for the 4 corpus PCDB-listed golden certs. Asserts (a) `main_heating_index_number` survives the mapper hop, (b) `cert_to_inputs` resolves Table 105 record by that ID and applies the winter efficiency. Catches future regressions where a mapper change might drop the PCDB pointer silently. Confirms the API → domain → calculator chain works end-to-end without any new domain object field — `MainHeatingDetail.main_heating_index_number` has existed since schema 17_1 and all mapper paths from 17_1+ pass it through verbatim. Co-Authored-By: Claude Opus 4.7 --- .../sap/rdsap/tests/test_golden_fixtures.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py b/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py index 616e7d70..a61187cf 100644 --- a/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py +++ b/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py @@ -162,3 +162,52 @@ def test_golden_cert_stays_within_tolerance(expectation: _GoldenExpectation) -> f"±{_PE_TOLERANCE_KWH_PER_M2} (expected ≈{expectation.expected_pe_resid_kwh_per_m2:+.2f}). " f"Notes: {expectation.notes}" ) + + +# Cert 0390 lodges Firebird Boilers S 150-200 oil boiler at PCDB index_number +# 9005 (Table 105 winter eff 86.4%). End-to-end mapper → cert_to_inputs chain +# must surface that PCDB winter efficiency on `inputs.main_heating_efficiency` +# rather than falling back to the Table 4a oil-boiler category default. +_PCDB_CHAIN_EXPECTATIONS: tuple[tuple[str, int, float], ...] = ( + ("0390-2954-3640-2196-4175", 9005, 0.864), # Firebird oil PCDB-listed + ("7536-3827-0600-0600-0276", 17679, None), # Vaillant gas PCDB-listed + ("0300-2747-7640-2526-2135", 17992, None), # gas PCDB-listed + ("8135-1728-8500-0511-3296", 17702, None), # gas PCDB-listed +) + + +@pytest.mark.parametrize( + "cert_number, expected_pcdb_id, expected_winter_eff", + _PCDB_CHAIN_EXPECTATIONS, + ids=lambda v: v if isinstance(v, str) else "", +) +def test_api_to_domain_mapper_preserves_main_heating_index_number( + cert_number: str, expected_pcdb_id: int, expected_winter_eff: float | None +) -> None: + """The full API JSON → EpcPropertyData → CalculatorInputs chain must + preserve `main_heating_index_number` end-to-end so the PCDB precedence + cascade (Appendix D2.1) fires correctly. Pins: + + 1. EpcPropertyDataMapper.from_api_response surfaces the PCDB pointer + on `sap_heating.main_heating_details[0].main_heating_index_number`. + 2. cert_to_inputs resolves Table 105 record by that ID and applies the + winter efficiency to `inputs.main_heating_efficiency`. + + Schema versions ≥17_1 carry the field on their dataclass; schema 17_0 + hardcodes None in the mapper (the field didn't exist in that schema's + EPC API contract). The 4 corpus golden certs are all post-17_1. + """ + # Arrange + doc = _load_cert(cert_number) + + # Act + epc = EpcPropertyDataMapper.from_api_response(doc) + inputs = cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES) + + # Assert + main = epc.sap_heating.main_heating_details[0] + assert main.main_heating_index_number == expected_pcdb_id + if expected_winter_eff is not None: + assert inputs.main_heating_efficiency == pytest.approx( + expected_winter_eff, abs=1e-3 + )