Compare full-week forecast against a full prev week, not a partial one

The "Prev wk" figure shown under Forecast Full Week was reusing
prevCost, which is deliberately scoped to "same elapsed days as WTD"
(e.g. only Mon-Wed of last week if today's Wednesday) — correct for
the per-department table's WTD-vs-WTD "vs Prev Wk" column, but wrong
paired with totalForecast, which projects a full 7 days. Comparing a
full-week projection against a few days of last week made prev week
look artificially low and the delta look inflated.

Added prevFullCost (full Mon-Sun of the prior week — already present
in the 14-day actuals fetch) for the full-week comparison specifically;
left prevCost/totalPrev untouched for the table's WTD comparisons.
This commit is contained in:
jtricerolph 2026-08-06 07:51:07 +00:00
parent 2ce51b036d
commit dfc863250c

View file

@ -198,7 +198,8 @@ export default function Weekly() {
department_name: string
wtdCost: number // actual WTD (or full week for past weeks)
forecastFull: number // full week forecast via prior-week same-day actuals
prevCost: number // prev week same elapsed days (or full prev week for past weeks)
prevCost: number // prev week same elapsed days (or full prev week for past weeks) — for WTD-vs-WTD comparisons
prevFullCost: number // full prev week (Mon-Sun) — for comparing against the full-week forecast
}
const deptRows: DeptRow[] = depts.map(dep => {
const wtdCost = Object.entries(dep.days)
@ -223,12 +224,17 @@ export default function Weekly() {
.filter(([d]) => d >= prevWeekFrom && d <= prevCutoff)
.reduce((s, [, v]) => s + v.cost, 0)
return { department_id: dep.department_id, department_name: dep.department_name, wtdCost, forecastFull, prevCost }
const prevFullCost = Object.entries(dep.days)
.filter(([d]) => d >= prevWeekFrom && d <= prevWeekTo)
.reduce((s, [, v]) => s + v.cost, 0)
return { department_id: dep.department_id, department_name: dep.department_name, wtdCost, forecastFull, prevCost, prevFullCost }
}).sort((a, b) => b.forecastFull - a.forecastFull)
const totalWTD = deptRows.reduce((s, d) => s + d.wtdCost, 0)
const totalForecast = deptRows.reduce((s, d) => s + d.forecastFull, 0)
const totalPrev = deptRows.reduce((s, d) => s + d.prevCost, 0)
const totalPrevFull = deptRows.reduce((s, d) => s + d.prevFullCost, 0)
const totalPyWages = pyWages ?? 0
const pctBudgWTD = budgetWTD != null && budgetWTD > 0 ? (totalWTD / budgetWTD) * 100 : null
@ -320,7 +326,7 @@ export default function Weekly() {
<div className="summary-card">
<div className="label">Forecast Full Week</div>
<div className="value">{fmtMoney(totalForecast)}</div>
{totalPrev > 0 && <div className="sub">Prev wk {fmtMoney(totalPrev)}{pyPct(totalForecast, totalPrev)}</div>}
{totalPrevFull > 0 && <div className="sub">Prev wk {fmtMoney(totalPrevFull)}{pyPct(totalForecast, totalPrevFull)}</div>}
</div>
<div className="summary-card">
<div className="label">Budget Full Week</div>