diff --git a/src/app/portfolio/[slug]/utils.ts b/src/app/portfolio/[slug]/utils.ts index b429f6c8..6014cc25 100644 --- a/src/app/portfolio/[slug]/utils.ts +++ b/src/app/portfolio/[slug]/utils.ts @@ -34,6 +34,7 @@ import { } from "@/lib/services/epcSources"; import { FilterGroups, + FilterField, PropertyFilter, PROPERTY_TYPE_OPTIONS, BUILT_FORM_OPTIONS, @@ -681,25 +682,62 @@ function buildWhereClause(filterGroups: FilterGroups): ReturnType { : sql``; } +// Filter fields whose SQL references the EPC graph (epc/bp/epl/epp) vs the +// default-plan LATERAL. The count query only needs a join when an active filter +// references it — otherwise joining is pure cost. The `pl` LATERAL in particular +// runs once per property (31k+ correlated plan lookups for a large portfolio), +// which alone pushed the unfiltered count past Vercel's 15s limit. +const EPC_JOIN_FILTER_FIELDS = new Set([ + "currentEpc", + "lodgedEpc", + "provenance", + "co2Emissions", + "floorArea", + "epcExpiryDate", + "mainfuel", +]); +const PLAN_JOIN_FILTER_FIELDS = new Set(["expectedEpc"]); + +function filterFieldsInUse(filterGroups: FilterGroups): Set { + const fields = new Set(); + for (const group of filterGroups) { + for (const cond of group.conditions) fields.add(cond.field); + } + return fields; +} + export async function getPropertiesCount( portfolioId: string, filterGroups: FilterGroups = [] ): Promise { const combinedWhere = buildWhereClause(filterGroups); + const fields = filterFieldsInUse(filterGroups); - const result = await db.execute<{ count: string }>(sql` - SELECT COUNT(DISTINCT p.id)::int AS count - FROM property p - LEFT JOIN property_details_epc epc ON epc.property_id = p.id - ${newApproachJoins} - LEFT JOIN LATERAL ( + // Only join what an active filter needs. COUNT is unaffected by the LEFT JOINs + // otherwise (none multiply rows), so omitting them is purely a speed-up. + const needsEpcJoins = [...fields].some((f) => EPC_JOIN_FILTER_FIELDS.has(f)); + const needsPlanJoin = [...fields].some((f) => PLAN_JOIN_FILTER_FIELDS.has(f)); + + const epcJoins = needsEpcJoins + ? sql`LEFT JOIN property_details_epc epc ON epc.property_id = p.id + ${newApproachJoins}` + : sql``; + const planJoin = needsPlanJoin + ? sql`LEFT JOIN LATERAL ( SELECT id, post_sap_points FROM plan WHERE property_id = p.id AND portfolio_id = p.portfolio_id AND is_default = true ORDER BY created_at DESC LIMIT 1 - ) pl ON true + ) pl ON true` + : sql``; + + const result = await db.execute<{ count: string }>(sql` + SELECT COUNT(DISTINCT p.id)::int AS count + FROM property p + ${epcJoins} + ${planJoin} WHERE p.portfolio_id = ${portfolioId} ${combinedWhere} `);