assessment-model/docs/adr
Jun-te Kim 0933f05173 fix(reporting): EPC cards count certificates, not predictions (ADR-0014)
"Homes Without an EPC" and "Expired EPCs" were both derived from estimatedSql,
which asks "was this picture gap-filled?". That conflates two different questions:
provenance (was it modelled?) and coverage (does a certificate exist?).

They used to agree, because a gap-fill implied no certificate. Model ADR-0054 ends
that: the gov EPC API only serves certs registered since 2012, so a pre-2012
dwelling is gap-filled even though a real, stale certificate exists — and gets the
new source='expired'. On portfolio 824, 375 of the 403 "without an EPC" homes have
exactly that: a real certificate, just an out-of-date one. A different, and far more
actionable, problem than never having been certified.

Two defects fell out of the same conflation:

  - Every query matched source='predicted' literally, and `source` is plain text
    with no enum or CHECK. An 'expired' row matches neither the lodged nor the
    predicted alias, so the home would have read as having a VALID CURRENT cert —
    silently gone from both cards, no Estimated badge, nothing to catch it.
  - isExpiredSql read epl.registration_date alone, but 714 of 824's 4,873 lodged
    certs carry only inspection_date. All 714 counted as current; 170 are in fact
    >10 years old. lodgementDateSql already coalesced the two; expiry didn't.

The cards now partition on coverage: withoutEpcSql = no lodged AND no historical
record; isExpiredSql = a cert of either kind, out of date. estimatedSql keeps its
own meaning and spans the whole predicted slot, so an expired home is estimated AND
has an EPC — both true. The `AND estimated = false` guard is gone from the Expired
card: an expired home IS estimated, so it excluded the very homes being counted.

Legacy portfolios are untouched (632: 8/1662 before and after). 824 moves to
913 → 1,083 expired immediately (the inspection_date fix), then to 25 / 1,461 once
Model writes expired rows. Safe to deploy BEFORE Model — the predicates just find
none. The reverse was not safe, which is why this goes first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 12:19:02 +00:00
..
0003-app-authored-scenarios.md feat(scenarios): portfolio Scenarios tab — gallery, detail, creation journey 2026-07-06 08:34:47 +00:00
0007-postcode-search-creates-properties.md fix(postcode-search): classify RH (HMO) family as addable residential 2026-07-09 10:33:07 +00:00
0008-modelling-runs-filters-to-distributor.md feat(modelling-runs): run config on tasks.inputs; backend contract updates 2026-07-07 10:46:29 +00:00
0009-portfolio-organisation-is-per-user.md docs(adr): 0009 — portfolio organisation is per-user, not shared 2026-07-07 21:11:21 +00:00
0011-app-owned-task-marker-in-task-source.md feat(live-reporting): bulk document download from the Documents tab 2026-07-08 14:11:42 +00:00
0012-portfolio-list-resolves-descriptors-via-override-precedence.md docs(portfolio): ADR-0012 — list resolves descriptors via override precedence 2026-07-10 23:21:21 +00:00
0013-tags-are-app-owned-property-groupings.md docs: ADR-0013 tagging system + CONTEXT (Bulk tag assignment, Run filter tags) 2026-07-13 15:45:14 +00:00
0014-epc-cards-count-certificates-not-predictions.md fix(reporting): EPC cards count certificates, not predictions (ADR-0014) 2026-07-14 12:19:02 +00:00