From 025290d64a7e0f3830c286bdb7914ebf3e3c2a06 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 12 Sep 2026 20:14:20 +0200 Subject: [PATCH] feat: retire Learning Hub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My Resources generates better slides than Learning Hub ever did — a typed deck the model fills in, rendered by python-pptx with fit-to-slide text, figures, a vision review and themes, against Learning Hub's markdown-through-pandoc — and the articles and quizzes now live in the quiz app. Keeping a second, weaker generator and a whole CMS beside it was not earning its maintenance. Removed: three routers, the Learning Hub and Content Manager tabs, their components and frontend modules, the five database tables, the WebDAV browser, the content embedding column and its vector index. Content was exported first — every article as markdown plus a full SQL dump of all five tables — to ops-backups/learning-hub-export-*. That export is the restore path; the migration's down() can recreate the shape but never the rows, and says so. Two things this simplifies rather than merely deletes: generated_image_links existed only to record which published content an image appeared in, and it was the sole reason a generated image could be read by someone who did not make it. Images are now owner-only — the visibility rule is one WHERE clause instead of a join across two tables and a published flag. embeddings.js keeps the model discovery the admin panel uses and loses searchSimilar and generateContentEmbedding, which queried a table that no longer exists. Kept deliberately: Nextcloud connect, disconnect and export, which are how a generated note reaches a real filesystem and have nothing to do with Learning Hub; learningRetrieval, which despite its name is the clinical corpus search My Resources depends on; and the pandoc reference deck, still the fallback when the python renderer fails, moved from assets/learning to assets/deck now that the old name misleads. Tests: four Learning-Hub-only files removed, and the individual cases inside shared files that asserted its behaviour. Where a test used a Learning endpoint only as a convenient example — the account-boundary token test, the policy matrix — it now uses one that still exists, so the property it proves is unchanged. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU --- README.md | 11 +- .../{learning => deck}/slides-reference.pptx | Bin docs/database.md | 5 - docs/features-explained.md | 17 +- docs/learning-hub.md | 101 -- docs/logic/auth-admin-learning.md | 2 +- .../1780800000000_retire-learning-hub.js | 39 + public/components/cms.html | 327 ------ public/components/learning.html | 64 -- public/index.html | 11 - public/js/learningHub.js | 1022 ----------------- public/js/learningHub/aiPanelController.js | 103 -- public/js/learningHub/api.js | 26 - public/js/learningHub/cmsController.js | 190 --- public/js/learningHub/cmsRenderer.js | 109 -- public/js/learningHub/feedRenderer.js | 69 -- public/js/learningHub/images.js | 76 -- public/js/learningHub/quizController.js | 100 -- public/js/learningHub/quizRenderer.js | 63 - public/js/learningHub/sanitize.js | 24 - public/js/learningHub/slideController.js | 149 --- public/js/learningHub/tiptapEditor.js | 165 --- public/js/learningHub/viewerRenderer.js | 42 - public/js/learningHub/webdavController.js | 116 -- public/js/myResources.js | 7 +- public/js/notes/utils.js | 2 +- server.js | 9 +- src/db/database.js | 98 +- src/routes/generatedImages.js | 25 +- src/routes/learningAI.js | 726 ------------ src/routes/learningAdmin.js | 444 ------- src/routes/learningHub.js | 369 ------ src/routes/memories.js | 2 +- src/routes/myResources.js | 12 +- src/utils/documentExport.js | 2 +- src/utils/embeddings.js | 72 -- src/utils/generatedImageLinks.js | 15 +- src/utils/generatedImages.js | 21 +- src/utils/resourceImages.js | 2 +- test/account-boundary.test.js | 8 +- test/assistant-workspace-layout.test.js | 24 - test/backend-hardening.test.js | 66 +- ...ieval.test.js => corpus-retrieval.test.js} | 44 +- test/generated-image-tools.test.js | 30 - test/generated-images-ui.test.js | 63 - test/generated-images.integration.js | 416 ------- test/learning-hub-ai-category.test.js | 37 - test/learning-hub-ai-panel-controller.test.js | 156 --- test/learning-hub-editor.test.js | 190 --- test/learning-hub-manual-category.test.js | 81 -- test/learning-hub-quiz-controller.test.js | 55 - test/learning-hub-webdav-controller.test.js | 72 -- test/learning-upload-limits.test.js | 42 - test/model-defaults.test.js | 14 - test/module-entrypoints.test.js | 1 - test/my-resources.test.js | 24 +- test/policy-flows.test.js | 38 +- test/policy-ui.test.js | 4 +- test/session-quiz-ed-regressions.test.js | 70 -- 59 files changed, 105 insertions(+), 5967 deletions(-) rename assets/{learning => deck}/slides-reference.pptx (100%) delete mode 100644 docs/learning-hub.md create mode 100644 migrations/1780800000000_retire-learning-hub.js delete mode 100644 public/components/cms.html delete mode 100644 public/components/learning.html delete mode 100644 public/js/learningHub.js delete mode 100644 public/js/learningHub/aiPanelController.js delete mode 100644 public/js/learningHub/api.js delete mode 100644 public/js/learningHub/cmsController.js delete mode 100644 public/js/learningHub/cmsRenderer.js delete mode 100644 public/js/learningHub/feedRenderer.js delete mode 100644 public/js/learningHub/images.js delete mode 100644 public/js/learningHub/quizController.js delete mode 100644 public/js/learningHub/quizRenderer.js delete mode 100644 public/js/learningHub/sanitize.js delete mode 100644 public/js/learningHub/slideController.js delete mode 100644 public/js/learningHub/tiptapEditor.js delete mode 100644 public/js/learningHub/viewerRenderer.js delete mode 100644 public/js/learningHub/webdavController.js delete mode 100644 src/routes/learningAI.js delete mode 100644 src/routes/learningAdmin.js delete mode 100644 src/routes/learningHub.js rename test/{learning-retrieval.test.js => corpus-retrieval.test.js} (62%) delete mode 100644 test/generated-images.integration.js delete mode 100644 test/learning-hub-ai-category.test.js delete mode 100644 test/learning-hub-ai-panel-controller.test.js delete mode 100644 test/learning-hub-editor.test.js delete mode 100644 test/learning-hub-manual-category.test.js delete mode 100644 test/learning-hub-quiz-controller.test.js delete mode 100644 test/learning-hub-webdav-controller.test.js delete mode 100644 test/learning-upload-limits.test.js diff --git a/README.md b/README.md index 1046e97b..660f9312 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ped-AI -Ped-AI is a pediatric clinical documentation, education, and bedside decision-support app. This fork has moved well beyond the original scribe app: it now combines encounter documentation, clinical workflows, Learning Hub CMS, admin controls, MCP-backed clinical assistant integration, Redis-backed operational state, and hardened deployment defaults. +Ped-AI is a pediatric clinical documentation, education, and bedside decision-support app. This fork has moved well beyond the original scribe app: it now combines encounter documentation, clinical workflows, private teaching material, admin controls, MCP-backed clinical assistant integration, Redis-backed operational state, and hardened deployment defaults. The app runs as an authenticated Express/Postgres service with a browser frontend and optional integrations for LiteLLM, AWS, OpenAI-compatible APIs, Nextcloud WebDAV, S3-compatible storage, OpenBao, Redis, OIDC, TOTP, and Cloudflare Turnstile. @@ -24,14 +24,6 @@ The app runs as an authenticated Express/Postgres service with a browser fronten - Mobile-friendly PWA layout for bedside use. - Per-user phone extension and pager directory with soft-delete, search, ZIP export, and JSON/ZIP import for handoff between users. -### Learning Hub - -- CMS for articles, clinical pearls, quizzes, and presentations. -- Tiptap article editor, quiz builder, category management, and draft/publish flow. -- AI-assisted content generation from topic text, uploaded files, or connected Nextcloud WebDAV files. -- Marp slide editing with preview and PPTX export. -- Keyword, semantic, and hybrid search using Postgres/pgvector where configured. - ### My Resources - Private teaching material any signed-in user can generate for themselves — nobody else sees it. @@ -180,7 +172,6 @@ Primary references: - `docs/embeddings-setup.md` — embedding model configuration. - `docs/global-prompt-administration.md` — prompt overrides and the conversation budget. - `docs/speech.md` — STT, TTS, recording, and audio backups. -- `docs/learning-hub.md` — the CMS and education workflow. - `docs/my-resources.md` — private teaching material, the slide renderer, and search sources. - `docs/deployment.md` — production deployment. - `docs/scaling.md` — scaling priorities and readiness work. diff --git a/assets/learning/slides-reference.pptx b/assets/deck/slides-reference.pptx similarity index 100% rename from assets/learning/slides-reference.pptx rename to assets/deck/slides-reference.pptx diff --git a/docs/database.md b/docs/database.md index 3981951d..64805066 100644 --- a/docs/database.md +++ b/docs/database.md @@ -180,11 +180,6 @@ Metadata for files in S3-compatible object storage. File bytes stay in S3. | description | TEXT | | | created_at | TIMESTAMPTZ DEFAULT NOW() | | -### `learning_categories`, `learning_content`, `learning_questions`, `learning_options`, `learning_progress` - -Learning Hub CMS tables. `learning_content.embedding` is `VECTOR(768)` for -semantic search (pgvector IVFFLAT index). See `docs/learning-hub.md`. - ### `developmental_milestones` AAP-aligned pediatric milestone reference data. Age group + domain keyed. diff --git a/docs/features-explained.md b/docs/features-explained.md index 7ec5ad19..aa4c2de7 100644 --- a/docs/features-explained.md +++ b/docs/features-explained.md @@ -26,19 +26,9 @@ Browser Whisper and browser-local model workers are removed. Do not expect a pre The voice preview button calls LiteLLM TTS and plays the returned audio in the browser. If preview is silent, check that a LiteLLM voice is selected, the gateway is configured, the user is authenticated, and browser autoplay has not blocked playback. -## Learning Hub - -Learning Hub is both a learner-facing content area and an admin/moderator CMS. - -- Articles and pearls render sanitized content. -- Quizzes support single-answer, multi-select, and true/false questions. -- Presentations use Marp-style markdown with preview and PPTX export. -- AI generation can use topic text, uploaded source files, or connected Nextcloud WebDAV files. -- Categories can organize content without deleting the content when category assignments change. - ## Nextcloud WebDAV -Users can connect a Nextcloud account with an app password. Learning Hub AI generation can browse files from the connected WebDAV account, and users can set a default browse path to avoid repeatedly navigating to the same clinical content folder. +Users can connect a Nextcloud account with an app password and export generated notes to it. ## Documents And S3 @@ -79,7 +69,7 @@ deliberately never cached. Full detail in ## Admin Panel -Admins can manage users, roles, registration, security settings, model defaults, prompts, logs, and Learning Hub content. Production deployments should enable SSO/2FA and restrict admin access. +Admins can manage users, roles, registration, security settings, model defaults, prompts and logs. Production deployments should enable SSO/2FA and restrict admin access. ## Feature Status @@ -89,7 +79,6 @@ Admins can manage users, roles, registration, security settings, model defaults, | Server transcription | Active | Routed through LiteLLM; the upstream vendor is a gateway choice. | | Browser Web Speech preview | Optional | Explicit opt-in only. | | Browser Whisper | Removed | No public worker or model download path. | -| Learning Hub CMS | Active | Articles, pearls, quizzes, presentations. | | Nextcloud WebDAV | Active | Used for file browsing/content import. | | Patient handouts | Active | Parent-facing, note-derived, preferred-language draft. | | Extension transfer | Active | ZIP export plus JSON/ZIP import preview. | @@ -107,4 +96,4 @@ Admins can manage users, roles, registration, security settings, model defaults, - Check `docker logs pediatric-ai-scribe -f` for backend errors. - Check `/api/health` for service status. - Check provider credentials and model names before debugging UI state. -- For Learning Hub file import failures, verify Nextcloud URL, username, app password, and folder path. +- For Nextcloud export failures, verify the URL, username, app password, and folder path. diff --git a/docs/learning-hub.md b/docs/learning-hub.md deleted file mode 100644 index c9b84d03..00000000 --- a/docs/learning-hub.md +++ /dev/null @@ -1,101 +0,0 @@ -# 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 / DOCX / PPTX / ODT / EPUB / TXT / MD / HTML / CSV / JSON, ≤ 10 MB each, max 10 files. The declared type must be in the allowlist *and* match the extension, and the bytes are sniffed before anything parses them. | -| 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). diff --git a/docs/logic/auth-admin-learning.md b/docs/logic/auth-admin-learning.md index 08821a68..bb907e53 100644 --- a/docs/logic/auth-admin-learning.md +++ b/docs/logic/auth-admin-learning.md @@ -43,7 +43,7 @@ generation can use topic text, uploaded files, or connected Nextcloud/WebDAV sources. Semantic search uses pgvector embeddings on `learning_content` when an embedding provider is configured. -See [`../learning-hub.md`](../learning-hub.md) and +See [`../embeddings-setup.md`](../embeddings-setup.md). ## Security Rules diff --git a/migrations/1780800000000_retire-learning-hub.js b/migrations/1780800000000_retire-learning-hub.js new file mode 100644 index 00000000..b1e66dd3 --- /dev/null +++ b/migrations/1780800000000_retire-learning-hub.js @@ -0,0 +1,39 @@ +// Learning Hub is retired. My Resources generates better slides from a typed +// deck, and the articles and quizzes live in the quiz app. +// +// The content was exported before this ran — every article as markdown plus a +// full SQL dump of all five tables — to ops-backups/learning-hub-export-*. +// That export is the restore path; this migration is not reversible in any +// useful sense, because down() can recreate the shape but never the rows. +// +// generated_image_links goes with it: it existed only to say which Learning Hub +// content an image was published in, and it was the sole reason a generated +// image could ever be read by someone who did not make it. Images are now +// owner-only, which is both simpler and stricter. + +exports.up = async function (pgm) { + // Order matters only for readability — CASCADE handles the dependencies. + pgm.sql('DROP TABLE IF EXISTS generated_image_links CASCADE'); + pgm.sql('DROP TABLE IF EXISTS learning_progress CASCADE'); + pgm.sql('DROP TABLE IF EXISTS learning_options CASCADE'); + pgm.sql('DROP TABLE IF EXISTS learning_questions CASCADE'); + pgm.sql('DROP TABLE IF EXISTS learning_content CASCADE'); + pgm.sql('DROP TABLE IF EXISTS learning_categories CASCADE'); + + // The workflow enum on image jobs. Any rows for the retired workflow go with + // it: those images belonged to Learning Hub content that no longer exists. + pgm.sql("DELETE FROM generated_image_jobs WHERE workflow = 'learning_hub'"); + pgm.sql('ALTER TABLE generated_image_jobs DROP CONSTRAINT IF EXISTS generated_image_jobs_workflow_check'); + pgm.sql("ALTER TABLE generated_image_jobs ADD CONSTRAINT generated_image_jobs_workflow_check " + + "CHECK (workflow = ANY (ARRAY['clinical_assistant'::text, 'my_resources'::text]))"); + + // Settings that only ever addressed Learning Hub. + pgm.sql("DELETE FROM app_settings WHERE key LIKE 'learning_hub.%'"); +}; + +exports.down = async function (pgm) { + // The shape, not the content. Restoring the rows means loading the export. + pgm.sql('ALTER TABLE generated_image_jobs DROP CONSTRAINT IF EXISTS generated_image_jobs_workflow_check'); + pgm.sql("ALTER TABLE generated_image_jobs ADD CONSTRAINT generated_image_jobs_workflow_check " + + "CHECK (workflow = ANY (ARRAY['clinical_assistant'::text, 'learning_hub'::text, 'my_resources'::text]))"); +}; diff --git a/public/components/cms.html b/public/components/cms.html deleted file mode 100644 index 6b4fb536..00000000 --- a/public/components/cms.html +++ /dev/null @@ -1,327 +0,0 @@ -
-

Content Manager

-

Create and manage Learning Hub content, quizzes, and categories

-
- - -
-
-Published
-
-Drafts
-
-Categories
-
-Quizzes
-
-Attempts
-
- - -
- - - - - -
- - -
-
-
- - - - -
-
- - -
-
- -
-
- Title - Category - Type - Status - Updated - -
-
-
Loading...
-
-
-
- - - - -
-
diff --git a/public/components/learning.html b/public/components/learning.html deleted file mode 100644 index 4f494663..00000000 --- a/public/components/learning.html +++ /dev/null @@ -1,64 +0,0 @@ -
-

Learning Hub

-

Pediatric education, clinical pearls, and self-assessment quizzes

-
- - -
-
- - -
-
- - -
- - -
- - - diff --git a/public/index.html b/public/index.html index 8c3a089d..c3eea3a2 100644 --- a/public/index.html +++ b/public/index.html @@ -288,14 +288,6 @@ My Resources - -