mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Slice S0380.37: drop cert 001479 hand-built fixture — covered by passing production-path chain tests
Cert 001479 was added in ee98dbe0 as "skeleton + 11 RED pins" — a
hand-built EpcPropertyData intended to cascade to worksheet
P960-0001-001479.pdf at 1e-4 for 9 SapResult fields. The skeleton
was never finished; the 9 _FIXTURE_PINS pin-checks have been red
the entire time (at HEAD: sap_score 65 vs 69, space_heating
9715 vs 8104 kWh, etc.).
Meanwhile the production-path chain tests for the same cert have
landed at 1e-4 vs the worksheet's continuous SAP 69.0094 and are
GREEN at HEAD:
- test_summary_001479_full_chain_sap_matches_worksheet_pdf_exactly
(Summary PDF -> extractor -> mapper -> calc, 1e-4 vs worksheet)
- test_api_001479_full_chain_sap_matches_worksheet_pdf_exactly
(API JSON -> mapper -> calc, 1e-4 vs worksheet)
- 5 test_summary_001479_*_<detail> mapper-shape unit tests
These exercise the actual from_elmhurst_site_notes /
from_api_response code paths the production runtime uses, which
is strictly stronger coverage than a hand-built mirror.
Drops 001479 from _FIXTURE_PINS / _FIXTURE_MODULES and deletes the
stub _elmhurst_worksheet_001479.py. Also fixes the stale "Slice
62 iteration" reference in test_summary_pdf_mapper_chain.py.
Test baseline: 9 fewer fails (10 -> 1; remaining FEE-round-trip
1e-9 noise to be fixed in S0380.38).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
b0919e8d6f
commit
1cea73df7c
3 changed files with 1 additions and 281 deletions
|
|
@ -2057,8 +2057,7 @@ def test_from_elmhurst_site_notes_matches_hand_built_000474() -> None:
|
||||||
# EpcPropertyData; any divergence is a mapper-coverage gap.
|
# EpcPropertyData; any divergence is a mapper-coverage gap.
|
||||||
#
|
#
|
||||||
# Tracer-bullet scope: cert 000474 only. Once GREEN, parametrize
|
# Tracer-bullet scope: cert 000474 only. Once GREEN, parametrize
|
||||||
# over the 5 other cohort fixtures and add cert 001479 (after
|
# over the 5 other cohort fixtures.
|
||||||
# `_elmhurst_worksheet_001479` lands at 1e-4 via Slice 62 iteration).
|
|
||||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000474_PDF)
|
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000474_PDF)
|
||||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||||
mapped = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
mapped = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
||||||
|
|
|
||||||
|
|
@ -1,267 +0,0 @@
|
||||||
"""Inputs + expected outputs from Elmhurst SAP10.2 worksheet P960-0001-001479.
|
|
||||||
|
|
||||||
Source: Summary_001479.pdf + P960-0001-001479.pdf (GOV.UK EPB cert
|
|
||||||
`0535-9020-6509-0821-6222`, lodged 31 Oct 2025). Semi-detached house
|
|
||||||
on Howick Park Drive, PR1 0LX. **First cohort fixture with a real
|
|
||||||
GOV.UK API counterpart** — this is the cross-mapper parity-test
|
|
||||||
reference for the API mapper.
|
|
||||||
|
|
||||||
Worksheet header:
|
|
||||||
Property Type House, Semi-Detached
|
|
||||||
Storeys 2
|
|
||||||
Habitable Rooms 4 (all heated)
|
|
||||||
Property Age Band C, Ext1: L, Ext2: C
|
|
||||||
Sheltered Sides 1
|
|
||||||
Living Area 17.13 m² / 28.0%
|
|
||||||
Thermal MassValue 250.00 (medium default)
|
|
||||||
Main Heating PCDF 17507 Worcester Greenstar 30i ErP
|
|
||||||
Mains gas, 89% winter / 86.6% summer
|
|
||||||
Controls SAP 2106 Programmer + Roomstat + TRVs
|
|
||||||
Boiler interlock yes, pump in heated space
|
|
||||||
Combi standard, gas/oil time-clock keep-hot
|
|
||||||
Secondary Heating SAP 605 — Flush-fitting live-effect gas fire,
|
|
||||||
sealed to chimney, 40% efficiency, MAINS GAS
|
|
||||||
Water Heating From Main Heating 1 (combi, no cylinder)
|
|
||||||
Mechanical Ventilation None
|
|
||||||
Intermittent Fans 2
|
|
||||||
|
|
||||||
Building parts:
|
|
||||||
Main: age C, 2 storeys (30.45 m² ground + 30.77 m² first); cavity
|
|
||||||
wall U=0.70 (worksheet); party wall CU (cavity unfilled,
|
|
||||||
U=0.50); 300 mm joist roof insulation U=0.14.
|
|
||||||
Ext1: age L (worksheet header — Summary §3 says "M 2023 onwards";
|
|
||||||
this fixture mirrors the worksheet at 1e-4 since the
|
|
||||||
worksheet is the calculator's source of truth). 5.37 m²
|
|
||||||
single-storey extension at ground level. Filled-cavity wall
|
|
||||||
U=0.26; PS sloping-ceiling roof insulated U=0.15;
|
|
||||||
insulated floor U=0.20.
|
|
||||||
Ext2: age C, **cantilevered upper-storey** extension hanging over
|
|
||||||
the back garden — 1.92 m² with exposed timber floor at U=1.20
|
|
||||||
(Table 20). Cavity wall U=0.70; PS sloping-ceiling roof
|
|
||||||
**uninsulated** at U=2.30 (Slice 57: pre-1950 PS + As Built
|
|
||||||
thickness → 0 mm).
|
|
||||||
|
|
||||||
Distinct features vs prior cohort fixtures (000474–000516):
|
|
||||||
- **Cert has a real GOV.UK API counterpart** — first cross-mapper
|
|
||||||
parity-test fixture (0535-9020-6509-0821-6222).
|
|
||||||
- **Multi-age building parts** (C, L, C) — Slice 60 dwelling-wide y
|
|
||||||
bridging convention picks up the dwelling primary age (C → 0.15).
|
|
||||||
- **Cantilevered upper-storey Ext2** with exposed floor (1.20).
|
|
||||||
- **PS Pitched sloping-ceiling roofs** on Ext1 (insulated, 0.15) and
|
|
||||||
Ext2 (uninsulated, 2.30).
|
|
||||||
- **Per-window U lodgement** — 8 Main windows at U=2.8 (g=0.76),
|
|
||||||
1 Ext1 window at U=1.4 (g=0.72) — manufacturer Argon-filled DG.
|
|
||||||
- **Mains-gas secondary heating** (SAP code 605, η=40%) — first
|
|
||||||
non-electric secondary in the cohort; exercises Slice 58's
|
|
||||||
secondary fuel cost routing through `secondary_fuel_type=26`.
|
|
||||||
|
|
||||||
Source-data caveat: Summary §3 lodges Ext1 age band as `M 2023
|
|
||||||
onwards`; the worksheet header records `Ext1: L` (2012-2022). The
|
|
||||||
hand-built encodes 'L' to reproduce the worksheet at 1e-4; the
|
|
||||||
Elmhurst mapper trusts the Summary (M) and will diverge on this field
|
|
||||||
during cross-mapper parity testing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from datatypes.epc.domain.epc_property_data import (
|
|
||||||
BuildingPartIdentifier,
|
|
||||||
EpcPropertyData,
|
|
||||||
SapBuildingPart,
|
|
||||||
SapFloorDimension,
|
|
||||||
SapVentilation,
|
|
||||||
SapWindow,
|
|
||||||
)
|
|
||||||
from domain.sap10_ml.tests._fixtures import (
|
|
||||||
make_main_heating_detail,
|
|
||||||
make_minimal_sap10_epc,
|
|
||||||
make_sap_heating,
|
|
||||||
make_window,
|
|
||||||
)
|
|
||||||
|
|
||||||
_WC_CAVITY = 4
|
|
||||||
_WALL_INSULATION_NONE = 4 # "As built" / uninsulated cavity
|
|
||||||
_WALL_INSULATION_FILLED_CAVITY = 2
|
|
||||||
|
|
||||||
|
|
||||||
def build_epc() -> EpcPropertyData:
|
|
||||||
"""EpcPropertyData mirroring the Elmhurst 001479 worksheet inputs.
|
|
||||||
|
|
||||||
Floor `room_height_m` mirrors the worksheet `(2x)` height column,
|
|
||||||
which adds +0.25 m to every storey above the lowest per the SAP
|
|
||||||
convention (cohort 000474 docstring §"Storey height convention").
|
|
||||||
"""
|
|
||||||
main = SapBuildingPart(
|
|
||||||
identifier=BuildingPartIdentifier.MAIN,
|
|
||||||
construction_age_band="C",
|
|
||||||
wall_construction=_WC_CAVITY,
|
|
||||||
wall_insulation_type=_WALL_INSULATION_NONE,
|
|
||||||
wall_thickness_measured=True,
|
|
||||||
# Summary §7 lodges "CU Cavity masonry unfilled" → U=0.50 per
|
|
||||||
# `u_party_wall`; Slice 55 added "CU" to the Elmhurst code map.
|
|
||||||
party_wall_construction=_WC_CAVITY,
|
|
||||||
sap_floor_dimensions=[
|
|
||||||
SapFloorDimension(
|
|
||||||
room_height_m=2.39, # lowest internal, no +0.25
|
|
||||||
total_floor_area_m2=30.45,
|
|
||||||
party_wall_length_m=6.94,
|
|
||||||
heat_loss_perimeter_m=11.99,
|
|
||||||
floor=0,
|
|
||||||
),
|
|
||||||
SapFloorDimension(
|
|
||||||
room_height_m=2.53, # = 2.28 internal + 0.25 floor-void
|
|
||||||
total_floor_area_m2=30.77,
|
|
||||||
party_wall_length_m=6.94,
|
|
||||||
heat_loss_perimeter_m=13.55,
|
|
||||||
floor=1,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
wall_thickness_mm=280,
|
|
||||||
# Worksheet §3: 300 mm joist roof insulation → U=0.14.
|
|
||||||
roof_insulation_thickness=300,
|
|
||||||
# Floor descriptive fields — required for the RdSAP 10 §5 (12)
|
|
||||||
# spec rule in `_has_suspended_timber_floor_per_spec` to recognise
|
|
||||||
# this as a "suspended timber ground floor" (cascade derives
|
|
||||||
# (12)=0.2 unsealed for age C with U=0.65 ≥ 0.5).
|
|
||||||
floor_type="Ground floor",
|
|
||||||
floor_construction_type="Suspended timber",
|
|
||||||
)
|
|
||||||
ext_1 = SapBuildingPart(
|
|
||||||
identifier=BuildingPartIdentifier.EXTENSION_1,
|
|
||||||
construction_age_band="L", # worksheet header (Summary §3 says M;
|
|
||||||
# cross-mapper diff will flag this)
|
|
||||||
wall_construction=_WC_CAVITY,
|
|
||||||
wall_insulation_type=_WALL_INSULATION_FILLED_CAVITY,
|
|
||||||
wall_thickness_measured=True,
|
|
||||||
# Ext1 sits flush against neighbours on no party walls
|
|
||||||
# (worksheet `Party wall length=0.00`). `party_wall_construction`
|
|
||||||
# is still type-required as int; 0 = "Unable to determine"
|
|
||||||
# (Slice 54 cohort convention) — the cascade multiplies by
|
|
||||||
# party_wall_length=0 so the U is irrelevant here.
|
|
||||||
party_wall_construction=0,
|
|
||||||
sap_floor_dimensions=[
|
|
||||||
SapFloorDimension(
|
|
||||||
room_height_m=2.48,
|
|
||||||
total_floor_area_m2=5.37,
|
|
||||||
party_wall_length_m=0.0,
|
|
||||||
heat_loss_perimeter_m=6.67,
|
|
||||||
floor=0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
wall_thickness_mm=280,
|
|
||||||
# Worksheet §3 lodges Ext1 sloping-ceiling roof U=0.15 — cascade
|
|
||||||
# default for age L pitched roof with no thickness lodged matches.
|
|
||||||
)
|
|
||||||
ext_2 = SapBuildingPart(
|
|
||||||
identifier=BuildingPartIdentifier.EXTENSION_2,
|
|
||||||
construction_age_band="C",
|
|
||||||
wall_construction=_WC_CAVITY,
|
|
||||||
wall_insulation_type=_WALL_INSULATION_NONE,
|
|
||||||
wall_thickness_measured=True,
|
|
||||||
# Ext2 has no party walls either (worksheet PWL=0). Use the
|
|
||||||
# "Unable to determine" sentinel 0 (cohort convention).
|
|
||||||
party_wall_construction=0,
|
|
||||||
sap_floor_dimensions=[
|
|
||||||
# Cantilevered upper-storey extension: single floor_dim with
|
|
||||||
# `is_exposed_floor=True` routes through Table 20 → U=1.20.
|
|
||||||
SapFloorDimension(
|
|
||||||
room_height_m=2.10,
|
|
||||||
total_floor_area_m2=1.92,
|
|
||||||
party_wall_length_m=0.0,
|
|
||||||
heat_loss_perimeter_m=2.81,
|
|
||||||
floor=0,
|
|
||||||
is_exposed_floor=True,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
wall_thickness_mm=280,
|
|
||||||
# Slice 57: PS sloping-ceiling + As Built + pre-1950 → thickness=0
|
|
||||||
# → Table 16 row 0 U=2.30.
|
|
||||||
roof_insulation_thickness=0,
|
|
||||||
)
|
|
||||||
|
|
||||||
# §11 Windows: 8 Main + 1 Ext1. All double-glazed; Ext1 has a low-U
|
|
||||||
# Argon-filled unit (Manufacturer 1.40 / g=0.72). Heights default to
|
|
||||||
# 1.0 m per the Elmhurst W×H=Area area-preserving convention; widths
|
|
||||||
# set to lodged Area / 1.0 = lodged Area.
|
|
||||||
main_windows: tuple[SapWindow, ...] = (
|
|
||||||
# Windows 1(Main) — area 3.34, orientation NW (8)
|
|
||||||
make_window(orientation=8, width=3.34, height=1.0,
|
|
||||||
solar_transmittance=0.76, u_value=2.8, window_location=0),
|
|
||||||
# Windows 2(Main) — area 0.73, NE (2)
|
|
||||||
make_window(orientation=2, width=0.73, height=1.0,
|
|
||||||
solar_transmittance=0.76, u_value=2.8, window_location=0),
|
|
||||||
# Windows 3(Main) — 6 entries
|
|
||||||
make_window(orientation=8, width=3.04, height=1.0,
|
|
||||||
solar_transmittance=0.76, u_value=2.8, window_location=0),
|
|
||||||
make_window(orientation=2, width=1.33, height=1.0,
|
|
||||||
solar_transmittance=0.76, u_value=2.8, window_location=0),
|
|
||||||
make_window(orientation=2, width=0.70, height=1.0,
|
|
||||||
solar_transmittance=0.76, u_value=2.8, window_location=0),
|
|
||||||
make_window(orientation=2, width=0.99, height=1.0,
|
|
||||||
solar_transmittance=0.76, u_value=2.8, window_location=0),
|
|
||||||
make_window(orientation=4, width=2.13, height=1.0,
|
|
||||||
solar_transmittance=0.76, u_value=2.8, window_location=0),
|
|
||||||
make_window(orientation=1, width=1.70, height=1.0,
|
|
||||||
solar_transmittance=0.76, u_value=2.8, window_location=0),
|
|
||||||
)
|
|
||||||
ext_1_window = make_window(
|
|
||||||
# Windows 2(Ext1) — area 6.37, orientation SE (4)
|
|
||||||
orientation=4, width=6.37, height=1.0,
|
|
||||||
solar_transmittance=0.72, u_value=1.4, window_location=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
return make_minimal_sap10_epc(
|
|
||||||
total_floor_area_m2=68.51,
|
|
||||||
country_code="ENG",
|
|
||||||
postcode="pr1 0lx",
|
|
||||||
sap_building_parts=[main, ext_1, ext_2],
|
|
||||||
habitable_rooms_count=4,
|
|
||||||
heated_rooms_count=4,
|
|
||||||
door_count=1,
|
|
||||||
# §13 Lightings: 17 LED + 6 CFL = 23 fittings, 73.91% LEL.
|
|
||||||
# SAP10 Appendix L scales each bulb type by its own efficacy ratio
|
|
||||||
# — keeping LED and CFL separate (not collapsed into `low_energy_*`)
|
|
||||||
# matches the worksheet's per-fitting lighting demand split.
|
|
||||||
led_fixed_lighting_bulbs_count=17,
|
|
||||||
cfl_fixed_lighting_bulbs_count=6,
|
|
||||||
incandescent_fixed_lighting_bulbs_count=0,
|
|
||||||
sap_windows=[*main_windows, ext_1_window],
|
|
||||||
percent_draughtproofed=90,
|
|
||||||
sap_ventilation=SapVentilation(
|
|
||||||
extract_fans_count=2,
|
|
||||||
sheltered_sides=1,
|
|
||||||
# `has_suspended_timber_floor` left None — the cascade
|
|
||||||
# derives the §2(12) value per RdSAP 10 spec rule (cert
|
|
||||||
# 001479 Main is G+T age C with U=0.65 ≥ 0.5 → unsealed →
|
|
||||||
# (12)=0.2). The lodged sap_ventilation block previously
|
|
||||||
# encoded the worksheet's (12) value directly via this
|
|
||||||
# boolean; the cascade now reproduces it mechanically.
|
|
||||||
has_draught_lobby=False,
|
|
||||||
),
|
|
||||||
sap_heating=make_sap_heating(
|
|
||||||
main_heating_details=[
|
|
||||||
make_main_heating_detail(
|
|
||||||
main_heating_index_number=17507,
|
|
||||||
main_heating_data_source=1,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
# SAP code 605, 40%, mains gas (fuel 26) — exercises Slice 58.
|
|
||||||
secondary_heating_type=605,
|
|
||||||
secondary_fuel_type=26,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# Cascade pins extracted from P960-0001-001479.pdf (Table 12 prices,
|
|
||||||
# Section 10a). All values at the worksheet's 4 d.p. precision.
|
|
||||||
# ============================================================================
|
|
||||||
# (258) SAP rating = 69
|
|
||||||
# "SAP value" = 69.0094
|
|
||||||
# (257) Energy cost factor = 2.2215
|
|
||||||
# (255) Total energy cost = 600.4001
|
|
||||||
# (272) Total CO2 kg/year = 2687.3610
|
|
||||||
# (98c) Σ monthly space heating = 8103.7054 kWh/yr
|
|
||||||
# (211) Main system 1 fuel = 8194.7583 kWh/yr
|
|
||||||
# (215) Secondary fuel = 2025.9264 kWh/yr
|
|
||||||
# (219) Water heating fuel = 2358.3123 kWh/yr
|
|
||||||
# (231) Pumps and fans = 160.0000 kWh/yr
|
|
||||||
# (232) Lighting electricity = 163.3584 kWh/yr
|
|
||||||
|
|
@ -33,7 +33,6 @@ from domain.sap10_calculator.worksheet.tests import (
|
||||||
_elmhurst_worksheet_000487 as _w000487,
|
_elmhurst_worksheet_000487 as _w000487,
|
||||||
_elmhurst_worksheet_000490 as _w000490,
|
_elmhurst_worksheet_000490 as _w000490,
|
||||||
_elmhurst_worksheet_000516 as _w000516,
|
_elmhurst_worksheet_000516 as _w000516,
|
||||||
_elmhurst_worksheet_001479 as _w001479,
|
|
||||||
)
|
)
|
||||||
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
|
from domain.sap10_calculator.worksheet.tests._elmhurst_fixtures import (
|
||||||
ALL_FIXTURES as _ELMHURST_FIXTURES,
|
ALL_FIXTURES as _ELMHURST_FIXTURES,
|
||||||
|
|
@ -130,16 +129,6 @@ _FIXTURE_PINS: Final[dict[str, FixtureCascadePins]] = {
|
||||||
lighting_kwh_per_yr=230.8853,
|
lighting_kwh_per_yr=230.8853,
|
||||||
pumps_fans_kwh_per_yr=160.0,
|
pumps_fans_kwh_per_yr=160.0,
|
||||||
),
|
),
|
||||||
"001479": FixtureCascadePins(
|
|
||||||
sap_score=69, sap_score_continuous=69.0094, ecf=2.2215,
|
|
||||||
total_fuel_cost_gbp=600.4001, co2_kg_per_yr=2687.3610,
|
|
||||||
space_heating_kwh_per_yr=8103.7054,
|
|
||||||
main_heating_fuel_kwh_per_yr=8194.7583,
|
|
||||||
secondary_heating_fuel_kwh_per_yr=2025.9264,
|
|
||||||
hot_water_kwh_per_yr=2358.3123,
|
|
||||||
lighting_kwh_per_yr=163.3584,
|
|
||||||
pumps_fans_kwh_per_yr=160.0,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -150,7 +139,6 @@ _FIXTURE_MODULES: Final[dict[str, ModuleType]] = {
|
||||||
"000487": _w000487,
|
"000487": _w000487,
|
||||||
"000490": _w000490,
|
"000490": _w000490,
|
||||||
"000516": _w000516,
|
"000516": _w000516,
|
||||||
"001479": _w001479,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue