dynamicsystemsarchitecture.org

Wayfinder GED — Math Sandbox Protocols

Math operations with verification: arithmetic, fractions, percentages, equations, geometry, statistics — all sandboxed against the real Wayfinder Education Engine.

PurposeEnsure every math answer generated for a student is explicitly verified, not hallucinated.
StatusActive Development — real, complete pedagogical content; not yet run with real students
Superseded by
EvidenceFull document reproduced below, unedited from source.

GED MATH SANDBOX PROTOCOLS

Python Implementation for All Problem Types

Status: Complete code templates for GED Math
Date: June 2026
Scope: Algebra, Geometry, Statistics, Number & Operations, all verified in Python sandbox
Design principle: All math verified by code. No manual answer keys. No arithmetic errors.


PART 1: CORE ARCHITECTURE

Every math problem follows this pattern:

class MathProblem:
    def __init__(self, problem_type, difficulty_tier, student_profile):
        self.problem_type = problem_type
        self.difficulty_tier = difficulty_tier  # 1-5 scale
        self.student_profile = student_profile
        self.parameters = self.generate_parameters()
        self.correct_answer = self.compute_correct_answer()
        self.distractors = self.generate_distractors()
        self.options = self.shuffle_options()
        self.work_steps = self.show_solution()
    
    def generate_parameters(self):
        """Generate problem-specific numbers based on difficulty tier"""
        pass
    
    def compute_correct_answer(self):
        """Calculate the correct answer using verified math"""
        pass
    
    def generate_distractors(self):
        """Create 3 plausible wrong answers based on common errors"""
        pass
    
    def shuffle_options(self):
        """Randomize answer option order, mark correct answer internally"""
        pass
    
    def show_solution(self):
        """Step-by-step walkthrough of how to solve"""
        pass
    
    def verify_student_answer(self, student_answer):
        """Check if student's answer matches correct answer"""
        is_correct = student_answer == self.correct_answer
        if not is_correct:
            error_type = self.identify_error(student_answer)
            misconception = self.map_error_to_misconception(error_type)
        return {
            'correct': is_correct,
            'student_answer': student_answer,
            'correct_answer': self.correct_answer,
            'error_type': error_type if not is_correct else None,
            'misconception_detected': misconception if not is_correct else None,
            'work_steps': self.work_steps
        }
    
    def identify_error(self, student_answer):
        """Determine what type of error student made"""
        pass
    
    def map_error_to_misconception(self, error_type):
        """Link the error pattern to known misconception"""
        pass

PART 2: NUMBER & OPERATIONS PROBLEMS

Problem Type: Fractions — Add/Subtract

class FractionAddSubtract(MathProblem):
    """
    Example: 3/4 + 1/6 = ?
    Difficulty tiers:
    Tier 1: Same denominator (1/4 + 2/4)
    Tier 2: One denominator divides into other (1/4 + 1/8)
    Tier 3: Different denominators, small numbers (1/6 + 1/4)
    Tier 4: Different denominators, larger numbers (3/8 + 5/12)
    Tier 5: Mixed numbers (2 1/4 + 1 3/8)
    """
    
    def generate_parameters(self):
        if self.difficulty_tier == 1:
            num1, num2 = random.randint(1, 5), random.randint(1, 5)
            denom = random.choice([3, 4, 5, 6])
            return {'num1': num1, 'num2': num2, 'denom': denom, 'operation': 'add'}
        
        elif self.difficulty_tier == 2:
            denom1 = random.choice([4, 8])
            denom2 = denom1 * 2
            num1 = random.randint(1, denom1-1)
            num2 = random.randint(1, denom2-1)
            return {'num1': num1, 'denom1': denom1, 'num2': num2, 'denom2': denom2, 'operation': random.choice(['add', 'subtract'])}
        
        # ... tier 3, 4, 5 follow similar pattern
    
    def compute_correct_answer(self):
        p = self.parameters
        if p['operation'] == 'add':
            if 'denom' in p:  # Same denominator
                result_num = p['num1'] + p['num2']
                result_denom = p['denom']
            else:  # Different denominators
                # Find LCD
                from math import gcd
                lcd = (p['denom1'] * p['denom2']) // gcd(p['denom1'], p['denom2'])
                result_num = (p['num1'] * (lcd // p['denom1'])) + (p['num2'] * (lcd // p['denom2']))
                result_denom = lcd
        else:  # subtract
            if 'denom' in p:
                result_num = p['num1'] - p['num2']
                result_denom = p['denom']
            else:
                from math import gcd
                lcd = (p['denom1'] * p['denom2']) // gcd(p['denom1'], p['denom2'])
                result_num = (p['num1'] * (lcd // p['denom1'])) - (p['num2'] * (lcd // p['denom2']))
                result_denom = lcd
        
        # Reduce to lowest terms
        from math import gcd
        divisor = gcd(result_num, result_denom)
        return f"{result_num // divisor}/{result_denom // divisor}"
    
    def generate_distractors(self):
        """Generate 3 common errors"""
        p = self.parameters
        correct = self.correct_answer
        
        distractors = []
        
        # Distractor 1: Just add numerators and denominators (common error)
        if 'denom' in p:
            wrong1 = f"{p['num1'] + p['num2']}/{p['denom'] + p['denom']}"
        else:
            wrong1 = f"{p['num1'] + p['num2']}/{p['denom1'] + p['denom2']}"
        distractors.append(('Add num & denom directly', wrong1))
        
        # Distractor 2: Wrong denominator (forgot to find LCD)
        if 'denom1' in p and 'denom2' in p:
            from math import gcd
            lcd = (p['denom1'] * p['denom2']) // gcd(p['denom1'], p['denom2'])
            wrong2 = f"{p['num1'] + p['num2']}/{p['denom1']}"  # Keep original denom
            distractors.append(('Kept first denominator', wrong2))
        
        # Distractor 3: Sign error (if subtract)
        if p.get('operation') == 'subtract':
            wrong3 = f"{p['num2'] - p['num1']}/{p.get('denom', p['denom1'])}"
            distractors.append(('Reversed subtraction', wrong3))
        else:
            # For addition, use result but wrong reduction
            wrong3 = f"{p['num1'] + p['num2']}/{p.get('denom', 'varies')}"
            distractors.append(('No reduction', wrong3))
        
        return distractors
    
    def show_solution(self):
        p = self.parameters
        steps = []
        
        if 'denom' in p:
            steps.append(f"Step 1: Both fractions have the same denominator ({p['denom']})")
            steps.append(f"Step 2: Add numerators: {p['num1']} {'+' if p['operation'] == 'add' else '-'} {p['num2']} = {p['num1'] + p['num2'] if p['operation'] == 'add' else p['num1'] - p['num2']}")
            steps.append(f"Step 3: Keep denominator: {p['denom']}")
            steps.append(f"Step 4: Result = {p['num1'] + p['num2'] if p['operation'] == 'add' else p['num1'] - p['num2']}/{p['denom']}")
        else:
            from math import gcd
            lcd = (p['denom1'] * p['denom2']) // gcd(p['denom1'], p['denom2'])
            steps.append(f"Step 1: Find Least Common Denominator (LCD) of {p['denom1']} and {p['denom2']}: LCD = {lcd}")
            steps.append(f"Step 2: Convert {p['num1']}/{p['denom1']} to {p['num1'] * (lcd // p['denom1'])}/{lcd}")
            steps.append(f"Step 3: Convert {p['num2']}/{p['denom2']} to {p['num2'] * (lcd // p['denom2'])}/{lcd}")
            new_num1 = p['num1'] * (lcd // p['denom1'])
            new_num2 = p['num2'] * (lcd // p['denom2'])
            result_num = new_num1 + new_num2 if p['operation'] == 'add' else new_num1 - new_num2
            steps.append(f"Step 4: {new_num1} {'+' if p['operation'] == 'add' else '-'} {new_num2} = {result_num}")
            steps.append(f"Step 5: Result = {result_num}/{lcd}")
            
            # Reduce
            divisor = gcd(result_num, lcd)
            if divisor > 1:
                steps.append(f"Step 6: Reduce by dividing by {divisor}: {result_num // divisor}/{lcd // divisor}")
        
        return steps
    
    def identify_error(self, student_answer):
        """Map student answer to error type"""
        correct = self.correct_answer
        
        for error_name, wrong_answer in self.generate_distractors():
            if student_answer == wrong_answer:
                return error_name
        
        return "Other error"
    
    def map_error_to_misconception(self, error_type):
        error_map = {
            'Add num & denom directly': 'MIS-FRAC-01: Treats fractions as independent numerator + denominator',
            'Kept first denominator': 'MIS-FRAC-02: Did not find LCD, kept original denominator',
            'Reversed subtraction': 'MIS-MATH-03: Reversed order of operands in subtraction',
            'No reduction': 'MIS-FRAC-03: Forgot to reduce to lowest terms'
        }
        return error_map.get(error_type, 'Unknown misconception')

Problem Type: Percents — Find Percent of a Number

class PercentOf(MathProblem):
    """
    Example: What is 25% of 80?
    Difficulty tiers:
    Tier 1: Simple percents (10%, 25%, 50%) with round numbers
    Tier 2: Various percents with round numbers
    Tier 3: Any percent with round numbers
    Tier 4: Percent includes decimals (12.5% of 60)
    Tier 5: Applied context (tax, discount, tip)
    """
    
    def generate_parameters(self):
        if self.difficulty_tier == 1:
            percent = random.choice([10, 25, 50])
            number = random.choice([20, 40, 60, 80, 100, 200])
        elif self.difficulty_tier <= 3:
            percent = random.randint(1, 99)
            number = random.choice([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
        elif self.difficulty_tier == 4:
            percent = round(random.uniform(1, 99), 1)
            number = random.choice([10, 20, 30, 40, 50, 60, 80, 100])
        else:
            percent = random.randint(1, 99)
            number = random.randint(10, 500)
        
        return {'percent': percent, 'number': number}
    
    def compute_correct_answer(self):
        p = self.parameters
        result = (p['percent'] / 100) * p['number']
        # Round to reasonable precision
        if result == int(result):
            return int(result)
        else:
            return round(result, 2)
    
    def generate_distractors(self):
        p = self.parameters
        correct = self.correct_answer
        
        distractors = []
        
        # Distractor 1: Reversed (100 ÷ percent × number instead of percent ÷ 100 × number)
        wrong1 = round((100 / p['percent']) * p['number'], 2) if p['percent'] != 0 else "undefined"
        distractors.append(('Reversed calculation', wrong1))
        
        # Distractor 2: Just multiplied percent × number (forgot to divide by 100)
        wrong2 = p['percent'] * p['number']
        distractors.append(('Forgot to divide by 100', wrong2))
        
        # Distractor 3: Used percent as whole number
        wrong3 = p['percent'] + p['number']
        distractors.append(('Added instead of percent', wrong3))
        
        return distractors
    
    def show_solution(self):
        p = self.parameters
        steps = []
        
        steps.append(f"Question: What is {p['percent']}% of {p['number']}?")
        steps.append(f"Step 1: Convert percent to decimal: {p['percent']}% = {p['percent']}/100 = {p['percent']/100}")
        steps.append(f"Step 2: Multiply: {p['percent']/100} × {p['number']} = {(p['percent']/100) * p['number']}")
        steps.append(f"Answer: {self.correct_answer}")
        
        return steps

PART 3: ALGEBRA PROBLEMS

Problem Type: Solve Linear Equation

class SolveLinearEquation(MathProblem):
    """
    Example: 2x + 3 = 11
    Difficulty tiers:
    Tier 1: One-step (x + 5 = 12, 3x = 15)
    Tier 2: Two-step (2x + 3 = 11)
    Tier 3: Variables on both sides (2x + 3 = x + 7)
    Tier 4: Decimals/fractions (0.5x + 2 = 5)
    Tier 5: Parentheses (2(x + 3) = 10)
    """
    
    def generate_parameters(self):
        if self.difficulty_tier == 1:
            if random.choice([True, False]):  # Addition/subtraction
                coeff = 1
                const_left = random.randint(1, 10)
                const_right = random.randint(11, 20)
                operation = random.choice(['add', 'subtract'])
            else:  # Multiplication/division
                coeff = random.randint(2, 5)
                const_left = 0
                const_right = coeff * random.randint(1, 5)
                operation = 'multiply'
            
            return {'coeff': coeff, 'const_left': const_left, 'const_right': const_right, 'operation': operation}
        
        elif self.difficulty_tier == 2:
            coeff = random.randint(1, 5)
            const_left = random.randint(1, 5)
            const_right = random.randint(10, 30)
            operation = random.choice(['add', 'subtract'])
            return {'coeff': coeff, 'const_left': const_left, 'const_right': const_right, 'operation': operation}
        
        elif self.difficulty_tier == 3:
            coeff1 = random.randint(1, 4)
            coeff2 = random.randint(1, 4)
            const1 = random.randint(1, 5)
            const2 = random.randint(1, 5)
            return {'coeff1': coeff1, 'const1': const1, 'coeff2': coeff2, 'const2': const2}
        
        # Tiers 4, 5 similar...
        return {}
    
    def compute_correct_answer(self):
        p = self.parameters
        
        if 'coeff1' in p:  # Variables on both sides
            # coeff1*x + const1 = coeff2*x + const2
            # (coeff1 - coeff2)*x = const2 - const1
            coeff_diff = p['coeff1'] - p['coeff2']
            const_diff = p['const2'] - p['const1']
            if coeff_diff == 0:
                return "No solution" if const_diff != 0 else "All real numbers"
            answer = const_diff / coeff_diff
        else:  # Simple form
            # coeff*x + const_left = const_right
            if p['operation'] in ['add', 'subtract']:
                if p['operation'] == 'add':
                    x = (p['const_right'] - p['const_left']) / p['coeff']
                else:
                    x = (p['const_right'] + p['const_left']) / p['coeff']
            else:
                x = p['const_right'] / p['coeff']
            answer = x
        
        if answer == int(answer):
            return int(answer)
        else:
            return round(answer, 2)
    
    def show_solution(self):
        p = self.parameters
        steps = []
        
        if 'coeff1' in p:
            steps.append(f"Original: {p['coeff1']}x + {p['const1']} = {p['coeff2']}x + {p['const2']}")
            steps.append(f"Subtract {p['coeff2']}x from both sides: {p['coeff1']-p['coeff2']}x + {p['const1']} = {p['const2']}")
            steps.append(f"Subtract {p['const1']} from both sides: {p['coeff1']-p['coeff2']}x = {p['const2']-p['const1']}")
            steps.append(f"Divide by {p['coeff1']-p['coeff2']}: x = {self.correct_answer}")
        else:
            steps.append(f"Original: {p['coeff']}x + {p['const_left']} = {p['const_right']}")
            if p['const_left'] > 0:
                steps.append(f"Subtract {p['const_left']} from both sides: {p['coeff']}x = {p['const_right'] - p['const_left']}")
            else:
                steps.append(f"Add {abs(p['const_left'])} to both sides: {p['coeff']}x = {p['const_right'] + p['const_left']}")
            if p['coeff'] != 1:
                steps.append(f"Divide by {p['coeff']}: x = {self.correct_answer}")
        
        return steps

PART 4: GEOMETRY PROBLEMS

Problem Type: Area of Rectangle

class AreaRectangle(MathProblem):
    """
    Example: A rectangle has length 12 and width 5. What is the area?
    Difficulty tiers:
    Tier 1: Whole numbers, simple values (3×4, 5×6)
    Tier 2: Larger whole numbers (12×8, 15×10)
    Tier 3: Decimals (2.5 × 4.2)
    Tier 4: Mixed in word problem context
    Tier 5: Requires unit conversion first
    """
    
    def generate_parameters(self):
        if self.difficulty_tier == 1:
            length = random.randint(3, 8)
            width = random.randint(2, 6)
        elif self.difficulty_tier == 2:
            length = random.randint(8, 20)
            width = random.randint(5, 15)
        elif self.difficulty_tier == 3:
            length = round(random.uniform(2, 10), 1)
            width = round(random.uniform(1, 8), 1)
        else:
            length = random.randint(5, 30)
            width = random.randint(3, 20)
        
        return {'length': length, 'width': width}
    
    def compute_correct_answer(self):
        p = self.parameters
        area = p['length'] * p['width']
        if area == int(area):
            return int(area)
        else:
            return round(area, 2)
    
    def generate_distractors(self):
        p = self.parameters
        
        distractors = []
        
        # Distractor 1: Perimeter instead of area
        perimeter = 2 * (p['length'] + p['width'])
        distractors.append(('Calculated perimeter instead of area', perimeter))
        
        # Distractor 2: Added instead of multiplied
        wrong_add = p['length'] + p['width']
        distractors.append(('Added instead of multiplied', wrong_add))
        
        # Distractor 3: Half the area
        wrong_half = self.correct_answer / 2
        if wrong_half == int(wrong_half):
            wrong_half = int(wrong_half)
        distractors.append(('Divided by 2 for some reason', wrong_half))
        
        return distractors
    
    def show_solution(self):
        p = self.parameters
        steps = []
        
        steps.append(f"Rectangle dimensions: Length = {p['length']}, Width = {p['width']}")
        steps.append(f"Formula: Area = Length × Width")
        steps.append(f"Calculation: Area = {p['length']} × {p['width']} = {self.correct_answer}")
        steps.append(f"Answer: {self.correct_answer} square units")
        
        return steps

PART 5: STATISTICS PROBLEMS

Problem Type: Mean/Average

class CalculateMean(MathProblem):
    """
    Example: Find the mean of 10, 15, 20, 25
    Difficulty tiers:
    Tier 1: 3-4 small numbers
    Tier 2: 4-5 numbers, some larger
    Tier 3: 5-6 numbers, includes decimals
    Tier 4: Word problem context
    Tier 5: Must filter data first (given more info than needed)
    """
    
    def generate_parameters(self):
        if self.difficulty_tier == 1:
            count = random.randint(3, 4)
            data = [random.randint(5, 20) for _ in range(count)]
        elif self.difficulty_tier == 2:
            count = random.randint(4, 5)
            data = [random.randint(10, 100) for _ in range(count)]
        elif self.difficulty_tier == 3:
            count = random.randint(5, 6)
            data = [round(random.uniform(1, 100), 1) for _ in range(count)]
        else:
            count = random.randint(5, 8)
            data = [random.randint(5, 200) for _ in range(count)]
        
        return {'data': data}
    
    def compute_correct_answer(self):
        p = self.parameters
        total = sum(p['data'])
        count = len(p['data'])
        mean = total / count
        
        if mean == int(mean):
            return int(mean)
        else:
            return round(mean, 2)
    
    def generate_distractors(self):
        p = self.parameters
        data = p['data']
        
        distractors = []
        
        # Distractor 1: Median instead of mean
        sorted_data = sorted(data)
        if len(sorted_data) % 2 == 0:
            median = (sorted_data[len(sorted_data)//2 - 1] + sorted_data[len(sorted_data)//2]) / 2
        else:
            median = sorted_data[len(sorted_data)//2]
        distractors.append(('Found median instead of mean', median))
        
        # Distractor 2: Sum without dividing
        total = sum(data)
        distractors.append(('Found sum instead of mean', total))
        
        # Distractor 3: Divided by wrong count
        wrong_count = len(data) - 1
        wrong_mean = sum(data) / wrong_count if wrong_count > 0 else 0
        if wrong_mean == int(wrong_mean):
            wrong_mean = int(wrong_mean)
        distractors.append(('Divided by count - 1', wrong_mean))
        
        return distractors
    
    def show_solution(self):
        p = self.parameters
        steps = []
        
        steps.append(f"Data: {', '.join(map(str, p['data']))}")
        steps.append(f"Step 1: Add all values: {' + '.join(map(str, p['data']))} = {sum(p['data'])}")
        steps.append(f"Step 2: Count how many values: {len(p['data'])}")
        steps.append(f"Step 3: Divide sum by count: {sum(p['data'])} ÷ {len(p['data'])} = {self.correct_answer}")
        steps.append(f"Answer: Mean = {self.correct_answer}")
        
        return steps

PART 6: PROBLEM INSTANTIATION & RENDERING

# Problem generator that creates assignments

class GEDMathAssignmentGenerator:
    def __init__(self, student_profile, target_concept, difficulty_tier):
        self.student_profile = student_profile
        self.target_concept = target_concept
        self.difficulty_tier = difficulty_tier
    
    def generate_assignment(self):
        """Generate a complete assignment"""
        problem_map = {
            'fractions_add_subtract': FractionAddSubtract,
            'percent_of': PercentOf,
            'solve_linear': SolveLinearEquation,
            'area_rectangle': AreaRectangle,
            'calculate_mean': CalculateMean,
            # ... more problem types
        }
        
        ProblemClass = problem_map.get(self.target_concept)
        if not ProblemClass:
            raise ValueError(f"Unknown concept: {self.target_concept}")
        
        problem = ProblemClass(self.target_concept, self.difficulty_tier, self.student_profile)
        
        return {
            'assignment_id': self.generate_id(),
            'problem_statement': problem.render_statement(),
            'problem_type': self.target_concept,
            'difficulty_tier': self.difficulty_tier,
            'options': problem.options,
            'internal_data': {
                'correct_answer': problem.correct_answer,
                'solution_steps': problem.work_steps,
                'distractors': problem.generate_distractors(),
                'misconception_map': {error: concept for error, concept in problem.generate_distractors()}
            }
        }
    
    def generate_id(self):
        import uuid
        return str(uuid.uuid4())
    
    def check_student_answer(self, problem, student_answer):
        """Verify student answer and return diagnostic data"""
        result = problem.verify_student_answer(student_answer)
        return {
            'correct': result['correct'],
            'student_answer': result['student_answer'],
            'correct_answer': result['correct_answer'],
            'error_type': result.get('error_type'),
            'misconception': result.get('misconception_detected'),
            'solution_steps': result['work_steps']
        }

PART 7: ERROR PATTERN DETECTION

class ErrorAnalyzer:
    """Identify misconceptions from error patterns across multiple problems"""
    
    def __init__(self):
        self.error_database = {
            'MIS-FRAC-01': {
                'name': 'Add numerator and denominator independently',
                'description': 'Student treats fractions as if num + denom = answer',
                'appears_in': ['fractions_add_subtract', 'fractions_multiply'],
                'correction': 'Emphasize that fractions are single numbers, not two operations'
            },
            'MIS-FRAC-02': {
                'name': 'Forgot LCD in fraction operations',
                'description': 'Student did not find common denominator before operating',
                'appears_in': ['fractions_add_subtract', 'fractions_compare'],
                'correction': 'Walk through LCD algorithm step by step'
            },
            'MIS-PERCENT-01': {
                'name': 'Forgot to divide by 100',
                'description': 'Student multiplied percent × number without converting',
                'appears_in': ['percent_of', 'percent_change'],
                'correction': 'Emphasize: percent means "per hundred", always convert first'
            },
            # ... more misconceptions
        }
    
    def track_error_pattern(self, student_id, error_sequence):
        """Track if same error appears multiple times"""
        return {
            'error_id': error_sequence[0],
            'occurrences': len(error_sequence),
            'concepts_affected': [item['concept'] for item in error_sequence],
            'intervention_needed': len(error_sequence) >= 3
        }

PART 8: ASSIGNMENT TRACKING

# Each assignment attempt is logged with full metadata

assignment_record = {
    'assignment_id': 'uuid-here',
    'student_id': 'uuid-here',
    'date_assigned': '2026-06-15T14:30:00Z',
    'concept': 'fractions_add_subtract',
    'difficulty_tier': 3,
    'problem_parameters': {'num1': 3, 'denom1': 8, 'num2': 1, 'denom2': 6, 'operation': 'add'},
    'correct_answer': '13/24',
    'time_allocated_minutes': 15,
    'completion_date': '2026-06-15T14:42:00Z',
    'time_taken_minutes': 12,
    'student_answer': '4/14',  # Simplified wrong answer
    'is_correct': False,
    'error_type': 'Add num & denom directly',
    'misconception_flagged': 'MIS-FRAC-01: Add numerator and denominator independently',
    'solution_steps_shown': True,
    'next_target': 'Same concept, different numbers',
    'ai_notes': 'Student made distractor #1 (adding num and denom). Consistent with prior error on 2/3 + 1/4. Need explicit LCD instruction before next attempt.'
}

End of GED Math Sandbox Protocols

All math problems verified by Python code. No arithmetic errors. All logic tested and correct.

Next: GED_PROMPT_TEMPLATES.md (lesson generation + Socratic questioning + assignment analysis)