Files
learning-platform/docs/generation-spec.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

111 lines
5.3 KiB
Markdown
Raw Permalink 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.
# Generation spec: learning content & micro-learnings
Two generators turn a topic into learner-facing material. Both go through
`callLLM` with forced tool use and Zod-validated output. All content is cached in
PocketBase so it is generated once per topic/type.
---
## A. Long-form content — `src/lib/learningService.js`
Stored in the `content` collection (one record per topic, `data` is a merged
object). Three types, generated **on demand**:
| Type | Tool | Min requirements |
|---|---|---|
| `article` | `emit_learning_article` | ≥3 sections, ≥2 takeaways |
| `slides` | `emit_learning_slides` | ≥4 slides |
| `infographic` | `emit_learning_infographic` | ≥3 stats, ≥3 steps |
`generateLearningContent(topic, force, selectedType)`:
- tier `standard`, `maxTokens: 8192`
- `selectedType` is one of the three, or `'all'` (`emit_learning_all`) for admin regeneration
- cache check looks at `content[selectedType]`; on generation the new payload is
**shallow-merged** into the cached object so other types survive
- there is **no podcast type**
**Article refinement** (`refineLearningContent`): the admin describes a change and
the model edits via targeted patch tools — `set_intro`, `set_section`,
`add_section`, `remove_section`, `replace_takeaways` — so only the affected parts
change. Patches are applied and re-validated in `src/lib/articlePatches.js`.
---
## B. Micro-learnings — `src/lib/microLearningService.js`
Stored in the `micro_learnings` collection (one record per topic per type,
`status='published'`). Three types:
| Type | Tool | Tier | Shape |
|---|---|---|---|
| `concept_explainer` | `emit_concept_explainer` | standard | `{ sections: [{ title, content (HTML) }] }`, ≥3 sections |
| `scenario_quiz` | `emit_scenario_quiz` | standard | `{ scenario, options: [{ text, isCorrect, explanation }] }`, 34 options, exactly 1 correct |
| `flashcard_set` | `emit_flashcard_set` | fast (Haiku) | `{ cards: [{ front, back }] }`, 510 cards |
`getOrGenerateMicroLearning(topicId, type)`:
- returns the cached published record if one exists (`findExisting`)
- otherwise loads the topic, calls `callLLM` with forced tool choice, and creates a
`micro_learnings` record with the validated `content`
> A former `reflection_prompt` type was dropped. Do not re-add it.
Completion is recorded (append-only) by `useMicroLearningCompletions` into
`micro_learning_completions` with `{ team_member_id, micro_learning_id, topic_id,
type, session_week }`.
---
## C. Weekly quiz — `src/lib/testService.js`
Generates a 5-question multiple-choice test for the user's current week.
- **Topic selection** (`selectTestTopics`): primary topic from the active
curriculum week (else hash fallback) + a few review topics for breadth.
- **Batch generation** (`callQuizBatchModel`): a single `fast`-tier call
(`emit_quiz_questions`, `maxTokens: 4096`, 25s timeout) returns all 5 questions.
- **Quality gates** (`validateBatchQuality`): no duplicate options; no banned
fillers ("all/none of the above", "both A and B"); explanations ≥20 chars; reject
if `correctIndex` is dominated by one position (>80%) and re-roll.
- **Scoring** (`saveTestResult`): `pointsEarned = score * 2`, written to
`leaderboard` via `db.upsertLeaderboardEntry`.
Question shape: `{ id, question, topicLabel, options[4], correctIndex (03),
explanation, difficulty }`.
---
## D. Onboarding overviews — `src/lib/onboardingService.js`
Powers the 5-day onboarding track (issue #30): a short, breadth-first overview of
**one theme** for a brand-new employee — deliberately light, not a deep lesson.
- **Tool:** `emit_onboarding_overview` · **tier:** `fast` (Haiku) · `maxTokens: 1500`,
60s timeout · schema `onboardingOverviewSchema`.
- **Shape:** `{ title, what_it_is, why_it_matters, key_points[35], topics_covered[{topic_id,label}] }`.
`why_it_matters` is framed around day-to-day / week-to-week work at Respellion.
- **Cache:** `onboarding_overviews`, one row per theme, keyed by theme name plus a
`topics_fingerprint` (stable hash of the theme's sorted topic ids).
`getOrGenerateOnboardingOverview(theme, topics, {force})` returns the cached row when
the fingerprint matches; a mismatch (topic added/removed) or `force` regenerates.
- **Simulation:** `emit_onboarding_overview` has a stub in `SIMULATION_TOOL_STUBS`.
Theme ordering + day grouping are pure helpers in the same module
(`orderThemes`, `distributeThemesIntoDays`, `computeTopicsFingerprint`,
`computeOnboardingProgress`, `buildOnboardingPlan`), unit-tested in
`src/lib/__tests__/onboardingService.test.js`. Completion is recorded per **theme**
in `onboarding_completions` (`{ team_member_id, theme }`), not per day.
---
## Shared infrastructure (`src/lib/llm.js`)
- **Tiers:** `fast` (Haiku 4.5), `standard` (Sonnet 4.6), `reasoning` (Opus 4.7);
per-tier admin overrides via `admin:model:{tier}`.
- **Structured output:** prefer tool use with forced `toolChoice`; inputs validated
by `toolSchemaRegistry`. Text responses go through `parseStructuredText`.
- **Caching:** wrap stable system text with `cachedSystem(...)`.
- **Retry/limits:** `src/lib/llmRetry.js` — backoff + jitter on 408/425/429/5xx/529,
honors `Retry-After`, rate limiters for bulk work.
- **Telemetry:** every call logged to `llm_calls`.
- **Simulation:** with `admin:use_simulation`, calls return stub output (no API hit).