Commit graph

128 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
9cbb8a95b5 Completing a re-run failed batch un-fails the task 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:11:52 +00:00
Daniel Roth
76896f9a8e rename tenant data sync to abri tenant data sync 2026-07-06 14:05:42 +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
81d25caead 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:47 +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
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
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
41d46e3910
Merge pull request #1454 from Hestia-Homes/feature/extend-sharepoint-renamer
Extend sharepoint renamer to work for batch subfolders
2026-07-03 14:48:07 +01:00
Daniel Roth
e533316771 UPRN-prefixed property folders rename to a single-UPRN filename 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:09:32 +00:00
Daniel Roth
46ed8bfe86 Renamer warns once per property only after all roots miss 🟪
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:19:34 +00:00
Daniel Roth
213cd92998 Renamer warns once per property only after all roots miss 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:18:42 +00:00
Daniel Roth
1196a8f75d Renamer finds properties filed under _Sero Batch roots 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:16:46 +00:00
Daniel Roth
a442dcfbaa Renamer discovers _Sero Batch roots under Property Folders 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:13:39 +00:00
Daniel Roth
dcd2a7442f Renamer discovers _Sero Batch roots under Property Folders 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:12:59 +00:00
Khalim Conn-Kowlessar
bf240e3315 ASHP sizing targets the Appendix-N efficiency peak (realistic installed pump) 🟩
Sizing selects the aroTHERM plus rung nearest PSR ~0.8 (0.8 x design heat
loss) — the Appendix-N efficiency peak, reproducing the pump a real
installer fits — rather than the MCS PSR>=1.0 capacity target, which
oversizes. Validated against the relodged Elmhurst ASHP cert (delta 0
held at the 5 kW Vaillant); the two self-snapshot pins re-pin to their
correctly-sized larger pumps, and the orchestrator/harness thread the
calculator's design heat loss so production sizes to the dwelling. Manual
SapResult test stubs carry the new design_heat_loss_kw field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:02:13 +00:00
Khalim Conn-Kowlessar
cb91954bb4 The orchestrator threads the dwelling design heat loss into ASHP sizing 🟥
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 10:31:57 +00:00
Khalim Conn-Kowlessar
da805bce21 Persist no Lodged Performance for a predicted Property 🟩
The PropertyBaselineOrchestrator now reads no Lodged Performance off a predicted
Property's neighbour-synthesised EPC: lodged is None when source_path is
'predicted', so no phantom lodged figure is manufactured. The Effective half is
unchanged. Rebaseliner port + PropertyBaselinePerformance.lodged widen to
Optional; the pristine-cert paths assert non-None (lodged is None only for a
predicted Property, which is always physical_state_changed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 22:10:57 +00:00
Khalim Conn-Kowlessar
4d5504fa10 feat: thread physical-state-change signal into rebaselining 🟩
Add Property.physical_state_changed (true on Site Notes / Landlord Overrides
/ Prediction — trigger (b)/(c)) and pass it from the
PropertyBaselineOrchestrator into the Rebaseliner. So an overridden or
predicted SAP>=10.2 property now stores calc(effective) as its Effective
baseline instead of echoing the lodged headline — closing the "81 in the DB"
divergence between the displayed baseline and the modelled plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:15:11 +00:00
Jun-te Kim
b1ff711260 perf(modelling_e2e): batch SubTask bookkeeping to stop per-property writes
Even after batching the data writes, the handler still wrote to the DB per
property through the orchestrator's SubTask bookkeeping: create + start +
complete each self-committed, and _cascade re-listed every sibling and re-saved
the parent on every transition — ~5 writes per property plus an O(N^2) cascade.

- TaskOrchestrator.run_subtasks: create all children in one INSERT, run each
  (failures isolated per child), then persist all terminal states in one bulk
  save and cascade the parent once. Children go WAITING -> terminal; the
  transient IN_PROGRESS row is never written.
- SubTaskRepository.create_many / save_many (bulk INSERT / bulk fetch + update).
- _cascade short-circuits when the Task is already FAILED (terminal) — skips the
  sibling roll-up entirely.
- modelling_e2e handler fans out via run_subtasks instead of per-property
  create_child_subtask + run_subtask.

Per N-property batch the SubTask bookkeeping drops from ~5N writes + an O(N^2)
cascade to ~2 writes + 1 cascade.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:26:42 +00:00
Jun-te Kim
6679d8c621 Fall back to computed kWh for predicted-EPC baselines (no lodged RHI)
16 modelling_e2e properties failed with "Effective EPC is missing
renewable_heat_incentive; cannot read baseline space-heating / hot-water kWh".

Baseline runs for predicted properties too (ADR-0031), reading space/water-
heating kWh off the EPC's lodged RHI block. Predicted EPCs deep-copy a neighbour
template that may carry no RHI, so `_require_rhi` hard-failed the whole subtask.

Fix: when the EPC has no RHI, fall back to the property's OWN computed figures
from the scored SapResult (space_heating_kwh_per_yr / hot_water_kwh_per_yr) —
more representative than a neighbour's lodged numbers. Only when there is also no
SapResult (the rebaseliner scored nothing) is there genuinely no demand to
record, and we still fail noisily. Lodged certs are unchanged (RHI still wins).

Regression tests: fallback-to-computed, and the no-RHI/no-result raise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 08:58:24 +00:00
Daniel Roth
25c695186b measured excluded plus get geospatial nearby stuff working 2026-06-22 16:12:16 +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
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
Khalim Conn-Kowlessar
3580d059ec feat(modelling): drive measure scoping from the Scenario's exclusions
The measures a run considers should come from the Scenario, not a CLI flag.
The live scenario table persists exclusions only (no inclusions column), as a
Postgres text-array of exact MeasureType values.

- Scenario gains `exclusions: frozenset[MeasureType]` + `considered_measures()`
  (all measures minus the excluded ones, or None when none are excluded).
- ScenarioModel.to_domain parses the `{a,b,c}` exclusions array into
  MeasureTypes, raising on a token that is not an exact MeasureType value
  (no high-level category expansion), per the strict-enum convention.
- ModellingOrchestrator._plan_for derives the allowlist from the Scenario's
  exclusions, combined (intersection) with any explicit considered_measures
  override via the new `combine_considered_measures`.
- run_modelling_e2e sources the allowlist from the Scenario; --measures /
  --exclude-measures become optional overlays (e.g. the technical
  secondary_heating_removal exclusion the catalogue cannot yet stock).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:26:25 +00:00
Khalim Conn-Kowlessar
5c19737fc5 feat(modelling): gate generation by the considered-measures allowlist
`restrict_to_considered_measures` filtered candidates only *after* every
generator had run, so an excluded measure still queried the catalogue.
That crashed properties with a lodged secondary heater: the live
`material.type` enum has no `secondary_heating_removal` value, so the
query raised a psycopg2 `InvalidTextRepresentation` before the allowlist
could drop it.

`_candidate_recommendations` now pairs each generator with the measure
types it can emit and runs it only when the allowlist admits one of them
(None = all), so an excluded measure never reaches the catalogue.
`restrict_to_considered_measures` still trims disallowed Options off the
multi-Option survivors. Add `--exclude-measures` to run_modelling_e2e
(allowlist minus the excluded set) for excluding one measure without
enumerating the rest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 14:56:09 +00:00
KhalimCK
90bed458f4
Merge pull request #1238 from Hestia-Homes/feature/epc-prediction
Feature/epc prediction
2026-06-16 21:58:40 +08:00
Khalim Conn-Kowlessar
7ca1f815f6 refactor(epc-prediction): PR review — rename ComparableProperty, relocate PredictionTarget
Two review points from @dancafc:

1) Rename the `Comparable` dataclass → `ComparableProperty` (it models one
   comparable *property*; the collection stays `ComparableProperties`). Applied
   across domain, repositories, orchestration, harness, scripts, and tests with a
   word-boundary rename so `ComparableProperties` is untouched.

2) Move `PredictionTarget` out of comparable_properties.py into prediction_target.py
   (where `PredictionTargetAttributes` + `build_prediction_target` already live).
   comparable_properties.py now imports it; no import cycle (prediction_target no
   longer depends on comparable_properties). Importers updated.

92 tests pass across the touched suites; pyright strict clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 13:34:44 +00:00
Daniel Roth
6063fe051e ignore jpg and heic files. resolve filepath relatively 2026-06-16 10:06:39 +00:00
Khalim Conn-Kowlessar
5727ac53c1 feat(epc-prediction): slice-5e ingestion wiring (gate → predict → persist)
Wire EPC Prediction gap-fill into IngestionOrchestrator (ADR-0031). When the
predictor collaborators are injected (ComparablesRepo + PredictionAttributesReader
+ EpcPrediction), an EPC-less Property is predicted from its postcode cohort and
persisted to the predicted slot; the eligibility gate (unknown property_type) and
"a lodged EPC is never predicted over" both hold. The two-phase contract is kept:
prediction attributes (Landlord Overrides) resolve in the unit prep phase, the
cohort fetch + select + predict run in the no-unit IO phase, persistence in the
write phase. All three collaborators are OPTIONAL — unwired, ingestion behaves
exactly as before (existing tests unchanged).

3 tests (predict+persist, gate, lodged-wins); 228 pass across orchestration +
epc_prediction + repositories; pyright strict clean. Production composition-root
wiring (real ComparableProperties + override-attributes adapters) is part of the
Jun-te handover.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 04:03:02 +00:00
Daniel Roth
963b7d70fe fix terraform error and pass handler bool for dry runs 2026-06-15 14:06:54 +00:00
Daniel Roth
beb4e5d0d9 Move SharePoint renamer logic from scripts/ into orchestrator and app-root handler 2026-06-15 11:01:51 +00:00
Jun-te Kim
a6123d762c Merge branch 'main' of https://github.com/Hestia-Homes/Model into feature/junte+khalim 2026-06-12 13:45:30 +00:00
Jun-te Kim
ff4a2e4242
Merge pull request #1198 from Hestia-Homes/feature/bill-derivation
Feature/bill derivation
2026-06-12 14:44:30 +01:00
Jun-te Kim
77c5f7da49 Merge branch 'feature/bill-derivation' of https://github.com/Hestia-Homes/Model into feature/junte+khalim 2026-06-12 12:52:40 +00:00
Jun-te Kim
32de7f6c3f 17.1 and 18 done by claude 2026-06-12 12:52:36 +00:00
Khalim Conn-Kowlessar
b211715750 feat(modelling): wire secondary-heating-removal into the pipeline (ADR-0028)
Orchestrator runs recommend_secondary_heating_removal; report._triggers_for
explains it via the lodged secondary_heating_type; harness catalogue + ARA seed
price it. Re-pins the golden/integration plans it shifts: it is a cheap (\£250)
SAP lever, so on gas-main certs lodging an electric secondary (691) it displaces
the \£12k ASHP (0330, 0036) or joins the all-beneficial-measures package (000490,
where its marginal SAP is 0 under the category-4 ASHP but the heater is still
physically removed). Consistent with the optimiser's existing kitchen-sink
package behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:04:07 +00:00
Daniel Roth
0edeeaefa6 populate_sheet writes to new Sero template column layout 🟩 2026-06-10 13:21:50 +00:00
Daniel Roth
236f33c25f move spreadsheet population logic to domain 2026-06-09 14:43:24 +00:00
Daniel Roth
608006d977 reapply conditional formatting to populated file 2026-06-09 13:29:11 +00:00
Daniel Roth
f8c955b2d3 local runner and correct template path 2026-06-09 13:03:05 +00:00
Daniel Roth
82156fac8f Audit generator populates XLSX, uploads to S3, and records UploadedFile row 🟪
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 12:05:55 +00:00
Daniel Roth
612d522b35 Audit generator populates XLSX, uploads to S3, and records UploadedFile row 🟩
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 12:01:08 +00:00
Daniel Roth
a1d09aa880 Audit generator populates XLSX, uploads to S3, and records UploadedFile row 🟥
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 11:59:09 +00:00
Daniel Roth
5178cd02c5 UploadedFile, FileTypeEnum, FileSourceEnum importable from infrastructure.postgres.uploaded_file_table 🟩
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 11:50:51 +00:00
Jun-te Kim
06cb4f7b6e Merge branch 'feature/bill-derivation' into feature/junte+khalim 2026-06-09 10:06:40 +00:00
Khalim Conn-Kowlessar
7942a8101a feat(modelling): considered_measures allowlist on the orchestrator
Add domain/modelling/considered_measures.py::restrict_to_considered_measures —
the pure allowlist that limits a run to a chosen set of MeasureType (mirroring
the legacy engine's `inclusions`). It filters at the Option level, so a
multi-option Recommendation (e.g. Heating & Hot Water competing HHRSH against
an ASHP bundle) is kept with only its allowed Options; a Recommendation left
with none is dropped. None = consider everything (unrestricted default).

Thread `considered_measures: frozenset[MeasureType] | None` through
ModellingOrchestrator.run -> _plan_for -> _scored_candidate_groups /
_candidate_recommendations (applies the filter) and _measure_dependencies
(suppresses a forced dependency whose required measure is outside the
allowlist, so a restricted run forces nothing it is not considering). The
local-run seam (harness.console.run_modelling) gains the same param.

The Optimiser still freely chooses among survivors — including none. Tests:
the pure filter (3 cases) + an orchestrator-seam test proving a
{solar_pv}-restricted run yields only solar_pv options. 257 pass + 3 xfail;
pyright clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 20:32:11 +00:00
Jun-te Kim
b48700e964 Merge branch 'main' into feature/junte+khalim 2026-06-08 16:56:15 +00:00
Daniel Roth
3136d5a0f8 use correct db session to write to db 2026-06-08 14:07:03 +00:00