Elmhurst 000490 fixture: tag Ext1 floor as exposed timber

Per the worksheet docstring on this fixture, Extension 1 hangs off the
main from the first storey upward — its lowest dimension is an exposed
timber floor (over outside air), not a ground floor on soil. Set
is_exposed_floor=True so heat_transmission_from_cert routes Ext1 through
the Table 20 lookup (U=1.20 W/m²K at age B unknown insulation) instead
of BS EN ISO 13370.

Combined with the Table 19 fn 1 default that routes Main to the
suspended-timber branch (U≈0.71), §3 LINE_28A floor sum lands at
≈32.4 W/K — matching the worksheet's 0.71×14.85 + 1.20×18.18.

A new floor-sum regression test pins the combined behaviour; the existing
LINE_31/36 parametrised test still passes (the exposed-floor route
contributes its area to LINE_31 the same way the ground-floor route did).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-20 13:28:23 +00:00
parent 6b99ad0a55
commit 269dd991b5
2 changed files with 23 additions and 1 deletions

View file

@ -68,12 +68,14 @@ def build_epc() -> EpcPropertyData:
sap_floor_dimensions=[ sap_floor_dimensions=[
# Cert records the extension at the dwelling's 1st/2nd-storey # Cert records the extension at the dwelling's 1st/2nd-storey
# level (no ground floor). Within our domain the lowest floor # level (no ground floor). Within our domain the lowest floor
# of the part is still floor=0. # of the part is still floor=0, but it's an *exposed timber*
# floor (over passageway / outside air below) — Table 20 route.
SapFloorDimension( SapFloorDimension(
room_height_m=2.88, # 1st-of-ext internal room_height_m=2.88, # 1st-of-ext internal
total_floor_area_m2=18.18, total_floor_area_m2=18.18,
party_wall_length_m=3.53, heat_loss_perimeter_m=8.68, party_wall_length_m=3.53, heat_loss_perimeter_m=8.68,
floor=0, floor=0,
is_exposed_floor=True,
), ),
SapFloorDimension( SapFloorDimension(
room_height_m=3.21, # = 2.96 internal + 0.25 floor structure room_height_m=3.21, # = 2.96 internal + 0.25 floor structure

View file

@ -1179,6 +1179,26 @@ def test_section_3_non_rr_line_31_and_36_match_elmhurst_worksheet(
) )
def test_section_3_floor_w_per_k_for_000490_uses_suspended_and_exposed_routes() -> None:
"""000490 exercises both new floor routes: Main is a suspended-timber
ground floor (Table 19 fn 1 default for age B U=0.71) and Extension
1 is an exposed-timber upper floor (Table 20 row AG unknown U=1.20).
Together the §3 LINE_28A floor sum is 0.71 × 14.85 + 1.20 × 18.18
32.4 W/K with the cascade keeping full precision. The Elmhurst
worksheet displays U-values to 2 d.p., so allow a small tolerance to
absorb whichever rounding policy lands first."""
# Arrange
epc = _w000490.build_epc()
# Act
result = heat_transmission_from_cert(
epc, window_total_area_m2=0.0, window_avg_u_value=None, door_count=0,
)
# Assert
assert result.floor_w_per_k == pytest.approx(32.4, abs=0.1)
@pytest.mark.parametrize("fixture", _ELMHURST_FIXTURES, ids=_elmhurst_fixture_id) @pytest.mark.parametrize("fixture", _ELMHURST_FIXTURES, ids=_elmhurst_fixture_id)
def test_section_3_partial_match_against_elmhurst_worksheet(fixture: ModuleType) -> None: def test_section_3_partial_match_against_elmhurst_worksheet(fixture: ModuleType) -> None:
"""Real Elmhurst SAP10.2 worksheets — partial §3 conformance. """Real Elmhurst SAP10.2 worksheets — partial §3 conformance.