# 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).