Files
learning-platform/docs/data-model.md
RaymondVerhoef 5214c9db3b
Some checks failed
On Pull Request to Main / test (pull_request) Failing after 24s
On Pull Request to Main / publish (pull_request) Has been skipped
On Pull Request to Main / deploy-dev (pull_request) Has been skipped
feat: 5-day theme-level onboarding track from the "New here?" card (#30)
A self-paced onboarding track that introduces a new employee to every KB
theme in breadth (not depth), so they grasp how Respellion works day to
day and week to week. Offered as a CTA inside the Dashboard "New here?"
explainer card; always available regardless of enrollment.

Design:
- Theme is the trackable unit; the 5 "days" are a read-time presentation
  grouping, so re-chunking never loses progress. Completion is stored per
  theme in onboarding_completions.
- Per-theme overview generated lazily on first open (fast-tier
  emit_onboarding_overview tool), cached in onboarding_overviews keyed by
  theme + a topics_fingerprint that triggers regeneration when the theme's
  topic set changes.
- Reachable via /onboarding-track using the existing skipEnrollmentGate
  prop, decoupled from the 26-week curriculum (distinct from /onboarding,
  the enrollment page).

Backend:
- pb_migrations/1781200000_created_onboarding.js: two collections with
  authenticated-only rules and unique indexes; TEXT team_member_id (no
  relation) per the post-#18/#27 convention. Mirrored in
  scripts/setup-pb-collections.mjs.
- src/lib/onboardingService.js: pure helpers (orderThemes,
  distributeThemesIntoDays, computeTopicsFingerprint,
  computeOnboardingProgress, buildOnboardingPlan) + generation + I/O.
- db.js onboarding helpers use pb.filter() bindings (theme is free text).
- LLM tool + Zod schema + registry + simulation stub.

Frontend:
- src/pages/OnboardingTrack.jsx (day list, per-theme overview, completion
  banner, progress ring/day bar).
- Dashboard "New here?" card CTA + X/5-days progress chip (hidden when the
  KB has no themes).

Docs: data-model, generation-spec (§D), frontend-spec updated.

Verified: 22 new unit tests (npm test 134/134), eslint clean on changed
files, npm run build OK, PocketBase v0.30.4 boot applies the migration
(collections + unique indexes + authed rules confirmed), and a backend
contract check (upsert idempotency, unique-index guard, special-char
theme filtering).

Closes #30

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 09:08:38 +02:00

9.2 KiB
Raw Blame History

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):

{
  "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:

// 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:

{ "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

onboarding_overviews

Cached, breadth-first per-theme overview for the onboarding track (issue #30). One row per theme, shared across all users. Generated lazily on first open.

Field Type Notes
theme text canonical theme name (from topics.theme); unique
content json validated onboardingOverviewSchema payload (title, what_it_is, why_it_matters, key_points, topics_covered)
topics_fingerprint text stable hash of the theme's sorted topic ids; a mismatch triggers regeneration

Unique index on (theme). Rules: authenticated-only (@request.auth.id != "").


onboarding_completions

Per-user, per-theme completion marker for the onboarding track (issue #30).

Field Type Notes
team_member_id text the employee — plain text, not a relation (admins can delete members; orphan rows are harmless)
theme text the theme marked complete

Unique index on (team_member_id, theme) → idempotent. Rules: authenticated-only. Completion is tracked per theme, not per day; the 5 "days" are a read-time presentation grouping, so re-chunking never loses progress.


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.