Files
learning-platform/docs/curriculum-spec.md
RaymondVerhoef 472685f0d7 Add specifications for gamification, generation, and R42 chat services
- Introduced gamification service spec detailing responsibilities, API surface, XP calculation, levels, streaks, badges, milestone cards, and heatmap data.
- Added generation service spec outlining the process for generating micro learning content, including API endpoints, AI call configuration, prompt strategies, and error handling.
- Created R42 chat service spec covering chatbot interactions, retrieval pipeline, prompt construction, response generation, and stateless design principles.
2026-05-23 18:13:08 +02:00

9.7 KiB
Raw Blame History

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:

{
  "triggeredBy": "string",
  "reason": "new_topics" | "manual"
}

Response (202 Accepted):

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

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

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

{
  "completedWeek": 7
}

Curriculum generation

Input

All published Themes and Topics retrieved from PocketBase:

type KBSnapshot = {
  themes: {
    id: string
    title: string
    description: string
    topics: {
      id: string
      title: string
      complexityWeight: number       // 15
      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 (1545 minutes per session)
- Return exactly 26 week slots

User prompt:

Knowledge base snapshot:
{KBSnapshot as JSON}

Generate a 26-week curriculum schedule.

Output schema:

type CurriculumDraft = {
  weeks: {
    weekNumber: number            // 126
    themeId: string
    topicIds: string[]            // ordered subset of theme's topics
    estimatedDurationMinutes: number
    rationale: string             // one sentence — shown to admin in preview
  }[]
}

AI call configuration:

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

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

{
  "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 15 unchanged, weeks 626 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