Smart Notifications — Detailed Technical Changes (2026-04-02)¶
This document provides a file-by-file breakdown of the Smart Notifications feature across all three repositories.
Summary¶
Smart Notifications is a multi-layered notification engine that sends personalized, AI-powered workout reminders and celebration messages to patients. The system spans 30+ files across the API, frontend, and mobile codebases.
See smart-notifications.md for the full architecture documentation.
OktaPT-API¶
New Service Files (src/services/smartNotifications/)¶
| File | Lines | Purpose |
|---|---|---|
dispatcher.ts |
395 | Core dispatch loop — runs every 15 minutes, finds eligible patients by checking if their target notification time falls in the current window, runs anti-annoyance filter, selects content, and enqueues to BullMQ |
antiAnnoyanceFilter.ts |
388 | 13-point decision checklist evaluated in order (first-block-wins). Checks: kill switch, mode off, paused, already completed, currently exercising, nothing scheduled, quiet hours, too early, daily cap, weekly cap, minimum gap, rest day, no channel. Lazy counter reset using DST-safe local day bounds |
contentGenerator.ts |
225 | AI content generation via OpenAI (GPT-4 variants). Constructs system prompts with segment-specific tone, humor level instructions, and constraint enforcement. Produces 3 variants per call with title/pushBody/emailBody. All calls logged via aiResponseLogService |
templateBank.ts |
284 | 18 handwritten notification templates (3 per engagement segment) in English and Russian. Template selection avoids recently used variant IDs and categories. Slot interpolation via regex replacement |
smartTimingCalculator.ts |
227 | Computes optimal notification time using trimmed circular weighted mean of last 21 workout start times. Exponential decay weighting (14-day half-life), 15% trimming, sin/cos circular mean for midnight wraparound, 30-minute lead time subtraction |
engagementClassifier.ts |
139 | Classifies patients into 6 segments (NEVER_STARTED, EARLY, BUILDING, STREAKING, LAPSING, DORMANT) based on workout count, recency, streak length, and completion rate |
profileRecomputation.ts |
154 | Nightly job (3:00 AM UTC) — iterates all enabled tenants and active patients, runs classifier + timing calculator, upserts SmartNotificationProfile. Handles expired admin pauses |
contentPreGeneration.ts |
223 | Nightly job (3:30 AM UTC) — pre-generates AI content for all active profiles. Checks cache sufficiency (>= 3 unexpired variants), gathers slots for "tomorrow" accuracy, stores with 48h TTL. Cleans up expired cache |
celebrationService.ts |
226 | Post-workout celebration handler. Idempotency check (one per local day), streak milestone detection (5, 10, 25, 50, 75, 100, 150, 200, 365), selects from milestone or standard celebration copy, enqueues as PROGRESS_CELEBRATION |
channelSelector.ts |
69 | Segment-aware channel routing. EMAIL_FIRST for NEVER_STARTED, PUSH_FIRST for others. Checks per-type patient preferences and available channels (device token for push, email address for email) |
notificationSender.ts |
163 | BullMQ job processor. Final freshness gate (skips if workout completed or in progress — celebrations bypass this). Calls notificationService.sendNotification(), updates profile counters and variety tracking (lastVariantIds, lastPersonalizationCategories) |
timezoneUtils.ts |
170 | DST-safe timezone utilities. localMidnightToUtc (iterative offset correction), getLocalDayBounds (independent start/end midnights for 23h/25h DST days), getLocalTimeString, getLocalDayOfWeek, isInQuietWindow (handles overnight spans) |
featureFlag.ts |
17 | Checks TenantSettings.features.smartNotifications |
defaults.ts |
43 | Default cadence caps per segment (e.g., STREAKING: 1 daily / 5 weekly, DORMANT: 1 daily / 2 weekly), default channel strategy |
index.ts |
21 | Barrel export for all smart notification services |
New Routes (src/routes/smart-notifications/)¶
| File | Lines | Purpose |
|---|---|---|
index.ts |
52 | Route definitions — 2 patient endpoints (GET/PATCH profile) + 9 admin endpoints (tenants, policy CRUD, patient list/detail, overrides, test-generate, test-send, analytics) |
controllers.ts |
732 | Controller logic for all endpoints. Key functions: getProfile, updateProfile, upsertPolicy (validates AI models), listPatients (paginated with profiles + streaks), testGenerate (gathers slots → ContentGenerator → 3 variants), testSend (enqueues test notification), getAnalytics (7-day suppression breakdown). Helper: resolveTestContext gathers comprehensive patient context |
New Queue File¶
| File | Lines | Purpose |
|---|---|---|
src/queue/smartNotificationQueue.ts |
53 | BullMQ enqueue helper. Queue name: "smart-notification". Job ID: smart-notif-${userId}-${Date.now()}. Retry: 2 attempts with exponential backoff (5s initial). Dev fallback: processes directly if REDIS_URL not set |
Modified Files¶
| File | Change |
|---|---|
src/worker.ts |
Added Worker 4: smart-notification queue consumer with concurrency 3 |
src/services/notificationService.ts |
Added smart notification metadata support, device token validation |
src/routes/user/controllers.ts |
Added registerDeviceToken, unregisterDeviceToken, getUserDeviceTokens controllers |
src/routes/user/index.ts |
Added 3 device token routes (POST, DELETE, GET) |
src/services/notifications/templates/feature-announcement.template.ts |
New branded email template for feature announcements |
Prisma Schema Changes¶
| Model | Lines | Purpose |
|---|---|---|
DeviceToken |
869–884 | Mobile push token storage — token (unique), platform, deviceInfo (JSON), isActive, lastUsed |
SmartNotificationProfile |
917–956 | Per-patient settings — mode, segment, intensity, timing, counters, variety tracking |
SmartNotificationPolicy |
958–984 | Tenant-wide config — enabled, killSwitch, cadenceCaps, AI settings, channel strategy |
SmartNotificationOverride |
986–1014 | Per-patient admin overrides — hard-lock mode/intensity/segment, pause with auto-resume |
NotificationContentCache |
1016–1038 | Pre-generated AI content — variantId, content fields, 48h TTL, isUsed flag |
NotificationLog |
1040–1066 | Audit trail — source, decisionReason, variantId, segmentAtSend |
OktaPT-FE¶
New Files¶
| File | Lines | Purpose |
|---|---|---|
pages/admin/smart-notifications.tsx |
1537 | Three-tab admin page: Policy (tenant-level config with cadence caps, AI settings, channel strategy), Patient Overrides (per-patient config with pause/unpause), Analytics (suppression breakdown, segment distribution, sent/blocked counts) |
pages/admin/notification-testing.tsx |
657 | Notification testing page — tenant/patient picker, content variable controls (type, segment, humor, locale, AI vs template), preview panel showing 3 variants with personalization slots, send-this-one buttons |
Modified Files¶
| File | Change |
|---|---|
pages/admin/dashboard.tsx |
Updated navigation menu with smart notifications and notification testing cards |
pages/admin/navigation-menu.tsx |
Added Smart Notifications and Notification Testing cards to the Actions grid |
middleware.ts |
No change to admin guard logic — new pages auto-protected by existing /admin prefix match |
Okta-Mobile¶
New Files¶
| File | Lines | Purpose |
|---|---|---|
services/notificationService.ts |
71 | Expo Notifications service — permission requests, device token registration/unregistration, Android channel setup (MAX importance, vibration), AsyncStorage token caching, notification tap routing by type |
components/NotificationPreferences.tsx |
~200 | Per-type notification toggles (6 types: workout reminder, workout assigned, daily reminder, admin message, doctor message, streak milestone) with channel selection (PUSH, EMAIL, SMS) |
components/SmartNotificationSettings.tsx |
~150 | Patient-facing smart settings — mode picker (OFF/FIXED_TIME/SMART), intensity picker (MINIMAL/NORMAL/MOTIVATIONAL), quiet hours config, custom scroll wheel time picker |
app/notifications-preferences.tsx |
~30 | Route page for notification preferences screen |
Modified Files¶
| File | Change |
|---|---|
services/api.ts |
Added 6 API methods: registerDeviceToken, unregisterDeviceToken, getUserDeviceTokens, getNotificationPreferences, updateNotificationPreference, getSmartNotificationProfile, updateSmartNotificationProfile |
context/AuthContext.tsx |
Initializes notifications on app startup with cached credentials, user login, and signup completion via notificationService.initializeNotifications() |
app/_layout.tsx |
Root layout sets up notification tap listeners via notificationService.initializeNotificationListeners() |
Cross-Repo Integration Points¶
- Device Token Flow: Mobile registers token → API stores in DeviceToken → Dispatcher checks for active tokens when selecting channel → NotificationSender delivers via Expo push API
- Smart Settings Flow: Mobile SmartNotificationSettings →
PATCH /v2/smart-notifications/profile→ API updates SmartNotificationProfile → Dispatcher uses updated mode/timing - Celebration Flow: API workout completion handler → CelebrationService → enqueueSmartNotification → Worker processes → notificationService.sendNotification → Expo push to mobile
- Admin Testing Flow: FE notification-testing page →
POST /v2/smart-notifications/admin/test-generate→ ContentGenerator or TemplateBank → 3 variants returned → Admin selects →POST /v2/smart-notifications/admin/test-send→ BullMQ → delivery