Commit graph

2503 commits

Author SHA1 Message Date
Jun-te Kim
dbcdf29bd9 refactor(address2uprn): name the match/decision return types; rename helper
Address PR review (dancafc):
- introduce UprnMatch NamedTuple (datatypes/address_match.py) for the
  (uprn, address, lexiscore, certificate_number) return, replacing the bare
  4-tuple in get_uprn_from_epc_df / get_uprn_from_historic_epc /
  HistoricEpcResolver.resolve_uprn. Tuple-compatible, so unpacking is unchanged.
- rename get_uprn_with_epc_df -> get_uprn_from_epc_df (+ callers).
- type resolve_group_ambiguity via a GroupDecision NamedTuple and trim its
  docstring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:00:00 +00:00
Jun-te Kim
4e0134dd27 Merge branch 'feat/address2uprn-certificate-number' into feature/uprn-confirmation-before-finalise
# Conflicts:
#	backend/address2UPRN/main.py
2026-07-07 15:17:47 +00:00
Jun-te Kim
daa1cd7967 feat(address2uprn): withhold ambiguous cross-row UPRN matches (ADR-0057)
Phase 1 of confirming UPRNs before finalise. address2uprn matched each
row independently, so one UPRN could be the best match for two distinct
addresses (a coarse EPC record absorbing several real addresses, e.g.
flats in a block). Those distinct addresses were then silently merged by
the property identity insert, and collided in property_overrides.

resolve_group_ambiguity() withholds a UPRN claimed by >=2 distinct
normalised addresses within a postcode group (keeps genuine same-address
re-listings), and the handler now emits an address2uprn_status column
(matched | ambiguous_duplicate | unmatched | invalid_postcode | error).
Withheld rows drop to a null UPRN but keep their lexiscore for triage on
the (upcoming) confirmation page.

Also adds the ADR-0057 backstop dedup in property_overrides upsert_all so
the ON CONFLICT statement can never double-touch a row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 15:15:59 +00:00
Daniel Roth
1085384f21 Merge branch 'main' into feature/abri-api-integration 2026-07-07 14:08:52 +00:00
KhalimCK
9ff4de0563
Merge pull request #1481 from Hestia-Homes/feature/modelling-trigger-run
Modelling Run Distributor: POST /v1/modelling/trigger-run
2026-07-07 14:36:45 +01:00
Jun-te Kim
3718743801 Carry the EPC certificate number through address2uprn to property
EpcClientService.search_by_postcode already returns the matched
certificate number alongside the UPRN, but it was dropped before
persistence. Thread it through get_epc_data_with_postcode ->
get_uprn_with_epc_df / get_uprn_from_historic_epc (using the historic
dataset's lmk_key) -> the address2uprn_certificate_number result
column -> PropertyIdentityInsert -> the property table's new
certificate_number column (assessment-model PR #362).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 13:26:49 +00:00
Khalim Conn-Kowlessar
cc4bf4394e Both postcode batchers share one group-preserving packing core 🟪
Review feedback (#1481): the address batcher and the Modelling Run batcher
implemented the same greedy packing; the core moves to
utilities/grouped_batching.py and both become thin wrappers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:24:02 +00:00
Khalim Conn-Kowlessar
07190fc332 Legacy property columns no longer resolve type or built form 🟩
ADR-0056 amended: override and EPC own type/form facts; a property with
neither is Unknown whatever the legacy columns say. Mirror columns removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:52:11 +00:00
Daniel Roth
7ad0dc0595 Merge branch 'main' into feature/abri-api-integration 2026-07-07 12:46:17 +00:00
Daniel Roth
a3985389ab The deal row names the job number client_booking_reference, matching HubSpot and the schema 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:43:40 +00:00
Daniel Roth
5286e2a2a2 The job number is stored in the client_booking_reference column 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:31:07 +00:00
Khalim Conn-Kowlessar
bf8647c9b5 The 409 guard and batch creation read as methods on ModellingRunTasks 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:16:42 +00:00
Khalim Conn-Kowlessar
b62901a9de The distributor coexists with both table stacks and mounts at /v1/modelling 🟪
DB access via parameterised SQL, not the infrastructure SQLModel mirrors:
importing those alongside the legacy backend.app.db.models mirrors of the
same tables double-registers them in the shared metadata and crashes the
app at import. Contained to the modelling package until the DDD cut-over.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:15:31 +00:00
Khalim Conn-Kowlessar
b22b9cbca0 Scenarios outside the portfolio refuse the run 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:06:42 +00:00
Khalim Conn-Kowlessar
e9eb068be9 Filters that match no properties refuse the run 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:05:39 +00:00
Khalim Conn-Kowlessar
cfedb8e9cf A task that already has sub_tasks refuses re-distribution 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:04:34 +00:00
Khalim Conn-Kowlessar
9031c16ba7 Trigger-run fans out one sub_task and message per scenario batch 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:03:33 +00:00
Khalim Conn-Kowlessar
489e2b5d47 Trigger-run fans out one sub_task and message per scenario batch 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:02:12 +00:00
Khalim Conn-Kowlessar
2dd948259f Batches never split a postcode 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:59:07 +00:00
Khalim Conn-Kowlessar
80c474b37b Batches never split a postcode 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:58:41 +00:00
Khalim Conn-Kowlessar
19d9e758ad Both filterable components resolve through one precedence engine 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:45:10 +00:00
Khalim Conn-Kowlessar
fc3f6a6f94 Built-form filter resolves with the same precedence as property type 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:43:03 +00:00
Khalim Conn-Kowlessar
92370504da Unknown is a selectable property-type bucket 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:41:19 +00:00
Khalim Conn-Kowlessar
ec7ba1f0d3 A property without override or EPC falls back to the legacy column 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:40:24 +00:00
Khalim Conn-Kowlessar
bf0e9343b2 An EPC without property_type falls back to its dwelling_type 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:39:32 +00:00
Khalim Conn-Kowlessar
dfe42a6374 Numeric RdSAP property-type codes map to their labels 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:38:47 +00:00
Khalim Conn-Kowlessar
e1dc0dc2f5 EPC-derived property type prefers lodged over predicted 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:37:45 +00:00
Khalim Conn-Kowlessar
4967ddfb3e Property-type override beats the lodged EPC 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:36:47 +00:00
Khalim Conn-Kowlessar
10b9015bce Postcode filter matches the canonical postcode exactly 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:34:07 +00:00
Khalim Conn-Kowlessar
3836729f92 No filters resolves the whole portfolio minus deletions 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:32:53 +00:00
Khalim Conn-Kowlessar
84fb9884d6 No filters resolves the whole portfolio minus deletions 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:32:10 +00:00
Khalim Conn-Kowlessar
b008bcc6ba The tasks mirror declares the FE-owned inputs column
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:32:09 +00:00
Daniel Roth
d70853bfd2 The scraper sends fired Abri flows to the abri queue on each deal change 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:16:13 +00:00
Jun-te Kim
bc1cca77db
Merge pull request #1356 from Hestia-Homes/feature/historic-epc-repository
Historic EPC repository: DDD port + resolver for address→UPRN
2026-07-07 10:56:33 +01:00
Daniel Roth
01f0392ce2 remove overly-specific comment 2026-07-06 16:14:25 +00:00
Daniel Roth
faac50eca4 A client_booking_reference change triggers a deal database update 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:04:54 +00:00
Jun-te Kim
adb1bf4b56 Log GITHUB_SHA on /health; move zip-size gate to its own PR workflow
/health already returned GITHUB_SHA in its response but never logged
it, and the fastapi lambda's Terraform environment never actually set
GITHUB_SHA — so every health check response contained "unknown" in
production. Wire var.github_sha through the fastapi lambda module
(default "unknown" for local/other invocations) via a new
TF_VAR_github_sha env var set from `github.sha` in
_deploy_lambda.yml's Terraform Plan step, and log it on every /health
call so a request in CloudWatch can be tied back to the deploy that
served it.

Also fix the zip-size gate added for PR #1469: putting it in
tests/test_lambda_zip_size.py (run via the Docker-based ddd_tests.yml
suite) broke CI, because Dockerfile.test's build context excludes
deployment/* (.dockerignore), so check_lambda_zip_size.py couldn't
find variables.tf to read zip_excludes from inside that container.
Move the check to its own lightweight workflow,
check_lambda_zip_size.yml, triggered on pull_request into main — a
plain checkout (no Docker build) has the full repo, so the check
works, runs fast, and still gates merges to main before a regression
can roll into the dev deploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 13:43:36 +00:00
Jun-te Kim
d5f1c61689 Move zip-size checker next to the fastapi requirements it checks
Relocate scripts/check_lambda_zip_size.py to
backend/app/requirements/, right beside the requirements.txt it's
built to measure, since it's specific to the fastapi lambda rather
than a generic repo script.

While moving it, also make the excludes list self-updating: the
script now reads zip_excludes straight out of
deployment/terraform/modules/lambda_with_api_gateway/variables.tf
by default instead of duplicating it as CLI flags, so the CI check
and the actual Terraform packaging can't drift apart. requirements.txt
defaults to the sibling file too, so both the CI step and local runs
now collapse to a bare `check_lambda_zip_size.py` invocation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 13:00:59 +00:00
Jun-te Kim
c238a0e6a0
Merge pull request #1449 from Hestia-Homes/feat/sap-corpus-campaign
fix(rdsap): RdSAP 10 Table 13 200/280mm solid-brick wall boundary
2026-07-06 12:25:17 +01:00
Khalim Conn-Kowlessar
ab245de68d Merge remote-tracking branch 'origin/main' into feature/historic-epc-repository 2026-07-04 11:19:59 +00:00
Jun-te Kim
5cae4a0ce5 fix(rdsap): RdSAP 10 Table 13 200/280mm solid-brick wall boundary
_u_brick_thin_wall_age_a_to_e's `<= 280` check put an uninsulated solid-
brick wall of exactly 280mm in the "200 to 280mm -> 1.7" row; RdSAP 10
§5.7 Table 13 (spec PDF p.41) shares 280 as an unlabelled edge between
that row and "280 to 420mm -> 1.4", so the table text alone doesn't say
which row owns it.

Found by building cert 100031768368 (280mm exactly, band C) in Elmhurst
and comparing every worksheet line to the calculator: volume/ACH/floor/
doors matched exactly, but the wall didn't (1.70 vs Elmhurst's 1.40)
despite an identical input crosswalk. Initially reverted a first attempt
at this fix when it appeared to regress an already-pinned cert
(217091901, band A, also nominally 280mm, previously "confirmed" at
U=1.70) — but that cert's build script never actually set a wall
thickness field, so its shared Elmhurst assessment had silently
inherited a stale 260mm from an earlier build. Fixed that build script
and rebuilt with the correct 280mm entry: Elmhurst's worksheet now also
gives U=1.40, matching 100031768368 and confirming the fix rather than
contradicting it.

Both certs move to (near-)exact lodged matches: 100031768368 59.12 ->
61.21 (lodged 61, within 0.5); 217091901 60.82 -> 61.59 (lodged 62,
exact). Corpus gauge 77.8% -> 78.6% within-0.5, MAE 0.636 -> 0.627 (14
corpus certs lodge exactly 280mm solid brick). TDD'd, 46 pins + full
suite green (2310 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:25:07 +00:00
Khalim Conn-Kowlessar
325c0ea257 Merge remote-tracking branch 'origin/main' into fix/full-sap-export-capable-mapping 2026-07-03 12:11:25 +00:00
Khalim Conn-Kowlessar
6656d8dec2 A full-SAP export-capable PV cert reproduces its lodged SAP 10.2 score 🟥
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 12:05:04 +00:00
Jun-te Kim
5c510e1b15
Merge pull request #1439 from Hestia-Homes/feat/sap-corpus-campaign
test(accuracy): corpus-1000 — 74.2% → 77.7% within-0.5, MAE 0.721 → 0.637 (8 fixes; mapper+calc validated vs Elmhurst)
2026-07-03 12:52:57 +01:00
Daniel Roth
9559f23741 Abri deal abandonment fires only when the deal newly becomes abandoned 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 10:12:59 +00:00
Jun-te Kim
6c1828e8bf chore(hyde): fix 100061275133 Elmhurst build — 0 fans + PCDB exact boiler
ventilation(): the cert lodges no extract-fan count and RdSAP 10 Table 5's
age-band B default is 0, but the build entered 1 — inflating Elmhurst's own
(8)/(21) infiltration lines relative to both the cert and our engine.

space_heating(): switched from the generic "Combi condens" SAP-Table dialog
code (84% efficiency, no PCDB record) to elmhurst_lib.set_pcdb_boiler(18908)
via clear_main_heating_code — resolves the exact lodged product (Worcester
Greenstar 4000, 88.70%) and its correct keep-hot classification (None),
matching the SAP engine's own PCDB lookup and removing the "Standard Combi"
keep-hot artifact the reused generic code carried.

Also corrected the file's stale docstring (described a different property
entirely — mid-terrace/250mm/DUAL meter/11 windows/lodged 63 — left over
from a copy-paste).

Verified against the refreshed worksheet: (21) shelter-adjusted ACH 0.6938
now EXACT vs our engine's ventilation_from_cert; (61) combi loss monthly
kWh matches ours to ~0.01 kWh (was carrying a ~360 kWh/yr keep-hot artifact).
Both open items from the PR #1439 handoff are resolved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 09:20:12 +00:00
Jun-te Kim
b9d36f72cb chore(hyde): refresh 100061275133 Elmhurst PDFs after draught-proofing rebuild
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 07:45:58 +00:00
Jun-te Kim
61934af97b chore(hyde): fix 100061275133 Elmhurst build faithfulness (lighting 5 LED, wall thickness 400)
Input reconciliation (Elmhurst summary vs EpcPropertyData vs raw cert) exposed
two Playwright-entry errors: lighting entered as 6 incandescent (cert has 5 LED,
100% low-energy) and wall thickness left at Elmhurst's 260mm default (cert lodges
400mm). Fixing both moved Elmhurst 67->69. Confirmed the MAPPER is faithful
(every fabric/heating/lighting field matches the raw cert) and the engine uses
the correct PCDB 18908 efficiency (88.7%). The remaining Elmhurst gap to lodged
76 is the un-enterable PCDB boiler (automation falls back to generic BGW 84% +
keep-hot); the engine (77) is faithful. Not a mapping error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 06:13:09 +00:00
Jun-te Kim
6c47951d60 test(accuracy): pin code-195 electric storage-boiler certs as spec-faithful (§12 Rule 2)
Electric-tariff cluster investigation. The 112-cert electric-main cohort is
mixed-sign within every category/meter - no systematic tariff bias. Root cause
of the biggest non-xfail divergences (10012334488 +13.2, 10091578598 +7.81):
SAP code 195 = 'Electric water storage boiler' (Table 4a p.170), which SAP 10.2
§12 Rule 2 correctly bills mostly at the 7-hour off-peak rate. Both are pure
cost gaps (PE/CO2 match lodged -> demand right); the lodged software over-billed
the storage boilers at peak rate. The engine is SPEC-FAITHFUL - matching lodged
would tune against §12/Table-4a. Pinned to the spec-faithful engine values.

Adds scripts/corpus_1000/elec_cohort.py (electric-main cohort analyzer).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 04:55:42 +00:00
Jun-te Kim
bf53d29690 test(accuracy): pin 100051051866 + 10070056075 (clean single-main loop complete)
Final two clean single-main gas certs. Both small cost-side residuals (+1.04 /
-1.01 SAP) on cavity-insulated fabric already Elmhurst-validated on sibling
certs, with properly-insulated cylinders (insulation type 1, 50/80mm - not the
type-0/NULL zero-storage-loss bug). No fabric anomaly; the register data fully
explains the engine output. Pinned to observed engine 71 / 79.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 04:30:22 +00:00