Commit graph

6686 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
cd43c52cf9 feat(epc-prediction): score the heating components (ADR-0030 Component Accuracy)
Heating is the dominant SAP lever (ablating it to actual cut the SAP error
~7 -> ~4.5) yet was entirely unscored. Add the heating group to
compare_prediction's categorical_hits: main fuel / category / control (off
the primary MainHeatingDetail), water-heating fuel / code, has-cylinder,
cylinder insulation, secondary heating (off SapHeating).

Template-copied baseline on the 40-postcode corpus (no predictor change
yet — this just makes the signal visible):
  heating_main_fuel        93.4%
  heating_main_category    92.7%
  water_heating_fuel/code  91.7% / 92.4%
  heating_main_control     62.1%   <- weak
  has_hot_water_cylinder   78.5%
  cylinder_insulation_type 35.8% (n=120)   <- weak
  secondary_heating_type   16.8% (n=125)   <- weak

Fuel/category predict well from the template; controls, cylinder, and
secondary heating are poor and now drive the next predictor slices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 08:53:15 +00:00
Khalim Conn-Kowlessar
41b5ce5057 refactor(epc-prediction): name-keyed categorical_hits for Component Accuracy (ADR-0030)
ADR-0030 commits Component Accuracy to ~19 categorical components (5 today
+ 8 heating + glazing/renewables). Flat *_correct dataclass fields don't
scale — each needs manual runner wiring. Collapse them into a single
`categorical_hits: dict[str, Optional[bool]]` keyed by component name, which
also matches the runner's name-keyed aggregation (now generic: it tallies
whatever components the comparison reports). No behaviour change; the
classification rates are identical (wall n 578->575 is the 3 certs whose
actual wall is None, now correctly counted as not-applicable via _classify).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 08:50:34 +00:00
Khalim Conn-Kowlessar
9ee3821138 fix(pv): zero exported PV when dwelling is not export-capable
SAP 10.2 Appendix M1 (PDF p.94): "EPV,ex,m = 0 if the PV system is not
connected to an export-capable meter." The cascade computed the β-split
export stream regardless of `is_dwelling_export_capable`, so a non-export-
capable dwelling was credited the full PV export — in the §10a COST it
credits at the Table 32 import rate (13.19 p/kWh), which dominates the rating.
On 7 Wybourn Terrace S2 5BJ the PE (144 vs lodged 151) and CO2 (27 vs 29)
already matched, yet the phantom export cost credit pulled SAP from ~73 to
92.1 (+19). Zero `epv_exported_monthly_kwh` after the Appendix-G4 diverter
adjustment when not export-capable; the onsite (EPV,dw) consumption and the
diverter HW reduction are unchanged.

Not-export-capable PV cohort (corpus, 4 certs): 7 Wybourn +19.1 -> +6.5,
4 Lime Ave +11.1 -> +0.4, 8 Hatherleigh +7.6 -> -0.2, Flat 5 ~-0.4. Gauge
66.1% -> 66.9%, MAE 1.124 -> 1.039. Floor 0.64 -> 0.65 / ceiling 1.18 -> 1.08.
Worksheet harness 47/47 0 diverge (Summary certs carry export-capable meters).
1 AAA test, pyright net-zero. Found by auditing the worst over-rater without a
worksheet: PE/CO2-match + cost-miss localised it to the PV export credit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 08:48:38 +00:00
Khalim Conn-Kowlessar
35a7c07812 docs(adr): ADR-0030 — SAP-version-aware, component-first EPC Prediction validation
Records the grilling-session decisions amending ADR-0029's validation:
- Source cohort keeps all cert vintages (components are agnostic of the SAP
  methodology that rated them); only the held-out validation TARGET is
  restricted to SAP 10.2. Amends ADR-0029 decision 5 ("pre-SAP10 dropped").
- Component Accuracy (predicted vs API actual components) is the primary,
  calculator-independent signal. calc(predicted) vs calc(actual) rejected
  (circular ground truth, hides calculator error); neighbour-mean-lodged-SAP
  baseline rejected (mixes SAP versions). calc(predicted) vs API-lodged
  SAP/carbon/PE kept as a secondary, calculator-floored guard.
- Two tiers: committed anonymized fixture (ratcheting CI gate) + bulk-export
  national battle-test on harness/epc_bulk.py + harness/cohort.py, emitting
  accuracy + a failure taxonomy, re-baselining the gate floors.

CONTEXT.md: Comparable Properties corrected to all-vintage source; new
Component Accuracy term. ADR-0029 Validation section marked superseded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 08:47:58 +00:00
Khalim Conn-Kowlessar
94275d07cc fix(hot-water): default present-but-unsized cylinder to Table 28 Normal 110 L
RdSAP 10 §10.5 (PDF p.55): "If the actual size is not determined, the size of
a hot-water cylinder is taken as according to Table 28." When a cylinder is
present (has_hot_water_cylinder) but no size descriptor resolves — the gov API
lodges cylinder_size=0, or Exact with no measured volume — `_hot_water_
cylinder_volume_l` returned None, silently dropping BOTH the cylinder's
storage loss and the Table 13 electric-DHW high-rate fraction, under-costing
and over-rating the dwelling. Default such cylinders to the Table 28 baseline
"Normal" 110 L (the value §10.7 also instantiates as the first-row default).

The context-dependent Inaccessible 210/160 values are deliberately NOT applied
here — they are tied to the explicit "Inaccessible" descriptor (code 5) the
assessor lodges, not to an unpopulated size field.

Scope: 7 of 301 cylinder certs in the corpus (2%). Correctness fix — closes a
real spec gap; marginal on the headline (within-0.5 66.1% unchanged, MAE
1.128 -> 1.124) because these certs' residual is dominated by a separate HW-
demand gap, not the cylinder. Worksheet harness 47/47 0 diverge (Summary certs
lodge a real size, so the fallback never fires). 1 AAA test, pyright net-zero.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 08:20:34 +00:00
Khalim Conn-Kowlessar
bec62b9167 fix(storage-heaters): Table 12a code-408 integrated-storage high-rate fraction
SAP 10.2 Table 12a Grid 1 (PDF p.191): electric storage heater SAP code 408
is an "Integrated (storage + direct-acting) system" with a 0.20 space-heating
high-rate fraction on a 7-hour tariff — NOT the 0.00 of "other storage
heaters". `_table_12a_system_for_main` returned None for all storage codes (an
explicit TODO), so code 408 fell back to the 100%-low-rate path and billed
space heating at the bare 7-hour low rate (5.50 p/kWh) — under-costing →
over-rating. Mapped cat-7 storage: 408 -> INTEGRATED_STORAGE_DIRECT (0.20),
others -> OTHER_STORAGE_HEATERS (0.00, unchanged behaviour). The enum +
fraction rows already existed; this only wires the dispatch, so the split
flows self-consistently to both the §10a cost and the Appendix-M1 D_PV
high-rate fraction.

Corpus: sap408 over-raters +14.6/+12.9/+12.7 -> +7.1/+5.1/+3.4 (two crossed
into within-0.5). Gauge 65.9% -> 66.1%, MAE 1.160 -> 1.128. Floor 0.63 -> 0.64
/ MAE ceiling 1.22 -> 1.18. Worksheet harness 47/47 0 diverge. The residual
+3..+7 is the "all other uses" 0.90 high-rate fraction (lighting/pumps/HW
still billed 100%-low on the off-peak legacy path) — the next slice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 02:12:39 +00:00
Khalim Conn-Kowlessar
dfcd7af57c fix(heat-network): apply Table 4c(3) flat-rate charging factor to demand
SAP 10.2 Table 4c(3) (PDF p.169) "Factor for controls and charging method"
multiplies a heat network's heat requirement by 1.05-1.10 for FLAT-RATE
charging (note d: household pays a fixed amount regardless of heat used, so
no incentive to economise), and by 1.0 for charging linked to use. The
worksheet folds it into the heat-network requirement alongside the Table 12c
distribution loss factor:
  (307) space = (98c) x (302) x (305) x (306)
  (310) DHW   = (64)  x (305a) x (306)
Our cascade applied (306) DLF but never (305)/(305a), so every flat-rate
community-heating cert under-counted demand -> over-rated SAP.

Folded the factor into the 1/DLF efficiency override at the space-heating
(206) and DHW (water-inherits-from-main) sites. Space column adds +0.05 for
no thermostatic control (2301/2302); DHW column is 1.05 flat-rate / 1.0
linked-to-use.

Corpus (RdSAP-21.0.1, 1000 certs): community cluster median +0.32 -> -0.19,
within-0.5 38% -> 62% (control 2307 +0.83 -> -0.19; 2306 unchanged at factor
1.0 as spec requires). Overall gauge 65.0% -> 65.9%, MAE 1.174 -> 1.160.
Ratcheted the corpus-test floor 0.62 -> 0.63 / MAE ceiling 1.25 -> 1.22.

Also records (corpus-test comment + scripts/decompose_co2_pe_error.py) the
disproof of the prior "CO2/PE +5% is a factor/scope bug" lead: factors are
spec-exact, scope identical, and the bias is per-cert demand fidelity
(corr(SAP-err, PE-diff) = -0.54), not a one-slice factor fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 01:54:51 +00:00
Khalim Conn-Kowlessar
c3d56b00dd chore(epc-prediction): grow validation corpus to 40 postcodes (ADR-0029)
Bump N_POSTCODES 150 -> 40 as the gradual-growth step from the 3-postcode
smoke. 40 postcodes / 1113 certs / 578 leave-one-out predictions is enough
for stable, trustworthy metrics (the smoke's 2 usable postcodes were
dominated by oddball flats — floor_area mean|.| 52.6 there vs 12.7 here).
Resumable + reproducible (random.seed(2026)); raise again to scale up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 01:52:44 +00:00
Khalim Conn-Kowlessar
fa11df56c2 fix(epc-prediction): dedupe re-lodgements + leak-free leave-one-out (ADR-0029)
The register lists every historical lodgement, so a postcode cohort
contains the same physical address many times (LS61AA: 15 certs / 11
addresses; NG71AA: 15 / 9 — "FLAT 3" appears 3x in each). Two
consequences:

  - Production: a re-lodged neighbour was counting up to 3x towards the
    cohort mode. select_comparables now dedupes candidates to the latest
    cert per address (one comparable per real neighbour) — Comparable
    gains address + registration_date (the register metadata its docstring
    already anticipated, read straight off the cached payload).

  - Validation: leave-one-out leaked — predicting a flat from a near-
    identical re-lodgement of itself. The harness now holds out a whole
    address (excludes every sibling cert) and evaluates on the latest cert
    per address (the best ground truth).

Removing the leak gives the honest numbers (19 distinct addresses):
  wall_construction      93.1% -> 89.5%
  construction_age_band  65.5% -> 52.6%
  roof_construction      79.3% -> 68.4%
  floor_area mean|.|     37.9  -> 52.6 m2
The earlier figures were inflated by self-leakage; these are the real
accuracy to beat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 00:40:23 +00:00
Khalim Conn-Kowlessar
54a57363f8 feat(epc-prediction): cohort-mode the roof/floor/insulation/age categoricals (ADR-0029)
Only main wall_construction was set to the cohort mode; the other
homogeneous categoricals (wall insulation, construction age band, roof
construction, floor construction) were left as template-copied, so one
median-size template's quirks set them. Apply the same cohort-mode
mechanism to all of them per ADR-0029 decision 4 — the template still
supplies geometry, only the categorical codes move to the mode.

Verified mode beats (or ties) template-copy per categorical before
applying. Smoke corpus (29 leave-one-out) classification rates:
  construction_age_band  55.2% -> 65.5%
  roof_construction      72.4% -> 79.3%
  floor_construction     46.2% -> 84.6%
  wall_insulation_type   93.1% (tie — already template-strong)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 00:31:16 +00:00
Khalim Conn-Kowlessar
ed96df9315 feat(epc-prediction): classify roof/floor/insulation/age categoricals (ADR-0029)
The comparison only scored main wall_construction; everything else the
predictor produces (by template-copy) went unmeasured. Extend
compare_prediction to the rest of the ADR-0029 homogeneous categoricals —
wall insulation type, construction age band, roof construction, floor
construction — and aggregate per-categorical classification rates in the
runner. A categorical hit is "not applicable" (None, excluded from the
denominator) when the actual lodges no value, so absent-roof flats don't
score free wins.

Smoke corpus (29 leave-one-out, all but wall are template-copied today):
  wall_construction      93.1%
  wall_insulation_type   93.1%
  construction_age_band  55.2%   <- loud; candidate for cohort-mode
  roof_construction      72.4%
  floor_construction     46.2%   (n=13)

These numbers drive the next slice (extend cohort-mode coverage).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 00:10:56 +00:00
Khalim Conn-Kowlessar
4fa20ae76b fix(epc-prediction): size-representative template selection (ADR-0029)
Template (the comparable whose structure/geometry is copied wholesale)
was members[0] — an arbitrary draw from the API search order. With floor
area varying widely within a property_type cohort (NG71AA houses span
51-340 m2), this made the copied geometry noisy and systematically large.

Pick the member whose floor area is closest to the cohort median instead,
implementing ADR-0029 decision 4's unimplemented "closest on size"
criterion while keeping the structure coherent (it is still one real
property, so floor dims / windows / parts stay internally consistent for
the calculator).

Smoke corpus (29 leave-one-out predictions):
  floor_area  mean|.| 68.0 -> 37.9 m2  (bias +46.8 -> -3.9)
  window_area mean|.| 11.1 -> 7.3 m2
  parts       mean|.| 1.00 -> 0.38
  SAP |pred-calc - calc(actual)| MAE 7.19 -> 4.86

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 00:05:40 +00:00
Khalim Conn-Kowlessar
f3ad6343a3 feat(epc-prediction): leave-one-out validation harness (ADR-0029)
Pure compare_prediction (TDD): wall-construction classification hit + signed
residuals on floor area, window count, total window area, building-parts count.
Plus validate_epc_prediction.py (IO plumbing): drops each cert from its postcode
cohort, predicts from the rest on guaranteed inputs only, aggregates the metrics,
and reports SAP three ways (pred-calc vs lodged / vs calc-on-actual / vs the
neighbour-mean baseline). Smoke run: wall 90.9%, floor-area mean|·| 42.6 m2 (a
real signal — template-copied floor area is noisy), SAP pred-calc edges baseline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:55:05 +00:00
Khalim Conn-Kowlessar
5e6d2cff16 feat(epc-prediction): EpcPrediction hybrid synthesis (ADR-0029)
predict() copies a representative template comparable's structure (coherent for
the calculator), overrides the homogeneous categorical with the cohort mode
(robust to an atypical template), then applies known Landlord Overrides on top
(a known value wins over the estimate). Proven on wall construction; roof/floor/
insulation/age extend on the same mode+override mechanism, driven next by the
validation harness metrics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:50:07 +00:00
Khalim Conn-Kowlessar
bf6b6fac17 feat(epc-prediction): Comparable Properties selection ladder (ADR-0029)
Pure-domain select_comparables: property type is an always-hard filter; built
form and known Landlord Overrides (e.g. solid brick) are conditioning filters on
the filter-then-relax ladder — applied while >= minimum_cohort survive, relaxed
otherwise (the mixed-street border case degrades gracefully). PredictionTarget
(known inputs) + Comparable (epc + register metadata) + ComparableProperties
(selected cohort). Weighting (recency x similarity) follows in the synthesis slice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:44:57 +00:00
Khalim Conn-Kowlessar
fbe1cb54ad test(epc): end-to-end SAP-accuracy gauge over the RdSAP-21.0.1 corpus
Adds a committed integration test driving the full API path — raw gov-EPC
response → from_api_response → cert_to_inputs → calculate_sap_from_inputs —
across all 1000 certs in the in-repo RdSAP-21.0.1 corpus, and pins the
aggregate accuracy of our continuous SAP (plus CO2 and primary energy)
against each cert's lodged figures. Mirrors scripts/eval_api_sap_accuracy.py
but runs in CI off the committed corpus (~2s, no /tmp sample needed).

Scoped to RdSAP-21.0.1 — the SAP 10.2-era schema whose lodged rating uses the
same methodology we compute (a fair target). Pre-SAP10 schemas (17.x-20.0.0)
lodge SAP 2012 ratings and are out of scope (guarded for mapping only by
test_mapper_corpus.py).

Current: SAP within-0.5 = 65.0%, MAE = 1.174 (tight floor/ceiling — the
optimised gauge). CO2 MAE = 0.27 t/yr (bias +0.17) and PE MAE = 14.6
kWh/m2/yr (bias +8.9) are reported + loosely guarded: cost is well-calibrated
but CO2/PE both run ~+5-10% high (uniform across fuels — a systematic
CO2/PE-factor or scope gap, not yet investigated). Thresholds ratchet as
slices tighten each metric.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:40:05 +00:00
Khalim Conn-Kowlessar
80b525f0f4 feat(epc-prediction): postcode-clustered corpus fetch script (ADR-0029)
Builds the frozen validation corpus: samples postcodes from the register, then
caches each postcode's full cohort of raw cert payloads (the shape
from_api_response consumes), grouped by postcode, resumably. Reads the token
from backend/.env; cache dir /tmp/epc_prediction_corpus (EPC_PREDICTION_CORPUS
override). IO plumbing, not test-driven. Pairs with the leave-one-out harness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:36:19 +00:00
Khalim Conn-Kowlessar
008a1b2783 docs(adr): EPC Prediction from Comparable Properties (ADR-0029)
Grill-with-docs outcome: deterministic neighbour synthesis (NOT ML) of an
EPC-less Property's EpcPropertyData picture, scored via Sap10Calculator.
Six decisions — predict-components-not-SAP; deterministic k-NN; fetch-phase
fallback behind a pure EpcPrediction service + ComparableProperties port;
hybrid synthesis (cohort-mode categoricals + coherent template structure +
overrides); filter-then-relax cohort weighted geo x recency x similarity;
dual-use gap-fill + anomaly flags. Frozen postcode-clustered corpus backs
leave-one-out validation. CONTEXT.md: new EPC Prediction term, Comparable
Properties refined, ML framing corrected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:36:19 +00:00
Khalim Conn-Kowlessar
5317175dd3 fix(water-heating): count electric showers in Noutlets for mixer demand (App J)
The mixer-shower hot-water demand (worksheet 42a) divided N_shower by the
count of MIXER outlets only. But SAP 10.2 Appendix J step 1a is explicit:
"Establish how many shower outlets are present in the dwelling, Noutlets
(including in the count any instantaneous electric showers)" — and the
electric-shower step (64a) uses that same Noutlets from step 1a. So a
dwelling with both a mixer and an electric shower assigned the FULL N_shower
to the mixer system AND billed the electric shower on top of it, double-
counting shower demand → over-counted main HW → under-rated the dwelling.

Fix: thread the electric-shower count into the mixer demand so the
denominator is the total outlet count (mixer + electric), iterating the
warm-water draw over the mixer outlets only (per step 1e).

shower_types=1,2 cohort: -0.37 median -> +0.28 (crossed zero); API gauge
68.4% -> 69.0% within-0.5. Golden cert 0300-2747 (1 mixer + 1 electric)
re-pinned: PE +0.93 -> -0.10, CO2 +0.25 -> +0.15 (both toward zero,
confirming the double-count). Worksheet harness 47/47, 0 divergers (the
Elmhurst fixtures have no electric showers).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:31:02 +00:00
Khalim Conn-Kowlessar
4fb9b853dc fix(ventilation): apply Table 4g note 3 in-use factor to index-less MEV SFP
The no-PCDB MEV fan-electricity path fed the SAP 10.2 Table 4g default SFP
(0.8 W/(l/s)) directly as SFPav. But Table 4g note 3 (PDF p.176) is explicit:
the default SFP values "are to be multiplied by the appropriate in-use factor
for default data from the PCDB" — PCDB Table 329 system_type 10 ("default
data, used when SFP is taken from Table 4g rather than the PCDB"), IUF 2.5
(duct-agnostic per note 2). Table 4h, which previously held these factors, is
retired ("no longer used – data now stored in the PCDB").

Omitting the IUF under-billed the index-less MEV fan electricity by 2.5x
(SFPav 0.8 instead of 0.8 x 2.5 = 2.0), so cost was too low and the cohort
over-rated. This is distinct from the with-index path, which already applies
the tested-product system_type-2 "no scheme" IUF (~1.45) per fan.

Index-less gas-house MEV cohort: +1.37 median -> -0.18 (12% -> 92% within 0.5),
no overshoot — the missing IUF was exactly the over-rate. API gauge 67.7% ->
68.4% within-0.5 (mean|err| 0.992 -> 0.986, signed +0.031 -> +0.006).
Worksheet harness 47/47, 0 divergers (Summary-path MEV certs carry a PCDB
index or are natural, so unaffected).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:15:32 +00:00
Khalim Conn-Kowlessar
5b2cf5edc7 Merge remote-tracking branch 'origin/main' into feature/per-cert-mapper-validation
# Conflicts:
#	datatypes/epc/domain/epc_property_data.py
#	datatypes/epc/domain/mapper.py
#	datatypes/epc/domain/tests/test_from_rdsap_schema.py
2026-06-13 22:20:15 +00:00
Daniel Roth
4c707212e7
Merge branch 'main' into improve-sharepoint-renamer 2026-06-12 17:16:15 +01:00
Daniel Roth
92ba2b9299 remove local file from branch 2026-06-12 16:15:16 +00:00
Jun-te Kim
015ab9d17b
Merge pull request #1219 from Hestia-Homes/feature/junte+khalim
rdSap 17, 18, 19, 20, now maps to EPCPropertyData
2026-06-12 17:14:52 +01:00
Daniel Roth
2bfbad5ced add local claude settings to gitignore 2026-06-12 16:11:48 +00:00
Jun-te Kim
1f40c3aeef fix engine dockerfile 2026-06-12 16:07:39 +00:00
Daniel Roth
a135d88721 Rename files in subfolders too 2026-06-12 16:04:19 +00:00
Jun-te Kim
0159176772 python upgraded due to enum 2026-06-12 15:47:28 +00:00
Jun-te Kim
0c211f401f
Merge pull request #1220 from Hestia-Homes/feature/make_test_more_readable
added floats helper
2026-06-12 16:04:56 +01:00
Jun-te Kim
80ccec9b68 added floats helper 2026-06-12 14:28:41 +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
Jun-te Kim
1ff50374e7 Record 17.0 band-4/5 synthesis transfer gaps at the seam (ADR-0028)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:50:29 +00:00
Jun-te Kim
6c03f3323c Guard all RdSAP-Schema-17.0 corpus certs in the strict parse+map bucket 🟩
Promote RdSAP-Schema-17.0 into SUPPORTED so all 1000 corpus certs are held to
the strict parse+map guard. Drop the now-redundant cert[0] tracer (subsumed by
the parametrised bucket); keep the reduced-field synthesis behavioural test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:48:47 +00:00
Jun-te Kim
9b01e1d0c9 Synthesise reduced-field windows for RdSAP-Schema-17.0 certs 🟩
Add the 17.0 synthesis seam over the shared _synthesise_reduced_field_windows
core (inherited 20.0.0 coefficients, ND glazing -> DG-modal default 2, per
ADR-0028). 17.0 glazed_type codes (1-4,7) are a subset of the verified 1-8
space. The 10 rich certs use lodged window_area directly; the windowless 990
synthesise a 4-way N/E/S/W split.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:47:40 +00:00
Jun-te Kim
887af58a25 Synthesise reduced-field windows for RdSAP-Schema-17.0 certs 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:46:27 +00:00
Jun-te Kim
26651ca71c Map RdSAP-Schema-17.0 certs to EpcPropertyData 🟩
Dispatch RdSAP-Schema-17.0 through from_api_response, parse-fix the schema
(data-driven required->optional, validated against the 1000-cert 17.0 corpus
per ADR-0028 — incl. SapHeating.cylinder_insulation_type and the
has_hot_water_cylinder / has_fixed_air_conditioning / has_heated_separate_
conservatory flags), and port the defensive mapper reads (dwelling_type
str/dict/number, photovoltaic_supply guard, sap_floor_dimensions guard). All
1000 corpus certs now parse and map.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:45:19 +00:00
Jun-te Kim
3995433816 Map RdSAP-Schema-17.0 certs to EpcPropertyData 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:40:04 +00:00
Jun-te Kim
24dcd9aa71 Record 19.0 band-4 synthesis transfer gap at the seam (ADR-0028)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:38:30 +00:00
Jun-te Kim
32eef951ee Add corpus profiler for the ADR-0028 seeing-the-data table
Reusable per-schema profiler: glazed_area band mix, Validation Cohort size,
observed-vs-predicted band glazing/floor ratio, and the ND/str sentinels that
drive schema widening. Regenerates the ADR-0028 transfer-check table from any
harvested corpus.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:36:08 +00:00
Jun-te Kim
99981e07e7 Guard all RdSAP-Schema-19.0 corpus certs in the strict parse+map bucket 🟩
Promote RdSAP-Schema-19.0 into SUPPORTED so all 1000 corpus certs are held to
the strict parse+map guard. Drop the now-redundant cert[0] tracer (subsumed by
the parametrised bucket); keep the reduced-field synthesis behavioural test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:34:29 +00:00
Jun-te Kim
1fcad454fa Synthesise reduced-field windows for RdSAP-Schema-19.0 certs 🟩
Add the 19.0 synthesis seam over the shared _synthesise_reduced_field_windows
core (inherited 20.0.0 coefficients, ND glazing -> DG-modal default 2, per
ADR-0028). 19.0 glazed_type codes (1-4,6,7) are a subset of the verified 1-8
space. The 6 rich certs use lodged window_area directly; the windowless 994
synthesise a 4-way N/E/S/W split.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:33:26 +00:00
Jun-te Kim
8d8e2b1208 Synthesise reduced-field windows for RdSAP-Schema-19.0 certs 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:31:23 +00:00
Jun-te Kim
792f76f2fa Map RdSAP-Schema-19.0 certs to EpcPropertyData 🟩
Dispatch RdSAP-Schema-19.0 through from_api_response, parse-fix the schema
(data-driven required->optional, validated against the 1000-cert 19.0 corpus
per ADR-0028), and port 18.0's defensive mapper reads (dwelling_type str/dict/
number, photovoltaic_supply guard, sap_room_in_roof Measurement coercion).
All 1000 corpus certs now parse and map.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:29:36 +00:00
Jun-te Kim
5178197dc2 Map RdSAP-Schema-19.0 certs to EpcPropertyData 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 12:19:16 +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
Khalim Conn-Kowlessar
797b71cd13 test(modelling): secondary-heating-removal cascade validation (ADR-0028)
Two cascade tests on the worksheet-pinned 001431 build_epc() (the user's
before/after Summary PDFs trip the documented 001431 window-extraction bug, so
the repo's sanctioned 001431 baseline is used instead):
- electric-storage main (code 402) + secondary 691: removal reproduces the
  secondary-removed cert at delta 0 — RdSAP §A.2.2 re-forces a default secondary,
  matching the user's F35→F35 example;
- gas combi main (code 104) + secondary 691: removal strictly raises SAP
  (74.22→77.61) — the Table 11 fraction reallocates to the cheaper main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 15:32:15 +00:00
Khalim Conn-Kowlessar
f9a89a8e11 docs(adr): secondary heating removal — ADR-0028 + CONTEXT term
Records the four load-bearing design decisions from the grill-with-docs session:
standalone co-selectable rec; eligibility = lodged-only (no effectiveness gate,
electric-storage §A.2.2 no-op is the Optimiser's call); dedicated clearing
SecondaryHeatingOverlay; flat per-dwelling cost (a lodged secondary is fixed per
RdSAP, so a real decommission job, not room-scaled).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 15:32:14 +00:00