- Add Cloudflare Turnstile to login, register, and password reset forms - Switch AI provider to LiteLLM, transcription to OpenAI Whisper - Change domain to scribe.pedshub.com - Fix PPTX export: add tables, bold/italic, numbered lists, code blocks, blockquotes - Fix announcement banner close button (CSP was blocking inline onclick) - Fix auth middleware: empty Bearer token now falls through to cookie auth - Fix audio backups: only save on transcription failure, stop auto-deleting on success - Soften AI correction injection to prevent model hallucination from correction history - Fix LiteLLM TTS model name handling (no incorrect openai/ prefix) - Expand AI instructions textarea in Learning Hub CMS - Update README for v6 with all features and providers - Add comprehensive docs/: architecture, API reference, database schema, authentication, AI providers, speech, learning hub, configuration, deployment
6.5 KiB
Pediatric AI Scribe - Architecture Overview
System Overview
The Pediatric AI Scribe is a self-hosted, Dockerized clinical documentation assistant built on the following stack:
- Runtime: Node.js 20 (Alpine) with Express
- Database: PostgreSQL 16 with the pgvector extension for embedding-based similarity search
- Containerization: Docker Compose with two services (app + database)
- Frontend: Vanilla JavaScript single-page application (no framework)
The application provides AI-powered transcription, note generation, and learning tools for pediatric clinicians. It runs entirely behind a reverse proxy and is designed for single-institution or personal deployment.
File Structure
/
├── server.js # Application entry point
├── package.json
├── Dockerfile
├── docker-compose.yml
├── sw.js # Service worker (copied into public/)
│
├── src/
│ ├── routes/ # 27 route files (Express routers)
│ │ ├── encounters.js
│ │ ├── auth.js
│ │ ├── admin.js
│ │ ├── learning.js
│ │ ├── ... # (27 total)
│ │
│ ├── utils/
│ │ ├── ai.js # LLM client abstraction (OpenAI-compatible)
│ │ ├── models.js # Model registry and selection
│ │ ├── prompts.js # System/user prompt templates
│ │ ├── config.js # App settings helpers (DB-backed)
│ │ ├── logger.js # Winston logger setup
│ │ ├── embeddings.js # pgvector embedding generation
│ │ ├── transcribeAWS.js # AWS Transcribe integration
│ │ ├── transcribeGoogle.js # Google Cloud Speech-to-Text
│ │ ├── transcribeLocal.js # Local Whisper WASM transcription
│ │ └── ttsGoogle.js # Google Cloud Text-to-Speech
│ │
│ ├── middleware/
│ │ ├── auth.js # JWT + session authentication
│ │ └── logging.js # Request/response logging middleware
│ │
│ └── db/
│ └── database.js # PostgreSQL connection pool + query helpers
│
├── public/ # Static frontend assets
│ ├── index.html # SPA shell
│ ├── app.js # Tab/navigation manager
│ ├── components/ # HTML partials loaded via fetch
│ ├── js/ # 20+ JS modules
│ └── css/ # Stylesheets
│
└── scripts/ # Utility and migration scripts
Request Flow
Every incoming HTTP request passes through the following middleware chain in order:
Client Request
|
v
Helmet (CSP headers, security hardening)
|
v
CORS (origin validation)
|
v
Cookie Parser (signed cookies for sessions)
|
v
Rate Limiting (per-IP and per-route limits)
|
v
Static File Serving (public/ directory)
|
v
Route Matching (src/routes/*.js)
|
v
Auth Middleware (JWT verification, role checks)
|
v
Route Handler (business logic, DB queries, AI calls)
|
v
JSON Response
Static assets are served before route matching, so unauthenticated users can load the SPA shell and login page. All API routes under /api/ require authentication unless explicitly excluded (e.g., /api/auth/login, /api/auth/register).
Frontend Architecture
The frontend is a vanilla JavaScript SPA with no build step and no framework.
Loading
index.html serves as the application shell. It contains a <div class="app-body"> placeholder and loads 20+ JS modules via <script defer> tags. On startup, app.js initializes the tab system and fetches HTML partials from components/ into the .app-body container.
Module Communication
Because there is no framework or module bundler, frontend modules communicate through two mechanisms:
- Window globals -- Shared state and utility functions are attached to
window(e.g.,window.currentUser,window.apiCall). - CustomEvents -- Modules dispatch and listen for
CustomEventinstances ondocumentto coordinate loosely-coupled updates (e.g., when an encounter is saved, other tabs refresh their data).
Tab Navigation
Tabs are managed by app.js. Clicking a tab fetches the corresponding HTML partial from components/, injects it into .app-body, and invokes the module's initialization function. Only one tab is active at a time; previous tab content is replaced.
Docker Configuration
Application Container
- Base image:
node:20-alpine - System dependencies:
ffmpeg(audio processing for transcription) - Bundled models: Self-hosted Whisper WASM models for browser-side and server-side local transcription
- Internal port: 3000
Database Container
- Image:
pgvector/pgvector:pg16 - Extension: pgvector is loaded automatically for vector similarity search on learning content embeddings
Port Mapping
The application binds to the loopback interface only:
127.0.0.1:3552 -> container:3000
This means the app is not directly accessible from the network. A reverse proxy (e.g., Nginx, Caddy) should terminate TLS and forward traffic to 127.0.0.1:3552.
Volumes
| Volume | Purpose |
|---|---|
pgdata |
PostgreSQL data directory (persistent) |
scribe-logs |
Application file logs written by Winston |
Service Worker
The file sw.js is registered by the frontend and implements a two-strategy caching model:
Static Assets (Cache-First)
Requests for CSS, JS, images, fonts, and HTML partials are served from the cache first. If the cache misses, the network is used and the response is cached for future requests. This enables fast repeat loads and basic offline shell rendering.
API Requests (Network-First)
Requests to /api/ endpoints always attempt the network first. If the network fails (e.g., offline or timeout), the service worker falls back to a cached response if one exists. This ensures users always see the freshest data when connected.
Precaching
On installation, the service worker precaches the application shell: index.html, app.js, core CSS, and critical component partials. This set of assets is enough to render the login screen and basic UI skeleton without any network requests.