Spaces:
Sleeping
Sleeping
| import json | |
| import os | |
| from dataclasses import dataclass, field, asdict | |
| from typing import Dict, List, Optional | |
| class CritiqueClassRecord: | |
| critique_class: str | |
| total_episodes: int = 0 | |
| resolved_episodes: int = 0 | |
| consecutive_resolutions: int = 0 | |
| mastery_threshold: int = 3 | |
| is_mastered: bool = False | |
| avg_r4_score: float = 0.0 | |
| last_10_r4_scores: List[float] = field(default_factory=list) | |
| class DifficultyTracker: | |
| CRITIQUE_CLASSES = [ | |
| "hook_weakness", | |
| "pacing_issue", | |
| "cultural_mismatch", | |
| "cta_buried", | |
| "coherence_break", | |
| "retention_risk", | |
| ] | |
| def __init__(self, persistence_path: str = "logs/difficulty_tracker.json"): | |
| self.persistence_path = persistence_path | |
| self.records: Dict[str, CritiqueClassRecord] = { | |
| cls: CritiqueClassRecord(critique_class=cls) for cls in self.CRITIQUE_CLASSES | |
| } | |
| self._load() | |
| def _load(self): | |
| if os.path.exists(self.persistence_path): | |
| try: | |
| with open(self.persistence_path, encoding="utf-8") as f: | |
| data = json.load(f) | |
| for cls, rec_data in data.get("records", {}).items(): | |
| if cls in self.records: | |
| r = self.records[cls] | |
| r.total_episodes = rec_data.get("total_episodes", 0) | |
| r.resolved_episodes = rec_data.get("resolved_episodes", 0) | |
| r.consecutive_resolutions = rec_data.get("consecutive_resolutions", 0) | |
| r.is_mastered = rec_data.get("is_mastered", False) | |
| r.avg_r4_score = rec_data.get("avg_r4_score", 0.0) | |
| r.last_10_r4_scores = rec_data.get("last_10_r4_scores", []) | |
| except (json.JSONDecodeError, KeyError): | |
| pass | |
| def _save(self): | |
| os.makedirs(os.path.dirname(self.persistence_path) if os.path.dirname(self.persistence_path) else ".", exist_ok=True) | |
| payload = { | |
| "records": {cls: asdict(rec) for cls, rec in self.records.items()} | |
| } | |
| with open(self.persistence_path, "w", encoding="utf-8") as f: | |
| json.dump(payload, f, indent=2) | |
| def record_episode(self, dominant_critique_class: str, r4_score: float, episode_id: str): | |
| if dominant_critique_class not in self.records: | |
| dominant_critique_class = "hook_weakness" | |
| rec = self.records[dominant_critique_class] | |
| rec.total_episodes += 1 | |
| rec.last_10_r4_scores.append(r4_score) | |
| if len(rec.last_10_r4_scores) > 10: | |
| rec.last_10_r4_scores.pop(0) | |
| rec.avg_r4_score = sum(rec.last_10_r4_scores) / len(rec.last_10_r4_scores) | |
| resolved = r4_score >= 0.8 | |
| if resolved: | |
| rec.resolved_episodes += 1 | |
| rec.consecutive_resolutions += 1 | |
| else: | |
| rec.consecutive_resolutions = 0 | |
| rec.is_mastered = False | |
| if rec.consecutive_resolutions >= rec.mastery_threshold: | |
| rec.is_mastered = True | |
| self._save() | |
| def get_next_difficulty_class(self) -> str: | |
| mastered = self.get_mastered_classes() | |
| if mastered: | |
| return mastered[0] | |
| eligible = [ | |
| cls for cls, rec in self.records.items() | |
| if rec.total_episodes >= 3 and not rec.is_mastered | |
| ] | |
| if eligible: | |
| return min(eligible, key=lambda c: self.records[c].avg_r4_score) | |
| return "hook_weakness" | |
| def get_mastered_classes(self) -> List[str]: | |
| return [cls for cls, rec in self.records.items() if rec.is_mastered] | |
| def get_hardest_unsolved_class(self) -> str: | |
| candidates = [ | |
| (cls, rec) for cls, rec in self.records.items() | |
| if not rec.is_mastered and rec.total_episodes > 0 | |
| ] | |
| if not candidates: | |
| return "hook_weakness" | |
| return min(candidates, key=lambda x: x[1].avg_r4_score)[0] | |
| def summary(self) -> dict: | |
| return { | |
| "mastered_classes": self.get_mastered_classes(), | |
| "hardest_unsolved": self.get_hardest_unsolved_class(), | |
| "records": { | |
| cls: { | |
| "total_episodes": rec.total_episodes, | |
| "resolved_episodes": rec.resolved_episodes, | |
| "consecutive_resolutions": rec.consecutive_resolutions, | |
| "is_mastered": rec.is_mastered, | |
| "avg_r4_score": round(rec.avg_r4_score, 4), | |
| } | |
| for cls, rec in self.records.items() | |
| }, | |
| } | |