mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 26: §7 LINE_92/93 closure — RdSAP §15 area rounding on living area
LINE_91 in the worksheet is `living_area / (4)`, where living_area itself is the §15-rounded materialisation of `Table 27 fraction × TFA`. RdSAP §9.2 (p.52): "The living area is then the fraction multiplied by the total floor area." §15 (p.66) lists "All internal floor areas and living area: 2 d.p." So the actual LINE_91 fed to the §7 zone blend is `round_half_up(Table_27 × TFA, 2) / TFA`, not the raw Table 27 entry. The roundtrip explains why the 4 holdout fixtures lodge LINE_91 = 0.3001 or 0.2501 instead of the Table 27 values 0.30 / 0.25: 000474: 0.30 × 56.79 → 17.04 / 56.79 = 0.3001 000477: 0.25 × 77.58 → 19.40 / 77.58 = 0.2501 000490: 0.25 × 66.06 → 16.52 / 66.06 = 0.2501 `_living_area_fraction` now takes TFA and materialises + rounds + divides; `_living_area_fraction_default` retains the bare Table 27 lookup. Existing `_round_half_up` from heat_transmission is the right utility (same §15 boundary, same half-up convention). Scoreboard: §7 cascade pins 52/60 → 60/60 (closes LINE_92/93 on 000474, 000477, 000480, 000490 — and tightens the already-passing 000487/000516 combinations). Full cascade: 304/312 → 312/312 (100%). e2e SapResult: 27/66 → 56/66 (continuous SAP, ECF, fuel cost, space heating kWh now close on 5/6 fixtures; 000487 still has unrelated downstream defects, all 6 CO2 fails await §12). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
144f08533f
commit
cd94da4d2e
1 changed files with 30 additions and 5 deletions
|
|
@ -84,6 +84,8 @@ from domain.sap.worksheet.solar_gains import (
|
|||
from domain.sap.worksheet.heat_transmission import (
|
||||
DwellingExposure,
|
||||
HeatTransmission,
|
||||
_AREA_ROUND_DP,
|
||||
_round_half_up,
|
||||
heat_transmission_from_cert,
|
||||
)
|
||||
from domain.sap.climate.appendix_u import external_temperature_c
|
||||
|
|
@ -348,9 +350,9 @@ def _is_timber_or_steel_frame(parts: list[SapBuildingPart]) -> bool:
|
|||
return isinstance(wc, int) and wc in (5, 6)
|
||||
|
||||
|
||||
def _living_area_fraction(habitable_rooms_count: Optional[int]) -> float:
|
||||
"""RdSAP 10 Table 27 by `habitable_rooms_count`. Defaults to the
|
||||
bottom of the table for ≥8 rooms; falls back to the SAP convention
|
||||
def _living_area_fraction_default(habitable_rooms_count: Optional[int]) -> float:
|
||||
"""RdSAP 10 Table 27 (p.52) lookup by `habitable_rooms_count`. Defaults
|
||||
to the bottom of the table for ≥8 rooms; falls back to the SAP convention
|
||||
0.21 when count missing or zero."""
|
||||
if not habitable_rooms_count or habitable_rooms_count <= 0:
|
||||
return _LIVING_AREA_FRACTION_DEFAULT
|
||||
|
|
@ -359,6 +361,25 @@ def _living_area_fraction(habitable_rooms_count: Optional[int]) -> float:
|
|||
return _LIVING_AREA_FRACTION_MIN
|
||||
|
||||
|
||||
def _living_area_fraction(
|
||||
habitable_rooms_count: Optional[int], total_floor_area_m2: float
|
||||
) -> float:
|
||||
"""SAP 10.2 §7 LINE_91 = Living area / TFA.
|
||||
|
||||
RdSAP §9.2 (p.52): living area = Table 27 fraction × TFA. RdSAP §15
|
||||
(p.66) requires "All internal floor areas and living area: 2 d.p." at
|
||||
the RdSAP→SAP boundary. So the materialised living area is rounded to
|
||||
2 d.p. half-up, then divided back by TFA to yield the LINE_91 that
|
||||
feeds the §7 zone blend. This roundtrip is why fixtures lodge
|
||||
e.g. 0.3001 (= 17.04/56.79) rather than the raw 0.30 Table 27 entry.
|
||||
"""
|
||||
fraction = _living_area_fraction_default(habitable_rooms_count)
|
||||
if total_floor_area_m2 <= 0.0:
|
||||
return fraction
|
||||
living_area_m2 = _round_half_up(fraction * total_floor_area_m2, _AREA_ROUND_DP)
|
||||
return living_area_m2 / total_floor_area_m2
|
||||
|
||||
|
||||
def _window_total_area_and_avg_u(windows: list[SapWindow]) -> tuple[float, Optional[float]]:
|
||||
"""Area-weighted total + U-value for the conduction worksheet."""
|
||||
if not windows:
|
||||
|
|
@ -877,7 +898,9 @@ def mean_internal_temperature_section_from_cert(
|
|||
total_floor_area_m2=dim.total_floor_area_m2,
|
||||
control_type=_control_type(main),
|
||||
responsiveness=_responsiveness(main),
|
||||
living_area_fraction=_living_area_fraction(epc.habitable_rooms_count),
|
||||
living_area_fraction=_living_area_fraction(
|
||||
epc.habitable_rooms_count, dim.total_floor_area_m2
|
||||
),
|
||||
control_temperature_adjustment_c=0.0,
|
||||
)
|
||||
|
||||
|
|
@ -1407,7 +1430,9 @@ def cert_to_inputs(
|
|||
# for the Elmhurst corpus (cert-side mapping is a future slice).
|
||||
control_type_value = _control_type(main)
|
||||
responsiveness_value = _responsiveness(main)
|
||||
living_area_fraction_value = _living_area_fraction(epc.habitable_rooms_count)
|
||||
living_area_fraction_value = _living_area_fraction(
|
||||
epc.habitable_rooms_count, dim.total_floor_area_m2
|
||||
)
|
||||
monthly_total_gains_w = tuple(
|
||||
internal_gains_monthly_w[m] + solar_gains_monthly_w[m] for m in range(12)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue