Insights — Behavior
Surface and auth
GET /organizations/:orgId/insights→{ data: Insight[] }.MembershipsService.requireMembershiprequires an active membership;isStaffRoleadmits owner, admin, and coach. Ordinary members are forbidden.- Owner/admin receive all 20 rules. Coach requests evaluate only the seven operations/engagement rules; restricted finance, members, and leads rules never run for that request.
- Spotter’s read-only
analytics.org_insightstool currently has owner/admin scope and callsgetOrgInsightswithout a role override. Do not infer its access from the HTTP controller’s broader staff gate.
Populations and destinations
Every rule counts the people (or cases, tasks, sessions) it names once, uses
org-local day cutoffs, and either carries a destination that shows exactly its
population or a null destination with named samples to act on. The analytics
Reports workspace and its KPI snapshot are a separate surface, not part of
this feed. The destination routes’ supported URL hydration works
unconditionally.
Response and rendering
interface Insight {
id: InsightId; // identifiers in @taikan/shared
category: 'finance' | 'members' | 'operations' | 'engagement' | 'leads';
severity: 'info' | 'warning' | 'urgent';
count: number;
drilldownHref: string | null;
meta?: Record<string, unknown>;
}The server sends no display copy. The web dictionary uses
insights.items.<id>.{title,description}. The renderer prefixes valid root
destinations with /{lang}. A null href renders a non-navigating row, not a
link to a generic page or a fabricated filter.
For a nonlinked row, named samples can link through a server-supplied
/dashboard/... href or their membershipId. Missing names or destinations
do not become links. Samples are capped at three and are not a complete queue.
Overdue-task samples are informational: there is no per-task URL and no
supported overdue-only open-task filter.
engagement.checkins_to_review always expands the existing inline review
queue. engagement.forms_missing expands the existing outstanding-forms queue
when meta.actionable === true; the client follows that payload.
Rule catalog
Audience staff means owner/admin/coach. All other rows are owner/admin. Destinations below omit the client-added language prefix. A count of zero omits the rule.
| Rule id | Audience | Population / count | Severity | Destination / action |
|---|---|---|---|---|
finance.outstanding_debt | Owner/admin | The org’s Outstanding (sumOutstanding): positive debt on non-deleted subscriptions that are live or cancelled for non-payment, member seats that train; the same figure as the revenue summary and the payments dashboard. Count = distinct owing memberships; sum in meta | Warning | /dashboard/payments?tab=debt |
finance.failed_charges | Owner/admin | Failed charge/recurring transaction rows from the last seven rolling days | Warning; urgent ≥5 | /dashboard/payments?tab=transactions; no named sample exists, so the row keeps its destination |
finance.refunds_pending | Owner/admin | Nondeleted open manual_refund tasks; completed/cancelled excluded | Warning | /dashboard/tasks?tab=not_completed&type=manual_refund |
finance.join_checkout_failed | Owner/admin | Distinct active, nondeleted member memberships linked to pending/in-progress automatic payment_follow_up tasks in the same org, without a count cap | Urgent | /dashboard/tasks?tab=not_completed&type=payment_follow_up |
members.at_risk_billing | Owner/admin | Distinct members matching atRiskMemberCondition (a member seat that trains, membership active, with a past_due or debt subscription or any positive balance) — the predicate ?paymentStatus=at_risk filters on, so the count equals the list it opens | Info; warning ≥3; urgent ≥10 | /dashboard/members?paymentStatus=at_risk |
members.expiring_memberships | Owner/admin | Distinct active member memberships with an active, nondeleted subscription ending from now through seven days ahead, not cancelAtPeriodEnd | Info; warning ≥5 | null; member samples |
members.low_credits | Owner/admin | Distinct active member memberships with an active, nondeleted subscription with tracked credits ≤2 and no period end or end ≥now | Info | null; member samples |
members.notices_open | Owner/admin | Unresolved retention cases, kind request or notice, live nondeleted subscription, nondeleted org membership | Info; warning past acknowledgement SLA; urgent final-week uncontacted | /dashboard/tasks?tab=not_completed&type=cancellation_review |
members.join_never_paid | Owner/admin | Active, nondeleted join-link member older than 24h, no live subscription, activation history, presale withdrawal, or settled-money transaction | Warning; urgent ≥10 | /dashboard/members?paymentStatus=not_paying |
members.presale_withdrawn | Owner/admin | Active, nondeleted member with a nondeleted cancelled presale-withdrawal subscription and no live subscription | Warning; urgent ≥5 | /dashboard/members?paymentStatus=withdrawn |
members.recent_churn | Owner/admin | Active, nondeleted member with an activated, nondeleted subscription cancelled in the last 30 rolling days and no live replacement | Info; warning ≥3; urgent ≥10 | /dashboard/members?paymentStatus=churned |
members.invitations_lapsed | Owner/admin | Non-cancelled, nondeleted member memberships whose account status is lapsed: invited, never signed up, and every invitation expired or was revoked | Info; warning ≥10 | /dashboard/members?accountStatus=lapsed |
operations.tasks_overdue | Staff | Nondeleted open org tasks with due date strictly before org-local today; completed/cancelled excluded | Warning; urgent ≥10 | /dashboard/tasks?tab=not_completed&sortBy=dueDate&sortOrder=asc; task samples stay informational |
operations.sessions_missing_workout | Staff | Nondeleted published org sessions from now until next org-local midnight with no attached workout | Warning | null; program/date/session sample actions |
operations.waitlisted_bookings | Staff | Distinct booking memberships, with matching org ownership and no membership deletion, waitlisted on upcoming published, nondeleted org sessions | Info | null; up to three distinct session actions |
engagement.checkin_missed | Staff | Distinct active, nondeleted roster people with unarchived scheduled/sent check-ins strictly before the org-local 14-day cutoff | Info; warning ≥5; urgent ≥10 | null; member samples |
engagement.checkins_to_review | Staff | Unarchived answered check-in occurrences for the org | Info | null; existing inline review queue |
engagement.forms_missing | Staff | Distinct active, nondeleted roster people with pending, unarchived compliance instances | Info; warning ≥5 | null; existing inline outstanding-forms queue |
engagement.birthdays | Staff | Distinct active, nondeleted role=member memberships with birthdays on org-local today or the next six dates | Info | null; member samples |
leads.attention_needed | Owner/admin, only for a feed read without a caller; a caller’s feed carries the Sales queue’s leads.needs_action (and leads.unassigned for Sales managers) instead, see Sales workspace | Active org-linked leads in inbound_unanswered, overdue, due_today, or new_unanswered attention states | Urgent for inbound-unanswered/overdue; otherwise warning | /dashboard/leads/list?sort=attention |
Population and sample boundaries
Populations are rule-specific, not a global roster predicate:
- Expiring / low credits: require active membership and deduplicate count and sample by membership. The earliest qualifying expiry or lowest qualifying credit balance represents that person.
- Failed join checkout: matches
tasks.linkedUserIdto membership user within the task’s organization, requires active nondeletedrole=member, and counts all distinct memberships.taskIdsstays capped at 20 and the distinct-member sample at three; neither cap limits the headline. - Notices: count,
meta.open,unacknowledged, andfinalWeekUncontactedremain case counts. One member can contribute multiple cases. Only the member sample is deduplicated, soonest effective departure first. Cases include both requests awaiting notice issuance and notices; they are not derived solely fromcancelAtPeriodEnd. - Waitlist: checks membership ownership/deletion in addition to
session-program organization ownership. It does not add a membership
role/status filter.
meta.sessionscounts distinct sessions; session samples use the same eligibility predicate as the count. - Missed check-ins: the cutoff is org-local midnight fourteen calendar
dates before org-local today, converted to UTC with timezone/DST handling.
scheduledFor < cutoffis strict. Active/nondeleted roster and unarchived-occurrence predicates apply. - Missing forms: the count is distinct active roster people. The sample
takes each person’s earliest pending form before the three-person cap and
sets
actionable: true. - Birthdays: membership-only, with no subscription requirement;
deduplicates membership count/sample and includes
membershipIdfor actions. The seven month-day dates handle December/January wrap; samples sort by birthday offset. - Lapsed invitations: the same predicate as the roster’s
?accountStatus=lapsedfilter, so the count is the list it opens. - Debt, at-risk, lifecycle, and lead-attention predicates read stored state; stored debt is not replaced by receipts or a new accounting calculation.
The three lifecycle rules share liveSubExists() for nondeleted subscriptions
in pending, scheduled, active, past_due, paused, or debt status.
Their remaining history predicates define their individual populations; this
is not cross-rule deduplication for the entire feed.
Retention case severity and destination
Live subscription statuses are active, paused, past_due, and debt.
Only cases without retentionOutcome count. Acknowledgement SLA uses
cancellationNoticeAt ?? cancellationRequestedAt plus
organizations.retentionSlaHours (default 48); deadline ≤now with no
acknowledgement increments unacknowledged.
A case with no contact and an effective date ≤now+7 rolling days increments
finalWeekUncontacted (there is no additional lower bound). Any such case
makes severity urgent; otherwise any overdue acknowledgement makes it warning.
The row routes to the existing open cancellation_review task queue. The Members ?paymentStatus=ending roster remains
separate context and can include rows whose ending status is not an open
retention case. See dashboard behavior.
Supported destination URLs
- Tasks:
tab=not_completed&type=manual_refund|payment_follow_up|cancellation_reviewexpresses real existing task tabs/types. The page independently validates supported fields fromlistTasksQuerySchema, hydrates direct links and history, and resets pagination on filter changes. Date bounds apply only totab=date_range; invalid bounds are dropped.tab=overdueis unsupported and falls back to Today; it is not emitted as a new exact queue. - Payments:
tab=debthydrates the Debt tab; supported tab changes update the URL, and invalid tabs fall back to Transactions. No failed-charge/date filter is invented to make that rule navigable. - Schedule samples:
/dashboard/schedule?programId=<uuid>&weekStart=<YYYY-MM-DD>&session=<uuid>redirects to/dashboard/programs/<uuid>?tab=schedulewith individually validated date/session context. The sample date is the session’s org-local date. Missing/invalid program context falls back to/dashboard/programs; invalid optional date/session values are dropped. The existing program calendar owns week hydration and session detail. There is no invented global schedule filter or global waitlist/workout queue. - Members / leads: existing
paymentStatusand ranked lead-list routes above remain in use;status=past_dueis not a valid membership-status substitute forpaymentStatus=at_risk.
All destination hydration is unconditional. Destination APIs retain their own authorization.
Ordering, caching, and failures
The applicable 20-rule catalog (seven for coach) runs via Promise.all.
Null results are dropped; severity sorts urgent → warning → info, followed by
count descending. Service reads are recomputed per request; Action Center’s
client query has a five-minute stale time and can reuse server-prefetched data.
Some rules use multiple reads or post-query grouping. No measured latency,
single-query-per-rule, or universal-index claim is made.
| Failure | Behavior |
|---|---|
| A rule throws | The feed request rejects; there is no per-rule error isolation. |
| Membership/staff authorization fails | Forbidden; a nonexistent org is not a public empty-feed endpoint. |
Extension points
New rules require the shared InsightId, RULE_ROLES, service orchestration,
and matching en/he/ru insights.items entries. A destination must be supported
by its owning page; otherwise use null and only real samples/queues. Thresholds
remain local to each rule, with no dismissal, snooze, or per-org overrides.