diff --git a/.env.example b/.env.example index 3622a44..68ea53c 100644 --- a/.env.example +++ b/.env.example @@ -91,6 +91,10 @@ ELEVENLABS_API_KEY= # App PORT=3000 APP_URL=https://your-domain.com + +# Cloudflare Turnstile (anti-bot on registration, optional) +# TURNSTILE_SITE_KEY=your-site-key +# TURNSTILE_SECRET_KEY=your-secret-key JWT_SECRET=generate-a-random-64-char-string-here # Email (for verification & password reset) diff --git a/README.md b/README.md index 46053af..c885589 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,78 @@ -# 🩺 Pediatric AI Scribe v3 +# Pediatric AI Scribe v6 -AI-powered clinical documentation platform for pediatric medicine. Generates HPIs, hospital courses, chart reviews, SOAP notes, and developmental milestone assessments from voice recordings or dictation β€” in seconds, in plain copy-ready text. +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 -- **Live Encounter β†’ HPI** β€” record a live doctor-patient conversation, AI generates a structured OLDCARTS HPI -- **Voice Dictation β†’ HPI / SOAP** β€” dictate your narrative, AI cleans and restructures it -- **Hospital Course Generator** β€” paste progress notes, AI generates prose, day-by-day, organ-system (ICU), or psych format summaries -- **Chart Review / Precharting** β€” summarize outpatient, subspecialty, and ED notes into a precharting brief -- **SOAP Note Generator** β€” full SOAP or subjective-only from dictation -- **Well Visit / Preventive Care** β€” AAP 2025 Bright Futures periodicity; vaccines, screenings, billing codes; By Visit Age, Milestones, SSHADESS (12+), and Visit Note subtabs -- **Sick Visit Note** β€” quick documentation with auto-suggested ROS and PE systems from chief complaint -- **Developmental Milestones** β€” AAP/Nelson milestone tracker (birth–11 years) with narrative, structured list, or 3-sentence summary; copy to Visit Note -- **SSHADESS Assessment** β€” adolescent psychosocial screening for ages 12+; auto-fills into Visit Note -- **Vaccine Schedule** β€” full AAP immunization schedule reference -- **Catch-Up Schedule** β€” catch-up immunization guide -- **Plain text output** β€” all documents generated without markdown, ready to paste into any EHR -- **Read Aloud** β€” browser TTS reads generated documents; ElevenLabs (Adam voice) supported -- **Copy & Export** β€” one-click copy or export to Nextcloud -- **Refine & Shorten** β€” edit any document with plain-language AI instructions -- **Per-tab model selector** β€” choose fast vs. smart vs. reasoning models per task -- **Collapsible sidebar** β€” desktop sidebar collapses to icon rail, state persisted -- **Save & Resume** β€” encounters saved with unique IDs; persist across page refresh -- **Admin Panel** β€” user management, registration control, audit logs +### 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 -- **Multi-provider AI** β€” OpenRouter, AWS Bedrock, or Azure OpenAI +- **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 (Docker) +## Quick Start -### 1. Clone and configure +### 1. Configure ```bash -git clone https://github.com/ifedan-ed/pediatric-ai-scribe-v3.git -cd pediatric-ai-scribe-v3 cp .env.example .env ``` Edit `.env` β€” at minimum set: ```env -OPENROUTER_API_KEY=sk-or-v1-... -OPENAI_API_KEY=sk-... # for Whisper transcription -JWT_SECRET=<64-char random string> +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= APP_URL=https://your-domain.com ``` -Generate a strong JWT secret: -```bash -openssl rand -hex 32 -``` - ### 2. Start ```bash docker compose up -d ``` -App runs on **port 3552** by default. The first user to register becomes admin automatically. +App runs on **port 3552**. First user to register becomes admin. -### 3. Admin CLI (inside container) +### 3. Admin CLI ```bash docker exec pediatric-ai-scribe node admin-cli.js list-users @@ -74,13 +85,90 @@ 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 [OPENID_SETUP.md](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 +``` + +--- + ## Docker Hub ```bash docker pull danielonyejesi/pediatric-ai-scribe-v3:latest ``` -### Minimal docker-compose without building +Minimal compose without building: ```yaml services: @@ -95,7 +183,7 @@ services: restart: unless-stopped postgres: - image: postgres:16-alpine + image: pgvector/pgvector:pg16 environment: POSTGRES_DB: pedscribe POSTGRES_USER: pedscribe @@ -114,182 +202,35 @@ volumes: --- -## AI Provider Configuration - -Switch providers by changing `AI_PROVIDER` in `.env`. No code changes needed. - -### OpenRouter (default β€” cheapest, NOT HIPAA) - -```env -AI_PROVIDER=openrouter -OPENROUTER_API_KEY=sk-or-v1-... -``` - -### AWS Bedrock (HIPAA compliant with BAA) - -```env -AI_PROVIDER=bedrock -AWS_BEDROCK_REGION=us-east-1 -AWS_ACCESS_KEY_ID=AKIA... -AWS_SECRET_ACCESS_KEY=... -``` - -Or use an IAM role (no keys needed when running on EC2/ECS β€” just set the region). - -Available Bedrock models (auto-selected when `AI_PROVIDER=bedrock`): -- vendor model Opus 4.6 β€” best language nuance (`anthropic.agent-config-opus-4-6-20251001-v1:0`) -- vendor model Sonnet 4.6 β€” recommended (`anthropic.agent-config-sonnet-4-6-20251001-v1:0`) -- vendor model Sonnet 4 (`anthropic.agent-config-sonnet-4-20250514-v1:0`) -- vendor model 3.5 Sonnet (`anthropic.agent-config-3-5-sonnet-20241022-v2:0`) -- vendor model 3 Haiku β€” cheapest (`anthropic.agent-config-3-haiku-20240307-v1:0`) -- Llama 3.1 70B / 8B -- Mistral Large - -### Azure OpenAI (HIPAA compliant with BAA) - -```env -AI_PROVIDER=azure -AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com -AZURE_OPENAI_API_KEY=... -AZURE_DEPLOYMENT_NAME=gpt-4o-mini -AZURE_OPENAI_API_VERSION=2024-02-01 -``` - ---- - -## Transcription (Speech-to-Text) - -Two providers supported. The app auto-selects AWS Transcribe when `AWS_BEDROCK_REGION` is set, otherwise falls back to OpenAI Whisper. - -### Amazon Transcribe (recommended for clinical use β€” HIPAA eligible) - -Uses your existing Bedrock AWS credentials. No S3 bucket required β€” audio streams directly to AWS. - -**Requires `ffmpeg` installed on the server** (handles audio format conversion from browser WebM to PCM). The Docker image includes ffmpeg automatically. - -```env -# Auto-enabled when AWS_BEDROCK_REGION is set. -# To force it explicitly: -TRANSCRIBE_PROVIDER=aws - -# Amazon Transcribe Medical β€” trained on clinical speech. -# Knows drug names, diagnoses, procedures. Recommended. -AWS_TRANSCRIBE_MEDICAL=true -AWS_TRANSCRIBE_SPECIALTY=PRIMARYCARE -# Other specialty options: CARDIOLOGY, NEUROLOGY, ONCOLOGY, RADIOLOGY, UROLOGY -``` - -Install ffmpeg on non-Docker servers: -```bash -# Ubuntu/Debian -sudo apt-get install -y ffmpeg - -# Amazon Linux / RHEL -sudo yum install -y ffmpeg - -# macOS -brew install ffmpeg -``` - -### OpenAI Whisper (default fallback) - -```env -OPENAI_API_KEY=sk-... -# Optionally force Whisper even when AWS is configured: -# TRANSCRIBE_PROVIDER=openai -``` - ---- - -## OpenID Connect / SSO (optional β€” PocketID, Keycloak, Azure AD, etc.) - -Enable Single Sign-On authentication with any OpenID Connect provider. Users can log in with their SSO provider, and existing accounts are automatically linked by email. - -**Supported providers:** PocketID, Keycloak, Azure AD / Entra ID, Okta, Google Workspace, and any OIDC-compliant provider. - -### Quick Setup - -1. Register this app with your OIDC provider using callback URL: - ``` - https://your-domain.com/api/auth/oidc/callback - ``` - -2. Log into the app as admin and navigate to **Admin Panel** β†’ **Settings** - -3. Configure OIDC settings: - - Enable OIDC - - Set Issuer URL (e.g., `https://id.example.com`) - - Enter Client ID and Client Secret from your provider - - Customize the button label (e.g., "Sign in with PocketID") - -4. Save and test the login - -**Detailed setup guide with examples for all providers:** [OPENID_SETUP.md](OPENID_SETUP.md) - -### Linking Existing Users - -When a user signs in via SSO, the system automatically links their account if the email matches an existing user. No manual intervention needed. - -For advanced scenarios (manual linking, CLI commands, troubleshooting), see [OPENID_SETUP.md](OPENID_SETUP.md). - ---- - -## Email (optional β€” for verification & password reset) - -Without SMTP configured, email verification is skipped and users are auto-verified on registration. - -```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 -``` - ---- - -## Environment Variables Reference - -| Variable | Required | Description | -|---|---|---| -| `OPENROUTER_API_KEY` | If using OpenRouter | OpenRouter API key | -| `AI_PROVIDER` | No | `openrouter` (default), `bedrock`, or `azure` | -| `AWS_BEDROCK_REGION` | If using Bedrock | e.g. `us-east-1` | -| `AWS_ACCESS_KEY_ID` | If using Bedrock (no IAM role) | AWS access key | -| `AWS_SECRET_ACCESS_KEY` | If using Bedrock (no IAM role) | AWS secret key | -| `AZURE_OPENAI_ENDPOINT` | If using Azure | Azure OpenAI endpoint URL | -| `AZURE_OPENAI_API_KEY` | If using Azure | Azure API key | -| `AZURE_DEPLOYMENT_NAME` | If using Azure | Deployment name, e.g. `gpt-4o-mini` | -| `OPENAI_API_KEY` | For Whisper transcription | OpenAI key (Whisper fallback) | -| `TRANSCRIBE_PROVIDER` | No | `aws` or `openai` β€” auto-detected from AWS config | -| `AWS_TRANSCRIBE_MEDICAL` | No | `true` to use Transcribe Medical (clinical accuracy) | -| `AWS_TRANSCRIBE_SPECIALTY` | No | `PRIMARYCARE` (default), `CARDIOLOGY`, `NEUROLOGY`, etc. | -| `ELEVENLABS_API_KEY` | No | ElevenLabs TTS (optional) | -| `JWT_SECRET` | **Yes** | Random 64-char string β€” keep secret | -| `DATABASE_URL` | No | PostgreSQL URL (auto-set by docker-compose) | -| `DB_PASSWORD` | **Yes** | PostgreSQL password | -| `APP_URL` | Recommended | Public URL e.g. `https://scribe.example.com` (used for CORS, emails) | -| `PORT` | No | Internal port, default `3000` | -| `SMTP_HOST` | No | SMTP server for email | -| `SMTP_PORT` | No | Default `587` | -| `SMTP_USER` | No | SMTP username | -| `SMTP_PASS` | No | SMTP password / app password | -| `SMTP_FROM` | No | From address for emails | - ---- - ## HIPAA Notice This application processes data through third-party AI APIs. -- βœ… All connections use HTTPS/TLS -- βœ… Authentication required for all AI endpoints -- βœ… 2FA available -- βœ… No patient data stored on server (only audit logs) -- ⚠️ **OpenRouter does not offer a BAA** β€” do not use with real PHI -- βœ… **AWS Bedrock** and **Azure OpenAI** offer BAAs β€” suitable for PHI with proper configuration +- 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 -**Recommendation:** Do not enter real patient data until your organization has executed BAAs with all AI providers in use. +**Do not use real PHI without executed BAAs with all providers in your deployment.** + +--- + +## Documentation + +See the [docs/](docs/) directory for detailed documentation: + +- [Architecture Overview](docs/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) --- @@ -298,6 +239,6 @@ This application processes data through third-party AI APIs. ```bash npm install cp .env.example .env # edit with your keys -# Requires a running PostgreSQL instance (see DATABASE_URL in .env) +# Requires PostgreSQL with pgvector node server.js ``` diff --git a/docker-compose.yml b/docker-compose.yml index 96b9836..a2d9543 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,9 @@ services: pediatric-scribe: - image: danielonyejesi/pediatric-ai-scribe-v3:v6 + build: . + image: ped-ai-local:latest ports: - - "3552:3000" + - "127.0.0.1:3552:3000" env_file: - .env volumes: @@ -20,11 +21,11 @@ services: start_period: 20s postgres: - image: postgres:16-alpine + image: pgvector/pgvector:pg16 environment: POSTGRES_DB: pedscribe POSTGRES_USER: pedscribe - POSTGRES_PASSWORD: ${DB_PASSWORD:-pedscribe_secret_change_me} + POSTGRES_PASSWORD: ${DB_PASSWORD:-zy9LK958SAcfUM7SJTkdthSYyYy6} volumes: - pgdata:/var/lib/postgresql/data restart: unless-stopped diff --git a/docs/ai-providers.md b/docs/ai-providers.md new file mode 100644 index 0000000..e205821 --- /dev/null +++ b/docs/ai-providers.md @@ -0,0 +1,114 @@ +# AI Providers + +This document covers the AI provider system, model management, prompt configuration, and usage logging for the Pediatric AI Scribe application. + +--- + +## Provider Selection + +The active AI provider is determined in one of two ways: + +1. **Explicit**: Set the `AI_PROVIDER` environment variable to one of the supported provider names. +2. **Auto-detect**: If `AI_PROVIDER` is not set, the system checks for provider-specific credentials in the environment and selects the first match using this priority order: + + `bedrock` > `azure` > `vertex` > `litellm` > `openrouter` + +All providers expose a unified `callAI()` interface defined in `src/utils/ai.js`. Calling code does not need to know which backend is active. + +--- + +## Provider Details + +### 1. AWS Bedrock (HIPAA-eligible with BAA) + +- **SDK**: `@aws-sdk/client-bedrock-runtime` +- Uses **inference profiles** for newer models, enabling cross-region routing. +- **Available model families**: vendor model (Anthropic), Amazon Nova, Llama (Meta), Mistral, DeepSeek, Cohere. +- **Default temperature**: 0.3 + +### 2. Azure OpenAI (HIPAA-eligible with BAA) + +- **SDK**: OpenAI SDK configured to point at an Azure endpoint. +- Requires a **deployment name** that maps to the desired model. +- **Available model families**: GPT-4o family, GPT-4.1 family. + +### 3. Google Vertex AI (HIPAA-eligible with BAA) + +- **SDK**: `@google-cloud/vertexai` +- In addition to text generation, Vertex AI handles: + - **Speech-to-Text (STT)**: via Gemini inline audio capabilities. + - **Text-to-Speech (TTS)**: via Vertex AI TTS endpoint. +- **Available model families**: Gemini 2.5/2.0, vendor model on Vertex (Anthropic models hosted on Google Cloud), Llama. + +### 4. LiteLLM Proxy (self-hosted) + +- **SDK**: OpenAI SDK pointed at the `LITELLM_API_BASE` URL. +- Acts as a proxy that routes requests to any backend configured within LiteLLM. +- **Model discovery**: queries `/v1/models` on the LiteLLM instance to populate the available model list. +- Also supports **TTS and STT** passthrough. +- Model names are used **as-configured in LiteLLM** -- the application does not auto-prefix or transform them. + +### 5. OpenRouter (NOT HIPAA-compliant) + +- **SDK**: OpenAI SDK pointed at `https://openrouter.ai`. +- **Cost discovery**: queries the OpenRouter API to retrieve per-model pricing. +- Offers the **cheapest option** and the **widest model selection** across many providers. +- Not suitable for environments that require HIPAA compliance. + +--- + +## Model Management + +### Model Definitions + +Models are defined in `src/utils/models.js` and organized into four categories: + +| Category | Description | +|-----------|------------------------------------------------| +| `free` | No-cost models (typically smaller or rate-limited) | +| `fast` | Low-latency models optimized for speed | +| `smart` | Balanced models with strong reasoning ability | +| `premium` | Top-tier models with the highest capability | + +### Admin Controls + +- Administrators can **enable or disable** individual models from the Admin Panel. +- A **default model** can be set for new users. +- **Custom models** can be added with a user-defined name and cost per token. + +### Model Discovery + +The system queries the active provider's API to retrieve the list of models that are actually available. This list is intersected with the configured model definitions to determine what appears in the UI. + +### Frontend Display + +The model selector dropdown groups models by category (free, fast, smart, premium) for easy navigation. + +--- + +## AI Prompts + +### Storage and Override + +- All default prompts are defined in `src/utils/prompts.js`. +- Prompts can be **overridden via the database** using the `app_settings` table with keys following the pattern `prompt.{name}`. +- Administrators can view and edit all prompts directly from the Admin Panel. + +### Physician Memories + +When a physician saves corrections or preferences (referred to as "memories"), these are injected into the prompt as **low-priority style hints**. This allows the AI to adapt its output to the physician's preferred documentation style without overriding the core clinical prompt. + +--- + +## Logging + +Every AI call is recorded in the `api_log` database table with the following fields: + +| Field | Description | +|------------|-----------------------------------------------------| +| `model` | The model identifier used for the request | +| `tokens` | Input and output token counts | +| `cost` | Estimated cost of the call | +| `duration` | Wall-clock time for the request in milliseconds | + +Cost estimates are calculated from **hardcoded per-model rates** defined in the codebase. For OpenRouter, rates may also be fetched from the OpenRouter pricing API. diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..3d37114 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,2144 @@ +# API Reference + +Complete endpoint reference for the PedAI application. All endpoints are prefixed with the application base URL. Unless noted otherwise, authenticated endpoints require a valid JWT token passed via cookie or `Authorization: Bearer ` header. + +--- + +## Table of Contents + +- [Authentication](#authentication) +- [OIDC / SSO](#oidc--sso) +- [AI Generation](#ai-generation) +- [Transcription](#transcription) +- [Text-to-Speech](#text-to-speech) +- [Models](#models) +- [Encounters](#encounters) +- [Memories](#memories) +- [Audio Backups](#audio-backups) +- [Documents](#documents) +- [User Preferences](#user-preferences) +- [Nextcloud Integration](#nextcloud-integration) +- [Learning Hub (Public)](#learning-hub-public) +- [Learning Hub CMS (Moderator+)](#learning-hub-cms-moderator) +- [Admin - Users](#admin---users) +- [Admin - Configuration](#admin---configuration) +- [Logs](#logs) +- [Milestones (Admin)](#milestones-admin) +- [Health](#health) + +--- + +## Authentication + +Base path: `/api/auth/` + +### POST /api/auth/register + +Register a new user account. The first user to register is automatically assigned the admin role. Requires Cloudflare Turnstile verification. A verification email is sent on success. + +- **Auth required:** No +- **Request body:** + ```json + { + "name": "string", + "email": "string", + "password": "string", + "turnstileToken": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "message": "Registration successful. Please check your email to verify your account." + } + ``` + +--- + +### GET /api/auth/verify-email + +Verify a user's email address via the link sent during registration. + +- **Auth required:** No +- **Query parameters:** + | Parameter | Type | Description | + |-----------|--------|--------------------------------------| + | `token` | string | Email verification token from the link | +- **Response:** Redirects to the login page with a success or error message. + +--- + +### POST /api/auth/resend-verification + +Resend the email verification link. + +- **Auth required:** No +- **Request body:** + ```json + { + "email": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "message": "Verification email sent." + } + ``` + +--- + +### POST /api/auth/login + +Authenticate a user. Supports optional TOTP two-factor authentication. On success, sets an HTTP-only JWT cookie and returns user data. + +- **Auth required:** No +- **Request body:** + ```json + { + "email": "string", + "password": "string", + "totpCode": "string (optional, required if 2FA is enabled)", + "turnstileToken": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "token": "jwt-string", + "user": { + "id": "number", + "name": "string", + "email": "string", + "role": "admin | moderator | user" + } + } + ``` + If 2FA is enabled but `totpCode` is not provided: + ```json + { + "requires2FA": true + } + ``` + +--- + +### POST /api/auth/setup-2fa + +Generate a TOTP secret and QR code for setting up two-factor authentication. + +- **Auth required:** Yes +- **Request body:** None +- **Response:** + ```json + { + "secret": "string", + "qrCode": "string (data URI)" + } + ``` + +--- + +### POST /api/auth/verify-2fa + +Verify a TOTP code and enable two-factor authentication for the user. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "code": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "message": "2FA enabled successfully." + } + ``` + +--- + +### POST /api/auth/disable-2fa + +Disable two-factor authentication. Requires password confirmation. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "password": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "message": "2FA disabled successfully." + } + ``` + +--- + +### POST /api/auth/forgot-password + +Initiate password reset. Sends a reset link to the user's email. + +- **Auth required:** No +- **Request body:** + ```json + { + "email": "string", + "turnstileToken": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "message": "If an account exists with that email, a reset link has been sent." + } + ``` + +--- + +### POST /api/auth/reset-password + +Reset the user's password using a valid reset token. + +- **Auth required:** No +- **Request body:** + ```json + { + "token": "string", + "password": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "message": "Password reset successfully." + } + ``` + +--- + +### POST /api/auth/logout + +Log out the current user by clearing the JWT cookie. + +- **Auth required:** No +- **Request body:** None +- **Response:** + ```json + { + "success": true, + "message": "Logged out." + } + ``` + +--- + +### GET /api/auth/me + +Retrieve the currently authenticated user's profile. + +- **Auth required:** Yes +- **Response:** + ```json + { + "id": "number", + "name": "string", + "email": "string", + "role": "admin | moderator | user", + "twoFactorEnabled": "boolean" + } + ``` + +--- + +### GET /api/auth/registration-status + +Check whether new user registration is enabled on this instance. + +- **Auth required:** No +- **Response:** + ```json + { + "enabled": true + } + ``` + +--- + +## OIDC / SSO + +Base path: `/api/auth/` + +### GET /api/auth/oidc-status + +Check whether OIDC single sign-on is configured and enabled. + +- **Auth required:** No +- **Response:** + ```json + { + "oidcEnabled": true, + "disableLocalAuth": false, + "buttonLabel": "Sign in with SSO" + } + ``` + +--- + +### GET /api/auth/oidc + +Initiates the OIDC login flow. Redirects the user to the configured identity provider. + +- **Auth required:** No +- **Response:** HTTP 302 redirect to the IdP authorization endpoint. + +--- + +### GET /api/auth/oidc/callback + +Handles the callback from the identity provider after authentication. Processes the authorization code and creates/logs in the user. + +- **Auth required:** No +- **Query parameters:** Standard OIDC callback parameters (`code`, `state`). +- **Response:** Redirects to the application with a session cookie set. + +--- + +## AI Generation + +All generation endpoints require authentication and stream or return AI-generated clinical text. Each accepts a `model` parameter to select the LLM. + +### POST /api/generate-hpi-encounter + +Generate a History of Present Illness note from a patient encounter transcript. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "transcript": "string", + "patientAge": "string", + "patientGender": "string", + "model": "string", + "setting": "string", + "physicianMemories": "string (optional)" + } + ``` +- **Response:** Streamed text (text/event-stream) or JSON with the generated HPI note. + +--- + +### POST /api/generate-hpi-dictation + +Generate an HPI note from a physician's dictated summary (same parameters as encounter). + +- **Auth required:** Yes +- **Request body:** + ```json + { + "transcript": "string", + "patientAge": "string", + "patientGender": "string", + "model": "string", + "setting": "string", + "physicianMemories": "string (optional)" + } + ``` +- **Response:** Streamed text (text/event-stream) or JSON with the generated HPI note. + +--- + +### POST /api/generate-soap + +Generate a SOAP note from a patient encounter transcript. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "transcript": "string", + "patientAge": "string", + "patientGender": "string", + "model": "string", + "type": "string", + "additionalInstructions": "string (optional)", + "physicianMemories": "string (optional)" + } + ``` +- **Response:** Streamed text (text/event-stream) or JSON with the generated SOAP note. + +--- + +### POST /api/generate-chart-review + +Generate a chart review summary from clinical notes. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "notes": "string", + "visitType": "string", + "model": "string" + } + ``` +- **Response:** Streamed text or JSON with the chart review. + +--- + +### POST /api/generate-hospital-course + +Generate a hospital course summary from clinical notes. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "notes": "string", + "patientAge": "string", + "patientGender": "string", + "model": "string", + "format": "string", + "physicianMemories": "string (optional)" + } + ``` +- **Response:** Streamed text or JSON with the hospital course summary. + +--- + +### POST /api/well-visit/shadess + +Generate a SHADESS (Strengths, Home, Activities, Drugs, Emotions, Sexuality, Safety) assessment from well-visit responses. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "responses": "object", + "patientAge": "string", + "model": "string" + } + ``` +- **Response:** Streamed text or JSON with the SHADESS assessment. + +--- + +### POST /api/generate-sick-visit + +Generate a sick visit note from a transcript, incorporating diagnosis information. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "transcript": "string", + "patientAge": "string", + "patientGender": "string", + "model": "string", + "diagnoses": "string | array", + "physicianMemories": "string (optional)" + } + ``` +- **Response:** Streamed text or JSON with the sick visit note. + +--- + +### POST /api/generate-milestone-narrative + +Generate a developmental milestone narrative from milestone data. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "milestones": "object | array", + "patientAge": "string", + "patientGender": "string", + "model": "string" + } + ``` +- **Response:** Streamed text or JSON with the milestone narrative. + +--- + +### POST /api/generate-milestone-summary + +Generate a concise summary from a previously generated milestone narrative. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "narrative": "string", + "model": "string" + } + ``` +- **Response:** Streamed text or JSON with the milestone summary. + +--- + +### POST /api/refine + +Refine existing clinical text according to provided instructions. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "text": "string", + "instructions": "string", + "model": "string" + } + ``` +- **Response:** Streamed text or JSON with the refined text. + +--- + +### POST /api/shorten + +Shorten a block of clinical text while preserving key information. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "text": "string", + "model": "string" + } + ``` +- **Response:** Streamed text or JSON with the shortened text. + +--- + +### POST /api/clarify + +Improve clarity and readability of clinical text. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "text": "string", + "model": "string" + } + ``` +- **Response:** Streamed text or JSON with the clarified text. + +--- + +## Transcription + +### GET /api/transcribe/status + +Check whether speech-to-text transcription is available and which provider is configured. + +- **Auth required:** Yes +- **Response:** + ```json + { + "available": true, + "provider": "whisper | deepgram | browser" + } + ``` + +--- + +### POST /api/transcribe + +Transcribe an audio file to text. Accepts multipart form data with the audio file. + +- **Auth required:** Yes +- **Content-Type:** `multipart/form-data` +- **File size limit:** 25 MB +- **Form fields:** + | Field | Type | Description | + |---------|------|--------------------| + | `audio` | file | Audio file to transcribe | +- **Response:** + ```json + { + "success": true, + "text": "string", + "provider": "string", + "duration": "number (seconds)" + } + ``` + +--- + +## Text-to-Speech + +### POST /api/text-to-speech + +Convert text to speech. Returns an audio stream. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "text": "string", + "voice": "string (optional)" + } + ``` +- **Response:** Audio stream (`audio/mpeg`). +- **Response headers:** + | Header | Description | + |------------------|------------------------------------| + | `X-TTS-Provider` | The TTS provider used for synthesis | + +--- + +## Models + +### GET /api/models + +List available AI models for the current instance. + +- **Auth required:** Yes +- **Response:** + ```json + { + "provider": "string", + "models": [ + { + "id": "string", + "name": "string", + "cost": "string", + "category": "string" + } + ], + "defaultModel": "string" + } + ``` + +--- + +## Encounters + +### GET /api/encounters/saved + +List all saved encounters for the authenticated user. + +- **Auth required:** Yes +- **Response:** + ```json + [ + { + "id": "number", + "label": "string", + "enc_type": "string", + "created_at": "string (ISO 8601)", + "updated_at": "string (ISO 8601)" + } + ] + ``` + +--- + +### GET /api/encounters/saved/:id + +Retrieve a single saved encounter with full data. + +- **Auth required:** Yes +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|-----------------| + | `id` | number | Encounter ID | +- **Response:** + ```json + { + "id": "number", + "label": "string", + "enc_type": "string", + "transcript": "string", + "generated_note": "string", + "partial_data": "object | null", + "created_at": "string (ISO 8601)", + "updated_at": "string (ISO 8601)" + } + ``` + +--- + +### POST /api/encounters/saved + +Create or update a saved encounter. Uses `idempotency_key` to prevent duplicates. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "label": "string", + "enc_type": "string", + "transcript": "string", + "generated_note": "string", + "partial_data": "object (optional)", + "idempotency_key": "string (optional)" + } + ``` +- **Response:** + ```json + { + "success": true, + "id": "number" + } + ``` + +--- + +### DELETE /api/encounters/saved/:id + +Delete a saved encounter. + +- **Auth required:** Yes +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|-----------------| + | `id` | number | Encounter ID | +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +## Memories + +Physician memories are reusable context snippets (preferences, style corrections, common instructions) injected into AI generation prompts. + +### GET /api/memories + +List all memories for the authenticated user. Returns up to 200 entries. + +- **Auth required:** Yes +- **Response:** + ```json + [ + { + "id": "number", + "category": "string", + "name": "string", + "content": "string", + "created_at": "string (ISO 8601)" + } + ] + ``` + +--- + +### POST /api/memories + +Create a new memory. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "category": "string", + "name": "string", + "content": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "id": "number" + } + ``` + +--- + +### PUT /api/memories/:id + +Update an existing memory. + +- **Auth required:** Yes +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|-------------| + | `id` | number | Memory ID | +- **Request body:** + ```json + { + "category": "string", + "name": "string", + "content": "string" + } + ``` +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### DELETE /api/memories/:id + +Delete a memory. + +- **Auth required:** Yes +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|-------------| + | `id` | number | Memory ID | +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### GET /api/memories/context + +Retrieve all memories formatted for injection into AI generation prompts. + +- **Auth required:** Yes +- **Response:** + ```json + { + "context": "string" + } + ``` + +--- + +### POST /api/memories/correction + +Automatically save a style correction as a memory. Used when the physician corrects AI output. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "section": "string", + "original_snippet": "string", + "corrected_snippet": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "id": "number" + } + ``` + +--- + +## Audio Backups + +Temporary audio backup storage with automatic 24-hour expiry. + +### POST /api/audio-backups + +Upload an audio backup. The file is gzip-compressed on the server. + +- **Auth required:** Yes +- **Content-Type:** `multipart/form-data` +- **Form fields:** + | Field | Type | Description | + |---------|------|------------------------| + | `audio` | file | Audio file to back up | +- **Response:** + ```json + { + "success": true, + "id": "number" + } + ``` + +--- + +### GET /api/audio-backups + +List all audio backups for the authenticated user. + +- **Auth required:** Yes +- **Response:** + ```json + [ + { + "id": "number", + "filename": "string", + "created_at": "string (ISO 8601)", + "expires_at": "string (ISO 8601)" + } + ] + ``` + +--- + +### GET /api/audio-backups/:id/audio + +Download a decompressed audio backup. + +- **Auth required:** Yes +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|-------------------| + | `id` | number | Audio backup ID | +- **Response:** Binary audio stream with appropriate content-type header. + +--- + +### DELETE /api/audio-backups/:id + +Delete an audio backup. + +- **Auth required:** Yes +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|-------------------| + | `id` | number | Audio backup ID | +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +## Documents + +S3-backed document storage for clinical files. + +### GET /api/documents + +List all documents for the authenticated user. + +- **Auth required:** Yes +- **Response:** + ```json + [ + { + "id": "number", + "filename": "string", + "mimetype": "string", + "size": "number", + "created_at": "string (ISO 8601)" + } + ] + ``` + +--- + +### POST /api/documents/upload + +Upload a document to S3 storage. + +- **Auth required:** Yes +- **Content-Type:** `multipart/form-data` +- **File size limit:** 10 MB +- **Allowed types:** PDF, images (JPEG, PNG, GIF, WebP), Word documents (.doc, .docx), plain text (.txt), CSV (.csv) +- **Form fields:** + | Field | Type | Description | + |--------|------|---------------------| + | `file` | file | Document to upload | +- **Response:** + ```json + { + "success": true, + "id": "number", + "filename": "string" + } + ``` + +--- + +### GET /api/documents/:id/download + +Download a document. + +- **Auth required:** Yes +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|---------------| + | `id` | number | Document ID | +- **Response:** Binary file stream with appropriate content-type and content-disposition headers. + +--- + +### DELETE /api/documents/:id + +Delete a document from S3 storage. + +- **Auth required:** Yes +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|---------------| + | `id` | number | Document ID | +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +## User Preferences + +### GET /api/user/preferences + +Get the authenticated user's preferences. + +- **Auth required:** Yes +- **Response:** + ```json + { + "stt_model": "string", + "tts_voice": "string" + } + ``` + +--- + +### POST /api/user/preferences + +Save user preferences. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "stt_model": "string", + "tts_voice": "string" + } + ``` +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### GET /api/user/preferences/options + +List available STT models and TTS voices that the user can choose from. + +- **Auth required:** Yes +- **Response:** + ```json + { + "sttModels": [ + { "id": "string", "name": "string" } + ], + "ttsVoices": [ + { "id": "string", "name": "string" } + ] + } + ``` + +--- + +### POST /api/user/webdav-path + +Save the user's preferred WebDAV learning content path. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "path": "string" + } + ``` +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +## Nextcloud Integration + +### POST /api/nextcloud/connect + +Connect the user's account to a Nextcloud instance via WebDAV. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "url": "string", + "username": "string", + "password": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "message": "Connected to Nextcloud." + } + ``` + +--- + +### POST /api/nextcloud/export + +Export text content to the connected Nextcloud instance. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "text": "string", + "title": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "path": "string" + } + ``` + +--- + +### POST /api/nextcloud/disconnect + +Disconnect the user's Nextcloud integration. + +- **Auth required:** Yes +- **Request body:** None +- **Response:** + ```json + { + "success": true, + "message": "Disconnected from Nextcloud." + } + ``` + +--- + +## Learning Hub (Public) + +Public-facing learning content endpoints. Authentication is required to track progress and submit quizzes. + +### GET /api/learning/categories + +List all learning content categories. + +- **Auth required:** Yes +- **Response:** + ```json + [ + { + "id": "number", + "name": "string", + "slug": "string", + "description": "string" + } + ] + ``` + +--- + +### GET /api/learning/feed + +Get a paginated feed of learning content. + +- **Auth required:** Yes +- **Query parameters:** + | Parameter | Type | Default | Description | + |-----------|--------|---------|-----------------------| + | `limit` | number | 20 | Items per page | + | `offset` | number | 0 | Pagination offset | +- **Response:** + ```json + [ + { + "id": "number", + "title": "string", + "slug": "string", + "summary": "string", + "category": "string", + "created_at": "string (ISO 8601)" + } + ] + ``` + +--- + +### GET /api/learning/category/:slug + +Get all learning content within a specific category. + +- **Auth required:** Yes +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|----------------| + | `slug` | string | Category slug | +- **Response:** Array of content items in the category. + +--- + +### GET /api/learning/content/:slug + +Get a single piece of learning content, including quiz questions, answer options, and the user's progress. + +- **Auth required:** Yes +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|---------------| + | `slug` | string | Content slug | +- **Response:** + ```json + { + "id": "number", + "title": "string", + "slug": "string", + "body": "string (markdown)", + "category": "object", + "questions": [ + { + "id": "number", + "text": "string", + "options": [ + { + "id": "number", + "text": "string" + } + ] + } + ], + "progress": { + "completed": "boolean", + "score": "number | null" + } + } + ``` + +--- + +### GET /api/learning/content/:slug/slides + +Render Marp-formatted markdown content as HTML presentation slides. + +- **Auth required:** Yes +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|---------------| + | `slug` | string | Content slug | +- **Response:** HTML presentation content. + +--- + +### POST /api/learning/submit-quiz + +Submit quiz answers for a piece of learning content. + +- **Auth required:** Yes +- **Request body:** + ```json + { + "contentId": "number", + "answers": [ + { + "questionId": "number", + "optionIds": ["number"] + } + ] + } + ``` +- **Response:** + ```json + { + "success": true, + "score": "number", + "total": "number", + "results": [ + { + "questionId": "number", + "correct": "boolean" + } + ] + } + ``` + +--- + +### GET /api/learning/search + +Keyword-based search across learning content. + +- **Auth required:** Yes +- **Query parameters:** + | Parameter | Type | Description | + |-----------|--------|-----------------| + | `q` | string | Search query | +- **Response:** Array of matching content items. + +--- + +### GET /api/learning/search/semantic + +Semantic (vector-based) search across learning content using embeddings. + +- **Auth required:** Yes +- **Query parameters:** + | Parameter | Type | Description | + |-----------|--------|-----------------| + | `q` | string | Search query | +- **Response:** Array of matching content items ranked by semantic similarity. + +--- + +### GET /api/learning/search/hybrid + +Combined keyword and semantic search for best-of-both-worlds results. + +- **Auth required:** Yes +- **Query parameters:** + | Parameter | Type | Description | + |-----------|--------|-----------------| + | `q` | string | Search query | +- **Response:** Array of matching content items with combined ranking. + +--- + +## Learning Hub CMS (Moderator+) + +Content management endpoints for learning content. Requires moderator or admin role. + +### GET /api/admin/learning/categories + +List all learning categories (admin view). + +- **Auth required:** Yes (moderator+) +- **Response:** Array of category objects. + +--- + +### POST /api/admin/learning/categories + +Create a new learning category. + +- **Auth required:** Yes (moderator+) +- **Request body:** + ```json + { + "name": "string", + "slug": "string", + "description": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "id": "number" + } + ``` + +--- + +### PUT /api/admin/learning/categories/:id + +Update a learning category. + +- **Auth required:** Yes (moderator+) +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|---------------| + | `id` | number | Category ID | +- **Request body:** + ```json + { + "name": "string", + "slug": "string", + "description": "string" + } + ``` +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### DELETE /api/admin/learning/categories/:id + +Delete a learning category. + +- **Auth required:** Yes (moderator+) +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|---------------| + | `id` | number | Category ID | +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### GET /api/admin/learning/content + +List all learning content (admin view, includes unpublished). + +- **Auth required:** Yes (moderator+) +- **Response:** Array of content objects. + +--- + +### POST /api/admin/learning/content + +Create new learning content. + +- **Auth required:** Yes (moderator+) +- **Request body:** + ```json + { + "title": "string", + "slug": "string", + "body": "string (markdown)", + "categoryId": "number", + "questions": "array (optional)" + } + ``` +- **Response:** + ```json + { + "success": true, + "id": "number" + } + ``` + +--- + +### GET /api/admin/learning/content/:id + +Get a single content item for editing. + +- **Auth required:** Yes (moderator+) +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|--------------| + | `id` | number | Content ID | +- **Response:** Full content object with questions and metadata. + +--- + +### PUT /api/admin/learning/content/:id + +Update existing learning content. + +- **Auth required:** Yes (moderator+) +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|--------------| + | `id` | number | Content ID | +- **Request body:** + ```json + { + "title": "string", + "slug": "string", + "body": "string (markdown)", + "categoryId": "number", + "questions": "array (optional)" + } + ``` +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### DELETE /api/admin/learning/content/:id + +Delete learning content. + +- **Auth required:** Yes (moderator+) +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|--------------| + | `id` | number | Content ID | +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### POST /api/admin/learning/ai-generate + +Generate learning content or presentations using AI. Accepts either multipart form data (with file uploads) or JSON. + +- **Auth required:** Yes (moderator+) +- **Content-Type:** `multipart/form-data` or `application/json` +- **Request body (JSON):** + ```json + { + "topic": "string", + "type": "article | presentation", + "model": "string" + } + ``` +- **Request body (multipart):** Same fields plus uploaded reference files. +- **Response:** + ```json + { + "success": true, + "content": "string (markdown)" + } + ``` + +--- + +### POST /api/admin/learning/ai-refine + +Refine learning content body text using AI. + +- **Auth required:** Yes (moderator+) +- **Request body:** + ```json + { + "body": "string", + "instructions": "string", + "model": "string" + } + ``` +- **Response:** + ```json + { + "success": true, + "content": "string" + } + ``` + +--- + +### POST /api/admin/learning/preview-slides + +Preview Marp-formatted markdown as rendered presentation slides. + +- **Auth required:** Yes (moderator+) +- **Request body:** + ```json + { + "markdown": "string" + } + ``` +- **Response:** + ```json + { + "css": "string", + "slides": ["string (HTML)"] + } + ``` + +--- + +### POST /api/admin/learning/generate-pptx + +Generate a PowerPoint file from Marp markdown. + +- **Auth required:** Yes (moderator+) +- **Request body:** + ```json + { + "markdown": "string", + "title": "string" + } + ``` +- **Response:** Binary `.pptx` file download. + +--- + +### GET /api/admin/learning/webdav-browse + +Browse files on the connected WebDAV/Nextcloud server. + +- **Auth required:** Yes (moderator+) +- **Query parameters:** + | Parameter | Type | Description | + |-----------|--------|------------------------------| + | `path` | string | Directory path to browse | +- **Response:** Array of file/directory entries. + +--- + +### GET /api/admin/learning/stats + +Get learning hub statistics (content counts, quiz completion rates, etc.). + +- **Auth required:** Yes (moderator+) +- **Response:** + ```json + { + "totalContent": "number", + "totalCategories": "number", + "totalQuizSubmissions": "number", + "averageScore": "number" + } + ``` + +--- + +## Admin - Users + +Requires admin role. + +### GET /api/admin/users + +List all registered users. + +- **Auth required:** Yes (admin) +- **Response:** + ```json + [ + { + "id": "number", + "name": "string", + "email": "string", + "role": "string", + "verified": "boolean", + "disabled": "boolean", + "created_at": "string (ISO 8601)" + } + ] + ``` + +--- + +### GET /api/admin/users/:id + +Get detailed information and usage statistics for a specific user. + +- **Auth required:** Yes (admin) +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|-------------| + | `id` | number | User ID | +- **Response:** + ```json + { + "id": "number", + "name": "string", + "email": "string", + "role": "string", + "verified": "boolean", + "disabled": "boolean", + "stats": { + "totalGenerations": "number", + "totalTranscriptions": "number", + "lastActive": "string (ISO 8601)" + } + } + ``` + +--- + +### POST /api/admin/users/:id/verify + +Manually verify a user's email address (bypass email verification). + +- **Auth required:** Yes (admin) +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|-------------| + | `id` | number | User ID | +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### POST /api/admin/users/:id/disable + +Disable a user account, preventing login. + +- **Auth required:** Yes (admin) +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|-------------| + | `id` | number | User ID | +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### POST /api/admin/users/:id/enable + +Re-enable a previously disabled user account. + +- **Auth required:** Yes (admin) +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|-------------| + | `id` | number | User ID | +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +## Admin - Configuration + +Requires admin role unless noted otherwise. + +### GET /api/admin/config/announcement + +Get the current announcement banner. This endpoint is public and does not require authentication. + +- **Auth required:** No +- **Response:** + ```json + { + "announcement": "string | null" + } + ``` + +--- + +### GET /api/admin/config + +Get all application configuration settings. + +- **Auth required:** Yes (admin) +- **Response:** Object containing all configuration key-value pairs. + +--- + +### POST /api/admin/config + +Update application configuration settings. + +- **Auth required:** Yes (admin) +- **Request body:** Object with configuration key-value pairs to update. +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### POST /api/admin/config/test-email + +Send a test email to verify SMTP configuration. + +- **Auth required:** Yes (admin) +- **Request body:** + ```json + { + "to": "string (email address)" + } + ``` +- **Response:** + ```json + { + "success": true, + "message": "Test email sent." + } + ``` + +--- + +### GET /api/admin/config/prompts + +Get all AI prompt templates, including any admin overrides. + +- **Auth required:** Yes (admin) +- **Response:** Object mapping prompt keys to their current values. + +--- + +### POST /api/admin/config/prompts + +Save AI prompt template overrides. + +- **Auth required:** Yes (admin) +- **Request body:** Object mapping prompt keys to new values. +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### POST /api/admin/config/prompts/:key/reset + +Reset a specific AI prompt template back to its default value. + +- **Auth required:** Yes (admin) +- **Path parameters:** + | Parameter | Type | Description | + |-----------|--------|---------------------| + | `key` | string | Prompt template key | +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### POST /api/admin/config/reset-defaults + +Reset all configuration settings to their default values. + +- **Auth required:** Yes (admin) +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### GET /api/admin/config/smtp/status + +Check the current SMTP configuration status. + +- **Auth required:** Yes (admin) +- **Response:** + ```json + { + "configured": "boolean", + "host": "string", + "port": "number" + } + ``` + +--- + +### POST /api/admin/config/smtp/update + +Update SMTP email configuration. + +- **Auth required:** Yes (admin) +- **Request body:** + ```json + { + "host": "string", + "port": "number", + "username": "string", + "password": "string", + "from": "string" + } + ``` +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### POST /api/admin/config/registration + +Update registration settings (enable/disable new registrations). + +- **Auth required:** Yes (admin) +- **Request body:** + ```json + { + "enabled": "boolean" + } + ``` +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### GET /api/admin/config/oidc + +Get the current OIDC/SSO configuration. + +- **Auth required:** Yes (admin) +- **Response:** + ```json + { + "enabled": "boolean", + "clientId": "string", + "issuerUrl": "string", + "buttonLabel": "string", + "disableLocalAuth": "boolean" + } + ``` + +--- + +### POST /api/admin/config/oidc + +Update the OIDC/SSO configuration. + +- **Auth required:** Yes (admin) +- **Request body:** + ```json + { + "enabled": "boolean", + "clientId": "string", + "clientSecret": "string", + "issuerUrl": "string", + "buttonLabel": "string", + "disableLocalAuth": "boolean" + } + ``` +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### GET /api/admin/config/models + +List all AI models with their enabled/disabled status. + +- **Auth required:** Yes (admin) +- **Response:** + ```json + [ + { + "id": "string", + "name": "string", + "enabled": "boolean", + "isDefault": "boolean", + "category": "string" + } + ] + ``` + +--- + +### PUT /api/admin/config/models/toggle + +Enable or disable a specific AI model. + +- **Auth required:** Yes (admin) +- **Request body:** + ```json + { + "modelId": "string", + "enabled": "boolean" + } + ``` +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### PUT /api/admin/config/models/default + +Set the default AI model for all users. + +- **Auth required:** Yes (admin) +- **Request body:** + ```json + { + "modelId": "string" + } + ``` +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### POST /api/admin/config/models/custom + +Add a custom AI model definition. + +- **Auth required:** Yes (admin) +- **Request body:** + ```json + { + "id": "string", + "name": "string", + "category": "string" + } + ``` +- **Response:** + ```json + { + "success": true + } + ``` + +--- + +### GET /api/admin/config/models/discover + +Discover available models from the configured AI provider. + +- **Auth required:** Yes (admin) +- **Response:** + ```json + { + "models": [ + { + "id": "string", + "name": "string" + } + ] + } + ``` + +--- + +## Logs + +Requires admin role. + +### GET /api/logs/usage + +Get a 30-day usage summary broken down by model and endpoint. + +- **Auth required:** Yes (admin) +- **Response:** + ```json + { + "byModel": [ + { + "model": "string", + "count": "number", + "tokens": "number" + } + ], + "byEndpoint": [ + { + "endpoint": "string", + "count": "number" + } + ] + } + ``` + +--- + +### GET /api/logs/audit + +Get the audit log of user actions (login, settings changes, etc.). + +- **Auth required:** Yes (admin) +- **Response:** Array of audit log entries with timestamps, user IDs, and action descriptions. + +--- + +### GET /api/logs/api + +Get API call metrics (request counts, latency, error rates). + +- **Auth required:** Yes (admin) +- **Response:** Array of API metric entries. + +--- + +### GET /api/logs/access + +Get user access history (login times, IP addresses). + +- **Auth required:** Yes (admin) +- **Response:** Array of access log entries. + +--- + +## Milestones (Admin) + +Developmental milestones management. Requires admin role. + +### GET /api/admin/milestones + +List all developmental milestones in the database. + +- **Auth required:** Yes (admin) +- **Response:** Array of milestone objects organized by age group and domain. + +--- + +### POST /api/admin/milestones/seed + +Seed the database with the default set of developmental milestones. + +- **Auth required:** Yes (admin) +- **Request body:** None +- **Response:** + ```json + { + "success": true, + "count": "number" + } + ``` + +--- + +## Health + +### GET /api/health + +Application health check endpoint. + +- **Auth required:** No +- **Response:** + ```json + { + "status": "ok", + "provider": "string", + "uptime": "number (seconds)" + } + ``` diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..56df15e --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,172 @@ +# 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 `
` placeholder and loads 20+ JS modules via ` @@ -50,6 +51,7 @@
+
+
+