Engineering reference: the canonical, code-anchored source of truth for every metric below — including the helper function each surface calls, the exact SQL filters, and the cross-surface reconciliation contract — lives in `docs/metrics-canonical-definitions.md`. Live reconciliation proofs against the staging database are in `docs/metrics-reconciliation-proofs.md`. The cross-helper assertion suite is tests/metrics-reconciliation.test.ts. These three artefacts together are the reconciliation gate that must remain green before the reporting suite ships.Overview
AllSquads provides analytics dashboards for coaches, clubs, and AllSquads admins. Analytics are updated in real time and cover revenue, bookings, attendance, and player retention.
Note (in progress): the dashboard home (/dashboard) and dedicated analytics surface (/dashboard/analytics) are being split — action-oriented widgets stay on the home page, deeper analytics tabs move to/dashboard/analytics. Until that ticket ships, the analytics tabs described below may render under the existing/dashboardroute.
Period presets
Every period filter in the product uses the same calendar preset set — the dashboard home "Showing" selector (club + coach), the Club Analytics range selector, the coach Analytics date selector, the platform-admin Analytics range, the admin Payouts bulk export, and the club Payouts CSV export range (where the date inputs remain available for a custom range). The presets are calendar periods, not rolling windows:
- This week — Monday–Sunday containing today (club timezone)
- Last week — the previous Monday–Sunday week. Matches the Reports weekly view and that week's payout batch exactly.
- Month to date — 1st of the month → today
- Last month — the full previous calendar month
- Quarter to date — quarter start (Jan/Apr/Jul/Oct) → today
- Year to date — 1 January → today
- Last 12 months — the last 12 calendar months
Weeks are Mon–Sun in the club's timezone, the same convention Reports and payout batches use — so Dashboard, Analytics, Reports, and Payouts are directly comparable for the same period. The Reports prev/next navigation anchors on the club-local today as well, so "this week" in Reports always equals the "This week" preset. On the Facility Hire tab, every widget — KPI cards, revenue trend, utilisation heatmap, per-court table, and court drill-downs — covers the selected calendar window, and the heatmap header names the active period.
Period comparisons
KPIs render with delta arrows (▲ green / ▼ red) comparing against the previous equivalent calendar period: this week vs last week, month-to-date vs the same day-span of last month, year-to-date vs the same day-span of last year, and so on. Each delta is labelled with the period it compares against.
Exports
- CSV transaction export — available from the Payments tab on coach and club analytics. Exports the filtered transaction list as a downloadable CSV.
- Payout PDF report — each completed payout has a downloadable PDF receipt summarising the included transactions and the payout amount. Available from the Payouts tab.
Coach Analytics
Revenue
- Total revenue by period (day, week, month, custom range)
- Revenue by class
- Revenue by sport
- Gross revenue vs net payout (after the platform fee deducted at payout time)
Bookings
- Total bookings by period
- New vs returning player bookings
- Booking type breakdown (full term, drop-in, one-off, private lesson)
- Waitlist conversion rate
Attendance
- Overall attendance rate
- Attendance rate by class
- No-show rate
- Late cancellation rate (within policy window)
Class performance
- Revenue per class
- Attendance rate per class
- Capacity utilisation per class
- Sessions delivered vs sessions cancelled
Club Analytics
Club analytics extend coach analytics across all coaches and add court hire and financial reporting.
Revenue
- Total club revenue by period
- Revenue by coach
- Revenue by class type
- Court hire revenue
- Membership revenue
- Session pack revenue
- Fee breakdown (booking fee paid by players at checkout vs platform fee deducted at payout)
Club Revenue composition
"Club Revenue" on the Analytics → Summary tab is the headline KPI. It is the sum of four received-payment streams over the selected period:
Club Revenue = Class income + Court hire + Memberships + Pro shop- Class income — class bookings with status
confirmedorcompletedwhere money was actually received: paid online (Stripe, account credits, or a paid Stripe payment link) or collected offline (cash, or marked "already paid" outside the platform). Free/complimentary bookings, club-internal bookings, and session-pack redemptions (the pack purchase already counted) are not revenue and are excluded. The previous breakdown split this into "Stripe (Online)" and "Cash Paid", but court hire can also be paid via Stripe — so the rail axis was the wrong partition for the top-level Club Revenue split. Stream axis (Alex 2026-06-07) is canonical for that split and is unchanged. Alex 2026-07-19 ruling: the Class income tile on the Summary Revenue Breakdown card additionally shows an "Online (Stripe) $X · Cash $Y" sub-line decomposing the Class income figure by payment rail — mirroring the Financials tab's existing rail split. This is a sub-split of Class income only (stripePaid + cashPaid === classIncome); it does not reintroduce rail as the top-level partition and Court hire/Memberships/Pro shop are not rail-split. - Subscriptions vs Drop-in online (Alex Option A ruling, 2026-07-20): nested one axis deeper than the rail split, under the "Online (Stripe)" figure specifically, the card shows a "↳ Subscriptions $A · Drop-in online $B" line splitting the online rail by payment MODEL rather than payment method. Subscriptions is the sum of paid weekly
subscription_payment_rowsinvoices (recurring class-subscription charges, recognised on the date each invoice was charged). Drop-in online is every other online class payment — single-session drop-ins, term-upfront lump sums, camps, one-off bookings — anything paid online that isn't a recurring subscription invoice. Drop-in online is derived, not independently computed:dropInOnlineRevenue = stripePaid − subscriptionsRevenue, soSubscriptions + Drop-in online === Online (Stripe)always, by construction, regardless of data shape. Coach-side analytics (/dashboard/coach/analytics/revenue-split) surfacessubscriptionsRevenueas an additive field for parity but does not derive a coach-side drop-in-online figure — coachstripePaidhas never included subscription money (a pre-existing, separate gap from the club-side figure). - Court hire — court hire bookings with status
confirmed(price collected at booking time). - Memberships — membership purchases, counted on their payment date (memberships have no session to recognise against). Memberships are platform-held like every other Stripe payment and settle through the same weekly payout batch. They appear as their own line in the Summary Revenue Breakdown card and squad-type donut, the Financials tab (Membership Revenue KPI + monthly column), Reports (both Performance and Payout views, with their own breakdown row), Top Squads (reconciling row) and the club Payouts page Current payout figure.
- Pro shop — pro shop product purchases paid online, counted on their payment date (pro shop orders have no session to recognise against, same basis as memberships). Pro shop sales are platform-held and settle through the same weekly payout batch. They show their revenue and order count on the Summary Revenue Breakdown card and appear as a "Pro Shop Revenue" KPI plus monthly column on the Financials tab, and in the club Reports + weekly payout breakdown.
Cash Outstanding (pending cash bookings) is not counted in Club Revenue — it is promised money, not received. It is shown beneath the Revenue Breakdown card as "Cash Outstanding (not in Club Revenue)" and will roll into Club Revenue once the cash is collected and the booking flips to confirmed.
The Revenue Breakdown card on the Summary tab renders the stream components side by side with a "Subtotal = Club Revenue" reconciliation row so the tiles visibly add up to the headline.
Note: Analytics surfaces (Summary / Classes / Players / Court Hire / Team / Financials tabs) are Performance / accrual — revenue is counted on the recognition date per BOOKING_RECOGNITION_DATE_EXPR (a booking's earliest booked session). Camps recognise on the camp START day (per-day camps recognise daily; a whole-camp booking recognises in full on the start day), superseding the earlier end-of-camp rule. Cash payouts (money settled to your Stripe account) live on /dashboard/payments and on /dashboard/reports → Payouts section, and use the booking-date (cash) basis. The two bases answer different questions and will not match for any window where bookings span the period boundary — this is by design.
Forecast vs Actual on the monthly Revenue chart (2026-09): because revenue recognises on a booking's session date, the MONTHLY chart can legitimately show a bucket for a future month — a term whose first session lands next month recognises its whole (lump-sum) amount then, even though it was booked and paid today. To keep that from reading as an error, the chart draws a future month's bar as a hatched Forecast segment (a texture over the same Actual colour, not a new colour) instead of solid Actual, and the in-progress current month splits into its own already-happened (Actual) portion plus whatever is still to come this month (Forecast) when the split can be computed. The chart legend names both; hovering a bar states which segment its dollars belong to. No total changes — this only recolours dollars that were already on the chart. Weekly buckets and the compare ("vs previous period") view are unaffected.
Pre-cutover "paid direct" annotation: payments charged before the platform-held cutover were destination charges — the money went straight to the club's Stripe account and is not in the weekly payout batch. On the Reports Payout (cash) view those amounts are flagged with a badge — "$X paid direct (pre-cutover) — not in weekly batch" — so a window whose Performance revenue exceeds its batch payout is explained, not silent.
Bookings
- Total bookings across all classes
- New player acquisition (first booking per player by period)
- Player retention (players with 2+ bookings vs single-booking players)
- Repeat booking rate
- Booking type distribution
Court hire utilisation
- Total court hire bookings by period
- Utilisation rate per court (booked hours vs available hours)
- Revenue per court
- Member vs non-member court hire breakdown
- Peak usage times (by hour of day, day of week)
Attendance
- Club-wide attendance rate
- Attendance by coach
- Attendance by class
- No-show and late cancellation rates
Contacts and CRM
- Total active contacts
- New contacts by period and source (booking, manual, import)
- Contacts by sport
- Contacts with active membership
- Credit balances outstanding
Financial
- Gross revenue vs net payout
- Refunds and credits issued
- Outstanding credits liability
- Payout history
Reports (period rollups)
Clubs have a dedicated reporting surface at Dashboard → Reports (/dashboard/reports) — separate from Analytics. Reports answer "how am I tracking over time?" with calendar-aligned period rollups, while Analytics is for real-time exploration and ad-hoc filtering.
Tabs
- Weekly — current ISO week (Monday–Sunday) + 11 prior weeks of trend.
- Monthly — current calendar month + 11 prior months of trend.
- Quarterly — current calendar quarter (Q1=Jan–Mar … Q4=Oct–Dec) + 7 prior quarters.
- Financial year — current AU FY (1 July → 30 June) + 4 prior FYs.
Per tab
- Summary cards: Total revenue, Cash collected, Bookings, Players, each with the previous-period delta (WoW / MoM / QoQ / YoY). Total revenue composition is Stripe + Cash + Court Hire (same canonical definition as the Analytics → Summary tab).
- Trend chart: Bars of total revenue across the trend window. Used to spot growth / decline at a glance.
- By squad type table: Players, bookings, cash, revenue broken down by effective squad type (Group / Social / Term / Private / Camp / Court hire). Uses the same
deriveSquadTyperules as the Squad-type donut on Analytics so Term bookings with legacysquad_type='group'bucket correctly. - Classes this period table: Per-class revenue, cash, bookings, players for the active window.
- Drill-down: Click "View bookings" on any row → drawer with per- booking detail (player, class/court, payment, amount). Drawer reuses the
/financials/revenue-breakdownendpoint scoped to the active window so the numbers reconcile with the row clicked.
Reconciliation
Reports figures reconcile with the Analytics → Summary tab Club Revenue for the same period. The route enforces summary.payoutCents === sum(bySquadType.payoutCents) (invariant — historic field name retained for backwards-compat; the field now carries gross revenue) before responding; mismatches surface as a "Reconciliation warning" banner on the report (also logged server-side for follow-up). Terminology is aligned so the "Reconciles" badge matches the Summary tab the operator compares against. Reports' class-income figures (Summary cards, By squad type table, Classes this period table) now exclude complimentary/internal/pack bookings using the same payment-method partition Summary uses — pre-fix Reports and the Financials tab both counted that money while Summary didn't, so the two "reconciling" surfaces agreed with each other but disagreed with the Summary tab they claim to reconcile with.
Two ways to read your money: Performance and Payout
Every money figure on AllSquads is one of exactly two things, and each one is labelled so you always know which you are looking at.
- Performance — revenue for the classes that RAN in the period. A term counts on the day of its first session; camps count per the camp's own rule; everything else counts on the day the session ran. This is the "how is the business doing?" number, and it is what Analytics' money cards, the Reports Performance summary and the "Revenue over time" bars all show.
- Payout — the money actually paid to your bank for that period's payout batch, including any correction top-ups and any carried-over amounts from the old platform. This is what the Payouts tab and drawer, the Reports Payout summary and the "Payouts over time" bars show, and it always equals the transfer that reached your account.
Two surfaces showing the same view for the same period will show the same number to the cent. If they ever do not, that is a bug — please report it.
The two views legitimately differ for the same week, and that is the point: cash you collected yourself is revenue but never lands in a transfer, credits pay for a place without new money arriving, and a week that has run may not have been paid out yet.
Counts follow the same discipline. "Players" on a payout means DISTINCT PEOPLE — a child who attends three sessions in the week is one player, not three. "Bookings" is counted the same way on Reports and Analytics, so the two tabs never disagree.
Payouts you have not approved yet
A payout week is only shown to a club once it has been approved and paid. Until then every club-facing surface — the Payouts tab, Club Home's payout card, the weekly pulse tile and the Reports Payout view — reads "Awaiting approval" with no dollar figure, and the week counts as $0.
That is deliberate. A pending week's amount can still change before it is approved (a fee-tier change, a price correction, a top-up), so publishing a figure would invite you to reconcile against a number that then moves. The week appears with its real amount the moment the transfer goes out.
For the same reason, the label "Money paid to you" only ever appears against money that has actually been transferred to your Stripe account. Where you see "Payout" instead, the money is either awaiting approval or there was none for that window.
All of these surfaces read one shared calculation, so they cannot show you different amounts for the same week. The platform fee shown on Analytics → Financials is the fee that was actually deducted from that payout — not a re-calculation at today's rate.
Source endpoints
GET /dashboard/club/reports/:period(period inweekly | monthly | quarterly | fy). Optional?anchor=YYYY-MM-DDto view a historical period.- Drill-down rows:
GET /dashboard/club/analytics/financials/revenue- breakdown?type=class|court_hire|total&from=...&to=....
Using Analytics Effectively
Identifying underperforming classes
Sort classes by attendance rate or revenue. Classes with consistently low attendance and high cancellation rates are candidates for rescheduling, repricing, or archiving.
Tracking player retention
The retention metric shows what percentage of players make a second booking after their first. Low retention on a specific class may indicate the class is not meeting expectations. Compare retention by coach to identify coaching quality signals.
Court hire optimisation
Use peak usage times to identify high-demand slots. Consider premium pricing for peak times. Identify underutilised courts and off-peak slots that could be promoted with discounts.
Revenue per session
Calculate revenue per session to identify which classes generate the most value per hour of coaching time. Useful for prioritising class types and scheduling.
Credit liability monitoring
Outstanding credits represent a future revenue obligation to the club (players can redeem credits for future bookings). Monitor the outstanding credits balance to understand cash flow implications.
Admin Analytics (AllSquads Platform)
Available to AllSquads platform administrators only.
Platform overview
- Total platform revenue by period
- Total bookings across all clubs and coaches
- Active clubs, coaches, and players
- Platform fees collected (booking fee + platform fee streams reported separately)
User analytics
- New user registrations by role and period
- User activation rate (users who complete onboarding and make/receive a first booking)
- User retention by cohort
Booking analytics
- Bookings by class type, sport, and state
- Cancellation rates and patterns
- Waitlist conversion rates
Financial
- Total booking fees and platform fees collected
- Payout volumes
- Refund and credit volumes (including ledger entries on credit issuance and redemption)
- Stripe processing fees
Reviews and moderation
- Review submission rate (reviews per booking)
- Average rating by club, coach, and sport
- Reviews pending moderation
Taxonomy and content
- Sports and class type distribution
- Geographic distribution of clubs and players