Arijit-07's picture
feat: ARIA Incident Generator — procedural incidents from seeds
4887b5f
from __future__ import annotations
from collections import deque
class CurriculumEngine:
def __init__(self):
self.tasks = ["easy", "medium", "hard", "bonus", "security", "database", "failover"]
self.scores: dict[str, deque] = {
task_id: deque(maxlen=5) for task_id in self.tasks
}
self.mastery: dict[str, int] = {task_id: 0 for task_id in self.tasks}
self._hints: dict[str, str] = {
"easy": "Focus on the service with highest memory_percent. Read its logs before acting.",
"medium": "Follow the dependency map backwards from the erroring service to find the root cause.",
"hard": "All services look green. Check WARN-level logs and business metrics, not error rates.",
"bonus": "There are two independent failures. Fix each one separately — do not conflate them.",
"security": "Look for repeated login failures from the same IP range in the access logs.",
"database": "Check for sequential scans in slow query logs. The fix is structural, not a restart.",
"failover": "Read the failover runbook first. Not all services are safe — check compliance constraints.",
}
self._rotation_index = 0
self._total_episodes_recorded = 0
def _ensure_task(self, task_id: str) -> None:
if task_id not in self.scores:
raise ValueError(f"Unknown task_id: {task_id}")
def record_episode(self, task_id: str, score: float) -> None:
self._ensure_task(task_id)
self.scores[task_id].append(float(score))
self._total_episodes_recorded += 1
self._update_mastery(task_id)
def _update_mastery(self, task_id: str) -> None:
self._ensure_task(task_id)
rolling_avg = self.get_rolling_avg(task_id)
if rolling_avg > 0.75 and self.mastery[task_id] < 3:
self.mastery[task_id] += 1
elif rolling_avg < 0.30 and self.mastery[task_id] > 0:
self.mastery[task_id] -= 1
def get_mastery(self, task_id: str) -> int:
self._ensure_task(task_id)
return self.mastery[task_id]
def get_rolling_avg(self, task_id: str) -> float:
self._ensure_task(task_id)
recent_scores = self.scores[task_id]
if not recent_scores:
return 0.0
return sum(recent_scores) / len(recent_scores)
def should_scaffold(self, task_id: str) -> bool:
self._ensure_task(task_id)
return len(self.scores[task_id]) >= 3 and self.get_rolling_avg(task_id) < 0.30
def get_hint(self, task_id: str) -> str:
self._ensure_task(task_id)
return self._hints[task_id]
def _get_non_mastered_tasks(self) -> list[str]:
return [task_id for task_id in self.tasks if self.mastery[task_id] < 3]
def _sorted_candidates(self) -> list[str]:
candidates = self._get_non_mastered_tasks()
return sorted(
candidates,
key=lambda task_id: (self.get_rolling_avg(task_id), self.tasks.index(task_id)),
)
def get_recommended_task(self) -> str:
candidates = self._sorted_candidates()
if not candidates:
return "bonus"
return candidates[0]
def get_next_curriculum_task(self) -> str:
candidates = self._sorted_candidates()
if not candidates:
return "bonus"
task_id = candidates[self._rotation_index % len(candidates)]
self._rotation_index = (self._rotation_index + 1) % len(candidates)
return task_id
def get_status(self) -> dict:
mastery_labels = {
0: "novice",
1: "intermediate",
2: "advanced",
3: "mastered",
}
tasks = {}
for task_id in self.tasks:
scaffold_needed = self.should_scaffold(task_id)
tasks[task_id] = {
"mastery_level": self.mastery[task_id],
"mastery_label": mastery_labels[self.mastery[task_id]],
"rolling_avg": self.get_rolling_avg(task_id),
"recent_scores": list(self.scores[task_id]),
"scaffold_needed": scaffold_needed,
"hint": self.get_hint(task_id) if scaffold_needed else None,
}
return {
"tasks": tasks,
"recommended_task": self.get_recommended_task(),
"total_episodes_recorded": self._total_episodes_recorded,
}