Skip to content

Therapist-Initiated AI Plan Generation

Overview

The Okta-Health system enables physical therapists to leverage AI for generating initial workout plans for their patients. By providing clinical notes about the patient, therapists receive a customized 5-day exercise plan that can be reviewed, modified, and finalized before saving.

Key Capabilities

Feature Description
AI-Powered Generation OpenAI gpt-5-mini-2025-08-07 generates personalized plans from therapist notes
File Attachments Therapists can attach images and PDFs alongside notes for multimodal AI analysis
Chat File Attachments Therapists can attach additional files mid-conversation in the AI assistant chat for contextual analysis
Skeleton Loading UI Day cards appear progressively as the AI generates each workout, driven by SSE section events
AI Assistant Side Panel After generation, therapists can refine the plan via conversational AI chat in a side panel
Exercise Database Matching AI selects exercises from the therapist's available exercise library
Patient Attribute Extraction AI extracts relevant patient attributes for future plan refinement
Manual Customization Therapists can edit, add, or remove exercises before finalizing

User Flow (Therapist Perspective)

Step-by-Step Workflow

  1. Therapist opens patient dashboard and selects a patient requiring an initial plan
  2. PatientNotesInputModal opens where therapist enters clinical notes (up to 4000 characters)
  3. Therapist optionally attaches files — images (PNG, JPEG, WebP, GIF) or PDFs up to 15 MB each, either via the attach button or by dragging and dropping files onto the notes input area. These are encoded as base64 and sent alongside the notes for multimodal AI analysis.
  4. Therapist clicks "Generate AI Plan" to initiate the streaming request
  5. Skeleton loading UI shows day cards appearing progressively as the backend sends SSE section events (plan_summary, workouts_count)
  6. AI generates 5-day plan based on notes, attachments, patient attributes, and exercise database
  7. InitialPlanCreationModal opens with the generated plan pre-populated
  8. Therapist optionally opens the AI assistant side panel to refine the plan via conversational chat (e.g., "make this workout easier", "remove dumbbell exercises"). Therapists can also attach new files mid-conversation (e.g., a new MRI scan) via the paperclip button or by dragging files onto the chat panel. Proposed changes appear in a comparison view for accept/reject.
  9. Therapist reviews and customizes each day's exercises (add, edit, remove, copy days)
  10. Therapist submits final plan which creates workout records in the database

Flow Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                         THERAPIST DASHBOARD                                 │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│                     PatientNotesInputModal                                  │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │  Enter clinical notes about patient (max 4000 chars)                │   │
│  │  • Diagnosis, injury details, pain levels                           │   │
│  │  • Treatment goals, mobility limitations                            │   │
│  │  • Exercise history, equipment access                               │   │
│  │                                                                     │   │
│  │  [📎 Attach files] or drag & drop — images/PDFs, ≤15 MB each      │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  [Skip and Create Manually]                    [Generate AI Plan]           │
└─────────────────────────────────────────────────────────────────────────────┘
                    ┌───────────────┴───────────────┐
                    │                               │
                    ▼                               ▼
          "Skip and Create"               "Generate AI Plan"
                    │                               │
                    │                               ▼
                    │              ┌────────────────────────────────┐
                    │              │   POST /v2/ai/generate-        │
                    │              │   initial-plan-from-notes      │
                    │              │                                │
                    │              │   • Validates doctor-patient   │
                    │              │     connection                 │
                    │              │   • Fetches exercise database  │
                    │              │   • Gets patient attributes    │
                    │              │   • Builds multimodal input    │
                    │              │     (text + file attachments)  │
                    │              │   • Streams to OpenAI          │
                    │              └────────────────────────────────┘
                    │                               │
                    │                               ▼
                    │              ┌────────────────────────────────┐
                    │              │   Skeleton Loading UI          │
                    │              │                                │
                    │              │   SSE section events:          │
                    │              │   • plan_summary extracted     │
                    │              │   • workouts_count increments  │
                    │              │   Day cards appear as AI       │
                    │              │   generates each workout       │
                    │              └────────────────────────────────┘
                    │                               │
                    ▼                               ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                   InitialPlanCreationModal                                  │
│                                                                             │
│  ┌─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐   │
│  │   Day 1     │   Day 2     │   Day 3     │   Day 4     │   Day 5     │   │
│  │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │   │
│  │ │Exercise1│ │ │Exercise1│ │ │Exercise1│ │ │Exercise1│ │ │Exercise1│ │   │
│  │ │Exercise2│ │ │Exercise2│ │ │Exercise2│ │ │Exercise2│ │ │Exercise2│ │   │
│  │ │Exercise3│ │ │Exercise3│ │ │Exercise3│ │ │Exercise3│ │ │Exercise3│ │   │
│  │ └─────────┘ │ └─────────┘ │ └─────────┘ │ └─────────┘ │ └─────────┘ │   │
│  └─────────────┴─────────────┴─────────────┴─────────────┴─────────────┘   │
│                                                                             │
│  Actions: [Copy Day] [Clear Day] [Edit Exercises] [Re-try AI]              │
│                                                                             │
│  [AI Assistant ▸]  Opens side panel for conversational refinement           │
│                                          [Cancel]  [Create 5 Day Plan]      │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│               POST /v2/doctor/create-initial-plan                           │
│                                                                             │
│   • Creates Workout records for each day                                    │
│   • Creates ExerciseInWorkout records for each exercise                     │
│   • Links to patient via patientId                                          │
└─────────────────────────────────────────────────────────────────────────────┘

Frontend Architecture

Components

Component Path Purpose
PatientNotesInputModal OktaPT-FE/components/plan-creation/PatientNotesInputModal.tsx Entry point for therapist to input clinical notes and attach files (via button or drag-and-drop). Renders skeleton loading UI during generation.
InitialPlanCreationModal OktaPT-FE/components/plan-creation/InitialPlanCreationModal.tsx Displays AI-generated plan for review and customization. Includes AI assistant side panel toggle.
AIPlanEditChat OktaPT-FE/components/plan-creation/AIPlanEditChat.tsx Conversational AI chat panel for refining the generated plan. Supports per-message file attachments via paperclip button and drag-and-drop.
PlanComparisonView OktaPT-FE/components/plan-creation/PlanComparisonView.tsx Side-by-side comparison of current vs. AI-proposed plan changes
AIStreamingPreview OktaPT-FE/components/plan-creation/AIStreamingPreview.tsx Streaming preview shown while AI chat is generating proposed changes
PlanDaySelector OktaPT-FE/components/plan-creation/PlanDaySelector.tsx Day picker with copy/clear functionality
DayWorkoutPreview OktaPT-FE/components/plan-creation/DayWorkoutPreview.tsx Preview of exercises for each day
DayWorkoutCreationModal OktaPT-FE/components/plan-creation/DayWorkoutCreationModal.tsx Modal for editing exercises within a specific day

Hooks

Hook Path Purpose
useAIInitialPlanGeneration OktaPT-FE/lib/hooks/useAIInitialPlanGeneration.ts Manages SSE streaming (including section events), file attachments, and plan transformation
useAIPlanEdit OktaPT-FE/lib/hooks/useAIPlanEdit.ts Manages AI chat refinement — conversation history, per-message attachments, proposed plan diffing, accept/reject
useInitialPlanState OktaPT-FE/lib/hooks/useInitialPlanState.ts Manages plan state, day operations, and submission

State Flow

// useAIInitialPlanGeneration state
interface AIGenerationState {
  isGenerating: boolean;                    // True during SSE streaming
  error: string | null;                     // Error message if generation fails
  streamingContent: InitialPlanStreamingContent; // Extracted section data from SSE
  streamingStatus: string;                  // Current status message translation key
}

interface InitialPlanStreamingContent {
  plan_summary?: string;   // Extracted when plan_summary section event arrives
  workouts_count?: number; // Incremented as each workout is detected
}

// Generated plan data structure
interface GeneratedPlanData {
  workoutsMap: Map<number, {
    dayNumber: number;
    actualDate: string;
    exercises: ExerciseInWorkout[];
  }>;
  planSummary: string;
  recommendations?: string[];
  progressionNotes?: string;
}

// useInitialPlanState manages the editable plan
interface DayWorkout {
  dayNumber: number;
  focus: string;
  exercises: ExerciseInWorkout[];
}

Chat Attachment Types

The AI chat supports two distinct attachment flows:

Field Source Sent with Purpose
attachments Initial plan creation (PatientNotesInputModal) Every chat request Initial context files (MRI scans, referral notes) attached when the plan was first generated
messageAttachments Chat input (AIPlanEditChat) Single request Per-message files attached mid-conversation for additional context

On the backend (therapistPlanEdit controller), initial attachments are placed on the first user message in the conversation so the AI always has access to them. Per-message messageAttachments are attached to the current user message only.

// Chat message metadata (no base64 — only filename/type stored in chat history)
interface ChatMessageAttachment {
  filename: string;
  type: string;
}

interface ChatMessage {
  id: string;
  role: "user" | "assistant" | "system";
  content: string;
  timestamp: Date;
  attachments?: ChatMessageAttachment[];  // Display metadata only
}

PatientNotesInputModal Props

interface PatientNotesInputModalProps {
  isOpen: boolean;
  onClose: () => void;
  patient: { id: number; firstName: string; lastName: string };
  onAIPlanGenerated: (generatedPlan: any, notes: string, attachments: FileData[]) => void;
  onSkipToManual: () => void;
  initialNotes?: string;  // Preserves notes when returning from plan modal
}

File Upload Utilities

Shared utilities in OktaPT-FE/lib/utils/fileUpload.ts:

interface FileData {
  filename: string;
  file_data: string;  // base64-encoded data URL
  type: string;       // MIME type
}

const MAX_FILE_SIZE = 15 * 1024 * 1024; // 15 MB
const ALLOWED_FILE_TYPES = [
  "application/pdf",
  "image/png", "image/jpeg", "image/jpg", "image/webp", "image/gif",
];

encodeFileToBase64(file: File): Promise<string>  // Returns data URL string
validateFile(file: File): { valid: boolean; error?: string }
buildMultimodalContent(text: string, files: FileData[]): ChatContent[]

Backend Architecture

API Routes

Endpoint Method Auth Purpose
/v2/ai/generate-initial-plan-from-notes POST Required Generate AI plan from therapist notes (SSE streaming)
/v2/ai/therapist-plan-edit POST Required AI chat refinement of existing plan (SSE streaming). Accepts attachments (initial) and messageAttachments (per-message).
/v2/doctor/create-initial-plan POST Required Save finalized plan to database

Route Definitions

AI Routes: OktaPT-API/src/routes/ai/index.ts:36

router.post("/generate-initial-plan-from-notes", generateInitialPlanFromDoctorNotes);

Doctor Routes: OktaPT-API/src/routes/doctor/index.ts:34

router.post("/create-initial-plan", authentication, createInitialPlan);

Controller: generateInitialPlanFromDoctorNotes

Location: OktaPT-API/src/routes/ai/controllers.ts:1046-1355

Processing Steps:

  1. Input Validation
  2. Validates doctorNotes is non-empty string
  3. Validates patientId is valid number

  4. Authorization Check

  5. Verifies active doctor-patient connection exists
  6. Returns 403 if no connection found

  7. Data Fetching

  8. Fetches available exercises (public + doctor's custom)
  9. Gets existing patient attributes for context

  10. SSE Setup

  11. Sets headers: Content-Type: text/event-stream
  12. Disables caching and buffering
  13. Flushes headers to initiate stream

  14. Heartbeat & Status Messages

  15. 1-second heartbeat interval prevents connection timeout
  16. Progressive status messages at 10s, 20s, 30s, 40s intervals

  17. Multimodal Input Building

  18. Starts with input_text containing the request prompt
  19. If attachments are provided, appends each as input_image (for images) or input_file (for PDFs/other files) to the content array
  20. The combined content array is sent as the user message to OpenAI

  21. OpenAI API Call

  22. Uses gpt-5-mini-2025-08-07 model
  23. Streams response with JSON schema enforcement

  24. Response Processing & Section Events

  25. Accumulates streamed text deltas
  26. Forwards chunks to client as SSE events
  27. Parses partial JSON to extract completed fields:
    • Sends section event with plan_summary when the summary string is fully extracted
    • Sends section event with workouts_count as each new workout object is detected
  28. These section events power the frontend skeleton loading UI

  29. Attribute Storage

  30. Parses final JSON response
  31. Stores extracted user attributes via createUserAttributesForAiModel

  32. Completion

    • Sends done event with parsed plan
    • Ends response stream

Controller: createInitialPlan

Location: OktaPT-API/src/routes/doctor/controller.ts:862-928

Processing Steps:

  1. Validation (lines 866-869)
  2. Ensures at least one workout is provided

  3. Transaction (lines 873-921)

  4. Creates Workout records for each day
  5. Creates ExerciseInWorkout records for each exercise
  6. Sets scheduled dates and exercise parameters

  7. Response (line 923)

  8. Returns created workouts with exercise details

AI Integration

Provider & Model

Property Value
Provider OpenAI
Model gpt-5-mini-2025-08-07
Reasoning Effort low
Response Format JSON Schema (strict mode)

System Prompt

Location: OktaPT-API/src/routes/ai/controllers.ts:1174-1180

You are a physical therapist and fitness specialist helping a doctor create an
initial workout plan for their patient. Based on the doctor's notes about the
patient, create a comprehensive 5 day initial plan with appropriate exercises.
The exercises MUST come from the provided exercise database - use the exact
exercise IDs provided. Focus on creating a safe, progressive plan that addresses
the patient's specific needs as described in the doctor's notes. Also extract
relevant patient attributes from the doctor's notes to help with future plan
generation. Always respond in the following language: {detectedLanguage}

User Input Structure

The user message is sent as an array of content blocks (multimodal input):

{
  role: "user",
  content: [
    // 1. Text prompt (always present)
    {
      type: "input_text",
      text: `Doctor's Notes About Patient: ${doctorNotes}\n\nAvailable Exercise Database: ...`
    },
    // 2. Image attachments (if any)
    { type: "input_image", image_url: "data:image/png;base64,..." },
    // 3. File attachments (if any)
    { type: "input_file", file_data: "data:application/pdf;base64,...", filename: "notes.pdf" }
  ]
}

When no attachments are provided, the content array contains only the text prompt.

JSON Schema Enforcement

Schema Location: OktaPT-API/lib/openai_response_formats/pt_initial_plan_from_notes_schema.ts

The schema enforces strict structure for AI responses:

{
  initial_plan: {
    plan_summary: string,           // Brief summary of the generated plan
    workouts: [                     // Exactly 5 workouts required
      {
        day: string,                // "Workout 1", "Workout 2", etc.
        focus: string,              // Main focus of this workout
        exercises: [                // 2-4 exercises per workout
          {
            exercise_id: number,    // MUST match database ID
            exercise_name: string,  // For verification
            exercise_type: "REPS_BASED" | "TIME_BASED",
            sets: number,           // 1-6 sets
            reps: number | null,    // 1-50 (null if TIME_BASED)
            duration: number | null // 10-300 seconds (null if REPS_BASED)
          }
        ]
      }
    ]
  },
  user_attributes: [                // Extracted from doctor's notes
    {
      category: "PHYSICAL" | "MEDICAL" | "BEHAVIORAL" | "GOALS" | "ASSESSMENT" | "PREFERENCES" | "CUSTOM",
      key: string,                  // e.g., "current_injury", "pain_level"
      value: string                 // Extracted value
    }
  ] | null
}

SSE Streaming Implementation

Headers:

res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("X-Accel-Buffering", "no");  // Nginx proxy support
res.setHeader("Connection", "keep-alive"); // HTTP/1.x only

Heartbeat Mechanism:

// Sends ping every 1 second to prevent connection timeout
heartbeat = setInterval(() => {
  res.write(`: ping ${Date.now()}\n\n`);
}, 1000);

Initial Stream Padding:

// 2KB padding forces buffered proxies to flush
res.write(":" + " ".repeat(2048) + "\n\n");
res.write(`data: ${JSON.stringify({ type: "keepalive", ts: Date.now() })}\n\n`);

Progressive Status Messages:

Delay Stage Message
Immediate analyzing "We have received your request and are analyzing your notes..."
Immediate analyzing "AI is analyzing doctor's notes..."
10 seconds reviewing "Reviewing evidence-based protocols..."
20 seconds generating "Generating personalized treatment plan..."
30 seconds generating "Starting to match exercises to patient needs..."
40 seconds generating "Getting together my thoughts..."

Chunk Processing:

for await (const chunk of response) {
  // Clear status timeouts when first AI token arrives
  if (chunk.type === "response.output_text.delta") {
    // Clear pending status timeouts
  }

  // Forward chunk to client
  res.write(`data: ${JSON.stringify(chunk)}\n\n`);

  // Accumulate text for final parsing
  if (chunk.type === "response.output_text.delta" && chunk.delta) {
    accumulatedText += chunk.delta;

    // Extract completed fields from partial JSON and send section events
    // plan_summary: sent when the full summary string is detected
    // workouts_count: sent each time a new "day" key is found in the workouts array
  }
}

Section Events (for skeleton loading):

data: {"type":"section","section":"plan_summary","content":"5-day ACL rehabilitation..."}

data: {"type":"section","section":"workouts_count","content":1}
data: {"type":"section","section":"workouts_count","content":2}
... (increments as each workout is generated)

Frontend SSE Handling

Location: OktaPT-FE/lib/hooks/useAIInitialPlanGeneration.ts

Key implementation details:

// Buffer for incomplete SSE lines (prevents JSON parse errors)
let incompleteLineBuffer = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const chunk = new TextDecoder().decode(value);
  const textToParse = incompleteLineBuffer + chunk;
  const lines = textToParse.split("\n");
  incompleteLineBuffer = lines.pop() || "";  // Keep incomplete line

  for (const line of lines) {
    if (line.startsWith("data: ")) {
      const data = JSON.parse(line.slice(6));

      if (data.type === "status") {
        setStreamingStatus(data.message);    // Show status to user
      } else if (data.type === "section") {
        // Section events power the skeleton loading UI
        setStreamingContent(prev => ({
          ...prev,
          [data.section]: data.content,      // e.g., plan_summary or workouts_count
        }));
      } else if (data.type === "response.output_text.delta") {
        accumulatedContent += data.delta;
        onStreamingUpdate(accumulatedContent);
      } else if (data.type === "done") {
        finalResult = data.initialPlan;
      }
    }
  }
}

Data Models & Database Storage

Prisma Models

Workout:

model Workout {
  id                               Int                 @id @default(autoincrement())
  doctorId                         Int
  patientId                        Int?
  isTemplate                       Boolean
  description                      String?             // Stores workout focus
  dateWorkoutScheduled             DateTime
  dateWorkoutCurrentlyScheduledFor DateTime            @default(now())
  isWorkoutCompleted               Boolean
  exercises                        ExerciseInWorkout[]
  tenantId                         Int                 @default(1)
}

ExerciseInWorkout:

model ExerciseInWorkout {
  id                              Int           @id @default(autoincrement())
  workoutId                       Int
  exerciseId                      Int
  exerciseType                    ExerciseType? @default(REPS_BASED)
  plannedSets                     Int
  plannedReps                     Int?
  plannedDuration                 Int?
  plannedWeight                   Float?
  prescribedPainToleranceLevel    Int?
  prescribedPhysicalExertionLevel Int?
  doctorNotesPreExercise          String?
  isExerciseCompleted             Boolean
  orderInWorkout                  Int
  exerciseBlock                   ExerciseBlock? @default(MAIN_SESSION)
  tenantId                        Int            @default(1)
}

UserAttributeForAiModel:

model UserAttributeForAiModel {
  id        Int               @id @default(autoincrement())
  userId    Int
  tenantId  Int               @default(1)
  category  AttributeCategory
  key       String            // e.g., "current_injury", "pain_level"
  value     String?
  createdAt DateTime          @default(now())
  createdBy String?           // "AI", "USER_INPUT", "SYSTEM"

  @@index([userId, category, key, createdAt])
}

enum AttributeCategory {
  PHYSICAL      // Weight, height, mobility
  MEDICAL       // Diagnoses, medications, surgeries
  BEHAVIORAL    // Exercise habits, adherence patterns
  GOALS         // Treatment goals, fitness targets
  ASSESSMENT    // Evaluations, progress markers
  PREFERENCES   // Equipment, workout preferences
  CUSTOM        // Anything else
}

Data Flow Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                        AI GENERATION PHASE                                  │
│                                                                             │
│  Doctor Notes ──► OpenAI API ──► Structured JSON Response                   │
│                                        │                                    │
│                          ┌─────────────┴─────────────┐                      │
│                          ▼                           ▼                      │
│                   initial_plan              user_attributes                 │
│                          │                           │                      │
│                          │                           ▼                      │
│                          │              ┌────────────────────────┐          │
│                          │              │ UserAttributeForAiModel│          │
│                          │              │ (stored immediately)   │          │
│                          │              └────────────────────────┘          │
│                          │                                                  │
└──────────────────────────│──────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│                      REVIEW & CUSTOMIZATION PHASE                           │
│                                                                             │
│                   Initial Plan (in-memory Map)                              │
│                          │                                                  │
│                   [Therapist Edits]                                         │
│                          │                                                  │
└──────────────────────────│──────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│                        SUBMISSION PHASE                                     │
│                                                                             │
│  POST /v2/doctor/create-initial-plan                                        │
│                          │                                                  │
│               ┌──────────┴──────────┐                                       │
│               ▼                     ▼                                       │
│        ┌──────────┐         ┌─────────────────┐                             │
│        │ Workout  │ ──────► │ExerciseInWorkout│                             │
│        │ (1 per   │ 1:N     │ (exercises for  │                             │
│        │  day)    │         │  each workout)  │                             │
│        └──────────┘         └─────────────────┘                             │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Security Considerations

Authentication & Authorization

Check Location Description
JWT Verification Middleware All endpoints require valid access token
Doctor-Patient Connection controllers.ts:1067-1080 Verifies active ACCEPTED connection exists
Tenant Isolation All queries tenantId filter on all database operations

Multi-Tenant Isolation

// Exercise fetch includes tenant filter
const exercises = await prisma.exercise.findMany({
  where: {
    OR: [{ isPublic: true }, { createdById: doctorId }],
    isActive: true,
  },
});

// Patient attributes include tenant filter
const patientAttributes = await getUserAttributesForAiModel(
  patientId,
  req.user.tenantId
);

Input Validation

Field Validation Location
doctorNotes Non-empty string controllers.ts:1053-1060
patientId Valid number controllers.ts:1062-1065
Frontend Max 4000 characters PatientNotesInputModal.tsx:47

SSE Security Measures

  • Heartbeat prevents timeout attacks - 1-second pings maintain connection
  • Cleanup on disconnect - Proper resource cleanup when client disconnects
  • Error handling - Errors sent as SSE events, not exposed stack traces

API Reference

POST /v2/ai/generate-initial-plan-from-notes

Request:

{
  "patientId": 123,
  "doctorNotes": "Patient is a 45-year-old male recovering from ACL reconstruction surgery 6 weeks ago...",
  "attachments": [
    {
      "type": "image/png",
      "file_data": "data:image/png;base64,iVBOR...",
      "filename": "xray.png"
    },
    {
      "type": "application/pdf",
      "file_data": "data:application/pdf;base64,JVBERi...",
      "filename": "referral.pdf"
    }
  ]
}

The attachments field is optional. When present, each attachment is sent to OpenAI as a multimodal content block (input_image for images, input_file for other types).

SSE Response Stream:

: (2KB padding)

data: {"type":"keepalive","ts":1706640000000}

data: {"type":"status","message":"We have received your request and are analyzing your notes...","stage":"analyzing"}

data: {"type":"status","message":"AI is analyzing doctor's notes...","stage":"analyzing"}

data: {"type":"response.output_text.delta","delta":"{\"initial_plan\":{\"plan_summary\":\"5-day ACL rehabilitation..."}

data: {"type":"section","section":"plan_summary","content":"5-day ACL rehabilitation plan..."}

data: {"type":"section","section":"workouts_count","content":1}

... more deltas and section events ...

data: {"type":"section","section":"workouts_count","content":5}

data: {"type":"done","initialPlan":{"plan_summary":"...","workouts":[...]}}

Error Response (SSE):

data: {"type":"error","error":"Failed to parse AI response"}

Error Response (HTTP):

{
  "error": "No active connection with this patient"
}

POST /v2/doctor/create-initial-plan

Request:

{
  "patientId": 123,
  "workouts": [
    {
      "dateScheduled": "2024-01-15",
      "focus": "Knee Mobility & Activation",
      "exercises": [
        {
          "exerciseId": 42,
          "exerciseType": "REPS_BASED",
          "sets": 3,
          "reps": 15,
          "weight": null,
          "prescribedPainToleranceLevel": 3,
          "prescribedPhysicalExertionLevel": 4,
          "doctorNotesPreExercise": "Focus on controlled movement",
          "exerciseBlock": "WARM_UP"
        },
        {
          "exerciseId": 56,
          "exerciseType": "TIME_BASED",
          "sets": 2,
          "reps": null,
          "duration": 60,
          "exerciseBlock": "MAIN_SESSION"
        }
      ]
    }
  ]
}

Success Response:

{
  "workouts": [
    {
      "id": 789,
      "doctorId": 1,
      "patientId": 123,
      "isTemplate": false,
      "description": "Knee Mobility & Activation",
      "dateWorkoutScheduled": "2024-01-15T00:00:00.000Z",
      "dateWorkoutCurrentlyScheduledFor": "2024-01-15T00:00:00.000Z",
      "isWorkoutCompleted": false,
      "exercises": [
        {
          "id": 1234,
          "exerciseId": 42,
          "exerciseType": "REPS_BASED",
          "plannedSets": 3,
          "plannedReps": 15,
          "plannedWeight": null,
          "plannedDuration": null,
          "prescribedPainToleranceLevel": 3,
          "prescribedPhysicalExertionLevel": 4,
          "doctorNotesPreExercise": "Focus on controlled movement",
          "exerciseBlock": "WARM_UP",
          "orderInWorkout": 0,
          "exercise": {
            "id": 42,
            "name": "Quad Sets",
            "description": "Tighten thigh muscles..."
          }
        }
      ]
    }
  ]
}

Error Response:

{
  "error": "At least one workout is required"
}