#1631: the label blocks in the main room-heater type table (Heating
System (Other) field), not the secondary table the issue cited — same
PCDB row either way. Wythenshawe 164→165 computed. The fixture scores
49.5 vs pre_sap 31: its own 2025 accredited cert
6890-0597-0722-0196-3943 lodges the identical codes (603/26/2601) at
band E agreeing with us, while pre_sap matches the dwelling's
superseded electric configuration (2017/2023 certs, band F) — accepted
divergence, MAE ceiling re-baselined 0.75→0.85 with the citation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#1628: fixture 500796700868 re-blocks on a later label
(first-exception-hit); metrics unchanged, ratchets hold.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-on surfaced by #1626: fixture 500796646644 progressed past the
2404 conflict onto cylinder insulation 'Loose jacket' → code 2 (gov-API
coding, Elmhurst 'Jacket' sibling). Wythenshawe 161→162 computed
(32.6 vs pre 34); ratchets hold at 75.3%/0.744.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#1626: ground truth resolved per the issue's option 3. The accredited
cert 0764-2894-7173-2720-5535 (62 Mayfair Road, UPRN 77066640 — the
blocked Wythenshawe fixture's dwelling) lodges 402 + 2402 for the same
heater description, and RdSAP 10 p.80 (item 7-3) requires a PCDB index
for any HHR lodgement — absent one, the surveyed type field is
authoritative and 2404 downgrades to 2402 (automatic charge control,
preserving the tick's automatic-charging semantics). Applies only to
the mapped non-HHR types (402/404); any other pairing still fails loud.
LRHA fixture 505091686590 (Fan storage heater, same slip) computes
64.0 vs pre 65; LRHA 76→77 computed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#1625: 'Same dwelling below' → the "(another dwelling below)" party-
floor suppression string, mirroring the gov-API code-8 precedent
(RdSAP 10 §3: an internal floor between heated storeys has no floor
heat loss). Wythenshawe 151→161 computed; 7/10 newly-unblocked land
within 0.5 — ratchets re-baselined 0.76/0.72 → 0.75/0.75 for the two
tail entries (−3.3/−5.5).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#1624: Wythenshawe 149→151 computed. 500767954156 lands within 0.5;
500797465814 is a −8.8 tail entry — MAE ceiling re-baselined 0.67→0.72
per the coverage-growth convention (within-0.5 floor 0.76 holds).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#1637: 'MV - Heat Recovery' → MVHR kind (Table 4g default 46.2% heat
recovery — PasHub lodges no PCDF product/wet-rooms/duct data, so these
land in the accuracy tail as the issue predicts). Unblocking them
surfaced 4 biomass heat networks whose fuel label 'Heat from boilers -
biomass' passed through raw to the calculator's MissingMainFuelType —
now mapped to Table 12 code 43, and any other uncovered community fuel
strict-raises UnmappedPasHubLabel at the boundary (ADR-0015).
LRHA 61→76 computed / 28 blocked / 0 errored, within-0.5 43.4%,
MAE 2.985 — ratchets hold.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#1636: the 6 single-line-label fixtures unblock and re-block on later
labels (MVHR/cylinder); the 6 line-wrap-truncated '…room thermostat
and' fixtures still need the extractor fix tracked under #1622.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#1635: 'Programmer, room thermostat and TRVs' on a heat pump → 2210
(PCDB heat_pump_controls), and the lodgement now carries Table 4a
category 4 — without it the calculator's Appendix N3.6/N3.7 gate never
fired and the PCDB heat pump was billed as 100%-efficient direct
electric (6.0/18.5 vs pre 62/57). With the category threaded the two
LRHA WAVE 3 fixtures score 52.1/56.9 (the latter within 0.5).
LRHA 59→61 computed, within-0.5 42.6%, MAE 2.830 — ratchets hold.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Positive input ventilation sourced from the loft is treated as natural
ventilation under SAP 10.2 §2 (24d) — matching the MechanicalVentilationKind
.NATURAL contract — so the label now resolves instead of raising
UnmappedElmhurstLabel and blocking the whole Elmhurst Summary. Surfaced on
8 WCHG Elmhurst certs (e.g. 29 Piper Hill Avenue, 38 Bucklow Drive).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add pashub_accuracy_wythenshawe fixtures + a sibling accuracy module,
built from project_code
[Wythenshawe WH:SHF RAs - WH:SF - 240426 - 494493718748] the same way as
LRHA WAVE 3 (#1620). First run: 149 computed / 19 blocked / 0 errored,
within-0.5 76.5%, MAE 0.665. Ratchets baselined to this cohort; the 19
blocks are separately-ticketed mapper-label gaps (floor 'Same dwelling
below' ×10 dominating), not tuning.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Teaches the pashub `from_site_notes` mapper the electric-storage-heater and
room-heater families, following the existing boiler/heat-network split exactly
(fix at the mapper boundary, ADR-0015 — never mis-rate, never strict-raise deep
in the calculator). Adds:
- Table 4e Group 4 storage charge-control codes (2401/2402/2404) and Group 6
room-heater control codes (2602/2605), each gated on its own system group so a
control label never takes a wrong-group 2xxx code.
- The Table 4a system code from the surveyed heater type lodged in the "Heating
System (Other)" field (`community_heat_source`): Fan storage 404, slimline
402, PCDF-Search product ⇒ HHR 409 (RdSAP 10 p.80), room heaters 691/694.
- A 2404⇔409 coherence guard: high-heat-retention controls on a non-HHR storage
type is a surveyor contradiction and fails loud rather than rating a mismatch.
All codes taken from the SAP 10.2 spec (14-03-2025 PDF) and already present in
the calculator's inventories — no calculator change.
LRHA WAVE 3 (lincs rural) accuracy: 8 computed / 96 blocked → 45 computed / 59
blocked / 0 errored (within-0.5 46.7%, MAE 2.989); ratchets re-baselined to the
new coverage. Guinness cohort (208) + gov-API corpus unchanged; pyright --strict
adds zero new errors.
Closes#1619
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 Paxton Place (deal 507639151845, uprn 10070864419) had a STALE fixture.
The same PAS Hub survey (report B74AA5A4-…, inspected 29 Jun 2026) has two
accredited revisions in S3: V2 (uploaded 6 Jul, ventilation "Natural",
SAP 80) and V5 (uploaded 14–15 Jul, ventilation "Mechanical Extract -
Centralised", SAP 76 — the current version). Our committed fixture was the
superseded V2; the portfolio-838 oracle tracks the latest V5 (76). Our
engine faithfully reproduces BOTH (V2 → 80.4, V5 → 75.6) — the +4.38 miss
vs the DB oracle was a fixture-version mismatch, NOT an engine over-rate
(a parallel over-credit sweep found no spec-correct from_site_notes lever;
every channel is faithful).
Refreshed the fixture to the V5 PDF (image-stripped, text layer preserved,
6.7MB→0.27MB, per scripts/build_pashub_accuracy_fixtures.py) and set the
manifest to C76. 5 Paxton flips from the cohort's largest miss to a
within-0.5 hit: DB-oracle gauge 86.9%→87.4% within-0.5, MAE 0.348→0.327.
Pinned via test_paxton_fixture_is_v5_resurvey_sap_76 (asserts a literal 76
against the V5 fixture) — a canary, because hubspot_deal_data.pre_sap is
still stale at C80 and a fixture-builder rerun would revert the manifest.
Confirmed against pashub: the current survey is SAP 76 (Khalim).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
13 Walsden (deal 499630239947, uprn 77179782) has TWO distinct gov-EPC
assessments: the PAS Hub site-note re-survey we parse (26 May 2026,
accredited SAP 64, backed by rd_sap_site_note files) and a SEPARATE
later lodged cert (24 Jun 2026, SAP 68, registered, no site-note). The
portfolio-838 baseline latched onto the 24-Jun SAP-68 cert; our engine
models the survey it was given and reproduces its own accredited 64
(64.19). 6 of 7 Walsden Street homes have pbp oracle == PAS Hub survey
SAP exactly — #13 is the lone collision.
A durable regression pin (asserts our SAP within 0.5 of a literal 64,
not the manifest value the fixture-builder can revert to a colliding DB
row) documents the provenance so nobody "corrects" the target to 68.
13 Walsden is a known re-survey-diverges outlier, not an engine
under-rate; confirmed against pashub (Khalim, 2026-07-15e).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`_PASHUB_FLOOR_EXPOSURE` mapped the surveyed "Semi Exposed (unheated)"
floor label to `is_above_partially_heated_space` (RdSAP §5.14, constant
U=0.7). A floor semi-exposed to an enclosed UNHEATED space belongs in
§5.13 Table 20 `u_exposed_floor` (band-B as-built U=1.20). §5.14's U=0.7
is reserved for a floor above a *partially* heated space. #1602
transposed the two branches; the too-low U under-counted floor loss on
the 9 label-carrying cohort fixtures → a shared over-credit.
Triangulated against all three siblings + the calculator docstring: the
gov-API code-2 "To unheated space" (mapper.py:4797) and Elmhurst
`_is_floor_exposed_to_unheated_space` (mapper.py:5970) both route to
Table 20; rdsap_uvalues.py:1416 documents Table 20 as collapsing exposed
and semi-exposed-to-unheated into one lookup. One-line data flip,
from_site_notes-only (gov-API RdSAP corpus untouched, still 78.9%).
Adjacent Ronald St mid-terrace pair (the clustered signal): 10 Ronald
60.35→59.02 (oracle 59), 12 Ronald 60.79→59.36 (oracle 59). Cohort vs
DB oracle (portfolio 838 effective_sap) 83.3%→86.9% within-0.5, MAE
0.377→0.348, no built form regresses, all 7 verified dwellings unchanged
(none carry the label). pashub harness 79.5%→82.4%, MAE 0.389→0.367;
ratchets tightened 0.78→0.82 / 0.40→0.37. pyright zero-new.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Electric-immersion DHW: bill at 100%, not the space boiler's efficiency 🟩
A separate electric immersion heater (SAP water-heating code 903) is 100%
efficient (SAP 10.2 Table 4a) and the space-heating boiler provides no water
heating, so Appendix D2.1 Eq D1 (the boiler seasonal-efficiency cascade) must
not apply to it. But `_water_heating_main` resolves DHW to the SPACE main — a
gas/oil boiler keeps its PCDB record — so three water-efficiency branches in
cert_to_inputs billed the immersion-heated cylinder at the boiler's ~87% summer
efficiency (a mongrel: electric fuel price x gas-boiler efficiency):
1. the scalar `water_eff = water_pcdb_main.summer_efficiency_pct / 100`,
2. the SAP §9.4.11 / Table 4c(2) -5pp no-interlock adjustment, and
3. the Eq D1 (winter, summer) seasonal pair from `pcdb_main`.
Gate all three on `not dhw_is_electric_immersion`; the correct immersion path
(`_water_efficiency_with_category_inherit` -> 1.0, Eq D1 off) then applies,
mirroring the Table 3 zero-primary-loss gate already present for WHC 903.
General bug — fires for any WHC-903 dwelling whose space main is a PCDB gas/oil
boiler. It surfaced via the PasHub campaign's #1600 no-water-heating default
(WHC 999 -> 903) on a gas-combi dwelling: 58 Hackle St M11 4WU, SAP 55.30 ->
57.67 (pre_sap 58, verified 59).
Guardrails: the gov-API RdSAP corpus IMPROVES 78.8% -> 78.9% within-0.5, MAE
0.625 -> 0.622 (a handful of corpus certs with a boiler space main + electric
immersion move closer to accredited) — MAE ceiling ratcheted 0.626 -> 0.625.
Regression pinned in test_cert_to_inputs (RED before / GREEN after). pyright
0-new (cert_to_inputs baseline 30).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* PasHub SAP accuracy: thread low-energy lights + draught lobby (from_site_notes) 🟩
Two systematic silent-drops in the PAS Hub `from_site_notes` path, each a field
the gov-API/Elmhurst mappers thread but site-notes dropped, letting a favourable
default reach the SAP-10.2 calculator. Validated against the correct pashub
oracle (property_baseline_performance.effective_sap_score, portfolio 838):
cohort within-0.5 55.1% → 62.6%, MAE 0.599 → 0.521.
- Low-energy lights: when "exact LED/CFL known = No", PAS Hub lodges an aggregate
"Number of fixed low energy lights?" count. Previously dropped (no dataclass
field / extractor key / mapper thread) → calculator saw 0 low-energy bulbs and
applied the pessimistic L5b/L8c no-data default, under-rating SAP. Now threaded
into low_energy_fixed_lighting_bulbs_count, mirroring from_elmhurst_site_notes.
~6% of the cohort; resolves 58 Hackle (−0.43 vs oracle 59 with #1615).
- Draught lobby: _map_sap_ventilation set only the legacy `draught_lobby` field,
never the canonical `has_draught_lobby` §2 (13) gate the cascade reads, so the
surveyed lobby was ignored and infiltration over-stated. Now mirrors Elmhurst.
Two other audited levers were REJECTED against the pashub oracle (they matched
the gov-cert/RdSAP convention but pashub does not apply them): percent_draughtproofed
(cohort 30.8%) and the §A.2.2 assumed secondary heater (19.2%). A heat-network
control-code fix (2306→2303 for 16 Bingley) is HELD pending spec/Elmhurst
adjudication — it contradicts ADR-0053; see the NOTE on _PASHUB_HEAT_NETWORK_CONTROL_TO_SAP10.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* PasHub heat-network control: room-thermostat-only maps to 2308, not 2306 🟩
The Table 4e Group 3 label "charging linked to use of community heating, room
thermostat only" was mapped to 2306 — the linked-to-use *with-TRVs* code
(control type 3, space charging 1.00). The label explicitly excludes TRVs, so
it is control type 2 / space 1.05 = code 2308/2309 (linked-to-use → DHW 1.00).
2306 asserted TRVs the survey doesn't have and over-rated the cohort's sole
heat-network dwelling (16 Bingley Close 56.89 → 54.01 vs pashub oracle 52).
Verified against SAP 10.2 Table 4e as encoded in cert_to_inputs.py
(_CONTROL_TYPE_BY_CODE 2308→2, space-charging 2308→1.05, DHW 2308→1.00) and the
RdSAP control-label vocabulary in MainheatControlAttributes.py. 2303 (the earlier
audit's guess) is rejected: it is a flat-rate code (DHW 1.05) whose oracle match
was a coincidence of two offsetting spec violations. The residual +2.0 to oracle
52 is the documented SAP-10.2-engine-vs-lodged offset, not a fuel/flags gap
(those were threaded by the #1590 follow-up) — supersedes the ADR-0053 /
HANDOVER_838 "community fuel/flags" attribution.
16 Bingley is the only heat-network fixture in portfolio 838, so this moves one
fixture strictly toward its oracle. Harness ratcheted: within-0.5 0.51→0.58,
MAE 0.625→0.521 (observed 58.5% / 0.520 with all three 2026-07-15 levers).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* PasHub SAP accuracy: restore 3 dropped §2 infiltration inputs (from_site_notes)
`from_site_notes` uniquely dropped three ventilation inputs that the gov-API
and Elmhurst sibling mappers all set, causing a systematic cohort-wide SAP
under-rate vs the company's own accredited SAP-10.2 certs
(property_baseline_performance.effective_sap_score, portfolio 838):
1. percent_draughtproofed — never set, so §2(15) window infiltration was
pinned at its 0.25-ACH worst case on every dwelling. Reconstructed as the
area-weighted % of draught-proofed windows (mirrors Elmhurst
`draught_proofing_percent`). Dominant term.
2. Upper-storey +0.25 m joist void — omitted; now added, matching
`_UPPER_FLOOR_HEIGHT_ADD_M` on the gov-API/Elmhurst paths. This
RE-ADJUDICATES #1601 ("keep raw"): that call was confounded by the
then-present draught-proofing drop suppressing every verified dwelling.
3. sheltered_sides — left None → calculator's flat default of 2, which
over-shelters end/semi/detached (RdSAP §S5 = 1/1/0). Now derived from
built form.
The three are NON-ADDITIVE — each overshoots alone (which is why all three
were previously rejected individually) — but together they land all 7
verified ground-truth dwellings toward truth (none regress) and every built
form near zero. Cohort 62.1% -> 83.3% within-0.5, MAE 0.507 -> 0.377.
Guardrails: gov-API RdSAP corpus unchanged (78.9%, these are from_site_notes-
only helpers). pashub harness 58.5% -> 79.5% / MAE 0.520 -> 0.389; ratchets
tightened 0.58->0.78 and 0.521->0.40. pyright zero-new (mapper baseline 39).
New focused tests in TestFromSiteNotesInfiltrationFixes cover all three
fields incl. area-weighting and the built-form shelter map; goldens updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>