fix(reporting): surface the coverage "no certificate" count on the strip

The confidence strip showed only the provenance "estimated" figure (824:
389), which counts expired-slot homes as modelled — so the #393 coverage
improvement (only 27 homes truly lack a certificate) wasn't visible on the
reporting page, only on data-quality.

Add the coverage count alongside it: getBaselineAggregates now also returns
noCertificate (withoutEpcSql — the tested classifyEpcEvidence "no-certificate"
case), threaded through BaselineMetrics; a new "no-certificate" drill filter
backs the strip item. The strip now reads e.g. "estimated 389 · no
certificate 27" — provenance and coverage side by side, per #393.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-27 15:05:30 +00:00
parent bc2cde3984
commit 75ee685cc2
5 changed files with 20 additions and 0 deletions

View file

@ -9,6 +9,7 @@ import {
effectiveEpcBandSql,
estimatedSql,
isExpiredSql,
withoutEpcSql,
likelyDowngradeSql,
likelyUpgradeSql,
provenanceSignalSql,
@ -29,6 +30,7 @@ const FILTERS = [
"band",
"estimated",
"expired",
"no-certificate",
"likely-downgrade",
"likely-upgrade",
"measure",
@ -84,6 +86,8 @@ export async function GET(
// guard — an expired-slot home is estimated AND expired, and this drill must
// match the Expired count (ADR-0014, PR #393). Twin: classifyEpcEvidence.
expired: sql`${isExpiredSql(sql`e`)} = true`,
// Coverage: no certificate of any kind (the #393 "without an EPC" homes).
"no-certificate": sql`${withoutEpcSql(sql`e`)} = true`,
"likely-downgrade": likelyDowngradeSql,
"likely-upgrade": likelyUpgradeSql,
measure: sql`EXISTS (

View file

@ -830,6 +830,13 @@ function ConfidenceStrip({
onDrill({ filter: "estimated", title: "Estimated EPCs" })
}
/>
<Item
label="no certificate"
value={baseline.noCertificate}
onClick={() =>
onDrill({ filter: "no-certificate", title: "Homes without a certificate" })
}
/>
<Item
label="expired"
value={baseline.expiredEpcs}

View file

@ -21,6 +21,7 @@ export interface DrillTarget {
| "band"
| "estimated"
| "expired"
| "no-certificate"
| "likely-downgrade"
| "likely-upgrade"
| "measure";

View file

@ -82,6 +82,7 @@ export async function getBaselineAggregates(
lodgedNoCertificate: number;
estimatedCounts: EstimatedCounts;
expiredEpcs: number;
noCertificate: number;
}> {
const est = estimatedSql(sql`e`);
const bandExpr = sql`COALESCE((${effectiveEpcBandSql})::text, 'Unknown')`;
@ -133,6 +134,9 @@ export async function getBaselineAggregates(
-- estimated AND expired, and the Expired count must include it (ADR-0014,
-- PR #393). Twin: classifyEpcEvidence (@/lib/reporting/epcEvidence).
SUM(CASE WHEN ${isExpiredSql(sql`e`)} = true THEN 1 ELSE 0 END)::int AS expired,
-- Coverage: no certificate of any kind (the #393 "without an EPC" figure),
-- distinct from the provenance estimated count above. Twin: classifyEpcEvidence.
SUM(CASE WHEN ${withoutEpcSql(sql`e`)} = true THEN 1 ELSE 0 END)::int AS no_certificate,
${bandCols},
${lodgedBandCols}
FROM property p
@ -189,6 +193,7 @@ export async function getBaselineAggregates(
actual: Number(row.actual ?? 0),
},
expiredEpcs: Number(row.expired ?? 0),
noCertificate: Number(row.no_certificate ?? 0),
};
}
@ -285,6 +290,7 @@ export async function loadBaselineMetrics(
lodgedNoCertificate: aggregates.lodgedNoCertificate,
estimatedCounts: aggregates.estimatedCounts,
expiredEpcs: aggregates.expiredEpcs,
noCertificate: aggregates.noCertificate,
likelyDowngrades,
};
}

View file

@ -54,6 +54,8 @@ export interface BaselineMetrics {
lodgedNoCertificate: number;
estimatedCounts: EstimatedCounts;
expiredEpcs: number;
/** Coverage: homes with no certificate of any kind (the #393 "without an EPC" figure). */
noCertificate: number;
likelyDowngrades: number;
}