Switch Utilities table to per-meter rows, add 12-month trend charts

Table now matches the format used elsewhere in the app: one row per
meter (Consumption/Usage/Standing/Extras/VAT/Total/Basis/Period
Estimate) instead of a category rollup, mirroring the utilities app's
own Reports/Estimates pages (basis text, merged extras column).

Adds two charts below the table: total utility cost (this year vs
prior year) and one small per-category consumption chart (this year
vs prior year), both fed by the new /api/internal/trend endpoint,
aligned to the report's own month so paging to an older week shows
the right trailing-12-month window rather than today's.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-28 14:59:18 +00:00
parent 0afecc2e24
commit 1efcc941bb
4 changed files with 180 additions and 30 deletions

View file

@ -39,3 +39,12 @@ export async function getUtilityCosts(period) {
export async function getUtilityEstimate() {
return utilFetch(`/api/internal/estimate?period=current`)
}
// GET /api/internal/trend?months=12&end=YYYY-MM — per-category cost/consumption
// for the last N months (ending at `end`, defaulting to the current month)
// plus the same N months a year earlier
export async function getUtilityTrend(months = 12, end) {
const qs = new URLSearchParams({ months: String(months) })
if (end) qs.set('end', end)
return utilFetch(`/api/internal/trend?${qs.toString()}`)
}

View file

@ -1,6 +1,6 @@
import { requireAuth } from '../auth.js'
import { getSetting } from '../db.js'
import { getUtilityCosts, getUtilityEstimate } from '../lib/utilities-client.js'
import { getUtilityCosts, getUtilityEstimate, getUtilityTrend } from '../lib/utilities-client.js'
async function fcFetch(path) {
const apiKey = await getSetting('forecasting_api_key') || process.env.FORECASTING_API_KEY
@ -294,19 +294,20 @@ export async function weeklyActualRoutes(fastify) {
// Fetched best-effort for the report's calendar month — a failure here
// (e.g. utilities app not deployed, or key not configured) must not break
// the rest of the report.
let utilities = { costs: null, estimate: null, error: null }
let utilities = { costs: null, estimate: null, trend: null, error: null }
try {
const period = `${year}-${String(month).padStart(2, '0')}`
const now = new Date()
const isCurrentPeriod = year === now.getFullYear() && month === now.getMonth() + 1
const [costs, estimate] = await Promise.all([
const [costs, estimate, trend] = await Promise.all([
getUtilityCosts(period),
isCurrentPeriod ? getUtilityEstimate() : Promise.resolve(null),
getUtilityTrend(12, period),
])
utilities = { costs, estimate, error: null }
utilities = { costs, estimate, trend, error: null }
} catch (err) {
utilities = { costs: null, estimate: null, error: err.message }
utilities = { costs: null, estimate: null, trend: null, error: err.message }
}
return {