Commit graph

5 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
7c59e9198a feat(modelling): Simulation Overlay grows a dwelling ventilation segment (#1161)
VentilationOverlay (all-optional partial of SapVentilation) + EpcSimulation.
ventilation; apply_simulations folds it onto sap_ventilation, creating one when
the baseline lodged none. This is the surface a Measure Dependency (ventilation)
writes — whole-dwelling, no building part.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 13:20:45 +00:00
Khalim Conn-Kowlessar
a0b6a952c3 feat(modelling): floor insulation-type overlay field + cascade pins (#1159)
Completes #1159 end-to-end with solid and suspended-floor before/after
cascade pins on cert 001431, both closing at delta 0.000000.

Adds floor_insulation_type_str to BuildingPartOverlay (the generic
field-fold applicator picks it up with no change) and has
recommend_floor_insulation set it to "Retro-fitted". Insulating an
as-built floor re-lodges its insulation as retro-fitted; the calculator
keys on this for a suspended timber floor's sealed/unsealed
determination (cert_to_inputs.py: "retro" + no U-value supplied →
sealed). Without it the suspended-floor cascade left a +1.40 SAP gap
(the floor stayed "unsealed", wrong U-value); with it the cascade
closes exactly. Solid floors are unaffected by the seal logic and stay
at delta 0; both Elmhurst after-certs lodge "Retro-fitted", so setting
it uniformly is faithful.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 09:41:54 +00:00
Khalim Conn-Kowlessar
4c10405071 feat(modelling): floor Recommendation Generator + ground-floor-area geometry
recommend_floor_insulation(epc, products) detects an uninsulated ground floor
(SapBuildingPart.floor_insulation_thickness blank/zero) and its construction
from floor_construction_type — 'Suspended timber' -> suspended_floor_insulation,
'Solid' -> solid_floor_insulation — emitting the matching single Option (a
floor is one construction, like a cavity wall) with the overlay
(floor_insulation_thickness = 100 mm) and a priced Cost (ground-floor area x
the Product's fully-loaded unit cost + contingency).

- building_geometry.ground_floor_area(epc, identifier): the lowest floor's
  (floor == 0) area. Pinned 14.85 m^2 on 000490 MAIN.
- BuildingPartOverlay gains floor_insulation_thickness (generic Applicator
  writes it unchanged). suspended (0.20) / solid (0.26) floor contingencies.

Progress on #1159 (generator + geometry); end-to-end + Elmhurst pin pending
the orchestrator (#1157) and parser. Four behaviour tests (suspended / solid
/ none / cost) + geometry pin. pyright strict clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 09:12:29 +00:00
Khalim Conn-Kowlessar
3c87be8e1e feat(modelling): roof (loft) Recommendation Generator + roof-area geometry
recommend_loft_insulation(epc, products) detects an uninsulated main loft
(SapBuildingPart.roof_insulation_thickness == 0) and emits a
Recommendation("Roof") with one loft_insulation Option carrying the overlay
(roof_insulation_thickness = 270 mm, the recommended top-up) and a priced
Cost (roof area x the Product's fully-loaded unit cost + contingency).

- building_geometry.roof_area(epc, identifier): the part's greatest
  per-storey floor area (RdSAP 10 §3.8). Pinned 14.85 m^2 on 000490 MAIN.
- BuildingPartOverlay gains roof_insulation_thickness; the generic Overlay
  Applicator writes it with NO change (validated by the tracer) — the
  deep-module field-fold paying off.
- loft_insulation contingency (0.10) added.

Progress on #1158 (generator + geometry); end-to-end + Elmhurst pin pending
the orchestrator (#1157) and the parser fix. Four behaviour tests
(geometry pin; detect / none / cost). pyright strict clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 09:05:38 +00:00
Khalim Conn-Kowlessar
350f4c8e76 feat(modelling): Overlay Applicator folds EpcSimulation onto EpcPropertyData
EpcSimulation is the Simulation Overlay — a narrow all-optional partial
mirror of EpcPropertyData/SapBuildingPart (wall surface first), targeting
building parts by BuildingPartIdentifier (composition, not inheritance).
apply_simulations(baseline, simulations) deep-copies the baseline, folds
overlays in order (later wins on a shared field) via a generic non-None
field write, and returns a throwaway EpcPropertyData for the calculator;
the baseline is never mutated.

Four behaviour tests (hand-built EPD from the 000490 fixture, no PDF):
targeted-write-leaves-others-untouched, empty-overlay no-op, sequential
last-wins, baseline-immutability. pyright strict clean.

Slice 1 of the Modelling stage rebuild (ADR-0016). Closes #1153.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 22:13:51 +00:00