Commit graph

20 commits

Author SHA1 Message Date
Jun-te Kim
de5e9a2362 perf(modelling_e2e): bulk reads and batch writes to cut RDS load
The handler fired ~2+2N read round-trips and N+N write transactions per
SQS batch, pinning RDS CPU under ~32 concurrent containers on pool_size=1.

Reads: merge the duplicate property query and add overrides_for_many /
SolarRepository.get_many so overrides, solar, and property rows each load
in one query (2+2N -> 3).

Writes: buffer each modelled property's persistence intent in memory
(_PropertyWrite) during the loop, then flush the whole batch in one
PostgresUnitOfWork with a single commit, and run the baseline orchestrator
once for all written ids (N+N -> 2 transactions). Per-property modelling
failures stay isolated in the loop; the batch write is all-or-nothing and
retried via SQS (saves are idempotent upserts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 18:43:42 +00:00
Jun-te Kim
17b9ae08eb Hold one DB connection per modelling_e2e invocation
The modelling_e2e Lambda held up to ~4 concurrent Postgres connections per
invocation: the read Session stayed open across the write loop (the catalogue
was queried live and overrides were read per-Property), each per-Property Unit
of Work opened a second, and the TaskOrchestrator ran on its own NullPool
engine — so the pool needed pool_size=2 + max_overflow=1 just for the modelling
work. Under 32 concurrent containers that approached RDS max_connections.

Restructure the handler to read everything up front — overrides, Scenario, an
in-memory catalogue snapshot, and stored Solar — through one short-lived read
Session, close it, then write each Property in a sequential Unit of Work. The
read and write Sessions no longer overlap, so the engine drops to pool_size=1,
max_overflow=0. Fold the orchestrator onto the same pooled engine: its repos
commit on every save, releasing the connection between bookkeeping calls, so it
holds none during the work. One invocation now uses one connection at a time.

The catalogue becomes a per-invocation snapshot (MaterialSnapshotRepository),
mirroring ProductPostgresRepository.get exactly — same drift mapping, lowest-id
pick, and errors — but priced after the Session closes. Transaction isolation
is preserved: per-Property writes and orchestrator bookkeeping keep their own
independent transactions, just drawn sequentially from a single connection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 16:58:21 +00:00
Jun-te Kim
85e43b3737 Silence untyped SAEnum.enums access in the consistency guard 🟪
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:19:32 +00:00
Jun-te Kim
e03fd27357 Type-clean the override_component consistency guard 🟪
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:19:07 +00:00
Jun-te Kim
f7ee47118b Add the 5 new components to the override_component pgEnum mirror 🟩
Caught live writing property_overrides on portfolio 796: the Python
override_component SAEnum lagged the DB enum, so reading a new-component row
back threw LookupError. Guard it with a consistency test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:18:12 +00:00
Jun-te Kim
9d0d12c278 Lock heating-override value coverage and reader/overlay registry parity 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:10:02 +00:00
Jun-te Kim
6c2d1dce34 Route a main_heating_system override row through its overlay mapper 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:06:04 +00:00
Jun-te Kim
55e83c7f9f Route a water_heating override row through its overlay mapper 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:03:51 +00:00
Jun-te Kim
55cdc1c777 Route a construction_age_band override row through its overlay mapper 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:42:19 +00:00
Jun-te Kim
1810f09240 Route a glazing override row through the glazing overlay mapper 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:20:09 +00:00
Jun-te Kim
991eb74132 Route a main_fuel override row through the fuel overlay mapper 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:23:37 +00:00
Jun-te Kim
4219ef9d8b Overlay landlord property-type and built-form corrections onto the Effective EPC 🟩
Adds whole-dwelling property_type/built_form to EpcSimulation (folded by
apply_simulations) and maps those override components. property_type drives
party-wall heat loss + ASHP/solar/wall eligibility, so a landlord correction now
moves both the SAP calc and the measure menu; built_form has no calculator
consumer today (feeds the ML transform). Written as the landlord text value
(park-home check is text-only). Refines ADR-0032 dec-4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:06:29 +00:00
Jun-te Kim
0305241ad3 Overlay more wall materials and roof loft-insulation depth from landlord overrides 🟩
Extends WallType coverage to timber/stone/system-built/cob/park-home/curtain and
adds RoofType "Pitched, N mm loft insulation" -> roof_insulation_thickness. The
"(assumed) insulated"/"partial" wall states stay deferred (ambiguous code, needs
Elmhurst validation per ADR-0032); property_type/built_form carry no SAP weight.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:15:50 +00:00
Jun-te Kim
70ca56415e Merge branch 'main' of https://github.com/Hestia-Homes/Model into feature/landlord-overrides 2026-06-17 08:50:52 +00:00
Khalim Conn-Kowlessar
694cdd9c23 feat(modelling): mark a Property as run via has_recommendations + updated_at
The new pipeline left no per-Property record of a run (the old engine set
property.has_recommendations and populated property_details_epc). Restore the
marker: PropertyRepository.mark_modelled sets has_recommendations (true when the
Plan carries measures, mirroring the old engine) and bumps updated_at, so a
first-run under the new process is identifiable as updated_at >= 2026-06-01.

ModellingOrchestrator marks each Property after its Scenarios (true if any
Scenario yielded a measure); run_modelling_e2e's --persist path marks it too
(its compute runs on in-memory fakes, so the DB UoW sets it directly). Adds the
has_recommendations/updated_at columns to the PropertyRow mirror.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:34:33 +00:00
Jun-te Kim
4c038ae8dc Hydrate landlord-override overlays onto the Property from property_overrides 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:29:31 +00:00
Jun-te Kim
864ba8dc1b Resolve a Property's prediction attributes from landlord overrides in gov-code space 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:18:44 +00:00
Jun-te Kim
c5cffd9047 Read a Property's resolved landlord overrides as a faithful value-space snapshot 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:14:53 +00:00
Khalim Conn-Kowlessar
3e8304ce46 feat(property): hydrate planning restrictions from the spatial cache
Slice 3c.5. `PropertyPostgresRepository` takes an injected `SpatialRepository`
and hydrates `Property.planning_restrictions` by UPRN (bulk in `get_many`,
single in `get`). A UPRN with no cached row — or a property with no UPRN —
defaults to unrestricted, matching legacy `empty_spatial_df` (ADR-0020). This
closes the loop: Ingestion caches the protections, Modelling reads them off the
Property to gate solid-wall EWI/IWI (ADR-0019).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:35:39 +00:00
Khalim Conn-Kowlessar
92de07efba feat(property): Property aggregate + PropertyRepository (#1132)
Add the Ara modelling aggregate root (ADR-0002): domain/property/ with
PropertyIdentity, SiteNotes, Property, Properties. Property.source_path
implements the two disjoint source paths + Recency Tie-Break (ADR-0001;
survey wins on an equal date); effective_epc resolves to the surveyed data
(Site Notes path) or the public EPC (epc_with_overlay path — Landlord
Overrides overlay is a later slice). Pure dataclasses, no infrastructure imports.

PropertyRepository port + PropertyPostgresRepository hydrate the aggregate
whole from a defensive view of the FE-owned 'property' table (identity columns)
plus the EPC slice via EpcRepository.get_for_property. Reads only from repos
(ADR-0003). 8 domain + 1 hydration test; pyright strict clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 19:39:54 +00:00