MetaDebate / viral_script_engine /data /curriculum /build_curriculum.py
vajeeda's picture
feat(phase4): critic escalation engine, difficulty tracker, env wiring, gate PASS
ebae6ab
Raw
History Blame Contribute Delete
6.56 kB
#!/usr/bin/env python3
"""
Build curriculum tiers from existing test scripts + synthetic scripts.
Generates:
- easy_tier.jsonl (20 configs)
- medium_tier.jsonl (15 configs)
- hard_tier.jsonl (10 configs)
Usage: python data/curriculum/build_curriculum.py
(Run generate_synthetic_scripts.py first if synthetic_scripts.json is missing)
"""
import json
import sys
from pathlib import Path
BASE_DIR = Path(__file__).parent.parent.parent
DATA_DIR = BASE_DIR / "data"
CURRICULUM_DIR = DATA_DIR / "curriculum"
SCRIPTS_PATH = DATA_DIR / "test_scripts" / "scripts.json"
SYNTHETIC_PATH = CURRICULUM_DIR / "synthetic_scripts.json"
sys.path.insert(0, str(BASE_DIR.parent))
_FLAW_TO_CRITIQUE_CLASS = {
"buried_hook": "hook_weakness",
"no_cta": "cta_weakness",
"pacing_issue": "coherence_issue",
"coherence_break": "coherence_issue",
"cultural_mismatch": "cultural_misalignment",
"conflicting_advice":"coherence_issue",
"retention_risk": "hook_weakness",
"cta_buried": "cta_weakness",
}
_FLAW_TO_ACTION = {
"buried_hook": "hook_rewrite",
"no_cta": "cta_placement",
"pacing_issue": "section_reorder",
"coherence_break": "section_reorder",
"cultural_mismatch": "cultural_ref_sub",
"conflicting_advice":"section_reorder",
"retention_risk": "hook_rewrite",
"cta_buried": "cta_placement",
}
_EASY_NOTES = "One obvious flaw. Critic should win immediately. Strong reward signal on step 1."
_MEDIUM_NOTES = "Trade-off scenario. Critic and Defender both have valid points. Reward signal emerges over 2–3 steps."
_HARD_NOTES = "Fixing the top critique risks damaging R3 cultural alignment. Explicit reward conflict."
def _load_json(path: Path) -> list:
with open(path, encoding="utf-8") as f:
return json.load(f)
def _make_config(
config_id: str,
difficulty: str,
script: dict,
notes: str,
) -> dict:
flaws = script.get("known_flaws", script.get("dominant_flaw", ["buried_hook"]))
if isinstance(flaws, str):
flaws = [flaws]
dominant = flaws[0] if flaws else "buried_hook"
return {
"episode_config_id": config_id,
"difficulty": difficulty,
"script_id": script["script_id"],
"script_text": script["script_text"],
"region": script["region"],
"platform": script["platform"],
"niche": script["niche"],
"dominant_flaw": dominant,
"expected_critique_class": _FLAW_TO_CRITIQUE_CLASS.get(dominant, "hook_weakness"),
"expected_action": _FLAW_TO_ACTION.get(dominant, "hook_rewrite"),
"curriculum_notes": notes,
}
def build_easy_tier(existing: list, synthetic: list) -> list:
"""
20 configs: 10 from existing easy scripts (S01–S04) + 10 from synthetic easy.
Existing scripts are used with slight context variations (platform/region cycling).
"""
easy_existing = [s for s in existing if s["script_id"] in ("S01", "S02", "S03", "S04")]
easy_synthetic = [s for s in synthetic if s["difficulty"] == "easy"]
configs = []
idx = 1
region_variants = ["Mumbai Gen Z", "Pan-India English", "Tier-2 Hindi belt"]
platform_variants = ["Reels", "Shorts", "Reels"]
for i, script in enumerate(easy_existing * 3):
if len(configs) >= 10:
break
variant = i % len(region_variants)
patched = dict(script)
patched["region"] = region_variants[variant]
patched["platform"] = platform_variants[variant]
cfg = _make_config(f"easy_{idx:03d}", "easy", patched, _EASY_NOTES)
configs.append(cfg)
idx += 1
for script in easy_synthetic[:10]:
cfg = _make_config(f"easy_{idx:03d}", "easy", script, _EASY_NOTES)
configs.append(cfg)
idx += 1
return configs[:20]
def build_medium_tier(existing: list, synthetic: list) -> list:
"""
15 configs: 10 from medium scripts (S05–S07) + 5 from synthetic medium.
"""
med_existing = [s for s in existing if s["script_id"] in ("S05", "S06", "S07")]
med_synthetic = [s for s in synthetic if s["difficulty"] == "medium"]
configs = []
idx = 1
for i, script in enumerate(med_existing * 5):
if len(configs) >= 10:
break
cfg = _make_config(f"medium_{idx:03d}", "medium", script, _MEDIUM_NOTES)
configs.append(cfg)
idx += 1
for script in med_synthetic[:5]:
cfg = _make_config(f"medium_{idx:03d}", "medium", script, _MEDIUM_NOTES)
configs.append(cfg)
idx += 1
return configs[:15]
def build_hard_tier(existing: list, synthetic: list) -> list:
"""
10 configs: 5 from hard scripts (S08–S10) + 5 from synthetic hard.
"""
hard_existing = [s for s in existing if s["script_id"] in ("S08", "S09", "S10")]
hard_synthetic = [s for s in synthetic if s["difficulty"] == "hard"]
configs = []
idx = 1
for i, script in enumerate(hard_existing * 4):
if len(configs) >= 5:
break
cfg = _make_config(f"hard_{idx:03d}", "hard", script, _HARD_NOTES)
configs.append(cfg)
idx += 1
for script in hard_synthetic[:5]:
cfg = _make_config(f"hard_{idx:03d}", "hard", script, _HARD_NOTES)
configs.append(cfg)
idx += 1
return configs[:10]
def write_jsonl(configs: list, path: Path):
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
for cfg in configs:
f.write(json.dumps(cfg, ensure_ascii=False) + "\n")
print(f" Wrote {len(configs)} configs -> {path.name}")
def main():
existing = _load_json(SCRIPTS_PATH)
print(f"Loaded {len(existing)} existing scripts.")
if SYNTHETIC_PATH.exists():
synthetic = _load_json(SYNTHETIC_PATH)
print(f"Loaded {len(synthetic)} synthetic scripts.")
else:
print(f"WARNING: {SYNTHETIC_PATH} not found — using empty list.")
print("Run generate_synthetic_scripts.py first for full curriculum.")
synthetic = []
easy = build_easy_tier(existing, synthetic)
medium = build_medium_tier(existing, synthetic)
hard = build_hard_tier(existing, synthetic)
write_jsonl(easy, CURRICULUM_DIR / "easy_tier.jsonl")
write_jsonl(medium, CURRICULUM_DIR / "medium_tier.jsonl")
write_jsonl(hard, CURRICULUM_DIR / "hard_tier.jsonl")
print(f"\nCurriculum built: easy={len(easy)}, medium={len(medium)}, hard={len(hard)}")
if __name__ == "__main__":
main()