From f9d8d331475063c08f00070a212adad3aafc0327 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 14 Jul 2026 22:30:28 +0000 Subject: [PATCH] =?UTF-8?q?Lodge=20the=20heat-network=20SAP=20code=20and?= =?UTF-8?q?=20Table=2012=20community=20fuel=20for=20PasHub=20community=20h?= =?UTF-8?q?eating=20=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 | 48 +++++++++++++++++++ .../epc/surveys/pashub_rdsap_site_notes.py | 4 ++ 2 files changed, 52 insertions(+) diff --git a/datatypes/epc/domain/tests/test_from_site_notes.py b/datatypes/epc/domain/tests/test_from_site_notes.py index 5c8d18233..95a6f123e 100644 --- a/datatypes/epc/domain/tests/test_from_site_notes.py +++ b/datatypes/epc/domain/tests/test_from_site_notes.py @@ -122,6 +122,54 @@ class TestPhotovoltaicArrayMapping: assert result.sap_energy_source.photovoltaic_arrays is None +class TestCommunityHeatingMapping: + """A "Community heating system" dwelling must lodge the Table 4a + heat-network SAP code and the Table 12 community fuel — with + `sap_main_heating_code=None`, `is_heat_network_main()` is False and the + calculator's whole heat-network branch (DLF, £120 standing charge, 80% + heat-source efficiency, Table 4c(3) charging) never fires; the dwelling is + priced as an ordinary mains-gas boiler (issue #1590 follow-up; fixture + 507644414148 over-rates +14.3, −8.6 of it from these two fields). + """ + + def test_community_boilers_lodge_heat_network_code_and_fuel(self) -> None: + # Arrange — the cohort's community lodgement (verbatim survey labels). + data = load("pashub_rdsap_site_notes_example1.json") + mh = data["heating_and_hot_water"]["main_heating"] + mh["system_type"] = "Community heating system" + mh["community_heat_source"] = "Community heating - boilers only" + mh["fuel"] = "Mains Gas" + mh["controls"] = ( + "Charging system linked to use of community heating, room thermostat only" + ) + mh["product_id"] = 0 # heat networks lodge no PCDB boiler + survey = from_dict(PasHubRdSapSiteNotes, data) + + # Act + main = EpcPropertyDataMapper.from_site_notes( + survey + ).sap_heating.main_heating_details[0] + + # Assert — Table 4a code 301 (community boilers) routes + # is_heat_network_main; Table 12 code 51 (community boilers, + # mains gas) prices/factors the heat correctly. + assert main.sap_main_heating_code == 301 + assert main.main_fuel_type == 51 + + def test_unknown_heat_source_strict_raises(self) -> None: + # Arrange + data = load("pashub_rdsap_site_notes_example1.json") + mh = data["heating_and_hot_water"]["main_heating"] + mh["system_type"] = "Community heating system" + mh["community_heat_source"] = "Some novel scheme" + mh["controls"] = "" + survey = from_dict(PasHubRdSapSiteNotes, data) + + # Act / Assert + with pytest.raises(UnmappedPasHubLabel, match="community heat source"): + EpcPropertyDataMapper.from_site_notes(survey) + + class TestRoomInRoofMapping: """A surveyed room-in-roof block (RdSAP §3.10 detailed measurement) must reach `sap_building_parts[*].sap_room_in_roof` — left None, ~32 m² of diff --git a/datatypes/epc/surveys/pashub_rdsap_site_notes.py b/datatypes/epc/surveys/pashub_rdsap_site_notes.py index e6577c5d9..564f5a425 100644 --- a/datatypes/epc/surveys/pashub_rdsap_site_notes.py +++ b/datatypes/epc/surveys/pashub_rdsap_site_notes.py @@ -202,6 +202,10 @@ class MainHeating: weather_compensator: bool emitter: str emitter_temperature: str + # §Heating "Heating System (Other)" — the community heat-source label + # (e.g. "Community heating - boilers only") lodged only by heat-network + # dwellings; blank elsewhere. + community_heat_source: str = "" @dataclass