Commit graph

8341 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
a80d6cad05 Re-pin the trigger-attributes fixture to one that still buys lighting 🟪
0390's floor+ASHP package reaches post 68.772, which publishes as 69 = band C,
so under ADR-0064 it now meets the goal and stops before low-energy lighting —
the same over-purchase the ADR removes, one level up at the package. 0380 fires
the same three measures with identical trigger attributes, so every assertion
stands unchanged, and it lands at post 74.4, clear of a band floor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:57:22 +00:00
Khalim Conn-Kowlessar
223ef7c196 Report missing baselines once per batch, and seed the harness's own 🟩
The modelling_e2e handler models one property per run_modelling call, so a
per-property "no persisted baseline" line would be one per property across a
whole re-model. The harness has no Baseline stage by design and cannot drift
from one, so it now establishes its own the way the Baseline orchestrator
would; the Postgres-backed lane is where the assertion has something to catch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:46:25 +00:00
Khalim Conn-Kowlessar
29157811e0 Renumber the baseline-coherence ADR to 0066 🟪
ADR-0065 is claimed by Scenario Export on the open #1546; this branch is the
later of the two, so it moves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:38:49 +00:00
Khalim Conn-Kowlessar
735a3a1c8e Record the banding and baseline-coherence decisions 🟪
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:04:49 +00:00
Khalim Conn-Kowlessar
31be36cca5 Flag works bought for no real SAP gain 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:57:25 +00:00
Khalim Conn-Kowlessar
08d2b874d5 Flag works bought for no real SAP gain 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:53:59 +00:00
Khalim Conn-Kowlessar
8c2c19495c Flag a modelling baseline that disagrees with the persisted effective 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:53:04 +00:00
Khalim Conn-Kowlessar
31edd01999 Flag a modelling baseline that disagrees with the persisted effective 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:50:39 +00:00
Khalim Conn-Kowlessar
6f83418c4c Type-clean the anomaly check tests 🟪
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:18:09 +00:00
Khalim Conn-Kowlessar
68ffb0c47c Rank costed works on an already-compliant dwelling HIGH 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:12:58 +00:00
Khalim Conn-Kowlessar
0df40278e5 Rank costed works on an already-compliant dwelling HIGH 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:12:33 +00:00
Khalim Conn-Kowlessar
27060f4d81 Band every continuous SAP score through the published-rating rule 🟪
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:11:06 +00:00
Khalim Conn-Kowlessar
07a71ad504 Band a continuous SAP score on its published rating 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:07:55 +00:00
Khalim Conn-Kowlessar
08abd3131b Band a continuous SAP score on its published rating 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:07:11 +00:00
Khalim Conn-Kowlessar
bc2f6c8a82 Sell no works to a dwelling already at the goal band 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:05:55 +00:00
Khalim Conn-Kowlessar
8548b8304b Sell no works to a dwelling already at the goal band 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:04:48 +00:00
Khalim Conn-Kowlessar
31ad9448d2 Locate a band's floor on the continuous SAP scale 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:03:54 +00:00
Khalim Conn-Kowlessar
1e4526f33f Locate a band's floor on the continuous SAP scale 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:03:25 +00:00
Khalim Conn-Kowlessar
149d957248 Publish a half-point SAP rating rounded up 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:02:44 +00:00
Khalim Conn-Kowlessar
f55982a4ee Publish a half-point SAP rating rounded up 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 10:00:35 +00:00
KhalimCK
0fdb15ad65
Merge pull request #1645 from Hestia-Homes/fix/terraced-rir-gable-party
Reclassify terraced room-in-roof gables as party, not external
2026-07-20 12:14:08 +01:00
KhalimCK
08dc81332c
Merge pull request #1650 from Hestia-Homes/fix/pashub-sitenotes-boiler-thermostat-gable
PasHub Wythenshawe: fix 6 from_site_notes defects + re-pin harness to DB oracle (#1649)
2026-07-20 12:04:16 +01:00
Daniel Roth
dc96cf5aba
Merge pull request #1648 from Hestia-Homes/feature/abri-api-resource-field
Sync third_party_surveyor_identifier deal field from HubSpot
2026-07-20 11:02:11 +01:00
Khalim Conn-Kowlessar
26e0584855 Record Elmhurst-validation provenance finding (#1649)
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>
2026-07-20 09:16:54 +00:00
Daniel Roth
d6226f8232 add docs for Ops 2026-07-20 08:54:12 +00:00
Daniel Roth
9c0bddd14d Add the Abri HubSpot ops guide, covering surveyor-change amendments
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 08:38:05 +00:00
Daniel Roth
1e38b44a9a A blank stored surveyor also counts as unset in the amendment check 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 08:37:33 +00:00
Daniel Roth
91dc359e0d A blank stored surveyor also counts as unset in the amendment check 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 08:36:50 +00:00
Daniel Roth
b4ce0ae83f An unchanged surveyor on a booked deal fires no amendment 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 08:36:05 +00:00
Daniel Roth
137ebd9456 Treat a blank surveyor as unset when checking for booking amendments 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 08:35:25 +00:00
Daniel Roth
234270a1ca Treat a blank surveyor as unset when checking for booking amendments 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 08:35:02 +00:00
Daniel Roth
26e2a98034 Amend the Abri booking when the deal's surveyor changes 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 08:34:43 +00:00
Daniel Roth
f91eb468cf Amend the Abri booking when the deal's surveyor changes 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 08:33:45 +00:00
Khalim Conn-Kowlessar
1424e2110e Re-pin Wythenshawe accuracy harness to the DB oracle (#1649)
`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>
2026-07-19 11:40:51 +00:00
Khalim Conn-Kowlessar
b7f127caec Fix six PasHub from_site_notes extractor/mapper defects (#1649)
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>
2026-07-19 11:30:00 +00:00
Khalim Conn-Kowlessar
ba90624a02 Re-pin 6035 golden fixture for terraced RR-gable reclassification
6035 is built_form 4 (Mid-Terrace), so this PR routes its Exposed Type-1
RR gable to a party gable (U=0.25) instead of the main-wall U. Heat loss
drops: PE resid -0.14 -> -0.92, CO2 -0.0362 -> -0.0549 (SAP still exact 70).
Residuals stay within noise (PE <0.5% of lodged 191). All 113 golden
fixtures pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 14:38:42 +00:00
KhalimCK
f69ab0b31c
Merge pull request #1641 from Hestia-Homes/feat/wchg-elmhurst-ingest-script
Add WCHG Elmhurst summary ingest script
2026-07-17 14:30:50 +01:00
KhalimCK
bd2253ee61
Merge pull request #1644 from Hestia-Homes/fix/elmhurst-community-dhw-rate
Bill community-heating DHW at the network fuel rate (not lodged mains-gas)
2026-07-17 14:30:25 +01:00
Daniel Roth
18dfda038d tweaks 2026-07-17 13:21:47 +00:00
Daniel Roth
f32628b293 Fix stale comments referencing the removed default-resource fallback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 13:20:47 +00:00
Daniel Roth
edd4ec00de Make the OpenHousing resource mandatory, dropping the default fallback
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>
2026-07-17 13:13:28 +00:00
Daniel Roth
c8fe4585bf Send the deal's third-party surveyor as the OpenHousing resource
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>
2026-07-17 13:00:47 +00:00
Daniel Roth
c07c59bb2d
Merge pull request #1642 from Hestia-Homes/1622-sitenotes-mapper-label-blocks
PasHub mapper: close the 14 ticketed label blocks under #1622 (LRHA 45→77, Wythenshawe 149→165 computed)
2026-07-17 13:54:25 +01:00
Daniel Roth
80113205c8 Sync third_party_surveyor_identifier deal field from HubSpot
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>
2026-07-17 12:53:26 +00:00
Khalim Conn-Kowlessar
85fe51b623 Reclassify terraced room-in-roof gables as party, not external
A mid-terrace / enclosed-mid-terrace (built_form 4/6) has external walls on
its two opposite (front/rear) faces only (RdSAP 10 §1.1, p.7-8); both end/
gable elevations — and their room-in-roof continuations — abut a neighbour
and are party walls (§1.4.3, p.9). The gov-API path is built_form-blind, so a
cert lodging an "Exposed" RR gable (`gable_wall_type=1`) on such a form had it
billed at the masonry main-wall U (~2.5) instead of the party 0.25 (Table 4,
p.22), over-counting fabric heat loss and under-rating the dwelling.

`_api_type_1_gable_kind` now takes `built_form` and reclassifies an Exposed RR
gable to Party on the both-ends-party forms {4, 6} only; end-terrace {3, 5},
semi {2} and detached {1} keep their genuine exposed gable. The API path lodges
no per-gable U, so this only ever overrides a cascade fallback.

Corroborated three ways: RdSAP §1.1 + Table 4; S10TP-05 Appendix A (a
mid-terrace's roof-gable junction length is zero); and three accredited
Elmhurst mid-terrace worksheets (000477/000480/000516), which all lodge Party
gables. Those worksheets run the site-notes path (already correct) so the 195
Elmhurst pins are untouched.

Corpus (RdSAP-21.0.1, 1000 certs): within-0.5 78.8% -> 79.5%, SAP MAE
0.625 -> 0.599, PE MAE 2.85 -> 2.71, CO2 MAE 0.070 -> 0.068. Cert
100040550095 -10.13 -> +0.37; the 18-cert terraced-external-gable cohort mean
-1.94 -> +0.11; end-terrace/semi/detached untouched. Thresholds ratcheted.

Tests: new test_mapper_rir_gable_terraced (9); 195 Elmhurst pins, 288
cert_to_inputs/real-cert pins, 54 mapper tests, and the corpus gauge all green;
no new pyright errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:32:42 +00:00
Khalim Conn-Kowlessar
fac64a90cc Bill community-heating DHW at the network fuel rate 🟩
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>
2026-07-16 18:38:02 +00:00
Khalim Conn-Kowlessar
eb5f52e9c4 Bill community-heating DHW at the network fuel rate 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 18:36:35 +00:00
Daniel Roth
4438cbc4b3 Document LRHA WAVE 3 end-state coverage and residual blocks 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:41:32 +00:00
Daniel Roth
7c979d60f6 Rate gas room-heater main systems at Table 4a code 603 🟩
#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>
2026-07-16 16:41:32 +00:00
Daniel Roth
316a112258 Rate gas room-heater main systems at Table 4a code 603 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:41:32 +00:00