pediatric-ai-scribe-v3/docs/configuration.md
Daniel fac8757ce8
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 56s
Forgejo Docker Build / Root app tests (push) Successful in 48s
Forgejo Android APK / Build signed APK (push) Successful in 2m13s
Forgejo Docker Build / Build Docker image (push) Successful in 14s
Forgejo Docker Build / Deploy to the host (push) Failing after 1s
feat: My Resources has a menu, a library and three downloads
The pathway existed but was reachable only by API. It now has a tab of its own
next to the Learning Hub — related, not the same thing, and sitting together is
how someone discovers the difference — visible to every signed-in user with no
role gate in the markup.

Generate a deck or an article, see everything you have made, download each as
PowerPoint, Word or PDF, delete what you no longer want. The screen says
"Private to you" and "Nobody else sees these", because the distinction from
published Learning content is the thing a person needs to understand before
typing a patient's condition into it.

Downloads are fetched rather than linked: an <a href> cannot carry the
Authorization header. The blob is saved under the filename the server chose and
the object URL is revoked afterwards. Resource titles come from a model, so rows
are built as elements and a title is only ever assigned to textContent.

The e2e stack now joins danvics_convert too. It could previously reach only
Postgres and Redis, so a PDF download failed there in a way production would
not — which did at least prove the degradation path works: with Gotenberg
unreachable the response is "PDF conversion is unavailable right now. PowerPoint
and Word still work", and the other two formats download unaffected.

Verified in a browser as an ordinary user: the tab appears and opens, the form
swaps slide count for word count when the format changes, the library lists
their own work, and pptx, docx and pdf all download with sensible filenames
(36360, 13285 and 68310 bytes).

Also documents retrieval sizing in docs/retrieval-tuning.md — the per-feature
budgets, and RERANKER_TOP_K, which caps all of them and had until now appeared
in no configuration file at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-11 15:00:19 +02:00

8.6 KiB

Configuration

Runtime configuration sources, in override order (later wins for overlapping keys):

  1. .env file / container environment variables (startup only)
  2. app_settings table (live, editable from Admin Panel with 2-minute cache)

Environment variables

Core (production-required)

Variable Purpose
APP_URL Public base URL. Enables production mode — fail-closed CORS, HSTS, secure cookies.
JWT_SECRET HMAC key for JWT signing and OIDC state. Server refuses to start without it in production.
DATA_ENCRYPTION_KEY AES-256-GCM key for PHI at rest (Nextcloud tokens, audio backups). 64 hex chars (openssl rand -hex 32). Refuses to start without it in production.
AUDIO_BACKUPS_S3_ENDPOINT, _BUCKET, _REGION Optional. Keeps the 24-hour recording copies in object storage instead of the audio_backups column; metadata stays in Postgres either way. See docs/speech.md.
AUDIO_BACKUPS_S3_ACCESS_KEY_FILE, _SECRET_KEY_FILE Credentials for the above, read from files so they never appear in the process environment. AUDIO_BACKUPS_S3_ACCESS_KEY / _SECRET_KEY are the inline fallback.
DB_PASSWORD / DATABASE_URL Postgres password or full connection string.
PORT HTTP listen port (default 3000).
NODE_ENV production forces prod-only guards on even without APP_URL.

CORS

Variable Purpose
CORS_ORIGINS Comma-separated additional allowed origins beyond APP_URL.

AI provider

Variable Purpose
AI_PROVIDER openrouter / bedrock / azure / vertex / litellm. If unset, the startup loader uses configured credentials and the last initialized provider in Bedrock → Azure → Vertex → LiteLLM order wins; otherwise OpenRouter is the default.
OPENROUTER_API_KEY OpenRouter key (not HIPAA-eligible).
AWS_BEDROCK_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY Bedrock chat provider.
AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_DEPLOYMENT_NAME, AZURE_OPENAI_API_VERSION Azure OpenAI.
GOOGLE_VERTEX_PROJECT, GOOGLE_VERTEX_LOCATION, GOOGLE_APPLICATION_CREDENTIALS Vertex AI chat provider.
LITELLM_API_BASE, LITELLM_API_KEY OpenAI-compatible AI gateway (Bifrost, LiteLLM, or similar).

Speech-to-text

Variable Purpose
TRANSCRIBE_PROVIDER Use litellm; auto mode uses LiteLLM when configured.
LITELLM_STT_MODEL Model name for LiteLLM-routed STT.

Text-to-speech

Variable Purpose
LITELLM_TTS_MODEL, LITELLM_TTS_VOICE LiteLLM-routed TTS model and default voice.
LITELLM_TTS_VOICES Comma-separated LiteLLM-compatible voices exposed in voice search and user preferences.

Embeddings

Variable Purpose
EMBEDDING_MODEL LiteLLM embedding model name (default openai-text-embedding-3-large).
EMBEDDING_DIMENSIONS Vector dimensions (default 3072).

Email (SMTP)

Variable Purpose
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM SMTP config for verification + password reset emails. Overridable per-instance via app_settings.

Security / external

Variable Purpose
TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY Cloudflare Turnstile. Turnstile check is no-op when secret is unset.
LOKI_URL Optional Loki ingest URL for shipping audit/api/access logs.
NTFY_URL, NTFY_TOPIC Optional ntfy push for new-login / password-change notifications.

Integrations

Variable Purpose
NEXTCLOUD_URL Nextcloud base URL (per-user credentials entered in app).
S3_BUCKET, S3_REGION, S3_PREFIX, S3_ENDPOINT, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE Document object storage. S3_FORCE_PATH_STYLE=true for MinIO, Backblaze B2, most non-AWS providers.

app_settings — live runtime configuration

Key-value rows in the app_settings table. Read via config.get(key, default) with 2-minute in-memory cache. Writes invalidate the cache immediately.

Registration & site

Key Purpose
registration_enabled true/false. Gate new signups.
site.name Display name.
site.auto_delete_days Days before encounters auto-expire (default 7).

Announcements

Key Purpose
announcement.text Banner text. Empty = banner hidden.
announcement.type info / warning / error / success.

SMTP overrides (override env)

smtp.host, smtp.port, smtp.user, smtp.pass, smtp.from.

Email templates

email.{flow}.subject, email.{flow}.body where {flow} is verify / reset / new_login / password_changed.

OIDC / SSO

Key Purpose
oidc.enabled Toggle SSO.
oidc.issuer OIDC issuer URL.
oidc.client_id, oidc.client_secret OAuth client credentials.
oidc.button_label Login-page button text (default "Sign in with SSO").
oidc.disable_local_auth Hide local login form when SSO is enabled.
oidc.allowed_ips CIDR whitelist for SSO (optional).

AI / models / prompts

Key Purpose
models.default Default model ID.
models.disabled JSON array of disabled model IDs.
models.custom JSON array of admin-added models.
ai.allow_model_fallback Enable silent fallback to secondary model on primary failure. Default false — fallback could spill to a non-BAA provider.
stt.model, tts.model, tts.voice System-wide STT/TTS defaults (users can override per-account).
prompt.{name} Prompt overrides. Any template in src/utils/prompts.js can be replaced live.
embeddings.model, embeddings.dimensions Override embedding config.

Feature flags

feature.* — any key matching this prefix can be consulted via config.get('feature.foo').

Internal migration flags

Key Purpose
migration.text_indexes_c Set to 'true' once lookup-critical text indexes have been converted to COLLATE "C". Prevents re-running.

Admin panel

The Admin Panel (/admin route, admin-only) exposes everything above plus:

  • User list: verify, disable, delete, promote to admin/moderator.
  • Session viewer: active sessions per user, admin-revoke.
  • Logs: audit / api / access tables with filtering.
  • Detailed health: /api/health/detailed reports configured providers (admin-only; the public /api/health returns only {ok: true} to avoid leaking stack info).
  • Model management: enable/disable, add custom, set default, discover from provider.
  • Prompt editor: live-edit any PROMPTS.* key.
  • Test SMTP / test STT / test TTS.

Switching AI gateways

The LITELLM_API_BASE and LITELLM_API_KEY variables work with any OpenAI-compatible gateway — LiteLLM, Bifrost, or other proxies.

Migration steps

  1. Set the base URLLITELLM_API_BASE should include /v1 if the gateway serves on that path (e.g., https://gateway.example.com/v1). The application normalizes double /v1 paths internally for TTS, STT, and embedding endpoints.

  2. Set the API keyLITELLM_API_KEY accepts any key format the gateway issues (virtual keys, bearer tokens, etc.).

  3. Update model names — Different gateways use different naming conventions. Bifrost requires provider/model format (e.g., openrouter/gpt-4.1), while LiteLLM can use deployment aliases (e.g., openrouter-gpt-4.1). Update model names in:

    • Admin Panel → Models (chat models)
    • Admin Panel → Settings → stt.model (speech-to-text)
    • Admin Panel → Settings → tts.model (text-to-speech)
    • LITELLM_TTS_MODEL env var (if set)
  4. Embedding model — Set via Admin Panel → Settings → embeddings.model. The embedding vector column is VECTOR(768), so any model producing 768 dimensions works without re-embedding (e.g., vertex/text-embedding-005). Switching to a model with different dimensions requires altering the column and re-embedding all content.

  5. Restart the containerdocker compose up -d --force-recreate to pick up .env changes (a plain restart does not re-read .env).

Verified gateways

Gateway Model format Notes
Bifrost provider/model Virtual keys, semantic caching, MCP gateway
LiteLLM Custom aliases Requires PostgreSQL + Redis
Any OpenAI-compatible Varies Must serve /v1/chat/completions, /v1/audio/speech, /v1/audio/transcriptions, /v1/embeddings

Retrieval sizing

How many corpus excerpts the Clinical Assistant, the Learning Hub and My Resources each receive, and the reranker cap that overrides all three: retrieval-tuning.md.