Revert rota comparison to real oncosts; remove stale NI footnote
Now that the sync uses /api/v2/rosters/on/{date} (2026-07-25) instead
of /api/v2/schedules, real employer-oncost figures are available for
rota data, so gatherRotaVsActualData() no longer needs the base-cost-
only workaround — it respects show_oncosts like the rest of the app
again. Also removes the now-inaccurate Monthly.tsx footnote claiming
rota-based forecasts exclude NI, and the now-obsolete methodology note
that told the model to expect a swing from the base-cost fix (moot —
insight history is being cleared, so there's nothing to compare
against).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
e06d04d517
commit
1ffaece874
2 changed files with 15 additions and 37 deletions
|
|
@ -9,11 +9,6 @@ export const DEFAULT_DAILY_TOKEN_BUDGET = 5000
|
|||
const MAX_OUTPUT_TOKENS = 900
|
||||
export const MANUAL_RATE_LIMIT_MINUTES = 5
|
||||
|
||||
// One-time context so the model doesn't flag the 25/07 rota-methodology fix (oncost-inclusive
|
||||
// -> base-pay-only comparison) as an unexplained swing when it sees a prior insight's figures
|
||||
// change. Safe to delete this constant and its usage below once it's a few days stale.
|
||||
const METHOD_CHANGE_NOTE_UNTIL = '2026-07-29'
|
||||
|
||||
function toISODate(d) {
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||
}
|
||||
|
|
@ -152,15 +147,16 @@ export async function gatherPriorPeriodData() {
|
|||
// Department-level variance between scheduled (rota) and actual cost, over the trailing
|
||||
// window. Rota rows for past dates aren't deleted once synced, so this covers real history.
|
||||
//
|
||||
// Always compares on BASE cost for both sides, regardless of the show_oncosts display
|
||||
// setting: Workforce's schedules API never actually supplies employer NI oncosts (confirmed —
|
||||
// published_total_cost is identical to published_base_cost on every synced row), so comparing
|
||||
// oncost-inclusive actuals against rota would inflate every variance by ~14-20% for reasons
|
||||
// that have nothing to do with real overspend. Matches the same caveat already surfaced on the
|
||||
// Monthly page's rota-based forecast footnote.
|
||||
// Respects show_oncosts like the rest of the app. Previously hardcoded to base cost because
|
||||
// the old /api/v2/schedules-based sync never supplied real oncosts on rota data (confirmed:
|
||||
// published_total_cost == published_base_cost on every row) — fixed 2026-07-25 by switching
|
||||
// the sync to /api/v2/rosters/on/{date}, which does supply real cost_with_oncosts.
|
||||
export async function gatherRotaVsActualData(days = 28) {
|
||||
const actualCostCol = 'base_cost'
|
||||
const schedCostExpr = '(published_base_cost + unpublished_base_cost)'
|
||||
const showOncosts = (await getConfig('show_oncosts')) !== 'false'
|
||||
const actualCostCol = showOncosts ? 'total_cost' : 'base_cost'
|
||||
const schedCostExpr = showOncosts
|
||||
? '(published_total_cost + unpublished_total_cost)'
|
||||
: '(published_base_cost + unpublished_base_cost)'
|
||||
|
||||
const today = new Date()
|
||||
const fromStr = toISODate(new Date(today.getTime() - days * 86_400_000))
|
||||
|
|
@ -213,7 +209,7 @@ export async function gatherRotaVsActualData(days = 28) {
|
|||
}))
|
||||
.sort((a, b) => Math.abs(b.variance) - Math.abs(a.variance))
|
||||
|
||||
return { fromStr, toStr, totalDaysInWindow, depts }
|
||||
return { fromStr, toStr, totalDaysInWindow, depts, costBasis: showOncosts ? 'incl. employer NI oncosts' : 'base pay only' }
|
||||
}
|
||||
|
||||
// Forward-looking projection — reuses the app's existing tiered forecastDayCost() logic
|
||||
|
|
@ -374,19 +370,6 @@ export function buildPrompt(monthProgress, priorPeriod, rotaVsActual, forecast,
|
|||
lines.push('')
|
||||
}
|
||||
|
||||
if (recentInsights.length && toISODate(new Date()) <= METHOD_CHANGE_NOTE_UNTIL) {
|
||||
lines.push('## Methodology Note (25/07/2026)')
|
||||
lines.push(
|
||||
"The Rota vs Actual Variance comparison was corrected on 25/07/2026 to use base pay only on both " +
|
||||
"sides (previously actual cost included employer NI oncosts while rota did not, since Workforce's " +
|
||||
"schedules API never supplies oncosts — this inflated every rota variance figure by roughly 14-20%). " +
|
||||
"If a recent previous insight below shows a notably different rota variance figure for the same " +
|
||||
"department than today's, that is this correction taking effect, not a real change in performance — " +
|
||||
"do not describe it as unexplained or needing clarification."
|
||||
)
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
if (recentInsights.length) {
|
||||
lines.push('## Recent Previous Insights (most recent first — reference these, do not just repeat them)')
|
||||
for (const r of recentInsights) {
|
||||
|
|
@ -414,15 +397,13 @@ export function buildPrompt(monthProgress, priorPeriod, rotaVsActual, forecast,
|
|||
for (const d of priorPeriod.lastYearDepts) lines.push(` ${d.department_name}: ${fmtMoney(d.cost)}`)
|
||||
lines.push('')
|
||||
|
||||
lines.push(`## Rota vs Actual Variance (${fmtDateUK(rotaVsActual.fromStr)} - ${fmtDateUK(rotaVsActual.toStr)}, ${rotaVsActual.totalDaysInWindow} days)`)
|
||||
lines.push(`## Rota vs Actual Variance (${fmtDateUK(rotaVsActual.fromStr)} - ${fmtDateUK(rotaVsActual.toStr)}, ${rotaVsActual.totalDaysInWindow} days, ${rotaVsActual.costBasis})`)
|
||||
lines.push(
|
||||
"Figures below are BASE PAY only (excluding employer NI) on both sides — Workforce's rota/schedules " +
|
||||
"API doesn't supply oncosts, so this is the only basis that's genuinely comparable; do not describe " +
|
||||
"this variance using total-cost figures from other sections. Variance is computed only over days that " +
|
||||
"have rota data (\"days w/ rota\" below) — if that's well below the total window, treat the variance " +
|
||||
"as partial/uncertain due to missing rota history, not a confirmed overspend, and say so explicitly."
|
||||
"Variance is computed only over days that have rota data (\"days w/ rota\" below) — if that's well " +
|
||||
"below the total window, treat the variance as partial/uncertain due to missing rota history, not a " +
|
||||
"confirmed overspend, and say so explicitly rather than stating it as fact."
|
||||
)
|
||||
lines.push('Department | Actual base pay (days w/ rota) | Rota base pay | Variance | Variance % | Days w/ rota | Actual base pay (full window)')
|
||||
lines.push('Department | Actual (days w/ rota) | Rota | Variance | Variance % | Days w/ rota | Actual (full window)')
|
||||
for (const d of rotaVsActual.depts) {
|
||||
lines.push(
|
||||
`${d.department_name} | ${fmtMoney(d.actualOnRotaDays)} | ${fmtMoney(d.schedTotal)} | ` +
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue