Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 47s
Forgejo Docker Build / Root app tests (push) Successful in 48s
Forgejo Android APK / Build signed APK (push) Successful in 2m38s
Forgejo Docker Build / Build Docker image (push) Successful in 12s
Forgejo Docker Build / Deploy to the host (push) Failing after 2s
Three pairs of docs described the same thing twice, and the copies had drifted
apart. Merged each into one file, keeping the unique content from both:
- ARCHITECTURE.md -> architecture.md (its operational map: ownership, request
flow, runtime boundaries, source of truth, deployment shape)
- DEVELOPMENT.md -> developer-guide.md (change workflow, Clinical Assistant
high-risk areas, frontend rendering rules, deployment checks)
- transcription-options.md -> speech.md (the clinic setup table, and the list
of browser-Whisper paths that must stay removed)
Then audited what remained against the code and the live database rather than
against the previous docs. Corrected:
- Google Vertex was still documented as a provider across nine files. The SDK
is gone; AI_PROVIDER=vertex now logs an advisory and falls back to
OpenRouter, and Gemini is reached through LiteLLM. Fixed the provider
selection order to match src/utils/ai.js, which starts from LITELLM_API_BASE.
- promptSafe was documented on 8 routes; it is on 13.
- Node 20 -> 24, "24 vanilla JS modules" -> no fixed count, and
transcribe.js/tts.js -> sttProvider.js/ttsProvider.js, which is what exists.
- STT/TTS are LiteLLM-only; README listed direct Google, AWS Transcribe and
ElevenLabs paths that are not in the runtime.
- Learning Hub PPTX export was documented as pptxgenjs, which is not a
dependency. It is pandoc against a reference deck.
- POST /api/admin/milestones/seed does not exist; it is /bulk-import.
- NEXTCLOUD_URL and NTFY_TOPIC are not read anywhere. Nextcloud is per-user in
the users table, and the ntfy topic is derived as pedscribe-{userId}.
- A prose paragraph sat inside the Clinical Assistant settings table, so half
the rows rendered as text.
Filled the gaps the audit exposed:
- database.md was missing 12 of 29 tables, including user_resources,
personal_notes, login_codes, registration_invites and generated_image_jobs.
- developer-guide.md was missing 11 routers and 10 frontend modules.
- api-reference.md detailed 121 of 244 endpoints and said so, but whole
features were absent. Added an endpoint index covering Clinical Assistant,
My Resources, Notes, Diagrams, ED Encounters, invites and sign-in codes.
- configuration.md was missing METRICS_TOKEN, REDIS_URL, API_RATE_LIMIT_MAX,
the LITELLM_* model variables, the DB_* ones maintenance.js reads, and the
per-purpose S3 resolution scheme.
- clinical-assistant.md documented 2 of its 17 environment variables.
- features-explained.md had no entry for My Resources or Clinical Assistant.
Renamed the three remaining SHOUTING filenames to kebab-case, which is what the
docs viewer's prettyName() was working around, and rewrote README's index,
which listed architecture.md twice and omitted nine files.
Noted but not changed: the Turnstile site key is hardcoded in index.html rather
than read from TURNSTILE_SITE_KEY, and /api/health/detailed can report
tts: 'elevenlabs' though no ElevenLabs path exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
101 lines
3.9 KiB
Markdown
101 lines
3.9 KiB
Markdown
# 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).
|