Construct sap_ventilation in from_rdsap_schema_19_0 / 21_0_0 🟥

19.0 + 21.0.0 pass no sap_ventilation= at all → EpcPropertyData default None,
dropping the entire §2 block (sheltered_sides + mechanical_ventilation_kind).
Extend the MVHR mapper test to both and add a sheltered_sides regression,
mirroring 21.0.1 (remaining ADR-0037 ventilation follow-up).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-26 09:53:19 +00:00
parent 04b3cb240a
commit 34d97a75f4

View file

@ -2710,22 +2710,48 @@ def test_rdsap_mappers_carry_main_heating_controls_display(
(RdSapSchema17_1, EpcPropertyDataMapper.from_rdsap_schema_17_1, "17_1.json"),
(RdSapSchema18_0, EpcPropertyDataMapper.from_rdsap_schema_18_0, "18_0.json"),
(RdSapSchema20_0_0, EpcPropertyDataMapper.from_rdsap_schema_20_0_0, "20_0_0.json"),
(RdSapSchema19_0, EpcPropertyDataMapper.from_rdsap_schema_19_0, "19_0.json"),
(RdSapSchema21_0_0, EpcPropertyDataMapper.from_rdsap_schema_21_0_0, "21_0_0.json"),
],
)
def test_rdsap_mappers_map_mechanical_ventilation_kind(
schema_cls: Any, mapper: Any, fixture: str
) -> None:
# Calc-facing: an MVHR cert (mechanical_ventilation=4) must reach the §2
# ventilation cascade as MVHR, not be silently treated as natural. These
# three build a bare SapVentilation(sheltered_sides=…) and dropped the
# top-level mechanical_ventilation; 17.0 + 21.0.1 + full-SAP already map it
# (via _sap_17_1_ventilation / their own wiring). 19.0 + 21.0.0 set no
# sap_ventilation at all — a bigger, separate gap (see followups). Natural
# ventilation cascade as MVHR, not be silently treated as natural. 17.1 /
# 18.0 / 20.0.0 built a bare SapVentilation(sheltered_sides=…) and dropped
# the top-level mechanical_ventilation (fixed in #1333); 19.0 + 21.0.0 set
# no sap_ventilation at all, dropping the whole block (fixed here — the
# remaining ADR-0037 ventilation follow-up). 17.0 + 21.0.1 + full-SAP
# already map it (via _sap_17_1_ventilation / their own wiring). Natural
# certs (code 0/5 → None) are unchanged, so the fix only moves genuine
# MEV/MVHR certs (ADR-0037 follow-up).
# MEV/MVHR certs.
data = load(fixture)
data["mechanical_ventilation"] = 4
result = mapper(from_dict(schema_cls, data))
assert result.sap_ventilation.mechanical_ventilation_kind == "MVHR"
@pytest.mark.parametrize(
"schema_cls, mapper, fixture",
[
(RdSapSchema19_0, EpcPropertyDataMapper.from_rdsap_schema_19_0, "19_0.json"),
(RdSapSchema21_0_0, EpcPropertyDataMapper.from_rdsap_schema_21_0_0, "21_0_0.json"),
],
)
def test_rdsap_19_0_21_0_0_construct_sap_ventilation_block(
schema_cls: Any, mapper: Any, fixture: str
) -> None:
# 19.0 + 21.0.0 previously passed no `sap_ventilation=` at all → the
# EpcPropertyData default empty SapVentilation, dropping the entire §2
# block (sheltered_sides + kind). Mirroring 21.0.1, they now construct it.
# `sheltered_sides` derives from built_form per §S5 (both fixtures lodge
# built_form=2 Semi-Detached → 1 sheltered side); a missing/empty block
# would leave it None and over-count shelter via the cascade default of 2.
schema = from_dict(schema_cls, load(fixture))
result = mapper(schema)
assert result.sap_ventilation.sheltered_sides == 1