All files / web/src/lib/curriculum/bkt evidence-quality.ts

87.5% Statements 63/72
40% Branches 4/10
66.66% Functions 2/3
87.5% Lines 63/72

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 732x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 28285x 28285x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 28285x 28285x 28285x 28285x 28285x 28285x 28285x 28285x 28285x 28285x     28285x 28285x 28285x 28285x 13837x 13837x 13837x 28285x 14448x 14448x 14448x 14448x 28285x 2x 2x 2x 2x 2x                
/**
 * Evidence Quality Modifiers
 *
 * Not all observations are equally informative. We adjust the weight
 * of evidence based on:
 * - Help usage: Using help = less confident the student really knows it
 * - Response time: Fast correct = strong mastery, slow correct = struggled
 */
 
/**
 * Adjust observation weight based on whether help was used.
 * Using help = less confident the student really knows it.
 *
 * @param hadHelp - true if help was used, false otherwise
 * @returns Weight multiplier [0.5, 1.0]
 */
export function helpWeight(hadHelp: boolean): number {
  return hadHelp ? 0.5 : 1.0
}
 
/**
 * Adjust observation weight based on response time.
 *
 * The intuition:
 * - Fast correct → strong evidence of mastery (automatic recall)
 * - Slow correct → might have struggled (mastery less certain)
 * - Fast incorrect → careless slip (don't penalize too much)
 * - Slow incorrect → genuine confusion (stronger negative evidence)
 *
 * @param responseTimeMs - Time to answer in milliseconds
 * @param isCorrect - Whether the answer was correct
 * @param expectedTimeMs - Expected response time (default 5000ms)
 * @returns Weight multiplier for the observation
 */
export function responseTimeWeight(
  responseTimeMs: number,
  isCorrect: boolean,
  expectedTimeMs: number = 5000
): number {
  // Guard against invalid values that would produce NaN
  if (
    typeof responseTimeMs !== 'number' ||
    !Number.isFinite(responseTimeMs) ||
    responseTimeMs <= 0
  ) {
    return 1.0 // Neutral weight for invalid data
  }
 
  const ratio = responseTimeMs / expectedTimeMs
 
  if (isCorrect) {
    if (ratio < 0.5) return 1.2 // Very fast - strong mastery
    if (ratio > 2.0) return 0.8 // Very slow - struggled
    return 1.0
  } else {
    if (ratio < 0.3) return 0.5 // Very fast error - careless slip
    if (ratio > 2.0) return 1.2 // Very slow error - genuine confusion
    return 1.0
  }
}
 
/**
 * Combined evidence weight from help usage and response time.
 */
export function combinedEvidenceWeight(
  hadHelp: boolean,
  responseTimeMs: number,
  isCorrect: boolean,
  expectedTimeMs: number = 5000
): number {
  return helpWeight(hadHelp) * responseTimeWeight(responseTimeMs, isCorrect, expectedTimeMs)
}