diff --git a/backend/src/lib/utilities-client.js b/backend/src/lib/utilities-client.js new file mode 100644 index 00000000..961738d8 --- /dev/null +++ b/backend/src/lib/utilities-client.js @@ -0,0 +1,33 @@ +// Client for the `utilities` app's internal API (meter readings, tariffs, energy costs). +// Mirrors the existing forecasting integration pattern used in +// routes/directors-forecast.js and routes/weekly-actual.js (X-API-Key header, +// base URL + key from env, throw on non-2xx). + +async function utilFetch(path) { + const apiKey = process.env.UTILITIES_API_KEY + const baseUrl = process.env.UTILITIES_URL || 'http://10.10.10.127:3080' + if (!apiKey) throw new Error('UTILITIES_API_KEY not configured') + const res = await fetch(`${baseUrl}${path}`, { + headers: { 'X-API-Key': apiKey }, + }) + if (!res.ok) throw new Error(`Utilities API ${res.status} — ${path}`) + return res.json() +} + +// GET /api/internal/readings?period=YYYY-MM&category= — raw reading summary +export async function getUtilityReadings(period, category) { + const qs = new URLSearchParams({ period }) + if (category) qs.set('category', category) + return utilFetch(`/api/internal/readings?${qs.toString()}`) +} + +// GET /api/internal/costs?period=YYYY-MM — cost breakdown by category (usage/standing/CCL/VAT/total) +export async function getUtilityCosts(period) { + const qs = new URLSearchParams({ period }) + return utilFetch(`/api/internal/costs?${qs.toString()}`) +} + +// GET /api/internal/estimate?period=current — projected cost for the open period +export async function getUtilityEstimate() { + return utilFetch(`/api/internal/estimate?period=current`) +} diff --git a/backend/src/routes/directors-forecast.js b/backend/src/routes/directors-forecast.js index 36090cc5..66c73e80 100644 --- a/backend/src/routes/directors-forecast.js +++ b/backend/src/routes/directors-forecast.js @@ -1,5 +1,6 @@ import { requireAuth, hasCap } from '../auth.js' import { pool, getSetting } from '../db.js' +import { getUtilityReadings, getUtilityCosts, getUtilityEstimate } from '../lib/utilities-client.js' async function fcFetch(path) { const apiKey = await getSetting('forecasting_api_key') || process.env.FORECASTING_API_KEY @@ -274,6 +275,25 @@ export async function directorsForecastRoutes(fastify) { [session.id] ) + // ── Utilities (meter readings/costs/estimate from the `utilities` app) ──── + // Fetched best-effort — a failure here (e.g. utilities app not deployed + // yet, or key not configured) must not break the rest of the report. + let utilities = { readings: null, costs: null, estimate: 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 [readings, costs, estimate] = await Promise.all([ + getUtilityReadings(period), + getUtilityCosts(period), + isCurrentPeriod ? getUtilityEstimate() : Promise.resolve(null), + ]) + utilities = { readings, costs, estimate, error: null } + } catch (err) { + utilities = { readings: null, costs: null, estimate: null, error: err.message } + } + return { year, month, forecast: { @@ -295,6 +315,7 @@ export async function directorsForecastRoutes(fastify) { pickup: { rooms: totalPickup, accomm: totalPickup * sessionRate, avg_rate: sessionRate }, weekly: Object.entries(weekBands).map(([label, v]) => ({ label, ...v })), snapshots: snapshotsRes.rows, + utilities, } })