# Test Me — Sehat Sahoolat v2

Hey Tariq — this is your hands-on runbook for the new monorepo. The goal is one command to bring everything up, then a checklist of things you can click through to convince yourself the platform actually works end to end.

If you only read one section, read **Daily startup**.

---

## Prerequisites (one-time)

You're on macOS. You need:

- **Docker Desktop** — running. The script will tell you if it isn't.
- **Node 22 LTS** — non-negotiable. Node 25 burned us for two days (see `backend/TESTING.md`).
  ```bash
  brew install node@22
  brew link --force --overwrite node@22
  node --version   # must print v22.x
  ```
- **Flutter 3.44+** — only needed if you're testing the mobile app.
  ```bash
  flutter --version
  ```
- **An Android phone with USB debugging** — for the mobile app. Same WiFi as your Mac.
- **Android command-line tools** — already installed on your machine via `brew install --cask android-commandlinetools` + the SDK managers. Don't redo it.

## First-time setup (only after a fresh clone)

```bash
cd ~/Desktop/sehat_sahoolat/v2

# Install everything
npm install                       # root devDeps (concurrently, tsx, pg, argon2)
cd backend && npm install && cd ..
cd web && npm install && cd ..
cd tests/e2e && npm install && cd ../..
cd mobile && flutter pub get && cd ..
```

That's it. From now on it's just:

---

## Daily startup

```bash
cd ~/Desktop/sehat_sahoolat/v2
./scripts/dev-start.sh
```

You'll see something like:

```
[step 1/6] Pre-flight checks
  ✓ docker, node v22.22.3
[step 2/6] Docker compose (Postgres + Redis)
  ✓ already running
  ✓ postgres healthy
[step 3/6] Root dev dependencies
  ✓ already installed
[step 4/6] Backend migrations
  No migrations are pending
[step 5/6] Dev seed
  [seed] + user admin / doctor / patient ...
  [seed] ✓ done.
[step 6/6] Starting dev servers

────────────────────────────────────────────────────────────────────
 Sehat Sahoolat — dev environment ready
────────────────────────────────────────────────────────────────────

 URLs
   Backend API ........ http://localhost:3000   (LAN: http://192.168.1.103:3000)
   Patient portal ..... http://localhost:3001
   Doctor portal ...... http://localhost:3002
   Admin portal ....... http://localhost:3003

 Test logins
   admin@sehat.local       AdminPass1!
   cardio@sehat.local      DoctorPass1!     (Dr. Imran Rashid — cardiology)
   derma@sehat.local       DoctorPass1!     (Dr. Sana Mahmood — dermatology)
   patient1@sehat.local    PatientPass1!    (has a pending appointment)
   patient2@sehat.local    PatientPass1!
   patient3@sehat.local    PatientPass1!

 Stop everything: Ctrl-C
────────────────────────────────────────────────────────────────────

[backend]  Nest application starting...
[patient]  ▲ Next.js 15.1.3 - Local:  http://localhost:3001
[doctor]   ▲ Next.js 15.1.3 - Local:  http://localhost:3002
[admin]    ▲ Next.js 15.1.3 - Local:  http://localhost:3003
```

The logs from all four services are colour-prefixed in one terminal. **Ctrl-C** tears everything down cleanly.

The seed is idempotent — it wipes the seeded users by email and reinserts them every run. Anything else in the DB is left alone.

---

## Login URLs (memorise these)

| Portal  | URL                       | Use account              |
|---------|---------------------------|--------------------------|
| Patient | http://localhost:3001     | patient1@sehat.local     |
| Doctor  | http://localhost:3002     | cardio@sehat.local       |
| Admin   | http://localhost:3003     | admin@sehat.local        |

Password formats: `AdminPass1!`, `DoctorPass1!`, `PatientPass1!`.

---

## Try these things first

1. **Patient — book an appointment.** Go to http://localhost:3001/login, sign in as `patient1@sehat.local`. Browse the doctor list — you should see Dr. Imran Rashid (cardiology) and Dr. Sana Mahmood (dermatology). Pick a slot Mon–Fri between 09:00 and 17:00 PKT and book it.
2. **Doctor — see the appointment.** Open a *different browser profile* (or use Chrome Incognito) so you don't clobber the patient session. Visit http://localhost:3002, log in as `cardio@sehat.local`. The appointment patient1 already has (seeded — tomorrow 10am PKT) should be on the dashboard. Confirm it.
3. **Doctor — go live.** Toggle live status on. Switch back to the patient portal — the doctor should now show as available for instant consultation.
4. **Admin — verify another doctor.** Open a third browser profile, log in to http://localhost:3003 as `admin@sehat.local`. Both cardio/derma are pre-verified by the seed; you can test the flow by toggling one back to "pending" and re-approving.
5. **Patient — start the video call.** From the patient portal, open the confirmed appointment and click "Join call". Agora should connect (browser will ask for camera/mic permission).

---

## Mobile app on your Android phone

The mobile app talks to the backend over your LAN, not localhost.

1. **Enable USB debugging on the phone.**
   - Settings → About phone → tap **Build number** 7 times. You're a developer now.
   - Settings → System → Developer options → enable **USB debugging**.
   - Plug into the Mac with a USB cable. When the phone shows "Allow USB debugging?", tap **Allow** and check "Always allow from this computer".
   - Verify: `adb devices` should list your phone.

2. **Make sure phone + Mac are on the same WiFi.**

3. **Run the app:**
   ```bash
   cd ~/Desktop/sehat_sahoolat/v2/mobile
   flutter run --dart-define=BACKEND_URL=http://192.168.1.103:3000
   ```

4. **Log in** with `patient1@sehat.local` / `PatientPass1!`.

If your Mac's LAN IP ever changes, find the new one with `ipconfig getifaddr en0` and pass that instead of `192.168.1.103`.

---

## Scribe (Whisper) — self-hosted transcription

Phase 20.3's AI Scribe (doctor records a snippet → SOAP draft) used to call OpenAI Whisper. It now POSTs to a **self-hosted faster-whisper service** running locally in docker. OpenAI Whisper is kept as an automatic fallback.

### Daily flow

`./scripts/dev-start.sh` brings up the whisper container as step 6. Verify it's running:

```bash
curl http://localhost:8090/health
# → {"status":"ok","model":"large-v3","modelLoaded":false,...}
```

The first `/transcribe` call pulls the model weights (~3 GB for `large-v3`) into a named docker volume — slow once, fast forever after.

### Try it end-to-end

1. Log in as `cardio@sehat.local` (the doctor) and open a consult.
2. Record a 10-second snippet via the Scribe panel.
3. Watch the transcript come back. The `modelVersion` on the row will be `local:large-v3` when the local service ran it, or `whisper-1` when it fell back to OpenAI.

### Admin controls

`Admin → Settings → AI` exposes the transcription block:

- **Provider toggle** — Local (default) or OpenAI.
- **Local whisper URL** — defaults to `http://localhost:8090`.
- **Model size** — informational; the active model is set by `WHISPER_MODEL` in `services/whisper/docker-compose.whisper.yml`.
- **Test connection** button — probes the service's `/health` and reports latency + which model is live.

### Pick a model

`large-v3` is best for Urdu / Roman-Urdu mixed audio but takes ~15-20 s for a 60-s clip on M2 (CPU only — Apple Silicon doesn't yet have Metal acceleration in CTranslate2). For most consult snippets, `small` is the sweet spot at ~3-5 s for a 60-s clip. Edit `services/whisper/docker-compose.whisper.yml` and change `WHISPER_MODEL`, then `docker compose -f services/whisper/docker-compose.whisper.yml up -d --force-recreate`.

### Troubleshooting

- **Scribe rows stuck in `failed` with `No transcription provider configured`** — local service is down AND no OpenAI key is set. Either start the whisper container or set the embedding API key in Admin → AI.
- **First transcribe takes forever** — expected; the model is downloading. Watch `docker compose -f services/whisper/docker-compose.whisper.yml logs -f`.
- **Want to revert to OpenAI entirely** — Admin → Settings → AI → set Transcription provider to `OpenAI` and save.

---

## Stripe test mode (optional)

If `STRIPE_SECRET_KEY` isn't set, the backend falls back to **mock payment mode** — packages can be "purchased" and the full flow works end to end, but no real Stripe call goes out. That's enough for most testing.

To exercise the real Stripe path locally:

1. Go to https://dashboard.stripe.com, switch to **Test mode** (toggle top-right).
2. Developers → API keys. Copy the **Secret key** (`sk_test_...`).
3. Edit `backend/.env`:
   ```
   STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
   ```
4. Install the Stripe CLI: `brew install stripe/stripe-cli/stripe`.
5. Log in: `stripe login` (opens the browser, click confirm).
6. Forward webhooks to your local backend:
   ```bash
   stripe listen --forward-to localhost:3000/api/v2/webhooks/stripe
   ```
   The CLI prints a `whsec_...` value. Paste that into `backend/.env`:
   ```
   STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxx
   ```
7. Restart `./scripts/dev-start.sh`. Now buy a package as `patient1` and use test card `4242 4242 4242 4242` — Stripe sends the webhook back through the CLI to your backend.

---

## Troubleshooting

**"Port already in use" (3000/3001/3002/3003).** Something didn't shut down cleanly last time. Kill it:
```bash
lsof -ti :3000 -ti :3001 -ti :3002 -ti :3003 | xargs kill -9
```

**"Cannot connect to the Docker daemon".** Docker Desktop isn't running. Open it from Applications and wait for the whale icon to settle.

**"Node v25.x detected" / weird module errors.** You're on the wrong Node. Run:
```bash
brew link --force --overwrite node@22
hash -r
node --version    # must be 22.x
```

**`simdjson` dyld / native module error in the backend.** A native module compiled against the wrong Node. Nuke and reinstall:
```bash
cd backend && rm -rf node_modules package-lock.json && npm install
```

**Phone not showing in `adb devices`.** Unplug, replug, tap "Allow" on the phone's USB debug prompt again. Make sure the cable is a data cable not charge-only. If still nothing: `adb kill-server && adb start-server`.

**Postgres migration fails: `extension "vector" is not available`.** Your `sehat-postgres` container is on the old stock postgres image. The dev-start script auto-detects this and recreates the container from the pgvector compose image (your data is in a named volume, so nothing is lost). If you skipped the script, do it yourself:
```bash
docker compose up -d --force-recreate postgres
```

---

When in doubt, blow it all away and start fresh:

```bash
# Stop everything
docker compose down
lsof -ti :3000 -ti :3001 -ti :3002 -ti :3003 | xargs kill -9 2>/dev/null

# Wipe the dev DB (you'll lose any manually-entered data)
docker volume rm v2_postgres_data

# Bring it back
./scripts/dev-start.sh
```
