diff --git a/packages/domain/src/domain/sap/rdsap/cert_to_inputs.py b/packages/domain/src/domain/sap/rdsap/cert_to_inputs.py index 8399f272..8a344348 100644 --- a/packages/domain/src/domain/sap/rdsap/cert_to_inputs.py +++ b/packages/domain/src/domain/sap/rdsap/cert_to_inputs.py @@ -1042,6 +1042,22 @@ def fabric_energy_efficiency_from_cert(epc: EpcPropertyData) -> Optional[float]: ) +def fuel_cost_section_from_cert( + epc: EpcPropertyData, +) -> Optional[FuelCostResult]: + """SAP 10.2 §10a cert→inputs cascade for `fuel_cost`. Off-peak certs + return the zero sentinel (Table 12a high-rate-fraction split deferred). + For STANDARD-tariff certs returns the full (240)..(255) FuelCostResult. + + Composes via `cert_to_inputs(epc)` — `_fuel_cost` is invoked there with + all upstream §4/§5/§6/§7/§8/§9a values plumbed in. Returns None when + TFA missing. + """ + if epc.total_floor_area_m2 is None: + return None + return cert_to_inputs(epc).fuel_cost + + def energy_requirements_section_from_cert( epc: EpcPropertyData, ) -> Optional[EnergyRequirementsResult]: @@ -1388,6 +1404,7 @@ def _fuel_cost( pumps_fans_kwh: float, lighting_kwh: float, cooling_kwh: float, + electric_shower_kwh: float = 0.0, ) -> FuelCostResult: """SAP10.2 §10a fuel-cost precompute — produce a `FuelCostResult` from the cert + the §9a `energy_requirements_result`. RdSAP10 target per @@ -1443,19 +1460,25 @@ def _fuel_cost( tariff=tariff, ) + # Worksheet display convention: when a row's kWh is zero (no main 2, no + # secondary system, etc.) the PDF reports the (high-rate fraction) + # column as 0 rather than 1. Cost columns already collapse to 0 via + # multiplication, so this is presentation-only. + main_2_kwh = energy_requirements_result.main_2_fuel_kwh_per_yr + secondary_kwh = energy_requirements_result.secondary_fuel_kwh_per_yr return fuel_cost( main_1_kwh_per_yr=energy_requirements_result.main_1_fuel_kwh_per_yr, main_1_high_rate_gbp_per_kwh=main_1_high_rate_gbp_per_kwh, main_1_low_rate_gbp_per_kwh=0.0, main_1_high_rate_fraction=1.0, - main_2_kwh_per_yr=energy_requirements_result.main_2_fuel_kwh_per_yr, + main_2_kwh_per_yr=main_2_kwh, main_2_high_rate_gbp_per_kwh=main_1_high_rate_gbp_per_kwh, main_2_low_rate_gbp_per_kwh=0.0, - main_2_high_rate_fraction=1.0, - secondary_kwh_per_yr=energy_requirements_result.secondary_fuel_kwh_per_yr, + main_2_high_rate_fraction=1.0 if main_2_kwh > 0.0 else 0.0, + secondary_kwh_per_yr=secondary_kwh, secondary_high_rate_gbp_per_kwh=secondary_high_rate_gbp_per_kwh, secondary_low_rate_gbp_per_kwh=0.0, - secondary_high_rate_fraction=1.0, + secondary_high_rate_fraction=1.0 if secondary_kwh > 0.0 else 0.0, hot_water_kwh_per_yr=hot_water_kwh, hot_water_high_rate_gbp_per_kwh=water_high_rate_gbp_per_kwh, hot_water_low_rate_gbp_per_kwh=0.0, @@ -1464,8 +1487,8 @@ def _fuel_cost( lighting_kwh_per_yr=lighting_kwh, cooling_kwh_per_yr=cooling_kwh, other_uses_gbp_per_kwh=other_uses_gbp_per_kwh, - instant_shower_kwh_per_yr=0.0, - instant_shower_gbp_per_kwh=0.0, + instant_shower_kwh_per_yr=electric_shower_kwh, + instant_shower_gbp_per_kwh=other_uses_gbp_per_kwh, pv_generation_kwh_per_yr=_pv_generation_kwh_per_yr(epc), pv_export_credit_gbp_per_kwh=pv_export_credit_gbp_per_kwh, additional_standing_charges_gbp=standing, @@ -1781,6 +1804,9 @@ def cert_to_inputs( fuel_cost=_fuel_cost( epc=epc, main=main, + electric_shower_kwh=( + wh_result.electric_shower_kwh_per_yr if wh_result is not None else 0.0 + ), energy_requirements_result=energy_requirements_result, hot_water_kwh=hw_kwh, pumps_fans_kwh=pumps_fans_kwh, diff --git a/packages/domain/src/domain/sap/worksheet/fuel_cost.py b/packages/domain/src/domain/sap/worksheet/fuel_cost.py index 5ac587f3..8c2e9827 100644 --- a/packages/domain/src/domain/sap/worksheet/fuel_cost.py +++ b/packages/domain/src/domain/sap/worksheet/fuel_cost.py @@ -37,7 +37,20 @@ def _split( high_rate_fraction: float, ) -> _OffPeakSplit: """Off-peak split arithmetic shared by main 1 / main 2 / secondary / - water-heating rows. (240c)=Q×frac×P_high, (240d)=Q×(1-frac)×P_low.""" + water-heating rows. (240c)=Q×frac×P_high, (240d)=Q×(1-frac)×P_low. + + Worksheet display convention: when the row's kWh is zero (e.g. no main + 2 system) the PDF reports BOTH fractions as 0 rather than 1/0. Cost + columns already collapse to 0 via the kWh×fraction multiplications, + so this is presentation-only — the math is unaffected.""" + if kwh_per_yr <= 0.0: + return _OffPeakSplit( + high_rate_fraction=0.0, + low_rate_fraction=0.0, + high_rate_cost=0.0, + low_rate_cost=0.0, + total=0.0, + ) low_rate_fraction = 1.0 - high_rate_fraction high_rate_cost = kwh_per_yr * high_rate_fraction * high_rate_gbp_per_kwh low_rate_cost = kwh_per_yr * low_rate_fraction * low_rate_gbp_per_kwh diff --git a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000474.py b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000474.py index dc7a60c4..9f263704 100644 --- a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000474.py +++ b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000474.py @@ -450,3 +450,41 @@ LINE_213_ANNUAL_KWH: float = 0.0000 LINE_215_M_SECONDARY_FUEL_KWH: tuple[float, ...] = (0.0,) * 12 LINE_215_ANNUAL_KWH: float = 0.0000 LINE_221_COOLING_FUEL_KWH: float = 0.0000 + +# ============================================================================ +# §10a Fuel costs — using Table 12 (RdSAP10 Table 32) prices +# ============================================================================ +# STANDARD-tariff fixture — off-peak splits collapse (high_rate_fraction=1, +# low_rate_fraction=0, high_rate_cost = total). +LINE_240A_MAIN_1_HIGH_RATE_FRACTION: float = 1.0000 +LINE_240B_MAIN_1_LOW_RATE_FRACTION: float = 0.0000 +LINE_240C_MAIN_1_HIGH_RATE_COST: float = 416.3783 +LINE_240D_MAIN_1_LOW_RATE_COST: float = 0.0000 +LINE_240E_MAIN_1_OTHER_FUEL_COST: float = 0.0000 +LINE_240_MAIN_1_TOTAL_COST: float = 416.3783 +LINE_241A_MAIN_2_HIGH_RATE_FRACTION: float = 0.0000 +LINE_241B_MAIN_2_LOW_RATE_FRACTION: float = 0.0000 +LINE_241C_MAIN_2_HIGH_RATE_COST: float = 0.0000 +LINE_241D_MAIN_2_LOW_RATE_COST: float = 0.0000 +LINE_241E_MAIN_2_OTHER_FUEL_COST: float = 0.0000 +LINE_241_MAIN_2_TOTAL_COST: float = 0.0000 +LINE_242A_SECONDARY_HIGH_RATE_FRACTION: float = 0.0000 +LINE_242B_SECONDARY_LOW_RATE_FRACTION: float = 0.0000 +LINE_242C_SECONDARY_HIGH_RATE_COST: float = 0.0000 +LINE_242D_SECONDARY_LOW_RATE_COST: float = 0.0000 +LINE_242E_SECONDARY_OTHER_FUEL_COST: float = 0.0000 +LINE_242_SECONDARY_TOTAL_COST: float = 0.0000 +LINE_243_WATER_HIGH_RATE_FRACTION: float = 1.0000 +LINE_244_WATER_LOW_RATE_FRACTION: float = 0.0000 +LINE_245_WATER_HIGH_RATE_COST: float = 79.7539 +LINE_246_WATER_LOW_RATE_COST: float = 0.0000 +LINE_247_WATER_OTHER_FUEL_COST: float = 0.0000 +LINE_247A_INSTANT_SHOWER_COST: float = 0.0000 +LINE_248_SPACE_COOLING_COST: float = 0.0000 +LINE_249_PUMPS_FANS_COST: float = 21.1040 +LINE_250_LIGHTING_COST: float = 18.4588 +LINE_251_STANDING_CHARGES: float = 120.0000 +LINE_252_PV_CREDIT: float = 0.0000 +LINE_253_APPENDIX_Q_SAVED: float = 0.0000 +LINE_254_APPENDIX_Q_USED: float = 0.0000 +LINE_255_TOTAL_COST: float = 655.6949 diff --git a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000477.py b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000477.py index fbe94efe..fef51471 100644 --- a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000477.py +++ b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000477.py @@ -419,3 +419,39 @@ LINE_215_M_SECONDARY_FUEL_KWH: tuple[float, ...] = ( ) LINE_215_ANNUAL_KWH: float = 1011.1202 LINE_221_COOLING_FUEL_KWH: float = 0.0000 + +# ============================================================================ +# §10a Fuel costs — using Table 12 (RdSAP10 Table 32) prices +# ============================================================================ +LINE_240A_MAIN_1_HIGH_RATE_FRACTION: float = 1.0000 +LINE_240B_MAIN_1_LOW_RATE_FRACTION: float = 0.0000 +LINE_240C_MAIN_1_HIGH_RATE_COST: float = 357.4298 +LINE_240D_MAIN_1_LOW_RATE_COST: float = 0.0000 +LINE_240E_MAIN_1_OTHER_FUEL_COST: float = 0.0000 +LINE_240_MAIN_1_TOTAL_COST: float = 357.4298 +LINE_241A_MAIN_2_HIGH_RATE_FRACTION: float = 0.0000 +LINE_241B_MAIN_2_LOW_RATE_FRACTION: float = 0.0000 +LINE_241C_MAIN_2_HIGH_RATE_COST: float = 0.0000 +LINE_241D_MAIN_2_LOW_RATE_COST: float = 0.0000 +LINE_241E_MAIN_2_OTHER_FUEL_COST: float = 0.0000 +LINE_241_MAIN_2_TOTAL_COST: float = 0.0000 +LINE_242A_SECONDARY_HIGH_RATE_FRACTION: float = 1.0000 +LINE_242B_SECONDARY_LOW_RATE_FRACTION: float = 0.0000 +LINE_242C_SECONDARY_HIGH_RATE_COST: float = 133.3668 +LINE_242D_SECONDARY_LOW_RATE_COST: float = 0.0000 +LINE_242E_SECONDARY_OTHER_FUEL_COST: float = 0.0000 +LINE_242_SECONDARY_TOTAL_COST: float = 133.3668 +LINE_243_WATER_HIGH_RATE_FRACTION: float = 1.0000 +LINE_244_WATER_LOW_RATE_FRACTION: float = 0.0000 +LINE_245_WATER_HIGH_RATE_COST: float = 73.6381 +LINE_246_WATER_LOW_RATE_COST: float = 0.0000 +LINE_247_WATER_OTHER_FUEL_COST: float = 0.0000 +LINE_247A_INSTANT_SHOWER_COST: float = 0.0000 +LINE_248_SPACE_COOLING_COST: float = 0.0000 +LINE_249_PUMPS_FANS_COST: float = 21.1040 +LINE_250_LIGHTING_COST: float = 26.6010 +LINE_251_STANDING_CHARGES: float = 120.0000 +LINE_252_PV_CREDIT: float = 0.0000 +LINE_253_APPENDIX_Q_SAVED: float = 0.0000 +LINE_254_APPENDIX_Q_USED: float = 0.0000 +LINE_255_TOTAL_COST: float = 732.1396 diff --git a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000480.py b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000480.py index 72a3bbf0..52fdbc5f 100644 --- a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000480.py +++ b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000480.py @@ -461,3 +461,39 @@ LINE_215_M_SECONDARY_FUEL_KWH: tuple[float, ...] = ( ) LINE_215_ANNUAL_KWH: float = 1239.8578 LINE_221_COOLING_FUEL_KWH: float = 0.0000 + +# ============================================================================ +# §10a Fuel costs — using Table 12 (RdSAP10 Table 32) prices +# ============================================================================ +LINE_240A_MAIN_1_HIGH_RATE_FRACTION: float = 1.0000 +LINE_240B_MAIN_1_LOW_RATE_FRACTION: float = 0.0000 +LINE_240C_MAIN_1_HIGH_RATE_COST: float = 437.7942 +LINE_240D_MAIN_1_LOW_RATE_COST: float = 0.0000 +LINE_240E_MAIN_1_OTHER_FUEL_COST: float = 0.0000 +LINE_240_MAIN_1_TOTAL_COST: float = 437.7942 +LINE_241A_MAIN_2_HIGH_RATE_FRACTION: float = 0.0000 +LINE_241B_MAIN_2_LOW_RATE_FRACTION: float = 0.0000 +LINE_241C_MAIN_2_HIGH_RATE_COST: float = 0.0000 +LINE_241D_MAIN_2_LOW_RATE_COST: float = 0.0000 +LINE_241E_MAIN_2_OTHER_FUEL_COST: float = 0.0000 +LINE_241_MAIN_2_TOTAL_COST: float = 0.0000 +LINE_242A_SECONDARY_HIGH_RATE_FRACTION: float = 1.0000 +LINE_242B_SECONDARY_LOW_RATE_FRACTION: float = 0.0000 +LINE_242C_SECONDARY_HIGH_RATE_COST: float = 163.5372 +LINE_242D_SECONDARY_LOW_RATE_COST: float = 0.0000 +LINE_242E_SECONDARY_OTHER_FUEL_COST: float = 0.0000 +LINE_242_SECONDARY_TOTAL_COST: float = 163.5372 +LINE_243_WATER_HIGH_RATE_FRACTION: float = 1.0000 +LINE_244_WATER_LOW_RATE_FRACTION: float = 0.0000 +LINE_245_WATER_HIGH_RATE_COST: float = 84.3426 +LINE_246_WATER_LOW_RATE_COST: float = 0.0000 +LINE_247_WATER_OTHER_FUEL_COST: float = 0.0000 +LINE_247A_INSTANT_SHOWER_COST: float = 0.0000 +LINE_248_SPACE_COOLING_COST: float = 0.0000 +LINE_249_PUMPS_FANS_COST: float = 21.1040 +LINE_250_LIGHTING_COST: float = 28.0358 +LINE_251_STANDING_CHARGES: float = 120.0000 +LINE_252_PV_CREDIT: float = 0.0000 +LINE_253_APPENDIX_Q_SAVED: float = 0.0000 +LINE_254_APPENDIX_Q_USED: float = 0.0000 +LINE_255_TOTAL_COST: float = 854.8139 diff --git a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000487.py b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000487.py index f3ba2699..c44416dc 100644 --- a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000487.py +++ b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000487.py @@ -484,3 +484,40 @@ LINE_215_M_SECONDARY_FUEL_KWH: tuple[float, ...] = ( ) LINE_215_ANNUAL_KWH: float = 1083.4778 LINE_221_COOLING_FUEL_KWH: float = 0.0000 + +# ============================================================================ +# §10a Fuel costs — using Table 12 (RdSAP10 Table 32) prices +# ============================================================================ +# Has electric shower → (247a) instant-shower cost = 79.3036. +LINE_240A_MAIN_1_HIGH_RATE_FRACTION: float = 1.0000 +LINE_240B_MAIN_1_LOW_RATE_FRACTION: float = 0.0000 +LINE_240C_MAIN_1_HIGH_RATE_COST: float = 383.4409 +LINE_240D_MAIN_1_LOW_RATE_COST: float = 0.0000 +LINE_240E_MAIN_1_OTHER_FUEL_COST: float = 0.0000 +LINE_240_MAIN_1_TOTAL_COST: float = 383.4409 +LINE_241A_MAIN_2_HIGH_RATE_FRACTION: float = 0.0000 +LINE_241B_MAIN_2_LOW_RATE_FRACTION: float = 0.0000 +LINE_241C_MAIN_2_HIGH_RATE_COST: float = 0.0000 +LINE_241D_MAIN_2_LOW_RATE_COST: float = 0.0000 +LINE_241E_MAIN_2_OTHER_FUEL_COST: float = 0.0000 +LINE_241_MAIN_2_TOTAL_COST: float = 0.0000 +LINE_242A_SECONDARY_HIGH_RATE_FRACTION: float = 1.0000 +LINE_242B_SECONDARY_LOW_RATE_FRACTION: float = 0.0000 +LINE_242C_SECONDARY_HIGH_RATE_COST: float = 142.9107 +LINE_242D_SECONDARY_LOW_RATE_COST: float = 0.0000 +LINE_242E_SECONDARY_OTHER_FUEL_COST: float = 0.0000 +LINE_242_SECONDARY_TOTAL_COST: float = 142.9107 +LINE_243_WATER_HIGH_RATE_FRACTION: float = 1.0000 +LINE_244_WATER_LOW_RATE_FRACTION: float = 0.0000 +LINE_245_WATER_HIGH_RATE_COST: float = 51.8208 +LINE_246_WATER_LOW_RATE_COST: float = 0.0000 +LINE_247_WATER_OTHER_FUEL_COST: float = 0.0000 +LINE_247A_INSTANT_SHOWER_COST: float = 79.3036 +LINE_248_SPACE_COOLING_COST: float = 0.0000 +LINE_249_PUMPS_FANS_COST: float = 21.1040 +LINE_250_LIGHTING_COST: float = 30.0318 +LINE_251_STANDING_CHARGES: float = 120.0000 +LINE_252_PV_CREDIT: float = 0.0000 +LINE_253_APPENDIX_Q_SAVED: float = 0.0000 +LINE_254_APPENDIX_Q_USED: float = 0.0000 +LINE_255_TOTAL_COST: float = 828.6119 diff --git a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000490.py b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000490.py index 97af0b59..6618be85 100644 --- a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000490.py +++ b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000490.py @@ -435,3 +435,39 @@ LINE_215_M_SECONDARY_FUEL_KWH: tuple[float, ...] = ( ) LINE_215_ANNUAL_KWH: float = 1118.3275 LINE_221_COOLING_FUEL_KWH: float = 0.0000 + +# ============================================================================ +# §10a Fuel costs — using Table 12 (RdSAP10 Table 32) prices +# ============================================================================ +LINE_240A_MAIN_1_HIGH_RATE_FRACTION: float = 1.0000 +LINE_240B_MAIN_1_LOW_RATE_FRACTION: float = 0.0000 +LINE_240C_MAIN_1_HIGH_RATE_COST: float = 397.1204 +LINE_240D_MAIN_1_LOW_RATE_COST: float = 0.0000 +LINE_240E_MAIN_1_OTHER_FUEL_COST: float = 0.0000 +LINE_240_MAIN_1_TOTAL_COST: float = 397.1204 +LINE_241A_MAIN_2_HIGH_RATE_FRACTION: float = 0.0000 +LINE_241B_MAIN_2_LOW_RATE_FRACTION: float = 0.0000 +LINE_241C_MAIN_2_HIGH_RATE_COST: float = 0.0000 +LINE_241D_MAIN_2_LOW_RATE_COST: float = 0.0000 +LINE_241E_MAIN_2_OTHER_FUEL_COST: float = 0.0000 +LINE_241_MAIN_2_TOTAL_COST: float = 0.0000 +LINE_242A_SECONDARY_HIGH_RATE_FRACTION: float = 1.0000 +LINE_242B_SECONDARY_LOW_RATE_FRACTION: float = 0.0000 +LINE_242C_SECONDARY_HIGH_RATE_COST: float = 147.5074 +LINE_242D_SECONDARY_LOW_RATE_COST: float = 0.0000 +LINE_242E_SECONDARY_OTHER_FUEL_COST: float = 0.0000 +LINE_242_SECONDARY_TOTAL_COST: float = 147.5074 +LINE_243_WATER_HIGH_RATE_FRACTION: float = 1.0000 +LINE_244_WATER_LOW_RATE_FRACTION: float = 0.0000 +LINE_245_WATER_HIGH_RATE_COST: float = 99.1998 +LINE_246_WATER_LOW_RATE_COST: float = 0.0000 +LINE_247_WATER_OTHER_FUEL_COST: float = 0.0000 +LINE_247A_INSTANT_SHOWER_COST: float = 0.0000 +LINE_248_SPACE_COOLING_COST: float = 0.0000 +LINE_249_PUMPS_FANS_COST: float = 21.1040 +LINE_250_LIGHTING_COST: float = 22.6105 +LINE_251_STANDING_CHARGES: float = 120.0000 +LINE_252_PV_CREDIT: float = 0.0000 +LINE_253_APPENDIX_Q_SAVED: float = 0.0000 +LINE_254_APPENDIX_Q_USED: float = 0.0000 +LINE_255_TOTAL_COST: float = 807.5421 diff --git a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000516.py b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000516.py index de13ee88..96ebbba1 100644 --- a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000516.py +++ b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000516.py @@ -461,3 +461,39 @@ LINE_215_M_SECONDARY_FUEL_KWH: tuple[float, ...] = ( ) LINE_215_ANNUAL_KWH: float = 1241.0317 LINE_221_COOLING_FUEL_KWH: float = 0.0000 + +# ============================================================================ +# §10a Fuel costs — using Table 12 (RdSAP10 Table 32) prices +# ============================================================================ +LINE_240A_MAIN_1_HIGH_RATE_FRACTION: float = 1.0000 +LINE_240B_MAIN_1_LOW_RATE_FRACTION: float = 0.0000 +LINE_240C_MAIN_1_HIGH_RATE_COST: float = 438.7033 +LINE_240D_MAIN_1_LOW_RATE_COST: float = 0.0000 +LINE_240E_MAIN_1_OTHER_FUEL_COST: float = 0.0000 +LINE_240_MAIN_1_TOTAL_COST: float = 438.7033 +LINE_241A_MAIN_2_HIGH_RATE_FRACTION: float = 0.0000 +LINE_241B_MAIN_2_LOW_RATE_FRACTION: float = 0.0000 +LINE_241C_MAIN_2_HIGH_RATE_COST: float = 0.0000 +LINE_241D_MAIN_2_LOW_RATE_COST: float = 0.0000 +LINE_241E_MAIN_2_OTHER_FUEL_COST: float = 0.0000 +LINE_241_MAIN_2_TOTAL_COST: float = 0.0000 +LINE_242A_SECONDARY_HIGH_RATE_FRACTION: float = 1.0000 +LINE_242B_SECONDARY_LOW_RATE_FRACTION: float = 0.0000 +LINE_242C_SECONDARY_HIGH_RATE_COST: float = 163.6921 +LINE_242D_SECONDARY_LOW_RATE_COST: float = 0.0000 +LINE_242E_SECONDARY_OTHER_FUEL_COST: float = 0.0000 +LINE_242_SECONDARY_TOTAL_COST: float = 163.6921 +LINE_243_WATER_HIGH_RATE_FRACTION: float = 1.0000 +LINE_244_WATER_LOW_RATE_FRACTION: float = 0.0000 +LINE_245_WATER_HIGH_RATE_COST: float = 86.7630 +LINE_246_WATER_LOW_RATE_COST: float = 0.0000 +LINE_247_WATER_OTHER_FUEL_COST: float = 0.0000 +LINE_247A_INSTANT_SHOWER_COST: float = 0.0000 +LINE_248_SPACE_COOLING_COST: float = 0.0000 +LINE_249_PUMPS_FANS_COST: float = 21.1040 +LINE_250_LIGHTING_COST: float = 30.4538 +LINE_251_STANDING_CHARGES: float = 120.0000 +LINE_252_PV_CREDIT: float = 0.0000 +LINE_253_APPENDIX_Q_SAVED: float = 0.0000 +LINE_254_APPENDIX_Q_USED: float = 0.0000 +LINE_255_TOTAL_COST: float = 860.7162 diff --git a/packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py b/packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py index 624c9aa2..aef2cab5 100644 --- a/packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py +++ b/packages/domain/src/domain/sap/worksheet/tests/test_section_cascade_pins.py @@ -20,6 +20,7 @@ from domain.sap.rdsap.cert_to_inputs import ( cert_to_inputs, energy_requirements_section_from_cert, fabric_energy_efficiency_from_cert, + fuel_cost_section_from_cert, heat_transmission_section_from_cert, internal_gains_section_from_cert, mean_internal_temperature_section_from_cert, @@ -780,3 +781,72 @@ def test_section_9a_scalar_line_refs_match_pdf( # Assert _pin(actual, expected, f"§9a {fixture_attr} {fixture_name}") + + +# ============================================================================ +# §10a Fuel costs — LINE_240..LINE_255 (using Table 32 prices) +# ============================================================================ + +_SECTION_10A_PINS: Final[tuple[tuple[str, str], ...]] = ( + ("LINE_240A_MAIN_1_HIGH_RATE_FRACTION", "main_1_high_rate_fraction"), + ("LINE_240B_MAIN_1_LOW_RATE_FRACTION", "main_1_low_rate_fraction"), + ("LINE_240C_MAIN_1_HIGH_RATE_COST", "main_1_high_rate_cost_gbp"), + ("LINE_240D_MAIN_1_LOW_RATE_COST", "main_1_low_rate_cost_gbp"), + ("LINE_240E_MAIN_1_OTHER_FUEL_COST", "main_1_other_fuel_cost_gbp"), + ("LINE_240_MAIN_1_TOTAL_COST", "main_1_total_cost_gbp"), + ("LINE_241A_MAIN_2_HIGH_RATE_FRACTION", "main_2_high_rate_fraction"), + ("LINE_241B_MAIN_2_LOW_RATE_FRACTION", "main_2_low_rate_fraction"), + ("LINE_241C_MAIN_2_HIGH_RATE_COST", "main_2_high_rate_cost_gbp"), + ("LINE_241D_MAIN_2_LOW_RATE_COST", "main_2_low_rate_cost_gbp"), + ("LINE_241E_MAIN_2_OTHER_FUEL_COST", "main_2_other_fuel_cost_gbp"), + ("LINE_241_MAIN_2_TOTAL_COST", "main_2_total_cost_gbp"), + ("LINE_242A_SECONDARY_HIGH_RATE_FRACTION", "secondary_high_rate_fraction"), + ("LINE_242B_SECONDARY_LOW_RATE_FRACTION", "secondary_low_rate_fraction"), + ("LINE_242C_SECONDARY_HIGH_RATE_COST", "secondary_high_rate_cost_gbp"), + ("LINE_242D_SECONDARY_LOW_RATE_COST", "secondary_low_rate_cost_gbp"), + ("LINE_242E_SECONDARY_OTHER_FUEL_COST", "secondary_other_fuel_cost_gbp"), + ("LINE_242_SECONDARY_TOTAL_COST", "secondary_total_cost_gbp"), + ("LINE_243_WATER_HIGH_RATE_FRACTION", "water_high_rate_fraction"), + ("LINE_244_WATER_LOW_RATE_FRACTION", "water_low_rate_fraction"), + ("LINE_245_WATER_HIGH_RATE_COST", "water_high_rate_cost_gbp"), + ("LINE_246_WATER_LOW_RATE_COST", "water_low_rate_cost_gbp"), + ("LINE_247_WATER_OTHER_FUEL_COST", "water_other_fuel_cost_gbp"), + ("LINE_247A_INSTANT_SHOWER_COST", "instant_shower_cost_gbp"), + ("LINE_248_SPACE_COOLING_COST", "space_cooling_cost_gbp"), + ("LINE_249_PUMPS_FANS_COST", "pumps_fans_cost_gbp"), + ("LINE_250_LIGHTING_COST", "lighting_cost_gbp"), + ("LINE_251_STANDING_CHARGES", "additional_standing_charges_gbp"), + ("LINE_252_PV_CREDIT", "pv_credit_gbp"), + ("LINE_253_APPENDIX_Q_SAVED", "appendix_q_saved_gbp"), + ("LINE_254_APPENDIX_Q_USED", "appendix_q_used_gbp"), + ("LINE_255_TOTAL_COST", "total_cost_gbp"), +) + + +@pytest.mark.parametrize( + "fixture_name,fixture_attr,result_attr", + [ + (fix, line, attr) + for fix in _FIXTURES + for line, attr in _SECTION_10A_PINS + ], + ids=lambda x: x if isinstance(x, str) else None, +) +def test_section_10a_line_refs_match_pdf( + fixture_name: str, fixture_attr: str, result_attr: str +) -> None: + """§10a pins — every (240a)..(255) line ref of `_fuel_cost` matches the + U985 PDF to abs=1e-4. STANDARD-tariff scope A (all 6 fixtures); off- + peak splits collapse: high_rate_fraction=1, low_rate_fraction=0.""" + # Arrange + mod = _FIXTURES[fixture_name] + epc = mod.build_epc() # type: ignore[attr-defined] + expected = getattr(mod, fixture_attr) + + # Act + fc = fuel_cost_section_from_cert(epc) + assert fc is not None, f"{fixture_name}: fuel_cost_from_cert returned None" + actual = getattr(fc, result_attr) + + # Assert + _pin(actual, expected, f"§10a {fixture_attr} {fixture_name}") diff --git a/packages/domain/src/domain/sap/worksheet/water_heating.py b/packages/domain/src/domain/sap/worksheet/water_heating.py index 16cc6748..836748e4 100644 --- a/packages/domain/src/domain/sap/worksheet/water_heating.py +++ b/packages/domain/src/domain/sap/worksheet/water_heating.py @@ -62,7 +62,9 @@ class WaterHeatingResult: total_demand_monthly_kwh: tuple[float, ...] output_monthly_kwh: tuple[float, ...] heat_gains_monthly_kwh: tuple[float, ...] + electric_shower_monthly_kwh: tuple[float, ...] # (64a)m — App J step 8 output_kwh_per_yr: float + electric_shower_kwh_per_yr: float # Σ (64a)m — feeds §10a (247a) # Table J2 — monthly factors for hot water use (also used by Appendix J # equation J11 for "other uses"). Symmetric about the year midpoint. @@ -763,5 +765,7 @@ def water_heating_from_cert( total_demand_monthly_kwh=total_demand, output_monthly_kwh=output, heat_gains_monthly_kwh=gains, + electric_shower_monthly_kwh=electric_shower, output_kwh_per_yr=sum(output), + electric_shower_kwh_per_yr=sum(electric_shower), )