diff --git a/domain/sap10_calculator/worksheet/heat_transmission.py b/domain/sap10_calculator/worksheet/heat_transmission.py index ceb086ce..5af20455 100644 --- a/domain/sap10_calculator/worksheet/heat_transmission.py +++ b/domain/sap10_calculator/worksheet/heat_transmission.py @@ -812,6 +812,12 @@ def heat_transmission_from_cert( # code feeds the documentary-evidence R-value calc when a # measured wall thickness is also present (else ignored). wall_insulation_thermal_conductivity=part.wall_insulation_thermal_conductivity, + # RdSAP 10 §5.7/§5.8 (PDF p.40-41), Table 14 — a dry-lined + # (incl. lath-and-plaster) uninsulated wall adds R=0.17. + # The alt-wall path already passes this; the main wall must + # too, else every lodged `wall_dry_lined=Y` main wall is + # billed at the un-adjusted U. + dry_lined=bool(part.wall_dry_lined), ) # When the per-bp `roof_insulation_thickness` is explicitly lodged # as 0 (uninsulated — e.g. cert 001479 Ext2 PS sloping ceiling diff --git a/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py b/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py index c1632a39..b6a21d0a 100644 --- a/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py +++ b/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py @@ -1275,6 +1275,50 @@ def test_corridor_door_on_sheltered_alt_wall_uses_table26_u_1p4() -> None: assert with_corridor.fabric_heat_loss_w_per_k < no_corridor.fabric_heat_loss_w_per_k +def test_main_wall_dry_lining_applies_table_14_resistance() -> None: + # Arrange — RdSAP 10 §5.7/§5.8 (PDF p.40-41), Table 14: a dry-lined + # (including lath-and-plaster) uninsulated wall adds R=0.17 m²K/W: + # U_adj = 1/(1/U₀ + 0.17). A solid-brick (construction 3) age-A wall + # with a measured 230 mm thickness has U₀=1.70 (Table 13, 200-280 mm + # band) → dry-lined U=1/(1/1.70+0.17)=1.32 (2 d.p.). The alt-wall path + # already applies this; the MAIN wall dropped the `dry_lined` kwarg, so + # every lodged `wall_dry_lined=Y` main wall was billed at the un-adjusted + # U — under-rating solid-brick stock (API wall_construction=3 cohort: + # 48 dry-lined certs at 10% within-0.5, signed -1.33). + from dataclasses import replace + + base_part = make_building_part( + construction_age_band="A", + wall_construction=3, # solid brick + wall_insulation_type=0, # uninsulated (as-built) + floor_dimensions=[ + make_floor_dimension( + total_floor_area_m2=50.0, room_height_m=2.5, + party_wall_length_m=0.0, heat_loss_perimeter_m=28.0, floor=0, + ), + ], + ) + not_dry = replace(base_part, wall_thickness_mm=230, wall_dry_lined=False) + dry = replace(base_part, wall_thickness_mm=230, wall_dry_lined=True) + epc_not_dry = make_minimal_sap10_epc( + total_floor_area_m2=50.0, country_code="ENG", sap_building_parts=[not_dry], + ) + epc_dry = make_minimal_sap10_epc( + total_floor_area_m2=50.0, country_code="ENG", sap_building_parts=[dry], + ) + + # Act + ht_not_dry = heat_transmission_from_cert(epc_not_dry, door_count=0) + ht_dry = heat_transmission_from_cert(epc_dry, door_count=0) + + # Assert — same net wall area, so the W/K ratio is the U ratio: the + # dry-lined wall is 1.32/1.70 = 0.776× the as-built wall. + assert ht_not_dry.walls_w_per_k > 0.0 + expected_ratio = 1.32 / 1.70 + assert abs(ht_dry.walls_w_per_k / ht_not_dry.walls_w_per_k - expected_ratio) <= 0.005 + assert ht_dry.fabric_heat_loss_w_per_k < ht_not_dry.fabric_heat_loss_w_per_k + + def test_window_uses_effective_u_value_with_curtain_resistance_per_sap10_2_section_3_2() -> None: """SAP10.2 §3.2: the window U-value used for heat-transmission is the effective form `U_eff = 1/(1/U_raw + 0.04)` — the 0.04 m²K/W is the