pdf-quiz-generator/README.md
Daniel d07be64f59 Fix two-phase extraction boundary detection; README CLI docs; scroll fix
Two-phase extraction improvements:
- Auto-detect answer section boundary by scanning in 10-page steps for
  'Preferred Response:' — finds exact page where questions end and answers begin
  (PREP 2013 answers start at page ~68, not at the end of the file)
- Restrict Phase 1 question chunks to pages BEFORE the answer section
- Extract answer key from answer section in CHUNKS (50 pages each) to handle
  large answer sections — accumulates all item→letter mappings
- Previous version used last 40% which missed items 1-~135 for PREP 2013

README: full CLI extraction documentation:
- list-sections: find document and section IDs
- extract <section_id> [--bg] [--title] [--mode] [--user]
- jobs / jobs --user <email>
- Explanation of auto-format detection (inline vs separate answer key)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 03:36:12 +02:00

215 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🩺 PedQuiz
AI-powered pediatric knowledge quiz platform. Upload PDF study materials, automatically extract MCQ questions with AI, and take quizzes with text-to-speech support and semantic search.
## Features
- **PDF → Quiz**: Upload PREP PDFs, AI extracts questions, answers, and explanations
- **Quiz Modes**: Study (instant feedback) and Exam (timed, scored)
- **Text-to-Speech**: OpenAI TTS, AWS Polly, ElevenLabs — voice selection per quiz
- **Semantic Search**: pgvector + AWS Titan Embed — finds questions by meaning, not just keywords
- **Email Verification**: Required before first login; password reset via email
- **Role system**: Admin / Moderator / User
- **Nextcloud Integration**: Browse and import PDFs from your Nextcloud in Upload page
- **Themes**: Default and Markdown (GitHub-style)
## Stack
| Layer | Tech |
|---|---|
| Frontend | React + React Router, plain CSS, Nginx |
| Backend | FastAPI, SQLAlchemy, PostgreSQL 16 + pgvector |
| AI/LLM | LiteLLM proxy (Claude, Gemini, GPT, Bedrock) |
| Embeddings | AWS Bedrock Titan Embed V2 via LiteLLM proxy |
| Document vectors | ChromaDB |
| TTS | OpenAI, AWS Polly, ElevenLabs |
| Queue | Celery + Redis |
| Email | SMTP (smtp2go) |
## Quick Start
```bash
git clone https://github.com/ifedan-ed/pdf-quiz-generator.git
cd pdf-quiz-generator
# Configure environment
cp backend/.env.example backend/.env # edit with your keys
docker compose up -d
```
Frontend available at `http://localhost:8081`. The first registered user becomes admin automatically.
## Environment Variables
Create `backend/.env`:
```env
# Database
DATABASE_URL=postgresql://pedquiz:<password>@postgres:5432/pedquiz
SECRET_KEY=<random-32-char-string>
# Redis
REDIS_URL=redis://redis:6379/0
# LLM (for question extraction)
LITELLM_MODEL=openai/claude-haiku-4.5
LITELLM_API_KEY=<your-litellm-or-openai-key>
LITELLM_API_BASE=https://your-litellm-proxy.com # or leave empty for direct OpenAI
LITELLM_EMBEDDING_MODEL=openai/titan-embed-v2
# OpenAI (for TTS — uses api.openai.com directly, not proxy)
OPENAI_API_KEY=<openai-api-key>
# AWS (for Polly TTS + Bedrock embedding fallback)
AWS_ACCESS_KEY_ID=<key>
AWS_SECRET_ACCESS_KEY=<secret>
AWS_REGION=us-east-1
AWS_BEDROCK_REGION=us-east-1
# ElevenLabs TTS (optional)
ELEVENLABS_API_KEY=<key>
# Google Cloud TTS (optional)
GOOGLE_TTS_API_KEY=<key>
# Email
MAIL_SERVER=mail.smtp2go.com
MAIL_PORT=587
MAIL_USERNAME=<smtp2go-username>
MAIL_PASSWORD=<smtp2go-password>
MAIL_FROM=noreply@yourdomain.com
MAIL_STARTTLS=true
# App
APP_URL=https://your-domain.com
UPLOAD_DIR=/app/uploads
MAX_UPLOAD_SIZE=524288000
CHROMA_PERSIST_DIR=/app/chroma_data
```
## CLI Management
All commands run inside the backend container:
```bash
# ── User management ──────────────────────────────────────────────────────────
# Reset a locked-out admin password
docker compose exec backend python manage.py reset-password admin@example.com NewPassword123
# List all users with email verification status
docker compose exec backend python manage.py list-users
# ── Quiz extraction ───────────────────────────────────────────────────────────
# 1. Find your document ID and section ID
docker compose exec backend python manage.py list-sections
# Output example:
# Doc 3: prep-PREP2012.pdf (ready, 767 pages)
# Section 6: 'ALL' pages 1767
# Doc 4: prep-PREP2013.pdf (ready, 227 pages)
# Section 7: 'Questions 1-100' pages 1100
# 2a. Extract in background (Celery) — returns immediately, monitor via navbar badge
docker compose exec backend python manage.py extract 6 --bg
docker compose exec backend python manage.py extract 6 --bg --title "PREP 2012 Full" --mode timed
# 2b. Extract inline (blocking) — shows live output in terminal
docker compose exec backend python manage.py extract 6
# 3. Check job status (shows progress, skipped questions, errors)
docker compose exec backend python manage.py jobs
docker compose exec backend python manage.py jobs --user admin@example.com
# CLI extract options:
# --title "My Quiz" Custom quiz title (default: auto-generated from section name)
# --mode timed|learning Quiz mode (default: timed)
# --user email Which user owns the quiz (default: first admin)
# --bg Run in background via Celery
# ── Embeddings ───────────────────────────────────────────────────────────────
# Regenerate all question embeddings (e.g. after switching embedding model)
docker compose exec backend python manage.py reembed
```
### How extraction works
1. **Upload PDF** via the web UI (Upload PDF page) — the system extracts text and stores it
2. **Create a section** on the document page (define page range, e.g. pages 1767)
3. **Extract quiz** — either from the web UI or CLI:
- The system auto-detects the PDF format:
- **Inline answers** (PREP 2012): "Correct Answer: X" after each question → standard extraction
- **Separate answer key** (PREP 2013): "Preferred Response: X" in a dedicated answer section → two-phase extraction (questions first, then answer key, then matched)
- Large sections are split into 50-page chunks automatically
- Progress shown live in the web UI extraction panel
4. Questions land in the **Question Bank** and can be assigned to categories
## Architecture
```
Browser
Nginx (frontend + API proxy)
├─► React SPA (static)
└─► FastAPI backend
├─ PostgreSQL (pgvector) ← users, quizzes, questions + embeddings
├─ ChromaDB ← document page chunks for quiz generation
├─ Redis ← Celery task queue
├─ Celery workers ← background PDF processing, emails
├─ LiteLLM proxy ← Claude/Gemini/GPT for extraction + embeddings
├─ AWS Bedrock ← Polly TTS, Titan embed fallback
└─ OpenAI ← TTS (direct, not via proxy)
```
### Search
Quiz search uses **hybrid retrieval**:
1. **Semantic** — embed the query with Titan Embed V2, cosine similarity against all questions via pgvector HNSW index
2. **Keyword** — PostgreSQL `ILIKE` on question text and options
3. Results merged and ranked — semantic matches shown first by score, keyword-only matches appended
### TTS Providers
| Provider | Model IDs | Key Needed |
|---|---|---|
| OpenAI | `tts-1:alloy`, `tts-1:nova`, `tts-1:echo`, `tts-1:shimmer`, `tts-1:onyx`, `tts-1:fable`, `tts-1-hd:*` | `OPENAI_API_KEY` |
| AWS Polly | `polly/Joanna`, `polly/Matthew`, `polly/Amy`, `polly/Brian` | `AWS_ACCESS_KEY_ID` + `polly:SynthesizeSpeech` IAM permission |
| ElevenLabs | `elevenlabs/<voice-id>` | `ELEVENLABS_API_KEY` |
| Google Cloud | `google/<voice-name>` | `GOOGLE_TTS_API_KEY` |
## Project Structure
```
├── backend/
│ ├── app/
│ │ ├── main.py # App startup, seeding, pgvector setup
│ │ ├── config.py # Settings (pydantic-settings)
│ │ ├── models/ # SQLAlchemy models
│ │ ├── routers/ # FastAPI route handlers
│ │ ├── services/
│ │ │ ├── ai_service.py # LLM extraction + TTS
│ │ │ ├── embedding_service.py # pgvector embeddings
│ │ │ ├── vector_service.py # ChromaDB (document pages)
│ │ │ ├── quiz_service.py # Quiz creation pipeline
│ │ │ └── email_service.py # Email templates + sending
│ │ └── utils/
│ ├── manage.py # CLI: reset-password, list-users, reembed
│ ├── requirements.txt
│ └── Dockerfile
├── frontend/
│ ├── src/
│ │ ├── pages/ # Dashboard, Quiz, Results, Settings, Search …
│ │ ├── components/ # Navbar, LineChart
│ │ └── context/ # AuthContext, ThemeContext
│ ├── nginx.conf
│ └── Dockerfile
└── docker-compose.yml
```
## Deployment Notes
- The frontend Nginx only binds to `127.0.0.1:8081` — put a reverse proxy (Caddy/Nginx) in front for HTTPS
- PostgreSQL data is persisted in the `postgres_data` Docker volume — back it up regularly
- Uploads live in `uploads_data` volume — includes extracted question images
- Set `APP_URL` to your public domain so verification/reset email links work