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:
parent
fdc1f5c176
commit
14d70c5bb3
2 changed files with 37 additions and 15 deletions
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue