R
RonanRx
Patient flow — verified behavior
End to end Verified by test ✓ Linq → OTP → dashboard

What the patient does · how the system responds

Onboarding to dashboard, one continuous path.

A patient texts in, the agent collects their intake, they sign the waiver, then they sign in by phone and land on their dashboard. Every step below is exercised by an end-to-end test (onboarding_to_dashboard_test.rb, 25 assertions). The three external services are stubbed in the local demo.

CAT Cathryn's Linq / portal OTP passwordless sign-in (PR #152) APP shared plumbing the seam — what makes phone sign-in work external service, stubbed in demo
01

Text intake → account + magic link

CAT
Patient

Texts the clinic's number: a "hi", then answers each question (name, age, meds, a photo of a bottle).

System

Each text hits POST /api/v1/linq/inbound — the signature is verified (fail-closed), the planner captures one answer, and the next prompt is sent back in the thread.

writes LinqConversation · LinqEvent (no PHI) · IntakeResponse.answers

Patient

Sends the final answer — intake is complete.

System

Linq::SignupForward bootstraps the account and mints a one-hour, single-use magic link, then texts it.◄ THE SEAM

writes User (+ phone_binding_digest) · Patient (+ user link) · enrollment (draft) · IntakeResponse → submitted

02

Portal completion (4-step machine)

CAT
Patient

Taps the secure link, then signs the waiver on the auth host (draws their name).

System

/portal/complete/:token opens the 4-step machine. The waiver mints a Consent + authorization token; signing flips it to signed.

writes Consent (signed) · AuthorizationToken · Event

Patient

Texts a photo of their ID, adds a card, picks a review time.

System

Each gate is verified before the next opens: waiver PDF is sealed, the ID is matched, the subscription goes active, the slot is booked.

⊘ seal = GCS · ID = Gemini OCR · payment = Stripe — stubbed in the local demo

03

Passwordless sign-in → session

OTP
Patient

Opens /sign_in/otp and enters their phone number.

System

SendLoginCode finds the active user by the phone binding stamped at signup, throttles, mints a challenge, and delivers a 6-digit code (console in dev, Linq in prod).◄ uses the seam

writes LoginChallenge (hashed code) · RateLimit · LoginAudit: login_sent

Patient

Gets the same neutral code screen for any number, then enters the 6-digit code.

System

VerifyLoginCode consumes the code in a single atomic row update (no double-grant), resets the session, and redirects to the patient landing.

writes LoginChallenge.consumed_at · LoginAudit: login_verified · session[:user_id]

04

Dashboard

CAT APP
Patient

Lands on /patient.

System

require_role(:patient) passes, current_patient resolves, and the dashboard branches on whether a prescription exists yet.

Right after signup

Applicant view

"Dr. Voss is reviewing your intake." A 5-step status timeline and the waiver chip. Nothing is required from the patient yet. This is what a fresh signup sees.

Once a prescription exists

Full Standing Order

A single prioritized next-action hero, the fulfillment stepper, refills and check-ins, imported labs, and the care team. The steady-state dashboard.

Where this lives

Phases 1–2 (Linq intake + portal completion) are on main today. The passwordless sign-in and the dashboard (phases 3–4) are on PR #152, rebased on the latest main and green (1,158 tests), held unmerged for review. The single backend change inside Cathryn's signup code is the seam: stamping the phone binding and linking the patient to a user at signup, so SendLoginCode can find an onboarded patient instead of silently doing nothing.