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>
111 lines
5.3 KiB
Markdown
111 lines
5.3 KiB
Markdown
# 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 }] }`, 3–4 options, exactly 1 correct |
|
||
| `flashcard_set` | `emit_flashcard_set` | fast (Haiku) | `{ cards: [{ front, back }] }`, 5–10 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 (0–3),
|
||
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[3–5], 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).
|