From 7ee33b9ce8fb7aeab7dd98d7bf262a118c4028b9 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Wed, 1 Jul 2026 14:24:12 +0000 Subject: [PATCH] Normalize RdSAP MVHR PCDF index + duct type on the 21.0.0 path PRD #1385 phase 2. The 21.0.0 mapper dropped mechanical_ventilation_index_number and mechanical_vent_duct_type, unlike 21.0.1. cert_to_inputs reads both (PCDB Table 4f/329 heat-recovery/SFP lookup + the Table 329 in-use factor), so an MVHR/MEV cert on the 21.0.0 path fell back to the SAP default efficiency. Mirrors 21.0.1 exactly -- only the two fields it actually maps. The other four mechanical_vent_duct_* schema fields have no mapper OR calculator consumer even on 21.0.1 (verified), so they're deliberately left alone rather than populating dead domain fields. Verification: new test 1 passed (harvested 21.0.0 sample lodges index=12, duct_type=3); corpus 6002 passed; SAP-accuracy regressions pass; pyright unchanged (39 -> 39). Co-Authored-By: Claude Opus 4.8 (1M context) --- datatypes/epc/domain/mapper.py | 9 +++++ .../domain/test_mapper_mvhr_ducts_21_0_0.py | 40 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 tests/datatypes/epc/domain/test_mapper_mvhr_ducts_21_0_0.py diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 9074b6867..3d3915b9b 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -2178,6 +2178,15 @@ class EpcPropertyDataMapper: # unlike every other path (17.0/…/21.0.1). Understated the §2 # draughtproofing/infiltration credit. percent_draughtproofed=schema.percent_draughtproofed, + # 21.0.0 lodges the MVHR/MEV PCDF index + duct type but the mapper + # dropped both, unlike 21.0.1. The index feeds the PCDB Table 4f/329 + # heat-recovery/SFP lookup and duct_type selects the Table 329 in-use + # factor (1=Flexible / 2=Rigid) — without them an MVHR/MEV cert on the + # 21.0.0 path fell back to the SAP default efficiency. + mechanical_ventilation_index_number=( + schema.mechanical_ventilation_index_number + ), + mechanical_vent_duct_type=schema.mechanical_vent_duct_type, led_fixed_lighting_bulbs_count=schema.led_fixed_lighting_bulbs_count or 0, cfl_fixed_lighting_bulbs_count=schema.cfl_fixed_lighting_bulbs_count or 0, incandescent_fixed_lighting_bulbs_count=schema.incandescent_fixed_lighting_bulbs_count, diff --git a/tests/datatypes/epc/domain/test_mapper_mvhr_ducts_21_0_0.py b/tests/datatypes/epc/domain/test_mapper_mvhr_ducts_21_0_0.py new file mode 100644 index 000000000..8cad4021e --- /dev/null +++ b/tests/datatypes/epc/domain/test_mapper_mvhr_ducts_21_0_0.py @@ -0,0 +1,40 @@ +"""Regression: the RdSAP-Schema-21.0.0 mapper must carry the lodged MVHR/MEV PCDF +index + duct type, like the 21.0.1 path. + +PRD #1385 phase 2. 21.0.0 dropped `mechanical_ventilation_index_number` and +`mechanical_vent_duct_type`, so an MVHR/MEV cert on this path lost its PCDB +Table 4f/329 heat-recovery lookup (`cert_to_inputs` reads both). Only the two +fields 21.0.1 actually maps are carried; the other four +`mechanical_vent_duct_*` schema fields have no mapper or calculator consumer even +on 21.0.1, so they are deliberately left alone. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +from datatypes.epc.domain.mapper import EpcPropertyDataMapper + +# The harvested 21.0.0 sample lodges index_number=12, duct_type=3. +_FIXTURE = Path("backend/epc_api/json_samples/RdSAP-Schema-21.0.0/epc.json") + + +def _load() -> dict[str, Any]: + data: dict[str, Any] = json.loads(_FIXTURE.read_text()) + assert data["schema_type"] == "RdSAP-Schema-21.0.0" + return data + + +def test_21_0_0_carries_mvhr_index_and_duct_type() -> None: + data = _load() + assert data["mechanical_ventilation_index_number"] == 12 + assert data["mechanical_vent_duct_type"] == 3 + + epc = EpcPropertyDataMapper.from_api_response(data) + + # Both were dropped to None on the 21.0.0 path; now carried through so + # cert_to_inputs can drive the PCDB heat-recovery lookup. + assert epc.mechanical_ventilation_index_number == 12 + assert epc.mechanical_vent_duct_type == 3