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:
parent
b97358e9fb
commit
8da7e46d72
3 changed files with 33 additions and 22 deletions
|
|
@ -92,9 +92,10 @@ export async function weeklyActualRoutes(fastify) {
|
||||||
const monthStart = new Date(year, month - 1, 1)
|
const monthStart = new Date(year, month - 1, 1)
|
||||||
const monthDays = Math.round((weekEndDate - monthStart) / 86400000) + 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 23–35) plus their LY equivalents (indices 0–23)
|
||||||
const trendMonths = []
|
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)
|
const d = new Date(year, month - 1 - i, 1)
|
||||||
trendMonths.push({ year: d.getFullYear(), month: d.getMonth() + 1 })
|
trendMonths.push({ year: d.getFullYear(), month: d.getMonth() + 1 })
|
||||||
}
|
}
|
||||||
|
|
@ -223,22 +224,35 @@ export async function weeklyActualRoutes(fastify) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Monthly trend ─────────────────────────────────────────────────────────
|
// ── Monthly trend (rolling 12-month average) ──────────────────────────────
|
||||||
const monthly_trend = trendMonths.map((tm, i) => {
|
// Build raw monthly totals for all 36 months
|
||||||
|
const allMonthTotals = trendMonths.map((_, i) => {
|
||||||
const data = trendRevRaw[i]?.data ?? []
|
const data = trendRevRaw[i]?.data ?? []
|
||||||
let rooms = 0, dry = 0, wet = 0
|
let total = 0
|
||||||
for (const d of data) {
|
for (const d of data) {
|
||||||
rooms += parseFloat(d.accom?.otb ?? 0)
|
total += parseFloat(d.accom?.otb ?? 0)
|
||||||
dry += parseFloat(d.dry?.otb ?? 0)
|
+ parseFloat(d.dry?.otb ?? 0)
|
||||||
wet += parseFloat(d.wet?.otb ?? 0)
|
+ parseFloat(d.wet?.otb ?? 0)
|
||||||
}
|
|
||||||
return {
|
|
||||||
label: `${MONTH_LABELS[tm.month - 1]}-${String(tm.year).slice(2)}`,
|
|
||||||
rooms, dry, wet,
|
|
||||||
total: rooms + dry + wet,
|
|
||||||
}
|
}
|
||||||
|
return total
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const rollingAvg = (arr, from, to) =>
|
||||||
|
arr.slice(from, to + 1).reduce((s, v) => s + v, 0) / (to - from + 1)
|
||||||
|
|
||||||
|
// Display points: indices 23–35 (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 {
|
return {
|
||||||
week_ending: toDateStr(weekEndDate),
|
week_ending: toDateStr(weekEndDate),
|
||||||
week_start: toDateStr(weekStartDate),
|
week_start: toDateStr(weekStartDate),
|
||||||
|
|
|
||||||
|
|
@ -349,7 +349,7 @@ function MonthProgressSection({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="wa-chart-card">
|
<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}>
|
<ResponsiveContainer width="100%" height={220}>
|
||||||
<LineChart data={trend} margin={{ top: 8, right: 8, left: 8, bottom: 0 }}>
|
<LineChart data={trend} margin={{ top: 8, right: 8, left: 8, bottom: 0 }}>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
|
<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} />
|
<YAxis tickFormatter={v => `£${(v/1000).toFixed(0)}k`} tick={{ fontSize: 10 }} width={52} />
|
||||||
<Tooltip formatter={fmtChartCcy} />
|
<Tooltip formatter={fmtChartCcy} />
|
||||||
<Legend iconSize={10} wrapperStyle={{ fontSize: 11 }} />
|
<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="this_year_avg" stroke={CHART_COLORS.thisWeek} strokeWidth={2.5} dot={false} name="This Year" />
|
||||||
<Line type="monotone" dataKey="dry" stroke={CHART_COLORS.dry} strokeWidth={2} dot={false} name="Dry" />
|
<Line type="monotone" dataKey="last_year_avg" stroke={CHART_COLORS.budget} strokeWidth={2} dot={false} name="Last Year" strokeDasharray="5 3" />
|
||||||
<Line type="monotone" dataKey="wet" stroke={CHART_COLORS.wet} strokeWidth={2} dot={false} name="Wet" />
|
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -102,10 +102,8 @@ export interface MonthProgress {
|
||||||
|
|
||||||
export interface MonthTrend {
|
export interface MonthTrend {
|
||||||
label: string
|
label: string
|
||||||
rooms: number
|
this_year_avg: number
|
||||||
dry: number
|
last_year_avg: number
|
||||||
wet: number
|
|
||||||
total: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WeeklyActualData {
|
export interface WeeklyActualData {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue