Commit graph

544 commits

Author SHA1 Message Date
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
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
Khalim Conn-Kowlessar
0b52a28808 Read the property selection from task.inputs; route takes only task_id 🟩
Per PR review: the FE writes the selection config
({portfolio_id, property_ids?, select_all?}) into the FE-owned task.inputs and
passes only task_id, so a large hand-picked selection never travels in an HTTP
body. The route reads task.inputs, resolves to landlord_property_ids, caps, and
pins the recipe onto sub_task.inputs as before. Declares the FE-owned inputs
column on the TaskRow mirror so the backend can read it and the test schema
builds it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 10:41:14 +00:00
Daniel Roth
fe167f077f Declare the XML content type the Abri relay requires 🟩
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:08:06 +00:00
Khalim Conn-Kowlessar
879581bb32 Stream a local file to S3 with managed multipart upload 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:50:34 +00:00
Khalim Conn-Kowlessar
79c8890d07 Stream a local file to S3 with managed multipart upload 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:50:10 +00:00
Khalim Conn-Kowlessar
79fa46d97c Read document bytes from an arbitrary source bucket 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:42:37 +00:00
Khalim Conn-Kowlessar
0a09b56425 Read document bytes from an arbitrary source bucket 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:42:05 +00:00
Khalim Conn-Kowlessar
74732c6e5a Send a document-download email to the requesting user over SES SMTP 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:27:09 +00:00
Khalim Conn-Kowlessar
7466b78e54 Send a document-download email to the requesting user over SES SMTP 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:25:56 +00:00
Khalim Conn-Kowlessar
da3e0cd4b4 Generate a presigned GET URL for an S3 object 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:18:54 +00:00
Khalim Conn-Kowlessar
ba13300ee1 Generate a presigned GET URL for an S3 object 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:18:10 +00:00
Daniel Roth
157f9fc4b4 Surface both canceljob failure shapes as verbatim rejections 🟩
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:34:35 +00:00
Daniel Roth
03f0f4d490 Abandon a job through the relay canceljob route 🟩
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:32:13 +00:00
Daniel Roth
c206a76b22 Abandon a job through the relay canceljob route 🟥
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:30:24 +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
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
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
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
ed08a6adfe HubSpot clients share one scrubbed-retry call policy 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:02:22 +00:00
Daniel Roth
2ea9257036 A rate-limited HubSpot write is retried before succeeding 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:59:53 +00:00
Daniel Roth
049475ec96 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:58:36 +00:00
Daniel Roth
96f86aacac 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:54:05 +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
06354a166e Phone-number selection policy lives in the domain layer 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:47:37 +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
3dd2dfe92c 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:32:13 +00:00
Daniel Roth
64d397e6b4 Tenant vulnerabilities are recorded on the tenant's own contact 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:31:00 +00:00
Daniel Roth
52f2286ee9 Runner-up number fills the contact's secondary phone number 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:29:38 +00:00
Daniel Roth
10636c1752 Runner-up number fills the contact's secondary phone number 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:28:56 +00:00
Daniel Roth
1752a6cf10 Contact carries the best-priority mobile and landline Abri lists 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:27:59 +00:00
Daniel Roth
76d0b4bac9 Abri tenancy signatory becomes an associated HubSpot deal contact 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:24:47 +00:00
Daniel Roth
78fcba45e5 Abri tenancy signatory becomes an associated HubSpot deal contact 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:22:52 +00:00
Khalim Conn-Kowlessar
246834fac0 Map shard rows to HistoricEpc field-by-field so every column is pyright-checked 🟩
The row→domain mapper now names all 93 constructor arguments explicitly
instead of splatting a lowercased dict, takes a plain Mapping (a
DataFrame.to_dict("records") row) instead of a pandas Series, and ignores
columns the domain type doesn't know. A missing/renamed CSV column fails
loudly as a KeyError at the row. Both iterrows() call sites move to
to_dict("records") — pandas-stubs types iterrows' Series unparameterized,
which strict mode rejects. pandas-stubs + boto3-stubs[s3] make the stack
check clean: pyright strict is now 0 errors across the PR's files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 14:58:09 +00:00
Khalim Conn-Kowlessar
ab245de68d Merge remote-tracking branch 'origin/main' into feature/historic-epc-repository 2026-07-04 11:19:59 +00:00
Daniel Roth
b5f7cc3548 Abri job logging and appointment amendment serialise bookings through one date rule 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:24:48 +00:00
Daniel Roth
096e4ae1f7 Abri amend_job treats an unconfirmed amendment response as retriable 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:23:10 +00:00
Daniel Roth
e363a1105a Abri amend_job omits the surveyor resource so amendments cannot clobber Abri-side reassignments 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:21:06 +00:00
Daniel Roth
7bdba469fb Abri amend_job serialises the spec's amendoptiappt envelope with the surveyor resource last 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:19:16 +00:00
Daniel Roth
c542a57ae5 Abri amend_job amends an OpenHousing appointment and returns the echoed booking 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:17:41 +00:00
Daniel Roth
2cfed0c33d Abri amend_job amends an OpenHousing appointment and returns the echoed booking 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:15:44 +00:00
Daniel Roth
a86607e420 Abri relay exchange reads as serialise, post, parse, then dispatch on the failure document 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:12:44 +00:00
Daniel Roth
32b56050b0 Abri relay requests share one envelope, transport and rejection path across request types 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:37:40 +00:00
Daniel Roth
c23c74be6b Abri log_job treats a logged-job response missing its job number as retriable 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:36:55 +00:00
Daniel Roth
62ad53df5a Abri relay credentials and defaults hydrate from environment configuration 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:34:53 +00:00
Daniel Roth
fef69145da Abri log_job treats an unexpectedly shaped relay response as retriable 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:30:29 +00:00
Daniel Roth
0df1d866fe Abri log_job treats a malformed relay response as retriable, never as success or rejection 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:29:41 +00:00
Daniel Roth
95bb951b60 Abri log_job raises a retriable transport error when the relay is unreachable 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:28:39 +00:00
Daniel Roth
d002a3defc Abri log_job raises a retriable transport error on an HTTP error status 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:27:43 +00:00