Commit graph

6407 commits

Author SHA1 Message Date
Jun-te Kim
bfedcd54dc Synthesise glazing from the glazed_area band for windowless 18.0 certs 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 11:49:38 +00:00
Jun-te Kim
162259b255 Use lodged window geometry for rich RdSAP-Schema-18.0 certs 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 11:45:12 +00:00
Jun-te Kim
17cb06a2f9 Use lodged window geometry for rich RdSAP-Schema-18.0 certs 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 11:41:19 +00:00
Jun-te Kim
51309328e6 Parse and map all RdSAP-Schema-18.0 corpus certs 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 11:39:07 +00:00
Jun-te Kim
024f3926af Parse and map all RdSAP-Schema-18.0 corpus certs 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 11:28:27 +00:00
Jun-te Kim
4fca5d7fed Dispatch and map RdSAP-Schema-18.0 certs end-to-end 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 11:18:15 +00:00
Jun-te Kim
cfc337f04a Dispatch and map RdSAP-Schema-18.0 certs end-to-end 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 11:12:53 +00:00
Jun-te Kim
362cd20f11 scripts? 2026-06-11 07:07:27 +00:00
Jun-te Kim
8ad560dc48 Merge branch 'feature/bill-derivation' of https://github.com/Hestia-Homes/Model into feature/junte+khalim 2026-06-10 16:44:43 +00:00
Jun-te Kim
deea05905b Record RdSAP 20.0.0 Reduced-Field Synthesis as implemented in resume doc
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 15:36:32 +00:00
Jun-te Kim
c0423295da Guard all 1000 RdSAP 20.0.0 certs as a strict parse-and-map bucket 🟩
Drops the xfail now that Reduced-Field Synthesis (ADR-0027) maps every
20.0.0 cert; the corpus test holds the whole bucket to a strict 1000/1000.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 15:35:19 +00:00
Jun-te Kim
c0b8a7d9f8 Use lodged window area for the 7 rich 20.0.0 certs' geometry 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 15:30:17 +00:00
Jun-te Kim
12ff15e55b Parse 20.0.0 conservatory building parts so all 1000 certs map 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 15:21:00 +00:00
Jun-te Kim
eb5bb89612 Map 20.0.0 lighting, ventilation and hot-water demand fields 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:46:54 +00:00
Jun-te Kim
3352f11be3 Synthesise 20.0.0 window geometry from glazed-area band and floor area 🟩
ADR-0027 Reduced-Field Synthesis: certs with no per-window array now get total
glazing = 0.148 x TFA x band-multiplier (median + quartile multipliers fit from
the 1000 real 21.0.1 certs), split 4-way across N/E/S/W with width=area/4,
height=1.0; glazing_type routed through the verified 21.0.1 cascade. Also guard
optional PhotovoltaicSupply.none_or_no_details (a parse straggler). Corpus maps
983/1000, up from 974.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:29:13 +00:00
Jun-te Kim
8074f4152c Map RdSAP 20.0.0 certs that omit reduced fields or lodge localised text 🟩
Required->optional defaults (kw_only + data-driven from corpus presence) so
993/1000 certs that omit sap_windows parse, and honest Union[str, DescriptionV1]
typing for description/dwelling_type which the corpus lodges as localised dicts
in ~half the certs. The never-run 20.0.0 mapper path now produces EpcPropertyData;
974/1000 corpus certs map (xpass), up from 7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:14:18 +00:00
Jun-te Kim
5589a66e7c Document RdSAP 20.0.0 Reduced-Field Synthesis (CONTEXT.md term + ADR-0027)
Sharpen the glossary to decouple deterministic old-schema re-mapping from
neighbour-prediction gap-fill (a separate, unimplemented ML path), and add
the Reduced-Field Synthesis term. ADR-0027 records the pre-SAP10 20.0.0
mapper's best-attempt synthesis (corpus-fit glazing 0.148xTFAxband, 4-way
orientation) and its trade-offs. Grill resume doc captures every resolved branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:14:18 +00:00
Khalim Conn-Kowlessar
07f534ee11 feat(modelling): system tune-up options (standard + zone controls)
Add the system tune-up to the heating Recommendation: keep the existing wet
boiler but install better heating controls and fix the cylinder. Two competing
Options (the Optimiser picks <=1 across the whole heating rec) per the user's
two best control end-states:

- system_tune_up        — standard controls (programmer + room thermostat +
  TRVs, SAP 10.2 Table 4e code 2106)
- system_tune_up_zoned  — time-and-temperature zone control (code 2110, type 3):
  more SAP uplift for more cost

Both keep the boiler (no fuel / SAP code / flue change), set the control
ABSOLUTELY to their end-state, and apply the conditional cylinder fixes (an
80 mm jacket when under-insulated, a thermostat when absent — only when a
cylinder exists). Each control option is offered only when it genuinely improves
the existing control — standard is skipped when the control is already 2106 /
2110 / 2112, zone when already 2110 / 2112 — so neither is ever a downgrade or a
no-op.

Validated against the Elmhurst "system tune up" re-lodgements (cert 001431):
nine befores spanning controls 2101-2113 all converge to the two common afters,
proving the control overlay is absolute. The cascade pin is parametrised over
two starting controls (2101 "no control" + 2113 "room thermostat and TRVs") x
both afters, delta 0 (SAP/CO2/PE).

Wires the two MeasureTypes through contingencies (0.15), the offline catalogue
(500 / 900), the catalogue-coverage list, the report triggers, and the ARA
first-run seed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 10:20:46 +00:00
Khalim Conn-Kowlessar
3d108a9d9e feat(harness): explain gas_boiler_upgrade in the report triggers
Add the `gas_boiler_upgrade` branch to `report._triggers_for`, mirroring the
generator's eligibility guard so a cohort report explains why the boiler upgrade
fired: the wet-boiler SAP code, the mains-gas connection that makes the gas
end-state installable, and the cylinder presence that shapes the bundle (combi
vs regular + cylinder fixes).

No golden API cert selects the boiler upgrade (it competes with — and on houses
loses to — the ASHP bundle within the one heating Recommendation), so the branch
is covered by a direct `_triggers_for` unit test, following the repo pattern for
testing internal helpers (cert_to_inputs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 08:44:37 +00:00
Khalim Conn-Kowlessar
2413bc87da feat(modelling): solid-fuel(coal)->gas boiler upgrade + boiler_flue_type end-state
Pin the coal-boiler-with-cylinder upgrade and add the `boiler_flue_type`
end-state field. A solid-fuel (coal) boiler (fuel 11, SAP code 153) on a
mains-gas street converts to a gas condensing boiler (fuel 11->26, code 102) —
the non-gas->gas path for a solid-fuel system, eligible because code 153 is in
the wet-boiler solid-fuel range 151-161 and mains gas is present.

New `boiler_flue_type` HeatingOverlay field, routed to main_heating_details[0]
and set to 2 (room-sealed/balanced) on both boiler shapes: every relodged after
lodges flue type 2, but coal's before lodged none. The field is SAP-inert (the
cascade score is unchanged by it), so it is written purely for end-state
fidelity — the overlay now represents the installed condensing boiler's flue.
Validated via the overlay-equality unit tests.

The coal after predates the user-locked "always add a cylinder thermostat when
absent" rule, so it stale-lodged thermostat 'N'; the pin corrects it to the
rule's end-state 'Y' in-test (the gas with-cylinder after got the same
correction by re-lodging). The cylinder is already 80 mm insulated, so the
jacket is skipped and only the thermostat is added; controls (2106) are
unchanged. Cascade-pinned delta 0 (SAP/CO2/PE).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 08:27:07 +00:00
Jun-te Kim
14dc4efeed save so i can continue 2026-06-09 17:07:55 +00:00
Khalim Conn-Kowlessar
7bc9797a26 test(modelling): pin non-gas->gas + already-insulated-cylinder boiler upgrades
Two more boiler-upgrade cascade pins, validating the existing generator across
fuels and cylinder states (no source change):

- oil combi: an oil boiler (fuel 28, code 130) on a mains-gas street converts to
  a gas condensing combi (fuel 28->26, code 104). Proves the non-gas -> gas
  conversion gated on a mains-gas connection (ADR-0024 revised).
- already-insulated cylinder: a gas boiler heating a pre-jacketed cylinder
  (type 2 / 80 mm, no thermostat) gets a new boiler + a thermostat, with the
  jacket NOT re-applied. Proves the cylinder path's skip-jacket branch against a
  real cert. (Sourced from an LPG re-lodgement whose fuel the Summary mapper
  reads as mains gas 26 — a separate LPG fuel-mapping gap, noted in the test.)

Both pin delta 0 (SAP/CO2/PE) against the relodged after.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 16:37:32 +00:00
Khalim Conn-Kowlessar
63dd69ff8b feat(modelling): gas combi boiler upgrade + controls-when-inadequate
Extend the gas-boiler-upgrade Option to combi (no-cylinder) dwellings and add
the controls upgrade shared by both boiler shapes. A dwelling has a cylinder or
it does not, so the one `gas_boiler_upgrade` Option is shaped per dwelling:

- no cylinder -> a gas condensing combi (Table 4b code 104), no cylinder fields
  touched;
- a cylinder  -> a regular boiler (code 102) heating it, with the conditional
  cylinder jacket/thermostat (slice 1).

Controls: bring an inadequate boiler control up to full programmer + room
thermostat + TRVs (SAP 10.2 Table 4e Group 1 code 2106). "Inadequate" = the
Group-1 codes with NO room thermostat (2101, 2102, 2107, 2108, 2109, 2111) —
these lack boiler interlock (Table 4c(2) / footnote c) p.171), so adding a room
thermostat genuinely improves SAP. Room-thermostatted (2103/2104/2105/2106/2113)
or better zone controls (2110/2112) are left unchanged — never downgraded, so
no phantom uplift. The with-cylinder cert (control 2106) is therefore untouched
and its pin still holds at delta 0.

Validated by the combi before/after re-lodgement (cert 001431, gas boiler
upgrade - no cylinder): control 2111 "TRVs and bypass" -> 2106, fan flue
False->True, SAP code 112 -> 104. Cascade-pinned delta 0 (SAP/CO2/PE). Removed
the slice-1 placeholder test asserting no boiler Option fires without a cylinder
(the combi Option now correctly fires there).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 16:28:00 +00:00
Khalim Conn-Kowlessar
31c74ab500 feat(modelling): gas-boiler-upgrade-with-cylinder option in the heating rec
Add the first boiler-upgrade option to the single "Heating & Hot Water"
Recommendation (ADR-0024 expansion): a dwelling whose existing wet boiler heats
a hot-water cylinder is offered a new gas condensing boiler, with the cylinder
jacketed when under-insulated and given a thermostat when absent. One competing
Option (the Optimiser picks <=1), folded into one composite Plan line.

The end-state is read from the Elmhurst before/after re-lodgements (cert 001431,
gas boiler upgrade - with cylinder), which REVISE ADR-0024:

- Target is always a gas condensing boiler, not fuel-preserving: every after
  lodges fuel 26. Gas->gas always; a non-gas wet boiler ->gas only with a
  mains-gas connection; electric boilers are left alone (electrification is the
  upgrade path). Eligibility = wet-boiler SAP code (Table 4a/4b 101-141 /
  151-161 / 191-196) + not an electric boiler + mains gas present.
- End-state is a Table 4b SAP code, not a PCDB index: code 102 (regular boiler
  + cylinder). The calculator derives the condensing seasonal efficiency from
  the code, so no efficiency input exists or is needed.
- A modern condensing boiler has a fanned flue: the after flips
  `fan_flue_present` False->True on every cert (SAP 10.2 Table 4f flue-fan +
  the Table 4b condensing-efficiency basis). Added as a new HeatingOverlay
  field, routed to main_heating_details[0].
- Cylinder thermostat is always added when absent (user-locked); the jacket is
  the 80 mm `cylinder_insulation_type=2` end-state, applied only when the
  cylinder is below 80 mm (never downgrading a better one). Both are conditional
  per-dwelling components, not a frozen overlay.

Cascade-pinned delta-0 (SAP/CO2/PE) against the relodged after via
`_assert_overlay_reproduces_after`. NB the absolute SAP on this dwelling is
subject to a separate Summary-path mapper roof-fidelity gap (we read the roof
better-insulated than Elmhurst, scoring ~75 vs the printed 56); the gap is
identical on before+after (the boiler measure never touches the roof) so it
cancels and the pin still proves the exact heating field-delta. Tracked on the
calculator branch.

Wires the new `gas_boiler_upgrade` MeasureType through contingencies (0.26),
the offline sample catalogue, the catalogue-coverage list, and the ARA
first-run integration seed (the option fires on any mains-gas boiler+cylinder
dwelling).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 16:16:46 +00:00
Jun-te Kim
3b7d26fe34 added test for a 1000 examples 2026-06-09 16:02:21 +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
4006753620 fix(scripts): authenticate the EPC client with OPEN_EPC_API_TOKEN
The new gov EPC API (api.get-energy-performance-data..., Bearer auth) returns
403 "Bad authentication header" with EPC_AUTH_TOKEN but 200 with
OPEN_EPC_API_TOKEN — the token name is misleading (it is the Bearer token for
the new API, not the open-data API). Verified live against
/api/domestic/search. Unblocks the live EPC fetch in run_modelling_e2e.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 20:58:22 +00:00
Khalim Conn-Kowlessar
0f6077a830 feat(scripts): DB-catalogue local run + optional --persist for run_modelling_e2e
Slice 5 (local run sources the DB, read-only) + slice 6 (optional persist),
landing together as one script rewrite (the persist path is interleaved with
the compute path).

The same local computation now runs whether or not the result is stored:
- Both modes price against the live `material` catalogue (read-only
  ProductPostgresRepository over one shared Session) and model against a real
  Scenario read from the DB (--scenario-id; its goal_value drives the band,
  rejected if null) — so the inspected recommendations are exactly what gets
  stored. The JSON sample catalogue is no longer used by this script.
- --measures restricts the run to a comma-separated considered_measures
  allowlist (e.g. high_heat_retention_storage_heaters,solar_pv).
- --persist writes the inputs (EPC + spatial + solar) and the *same* computed
  Plan via the production repos in one PostgresUnitOfWork, then commits
  (idempotent: PlanPostgresRepository replaces by (property_id, scenario_id)).
  Gated: --persist requires --scenario-id and --portfolio-id. Default is
  inspect-only — no DB writes.

harness.console.run_modelling gains `products` and `scenario` overrides (the
seam the script drives); defaults unchanged, so existing callers are
unaffected. Suite 257 pass + 3 xfail; pyright clean; --help/guard/measure
parsing verified. Not yet executed against the DB (awaiting property_ids +
write-confirm).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 20:45:50 +00:00
Khalim Conn-Kowlessar
62e1d4b813 fix(product): deterministic catalogue pick by ordering get() by id
ProductPostgresRepository.get took .first() with no ORDER BY, so when a
measure type has several active material rows (the live catalogue holds 74
solar_pv, 5 high_heat_retention_storage_heaters) the chosen row — hence the
cost and material_id — depended on the database's physical row order. Order by
id so a re-seed prices the same product every time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 20:35:45 +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
Khalim Conn-Kowlessar
9ef97be958 refactor(modelling): type measure_type fields as MeasureType
Tighten the recommendation/plan vocabulary off generic str:
MeasureOption.measure_type and PlanMeasure.measure_type are now MeasureType
(also _GlazingTarget.measure_type, MeasureDependency.triggers ->
frozenset[MeasureType], and the optimiser's chosen/required-type locals).
Because MeasureType is a StrEnum the change is transparent to persistence
(the `recommendation` varchar column), the optimiser group-by key, and every
`== "solar_pv"`-style comparison — so pyright now enforces the enum at every
construction site with no runtime behaviour change.

The catalogue boundary stays str: ProductRepository.get(measure_type: str)
and Product.measure_type are unchanged (they map arbitrary DB/JSON rows), so
the fake product repos in tests need no edit. Test construction helpers coerce
their str arg via MeasureType(...); direct constructions use members.

Suite green: tests/domain/modelling + orchestration + harness 253 pass + 3
xfail; pyright clean on production + tests (pre-existing moto + property-
override-rowcount baselines untouched).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 20:13:31 +00:00
Khalim Conn-Kowlessar
d58ac60d29 feat(modelling): MeasureType StrEnum as the canonical measure vocabulary
Introduce domain/modelling/measure_type.py — a StrEnum with one member per
modelled measure (the 15 the generators emit). A StrEnum so each member *is*
its string value: it persists straight into the `recommendation` varchar
column, is the optimiser's group-by key, and compares equal to the catalogue /
EPC strings — so it replaces the per-generator string constants with no
persistence or optimiser change.

Repoint every generator's measure-type constant/literal to a MeasureType
member (wall, solid_wall, roof, floor, glazing, lighting, ventilation,
heating, solar). Field annotations stay `str` for now; tightening them to
MeasureType is the next slice.

This is the enum the historical engine deferred (engine.py:970
"TODO - formalise property measure types into an enum") and the vocabulary the
forthcoming `considered_measures` allowlist will speak (mirroring the legacy
`inclusions`).

Suite green: tests/domain/modelling + orchestration + harness 253 pass + 3
xfail; pyright clean on the enum + generators.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 19:54:04 +00:00
Jun-te Kim
b48700e964 Merge branch 'main' into feature/junte+khalim 2026-06-08 16:56:15 +00:00
Jun-te Kim
0ccb0e0bf9
Merge pull request #1196 from Hestia-Homes/feautre/additional_properties_for_tracking
Feautre/additional properties for tracking
2026-06-08 16:19:34 +01:00
Khalim Conn-Kowlessar
1b4806f8e4 feat(scripts): wire S3 geospatial + Google Solar into run_modelling_e2e
Per Property the inspection script now resolves the UPRN's spatial
reference from the Ordnance Survey Open-UPRN parquet in S3
(GeospatialS3Repository over a boto3 ParquetReader) and threads both
levers into run_modelling:

- planning_restrictions: the conservation/listed/heritage flags that gate
  the wall + solar measures (ADR-0019/0020).
- solar_insights: a live Google Solar buildingInsights fetch keyed on the
  reference coordinates, so the Solar PV Options can fire (ADR-0026).

Mirrors IngestionOrchestrator._fetch's coords->solar flow. Degrades
gracefully per Property: a UPRN S3 doesn't cover -> unrestricted/no-solar;
a point Google has no coverage for (BuildingInsightsNotFoundError) ->
no-solar; both still modelled. --no-solar skips the Google leg. A context
note (restrictions; solar) is printed and written to the md/csv summary.

Verified live: spatial_for + solar fetch round-trip on real UPRNs (S3 via
ambient ~/.aws creds, pyarrow reads parquet bytes). pyright clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 14:55:33 +00:00
Daniel Roth
26ba09a1c7
Merge pull request #1194 from Hestia-Homes/correct-path-to-magicplan-dockerfile
Correct magicplan dockerfile path in terraform
2026-06-08 15:54:47 +01:00
Daniel Roth
e1515b0c00 correct path in terraform 2026-06-08 14:49:41 +00:00
Daniel Roth
c40a7c0af9
Merge pull request #1192 from Hestia-Homes/bug/fix-magicplan-orchestrator-tests
Fix magicplan tests to reflect changes to the orchestrator
2026-06-08 15:44:01 +01:00
Jun-te Kim
a1b4bf4e98 added 4 deal proeprties 2026-06-08 14:42:06 +00:00
Daniel Roth
648e726ca2 fix orchestration tests 2026-06-08 14:40:14 +00:00
Khalim Conn-Kowlessar
0918dd37ec feat(scripts): run_modelling_e2e — inspect recommendations per property_id
Revives the local recommendation-inspection flow for specific Properties.
`scripts/run_modelling_e2e.py` reads each Property's UPRN from the DB
(read-only), fetches the latest EPC live from the gov EPC API by UPRN, runs the
Modelling stage in memory (all Generators → Optimiser → costed, attributed
Plan), and prints a per-Property plan table + writes a Markdown/CSV summary.
Persists nothing — purely for inspection.

The local DB's Properties have no linked ingested EPC (epc_property.property_id
is NULL for all rows; Ingestion's source clients are stubbed, #1136), so the
EPC must be fetched inline rather than read back. Builds the connection from the
`DB_*` env vars in backend/.env and the EPC token from `EPC_AUTH_TOKEN`.

Threads optional solar insights through harness `run_modelling` (so Solar PV
Options can fire once coordinates are wired) and adds the `solar_pv` catalogue
row. Solar + planning restrictions + DB persistence are noted follow-ups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 14:25:33 +00:00
Daniel Roth
1b673622e2
Merge pull request #1191 from Hestia-Homes/bump-rds-version
Bump RDS to 14.22
2026-06-08 15:19:22 +01:00
Daniel Roth
84d4fa096d remove tests/ from pytest.ini 2026-06-08 14:15:50 +00:00
Daniel Roth
d23946a0a3 bump to 14.22 2026-06-08 14:14:15 +00:00
Daniel Roth
19c0a583cb
Merge pull request #1183 from Hestia-Homes/feature/handle-new-magicplan-response-structure
Get ventilation fields from new magicplan API response structure
2026-06-08 15:07:35 +01:00
Daniel Roth
3136d5a0f8 use correct db session to write to db 2026-06-08 14:07:03 +00:00
Jun-te Kim
18ad879f00 devcontainer: use dynamic host ports so worktrees don't collide
Publish container ports without a fixed host side in the backend and
asset_list compose files, and switch forwardPorts to service-qualified
form. Lets a second worktree's devcontainer spin up without conflicting
on host 8000/5432 (backend) or 8081 (asset_list), matching the pattern
in assessment-model-sales-frontend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 13:53:22 +00:00
Daniel Roth
aaeb339254 Logging plus invoke locally bypassing subtask handler 2026-06-08 13:32:59 +00:00
Khalim Conn-Kowlessar
24492aa4ba Merge origin/main into feature/bill-derivation (calculator + mapper fixes)
Pulls in 42 commits of calculator/mapper accuracy fixes from the per-cert
mapper-validation and floor/roof/heating fronts.

Conflict resolutions:
- mapper `_is_elmhurst_roof_window`: main dropped the branch's "wall location →
  vertical" guard (it broke cert 000516's rooflight), but that re-broke cert
  001431's two External-wall U>3.0 windows (which must stay vertical). The two
  certs lodge a BYTE-IDENTICAL §11 row, so neither location nor U separates
  them — the real discriminator is the room-in-roof context. Replaced the
  unconditional U>3.0 backstop with one gated on the BP having a room-in-roof
  (`_elmhurst_bp_has_room_in_roof`): 000516's Main BP has a "Room in roof type
  1" (→ rooflight), 001431's does not (→ vertical). Validated against BOTH —
  full Elmhurst worksheet suite 1038 pass + the 001431 window-extraction pin.
- property_postgres_repository: kept main's `ids_by_uprn` method + the branch's
  `_restrictions_of` helper.
- sap_fuel.py: the branch relocated it to domain/billing/ (already carrying
  main's to_table_32_code normalization), so kept the old path deleted.

Fallout from main's fabric fixes (validated by the boiler-3 real-cert pin which
still reproduces at delta 0):
- re-pinned the boiler-1 + boiler-instant-hw ASHP snapshot scores;
- main's §14.2 gas-boiler main-fuel derivation resolved the BGB/102 baseline
  gap, so `test_gas_boiler_instant_hw_before_baselines` is now a passing test
  (was an xfail tripwire).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 13:12:21 +00:00
Khalim Conn-Kowlessar
2f3b1dbd3f test(modelling): Solar PV Elmhurst cascade pins + battery tripwire
Slice 9 of the Solar PV Recommendation Generator (ADR-0026). Pins the
overlay→calculator PV cascade against Elmhurst's before/after re-lodgements of
cert 001431, across the orientation/pitch/overshading config space (the certs
lodge synthetic 1.00 kWp test vectors):
 - SE/SW, shaded (overshading 3/2), pitch 30°/45°
 - E/W, unshaded, pitch 60°/45°
 - NW/N, unshaded, pitch 60°/45° (the low-yield orientations)
Each hand-built SolarOverlay reproduces the relodged after at abs ≤ 1e-4 on
SAP / CO2 / primary energy.

Battery tripwire (per user): the "with battery" cert lodges a §19 5 kWh battery
the current extractor does NOT parse, so it scores identically to its
no-battery twin — the no-battery overlay reproduces it today, and the pin will
fail (alerting us to switch to the with-battery overlay) once the extractor
parses the battery. A companion test pins that the calculator already models
the 5 kWh battery (it raises SAP), so the fix target is established.

All five certs share an EES 'WGK' / SAP-code-502 main-heating lodgement the
mapper doesn't yet derive a fuel for; the pins patch the shared fuel (mains gas
26) identically on before+after to isolate the PV delta (the solar overlay
never touches heating), and `test_solar_before_baselines` xfails as the
forcing-function tripwire for that separate mapper-front gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:42:24 +00:00