Monthly.tsx and Dashboard.tsx only ever fetched actuals for the current
period (month-start..month-end, or a 7-day lookback for the week view),
but forecastDayCost's same-weekday fallback hops back up to 42 days
looking for a match. For any early-period date whose hop landed before
the fetch window started, that data was never in memory at all — it
silently fell through to the 'none' tier (£0) once published rota ran
out, rather than finding a real historical match.
ai-insights.js's gatherForecastData already fetched a proper 42-day
lookback; these two pages didn't. Widened both fetches to match, with
explicit >= period-start filters on the actual-sum calculations so the
extra history is only ever used by the hop fallback, never counted
twice into "actual".
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>
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>
Legend was sorted to mirror bar stack order, making it hard to tell
segments apart at a glance; reverse it. Tooltip also rendered behind
the legend div when they overlapped — raise its z-index above it.
Distributes the monthly wage budget across days proportionally to each
day's share of last year's same-day-of-week sales, so pacing on Weekly/
Monthly views reflects real demand shape (e.g. weekend-heavy) rather than
an even calendar split. Falls back to the old flat day-count split when
PY sales data is unavailable. Weekly also now sources each day's budget
from its own calendar month, so weeks spanning a month boundary split
correctly across both months' budgets.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The forecasting API's total.otb is booked-to-date only; total.forecast adds
expected pickup and equals otb for past dates anyway. Using otb-only for
future days understated expected net sales in the current-week/month
forecast sections. Relabelled "(OTB)" cards to "(Forecast)" to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both views already showed % Net Sales with a PY % sub-line, but lacked a
raw-£ Net Sales card with PY value + delta% — the style already used for
past weeks/months. Adds it consistently across current-period, forecast,
and past-period sections in both views.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes an edge case where days more than 7 days past the actual cutoff had no
valid prior-week source day (itself a forecast day), silently contributing
£0. Now walks back in 7-day steps until landing on an actual day, so the
last complete actual week tiles forward for the rest of the month.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Monthly: fetches previous month actuals; adds % delta vs prior month full
alongside existing vs PY column in the dept breakdown table.
Weekly: expanded 14-day actuals fetch (prev+current week) so prior-week
per-dept data is available for both forecast and comparison. Two-row summary
cards for current week (WTD actuals + full-week forecast). Past weeks keep
single-row layout. Dept table gains vs PY and vs Prev Wk delta columns;
budget columns use WTD pro-rata for current week, 7-day proportion for past.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows (actual_mtd - PY_MTD) / PY_MTD for current month rows,
(actual - PY_full) / PY_full for past months. Red if wages up vs PY,
green if down. Total row uses same basis. Matches department by ID
against pyDepts so departments with no PY data show —.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both Weekly and Monthly now cut off actuals at yesterday (not today) to avoid
partial clockins and open timesheets skewing the WTD/MTD figures and % net sales.
PY comparison periods also cap at the equivalent yesterday for fair comparison.
Monthly rota forecast removed — all future days use prior-week same-day actuals
from yesterday back, which is more reliable than partially-published future rotas.
Monthly summary cards split into two rows for current month:
Row 1 (MTD to yesterday): Actual MTD · Budget pro-rata · % Budget MTD · % Net Sales MTD
Row 2 (Full month): Forecast EOM · Monthly Budget · % Budget (Forecast) · % Net Sales (OTB)
Net sales fetched for full month so row 2 % net sales uses OTB forecast revenue.
Past months show one row (full month actuals only).
Weekly: budget pro-rata now counts days Monday to yesterday for current week;
dept totals filtered to yesterday for current week.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rota forecasting: only trust Workforce rota for the current Mon–Sun week —
beyond that use prior-week actuals to avoid incomplete next-week rotas
deflating the EOM forecast.
% vs Budget cards (Weekly + Monthly): show as coloured headline number
(green/amber/red) instead of small pct-badge chip, matching other cards.
Forecast EOM: PY sub-label shows full prior-year month total + % delta,
replacing the static "rota + prior-week actual" note once PY data exists.
Achieves this by fetching the full PY month rather than MTD-capped range,
then deriving both MTD (for Actual MTD card) and full-month (for EOM card).
Backfill depth: 13 → 25 months so past months in Rolling 12 have PY wage
data available (viewing Dec 2024 needs Dec 2023 = 19 months ago).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stores per-employee shift costs in wage_actuals_detail (APPROVED shifts
only). Sync fetches employee name map from /api/v2/users alongside dept
names. Clicking any dept row in Weekly or Monthly opens a modal showing
Employee · Shifts · Cost · % of Dept, fetched on demand.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stores full PY department breakdown from already-fetched pyActRes.
A toggle button (collapsed by default) expands an inline table showing
PY wages per dept, % of PY total, and % of PY net sales. Label adapts
to PY WTD / PY MTD for current partial periods.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows prior year equivalent % (wages/net sales) as a sub-label under the
current period value — labelled PY WTD/MTD for current partial periods,
PY for complete past periods.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Weekly: cap pyToStr at elapsed days (not full Mon-Sun), and filter
py_sales to past days only — both give WTD vs WTD. Labels show
'PY WTD' for current week, 'PY' for complete past weeks.
Monthly: pyToStr already capped at same day-of-month; salesTo already
capped at today. Labels now show 'PY MTD' for current month, 'PY' for
complete past months.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Show PY value + % change as a subtitle line under the period card,
same pattern as the original PY sales sub-label.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Weekly/Monthly: PY Wages summary card (fetches equivalent week/month last year)
- Weekly/Monthly: PY Net Sales as its own card (was a sub-label, now prominent)
- Weekly/Monthly: dept table gains '% of Total' column
- Monthly: PY comparison caps at same day-of-month for fair PYTD comparison
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
toISOString() returns UTC — in BST (UTC+1) dates computed from new Date()
land one day early. Replace with local date components via fmt()/localStr().
Affects Weekly, Monthly, Rolling12Weeks, Rolling12Months.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full wage cost reporting app — weekly/monthly views, rolling 12-week/12-month
history, budget management, Workforce API sync with SSE backfill, net sales
via forecasting public API, department filter, CSV export.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>