mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.67: Appendix H (H9) helper + fix (H23) W·h → kWh units
S0380.66 landed (H23) with an incorrect unit treatment: the spec
formula on SAP 10.2 p.76 is
Y_HW = [(H18)m × (H6) × (H5) × (H9)m × (41)m × 24] ÷ [1000 × (H17)m]
and Appendix U (per `domain/sap10_calculator/climate/appendix_u.
horizontal_solar_irradiance_w_per_m2`) returns (H7)m as a monthly-
average flux in W/m². That makes (H9)m = (H1) × (H2) × (H7)m × (H8)
an instantaneous power in W — the `× hours × 24 / 1000` factor in
the (H23) formula is what time-integrates W·h → kWh so the Y_HW
ratio lands dimensionless against (H17)m (kWh/month).
S0380.66's (H23) elided the time integration by absorbing it into
the input parameter (a kWh/m²/month name) — that broke unit
consistency with the downstream Appendix U integration this module
will consume in the next slice.
Changes:
- New `monthly_solar_energy_available_h9_w` — pure (H9)m calculator
taking aperture, η₀, (H7)m flux tuple, and overshading. Returns W.
- `hot_water_factor_y_monthly_h23`: parameter renamed
`monthly_solar_energy_available_h9_w` (was `..._kwh_per_m2`); new
`hours_in_month` parameter; formula now includes the spec's
`× hours / 1000` time integration explicitly.
Tests:
- `test_monthly_solar_energy_available_h9_applies_spec_formula` —
cert 000565 H1/H2/H8 with flat 100 W/m² flux → 192 W (the spec
multiplicand 3 × 0.8 × 100 × 0.8).
- `test_hot_water_factor_y_h23_applies_w_to_kwh_time_integration` —
unit-consistency pin: H9=1000 W, hours=744, H17=744 kWh → Y=1.0.
- `test_hot_water_factor_y_h23_clamps_lower_bound_at_zero` updated
to the new parameter name and supplies `hours_in_month`.
Test suite: 277 pass + 9 expected 000565 cascade-gap fails. Pyright
net-zero on both touched files.
Spec source: SAP 10.2 specification (14-03-2025) Appendix H p.76.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
db4f1b3167
commit
2795e2569d
2 changed files with 73 additions and 13 deletions
|
|
@ -247,13 +247,35 @@ def hot_water_factor_x_monthly_h22(
|
|||
return tuple(out)
|
||||
|
||||
|
||||
def monthly_solar_energy_available_h9_w(
|
||||
*,
|
||||
aperture_area_m2: float, # (H1)
|
||||
zero_loss_efficiency: float, # (H2)
|
||||
monthly_solar_flux_w_per_m2: tuple[float, ...], # (H7)m from Appendix U §U3.3
|
||||
overshading_factor: float, # (H8)
|
||||
) -> tuple[float, ...]:
|
||||
"""SAP 10.2 (H9)m — solar energy available on collector aperture (W).
|
||||
|
||||
Spec p.76: "(H1) × (H2) × (H7)m × (H8)". The result is an
|
||||
instantaneous (monthly-average) power in watts — the worksheet's
|
||||
downstream (H22) and (H23) formulas multiply by `(41)m × 24`
|
||||
(hours-in-month) and divide by 1000 to convert to kWh/month, so
|
||||
keeping (H9)m in W here matches the spec's stepwise units.
|
||||
"""
|
||||
return tuple(
|
||||
aperture_area_m2 * zero_loss_efficiency * flux * overshading_factor
|
||||
for flux in monthly_solar_flux_w_per_m2
|
||||
)
|
||||
|
||||
|
||||
def hot_water_factor_y_monthly_h23(
|
||||
*,
|
||||
proportion_solar_to_hw_h18: tuple[float, ...], # (H18)m
|
||||
incidence_angle_modifier: float, # (H6)
|
||||
loop_efficiency: float, # (H5)
|
||||
monthly_solar_energy_available_h9_kwh_per_m2: tuple[float, ...], # (H9)m as ENERGY in kWh/m²/month
|
||||
hw_demand_seen_by_solar_h17: tuple[float, ...], # (H17)m
|
||||
proportion_solar_to_hw_h18: tuple[float, ...], # (H18)m
|
||||
incidence_angle_modifier: float, # (H6)
|
||||
loop_efficiency: float, # (H5)
|
||||
monthly_solar_energy_available_h9_w: tuple[float, ...], # (H9)m in W (per `monthly_solar_energy_available_h9_w`)
|
||||
hours_in_month: tuple[int, ...], # (41)m × 24
|
||||
hw_demand_seen_by_solar_h17: tuple[float, ...], # (H17)m
|
||||
) -> tuple[float, ...]:
|
||||
"""SAP 10.2 (H23)m — HW factor Y.
|
||||
|
||||
|
|
@ -261,11 +283,9 @@ def hot_water_factor_y_monthly_h23(
|
|||
[1000 × (H17)m]
|
||||
|
||||
Clamped to a lower bound of 0 per spec p.76 (`if Y < 0, enter zero`).
|
||||
The `(H9)m × hours_in_month × 24 / 1000` shape in the spec captures
|
||||
a flux-to-energy conversion; in this module (H9)m is supplied
|
||||
directly as energy in kWh/m²/month (the upstream computation
|
||||
`aperture × η₀ × overshading × monthly_radiation` already includes
|
||||
the time integration), so the conversion factors collapse out.
|
||||
(H9)m is in W (per `monthly_solar_energy_available_h9_w`); the
|
||||
`× hours_in_month / 1000` factor here converts W·h → kWh so the
|
||||
ratio Y_HW lands dimensionless against (H17)m (kWh/month).
|
||||
"""
|
||||
out: list[float] = []
|
||||
for m in range(12):
|
||||
|
|
@ -277,9 +297,10 @@ def hot_water_factor_y_monthly_h23(
|
|||
proportion_solar_to_hw_h18[m]
|
||||
* incidence_angle_modifier
|
||||
* loop_efficiency
|
||||
* monthly_solar_energy_available_h9_kwh_per_m2[m]
|
||||
* monthly_solar_energy_available_h9_w[m]
|
||||
* hours_in_month[m]
|
||||
)
|
||||
y = numerator / h17
|
||||
y = numerator / (1000.0 * h17)
|
||||
out.append(max(0.0, y))
|
||||
return tuple(out)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from domain.sap10_calculator.worksheet.appendix_h_solar import (
|
|||
hot_water_reference_temperature_difference_h21_c,
|
||||
hot_water_reference_temperature_h20_c,
|
||||
loop_heat_loss_coefficient_h11,
|
||||
monthly_solar_energy_available_h9_w,
|
||||
overall_heat_loss_coefficient_h10,
|
||||
proportion_solar_to_hot_water_monthly_h18,
|
||||
reference_volume_h15,
|
||||
|
|
@ -257,6 +258,23 @@ def test_hot_water_factor_x_h22_clamps_upper_bound_at_18() -> None:
|
|||
assert actual == (18.0,) * 12
|
||||
|
||||
|
||||
def test_monthly_solar_energy_available_h9_applies_spec_formula() -> None:
|
||||
# Arrange — SAP 10.2 (H9)m spec p.76: (H1) × (H2) × (H7)m × (H8)
|
||||
# Cert 000565 worksheet (H1)=3.0, (H2)=0.8, (H8)=0.8. With a flat
|
||||
# 100 W/m² flux input, expected = 3 × 0.8 × 100 × 0.8 = 192 W.
|
||||
|
||||
# Act
|
||||
actual = monthly_solar_energy_available_h9_w(
|
||||
aperture_area_m2=_CERT_000565_APERTURE_AREA_M2,
|
||||
zero_loss_efficiency=_CERT_000565_ETA_0,
|
||||
monthly_solar_flux_w_per_m2=(100.0,) * 12,
|
||||
overshading_factor=_CERT_000565_OVERSHADING,
|
||||
)
|
||||
|
||||
# Assert — H1=3, H2=0.8, H7=100, H8=0.8 → 192 W/month
|
||||
assert all(abs(h9 - 192.0) < 1e-9 for h9 in actual)
|
||||
|
||||
|
||||
def test_hot_water_factor_y_h23_clamps_lower_bound_at_zero() -> None:
|
||||
# Arrange — spec p.76 "if Y_HW < 0, enter zero". Negative solar
|
||||
# energy available (theoretical edge case) must not flow through.
|
||||
|
|
@ -266,7 +284,8 @@ def test_hot_water_factor_y_h23_clamps_lower_bound_at_zero() -> None:
|
|||
proportion_solar_to_hw_h18=(1.0,) * 12,
|
||||
incidence_angle_modifier=0.94,
|
||||
loop_efficiency=0.9,
|
||||
monthly_solar_energy_available_h9_kwh_per_m2=(-5.0,) * 12,
|
||||
monthly_solar_energy_available_h9_w=(-5.0,) * 12,
|
||||
hours_in_month=(744,) * 12,
|
||||
hw_demand_seen_by_solar_h17=(100.0,) * 12,
|
||||
)
|
||||
|
||||
|
|
@ -274,6 +293,26 @@ def test_hot_water_factor_y_h23_clamps_lower_bound_at_zero() -> None:
|
|||
assert actual == (0.0,) * 12
|
||||
|
||||
|
||||
def test_hot_water_factor_y_h23_applies_w_to_kwh_time_integration() -> None:
|
||||
# Arrange — spec p.76: Y_HW = [(H18) × (H6) × (H5) × (H9) × hours]
|
||||
# ÷ [1000 × (H17)]. With H18=1, H6=1, H5=1, H9=1000 W,
|
||||
# hours=744 (Jan), H17=744 kWh → numerator = 1000 × 744 = 744000 W·h
|
||||
# ÷ 1000 = 744 kWh, ÷ H17 = 744/744 = 1.0 ✓ dimensionless.
|
||||
|
||||
# Act
|
||||
actual = hot_water_factor_y_monthly_h23(
|
||||
proportion_solar_to_hw_h18=(1.0,) * 12,
|
||||
incidence_angle_modifier=1.0,
|
||||
loop_efficiency=1.0,
|
||||
monthly_solar_energy_available_h9_w=(1000.0,) * 12,
|
||||
hours_in_month=(744,) * 12,
|
||||
hw_demand_seen_by_solar_h17=(744.0,) * 12,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert all(abs(y - 1.0) < 1e-9 for y in actual)
|
||||
|
||||
|
||||
def test_heat_delivered_to_hot_water_h24_applies_equation_h1_polynomial() -> None:
|
||||
# Arrange — spec Equation H1 with Table H3 (p.78) factors:
|
||||
# Q = (Ca·Y + Cb·X + Cc·Y² + Cd·X² + Ce·Y³ + Cf·X³) × demand
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue