Two outputs of validating the Wythenshawe cohort against accredited Elmhurst:
1. Harness note: the DB-oracle residual tail is not engine defects. The two
worst under-raters, 6 Barry Road (ours 60.12, oracle 62) and 87 Alderue
(ours 61.80, oracle 63), were re-keyed into accredited Elmhurst on our exact
inputs — Elmhurst returned 60 and 62, matching our engine and both below the
lodged oracle. So our engine computes correct RdSAP; the lodged certs carried
a lighter input set (dropped the surveyed 17.4 m2 solid-brick alternative
wall, +1.7 SAP). It's input-provenance drift, not a calculation gap — don't
chase these as engine bugs or tune the engine to close them. Corrects the
earlier "spec-literal alt-wall" note.
2. epc-to-elmhurst skill gotcha: give Elmhurst the RAW surveyed room height, not
the engine's storey height. The mapper already adds +0.25 m to upper floors
(_UPPER_FLOOR_HEIGHT_ADD_M), so feeding sap_floor_dimensions.room_height_m
makes Elmhurst double-add the joist void (~1 SAP too low — it under-scored
6 Barry 60->59 until the first-floor height was corrected 2.76 -> raw 2.51).
Comment/doc only; no behaviour change (harness still 89.2%/0.297, pyright clean).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`test_pashub_sap_accuracy_wythenshawe` graded against the manifest `pre_sap`
(`hubspot_deal_data.pre_sap`) — surveyor hand-entered and frequently wrong,
which floated a misleading 79% / MAE 0.75. Grade instead against the DB
PasHub-assessment SAP (`epc_property_energy_performance.energy_rating_current`,
`ep.source='lodged'`), matched by UPRN or postcode+house-number.
`scripts/enrich_pashub_wythenshawe_oracle.py` resolves each fixture's oracle SAP
once (DB required, not run in CI) and bakes `oracle_sap` / `oracle_match` into
`manifest.json`, so the harness stays DB-free. 148/168 resolve (39 UPRN + 109
address); the 20 unmatched carry `oracle_sap` null and are excluded from the
aggregate until PasHub sources their SAP.
With the six from_site_notes fixes, the 148 matched score within-0.5 89.2%
(132/148), MAE 0.297 (was, vs pre_sap, 79.2% / 0.754). Ratchets re-baselined
0.75→0.88 floor, 0.85→0.31 ceiling. The residual tail is the integer-oracle
rounding floor (spec-correct dwellings) plus a couple of spec-literal gaps, not
engine defects.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All six are `from_site_notes`-only (gov-API RdSAP corpus unmoved at 78.9%),
graded against the DB PasHub-assessment oracle (energy_rating_current,
source='lodged'), not the surveyor-entered pre_sap.
A. Manual-Entry boiler efficiency. Extractor read PCDF-Search columns that a
manual survey lacks; mapper returned None for all boilers → the generic
0.80 gas default. Now read `Type of boiler:` / `Heating System (Boiler):`
/ `Is there an open flue?` and resolve the descriptor to its SAP 10.2
Table 4b code. Moves all 7 Wythenshawe manual boilers to the oracle
(4a Hollyhedge back boiler 63.6→53.4=53; group MAE 2.42→0.42).
B. Room-in-roof gable type None → strict-raise. A form variant omits the
gable-type line (genuine field-absence); default None → party `gable_wall`
(U=0.25), mirroring `_api_type_1_gable_kind(None)`. Present-but-unknown
labels still strict-raise. Unblocks 8 & 34 Bucklow Drive.
C. Secondary `Closed room heater` unmapped → add Table 4a code 633.
Unblocks 8 Brinkshaw Avenue.
D. Alternative-wall insulation thickness never extracted → parse
`Wall insulation thickness:` and pass it through instead of the RdSAP 10
§5.4 100 mm favourable default.
E. `Number of heated rooms?` label mismatch → the PDF lodges
`Please enter the number of HEATED rooms:`.
F. HW cylinder thermostat dropped (NEW, systematic). `_map_sap_heating`
never set `cylinder_thermostat`, so a surveyed thermostat defaulted None
and the calculator applied the SAP 10.2 Table 2b Note a) ×1.3 penalty.
Map it ("Y"/"N", gated on a lodged cylinder) as the Elmhurst path does.
Fixes the two worst DB-oracle under-raters (52 Bucklow 59.4→61.8=62;
13 Kerne Grove 65.0→67.1=67).
DB-oracle cohort (148 matched): within-0.5 83.8%→87.8%, MAE 0.427→0.303.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The deal's third_party_surveyor_identifier is now required by the log
and amend flows: the trigger contract rejects messages without it
(blank treated as missing, since a blank resource makes OpenHousing
silently drop the appointment), and LogJobRequest/AmendJobRequest carry
it as a required field. The ABRI_RELAY_DEFAULT_RESOURCE fallback and
its config/terraform/workflow wiring are removed as dead code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Carry third_party_surveyor_identifier on the Abri trigger message and
through dispatch into the log and amend flows, booking the job against
that resource. LogJobRequest gains the optional resource field
AmendJobRequest already had; the client falls back to the configured
default surveyor when the deal names no third-party resource, so
existing deals behave unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fetch the property from the HubSpot deals API, persist it on
hubspot_deal_data (column added by the frontend repo's drizzle
migration), and include it in the differ's FIELD_MAP so a change
to the field triggers a DB reload.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When DHW is supplied by a community heat network (WHC 901/902/914 on a
heat-network main), the network heat -- space AND water -- bills at the main's
Table-12 heat-network rate. The STANDARD-tariff §10a cost path (_fuel_cost)
re-derived the DHW rate inline from the cert-lodged HW fuel, which Elmhurst
defaults to 'Mains gas' (Table 32 code 26, 3.48 p/kWh) on community certs --
under-costing DHW and over-rating SAP. Now inherits the main heat-network rate
via _fuel_cost_gbp_per_kwh, mirroring the .173 inherit already applied on the
legacy/off-peak HW-rate paths. Scoped to community heating, so all other certs
are unchanged; the RdSAP-21.0.1 corpus gauge improves 78.6%->78.9% within-0.5
(MAE 0.628->0.622) with all 195 Elmhurst worksheet pins green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#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>