Skip to content

App Entrance Tracking — Detailed Changes (2026-04-08)

Summary

Added a full-stack "App Entrance Tracking" system across OktaPT-API (14 files), OktaPT-FE (8 files), and Okta-Mobile (5 files). When a patient opens the app without completing a workout, the system creates an inferred workout after a 2-hour delay window, giving therapists engagement visibility.

Data Flow

┌─────────────────┐     ┌─────────────────┐
│  Web Dashboard   │     │ Mobile Dashboard │
│  (OktaPT-FE)     │     │ (Okta-Mobile)    │
└────────┬─────────┘     └────────┬─────────┘
         │  POST /v2/app-entrances/log       │
         └────────────┬──────────────────────┘
         ┌────────────────────────┐
         │  appEntrances/         │
         │  controller.ts         │
         │  (check flag, compute  │
         │   localDate, upsert    │
         │   AppEntrance)         │
         └────────────┬───────────┘
                      │ enqueueAppEntranceFinalization()
         ┌────────────────────────┐
         │  BullMQ Queue          │
         │  "app-entrance-        │
         │   finalization"        │
         │  (2h delay)            │
         └────────────┬───────────┘
                      │ after delay
         ┌────────────────────────┐
         │  Worker (worker.ts)    │
         │  → processAppEntrance  │
         │    (jobs/process-      │
         │     AppEntrance.ts)    │
         └────────────┬───────────┘
              ┌───────┴────────┐
              ▼                ▼
     ┌──────────────┐  ┌──────────────┐
     │ Same-day real │  │ No real      │
     │ workout?      │  │ workout      │
     │ → CANCELED    │  │ → Create     │
     └──────────────┘  │   mirror     │
                       │   workout    │
                       └──────┬───────┘
                    ┌─────────┴──────────┐
                    ▼                    ▼
           ┌──────────────┐    ┌──────────────┐
           │ Streak        │    │ RTM Billing   │
           │ Rebuild       │    │ Recalculate   │
           └──────────────┘    └──────────────┘

Status Lifecycle State Machine

                        ┌──────────────────────────┐
                        │        PENDING            │
                        │  (created on app open)    │
                        └──┬────┬────┬────┬────────┘
                           │    │    │    │
          real workout ────┘    │    │    └──── feature disabled
          completed             │    │         at finalization
              │                 │    │              │
              ▼                 │    │              ▼
 ┌──────────────────────┐      │    │    ┌─────────────────────┐
 │ CANCELED_BY_REAL_     │      │    │    │ SKIPPED_FEATURE_    │
 │ WORKOUT               │      │    │    │ DISABLED            │
 └──────────────────────┘      │    │    └─────────────────────┘
                                │    │
               no source ───────┘    └──── source workout found,
               workout                     no same-day real workout
                  │                              │
                  ▼                              ▼
    ┌──────────────────────┐    ┌──────────────────────────┐
    │ SKIPPED_NO_SOURCE_   │    │ FINALIZED_TO_INFERRED_   │
    │ WORKOUT              │    │ WORKOUT                  │
    └──────────────────────┘    └──────────────────────────┘

OktaPT-API Changes (14 files)

New Files

File Purpose
prisma/migrations/20260409011908_add_app_entrance_tracking/migration.sql Creates AppEntranceStatus enum, AppEntrance table, adds completedViaAppEntrance and mirroredFromWorkoutId to Workout
src/routes/appEntrances/controller.ts logEntrance handler — feature flag check, timezone lookup, localDate computation, upsert-or-extend logic, same-day workout check, queue enqueue
src/routes/appEntrances/index.ts Express router: POST /log with authentication middleware
src/queue/appEntranceQueue.ts BullMQ queue setup, enqueueAppEntranceFinalization() with dedup (deterministic ID) and re-queue (timestamped ID) modes
src/jobs/processAppEntrance.ts Finalization logic: pre-checks, feature flag re-check, source workout lookup, serialized transaction with FOR UPDATE row lock, mirror workout creation with exercise copy, post-transaction streak rebuild and RTM recalc
src/services/streakSimulation.ts simulateStreaks() — replays full workout history to compute streak state from scratch. Groups workouts by local calendar day, walks chronologically computing daily/weekly streaks with rest-day logic

Modified Files

File Change
prisma/schema.prisma Added AppEntranceStatus enum, AppEntrance model, completedViaAppEntrance/mirroredFromWorkoutId on Workout, relations on User and Tenant
src/middleware/prisma.ts Added AppEntrance to TENANT_MODELS set for tenant isolation middleware
src/routes/index.ts Registered /app-entrances route
src/routes/doctor/controller.ts getWorkoutSessions now includes completedViaAppEntrance in response
src/routes/legacy.ts Workout completion handler cancels PENDING entrances via updateMany
src/routes/streaks/controllers.ts Added rebuildStreakForPatient() using simulateStreaks for full streak replay
src/jobs/notificationJobs.ts Added 15-minute repair sweep cron for stale PENDING entrances
src/worker.ts Registered app-entrance-finalization worker (concurrency 3)

OktaPT-FE Changes (8 files)

File Change
pages/patient/dashboard.tsx Fires POST /v2/app-entrances/log on mount (behind feature flag). Changed useEffect to await fetchFeatures() before refreshDashboardData() so hasFeature() is populated.
pages/patient/past-workouts.tsx "App Visit" badge (emerald green) on workouts with completedViaAppEntrance
components/doctor/dashboardV2/WorkoutSessionsTable.tsx "App Entrance" badge (emerald green) on sessions with completedViaAppEntrance — both compact and expanded views
lib/stores/doctorDashboard.ts Added completedViaAppEntrance to WorkoutSessionV2 interface
lib/types/exercise.ts Added completedViaAppEntrance to PatientWorkout interface
pages/admin/feature-flags.tsx Added appEntranceTracking to KNOWN_FEATURE_FLAGS
public/locales/en/common.json Added pastWorkouts.workoutDetails.appVisit and doctorDashboardV2.appEntrance
public/locales/ru/common.json Russian translations for the above

Okta-Mobile Changes (5 files)

File Change
components/patient/PatientDashboard.tsx Fires POST /v2/app-entrances/log once per mount via useEffect + useRef guard. Uses useTenantFeatures hook.
app/(tabs)/history.tsx "App Visit" badge (emerald green with phone icon) on workouts with completedViaAppEntrance
types/workout.ts Added completedViaAppEntrance to Workout interface
locales/en.json Added history.appVisit
locales/ru.json Russian translation for the above

Design Decisions

  1. 2-hour delay window: Gives patients time to complete a real workout after opening the app. Window extends on re-entry.
  2. Row-level locking: SELECT ... FOR UPDATE in the finalization transaction prevents two concurrent workers from both creating a mirror workout for the same entrance.
  3. Full streak rebuild (not incremental): Finalization may fire after the local day boundary, making incremental streak update unsafe. Uses simulateStreaks to replay full history.
  4. No celebrations or workout generation: Mirror workouts don't trigger CelebrationService or queueWorkoutGeneration to avoid noise.
  5. Fire-and-forget client calls: The POST /v2/app-entrances/log call never fails the dashboard load — errors are silently caught.
  6. Repair sweep: 15-minute cron compensates for lost BullMQ jobs (Redis restart, worker crash).