Objective
- Replace the current period-keyed forecast model with a month-first budget model so leadership can enter a specific budget amount for each calendar month at the brand (instance) level.
- Keep monthly budget values as the authoritative source of truth; weekly numbers are derived from monthly values, not entered as the primary record.
- Preserve compatibility for existing instance-level forecast consumers so dashboard surfaces such as Revenue, Future Capacity, and Annual Forecast Progress continue to read meaningful forecast data through the current APIs.
- Support optional segment-level budget rows that sum to the company monthly total for each month.
- Lay normalized per-instance data for a future admin cross-brand dashboard (ATT-752) — v1 is instance-only.
Background
- Forecast data is stored per company in
attik-backend/src/models/forecastSchema.ts with year, _companyId, totals, and segmentForecasts, where entries are keyed by period-start date strings rather than by month.
- The settings flow in
attik-backend/src/routes/forecast.ts and attik-backend/src/util/functions/forecast/getTotalsWithRange.ts reads those date-keyed totals for arbitrary ranges. GET /forecast/range returns range totals; GET /dashboard/forecast-status in attik-backend/src/routes/dashboard.ts uses the same helper with period division enabled.
- The UI in
attik-frontend/src/app/tools/settings/forecast/ (ForecastBody.tsx, ForecastTable.tsx) is weekly-first (pay-period or custom period length), which does not match how leadership plans in monthly budgets.
- Product direction (Chris / transcript): monthly entry is primary; week-level shaping redistributes each month's total across weeks without changing the month unless the user edits the month amount.
- v1 is instance-only — Settings → Forecast per company. Admin aggregation remains follow-on.
Product decisions (v1)
- Monthly totals are always locked while adjusting week-level shaping within a month.
- Overlap weeks between months — when a calendar week spans two months, allocate that week's budget proportionally by days in each month (per company timezone). Monthly totals remain locked.
- Calendar months in company timezone — months are calendar months in the company timezone, consistent with other dashboard date boundaries.
- Default week distribution — if the user enters a monthly amount and does not adjust sliders, split that month evenly across weeks that touch the month (before any user reshaping).
- Segment budgets sum to company total — optional segment rows allocate the company monthly budget; for each month, segment amounts must sum to the company monthly total. Segment rows are optional; only segments with values appear as selectable categories in widgets.
- One-time migration — all instances move to the new experience. Legacy weekly period-keyed data is not converted into authoritative monthly budgets. Monthly values start empty; brands re-enter monthly budgets and optional week shaping.
- Empty / unset forecast — when no monthly budget is configured (including post-migration), instance widgets show an empty state prompting setup rather than implying a zero forecast is intentional.
Scope
Backend (attik-backend)
- Extend or replace
src/models/forecastSchema.ts for month-first budgeting per _companyId / year, with derived or persisted weekly breakdown and optional segment allocations that reconcile to company monthly totals.
- Update
src/routes/forecast.ts and src/util/functions/forecast/getTotalsWithRange.ts so range reads honor calendar months, proportional overlap-week allocation, and even-split defaults where shaping was not customized.
- Keep
GET /dashboard/forecast-status and GET /forecast/range returning sensible totals for arbitrary date ranges from the normalized model.
- Enforce segment-sum-to-company validation on write where segment rows are present.
Frontend (attik-frontend)
- Rebuild or extend Settings → Forecast in
src/app/tools/settings/forecast/ with:
- Monthly budget grid (calendar months, company timezone context)
- Week-level shaping controls per month that redistribute without unlocking the monthly total
- Segment rows (optional) with validation that monthly segment sums match company total
- Maintain existing access (
loginCheck('/forecast', 'settings-admin') on page.tsx).
- After migration, show setup prompt / empty state when monthly budgets are unset.
- Ensure instance consumers (
Revenue.tsx, ForecastProgress.tsx, FutureCapacity.tsx via forecast/range) handle unset forecast with empty-state UX where appropriate.
Out of scope for v1
- Admin Attik cross-brand budget entry or dashboard UI
- ATT-752 company-wide KPI dashboard implementation
- ATT-753 widget scoping split
- ATT-1471 forecast/capacity availability bugs
- Changes to actual revenue calculation logic
Done when
- [ ] Company admin can enter monthly budget amounts per calendar month (company timezone) for a year in Settings → Forecast.
- [ ] Monthly total stays locked while week sliders reshape distribution within the month.
- [ ] Weeks spanning two months split proportionally by days in each month; monthly totals remain correct.
- [ ] Default distribution for untouched months is even split across weeks touching the month.
- [ ] Optional segment rows sum to company monthly total each month; validation prevents save when out of balance.
- [ ] Normalized data persists per instance;
forecast/range and forecast-status return sensible range totals for Revenue / forecast progress / Future Capacity.
- [ ] One-time cutover: legacy weekly rows not auto-converted; monthly fields start empty; brands re-enter budgets.
- [ ] Unset forecast shows empty state prompting setup in settings and relevant dashboard widgets.
- [ ] No admin cross-brand UI required to ship v1.
References
- ATT-752
- ATT-753
- ATT-1471
- ATT-627
- ATT-1578
- ATT-734
attik-backend/src/models/forecastSchema.ts
attik-backend/src/routes/forecast.ts
attik-backend/src/util/functions/forecast/getTotalsWithRange.ts
attik-backend/src/routes/dashboard.ts
attik-frontend/src/app/tools/settings/forecast/ForecastBody.tsx
attik-frontend/src/app/tools/settings/forecast/ForecastTable.tsx
attik-frontend/src/components/dashboard/Revenue/Revenue.tsx
attik-frontend/src/components/dashboard/ForecastProgress/ForecastProgress.tsx
attik-frontend/src/components/dashboard/FutureCapacity/FutureCapacity.tsx
Diagram
flowchart TD
A[Company admin enters monthly budget] --> B[Monthly values stored per company and year]
B --> C[Optional segment rows sum to company monthly total]
B --> D[Weekly values derived from monthly totals]
C --> D
D --> E[Forecast range reads normalized data]
D --> F[Forecast status reads normalized data]
E --> G[Instance Revenue widget]
F --> H[Instance forecast progress widget]
B --> I[No admin cross-brand dashboard in v1]
B --> J[Unset budget shows empty state prompting setup]