Skip to content

Treatment Plan Lifecycle

Plan Status State Machine

                    ┌───────┐
    Create plan     │ draft │◄──────────────────┐
                    └───┬───┘                   │
                        │                       │
        ┌───────────────┤                       │
        │               │                       │ Reject / Revise
        │ requires_approval = false              │
        │               │                       │
        │ Publish       │ Publish               │
        │               │                       │
        ▼               ▼                       │
    ┌────────┐    ┌──────────────────┐          │
    │ active │    │ pending_approval │──────────┘
    └────┬───┘    └────────┬─────────┘
         │                 │
         │                 │ Approve
         │                 │
         │                 ▼
         │            ┌────────┐
         ├◄───────────┤ active │
         │            └────────┘

         ├──────────────┐
         │              │
         │ Pause        │
         ▼              │
    ┌────────┐          │
    │ paused │──────┐   │
    └────┬───┘      │   │
         │          │   │
         │ Resume   │   │
         │          │   │
         ▼          │   │
    ┌────────┐      │   │
    │ active │      │   │
    └────┬───┘      │   │
         │          │   │
    ┌────┼──────────┼───┤
    │    │          │   │
    │    ▼          ▼   ▼
    │ ┌───────────┐ ┌───────────┐
    │ │ cancelled │ │ cancelled │
    │ └───────────┘ └───────────┘

    ├─── All sessions done ──► ┌───────────┐
    │                          │ completed │
    │                          └───────────┘

    └─── end_date passed ────► ┌─────────┐
         (background job)      │ expired │
                               └─────────┘

State Transitions

FromToTriggerWhoValidation
draftactivePublish (no approval)specialist, adminAt least 1 session with 1 exercise
draftpending_approvalPublish (approval required)specialist, adminAt least 1 session with 1 exercise
pending_approvalactiveApprovespecialist, adminMust be authorized approver
pending_approvaldraftReject / Revisespecialist, adminReturns to editable state
activepausedManual pausespecialist, admin-
pausedactiveResumespecialist, adminend_date not passed
activecompletedAll sessions donesystem (auto)sessions_completed >= sessions_total
activecancelledManual cancelspecialist, adminClinical decision
pausedcancelledManual cancelspecialist, admin-
activeexpiredBackground jobsystem (cron)end_date < NOW()
pausedexpiredBackground jobsystem (cron)end_date < NOW()

Terminal States

These states are final — no further transitions allowed:

  • completed — All sessions done successfully
  • cancelled — Manually cancelled by specialist/admin
  • expired — Plan validity period ended

Approval Workflow

Organization Configuration

Each organization decides whether plans require specialist approval before patient activation:

json
// Treatment plan creation
{
  "requires_approval": true  // org policy or per-plan setting
}

Without Approval (Default)

Staff creates plan → Publishes → Assigns to patient → Active immediately
  1. Specialist or admin creates plan in draft
  2. Adds sessions and exercises
  3. Publishes plan → status becomes active
  4. Assigns to patient → patient_treatment_plans.status = 'active'
  5. Patient can start sessions immediately

With Approval

Staff creates plan → Publishes → Assigns to patient (pending_approval)

                            Specialist reviews plan

                         ┌───────────────┼───────────────┐
                         │               │               │
                    Approves         Requests changes   Rejects
                         │               │               │
                    Plan active     Returns to draft    Cancelled
  1. Staff creates plan in draft
  2. Adds sessions and exercises
  3. Publishes plan → status becomes pending_approval
  4. Assigns to patient → patient_treatment_plans.status = 'pending_approval'
  5. Specialist reviews:
    • Approve: POST /v1/patient-treatment-plans/{id}/approve
    • Reject/Revise: PUT /v1/patient-treatment-plans/{id}/statusdraft
  6. On approval: patient_treatment_plan becomes active, patient is notified

Versioning Workflow

Treatment plans use the same versioning pattern as form templates:

Creating Versions

Version 1 (draft):
  Session 1: Exercise A (3×10), Exercise B (2×15)

Publish → Version 1 snapshot created
  sessions_snapshot: [{...}]

Edit → New draft (modifying current state):
  Session 1: Exercise A (3×12), Exercise B (2×15), Exercise C (3×8)

Publish → Version 2 snapshot created
  sessions_snapshot: [{...}]

Version Assignment

When assigning a plan to a patient, the system always uses the latest published version:

sql
INSERT INTO patient_treatment_plans (
    patient_id, treatment_plan_id, treatment_plan_version, ...
) VALUES (
    123, 'plan-uuid', 2, ...  -- version 2 (latest)
);

Existing Enrollments

Existing patient enrollments are NOT affected by new versions:

  • Patient A enrolled on version 1 → continues on version 1
  • Patient B enrolled on version 2 → continues on version 2

This ensures clinical continuity — a patient's program doesn't change mid-treatment.

Rollback

Rolling back restores a previous version's session structure but creates a NEW version number:

Rollback to version 1:
  Current version: 2
  After rollback: version 3 (with version 1's content)

This preserves the audit trail — you can always see what happened and when.

Background Jobs

Plan Expiration Checker

A periodic background job (runs daily) checks for expired plans:

sql
UPDATE patient_treatment_plans
SET status = 'expired', updated_at = NOW()
WHERE status IN ('active', 'paused')
  AND end_date < CURRENT_DATE;

For each expired plan, the system:

  1. Transitions status to expired
  2. Fires treatment_plan.expired automation trigger
  3. Notifies patient (via automation action)

Session Reminders

A daily background job can send session reminders:

sql
SELECT ptp.*
FROM patient_treatment_plans ptp
WHERE ptp.status = 'active'
  AND ptp.start_date <= CURRENT_DATE
  AND ptp.end_date >= CURRENT_DATE
  -- Patient hasn't completed a session today
  AND NOT EXISTS (
    SELECT 1 FROM patient_session_completions psc
    WHERE psc.patient_treatment_plan_id = ptp.id
      AND psc.started_at::DATE = CURRENT_DATE
  );

This is implemented as an automation action (send_session_reminder) that can be configured per organization.

Pause/Resume

Pausing a plan temporarily suspends it:

  • Patient cannot start new sessions while paused
  • The plan's end_date is NOT extended (the validity window continues ticking)
  • Resuming simply returns the plan to active

If an organization wants to extend the plan duration after a pause, they should create a new enrollment or adjust the end_date manually.