All files / web/src/lib/grading updateMasteryProfile.ts

100% Statements 116/116
100% Branches 28/28
100% Functions 2/2
100% Lines 116/116

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 104 105 106 107 108 109 110 111 112 113 114 115 116 1171x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 8x 8x 8x 8x 8x 8x 8x 8x 8x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 8x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 8x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 8x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 2x 2x  
import { randomUUID } from 'crypto'
import { and, eq } from 'drizzle-orm'
import { db } from '@/db'
import { worksheetMastery } from '@/db/schema'
import { SINGLE_CARRY_PATH } from '@/app/create/worksheets/progressionPath'
import type { GradingResult } from '@/lib/ai/gradeWorksheet'
 
/**
 * Update user's mastery profile based on worksheet grading results
 *
 * This function:
 * 1. Finds or creates a mastery record for the suggested step
 * 2. Updates statistics (attempts, accuracy)
 * 3. Marks as mastered if threshold is met
 * 4. Returns whether mastery was achieved
 */
export async function updateMasteryFromGrading(
  userId: string,
  gradingResult: GradingResult
): Promise<{ mastered: boolean; stepId: string }> {
  const { suggestedStepId, accuracy, totalProblems, correctCount } = gradingResult
 
  // Find the step configuration
  const step = SINGLE_CARRY_PATH.find((s) => s.id === suggestedStepId)
  if (!step) {
    console.warn(`Step ${suggestedStepId} not found in progression path`)
    return { mastered: false, stepId: suggestedStepId }
  }
 
  const now = new Date()
 
  // Look up existing mastery record
  const [existing] = await db
    .select()
    .from(worksheetMastery)
    .where(and(eq(worksheetMastery.userId, userId), eq(worksheetMastery.skillId, suggestedStepId)))
    .limit(1)
 
  if (existing) {
    // Update existing record
    const newTotalAttempts = existing.totalAttempts + totalProblems
    const newCorrectAttempts = existing.correctAttempts + correctCount
    const overallAccuracy = newCorrectAttempts / newTotalAttempts
 
    // Check if mastery threshold is met
    const meetsThreshold = overallAccuracy >= step.masteryThreshold
    const meetsMinimum = newTotalAttempts >= step.minimumAttempts
    const isMastered = meetsThreshold && meetsMinimum
 
    await db
      .update(worksheetMastery)
      .set({
        totalAttempts: newTotalAttempts,
        correctAttempts: newCorrectAttempts,
        lastAccuracy: accuracy,
        lastPracticedAt: now,
        isMastered,
        masteredAt: isMastered && !existing.isMastered ? now : existing.masteredAt,
        updatedAt: now,
      })
      .where(eq(worksheetMastery.id, existing.id))
 
    return { mastered: isMastered, stepId: suggestedStepId }
  } else {
    // Create new record
    const overallAccuracy = correctCount / totalProblems
    const meetsThreshold = overallAccuracy >= step.masteryThreshold
    const meetsMinimum = totalProblems >= step.minimumAttempts
    const isMastered = meetsThreshold && meetsMinimum
 
    await db.insert(worksheetMastery).values({
      id: randomUUID(),
      userId,
      skillId: suggestedStepId,
      totalAttempts: totalProblems,
      correctAttempts: correctCount,
      lastAccuracy: accuracy,
      firstAttemptAt: now,
      lastPracticedAt: now,
      isMastered,
      masteredAt: isMastered ? now : null,
      createdAt: now,
      updatedAt: now,
    })
 
    return { mastered: isMastered, stepId: suggestedStepId }
  }
}
 
/**
 * Get user's mastery progress for all steps in the progression path
 *
 * Returns an array of step mastery status, useful for showing progression UI
 */
export async function getMasteryProgress(userId: string) {
  const masteryRecords = await db
    .select()
    .from(worksheetMastery)
    .where(eq(worksheetMastery.userId, userId))
 
  return SINGLE_CARRY_PATH.map((step) => {
    const record = masteryRecords.find((r) => r.skillId === step.id)
 
    return {
      stepId: step.id,
      stepNumber: step.stepNumber,
      name: step.name,
      isMastered: record?.isMastered || false,
      totalAttempts: record?.totalAttempts || 0,
      correctAttempts: record?.correctAttempts || 0,
      lastAccuracy: record?.lastAccuracy || null,
      masteredAt: record?.masteredAt || null,
      lastPracticedAt: record?.lastPracticedAt || null,
    }
  })
}