Widen actuals fetch window so the hop-back forecast fallback can reach history
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".
This commit is contained in:
parent
221a5598c0
commit
2ce51b036d
2 changed files with 19 additions and 6 deletions
|
|
@ -92,7 +92,6 @@ export default function Dashboard() {
|
||||||
|
|
||||||
const weekFrom = mondayOf(new Date())
|
const weekFrom = mondayOf(new Date())
|
||||||
const weekTo = addDaysStr(weekFrom, 6)
|
const weekTo = addDaysStr(weekFrom, 6)
|
||||||
const prevWeekFrom = addDaysStr(weekFrom, -7)
|
|
||||||
const weekMonthKeys = Array.from(new Set([monthKeyOf(weekFrom), monthKeyOf(weekTo)]))
|
const weekMonthKeys = Array.from(new Set([monthKeyOf(weekFrom), monthKeyOf(weekTo)]))
|
||||||
const weekDates = Array.from({ length: 7 }, (_, i) => addDaysStr(weekFrom, i))
|
const weekDates = Array.from({ length: 7 }, (_, i) => addDaysStr(weekFrom, i))
|
||||||
const weekElapsed = weekDates.filter(d => d <= yesterdayStr).length
|
const weekElapsed = weekDates.filter(d => d <= yesterdayStr).length
|
||||||
|
|
@ -104,11 +103,19 @@ export default function Dashboard() {
|
||||||
const monthDates = Array.from({ length: dim }, (_, i) => `${monthKey}-${String(i + 1).padStart(2, '0')}`)
|
const monthDates = Array.from({ length: dim }, (_, i) => `${monthKey}-${String(i + 1).padStart(2, '0')}`)
|
||||||
const monthElapsed = monthDates.filter(d => d <= yesterdayStr).length
|
const monthElapsed = monthDates.filter(d => d <= yesterdayStr).length
|
||||||
|
|
||||||
|
// forecastDayCost hops back up to 42 days looking for a same-weekday actual — fetch
|
||||||
|
// that much extra history before each period start so early-period dates can actually
|
||||||
|
// find it, instead of falling through to the 'none' tier once rota runs out. The actual
|
||||||
|
// sums below stay explicitly bounded to weekFrom/monthFrom, so this extra history is
|
||||||
|
// only ever used for the hop fallback, never double-counted into "actual".
|
||||||
|
const weekActualsFrom = addDaysStr(weekFrom, -42)
|
||||||
|
const monthActualsFrom = addDaysStr(monthFrom, -42)
|
||||||
|
|
||||||
const [weekActRes, weekSchedRes, weekSalesRes, monthActRes, monthSchedRes, monthSalesRes, budgetRes] = await Promise.all([
|
const [weekActRes, weekSchedRes, weekSalesRes, monthActRes, monthSchedRes, monthSalesRes, budgetRes] = await Promise.all([
|
||||||
getActuals(prevWeekFrom, weekTo),
|
getActuals(weekActualsFrom, weekTo),
|
||||||
getScheduled(weekFrom, weekTo),
|
getScheduled(weekFrom, weekTo),
|
||||||
getNetSales(weekFrom, weekTo),
|
getNetSales(weekFrom, weekTo),
|
||||||
getActuals(monthFrom, monthTo),
|
getActuals(monthActualsFrom, monthTo),
|
||||||
getScheduled(monthFrom, monthTo),
|
getScheduled(monthFrom, monthTo),
|
||||||
getNetSales(monthFrom, monthTo),
|
getNetSales(monthFrom, monthTo),
|
||||||
getBudgets(),
|
getBudgets(),
|
||||||
|
|
@ -149,7 +156,7 @@ export default function Dashboard() {
|
||||||
// ── Month ─────────────────────────────────────────────────────
|
// ── Month ─────────────────────────────────────────────────────
|
||||||
const monthDeptRows = monthActRes.departments.map(dep => {
|
const monthDeptRows = monthActRes.departments.map(dep => {
|
||||||
const actual = Object.entries(dep.days)
|
const actual = Object.entries(dep.days)
|
||||||
.filter(([d]) => d <= yesterdayStr)
|
.filter(([d]) => d >= monthFrom && d <= yesterdayStr)
|
||||||
.reduce((s, [, v]) => s + v.cost, 0)
|
.reduce((s, [, v]) => s + v.cost, 0)
|
||||||
let forecast = actual
|
let forecast = actual
|
||||||
const schedDep = monthActRes.forecast_method === 'rota'
|
const schedDep = monthActRes.forecast_method === 'rota'
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,12 @@ export default function Monthly() {
|
||||||
const toStr = `${monthStr}-${String(dim).padStart(2, '0')}`
|
const toStr = `${monthStr}-${String(dim).padStart(2, '0')}`
|
||||||
const isCurrentMonth = year === todayYear && month === todayMonth
|
const isCurrentMonth = year === todayYear && month === todayMonth
|
||||||
|
|
||||||
|
// forecastDayCost hops back up to 42 days looking for a same-weekday actual — fetch that
|
||||||
|
// much extra history before the month start so early-month dates can actually find it,
|
||||||
|
// instead of falling through to the 'none' tier once rota runs out. actualMTD/etc. below
|
||||||
|
// still filter to >= fromStr, so this extra history is only ever used for the hop fallback.
|
||||||
|
const actualsFromStr = fmt(addDays(new Date(fromStr + 'T00:00:00'), -42))
|
||||||
|
|
||||||
// Prior year: always fetch full month (derive both MTD and full totals from one call)
|
// Prior year: always fetch full month (derive both MTD and full totals from one call)
|
||||||
const pyDim = daysInMonth(year - 1, month)
|
const pyDim = daysInMonth(year - 1, month)
|
||||||
const pyFromStr = `${year - 1}-${String(month).padStart(2, '0')}-01`
|
const pyFromStr = `${year - 1}-${String(month).padStart(2, '0')}-01`
|
||||||
|
|
@ -80,7 +86,7 @@ export default function Monthly() {
|
||||||
try {
|
try {
|
||||||
// Net sales: full month — OTB/forecast for future dates, actuals for past dates
|
// Net sales: full month — OTB/forecast for future dates, actuals for past dates
|
||||||
const [actRes, schedRes, salesRes, budRes, pyActRes, pmActRes] = await Promise.all([
|
const [actRes, schedRes, salesRes, budRes, pyActRes, pmActRes] = await Promise.all([
|
||||||
getActuals(fromStr, toStr),
|
getActuals(actualsFromStr, toStr),
|
||||||
getScheduled(fromStr, toStr),
|
getScheduled(fromStr, toStr),
|
||||||
getNetSales(fromStr, toStr),
|
getNetSales(fromStr, toStr),
|
||||||
getBudgets(),
|
getBudgets(),
|
||||||
|
|
@ -136,7 +142,7 @@ export default function Monthly() {
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [fromStr, toStr, pyFromStr, pyToStr, pmFromStr, pmToStr, isCurrentMonth, yesterdayStr, year, month, pyDim])
|
}, [fromStr, toStr, actualsFromStr, pyFromStr, pyToStr, pmFromStr, pmToStr, isCurrentMonth, yesterdayStr, year, month, pyDim])
|
||||||
|
|
||||||
useEffect(() => { load() }, [load])
|
useEffect(() => { load() }, [load])
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue