#!/usr/bin/env python3
"""Audit weeks 1-8 of the rewritten math curriculum."""
import os
import re
import subprocess
import sys
from pathlib import Path
BASE = Path("/home/vincent/Home_School/2nd_Grade/Math")
REPORT_LINES = []
def log(msg=""):
print(msg)
REPORT_LINES.append(msg)
def check_week(week_num, rewritten_file):
log(f"\n{'='*60}")
log(f"## Week {week_num}")
log(f"{'='*60}")
issues = []
warnings = []
principles_met = {
"fewer_drill": False,
"estimation": False,
"multiple_strategies": False,
"better_word_problems": False,
"open_ended": False,
"number_talks": False,
"friday_reflection": False,
}
# 1. Check which generator to use
rewritten = BASE / rewritten_file
original = BASE / f"generate_week{week_num}.py"
gen_file = rewritten if rewritten.exists() else original
if not gen_file.exists():
issues.append(f"Generator NOT FOUND: {gen_file.name}")
return issues, warnings, principles_met
log(f"**Generator:** {gen_file.name}")
content = gen_file.read_text()
# 2. Check for corruption
if "File unchanged since last read" in content:
issues.append("CORRUPTION: Contains 'File unchanged since last read'")
# 3. Check docstring
doc_match = re.search(r'(?:"""|\'\'\')(.*?)\1', content, re.DOTALL)
if doc_match:
docstring = doc_match.group(1)
log(f"**Docstring:** {docstring[:200].strip()[:100]}...")
else:
warnings.append("No docstring found")
# 4. Check imports
import_lines = [l.strip() for l in content.split('\n') if l.strip().startswith('import ') or l.strip().startswith('from ')]
log(f"**Imports:** {len(import_lines)} lines")
# 5. Run generator and check for errors
log(f"**Running generator...**")
result = subprocess.run(
[sys.executable, str(gen_file)],
capture_output=True, text=True, timeout=120,
cwd=str(BASE)
)
if result.returncode != 0:
issues.append(f"GENERATION FAILED (exit {result.returncode})")
if result.stderr:
# Get last 5 lines of stderr
stderr_lines = result.stderr.strip().split('\n')[-5:]
issues.append(f" stderr: {'; '.join(stderr_lines)}")
else:
log(f" ā Generated successfully")
stdout_lines = [l.strip() for l in result.stdout.strip().split('\n') if l.strip()]
for line in stdout_lines:
log(f" {line}")
# 6. Check PDF output
week_dir = BASE / f"Week_{week_num}"
if week_dir.exists():
pdfs = sorted(week_dir.glob("*.pdf"))
log(f"**PDFs in {week_dir.name}:** {len(pdfs)}")
for pdf in pdfs:
size_kb = pdf.stat().st_size / 1024
log(f" - {pdf.name} ({size_kb:.0f} KB)")
if size_kb < 5:
warnings.append(f" {pdf.name} is very small ({size_kb:.0f} KB) ā possibly empty")
if size_kb > 500:
warnings.append(f" {pdf.name} is very large ({size_kb:.0f} KB)")
else:
issues.append(f"Output directory Week_{week_num} does not exist")
# 7. Check New Standard compliance by scanning content
# Look for evidence of each principle
lower_content = content.lower()
# Principle 1: 40% fewer drill problems
# Count problem-generating loops - fewer = better
problem_count_matches = re.findall(r'for\s+\w+\s+in\s+range\((\d+)\)', content)
if problem_count_matches:
max_problems = max(int(x) for x in problem_count_matches)
if max_problems <= 6:
principles_met["fewer_drill"] = True
log(f" ā Fewer drill (max {max_problems} problems)")
else:
issues.append(f" Too many drill problems (max {max_problems}, target ā¤6)")
else:
log(f" ? No range() loops found ā activity count unclear")
# Principle 2: Estimation before calculating
if any(w in lower_content for w in ['estimate', 'about how', 'roughly', 'approximately', 'before you calculate']):
principles_met["estimation"] = True
log(f" ā Has estimation activities")
else:
warnings.append(" No estimation activities detected")
# Principle 3: Multiple strategies
if any(w in lower_content for w in ['two ways', 'show another way', 'different method', 'number line', 'draw a picture', 'mental math', 'strategy']):
principles_met["multiple_strategies"] = True
log(f" ā Multiple strategies encouraged")
else:
warnings.append(" No multiple-strategy prompts detected")
# Principle 4: Better word problems (not keyword-spotting)
if any(w in lower_content for w in ['explain', 'describe how', 'tell why', 'reason', 'thinking']):
principles_met["better_word_problems"] = True
log(f" ā Reasoning-based word problems")
else:
warnings.append(" Word problems may be keyword-spotting style")
# Principle 5: Open-ended challenges
if any(w in lower_content for w in ['create your own', 'make up', 'design your', 'write a problem', 'challenge', 'bonus']):
principles_met["open_ended"] = True
log(f" ā Open-ended challenges present")
else:
warnings.append(" No open-ended challenges detected")
# Principle 6: Number talks
if any(w in lower_content for w in ['number talk', 'explain without', 'compare without', 'which is larger', 'which is bigger', 'without calculating']):
principles_met["number_talks"] = True
log(f" ā Number talks present")
else:
warnings.append(" No number talks detected")
# Principle 7: Friday reflection
if any(w in lower_content for w in ['self-check', 'self check', 'reflection', 'one thing i learned', 'what i learned', 'how did you', 'ā', 'self-assess']):
principles_met["friday_reflection"] = True
log(f" ā Friday reflection present")
else:
warnings.append(" No Friday self-check/reflection detected")
# 8. Check for pre-filled answers
if re.search(r'answer\s*=\s*["\']?\d+', content):
# Check if answers appear in the HTML/template
if 'answer' in lower_content and ('blank' not in lower_content or 'fill' not in lower_content):
pass # answers in code are fine if not rendered
# Look for answer keys in wrong places
if re.findall(r'<td>\s*\d+\s*</td>', content):
potential_answers = re.findall(r'<td>\s*(\d+)\s*</td>', content)
if len(potential_answers) > 10:
warnings.append(f" Many <td> with numbers ({len(potential_answers)}) ā check for pre-filled answers")
# 9. Activity count check (max 5 per day)
# Count distinct activity sections
activity_sections = re.findall(r'class="section|id="section|<div class="activity|section_title|activity_title', content)
if activity_sections:
# Rough estimate: 5 sections/day * 5 days = 25
sections_per_day = len(activity_sections) / 5
if sections_per_day > 6:
warnings.append(f" ~{sections_per_day:.0f} sections/day (max 5 recommended)")
return issues, warnings, principles_met
def main():
log("# Math Curriculum Audit ā Weeks 1-8")
log(f"Date: {__import__('datetime').date.today().isoformat()}")
log("")
# Week mapping: which generator is the "rewritten" version
week_generators = {
1: "generate_week1.py", # original, marked ā empty
2: "generate_week2_rewritten.py", # rewritten
3: "generate_week3.py", # rewritten (no _rewritten suffix)
4: "generate_week4_rewritten.py", # rewritten
5: "generate_week5_rewritten.py", # rewritten
6: "generate_week6.py", # rewritten
7: "generate_week7_rewritten.py", # rewritten
8: "generate_week8_rewritten.py", # rewritten
}
all_results = {}
for week, gen_file in week_generators.items():
issues, warnings, principles = check_week(week, gen_file)
all_results[week] = {
"issues": issues,
"warnings": warnings,
"principles": principles,
}
# Summary table
log(f"\n\n{'='*60}")
log("## SUMMARY")
log(f"{'='*60}")
log("")
header = f"{'Week':>4} | {'Gen':<35} | {'Errs':>4} | {'Warn':>4} | {'Principles':>10} | Grade"
log(header)
log("-" * len(header))
for week in range(1, 9):
r = all_results[week]
p = r["principles"]
met_count = sum(1 for v in p.values() if v)
if len(r["issues"]) == 0 and met_count >= 5:
grade = "A"
elif len(r["issues"]) == 0 and met_count >= 3:
grade = "B"
elif len(r["issues"]) <= 2 and met_count >= 2:
grade = "C"
elif len(r["issues"]) <= 3:
grade = "D"
else:
grade = "F"
log(f"{week:>4} | {week_generators[week]:<35} | {len(r['issues']):>4} | {len(r['warnings']):>4} | {met_count:>4}/7 | {grade}")
# Detail issues
log(f"\n\n## ISSUES BY WEEK")
for week in range(1, 9):
r = all_results[week]
if r["issues"] or r["warnings"]:
log(f"\n### Week {week}")
for issue in r["issues"]:
log(f" ā {issue}")
for warn in r["warnings"]:
log(f" ā ļø {warn}")
# Principles summary
log(f"\n\n## PRINCIPLES COMPLIANCE")
principles_names = {
"fewer_drill": "40% fewer drill",
"estimation": "Estimation first",
"multiple_strategies": "Multiple strategies",
"better_word_problems": "Better word problems",
"open_ended": "Open-ended challenges",
"number_talks": "Number talks",
"friday_reflection": "Friday reflection",
}
log("")
header = f"{'Principle':<25} " + "".join(f" W{w:>2}" for w in range(1, 9))
log(header)
for pname, label in principles_names.items():
row = f"{label:<25}"
for week in range(1, 9):
met = all_results[week]["principles"][pname]
row += f" {'ā' if met else 'ā':>2}"
log(row)
# Save report
report_path = BASE / "AUDIT_WEEKS1to8.md"
report_path.write_text("\n".join(REPORT_LINES))
log(f"\n\nā Report saved to {report_path}")
if __name__ == "__main__":
main()