mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
u_exposed_floor: Table 20 lookup for exposed/semi-exposed upper floors
RdSAP10 §5.13 Table 20 (page 47) gives U-values for upper floors that sit over outside air (exposed) or enclosed unheated space (semi-exposed) — e.g. an extension hanging off the main from the first storey upward. The spec collapses both into the same lookup: keyed on age band × insulation thickness, no geometry needed. Elmhurst worksheet U985-0001-000490 Extension 1 records U=1.20 W/m²K for its exposed timber floor (age B, no insulation). Table 20 row "A to G, insulation unknown or as built" returns 1.20 exactly. Caller wiring (heat_transmission_from_cert routing on a floor_position discriminator) lands in the next slice. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
344a9c9d5e
commit
e2c37300ec
2 changed files with 65 additions and 0 deletions
|
|
@ -587,6 +587,54 @@ def u_floor(
|
|||
return soil_g / (0.457 * b + d_t)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Exposed / semi-exposed upper-floor U-values (Table 20, §5.13)
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# Table 20 (page 47): the spec collapses exposed (to outside air) and
|
||||
# semi-exposed (to enclosed unheated space) into the same lookup. Keyed
|
||||
# on age band × insulation thickness — no geometry input. This is the
|
||||
# floor of e.g. a single-storey extension that hangs off the main from
|
||||
# the first storey upward (000490 Extension 1 is exactly this shape).
|
||||
#
|
||||
# Country footnotes:
|
||||
# (1) Use the 50 mm row if known to be insulated but thickness unknown.
|
||||
# (2) Band L → 0.18 W/m²K in Scotland.
|
||||
# (3) Band M → 0.15 W/m²K in Scotland AND Wales.
|
||||
# These are England-and-Wales values; country overrides land later.
|
||||
|
||||
_EXPOSED_FLOOR_BY_AGE_AND_INS: Final[dict[str, tuple[float, float, float, float]]] = {
|
||||
# (unknown/as-built, 50mm, 100mm, 150mm)
|
||||
"A": (1.20, 0.50, 0.30, 0.22), "B": (1.20, 0.50, 0.30, 0.22),
|
||||
"C": (1.20, 0.50, 0.30, 0.22), "D": (1.20, 0.50, 0.30, 0.22),
|
||||
"E": (1.20, 0.50, 0.30, 0.22), "F": (1.20, 0.50, 0.30, 0.22),
|
||||
"G": (1.20, 0.50, 0.30, 0.22),
|
||||
"H": (0.51, 0.50, 0.30, 0.22), "I": (0.51, 0.50, 0.30, 0.22),
|
||||
"J": (0.25, 0.25, 0.25, 0.22),
|
||||
"K": (0.22, 0.22, 0.22, 0.22),
|
||||
"L": (0.22, 0.22, 0.22, 0.22),
|
||||
"M": (0.18, 0.18, 0.18, 0.18),
|
||||
}
|
||||
|
||||
|
||||
def u_exposed_floor(
|
||||
age_band: Optional[str], insulation_thickness_mm: Optional[int]
|
||||
) -> float:
|
||||
"""RdSAP10 Table 20 exposed/semi-exposed upper-floor U-value in W/m²K.
|
||||
Used when the part's floor is open to outside air or sits over an
|
||||
unheated space (e.g. an over-passageway extension) rather than over
|
||||
soil. No geometry input — the lookup is age × insulation only."""
|
||||
band = (age_band or "A").upper()
|
||||
row = _EXPOSED_FLOOR_BY_AGE_AND_INS.get(band, _EXPOSED_FLOOR_BY_AGE_AND_INS["A"])
|
||||
if insulation_thickness_mm is None or insulation_thickness_mm < 25:
|
||||
return row[0]
|
||||
if insulation_thickness_mm < 75:
|
||||
return row[1]
|
||||
if insulation_thickness_mm < 125:
|
||||
return row[2]
|
||||
return row[3]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Window U-values (Table 24)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ from domain.ml.rdsap_uvalues import (
|
|||
WALL_TIMBER_FRAME,
|
||||
thermal_bridging_y,
|
||||
u_door,
|
||||
u_exposed_floor,
|
||||
u_floor,
|
||||
u_party_wall,
|
||||
u_roof,
|
||||
|
|
@ -783,6 +784,22 @@ def test_u_floor_with_insulation_lowers_u_value() -> None:
|
|||
assert insulated < 0.3
|
||||
|
||||
|
||||
def test_u_exposed_floor_age_b_unknown_insulation_uses_table_20_row_a_to_g() -> None:
|
||||
# Arrange — RdSAP10 §5.13 Table 20 (page 47) gives U-values for
|
||||
# exposed and semi-exposed upper floors keyed on age band +
|
||||
# insulation thickness. The "Insulation unknown or as built"
|
||||
# column at age band A-G = 1.20 W/m²K. Elmhurst worksheet
|
||||
# U985-0001-000490 Extension 1 records U=1.20 for its exposed
|
||||
# timber floor (1900-1929, no insulation lodged) — this lookup
|
||||
# reproduces that exact value without any geometry input.
|
||||
|
||||
# Act
|
||||
result = u_exposed_floor(age_band="B", insulation_thickness_mm=None)
|
||||
|
||||
# Assert
|
||||
assert result == pytest.approx(1.20, abs=0.001)
|
||||
|
||||
|
||||
def test_u_floor_falls_back_to_mid_range_when_geometry_unknown() -> None:
|
||||
# Arrange — geometry missing.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue