Commit graph

514 commits

Author SHA1 Message Date
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
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
Daniel Roth
9fefdf81aa 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:14 +00:00
Daniel Roth
e0830169ad Abri log_job surfaces an OpenHousing rejection as a typed result with verbatim code and message 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:26:23 +00:00
Daniel Roth
79ce9dc4b1 Abri log_job sends the spec's recorded LogJob envelope to the relay endpoint 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:24:57 +00:00
Daniel Roth
dd451a03a8 Abri log_job returns the OpenHousing job number from the relay success response 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:22:56 +00:00
Daniel Roth
b22afcde9f Abri log_job returns the OpenHousing job number from the relay success response 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:22:09 +00:00
Daniel Roth
3b4415e773 Rename DB column energy_mains_gas → energy_gas_connection_available
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 11:22:29 +00:00
Daniel Roth
a174301136 rename mains_gas to gas_connection_available 2026-07-01 10:50:58 +00:00
Khalim Conn-Kowlessar
b9bec18f44 Round-trip a null Lodged Performance through persistence 🟩
The four lodged_* columns are nullable as a unit; from_domain writes them all
NULL when a predicted Property has no Lodged Performance, and to_domain
reconstructs lodged=None (lodged_sap_score is the read discriminator, mirroring
the bill block). The production lodged_* columns are FE-owned (Drizzle) and need
a companion ALTER ... DROP NOT NULL migration before the backend writes NULL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 22:13:04 +00:00
Khalim Conn-Kowlessar
513c9b9897 feat: persist 7 calculator-read EPC fields 🟩
Wire community heating fuel + CHP fraction (epc_main_heating_detail),
alt-wall is_sheltered + wall insulation thermal conductivity
(epc_building_part), and pv_diverter_present / measured cylinder volume /
AP50 air permeability (epc_property) through save + _compose/_to_*. All
deep-equal round-trip; coverage guard now enforces their reconstruction.
Columns live (FE migration applied).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:46:42 +00:00
Khalim Conn-Kowlessar
09ba84edf9 feat: persist solar PV arrays through the EPC round-trip 🟩
Add EpcPhotovoltaicArrayModel (epc_photovoltaic_array child table) and wire
save / delete / read so sap_energy_source.photovoltaic_arrays survives
load->save->load in order. Threaded through both the single get() and the
bulk _for_properties() paths via _compose -> _to_energy_source. Column
names match the FE migration (feature/epc-pv-and-floor-heatloss-schema).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:47:37 +00:00
Khalim Conn-Kowlessar
e5d94adc36 feat: persist floor heat-loss flags through the EPC round-trip 🟩
Add is_exposed_floor / is_above_partially_heated_space to
EpcFloorDimensionModel and wire from_domain + _to_floor_dimension. Column
names match the FE schema (feature/epc-pv-and-floor-heatloss-schema).
Live DB migration is run post-merge (drizzle-kit generate picks them up).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:42:20 +00:00
Khalim Conn-Kowlessar
4b9bd495e1 Pace Solar calls per container to stay under the 600 QPM fleet cap 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:00:33 +00:00
Khalim Conn-Kowlessar
48a47590e9 Pace Solar calls per container to stay under the 600 QPM fleet cap 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:59:00 +00:00
Khalim Conn-Kowlessar
440159e7f4 Harden Solar API client against 429s: jitter, Retry-After, bounded backoff 🟩
Route the Google Solar client through the shared call_with_retry with
full jitter (de-synchronises the 32 concurrent containers per Google's
"avoid synchronised requests" guidance), honouring Retry-After, a 60s max
backoff (rides out the 600 QPM per-minute window), and 6 bounded retries.
429/5xx/transport errors are transient; other 4xx propagate immediately;
404-entity-not-found stays BuildingInsightsNotFoundError. On exhaustion a
TransientHttpError surfaces so the subtask fails and is re-triggered (no
silent degrade).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:51:23 +00:00
Khalim Conn-Kowlessar
88ed0c2e88 Add opt-in full-jitter backoff to de-synchronise concurrent retries 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:45:00 +00:00
Khalim Conn-Kowlessar
03a0d9c1ca Round-trip the non-separated conservatory through persistence 🟩
Persist SapConservatory as five nullable conservatory_* columns on epc_property
(1:1 with the dwelling) and rebuild it in _compose, so the §6.1 fold survives
save -> reload -> score. Without this the scored (re-hydrated) EPC silently
dropped the conservatory (persist != score) — a latent gap shared with the
21.0.1 path. Adds a deep-equality round-trip test. ADR-0036.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:56:05 +00:00
Khalim Conn-Kowlessar
4f4ec32e51 Merge remote-tracking branch 'origin/main' into feature/e2e-runs
# Conflicts:
#	repositories/comparable_properties/epc_comparable_properties_repository.py
#	tests/repositories/comparable_properties/test_epc_comparable_properties_repository.py
2026-06-23 17:07:27 +00:00
Khalim Conn-Kowlessar
de7fb94ff7 docs(adr): record nearby-postcode broadening (0034) + share HTTP retry primitive
Closes out the cohort-broadening work with its decision record and consolidates
the retry plumbing.

ADR-0034 documents broadening the EPC-Prediction cohort to the real unit
postcodes nearest the target (via postcodes.io) when its own postcode holds no
same-type comparable — extending ADR-0031 decision 5. Records why postcodes.io
was chosen over council[] (whole-LA, no property_type in rows), a bulk Code-Point
Open / ONSPD dataset, and the OS Places radius API, and the lazy / nearest-first
early-stop / soft-fail policy. Broadening-specific docstrings now cite 0034.

Retry consolidation: extract the EPC client's call_with_retry into a shared
infrastructure/http_retry.py keyed off a generic TransientHttpError marker, so
the mechanism (exponential backoff, Retry-After) is shared while each client
keeps its own transient policy. EpcRateLimitError now subclasses TransientHttpError
(still an EpcApiError); PostcodesIoClient routes through the same helper, raising
TransientHttpError on 429/5xx and soft-failing to the seed once exhausted (the EPC
client propagates instead). Direct tests for the shared helper; EPC + postcodes.io
suites repointed at the shared sleep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:54:06 +00:00
Daniel Roth
79a76b10a9 fix db issues 2026-06-23 16:36:59 +00:00
Khalim Conn-Kowlessar
0bd2db4f03 feat(modelling_e2e): price gap measures via overlay + broaden prediction to nearby postcodes
Two reconciliations to make the modelling_e2e Lambda handler production-ready.

1. Price through the off-catalogue overlay, drop the workarounds
   The handler priced through a plain ProductPostgresRepository and excluded
   secondary_heating_removal / system_tune_up / system_tune_up_zoned to dodge
   ProductNotFound (and a poisoning pgEnum DataError). Those measures are now
   priced by catalogue_with_off_catalogue_overrides (already used by the e2e
   runner and PostgresUnitOfWork), so the exclusions are removed and ALL measure
   types are considered. This also fixes gas-boiler / single-glazed properties,
   which Dan's handler never excluded and so still crashed (the standard
   system_tune_up option is built unconditionally — the considered-measures
   exclusion never actually gated it).

2. Broaden the EPC-Prediction cohort to nearby real postcodes (ADR-0031)
   A property with no lodged EPC and no same-type comparable in its own postcode
   (e.g. the only flat among houses) used to gate out and fail the subtask. The
   gov EPC API cannot search by radius/outcode, so we resolve the real unit
   postcodes physically nearest the target via postcodes.io (keyless; already a
   trusted in-repo dependency) and walk them nearest-first until enough same-type
   comparables surface. New PostcodesIoClient (transient-failure retry with
   exponential backoff, soft-failing to the seed so broadening never breaks
   prediction) and EpcComparablePropertiesRepository.candidates_near. Wired into
   the handler and e2e runner; broadening is lazy (only on gate-out) and memoised
   per (postcode, property_type).

Validated live: property 728476 (gas boiler) prices system_tune_up at GBP295;
property 718580 (lone flat in BR6 6BS) now predicts via nearby BR6 postcodes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:25:18 +00:00
Khalim Conn-Kowlessar
c3422704f5 revert epc timeout to 10s 2026-06-23 11:19:23 +00:00
Khalim Conn-Kowlessar
efaff228ac feat(scripts): add --from-db re-model path + raise EPC API timeout
- run_modelling_e2e --from-db re-models from already-persisted inputs (reads
  each Property's Effective EPC + planning protections + solar from the DB) and
  skips every live fetcher — zero gov-API calls. With --persist it re-writes the
  Plan and, for lodged-EPC Properties, the Baseline. Self-contained loop; the
  live-fetch path is untouched. Makes local re-runs instant and avoids tripping
  the gov API's per-IP rate limit (6000 req / 5 min) during iteration.
- EpcClientService.REQUEST_TIMEOUT 10s -> 30s: a cold per-UPRN search can exceed
  10s and the old timeout turned it into a timeout-then-retry; 30s rides it out.

Note: an open perf question remains — modelling is fast in isolation (<0.5s/
property) but a long-lived --persist run shows ~1 min/property; suspected in the
persist path (plan.save / baseline) or connection handling, NOT the API. Left
mid-diagnosis for handover.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:05:06 +00:00
Jun-te Kim
9c89a0e680 neighbouring properties added 2026-06-22 14:38:00 +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
3a0a122b7f Group landlord property-override enums under domain/epc/property_overrides 🟪
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:49:58 +00:00
Jun-te Kim
cc228b3da5 Classify the landlord Hot Water and Heating columns into categories 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:09:05 +00:00
Jun-te Kim
89bb075ce6 Keep the age-band unique constraint within Postgres's 63-char limit 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:52:28 +00:00
Jun-te Kim
cb6a335382 Classify the landlord Age column into a construction-age-band category 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:44:40 +00:00
Jun-te Kim
db95205fc5 Classify the landlord Glazing column into a glazing category 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:36:34 +00:00