From a213150d7d0ab0bfcd530c394e81413a14aab5e3 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 14 Jul 2026 16:20:38 +0000 Subject: [PATCH] =?UTF-8?q?Resolve=20the=20cohort's=20remaining=20PasHub?= =?UTF-8?q?=20boiler=20control=20labels=20to=20Table=204e=20codes=20?= =?UTF-8?q?=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../epc/domain/tests/test_from_site_notes.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/datatypes/epc/domain/tests/test_from_site_notes.py b/datatypes/epc/domain/tests/test_from_site_notes.py index 7aad4e162..96e5620e5 100644 --- a/datatypes/epc/domain/tests/test_from_site_notes.py +++ b/datatypes/epc/domain/tests/test_from_site_notes.py @@ -866,3 +866,63 @@ class TestPasHubUnmappedMainFuel: # Assert assert result.sap_heating.main_heating_details[0].main_fuel_type == 30 + + +class TestPasHubMainHeatingControlCoding: + """Each main-heating Controls label the Guinness cohort lodges resolves to + its SAP 10.2 Table 4e Group 1 (PDF p.171) boiler-control code. The mapper + emits the int code (not the raw label) so the calculator's `_control_type` + keys off it instead of strict-raising `UnmappedSapCode` deep in the cascade. + + The resulting control type is the spec's, not a blanket "type 2": a + programmer + a single room thermostat (2104) is type 1; type 2 needs more + (2+ room thermostats, or TRVs). + """ + + # (label, Table 4e code, Table 9 control type) — codes/types straight from + # SAP 10.2 Table 4e Group 1. + _LABEL_CODE_TYPE = [ + ("Programmer and room thermostat", 2104, 1), + ("Programmer, TRVs and bypass", 2107, 2), + ("Room thermostat and TRVs", 2113, 2), + ("TRVs and bypass", 2111, 2), + ] + + @pytest.mark.parametrize( + "label, code", [(lbl, code) for lbl, code, _ in _LABEL_CODE_TYPE] + ) + def test_control_label_maps_to_sap_code(self, label: str, code: int) -> None: + # Arrange — the example fixture with its Controls cell overridden. + raw = load("pashub_rdsap_site_notes_example1.json") + raw["heating_and_hot_water"]["main_heating"]["controls"] = label + survey = from_dict(PasHubRdSapSiteNotes, raw) + + # Act + result = EpcPropertyDataMapper.from_site_notes(survey) + + # Assert + assert ( + result.sap_heating.main_heating_details[0].main_heating_control == code + ) + + @pytest.mark.parametrize( + "label, control_type", [(lbl, ct) for lbl, _, ct in _LABEL_CODE_TYPE] + ) + def test_mapped_control_yields_spec_control_type( + self, label: str, control_type: int + ) -> None: + # Arrange — drive the mapped code through the calculator's control-type + # classifier to pin the end-to-end control type per Table 4e. + from domain.sap10_calculator.rdsap.cert_to_inputs import ( # pyright: ignore[reportPrivateUsage] + _control_type, + ) + + raw = load("pashub_rdsap_site_notes_example1.json") + raw["heating_and_hot_water"]["main_heating"]["controls"] = label + survey = from_dict(PasHubRdSapSiteNotes, raw) + main = EpcPropertyDataMapper.from_site_notes( + survey + ).sap_heating.main_heating_details[0] + + # Act / Assert + assert _control_type(main) == control_type