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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 121x 121x 121x 121x 1821x 1821x 1821x 1821x 1821x 121x 121x 121x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 33x 33x 33x 33x 33x 33x 11x 11x 7x 11x 2x 11x 2x 2x 11x 11x 22x 22x 22x 33x 11x 11x 11x 11x 11x 11x 11x 33x 33x 7x 7x 7x 7x 33x 33x 4x 4x 4x 4x 4x 4x 4x 21x 21x 21x 21x 21x 21x 21x 21x 4x 4x 4x 18x 18x 18x 18x 290x 290x 290x 290x 147x 147x 290x 143x 143x 143x 290x 18x 18x 4x 4x 4x 18x 18x 18x 18x 18x 18x 18x 18x 4x 4x 4x 18x 18x 18x 18x 18x 18x 18x 4x 4x 4x 4x 21x 21x 21x 75x 75x 75x 75x 75x 75x 75x 75x 75x 3x 3x 75x 18x 1x 1x 4x 15x 15x 15x 15x 15x 15x 15x 1x 1x 15x 33x 33x | import { applyLearning, bktUpdate } from '../curriculum/bkt/bkt-core'
import { getDefaultParams } from '../curriculum/bkt/skill-priors'
import { BKT_THRESHOLDS } from '../curriculum/config/bkt-integration'
import type { TargetClassification } from './types'
/**
* Simulate BKT computation for a sequence of correct/incorrect answers.
* Used to predict what pKnown will result from a given sequence.
*
* IMPORTANT: This matches the actual BKT computation behavior:
* - CORRECT: bktUpdate + applyLearning (student may have learned from this)
* - INCORRECT: bktUpdate only (no learning transition on failure)
*/
export function simulateBktSequence(skillId: string, sequence: boolean[]): number {
const params = getDefaultParams(skillId)
let pKnown = params.pInit
for (const isCorrect of sequence) {
const updated = bktUpdate(pKnown, isCorrect, params)
// Only apply learning transition on CORRECT answers
// (matches updateOnCorrect vs updateOnIncorrect behavior)
pKnown = isCorrect ? applyLearning(updated, params.pLearn) : updated
}
return pKnown
}
/**
* Design a sequence of correct/incorrect answers that will reliably produce
* the target BKT classification.
*
* Key insight: The ORDER of correct/incorrect matters more than the ratio.
* - Ending with correct answers → higher pKnown
* - Ending with incorrect answers → lower pKnown
*
* IMPORTANT: BKT dynamics are "swingy" - a single correct can push pKnown
* from 0.3 to ~0.7, and a single incorrect can drop from 0.7 to ~0.3.
* The "developing" range (0.5-0.8) is narrow and requires careful calibration.
*/
export function designSequenceForClassification(
skillId: string,
problemCount: number,
target: TargetClassification
): boolean[] {
// For very few problems, use simple patterns
if (problemCount <= 3) {
switch (target) {
case 'strong':
return Array(problemCount).fill(true)
case 'weak':
return Array(problemCount).fill(false)
case 'developing':
// All correct for tiny counts since multi-skill coupling pulls down
return Array(problemCount).fill(true)
}
}
// For longer sequences, use empirically-tuned patterns
switch (target) {
case 'strong': {
// 85% correct, ending with streak of correct
const incorrectCount = Math.max(1, Math.floor(problemCount * 0.15))
return [
...Array(incorrectCount).fill(false),
...Array(problemCount - incorrectCount).fill(true),
]
}
case 'weak': {
// 90% incorrect, ending with long streak of incorrect
const correctCount = Math.max(1, Math.floor(problemCount * 0.1))
return [...Array(correctCount).fill(true), ...Array(problemCount - correctCount).fill(false)]
}
case 'developing': {
// The developing range (0.5-0.8) is narrow and BKT is swingy.
// Try multiple pattern types to find one that lands in range.
// Pattern generators to try (in order of preference)
const patternGenerators = [
// Pattern 1: End with exactly 1 correct after many incorrect
(n: number, correct: number) => {
const endCorrect = 1
const startCorrect = correct - endCorrect
return [
...Array(startCorrect).fill(true),
...Array(n - correct).fill(false),
...Array(endCorrect).fill(true),
]
},
// Pattern 2: Alternating ending with correct
(n: number, correct: number) => {
const seq: boolean[] = []
let remainingCorrect = correct
let remainingIncorrect = n - correct
while (remainingCorrect > 0 || remainingIncorrect > 0) {
if (
remainingIncorrect > 0 &&
(remainingIncorrect > remainingCorrect || remainingCorrect === 0)
) {
seq.push(false)
remainingIncorrect--
} else if (remainingCorrect > 0) {
seq.push(true)
remainingCorrect--
}
}
return seq
},
// Pattern 3: Front-loaded correct, then incorrect, ending with 1 correct
(n: number, correct: number) => {
const endCorrect = 1
const frontCorrect = correct - endCorrect
return [
...Array(frontCorrect).fill(true),
...Array(n - correct).fill(false),
...Array(endCorrect).fill(true),
]
},
// Pattern 4: Sandwich - incorrect, correct, incorrect
(n: number, correct: number) => {
const thirdIncorrect = Math.floor((n - correct) / 2)
return [
...Array(thirdIncorrect).fill(false),
...Array(correct).fill(true),
...Array(n - correct - thirdIncorrect).fill(false),
]
},
]
// Try different correct counts with each pattern
for (const correctRatio of [0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7]) {
const correctCount = Math.max(1, Math.round(problemCount * correctRatio))
for (const generatePattern of patternGenerators) {
const sequence = generatePattern(problemCount, correctCount)
// Verify sequence length is correct
if (sequence.length !== problemCount) continue
const pKnown = simulateBktSequence(skillId, sequence)
// Check if it lands in developing range
if (pKnown >= BKT_THRESHOLDS.weak && pKnown < BKT_THRESHOLDS.strong) {
return sequence
}
}
}
// If we still can't find a pattern, try edge cases
for (let correct = 1; correct < problemCount; correct++) {
const sequence = [
...Array(correct - 1).fill(true),
...Array(problemCount - correct).fill(false),
true, // End with one correct
]
const pKnown = simulateBktSequence(skillId, sequence)
if (pKnown >= BKT_THRESHOLDS.weak && pKnown < BKT_THRESHOLDS.strong) {
return sequence
}
}
// Ultimate fallback: Just end with 1 correct after all incorrect
return [...Array(problemCount - 1).fill(false), true]
}
}
}
|