# 🩺 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:@postgres:5432/pedquiz SECRET_KEY= # Redis REDIS_URL=redis://redis:6379/0 # LLM (for question extraction) LITELLM_MODEL=openai/claude-haiku-4.5 LITELLM_API_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= # AWS (for Polly TTS + Bedrock embedding fallback) AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_REGION=us-east-1 AWS_BEDROCK_REGION=us-east-1 # ElevenLabs TTS (optional) ELEVENLABS_API_KEY= # Google Cloud TTS (optional) GOOGLE_TTS_API_KEY= # Email MAIL_SERVER=mail.smtp2go.com MAIL_PORT=587 MAIL_USERNAME= MAIL_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 1–767 # Doc 4: prep-PREP2013.pdf (ready, 227 pages) # Section 7: 'Questions 1-100' pages 1–100 # 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 1–767) 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/` | `ELEVENLABS_API_KEY` | | Google Cloud | `google/` | `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