pcdb followup: e2e mapper-chain regression test for main_heating_index_number

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 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-21 13:45:33 +00:00
parent 7d4f3d78dc
commit 15d6b78149

View file

@ -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
)