Parent: Aligner Tracker Api


User Onboarding Flow


sequenceDiagram

participant Jarvis as Jarvis/Admin

participant API as FastAPI

participant Cognito as Cognito

participant DB as PostgreSQL

  

Jarvis->>API: POST /internal/users

API->>DB: Check email uniqueness

API->>Cognito: admin_create_user()

Cognito-->>API: Return cognito_sub

API->>DB: Insert User record

API-->>Jarvis: Return user + temp_password

Note over Jarvis: Admin sets permanent password via AWS CLI

Key Points:

  • User creation is atomic: if Cognito provisioning fails, no DB record is created

  • If DB insert fails after Cognito creation, the Cognito user is cleaned up

  • Temporary password is returned but email notification is suppressed (MessageAction=SUPPRESS)

  • If user already exists in Cognito, returns existing cognito_sub (idempotent)

  • An initial Journey is automatically created with the specified product (defaults to aligners)

Journey Lifecycle

Journeys track a user’s treatment cycle for a specific product. A user can only have one active journey at a time.


flowchart TD

subgraph UserRegistration [User Registration]

A[POST /internal/users] --> B[Create User]

B --> C[Auto-create Journey]

C --> D["Journey(status=ACTIVE, product=payload.product)"]

end

subgraph JourneyLifecycle [Journey Lifecycle]

D --> E[Progress Events recorded]

E --> F[HubSpot: aftercare_stage = Journey Complete]

F --> G["Journey(status=COMPLETED)"]

G --> H[Wait for new journey creation]

end

subgraph NewJourney [New Journey]

H --> I["POST /auth/users/{sub}/journeys"]

I --> J["New Journey(status=ACTIVE)"]

J --> E

end

Journey States:

  • active: Currently in progress. Only one active journey per user allowed.
  • completed: Treatment finished. Triggered automatically when HubSpot aftercare_stage is set to “Journey Complete”.

Products:

  • aligners: Clear aligner treatment (default)
  • clear_braces: Clear braces treatment

Progress Change Cycle

The core treatment flow is based on a 10-day progress cycle:


PROGRESS_CHANGE_INTERVAL_DAYS = 10


flowchart TD

A[User logs progress change] --> B[Create ProgressEvent on active Journey]

B --> C{First progress event?}

C -->|Yes| D[Set journey.date_of_starting_treatment]

C -->|No| E[Continue]

D --> E

E --> F[Update journey.current_progress_stage]

F --> F2[Cancel all pending progress notifications]

F2 --> G[Calculate next_change = now + 10 days]

G --> H[Create scheduled Notification]

H --> I[Link notification to event]

Progress Stage Validation

When creating a progress event:

  1. Cannot go backwards: new_stage > current_progress_stage

  2. Cannot exceed total: new_stage <= total_progress_stages (if set)

  3. Cannot be negative: progress_stage >= 0

  4. Requires active journey: Returns 404 if no active journey exists

Expected End Date Calculation

A computed field on the journey response:


expected_end_date = date_of_starting_treatment + (total_progress_stages * 10 days)

Returns null if either field is not set.

Notification Lifecycle


stateDiagram-v2

[*] --> Pending: Created

Pending --> Sent: OneSignal accepts

Pending --> Cancelled: Cancelled before OneSignal

Sent --> Cancelled: Cancelled before scheduled_for

Sent --> Delivered: scheduled_for time reached

Delivered --> [*]

Cancelled --> [*]

Notification Sources:

  • progress: Auto-created when user logs aligner change

  • other: Future use

OneSignal Integration:

OneSignal API calls are processed asynchronously via FastAPI background tasks, allowing the API to respond immediately while push notifications are scheduled in the background.

When a progress notification is created:

  1. Notification record is saved to database with status PENDING
  2. API response is returned to client
  3. (Background) Push notification is scheduled via OneSignal with send_after set to scheduled_for
  4. (Background) On success: status set to SENT, onesignal_id stored

Cancellation logic:

A notification can only be cancelled in OneSignal if:

  • Status is SENT (OneSignal accepted it)
  • Current time is before scheduled_for (not yet delivered)

If notification is still PENDING (OneSignal hasn’t processed it yet), it’s marked as CANCELLED in DB only.

If notification has status SENT but scheduled_for has passed, no cancellation is attempted (already delivered).

Users are targeted via their cognito_sub_identifier as the OneSignal external_id.

When user reschedules:

  1. Find existing notification for the progress event

  2. If SENT and scheduled_for is in the future: cancel in OneSignal and set status to CANCELLED

  3. If PENDING: set status to CANCELLED (no OneSignal call needed)

  4. Create new notification with updated scheduled_for

  5. Update event’s next_notification_id

Progress Event Types

Progress events track different stages of treatment. Each event type is created via the POST endpoint with a type field. Events are always created on the user’s active journey.

Event TypePurposeend_datereasonNotification Content
progressRecord progress stage changeAuto-computed (now + 10 days)Not allowedStandard: “Time to change to stage X”
pausePause treatmentUser-providedRequired”Your treatment is paused. Resume with stage X.”
delayDelay next changeUser-providedRequiredStandard: “Time to change to stage X”
continueResume treatmentAuto-computedNot allowedStandard: “Time to change to stage X”

Event Fields:

  • user_id: FK to user (for direct queries without joins)
  • journey_id: FK to journey (for journey-specific queries)
  • start_date: When the event was created (automatically set to current time)
  • end_date: When the next action is scheduled (computed or user-provided depending on type)
  • type: One of progress, pause, delay, continue
  • reason: Explanation for pause/delay events (stored in unified reason field)

Validation Rules:

  • pause and delay require both end_date and reason to be provided
  • progress and continue must not have end_date or reason in the request
  • end_date cannot be in the past

Continue Event Calculation: When a user creates a continue event to resume treatment after a pause/delay:

last_progress = most recent "progress" type event
pause_event = most recent "pause" or "delay" event

if pause_event.start_date >= last_progress.end_date:
    # Pause started after progress end_date, use standard interval
    new_end_date = now + 10 days
else:
    # Add remaining time from when pause started
    remaining_time = last_progress.end_date - pause_event.start_date
    new_end_date = now + remaining_time

Notification Behavior: When a user pauses treatment, the scheduled notification content changes to inform them that their treatment is paused and which aligner to resume with. This helps users remember their current aligner when they’re ready to continue.

Image Upload Flow


flowchart LR

A[User uploads photo] --> B[Generate S3 key]

B --> C[Upload to S3]

C --> D[Create UserImage record]

D --> E[Link to current_aligner]

E --> F[Return image ID]

Images are uploaded to the user’s active journey. Each image is linked to both the user and the journey for flexible querying.

S3 Key Format:


aligner-tracker-api/users/{cognito_sub}/{uuid}.{ext}

Pre-signed URLs:

  • Download URLs expire in 60 seconds by default

  • Generated on-demand when listing or fetching images

External System IDs

FieldSystemPurpose
jarvis_idJarvisLegacy patient management system
hubspot_idHubSpotCRM for sales/marketing
assigned_dentist_idInternalLinks to dentist (external table)
These are optional identifiers that allow the Aligner Tracker to integrate with other Smile White systems.

Dentist Change Flow


flowchart TD

A[User requests dentist change] --> B{Already pending?}

B -->|Yes| C[Return 409 Conflict]

B -->|No| D[Submit to Jarvis API]

D --> E{Jarvis response}

E -->|Success| F[Set has_pending_dentist_change = true]

F --> G[Return ticket ID to user]

E -->|409| H[Return 409 Conflict]

E -->|Error| I[Return 502 Bad Gateway]

Completion via HubSpot Webhook:

When the dentist change is approved, HubSpot sends a contact-property-change webhook with propertyName: dentist_assigned. This:

  1. Updates assigned_dentist_id to the new dentist
  2. Clears has_pending_dentist_change flag
  3. Creates a notification informing the user

HubSpot Property Sync

The /public/webhooks/hubspot/contact-property-change endpoint handles property updates:

PropertyAction
dentist_assignedUpdates dentist, clears pending flag, notifies user
waiver_signed_version_jarvisSets terms_accepted = true
aftercare_stage__contact_Updates active journey’s aftercare_stage field

Aftercare Stage Values:

  • “Aligner Journey Underway”: Sets journey.aftercare_stage = aligner_journey_underway
  • “Journey Complete”: Sets journey.aftercare_stage = journey_complete and journey.status = completed

Key Constants

ConstantValueLocation
PROGRESS_CHANGE_INTERVAL_DAYS10app/constants.py
Presigned URL expiry60sapp/services/images.py

Enums

Product (app/models/journey.py):

  • aligners - Clear aligner treatment
  • clear_braces - Clear braces treatment

JourneyStatus (app/models/journey.py):

  • active - Journey in progress
  • completed - Journey finished

AfterCareStage (app/models/journey.py):

  • scan_appointment_booked - Initial scan appointment scheduled
  • aligner_journey_underway - Treatment actively in progress
  • journey_complete - Treatment finished