From 55d49201678fa8ad7f8e7530b13461e5984838c6 Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Wed, 12 Nov 2025 09:09:36 -0600 Subject: [PATCH] fix: properly cycle through problem sets when exceeding unique problem space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Problem**: When requesting more problems than exist in the problem space (e.g., 100 problems from 45 unique 1-digit regrouping problems), the generator would repeat the same problem over and over after exhausting unique problems. **Root Cause**: - Non-interpolate branch: Used `shuffled.slice(0, ...)` in a loop, always taking from the beginning of the array instead of cycling through - Interpolate branch: When exhausting unique problems, would mark problem as "seen" and add it, then continue adding the same problem repeatedly **Fixes**: 1. **Non-interpolate branch** (no progressive difficulty): - Changed from `while` loop with `slice(0, ...)` to simple `for` loop with modulo - Now uses `shuffled[i % shuffled.length]` to cycle through entire array - Example: Problems 0-44 = first shuffle, 45-89 = second shuffle (same order), etc. 2. **Interpolate branch** (progressive difficulty enabled): - When all unique problems exhausted, now clears the `seen` set to start fresh cycle - Maintains progressive difficulty curve across all cycles - Logs cycle count: "Exhausted all 45 unique problems at position 45. Starting cycle 2." - Example: Each cycle maintains easy→hard progression, just repeats the sorted sequence **Testing**: With 1-digit regrouping (45 unique problems), requesting 100 problems now produces: - First 45: All unique problems - Next 45: Complete repeat of the set - Final 10: First 10 problems from third cycle 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../DuplicateWarningBanner.tsx | 117 ++++++++++++++---- .../app/create/worksheets/problemGenerator.ts | 25 ++-- 2 files changed, 108 insertions(+), 34 deletions(-) diff --git a/apps/web/src/app/create/worksheets/components/worksheet-preview/DuplicateWarningBanner.tsx b/apps/web/src/app/create/worksheets/components/worksheet-preview/DuplicateWarningBanner.tsx index c4c89b49..2bbd91e6 100644 --- a/apps/web/src/app/create/worksheets/components/worksheet-preview/DuplicateWarningBanner.tsx +++ b/apps/web/src/app/create/worksheets/components/worksheet-preview/DuplicateWarningBanner.tsx @@ -1,81 +1,146 @@ 'use client' +import { useState } from 'react' import { css } from '@styled/css' import { useWorksheetPreview } from './WorksheetPreviewContext' export function DuplicateWarningBanner() { const { warnings, isDismissed, setIsDismissed } = useWorksheetPreview() + const [showDetails, setShowDetails] = useState(false) if (warnings.length === 0 || isDismissed) { return null } + // Parse warnings to extract actionable items + const firstWarning = warnings[0] + const hasMultipleWarnings = warnings.length > 1 + return (
+ {/* Warning Icon */} +
+ ⚠️ +
+ + {/* Content */}
- ⚠️ - Duplicate Problem Risk + Not Enough Unique Problems
- {warnings.join('\n\n')} + {firstWarning}
+ + {/* Show/Hide Details Toggle */} + {hasMultipleWarnings && ( + + )} + + {/* Detailed warnings (collapsed by default) */} + {showDetails && hasMultipleWarnings && ( +
+ {warnings.slice(1).join('\n\n')} +
+ )}
+ + {/* Prominent Dismiss Button */}
) diff --git a/apps/web/src/app/create/worksheets/problemGenerator.ts b/apps/web/src/app/create/worksheets/problemGenerator.ts index 0cf3bf1b..d9755472 100644 --- a/apps/web/src/app/create/worksheets/problemGenerator.ts +++ b/apps/web/src/app/create/worksheets/problemGenerator.ts @@ -384,6 +384,7 @@ export function generateProblems( // Sample problems based on difficulty curve const result: AdditionProblem[] = [] const seen = new Set() + let cycleCount = 0 // Track how many times we've cycled through all problems for (let i = 0; i < total; i++) { const frac = total <= 1 ? 0 : i / (total - 1) @@ -415,8 +416,15 @@ export function generateProblems( } if (found) break } - // If still not found, allow duplicate + // If still not found, we've exhausted all unique problems + // Reset the seen set and start a new cycle if (!found) { + cycleCount++ + console.log( + `[ADD GEN] Exhausted all ${sortedByDifficulty.length} unique problems at position ${i}. Starting cycle ${cycleCount + 1}.` + ) + seen.clear() + // Use the target problem for this position seen.add(key) } } else { @@ -428,26 +436,27 @@ export function generateProblems( const elapsed = Date.now() - startTime console.log( - `[ADD GEN] Complete: ${result.length} problems in ${elapsed}ms (0 retries, generate-all with progressive difficulty)` + `[ADD GEN] Complete: ${result.length} problems in ${elapsed}ms (0 retries, generate-all with progressive difficulty, ${cycleCount} cycles)` ) return result } else { // No interpolation - just shuffle and take first N const shuffled = shuffleArray(allProblems, rand) - // If we need more problems than available, we'll have duplicates + // If we need more problems than available, cycle through the shuffled array if (total > shuffled.length) { + const cyclesNeeded = Math.ceil(total / shuffled.length) console.warn( - `[ADD GEN] Warning: Requested ${total} problems but only ${shuffled.length} unique problems exist. Some duplicates will occur.` + `[ADD GEN] Warning: Requested ${total} problems but only ${shuffled.length} unique problems exist. Will cycle ${cyclesNeeded} times.` ) - // Repeat the shuffled array to fill the request + // Build result by repeating the entire shuffled array as many times as needed const result: AdditionProblem[] = [] - while (result.length < total) { - result.push(...shuffled.slice(0, Math.min(shuffled.length, total - result.length))) + for (let i = 0; i < total; i++) { + result.push(shuffled[i % shuffled.length]) } const elapsed = Date.now() - startTime console.log( - `[ADD GEN] Complete: ${result.length} problems in ${elapsed}ms (0 retries, generate-all method)` + `[ADD GEN] Complete: ${result.length} problems in ${elapsed}ms (0 retries, generate-all method, ${cyclesNeeded} cycles)` ) return result }