diff --git a/src/app/api/portfolio/[portfolioId]/scenario/[scenarioId]/metrics/route.ts b/src/app/api/portfolio/[portfolioId]/scenario/[scenarioId]/metrics/route.ts index 93f3ed6..ce373b6 100644 --- a/src/app/api/portfolio/[portfolioId]/scenario/[scenarioId]/metrics/route.ts +++ b/src/app/api/portfolio/[portfolioId]/scenario/[scenarioId]/metrics/route.ts @@ -25,6 +25,14 @@ type UpgradedAggregates = { total_funding: number | null; }; +type PortfolioAggregates = { + avg_sap: number | null; + avg_carbon: number | null; + avg_bills: number | null; + total_carbon: number | null; + total_bills: number | null; +}; + type EpcRow = { effective_sap: number | null; }; @@ -87,7 +95,7 @@ export async function GET( : null; /* ---------------------------------------------------------- - QUERY 1 — Scenario metrics (plans ONLY) + QUERY 1 — Scenario metrics (PLANS ONLY) ---------------------------------------------------------- */ const scenarioMetricsResult = await db.execute(sql` WITH latest_plans AS ( @@ -124,19 +132,10 @@ export async function GET( JOIN property p ON p.id = lp.property_id; `); - const scenarioAgg = scenarioMetricsResult.rows[0] as - | ScenarioAggregates - | undefined; - - if (!scenarioAgg) { - return NextResponse.json( - { error: "No scenario metrics found" }, - { status: 404 }, - ); - } + const scenarioAgg = scenarioMetricsResult.rows[0] as ScenarioAggregates; /* ---------------------------------------------------------- - QUERY 1b — Upgrade costs (still plan-only) + QUERY 1b — Upgrade costs (PLANS ONLY) ---------------------------------------------------------- */ const upgradedResult = await db.execute(sql` WITH latest_plans AS ( @@ -170,14 +169,78 @@ export async function GET( const upgraded = upgradedResult.rows[0] as UpgradedAggregates; /* ---------------------------------------------------------- - QUERY 2 — EPC distribution (ALL properties) - ★ This is the important new one + QUERY 2 — Portfolio AFTER scenario (ALL properties) + ---------------------------------------------------------- */ + const portfolioMetricsResult = await db.execute(sql` + SELECT + AVG(effective_sap)::float AS avg_sap, + AVG(effective_carbon)::float AS avg_carbon, + AVG(effective_bills)::float AS avg_bills, + SUM(effective_carbon)::float AS total_carbon, + SUM(effective_bills)::float AS total_bills + FROM ( + SELECT + /* ---------- SAP ---------- */ + CASE + WHEN lp.id IS NOT NULL THEN lp.post_sap_points + ELSE p.current_sap_points + END AS effective_sap, + + /* ---------- Carbon ---------- */ + CASE + WHEN lp.id IS NOT NULL THEN lp.post_co2_emissions + ELSE e.co2_emissions + END AS effective_carbon, + + /* ---------- Bills ---------- */ + CASE + WHEN lp.id IS NOT NULL THEN lp.post_energy_bill + ELSE ( + e.heating_cost_current + + e.hot_water_cost_current + + e.lighting_cost_current + + e.appliances_cost_current + + e.gas_standing_charge + + e.electricity_standing_charge - + COALESCE(e.installed_measures_total_energy_bill_adjustment, 0) + ) + END AS effective_bills + + FROM property p + LEFT JOIN property_details_epc e + ON e.property_id = p.id + + LEFT JOIN LATERAL ( + SELECT * + FROM plan + WHERE plan.property_id = p.id + AND plan.portfolio_id = ${pid} + AND plan.scenario_id = ${sid} + AND ( + ${hideNonCompliant} = false + OR ( + ${minSap}::float IS NOT NULL + AND plan.post_sap_points >= ${minSap}::float + ) + ) + ORDER BY created_at DESC + LIMIT 1 + ) lp ON true + + WHERE p.portfolio_id = ${pid} + ) q; + `); + + const portfolioAgg = portfolioMetricsResult.rows[0] as PortfolioAggregates; + + /* ---------------------------------------------------------- + QUERY 3 — EPC band distribution (ALL properties) ---------------------------------------------------------- */ const epcRows = await db.execute(sql` SELECT CASE WHEN lp.id IS NOT NULL THEN lp.post_sap_points - ELSE COALESCE(p.current_sap_points, 0) + ELSE p.current_sap_points END AS effective_sap FROM property p LEFT JOIN LATERAL ( @@ -218,30 +281,35 @@ export async function GET( /* ---------------------------------------------------------- RESPONSE ---------------------------------------------------------- */ - const pc_cost = (upgraded.total_cost ?? 0) * 0.3; + + const constructionCost = upgraded.total_cost ?? 0; + const nUpgraded = upgraded.n_units_upgraded ?? 0; + const pc_cost = constructionCost * 0.3; return NextResponse.json({ + /* -------- portfolio-after-scenario -------- */ avg_sap: - scenarioAgg.avg_sap !== null - ? Number(scenarioAgg.avg_sap).toFixed(1) + portfolioAgg.avg_sap !== null + ? Number(portfolioAgg.avg_sap).toFixed(1) : null, - avg_carbon: scenarioAgg.avg_carbon, - avg_bills: scenarioAgg.avg_bills, - total_carbon: scenarioAgg.total_carbon, - total_bills: scenarioAgg.total_bills, - n_units: scenarioAgg.n_units, - scenario_epc_counts, - pc_cost, + avg_carbon: portfolioAgg.avg_carbon, + avg_bills: portfolioAgg.avg_bills, + total_carbon: portfolioAgg.total_carbon, + total_bills: portfolioAgg.total_bills, - n_units_upgraded: upgraded.n_units_upgraded, - construction_cost: upgraded.total_cost ?? 0, + /* -------- scenario-only -------- */ + n_units: scenarioAgg.n_units, + n_units_upgraded: nUpgraded, + construction_cost: constructionCost, contingency: upgraded.contingency ?? 0, total_funding: upgraded.total_funding ?? 0, - net_cost: (upgraded.total_cost ?? 0) - (upgraded.total_funding ?? 0), - gross_per_unit: - upgraded.n_units_upgraded > 0 - ? ((upgraded.total_cost ?? 0) + pc_cost) / upgraded.n_units_upgraded - : 0, + net_cost: constructionCost - (upgraded.total_funding ?? 0), total_sap_uplift: scenarioAgg.total_sap_uplift ?? 0, + gross_per_unit: + nUpgraded > 0 ? (constructionCost + pc_cost) / nUpgraded : 0, + + /* -------- shared -------- */ + scenario_epc_counts, + pc_cost, }); } diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/page.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/page.tsx index 3bd3fc2..739436e 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/page.tsx @@ -17,8 +17,6 @@ export default async function ReportingPage(props: { ]); const scenarios = await getScenarios(Number(portfolioId)); - console.log("Baseline EPC counts", baseline.epcBands); - return (
diff --git a/src/middleware.ts b/src/middleware.ts index f1f44e9..4fa8c46 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -37,7 +37,7 @@ export async function middleware(req: NextRequest) { export const config = { matcher: [ - // Protect only your app’s authenticated areas + // Protect only app’s authenticated areas "/home/:path*", "/portfolio/:path*", "/search/:path*",