Files
learning-platform/docs/data-model.md
RaymondVerhoef 07af2783dc
All checks were successful
On Push to Main / test (push) Successful in 1m33s
On Push to Main / publish (push) Successful in 1m31s
On Push to Main / deploy-dev (push) Successful in 2m3s
Add comprehensive documentation for key organizational aspects
- Introduced "Pension Scheme & Benefits" detailing secondary employment benefits and pension specifics.
- Created "Roles & Accountabilities" outlining the Holacracy role structure and responsibilities within Respellion.
- Added "Security" section covering GDPR compliance and workplace safety protocols.
- Established "Spending and Contracting" policy detailing expense categories and submission processes.
- Documented "Who We Are" to define Respellion's identity, services, and operational model under Holacracy and ISO 9001.
2026-05-27 08:24:56 +02:00

253 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Data model: Respellion Learning Platform
## Overview
All structured data lives in **PocketBase** (SQLite). There is **no vector store**
— retrieval is computed at runtime with a local TF-IDF index over `topics`
(`src/lib/retrieval.js`).
Schema is defined by JS migrations in `pb_migrations/` (applied automatically by
the PocketBase binary) and mirrored for local bootstrap in
`scripts/setup-pb-collections.mjs`. The data-access layer is `src/lib/db.js`.
All collections use PocketBase's auto `id`, plus `created` / `updated` autodate
fields unless noted otherwise.
---
## PocketBase collections
### `topics`
Knowledge graph nodes. Created during ingestion, enriched for curriculum.
| Field | Type | Notes |
|---|---|---|
| id | text | kebab-case slug (e.g. `holacratic-roles`) |
| label | text | display name |
| type | text | `concept` · `role` · `process` (ingestion); `fact` is excluded from learning |
| description | text | 12 sentence summary |
| learning_relevance | text | `core` · `standard` · `peripheral` · `exclude` |
| relevance_locked | bool | if true, re-ingestion will not overwrite `learning_relevance` |
| theme | text | subject grouping (used by curriculum generation) |
| complexity_weight | number | 15 (curriculum ordering) |
| difficulty | text | `introductory` · `intermediate` · `advanced` |
Topics with `type='fact'` or `learning_relevance='exclude'` are filtered out of
learning, micro-learning, curriculum, and test selection.
---
### `relations`
Knowledge graph edges between topics.
| Field | Type | Notes |
|---|---|---|
| source | text | topic id |
| target | text | topic id |
| type | text | `related_to` · `depends_on` · `part_of` · `executed_by` |
Edges are de-duplicated on the `(source, target, type)` tuple.
---
### `content`
On-demand long-form learning content, one record per topic.
| Field | Type | Notes |
|---|---|---|
| topic_id | text | topic this content belongs to |
| data | json | merged object — only generated types are present |
`data` shape (each key generated independently and shallow-merged):
```json
{
"article": { "title", "intro", "sections": [{ "heading", "body" }], "keyTakeaways": [] },
"slides": [ { "title", "bullets": [], "speakerNote" } ],
"infographic": { "headline", "tagline", "stats": [{ "value", "label", "icon" }],
"steps": [{ "number", "title", "description", "icon" }], "quote", "colorTheme" }
}
```
> There is no `podcast` key. The podcast type was removed.
---
### `micro_learnings`
Generated micro-learning artifacts. One record per topic per type.
| Field | Type | Notes |
|---|---|---|
| topic_id | relation → `topics` | cascade delete |
| type | select | `concept_explainer` · `scenario_quiz` · `flashcard_set` |
| content | json | structured output, schema varies per type |
| status | select | `draft` · `published` (only `published` is visible to employees) |
**Content JSON per type:**
```json
// concept_explainer
{ "sections": [ { "title": "string", "content": "string (HTML: <p>, <ul>, <li>, <strong>)" } ] } // ≥3 sections
// scenario_quiz
{ "scenario": "string",
"options": [ { "text": "string", "isCorrect": true, "explanation": "string" } ] } // 34 options, exactly 1 correct
// flashcard_set
{ "cards": [ { "front": "string", "back": "string" } ] } // 510 cards
```
> A former `reflection_prompt` type was dropped and is no longer generated.
---
### `micro_learning_completions`
Append-only completion events. Never updated or deleted.
| Field | Type | Notes |
|---|---|---|
| team_member_id | relation → `team_members` | the employee |
| micro_learning_id | relation → `micro_learnings` | the artifact completed |
| topic_id | relation → `topics` | denormalized topic |
| type | text | type at time of completion |
| session_week | number | the user's **absolute** curriculum week (week 1 = day they enrolled) |
The 26-week slot and cycle are derived from `session_week`; there is no stored
`cycle` field.
---
### `curriculum_versions`
Versioned 26-week schedules. New version on each (re)generation.
| Field | Type | Notes |
|---|---|---|
| version_number | number | increments per generation |
| status | text | `draft` · `active` · `superseded` (exactly one `active`) |
| generation_reason | text | why this version was created |
| confirmed_by | text | admin id who activated it |
| confirmed_at | text | ISO datetime |
| schedule | json | array of 26 week objects (below) |
| coverage_stats | json | `{ themes_kb, themes_scheduled, topics_kb, topics_scheduled }` |
**`schedule[]` week object:**
```json
{ "week_number": 1, // 1..26
"theme": "string",
"topic_ids": ["topic-id"], // 1+ topic ids
"estimated_duration": 30, // 15..45 minutes
"week_rationale": "string" }
```
---
### `team_members`
Registered users with PIN auth. This is the auth + employee record.
| Field | Type | Notes |
|---|---|---|
| name | text | display name |
| pin | text | login PIN |
| role | text | `admin` or empty/`employee` |
| curriculum_started_at | date | timestamp the user enrolled (week 1 anchor); empty until enrolled |
| enrollment_status | text | `not_started` · `active` |
A user is gated through the `/onboarding` screen until `enrollment_status='active'`
(admins are exempt when heading to the admin panel).
---
### `sources`
Uploaded source documents and their extraction status.
| Field | Type | Notes |
|---|---|---|
| name | text | original filename |
| status | text | `processing` · `completed` · `failed` · `cancelled` |
| error | text | failure message, if any |
| progress | json | `{ current, total, message }` during chunked extraction |
---
### `leaderboard`
Points ledger, one row per user.
| Field | Type | Notes |
|---|---|---|
| user_id | text | team member id |
| name | text | display name |
| points | number | cumulative (+2 per correct quiz answer) |
| tests_completed | number | count of completed tests |
| learnings_completed | number | reserved counter |
Admins are filtered out of the public leaderboard at render time.
---
### `settings`
App-wide key/value store.
| Field | Type | Notes |
|---|---|---|
| key | text | setting key |
| value | text | stringified value |
---
### `llm_calls`
Best-effort telemetry for every Anthropic call (written by `callLLM`).
| Field | Type | Notes |
|---|---|---|
| task | text | logging label (e.g. `learning.article`, `chat.r42`) |
| model | text | resolved model string |
| tier | text | `fast` · `standard` · `reasoning` |
| duration_ms | number | wall-clock |
| input_tokens / output_tokens | number | usage |
| cache_read_tokens / cache_create_tokens | number | prompt-cache usage |
| stop_reason | text | `end_turn` · `tool_use` · `max_tokens` |
| ok | bool | success flag |
| error_msg | text | error, if any |
---
## Dropped / legacy collections
These existed in earlier iterations and have been removed. Their `db.js` helpers
remain as deprecated no-op stubs — do not build on them:
`quiz_banks`, `quiz_results`, `quiz_cache`, `learn_progress`, and the v1
`curriculum` collection.
---
## Client-side storage (not PocketBase)
`localStorage` is used only for admin/browser-local state:
| Key | Purpose |
|---|---|
| `admin:model:{fast,standard,reasoning}` | per-tier model overrides (legacy `admin:model`) |
| `admin:use_simulation` | stub LLM responses instead of calling Anthropic |
| `kb:suggestions` | R42 graph-delta suggestion queue (managed via `kbStore`) |
| `quiz:active:{userId}` | mid-quiz flag (hides R42) |
| `chat:thread:{userId}` | R42 conversation, capped at 50 messages |
`sessionStorage.respellion_session` holds the logged-in team member id.
---
## Retrieval (no vector DB)
R42 context is built by `src/lib/retrieval.js`:
```
buildIndex(topics) → TF-IDF index over (label + description), cached by array ref
retrieveTopK(index, q, k) → top-K topics, score = Σ (1 + log(tf)) · log((N+1)/(df+1))
```
`src/components/chat/rag.js` combines top-K results with verbatim topic mentions,
filters relations to the retrieved set, and injects limited deep content for
explicitly named topics.