Dan's review on #1546:
- Stream the workbook to a /tmp temp file and S3Client.upload_file it, instead
of render_workbook -> bytes -> put_object. render_workbook now saves straight
to a path; the orchestrator renders to a temp file, multipart-uploads it, and
always cleans it up. Restores ADR-0065's "never an in-memory BytesIO" decision
(the OOM path at the 100k-row cap).
- Move the raw `SELECT ... FROM scenario` out of the Lambda handler into
ScenarioNamesPostgresRepository, so the handler stays composition-only and all
SQL lives in repositories/.
- current_sap_points goes through _float, matching its Optional[float] read-model
type and the sibling numeric facts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "latest EPC per UPRN" selection ordered by e.id DESC, but row-id does not
track recency: a gov-EPC cert re-ingested after a PasHub survey lands with a
higher e.id, so the header/perf reads silently picked the stale gov cert (wrong
TFA, lodgement, property_type, current band) on 3 of portfolio 838. Order by the
effective lodgement date (registration -> completion -> inspection) DESC NULLS
LAST, e.id DESC tiebreak.
Compose the descriptive fabric from the structured SAP fields a re-survey lodges
(no gov-EPC prose): walls/roof/floor from the main epc_building_part, heating and
hot water from epc_main_heating_detail + the water-heating codes, decoded with
the SAP engine's own code space (fabric_description.py). Structured fabric wins
where present and falls back to the gov-EPC prose otherwise; windows and lighting
stay on prose (their per-element codes do not reconstruct the dwelling phrase).
Add built_form (epc_property, code-decoded) and property_age_band (building part
construction_age_band -> RdSAP date range) columns.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two fixes so a multi-scenario export over a PasHub-fetched portfolio is correct:
1. Plan selection: take the NEWEST plan per (property, scenario), not the
is_default one. is_default is one-per-property (not per-scenario), so it
cannot scope a multi-scenario export — a property's default sits under a
single scenario, leaving every other scenario with zero rows. _ROWS_SQL and
_MEASURES_SQL now DISTINCT ON (property_id) ORDER BY created_at DESC, id DESC.
2. Effective-EPC descriptive block: match the header facts + current performance
by UPRN (the latest epc_property for the source), because the PasHub re-fetch
lands records with property_id = NULL — a property_id join silently fell back
to a stale gov-EPC cert (wrong 2013-2025 lodgement dates / int property_type)
or nothing. Lodgement date coalesces registration -> completion -> inspection
(PasHub surveys carry only inspection_date). Descriptive prose elements stay
on the property_id gov-EPC fallback (a survey lodges structured fields, not
gov-EPC prose).
Result on portfolio 838 / scenarios 1303+1304: both sheets populate 205 rows;
property_type text on 204/205; lodgement dates 2026 on 200/205. Repository tests
updated (uprn fixtures, newest-plan selection + a newest-wins case); 22 export
tests pass, pyright clean.
Self-contained root module mirroring bulk_document_download (4GB memory per
ADR-0065, exports-bucket-only IAM). Cross-module wiring (shared ECR/state,
fast-api remote_state + ARA_EXPORT_SQS_URL env, CI image build) still needs a
terraform plan review — unverifiable in this environment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wires POST /v1/exports/scenario -> tasks.inputs recipe -> pinned sub_task ->
ARA_EXPORT_SQS_URL -> ara_export Lambda -> orchestrator. Route resolution and the
trigger body are covered by tests; the Lambda handler mirrors bulk_document_download.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>