Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesInsightsInsights — Behavior

Insights — Behavior

Surface and auth

  • GET /organizations/:orgId/insights → { data: Insight[] }.
  • MembershipsService.requireMembership requires an active membership; isStaffRole admits 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_insights tool currently has owner/admin scope and calls getOrgInsights without 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 idAudiencePopulation / countSeverityDestination / action
finance.outstanding_debtOwner/adminThe 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 metaWarning/dashboard/payments?tab=debt
finance.failed_chargesOwner/adminFailed charge/recurring transaction rows from the last seven rolling daysWarning; urgent ≥5/dashboard/payments?tab=transactions; no named sample exists, so the row keeps its destination
finance.refunds_pendingOwner/adminNondeleted open manual_refund tasks; completed/cancelled excludedWarning/dashboard/tasks?tab=not_completed&type=manual_refund
finance.join_checkout_failedOwner/adminDistinct active, nondeleted member memberships linked to pending/in-progress automatic payment_follow_up tasks in the same org, without a count capUrgent/dashboard/tasks?tab=not_completed&type=payment_follow_up
members.at_risk_billingOwner/adminDistinct 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 opensInfo; warning ≥3; urgent ≥10/dashboard/members?paymentStatus=at_risk
members.expiring_membershipsOwner/adminDistinct active member memberships with an active, nondeleted subscription ending from now through seven days ahead, not cancelAtPeriodEndInfo; warning ≥5null; member samples
members.low_creditsOwner/adminDistinct active member memberships with an active, nondeleted subscription with tracked credits ≤2 and no period end or end ≥nowInfonull; member samples
members.notices_openOwner/adminUnresolved retention cases, kind request or notice, live nondeleted subscription, nondeleted org membershipInfo; warning past acknowledgement SLA; urgent final-week uncontacted/dashboard/tasks?tab=not_completed&type=cancellation_review
members.join_never_paidOwner/adminActive, nondeleted join-link member older than 24h, no live subscription, activation history, presale withdrawal, or settled-money transactionWarning; urgent ≥10/dashboard/members?paymentStatus=not_paying
members.presale_withdrawnOwner/adminActive, nondeleted member with a nondeleted cancelled presale-withdrawal subscription and no live subscriptionWarning; urgent ≥5/dashboard/members?paymentStatus=withdrawn
members.recent_churnOwner/adminActive, nondeleted member with an activated, nondeleted subscription cancelled in the last 30 rolling days and no live replacementInfo; warning ≥3; urgent ≥10/dashboard/members?paymentStatus=churned
members.invitations_lapsedOwner/adminNon-cancelled, nondeleted member memberships whose account status is lapsed: invited, never signed up, and every invitation expired or was revokedInfo; warning ≥10/dashboard/members?accountStatus=lapsed
operations.tasks_overdueStaffNondeleted open org tasks with due date strictly before org-local today; completed/cancelled excludedWarning; urgent ≥10/dashboard/tasks?tab=not_completed&sortBy=dueDate&sortOrder=asc; task samples stay informational
operations.sessions_missing_workoutStaffNondeleted published org sessions from now until next org-local midnight with no attached workoutWarningnull; program/date/session sample actions
operations.waitlisted_bookingsStaffDistinct booking memberships, with matching org ownership and no membership deletion, waitlisted on upcoming published, nondeleted org sessionsInfonull; up to three distinct session actions
engagement.checkin_missedStaffDistinct active, nondeleted roster people with unarchived scheduled/sent check-ins strictly before the org-local 14-day cutoffInfo; warning ≥5; urgent ≥10null; member samples
engagement.checkins_to_reviewStaffUnarchived answered check-in occurrences for the orgInfonull; existing inline review queue
engagement.forms_missingStaffDistinct active, nondeleted roster people with pending, unarchived compliance instancesInfo; warning ≥5null; existing inline outstanding-forms queue
engagement.birthdaysStaffDistinct active, nondeleted role=member memberships with birthdays on org-local today or the next six datesInfonull; member samples
leads.attention_neededOwner/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 workspaceActive org-linked leads in inbound_unanswered, overdue, due_today, or new_unanswered attention statesUrgent 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.linkedUserId to membership user within the task’s organization, requires active nondeleted role=member, and counts all distinct memberships. taskIds stays capped at 20 and the distinct-member sample at three; neither cap limits the headline.
  • Notices: count, meta.open, unacknowledged, and finalWeekUncontacted remain 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 from cancelAtPeriodEnd.
  • Waitlist: checks membership ownership/deletion in addition to session-program organization ownership. It does not add a membership role/status filter. meta.sessions counts 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 < cutoff is 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 membershipId for 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=lapsed filter, 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_review expresses real existing task tabs/types. The page independently validates supported fields from listTasksQuerySchema, hydrates direct links and history, and resets pagination on filter changes. Date bounds apply only to tab=date_range; invalid bounds are dropped. tab=overdue is unsupported and falls back to Today; it is not emitted as a new exact queue.
  • Payments: tab=debt hydrates 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=schedule with 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 paymentStatus and ranked lead-list routes above remain in use; status=past_due is not a valid membership-status substitute for paymentStatus=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.

FailureBehavior
A rule throwsThe feed request rejects; there is no per-rule error isolation.
Membership/staff authorization failsForbidden; 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.