Skip to content

Client risk flags surface clients who may churn, stall, or need support before the coach misses the signal.

Risk flags are coach-facing operational alerts derived from adherence, missed logs, progress trend, communication, onboarding, and plan-following signals. The feature stores explainable risk state, dedupes repeated signals, routes actionable items into the coach task inbox, and records how the coach handled the risk.

Tags: V1 baseline retention signal · Explainable scoring · Inbox integrated · Audited handling

V1 baseline decisions

use deterministic, explainable rules before predictive modeling. Ship a seeded default rule set with admin-only preset tuning; coaches can create manual flags and handle flags, but cannot edit thresholds in Public V1. Critical flags trigger inbox priority plus a rate-limited coach push notification. Weight-stall rules require an approved goal direction and enough recent measurements. Planned travel, illness, deload, Ramadan, or vacation pauses escalation through an explicit client status note. Resolved flags stay as compact profile timeline history for 30 days.

Risk Flag Flows

System jobs evaluate client signals, create or refresh risk flags, and push actionable work into the coach workflow.

System Detects Risk

  1. A source context emits activity events such as DailyLogMissed, WorkoutSkipped, CheckInMissed, MeasurementTrendUpdated, or ClientMessageUnanswered.
  2. The risk signal projector updates normalized daily signal snapshots for the coach-client relationship.
  3. A scheduled evaluator compares signal windows against the active default or admin-selected RiskRuleSet preset for the workspace/package.
  4. When a threshold is crossed, the service opens or refreshes one active ClientRiskFlag per risk type and dedupe window.
  5. Medium, high, and critical flags emit ClientRiskFlagRaised so the coach task inbox can create or refresh a task. Critical flags also trigger one rate-limited coach push notification per flag per 24 hours.

Coach Reviews And Intervenes

  1. Coach sees the risk flag on dashboard, client list, client profile, and task inbox according to severity.
  2. Opening the flag shows the reason, evidence window, last client activity, related logs, and suggested next actions.
  3. Coach sends a message, updates the plan, requests a check-in, schedules a call, snoozes, dismisses, or marks handled outside the app.
  4. The intervention creates a RiskFlagAction. Supportive actions move the flag to MONITORING; invalid data, duplicate, planned pause, or explicit coach resolution can close it immediately with a note.
  5. Resolved flags remain visible in profile history and can reopen only after a new material signal crosses policy.

Flag Recovery

  1. Client resumes logging, replies, submits a check-in, completes workouts, or records new progress data.
  2. Signal snapshots update and the evaluator sees recovery criteria for the active flag.
  3. The flag moves to RECOVERING when evidence improves but still needs a short observation window.
  4. After the recovery window passes, the system auto-resolves with resolutionType=AUTO_RECOVERED.
  5. The inbox auto-resolves matching risk tasks through source completion events.

Manual Flag

  1. Coach can create a manual risk flag from client profile or message thread when an issue is visible outside system data.
  2. Manual flags require a risk type, severity, reason, and optional follow-up date. Manual critical flags also follow the rate-limited push policy.
  3. Manual flags use the same review, action, snooze, and resolution model as system flags.
  4. System evaluators can refresh the flag with evidence, but they cannot downgrade a manual severity without coach action.
  5. Manual flags are never visible to the client unless the coach creates a client-facing message, request, or plan change.

Domain Model

Risk flags belong to CoachWorkflow as operational risk state, while source contexts own the raw client activity.

Aggregates & Entities

  • ClientRiskFlag: aggregate root for one active or historical risk concern in a workspace and coach-client relationship.
  • RiskSignalSnapshot: read-model style record of computed signal windows used by the evaluator.
  • RiskRuleSet: versioned deterministic rule thresholds. V1 baseline ships system defaults plus admin-selectable workspace/package presets; individual coaches cannot edit thresholds.
  • RiskEvidence: immutable evidence summary attached to a flag version, including signal values and source references.
  • RiskFlagAction: append-only coach or system action taken against a flag.
  • RiskFlagPolicy: domain service for threshold evaluation, dedupe, severity, recovery, reopening, and permissions.

Value Objects & Rules

  • RiskType: LOW_ADHERENCE, MISSED_LOGS, WEIGHT_STALL, REPLY_GAP, ONBOARDING_STALLED, PLAN_NONCOMPLIANCE, MANUAL_CONCERN.
  • RiskSeverity: LOW, MEDIUM, HIGH, CRITICAL. Critical is reserved for immediate coach attention, not medical emergency routing.
  • RiskFlagStatus: OPEN, ACKNOWLEDGED, MONITORING, RECOVERING, RESOLVED, DISMISSED, ARCHIVED.
  • RiskEvidenceWindow: start date, end date, timezone, sample counts, missing data indicators, and source freshness.
  • RiskSuppressionReason: PLANNED_TRAVEL, ILLNESS, DELOAD, RAMADAN, VACATION, CLIENT_PAUSED, OTHER. Suppression requires reason, start/end dates, and actor attribution.
  • DataFreshnessPolicy: adherence, workouts, and onboarding signals need snapshots updated within 24 hours; communication signals within 1 hour; weight-stall evaluation requires a measurement in the last 14 days and at least 3 measurements in the evaluation window.
  • InterventionDisposition: message, nudge, check-in request, plan adjustment, or call scheduled moves the flag to MONITORING; invalid data, duplicate, planned pause, client paused, or explicit coach resolution can close it immediately.
  • One active flag per workspaceId, relationshipId, riskType, and dedupe window unless manual policy allows a separate flag.
  • Weight-stall rules are inactive unless the client has an approved goal direction, a current target metric, and the package includes measurement tracking.
  • Flags store summary evidence and source references only; raw logs, photos, messages, health answers, and nutrition details remain in source contexts.
ModelOwned ByImportant MethodsEmits
ClientRiskFlagCoachWorkflowraise(), refreshEvidence(), acknowledge(), recordAction(), snooze(), startRecovery(), resolve(), dismiss(), reopen()ClientRiskFlagRaised, ClientRiskFlagSeverityChanged, ClientRiskFlagResolved
RiskSignalSnapshotCoachWorkflow read modelfromSourceEvents(), calculateWindow(), markIncomplete()None
RiskRuleSetCoachWorkflowactiveForRelationship(), evaluate(), recoveryCriteria()RiskRuleSetUpdated
CoachTaskCoachWorkflowReceives risk flag source events and creates the actionable coach queue item.CoachTaskCreated, CoachTaskResolved
Source AggregateDailyExecution, ProgressAnalytics, Communication, Forms, ProgramsOwns logs, check-ins, measurements, messages, workouts, and submissions.Source-specific activity events consumed by risk signal projectors.

Table Structure

Persist current risk state, evidence, actions, rules, and normalized signal snapshots without duplicating source records.

V1 baseline Rule Presets

  • Low adherence: medium below 70% completion over 7 days with at least 5 expected tasks; high below 50%; critical is not auto-raised for adherence.
  • Missed logs: medium after 2 consecutive required daily logs missed; high after 4; critical only for coach-created manual concern.
  • Weight stall: medium after 21 days with less than 0.25% bodyweight movement per week toward goal and at least 3 measurements; high after 35 days. Requires approved goal direction and active measurement cadence.
  • Reply gap: medium after 48 hours without client reply to a coach question/request; high after 72 hours; critical after 7 days only for high-touch packages.
  • Onboarding stalled: medium after 2 days incomplete; high after 5 days. Skipped blocking questionnaire can feed this signal.
  • Plan noncompliance: medium below 60% workout/nutrition task completion over 7 days; high below 40% or 5 missed assigned tasks.

Configuration & Notifications

  • Workspace admins can choose the default, relaxed, or high-touch preset per workspace/package. Coaches can pause/snooze/dismiss/create manual flags but cannot edit thresholds in Public V1.
  • Critical severity creates dashboard/inbox priority and a coach push notification, rate-limited to one push per flag per 24 hours.
  • Planned travel, illness, deload, Ramadan, vacation, or client pause suppresses escalation and push notifications until the suppression end date, while preserving evidence snapshots.
  • Resolved or dismissed flags stay on the client profile timeline for 30 days, capped at 10 compact items, then move to historical/support views.
TablePurposeImportant ColumnsConstraints & Indexes
client_risk_flagsCurrent and historical risk flag state for client profile, dashboard, and inbox integration.id, workspace_id, relationship_id, client_user_id, risk_type, status, severity, source, dedupe_key, score, reason, latest_evidence_id, first_detected_at, last_detected_at, next_review_at, snoozed_until, suppressed_until, suppression_reason, last_push_sent_at, resolved_at, timeline_visible_until, created_at, updated_atPartial unique (workspace_id, dedupe_key) where status is OPEN, ACKNOWLEDGED, MONITORING, or RECOVERING; index (workspace_id, status, severity, next_review_at); index (relationship_id, status, risk_type).
client_risk_evidenceImmutable evidence versions for why a flag was raised or refreshed.id, risk_flag_id, rule_set_id, window_started_at, window_ended_at, timezone, summary jsonb, source_refs jsonb, created_atIndex (risk_flag_id, created_at desc); evidence JSON stores numeric signal summaries and safe source refs, not raw sensitive content.
risk_signal_snapshotsNormalized rolling signal windows used by evaluators and dashboards.id, workspace_id, relationship_id, signal_type, window_key, window_started_at, window_ended_at, value numeric, sample_count, missing_count, freshness_at, metadata jsonb, created_at, updated_atUnique (relationship_id, signal_type, window_key); index (workspace_id, signal_type, window_ended_at desc).
risk_rule_setsVersioned risk thresholds and recovery criteria.id, workspace_id, scope_type, scope_id, preset_key, version, status, rules jsonb, created_by_user_id, activated_at, retired_at, created_atOne active rule set per scope; append new versions instead of mutating active historical rules; V1 baseline rule edits require workspace admin role.
risk_flag_actionsAppend-only coach and system handling history.id, risk_flag_id, actor_user_id, action_type, linked_source_type, linked_source_id, note, metadata jsonb, created_atIndex (risk_flag_id, created_at); idempotency unique on linked action id where present.
outbox_eventsTransactional handoff to task inbox, notifications, analytics, and mobile badge updates.id, aggregate_type, aggregate_id, event_type, payload jsonb, status, available_atStandard outbox worker with retries; payload contains flag id, relationship id, risk type, severity, and safe summary.

Bounded-context references

Use plain UUID references for source records from DailyExecution, Communication, Forms, Programs, Nutrition, and ProgressAnalytics. Risk flags can validate visibility through application services but must not navigate or mutate source aggregates directly.

Lifecycle Policy

Risk records must explain operational decisions without exposing more client data than the coach needs for intervention.

Flag Lifecycle

  • OPEN when a threshold is first crossed or a coach creates a manual flag.
  • ACKNOWLEDGED when a coach views and accepts ownership of the concern.
  • MONITORING when the coach intervened and the system is waiting for new client activity.
  • RECOVERING when the latest evidence meets recovery criteria but the observation window is not complete.
  • RESOLVED when recovery is confirmed or coach records a supported resolution.
  • DISMISSED when the coach marks the flag as not relevant, duplicate, invalid data, planned deload, vacation, or handled outside app.
  • ARCHIVED when retention policy removes old closed flags from normal operational views.

Retention & Redaction

  • Open and monitoring flags remain visible while the relationship is active or onboarding.
  • Planned travel, illness, deload, Ramadan, vacation, or client pause suppresses escalation and coach push notifications until the suppression window ends.
  • Paused relationships keep flags queryable but suspend escalation, reminder, and recovery jobs.
  • Ended relationships hide active flags from normal coach views and prevent new coach actions except support/admin workflows.
  • Evidence remains append-only for audit, but source deletion or privacy erasure redacts source refs and sensitive summaries.
  • Resolved and dismissed flags remain on the client profile timeline for 30 days, capped at the latest 10 compact items; detailed action/evidence history stays in the risk/support view.

API Contracts

Coach-facing APIs expose safe summaries and handling actions. Source ingestion and scheduled evaluation stay internal.

EndpointPurposeRequestResponse
GET /api/v1/workspaces/{workspaceId}/client-risk-flagsList flags for dashboard, client list, and risk review views.Query: status, severity, riskType, relationshipId, includeSnoozed, cursor.Cursor page of ClientRiskFlagListItemDto with client card, severity, reason, evidence summary, next review, and available actions.
GET /api/v1/workspaces/{workspaceId}/client-risk-flags/summaryReturn counts and severity breakdowns for dashboard badges.Optional relationship, coach, status, and package filters.Open, high severity, critical, recovering, snoozed, suppressed, and due today counts. Critical count maps to coach push eligibility after rate limiting.
GET /api/v1/workspaces/{workspaceId}/client-risk-flags/{flagId}Read flag detail and action history.Authorized coach workspace session.Flag detail, latest evidence, evidence timeline, source links the actor can open, action history, and suggested actions.
POST /api/v1/workspaces/{workspaceId}/clients/{relationshipId}/risk-flagsCreate a manual risk flag.riskType, severity, reason, optional nextReviewAt, idempotency key.Created flag and optional linked coach task.
POST /api/v1/workspaces/{workspaceId}/client-risk-flags/{flagId}/acknowledgeRecord coach acknowledgement.Optional note, idempotency key.Updated flag, action event, and task state if linked.
POST /api/v1/workspaces/{workspaceId}/client-risk-flags/{flagId}/actionsRecord intervention or linked handling action.actionType, optional linked source reference, optional note, optional nextReviewAt, idempotency key.Updated flag, created action, and recommended next state. Message, nudge, check-in request, plan update, and call scheduled move to MONITORING.
POST /api/v1/workspaces/{workspaceId}/client-risk-flags/{flagId}/suppressPause escalation for planned travel, illness, deload, Ramadan, vacation, or client pause.suppressionReason, suppressedUntil, optional note, idempotency key.Suppressed flag state, action event, and updated counts.
POST /api/v1/workspaces/{workspaceId}/client-risk-flags/{flagId}/snoozeHide from default active views until a later time.snoozedUntil, reason, idempotency key.Snoozed flag and updated counts.
POST /api/v1/workspaces/{workspaceId}/client-risk-flags/{flagId}/resolveResolve after recovery or coach action.resolutionType, optional note, optional linked action id, idempotency key.Resolved flag, action event, and task auto-resolution signal.
POST /api/v1/workspaces/{workspaceId}/client-risk-flags/{flagId}/dismissDismiss non-actionable flag with supported reason.dismissReason, required note for selected reasons, idempotency key.Dismissed flag and updated counts.
PUT /api/v1/workspaces/{workspaceId}/risk-rule-sets/{scopeType}/{scopeId}Workspace admin selects or updates workspace/package risk preset.presetKey, optional versioned threshold overrides within allowed V1 baseline presets, recovery criteria, activation mode.Draft or active RiskRuleSetDto. Non-admin coaches are rejected.

Key Code Snippets

Implementation sketches for the planned TypeScript/NestJS/Drizzle stack. Risk flags keep explainable operational state and publish inbox events through the outbox.

Drizzle schema shape

apps/api/src/coach-workflow/db/client-risk-flags.schema.ts

ts
export const clientRiskFlags = pgTable("client_risk_flags", {
  id: uuid("id").primaryKey().defaultRandom(),
  workspaceId: uuid("workspace_id").notNull(),
  relationshipId: uuid("relationship_id").notNull(),
  clientUserId: uuid("client_user_id").notNull(),
  riskType: text("risk_type").$type<RiskType>().notNull(),
  status: text("status").$type<RiskFlagStatus>().notNull().default("OPEN"),
  severity: text("severity").$type<RiskSeverity>().notNull(),
  source: text("source").$type<"SYSTEM" | "COACH">().notNull(),
  dedupeKey: text("dedupe_key").notNull(),
  score: numeric("score", { precision: 6, scale: 2 }).notNull(),
  reason: text("reason").notNull(),
  latestEvidenceId: uuid("latest_evidence_id"),
  firstDetectedAt: timestamp("first_detected_at", { withTimezone: true }).notNull(),
  lastDetectedAt: timestamp("last_detected_at", { withTimezone: true }).notNull(),
  nextReviewAt: timestamp("next_review_at", { withTimezone: true }),
  snoozedUntil: timestamp("snoozed_until", { withTimezone: true }),
  suppressedUntil: timestamp("suppressed_until", { withTimezone: true }),
  suppressionReason: text("suppression_reason").$type<RiskSuppressionReason>(),
  lastPushSentAt: timestamp("last_push_sent_at", { withTimezone: true }),
  resolvedAt: timestamp("resolved_at", { withTimezone: true }),
  timelineVisibleUntil: timestamp("timeline_visible_until", { withTimezone: true }),
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
}, (table) => ({
  activeDedupe: uniqueIndex("client_risk_flags_active_dedupe_idx")
    .on(table.workspaceId, table.dedupeKey)
    .where(sql`${table.status} in ('OPEN', 'ACKNOWLEDGED', 'MONITORING', 'RECOVERING')`),
  workspaceQueue: index("client_risk_flags_workspace_queue_idx")
    .on(table.workspaceId, table.status, table.severity, table.nextReviewAt),
  relationshipStatus: index("client_risk_flags_relationship_status_idx")
    .on(table.relationshipId, table.status, table.riskType),
}));

Risk evaluation use case

apps/api/src/coach-workflow/use-cases/evaluate-client-risk.ts

ts
@Injectable()
export class EvaluateClientRiskUseCase {
  constructor(
    private readonly relationships: CoachClientRelationshipReadPort,
    private readonly snapshots: RiskSignalSnapshotRepository,
    private readonly ruleSets: RiskRuleSetRepository,
    private readonly flags: ClientRiskFlagRepository,
    private readonly policy: RiskFlagPolicy,
    private readonly outbox: OutboxPort,
    private readonly tx: TransactionRunner,
  ) {}

  async execute(command: EvaluateClientRiskCommand): Promise<void> {
    await this.tx.run(async () => {
      const relationship = await this.relationships.getVisibleRelationship(command.relationshipId);
      if (!relationship.isActiveOrOnboarding()) return;

      const ruleSet = await this.ruleSets.activeForRelationship(relationship);
      const signals = await this.snapshots.latestWindow(command.relationshipId, ruleSet.window);
      const decisions = this.policy.evaluate(ruleSet, signals, command.evaluatedAt);

      for (const decision of decisions) {
        const flag = await this.flags.findActiveByDedupeKey(decision.dedupeKey)
          ?? ClientRiskFlag.raise(decision, relationship);

        flag.refreshEvidence(decision.toEvidence());
        await this.flags.save(flag);
        await this.outbox.publish(flag.pullDomainEvents());
      }
    });
  }
}

Policy boundary

apps/api/src/coach-workflow/policies/risk-flag.policy.ts

ts
export class RiskFlagPolicy {
  evaluate(ruleSet: RiskRuleSet, signals: RiskSignalWindow, now: Date): RiskDecision[] {
    if (!signals.meetsFreshnessPolicy(now)) return [];
    if (signals.hasActiveSuppression(now)) return [];
    return ruleSet.rules
      .map((rule) => rule.evaluate(signals, now))
      .filter((decision): decision is RiskDecision => decision !== null);
  }

  assertCanRead(actor: Actor, flag: ClientRiskFlag, relationship: CoachClientRelationship): void {
    actor.requireWorkspaceMembership(flag.workspaceId, ["OWNER", "COACH"]);
    actor.requirePermission("client.risk_flags.read");
    relationship.assertVisibleToCoach(actor.userId);
  }

  assertCanHandle(actor: Actor, flag: ClientRiskFlag, relationship: CoachClientRelationship): void {
    this.assertCanRead(actor, flag, relationship);
    actor.requirePermission("client.risk_flags.handle");
    if (!relationship.isActiveOrOnboarding()) {
      throw new ForbiddenException("Inactive relationships cannot receive risk flag actions.");
    }
    if (flag.isTerminal()) throw new ConflictException("Risk flag is already closed.");
  }

  assertDismissAllowed(reason: RiskDismissReason, note?: string): void {
    const noteRequired: RiskDismissReason[] = ["INVALID_DATA", "HANDLED_OUTSIDE_APP", "OTHER"];
    if (noteRequired.includes(reason) && !note?.trim()) {
      throw new BadRequestException("Dismiss reason requires a note.");
    }
  }

  nextStateForAction(actionType: RiskFlagActionType): RiskFlagStatus {
    if (["MESSAGE_SENT", "NUDGE_SENT", "CHECK_IN_REQUESTED", "PLAN_UPDATED", "CALL_SCHEDULED"].includes(actionType)) {
      return "MONITORING";
    }
    if (["INVALID_DATA", "DUPLICATE", "PLANNED_PAUSE", "CLIENT_PAUSED", "RESOLVED_BY_COACH"].includes(actionType)) {
      return "RESOLVED";
    }
    return "ACKNOWLEDGED";
  }

  shouldPushCritical(flag: ClientRiskFlag, now: Date): boolean {
    if (flag.severity !== "CRITICAL") return false;
    if (flag.suppressedUntil && flag.suppressedUntil > now) return false;
    return !flag.lastPushSentAt || differenceInHours(now, flag.lastPushSentAt) >= 24;
  }
}

Inbox event mapping

apps/api/src/coach-workflow/projectors/risk-flag-task.projector.ts

ts
@Injectable()
export class RiskFlagTaskProjector {
  constructor(private readonly coachTasks: CoachTaskUpsertPort) {}

  async handle(event: ClientRiskFlagRaised): Promise<void> {
    if (event.severity === "LOW") return;

    await this.coachTasks.upsert({
      workspaceId: event.workspaceId,
      relationshipId: event.relationshipId,
      clientUserId: event.clientUserId,
      sourceContext: "ANALYTICS",
      sourceType: "CLIENT_RISK_FLAG",
      sourceId: event.flagId,
      taskType: "PROGRESS_ALERT",
      severity: event.severity === "CRITICAL" ? "URGENT" : event.severity === "HIGH" ? "HIGH" : "NORMAL",
      dedupeKey: `risk-flag:${event.flagId}`,
      dueAt: event.nextReviewAt ?? event.detectedAt,
      payload: {
        title: event.riskType,
        summary: event.reason,
        score: event.score,
      },
    });
  }
}

Events & Background Jobs

Risk flags are fed by source events and scheduled evaluation, then publish operational events for tasks, dashboards, and notifications.

Consumed Events

  • DailyLogCompleted, DailyLogMissed: update adherence and missed-log snapshots.
  • WorkoutCompleted, WorkoutSkipped: update program adherence signals.
  • CheckInSubmitted, CheckInMissed: update check-in freshness and reflection signals.
  • MeasurementEntrySubmitted, ProgressTrendDetected: update progress and stall evidence.
  • ClientMessageSent, CoachMessageSent: update reply-gap windows.
  • OnboardingTaskCompleted, OnboardingTaskOverdue: update onboarding-stalled signals.
  • CoachClientRelationshipPaused, CoachClientRelationshipEnded: suspend or hide active risk processing.

Emitted Events

  • RiskSignalSnapshotUpdated: optional internal event for dashboards and evaluation scheduling.
  • ClientRiskFlagRaised: create or refresh task inbox item, dashboard counts, and critical push notification when rate-limit policy allows it.
  • ClientRiskFlagRefreshed: update score, severity, evidence, and next review time.
  • ClientRiskFlagSeverityChanged: notify coach when a monitored client worsens.
  • ClientRiskFlagAcknowledged: audit that a coach saw and accepted the risk.
  • RiskFlagActionRecorded: link intervention to messages, plan updates, calls, or notes and move flag to monitoring or resolved based on action policy.
  • ClientRiskFlagResolved: auto-resolve linked inbox tasks and update retention analytics.
  • ClientRiskFlagDismissed: record non-actionable outcome for rule tuning.

Jobs

  • Signal projector consumes source events and updates rolling risk_signal_snapshots.
  • Nightly evaluator scans active relationships by workspace timezone and applies active admin-selected rule presets.
  • Near-realtime evaluator can run after high-signal events such as repeated missed check-ins or urgent message gaps.
  • Snooze wake-up job returns flags to active views when snoozed_until passes.
  • Recovery evaluator checks active and monitoring flags for improvement criteria.
  • Suppression wake-up job resumes escalation after planned travel, illness, deload, Ramadan, vacation, or client pause windows end.
  • Retention job archives old resolved or dismissed flags after their 30-day timeline window and redacts evidence summaries when source records are erased.

V1 baseline Rule Examples

  • Low adherence: below 70% completion across 7 days raises medium; below 50% raises high.
  • Missed logs: 2 consecutive required daily logs raises medium; 4 raises high.
  • Weight stall: 21 days below goal movement raises medium; 35 days raises high; requires approved goal direction and enough recent measurements.
  • Reply gap: 48 hours without client reply raises medium; 72 hours raises high; 7 days can raise critical for high-touch packages.
  • Onboarding stalled: required onboarding task incomplete after 2 days raises medium; after 5 days raises high.
  • Plan noncompliance: below 60% completion over 7 days raises medium; below 40% or 5 missed assigned tasks raises high.

Permissions & Access Rules

Risk flags expose sensitive operational conclusions, so reads and actions require explicit workspace, relationship, and source permissions.

Coach

  • Must have active workspace membership and visibility into the coach-client relationship.
  • Needs client.risk_flags.read to list or open risk flags.
  • Needs client.risk_flags.handle to acknowledge, snooze, suppress, resolve, dismiss, or record actions.
  • Only workspace admins can activate or change risk rule presets in Public V1.
  • Needs source-specific permissions to open detailed logs, measurements, messages, nutrition data, health context, or progress photos referenced by evidence.
  • Can create manual flags only for active or onboarding relationships.

Client & System

  • Clients do not read internal risk flags, scores, dismissal notes, or coach-only evidence summaries.
  • Client-facing support is delivered through explicit messages, check-in requests, reminders, or plan changes.
  • System workers can create, refresh, recover, resolve, archive, and redact flags through internal service accounts.
  • System jobs must use deterministic idempotency keys based on relationship, risk type, signal window, and source event id.

Web, Coach Mobile, Client Mobile

Risk flags are coach-operational state. Client surfaces only show intentional follow-up generated by the coach or system.

Coach Web

  • Dashboard cards show high severity count, critical count, due review count, suppressed count, and clients worsening since last review.
  • Client list can filter or sort by active risk severity, risk type, and next review date.
  • Client profile shows active flags above timeline with evidence summary, history, actions, and source deep links.
  • Risk review page supports bulk scanning, owner filters, snoozed/suppressed visibility, and admin-only preset selection.

Coach Mobile

  • Shows urgent and due flags in a compact action list with message, call note, snooze, suppress, and resolve actions.
  • Prioritizes one-tap intervention over dense analytics tables.
  • Can open lightweight evidence summaries and deep link to mobile-native logs, messages, check-ins, or profile screens.
  • Defers rule configuration and deep historical analysis to web for V1 baseline.

Client Mobile

  • Does not expose risk scores, flags, or internal coach classifications.
  • Receives only explicit actions such as supportive messages, check-in prompts, plan changes, reminders, or resource links.
  • Offline logging updates risk signals only after sync reaches the backend.
  • Client recovery is reflected through normal tasks and feedback, not by showing internal flag state.

Test Scenarios & V1 Baseline Decisions

Validate deterministic rule behavior, thresholds, dedupe, permissions, recovery, suppression, notifications, and inbox integration before implementation.

Required Tests

  • Domain: repeated signal windows refresh one active flag instead of creating duplicates.
  • Domain: low adherence threshold raises expected severity with explainable evidence summary.
  • Domain: default thresholds raise medium/high severity for low adherence, missed logs, reply gap, onboarding stalled, plan noncompliance, and weight stall as specified by the V1 baseline preset.
  • Domain: weight-stall evaluation is skipped without approved goal direction, active measurement cadence, at least 3 measurements, and a fresh measurement in the last 14 days.
  • Domain: manual flag cannot be downgraded or auto-dismissed by system evidence alone.
  • Domain: planned travel, illness, deload, Ramadan, vacation, or client pause suppresses escalation and push notifications until the end date.
  • Domain: stale signal snapshots do not raise flags except where missing data is itself the signal.
  • Domain: message, nudge, check-in request, plan update, and call scheduled move flag to MONITORING; invalid data, duplicate, planned pause, client paused, and explicit coach resolution can close immediately.
  • Domain: recovery criteria move a flag from monitoring to recovering and then resolved after observation window.
  • Domain: dismissed flag reopens only when a new material signal crosses reopening policy.
  • API: coach cannot list flags outside visible workspace relationships.
  • API: non-admin coach cannot update or activate risk rule presets.
  • API: evidence detail hides source links when actor lacks source-specific permission.
  • API: dismiss requires supported reason and note for invalid data, handled outside app, or other.
  • Notification: critical flag creates at most one coach push notification per flag per 24 hours and never sends while suppressed.
  • Integration: high severity flag creates or refreshes a coach task with sourceType=CLIENT_RISK_FLAG.
  • Integration: resolving a risk flag auto-resolves linked open or snoozed inbox task.
  • Integration: resolved or dismissed flags appear as compact client profile timeline history for 30 days, capped at 10 items.
  • Integration: source redaction removes sensitive evidence summary fields without deleting flag audit history.
  • E2E: missed logs raise a flag, coach messages client, client resumes logging, and flag resolves after recovery window.

Resolved V1 Baseline Decisions

  • Thresholds: ship seeded default, relaxed, and high-touch presets. Default thresholds are 70%/50% adherence, 2/4 missed logs, 21/35 day weight stall, 48/72 hour reply gap, 2/5 day onboarding stall, and 60%/40% plan compliance.
  • Critical notifications: critical flags create dashboard/inbox priority and one rate-limited coach push notification per flag per 24 hours.
  • Configuration: workspace admins can choose or update rule presets; regular coaches can handle flags and create manual flags but cannot edit thresholds in Public V1.
  • Weight stall: requires approved goal direction, target metric, active measurement cadence, at least 3 measurements, and a fresh measurement within 14 days.
  • Planned pauses: travel, illness, deload, Ramadan, vacation, and client pause suppress escalation and push notifications until the suppression end date.
  • Data freshness: adherence/workout/onboarding snapshots need 24-hour freshness, communication needs 1-hour freshness, and measurements need the weight-stall freshness/sample rules.
  • Interventions: supportive actions move flags to monitoring; invalid data, duplicate, planned pause, client paused, and explicit coach resolution can close immediately with audit.
  • Profile timeline: resolved/dismissed flags remain visible for 30 days, capped at 10 compact items, then move to historical/support views.

CoachMe internal planning documentation.