pdf-quiz-generator/backend/.env.example
Daniel febb14490c
Some checks failed
Tests / backend (push) Failing after 15s
Tests / frontend (push) Successful in 28s
Tests / e2e (push) Failing after 26s
feat: ground AI drafts in the library and PubMed, and mend the card system
**Two sources an AI draft can draw on**, both off until an administrator turns
them on, both appended to the prompt as extra material rather than woven into
it — so a draft with nothing to draw on is byte-for-byte the draft that has
been working well.

- *The clinical library.* The indexed shelf the clinical assistant already
  searches, over MCP on the internal network. Ported from ped-ai: sessions are
  reused, a dead one is reopened once, and a library that cannot be reached
  never fails the article — it just means the educator is writing without it,
  and the progress line says so.
- *PubMed.* NCBI's E-utilities, no key required. Ported whole, including the
  two lessons that cost somebody an afternoon over there: PubMed ANDs every
  term, so "bronchiolitis management in infants" can find nothing where
  "bronchiolitis management" finds six — hence the query ladder — and three
  esearch calls in a row will trip the rate limit, hence the spacing. The
  reference list is written from the records rather than by the model, so every
  line is a paper that exists with a PMID somebody can look up.

Measured on the live stack: 24 excerpts, 6 papers, 6 references, 6 in-text
citations, in one draft.

**The card system, which turned out to be half-built:**

- There was no way to make a deck by hand, and no way to edit a card at all —
  you could browse, view and delete. Both are there now, the editor taking
  front, back and a picture.
- Filing, writing, sharing and deleting are all educator work now, behind one
  named gate rather than four scattered checks. A learner studies.
- A deck generated from an article inherits that article's category instead of
  landing in Uncategorized for somebody to file by hand.
- A link inside a card previewed instead of going. A card is a box a few lines
  tall, often inside a flipping panel, and a hover card anchored in one is
  clipped by it — so the link read as broken because clicking it did nothing.
  Where there is no room to preview, the honest behaviour is to take you there.

**An AI draft belonged to no editorial queue.** Nothing set `generated_by`, so
a drafted article was neither "generated, unread" nor anything else: the tile
counted it and there was nowhere to click. Drafts are stamped with the model
that wrote them, and there is now a plain Drafts queue that cannot be fallen
through.

**The sign-in code email** is laid out rather than written: the code is the
biggest thing on the screen, then which account it signs into, then a way back
to the page, then permission to ignore the whole thing.

Also: a back link out of a deck, in the same words as the rest of the app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
2026-09-13 02:44:42 +02:00

115 lines
5.4 KiB
Text

# Every setting the backend reads, with the default it falls back to. Blank
# means "off" or "not configured" throughout — nothing here has a secret in it,
# and nothing here is required except the database, the secret key and a model.
# ── Database and sessions ──────────────────────────────────────────────
DATABASE_URL=postgresql://quiz:quiz@postgres:5432/quiz
SECRET_KEY=change-me-to-a-random-secret-key-in-production
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=1440
# The first admin, created only when the database has none. Leave the email
# blank too and the first person to register becomes the admin instead.
# Set the email and leave the password blank, and one is generated and printed
# to the log at first start: docker compose logs backend | grep -A3 "FIRST ADMIN"
DEFAULT_ADMIN_EMAIL=
DEFAULT_ADMIN_PASSWORD=
# Redis: Celery's broker, the rate limiters, job progress and site settings.
REDIS_URL=redis://redis:6379/0
# ── Models ─────────────────────────────────────────────────────────────
# Everything goes through one LiteLLM proxy. Per-job models (extraction, the
# tutor, TTS, and so on) are chosen by an administrator in Settings → AI
# models; this is the fallback when a job has no model of its own.
LITELLM_MODEL=gpt-4o-mini
LITELLM_API_KEY=your-api-key-here
LITELLM_API_BASE=
# The embedding model, and the one model setting that is NOT editable in the
# interface. Every stored vector was produced by it, and vectors from different
# models are not comparable — change this and search returns noise until every
# question, article and card has been re-embedded. That is a deployment, so it
# lives here. `EMBEDDING_DIMENSIONS` must match what the model returns.
LITELLM_EMBEDDING_MODEL=
EMBEDDING_DIMENSIONS=1024
# The cross-encoder that reorders search results. Unset, unreachable or
# malformed and the fused ranking is returned untouched — never fewer results.
LITELLM_RERANK_MODEL=cohere-rerank-v4.0-pro
# Direct provider keys, used only where the proxy does not carry the service.
OPENAI_API_KEY=
ELEVENLABS_API_KEY=
GOOGLE_TTS_API_KEY=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=us-east-1
AWS_BEDROCK_REGION=us-east-1
# Self-hosted speech, for TTS and dictation without leaving the machine.
LOCAL_SPEECH_GATEWAY_URL=http://local-speech-gateway:8110
# ── Where the site lives ───────────────────────────────────────────────
# Used in the links inside emails, so a wrong value sends people nowhere.
APP_URL=https://pedshub.com
LOG_LEVEL=INFO
# ── Storage ────────────────────────────────────────────────────────────
# `local` keeps uploads on the volume at UPLOAD_DIR; `s3` puts them in a
# bucket and reads them back through it. Thumbnails live beside the original
# either way.
STORAGE_BACKEND=local
UPLOAD_DIR=/app/uploads
MAX_UPLOAD_SIZE=524288000
S3_ENDPOINT_URL=http://minio:9000
S3_ACCESS_KEY=
S3_SECRET_KEY=
S3_BUCKET=pedshub-media
S3_REGION=us-east-1
# Page chunks for extraction context.
CHROMA_PERSIST_DIR=/app/chroma_data
# ── Email ──────────────────────────────────────────────────────────────
# With MAIL_USERNAME or MAIL_FROM blank, mail is logged instead of sent — which
# also means sign-in codes and verification links go nowhere.
MAIL_USERNAME=your-email@example.com
MAIL_PASSWORD=your-app-password
MAIL_FROM=your-email@example.com
MAIL_PORT=587
MAIL_SERVER=smtp.gmail.com
MAIL_STARTTLS=true
MAIL_SSL_TLS=false
# Where the contact form's messages land.
ADMIN_EMAIL=
# ── Bot protection ─────────────────────────────────────────────────────
# Cap, self-hosted beside the app. Leave the secret blank to disable the
# challenge; sign-up then accepts submissions without one.
CAP_SECRET_KEY=
CAP_SITE_KEY=
CAP_API_URL=http://cap:3000
# ── Single sign-on ─────────────────────────────────────────────────────
# Any OIDC provider. Blank means the site uses its own accounts only.
OIDC_PROVIDER_URL=
OIDC_CLIENT_ID=
OIDC_CLIENT_SECRET=
OIDC_SCOPES=openid email profile
OIDC_PROVIDER_NAME=SSO
# ── Live sessions ──────────────────────────────────────────────────────
BBB_SERVER_URL=
BBB_SECRET=
# ── Clinical corpus (read-only) ────────────────────────────────────────
# A separate Milvus collection the assistant may query but never write to.
CLINICAL_MILVUS_URI=
CLINICAL_MILVUS_TOKEN=
CLINICAL_MILVUS_COLLECTION=mcp_bge_m3_1024
# The indexed clinical library an AI draft can be grounded in, if this
# deployment has one. Reached over MCP on an internal network; the search never
# leaves it. Blank means the feature is simply not offered.
CLINICAL_MCP_URL=