/admin/insights is the user-side counterpart to /admin/runway. Where Runway answers "what's our financial picture?", Insights answers "what's our cohort doing?". Both are read-only, both pull from live data on page load, and both are designed so an investor or co-founder can read them without ever seeing customer PII.
Who can see it
Anyone with the kpi.read permission. Same three roles as Runway:
- Investor — read-only access to financials + KPIs + runway. No PII anywhere.
- Co-founder — broad operator access including KPI views.
- Super-admin — holds every permission.
Other admin roles (admin, moderator, verifier, finance) do not have this permission and are redirected to /dashboard if they open the URL directly. The page shows up as a card on /admin (Quick Links → "Insights") for permission holders.
Two privacy primitives baked into the page
Both apply automatically before any number is computed, so you don't have to remember to filter anything yourself.
Internal-team exclusion. Every aggregate filters out accounts in the internal_users registry — the founder's team, test accounts, and the agcoms-alias accounts used for development. Without this filter, the numbers would over-count because the team uses the platform like real users do (creating businesses, signing up to tiers, placing test orders). With the filter, the cohort number reflects actual external users.
As of the most recent audit: 37 total profiles in the system, 16 tagged as internal, leaves 21 real-cohort users. Every aggregate on this page is computed against the 21.
No bucket suppression — role gate is the privacy boundary. Geographic spread shows every bucket, including single-user ones. The privacy contract for this page is the kpi.read role gate (investor + co-founder + super-admin only, all trusted insiders), not row-level suppression.
Earlier versions of the page suppressed buckets below a threshold (initially <5, then <2). Both versions hurt UX at small cohort sizes — the <5 cut left the page reading mostly em-dashes; the <2 cut still hid singletons that the audience had every right to see. The honest fix is full transparency at this layer; if this page ever surfaces to a less-trusted audience the suppression logic should come back.
The four-tile summary strip
At the top of the page, four numbers you can read in 5 seconds:
- Real users (all-time) — total profiles minus internal-team. Today's baseline number.
- Active 30d — sum of the "last 7 days" + "7-30 days ago" sign-in buckets. Proxy for monthly active users (MAU).
- Est. MRR — placeholder monthly recurring revenue estimate. Computed as
Pro count × ₦2,000 + Growth count × ₦5,000 + Institutional count × ₦20,000. Prices are placeholders, not driven from the live tier-pricing settings, so treat this as a rough indicator rather than a finance-grade number. - Waitlist (all-time) — total waitlist sign-ups since launch.
The five aggregates
1. Growth velocity (last 90 days)
Two side-by-side counts: real signups + new waitlist sign-ups in the trailing 90 days. Each card shows the total + a per-week average (total ÷ 13). The per-week number is the more useful one for spotting trend changes — a 3/week signup pace is meaningfully different from a 10/week pace.
2. Tier distribution
Four cards: Free, Pro, Growth, Institutional. Each shows the count + share-of-cohort percentage. Use this to see at a glance what the tier mix looks like — a healthy platform shows a paying-tier ratio that's growing relative to Free over time.
3. Marketplace (last 30 days)
Four metrics from marketplace_orders filtered to status completed in the trailing 30 days:
- Orders — count of completed orders. Sub-line shows "X attempted" (including in-flight + cancelled) so you can see the completion rate.
- GMV — gross merchandise value, the sum of order amounts.
- Commission — what AgroYield kept (the 3% platform fee, deducted from seller payout).
- Take rate — commission ÷ GMV, rendered as a percentage. Should hover around 3% by design; deviations signal either fee-structure drift or refund noise.
If the marketplace has zero completed orders in the window, the GMV and take-rate cards render —.
4. Active users (sign-in buckets)
Five buckets categorising every real user by recency of their last sign-in:
- Last 7 days — most engaged cohort. Proxy for weekly active users.
- 7-30 days ago — still active, just not this week.
- 30-90 days ago — dormant but not lost.
- >90 days — likely churned but not formally cancelled.
- Never — confirmed an account but never signed back in. Could be confirmation-email-clicked-then-bounced users.
This is a retention proxy, not a true cohort-retention curve. Cohort retention (per-week cohorts measured at day 7 / 30 / 60) requires event-stream instrumentation we haven't built yet — see the deferred list below.
5. Geographic spread
The top 12 self-reported locations from profiles.location, ranked by user count. Two cells per row: count + share-of-cohort percentage. Every bucket is shown — including ones with a single user (see the privacy primitives section for the rationale).
Locations are normalised on read (lowercase, strip a trailing "Nigeria", collapse whitespace, then match against the canonical AGR-286 lists for proper casing — FCT Abuja, Port Harcourt, etc.). So the 6 raw variants of "Abuja" in the database collapse into one bucket.
If there are more than 12 distinct locations, the long tail rolls into a footer row labelled +N other locations. No data is hidden — the footer just keeps the table scannable.
(not specified) appears as its own row counting users who haven't filled in a location on their profile. (country-only) counts users who typed just "Nigeria" — useful to distinguish from the truly blank case.
What's deferred and why
The Insights page shipped 5 of the 8 candidate aggregates from the original ticket. The other three need infrastructure that doesn't exist yet:
- Cohort retention curves — true week-by-week cohort tracking (what % of week-N signups are still active at day 7, 30, 60, 90). Needs event-stream instrumentation; today we proxy retention via the sign-in buckets above.
- Module engagement heatmap — % of users who've touched each platform module (Community, Marketplace, Mentorship, Prices, Grants, Research). Needs per-module
module_visitedevent tracking that isn't wired yet. - Traffic-source attribution — % of signups via direct, OAuth (Google / LinkedIn), waitlist conversion, etc. Today we capture some source data but not consistently, so a published metric would be wrong as often as it's right.
Plus two product-iteration features we deliberately didn't ship in the first cut:
- Date-range filter / cohort selector — current view is fixed windows (90d for growth, 30d for marketplace). An interactive selector would add ~1-2 hours of work; we'll add it once the page has enough usage to know which windows people actually want.
- Charts (vs tables) — the page is text + cards today, not bar charts or line charts. Adding Recharts would make the trends more readable for large date ranges; deferred until the data warrants it.
How to read the page in practice
An investor doing a quarterly check-in: scroll all five sections, note the trend in Growth velocity (signups + waitlist) and Active users (the 7d + 30d buckets vs everything else), eyeball the Tier distribution share-of-cohort to see paying-tier traction.
A co-founder doing a weekly review: glance at the four-tile summary strip, scroll Marketplace + Active users for the leading indicators, ignore Geographic spread unless an outreach push is on.
A founder doing investor prep: pair Insights with Runway in the same window — Insights shows the user side, Runway shows the financial side. Together they're the picture.
What this page is NOT
- A finance-grade accounting view. The Est. MRR uses placeholder per-tier prices; the real numbers require pulling from
admin_settings(which the page deliberately doesn't yet, to keep the first-cut simple). - A real-time dashboard. Every aggregate is computed on page load. If the platform processed a payment 30 seconds ago, a reload picks it up; there's no auto-refresh and no WebSocket stream.
- A drill-in tool. Every aggregate is at the cohort level. To investigate a specific user, that's a separate admin surface (and gated behind
users.readwhich investor + co-founder do not hold).
Related pages
/admin/runway— the financial counterpart. Burn vs revenue, vendor breakdown, recent subscription payments. Same audience, same access gate./admin/vendors— vendor subscription tracker (the source of the burn numbers on Runway)./admin?tab=members— full members tab with PII (gated onusers.read; investor + co-founder do not have access).
Where the data comes from
profilestable — signups (filtered to real cohort), tier distribution, location aggregationauth.userstable — last sign-in timestamps for the active-users bucketswaitlist_signupstable — waitlist countmarketplace_orderstable — orders, GMV, commission (filtered to statuscompleted)internal_userstable — registry of internal-team accounts to exclude from all aggregates
All five are server-rendered fresh on every page load. No caching layer; refreshing the page reloads the data.

