All files / web/src/app/api/settings/bkt/aggregate route.ts

0% Statements 0/102
0% Branches 0/1
0% Functions 0/1
0% Lines 0/102

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103                                                                                                                                                                                                             
import { NextResponse } from 'next/server'
import { db } from '@/db'
import { players } from '@/db/schema'
import { withAuth } from '@/lib/auth/withAuth'
import {
  computeBktFromHistory,
  DEFAULT_BKT_OPTIONS,
  type SkillBktResult,
} from '@/lib/curriculum/bkt'
import { BKT_THRESHOLDS } from '@/lib/curriculum/config/bkt-integration'
import { getRecentSessionResults } from '@/lib/curriculum/session-planner'

/**
 * GET /api/settings/bkt/aggregate
 *
 * Returns aggregate BKT stats across all students.
 *
 * Query params:
 * - threshold: confidence threshold (default from BKT_THRESHOLDS.confidence)
 */
export const GET = withAuth(async (request) => {
  try {
    const { searchParams } = new URL(request.url)
    const threshold = parseFloat(searchParams.get('threshold') ?? String(BKT_THRESHOLDS.confidence))

    // Get all players
    const allPlayers = await db.select({ id: players.id }).from(players)

    if (allPlayers.length === 0) {
      return NextResponse.json({
        totalStudents: 0,
        totalSkills: 0,
        weak: 0,
        developing: 0,
        strong: 0,
        // Legacy aliases for backwards compatibility
        struggling: 0,
        learning: 0,
        mastered: 0,
      })
    }

    // Track aggregate counts
    let totalStudents = 0
    let totalSkills = 0
    let weak = 0
    let developing = 0
    let strong = 0

    // Process each player
    for (const player of allPlayers) {
      // Fetch problem history using the session-planner's helper
      const problemHistory = await getRecentSessionResults(player.id, 500)

      if (problemHistory.length === 0) continue

      // Compute BKT
      const bktResult = computeBktFromHistory(problemHistory, {
        ...DEFAULT_BKT_OPTIONS,
        confidenceThreshold: threshold,
      })

      // Count classifications
      totalStudents++
      for (const skill of bktResult.skills) {
        totalSkills++
        const classification = classifySkill(skill, threshold)
        if (classification === 'weak') weak++
        else if (classification === 'developing') developing++
        else if (classification === 'strong') strong++
      }
    }

    return NextResponse.json({
      totalStudents,
      totalSkills,
      weak,
      developing,
      strong,
      // Legacy aliases for backwards compatibility with BktSettingsClient
      struggling: weak,
      learning: developing,
      mastered: strong,
    })
  } catch (error) {
    console.error('Error computing aggregate BKT stats:', error)
    return NextResponse.json({ error: 'Failed to compute stats' }, { status: 500 })
  }
})

function classifySkill(skill: SkillBktResult, threshold: number): 'weak' | 'developing' | 'strong' {
  if (skill.confidence < threshold) {
    return 'developing' // Not enough data - safest default
  }
  if (skill.pKnown >= BKT_THRESHOLDS.strong) {
    return 'strong'
  }
  if (skill.pKnown < BKT_THRESHOLDS.weak) {
    return 'weak'
  }
  return 'developing'
}