diff --git a/src/app/api/portfolio/[portfolioId]/reporting/properties/route.ts b/src/app/api/portfolio/[portfolioId]/reporting/properties/route.ts index 462cd8e0..3e9796f4 100644 --- a/src/app/api/portfolio/[portfolioId]/reporting/properties/route.ts +++ b/src/app/api/portfolio/[portfolioId]/reporting/properties/route.ts @@ -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 ( diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx index b363801b..25027b4c 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx @@ -830,6 +830,13 @@ function ConfidenceStrip({ onDrill({ filter: "estimated", title: "Estimated EPCs" }) } /> + + onDrill({ filter: "no-certificate", title: "Homes without a certificate" }) + } + /> { 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, }; } diff --git a/src/lib/reporting/types.ts b/src/lib/reporting/types.ts index 37f9c2ed..7ff79ecc 100644 --- a/src/lib/reporting/types.ts +++ b/src/lib/reporting/types.ts @@ -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; }