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¶
- Therapist opens patient dashboard and selects a patient requiring an initial plan
- PatientNotesInputModal opens where therapist enters clinical notes (up to 4000 characters)
- 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.
- Therapist clicks "Generate AI Plan" to initiate the streaming request
- Skeleton loading UI shows day cards appearing progressively as the backend sends SSE section events (
plan_summary,workouts_count) - AI generates 5-day plan based on notes, attachments, patient attributes, and exercise database
- InitialPlanCreationModal opens with the generated plan pre-populated
- 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.
- Therapist reviews and customizes each day's exercises (add, edit, remove, copy days)
- 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
Doctor Routes: OktaPT-API/src/routes/doctor/index.ts:34
Controller: generateInitialPlanFromDoctorNotes¶
Location: OktaPT-API/src/routes/ai/controllers.ts:1046-1355
Processing Steps:
- Input Validation
- Validates
doctorNotesis non-empty string -
Validates
patientIdis valid number -
Authorization Check
- Verifies active doctor-patient connection exists
-
Returns 403 if no connection found
-
Data Fetching
- Fetches available exercises (public + doctor's custom)
-
Gets existing patient attributes for context
-
SSE Setup
- Sets headers:
Content-Type: text/event-stream - Disables caching and buffering
-
Flushes headers to initiate stream
-
Heartbeat & Status Messages
- 1-second heartbeat interval prevents connection timeout
-
Progressive status messages at 10s, 20s, 30s, 40s intervals
-
Multimodal Input Building
- Starts with
input_textcontaining the request prompt - If
attachmentsare provided, appends each asinput_image(for images) orinput_file(for PDFs/other files) to the content array -
The combined content array is sent as the user message to OpenAI
-
OpenAI API Call
- Uses
gpt-5-mini-2025-08-07model -
Streams response with JSON schema enforcement
-
Response Processing & Section Events
- Accumulates streamed text deltas
- Forwards chunks to client as SSE events
- Parses partial JSON to extract completed fields:
- Sends
sectionevent withplan_summarywhen the summary string is fully extracted - Sends
sectionevent withworkouts_countas each new workout object is detected
- Sends
-
These section events power the frontend skeleton loading UI
-
Attribute Storage
- Parses final JSON response
-
Stores extracted user attributes via
createUserAttributesForAiModel -
Completion
- Sends
doneevent with parsed plan - Ends response stream
- Sends
Controller: createInitialPlan¶
Location: OktaPT-API/src/routes/doctor/controller.ts:862-928
Processing Steps:
- Validation (lines 866-869)
-
Ensures at least one workout is provided
-
Transaction (lines 873-921)
- Creates Workout records for each day
- Creates ExerciseInWorkout records for each exercise
-
Sets scheduled dates and exercise parameters
-
Response (line 923)
- 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):
Error Response (HTTP):
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: