Weekly actuals: utility trend charts use projected figure for the in-progress month

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-10-23 12:35:04 +00:00
parent fdc1f5c176
commit 14d70c5bb3
2 changed files with 37 additions and 15 deletions

View file

@ -10,7 +10,7 @@ import {
CartesianGrid, CartesianGrid,
} from 'recharts' } from 'recharts'
import { getWeeklyActual } from '../api' import { getWeeklyActual } from '../api'
import type { WeeklyActualData, WeekSummaryRow, MonthSplitEntry, MonthDailyEntry, UtilityConsumptionBasis, UtilityTrend } from '../types' import type { WeeklyActualData, WeekSummaryRow, MonthSplitEntry, MonthDailyEntry, UtilityConsumptionBasis, UtilityTrend, UtilityTrendMonth, UtilityEstimate } from '../types'
// ── Formatters ───────────────────────────────────────────────────────────── // ── Formatters ─────────────────────────────────────────────────────────────
@ -487,12 +487,32 @@ function fmtBasis(status: UtilityConsumptionBasis, asOfDate: string | null): str
return asOfDate ? `As of ${fmtShortDate(asOfDate)}` : 'Partial' return asOfDate ? `As of ${fmtShortDate(asOfDate)}` : 'Partial'
} }
function UtilitiesCostTrendChart({ trend }: { trend: UtilityTrend }) { // Builds the "This Year" series, swapping the in-progress month's partial
const chartData = trend.this_year.map((ty, i) => ({ // actuals for the utilities estimate. The projected month is emitted on a
label: ty.label, // separate dashed series (anchored on the previous month's actual so the line
'This Year': ty.total_pence / 100, // is continuous) and left null on the solid one.
'Last Year': (trend.last_year[i]?.total_pence ?? 0) / 100, function withProjection(
})) trend: UtilityTrend,
estimate: UtilityEstimate | null,
pick: (m: UtilityTrendMonth, est: UtilityEstimate | null) => number,
) {
const lastIdx = trend.this_year.length - 1
const last = trend.this_year[lastIdx]
const projecting = !!estimate && !!last && last.year === estimate.period.year && last.month === estimate.period.month
return trend.this_year.map((ty, i) => {
const isProjected = projecting && i === lastIdx
const value = pick(ty, isProjected ? estimate : null)
return {
label: ty.label,
'This Year': isProjected ? null : value,
'Projected': isProjected ? value : (projecting && i === lastIdx - 1 ? value : null),
'Last Year': pick(trend.last_year[i], null),
}
})
}
function UtilitiesCostTrendChart({ trend, estimate }: { trend: UtilityTrend; estimate: UtilityEstimate | null }) {
const chartData = withProjection(trend, estimate, (m, est) => (est ? est.totals.total_pence : (m?.total_pence ?? 0)) / 100)
return ( return (
<div className="wa-chart-card"> <div className="wa-chart-card">
@ -505,6 +525,7 @@ function UtilitiesCostTrendChart({ trend }: { trend: UtilityTrend }) {
<Tooltip formatter={fmtChartCcy} /> <Tooltip formatter={fmtChartCcy} />
<Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} /> <Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} />
<Line type="monotone" dataKey="This Year" stroke={CHART_COLORS.thisWeek} strokeWidth={2.5} dot={false} /> <Line type="monotone" dataKey="This Year" stroke={CHART_COLORS.thisWeek} strokeWidth={2.5} dot={false} />
<Line type="monotone" dataKey="Projected" name="This Month (projected)" stroke={CHART_COLORS.thisWeek} strokeWidth={2.5} strokeDasharray="2 3" dot={{ r: 3 }} connectNulls />
<Line type="monotone" dataKey="Last Year" stroke={CHART_COLORS.budget} strokeWidth={2} dot={false} strokeDasharray="5 3" /> <Line type="monotone" dataKey="Last Year" stroke={CHART_COLORS.budget} strokeWidth={2} dot={false} strokeDasharray="5 3" />
</LineChart> </LineChart>
</ResponsiveContainer> </ResponsiveContainer>
@ -512,15 +533,14 @@ function UtilitiesCostTrendChart({ trend }: { trend: UtilityTrend }) {
) )
} }
function UtilitiesConsumptionCharts({ trend }: { trend: UtilityTrend }) { function UtilitiesConsumptionCharts({ trend, estimate }: { trend: UtilityTrend; estimate: UtilityEstimate | null }) {
return ( return (
<div className="wa-charts-row"> <div className="wa-charts-row">
{trend.categories.map(cat => { {trend.categories.map(cat => {
const chartData = trend.this_year.map((ty, i) => ({ const chartData = withProjection(trend, estimate, (m, est) =>
label: ty.label, est
'This Year': ty.by_category[cat.category]?.consumption ?? 0, ? est.categories.find(c => c.category === cat.category)?.projected_consumption ?? 0
'Last Year': trend.last_year[i]?.by_category[cat.category]?.consumption ?? 0, : m?.by_category[cat.category]?.consumption ?? 0)
}))
return ( return (
<div className="wa-chart-card" key={cat.category}> <div className="wa-chart-card" key={cat.category}>
<h4>{cat.category_name} Consumption ({cat.unit_label}) — Last 12 Months vs Prior Year</h4> <h4>{cat.category_name} Consumption ({cat.unit_label}) — Last 12 Months vs Prior Year</h4>
@ -532,6 +552,7 @@ function UtilitiesConsumptionCharts({ trend }: { trend: UtilityTrend }) {
<Tooltip /> <Tooltip />
<Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} /> <Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} />
<Line type="monotone" dataKey="This Year" stroke={CHART_COLORS.rooms} strokeWidth={2.5} dot={false} /> <Line type="monotone" dataKey="This Year" stroke={CHART_COLORS.rooms} strokeWidth={2.5} dot={false} />
<Line type="monotone" dataKey="Projected" name="This Month (projected)" stroke={CHART_COLORS.rooms} strokeWidth={2.5} strokeDasharray="2 3" dot={{ r: 3 }} connectNulls />
<Line type="monotone" dataKey="Last Year" stroke={CHART_COLORS.budget} strokeWidth={2} dot={false} strokeDasharray="5 3" /> <Line type="monotone" dataKey="Last Year" stroke={CHART_COLORS.budget} strokeWidth={2} dot={false} strokeDasharray="5 3" />
</LineChart> </LineChart>
</ResponsiveContainer> </ResponsiveContainer>
@ -618,8 +639,8 @@ function UtilitiesSection({ utilities }: { utilities: WeeklyActualData['utilitie
)} )}
{trend && trend.this_year.length > 0 && ( {trend && trend.this_year.length > 0 && (
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<UtilitiesCostTrendChart trend={trend} /> <UtilitiesCostTrendChart trend={trend} estimate={estimate} />
<UtilitiesConsumptionCharts trend={trend} /> <UtilitiesConsumptionCharts trend={trend} estimate={estimate} />
</div> </div>
)} )}
</> </>

View file

@ -200,6 +200,7 @@ export interface UtilityCosts {
export interface UtilityEstimate { export interface UtilityEstimate {
period: { year: number; month: number; start: string; end: string; remaining_days: number } period: { year: number; month: number; start: string; end: string; remaining_days: number }
categories: { category: string; category_name: string; unit_label: string; projected_consumption: number; total_pence: number }[]
meters: UtilityMeterEstimate[] meters: UtilityMeterEstimate[]
totals: { total_pence: number; projected_consumption: number } totals: { total_pence: number; projected_consumption: number }
} }