From 11d48465d710d0293ebf41f64b4fd0f1f03d8bf8 Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Sat, 20 Dec 2025 09:39:38 -0600 Subject: [PATCH] fix(dashboard): compute skill stats from session results in curriculum API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard fetches skills from /api/curriculum/[playerId] which was returning raw playerSkillMastery records with persisted attempts/correct values (always 0 for seeded data). Updated the API to compute skill stats from session results, consistent with the recent single-source-of-truth refactor. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../app/api/curriculum/[playerId]/route.ts | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/api/curriculum/[playerId]/route.ts b/apps/web/src/app/api/curriculum/[playerId]/route.ts index 30a53e80..98d18472 100644 --- a/apps/web/src/app/api/curriculum/[playerId]/route.ts +++ b/apps/web/src/app/api/curriculum/[playerId]/route.ts @@ -12,6 +12,7 @@ import { getRecentSessions, upsertPlayerCurriculum, } from '@/lib/curriculum/progress-manager' +import { getRecentSessionResults } from '@/lib/curriculum/session-planner' interface RouteParams { params: Promise<{ playerId: string }> @@ -28,12 +29,43 @@ export async function GET(_request: Request, { params }: RouteParams) { return NextResponse.json({ error: 'Player ID required' }, { status: 400 }) } - const [curriculum, skills, recentSessions] = await Promise.all([ + const [curriculum, rawSkills, recentSessions, sessionResults] = await Promise.all([ getPlayerCurriculum(playerId), getAllSkillMastery(playerId), getRecentSessions(playerId, 10), + getRecentSessionResults(playerId, 100), ]) + // Compute skill stats from session results (single source of truth) + const skillStats = new Map() + for (const result of sessionResults) { + for (const skillId of result.skillsExercised) { + if (!skillStats.has(skillId)) { + skillStats.set(skillId, { attempts: 0, correct: 0, responseTimes: [] }) + } + const stats = skillStats.get(skillId)! + stats.attempts++ + if (result.isCorrect) { + stats.correct++ + } + if (result.responseTimeMs > 0) { + stats.responseTimes.push(result.responseTimeMs) + } + } + } + + // Enrich skills with computed stats + const skills = rawSkills.map((skill) => { + const stats = skillStats.get(skill.skillId) + return { + ...skill, + attempts: stats?.attempts ?? 0, + correct: stats?.correct ?? 0, + totalResponseTimeMs: stats?.responseTimes.reduce((a, b) => a + b, 0) ?? 0, + responseTimeCount: stats?.responseTimes.length ?? 0, + } + }) + return NextResponse.json({ curriculum, skills,