mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
§5 slice 13: rooflight Z_L=1.0 closes 000516 to ≤5e-3 W on every line
Table 6d note 2: roof windows / rooflights use Z_L = 1.0 regardless of
the overshading bucket applied to the rest of the dwelling's glazing.
Before this slice the orchestrator approximated rooflights as average
overshading (Z_L=0.83), driving 000516's (67) lighting 0.18 W (0.54%)
high. All wall windows in our 6-fixture corpus were correctly handled;
000516 is the only fixture with a lodged rooflight (the 1.18 m² NE
"window" showing Z=1.0 in the worksheet §6).
fixture | (67) max |err| before | after
--------+----------------------+--------
000516 | 0.1823 W (0.54%) | <0.005 W (<0.02%)
others | <0.0003 W | <0.0003 W
Changes:
- internal_gains_from_cert gains rooflight_total_area_m2 (default 0).
Rooflights summed at g_L=0.80 (Table 6b DG) × FF=0.7 (Table 6c PVC)
× Z_L=1.0 alongside wall windows (which still use the dwelling's
overshading-derived Z_L).
- SECTION_5_ROOFLIGHT_AREAS_M2 added to every fixture (empty tuple
except 000516 which carries (1.18,)).
- Tolerances on the §5 parametrised e2e test tightened from 2e-1 W
on (67) and 3e-1 W on (73) to 5e-3 W on both — every fixture now
closes to display rounding.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2d4fa24de9
commit
380115e244
8 changed files with 40 additions and 16 deletions
|
|
@ -493,23 +493,36 @@ def _lighting_capacity_and_efficacy_from_cert(
|
|||
def _daylight_factor_from_cert(
|
||||
epc: EpcPropertyData,
|
||||
overshading: OvershadingCategory,
|
||||
rooflight_total_area_m2: float,
|
||||
) -> float:
|
||||
"""Compute C_daylight via L2a + L2b from the cert's windows. Per
|
||||
Table 6d note 3 a single Z_L applies to all glazing in the dwelling.
|
||||
"""Compute C_daylight via L2a + L2b from the cert's windows + any
|
||||
rooflights.
|
||||
|
||||
Per Table 6d note 3 a single Z_L applies to all wall glazing. Per
|
||||
Table 6d note 2 rooflights use Z_L = 1.0 regardless of overshading.
|
||||
Rooflights are summed at default g_L = 0.80 (Table 6b DG) × FF = 0.7
|
||||
(Table 6c PVC) — non-default rooflight glazing or framing requires a
|
||||
richer cert-derived representation in a future slice.
|
||||
|
||||
When `total_floor_area_m2` is missing or no windows are lodged the
|
||||
SAP "no-bonus" default 1.433 is used.
|
||||
"""
|
||||
tfa = float(epc.total_floor_area_m2 or 0.0)
|
||||
if tfa <= 0.0 or not epc.sap_windows:
|
||||
if tfa <= 0.0 or (not epc.sap_windows and rooflight_total_area_m2 <= 0.0):
|
||||
return 1.433
|
||||
z_l = _Z_L_BY_OVERSHADING[overshading]
|
||||
g_l_numerator = sum(
|
||||
wall_g_l_numerator = sum(
|
||||
float(w.window_width) * float(w.window_height)
|
||||
* _g_light(w) * _frame_factor(w)
|
||||
* _g_light(w) * _frame_factor(w) * z_l
|
||||
for w in epc.sap_windows
|
||||
)
|
||||
g_l = 0.9 * g_l_numerator * z_l / tfa
|
||||
rooflight_g_l_numerator = (
|
||||
rooflight_total_area_m2
|
||||
* _G_LIGHT_DEFAULT
|
||||
* _FRAME_FACTOR_DEFAULT
|
||||
* 1.0 # Z_L = 1.0 for rooflights per Table 6d note 2
|
||||
)
|
||||
g_l = 0.9 * (wall_g_l_numerator + rooflight_g_l_numerator) / tfa
|
||||
if g_l > 0.095:
|
||||
return 0.96
|
||||
return 52.2 * g_l * g_l - 9.94 * g_l + 1.433
|
||||
|
|
@ -537,6 +550,7 @@ def internal_gains_from_cert(
|
|||
dwelling_volume_m3: float,
|
||||
heat_gains_from_water_heating_monthly_kwh: tuple[float, ...],
|
||||
overshading: OvershadingCategory = OvershadingCategory.AVERAGE,
|
||||
rooflight_total_area_m2: float = 0.0,
|
||||
) -> InternalGainsResult:
|
||||
"""SAP 10.2 §5 orchestrator — chain every line ref (66)..(73) for the
|
||||
dwelling identified by `epc`.
|
||||
|
|
@ -565,7 +579,9 @@ def internal_gains_from_cert(
|
|||
appliances = appliances_monthly_w(total_floor_area_m2=tfa, n_occupants=n)
|
||||
|
||||
c_l_fixed, eff_fixed = _lighting_capacity_and_efficacy_from_cert(epc)
|
||||
c_daylight = _daylight_factor_from_cert(epc, overshading)
|
||||
c_daylight = _daylight_factor_from_cert(
|
||||
epc, overshading, rooflight_total_area_m2=rooflight_total_area_m2
|
||||
)
|
||||
lighting = lighting_monthly_w(
|
||||
total_floor_area_m2=tfa,
|
||||
n_occupants=n,
|
||||
|
|
|
|||
|
|
@ -246,8 +246,9 @@ COMBI_LOSS_OVERRIDE: Optional[tuple[float, ...]] = LINE_61_M_COMBI_LOSS_KWH
|
|||
# §12-1 default 80 lm/W × 15 W = 1200 lm each.
|
||||
SECTION_5_BULB_COUNT_LEL: int = 8
|
||||
# Window areas per worksheet §6 (5 windows: East 3.74 ×2, NW 1.76 + 1.98,
|
||||
# SE 0.5). All DG air-filled (g_L=0.80) on PVC frames (FF=0.7).
|
||||
# SE 0.5). All DG air-filled (g_L=0.80) on PVC frames (FF=0.7). No rooflights.
|
||||
SECTION_5_WINDOW_AREAS_M2: tuple[float, ...] = (3.74, 3.74, 1.76, 1.98, 0.5)
|
||||
SECTION_5_ROOFLIGHT_AREAS_M2: tuple[float, ...] = ()
|
||||
# Vaillant ecoTEC pro 28 combi, pump in heated space, unknown install date
|
||||
# → Table 5a 7 W heating-season-only row.
|
||||
SECTION_5_PUMP_AGE_STR: str = "Unknown"
|
||||
|
|
|
|||
|
|
@ -185,8 +185,9 @@ COMBI_LOSS_OVERRIDE: Optional[tuple[float, ...]] = LINE_61_M_COMBI_LOSS_KWH
|
|||
# §5 Internal gains — cert-derived inputs + expected outputs
|
||||
# ============================================================================
|
||||
SECTION_5_BULB_COUNT_LEL: int = 9
|
||||
# 3 windows: East 1.28, West 1.17 + 6.76. All DG / PVC.
|
||||
# 3 windows: East 1.28, West 1.17 + 6.76. All DG / PVC. No rooflights.
|
||||
SECTION_5_WINDOW_AREAS_M2: tuple[float, ...] = (1.28, 1.17, 6.76)
|
||||
SECTION_5_ROOFLIGHT_AREAS_M2: tuple[float, ...] = ()
|
||||
SECTION_5_PUMP_AGE_STR: str = "Unknown"
|
||||
|
||||
LINE_66_M_METABOLIC_W: tuple[float, ...] = (144.9204,) * 12
|
||||
|
|
|
|||
|
|
@ -217,8 +217,9 @@ COMBI_LOSS_OVERRIDE: Optional[tuple[float, ...]] = LINE_61_M_COMBI_LOSS_KWH
|
|||
# §5 Internal gains — cert-derived inputs + expected outputs
|
||||
# ============================================================================
|
||||
SECTION_5_BULB_COUNT_LEL: int = 10
|
||||
# 2 windows: NE 8.74, SW 1.8. All DG / PVC.
|
||||
# 2 windows: NE 8.74, SW 1.8. All DG / PVC. No rooflights.
|
||||
SECTION_5_WINDOW_AREAS_M2: tuple[float, ...] = (8.74, 1.8)
|
||||
SECTION_5_ROOFLIGHT_AREAS_M2: tuple[float, ...] = ()
|
||||
SECTION_5_PUMP_AGE_STR: str = "Unknown"
|
||||
|
||||
LINE_66_M_METABOLIC_W: tuple[float, ...] = (152.4740,) * 12
|
||||
|
|
|
|||
|
|
@ -232,8 +232,9 @@ ELECTRIC_SHOWER_OVERRIDE: Optional[tuple[float, ...]] = LINE_64A_M_ELECTRIC_SHOW
|
|||
# §5 Internal gains — cert-derived inputs + expected outputs
|
||||
# ============================================================================
|
||||
SECTION_5_BULB_COUNT_LEL: int = 7
|
||||
# 2 windows: South 0.77, South 6.69. All DG / PVC.
|
||||
# 2 windows: South 0.77, South 6.69. All DG / PVC. No rooflights.
|
||||
SECTION_5_WINDOW_AREAS_M2: tuple[float, ...] = (0.77, 6.69)
|
||||
SECTION_5_ROOFLIGHT_AREAS_M2: tuple[float, ...] = ()
|
||||
SECTION_5_PUMP_AGE_STR: str = "Unknown"
|
||||
|
||||
LINE_66_M_METABOLIC_W: tuple[float, ...] = (149.5185,) * 12
|
||||
|
|
|
|||
|
|
@ -228,8 +228,9 @@ LINE_65_M_HEAT_GAINS_FROM_WH_KWH: tuple[float, ...] = (
|
|||
# §5 Internal gains — cert-derived inputs + expected outputs
|
||||
# ============================================================================
|
||||
SECTION_5_BULB_COUNT_LEL: int = 8
|
||||
# 3 windows: NE 0.81, NW 2.7, SE 5.52. All DG / PVC.
|
||||
# 3 windows: NE 0.81, NW 2.7, SE 5.52. All DG / PVC. No rooflights.
|
||||
SECTION_5_WINDOW_AREAS_M2: tuple[float, ...] = (0.81, 2.7, 5.52)
|
||||
SECTION_5_ROOFLIGHT_AREAS_M2: tuple[float, ...] = ()
|
||||
SECTION_5_PUMP_AGE_STR: str = "Unknown"
|
||||
|
||||
LINE_66_M_METABOLIC_W: tuple[float, ...] = (128.8087,) * 12
|
||||
|
|
|
|||
|
|
@ -194,8 +194,10 @@ COMBI_LOSS_OVERRIDE: Optional[tuple[float, ...]] = LINE_61_M_COMBI_LOSS_KWH
|
|||
# §5 Internal gains — cert-derived inputs + expected outputs
|
||||
# ============================================================================
|
||||
SECTION_5_BULB_COUNT_LEL: int = 9
|
||||
# 3 windows: NE 1.18 + 3.88, SW 4.43. All DG / PVC.
|
||||
SECTION_5_WINDOW_AREAS_M2: tuple[float, ...] = (1.18, 3.88, 4.43)
|
||||
# Wall windows: NE 3.88, SW 4.43. The 1.18 m² NE entry is a rooflight
|
||||
# (Z=1.0 in the worksheet §6 column, per Table 6d note 2).
|
||||
SECTION_5_WINDOW_AREAS_M2: tuple[float, ...] = (3.88, 4.43)
|
||||
SECTION_5_ROOFLIGHT_AREAS_M2: tuple[float, ...] = (1.18,)
|
||||
SECTION_5_PUMP_AGE_STR: str = "Unknown"
|
||||
|
||||
LINE_66_M_METABOLIC_W: tuple[float, ...] = (157.9824,) * 12
|
||||
|
|
|
|||
|
|
@ -601,6 +601,7 @@ def test_internal_gains_from_cert_matches_elmhurst_worksheet_all_fixtures(
|
|||
dwelling_volume_m3=fixture.LINE_5_VOLUME_M3,
|
||||
heat_gains_from_water_heating_monthly_kwh=fixture.LINE_65_M_HEAT_GAINS_FROM_WH_KWH,
|
||||
overshading=OvershadingCategory.AVERAGE,
|
||||
rooflight_total_area_m2=sum(fixture.SECTION_5_ROOFLIGHT_AREAS_M2),
|
||||
)
|
||||
|
||||
# Assert
|
||||
|
|
@ -609,7 +610,7 @@ def test_internal_gains_from_cert_matches_elmhurst_worksheet_all_fixtures(
|
|||
fixture.LINE_66_M_METABOLIC_W[m], abs=1e-3
|
||||
), f"(66) month {m+1}"
|
||||
assert result.lighting_monthly_w[m] == pytest.approx(
|
||||
fixture.LINE_67_M_LIGHTING_W[m], abs=2e-1
|
||||
fixture.LINE_67_M_LIGHTING_W[m], abs=5e-3
|
||||
), f"(67) month {m+1}"
|
||||
assert result.appliances_monthly_w[m] == pytest.approx(
|
||||
fixture.LINE_68_M_APPLIANCES_W[m], abs=5e-2
|
||||
|
|
@ -627,5 +628,5 @@ def test_internal_gains_from_cert_matches_elmhurst_worksheet_all_fixtures(
|
|||
fixture.LINE_72_M_WATER_HEATING_GAINS_W[m], abs=1e-3
|
||||
), f"(72) month {m+1}"
|
||||
assert result.total_internal_gains_monthly_w[m] == pytest.approx(
|
||||
fixture.LINE_73_M_TOTAL_INTERNAL_GAINS_W[m], abs=3e-1
|
||||
fixture.LINE_73_M_TOTAL_INTERNAL_GAINS_W[m], abs=5e-3
|
||||
), f"(73) month {m+1}"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue