mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 26c: §7 mean internal temperature cascade pin (44/60 PASS)
Added `mean_internal_temperature_section_from_cert` composing §1 (dim)
+ §2 (effective_monthly_ach) + §3 (total HLC) + §5 (internal gains)
+ §6 (solar gains) + climate (external temp) and threading them through
the §7 orchestrator — exact mirror of the cert_to_inputs internal
cascade.
Added 60 strict pin cases for §7 worksheet lines (85)..(94): T_h1
scalar, living_area_fraction scalar, η_living + T_living + T_h2 +
η_elsewhere + T_elsewhere + T_92 + T_93 + η_whole monthly tuples.
§7 per-fixture monthly pin status:
fixture | passing
000474 | 6 of 8 (LINE_92/93 ~0.0001 K residual)
000477 | 6 of 8 (LINE_92/93 ~0.0002 K residual)
000480 | 6 of 8 (LINE_92/93 ~0.0001 K residual)
000487 | 0 of 8 (cascade from §3 RR + §4 HW defects)
000490 | 6 of 8 (LINE_92/93 ~0.0001 K residual)
000516 | 8 of 8 ✓
LINE_92/93 marginal fails on 4 fixtures: weighted-sum of T_living +
T_elsewhere drifts by ~1e-4 K from PDF despite the per-zone temps
matching at 1e-4 individually. Likely a PDF intermediate-precision
artefact (analogous to U_eff at 5 dp in §3 windows); investigation
deferred — no widening per project policy.
Scoreboard:
section_cascade_pins: 230 → 274 PASS (+44; 60 new tests, 16 fail)
e2e SapResult: 32 → 32 PASS (unchanged — §7 cascade was
already running internally, pin tests just surface the line refs)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
1e9654ce28
commit
6e6bba7e67
3 changed files with 138 additions and 7 deletions
|
|
@ -133,12 +133,14 @@ Two test files contain the strict pins:
|
|||
Total: **169 PASS / 83 FAIL** across the strict pins. 4 of 6 fixtures fully
|
||||
close §1+§2+§4. 000487 is the worst (RR fixture defect propagates everywhere).
|
||||
|
||||
(Post-slice-26b: section_cascade_pins 230 PASS / 22 FAIL, e2e SapResult
|
||||
32 PASS / 40 FAIL. §3 + §5 + §6 fully close for 5 of 6 fixtures at
|
||||
abs=1e-4. Remaining cascade failures: §4 monthly (000477/487 HW defects,
|
||||
slice 25), §5 LINE_72/73 + §6 LINE_84 on 000477/487 (cascaded from §4),
|
||||
§3 (000487 RR defect, slice 25), and downstream SapResult pins still
|
||||
drifting because of §7–§9a precision not yet pinned.)
|
||||
(Post-slice-26c: section_cascade_pins 274 PASS / 38 FAIL, e2e SapResult
|
||||
32 PASS / 40 FAIL. §3 + §5 + §6 + §7 (mostly) pinned. §7 LINE_85..91
|
||||
+ LINE_87/88/89/90 close at abs=1e-4 for all 5 non-487 fixtures.
|
||||
LINE_92/93 marginal residuals (~0.0001 K, just over threshold) on
|
||||
000474/477/480/490 — investigation needed (possible PDF intermediate
|
||||
rounding precision artefact). 000487 fully cascades from §3/§4 defects
|
||||
(slice 25). e2e SapResult unchanged because cert_to_inputs was already
|
||||
running the §7 calc internally — pin tests just surface it now.)
|
||||
|
||||
### B.2 SapResult pin matrix (post-slice-22/23)
|
||||
|
||||
|
|
@ -199,6 +201,7 @@ fixture | section §4 pin status
|
|||
### B.5 Recent slices (in reverse order — newest first)
|
||||
|
||||
```
|
||||
Slice 26c: §7 mean internal temp cascade pin (60 cases, 44 PASS) — LINE_85..94
|
||||
Slice 26b: §6 solar gains cascade pin (12 cases, 10 PASS) + SapRoofWindow solar attrs + plumb to §6 cascade
|
||||
Slice 26: §5 internal gains cascade pin (54 cases, 50 PASS / 4 FAIL) + rooflight plumb to daylight factor
|
||||
Slice 27b: §3 element-area + door-area rounding to 2 d.p. per RdSAP10 §15 (p.66)
|
||||
|
|
@ -268,7 +271,7 @@ The cascade pin work continues in worksheet order. For each section:
|
|||
Sections still to pin:
|
||||
- ~~**§5 internal gains** (lines 66-73 + 232 lighting kWh)~~ DONE (slice 26)
|
||||
- ~~**§6 solar gains** (lines 83-84)~~ DONE (slice 26b — 5/6 fixtures close, 000477/487 cascade from §4)
|
||||
- **§7 mean internal temperature** (lines 85-94). 10 line refs, mostly monthly.
|
||||
- ~~**§7 mean internal temperature** (lines 85-94)~~ MOSTLY DONE (slice 26c — 44/60 PASS; LINE_92/93 marginal ~0.0001 K residual on 000474/477/480/490 needs investigation; 000487 cascades from §3/§4 defects).
|
||||
- **§8 space heating** (lines 95-99). 4 monthly + 2 annual.
|
||||
- **§9a energy requirements** (lines 201, 206-208, 211-215, 219). 5 scalar + 2
|
||||
monthly. Currently only the annual aggregates show on `SapResult` — may need
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ from domain.sap.worksheet.heat_transmission import (
|
|||
)
|
||||
from domain.sap.climate.appendix_u import external_temperature_c
|
||||
from domain.sap.worksheet.mean_internal_temperature import (
|
||||
MeanInternalTemperatureResult,
|
||||
mean_internal_temperature_monthly,
|
||||
)
|
||||
from domain.sap.worksheet.energy_requirements import (
|
||||
|
|
@ -827,6 +828,54 @@ def _roof_windows_for_solar_gains(
|
|||
)
|
||||
|
||||
|
||||
def mean_internal_temperature_section_from_cert(
|
||||
epc: EpcPropertyData,
|
||||
) -> Optional[MeanInternalTemperatureResult]:
|
||||
"""SAP 10.2 §7 cert→inputs cascade for `mean_internal_temperature_monthly`.
|
||||
|
||||
Composes §1 (dim) + §2 (effective_monthly_ach) + §3 (total HLC) + §5
|
||||
(internal gains) + §6 (solar gains) + climate (external temp) and
|
||||
threads them through the §7 orchestrator — exactly as cert_to_inputs
|
||||
computes internally. Returns the full
|
||||
`MeanInternalTemperatureResult` (every (85)..(94) line ref) so
|
||||
cascade pin tests can assert each §7 line ref against the U985 PDF.
|
||||
|
||||
Returns `None` when TFA is missing (matches other section helpers).
|
||||
"""
|
||||
if epc.total_floor_area_m2 is None:
|
||||
return None
|
||||
dim = dimensions_from_cert(epc)
|
||||
ventilation = ventilation_from_cert(epc)
|
||||
ht = heat_transmission_section_from_cert(epc)
|
||||
ig = internal_gains_section_from_cert(epc)
|
||||
sg = solar_gains_section_from_cert(epc)
|
||||
assert ig is not None, "internal_gains None despite TFA present"
|
||||
internal_gains_monthly_w = ig.total_internal_gains_monthly_w
|
||||
solar_gains_monthly_w = sg.total_solar_gains_monthly_w
|
||||
monthly_total_gains_w = tuple(
|
||||
internal_gains_monthly_w[m] + solar_gains_monthly_w[m] for m in range(12)
|
||||
)
|
||||
monthly_htc_w_per_k = tuple(
|
||||
ht.total_w_per_k + 0.33 * dim.volume_m3 * ventilation.effective_monthly_ach[m]
|
||||
for m in range(12)
|
||||
)
|
||||
main = _first_main_heating(epc)
|
||||
region = _region_index(epc.region_code)
|
||||
return mean_internal_temperature_monthly(
|
||||
monthly_external_temp_c=tuple(
|
||||
external_temperature_c(region, m) for m in range(1, 13)
|
||||
),
|
||||
monthly_total_gains_w=monthly_total_gains_w,
|
||||
monthly_heat_transfer_coefficient_w_per_k=monthly_htc_w_per_k,
|
||||
thermal_mass_parameter_kj_per_m2_k=_DEFAULT_THERMAL_MASS_PARAMETER_KJ_PER_M2_K,
|
||||
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),
|
||||
control_temperature_adjustment_c=0.0,
|
||||
)
|
||||
|
||||
|
||||
def solar_gains_section_from_cert(epc: EpcPropertyData) -> SolarGainsResult:
|
||||
"""SAP 10.2 §6 cert→inputs cascade for `solar_gains_from_cert`.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from domain.sap.rdsap.cert_to_inputs import (
|
|||
cert_to_inputs,
|
||||
heat_transmission_section_from_cert,
|
||||
internal_gains_section_from_cert,
|
||||
mean_internal_temperature_section_from_cert,
|
||||
solar_gains_section_from_cert,
|
||||
ventilation_from_cert,
|
||||
water_heating_section_from_cert,
|
||||
|
|
@ -439,3 +440,81 @@ def test_section_6_line_84_total_gains_match_pdf(
|
|||
# Assert
|
||||
for m in range(12):
|
||||
_pin(actual[m], expected[m], f"§6 (84)[{m+1}] {fixture_name}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# §7 Mean internal temperature — LINE_85..LINE_94 scalar + monthly tuples
|
||||
# ============================================================================
|
||||
|
||||
_SECTION_7_SCALAR_PINS: Final[tuple[tuple[str, str], ...]] = (
|
||||
("LINE_85_T_H1_C", "living_area_heating_temp_c"),
|
||||
("LINE_91_LIVING_AREA_FRACTION", "living_area_fraction"),
|
||||
)
|
||||
|
||||
_SECTION_7_MONTHLY_PINS: Final[tuple[tuple[str, str], ...]] = (
|
||||
("LINE_86_M_UTILISATION_LIVING", "utilisation_factor_living_monthly"),
|
||||
("LINE_87_M_MIT_LIVING_C", "mean_internal_temp_living_monthly"),
|
||||
("LINE_88_M_T_H2_C", "elsewhere_heating_temp_monthly"),
|
||||
("LINE_89_M_UTILISATION_ELSEWHERE", "utilisation_factor_elsewhere_monthly"),
|
||||
("LINE_90_M_MIT_ELSEWHERE_C", "mean_internal_temp_elsewhere_monthly"),
|
||||
("LINE_92_M_MIT_C", "mean_internal_temp_monthly"),
|
||||
("LINE_93_M_ADJUSTED_MIT_C", "adjusted_mean_internal_temp_monthly"),
|
||||
("LINE_94_M_UTILISATION_WHOLE", "utilisation_factor_whole_monthly"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture_name,fixture_attr,result_attr",
|
||||
[
|
||||
(fix, line, attr)
|
||||
for fix in _FIXTURES
|
||||
for line, attr in _SECTION_7_SCALAR_PINS
|
||||
],
|
||||
ids=lambda x: x if isinstance(x, str) else None,
|
||||
)
|
||||
def test_section_7_scalar_line_refs_match_pdf(
|
||||
fixture_name: str, fixture_attr: str, result_attr: str
|
||||
) -> None:
|
||||
"""§7 scalar pins — (85) T_h1 living-area heating temp + (91) living
|
||||
area fraction."""
|
||||
# Arrange
|
||||
mod = _FIXTURES[fixture_name]
|
||||
epc = mod.build_epc() # type: ignore[attr-defined]
|
||||
expected = getattr(mod, fixture_attr)
|
||||
|
||||
# Act
|
||||
mit = mean_internal_temperature_section_from_cert(epc)
|
||||
assert mit is not None, f"{fixture_name}: mit_from_cert returned None"
|
||||
actual = getattr(mit, result_attr)
|
||||
|
||||
# Assert
|
||||
_pin(actual, expected, f"§7 {fixture_attr} {fixture_name}")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture_name,fixture_attr,result_attr",
|
||||
[
|
||||
(fix, line, attr)
|
||||
for fix in _FIXTURES
|
||||
for line, attr in _SECTION_7_MONTHLY_PINS
|
||||
],
|
||||
ids=lambda x: x if isinstance(x, str) else None,
|
||||
)
|
||||
def test_section_7_monthly_line_refs_match_pdf(
|
||||
fixture_name: str, fixture_attr: str, result_attr: str
|
||||
) -> None:
|
||||
"""§7 monthly pins — every Jan..Dec value of (86)..(94) MIT + η lines
|
||||
matches the U985 PDF to abs=1e-4."""
|
||||
# Arrange
|
||||
mod = _FIXTURES[fixture_name]
|
||||
epc = mod.build_epc() # type: ignore[attr-defined]
|
||||
expected = getattr(mod, fixture_attr)
|
||||
|
||||
# Act
|
||||
mit = mean_internal_temperature_section_from_cert(epc)
|
||||
assert mit is not None, f"{fixture_name}: mit_from_cert returned None"
|
||||
actual = getattr(mit, result_attr)
|
||||
|
||||
# Assert
|
||||
for m in range(12):
|
||||
_pin(actual[m], expected[m], f"§7 {fixture_attr}[{m+1}] {fixture_name}")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue