# 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

Uses `pptxgenjs`.

- 16:9 widescreen.
- Bottom-right slide numbers.
- Supported Markdown elements: headings, sub-headings, bold, italic, inline
  code, numbered + bulleted lists, code blocks (grey background), blockquotes
  (blue accent bar), tables with alternating rows.
- Mixed content per slide allowed.

## Semantic search

| | |
|---|---|
| Store | `pgvector` on `learning_content.embedding VECTOR(768)` |
| Index | IVFFLAT, cosine distance |
| Primary model | Google Vertex `text-embedding-005` (768 dims) |
| 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 |
