claude-code-ultimate-guide/scripts/generate-audit-batches.py
Florian BRUNIAUX a55ff38143 feat(quiz): add complete audit system for 256 questions
Implements automated pipeline for quiz question quality control:

**Phase 1: Context Extraction**
- Script: extract-audit-context.py
- Resolves doc_reference anchors to guide sections (97.3% success)
- Multi-file support (ultimate-guide.md, learning-with-ai.md, etc.)
- Fuzzy matching + substring fallback
- Output: audit-context.json (256 questions + context)

**Phase 2: Batch Generation**
- Script: generate-audit-batches.py
- 16 prioritized review batches by category
- Advanced Patterns split into 2 batches (29 questions)
- Embedded review instructions in each batch
- Output: audit-batches/*.md (16,559 lines)

**Phase 3: Report Compilation**
- Script: generate-audit-report.py
- Parses agent review outputs (PASS/ISSUE format)
- Aggregates by severity (critical/warning/info)
- Output: audit-report.md

**Validation:**
- Q01-001 error found immediately (curl vs npm contradiction)
- System working as designed 

**Documentation:**
- AUDIT-WORKFLOW.md (complete 5-phase guide)
- AUDIT-SYSTEM-SUMMARY.md (architecture + metrics)
- IMPLEMENTATION-COMPLETE.md (status + validation)
- DEMO-REVIEW-OUTPUT.txt (example review)

**Next Steps:** Manual agent reviews (16 batches, ~2-3 hours)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-04 16:45:47 +01:00

228 lines
7.8 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Generate audit batches for quiz question review.
Splits 256 questions into category-based batches for agent review.
Each batch includes: question, options, correct answer, explanation, and guide context.
Output: claudedocs/audit-batches/*.md (16 files)
Usage:
python3 scripts/generate-audit-batches.py
"""
import json
from pathlib import Path
from typing import Dict, List
BASE_DIR = Path(__file__).parent.parent
AUDIT_CONTEXT = BASE_DIR / "claudedocs" / "audit-context.json"
BATCH_TEMPLATE = BASE_DIR / "claudedocs" / "audit-batch-template.md"
OUTPUT_DIR = BASE_DIR / "claudedocs" / "audit-batches"
# Category names (from _categories.yaml in landing repo)
CATEGORIES = {
1: "Quick Start",
2: "Core Concepts",
3: "Best Practices",
4: "Configuration",
5: "Context Management",
6: "Tools & Features",
7: "Workflows",
8: "MCP Ecosystem",
9: "Advanced Patterns",
10: "Reference",
11: "Learning with AI",
12: "Methodologies",
13: "Security",
14: "Philosophy",
15: "Ecosystem"
}
# Priority order for review
PRIORITY_ORDER = [
1, # Quick Start
2, # Core Concepts
13, # Security
10, # Reference
8, # MCP Ecosystem
9, # Advanced Patterns
3, # Best Practices
5, # Context Management
6, # Tools & Features
7, # Workflows
11, # Learning with AI
12, # Methodologies
4, # Configuration
14, # Philosophy
15 # Ecosystem
]
def format_question_for_review(q: Dict) -> str:
"""Format a single question for human review."""
lines = []
lines.append(f"### Question {q['id']}")
lines.append("")
lines.append(f"**Difficulty**: {q['difficulty']}")
lines.append(f"**Profiles**: {', '.join(q['profiles'])}")
lines.append("")
lines.append("**Question:**")
lines.append(q['question'])
lines.append("")
lines.append("**Options:**")
for key in ['a', 'b', 'c', 'd']:
marker = "" if key == q['correct'] else " "
lines.append(f" {key}. {q['options'][key]} {marker}")
lines.append("")
lines.append(f"**Correct Answer**: {q['correct']}")
lines.append("")
lines.append("**Explanation:**")
lines.append(q['explanation'])
lines.append("")
# Guide context
if q.get('guide_context'):
lines.append("**Guide Context:**")
lines.append(f"*Source: {q.get('guide_source_file', 'N/A')} (line {q.get('guide_line_number', 'N/A')})*")
lines.append(f"*Resolution: {q.get('resolution_strategy')} (confidence: {q.get('resolution_confidence', 0)}%)*")
lines.append("```")
# Truncate context if too long (max 100 lines)
context_lines = q['guide_context'].split('\n')
if len(context_lines) > 100:
lines.extend(context_lines[:100])
lines.append(f"... (truncated {len(context_lines) - 100} lines)")
else:
lines.extend(context_lines)
lines.append("```")
else:
lines.append("**Guide Context:** ⚠️ UNRESOLVED")
if 'doc_reference' in q:
lines.append(f"*Intended reference: {q['doc_reference']}*")
lines.append("")
lines.append("---")
lines.append("")
return '\n'.join(lines)
def generate_batch(category_id: int, questions: List[Dict], template: str) -> str:
"""Generate a batch file for a category."""
category_name = CATEGORIES[category_id]
# Format questions
questions_text = []
for q in questions:
questions_text.append(format_question_for_review(q))
# Fill template
batch_content = template.replace('{questions}', '\n'.join(questions_text))
# Add header
header = f"""# Audit Batch: Category {category_id:02d} - {category_name}
**Questions**: {len(questions)}
**Priority**: {PRIORITY_ORDER.index(category_id) + 1}/{len(PRIORITY_ORDER)}
---
"""
return header + batch_content
def main():
"""Main entry point."""
print("═══════════════════════════════════════════════════════════════")
print("Quiz Question Audit Batch Generation")
print("═══════════════════════════════════════════════════════════════")
print()
# Load audit context
if not AUDIT_CONTEXT.exists():
print(f"Error: audit-context.json not found. Run extract-audit-context.py first.", file=sys.stderr)
return 1
data = json.loads(AUDIT_CONTEXT.read_text())
questions = data['questions']
print(f"Loaded {len(questions)} questions")
# Load template
if not BATCH_TEMPLATE.exists():
print(f"Error: batch template not found: {BATCH_TEMPLATE}", file=sys.stderr)
return 1
template = BATCH_TEMPLATE.read_text()
# Group by category
by_category = {}
for q in questions:
cat_id = q['category_id']
if cat_id not in by_category:
by_category[cat_id] = []
by_category[cat_id].append(q)
print(f"Categories: {len(by_category)}")
print()
# Create output directory
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# Generate batches
for cat_id in PRIORITY_ORDER:
if cat_id not in by_category:
continue
cat_questions = by_category[cat_id]
batch_content = generate_batch(cat_id, cat_questions, template)
# Handle advanced-patterns (split into 2 batches if >20 questions)
if cat_id == 9 and len(cat_questions) > 20:
# Split into 2 batches
mid = len(cat_questions) // 2
batch1 = cat_questions[:mid]
batch2 = cat_questions[mid:]
batch1_content = generate_batch(cat_id, batch1, template)
batch2_content = generate_batch(cat_id, batch2, template)
output_file1 = OUTPUT_DIR / f"{cat_id:02d}-{CATEGORIES[cat_id].lower().replace(' ', '-')}-part1.md"
output_file2 = OUTPUT_DIR / f"{cat_id:02d}-{CATEGORIES[cat_id].lower().replace(' ', '-')}-part2.md"
# Add part indicators
batch1_content = batch1_content.replace(
f"# Audit Batch: Category {cat_id:02d}",
f"# Audit Batch: Category {cat_id:02d} - Part 1/2"
)
batch2_content = batch2_content.replace(
f"# Audit Batch: Category {cat_id:02d}",
f"# Audit Batch: Category {cat_id:02d} - Part 2/2"
)
output_file1.write_text(batch1_content)
output_file2.write_text(batch2_content)
print(f"✓ Generated {output_file1.name} ({len(batch1)} questions)")
print(f"✓ Generated {output_file2.name} ({len(batch2)} questions)")
else:
output_file = OUTPUT_DIR / f"{cat_id:02d}-{CATEGORIES[cat_id].lower().replace(' ', '-')}.md"
output_file.write_text(batch_content)
print(f"✓ Generated {output_file.name} ({len(cat_questions)} questions)")
print()
print("═══════════════════════════════════════════════════════════════")
print(f"✓ Batches generated in: {OUTPUT_DIR}")
print(f" Total files: {len(list(OUTPUT_DIR.glob('*.md')))}")
print()
print("Review order (priority):")
for idx, cat_id in enumerate(PRIORITY_ORDER, 1):
if cat_id in by_category:
count = len(by_category[cat_id])
print(f" {idx:2d}. Category {cat_id:02d} - {CATEGORIES[cat_id]} ({count} questions)")
print("═══════════════════════════════════════════════════════════════")
if __name__ == '__main__':
import sys
sys.exit(main() or 0)