Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesAnalyticsAnalytics — Data Model

Analytics — Data Model

Analytics is read-only and owns no tables. Each method projects from the operational schema.

Tables read

TableUsed in
subscriptionsMRR, plan distribution, outstanding debt, at-risk members.
payment_transactionsCollected MTD, revenue trend.
membershipsMembers summary, growth, activation, at-risk, coach overview.
usersAt-risk sample, members activation (Clerk linkage).
invitationsMembers activation (invited bucket).
class_sessionsPopular classes, class utilization.
class_typesSame.
bookingsSame.
plansPlan distribution.
workouts, workout_assignments, workout_results, personal_recordsWorkouts summary.
organizationsTimezone normalization where required (currency, locale).

Computed fields

MRR

For every active non-deleted subscription:

mrr_contribution = subscription.price_in_cents / INTERVAL_MONTHS[subscription.interval]

INTERVAL_MONTHS:

IntervalMonths
weekly0.2308
monthly1
quarterly3
yearly12

The constant lives in analytics.service.ts.

Member activation buckets

  • importedusers.clerk_id IS NULL AND no pending invitation.
  • invitedinvitations.status = 'pending'.
  • activatedusers.clerk_id IS NOT NULL.

activatedActive is the intersection of active membership ∧ Clerk-linked user. Use this denominator for engagement metrics — it excludes import placeholders.

Class utilization

bookings count / class_session.capacity averaged across sessions in the window. Sessions with capacity = null are excluded.

Trend buckets

Date-bucketed by date_trunc('month', x) for monthly trends. Labels are localized at render time on the client.

Indexes assumed

  • subscriptions(membership_id, status, deleted_at)
  • payment_transactions(organization_id, created_at, status)
  • memberships(organization_id, status, role)
  • class_sessions(organization_id, starts_at)
  • bookings(class_session_id, status)
  • workout_assignments(organization_id, scheduled_for)

Drops to these will produce visible latency regressions on the analytics tab.

CSV outputs

Synchronous GET /analytics/export?tab=<tab> writes a CSV directly to the response stream. Per-tab row shape:

TabRows
revenue-summary1 row (the summary object flattened).
revenue-trendN month rows { bucket, label, totalInCents }.
plan-distributionN plan rows { planId, planName, interval, activeSubscriptions, monthlyRevenueInCents }.
members-summary1 row.
members-growthN month rows.
popular-classesN rows.
class-utilizationbyClassType[] rows.
workouts-summary1 row.