added the run_episode function
Browse files- graders/grader_classification.py +6 -10
- graders/grader_detection.py +6 -9
- graders/grader_fix.py +6 -9
- inference.py +5 -5
- server/fin_auditor_environment.py +1 -1
- tasks/task1_easy.py +37 -1
- tasks/task2_medium.py +38 -1
- tasks/task3_hard.py +38 -1
graders/grader_classification.py
CHANGED
|
@@ -13,31 +13,27 @@ class MediumClassificationGrader:
|
|
| 13 |
def __init__(self) -> None:
|
| 14 |
self.last_breakdown: dict[str, Any] = {}
|
| 15 |
|
| 16 |
-
def grade(self, state: Any, ground_truth: dict[str, Any] | None = None) -> float:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
tp = float(getattr(state, "total_tp", 0))
|
| 18 |
tn = float(getattr(state, "total_tn", 0))
|
| 19 |
fp = float(getattr(state, "total_fp", 0))
|
| 20 |
fn = float(getattr(state, "total_fn", 0))
|
| 21 |
|
| 22 |
-
total = tp + tn + fp + fn
|
| 23 |
-
|
| 24 |
-
# The maximum possible score if they made zero mistakes
|
| 25 |
actual_anomalies = tp + fn
|
| 26 |
actual_valid = tn + fp
|
| 27 |
-
|
| 28 |
perfect_signal = (actual_anomalies * _TP_WEIGHT) + (actual_valid * _TN_WEIGHT)
|
| 29 |
-
|
| 30 |
if perfect_signal == 0:
|
| 31 |
return 0.1
|
| 32 |
|
| 33 |
positive_signal = (tp * _TP_WEIGHT) + (tn * _TN_WEIGHT)
|
| 34 |
negative_signal = (fp * _FP_PENALTY) + (fn * _FN_PENALTY)
|
| 35 |
|
| 36 |
-
# Normalize against the true perfect scenario
|
| 37 |
raw_score = max(0.0, positive_signal - negative_signal) / perfect_signal
|
| 38 |
-
|
| 39 |
-
# Strict hackathon boundary
|
| 40 |
score = max(0.1, min(0.99, raw_score))
|
| 41 |
-
|
| 42 |
self.last_breakdown = {"tp": int(tp), "tn": int(tn), "fp": int(fp), "fn": int(fn), "score": score}
|
| 43 |
return score
|
|
|
|
| 13 |
def __init__(self) -> None:
|
| 14 |
self.last_breakdown: dict[str, Any] = {}
|
| 15 |
|
| 16 |
+
def grade(self, state: Any = None, ground_truth: dict[str, Any] | None = None) -> float:
|
| 17 |
+
if state is None:
|
| 18 |
+
self.last_breakdown = {"error": "empty_state_ping", "score": 0.1}
|
| 19 |
+
return 0.1
|
| 20 |
+
|
| 21 |
tp = float(getattr(state, "total_tp", 0))
|
| 22 |
tn = float(getattr(state, "total_tn", 0))
|
| 23 |
fp = float(getattr(state, "total_fp", 0))
|
| 24 |
fn = float(getattr(state, "total_fn", 0))
|
| 25 |
|
|
|
|
|
|
|
|
|
|
| 26 |
actual_anomalies = tp + fn
|
| 27 |
actual_valid = tn + fp
|
|
|
|
| 28 |
perfect_signal = (actual_anomalies * _TP_WEIGHT) + (actual_valid * _TN_WEIGHT)
|
| 29 |
+
|
| 30 |
if perfect_signal == 0:
|
| 31 |
return 0.1
|
| 32 |
|
| 33 |
positive_signal = (tp * _TP_WEIGHT) + (tn * _TN_WEIGHT)
|
| 34 |
negative_signal = (fp * _FP_PENALTY) + (fn * _FN_PENALTY)
|
| 35 |
|
|
|
|
| 36 |
raw_score = max(0.0, positive_signal - negative_signal) / perfect_signal
|
|
|
|
|
|
|
| 37 |
score = max(0.1, min(0.99, raw_score))
|
|
|
|
| 38 |
self.last_breakdown = {"tp": int(tp), "tn": int(tn), "fp": int(fp), "fn": int(fn), "score": score}
|
| 39 |
return score
|
graders/grader_detection.py
CHANGED
|
@@ -13,30 +13,27 @@ class EasyDetectionGrader:
|
|
| 13 |
def __init__(self) -> None:
|
| 14 |
self.last_breakdown: dict[str, Any] = {}
|
| 15 |
|
| 16 |
-
def grade(self, state: Any, ground_truth: dict[str, Any] | None = None) -> float:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
tp = float(getattr(state, "total_tp", 0))
|
| 18 |
tn = float(getattr(state, "total_tn", 0))
|
| 19 |
fp = float(getattr(state, "total_fp", 0))
|
| 20 |
fn = float(getattr(state, "total_fn", 0))
|
| 21 |
|
| 22 |
-
total = tp + tn + fp + fn
|
| 23 |
-
|
| 24 |
-
# The maximum possible score if they made zero mistakes
|
| 25 |
actual_anomalies = tp + fn
|
| 26 |
actual_valid = tn + fp
|
| 27 |
-
|
| 28 |
perfect_signal = (actual_anomalies * _TP_WEIGHT) + (actual_valid * _TN_WEIGHT)
|
| 29 |
-
|
| 30 |
if perfect_signal == 0:
|
| 31 |
return 0.1
|
| 32 |
|
| 33 |
positive_signal = (tp * _TP_WEIGHT) + (tn * _TN_WEIGHT)
|
| 34 |
negative_signal = (fp * _FP_PENALTY) + (fn * _FN_PENALTY)
|
| 35 |
|
| 36 |
-
# Normalize against the true perfect scenario
|
| 37 |
raw_score = max(0.0, positive_signal - negative_signal) / perfect_signal
|
| 38 |
-
|
| 39 |
-
# Strict hackathon boundary
|
| 40 |
score = max(0.1, min(0.99, raw_score))
|
| 41 |
self.last_breakdown = {"tp": int(tp), "tn": int(tn), "fp": int(fp), "fn": int(fn), "score": score}
|
| 42 |
return score
|
|
|
|
| 13 |
def __init__(self) -> None:
|
| 14 |
self.last_breakdown: dict[str, Any] = {}
|
| 15 |
|
| 16 |
+
def grade(self, state: Any = None, ground_truth: dict[str, Any] | None = None) -> float:
|
| 17 |
+
if state is None:
|
| 18 |
+
self.last_breakdown = {"error": "empty_state_ping", "score": 0.1}
|
| 19 |
+
return 0.1
|
| 20 |
+
|
| 21 |
tp = float(getattr(state, "total_tp", 0))
|
| 22 |
tn = float(getattr(state, "total_tn", 0))
|
| 23 |
fp = float(getattr(state, "total_fp", 0))
|
| 24 |
fn = float(getattr(state, "total_fn", 0))
|
| 25 |
|
|
|
|
|
|
|
|
|
|
| 26 |
actual_anomalies = tp + fn
|
| 27 |
actual_valid = tn + fp
|
|
|
|
| 28 |
perfect_signal = (actual_anomalies * _TP_WEIGHT) + (actual_valid * _TN_WEIGHT)
|
| 29 |
+
|
| 30 |
if perfect_signal == 0:
|
| 31 |
return 0.1
|
| 32 |
|
| 33 |
positive_signal = (tp * _TP_WEIGHT) + (tn * _TN_WEIGHT)
|
| 34 |
negative_signal = (fp * _FP_PENALTY) + (fn * _FN_PENALTY)
|
| 35 |
|
|
|
|
| 36 |
raw_score = max(0.0, positive_signal - negative_signal) / perfect_signal
|
|
|
|
|
|
|
| 37 |
score = max(0.1, min(0.99, raw_score))
|
| 38 |
self.last_breakdown = {"tp": int(tp), "tn": int(tn), "fp": int(fp), "fn": int(fn), "score": score}
|
| 39 |
return score
|
graders/grader_fix.py
CHANGED
|
@@ -13,30 +13,27 @@ class HardFixGrader:
|
|
| 13 |
def __init__(self) -> None:
|
| 14 |
self.last_breakdown: dict[str, Any] = {}
|
| 15 |
|
| 16 |
-
def grade(self, state: Any, ground_truth: dict[str, Any] | None = None) -> float:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
tp = float(getattr(state, "total_tp", 0))
|
| 18 |
tn = float(getattr(state, "total_tn", 0))
|
| 19 |
fp = float(getattr(state, "total_fp", 0))
|
| 20 |
fn = float(getattr(state, "total_fn", 0))
|
| 21 |
|
| 22 |
-
total = tp + tn + fp + fn
|
| 23 |
-
|
| 24 |
-
# The maximum possible score if they made zero mistakes
|
| 25 |
actual_anomalies = tp + fn
|
| 26 |
actual_valid = tn + fp
|
| 27 |
-
|
| 28 |
perfect_signal = (actual_anomalies * _TP_WEIGHT) + (actual_valid * _TN_WEIGHT)
|
| 29 |
-
|
| 30 |
if perfect_signal == 0:
|
| 31 |
return 0.1
|
| 32 |
|
| 33 |
positive_signal = (tp * _TP_WEIGHT) + (tn * _TN_WEIGHT)
|
| 34 |
negative_signal = (fp * _FP_PENALTY) + (fn * _FN_PENALTY)
|
| 35 |
|
| 36 |
-
# Normalize against the true perfect scenario
|
| 37 |
raw_score = max(0.0, positive_signal - negative_signal) / perfect_signal
|
| 38 |
-
|
| 39 |
-
# Strict hackathon boundary
|
| 40 |
score = max(0.1, min(0.99, raw_score))
|
| 41 |
self.last_breakdown = {"tp": int(tp), "tn": int(tn), "fp": int(fp), "fn": int(fn), "score": score}
|
| 42 |
return score
|
|
|
|
| 13 |
def __init__(self) -> None:
|
| 14 |
self.last_breakdown: dict[str, Any] = {}
|
| 15 |
|
| 16 |
+
def grade(self, state: Any = None, ground_truth: dict[str, Any] | None = None) -> float:
|
| 17 |
+
if state is None:
|
| 18 |
+
self.last_breakdown = {"error": "empty_state_ping", "score": 0.1}
|
| 19 |
+
return 0.1
|
| 20 |
+
|
| 21 |
tp = float(getattr(state, "total_tp", 0))
|
| 22 |
tn = float(getattr(state, "total_tn", 0))
|
| 23 |
fp = float(getattr(state, "total_fp", 0))
|
| 24 |
fn = float(getattr(state, "total_fn", 0))
|
| 25 |
|
|
|
|
|
|
|
|
|
|
| 26 |
actual_anomalies = tp + fn
|
| 27 |
actual_valid = tn + fp
|
|
|
|
| 28 |
perfect_signal = (actual_anomalies * _TP_WEIGHT) + (actual_valid * _TN_WEIGHT)
|
| 29 |
+
|
| 30 |
if perfect_signal == 0:
|
| 31 |
return 0.1
|
| 32 |
|
| 33 |
positive_signal = (tp * _TP_WEIGHT) + (tn * _TN_WEIGHT)
|
| 34 |
negative_signal = (fp * _FP_PENALTY) + (fn * _FN_PENALTY)
|
| 35 |
|
|
|
|
| 36 |
raw_score = max(0.0, positive_signal - negative_signal) / perfect_signal
|
|
|
|
|
|
|
| 37 |
score = max(0.1, min(0.99, raw_score))
|
| 38 |
self.last_breakdown = {"tp": int(tp), "tn": int(tn), "fp": int(fp), "fn": int(fn), "score": score}
|
| 39 |
return score
|
inference.py
CHANGED
|
@@ -190,7 +190,7 @@ def run_inference() -> None:
|
|
| 190 |
action = AuditorAction(decisions=decisions)
|
| 191 |
|
| 192 |
obs = env.step(action)
|
| 193 |
-
step_reward = float(obs.reward) if obs.reward is not None else 0.
|
| 194 |
all_rewards.append(step_reward)
|
| 195 |
steps_completed = step_num
|
| 196 |
|
|
@@ -213,13 +213,13 @@ def run_inference() -> None:
|
|
| 213 |
finally:
|
| 214 |
# 4. The Ultimate Safety Clamp
|
| 215 |
if not all_rewards:
|
| 216 |
-
all_rewards = [0.
|
| 217 |
|
| 218 |
current_sum = sum(all_rewards)
|
| 219 |
|
| 220 |
-
if current_sum <= 0.
|
| 221 |
-
# If the script crashed or agent scored
|
| 222 |
-
all_rewards[-1] = 0.
|
| 223 |
elif current_sum >= 1.0:
|
| 224 |
# If floating point math drifted to 1.0+, force the final entry down
|
| 225 |
excess = current_sum - 0.99
|
|
|
|
| 190 |
action = AuditorAction(decisions=decisions)
|
| 191 |
|
| 192 |
obs = env.step(action)
|
| 193 |
+
step_reward = float(obs.reward) if obs.reward is not None else 0.1
|
| 194 |
all_rewards.append(step_reward)
|
| 195 |
steps_completed = step_num
|
| 196 |
|
|
|
|
| 213 |
finally:
|
| 214 |
# 4. The Ultimate Safety Clamp
|
| 215 |
if not all_rewards:
|
| 216 |
+
all_rewards = [0.1]
|
| 217 |
|
| 218 |
current_sum = sum(all_rewards)
|
| 219 |
|
| 220 |
+
if current_sum <= 0.1:
|
| 221 |
+
# If the script crashed or agent scored nothing, inject the grader floor
|
| 222 |
+
all_rewards[-1] = 0.1
|
| 223 |
elif current_sum >= 1.0:
|
| 224 |
# If floating point math drifted to 1.0+, force the final entry down
|
| 225 |
excess = current_sum - 0.99
|
server/fin_auditor_environment.py
CHANGED
|
@@ -137,7 +137,7 @@ class FinAuditorEnvironment(Environment):
|
|
| 137 |
return FinAuditorObservation(
|
| 138 |
features=anomalies,
|
| 139 |
message=f"Fin Auditor engine ready. {len(anomalies)} trades loaded.",
|
| 140 |
-
reward=0.
|
| 141 |
done=False
|
| 142 |
)
|
| 143 |
|
|
|
|
| 137 |
return FinAuditorObservation(
|
| 138 |
features=anomalies,
|
| 139 |
message=f"Fin Auditor engine ready. {len(anomalies)} trades loaded.",
|
| 140 |
+
reward=0.1,
|
| 141 |
done=False
|
| 142 |
)
|
| 143 |
|
tasks/task1_easy.py
CHANGED
|
@@ -33,4 +33,40 @@ def setup_env(env) -> None:
|
|
| 33 |
env.difficulty = hft_auditor.Difficulty.EASY
|
| 34 |
env._MAX_EPISODE_STEPS = MAX_STEPS
|
| 35 |
except Exception as e:
|
| 36 |
-
print(f"[task_easy] Could not set difficulty: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
env.difficulty = hft_auditor.Difficulty.EASY
|
| 34 |
env._MAX_EPISODE_STEPS = MAX_STEPS
|
| 35 |
except Exception as e:
|
| 36 |
+
print(f"[task_easy] Could not set difficulty: {e}")
|
| 37 |
+
|
| 38 |
+
def run_episode(env, agent_fn) -> dict:
|
| 39 |
+
"""Run a single EASY anomaly-detection episode and return the graded result.
|
| 40 |
+
|
| 41 |
+
Called by the OpenEnv evaluator. agent_fn receives an observation and
|
| 42 |
+
returns a list of binary decisions (0 = valid, 1 = anomaly).
|
| 43 |
+
"""
|
| 44 |
+
setup_env(env)
|
| 45 |
+
total_reward = 0.0
|
| 46 |
+
steps_done = 0
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
obs = env.reset()
|
| 50 |
+
for _ in range(MAX_STEPS):
|
| 51 |
+
decisions = agent_fn(obs)
|
| 52 |
+
from models import AuditorAction
|
| 53 |
+
action = AuditorAction(decisions=decisions)
|
| 54 |
+
obs = env.step(action)
|
| 55 |
+
total_reward += float(obs.reward) if obs.reward is not None else 0.0
|
| 56 |
+
steps_done += 1
|
| 57 |
+
if obs.done:
|
| 58 |
+
break
|
| 59 |
+
except Exception as exc:
|
| 60 |
+
print(f"[task_easy] run_episode error at step {steps_done}: {exc}")
|
| 61 |
+
|
| 62 |
+
# Always grade — even partial data yields a valid score via perfect_signal fallback
|
| 63 |
+
final_score = grader.grade(env.state)
|
| 64 |
+
|
| 65 |
+
return {
|
| 66 |
+
"task": TASK_ID,
|
| 67 |
+
"difficulty": DIFFICULTY,
|
| 68 |
+
"steps": steps_done,
|
| 69 |
+
"total_reward": round(total_reward, 4),
|
| 70 |
+
"score": round(final_score, 4),
|
| 71 |
+
"grader_breakdown": grader.last_breakdown,
|
| 72 |
+
}
|
tasks/task2_medium.py
CHANGED
|
@@ -33,4 +33,41 @@ def setup_env(env) -> None:
|
|
| 33 |
env.difficulty = hft_auditor.Difficulty.MEDIUM
|
| 34 |
env._MAX_EPISODE_STEPS = MAX_STEPS
|
| 35 |
except Exception as e:
|
| 36 |
-
print(f"[task_medium] Could not set difficulty: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
env.difficulty = hft_auditor.Difficulty.MEDIUM
|
| 34 |
env._MAX_EPISODE_STEPS = MAX_STEPS
|
| 35 |
except Exception as e:
|
| 36 |
+
print(f"[task_medium] Could not set difficulty: {e}")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def run_episode(env, agent_fn) -> dict:
|
| 40 |
+
"""Run a single MEDIUM anomaly-detection episode and return the graded result.
|
| 41 |
+
|
| 42 |
+
Called by the OpenEnv evaluator. agent_fn receives an observation and
|
| 43 |
+
returns a list of binary decisions (0 = valid, 1 = anomaly).
|
| 44 |
+
"""
|
| 45 |
+
setup_env(env)
|
| 46 |
+
total_reward = 0.0
|
| 47 |
+
steps_done = 0
|
| 48 |
+
|
| 49 |
+
try:
|
| 50 |
+
obs = env.reset()
|
| 51 |
+
for _ in range(MAX_STEPS):
|
| 52 |
+
decisions = agent_fn(obs)
|
| 53 |
+
from models import AuditorAction
|
| 54 |
+
action = AuditorAction(decisions=decisions)
|
| 55 |
+
obs = env.step(action)
|
| 56 |
+
total_reward += float(obs.reward) if obs.reward is not None else 0.0
|
| 57 |
+
steps_done += 1
|
| 58 |
+
if obs.done:
|
| 59 |
+
break
|
| 60 |
+
except Exception as exc:
|
| 61 |
+
print(f"[task_medium] run_episode error at step {steps_done}: {exc}")
|
| 62 |
+
|
| 63 |
+
# Always grade — even partial data yields a valid score via perfect_signal fallback
|
| 64 |
+
final_score = grader.grade(env.state)
|
| 65 |
+
|
| 66 |
+
return {
|
| 67 |
+
"task": TASK_ID,
|
| 68 |
+
"difficulty": DIFFICULTY,
|
| 69 |
+
"steps": steps_done,
|
| 70 |
+
"total_reward": round(total_reward, 4),
|
| 71 |
+
"score": round(final_score, 4),
|
| 72 |
+
"grader_breakdown": grader.last_breakdown,
|
| 73 |
+
}
|
tasks/task3_hard.py
CHANGED
|
@@ -33,4 +33,41 @@ def setup_env(env) -> None:
|
|
| 33 |
env.difficulty = hft_auditor.Difficulty.HARD
|
| 34 |
env._MAX_EPISODE_STEPS = MAX_STEPS
|
| 35 |
except Exception as e:
|
| 36 |
-
print(f"[task_hard] Could not set difficulty: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
env.difficulty = hft_auditor.Difficulty.HARD
|
| 34 |
env._MAX_EPISODE_STEPS = MAX_STEPS
|
| 35 |
except Exception as e:
|
| 36 |
+
print(f"[task_hard] Could not set difficulty: {e}")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def run_episode(env, agent_fn) -> dict:
|
| 40 |
+
"""Run a single HARD anomaly-detection episode and return the graded result.
|
| 41 |
+
|
| 42 |
+
Called by the OpenEnv evaluator. agent_fn receives an observation and
|
| 43 |
+
returns a list of binary decisions (0 = valid, 1 = anomaly).
|
| 44 |
+
"""
|
| 45 |
+
setup_env(env)
|
| 46 |
+
total_reward = 0.0
|
| 47 |
+
steps_done = 0
|
| 48 |
+
|
| 49 |
+
try:
|
| 50 |
+
obs = env.reset()
|
| 51 |
+
for _ in range(MAX_STEPS):
|
| 52 |
+
decisions = agent_fn(obs)
|
| 53 |
+
from models import AuditorAction
|
| 54 |
+
action = AuditorAction(decisions=decisions)
|
| 55 |
+
obs = env.step(action)
|
| 56 |
+
total_reward += float(obs.reward) if obs.reward is not None else 0.0
|
| 57 |
+
steps_done += 1
|
| 58 |
+
if obs.done:
|
| 59 |
+
break
|
| 60 |
+
except Exception as exc:
|
| 61 |
+
print(f"[task_hard] run_episode error at step {steps_done}: {exc}")
|
| 62 |
+
|
| 63 |
+
# Always grade — even partial data yields a valid score via perfect_signal fallback
|
| 64 |
+
final_score = grader.grade(env.state)
|
| 65 |
+
|
| 66 |
+
return {
|
| 67 |
+
"task": TASK_ID,
|
| 68 |
+
"difficulty": DIFFICULTY,
|
| 69 |
+
"steps": steps_done,
|
| 70 |
+
"total_reward": round(total_reward, 4),
|
| 71 |
+
"score": round(final_score, 4),
|
| 72 |
+
"grader_breakdown": grader.last_breakdown,
|
| 73 |
+
}
|