# Learning Hub A CMS + content-delivery module for clinical education material inside the app. Supports articles, clinical pearls, quizzes, and Marp-rendered presentations with PPTX export. Quiz questions are stored alongside article content and can optionally be generated by AI from uploaded source material. ## Content types | Type | Description | |---|---| | `article` | Rich HTML body with an optional attached quiz | | `pearl` | Short clinical snippet (no quiz, no heavy media) | | `quiz` | Standalone quiz (no article body) | | `presentation` | Marp markdown rendered as slides; PPTX export supported | ## User-facing features - Browse by category. - Three search modes: - **Keyword** — Postgres full-text. - **Semantic** — pgvector cosine similarity on the embedding column. - **Hybrid** — weighted merge of both result sets. - Articles render with sanitized HTML (DOMPurify, loaded via SRI-pinned cdnjs). - Quizzes: multiple-choice, multi-select, true/false. Score computed on submit, per-question explanations revealed after. - Presentation viewer: modal with keyboard / swipe navigation. - Progress: `learning_progress` stores per-attempt score + total. ## CMS (moderator / admin) - Tiptap rich-text editor for article body. - Draft / published toggle. - Category assignment. - Quiz builder: add/remove questions, add/remove options, mark correct, enter explanation. - Marp editor for presentations with live preview. ## AI content generation `POST /api/admin/learning/generate` takes one of: | Input | Notes | |---|---| | `topic` | Plain-text description of the topic | | Uploaded files | PDF / TXT / MD / HTML / CSV / JSON, ≤ 100 MB each, max 10 files | | WebDAV path | Pulled from the user's connected Nextcloud instance | Parameters: `model` (from the provider whitelist), `slideCount` for presentations, `wordCount` for articles. File uploads pass the `src/utils/fileType.js` magic-byte check so a mismatched extension is rejected before it reaches the parser. ## Marp → PPTX export `POST /api/admin/learning/generate-pptx` writes the markdown to a temp directory and runs `pandoc` against `assets/learning/slides-reference.pptx`. The reference deck carries the fonts, palette and slide layouts, so restyling the export means editing that file in PowerPoint — not changing code. Images are handled before pandoc sees the markdown. Each `/api/generated-images/{id}` link is resolved through the ownership check and written beside the deck under a name this route chooses; any link that does not resolve to one of those is dropped. Pandoc resolves image links against the filesystem, so passing an arbitrary local path through would embed that file into the deck. This is the Learning Hub's own path and is separate from My Resources, which renders decks with python-pptx from a typed deck rather than from markdown — see [`my-resources.md`](my-resources.md). ## Semantic search | | | |---|---| | Store | `pgvector` on `learning_content.embedding VECTOR(768)` | | Index | IVFFLAT, cosine distance | | Primary model | `vertex/text-embedding-005` (768 dims), served through LiteLLM | | Fallback model | OpenAI `text-embedding-3-small` (truncated to 768 to match the column) | Embeddings are generated on content publish + on every edit. If the embedding provider is unreachable, the content still saves — keyword search remains available. ## Tables | Table | Purpose | |---|---| | `learning_categories` | Top-level groupings | | `learning_content` | Articles / pearls / quizzes / presentations. Body + `embedding` vector. | | `learning_questions` | Quiz question prompts (FK to content) | | `learning_options` | Answer options (FK to question) | | `learning_progress` | Per-user attempt history | ## 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](retrieval-tuning.md).