mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 102f-prep.8: API mapper resolves shower_outlets=None → 0 mixers
Cert 2225 (Mitsubishi PUZ-WM50VHA, semi-detached 2-bp, TFA 82.49) lodges `sap_heating.shower_outlets = None` in the Open EPC API JSON. The worksheet (42a) "Hot water usage for mixer showers" reads 0 every month — Elmhurst's convention is "absent ⇒ no shower". Pre-fix the API mapper returned `mixer_shower_count = None`, deferring to the cert→inputs cascade's "RdSAP modal lodging" default of 1 vented mixer. That added ~7 L/day to (44) daily HW use, ~113 kWh/yr to (62) HW demand, and shifted cert 2225's SAP residual from -0.31 → +0.04 (now aligned with the cohort's +0.03..+0.06 cluster) once the mapper returns 0. `_count_shower_outlets_by_type` now treats None as 0 (the API mapper-only path). The cert→inputs cascade's `_mixer_shower_flow_rates_from_cert` keeps the None→1 default for the Elmhurst hand-built fixture path that doesn't route through this helper. Cohort impact: 6 of 7 ASHP certs now cluster at SAP Δ +0.03 to +0.06 (vs worksheet); only cert 2636 remains an outlier (+0.49). Golden cert PE/CO2 pins re-pinned for 6035, 8135, 0390 (the three certs that previously lodged shower_outlets=None and consumed the spurious 1-mixer default). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
5f9978ca33
commit
dfe2f2ce6e
3 changed files with 54 additions and 13 deletions
|
|
@ -685,6 +685,34 @@ _API_9418_JSON = (
|
|||
)
|
||||
|
||||
|
||||
_API_2225_JSON = (
|
||||
Path(__file__).parents[3]
|
||||
/ "domain/sap10_calculator/rdsap/tests/fixtures/golden"
|
||||
/ "2225-3062-8205-2856-7204.json"
|
||||
)
|
||||
|
||||
|
||||
def test_api_2225_no_mixer_lodged_uses_zero_showers_per_worksheet() -> None:
|
||||
# Arrange — cert 2225 lodges `mixer_shower_count = None` (the field
|
||||
# is unlodged in the API JSON, not "0"). The worksheet (42a) "Hot
|
||||
# water usage for mixer showers" shows 0.0000 every month — the
|
||||
# Elmhurst convention is "absent ⇒ no shower". Cascade previously
|
||||
# defaulted to a single 7 L/min vented mixer when unlodged, which
|
||||
# raised (44) daily HW use from 122.89 → 130.56 l/day (Jan) and
|
||||
# added ~113 kWh/yr to (62) HW demand. The cohort-modal lodging
|
||||
# is 0 (5/7 certs lodge mixer=0 explicitly).
|
||||
doc = json.loads(_API_2225_JSON.read_text())
|
||||
epc = EpcPropertyDataMapper.from_api_response(doc)
|
||||
|
||||
# Act
|
||||
inputs = cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)
|
||||
|
||||
# Assert — HW fuel kWh tracks worksheet (247) 1634.04 at 1e-1
|
||||
# (η_water = 172.85 implies demand 2824.44; fuel = demand / η).
|
||||
worksheet_hw_fuel_kwh = 1634.04
|
||||
assert abs(inputs.hot_water_kwh_per_yr - worksheet_hw_fuel_kwh) <= 0.1
|
||||
|
||||
|
||||
def test_api_9418_daikin_24h_duration_mean_internal_temp_matches_worksheet_92() -> None:
|
||||
# Arrange — cert 9418 (Daikin Altherma EDLQ05CAV3, PCDB 102421)
|
||||
# lodges `heating_duration_code = "24"`. Per SAP 10.2 Table N4 (PDF
|
||||
|
|
|
|||
|
|
@ -1971,16 +1971,22 @@ def _count_shower_outlets_by_type(
|
|||
schema_shower_outlets: Any, target_type: int,
|
||||
) -> Optional[int]:
|
||||
"""Count how many outlets in the schema list lodge the given
|
||||
`shower_outlet_type` integer. Returns None when the schema field
|
||||
is None or empty (the cascade reads None as "use the spec default"
|
||||
rather than 0 — RdSAP modal lodging assumption).
|
||||
`shower_outlet_type` integer. Returns 0 when the schema field is
|
||||
None — the Open EPC API convention is "no shower_outlets entry
|
||||
means no showers". Cert 2225 confirms: API lodges `shower_outlets
|
||||
= None` and the worksheet (42a) "Hot water usage for mixer
|
||||
showers" reads 0 for every month.
|
||||
|
||||
(The cert→inputs cascade's `_mixer_shower_flow_rates_from_cert`
|
||||
still keeps a None→1 default for the Elmhurst hand-built fixture
|
||||
path, which doesn't route through this helper.)
|
||||
|
||||
Assumes the input has been passed through
|
||||
`_normalize_shower_outlets` first — every list element is the
|
||||
wrapped `ShowerOutlets(shower_outlet=ShowerOutlet)` shape.
|
||||
"""
|
||||
if schema_shower_outlets is None:
|
||||
return None
|
||||
return 0
|
||||
if not isinstance(schema_shower_outlets, list):
|
||||
outlet = schema_shower_outlets.shower_outlet
|
||||
if outlet is None:
|
||||
|
|
|
|||
|
|
@ -140,14 +140,17 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="6035-7729-2309-0879-2296",
|
||||
actual_sap=70,
|
||||
expected_sap_resid=-6,
|
||||
expected_pe_resid_kwh_per_m2=+47.8483,
|
||||
expected_co2_resid_tonnes_per_yr=+1.0911,
|
||||
expected_pe_resid_kwh_per_m2=+46.7562,
|
||||
expected_co2_resid_tonnes_per_yr=+1.0652,
|
||||
notes=(
|
||||
"Mid-terrace, TFA 128, age A, gas combi Table 4b code 104. "
|
||||
"Slice 59 per-bp window apportionment tightens all 3 "
|
||||
"residuals: SAP -5 → -4, PE +36.15 → +34.02, CO2 +0.81 → "
|
||||
"+0.76 (2 of 8 windows route to Ext1 with ins_type 4 vs "
|
||||
"Main ins_type 3, lowering Ext1's net wall U-loss)."
|
||||
"Main ins_type 3, lowering Ext1's net wall U-loss). Slice "
|
||||
"102f-prep.8 mapper fix: shower_outlets=None now resolves to "
|
||||
"0 mixers (was 1) — drops daily HW by ~7 l/day → PE +47.85 "
|
||||
"→ +46.76, CO2 +1.09 → +1.07."
|
||||
),
|
||||
),
|
||||
_GoldenExpectation(
|
||||
|
|
@ -173,15 +176,17 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="8135-1728-8500-0511-3296",
|
||||
actual_sap=72,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=-3.6590,
|
||||
expected_co2_resid_tonnes_per_yr=-0.0432,
|
||||
expected_pe_resid_kwh_per_m2=-4.9611,
|
||||
expected_co2_resid_tonnes_per_yr=-0.0678,
|
||||
notes=(
|
||||
"Semi-detached, TFA 102, age C, gas PCDB-listed. Cert lodges "
|
||||
"blocked_chimneys_count=1. Slice 59 per-bp window apportionment "
|
||||
"tightens PE -16.98 → -16.51 and CO2 -0.30 → -0.29; SAP "
|
||||
"residual unchanged at +1. Slice 97 added glazing_type=2 — "
|
||||
"SAP residual unchanged (cert rounds to 72 either way); PE "
|
||||
"-2.41 → -5.31 and CO2 -0.02 → -0.07."
|
||||
"-2.41 → -5.31 and CO2 -0.02 → -0.07. Slice 102f-prep.8: "
|
||||
"shower_outlets=None → 0 mixers shifts PE -3.66 → -4.96, "
|
||||
"CO2 -0.04 → -0.07."
|
||||
),
|
||||
),
|
||||
_GoldenExpectation(
|
||||
|
|
@ -208,8 +213,8 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
cert_number="0390-2254-6420-2126-5561",
|
||||
actual_sap=65,
|
||||
expected_sap_resid=+0,
|
||||
expected_pe_resid_kwh_per_m2=+1.6962,
|
||||
expected_co2_resid_tonnes_per_yr=+0.0639,
|
||||
expected_pe_resid_kwh_per_m2=+0.1521,
|
||||
expected_co2_resid_tonnes_per_yr=+0.0409,
|
||||
notes=(
|
||||
"End-terrace + 1 extension, TFA 80, gas combi PCDB index 18119, "
|
||||
"no PV, no secondary, postcode LN12 (PCDB Table 172 match). "
|
||||
|
|
@ -219,7 +224,9 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
|||
"infiltration vs the pre-fix zero default). PE / CO2 residuals "
|
||||
"are now small enough that the remaining drivers are likely "
|
||||
"lighting efficacy (schema-21 doesn't carry led_/cfl bulb "
|
||||
"counts for this cert) + boiler PCDB winter efficiency lookup."
|
||||
"counts for this cert) + boiler PCDB winter efficiency lookup. "
|
||||
"Slice 102f-prep.8: shower_outlets=None → 0 mixers tightens "
|
||||
"PE +1.70 → +0.15 and CO2 +0.06 → +0.04."
|
||||
),
|
||||
),
|
||||
# Retired early at P2.2: 9390-2722-3520-2105-8715 (mid-floor flat,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue