Skip to content

Instantly share code, notes, and snippets.

@mattpocock
Created October 19, 2026 13:50
Show Gist options
  • Select an option

  • Save mattpocock/a0092a9d28fc19eb1c1e24253b57de65 to your computer and use it in GitHub Desktop.

Select an option

Save mattpocock/a0092a9d28fc19eb1c1e24253b57de65 to your computer and use it in GitHub Desktop.
Cohort 005, The Hero Moment: Admin Platform Analytics spec (paste into an issue, word for word)

Admin Platform Analytics: revenue and enrolment across the platform

A Platform Admin can see revenue and enrolment for the whole platform over a date range: per course, per Instructor, and month by month. Today the only analytics page is the Instructor's own (/instructor/analytics), which shows one Instructor their own courses. Nobody can see the platform as a whole.

Everything here reads the tables that already exist: purchases, refunds, courses and enrollments. No schema changes.

What the Platform Admin sees

A new page, /admin/analytics, next to the other admin pages (admin.courses, admin.refunds, …).

Date range. A from/to range at the top, defaulting to the last 12 months up to today. It lives in the URL (?from=YYYY-MM-DD&to=YYYY-MM-DD), so a report can be linked. An invalid or reversed range falls back to the default instead of throwing.

Platform totals for the range:

  • Gross revenue: the sum of purchases.amount_paid for Purchases made in the range.
  • Refunded: the sum of refunds.amount_refunded on Approved Refund Requests against those Purchases.
  • Net revenue: gross minus refunded.
  • Purchases: how many Purchases were made in the range.
  • New Enrollments: how many Enrollments started (enrollments.enrolled_at) in the range.

Per course: one row per course with at least one Purchase or Enrollment in the range. It shows the course, its Instructor, Purchases, gross, refunded, net and new Enrollments. Sorted by net revenue, highest first.

Per Instructor: the same figures rolled up across each Instructor's courses, one row per Instructor. Sorted by net revenue, highest first.

Monthly trend: one row per calendar month (UTC) in the range, showing gross, refunded, net, Purchases and new Enrollments. Months with no activity still appear, with zeros.

Rules

  • Refunds count against the Purchase they refund. An Approved Refund Request is subtracted in the period, course and Instructor of its Purchase, not in the month it was decided. Open and Declined Refund Requests subtract nothing.
  • Money is in the same integer unit as purchases.amount_paid and is shown with the app's existing price formatting.
  • Dates are UTC. from and to are both inclusive whole days.
  • Platform Admins only. Anyone else, including an Instructor, gets the same response the other admin pages give a non-admin. Use the existing permissions module; don't write a new check.

Acceptance criteria

  • A Platform Admin can open /admin/analytics and see the totals, per-course table, per-Instructor table and monthly trend for the default range.
  • Changing the range changes every figure, and the range survives a reload.
  • With one Purchase and one Approved Refund Request for the full amount, net revenue for that course, its Instructor, its month and the platform total is zero, and gross shows the Purchase.
  • A Declined or Open Refund Request leaves net revenue unchanged.
  • A month with no Purchases and no Enrollments shows as a row of zeros.
  • A Student or an Instructor who visits /admin/analytics is refused.
  • The figures come from a module under app/modules/ with an entry point the tests call directly against a test database, as instructor-analytics does. The loader only calls it.

Known characteristic: do not fix

purchases.created_at and courses.instructor_id have no index. Every date-ranged query here is a full scan of purchases, and every per-Instructor rollup scans courses. That is intended. Do not add either index, or any other index or migration, as part of this spec. Something downstream is meant to find the slow queries for itself.

Out of scope

  • Any schema change, index or migration.
  • Charts. Tables are enough for this spec.
  • CSV export.
  • Filtering by category, country or Purchasing-Power Price.
  • Splitting Individual Purchases from Team Licences.
  • Changing the Instructor's own analytics page.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment