loadOidcConfig() was called from the first IIFE in admin.js but declared in the second IIFE. Function declarations don't cross IIFE boundaries, so the call threw ReferenceError and left the SSO form empty with "Disabled" selected — even when SSO was configured and working. Moved the call into the tabChanged handler of the IIFE where the function lives. |
||
|---|---|---|
| .github/workflows | ||
| android | ||
| docs | ||
| e2e | ||
| migrations | ||
| mobile | ||
| monitoring | ||
| public | ||
| scripts | ||
| src | ||
| test | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .gitmessage | ||
| .node-pg-migraterc.json | ||
| admin-cli.js | ||
| BROWSER_WHISPER_SETUP.md | ||
| BROWSER_WHISPER_TROUBLESHOOTING.md | ||
| CONTRIBUTING.md | ||
| DEVELOPER_GUIDE.md | ||
| docker-compose.e2e.yml | ||
| docker-compose.local.yml | ||
| docker-compose.monitoring.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| EMBEDDINGS_SETUP.md | ||
| FEATURES_EXPLAINED.md | ||
| grafana-dashboard.json | ||
| IMPROVEMENTS.md | ||
| OPENID_SETUP.md | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| server.js | ||
| TRANSCRIPTION_OPTIONS.md | ||
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
cp .env.example .env
Edit .env — at minimum set:
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
docker compose up -d
App runs on port 3552. First user to register becomes admin.
3. Admin CLI
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.
- Register callback URL:
https://your-domain.com/api/auth/oidc/callback - Admin Panel > Settings > Configure OIDC (Issuer URL, Client ID, Client Secret)
- Users are auto-created and linked by email on first SSO login
See OPENID_SETUP.md for provider-specific guides.
Cloudflare Turnstile (Bot Protection)
Optional CAPTCHA on login, registration, and password reset forms.
TURNSTILE_SITE_KEY=0x4AAA...
TURNSTILE_SECRET_KEY=0x4AAA...
Without SMTP, email verification is skipped and users are auto-verified.
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:
# 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 rowsreturned 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
docker pull danielonyejesi/pediatric-ai-scribe-v3:latest
Minimal compose without building:
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 the docs/ directory for detailed documentation:
- Architecture Overview
- API Reference
- Database Schema
- Authentication & Security
- AI Providers & Models
- Speech (STT/TTS)
- Learning Hub & CMS
- Configuration Reference
- Deployment Guide
- Developer Guide
Development
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)
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.
# 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 apppediatric-ai-scribe-e2e(port 3553) — identical image, but withTURNSTILE_SECRET_KEY=""andSMTP_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.htmlloads 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 failuretrace.zip— full action trace (replay withnpx 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 modulee2e/tests/top-calculators.spec.js— 27 tests for BP / BMI / Growth / Bili / Vitals / BSA / Dose / Resus / GCS / Equipmente2e/tests/auth-gated-smoke.spec.js— 11 tests for the auth-gated tabse2e/playwright.config.js— runs all the above under bothchromium(Desktop Chrome) andmobile-chrome(Pixel 5) projects
Writing a new test:
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.