refactor(help): rename helpLevel terminology to hadHelp boolean
The codebase previously used "help level" terminology (HelpLevel type, helpLevelUsed field, helpLevelWeight function) which implied a graduated scale. Since the system now only tracks whether help was used or not, this renames everything to use proper boolean terminology. Changes: - Delete HelpLevel type, use boolean directly - Rename helpLevelUsed → hadHelp in SlotResult - Rename lastHelpLevel → lastHadHelp in PlayerSkillMastery schema - Rename helpLevelWeight() → helpWeight() with boolean parameter - Update all components, tests, stories, and documentation - Add database migration for column rename 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
446678799c
commit
c522620e46
|
|
@ -68,7 +68,7 @@ export interface SlotResult {
|
|||
timestamp: number;
|
||||
responseTimeMs: number;
|
||||
userAnswer: number | null;
|
||||
helpLevel: 0 | 1; // Boolean: 0 = no help, 1 = used help
|
||||
hadHelp: boolean; // Whether student used help during this problem
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -223,13 +223,13 @@ export function updateOnIncorrect(
|
|||
* Adjust observation weight based on whether help was used.
|
||||
* Using help = less confident the student really knows it.
|
||||
*
|
||||
* Note: Help is binary (0 = no help, 1 = used help).
|
||||
* Note: Help is a boolean (hadHelp: true = used help, false = no help).
|
||||
* We can't determine which skill needed help for multi-skill problems,
|
||||
* so we apply the discount uniformly and let conjunctive BKT identify
|
||||
* weak skills from aggregated evidence.
|
||||
*/
|
||||
export function helpLevelWeight(helpLevel: 0 | 1): number {
|
||||
return helpLevel === 0 ? 1.0 : 0.5; // 50% weight for helped answers
|
||||
export function helpWeight(hadHelp: boolean): number {
|
||||
return hadHelp ? 0.5 : 1.0; // 50% weight for helped answers
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -345,7 +345,7 @@ export function getUncertaintyRange(
|
|||
import type { ProblemResultWithContext } from "../session-planner";
|
||||
import { getDefaultParams, type BktParams } from "./skill-priors";
|
||||
import { updateOnCorrect, updateOnIncorrect } from "./conjunctive-bkt";
|
||||
import { helpLevelWeight, responseTimeWeight } from "./evidence-quality";
|
||||
import { helpWeight, responseTimeWeight } from "./evidence-quality";
|
||||
import { calculateConfidence, getUncertaintyRange } from "./confidence";
|
||||
|
||||
export interface BktComputeOptions {
|
||||
|
|
@ -428,12 +428,12 @@ export function computeBktFromHistory(
|
|||
});
|
||||
|
||||
// Calculate evidence weight
|
||||
const helpWeight = helpLevelWeight(result.helpLevel);
|
||||
const helpW = helpWeight(result.hadHelp);
|
||||
const rtWeight = responseTimeWeight(
|
||||
result.responseTimeMs,
|
||||
result.isCorrect,
|
||||
);
|
||||
const evidenceWeight = helpWeight * rtWeight;
|
||||
const evidenceWeight = helpW * rtWeight;
|
||||
|
||||
// Compute updates
|
||||
const updates = result.isCorrect
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
-- Custom SQL migration file, put your code below! --
|
||||
-- Rename last_help_level to last_had_help (terminology change: "help level" is no longer accurate since it's a boolean)
|
||||
ALTER TABLE `player_skill_mastery` RENAME COLUMN `last_help_level` TO `last_had_help`;
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -281,6 +281,13 @@
|
|||
"when": 1766275200000,
|
||||
"tag": "0039_add_player_archived",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 40,
|
||||
"version": "6",
|
||||
"when": 1766320890578,
|
||||
"tag": "0040_rename_last_help_level_to_last_had_help",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -388,7 +388,7 @@ function InteractiveSessionDemo() {
|
|||
partNumber: (plan.currentPartIndex + 1) as 1 | 2 | 3,
|
||||
timestamp: new Date(),
|
||||
// Default help tracking fields if not provided
|
||||
helpLevelUsed: result.helpLevelUsed ?? 0,
|
||||
hadHelp: result.hadHelp ?? false,
|
||||
incorrectAttempts: result.incorrectAttempts ?? 0,
|
||||
helpTrigger: result.helpTrigger ?? 'none',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -899,7 +899,7 @@ export function ActiveSession({
|
|||
skillsExercised: attemptData.problem.skillsRequired,
|
||||
usedOnScreenAbacus: phase.phase === 'helpMode',
|
||||
incorrectAttempts: 0, // TODO: track this properly
|
||||
helpLevelUsed: phase.phase === 'helpMode' ? 1 : 0,
|
||||
hadHelp: phase.phase === 'helpMode',
|
||||
}
|
||||
|
||||
await onAnswer(result)
|
||||
|
|
|
|||
|
|
@ -463,14 +463,14 @@ function ProblemDetailPopover({
|
|||
</span>
|
||||
</div>
|
||||
|
||||
{/* Help level */}
|
||||
{result && result.helpLevelUsed > 0 && (
|
||||
{/* Help used */}
|
||||
{result?.hadHelp && (
|
||||
<div className={css({ display: 'flex', justifyContent: 'space-between' })}>
|
||||
<span className={css({ color: isDark ? 'gray.400' : 'gray.600' })}>Help used:</span>
|
||||
<span
|
||||
className={css({ fontWeight: 'bold', color: isDark ? 'orange.300' : 'orange.600' })}
|
||||
>
|
||||
Level {result.helpLevelUsed}
|
||||
Yes
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1136,7 +1136,7 @@ export function DetailedProblemCard({
|
|||
result.responseTimeMs > autoPauseStats.thresholdMs &&
|
||||
' (over threshold)'}
|
||||
</span>
|
||||
{result.helpLevelUsed > 0 && <span>Help level: {result.helpLevelUsed}</span>}
|
||||
{result.hadHelp && <span>Used help</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ function createMockResults(
|
|||
skillsExercised: ['basic.directAddition'],
|
||||
usedOnScreenAbacus: partType === 'abacus',
|
||||
timestamp: new Date(Date.now() - (count - i) * 30000),
|
||||
helpLevelUsed: 0,
|
||||
hadHelp: false,
|
||||
incorrectAttempts: 0,
|
||||
}))
|
||||
}
|
||||
|
|
@ -141,7 +141,7 @@ function createSessionHud(config: {
|
|||
skillsExercised: ['basic.directAddition'],
|
||||
usedOnScreenAbacus: pIdx === 0,
|
||||
timestamp: new Date(),
|
||||
helpLevelUsed: 0,
|
||||
hadHelp: false,
|
||||
incorrectAttempts: 0,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -420,13 +420,13 @@ export function ProblemToReview({
|
|||
🧮 Used on-screen abacus
|
||||
</span>
|
||||
)}
|
||||
{result.helpLevelUsed > 0 && (
|
||||
{result.hadHelp && (
|
||||
<span
|
||||
className={css({
|
||||
color: isDark ? 'orange.400' : 'orange.600',
|
||||
})}
|
||||
>
|
||||
💡 Help level: {result.helpLevelUsed}
|
||||
💡 Used help
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ export interface SkillProgress {
|
|||
attempts: number
|
||||
correct: number
|
||||
consecutiveCorrect: number
|
||||
/** Last help level used on this skill (0 or 1) */
|
||||
lastHelpLevel?: number
|
||||
/** Whether help was used when this skill was last practiced */
|
||||
lastHadHelp?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ function createMockSessionPlan(config: {
|
|||
skillsExercised: ['basic.directAddition'],
|
||||
usedOnScreenAbacus: i < 5,
|
||||
timestamp: new Date(Date.now() - (completedCount - i) * 30000),
|
||||
helpLevelUsed: 0,
|
||||
hadHelp: false,
|
||||
incorrectAttempts: 0,
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ export function filterProblemsNeedingAttention(
|
|||
reasons.push('slow')
|
||||
}
|
||||
|
||||
// Check if used help (helpLevelUsed is binary: 0 = no help, 1 = help used)
|
||||
if (problem.result.helpLevelUsed >= 1) {
|
||||
// Check if used help
|
||||
if (problem.result.hadHelp) {
|
||||
reasons.push('help-used')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@ export const playerSkillMastery = sqliteTable(
|
|||
.$defaultFn(() => new Date()),
|
||||
|
||||
/**
|
||||
* Last help level used on this skill (0 = no help, 1 = used help)
|
||||
* Whether help was used the last time this skill was practiced
|
||||
*/
|
||||
lastHelpLevel: integer('last_help_level').notNull().default(0),
|
||||
lastHadHelp: integer('last_had_help', { mode: 'boolean' }).notNull().default(false),
|
||||
},
|
||||
(table) => ({
|
||||
/** Index for fast lookups by playerId */
|
||||
|
|
|
|||
|
|
@ -213,16 +213,6 @@ export interface SessionAdjustment {
|
|||
previousHealth: SessionHealth
|
||||
}
|
||||
|
||||
/**
|
||||
* Help level used during a problem (boolean)
|
||||
* - 0: No help requested
|
||||
* - 1: Help was used (interactive abacus overlay shown)
|
||||
*
|
||||
* Note: The system previously defined levels 0-3, but only 0/1 are ever recorded.
|
||||
* BKT uses conjunctive blame attribution to identify weak skills.
|
||||
*/
|
||||
export type HelpLevel = 0 | 1
|
||||
|
||||
/**
|
||||
* Result of a single problem slot
|
||||
*/
|
||||
|
|
@ -241,8 +231,8 @@ export interface SlotResult {
|
|||
|
||||
// ---- Help Tracking (for feedback loop) ----
|
||||
|
||||
/** Maximum help level used during this problem (0 = no help) */
|
||||
helpLevelUsed: HelpLevel
|
||||
/** Whether the student used help during this problem */
|
||||
hadHelp: boolean
|
||||
|
||||
/** Number of incorrect attempts before getting the right answer */
|
||||
incorrectAttempts: number
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { BKT_THRESHOLDS } from '../config/bkt-integration'
|
|||
import type { ProblemResultWithContext } from '../session-planner'
|
||||
import { calculateConfidence, getUncertaintyRange } from './confidence'
|
||||
import { type BlameMethod, updateOnCorrect, updateOnIncorrectWithMethod } from './conjunctive-bkt'
|
||||
import { helpLevelWeight, responseTimeWeight } from './evidence-quality'
|
||||
import { helpWeight, responseTimeWeight } from './evidence-quality'
|
||||
import { getDefaultParams } from './skill-priors'
|
||||
import type {
|
||||
BktComputeOptions,
|
||||
|
|
@ -125,10 +125,10 @@ export function computeBktFromHistory(
|
|||
}
|
||||
})
|
||||
|
||||
// Calculate evidence weight based on help level and response time
|
||||
const helpWeight = helpLevelWeight(result.helpLevelUsed)
|
||||
// Calculate evidence weight based on help usage and response time
|
||||
const helpW = helpWeight(result.hadHelp)
|
||||
const rtWeight = responseTimeWeight(result.responseTimeMs, result.isCorrect)
|
||||
const evidenceWeight = helpWeight * rtWeight
|
||||
const evidenceWeight = helpW * rtWeight
|
||||
|
||||
// Compute BKT updates (conjunctive model)
|
||||
const blameMethod = opts.blameMethod ?? 'heuristic'
|
||||
|
|
|
|||
|
|
@ -3,26 +3,19 @@
|
|||
*
|
||||
* Not all observations are equally informative. We adjust the weight
|
||||
* of evidence based on:
|
||||
* - Help level: Using help = less confident the student really knows it
|
||||
* - Help usage: Using help = less confident the student really knows it
|
||||
* - Response time: Fast correct = strong mastery, slow correct = struggled
|
||||
*/
|
||||
|
||||
import type { HelpLevel } from '@/db/schema/session-plans'
|
||||
|
||||
/**
|
||||
* Adjust observation weight based on whether help was used.
|
||||
* Using help = less confident the student really knows it.
|
||||
*
|
||||
* @param helpLevel - 0 = no help, 1 = help used
|
||||
* @param hadHelp - true if help was used, false otherwise
|
||||
* @returns Weight multiplier [0.5, 1.0]
|
||||
*/
|
||||
export function helpLevelWeight(helpLevel: HelpLevel): number {
|
||||
// Guard against unexpected values (legacy data, JSON parsing issues)
|
||||
if (helpLevel !== 0 && helpLevel !== 1) {
|
||||
return 1.0
|
||||
}
|
||||
// 0 = no help (full evidence), 1 = used help (50% evidence)
|
||||
return helpLevel === 0 ? 1.0 : 0.5
|
||||
export function helpWeight(hadHelp: boolean): number {
|
||||
return hadHelp ? 0.5 : 1.0
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -67,13 +60,13 @@ export function responseTimeWeight(
|
|||
}
|
||||
|
||||
/**
|
||||
* Combined evidence weight from help and response time.
|
||||
* Combined evidence weight from help usage and response time.
|
||||
*/
|
||||
export function combinedEvidenceWeight(
|
||||
helpLevel: HelpLevel,
|
||||
hadHelp: boolean,
|
||||
responseTimeMs: number,
|
||||
isCorrect: boolean,
|
||||
expectedTimeMs: number = 5000
|
||||
): number {
|
||||
return helpLevelWeight(helpLevel) * responseTimeWeight(responseTimeMs, isCorrect, expectedTimeMs)
|
||||
return helpWeight(hadHelp) * responseTimeWeight(responseTimeMs, isCorrect, expectedTimeMs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export { getDefaultParams, getSkillCategory } from './skill-priors'
|
|||
// Evidence quality (for advanced use cases)
|
||||
export {
|
||||
combinedEvidenceWeight,
|
||||
helpLevelWeight,
|
||||
helpWeight,
|
||||
responseTimeWeight,
|
||||
} from './evidence-quality'
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { db, schema } from '@/db'
|
|||
import type { NewPlayerCurriculum, PlayerCurriculum } from '@/db/schema/player-curriculum'
|
||||
import type { NewPlayerSkillMastery, PlayerSkillMastery } from '@/db/schema/player-skill-mastery'
|
||||
import type { PracticeSession } from '@/db/schema/practice-sessions'
|
||||
import type { HelpLevel } from '@/db/schema/session-plans'
|
||||
import {
|
||||
isTutorialSatisfied,
|
||||
type NewSkillTutorialProgress,
|
||||
|
|
@ -261,20 +260,18 @@ export async function recordSkillAttempt(
|
|||
}
|
||||
|
||||
/**
|
||||
* Record a skill attempt with help level tracking
|
||||
* Record a skill attempt with help tracking
|
||||
*
|
||||
* Updates the lastPracticedAt timestamp and tracks whether help was used.
|
||||
* BKT handles mastery estimation via evidence weighting (helped answers get 0.5x weight).
|
||||
*
|
||||
* NOTE: The old reinforcement system (based on help levels 2+) has been removed.
|
||||
* Only boolean help (0 or 1) is recorded. BKT's conjunctive blame attribution
|
||||
* identifies weak skills from multi-skill problems.
|
||||
* NOTE: BKT's conjunctive blame attribution identifies weak skills from multi-skill problems.
|
||||
*/
|
||||
export async function recordSkillAttemptWithHelp(
|
||||
playerId: string,
|
||||
skillId: string,
|
||||
_isCorrect: boolean,
|
||||
helpLevel: HelpLevel,
|
||||
hadHelp: boolean,
|
||||
_responseTimeMs?: number
|
||||
): Promise<PlayerSkillMastery> {
|
||||
const existing = await getSkillMastery(playerId, skillId)
|
||||
|
|
@ -286,7 +283,7 @@ export async function recordSkillAttemptWithHelp(
|
|||
.set({
|
||||
lastPracticedAt: now,
|
||||
updatedAt: now,
|
||||
lastHelpLevel: helpLevel,
|
||||
lastHadHelp: hadHelp,
|
||||
})
|
||||
.where(eq(schema.playerSkillMastery.id, existing.id))
|
||||
|
||||
|
|
@ -299,7 +296,7 @@ export async function recordSkillAttemptWithHelp(
|
|||
skillId,
|
||||
isPracticing: true, // skill is being practiced
|
||||
lastPracticedAt: now,
|
||||
lastHelpLevel: helpLevel,
|
||||
lastHadHelp: hadHelp,
|
||||
}
|
||||
|
||||
await db.insert(schema.playerSkillMastery).values(newRecord)
|
||||
|
|
@ -314,7 +311,7 @@ export async function recordSkillAttemptWithHelp(
|
|||
export async function recordSkillAttemptsWithHelp(
|
||||
playerId: string,
|
||||
skillResults: Array<{ skillId: string; isCorrect: boolean }>,
|
||||
helpLevel: HelpLevel,
|
||||
hadHelp: boolean,
|
||||
responseTimeMs?: number
|
||||
): Promise<PlayerSkillMastery[]> {
|
||||
const results: PlayerSkillMastery[] = []
|
||||
|
|
@ -324,7 +321,7 @@ export async function recordSkillAttemptsWithHelp(
|
|||
playerId,
|
||||
skillId,
|
||||
isCorrect,
|
||||
helpLevel,
|
||||
hadHelp,
|
||||
responseTimeMs
|
||||
)
|
||||
results.push(result)
|
||||
|
|
|
|||
|
|
@ -805,7 +805,7 @@ export async function recordSlotResult(
|
|||
await recordSkillAttemptsWithHelp(
|
||||
plan.playerId,
|
||||
skillResults,
|
||||
result.helpLevelUsed,
|
||||
result.hadHelp,
|
||||
result.responseTimeMs
|
||||
)
|
||||
} catch (skillError) {
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ function createResultFromProblem(
|
|||
skillsExercised: problem.skillsUsed,
|
||||
usedOnScreenAbacus: false,
|
||||
timestamp,
|
||||
helpLevelUsed: 0,
|
||||
hadHelp: false,
|
||||
incorrectAttempts: 0,
|
||||
sessionCompletedAt: timestamp,
|
||||
partType: 'abacus',
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ function createResult(
|
|||
skillsExercised,
|
||||
usedOnScreenAbacus: false,
|
||||
timestamp,
|
||||
helpLevelUsed: 0,
|
||||
hadHelp: false,
|
||||
incorrectAttempts: 0,
|
||||
sessionCompletedAt: timestamp,
|
||||
partType: 'abacus',
|
||||
|
|
@ -174,7 +174,7 @@ function generateSyntheticResults(
|
|||
isCorrect,
|
||||
responseTimeMs: containsWeakSkill ? 8000 : 3000, // Slower on weak skill
|
||||
usedOnScreenAbacus: false,
|
||||
helpLevelUsed: 0,
|
||||
hadHelp: false,
|
||||
incorrectAttempts: 0,
|
||||
sessionCompletedAt: new Date(baseTime + i * 5000),
|
||||
partType: 'abacus',
|
||||
|
|
|
|||
|
|
@ -142,9 +142,7 @@ export async function initializeSkillMastery(
|
|||
playerId,
|
||||
skillId,
|
||||
isPracticing,
|
||||
needsReinforcement: false,
|
||||
lastHelpLevel: 0,
|
||||
reinforcementStreak: 0,
|
||||
lastHadHelp: false,
|
||||
createdAt: now,
|
||||
lastPracticedAt: null,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -158,9 +158,9 @@ export class JourneyRunner {
|
|||
responseTimeMs: answer.responseTimeMs,
|
||||
skillsExercised: answer.skillsChallenged,
|
||||
usedOnScreenAbacus: false,
|
||||
helpLevelUsed: answer.helpLevelUsed,
|
||||
hadHelp: answer.hadHelp,
|
||||
incorrectAttempts: answer.isCorrect ? 0 : 1,
|
||||
helpTrigger: answer.helpLevelUsed > 0 ? 'manual' : 'none',
|
||||
helpTrigger: answer.hadHelp ? 'manual' : 'none',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
* - Help provides additive bonus to probability
|
||||
*/
|
||||
|
||||
import type { GeneratedProblem, HelpLevel } from '@/db/schema/session-plans'
|
||||
import type { GeneratedProblem } from '@/db/schema/session-plans'
|
||||
import type { SeededRandom } from './SeededRandom'
|
||||
import type { SimulatedAnswer, StudentProfile } from './types'
|
||||
|
||||
|
|
@ -175,20 +175,20 @@ export class SimulatedStudent {
|
|||
}
|
||||
|
||||
// Determine if student uses help (binary)
|
||||
const helpLevelUsed = this.selectHelpLevel()
|
||||
const hadHelp = this.selectHelpUsage()
|
||||
|
||||
// Calculate answer probability using Hill function + conjunctive model
|
||||
const answerProbability = this.calculateAnswerProbability(skillsChallenged, helpLevelUsed)
|
||||
const answerProbability = this.calculateAnswerProbability(skillsChallenged, hadHelp)
|
||||
|
||||
const isCorrect = this.rng.chance(answerProbability)
|
||||
|
||||
// Calculate response time
|
||||
const responseTimeMs = this.calculateResponseTime(skillsChallenged, helpLevelUsed, isCorrect)
|
||||
const responseTimeMs = this.calculateResponseTime(skillsChallenged, hadHelp, isCorrect)
|
||||
|
||||
return {
|
||||
isCorrect,
|
||||
responseTimeMs,
|
||||
helpLevelUsed,
|
||||
hadHelp,
|
||||
skillsChallenged,
|
||||
fatigue,
|
||||
}
|
||||
|
|
@ -202,7 +202,7 @@ export class SimulatedStudent {
|
|||
*
|
||||
* Help bonus is additive (applied after the product).
|
||||
*/
|
||||
private calculateAnswerProbability(skillIds: string[], helpLevel: HelpLevel): number {
|
||||
private calculateAnswerProbability(skillIds: string[], hadHelp: boolean): number {
|
||||
if (skillIds.length === 0) {
|
||||
// Basic problems (no special skills) almost always correct
|
||||
return 0.95
|
||||
|
|
@ -220,7 +220,8 @@ export class SimulatedStudent {
|
|||
}
|
||||
|
||||
// Add help bonus (additive, not multiplicative)
|
||||
const helpBonus = this.profile.helpBonuses[helpLevel]
|
||||
// helpBonuses[0] = no help, helpBonuses[1] = with help
|
||||
const helpBonus = this.profile.helpBonuses[hadHelp ? 1 : 0]
|
||||
probability += helpBonus
|
||||
|
||||
// Clamp to valid probability range
|
||||
|
|
@ -229,12 +230,12 @@ export class SimulatedStudent {
|
|||
}
|
||||
|
||||
/**
|
||||
* Select whether student uses help (binary).
|
||||
* Select whether student uses help.
|
||||
* Based on profile's helpUsageProbabilities [P(no help), P(help)].
|
||||
*/
|
||||
private selectHelpLevel(): HelpLevel {
|
||||
private selectHelpUsage(): boolean {
|
||||
const [pNoHelp] = this.profile.helpUsageProbabilities
|
||||
return this.rng.next() < pNoHelp ? 0 : 1
|
||||
return this.rng.next() >= pNoHelp
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -242,7 +243,7 @@ export class SimulatedStudent {
|
|||
*/
|
||||
private calculateResponseTime(
|
||||
skillIds: string[],
|
||||
helpLevel: HelpLevel,
|
||||
hadHelp: boolean,
|
||||
isCorrect: boolean
|
||||
): number {
|
||||
const base = this.profile.baseResponseTimeMs
|
||||
|
|
@ -258,7 +259,7 @@ export class SimulatedStudent {
|
|||
const exposureFactor = 2.0 - Math.min(1.0, avgExposure / (this.profile.halfMaxExposure * 2))
|
||||
|
||||
// Help usage adds time (reading hints, etc.)
|
||||
const helpFactor = 1.0 + helpLevel * 0.25
|
||||
const helpFactor = hadHelp ? 1.25 : 1.0
|
||||
|
||||
// Incorrect answers: sometimes faster (gave up), sometimes slower (struggled)
|
||||
const correctnessFactor = isCorrect ? 1.0 : this.rng.chance(0.5) ? 0.7 : 1.4
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
* Type definitions for the BKT validation test infrastructure.
|
||||
*/
|
||||
|
||||
import type { HelpLevel } from '@/db/schema/session-plans'
|
||||
import type { BlameMethod } from '@/lib/curriculum/bkt'
|
||||
import type { ProblemGenerationMode } from '@/lib/curriculum/config/bkt-integration'
|
||||
|
||||
|
|
@ -114,8 +113,8 @@ export interface SimulatedAnswer {
|
|||
isCorrect: boolean
|
||||
/** Time taken to answer in milliseconds */
|
||||
responseTimeMs: number
|
||||
/** Help level used (0 = no help, 1 = used help) */
|
||||
helpLevelUsed: HelpLevel
|
||||
/** Whether help was used during this problem */
|
||||
hadHelp: boolean
|
||||
/** Skills that were actually challenged by this problem */
|
||||
skillsChallenged: string[]
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ describe('Session Targeting Trace', () => {
|
|||
await recordSlotResult(plan1.id, part.partNumber, slot.index, {
|
||||
isCorrect: answer.isCorrect,
|
||||
responseTimeMs: answer.responseTimeMs,
|
||||
helpLevelUsed: answer.helpLevelUsed,
|
||||
hadHelp: answer.hadHelp,
|
||||
skillsExercised: answer.skillsChallenged,
|
||||
})
|
||||
}
|
||||
|
|
@ -189,7 +189,7 @@ describe('Session Targeting Trace', () => {
|
|||
await recordSlotResult(classicPlan1.id, part.partNumber, slot.index, {
|
||||
isCorrect: answer.isCorrect,
|
||||
responseTimeMs: answer.responseTimeMs,
|
||||
helpLevelUsed: answer.helpLevelUsed,
|
||||
hadHelp: answer.hadHelp,
|
||||
skillsExercised: answer.skillsChallenged,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue