mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +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)
|
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)
|
# Window U-values (Table 24)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ from domain.ml.rdsap_uvalues import (
|
||||||
WALL_TIMBER_FRAME,
|
WALL_TIMBER_FRAME,
|
||||||
thermal_bridging_y,
|
thermal_bridging_y,
|
||||||
u_door,
|
u_door,
|
||||||
|
u_exposed_floor,
|
||||||
u_floor,
|
u_floor,
|
||||||
u_party_wall,
|
u_party_wall,
|
||||||
u_roof,
|
u_roof,
|
||||||
|
|
@ -783,6 +784,22 @@ def test_u_floor_with_insulation_lowers_u_value() -> None:
|
||||||
assert insulated < 0.3
|
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:
|
def test_u_floor_falls_back_to_mid_range_when_geometry_unknown() -> None:
|
||||||
# Arrange — geometry missing.
|
# Arrange — geometry missing.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue