diff --git a/domain/sap10_calculator/rdsap/cert_to_inputs.py b/domain/sap10_calculator/rdsap/cert_to_inputs.py index dd22c0f0..dd32a9d3 100644 --- a/domain/sap10_calculator/rdsap/cert_to_inputs.py +++ b/domain/sap10_calculator/rdsap/cert_to_inputs.py @@ -556,6 +556,38 @@ def _first_main_heating(epc: EpcPropertyData) -> Optional[MainHeatingDetail]: return details[0] if details else None +# Elmhurst RdSAP water-heating codes that route DHW to a non-Main-1 +# system. RdSAP code 914 = "from second main system" — DHW is +# serviced by Main 2 (typically a gas combi providing DHW only) while +# Main 1 handles space heat (e.g. cert 000565: HP Main 1 + gas combi +# Main 2 + WHC 914). The water-heating cascade reads Main 2's PCDB +# record / SAP code / fuel when this routing applies. +_WATER_FROM_SECOND_MAIN_CODES: Final[frozenset[int]] = frozenset({914}) + + +def _water_heating_main( + epc: EpcPropertyData, +) -> Optional[MainHeatingDetail]: + """The `MainHeatingDetail` that services DHW per the cert's + `water_heating_code` routing. WHC 914 ("from second main system") + returns Main 2 when present; otherwise returns Main 1. + + The water-heating cascade (Table 4a / Appendix D2.1 summer + efficiency, water-heating fuel cost / CO2 / PE) keys off this + helper rather than `_first_main_heating` so the right system's + efficiency and fuel propagate to DHW. + """ + details = epc.sap_heating.main_heating_details if epc.sap_heating else [] + if not details: + return None + if ( + epc.sap_heating.water_heating_code in _WATER_FROM_SECOND_MAIN_CODES + and len(details) >= 2 + ): + return details[1] + return details[0] + + def _main_heating_efficiency(epc: EpcPropertyData) -> float: """SAP 10.2 (206) main system 1 efficiency as a 0..1 fraction. @@ -2877,14 +2909,24 @@ def cert_to_inputs( # `main_fuel_kwh = q_useful × DLF = q_generated`, matching the spec's # "unit prices per kWh of heat generated" convention. eff = _main_heating_efficiency(epc) - if pcdb_main is not None and pcdb_main.summer_efficiency_pct is not None: - water_eff = pcdb_main.summer_efficiency_pct / 100.0 + # Water-heating efficiency reads from the main that ACTUALLY services + # DHW per the cert's `water_heating_code` routing (Elmhurst WHC 914 + # = "from second main system" → Main 2). For single-main certs and + # WHC 901/902 this resolves to Main 1, matching the prior behaviour. + water_main = _water_heating_main(epc) + water_pcdb_main = ( + gas_oil_boiler_record(water_main.main_heating_index_number) + if water_main is not None and water_main.main_heating_index_number is not None + else None + ) + if water_pcdb_main is not None and water_pcdb_main.summer_efficiency_pct is not None: + water_eff = water_pcdb_main.summer_efficiency_pct / 100.0 else: water_eff = _water_efficiency_with_category_inherit( water_heating_code=epc.sap_heating.water_heating_code, - main_code=main_code, - main_category=main_category, - main_fuel=main_fuel, + main_code=water_main.sap_main_heating_code if water_main is not None else None, + main_category=water_main.main_heating_category if water_main is not None else None, + main_fuel=_main_fuel_code(water_main), ) # SAP 10.2 Appendix N3.6 + N3.7(a) — when an HP cert lodges a PCDB # Table 362 record, the cascade replaces the Table 4a defaults with