File size: 2,665 Bytes
ba693b7
 
 
52cb935
ba693b7
 
 
 
52cb935
ba693b7
52cb935
 
ba693b7
 
52cb935
ba693b7
 
 
 
 
 
 
52cb935
 
 
 
 
a523ae1
 
 
ba693b7
 
52cb935
a523ae1
52cb935
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ba693b7
52cb935
a523ae1
52cb935
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ba693b7
 
 
52cb935
 
a523ae1
ba693b7
 
a523ae1
 
 
 
 
 
52cb935
 
a523ae1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"""
task3_hard.py
─────────────
Task 3 β€” Full Triage Episode (Hard)
"""

from __future__ import annotations

from typing import Callable, Dict, Any, List, Optional, Tuple

from cognitive_env import CogTraceEnv
from patient_simulator import PatientConfig


def _compute_f1(tp: int, fp: int, fn: int) -> float:
    precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0
    recall    = tp / (tp + fn) if (tp + fn) > 0 else 0.0
    if precision + recall == 0:
        return 0.0
    return 2 * precision * recall / (precision + recall)


LEVEL_BONUS_MAP = {3: 1.0, 2: 0.6, 1: 0.3, 0: 0.0}


def grade(results: List[Dict[str, Any]]) -> float:
    if not results:
        return 0.001
    score = sum(r["score"] for r in results) / len(results)
    return max(0.001, min(0.999, score))


def build_env(seed: int = 0) -> CogTraceEnv:
    stage = (seed % 4) + 1
    cfg = PatientConfig(
        true_stage=stage,
        episode_length=30,
        decline_rate=0.01,
        noise_level=1.2,
        anomaly_day=None,
        anomaly_duration=5,
        seed=seed,
        patient_id=f"task3_seed{seed}",
    )
    return CogTraceEnv(config=cfg)


def run_episode(
    env: CogTraceEnv,
    agent_fn: Callable[[Dict[str, Any], int, List[int]], int],
) -> Dict[str, Any]:
    obs = env.reset()
    anomaly_start = env._sim.anomaly_day
    anomaly_end   = env._sim.anomaly_end

    alert_history: List[int] = []
    step = 0
    tp = fp = fn = 0
    level_bonus_total = 0.0
    anomaly_steps = 0

    while True:
        obs_dict = obs.model_dump()
        action   = agent_fn(obs_dict, step, list(alert_history))
        action   = max(0, min(3, int(action)))

        in_anomaly = anomaly_start <= step < anomaly_end

        if in_anomaly:
            anomaly_steps += 1
            if action > 0:
                tp += 1
                level_bonus_total += LEVEL_BONUS_MAP[action]
            else:
                fn += 1
        else:
            if action > 0:
                fp += 1

        alert_history.append(action)
        obs, reward, done, info = env.step(action)
        step += 1
        if done:
            break

    f1          = _compute_f1(tp, fp, fn)
    level_bonus = (level_bonus_total / anomaly_steps) if anomaly_steps > 0 else 0.0
    score       = max(0.001, min(0.999, round(0.70 * f1 + 0.30 * level_bonus, 4)))

    return {
        "tp":            tp,
        "fp":            fp,
        "fn":            fn,
        "f1_score":      round(f1, 4),
        "level_bonus":   round(level_bonus, 4),
        "score":         score,
        "anomaly_start": anomaly_start,
        "anomaly_end":   anomaly_end,
    }