Fix monthly trend chart — rolling 12-month average with LY comparison

Replace raw monthly totals with a proper rolling 12-month average trend.
Fetches 36 months of data; the 12 display points each show the average
of the 12 months ending on that point (TY) and the same window shifted
back a year (LY), producing a smooth upward trend vs last year.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-20 13:38:59 +00:00
parent b97358e9fb
commit 8da7e46d72
3 changed files with 33 additions and 22 deletions

View file

@ -92,9 +92,10 @@ export async function weeklyActualRoutes(fastify) {
const monthStart = new Date(year, month - 1, 1)
const monthDays = Math.round((weekEndDate - monthStart) / 86400000) + 1
// 12-month trend months (most recent last)
// 36 months of raw totals needed to compute rolling 12-month averages for
// 12 display points (indices 2335) plus their LY equivalents (indices 023)
const trendMonths = []
for (let i = 11; i >= 0; i--) {
for (let i = 35; i >= 0; i--) {
const d = new Date(year, month - 1 - i, 1)
trendMonths.push({ year: d.getFullYear(), month: d.getMonth() + 1 })
}
@ -223,22 +224,35 @@ export async function weeklyActualRoutes(fastify) {
},
}
// ── Monthly trend ─────────────────────────────────────────────────────────
const monthly_trend = trendMonths.map((tm, i) => {
// ── Monthly trend (rolling 12-month average) ──────────────────────────────
// Build raw monthly totals for all 36 months
const allMonthTotals = trendMonths.map((_, i) => {
const data = trendRevRaw[i]?.data ?? []
let rooms = 0, dry = 0, wet = 0
let total = 0
for (const d of data) {
rooms += parseFloat(d.accom?.otb ?? 0)
dry += parseFloat(d.dry?.otb ?? 0)
wet += parseFloat(d.wet?.otb ?? 0)
}
return {
label: `${MONTH_LABELS[tm.month - 1]}-${String(tm.year).slice(2)}`,
rooms, dry, wet,
total: rooms + dry + wet,
total += parseFloat(d.accom?.otb ?? 0)
+ parseFloat(d.dry?.otb ?? 0)
+ parseFloat(d.wet?.otb ?? 0)
}
return total
})
const rollingAvg = (arr, from, to) =>
arr.slice(from, to + 1).reduce((s, v) => s + v, 0) / (to - from + 1)
// Display points: indices 2335 (the last 12 months)
// TY rolling avg at index i = average of allMonthTotals[i-11 .. i]
// LY rolling avg at index i = average of allMonthTotals[i-23 .. i-12]
const monthly_trend = []
for (let i = 23; i < 36; i++) {
const tm = trendMonths[i]
monthly_trend.push({
label: `${MONTH_LABELS[tm.month - 1]}-${String(tm.year).slice(2)}`,
this_year_avg: rollingAvg(allMonthTotals, i - 11, i),
last_year_avg: rollingAvg(allMonthTotals, i - 23, i - 12),
})
}
return {
week_ending: toDateStr(weekEndDate),
week_start: toDateStr(weekStartDate),

View file

@ -349,7 +349,7 @@ function MonthProgressSection({
</div>
<div className="wa-chart-card">
<h4>Monthly Average Turnover Last 12 Months</h4>
<h4>Rolling 12-Month Avg Turnover Trend</h4>
<ResponsiveContainer width="100%" height={220}>
<LineChart data={trend} margin={{ top: 8, right: 8, left: 8, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
@ -357,9 +357,8 @@ function MonthProgressSection({
<YAxis tickFormatter={v => `£${(v/1000).toFixed(0)}k`} tick={{ fontSize: 10 }} width={52} />
<Tooltip formatter={fmtChartCcy} />
<Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} />
<Line type="monotone" dataKey="rooms" stroke={CHART_COLORS.rooms} strokeWidth={2} dot={false} name="Rooms" />
<Line type="monotone" dataKey="dry" stroke={CHART_COLORS.dry} strokeWidth={2} dot={false} name="Dry" />
<Line type="monotone" dataKey="wet" stroke={CHART_COLORS.wet} strokeWidth={2} dot={false} name="Wet" />
<Line type="monotone" dataKey="this_year_avg" stroke={CHART_COLORS.thisWeek} strokeWidth={2.5} dot={false} name="This Year" />
<Line type="monotone" dataKey="last_year_avg" stroke={CHART_COLORS.budget} strokeWidth={2} dot={false} name="Last Year" strokeDasharray="5 3" />
</LineChart>
</ResponsiveContainer>
</div>

View file

@ -102,10 +102,8 @@ export interface MonthProgress {
export interface MonthTrend {
label: string
rooms: number
dry: number
wet: number
total: number
this_year_avg: number
last_year_avg: number
}
export interface WeeklyActualData {