Spaces:
Sleeping
Sleeping
OpenEnv Agent commited on
Commit ·
cd5a326
1
Parent(s): c3e6ffa
fix: scores strictly between 0 and 1 exclusive (0.01-0.99), no exact boundaries
Browse files- app/environment.py +28 -42
- app/models.py +7 -7
- app/tasks.py +35 -73
app/environment.py
CHANGED
|
@@ -19,13 +19,12 @@ class IncidentResponseEnv:
|
|
| 19 |
def _reset_state(self):
|
| 20 |
self.step_count = 0
|
| 21 |
self.done = False
|
| 22 |
-
self.cumulative_score = 0.
|
| 23 |
self.history = []
|
| 24 |
self.grader_state: Dict[str, Any] = {
|
| 25 |
"investigated_root_cause": False,
|
| 26 |
"correct_escalation": False,
|
| 27 |
"correct_fix_applied": False,
|
| 28 |
-
# medium/hard extras
|
| 29 |
"identified_cascade_origin": False,
|
| 30 |
"identified_root_cause": False,
|
| 31 |
"investigated_config_service": False,
|
|
@@ -41,7 +40,6 @@ class IncidentResponseEnv:
|
|
| 41 |
def state(self) -> StateModel:
|
| 42 |
grader_fn = GRADERS[self.task_id]
|
| 43 |
score, _ = grader_fn(self.grader_state)
|
| 44 |
-
score = max(0.01, min(0.99, score))
|
| 45 |
return StateModel(
|
| 46 |
task_id=self.task_id,
|
| 47 |
step=self.step_count,
|
|
@@ -55,42 +53,40 @@ class IncidentResponseEnv:
|
|
| 55 |
def step(self, action: Action) -> StepResult:
|
| 56 |
if self.done:
|
| 57 |
obs = self._build_observation("Episode already finished.")
|
| 58 |
-
return StepResult(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
self.step_count += 1
|
| 61 |
reward_value, reward_reason = self._process_action(action)
|
| 62 |
|
| 63 |
-
# penalize no_op
|
| 64 |
if action.action_type == ActionType.NO_OP:
|
| 65 |
self.grader_state["no_op_count"] += 1
|
| 66 |
-
reward_value = -0.
|
| 67 |
reward_reason = "no_op penalty"
|
| 68 |
|
| 69 |
-
# penalize excessive no_ops
|
| 70 |
if self.grader_state["no_op_count"] >= 3:
|
| 71 |
-
reward_value = -0.
|
| 72 |
reward_reason = "repeated no_op — agent appears stuck"
|
| 73 |
|
| 74 |
grader_fn = GRADERS[self.task_id]
|
| 75 |
current_score, _ = grader_fn(self.grader_state)
|
| 76 |
-
current_score = max(0.01, min(0.99, current_score))
|
| 77 |
|
| 78 |
-
# check done conditions
|
| 79 |
max_steps_reached = self.step_count >= self.meta["max_steps"]
|
| 80 |
task_complete = self._is_task_complete()
|
| 81 |
|
| 82 |
if task_complete:
|
| 83 |
self.done = True
|
| 84 |
-
|
| 85 |
-
reward_reason += " | task complete bonus"
|
| 86 |
|
| 87 |
if max_steps_reached and not self.done:
|
| 88 |
self.done = True
|
| 89 |
-
reward_value = min(reward_value, -0.05)
|
| 90 |
reward_reason += " | max steps reached"
|
| 91 |
|
| 92 |
self.cumulative_score = current_score
|
| 93 |
-
|
| 94 |
self.history.append({
|
| 95 |
"step": self.step_count,
|
| 96 |
"action": action.model_dump(),
|
|
@@ -113,44 +109,37 @@ class IncidentResponseEnv:
|
|
| 113 |
|
| 114 |
if atype == ActionType.INVESTIGATE:
|
| 115 |
return self._handle_investigate(target)
|
| 116 |
-
|
| 117 |
elif atype == ActionType.ESCALATE:
|
| 118 |
correct = scenario["correct_escalation"]
|
| 119 |
if target == correct:
|
| 120 |
if not self.grader_state["correct_escalation"]:
|
| 121 |
self.grader_state["correct_escalation"] = True
|
| 122 |
-
return 0.
|
| 123 |
-
return 0.
|
| 124 |
-
|
| 125 |
-
return -0.1, f"wrong escalation target: {target}"
|
| 126 |
-
|
| 127 |
elif atype == ActionType.APPLY_FIX:
|
| 128 |
correct = scenario["correct_fix"]
|
| 129 |
if target == correct:
|
| 130 |
if not self.grader_state["correct_fix_applied"]:
|
| 131 |
self.grader_state["correct_fix_applied"] = True
|
| 132 |
-
return 0.
|
| 133 |
-
return 0.
|
| 134 |
-
|
| 135 |
-
return -0.15, f"wrong fix applied: {target} — may worsen incident"
|
| 136 |
-
|
| 137 |
elif atype == ActionType.POSTMORTEM:
|
| 138 |
return self._handle_postmortem(action.details or "")
|
| 139 |
-
|
| 140 |
-
return 0.0, "unknown action"
|
| 141 |
|
| 142 |
def _handle_investigate(self, target: str) -> Tuple[float, str]:
|
| 143 |
scenario = self.scenario
|
| 144 |
root_cause = scenario["root_cause"]
|
| 145 |
|
| 146 |
-
# task-specific investigation tracking
|
| 147 |
if self.task_id == "task_hard":
|
| 148 |
if target == "config-service" and not self.grader_state["investigated_config_service"]:
|
| 149 |
self.grader_state["investigated_config_service"] = True
|
| 150 |
-
return 0.15, "investigated config-service —
|
| 151 |
if target == "auth-service" and not self.grader_state["investigated_auth_service"]:
|
| 152 |
self.grader_state["investigated_auth_service"] = True
|
| 153 |
-
return 0.10, "investigated auth-service —
|
| 154 |
|
| 155 |
if target == root_cause:
|
| 156 |
if not self.grader_state["investigated_root_cause"]:
|
|
@@ -159,31 +148,28 @@ class IncidentResponseEnv:
|
|
| 159 |
self.grader_state["identified_cascade_origin"] = True
|
| 160 |
if self.task_id == "task_hard":
|
| 161 |
self.grader_state["identified_root_cause"] = True
|
| 162 |
-
return 0.
|
| 163 |
return 0.05, f"re-investigated {target} — no new findings"
|
| 164 |
|
| 165 |
if target in scenario.get("available_services", []):
|
| 166 |
-
return 0.05, f"investigated {target} — no critical issues
|
| 167 |
-
|
| 168 |
return -0.05, f"unknown service: {target}"
|
| 169 |
|
| 170 |
def _handle_postmortem(self, text: str) -> Tuple[float, str]:
|
| 171 |
keywords = self.scenario.get("postmortem_keywords", [])
|
| 172 |
if not keywords:
|
| 173 |
-
|
| 174 |
-
return 0.0, "postmortem not required for this task"
|
| 175 |
-
|
| 176 |
text_lower = text.lower()
|
| 177 |
matched = [kw for kw in keywords if kw in text_lower]
|
| 178 |
quality = len(matched) / len(keywords)
|
| 179 |
-
self.grader_state["postmortem_quality"] = max(
|
| 180 |
-
|
|
|
|
| 181 |
if quality >= 0.8:
|
| 182 |
-
return 0.
|
| 183 |
elif quality >= 0.5:
|
| 184 |
-
return 0.
|
| 185 |
-
|
| 186 |
-
return 0.0, f"low quality postmortem ({len(matched)}/{len(keywords)} keywords)"
|
| 187 |
|
| 188 |
def _is_task_complete(self) -> bool:
|
| 189 |
gs = self.grader_state
|
|
|
|
| 19 |
def _reset_state(self):
|
| 20 |
self.step_count = 0
|
| 21 |
self.done = False
|
| 22 |
+
self.cumulative_score = 0.01
|
| 23 |
self.history = []
|
| 24 |
self.grader_state: Dict[str, Any] = {
|
| 25 |
"investigated_root_cause": False,
|
| 26 |
"correct_escalation": False,
|
| 27 |
"correct_fix_applied": False,
|
|
|
|
| 28 |
"identified_cascade_origin": False,
|
| 29 |
"identified_root_cause": False,
|
| 30 |
"investigated_config_service": False,
|
|
|
|
| 40 |
def state(self) -> StateModel:
|
| 41 |
grader_fn = GRADERS[self.task_id]
|
| 42 |
score, _ = grader_fn(self.grader_state)
|
|
|
|
| 43 |
return StateModel(
|
| 44 |
task_id=self.task_id,
|
| 45 |
step=self.step_count,
|
|
|
|
| 53 |
def step(self, action: Action) -> StepResult:
|
| 54 |
if self.done:
|
| 55 |
obs = self._build_observation("Episode already finished.")
|
| 56 |
+
return StepResult(
|
| 57 |
+
observation=obs,
|
| 58 |
+
reward=Reward(value=0.01, reason="already done"),
|
| 59 |
+
done=True,
|
| 60 |
+
info={},
|
| 61 |
+
)
|
| 62 |
|
| 63 |
self.step_count += 1
|
| 64 |
reward_value, reward_reason = self._process_action(action)
|
| 65 |
|
|
|
|
| 66 |
if action.action_type == ActionType.NO_OP:
|
| 67 |
self.grader_state["no_op_count"] += 1
|
| 68 |
+
reward_value = -0.05
|
| 69 |
reward_reason = "no_op penalty"
|
| 70 |
|
|
|
|
| 71 |
if self.grader_state["no_op_count"] >= 3:
|
| 72 |
+
reward_value = -0.10
|
| 73 |
reward_reason = "repeated no_op — agent appears stuck"
|
| 74 |
|
| 75 |
grader_fn = GRADERS[self.task_id]
|
| 76 |
current_score, _ = grader_fn(self.grader_state)
|
|
|
|
| 77 |
|
|
|
|
| 78 |
max_steps_reached = self.step_count >= self.meta["max_steps"]
|
| 79 |
task_complete = self._is_task_complete()
|
| 80 |
|
| 81 |
if task_complete:
|
| 82 |
self.done = True
|
| 83 |
+
reward_reason += " | task complete"
|
|
|
|
| 84 |
|
| 85 |
if max_steps_reached and not self.done:
|
| 86 |
self.done = True
|
|
|
|
| 87 |
reward_reason += " | max steps reached"
|
| 88 |
|
| 89 |
self.cumulative_score = current_score
|
|
|
|
| 90 |
self.history.append({
|
| 91 |
"step": self.step_count,
|
| 92 |
"action": action.model_dump(),
|
|
|
|
| 109 |
|
| 110 |
if atype == ActionType.INVESTIGATE:
|
| 111 |
return self._handle_investigate(target)
|
|
|
|
| 112 |
elif atype == ActionType.ESCALATE:
|
| 113 |
correct = scenario["correct_escalation"]
|
| 114 |
if target == correct:
|
| 115 |
if not self.grader_state["correct_escalation"]:
|
| 116 |
self.grader_state["correct_escalation"] = True
|
| 117 |
+
return 0.30, f"correct escalation to {target}"
|
| 118 |
+
return 0.01, "already escalated correctly"
|
| 119 |
+
return -0.10, f"wrong escalation: {target}"
|
|
|
|
|
|
|
| 120 |
elif atype == ActionType.APPLY_FIX:
|
| 121 |
correct = scenario["correct_fix"]
|
| 122 |
if target == correct:
|
| 123 |
if not self.grader_state["correct_fix_applied"]:
|
| 124 |
self.grader_state["correct_fix_applied"] = True
|
| 125 |
+
return 0.40, f"correct fix applied: {target}"
|
| 126 |
+
return 0.01, "fix already applied"
|
| 127 |
+
return -0.15, f"wrong fix: {target}"
|
|
|
|
|
|
|
| 128 |
elif atype == ActionType.POSTMORTEM:
|
| 129 |
return self._handle_postmortem(action.details or "")
|
| 130 |
+
return 0.01, "unknown action"
|
|
|
|
| 131 |
|
| 132 |
def _handle_investigate(self, target: str) -> Tuple[float, str]:
|
| 133 |
scenario = self.scenario
|
| 134 |
root_cause = scenario["root_cause"]
|
| 135 |
|
|
|
|
| 136 |
if self.task_id == "task_hard":
|
| 137 |
if target == "config-service" and not self.grader_state["investigated_config_service"]:
|
| 138 |
self.grader_state["investigated_config_service"] = True
|
| 139 |
+
return 0.15, "investigated config-service — partial secret rotation failure found"
|
| 140 |
if target == "auth-service" and not self.grader_state["investigated_auth_service"]:
|
| 141 |
self.grader_state["investigated_auth_service"] = True
|
| 142 |
+
return 0.10, "investigated auth-service — JWT verification failures found"
|
| 143 |
|
| 144 |
if target == root_cause:
|
| 145 |
if not self.grader_state["investigated_root_cause"]:
|
|
|
|
| 148 |
self.grader_state["identified_cascade_origin"] = True
|
| 149 |
if self.task_id == "task_hard":
|
| 150 |
self.grader_state["identified_root_cause"] = True
|
| 151 |
+
return 0.30, f"investigated root cause: {target}"
|
| 152 |
return 0.05, f"re-investigated {target} — no new findings"
|
| 153 |
|
| 154 |
if target in scenario.get("available_services", []):
|
| 155 |
+
return 0.05, f"investigated {target} — no critical issues"
|
|
|
|
| 156 |
return -0.05, f"unknown service: {target}"
|
| 157 |
|
| 158 |
def _handle_postmortem(self, text: str) -> Tuple[float, str]:
|
| 159 |
keywords = self.scenario.get("postmortem_keywords", [])
|
| 160 |
if not keywords:
|
| 161 |
+
return 0.01, "postmortem not required for this task"
|
|
|
|
|
|
|
| 162 |
text_lower = text.lower()
|
| 163 |
matched = [kw for kw in keywords if kw in text_lower]
|
| 164 |
quality = len(matched) / len(keywords)
|
| 165 |
+
self.grader_state["postmortem_quality"] = max(
|
| 166 |
+
self.grader_state.get("postmortem_quality", 0), quality
|
| 167 |
+
)
|
| 168 |
if quality >= 0.8:
|
| 169 |
+
return 0.20, f"high quality postmortem ({len(matched)}/{len(keywords)} keywords)"
|
| 170 |
elif quality >= 0.5:
|
| 171 |
+
return 0.10, f"partial postmortem ({len(matched)}/{len(keywords)} keywords)"
|
| 172 |
+
return 0.01, f"low quality postmortem ({len(matched)}/{len(keywords)} keywords)"
|
|
|
|
| 173 |
|
| 174 |
def _is_task_complete(self) -> bool:
|
| 175 |
gs = self.grader_state
|
app/models.py
CHANGED
|
@@ -4,11 +4,11 @@ from enum import Enum
|
|
| 4 |
|
| 5 |
|
| 6 |
class ActionType(str, Enum):
|
| 7 |
-
INVESTIGATE = "investigate"
|
| 8 |
-
ESCALATE = "escalate"
|
| 9 |
-
APPLY_FIX = "apply_fix"
|
| 10 |
-
POSTMORTEM = "postmortem"
|
| 11 |
-
NO_OP = "no_op"
|
| 12 |
|
| 13 |
|
| 14 |
class Action(BaseModel):
|
|
@@ -29,7 +29,7 @@ class Observation(BaseModel):
|
|
| 29 |
|
| 30 |
|
| 31 |
class Reward(BaseModel):
|
| 32 |
-
value: float = Field(...,
|
| 33 |
reason: str
|
| 34 |
|
| 35 |
|
|
@@ -44,7 +44,7 @@ class StateModel(BaseModel):
|
|
| 44 |
task_id: str
|
| 45 |
step: int
|
| 46 |
max_steps: int
|
| 47 |
-
score: float
|
| 48 |
done: bool
|
| 49 |
history: List[Dict[str, Any]] = Field(default_factory=list)
|
| 50 |
grader_state: Dict[str, Any] = Field(default_factory=dict)
|
|
|
|
| 4 |
|
| 5 |
|
| 6 |
class ActionType(str, Enum):
|
| 7 |
+
INVESTIGATE = "investigate"
|
| 8 |
+
ESCALATE = "escalate"
|
| 9 |
+
APPLY_FIX = "apply_fix"
|
| 10 |
+
POSTMORTEM = "postmortem"
|
| 11 |
+
NO_OP = "no_op"
|
| 12 |
|
| 13 |
|
| 14 |
class Action(BaseModel):
|
|
|
|
| 29 |
|
| 30 |
|
| 31 |
class Reward(BaseModel):
|
| 32 |
+
value: float = Field(..., description="Reward signal for this step")
|
| 33 |
reason: str
|
| 34 |
|
| 35 |
|
|
|
|
| 44 |
task_id: str
|
| 45 |
step: int
|
| 46 |
max_steps: int
|
| 47 |
+
score: float = Field(..., description="Task score strictly between 0 and 1 exclusive")
|
| 48 |
done: bool
|
| 49 |
history: List[Dict[str, Any]] = Field(default_factory=list)
|
| 50 |
grader_state: Dict[str, Any] = Field(default_factory=dict)
|
app/tasks.py
CHANGED
|
@@ -1,112 +1,74 @@
|
|
| 1 |
-
"""Task definitions and graders
|
| 2 |
from typing import Any, Dict, Tuple
|
| 3 |
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
def grade_easy(grader_state: Dict[str, Any]) -> Tuple[float, str]:
|
| 6 |
-
"""
|
| 7 |
-
Task 1 (Easy): Single service outage.
|
| 8 |
-
Agent must:
|
| 9 |
-
- Investigate postgres-db (0.3)
|
| 10 |
-
- Escalate to database-team (0.3)
|
| 11 |
-
- Apply fix: increase_db_connections (0.4)
|
| 12 |
-
"""
|
| 13 |
score = 0.0
|
| 14 |
reasons = []
|
| 15 |
-
|
| 16 |
if grader_state.get("investigated_root_cause"):
|
| 17 |
-
score += 0.
|
| 18 |
-
reasons.append("
|
| 19 |
-
|
| 20 |
if grader_state.get("correct_escalation"):
|
| 21 |
-
score += 0.
|
| 22 |
-
reasons.append("
|
| 23 |
-
|
| 24 |
if grader_state.get("correct_fix_applied"):
|
| 25 |
-
score += 0.
|
| 26 |
-
reasons.append("
|
| 27 |
-
|
| 28 |
-
score = max(0.01, min(0.99, score))
|
| 29 |
-
return round(score, 2), "; ".join(reasons) if reasons else "no progress"
|
| 30 |
|
| 31 |
|
| 32 |
def grade_medium(grader_state: Dict[str, Any]) -> Tuple[float, str]:
|
| 33 |
-
"""
|
| 34 |
-
Task 2 (Medium): Cascading failure.
|
| 35 |
-
Agent must:
|
| 36 |
-
- Investigate redis-cache (0.2)
|
| 37 |
-
- Identify it as root cause (0.2)
|
| 38 |
-
- Escalate to infra-team (0.2)
|
| 39 |
-
- Apply fix: flush_redis_cache (0.2)
|
| 40 |
-
- Submit postmortem mentioning redis/memory/oom (0.2)
|
| 41 |
-
"""
|
| 42 |
score = 0.0
|
| 43 |
reasons = []
|
| 44 |
-
|
| 45 |
if grader_state.get("investigated_root_cause"):
|
| 46 |
-
score += 0.
|
| 47 |
-
reasons.append("investigated
|
| 48 |
-
|
| 49 |
if grader_state.get("identified_cascade_origin"):
|
| 50 |
-
score += 0.
|
| 51 |
-
reasons.append("identified cascade origin (+0.
|
| 52 |
-
|
| 53 |
if grader_state.get("correct_escalation"):
|
| 54 |
-
score += 0.
|
| 55 |
-
reasons.append("correct escalation (+0.
|
| 56 |
-
|
| 57 |
if grader_state.get("correct_fix_applied"):
|
| 58 |
-
score += 0.
|
| 59 |
-
reasons.append("correct fix applied (+0.
|
| 60 |
-
|
| 61 |
-
if
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
return round(min(score, 1.0), 2), "; ".join(reasons) if reasons else "no progress"
|
| 67 |
|
| 68 |
|
| 69 |
def grade_hard(grader_state: Dict[str, Any]) -> Tuple[float, str]:
|
| 70 |
-
"""
|
| 71 |
-
Task 3 (Hard): Intermittent auth failure.
|
| 72 |
-
Agent must:
|
| 73 |
-
- Investigate config-service (0.15)
|
| 74 |
-
- Investigate auth-service (0.1)
|
| 75 |
-
- Identify root cause as config-service (0.2)
|
| 76 |
-
- Escalate to security-team (0.15)
|
| 77 |
-
- Apply correct fix: complete_secret_rotation (0.2)
|
| 78 |
-
- Submit detailed postmortem with 4+ keywords (0.2)
|
| 79 |
-
"""
|
| 80 |
score = 0.0
|
| 81 |
reasons = []
|
| 82 |
-
|
| 83 |
if grader_state.get("investigated_config_service"):
|
| 84 |
score += 0.15
|
| 85 |
reasons.append("investigated config-service (+0.15)")
|
| 86 |
-
|
| 87 |
if grader_state.get("investigated_auth_service"):
|
| 88 |
score += 0.10
|
| 89 |
reasons.append("investigated auth-service (+0.10)")
|
| 90 |
-
|
| 91 |
if grader_state.get("identified_root_cause"):
|
| 92 |
score += 0.20
|
| 93 |
reasons.append("identified root cause (+0.20)")
|
| 94 |
-
|
| 95 |
if grader_state.get("correct_escalation"):
|
| 96 |
-
score += 0.
|
| 97 |
-
reasons.append("correct escalation (+0.
|
| 98 |
-
|
| 99 |
if grader_state.get("correct_fix_applied"):
|
| 100 |
score += 0.20
|
| 101 |
reasons.append("correct fix applied (+0.20)")
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
score +=
|
| 106 |
-
reasons.append(f"postmortem quality (+{
|
| 107 |
-
|
| 108 |
-
score = max(0.01, min(0.99, score))
|
| 109 |
-
return round(min(score, 1.0), 2), "; ".join(reasons) if reasons else "no progress"
|
| 110 |
|
| 111 |
|
| 112 |
GRADERS = {
|
|
|
|
| 1 |
+
"""Task definitions and graders — scores strictly between 0 and 1 exclusive."""
|
| 2 |
from typing import Any, Dict, Tuple
|
| 3 |
|
| 4 |
|
| 5 |
+
def _strict(score: float) -> float:
|
| 6 |
+
"""Ensure score is strictly between 0 and 1 (never 0.0 or 1.0)."""
|
| 7 |
+
return round(max(0.01, min(0.99, score)), 2)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
def grade_easy(grader_state: Dict[str, Any]) -> Tuple[float, str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
score = 0.0
|
| 12 |
reasons = []
|
|
|
|
| 13 |
if grader_state.get("investigated_root_cause"):
|
| 14 |
+
score += 0.32
|
| 15 |
+
reasons.append("investigated root cause (+0.32)")
|
|
|
|
| 16 |
if grader_state.get("correct_escalation"):
|
| 17 |
+
score += 0.32
|
| 18 |
+
reasons.append("correct escalation (+0.32)")
|
|
|
|
| 19 |
if grader_state.get("correct_fix_applied"):
|
| 20 |
+
score += 0.35
|
| 21 |
+
reasons.append("correct fix applied (+0.35)")
|
| 22 |
+
return _strict(score), "; ".join(reasons) if reasons else "no progress"
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
def grade_medium(grader_state: Dict[str, Any]) -> Tuple[float, str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
score = 0.0
|
| 27 |
reasons = []
|
|
|
|
| 28 |
if grader_state.get("investigated_root_cause"):
|
| 29 |
+
score += 0.20
|
| 30 |
+
reasons.append("investigated root cause (+0.20)")
|
|
|
|
| 31 |
if grader_state.get("identified_cascade_origin"):
|
| 32 |
+
score += 0.19
|
| 33 |
+
reasons.append("identified cascade origin (+0.19)")
|
|
|
|
| 34 |
if grader_state.get("correct_escalation"):
|
| 35 |
+
score += 0.20
|
| 36 |
+
reasons.append("correct escalation (+0.20)")
|
|
|
|
| 37 |
if grader_state.get("correct_fix_applied"):
|
| 38 |
+
score += 0.20
|
| 39 |
+
reasons.append("correct fix applied (+0.20)")
|
| 40 |
+
pm = grader_state.get("postmortem_quality", 0)
|
| 41 |
+
if pm > 0:
|
| 42 |
+
pts = round(0.20 * pm, 2)
|
| 43 |
+
score += pts
|
| 44 |
+
reasons.append(f"postmortem quality (+{pts})")
|
| 45 |
+
return _strict(score), "; ".join(reasons) if reasons else "no progress"
|
|
|
|
| 46 |
|
| 47 |
|
| 48 |
def grade_hard(grader_state: Dict[str, Any]) -> Tuple[float, str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
score = 0.0
|
| 50 |
reasons = []
|
|
|
|
| 51 |
if grader_state.get("investigated_config_service"):
|
| 52 |
score += 0.15
|
| 53 |
reasons.append("investigated config-service (+0.15)")
|
|
|
|
| 54 |
if grader_state.get("investigated_auth_service"):
|
| 55 |
score += 0.10
|
| 56 |
reasons.append("investigated auth-service (+0.10)")
|
|
|
|
| 57 |
if grader_state.get("identified_root_cause"):
|
| 58 |
score += 0.20
|
| 59 |
reasons.append("identified root cause (+0.20)")
|
|
|
|
| 60 |
if grader_state.get("correct_escalation"):
|
| 61 |
+
score += 0.14
|
| 62 |
+
reasons.append("correct escalation (+0.14)")
|
|
|
|
| 63 |
if grader_state.get("correct_fix_applied"):
|
| 64 |
score += 0.20
|
| 65 |
reasons.append("correct fix applied (+0.20)")
|
| 66 |
+
pm = grader_state.get("postmortem_quality", 0)
|
| 67 |
+
if pm > 0:
|
| 68 |
+
pts = round(0.20 * pm, 2)
|
| 69 |
+
score += pts
|
| 70 |
+
reasons.append(f"postmortem quality (+{pts})")
|
| 71 |
+
return _strict(score), "; ".join(reasons) if reasons else "no progress"
|
|
|
|
|
|
|
| 72 |
|
| 73 |
|
| 74 |
GRADERS = {
|