# Curriculum service spec ## Responsibility Generates a versioned 26-week learning schedule from the published knowledge base. Manages perpetual cycling, version transitions, and employee curriculum state. Handles regeneration when the KB changes. --- ## Service location ``` app/services/curriculum/ ├── src/ │ ├── index.ts entry point, Fastify server │ ├── routes/ │ │ ├── curriculum.ts POST /generate, GET /current, GET /preview │ │ └── employee.ts GET /state/:userId, POST /advance/:userId │ ├── generator/ │ │ ├── build.ts KB graph → 26-week schedule (AI call) │ │ ├── sequence.ts prerequisite + complexity ordering │ │ └── cycle.ts cycle 2+ variation logic │ ├── versioning/ │ │ ├── apply.ts apply new version to active employees │ │ └── freeze.ts protect completed weeks │ └── lib/ │ ├── pocketbase.ts │ └── anthropic.ts ├── package.json ├── tsconfig.json └── .env.example ``` --- ## API surface ### POST /generate Triggers curriculum generation from current published KB. Called by admin app after confirming regeneration. Request: ```json { "triggeredBy": "string", "reason": "new_topics" | "manual" } ``` Response (202 Accepted): ```json { "jobId": "string", "status": "queued" } ``` --- ### GET /preview Returns proposed new curriculum before admin confirms. Called by admin app to show preview before regeneration is applied. Response: ```json { "version": 3, "weeks": [ { "weekNumber": 1, "theme": { "id": "string", "title": "string" }, "topics": [ { "id": "string", "title": "string", "complexityWeight": 2 } ], "estimatedDurationMinutes": 25 } ], "coverageStats": { "themesTotal": 8, "themesCovered": 8, "topicsTotal": 42, "topicsCovered": 42 } } ``` --- ### GET /current Returns the currently active curriculum version with all week slots. --- ### GET /state/:userId Returns an employee's current curriculum state. Response: ```json { "userId": "string", "currentCycle": 1, "currentWeek": 7, "startDate": "2026-01-15T00:00:00Z", "activeVersionId": "string", "nextSessionTheme": { "id": "string", "title": "string" }, "nextSessionTopics": [] } ``` --- ### POST /advance/:userId Called by progress service when an employee completes a week. Increments currentWeek, handles cycle transition at week 26. Request: ```json { "completedWeek": 7 } ``` --- ## Curriculum generation ### Input All published Themes and Topics retrieved from PocketBase: ```typescript type KBSnapshot = { themes: { id: string title: string description: string topics: { id: string title: string complexityWeight: number // 1–5 difficulty: string prerequisiteTopics: string[] // topic IDs relatedTopics: string[] contrastTopics: string[] }[] }[] } ``` --- ### Pre-processing: sequence topics within themes Before the AI call, the service resolves topic ordering within each Theme using a topological sort on prerequisite relationships. ``` For each Theme: Build directed graph: prerequisite_topics edges Topological sort → ordered topic list If cycle detected (should not occur but handle): log warning, fall back to complexity_weight ascending order ``` This pre-processing means the AI does not need to reason about prerequisites — it receives already-ordered topic lists and focuses on Theme sequencing. --- ### AI call: Theme sequencing across 26 weeks System prompt: ``` You are a curriculum designer. Your task is to distribute a set of learning Themes across 26 weekly sessions to create an effective learning journey. Output ONLY valid JSON matching the schema provided. No preamble, no explanation, no markdown fences. Rules: - Every Theme must appear at least once across 26 weeks - Themes with more Topics (higher topic count) may span multiple weeks or appear in multiple cycles within the 26 weeks - Sequence Themes so foundational concepts precede dependent ones - Distribute complexity progressively: introductory Themes early, advanced Themes in the second half - If total Topics across all Themes exceeds what 26 weeks can cover in depth, prioritise breadth in cycle 1 — every Theme covered, key Topics per Theme - Assign an estimated duration in minutes per week (15–45 minutes per session) - Return exactly 26 week slots ``` User prompt: ``` Knowledge base snapshot: {KBSnapshot as JSON} Generate a 26-week curriculum schedule. ``` Output schema: ```typescript type CurriculumDraft = { weeks: { weekNumber: number // 1–26 themeId: string topicIds: string[] // ordered subset of theme's topics estimatedDurationMinutes: number rationale: string // one sentence — shown to admin in preview }[] } ``` AI call configuration: ```typescript { model: 'claude-sonnet-4-20250514', max_tokens: 4000, temperature: 0 } ``` Validation: Zod schema on output. Check all themeIds and topicIds exist in the KB snapshot before writing. Reject and retry once on validation failure. --- ### Write to PocketBase ``` Create curriculum_versions record { version: latest + 1, status: 'draft', generated_at: now, generation_notes: reason } For each week in CurriculumDraft: Create curriculum_weeks record { curriculum_version: versionId, week_number: weekNumber, theme: themeId, topics: topicIds, topic_order: [0, 1, 2, ...], estimated_duration_minutes: value, admin_notes: '' } Set curriculum_versions.status → 'draft' Notify admin: preview available at GET /preview ``` Draft version is not applied until admin confirms via POST /generate confirm. --- ## Versioning and regeneration ### Applying a new version When admin confirms, `apply.ts` runs: ``` Get all employees from employee_curriculum_state For each employee: frozenWeek = employee.current_week Update employee_curriculum_state: active_version = new version ID Note: completed weeks are protected by current_week value The frontend only renders weeks >= current_week from active_version Weeks < current_week are rendered from session_completions history (immutable records — not from curriculum_weeks) Set old curriculum_versions.status → 'superseded' Set new curriculum_versions.status → 'active' ``` Completed weeks are never stored against a curriculum version — they live in session_completions. The version only determines future week content. --- ## Perpetual cycling ### Week 26 completion → cycle transition When progress service calls POST /advance/:userId with completedWeek: 26: ``` employee.currentCycle += 1 employee.currentWeek = 1 employee.startDate = now employee.activeVersion = current active version Generate cycle variant (see below) ``` ### Cycle variant generation Cycle 2+ is not identical to cycle 1. The AI call receives additional context: Additional fields in user prompt for cycle 2+: ```json { "cycleNumber": 2, "employeeHistory": { "typesUsed": ["concept_explainer", "scenario_quiz", "how_to"], "typesNotUsed": ["case_study", "myth_vs_evidence", "comparison_card"], "lowEngagementTopics": ["topic-id-1", "topic-id-2"] } } ``` Additional rules added to system prompt for cycle 2+: ``` - Vary the Theme sequence from the previous cycle - Topics identified as low engagement should appear earlier in this cycle - The rationale field should note what is different from cycle 1 ``` Low engagement is determined by: topics where the employee completed only one micro learning type (minimum engagement). Retrieved from session_completions by progress service and passed to curriculum service on cycle transition. --- ## Admin curriculum editor The curriculum editor in the admin app (built in frontend phase) calls: - GET /preview to display the proposed schedule - PATCH /weeks/:weekId to update theme or topic assignment - POST /confirm to apply the version The PATCH route allows admin to: - Reassign a Theme to a different week (swap two weeks) - Add or remove Topics from a week's topic list - Edit admin_notes per week Changes made via PATCH update the draft curriculum_weeks records before the version is confirmed and applied. --- ## Environment variables ``` ANTHROPIC_API_KEY= POCKETBASE_URL= POCKETBASE_ADMIN_EMAIL= POCKETBASE_ADMIN_PASSWORD= CURRICULUM_PORT=3003 ``` --- ## Dependencies ```json { "dependencies": { "fastify": "^4", "@anthropic-ai/sdk": "^0.24", "pocketbase": "^0.21", "zod": "^3", "uuid": "^9" } } ``` --- ## TypeScript strict mode requirements - No `any` types - KBSnapshot typed explicitly — validated against PocketBase response - CurriculumDraft validated through Zod before any PocketBase writes - Topological sort implemented with explicit typed graph structure --- ## What this service does NOT do - Does not generate micro learnings → generation service - Does not record completions → progress service - Does not serve KB content → frontend reads PocketBase directly - Does not handle auth → PocketBase + frontend --- ## Testing checkpoints 1. Generate curriculum from a KB with 5+ themes → confirm 26 weeks produced 2. Confirm all themes appear at least once 3. Confirm topic order within a week respects prerequisites 4. Add a new theme to KB → trigger regeneration → confirm employee at week 5 sees weeks 1–5 unchanged, weeks 6–26 updated 5. Advance employee through week 26 → confirm cycle 2 starts with varied sequence 6. Admin edits week 3 theme → confirm patch updates draft before confirmation