Spaces:
Sleeping
Sleeping
| #!/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() | |