pediatric-ai-scribe-v3/README.md
Daniel 503f5afaad feat: ED multi-stage UX, extensions polish, docs viewer + application-logic docs
Three concurrent themes from this session:

═══════════════════════════════════════════════════════════════════
ED ENCOUNTERS — per-stage cards + consolidate→MDM finalize
═══════════════════════════════════════════════════════════════════

UX redesign per Daniel's feedback ("every stage note should be shown,
if AI is told to modify that particular note then the modified version
is used in final mdm"):

- Each generated stage stays on screen as its own editable card with
  its own embedded "Don't Miss" panel. No more single rolling note
  element that gets replaced on each generation.
- gatherCurrentNotes() reads contenteditable text from each stage card
  before any operation (advance, finalize, persist) so inline edits
  flow into the next AI call and the final consolidate.
- Stage badge is now state-accurate. "Stage N (recording)" with yellow
  background after Add-more before generation; "Stage N" with gray
  after generation. Fixes the bug where the badge flipped to Stage 2
  the moment Add-more was clicked.
- Save & Done now runs TWO server-side AI calls in /finalize:
  1. edConsolidate (new prompt) → polished single final note that
     integrates every stage chronologically (HPI / ROS / PE / ED Course /
     A&P with disposition).
  2. edFinalize (rewritten with full inline 2023 AMA E/M element
     rubric — problems / data / risk definitions, level mapping with
     concrete examples) → MDM JSON.
- Two new cards render after finalize: blue-bordered Final Consolidated
  Note + green-bordered MDM. Stage cards become read-only.
- partial_data on the saved row now stores {stages, finalNote, mdm,
  finalized} so resume re-renders the full state.

Why two-call finalize: a single combined prompt makes the model cut
corners on one task. Two focused calls cost ~2× latency at the very end
of an encounter — acceptable since finalize is a one-time terminal
action, not a per-stage hot path.

Files: public/components/ed-encounter.html, public/js/ed-encounters.js,
src/routes/edEncounters.js, src/utils/prompts.js (edConsolidate added,
edFinalize rewritten).

═══════════════════════════════════════════════════════════════════
EXTENSIONS / PAGERS — visual polish
═══════════════════════════════════════════════════════════════════

Multiple iterations based on Daniel's feedback:

- Layout: align-items:flex-start so action buttons stay pinned top-right
  when long numbers wrap (was align-items:center → buttons drifted into
  the text area, causing visible overlap).
- Number: word-break:break-all + min-width:0 + font-feature-settings:tnum
  so long numbers wrap within their column instead of pushing under the
  buttons. Click-to-copy with a 0.55s green flash + ✓ copied badge.
- Phone/pager Font Awesome icon next to the number in the type color —
  at-a-glance type signal (replacing an earlier 3px left stripe that
  Daniel found visually bulky).
- Name: font-weight 700, font-size 14.5px, color g900, letter-spacing
  -0.012em — scan-target headline typography for long lists.
- Alternating subtle backgrounds by index (white vs #fafbfc) so a long
  list reads as distinct rows.
- Hover: card lifts 1px with a soft shadow; action buttons fade from
  55% to 100% opacity. Cubic-bezier transition on transform.
- Entrance: staggered fade-up animation per card (35ms × index, capped
  at 12). prefers-reduced-motion media query disables motion.
- Empty state: 48px FA icon + heading instead of plain gray text.

Files: public/js/extensions.js, public/css/styles.css.

═══════════════════════════════════════════════════════════════════
DOCS REORGANIZATION + APPLICATION-LOGIC DOCS + ADMIN VIEWER
═══════════════════════════════════════════════════════════════════

Document moves (preserving git history via git mv):
  BROWSER_WHISPER_SETUP.md          → docs/browser-whisper-setup.md
  BROWSER_WHISPER_TROUBLESHOOTING.md → docs/browser-whisper-troubleshooting.md
  DEVELOPER_GUIDE.md                → docs/developer-guide-extended.md
  EMBEDDINGS_SETUP.md               → docs/embeddings-setup.md
  FEATURES_EXPLAINED.md             → docs/features-explained.md
  IMPROVEMENTS.md                   → docs/improvements.md
  OPENID_SETUP.md                   → docs/openid-setup.md
  TRANSCRIPTION_OPTIONS.md          → docs/transcription-options.md
README.md updated with the new paths + a Documentation section that
links to docs/logic/ at the top.

New application-logic doc series (~8,300 lines total) at docs/logic/.
Built with 5 parallel doc-writing agents per Daniel's "use multiple
agents" directive. Each doc explains how a part of the app actually
works — application logic, data flow, design decisions, sacred zones,
how-to-extend recipes — at a depth that lets a new dev (or an AI
assistant) modify the code confidently.

  docs/logic/README.md                — index + recommended reading order
  docs/logic/architecture.md (2166 L) — frontend IIFE pattern, lazy tab
                                         load, backend route convention,
                                         schema, encryption, deployment
  docs/logic/clinical-notes.md (1546L) — every note tab + helper trio
  docs/logic/bedside-and-calculators.md (1373L) — bedside ES module
                                         pocket + calculators + PE Guide
                                         + suture selector
  docs/logic/auth-admin-learning.md (1281L) — auth (local+OIDC+2FA) +
                                         admin panel + Learning Hub
                                         (Quiz engine logic at sub-detail
                                         only — TODO follow-up)
  docs/logic/ai-and-voice.md (1128 L) — callAI 5-provider routing,
                                         prompts, voice/STT, helper trio
  docs/logic/ed-encounters.md (821 L) — multi-stage ED + MDM (this
                                         session's worked example)

Admin-only docs viewer:
- New route /api/admin/docs/{tree,file}: recursively walks docs/, returns
  the tree as JSON; /file?path=X validates path stays inside docs/ and
  renders markdown via marked. Both gated by req.user.role==='admin'.
- New tab "Docs" (book icon) in the sidebar, hidden by default and
  revealed in auth.js when user.role==='admin' (same pattern as the
  existing Admin and CMS tabs).
- New component public/components/admin-docs.html: split-pane layout
  with a tree sidebar + filter input + a markdown reader pane.
- New module public/js/admin-docs.js: lazy-loads the tree on first tab
  activation, renders collapsible folders, persists expanded state and
  last-opened path via UIState. Server-rendered HTML so no client
  markdown parser needed.
- CSS for the viewer (responsive split-pane, code-block styling, table
  scrolling, etc.).
- Mounted at /api/admin/docs (NOT /api) — important: mounting a router
  with router.use(authMiddleware) at /api accidentally 401s every other
  /api/* path (caught and fixed during testing — /api/health was 401'ing).

Files: docs/* (moved + new), README.md, public/components/admin-docs.html
(new), public/js/admin-docs.js (new), src/routes/adminDocs.js (new),
public/index.html (tab + section + script), public/js/auth.js (admin
gate + logout cleanup), public/css/styles.css (viewer styles), server.js
(mount).

═══════════════════════════════════════════════════════════════════
KNOWN GAPS (TODO follow-ups)
═══════════════════════════════════════════════════════════════════

- Learning Hub quiz engine (MCQ / multi-select / T-F scoring + attempt
  tracking + progress dashboard) is covered at the architectural level
  in docs/logic/auth-admin-learning.md but not drilled into the quiz
  data model and scoring flow. Worth a focused follow-up doc.
- ED finalize: if MDM step JSON parse fails, server returns 502 with
  the consolidated finalNote in the error payload, but client doesn't
  surface the partial result. Add a "MDM failed, retry" affordance.
- No e2e Playwright coverage for ED encounters or the new docs viewer.
2026-04-28 03:09:38 +02:00

375 lines
14 KiB
Markdown

# Pediatric AI Scribe v6
AI-powered clinical documentation platform for pediatric medicine. Generates HPIs, hospital courses, chart reviews, SOAP notes, well/sick visit notes, and developmental milestone assessments from voice recordings or dictation.
## Features
### Clinical Documentation
- **Live Encounter** — record doctor-patient conversations, AI generates structured OLDCARTS HPI
- **Voice Dictation** — dictate narrative, AI cleans and restructures
- **Hospital Course** — paste progress notes, generates prose, day-by-day, organ-system (ICU), or psych format
- **Chart Review / Precharting** — summarize outpatient, subspecialty, and ED notes
- **SOAP Notes** — full SOAP or subjective-only from dictation
- **Well Visit** — AAP 2025 Bright Futures periodicity with vaccines, screenings, billing codes, SSHADESS (12+), milestones
- **Sick Visit** — quick documentation with auto-suggested ROS and PE from chief complaint
- **Developmental Milestones** — AAP/Nelson tracker (birth-11y) with narrative/structured/summary output
### AI & Speech
- **5 AI Providers** — OpenRouter, AWS Bedrock, Azure OpenAI, Google Vertex AI, LiteLLM
- **5 STT Providers** — Google Gemini, Amazon Transcribe (Medical), OpenAI Whisper, Local Whisper, LiteLLM
- **3 TTS Providers** — Google Cloud TTS, LiteLLM (OpenAI), ElevenLabs
- **Browser Whisper** — fully offline in-browser transcription via WebAssembly (HIPAA-safe)
- **Per-tab model selector** — choose fast vs. smart vs. premium models per task
- **Physician memory system** — Dragon-like learning from your corrections
### Learning Hub
- **Content Management** — articles, clinical pearls, quizzes, presentations
- **AI Content Generation** — generate from topics, uploaded PDFs, or Nextcloud files
- **Marp Presentations** — slide editor with preview and PPTX export
- **Semantic Search** — vector-based search via pgvector embeddings
- **Quiz System** — MCQ, multi-select, true/false with scoring and progress tracking
### Platform
- **Multi-user with roles** — admin, moderator, user
- **OIDC/SSO** — Azure AD, Okta, Keycloak, PocketID, Google
- **2FA** — TOTP-based two-factor authentication
- **Cloudflare Turnstile** — bot protection on login, register, password reset
- **Email verification** — with customizable templates
- **Nextcloud integration** — WebDAV export
- **S3 Document Storage** — AWS S3, Backblaze B2, MinIO
- **PWA** — installable, works on mobile
- **Admin Panel** — user management, settings, prompt editor, model configuration, logs
---
## Quick Start
### 1. Configure
```bash
cp .env.example .env
```
Edit `.env` — at minimum set:
```env
AI_PROVIDER=litellm # or openrouter, bedrock, azure, vertex
LITELLM_API_BASE=https://your-litellm.example.com
LITELLM_API_KEY=sk-...
OPENAI_API_KEY=sk-... # for Whisper transcription (if not using LiteLLM STT)
JWT_SECRET=<64-char random> # openssl rand -hex 32
DB_PASSWORD=<strong password>
APP_URL=https://your-domain.com
```
### 2. Start
```bash
docker compose up -d
```
App runs on **port 3552**. First user to register becomes admin.
### 3. Admin CLI
```bash
docker exec pediatric-ai-scribe node admin-cli.js list-users
docker exec pediatric-ai-scribe node admin-cli.js create-admin admin@example.com password123 "Dr. Admin"
docker exec pediatric-ai-scribe node admin-cli.js make-admin user@example.com
docker exec pediatric-ai-scribe node admin-cli.js reset-password user@example.com newpassword
docker exec pediatric-ai-scribe node admin-cli.js toggle-registration
docker exec pediatric-ai-scribe node admin-cli.js stats
```
---
## AI Provider Configuration
Switch providers by setting `AI_PROVIDER` in `.env`. No code changes needed.
| Provider | HIPAA | Config |
|----------|-------|--------|
| **LiteLLM** | Depends on backend | `LITELLM_API_BASE`, `LITELLM_API_KEY` |
| **AWS Bedrock** | Yes (with BAA) | `AWS_BEDROCK_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` |
| **Azure OpenAI** | Yes (with BAA) | `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`, `AZURE_DEPLOYMENT_NAME` |
| **Google Vertex AI** | Yes (with BAA) | `GOOGLE_VERTEX_PROJECT`, `GOOGLE_VERTEX_LOCATION` |
| **OpenRouter** | No | `OPENROUTER_API_KEY` |
---
## Transcription (Speech-to-Text)
Set `TRANSCRIBE_PROVIDER` or let the app auto-detect.
| Provider | HIPAA | Config |
|----------|-------|--------|
| **Google Gemini** | Yes | `GOOGLE_VERTEX_PROJECT`, `GOOGLE_STT_MODEL` |
| **Amazon Transcribe** | Yes | AWS creds + `TRANSCRIBE_PROVIDER=aws` |
| **Amazon Transcribe Medical** | Yes | `AWS_TRANSCRIBE_MEDICAL=true`, `AWS_TRANSCRIBE_SPECIALTY=PRIMARYCARE` |
| **Local Whisper** | Yes (offline) | `TRANSCRIBE_PROVIDER=local`, `WHISPER_BINARY`, `WHISPER_MODEL_SIZE` |
| **OpenAI Whisper** | No | `OPENAI_API_KEY` |
| **LiteLLM** | Depends | `TRANSCRIBE_PROVIDER=litellm`, `LITELLM_STT_MODEL` |
| **Browser Whisper** | Yes (client-side) | No config needed — toggle in user settings |
---
## Text-to-Speech
| Provider | HIPAA | Config |
|----------|-------|--------|
| **Google Cloud TTS** | Yes | `GOOGLE_VERTEX_PROJECT`, `GOOGLE_TTS_VOICE` |
| **LiteLLM** | Depends | `LITELLM_TTS_MODEL`, `LITELLM_TTS_VOICE` |
| **ElevenLabs** | No | `ELEVENLABS_API_KEY` |
---
## OpenID Connect / SSO
Supports Azure AD, Okta, Keycloak, PocketID, Google, and any OIDC-compliant provider.
1. Register callback URL: `https://your-domain.com/api/auth/oidc/callback`
2. Admin Panel > Settings > Configure OIDC (Issuer URL, Client ID, Client Secret)
3. Users are auto-created and linked by email on first SSO login
See [docs/openid-setup.md](docs/openid-setup.md) for provider-specific guides.
---
## Cloudflare Turnstile (Bot Protection)
Optional CAPTCHA on login, registration, and password reset forms.
```env
TURNSTILE_SITE_KEY=0x4AAA...
TURNSTILE_SECRET_KEY=0x4AAA...
```
---
## Email
Without SMTP, email verification is skipped and users are auto-verified.
```env
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
SMTP_FROM=noreply@yourdomain.com
```
---
## Maintenance CLI
After a Postgres image upgrade (major version bump or silent base-layer change),
btree indexes on text columns can become inconsistent with the new ICU/glibc
library. The app auto-detects this at startup and reindexes on drift, but you
can also trigger it manually:
```bash
# Health check — no writes
docker exec pediatric-ai-scribe npm run maint:check
# Rebuild all indexes + refresh collation + ANALYZE
docker exec pediatric-ai-scribe npm run maint:reindex
```
Run `maint:reindex` any time after:
- Upgrading the Postgres image (major or minor)
- Restoring from a dump created on a different Linux distro
- Seeing "invalid credentials" on credentials you know are correct
- Seeing `0 rows` returned from a lookup that should match
The reindex takes seconds on a small DB and a minute or two on larger ones.
Safe to run while the app is serving traffic, though queries may slow briefly.
---
## Docker Hub
```bash
docker pull danielonyejesi/pediatric-ai-scribe-v3:latest
```
Minimal compose without building:
```yaml
services:
app:
image: danielonyejesi/pediatric-ai-scribe-v3:latest
ports:
- "3552:3000"
env_file: .env
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
postgres:
image: pgvector/pgvector:pg16
environment:
POSTGRES_DB: pedscribe
POSTGRES_USER: pedscribe
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U pedscribe"]
interval: 10s
retries: 5
volumes:
pgdata:
```
---
## HIPAA Notice
This application processes data through third-party AI APIs.
- All connections use HTTPS/TLS
- Authentication required for all AI endpoints
- 2FA and SSO available
- Cloudflare Turnstile bot protection
- **AWS Bedrock**, **Azure OpenAI**, and **Google Vertex AI** offer BAAs
- **OpenRouter** and **ElevenLabs** do NOT offer BAAs
- **Browser Whisper** and **Local Whisper** keep audio fully private
**Do not use real PHI without executed BAAs with all providers in your deployment.**
---
## Documentation
See [docs/](docs/) for the full documentation set.
### Application logic — start here if you're new to the codebase
[**docs/logic/**](docs/logic/) is a deep, dev-friendly walkthrough of how each
part of the app actually works. ~8,300 lines of "how it works and why" — read
the index first to know what's there:
- [docs/logic/README.md](docs/logic/README.md) — index + recommended reading order
- [docs/logic/architecture.md](docs/logic/architecture.md) — frontend IIFE pattern, lazy tab loading, backend route convention, schema, encryption, sacred zones
- [docs/logic/clinical-notes.md](docs/logic/clinical-notes.md) — every note tab (HPI, dictation, sick, well, SOAP, hospital, chart, notes) with the shared record→generate→save lifecycle
- [docs/logic/ed-encounters.md](docs/logic/ed-encounters.md) — multi-stage ED notes, per-stage don't-miss, consolidate→MDM finalize. Worked example of how a clinical workflow is composed in this codebase.
- [docs/logic/bedside-and-calculators.md](docs/logic/bedside-and-calculators.md) — Bedside emergencies module (ES-module pocket of the frontend), pediatric calculators, PE Guide, suture selector. Lists every clinical formula that must NOT be modified without test vectors.
- [docs/logic/ai-and-voice.md](docs/logic/ai-and-voice.md) — `callAI` 5-provider routing, prompt centralization with DB overrides, `wrapUserText`+`INJECTION_GUARD`, server STT routing, browser Whisper, the helper trio (refine/billing/don't-miss).
- [docs/logic/auth-admin-learning.md](docs/logic/auth-admin-learning.md) — local + OIDC auth, 2FA, sessions, OpenBao secret loading, Admin panel, Learning Hub.
### Operational + reference
- [Architecture Overview](docs/architecture.md) — high-level (the deep version is in docs/logic/architecture.md)
- [API Reference](docs/api-reference.md)
- [Database Schema](docs/database.md)
- [Authentication & Security](docs/authentication.md)
- [AI Providers & Models](docs/ai-providers.md)
- [Speech (STT/TTS)](docs/speech.md)
- [Learning Hub & CMS](docs/learning-hub.md)
- [Configuration Reference](docs/configuration.md)
- [Deployment Guide](docs/deployment.md)
- [Developer Guide (short)](docs/developer-guide.md)
- [Developer Guide (extended)](docs/developer-guide-extended.md)
- [Browser Whisper Setup](docs/browser-whisper-setup.md) · [Troubleshooting](docs/browser-whisper-troubleshooting.md)
- [Embeddings Setup](docs/embeddings-setup.md)
- [OpenID Connect Setup](docs/openid-setup.md)
- [Transcription Options](docs/transcription-options.md)
- [Features Explained](docs/features-explained.md)
- [Improvement Roadmap](docs/improvements.md)
---
## Development
```bash
npm install
cp .env.example .env # edit with your keys
# Requires PostgreSQL with pgvector
node server.js
```
---
## Testing
Two layers, both zero-config after the initial setup.
### Unit tests — pure dose math (Node built-in)
```bash
npm test
```
Runs `node --test test/` against `public/js/calc-math.js` — pure functions for
APLS / Best Guess weight, Parkland, Holliday-Segar 4-2-1, PRAM, Westley,
epi (anaphylaxis vs arrest vs NRP, different concentrations), RSI drugs,
min SBP, ETT sizing, Lund-Browder TBSA. **36 assertions, no dependencies.**
### End-to-end tests — Playwright smoke suite
Runs a headless Chromium against the live app. **128 tests** covering every
calculator tab, every Bedside sub-pill + widget, auth-gated pages (encounter,
well visit, charts, vaccines, catch-up, learning hub, dictation, settings,
FAQ), at **both desktop and mobile (Pixel 5) viewports**.
```bash
# First-time setup: spin up the auth-less test container (port 3553)
docker compose -f docker-compose.yml -f docker-compose.e2e.yml up -d pediatric-scribe-e2e
# Then run the full suite (runs inside an official Playwright container)
npm run e2e
```
The runner script (`scripts/e2e.sh`) uses `mcr.microsoft.com/playwright` so you
don't need Node or browsers on the host.
**Test environment:**
- `pediatric-ai-scribe` (port 3552) — your normal app
- `pediatric-ai-scribe-e2e` (port 3553) — identical image, but with
`TURNSTILE_SECRET_KEY=""` and `SMTP_HOST=""` so Playwright can log in
without a bot challenge. Shares the same Postgres + pgdata volume.
- Test user: `e2e-user@ped-ai.test` (auto-verified on first register)
- Harness page: `public/e2e-harness.html` loads the calculators component
without the auth wall for smoke tests that don't need a logged-in session.
**Viewing failures** — Playwright writes `e2e/test-results/<test-name>/`
with:
- `test-failed-1.png` — screenshot at the point of failure
- `trace.zip` — full action trace (replay with `npx playwright show-trace`)
- `error-context.md` — DOM snapshot and console logs
Everything but the specs and config is gitignored under `e2e/`.
**Files:**
- `e2e/tests/bedside-smoke.spec.js` — 26 tests for the Bedside module
- `e2e/tests/top-calculators.spec.js` — 27 tests for BP / BMI / Growth /
Bili / Vitals / BSA / Dose / Resus / GCS / Equipment
- `e2e/tests/auth-gated-smoke.spec.js` — 11 tests for the auth-gated tabs
- `e2e/playwright.config.js` — runs all the above under both `chromium`
(Desktop Chrome) and `mobile-chrome` (Pixel 5) projects
**Writing a new test:**
```js
const { test, expect } = require('@playwright/test');
test('my new smoke test', async ({ page }) => {
await page.goto('/e2e-harness.html'); // bypasses auth for calculators
await page.waitForFunction(() => window.__harnessReady === true);
await page.click('button.calc-nav-pill[data-calc="bedside"]');
await expect(page.locator('#calc-bedside')).toBeVisible();
});
```
For auth-gated routes, use the login fixture in `auth-gated-smoke.spec.js`
as a template — it caches the token at module scope so you don't hit the
login rate-limit.