mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-27 22:45:03 +00:00
refactor(reporting): fold in duplication cleanups
- Money formatters: model.ts owns the single formatMoneyCompact + moneyFull; components/primitives re-exports them (moneyCompact = formatMoneyCompact) and the PDF's local money() is gone. - ReportView: viewState.ts is the single definition; ScenarioCombobox re-exports it instead of re-declaring. - EPC_BANDS: the reporting API routes import the canonical @/lib/epc/bands rather than the scenarios-model copy. - compare/page uses countBelowBand(toBandCounts(...), "C") instead of a hardcoded ["D","E","F","G"] reduce. Typecheck + 95 reporting/epc tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
966381ded2
commit
03e62a17ed
7 changed files with 28 additions and 25 deletions
|
|
@ -13,7 +13,7 @@ import {
|
|||
likelyUpgradeSql,
|
||||
provenanceSignalSql,
|
||||
} from "@/lib/services/epcSources";
|
||||
import { EPC_BANDS } from "@/lib/scenarios/model";
|
||||
import { EPC_BANDS } from "@/lib/epc/bands";
|
||||
import { tagFilterCondition } from "@/lib/reporting/tagFilterSql";
|
||||
import { parseReportingViewState } from "@/lib/reporting/viewState";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { EPC_BANDS } from "@/lib/scenarios/model";
|
||||
import { EPC_BANDS } from "@/lib/epc/bands";
|
||||
import { DEFAULT_GOAL_BAND } from "@/lib/reporting/model";
|
||||
import { getScenarioOverlay, type OverlayFilters } from "@/lib/reporting/overlay";
|
||||
import { parseReportingViewState } from "@/lib/reporting/viewState";
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
loadBaselineMetrics,
|
||||
getReportingScenarios,
|
||||
} from "@/lib/reporting/server";
|
||||
import { countBelowBand, toBandCounts } from "@/lib/reporting/model";
|
||||
import { CompareClientArea } from "./CompareClientArea";
|
||||
|
||||
export default async function ComparePage(props: {
|
||||
|
|
@ -36,9 +37,7 @@ export default async function ComparePage(props: {
|
|||
avgSap: baseline.averages.avg_sap ?? 0,
|
||||
totalCarbon: baseline.totals.total_carbon ?? 0,
|
||||
totalBills: baseline.totals.total_bills ?? 0,
|
||||
belowC: baseline.epcBands
|
||||
.filter((b) => ["D", "E", "F", "G"].includes(b.epc))
|
||||
.reduce((s, b) => s + b.actual + b.estimated, 0),
|
||||
belowC: countBelowBand(toBandCounts(baseline.epcBands), "C"),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import {
|
|||
import { ChevronDown, Check, Search } from "lucide-react";
|
||||
import type { ScenarioSummary } from "@/lib/reporting/types";
|
||||
|
||||
export type ReportView = "current-stock" | "recommended" | number;
|
||||
// ReportView is part of the reporting view-state (viewState.ts owns it);
|
||||
// re-exported here so existing imports from this component keep resolving.
|
||||
export type { ReportView } from "@/lib/reporting/viewState";
|
||||
import type { ReportView } from "@/lib/reporting/viewState";
|
||||
|
||||
const GOAL_SHORT: Record<string, string> = {
|
||||
"Increasing EPC": "Target EPC",
|
||||
|
|
|
|||
|
|
@ -139,16 +139,10 @@ export function Panel({
|
|||
);
|
||||
}
|
||||
|
||||
/** £2,477,000 → "£2.48m"; £850,000 → "£850k". Presentation only. */
|
||||
export function moneyCompact(amount: number): string {
|
||||
const abs = Math.abs(amount);
|
||||
const sign = amount < 0 ? "−" : "";
|
||||
if (abs >= 1_000_000) return `${sign}£${(abs / 1_000_000).toFixed(2)}m`;
|
||||
if (abs >= 1_000) return `${sign}£${Math.round(abs / 1_000)}k`;
|
||||
return `${sign}£${Math.round(abs)}`;
|
||||
}
|
||||
|
||||
export function moneyFull(amount: number): string {
|
||||
const sign = amount < 0 ? "−" : "";
|
||||
return `${sign}£${Math.abs(Math.round(amount)).toLocaleString("en-GB")}`;
|
||||
}
|
||||
// Money formatters live in the reporting model (the single source, shared with
|
||||
// the headline + PDF); re-exported here so components keep importing them from
|
||||
// the primitives barrel. `moneyCompact` is the model's `formatMoneyCompact`.
|
||||
export {
|
||||
formatMoneyCompact as moneyCompact,
|
||||
moneyFull,
|
||||
} from "@/lib/reporting/model";
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
shapeKpiDelta,
|
||||
toBandCounts,
|
||||
deriveLedgerView,
|
||||
moneyFull as money,
|
||||
type ScenarioOverlay,
|
||||
} from "@/lib/reporting/model";
|
||||
import { sapToEpc, formatNumber, getEpcColorClass } from "@/app/utils";
|
||||
|
|
@ -67,11 +68,6 @@ async function fetchScenarioReport(
|
|||
return res.json();
|
||||
}
|
||||
|
||||
function money(n: number): string {
|
||||
const sign = n < 0 ? "−" : "";
|
||||
return `${sign}£${Math.abs(Math.round(n)).toLocaleString("en-GB")}`;
|
||||
}
|
||||
|
||||
const generatedOn = () =>
|
||||
new Date().toLocaleDateString("en-GB", {
|
||||
day: "numeric",
|
||||
|
|
|
|||
|
|
@ -384,7 +384,12 @@ export function shapeKpiDelta(input: {
|
|||
Board headline
|
||||
------------------------------------------------------------------ */
|
||||
|
||||
/** £2,477,000 → "£2.48m"; £850,000 → "£850k"; £900 → "£900". */
|
||||
/**
|
||||
* £2,477,000 → "£2.48m"; £850,000 → "£850k"; £900 → "£900". The single
|
||||
* compact money formatter for every reporting surface (headline, KPI tiles,
|
||||
* the money-allocation bar) — re-exported from components/primitives as
|
||||
* `moneyCompact`.
|
||||
*/
|
||||
export function formatMoneyCompact(amount: number): string {
|
||||
const abs = Math.abs(amount);
|
||||
const sign = amount < 0 ? "-" : "";
|
||||
|
|
@ -393,6 +398,12 @@ export function formatMoneyCompact(amount: number): string {
|
|||
return `${sign}£${Math.round(abs)}`;
|
||||
}
|
||||
|
||||
/** £2,477,000 → "£2,477,000". The single full money formatter (ledger, PDF). */
|
||||
export function moneyFull(amount: number): string {
|
||||
const sign = amount < 0 ? "−" : "";
|
||||
return `${sign}£${Math.abs(Math.round(amount)).toLocaleString("en-GB")}`;
|
||||
}
|
||||
|
||||
export interface HeadlineInput {
|
||||
goal: string;
|
||||
goalValue: string | null;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue