mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 21a: relabel ambient SAP 10.3 → SAP 10.2 in calculator docstrings
The codebase targets SAP 10.2 (14-03-2025) per ADR-0010 and the values match SAP 10.2 (grid CO2 = 0.136 not 0.086, ECF deflator = 0.42, etc.). But ~35 docstrings/comments labelled formulas / sections / appendices as "SAP 10.3 (13-01-2026)" — mis-labeling without affecting behaviour. Relabels all of them to "SAP 10.2 specification (14-03-2025)" where the formula being implemented is identical between 10.2 and 10.3 (which is the vast majority — §1-§9 heat balance, §11/§13 SAP rating equations, Appendix U climate tables, Table 9a/9c utilisation factor). Intentionally retained: - `worksheet/rating.py:14` — explicit comparison "SAP 10.3 widens these to 0.36 / 16.21 / 108.8 / 120.5" annotating where 10.3 values would differ from the 10.2 values we ship. - `tables/table_12.py` — its docstring explicitly compares 10.2 vs 10.3 CO2 / PEF differences; the file's purpose is the 10.2 → 10.3 reference table, so the 10.3 label is intentional discussion. All 515 passing tests continue to pass (only the 48 known cascade-pin failures from slice 19a remain — those are real residuals, not label issues). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e2d9f77d0f
commit
20424a2dca
11 changed files with 41 additions and 41 deletions
|
|
@ -1,4 +1,4 @@
|
|||
"""SAP 10.3 synthetic-input calculator orchestrator.
|
||||
"""SAP 10.2 synthetic-input calculator orchestrator.
|
||||
|
||||
Drives the 12-month heat-balance loop from a typed `CalculatorInputs`
|
||||
aggregate and emits a typed `SapResult`. This module is the physics
|
||||
|
|
@ -26,7 +26,7 @@ Annual totals = month sums; ECF = §13 Table 12 deflator × total cost /
|
|||
emission factor × delivered fuel (single-fuel approximation in this
|
||||
slice — slice S-A8 splits hot-water/lighting onto per-fuel factors).
|
||||
|
||||
Reference: SAP 10.3 specification (13-01-2026) §§5-13 (pages 23-43), Table
|
||||
Reference: SAP 10.2 specification (14-03-2025) §§5-13 (pages 23-43), Table
|
||||
9a/9b/9c (pages 184-186), Table 12 (page 191), Appendix L + U.
|
||||
"""
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ _ZERO_FUEL_COST_RESULT: Final[FuelCostResult] = FuelCostResult(
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class CalculatorInputs:
|
||||
"""Synthetic SAP 10.3 calculator inputs. The cert→inputs mapper
|
||||
"""Synthetic SAP 10.2 calculator inputs. The cert→inputs mapper
|
||||
(S-A7b) produces one of these from an `EpcPropertyData`.
|
||||
|
||||
Fuel-cost fields are per-end-use because SAP §12 / Table 32 charges
|
||||
|
|
@ -224,7 +224,7 @@ class CalculatorInputs:
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class MonthlyEntry:
|
||||
"""Per-month worksheet outputs for downstream audit. SAP 10.3 §§5-9."""
|
||||
"""Per-month worksheet outputs for downstream audit. SAP 10.2 §§5-9."""
|
||||
|
||||
month: int
|
||||
external_temp_c: float
|
||||
|
|
@ -321,7 +321,7 @@ def _solve_month(
|
|||
|
||||
|
||||
def calculate_sap_from_inputs(inputs: CalculatorInputs) -> SapResult:
|
||||
"""Run SAP 10.3 §§5-13 monthly loop on synthetic inputs; return a
|
||||
"""Run SAP 10.2 §§5-13 monthly loop on synthetic inputs; return a
|
||||
typed `SapResult`. Cert-shape mapping is the job of `cert_to_inputs`
|
||||
(S-A7b); this entry point is pure physics."""
|
||||
tfa = inputs.dimensions.total_floor_area_m2
|
||||
|
|
@ -521,7 +521,7 @@ def calculate_sap_from_inputs(inputs: CalculatorInputs) -> SapResult:
|
|||
|
||||
|
||||
class Sap10Calculator:
|
||||
"""Deterministic SAP 10.3 calculator entry point. Maps an
|
||||
"""Deterministic SAP 10.2 calculator entry point. Maps an
|
||||
`EpcPropertyData` to typed `CalculatorInputs` via the RdSAP-driven
|
||||
`cert_to_inputs` mapper and runs the 12-month worksheet loop.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""SAP 10.3 Appendix U — climate data lookups.
|
||||
"""SAP 10.2 Appendix U — climate data lookups.
|
||||
|
||||
Source: BRE, *The Government's Standard Assessment Procedure for Energy
|
||||
Rating of Dwellings, SAP 10.3* (13-01-2026), Appendix U.
|
||||
Rating of Dwellings, SAP 10.2* (14-03-2025), Appendix U.
|
||||
|
||||
Three monthly tables across 22 SAP climate regions (index 0 = UK average,
|
||||
1-21 = named regions per Table U6 postcode mapping):
|
||||
|
|
@ -11,7 +11,7 @@ Three monthly tables across 22 SAP climate regions (index 0 = UK average,
|
|||
- Table U3: Mean global solar irradiance on a horizontal plane (W/m²)
|
||||
plus monthly solar declination (°)
|
||||
|
||||
Month is 1-12 (January = 1). Region indices map to the SAP 10.3 region
|
||||
Month is 1-12 (January = 1). Region indices map to the SAP 10.2 region
|
||||
names; lookup helpers raise `ValueError` on out-of-range inputs so callers
|
||||
can fail fast.
|
||||
"""
|
||||
|
|
@ -109,7 +109,7 @@ def wind_speed_m_per_s(region: int, month: int) -> float:
|
|||
# Table U3 — Mean global solar irradiance on a horizontal plane (W/m²),
|
||||
# 22 regions × 12 months. Used (with Table U3 declination + per-window
|
||||
# orientation/pitch) to derive surface flux for solar-gains calculation
|
||||
# (SAP 10.3 §6.1).
|
||||
# (SAP 10.2 §6.1).
|
||||
_TABLE_U3: Final[tuple[tuple[float, ...], ...]] = (
|
||||
(26, 54, 96, 150, 192, 200, 189, 157, 115, 66, 33, 21), # 0 UK average
|
||||
(30, 56, 98, 157, 195, 217, 203, 173, 127, 73, 39, 24), # 1 Thames
|
||||
|
|
@ -139,7 +139,7 @@ _TABLE_U3: Final[tuple[tuple[float, ...], ...]] = (
|
|||
def horizontal_solar_irradiance_w_per_m2(region: int, month: int) -> float:
|
||||
"""Mean global solar irradiance on a horizontal plane (W/m²) for a SAP
|
||||
climate region in a month. The starting point for the per-orientation
|
||||
surface-flux calculation in SAP 10.3 §6.1."""
|
||||
surface-flux calculation in SAP 10.2 §6.1."""
|
||||
_validate(region, month)
|
||||
return float(_TABLE_U3[region][month - 1])
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ _SOLAR_DECLINATION: Final[tuple[float, ...]] = (
|
|||
|
||||
|
||||
def solar_declination_deg(month: int) -> float:
|
||||
"""Solar declination angle (°) for the given month. SAP 10.3 Appendix U
|
||||
"""Solar declination angle (°) for the given month. SAP 10.2 Appendix U
|
||||
Table U3 footer — independent of region."""
|
||||
_validate_month(month)
|
||||
return _SOLAR_DECLINATION[month - 1]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""RdSAP 10 cert → SAP 10.3 CalculatorInputs mapping.
|
||||
"""RdSAP 10 cert → SAP 10.2 CalculatorInputs mapping.
|
||||
|
||||
Reads `EpcPropertyData` (the gov EPC API / site-notes domain model) and
|
||||
produces the typed `CalculatorInputs` the synthetic-input orchestrator
|
||||
|
|
@ -13,7 +13,7 @@ Defaulting rules per RdSAP 10 (10-06-2025):
|
|||
- Heat transmission: §5 (port in `worksheet/heat_transmission.py`)
|
||||
- Infiltration: §4 Table 5 (port in `worksheet/ventilation.py`)
|
||||
- Living-area fraction: Table 27 by `habitable_rooms_count`
|
||||
- Heating efficiency: SAP 10.3 Tables 4a/4b (existing
|
||||
- Heating efficiency: SAP 10.2 Tables 4a/4b (existing
|
||||
`domain.ml.sap_efficiencies.seasonal_efficiency` cascade)
|
||||
- Hot-water demand: Appendix J (existing `domain.ml.demand`)
|
||||
- Lighting demand: Appendix L simplified (`domain.ml.demand`)
|
||||
|
|
@ -30,7 +30,7 @@ Edge cases deliberately deferred to Session B:
|
|||
- control_temperature_adjustment from main_heating_control code 2101/2103/2106
|
||||
(defaults to 0)
|
||||
|
||||
Reference: RdSAP 10 specification (10-06-2025); SAP 10.3 specification
|
||||
Reference: RdSAP 10 specification (10-06-2025); SAP 10.2 specification
|
||||
(13-01-2026) Tables 4a/4b/4e/12.
|
||||
"""
|
||||
|
||||
|
|
@ -277,7 +277,7 @@ SAP_10_2_SPEC_PRICES: Final[PriceTable] = PriceTable(
|
|||
)
|
||||
|
||||
|
||||
# SAP 10.3 Table 9 main_heating_control codes → control type (1/2/3).
|
||||
# SAP 10.2 Table 9 main_heating_control codes → control type (1/2/3).
|
||||
# Type 1: no time + temp control, or one but not both.
|
||||
# Type 2: programmer + room thermostat (+/− TRVs).
|
||||
# Type 3: time-and-temperature zone control (e.g. separate living-zone
|
||||
|
|
@ -376,7 +376,7 @@ def _first_main_heating(epc: EpcPropertyData) -> Optional[MainHeatingDetail]:
|
|||
|
||||
|
||||
def _control_type(main: Optional[MainHeatingDetail]) -> int:
|
||||
"""SAP 10.3 §7.1 / Table 9 control type 1/2/3 from the
|
||||
"""SAP 10.2 §7.1 / Table 9 control type 1/2/3 from the
|
||||
`main_heating_control` code on `MainHeatingDetail`. Defaults to 2
|
||||
(programmer + room thermostat) when the code is missing — the modal
|
||||
RdSAP case."""
|
||||
|
|
@ -389,7 +389,7 @@ def _control_type(main: Optional[MainHeatingDetail]) -> int:
|
|||
|
||||
|
||||
def _responsiveness(main: Optional[MainHeatingDetail]) -> float:
|
||||
"""SAP 10.3 Table 9b responsiveness R ∈ [0, 1]. Radiators ≈ 1.0;
|
||||
"""SAP 10.2 Table 9b responsiveness R ∈ [0, 1]. Radiators ≈ 1.0;
|
||||
underfloor ≈ 0.25. Defaults to radiators."""
|
||||
if main is None:
|
||||
return 1.0
|
||||
|
|
@ -656,7 +656,7 @@ def _water_efficiency_with_category_inherit(
|
|||
|
||||
|
||||
def _co2_factor_kg_per_kwh(main: Optional[MainHeatingDetail]) -> float:
|
||||
"""SAP 10.3 Table 12 CO2 emission factor by fuel code."""
|
||||
"""SAP 10.2 Table 12 CO2 emission factor by fuel code."""
|
||||
return co2_factor_kg_per_kwh(_main_fuel_code(main))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Parity-validation report for the deterministic SAP 10.3 calculator.
|
||||
"""Parity-validation report for the deterministic SAP 10.2 calculator.
|
||||
|
||||
ADR-0009 Session B compares `Sap10Calculator.calculate(epc).sap_score`
|
||||
to the cert's `energy_rating_current` across a 1000-cert stratified
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""SAP 10.3 §1 — dwelling dimensions.
|
||||
"""SAP 10.2 §1 — dwelling dimensions.
|
||||
|
||||
Builds the typed `Dimensions` aggregate that the rest of the worksheet
|
||||
reads: total floor area, volume, gross/party wall areas, ground and top
|
||||
|
|
@ -7,7 +7,7 @@ floor areas, perimeter. Geometry is summed across every entry in
|
|||
N parts produces totals over all N. Room-in-roof contributes one
|
||||
additional storey per part where present (RdSAP §1.8 + §3.9).
|
||||
|
||||
Reference: SAP 10.3 specification (13-01-2026), §1 (pages 10-12); for
|
||||
Reference: SAP 10.2 specification (14-03-2025), §1 (pages 10-12); for
|
||||
existing dwellings see RdSAP 10 §3 (areas and dimensions).
|
||||
|
||||
Edge cases explicitly out of scope for the first slice (see ADR-0009
|
||||
|
|
@ -34,7 +34,7 @@ _RR_SIMPLIFIED_STOREY_HEIGHT_M: Final[float] = 2.45
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class Dimensions:
|
||||
"""SAP 10.3 §1 geometric inputs to the monthly heat-balance loop."""
|
||||
"""SAP 10.2 §1 geometric inputs to the monthly heat-balance loop."""
|
||||
|
||||
total_floor_area_m2: float
|
||||
volume_m3: float
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ class DwellingExposure:
|
|||
dwelling. Houses + bungalows expose both floor and roof; flats expose
|
||||
only the surfaces that aren't party with neighbouring dwellings.
|
||||
|
||||
SAP 10.3 §3 / RdSAP 10 §5: heat-transmission excludes party surfaces;
|
||||
SAP 10.2 §3 / RdSAP 10 §5: heat-transmission excludes party surfaces;
|
||||
`party_walls_w_per_k` already captures the party-wall channel using
|
||||
its own U_party.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""SAP 10.3 mean internal temperature (Tables 9, 9b, 9c).
|
||||
"""SAP 10.2 mean internal temperature (Tables 9, 9b, 9c).
|
||||
|
||||
The dwelling has two temperature zones during heating periods:
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ Standard SAP heating schedule:
|
|||
- 9 hours heating per day, two off-periods of 7 h and 8 h (control types 1, 2)
|
||||
- Control type 3 zones the heating: 9 h and 8 h off in 'elsewhere'
|
||||
|
||||
Reference: SAP 10.3 specification (13-01-2026) Table 9 (page 183),
|
||||
Reference: SAP 10.2 specification (14-03-2025) Table 9 (page 183),
|
||||
Table 9b (page 184), Table 9c (page 185).
|
||||
"""
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ def off_period_temperature_reduction_c(
|
|||
utilisation_factor: float,
|
||||
time_constant_h: float,
|
||||
) -> float:
|
||||
"""SAP 10.3 Table 9b — temperature reduction `u` during a heating-off
|
||||
"""SAP 10.2 Table 9b — temperature reduction `u` during a heating-off
|
||||
period, in °C below the heating-period temperature.
|
||||
|
||||
t_c = 4 + 0.25 × τ
|
||||
|
|
|
|||
|
|
@ -43,12 +43,12 @@ def energy_cost_factor(
|
|||
total_cost_gbp: float,
|
||||
total_floor_area_m2: float,
|
||||
) -> float:
|
||||
"""SAP 10.3 §13 equation (7): ECF = 0.36 × cost / (TFA + 45)."""
|
||||
"""SAP 10.2 §13 equation (7): ECF = 0.36 × cost / (TFA + 45)."""
|
||||
return ENERGY_COST_DEFLATOR * total_cost_gbp / (total_floor_area_m2 + FLOOR_AREA_OFFSET_M2)
|
||||
|
||||
|
||||
def sap_rating(*, ecf: float) -> float:
|
||||
"""SAP 10.3 §13 equations (8)/(9). Un-rounded result so callers can
|
||||
"""SAP 10.2 §13 equations (8)/(9). Un-rounded result so callers can
|
||||
inspect the continuous value; `sap_rating_integer` rounds and clamps."""
|
||||
if ecf >= ECF_LOG_THRESHOLD:
|
||||
return _SAP_LOG_INTERCEPT - _SAP_LOG_SLOPE * log10(ecf)
|
||||
|
|
@ -56,7 +56,7 @@ def sap_rating(*, ecf: float) -> float:
|
|||
|
||||
|
||||
def sap_rating_integer(*, ecf: float) -> int:
|
||||
"""SAP 10.3 §13: round the continuous SAP rating to the nearest integer
|
||||
"""SAP 10.2 §13: round the continuous SAP rating to the nearest integer
|
||||
and clamp to a minimum of 1 ("if the result of the calculation is less
|
||||
than 1 the rating should be quoted as 1"). The integer value is the
|
||||
one published on the EPC."""
|
||||
|
|
@ -68,7 +68,7 @@ def environmental_impact_rating(
|
|||
co2_emissions_kg_per_yr: float,
|
||||
total_floor_area_m2: float,
|
||||
) -> float:
|
||||
"""SAP 10.3 §14 equations (10)-(12). Un-rounded EI rating; mirrors the
|
||||
"""SAP 10.2 §14 equations (10)-(12). Un-rounded EI rating; mirrors the
|
||||
SAP rating curve but uses CO2 emissions per (TFA + 45) as the input."""
|
||||
cf = co2_emissions_kg_per_yr / (total_floor_area_m2 + FLOOR_AREA_OFFSET_M2)
|
||||
if cf >= _CF_LOG_THRESHOLD:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""SAP 10.3 §6 + Appendix U §U3.2 — solar gains.
|
||||
"""SAP 10.2 §6 + Appendix U §U3.2 — solar gains.
|
||||
|
||||
Two layers:
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ Defaults for g⊥ (Table 6b), FF (Table 6c), Z (Table 6d) are deferred to
|
|||
the cert→inputs mapper slice — this module takes them as caller inputs so
|
||||
its physics is independent of cert-shape assumptions.
|
||||
|
||||
Reference: SAP 10.3 specification §6 + Appendix U §§U3.2-3.3 (pages
|
||||
Reference: SAP 10.2 specification §6 + Appendix U §§U3.2-3.3 (pages
|
||||
127-129); Table U5 columns map 8 cardinal cert orientations to 5
|
||||
coefficient sets (N, NE/NW, E/W, SE/SW, S).
|
||||
"""
|
||||
|
|
@ -116,7 +116,7 @@ def surface_solar_flux_w_per_m2(
|
|||
) -> float:
|
||||
"""Per-orientation per-pitch monthly solar flux on a surface (W/m²).
|
||||
|
||||
SAP 10.3 Appendix U §U3.2 polynomial conversion from the horizontal
|
||||
SAP 10.2 Appendix U §U3.2 polynomial conversion from the horizontal
|
||||
irradiance in Table U3 to any orientation/tilt combination.
|
||||
"""
|
||||
s_h = horizontal_solar_irradiance_w_per_m2(region, month)
|
||||
|
|
@ -146,7 +146,7 @@ def window_solar_gain_w(
|
|||
frame_factor: float,
|
||||
overshading_factor: float,
|
||||
) -> float:
|
||||
"""Solar gain through a window (W). SAP 10.3 §6.1 equation (5):
|
||||
"""Solar gain through a window (W). SAP 10.2 §6.1 equation (5):
|
||||
|
||||
G_solar = 0.9 × A_w × S × g⊥ × FF × Z
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""SAP 10.3 Table 9c step 10 — monthly space-heating requirement.
|
||||
"""SAP 10.2 Table 9c step 10 — monthly space-heating requirement.
|
||||
|
||||
Final step of the heating worksheet: the heat the heating system has to
|
||||
deliver to maintain the mean internal temperature, given the loss rate to
|
||||
|
|
@ -10,7 +10,7 @@ the outside and the gains the dwelling has already accumulated.
|
|||
If Q_heat would be negative or below 1 kWh in any month, set it to 0 per
|
||||
the Table 9c clamp.
|
||||
|
||||
Reference: SAP 10.3 specification (13-01-2026) Table 9c (page 185).
|
||||
Reference: SAP 10.2 specification (14-03-2025) Table 9c (page 185).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -37,7 +37,7 @@ def monthly_heat_requirement_kwh(
|
|||
total_gains_w: float,
|
||||
days_in_month: int,
|
||||
) -> float:
|
||||
"""SAP 10.3 Table 9c step 10. Returns delivered kWh required for the
|
||||
"""SAP 10.2 Table 9c step 10. Returns delivered kWh required for the
|
||||
month; clamps to 0 when below 1 kWh or negative."""
|
||||
loss_rate_w = heat_transfer_coefficient_w_per_k * (
|
||||
internal_temperature_c - external_temperature_c
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""SAP 10.3 Table 9a — heating utilisation factor η.
|
||||
"""SAP 10.2 Table 9a — heating utilisation factor η.
|
||||
|
||||
η reduces the contribution of internal + solar gains when they outpace the
|
||||
dwelling's heat-loss rate. A well-insulated dwelling with large solar gains
|
||||
|
|
@ -16,7 +16,7 @@ The time constant τ = TMP / (3.6 × HLP) comes from the dwelling's thermal
|
|||
mass parameter and heat-loss parameter; computed by the orchestrator and
|
||||
passed in here.
|
||||
|
||||
Reference: SAP 10.3 specification (13-01-2026) Table 9a (page 184).
|
||||
Reference: SAP 10.2 specification (14-03-2025) Table 9a (page 184).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -28,7 +28,7 @@ def utilisation_factor(
|
|||
heat_loss_rate_w: float,
|
||||
time_constant_h: float,
|
||||
) -> float:
|
||||
"""SAP 10.3 Table 9a heating utilisation factor η.
|
||||
"""SAP 10.2 Table 9a heating utilisation factor η.
|
||||
|
||||
γ = total_gains_w / heat_loss_rate_w; η ∈ (0, 1]. When the heat-loss
|
||||
rate is non-positive (dwelling already balanced or gaining), η = 1
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue