Commit graph

8271 commits

Author SHA1 Message Date
Jun-te Kim
8115970d59
Merge pull request #1529 from Hestia-Homes/feat/epb-data-warehouse-mapper
Reject CEPC (commercial EPC) certs explicitly at the API mapper boundary
2026-07-10 11:10:19 +01:00
Jun-te Kim
44275aa6a1 Confirm multi-orientation window gap is not closeable for UPRN 100010086084
Re-attempted entering this SAP-15.0 cert's 4 real N/E/S/W windows (gov-API
sap_windows, 2.516 m^2 each) as separate Elmhurst Openings rows instead of
the accepted single combined South row, to test whether it closes the
residual ~2pt gap (engine 53 vs Elmhurst worksheet 51).

Reproduced live the documented elmhurst_lib.py window-grid limitation:
adding a 2nd row wipes the previously-added row's width/height to 0.00,
in both same-session and fresh-session-per-window attempts. Not fixable
via current Playwright automation. Restored the assessment to its original
single South 10.06 m^2 row (no PDFs re-downloaded, no worksheet change).

Separately confirmed our own engine already models this cert's windows
per-orientation correctly from gov-API sap_windows via
solar_gains.py's ORIENTATION_BY_SAP10_CODE cascade -- this is an Elmhurst
build-tooling ceiling, not an engine or mapper bug. sap_score=53 remains
unchanged; no calculator/mapper code was touched. Full accuracy suite
re-run clean (66 passed/67 skipped/1 xfailed + corpus test passed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:38:10 +00:00
Jun-te Kim
e91e0fa33e Fix cylinder_size/boiler_flue_type input errors for UPRN 100010086084 (SAP-15.0)
Both flagged mismatches were Elmhurst input errors (same silent-stale-value
contamination class as the earlier chimneys/wall-thickness bugs), not
parser or calculator bugs:

- cylinder_size: build_100010086084.py's water_heating() selected
  DropDownListCylinderSize by raw value "2", but this DOM's option values
  ARE their visible litre-band text (no "2" among them) — Playwright
  silently no-ops on a non-matching value, leaving a prior cert's "Medium
  (131-170L)" selection in place. Fixed to match by text ("Normal"), and
  added the missing mapper dict entries (Normal/Large litre-suffixed
  labels) to _ELMHURST_CYLINDER_SIZE_LABEL_TO_SAP10 (a real mapper-coverage
  gap — the calculator raises UnmappedElmhurstLabel rather than silently
  mis-mapping).

- boiler_flue_type: the boiler-code search dialog's combined "Balanced/Open
  Flue" Table 4b category doesn't drive the separate, independently
  selectable RadioButtonListFlueType field, which was left at an inherited
  "Balanced" from a prior cert. Fixed space_heating() to explicitly select
  "Open" to match the lodged gov-API code. This field isn't consumed by
  Sap10Calculator (ML/generator-only), so it had zero effect on the SAP
  score.

Re-downloaded elmhurst_summary.pdf/elmhurst_worksheet.pdf after the fixes
(Recommendations page confirmed clean). Elmhurst-PDF-inputs path moved
46->47 SAP from the cylinder-volume correction; gov-API SAP (53) and
Elmhurst's own worksheet (51) are unchanged. Full accuracy suites re-run
clean (67 passed, 67 skipped, 1 xfailed, same as before).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:11:41 +00:00
Jun-te Kim
798d19d6ff Fix contaminated Elmhurst ground truth for UPRN 100010086084 (SAP-15.0)
The Elmhurst rebuild for this cert had two carryover bugs from the shared
Playwright session: TextBoxOpenChimneys silently inherited "2" from a prior
cert's build (this cert's register lodges open_fireplaces_count=0, which
the gov-API mapper already reads correctly), and wall thickness inherited
a stale 280mm. Also fixed a real bug in the space_heating() PCDB-ref-clearing
logic: input_value() returns the string "0" when already cleared, which is
truthy in Python, causing a spurious navigate-away that broke the next
E.goto() call.

Rebuilt cleanly (chimneys=0, wall thickness=220mm per RdSAP convention for
unrecorded solid-wall thickness, since CheckBoxWallThicknessUnknown does not
persist via Playwright in this tool version). Elmhurst's own worksheet score
moves from a contaminated 46/48 to 51, closing the gap against this engine's
53-on-gov-API-inputs from ~5-7pt to ~2pt. No mapper.py change was needed --
the gov-API mapper's open_chimneys_count=0 was correct all along.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 08:36:03 +00:00
Jun-te Kim
283b311fcd Real Elmhurst RdSAP10 ground truth for one SAP-Schema-15.0 cert
Built UPRN 100010086084 (the largest gov-API-vs-lodged outlier from the
20-cert batch) in Elmhurst's accredited RdSAP10 tool to get real ground
truth, per feedback that lodged rating isn't a valid comparison target.

Elmhurst worksheet: SAP 46-48 (orientation-dependent), vs this engine's
53 on gov-API inputs -- a real ~5-7pt residual gap. Ruled out with
direct empirical tests: total_floor_area, missing party_wall_length,
region/climate (SAP rating always uses UK-average weather per Appendix
U -- confirmed inert), boiler/secondary efficiency (exact match, 66%/
63% both sides). Confirmed real but NOT blind-fixed (need more than one
cert's evidence, and a wall-U-value patch tested the wrong direction in
isolation): a wall-thickness-unknown U-value fallback mismatch (this
engine 1.70, Elmhurst's own "unknown" resolution 1.40) and a window
U-value divergence (1.85 vs 2.52 W/m2K for the same nominal glazing
pick). Single-window orientation confirmed a ~2pt partial contributor.

Also fixes a small, separate, evidenced gap the build surfaced: the
Elmhurst summary parser didn't recognise the live tool's "Medium
(131-170L)" cylinder-size label (only the bare "Medium").

Full write-up is in the RealCertExpectation comment for this cert in
test_real_cert_sap_accuracy.py and the worklist. Residual gap is
intentionally left open for follow-up, not force-closed.
2026-07-09 22:31:01 +00:00
Jun-te Kim
e31e01e85f Fix framing: lodged rating is not the accuracy target for SAP-Schema-15.0
These are 2011 SAP-2009 lodgements; this calculator implements current
RdSAP10, which has since revised several U-value tables. Closeness to
lodged was never evidence of mapper correctness (nor is a gap evidence
against it) -- the prior commit's comments and worklist entries wrongly
framed 16/20 "matching lodged" as validation and the other 4 as
anomalies needing explanation. Corrected the comments to state what the
batch actually shows: all 20 structurally distinct certs map without
error and produce plausible scores. Real accuracy validation still
needs an Elmhurst RdSAP10 rebuild on the same methodology as the
engine, which remains open.
2026-07-09 20:04:20 +00:00
Jun-te Kim
ce5899c5ec Validate SAP-Schema-15.0 mapper against 20 real certs from the failing task
Sampled all 20 unique property_ids that failed with "Unsupported EPC
schema: 'SAP-Schema-15.0'" across the sub_task history (114 total across
72 subtask rows), captured 20 real live certs, and pinned each as a
RealCertExpectation regression in the accuracy corpus.

16/20 land exact or within 1-3 SAP of the lodged rating, confirming the
door_count/WWHRS/FGHRS/draught-proofing defaults generalise beyond the
original two properties. The other 4 (all solid-brick, uninsulated,
age band C) diverge +6 to +12 — traced to a legitimate SAP-2009-vintage-
lodgement vs RdSAP10-recalculation methodology gap (u_wall() checked
directly against RdSAP10's own Table 6, returns the correct 1.70 — no
mapper defect), not a mapper bug, so pinned to the engine's observed
value rather than tuned to lodged.
2026-07-09 19:06:21 +00:00
Jun-te Kim
c9fc2c6baf Map SAP-Schema-15.0 certs (LIG-lodged, 2011-era reduced-field RdSAP shape)
Task a40e71c4-fd56-4a33-8677-e8643e6a5bb4 (portfolio 824, scenario 1278)
failed 2 of 29 properties with "Unsupported EPC schema: 'SAP-Schema-15.0'"
(property_id 748445/748451). Traced to two real 2011 lodgements
(schema_version "LIG-15.0", assessment_type "RdSAP") for UPRNs
100010359769/100010359788 — structurally the same reduced-field RdSAP-17.1
shape as the already-handled SAP-Schema-16.x family, one revision older.

from_sap_schema_15_0 reuses _normalize_sap_schema_16_x and adds three
schema-wide defaults this older generation never lodges at all (confirmed
absent on both real certs, not just sparse on one):
  - door_count=1 (single external door, standard RdSAP assumption)
  - instantaneous_wwhrs=0/0/0 and has_fghrs="N" (predates WWHRS/FGHRS
    lodging — absence-means-none-fitted, same convention used elsewhere)
  - percent_draughtproofed=0 (mirrors the existing has_draught_lobby
    "assume none if unknown" convention in cert_to_inputs.py)
  - energy_rating_average=60 (inert metadata, never read by the calculator)

Both real certs now map and run end-to-end through the SAP-10 engine,
matching (or within 1 point of) their lodged SAP score.
2026-07-09 18:30:08 +00:00
Daniel Roth
7997d88375 minor updates following Abri feedback 2026-07-09 16:27:50 +00:00
Jun-te Kim
93b34981c9 Reject CEPC (commercial EPC) certs explicitly at the API mapper boundary
epb-data-warehouse's fixture set covers CEPC-7.0/7.1/8.0.0 (SBEM
non-domestic certs) alongside the RdSAP/SAP families we already map.
CEPC has no sensible EpcPropertyData shape (asset_rating/BER instead of
a SAP score, activities/hvac_systems instead of building elements), so
from_api_response now raises a typed NonDomesticSchema instead of
falling through to the generic "Unsupported EPC schema" ValueError.
2026-07-09 14:58:25 +00:00
Khalim Conn-Kowlessar
877d0043cb Ventilation-once test pairs the wall with glazing, not a competing wall 🟪
Review feedback: cavity and internal wall insulation are competing options,
so a package selecting both read as nonsense even in a synthetic fixture.
The behaviour under test (a dependency triggered in both phases injects
once) now uses an airtightness pair that genuinely coexists — cavity wall
in phase 1, double glazing re-entering in phase 2 — with the same numbers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 13:59:14 +00:00
Jun-te Kim
84c259c17e
Merge pull request #1525 from Hestia-Homes/fix/rdsap-optional-built-form
Make RdSapSchema21_0_1.built_form optional — some real certs omit it entirely
2026-07-09 13:10:28 +01:00
Khalim Conn-Kowlessar
38206c227b Record the fabric-first two-phase decision as ADR-0061
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:45:56 +00:00
Khalim Conn-Kowlessar
4d7434a954 Hoist consumed-group lookup out of the phase-2 comprehension 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:45:56 +00:00
Khalim Conn-Kowlessar
012b0b03ba A Fabric First Scenario spends the budget on the envelope before heating 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:44:32 +00:00
Khalim Conn-Kowlessar
a9173bd492 A Fabric First Scenario spends the budget on the envelope before heating 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:44:31 +00:00
Khalim Conn-Kowlessar
e75798b281 Scenario carries its Fabric First flag from the scenario table 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:44:31 +00:00
Khalim Conn-Kowlessar
caa0847b70 Scenario carries its Fabric First flag from the scenario table 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:44:31 +00:00
Khalim Conn-Kowlessar
d51801089b Fabric gets first claim on the budget for every goal, not just Increasing EPC 🟩
Documents behaviour already delivered: with no SAP target both phases run
max-gain, so the envelope still consumes the budget first. Test passed on
arrival.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:42:54 +00:00
Khalim Conn-Kowlessar
dcc9f5d8aa A dwelling with no fabric candidates proceeds straight to the full pool 🟩
Documents behaviour already delivered: an empty phase 1 meets no target, so
phase 2 optimises every group — identical to a plain run. Test passed on
arrival.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:42:54 +00:00
Khalim Conn-Kowlessar
471728db0a Forced ventilation is injected once across both fabric-first phases 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:42:54 +00:00
Khalim Conn-Kowlessar
0220bee87c Forced ventilation is injected once across both fabric-first phases 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:41:27 +00:00
Khalim Conn-Kowlessar
adf60b82b1 Fabric unpicked in phase 1 can re-enter phase 2 on post-fabric worth 🟩
Documents behaviour already delivered: phase 2 optimises every group phase 1
did not consume, with signals re-scored against the fabric-applied dwelling,
so glazing skipped on raw-baseline merit re-enters when it closes the target.
Test passed on arrival.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:41:22 +00:00
Khalim Conn-Kowlessar
48ce18dbb6 Fabric spend comes out of the shared budget before phase 2 🟩
Documents behaviour already delivered by the phase-2 structure: the leftover
budget is the envelope minus the committed fabric cost, so a heating system
the plain optimiser would buy can be priced out. Test passed on arrival.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:41:22 +00:00
Khalim Conn-Kowlessar
7f4d313932 Phase-2 candidates are valued against the post-fabric dwelling 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:40:34 +00:00
Khalim Conn-Kowlessar
705c86d5e6 Phase-2 candidates are valued against the post-fabric dwelling 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:40:29 +00:00
Khalim Conn-Kowlessar
e652780873 Fabric short of the target is topped up with non-fabric measures 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:39:21 +00:00
Khalim Conn-Kowlessar
e880ae868c Fabric short of the target is topped up with non-fabric measures 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:36:43 +00:00
Khalim Conn-Kowlessar
9ad2c2edfd Fabric-first scenario stops at fabric when the target is met 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:36:27 +00:00
Khalim Conn-Kowlessar
fcf46263bc Fabric-first scenario stops at fabric when the target is met 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 11:36:02 +00:00
Jun-te Kim
460058d970 Make RdSapSchema21_0_1.built_form optional — some real certs omit it entirely
Some gov-API RdSAP-21.0.1 certs (e.g. 2461-5385-9264-6821-5357, property_id=750232,
portfolio 824) omit `built_form` from the response entirely rather than sending a
code. `from_dict` hard-fails any dataclass field with no default that's absent
from the raw dict, so the whole cert failed to parse.

built_form: Optional[int] = None (kw_only, matching the has_fixed_air_conditioning
precedent already on this dataclass) — the downstream SAP-cascade consumer
(_api_sheltered_sides) already treats a non-int built_form as "no lodging" and
falls back to the documented default, so None here is an already-safe
degradation, not a new failure mode. Also guards mapper.py's
`built_form=str(schema.built_form)` against stringifying None into "None".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 11:24:43 +00:00
Daniel Roth
6137a0e946 Merge branch 'main' into feature/abri-api-integration 2026-07-09 10:52:13 +00:00
Daniel Roth
1287cb1cf1
Merge pull request #1523 from Hestia-Homes/feature/abri-api-integration
Abri api integration: add missing reference to hubspot deal client booking reference
2026-07-09 11:39:23 +01:00
Daniel Roth
2a8878a0fb add missing reference to hubspot deal client booking reference 2026-07-09 10:32:34 +00:00
Daniel Roth
d235ce9f7d Merge branch 'main' into feature/abri-api-integration 2026-07-09 10:26:51 +00:00
Daniel Roth
274932b2b9
Merge pull request #1521 from Hestia-Homes/fix/hubspot-etl-import-error
Add utilities to hubspot etl dockerfile
2026-07-08 17:53:03 +01:00
Daniel Roth
4bf85375e7 add utilities to hubspot etl dockerfile 2026-07-08 16:49:55 +00:00
Daniel Roth
2c83c977e6
Merge pull request #1519 from Hestia-Homes/feature/abri-api-integration
Add logging to logic for deciding whether abri API should be triggered
2026-07-08 17:18:13 +01:00
Daniel Roth
a065857e48 more detailed logging 2026-07-08 16:06:41 +00:00
Daniel Roth
0306b43584 logging for debug purposes 2026-07-08 16:02:33 +00:00
Jun-te Kim
bd3ff8fcdc
Merge pull request #1513 from Hestia-Homes/fix/widen-cohort-search-v2
Widen EPC-prediction cohort search one step when the normal radius is too sparse
2026-07-08 16:17:57 +01:00
Jun-te Kim
8a04e9fca9
Merge pull request #1515 from Hestia-Homes/feature/hubspot-planning-fields
Add planning/address-profiling fields to HubSpot deal ETL sync
2026-07-08 16:14:01 +01:00
Jun-te Kim
04d1df1343 Add remaining planning/address-profiling fields to HubSpot ETL sync
Sync Design Constraints, Planning Comments, Planning Status, and
Planning Suggested Approach, matching the columns added in
Hestia-Homes/assessment-model#377.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 15:12:24 +00:00
KhalimCK
7f4fe43d6d
Merge pull request #1517 from Hestia-Homes/fix/bulk-download-worker-observability
Bulk download: readable email, deal-name folders, visible worker logs
2026-07-08 16:08:14 +01:00
Khalim Conn-Kowlessar
91db972d48 Bulk download: readable email, deal-name folders, visible worker logs
First successful live run surfaced three issues:

1. Email looked rubbish (a giant raw presigned URL). Now sends a proper HTML
   email with a 'Download documents' button plus a plain-text fallback, and a
   summary (N documents across M properties, expiry). Email delivery is now
   best-effort: a transport failure no longer loses an already-built package
   (the link is still on sub_task.outputs), and the SMTP connect has a 30s
   timeout so an unreachable SES endpoint fails fast instead of hanging to the
   900s Lambda timeout.

2. Every folder was 'address unavailable (...)': the resolver read property.address,
   but these are HubSpot deals with no property row. It now uses the deal's
   dealname from hubspot_deal_data.

3. No logs / no idea why a run took ~9 minutes: the worker's INFO logs were
   dropped (Lambda root logger defaults to WARNING). The handler now raises the
   level, and the orchestrator logs per-phase timing and volume (gather+plan,
   packaged N files / X MB, upload, email, total) so the slow phase is visible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 15:02:38 +00:00
Daniel Roth
d7648fd5e0
Merge pull request #1514 from Hestia-Homes/feature/abri-api-integration
Map Hubspot outcomes to their Abri abandonment reason codes
2026-07-08 15:46:28 +01:00
Jun-te Kim
b8316418e1 Add planning-authority HubSpot deal properties to ETL sync
Sync Planning Authority, Designated Area, Article 4 PD Rights, and
Listed Building from HubSpot into hubspot_deal_data so downstream
consumers of the existing HubSpot ETL process can read them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 14:45:03 +00:00
Daniel Roth
31fc7ee115 use unsuccesseful outcoems constant directly in hubspot deal differ 🟪 2026-07-08 14:37:15 +00:00
Daniel Roth
4ac4dd8683 Collapse cancelled/no show to HubSpot's single 'Cancelled / No Show' value
'Cancelled' and 'no show' were separate strings in the outcome vocabulary,
but HubSpot only ever emits the one dropdown value 'Cancelled / No Show' -
so neither phantom string could ever match. Replace both with the real
value (mapped to NOACCESS, best-guess pending client), which also fixes the
long-standing differ mismatch: the abandonment trigger now actually fires on
Cancelled / No Show. Drops the now-unused REQT enum member.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:33:59 +00:00
Jun-te Kim
c9e0180c12 Widen EPC-prediction cohort search one step when the normal radius is too sparse
ADR-0034's nearby-postcode broadening degrades to a genuine
NoSameTypeComparablesError whenever the default 1000m/30-postcode reach has no
same-type comparable nearby (e.g. property_id=752685, portfolio 824 — the only
Maisonette within reach). Adds one configurable extra widening step
(EpcComparablePropertiesRepository.candidates_near now accepts
widen_nearby_postcodes), tried only when the normal-radius walk falls short of
`minimum` matches. modelling_e2e's handler wires this to a 3000m/60-postcode
PostcodesIoClient as the single wider step.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 14:29:21 +00:00