Mobile Doctor HEP Management¶
Overview¶
The mobile app now includes a doctor-only Manage HEP flow for quickly editing a patient's next 10 home exercise sessions while the doctor is still in the room with the patient.
This feature is designed for: - Fast manual plan edits on mobile - Bulk exercise insertion across multiple upcoming sessions - Per-session exercise customization - Lightweight AI-assisted modifications from a bottom composer - Patient-specific exercise media, including personalized exercise videos
The goal is to make plan adjustment on mobile feel native, fast, and safe, rather than forcing doctors into the heavier web editing workflow.
Gating¶
This feature is behind the tenant feature flag:
therapistMobileHepManagement
Where It Is Checked¶
- Mobile UI:
Okta-Mobile/components/doctor/DoctorDashboard.tsx - API read endpoint:
OktaPT-API/src/routes/doctor/controller.ts - API save endpoint:
OktaPT-API/src/routes/doctor/controller.ts - Shared access helper:
OktaPT-API/src/helpers/verifyDoctorPatientAccess.ts
If the flag is disabled:
- The Manage HEP button does not appear on the doctor dashboard
- The doctor HEP session API returns 403
Tenant-Wide Access¶
HEP now uses the shared verifyDoctorPatientAccess() helper.
- If
allActivePatientsDashboardistrue, doctors can manage any active accepted non-archived patient within the tenant. - Otherwise, the doctor must still have a direct active accepted connection to the patient.
This same rule now governs HEP reads, HEP saves, therapist AI edits, therapist AI rebalance, and patient-scoped exercise picker enrichment.
Entry Point¶
Doctor Dashboard¶
File: Okta-Mobile/components/doctor/DoctorDashboard.tsx
Each patient card in the in-clinic dashboard now supports:
- Start Test as the primary action
- Manage HEP as a secondary action when therapistMobileHepManagement is enabled
- Record Exercise alongside Manage HEP when therapistPersonalizedExerciseVideo is also enabled
Navigation¶
Files:
- Okta-Mobile/app/(doctor)/_layout.tsx
- Okta-Mobile/app/(doctor)/(hep)/_layout.tsx
- Okta-Mobile/app/(doctor)/(hep)/manage.tsx
The HEP editor lives in the doctor route group:
It receives:
- patientId
- patientName
through Expo Router params.
Screen Design¶
Main Screen¶
File: Okta-Mobile/app/(doctor)/(hep)/manage.tsx
The main screen is a single full-screen editor with:
- Header: patient name + Editing next 10 sessions
- Top CTA: Add exercises for each day
- Scrollable list of the next 10 sessions
- Persistent bottom AI composer
The screen uses modal/sheet-style subflows for: - Exercise picker - Dosage editor - Video preview - Delete scope confirmation - Save/rebalance confirmation
During save, the screen also shows an inline Saving plan... status banner below the
top CTA so the save feels responsive immediately after the doctor taps Save.
Safe Areas¶
The HEP-specific add-exercise picker and dosage editor explicitly respect top safe areas on iPhone so header actions stay below the notch / Dynamic Island.
Files:
- Okta-Mobile/components/doctor/hep/HepExercisePickerModal.tsx
- Okta-Mobile/components/doctor/hep/HepDosageEditorModal.tsx
Session Window¶
Window Definition¶
The editor always works on the next 10 incomplete sessions after overdue rescheduling runs.
Server-Side Construction¶
Endpoint: GET /v2/doctor/patients/:patientId/hep-management?limit=10
Implementation: OktaPT-API/src/routes/doctor/controller.ts
The API:
1. Verifies doctor access to the patient
2. Triggers rescheduleOverdueWorkouts() for that patient before loading the HEP window
3. Loads incomplete workouts ordered by dateWorkoutCurrentlyScheduledFor, including any overdue workouts that were not moved
4. Marks sessions as protected if they started within the last hour
5. Attaches personalized exercise videos when available
6. Appends placeholder sessions until exactly 10 slots exist
Response Shape¶
{
"sessions": [
{
"slotIndex": 1,
"dayNumber": 1,
"date": "2026-03-02T00:00:00.000Z",
"workoutId": 123,
"isPlaceholder": false,
"isProtected": false,
"focus": "",
"exercises": [
{
"exerciseInWorkoutId": 456,
"exerciseId": 12,
"name": "Bridge",
"exerciseType": "REPS_BASED",
"exerciseBlock": "MAIN_SESSION",
"sets": 3,
"reps": 10,
"duration": null,
"weight": null,
"thumbnailUrl": "...",
"videoUrl": null,
"photoUrl": null,
"cloudflareStreamId": null,
"kinescopeVideoId": "abc123",
"personalizedVideo": {
"id": 99,
"cloudflareStreamId": "stream_123",
"updatedAt": "2026-03-01T12:00:00.000Z"
}
}
]
}
]
}
Protected Session Behavior¶
A session is treated as protected when:
- timeWorkoutStart is set
- isWorkoutCompleted is false
- the workout started within the last hour
Protected sessions: - remain visible in the list - show a lock banner - cannot be edited - are preserved by the save API
This uses the same protection logic as bulk web plan editing.
Manual Editing Flow¶
Bulk Add¶
Doctors can tap:
Add exercises for each day
This opens the exercise picker, then the dosage editor, then inserts the configured exercises into every editable session in the 10-session window.
Per-Session Add¶
Each session card has its own:
Add
button that uses the same picker and dosage editor, but inserts only into that session.
Edit Existing Exercise¶
Tapping the non-thumbnail portion of an exercise row opens the dosage editor for that exercise.
Editable fields:
- tracking type (REPS_BASED / TIME_BASED)
- sets
- reps or duration
- weight
- exercise block
Delete Existing Exercise¶
Trash removes an exercise from the current session.
If the same exerciseId appears in future editable sessions in the current 10-session window, the doctor is prompted to choose:
- This session only
- This and future sessions
If there are no future matches, only the current-session removal prompt is shown.
No Reordering in v1¶
Exercises are not drag-reorderable in this mobile flow.
New exercises append to the end of the session.
Exercise Picker¶
Component: Okta-Mobile/components/doctor/hep/HepExercisePickerModal.tsx
The picker reuses the mobile exercise-library patterns: - search - tag filters - segments - exercise cards - thumbnails
Shared components reused:
- Okta-Mobile/components/exercises/ExerciseCard.tsx
- Okta-Mobile/components/exercises/ExerciseListHeader.tsx
- Okta-Mobile/components/exercises/TagFilterSheet.tsx
- Okta-Mobile/hooks/useExercises.ts
Picker Behavior¶
- Card body press: select / deselect exercise
- Thumbnail press: open video preview if the exercise has a playable video source
Patient-Specific Media in Picker¶
The HEP screen owns a patient-scoped exercise catalog and refreshes it whenever the picker opens.
The picker requests exercises with patientId, so exercise results are enriched with:
- personalizedVideo when that patient has a custom exercise demo
That means a doctor sees patient-specific thumbnails and playback directly in the picker, without needing to save and reload.
API Support¶
The exercises endpoint now accepts optional patientId:
Implementation: OktaPT-API/src/routes/exercises/exercises.ts
When a doctor passes patientId, the backend:
1. verifies access to that patient
2. loads exercise results
3. attaches personalized video metadata for that patient's exercises
Exercise Media Behavior¶
Priority Rules¶
The HEP flow uses the same media priority as the rest of mobile:
personalizedVideo.cloudflareStreamIdkinescopeVideoIdcloudflareStreamIdvideoUrl(YouTube)photoUrl
Cross-reference:
- docs/personalized-exercise-videos.md
- docs/exercise-media-rendering.md
Thumbnail Clickability¶
In the HEP flow, thumbnails are clickable anywhere a playable video source exists:
- Current session exercise rows
- Exercise picker cards
Non-thumbnail taps keep their original behavior: - HEP session row body = edit exercise - picker card body = select exercise
Initial Kinescope Fix¶
Some HEP session payloads could arrive with a thumbnail URL but without all raw video source IDs fully populated.
To keep thumbnail playback working immediately on first load, the mobile normalization layer now infers missing playable IDs from known thumbnail URL patterns:
- Kinescope poster URL → infer kinescopeVideoId
- Cloudflare thumbnail URL → infer cloudflareStreamId
Implementation: Okta-Mobile/lib/doctorHepUtils.ts
Preview Modal¶
Thumbnail playback uses:
Okta-Mobile/components/workout/ExerciseMediaPreviewModal.tsxOkta-Mobile/components/workout/WorkoutVideoPlayer.tsx
This is a generic fullscreen preview modal for HEP media playback that supports: - personalized Cloudflare videos - Cloudflare Stream - Kinescope - YouTube
Personalized Videos in HEP¶
This feature fully supports patient-specific personalized exercise videos.
Where Personalized Videos Appear¶
If a patient has a personalized video for an exercise, that personalized media should be visible: - in the current HEP session list - in the add-exercise picker - immediately after adding the exercise to the plan - in thumbnail previews and fullscreen playback
Backend Support¶
The HEP session API uses:
attachPersonalizedVideos()fromOktaPT-API/src/services/personalizedVideoService.ts
to attach patient-specific personalizedVideo objects to workout exercises.
Mobile Preservation¶
When an exercise is selected from the picker and added into the local HEP draft, the app preserves:
- personalizedVideo
- cloudflareStreamId
- kinescopeVideoId
- videoUrl
- photoUrl
- thumbnailUrl
in local state so the correct media is shown immediately, not only after save/reload.
Implementation: Okta-Mobile/lib/doctorHepUtils.ts
AI Editing¶
Bottom Composer¶
The HEP screen includes a persistent bottom composer:
Okta-Mobile/components/doctor/hep/HepAiComposer.tsx
Doctors can type natural language requests such as: - symptoms the patient is reporting - day-specific changes - adding or removing certain movements - intensity adjustments
Endpoint¶
Request Shape¶
The mobile app sends:
- patientId
- currentPlan
- userMessage
- conversationHistory (currently empty in this mobile flow)
Apply Behavior¶
AI updates are applied only after the stream completes successfully.
The screen then shows:
- a compact change summary
- an Undo AI action that restores the previous local plan snapshot
- immediate thumbnail/video hydration for AI-added exercises from the patient-scoped exercise catalog
Streaming¶
The mobile screen uses the shared XHR/SSE-like streaming hook:
Okta-Mobile/hooks/useStreamingRequest.ts
During streaming:
- the keyboard is dismissed immediately
- the normal session list is replaced with skeleton session cards
- the skeleton session area remains vertically scrollable
- workouts_count marks cards as ready as the AI completes each session
The mobile streaming hook stores both section and section_streaming events in
streamingSections, which is how HEP consumes change_overview and
workouts_count from the therapist-plan-edit stream.
Save Flow¶
Primary Save Endpoint¶
The HEP editor saves through:
This is a dedicated mobile persistence path for the 10-session HEP window.
It avoids the heavier shared web edit-plan rewrite flow and updates sessions by
workoutId when available.
Save Payload¶
The mobile app converts the local 10-session window into:
- sessions[]
- slotIndex
- dayNumber
- workoutId
- full ISO date
- focus
- exercises
Empty placeholder sessions are still sent, but the backend treats them as:
- delete the existing workout if workoutId is present
- no-op if the slot is still a placeholder
Protected sessions are preserved by the backend even if they became protected after the screen first loaded.
When HEP performs the follow-up save after a background rebalance, the payload also
includes:
- notifyDoctorOnCompletion: true
focus now persists through Workout.doctorNote, so AI-generated or pre-existing
session focus text survives save/reload.
Mobile adapter: Okta-Mobile/services/doctorHepService.ts
Save-Time AI Rebalance¶
Why It Exists¶
After manual editing, some sessions may end up: - below target (fewer than 3 exercises) - above target (more than 5 exercises)
The doctor can choose to let AI rebalance the plan around the changes they just made.
Trigger¶
After a successful manual save, the app checks only the first 5 editable session slots for rebalance issues.
If any session is outside the target range, it prompts with:
- Rebalance plan?
- {{count}} sessions are outside the target of 3-5 exercises per day. Let us rebalance the plan for you.
Actions:
- No
- Yes
Rebalance Endpoint¶
Request Shape¶
{
"patientId": 123,
"currentPlan": [...],
"lockedChanges": [
{
"dayNumber": 1,
"lockedAddedExerciseIds": [12],
"lockedModifiedExerciseIds": [44],
"lockedRemovedExerciseIds": [87]
}
],
"rules": {
"minExercisesPerSession": 3,
"maxExercisesPerSession": 5
}
}
AI Scope¶
Both AI edit and AI rebalance remain intentionally scoped to the first 5 session slots in the HEP window, even though the screen now displays and saves 10 sessions. Manual editing continues to work across the full 10-session list.
Locked-Change Contract¶
The rebalance endpoint is constrained to preserve doctor edits.
It must: - not remove locked added exercises - not re-add locked removed exercises - not modify dosage or block for locked modified exercises - return every session in the input window - enforce 3-5 exercises per workout in the response
Response Schema¶
Defined in:
OktaPT-API/lib/openai_response_formats/pt_therapist_plan_rebalance_schema.ts
UX¶
The rebalance flow is now a background handoff rather than an in-place blocking flow.
After the initial manual save succeeds:
1. the app immediately marks the editor clean and shows a lightweight saved acknowledgement
2. if any editable sessions are outside the 3-5 exercise target, it then prompts the doctor to rebalance
3. if the doctor chooses Yes, mobile starts the rebalance request in the background
4. the doctor is immediately routed back to the in-clinic dashboard
5. the in-clinic dashboard shows a green top toast: The rebalanced plan will be emailed to you shortly.
6. if rebalance succeeds, mobile saves the rebalanced plan again with notifyDoctorOnCompletion: true
7. the backend then sends the doctor a PLAN_UPDATE notification/email that the updated plan is ready
If the doctor chooses No:
- the initial manual save stands as-is
- the HEP screen shows the normal saved toast before the user leaves
If the background rebalance fails: - the original manual save still stands - the failure is logged on mobile - no follow-up success notification is sent
Local State Architecture¶
Types¶
File: Okta-Mobile/types/doctorHep.ts
Main types:
- DoctorHepSession
- DoctorHepExercise
- DoctorHepEditLedgerEntry
- DoctorHepSaveResult
Hook¶
File: Okta-Mobile/hooks/useDoctorHepEditor.ts
Responsibilities: - load initial 5-session window - maintain local working plan - track dirty state - track locked add/edit/remove exercise IDs for AI rebalance - apply AI updates - support AI undo - save through the doctor HEP service
Utilities¶
File: Okta-Mobile/lib/doctorHepUtils.ts
Responsibilities: - normalize HEP session payloads - build new exercise drafts with defaults - preserve media metadata - convert local state to API payloads - apply AI plan results to local sessions - summarize session count violations for rebalance
Components¶
HepSessionCard¶
File: Okta-Mobile/components/doctor/hep/HepSessionCard.tsx
Shows: - day label - date - exercise count - protected banner - per-exercise rows - per-session add action
HepExerciseRow¶
File: Okta-Mobile/components/doctor/hep/HepExerciseRow.tsx
Shows: - thumbnail - play overlay when preview is available - exercise name - dosage summary - block label - delete button
Interaction split: - thumbnail press = preview - row press = edit
HepExercisePickerModal¶
File: Okta-Mobile/components/doctor/hep/HepExercisePickerModal.tsx
Uses the exercise-library UI pattern for patient-aware exercise selection.
HepDosageEditorModal¶
File: Okta-Mobile/components/doctor/hep/HepDosageEditorModal.tsx
Edits: - type - sets - reps / duration - weight - block
HepAiComposer¶
File: Okta-Mobile/components/doctor/hep/HepAiComposer.tsx
Bottom AI input with: - summary card - undo action - send button
Translations¶
All HEP strings were added to:
- Okta-Mobile/locales/en.json
- Okta-Mobile/locales/ru.json
Key namespace:
- hepManagement
Supporting additions were also made to:
- common.yes
- common.no
- inClinic.manageHep
Cross-reference:
- docs/mobile-translations.md
Verification¶
Implementation was verified with:
in:
- OktaPT-API
and:
in:
- Okta-Mobile
and:
in:
- OktaPT-API
This confirms the mobile HEP codepaths type-check and the backend HEP codepaths build successfully.
Key Files¶
Mobile¶
Okta-Mobile/app/(doctor)/(hep)/manage.tsxOkta-Mobile/app/(doctor)/(hep)/_layout.tsxOkta-Mobile/components/doctor/DoctorDashboard.tsxOkta-Mobile/components/doctor/hep/HepSessionCard.tsxOkta-Mobile/components/doctor/hep/HepExerciseRow.tsxOkta-Mobile/components/doctor/hep/HepExercisePickerModal.tsxOkta-Mobile/components/doctor/hep/HepDosageEditorModal.tsxOkta-Mobile/components/doctor/hep/HepAiComposer.tsxOkta-Mobile/components/workout/ExerciseMediaPreviewModal.tsxOkta-Mobile/hooks/useDoctorHepEditor.tsOkta-Mobile/hooks/useExercises.tsOkta-Mobile/lib/doctorHepUtils.tsOkta-Mobile/services/doctorHepService.tsOkta-Mobile/services/exerciseService.tsOkta-Mobile/types/doctorHep.tsOkta-Mobile/types/exercise.tsOkta-Mobile/locales/en.jsonOkta-Mobile/locales/ru.json
API¶
OktaPT-API/src/routes/doctor/index.tsOktaPT-API/src/routes/doctor/controller.tsOktaPT-API/src/routes/__tests__/doctor-hep-management.test.tsOktaPT-API/src/routes/ai/index.tsOktaPT-API/src/routes/ai/controllers.tsOktaPT-API/src/routes/exercises/exercises.tsOktaPT-API/src/services/personalizedVideoService.tsOktaPT-API/lib/openai_response_formats/pt_therapist_plan_rebalance_schema.tsOktaPT-API/prisma/migrations/20260301120000_add_workout_patient_incomplete_schedule_index/migration.sql
Related Docs¶
docs/mobile-ui-ux-design.mddocs/mobile-keyboard-handling.mddocs/mobile-exercise-management.mddocs/patient-workout-management.mddocs/therapist-ai-plan-generation.mddocs/personalized-exercise-videos.mddocs/exercise-media-rendering.mddocs/ai-integration-guide.mddocs/feature-flags.md