Pull utilities readings/costs/estimate into Directors Forecast report
Adds a utilities-client.js mirroring the existing forecasting-client HTTP pattern (UTILITIES_URL/UTILITIES_API_KEY). Directors report response gains a separate `utilities` field; degrades gracefully with an error string if the utilities app is unreachable or unconfigured. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
21224c0ebe
commit
9e9e1d7400
2 changed files with 54 additions and 0 deletions
33
backend/src/lib/utilities-client.js
Normal file
33
backend/src/lib/utilities-client.js
Normal file
|
|
@ -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=<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`)
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue