Wayfinder GED — Math Sandbox Protocols
Math operations with verification: arithmetic, fractions, percentages, equations, geometry, statistics — all sandboxed against the real Wayfinder Education Engine.
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"""
passPART 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 stepsPART 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 stepsPART 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 stepsPART 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 stepsPART 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)