Insights — QA Plan
This is a scenario plan, not an execution record. Runtime verification is recorded separately; no production, responsive-coverage, or latency claim is implied.
Auth and roles
Use GET /organizations/:orgId/insights.
| Scenario | Expected |
|---|---|
| Owner/admin with active org membership | 200; all 20 rules are eligible, empty rules omitted. |
| Coach with active org membership | 200; only the seven operations/engagement rules; finance, members, and leads rules are not evaluated. |
| Ordinary member or caller without active org membership | Forbidden. |
| Foreign/nonexistent org | Membership authorization prevents treating it as an empty public feed. |
Owner/admin analytics.org_insights | Read-only tool returns the same service projection. |
| Coach using Spotter | Do not expect the HTTP feed’s role scope to apply to analytics.org_insights: its current tool registration is owner/admin-only. The coach’s dashboard HTTP feed remains allowed. |
Organization isolation
| Scenario | Expected |
|---|---|
| Different organizations | No cross-org population, sample, or route-data leakage. |
Full 20-rule matrix
Use the exact destinations for every row. Seed isolated populations so an empty-rule expectation is not obscured by another legitimate rule.
| Rule | Population / boundary scenario | Expected |
|---|---|---|
finance.outstanding_debt | Positive debt on live, non-payment-cancelled and otherwise-cancelled rows; a former member who owes; a guardian_only seat | Live and non-payment-cancelled rows count (former member included), otherwise-cancelled and guardian_only do not; total equals revenue summary and payments dashboard outstanding (analytics-vocabulary.int.spec.ts); tab=debt. |
finance.failed_charges | Failed charge/recurring rows inside/outside rolling seven-day cutoff and other transaction types/statuses | Count only eligible rows; warning below five, urgent at five; Transactions href. |
finance.refunds_pending | Open/closed/deleted manual-refund tasks | Only nondeleted open tasks; warning; not_completed&type=manual_refund. |
finance.join_checkout_failed | More than 20 tasks, duplicate tasks for one person, foreign/inactive/deleted/staff memberships | Uncapped distinct active member count, same-org linked-user match, task IDs ≤20, unique sample ≤3; urgent; not_completed&type=payment_follow_up. |
members.at_risk_billing | Past-due, debt-status and positive-balance members; a cancelled membership, a coach and a guardian_only seat with past-due rows | Distinct-member scope; cancelled/staff/guardian_only excluded; count equals ?paymentStatus=at_risk list size (analytics-vocabulary.int.spec.ts); sample ≤3; info/3 warning/10 urgent. |
members.expiring_memberships | Multiple qualifying subscriptions per person, inactive member, cancelling/deleted/expired/outside-window subscription | Active-member dedup, earliest eligible expiry sample; now through now+7d inclusive; warning at five; member sample action, root null. |
members.low_credits | Multiple eligible balances, untracked credits, expired period, null period end, inactive member | Active-member dedup, lowest eligible balance sample; tracked balance ≤2 with time remaining/no end; info; root null. |
members.notices_open | Request + notice for same person, resolved case, deleted subscription, SLA and effective-date boundaries | Count cases, not unique members; deleted subscriptions excluded; unique sample ≤3. Severity info → warning past acknowledgement SLA → urgent when any qualifying uncontacted effective date ≤now+7d. |
members.join_never_paid | Join-link membership younger/older than 24h, live subscription, activation, presale withdrawal, settled/refunded money | Grace/history exclusions; warning/10 urgent; paymentStatus=not_paying. |
members.presale_withdrawn | Cancelled presale withdrawal, re-buy/live replacement, deleted subscription | Active-member win-back population; warning/5 urgent; paymentStatus=withdrawn. |
members.recent_churn | Activated cancellation inside/outside rolling 30 days and live replacement | Recent-churn scope; info/3 warning/10 urgent; paymentStatus=churned. |
members.invitations_lapsed | Invited member whose every invitation expired/was revoked, one with a live invitation, a signed-up member, a cancelled membership | Only non-cancelled lapsed members; info/10 warning; accountStatus=lapsed, and the roster filter lists the same people. |
operations.tasks_overdue | Due yesterday/today in org timezone; completed/cancelled/deleted tasks | Strictly before org-local today only; warning/10 urgent; not_completed sorted by due date; oldest three samples informational. |
operations.sessions_missing_workout | Published upcoming session today with/without workout, already started, next-local-day, deleted | Only eligible sessions from now to next org midnight; warning; valid program/date/session sample actions, no generic root queue. |
operations.waitlisted_bookings | Same member across sessions, duplicate booking rows, foreign/deleted membership, foreign/deleted/unpublished/past session | Distinct members with matching ownership/deletion; no role/status restriction; distinct-session meta and sample ≤3 share eligibility; root null. |
engagement.checkin_missed | Multiple unanswered occurrences per person; archived occurrence; inactive/deleted/foreign roster; boundary in DST timezone | Distinct active roster people; strict < cutoff; cutoff org-local midnight fourteen dates earlier; info/5 warning/10 urgent; membership sample actions. |
engagement.checkins_to_review | Answered, reviewed, archived check-in occurrences | Only answered/unarchived occurrences; info; existing inline review queue; root null. |
engagement.forms_missing | Several pending forms per person; archived/answered forms; inactive/deleted/foreign roster | Distinct active roster people; warning at five; earliest pending form per person before sample cap; inline queue. |
engagement.birthdays | Active member with no subscription, staff birthday, inactive/deleted member, today/+6/+7 dates, Dec→Jan | Membership-only scope; today through +6 inclusive; membership dedup and sample actions; info; no subscription requirement. |
leads.attention_needed | Unanswered inbound, overdue, due today, new unanswered, stale/waiting/archived/foreign lead | Shared lead attention states and clock; urgent for inbound/overdue, otherwise warning; sample ≤3; ranked lead-list href unchanged. |
Case count versus membership context
- Seed two unresolved request/notice cases on one eligible person. Count and severity metadata include both cases; sample names that person once.
- Follow
not_completed&type=cancellation_review; the Tasks tab and type are selected; it is not Members ending. - Open Members
paymentStatus=endingseparately. Treat it as an ending-roster view, not a claim that its row count equals open requests/notices.
Real URL hydration and actions
- Direct-load, reload, and use back/forward for the three task links:
tab=not_completed&type=manual_refund,payment_follow_up, andcancellation_review. Confirm matching tab/type and API fields. - A malformed supported field must not discard other valid task fields.
Date bounds are valid only for
date_range; reversed/invalid dates are dropped. Filter changes reset pagination. tab=overdueis unsupported and falls back to Today. Do not fabricate an overdue-only open-task filter./dashboard/payments?tab=debtselects Debt; supported tab navigation and history update state. Invalid tab falls back to Transactions. Failed charges link to Transactions without inventing a status/date filter.- Follow a named missing-workout/waitlist sample with valid UUID program and
session plus a real date. Confirm redirect to the correct program’s
tab=schedule, selected week, and matching session detail. - Missing/malformed program context falls back to Programs. Invalid optional date/session values are dropped individually; no global schedule queue is invented. Destination authorization still applies.
- Null-root rows have no root navigation. Named member/session sample links work where supplied; missing/unnamed samples do not create fake actions. Overdue-task samples remain informational.
- Check-in review expands inline; marking reviewed refreshes its queue/feed through the existing workflow. Missing forms expands the existing queue when actionable. Ask Spotter remains separate from row navigation.
Ordering, empty state, locale, and failures
- Seed no qualifying population across all 20 rules:
{ data: [] }and explicit Action Center all-clear state. - Mixed severities sort urgent → warning → info; ties use descending count.
- Check en/he/ru dictionary copy, count plurals, bounded sample names, language-prefixed destinations, and Hebrew logical-direction controls.
- A rule error rejects the feed request; there is no per-rule isolation.
- Record actual viewport and environment with any browser evidence; an 800×600 local smoke does not establish full responsive or production coverage.