mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
§7 slice 2: two-main case 1 weighted-R per Table 9b
Adds secondary_fraction (203) + secondary_responsiveness orchestrator params. When both main systems heat the whole house (Table 9c case 1), the u-formula consumes a weighted responsiveness: R_eff = (1 - (203)) × R_primary + (203) × R_secondary Synthetic equivalence test pins the contract: any (frac, R_primary, R_secondary) call lands the same MIT as a single-main call with the weighted R. No fixture exercises case 1 (all 6 Elmhurst = single combi), so secondary_fraction defaults to 0 → identity behaviour. Case 2 (different parts heated separately) deferred — needs (203) > 1-(91) branch + conditional T_2 averaging + per-system Table 4e adjustment. No fixture data to drive. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
fa49d7b946
commit
13c2c6514f
2 changed files with 53 additions and 2 deletions
|
|
@ -237,6 +237,8 @@ def mean_internal_temperature_monthly(
|
|||
responsiveness: float,
|
||||
living_area_fraction: float,
|
||||
control_temperature_adjustment_c: float = 0.0,
|
||||
secondary_fraction: float = 0.0,
|
||||
secondary_responsiveness: float = 1.0,
|
||||
) -> MeanInternalTemperatureResult:
|
||||
"""SAP 10.2 §7 orchestrator — chain Table 9c steps 1–9 for all 12 months.
|
||||
|
||||
|
|
@ -255,7 +257,16 @@ def mean_internal_temperature_monthly(
|
|||
control_temperature_adjustment_c (93) Table 4e adj (defaults 0 — all 6
|
||||
Elmhurst fixtures have (93) = (92), so this is a
|
||||
known shortcut; cert-side mapping is a future slice).
|
||||
secondary_fraction (203) fraction of main heat from second main system
|
||||
when both heat the whole house (Table 9c case 1).
|
||||
Defaults 0 (single-main); used to compute weighted R
|
||||
per Table 9b: R_eff = (1-frac)·R_primary + frac·R_secondary.
|
||||
Case 2 (different parts heated) deferred — no fixture.
|
||||
"""
|
||||
effective_responsiveness = (
|
||||
(1.0 - secondary_fraction) * responsiveness
|
||||
+ secondary_fraction * secondary_responsiveness
|
||||
)
|
||||
elsewhere_off_hours = (
|
||||
_ELSEWHERE_OFF_HOURS_TYPE_3 if control_type == 3 else _ELSEWHERE_OFF_HOURS_TYPE_12
|
||||
)
|
||||
|
|
@ -285,7 +296,7 @@ def mean_internal_temperature_monthly(
|
|||
heating_temperature_c=_T_H1_C,
|
||||
off_hours_first=_LIVING_AREA_OFF_HOURS[0],
|
||||
off_hours_second=_LIVING_AREA_OFF_HOURS[1],
|
||||
external_temp_c=ext, responsiveness=responsiveness,
|
||||
external_temp_c=ext, responsiveness=effective_responsiveness,
|
||||
total_gains_w=gains, heat_transfer_coefficient_w_per_k=h,
|
||||
time_constant_h=tau,
|
||||
)
|
||||
|
|
@ -301,7 +312,7 @@ def mean_internal_temperature_monthly(
|
|||
heating_temperature_c=t_h2_m,
|
||||
off_hours_first=elsewhere_off_hours[0],
|
||||
off_hours_second=elsewhere_off_hours[1],
|
||||
external_temp_c=ext, responsiveness=responsiveness,
|
||||
external_temp_c=ext, responsiveness=effective_responsiveness,
|
||||
total_gains_w=gains, heat_transfer_coefficient_w_per_k=h,
|
||||
time_constant_h=tau,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -65,6 +65,46 @@ def test_mean_internal_temperature_monthly_reproduces_000490_line_92_jan() -> No
|
|||
assert result.mean_internal_temp_monthly[0] == pytest.approx(15.1899, abs=5e-3)
|
||||
|
||||
|
||||
def test_two_main_systems_case_1_uses_weighted_responsiveness_per_table_9b() -> None:
|
||||
# Arrange — SAP10.2 Table 9b weighted-R formula when both main systems
|
||||
# heat the whole house (same control type, shared T_h2 + Th-time grid):
|
||||
# R = (203) × R_system2 + [1 - (203)] × R_system1
|
||||
# Equivalent-R contract: passing secondary_fraction + secondary_R must
|
||||
# produce the same MIT as a single-main run with the weighted R.
|
||||
secondary_fraction = 0.3 # (203)
|
||||
primary_r = 1.0 # gas wet system (Table 4d)
|
||||
secondary_r = 0.0 # extreme — exaggerates the weighting effect
|
||||
expected_r = (1.0 - secondary_fraction) * primary_r + secondary_fraction * secondary_r
|
||||
|
||||
common_kwargs = dict(
|
||||
monthly_external_temp_c=_W000490_EXT_TEMP_C,
|
||||
monthly_total_gains_w=_W000490_TOTAL_GAINS_W,
|
||||
monthly_heat_transfer_coefficient_w_per_k=_W000490_HTC_W_PER_K,
|
||||
thermal_mass_parameter_kj_per_m2_k=_W000490_TMP_KJ_PER_M2_K,
|
||||
total_floor_area_m2=_W000490_TFA_M2,
|
||||
control_type=_W000490_CONTROL_TYPE,
|
||||
living_area_fraction=_W000490_LIVING_AREA_FRACTION,
|
||||
)
|
||||
|
||||
# Act
|
||||
two_system = mean_internal_temperature_monthly(
|
||||
responsiveness=primary_r,
|
||||
secondary_fraction=secondary_fraction,
|
||||
secondary_responsiveness=secondary_r,
|
||||
**common_kwargs,
|
||||
)
|
||||
equivalent_single = mean_internal_temperature_monthly(
|
||||
responsiveness=expected_r,
|
||||
**common_kwargs,
|
||||
)
|
||||
|
||||
# Assert — every month identical to floating-point precision
|
||||
for m in range(12):
|
||||
assert two_system.mean_internal_temp_monthly[m] == pytest.approx(
|
||||
equivalent_single.mean_internal_temp_monthly[m], abs=1e-12
|
||||
), f"(92) month {m+1} weighted-R contract"
|
||||
|
||||
|
||||
def test_elsewhere_temperature_control_type_2_uses_quadratic_drop() -> None:
|
||||
# Arrange — Table 9 elsewhere formula for control type 2 (programmer +
|
||||
# room thermostat, default for boiler systems with reasonable control):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue