Commit graph

177 commits

Author SHA1 Message Date
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
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
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
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
f7328f54c5 Fetch a property's lodged EPC by UPRN, not property_id
An epc_property row can carry a null property_id (FE/historic ingestion
persists EPC rows never linked to a property row), so a property_id-keyed
read silently misses them. UPRN is the durable key both the property row
and the epc_property row share. Repoint the lodged reads (get_for_property
+ get_for_properties) onto UPRN; the predicted reads stay on property_id
because a predicted EPC deep-copies a neighbour's UPRN, so its UPRN column
is never the property's own.

UPRN is not unique on epc_property, so the read tie-break is now load-
bearing (it was dormant under property_id, where the write path guarantees
one lodged row per property): the most recently ingested (highest-id) row
wins. Write path stays keyed on (property_id, source).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:35:51 +00:00
KhalimCK
660e42b920
Merge pull request #1526 from Hestia-Homes/feature/fabric-first-scenario
Fabric-first scenario constraint: two-phase optimisation (ADR-0061)
2026-07-10 12:48:06 +01:00
Khalim Conn-Kowlessar
102b250e53 A goal-aligned scenario without a budget fails loudly 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:10:06 +00:00
Khalim Conn-Kowlessar
6b64104dc5 An Energy-Savings scenario prices packages at the live fuel rates 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:10:06 +00:00
Khalim Conn-Kowlessar
aac35327f7 A Reducing-CO2 scenario maximises carbon reduction, not SAP 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:10:06 +00:00
Daniel Roth
f8b9d82200 Always send a bookable resource on Abri amend_job
OpenHousing logs a job but silently drops the appointment when no bookable
resource is sent, so the survey date never lands. log_job already sends the
configured default_resource; amend_job omitted the parameter unless the
request carried an explicit surveyor. Default amend's resource to the
configured surveyor too, and fail loudly at config load on a blank
ABRI_RELAY_DEFAULT_RESOURCE so the misconfiguration can't ship an empty
resource again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 10:37:35 +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
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
Khalim Conn-Kowlessar
26857f8df7 Bulk download: match uploaded_files via hubspot_deal_id bridge + add worker observability
A live test returned 'no documents could be packaged' for 91 correctly-resolved
properties. Root cause: no upload source populates uploaded_files.landlord_property_id
(pashub/magic-plan/audit set hubspot_deal_id; ECMK sets hubspot_listing_id), so
matching on landlord_property_id found zero rows.

Match fix: the worker now joins uploaded_files -> hubspot_deal_data (on deal_id) ->
landlord_property_id, taking the property identity from the bridge. The repository
returns a small PropertyDocument read-model instead of the infra ORM row, so the
orchestrator no longer names infrastructure.postgres.* (resolves the leak dancafc
flagged) and the s3_upload_timestamp cast is gone. Coverage is limited to
deal-id-linked sources; listing_id/uprn-only files are a noted follow-up.

Observability: the empty-selection failure now carries stage counts
(selected/matched/planned/skipped) in both the message (-> the worker WARNING log)
and details (-> sub_task.outputs), and the run logs those counts. The next failure
says 'matched 0 documents' instead of failing opaquely.

ADR-0060 + CONTEXT.md updated (matching decision + considered options).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 13:58:46 +00:00
KhalimCK
bd24a789b9
Merge pull request #1498 from Hestia-Homes/feature/bulk-document-download
Bulk Document Download: emailed ZIP of a property set's documents (ADR-0059/0060)
2026-07-08 12:49:46 +01:00
Khalim Conn-Kowlessar
9ec7987e97 PR review: best-effort read path, true per-member streaming, race-safe 409, local email 🟩
Addresses PR #1498 review:
- A per-file read failure (missing/deleted object, or a bucket the role can't
  reach) is now a SkippedDocument(reason=unreadable), not a whole-run abort;
  an all-unreadable selection still fails (no empty package). Best-effort on
  the read path (ADR-0060).
- Each ZIP member is streamed to a temp file (S3DocumentDownloader.download,
  boto download_file) and added from disk, so a single multi-GB member never
  hits the heap — the 'never held whole in memory' claim is now true.
- The 409 double-submit guard is DB-arbitrated: the sub_task insert is
  conditional on the task having none, so a race creates only one.
- Local env gets a placeholder recipient email so the route is exercisable.
- PackageEntry carries landlord_property_id so a read failure can be reported.
- TODO noting the UploadedFile.s3_upload_timestamp typing fix.
Two new tests: per-file read failure skips-and-reports; address fallback flows
to the folder name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 11:08:46 +00:00
Daniel Roth
8b7d32433e
Merge pull request #1492 from Hestia-Homes/feature/abri-api-integration
Abri API integration: abandon job
2026-07-08 10:53:21 +01:00
Khalim Conn-Kowlessar
77e7b0fda9 Package the good documents and report the skipped ones 🟩
Pins the best-effort contract (ADR-0060) delivered with the tracer — no red
phase; discriminating: a strict impl would fail the run or omit the report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:33:58 +00:00
Khalim Conn-Kowlessar
73b21e8d64 Treat a selection with no documents as a recorded failure 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:32:37 +00:00
Khalim Conn-Kowlessar
115f506353 Build, upload and email a Download Package for a property set 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:30:38 +00:00
Daniel Roth
84d0e1e0d5 Date an abandonment to the deal's own dates, not now() 🟩
date_abandoned now resolves to the third failed attempt's confirmed
survey date, falling back to its last submission date, rather than the
processing date. This dates the OpenHousing cancellation to when the
job actually lapsed even if the trigger message lags or redelivers.

- domain: abandonment_date() encodes the confirmed-survey -> last-
  submission fallback
- DealAbandonment carries both dates; abandon_job raises
  AbandonmentDateUnknownError when a deal has neither
- last_submission_date now flows through the trigger message and request
- the injected clock is dropped: nothing reads now() any more

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 08:51:39 +00:00
Khalim Conn-Kowlessar
8dd125f199 Review nitpicks: pids_with_default naming, empty-batch guard, truthful fake 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:44:23 +00:00
Khalim Conn-Kowlessar
015db4275b The plan repository reports which properties already have a default plan 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:08:30 +00:00
Daniel Roth
83af3ff4d0 Orchestrate an abandonment from a deal's recorded job 🟥
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:40:25 +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
Daniel Roth
7ad0dc0595 Merge branch 'main' into feature/abri-api-integration 2026-07-07 12:46:17 +00:00
Khalim Conn-Kowlessar
561b8b078b Test names state the surviving failure-recovery behaviors 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:12:12 +00:00
Khalim Conn-Kowlessar
d0439dd11e Completing a re-run failed batch un-fails the task 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:10:43 +00:00
Daniel Roth
8d70679ab0 Amending an appointment needs only the deal's changed date and time 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:07:23 +00:00
Daniel Roth
9bae8df6ea A changed booking amends the deal's OpenHousing appointment 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:01:01 +00:00
Daniel Roth
a93346ec7f A changed booking amends the deal's OpenHousing appointment 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 09:56:54 +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
a5868cc2e0 One AbriOrchestrator runs every Abri flow for a deal 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:26:48 +00:00
Daniel Roth
7e5dc1722e A rate-limited HubSpot write is retried before succeeding 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:59:27 +00:00
Daniel Roth
350a76f942 A failed HubSpot write raises a scrubbed error carrying the orphaned job_no 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:57:16 +00:00
Daniel Roth
b847fa2f4c Transport and parse failures propagate with nothing recorded 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:55:38 +00:00
Daniel Roth
ddfbbd0052 An OpenHousing rejection is surfaced verbatim with nothing recorded 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:54:58 +00:00
Daniel Roth
6912dabf22 A logged job's job_no is written to HubSpot and then the deal database 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:52:35 +00:00
Daniel Roth
98674ae895 Log an OpenHousing job from a confirmed survey booking's deal fields 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:50:07 +00:00
Daniel Roth
76896f9a8e rename tenant data sync to abri tenant data sync 2026-07-06 14:05:42 +00:00
Daniel Roth
578de8845a Tenant-data fixtures carry fictional names and Ofcom-reserved phone numbers 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:02:38 +00:00
Daniel Roth
8fd808ffd9 HubSpot deal-contacts transport lives under infrastructure like the other API clients 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:01:47 +00:00
Daniel Roth
8d684c57c7 Transient HubSpot failures are retried under the shared rate-limit policy 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:36:43 +00:00
Daniel Roth
065217f8af A failed HubSpot write stops the sync and reports the orphaned contacts 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:35:20 +00:00
Daniel Roth
2e19085875 HubSpot failures surface without the response body that echoes tenant PII 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:33:42 +00:00
Daniel Roth
203ffac01f Empty tenancies, Abri rejections and ambiguous responses resolve like the other relay operations 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:31:55 +00:00