mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 26: §5 internal gains cascade pin (50/54 PASS) + rooflight daylight plumb
Added `internal_gains_section_from_cert` helper composing §1 (volume) + §4 (heat_gains line 65)m → §5 orchestrator, and 54 strict pin cases for worksheet lines (66)..(73) monthly + (232) annual lighting kWh. Also fixed a missing input plumb: cert_to_inputs was passing `rooflight_total_area_m2=0` to `internal_gains_from_cert`, so the 000516 roof window (lodged on `epc.sap_roof_windows` since slice 24) wasn't contributing to the L2a daylight factor. Added `_rooflight_total_area_m2_from_cert` and routed it through both the public cert→inputs cascade and the new §5 section helper. §5 cascade: field | 474 | 477 | 480 | 487 | 490 | 516 LINE_66 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ LINE_67 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ (rooflight plumb) LINE_68 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ LINE_69 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ LINE_70 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ LINE_71 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ LINE_72 | ✓ | ✗ | ✓ | ✗ | ✓ | ✓ LINE_73 | ✓ | ✗ | ✓ | ✗ | ✓ | ✓ LINE_232 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ Remaining failures are 000477 + 000487 LINE_72/73 — cascaded from §4 LINE_65 heat_gains residuals (000477 combi loss, 000487 HW lodgement defect). Both fixtures are slice 25 territory. Scoreboard: section_cascade_pins: 170 → 220 PASS (+50; 54 new tests, 4 fail) e2e SapResult: 29 → 30 PASS (+1, downstream from rooflight plumb) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
d4c090fc7c
commit
9cb79d9c98
3 changed files with 123 additions and 8 deletions
|
|
@ -133,12 +133,12 @@ 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-27b: section_cascade_pins 170 PASS / 16 FAIL, e2e SapResult
|
||||
29 PASS / 43 FAIL. §3 fully closes for 5 of 6 fixtures at abs=1e-4 — every
|
||||
LINE_31/33/36/37 pin passes on 000474/477/480/490/516. Remaining cascade
|
||||
failures are §4 monthly (000477/487 HW defects, slice 25), §3 (000487 RR
|
||||
defect, slice 25), and downstream SapResult pins still drifting because
|
||||
of §5–§9a precision not yet pinned.)
|
||||
(Post-slice-26: section_cascade_pins 220 PASS / 20 FAIL, e2e SapResult
|
||||
30 PASS / 42 FAIL. §3 + §5 fully close for 5 of 6 fixtures at abs=1e-4.
|
||||
Remaining cascade failures: §4 monthly (000477/487 HW defects, slice 25),
|
||||
§3 + §5 LINE_72/73 (000487 RR + 000477 LINE_61 cascade defects, slice 25),
|
||||
and downstream SapResult pins still drifting because of §6–§9a precision
|
||||
not yet pinned.)
|
||||
|
||||
### B.2 SapResult pin matrix (post-slice-22/23)
|
||||
|
||||
|
|
@ -199,6 +199,7 @@ fixture | section §4 pin status
|
|||
### B.5 Recent slices (in reverse order — newest first)
|
||||
|
||||
```
|
||||
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)
|
||||
Slice 27: BS EN ISO 13370 floor U rounded to 2 d.p. per RdSAP10 §5.12
|
||||
Slice 24: rooflight (line 27a) — SapRoofWindow datatype + 000516 cascade closure
|
||||
|
|
@ -256,14 +257,15 @@ The cascade pin work continues in worksheet order. For each section:
|
|||
1. Identify the cert→inputs cascade entry point. May need to extract a
|
||||
`<section>_from_cert(epc)` helper from `cert_to_inputs` (mirroring slice
|
||||
21c's `ventilation_from_cert`, 21d's `heat_transmission_section_from_cert`,
|
||||
21e's `water_heating_section_from_cert`).
|
||||
21e's `water_heating_section_from_cert`, 26's
|
||||
`internal_gains_section_from_cert`).
|
||||
2. Map fixture `LINE_X_<NAME>` constants to result struct attributes.
|
||||
3. Add scalar + monthly pin tests at abs=1e-4 to `test_section_cascade_pins.py`.
|
||||
4. Run, see failures, diagnose. Fixture defect or calculator bug — fix in place,
|
||||
no widening.
|
||||
|
||||
Sections still to pin:
|
||||
- **§5 internal gains** (lines 66-73 + 232 lighting kWh). 6 monthly + 1 annual.
|
||||
- ~~**§5 internal gains** (lines 66-73 + 232 lighting kWh)~~ DONE (slice 26)
|
||||
- **§6 solar gains** (lines 83-84). 2 monthly tuples.
|
||||
- **§7 mean internal temperature** (lines 85-94). 10 line refs, mostly monthly.
|
||||
- **§8 space heating** (lines 95-99). 4 monthly + 2 annual.
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ from domain.sap.tables.table_32 import (
|
|||
from domain.sap.worksheet.fuel_cost import FuelCostResult, fuel_cost
|
||||
from domain.sap.worksheet.dimensions import dimensions_from_cert
|
||||
from domain.sap.worksheet.internal_gains import (
|
||||
InternalGainsResult,
|
||||
OvershadingCategory,
|
||||
internal_gains_from_cert,
|
||||
)
|
||||
|
|
@ -755,6 +756,48 @@ def heat_transmission_section_from_cert(epc: EpcPropertyData) -> HeatTransmissio
|
|||
)
|
||||
|
||||
|
||||
def _rooflight_total_area_m2_from_cert(epc: EpcPropertyData) -> float:
|
||||
"""Σ area of `epc.sap_roof_windows` for §5 daylight-factor L2a +
|
||||
§6 horizontal solar gain. Returns 0.0 when none are lodged.
|
||||
|
||||
Roof windows behave as rooflights for §5 L2a (Z_L = 1.0 per Table 6d
|
||||
note 2) — same treatment as horizontal rooflights for the daylight
|
||||
bonus. Areas are 2-d.p.-rounded inputs (RdSAP10 §15) when lodged on
|
||||
the SapRoofWindow datatype."""
|
||||
return sum(float(rw.area_m2) for rw in epc.sap_roof_windows or [])
|
||||
|
||||
|
||||
def internal_gains_section_from_cert(
|
||||
epc: EpcPropertyData,
|
||||
) -> Optional[InternalGainsResult]:
|
||||
"""SAP 10.2 §5 cert→inputs cascade for `internal_gains_from_cert`.
|
||||
|
||||
Composes §1 (dim.volume_m3) + §4 (heat_gains_from_water_heating
|
||||
monthly_kwh, line (65)m) and threads them through the §5 orchestrator
|
||||
— exactly as `cert_to_inputs` computes internally. Returns the full
|
||||
`InternalGainsResult` (every (66)..(73) line ref + annual lighting kWh
|
||||
line (232)) so cascade pin tests can assert each §5 line ref against
|
||||
the U985 PDF.
|
||||
|
||||
Returns `None` when TFA is missing (matches the §4 helper contract;
|
||||
tests using this helper should skip those fixtures).
|
||||
"""
|
||||
if epc.total_floor_area_m2 is None:
|
||||
return None
|
||||
dim = dimensions_from_cert(epc)
|
||||
wh = water_heating_section_from_cert(epc)
|
||||
hw_heat_gains_monthly_kwh = (
|
||||
wh.heat_gains_monthly_kwh if wh is not None else (0.0,) * 12
|
||||
)
|
||||
return internal_gains_from_cert(
|
||||
epc=epc,
|
||||
dwelling_volume_m3=dim.volume_m3,
|
||||
heat_gains_from_water_heating_monthly_kwh=hw_heat_gains_monthly_kwh,
|
||||
overshading=_INTERNAL_GAINS_DEFAULT_OVERSHADING,
|
||||
rooflight_total_area_m2=_rooflight_total_area_m2_from_cert(epc),
|
||||
)
|
||||
|
||||
|
||||
def ventilation_from_cert(epc: EpcPropertyData) -> VentilationResult:
|
||||
"""SAP 10.2 §2 cert→inputs cascade for `ventilation_from_inputs`.
|
||||
|
||||
|
|
@ -1212,6 +1255,7 @@ def cert_to_inputs(
|
|||
dwelling_volume_m3=dim.volume_m3,
|
||||
heat_gains_from_water_heating_monthly_kwh=hw_heat_gains_monthly_kwh,
|
||||
overshading=_INTERNAL_GAINS_DEFAULT_OVERSHADING,
|
||||
rooflight_total_area_m2=_rooflight_total_area_m2_from_cert(epc),
|
||||
)
|
||||
internal_gains_monthly_w = (
|
||||
internal_gains_result.total_internal_gains_monthly_w
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import pytest
|
|||
from domain.sap.rdsap.cert_to_inputs import (
|
||||
cert_to_inputs,
|
||||
heat_transmission_section_from_cert,
|
||||
internal_gains_section_from_cert,
|
||||
ventilation_from_cert,
|
||||
water_heating_section_from_cert,
|
||||
)
|
||||
|
|
@ -310,3 +311,71 @@ def test_section_4_monthly_line_refs_match_pdf(
|
|||
# Assert
|
||||
for m in range(12):
|
||||
_pin(actual[m], expected[m], f"§4 {fixture_attr}[{m+1}] {fixture_name}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# §5 Internal gains — LINE_66..LINE_73 monthly + LINE_232 annual
|
||||
# ============================================================================
|
||||
|
||||
_SECTION_5_MONTHLY_PINS: Final[tuple[tuple[str, str], ...]] = (
|
||||
("LINE_66_M_METABOLIC_W", "metabolic_monthly_w"),
|
||||
("LINE_67_M_LIGHTING_W", "lighting_monthly_w"),
|
||||
("LINE_68_M_APPLIANCES_W", "appliances_monthly_w"),
|
||||
("LINE_69_M_COOKING_W", "cooking_monthly_w"),
|
||||
("LINE_70_M_PUMPS_FANS_W", "pumps_fans_monthly_w"),
|
||||
("LINE_71_M_LOSSES_W", "losses_monthly_w"),
|
||||
("LINE_72_M_WATER_HEATING_GAINS_W", "water_heating_gains_monthly_w"),
|
||||
("LINE_73_M_TOTAL_INTERNAL_GAINS_W", "total_internal_gains_monthly_w"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture_name,fixture_attr,result_attr",
|
||||
[
|
||||
(fix, line, attr)
|
||||
for fix in _FIXTURES
|
||||
for line, attr in _SECTION_5_MONTHLY_PINS
|
||||
],
|
||||
ids=lambda x: x if isinstance(x, str) else None,
|
||||
)
|
||||
def test_section_5_monthly_line_refs_match_pdf(
|
||||
fixture_name: str, fixture_attr: str, result_attr: str
|
||||
) -> None:
|
||||
"""§5 monthly pins — every Jan..Dec value of (66)..(73) internal-gain
|
||||
component 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
|
||||
ig = internal_gains_section_from_cert(epc)
|
||||
assert ig is not None, f"{fixture_name}: internal_gains_from_cert returned None"
|
||||
actual = getattr(ig, result_attr)
|
||||
|
||||
# Assert
|
||||
for m in range(12):
|
||||
_pin(actual[m], expected[m], f"§5 {fixture_attr}[{m+1}] {fixture_name}")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fixture_name", list(_FIXTURES), ids=lambda x: x)
|
||||
def test_section_5_line_232_lighting_kwh_per_yr_matches_pdf(
|
||||
fixture_name: str,
|
||||
) -> None:
|
||||
"""§5 (232) — annual lighting kWh from Appendix L, fuels the cost side
|
||||
`inputs.lighting_kwh_per_yr`."""
|
||||
# Arrange
|
||||
mod = _FIXTURES[fixture_name]
|
||||
epc = mod.build_epc() # type: ignore[attr-defined]
|
||||
expected = mod.LINE_232_LIGHTING_KWH_PER_YR # type: ignore[attr-defined]
|
||||
|
||||
# Act
|
||||
ig = internal_gains_section_from_cert(epc)
|
||||
assert ig is not None, f"{fixture_name}: internal_gains_from_cert returned None"
|
||||
|
||||
# Assert
|
||||
_pin(
|
||||
ig.lighting_kwh_per_yr,
|
||||
expected,
|
||||
f"§5 (232) {fixture_name}",
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue