From 34d97a75f4821703a62988daf64714411726b8f0 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 26 Jun 2026 09:53:19 +0000 Subject: [PATCH] =?UTF-8?q?Construct=20sap=5Fventilation=20in=20from=5Frds?= =?UTF-8?q?ap=5Fschema=5F19=5F0=20/=2021=5F0=5F0=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../domain/tests/test_from_rdsap_schema.py | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/datatypes/epc/domain/tests/test_from_rdsap_schema.py b/datatypes/epc/domain/tests/test_from_rdsap_schema.py index a817414c..07fc7ddb 100644 --- a/datatypes/epc/domain/tests/test_from_rdsap_schema.py +++ b/datatypes/epc/domain/tests/test_from_rdsap_schema.py @@ -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