wages/backend/src/lib/forecast.js
jtricerolph 95da5ea237 Add AI cost insights dashboard; rota-informed forecast method
AI Insights: a daily Claude-generated wage cost briefing covering
month-to-date pace vs budget, prior-month/prior-year comparison,
rota-vs-actual variance by department, a rota-informed forecast to
month-end, employee-level anomalies, and wage cost as a % of revenue.
Runs on a configurable daily schedule or on demand, gated by a manual
5-minute rate limit and a daily token budget. Uses the Anthropic key
configured centrally in Portal → Settings → Integrations.

Also includes the rota-vs-repeat-pattern forecast method (published/
draft rota tiers with same-weekday fallback) already built into the
Weekly/Monthly views, and adds a .gitignore for node_modules/dist.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 20:43:18 +00:00

29 lines
1.3 KiB
JavaScript

// Backend port of wages/frontend/src/lib/forecast.ts's forecastDayCost — keep both in sync.
const MAX_HOPS = 6 // 6 * 7 = 42 days back, comfortably within the 35-day actuals sync window
function addDaysStr(dateStr, n) {
const d = new Date(dateStr + 'T00:00:00')
d.setDate(d.getDate() + n)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}
// Tiered forecast cost for a single department + date: actual (if present) → published
// rota → draft rota (if includeUnpublished) → same-weekday recurring fallback (7-day hops).
export function forecastDayCost(dateStr, actualDays, scheduledDays, includeUnpublished) {
let probe = dateStr
for (let hop = 0; hop <= MAX_HOPS; hop++) {
const actual = actualDays?.[probe]
if (actual != null) return { cost: actual.cost, tier: 'actual' }
const sched = scheduledDays?.[probe]
const hasPublished = (sched?.published_shift_count ?? 0) > 0
const hasUnpublished = (sched?.unpublished_shift_count ?? 0) > 0
if (hasPublished || (includeUnpublished && hasUnpublished)) {
const cost = (sched?.published_cost ?? 0) + (includeUnpublished ? (sched?.unpublished_cost ?? 0) : 0)
return { cost, tier: hasPublished ? 'rota-published' : 'rota-draft' }
}
probe = addDaysStr(probe, -7)
}
return { cost: 0, tier: 'none' }
}