Patient Workout Management (Therapist Perspective)¶
Overview¶
The Okta-Health system provides physical therapists with comprehensive tools to create, edit, and manage patient workout plans. This document covers all therapist-side workout management operations from the doctor dashboard.
Key Concepts¶
| Concept | Description |
|---|---|
| Workout | A single exercise session scheduled for a specific date |
| Session | An additional workout on the same day — a day can hold up to 3 sessions when the multipleSessionsPerDay flag is on (see Multiple Sessions Per Day) |
| Exercise | An individual movement within a workout (reps-based or time-based) |
| Plan | A collection of workouts spanning multiple days; the editors work on one week at a time (Day 1 = today) and the schedule carries it forward |
| Template | A reusable workout configuration that can be applied to any patient |
| Exercise Block | Workout structure category: WARM_UP, MAIN_SESSION, SUPPLEMENTAL_WORK, COOL_DOWN |
| Schedule | Which days a patient's sessions fall on and what happens when the plan runs out or a session is missed — see Plan Scheduling |
Creating Patient Workouts¶
A newer builder exists in beta
The HEP Builder is an opt-in, full-screen replacement for both the Initial Plan Creation Modal and the Edit Plan Modal, enabled per browser from the "New plan builder (beta)" checkbox on the doctor dashboard. It is off by default, and the flows documented on this page remain the default path for everyone who hasn't turned it on. Both write to the same underlying workout records.
Initial Plan Creation (New Patient)¶
For new patients, therapists create an initial multi-day plan using the Initial Plan Creation Modal.
Component: OktaPT-FE/components/plan-creation/InitialPlanCreationModal.tsx
State Hook: OktaPT-FE/lib/hooks/useInitialPlanState.ts
AI Generation Hook: OktaPT-FE/lib/hooks/useAIInitialPlanGeneration.ts
| API Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/v2/doctor/create-initial-plan |
POST | Required | Create initial patient plan |
Features:
- Days 1–7 scheduling with day picker (PlanDaySelector.tsx); Day 1 is today. Each day is a tile with its weekday and date and how many exercises it has (or Add when it has none); the day being edited is ringed in the accent, a day the patient already started says In progress and cannot be opened
- Copy day functionality (duplicate exercises to another day): the copy icon in the corner of an empty tile
- Clear day functionality (remove all exercises from a day): the trash icon in the corner of a filled tile
- AI-generated plan support with manual customization; an AI plan says Written by AI under the title
- Exercise block organization (warm-up, main session, etc.)
Look (redesigned 2026-10-21 on the design system): the plan preview is one card per day with the exercises as rows (picture, name, dose, weight, block, the note to the patient on one line); a day with several sessions lists them as sections of its card with Edit and remove per session. Opening a day shows the day's date in the title and its exercises as rows: picture and name, the dose as steppers (sets × reps or seconds, with the Reps/Time toggle and the weight when there is one), + Note for the instructions to the patient, and More for the weight, the block and the pain and effort targets; Add exercise opens the library picker and Add from template a saved template. The same rows are used by Edit session on a planned session and by Create session; leaving any of them with unsaved changes asks first.
Request Body:
{
"patientId": 123,
"startDate": "2024-01-15",
"endDate": "2024-01-24",
"schedule": { "kind": "WEEKLY", "weekdays": [1, 3, 5] },
"autoContinue": true,
"workouts": [
{
"dateScheduled": "2024-01-15",
"exercises": [
{
"exerciseId": 1,
"exerciseType": "REPS_BASED",
"sets": 3,
"reps": 10,
"exerciseBlock": "MAIN_SESSION"
}
]
}
]
}
Single Workout Creation¶
For ad-hoc workouts outside of the initial plan flow.
Component: OktaPT-FE/components/WorkoutCreationModal.tsx
| API Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/create-workout |
POST | Required | Create single workout |
Backend Location: OktaPT-API/src/routes/legacy.ts
Request Body:
{
"patientId": 123,
"dateScheduled": "2024-01-20",
"exercises": [
{
"exerciseId": 1,
"exerciseType": "REPS_BASED",
"sets": 3,
"reps": 12,
"weight": 10,
"exerciseBlock": "MAIN_SESSION",
"doctorNotesPreExercise": "Focus on form"
}
]
}
Multiple Sessions Per Day¶
When the multipleSessionsPerDay tenant feature flag is enabled, therapists can schedule more than one session on a single day of a plan — for example a morning mobility routine and an evening strengthening block. The cap is MAX_SESSIONS_PER_DAY = 3 (one primary session plus up to two extras).
Each session is independently named, collapsible, editable, removable, and reorderable in the plan editor. Adding sessions works the same way in both the Initial Plan Creation Modal and the Edit Plan Modal.
Components & State:
| File | Role |
|---|---|
OktaPT-FE/components/plan-creation/DayWorkoutPreview.tsx |
Renders a day as a card with its session (or sessions as sections); add / rename / edit / remove session controls |
OktaPT-FE/components/ExerciseSelector/SelectedExerciseCard.tsx |
One exercise row of the day editor, Edit session and Create session: dose steppers, note, and weight / block / targets under More (the controls come from components/plan-creation/hep-builder/controls/) |
OktaPT-FE/components/plan-creation/EditPlanModal.tsx |
Gated by multipleSessionsPerDay; wires onAddSession / onEditSession / onRemoveSession / onRenameSession |
OktaPT-FE/components/plan-creation/InitialPlanCreationModal.tsx |
Same gating and session controls for new patients |
OktaPT-FE/lib/hooks/useEditPlanState.ts, useInitialPlanState.ts |
Hold the primary workouts map plus an extraSessions map (sessions 2–3 per day) and the MAX_SESSIONS_PER_DAY constant |
Data Model: No schema change is needed. Extra sessions are simply additional Workout rows sharing the same dateWorkoutCurrentlyScheduledFor. Same-day sessions are returned in creation order — patient upcoming workouts are ordered by (dateWorkoutCurrentlyScheduledFor ASC, id ASC) (see OktaPT-API/src/routes/workouts/patientUpcoming.ts), so the id tiebreak keeps sessions in the order they were added.
Days that already started keep the same protection described under Workout Locking — a session that's in progress can't be modified.
Editing Patient Workouts¶
Bulk Plan Editing (Edit Plan Modal)¶
Edit upcoming workouts across the coming week (Day 1 = today).
Component: OktaPT-FE/components/plan-creation/EditPlanModal.tsx
State Hook: OktaPT-FE/lib/hooks/useEditPlanState.ts
| API Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/v2/doctor/edit-plan |
PUT | Required | Bulk edit patient plan |
Backend Location: OktaPT-API/src/routes/doctor/controller.ts:1185 (editPlan function)
Key Constraint: Workouts started within the last hour are protected and cannot be modified. This prevents disrupting patients who are actively exercising.
Cross-Therapist Access: Any therapist within the same clinic (tenant) can edit any patient's plan, not just the therapist assigned to that patient. This allows clinics to cover for each other when a therapist is unavailable.
How It Works:
1. Calls POST /v2/doctor/patients/{id}/plan-schedule/prepare-editing, which settles missed sessions and fills the week with the sessions the plan would add anyway, then refetches the patient's workouts
2. Loads them into the day selector for editing
3. On save, deletes non-protected incomplete workouts in the range
4. Creates new workouts from the updated configuration (marked source: CLINICIAN), records the schedule, and drops system-scheduled sessions after the window so they are regenerated from the new plan
The request body accepts the same optional schedule and autoContinue fields as create-initial-plan.
AI-Assisted Plan Editing¶
Therapists can modify patient workout plans using natural language instructions instead of manually editing each exercise.
Component: OktaPT-FE/components/plan-creation/ai-edit/AIEditPlanPanel.tsx
State Hook: OktaPT-FE/lib/hooks/useAIPlanEdit.ts
Streaming Hook: OktaPT-FE/lib/hooks/useAIPlanEditStream.ts
| API Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/v2/ai/therapist-plan-edit |
POST | Required | Generate AI-modified workout plan |
How to Access: 1. Open the Edit Plan Modal for a patient 2. Click the "AI Assistant" button in the modal header 3. The AI chat panel slides in from the right
Quick Actions: The AI panel provides quick action buttons for common modifications: - Make easier - Reduce intensity, sets, reps, or weight - Make harder - Increase difficulty parameters - Remove equipment - Filter out exercises requiring specific equipment - Add warm-up - Insert warm-up exercises at the start - Add cool-down - Append stretching/cool-down exercises
File Attachments in Chat: Therapists can attach files mid-conversation to provide additional context for the AI (e.g., a new MRI scan, updated referral notes). Files can be attached via the paperclip button or by dragging and dropping onto the chat panel. Attachment metadata (filename, type) appears as chips in the sent message bubble. Per-message attachments are sent separately from any initial-plan attachments.
Custom Instructions: Therapists can type natural language instructions such as: - "Remove all dumbbell exercises and replace with bodyweight alternatives" - "Add 5 minutes of stretching to each day" - "Make Monday and Wednesday harder, keep Friday light" - "Focus on lower body strengthening for knee rehab"
Review Workflow:
- Streaming Preview: As the AI generates modifications, exercises appear progressively with skeleton loading states
- Comparison View: Once complete, a side-by-side comparison shows:
- Original exercises on the left
- Modified exercises on the right
- NEW badge for added exercises
- CHANGED badge for modified exercises
- Exercise thumbnails for visual reference
- Accept/Reject: Therapist can accept all changes, reject all, or selectively approve individual modifications
Patient Context:
The AI automatically extracts and saves patient attributes mentioned in instructions (e.g., "patient has knee pain" → saves kneeIssues: true). These attributes persist and inform future AI suggestions.
Key Components:
| Component | Purpose |
|---|---|
AIEditPlanPanel.tsx |
Main chat interface with message history |
AIQuickActions.tsx |
Quick action button grid |
AIPlanComparisonView.tsx |
Before/after comparison display |
AIExerciseCard.tsx |
Individual exercise card with change indicators |
AISkeletonCard.tsx |
Loading placeholder during streaming |
Protected Workout Logic:
const ONE_HOUR_AGO = new Date(Date.now() - 60 * 60 * 1000);
// Only preserve if started within last hour
const startedWorkouts = await tx.workout.findMany({
where: {
patientId,
isWorkoutCompleted: false,
timeWorkoutStart: {
not: null,
gte: ONE_HOUR_AGO,
},
dateWorkoutCurrentlyScheduledFor: {
gte: startDateObj,
lte: endDateObj,
},
},
});
Single Workout Editing¶
Edit individual workout details from the workout details view.
Component: OktaPT-FE/components/doctor/dashboard/WorkoutDetailsModalV2.tsx
| API Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/workout/{workoutId}/exercises |
POST | Required | Update workout exercises |
Backend Location: OktaPT-API/src/routes/legacy.ts
Capabilities: - Add/remove/reorder exercises (drag and drop to reorder; exercises automatically reposition when their block type is changed) - Change scheduled date - Modify exercise parameters (sets, reps, duration, weight) - Add pre-exercise doctor notes - Set exercise blocks
Request Body:
{
"dateScheduled": "2024-01-20",
"formattedExercises": [
{
"exerciseInWorkoutId": 456,
"exerciseId": 1,
"exerciseType": "REPS_BASED",
"sets": 3,
"reps": 12,
"exerciseBlock": "MAIN_SESSION"
}
]
}
Adding Doctor Notes¶
Workout-Level Notes¶
Add notes that apply to the entire workout session.
Component: OktaPT-FE/components/doctor/dashboard/WorkoutNote.tsx
| API Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/workout/{workoutId}/note |
PATCH | Required | Add/update workout note |
Backend Location: OktaPT-API/src/routes/legacy.ts
Request Body:
Constraints: - Maximum 1000 characters
Exercise-Level Post-Notes¶
Add notes after reviewing patient's exercise performance or video.
| API Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/v2/exercises/exercises-in-workout/{id}/doctor-post-notes |
PATCH | Required | Add post-exercise note |
/v2/exercises/workouts/{workoutId}/exercises/doctor-post-notes |
PATCH | Required | Bulk add post-exercise notes |
Backend Location: OktaPT-API/src/routes/exercises/doctorFeedback.ts
Single Exercise Request:
Bulk Request:
{
"exercises": [
{
"exerciseInWorkoutId": 123,
"doctorNotesPostExercise": "Improve hip mobility"
}
]
}
Deleting/Clearing Workouts¶
Clear Day (Within Plan Editor)¶
UI Components:
- OktaPT-FE/components/plan-creation/PlanDaySelector.tsx - Day picker with clear button
- OktaPT-FE/components/plan-creation/DayButton.tsx - Individual day with trash icon
How It Works:
1. Therapist clicks trash icon on a day card
2. window.confirm() dialog asks for confirmation
3. Workout is removed from local state
4. On plan save, the backend handles deletion
Backend Deletion on Save¶
When saving an edited plan, the backend:
- Identifies workouts started within the last hour (protected)
- Deletes ExerciseInWorkout records for non-protected workouts
- Deletes Workout records for non-protected workouts
- Creates new workouts from the submitted configuration
Deletion Logic (in transaction):
// Delete exercises first (foreign key constraint)
await tx.exerciseInWorkout.deleteMany({
where: {
workout: {
patientId,
isWorkoutCompleted: false,
id: { notIn: preservedWorkoutIds },
dateWorkoutCurrentlyScheduledFor: {
gte: startDateObj,
lte: endDateObj,
},
},
},
});
// Then delete workouts
await tx.workout.deleteMany({
where: {
patientId,
isWorkoutCompleted: false,
id: { notIn: preservedWorkoutIds },
dateWorkoutCurrentlyScheduledFor: {
gte: startDateObj,
lte: endDateObj,
},
},
});
Workout Templates¶
Reusable sessions a therapist can drop into any patient's plan. A therapist sees three kinds in one list, on the Workout Templates page and in the template picker of the plan builders:
- Ready-made templates (system templates in the code and the admin area) — sessions every clinic has (a knee program after surgery, a low back routine, a balance program and so on), built from the platform exercise library and managed by the platform team in the admin area. They are read-only: opening one shows every exercise with its dose, block and the note the patient would get (each exercise opens its details and video, as in the exercise library), and Copy and edit there gives the therapist a private copy of their own, in their language, to change freely. On the page they sit in their own section as cards (name, category, length, level, a line about the program and its exercises as thumbnails) with category filters.
- Your templates — the therapist's own, as rows: open one to edit it, or copy and delete it from the row.
- From your clinic — colleagues' public templates, which open the same way and can be copied.
The template picker of the plan builders lists the same three kinds, each card with the template's first exercises and their doses. When a template goes into a plan, each exercise keeps its type (reps or time), dose (per-set targets included), block and the note to the patient; a Russian-speaking therapist gets the Russian text of a ready-made template.
Backend Location: OktaPT-API/src/routes/workouts/templates.ts (therapist side), OktaPT-API/src/services/systemTemplateService.ts and OktaPT-API/src/routes/admin/systemTemplates.controllers.ts (admin side). Internals: System Templates.
| API Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/v2/workouts/templates |
GET | Required | The templates the therapist can use: their own and the clinic's public ones; with ?include=system also the system ones (isSystem: true, nameRu / descriptionRu set). A client that doesn't ask (a web bundle from before system templates) gets none |
/v2/workouts/templates |
POST | Required | Create new template |
/v2/workouts/templates/{id}/copy |
POST | Doctor | Copy any visible template into a private one of the therapist's own (per-set targets and block notes included); body { "lang": "en" \| "ru" } picks the language of the copied text |
/v2/workouts/templates/{id} |
PUT | Required | Update one of the therapist's own templates (every templateExerciseId must belong to it, every exerciseId must be in the clinic's library) |
/v2/workouts/templates/{id} |
DELETE | Required | Soft delete one of the therapist's own templates (sets isActive: false) |
/v2/workouts/templates/{id}/create-workout |
POST | Doctor | Create a session from any visible template for one of the doctor's patients (patientId, dateScheduled, optional lang); not used by the apps, which put a template into the plan builder instead |
Template Visibility¶
- Private templates: Only visible to the creator
- Public templates: Visible to all therapists in the same clinic (tenant)
- System templates ("Ready-made templates" in the app): Visible to every therapist in every clinic; edited only in the admin area. The tenant middleware reads them next to the clinic's own rows (
WorkoutTemplate.tenantIdis NULL), and writes through the tenant client never reach them.
Creating a Template¶
Create template (and editing one of your own) opens the template editor, full screen like the plan builder: the name, a category and a length (one row on a wide screen) and a description on the left with the exercise rows under them, and the exercise library beside them on a wide screen (on a phone, Add exercises opens it as a sheet). The library is the same picker as the HEP builder's: search by name (the ranked matcher, and search by meaning as you type where the clinic has hepSmartSearch), All / My exercises / Favorites, the tag filter, and Search with AI (or Extended search) at the end of the grid; see exercise-ai-search.md. Only the search box stays pinned: the scope buttons, the tag filter and the AI results line scroll away with the cards, and the grid fits as many cards per row as its column allows (four on a big monitor), so on a 13–14" laptop two rows of cards stay in view with each name and its + together. + on a card adds the exercise to the template without closing anything, the card then shows Added, and a thumbnail or name opens the exercise's details and video. Each row sets the type (reps or time), sets, reps or seconds, the block and a note to the patient; rows keep their block order (warm-up, main session, supplemental, cool-down) and move up or down within their block. A row with different targets per set (see the workout logbook) shows them ("Sets differ: 12 / 10 / 8") and keeps them until its sets, reps or seconds are changed.
The request behind the editor:
{
"name": "Lower Body Strength",
"description": "Focus on quad and glute activation",
"category": "strength",
"difficultyLevel": "INTERMEDIATE",
"estimatedDuration": 45,
"suitableConditions": ["knee_rehab", "hip_mobility"],
"isPublic": false,
"exercises": [
{
"exerciseId": 1,
"exerciseType": "REPS_BASED",
"sets": 3,
"reps": 10,
"exerciseBlock": "MAIN_SESSION"
}
]
}
Copying a Template¶
POST /v2/workouts/templates/{id}/copy with { "lang": "ru" } answers 201 with the new template: the therapist's own (creatorId, their tenant), private, named like the source (a copy of the therapist's own template gets " (copy)" added), with every exercise row and the note in the chosen language. The web app opens the copy in the editor right away.
Creating Workout from Template¶
Key Constraints & Rules¶
Workout Locking¶
| Condition | Can Edit? | Rationale |
|---|---|---|
| Not started | Yes | Patient hasn't begun |
| Started > 1 hour ago | Yes | Likely abandoned |
| Started within 1 hour | No | Patient may be exercising |
| Completed | No | Historical record |
Plan Edit Window¶
- Every plan editor (classic modals, HEP Builder, mobile Manage HEP) edits the coming week: today through six days ahead
- Past workouts cannot be modified through the plan editor
- Completed workouts are preserved regardless of date
- Sessions the system scheduled after the week are dropped on save and regenerated from the saved plan; a clinician's own sessions out there are kept
After the Plan Runs Out¶
Sessions are added on the patient's schedule when fewer than three session days remain, as copies of the current plan (AI-adapted when the tenant and therapists allow it). Missed sessions move to the next scheduled day in order. The full rules, the data model and the endpoints are in Plan Scheduling.
Exercise Configuration¶
Required fields for each exercise:
- exerciseId - Reference to exercise definition
- exerciseType - Either REPS_BASED or TIME_BASED
- sets - Minimum 1 (auto-corrected from 0)
Optional fields:
- reps - For REPS_BASED exercises
- duration - For TIME_BASED exercises (in seconds)
- weight - In kilograms
- exerciseBlock - Workout structure category
- prescribedPainToleranceLevel - 1-10 scale
- prescribedPhysicalExertionLevel - 1-10 scale
- doctorNotesPreExercise - Instructions for patient
Legacy Code Notes¶
WorkoutDetailsModal.tsx (Legacy)¶
Path: OktaPT-FE/components/doctor/dashboard/WorkoutDetailsModal.tsx
Status: Superseded by WorkoutDetailsModalV2.tsx
Differences from V2:
- Does not support exerciseBlock field in exercise mapping
- Uses PatientSummary type instead of PatientSummaryV2
- Action buttons may be in different positions
Recommendation: Use WorkoutDetailsModalV2.tsx for new development.
API Reference Table¶
| Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/v2/doctor/create-initial-plan |
POST | Required | Create initial patient plan |
/v2/doctor/edit-plan |
PUT | Required | Bulk edit patient plan |
/v2/ai/therapist-plan-edit |
POST | Required | Generate AI-modified workout plan |
/create-workout |
POST | Required | Create single workout |
/workout/{id}/exercises |
POST | Required | Update workout exercises |
/workout/{id}/note |
PATCH | Required | Add/update workout note |
/v2/exercises/exercises-in-workout/{id}/doctor-post-notes |
PATCH | Required | Add post-exercise note |
/v2/exercises/workouts/{id}/exercises/doctor-post-notes |
PATCH | Required | Bulk add post-exercise notes |
/v2/workouts/templates |
GET | Required | List templates |
/v2/workouts/templates |
POST | Required | Create template |
/v2/workouts/templates/{id} |
PUT | Required | Update template |
/v2/workouts/templates/{id} |
DELETE | Required | Soft delete template |
/v2/workouts/templates/{id}/create-workout |
POST | Required | Create workout from template |
Database Models¶
Workout¶
model Workout {
id Int @id @default(autoincrement())
name String?
category String?
doctor User @relation("DoctorWorkouts", ...)
doctorId Int
patient User? @relation("PatientWorkouts", ...)
patientId Int?
isTemplate Boolean
dateWorkoutScheduled DateTime
dateWorkoutCurrentlyScheduledFor DateTime @default(now())
timeWorkoutStart DateTime?
timeWorkoutEnd DateTime?
isWorkoutCompleted Boolean
doctorNote String?
description String?
exercises ExerciseInWorkout[]
source WorkoutSource? // who put the session on the plan
continuedFromWorkoutId Int? // the session a plan-continuation copy repeats
tenantId Int @default(1)
}
ExerciseInWorkout¶
model ExerciseInWorkout {
id Int @id @default(autoincrement())
workout Workout @relation(...)
workoutId Int
exercise Exercise @relation(...)
exerciseId Int
exerciseType ExerciseType? @default(REPS_BASED)
plannedSets Int
actualSets Int?
plannedReps Int?
actualReps Int?
plannedWeight Float?
actualWeight Float?
plannedDuration Int?
actualDuration Int?
doctorNotesPreExercise String?
doctorNotesPreExerciseAt DateTime?
doctorNotesPostExercise String?
doctorNotesPostExerciseAt DateTime?
prescribedPainToleranceLevel Int?
prescribedPhysicalExertionLevel Int?
isExerciseCompleted Boolean
orderInWorkout Int
exerciseBlock ExerciseBlock? @default(MAIN_SESSION)
tenantId Int @default(1)
}
Testing Checklist¶
Initial Plan Creation¶
- [ ] Create plan for new patient with AI generation
- [ ] Manually add exercises to multiple days
- [ ] Copy day exercises to another day
- [ ] Clear a day and verify exercises removed
- [ ] Save plan and verify workouts created in database
Single Workout Creation¶
- [ ] Create workout from doctor dashboard
- [ ] Select date and add exercises
- [ ] Verify patient notification sent
Plan Editing¶
- [ ] Edit existing patient plan
- [ ] Verify protected workouts (started within 1 hour) cannot be modified
- [ ] Add exercises to empty days
- [ ] Remove exercises from existing days
- [ ] Save and verify changes persisted
Single Workout Editing¶
- [ ] Edit workout exercises
- [ ] Change scheduled date
- [ ] Add pre-exercise notes
- [ ] Verify incomplete workout can be edited
- [ ] Verify completed workout cannot be edited
Doctor Notes¶
- [ ] Add workout-level note
- [ ] Add post-exercise note after reviewing performance
- [ ] Verify note length validation (max 1000 chars)
Templates¶
- [ ] Create private template
- [ ] Create public template
- [ ] Apply template to patient
- [ ] Edit template exercises
- [ ] Delete template (verify soft delete)