RohitChandramouli6618 commited on
Commit
4d57df8
Β·
1 Parent(s): fe22c22

Fix treatment/recovery calibration, hospital breach reward, efficiency grader, recalibrate seeds

Browse files
baseline/__pycache__/policy.cpython-313.pyc CHANGED
Binary files a/baseline/__pycache__/policy.cpython-313.pyc and b/baseline/__pycache__/policy.cpython-313.pyc differ
 
epidemic_containment_env.zip ADDED
Binary file (90.9 kB). View file
 
server/constants.py CHANGED
@@ -1,78 +1,44 @@
1
  # constants.py
2
- # ─────────────────────────────────────────────────────────────────────────────
3
- # Single source of truth for all numeric configuration in the environment.
4
- # ─────────────────────────────────────────────────────────────────────────────
5
-
6
- import sys
7
- import os
8
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
9
 
10
- # ── Task Configuration ────────────────────────────────────────────────────────
11
-
12
  TASK_CONFIG = {
13
- "easy": {
14
- "num_districts": 2,
15
- "max_steps": 10,
16
- "resource_pool": 10,
17
- "data_lag_days": 0,
18
- },
19
- "medium": {
20
- "num_districts": 4,
21
- "max_steps": 15,
22
- "resource_pool": 8,
23
- "data_lag_days": 0,
24
- },
25
- "hard": {
26
- "num_districts": 6,
27
- "max_steps": 15,
28
- "resource_pool": 7,
29
- "data_lag_days": 3,
30
- },
31
  }
32
 
33
-
34
- # ── Infection Thresholds ──────────────────────────────────────────────────────
35
-
36
- INFECTION_THRESHOLD = 0.40 # Above this β†’ district is in danger
37
- SAFE_THRESHOLD = 0.20 # Below this β†’ district is contained
38
- LOW_THRESHOLD = 0.20 # Below this β†’ restriction is unnecessary
39
-
40
  # Real health systems reach operational collapse before zero capacity.
41
- # ICU overflow, staff exhaustion and triage failure begin at ~10% remaining.
42
- HOSPITAL_BREACH_POINT = 0.10 # ≀ 10% capacity remaining = collapse
43
-
44
-
45
- # ── Spread Mechanics ──────────────────────────────────────────────────────────
46
 
47
  SPREAD_RATE_MIN = 0.03
48
  SPREAD_RATE_MAX = 0.08
49
  GROWTH_HINT_NOISE = 0.03
50
 
51
- # Natural recovery: ~2% of active cases resolve per day without intervention.
52
- # Reflects a realistic R-effective slightly above 1 in an uncontrolled outbreak.
53
- # Infection grows without intervention but not explosively β€” it needs active
54
- # management to be pushed below safe threshold.
55
- NATURAL_RECOVERY_RATE = 0.02
56
 
57
- TREATMENT_REDUCTION = 0.10 # Allocating reduces existing infection this step
58
- ALLOCATE_REDUCTION = 0.10 # Allocating reduces future spread rate this step
 
 
59
  RESTRICT_REDUCTION = 0.05 # Restricting reduces spread per step
60
- SPILLOVER_RATE = 0.01 # Infection fraction spilling to adjacent districts
61
 
62
  RESOURCE_REPLENISH = 1 # Resource units restored each step (capped at pool)
63
 
64
-
65
- # ── Reward Weights ────────────────────────────────────────────────────────────
66
-
67
  REWARD_INFECTION_PENALTY = -0.50
68
  REWARD_HOSPITAL_BREACH = -1.00
69
  REWARD_EARLY_CONTAINMENT = +0.50
70
  REWARD_UNNECESSARY_RESTRICTION = -0.20
71
  REWARD_CORRECT_PRIORITISATION = +0.30
72
 
73
-
74
- # ── Episode Terminal Conditions ───────────────────────────────────────────────
75
-
76
  # Success: ALL districts below SAFE_THRESHOLD β†’ speed bonus fires
77
  # Failure: ANY hospital at or below HOSPITAL_BREACH_POINT β†’ episode ends
78
  # Natural: max_steps reached
 
1
  # constants.py
2
+ import sys, os
 
 
 
 
 
3
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
4
 
 
 
5
  TASK_CONFIG = {
6
+ "easy": {"num_districts": 2, "max_steps": 10, "resource_pool": 10, "data_lag_days": 0},
7
+ "medium": {"num_districts": 4, "max_steps": 15, "resource_pool": 8, "data_lag_days": 0},
8
+ "hard": {"num_districts": 6, "max_steps": 15, "resource_pool": 7, "data_lag_days": 3},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  }
10
 
11
+ INFECTION_THRESHOLD = 0.40
12
+ SAFE_THRESHOLD = 0.20
13
+ LOW_THRESHOLD = 0.20
 
 
 
 
14
  # Real health systems reach operational collapse before zero capacity.
15
+ # ICU overflow and triage failure begin at ~10% remaining capacity.
16
+ HOSPITAL_BREACH_POINT = 0.10
 
 
 
17
 
18
  SPREAD_RATE_MIN = 0.03
19
  SPREAD_RATE_MAX = 0.08
20
  GROWTH_HINT_NOISE = 0.03
21
 
22
+ # Natural recovery: ~1% of active cases resolve per day without intervention.
23
+ # Reflects a realistic epidemic where spread dominates unless actively managed.
24
+ # Infection grows without intervention, but sustained allocation can drive it below threshold.
25
+ NATURAL_RECOVERY_RATE = 0.01
 
26
 
27
+ # Treatment reduces existing infection by 5% per allocation action.
28
+ # Medical deployment (antivirals, PPE, rapid response teams) realistic at this scale.
29
+ TREATMENT_REDUCTION = 0.05
30
+ ALLOCATE_REDUCTION = 0.10 # Reduces future spread rate this step
31
  RESTRICT_REDUCTION = 0.05 # Restricting reduces spread per step
32
+ SPILLOVER_RATE = 0.01 # Infection spilling to adjacent districts
33
 
34
  RESOURCE_REPLENISH = 1 # Resource units restored each step (capped at pool)
35
 
 
 
 
36
  REWARD_INFECTION_PENALTY = -0.50
37
  REWARD_HOSPITAL_BREACH = -1.00
38
  REWARD_EARLY_CONTAINMENT = +0.50
39
  REWARD_UNNECESSARY_RESTRICTION = -0.20
40
  REWARD_CORRECT_PRIORITISATION = +0.30
41
 
 
 
 
42
  # Success: ALL districts below SAFE_THRESHOLD β†’ speed bonus fires
43
  # Failure: ANY hospital at or below HOSPITAL_BREACH_POINT β†’ episode ends
44
  # Natural: max_steps reached
server/environment.py CHANGED
@@ -27,6 +27,7 @@ from models import (
27
  ContainmentAction,
28
  )
29
  from server.constants import (
 
30
  TASK_CONFIG,
31
  INFECTION_THRESHOLD,
32
  SAFE_THRESHOLD,
@@ -286,7 +287,7 @@ class EpidemicContainmentEnv(Environment):
286
 
287
  # Term 2: Heavy penalty for hospital capacity breach
288
  for district in self._city.districts:
289
- if district.hospital_capacity_remaining <= 0.0:
290
  reward += REWARD_HOSPITAL_BREACH
291
 
292
  # Term 3: Early containment bonus (decays over time)
 
27
  ContainmentAction,
28
  )
29
  from server.constants import (
30
+ HOSPITAL_BREACH_POINT,
31
  TASK_CONFIG,
32
  INFECTION_THRESHOLD,
33
  SAFE_THRESHOLD,
 
287
 
288
  # Term 2: Heavy penalty for hospital capacity breach
289
  for district in self._city.districts:
290
+ if district.hospital_capacity_remaining <= HOSPITAL_BREACH_POINT:
291
  reward += REWARD_HOSPITAL_BREACH
292
 
293
  # Term 3: Early containment bonus (decays over time)
server/grader.py CHANGED
@@ -1,190 +1,119 @@
1
  # server/grader.py
2
- # ─────────────────────────────────────────────────────────────────────────────
3
- # Deterministic scorer for completed Cascade Containment episodes.
4
- # Called by baseline/evaluator.py after each full episode.
5
- # Always returns a float in [0.0, 1.0].
6
- # ─────────────────────────────────────────────────────────────────────────────
7
-
8
- import sys
9
- import os
10
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
11
 
12
- from typing import List, Tuple
13
  from dataclasses import dataclass
14
-
15
  from models import CityState, ContainmentAction
16
  from server.constants import (
17
  INFECTION_THRESHOLD,
18
  SAFE_THRESHOLD,
19
  HOSPITAL_BREACH_POINT,
 
20
  TASK_CONFIG,
21
  )
22
 
23
 
24
- # ── Trajectory Record ─────────────────────────────────────────────────────────
25
-
26
  @dataclass
27
  class TrajectoryStep:
28
- """
29
- A single recorded step in an episode.
30
- Stored by environment.py and passed to the grader after episode ends.
31
- """
32
  step: int
33
- city_state: CityState # Hidden ground truth at this step
34
- action: ContainmentAction # What the agent did
35
- reward: float # Reward received
36
- done: bool # Was this the final step
37
-
38
 
39
- # ── Grader Score Breakdown ────────────────────────────────────────────────────
40
 
41
  @dataclass
42
  class GradeResult:
43
- """
44
- Full scoring breakdown for one episode.
45
- The final_score is what the evaluator reports.
46
- """
47
- final_score: float # Weighted composite: 0.0 to 1.0
48
- containment_score: float # How well infection was kept below threshold
49
- hospital_score: float # How well hospital capacity was preserved
50
- efficiency_score: float # How well resources were directed
51
- speed_score: float # How quickly the episode was resolved
52
- hospital_breached: bool # Whether any hospital collapse occurred
53
- districts_contained: int # How many districts ended below safe threshold
54
- total_steps: int # Steps taken before episode ended
55
-
56
-
57
- # ── Main Grader ───────────────────────────────────────────────────────────────
58
-
59
- def grade_trajectory(
60
- trajectory: List[TrajectoryStep],
61
- task_name: str,
62
- ) -> GradeResult:
63
- """
64
- Score a completed episode trajectory.
65
-
66
- Args:
67
- trajectory: Ordered list of TrajectoryStep from one full episode.
68
- task_name: "easy", "medium", or "hard" β€” affects scoring strictness.
69
-
70
- Returns:
71
- GradeResult with final_score in [0.0, 1.0] and full breakdown.
72
- """
73
  if not trajectory:
74
- return GradeResult(
75
- final_score = 0.0,
76
- containment_score = 0.0,
77
- hospital_score = 0.0,
78
- efficiency_score = 0.0,
79
- speed_score = 0.0,
80
- hospital_breached = False,
81
- districts_contained = 0,
82
- total_steps = 0,
83
- )
84
-
85
- config = TASK_CONFIG[task_name]
86
  num_districts = config["num_districts"]
87
  max_steps = config["max_steps"]
88
  total_steps = len(trajectory)
89
 
90
  # ── Component 1: Containment Score ───────────────────────────────────────
91
  # Fraction of district-days that stayed below infection threshold.
92
- # Perfect agent = 1.0 (no district ever exceeded threshold).
93
-
94
- total_district_days = total_steps * num_districts
95
- safe_district_days = 0
96
-
97
- for step in trajectory[2:]: # skip first 2 steps
98
  for district in step.city_state.districts:
99
  if district.true_infection_rate <= INFECTION_THRESHOLD:
100
  safe_district_days += 1
101
-
102
  total_district_days = max(len(trajectory) - 2, 1) * num_districts
103
-
104
- containment_score = safe_district_days / total_district_days
105
 
106
  # ── Component 2: Hospital Score ───────────────────────────────────────────
107
- # Measures how well hospital capacity was preserved across the episode.
108
- # Any breach = heavy penalty. Near-breach is also penalised proportionally.
109
-
110
  hospital_breached = False
111
  total_capacity_preserved = 0.0
112
-
113
  for step in trajectory:
114
  for district in step.city_state.districts:
115
  if district.hospital_capacity_remaining <= HOSPITAL_BREACH_POINT:
116
  hospital_breached = True
117
  total_capacity_preserved += district.hospital_capacity_remaining
118
-
119
- hospital_district_days = total_steps * num_districts # all steps, not grace-period-adjusted
120
- avg_capacity = total_capacity_preserved / hospital_district_days
121
- hospital_score = avg_capacity * (0.6 if hospital_breached else 1.0)
122
- hospital_score = round(min(1.0, max(0.0, hospital_score)), 4)
123
 
124
  # ── Component 3: Efficiency Score ────────────────────────────────────────
125
- # Fraction of allocate/test actions that targeted districts above threshold.
126
- # Rewards directing resources where they're actually needed.
127
-
128
- # ── Component 3: Efficiency Score ────────────────────────────────────────
129
- # Rewards directing resources to the highest-infected district.
130
- # Checks which district had the highest infection at each step,
131
- # then rewards targeting it regardless of whether it crossed threshold.
132
-
133
- resource_actions = [
134
- s for s in trajectory
135
- if s.action.action_type in {"allocate", "test"}
136
- ]
137
-
138
- if resource_actions:
139
- correct_actions = 0
140
- for step in resource_actions:
141
- districts = step.city_state.districts
142
- if not districts:
143
- continue
144
- target = districts[step.action.district_id]
145
- # Account for TREATMENT_REDUCTION: if post-treatment rate > 0.30,
146
- # the district was above 0.40 before treatment β€” agent made the right call
147
- pre_treatment_estimate = target.true_infection_rate + 0.10
148
- highest_id = max(districts, key=lambda d: d.true_infection_rate).district_id
149
- if pre_treatment_estimate > INFECTION_THRESHOLD:
150
- correct_actions += 1
151
- elif step.action.district_id == highest_id:
152
- correct_actions += 1
153
- efficiency_score = correct_actions / len(resource_actions)
154
- else:
155
- efficiency_score = 0.5
156
 
157
  # ── Component 4: Speed Score ──────────────────────────────────────────────
158
- # Rewards finishing faster than max_steps.
159
- # If episode ran to max_steps, speed_score = 0.0.
160
- # If contained in half the steps, speed_score = 0.5. Etc.
161
-
162
- last_step = trajectory[-1]
163
- if last_step.done and total_steps < max_steps:
164
- speed_score = round(1.0 - (total_steps / max_steps), 4)
165
- speed_score = max(0.0, speed_score)
166
- else:
167
- speed_score = 0.0 # No speed bonus for failed or incomplete episodes
168
 
169
  # ── Final Weighted Score ──────────────────────────────────────────────────
170
- # Weights reflect judging priorities:
171
- # containment = primary signal
172
- # hospital = safety constraint
173
- # efficiency = quality differentiator
174
- # speed = tiebreaker
175
-
176
- final_score = (
177
- containment_score * 0.30 +
178
- hospital_score * 0.45 +
179
- efficiency_score * 0.15 +
180
- speed_score * 0.10
181
- )
182
- final_score = round(min(1.0, max(0.0, final_score)), 4)
183
 
184
- # ── Final district count ──────────────────────────────────────────────────
185
- final_step = trajectory[-1]
186
  districts_contained = sum(
187
- 1 for d in final_step.city_state.districts
188
  if d.true_infection_rate < SAFE_THRESHOLD
189
  )
190
 
@@ -200,15 +129,5 @@ def grade_trajectory(
200
  )
201
 
202
 
203
- # ── Convenience: Grade a Single Score to 0.0–1.0 ─────────────────────────────
204
-
205
- def grade_task(
206
- trajectory: List[TrajectoryStep],
207
- task_name: str,
208
- ) -> float:
209
- """
210
- Thin wrapper that returns just the final_score float.
211
- Used by baseline/evaluator.py for clean score reporting.
212
- """
213
- result = grade_trajectory(trajectory, task_name)
214
- return result.final_score
 
1
  # server/grader.py
2
+ import sys, os
 
 
 
 
 
 
 
3
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
4
 
5
+ from typing import List
6
  from dataclasses import dataclass
 
7
  from models import CityState, ContainmentAction
8
  from server.constants import (
9
  INFECTION_THRESHOLD,
10
  SAFE_THRESHOLD,
11
  HOSPITAL_BREACH_POINT,
12
+ TREATMENT_REDUCTION,
13
  TASK_CONFIG,
14
  )
15
 
16
 
 
 
17
  @dataclass
18
  class TrajectoryStep:
 
 
 
 
19
  step: int
20
+ city_state: CityState
21
+ action: ContainmentAction
22
+ reward: float
23
+ done: bool
 
24
 
 
25
 
26
  @dataclass
27
  class GradeResult:
28
+ final_score: float
29
+ containment_score: float
30
+ hospital_score: float
31
+ efficiency_score: float
32
+ speed_score: float
33
+ hospital_breached: bool
34
+ districts_contained: int
35
+ total_steps: int
36
+
37
+
38
+ def grade_trajectory(trajectory: List[TrajectoryStep], task_name: str) -> GradeResult:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  if not trajectory:
40
+ return GradeResult(0.0, 0.0, 0.0, 0.0, 0.0, False, 0, 0)
41
+
42
+ config = TASK_CONFIG[task_name]
 
 
 
 
 
 
 
 
 
43
  num_districts = config["num_districts"]
44
  max_steps = config["max_steps"]
45
  total_steps = len(trajectory)
46
 
47
  # ── Component 1: Containment Score ───────────────────────────────────────
48
  # Fraction of district-days that stayed below infection threshold.
49
+ # Skips first 2 steps (initial conditions outside agent's control).
50
+ safe_district_days = 0
51
+ for step in trajectory[2:]:
 
 
 
52
  for district in step.city_state.districts:
53
  if district.true_infection_rate <= INFECTION_THRESHOLD:
54
  safe_district_days += 1
 
55
  total_district_days = max(len(trajectory) - 2, 1) * num_districts
56
+ containment_score = safe_district_days / total_district_days
 
57
 
58
  # ── Component 2: Hospital Score ───────────────────────────────────────────
59
+ # Average capacity preserved. Breach multiplier of 0.6 if any district collapsed.
 
 
60
  hospital_breached = False
61
  total_capacity_preserved = 0.0
 
62
  for step in trajectory:
63
  for district in step.city_state.districts:
64
  if district.hospital_capacity_remaining <= HOSPITAL_BREACH_POINT:
65
  hospital_breached = True
66
  total_capacity_preserved += district.hospital_capacity_remaining
67
+ hospital_district_days = total_steps * num_districts
68
+ avg_capacity = total_capacity_preserved / hospital_district_days
69
+ hospital_score = round(min(1.0, max(0.0, avg_capacity * (0.6 if hospital_breached else 1.0))), 4)
 
 
70
 
71
  # ── Component 3: Efficiency Score ────────────────────────────────────────
72
+ # Fraction of resource actions that targeted the right district.
73
+ # Uses the PREVIOUS step's infection rates (pre-action state) so that
74
+ # successful treatments are not penalised retroactively.
75
+ correct_actions = 0
76
+ total_resource = 0
77
+ for idx, step in enumerate(trajectory):
78
+ if step.action.action_type not in {"allocate", "test"}:
79
+ continue
80
+ total_resource += 1
81
+ # Determine pre-action infection state
82
+ if idx > 0:
83
+ prev_districts = trajectory[idx - 1].city_state.districts
84
+ pre_action_rate = prev_districts[step.action.district_id].true_infection_rate
85
+ highest_before = max(prev_districts, key=lambda d: d.true_infection_rate).district_id
86
+ else:
87
+ # First step: estimate pre-action rate from post-action + treatment
88
+ curr_d = step.city_state.districts[step.action.district_id]
89
+ pre_action_rate = curr_d.true_infection_rate + TREATMENT_REDUCTION
90
+ highest_before = max(step.city_state.districts, key=lambda d: d.true_infection_rate).district_id
91
+ # Correct if the targeted district was above threshold before action,
92
+ # or if it was the most infected district at the time
93
+ if pre_action_rate > INFECTION_THRESHOLD or step.action.district_id == highest_before:
94
+ correct_actions += 1
95
+ efficiency_score = correct_actions / max(total_resource, 1)
 
 
 
 
 
 
 
96
 
97
  # ── Component 4: Speed Score ──────────────────────────────────────────────
98
+ # Reward early containment. Only fires if episode ended before max_steps.
99
+ last_step = trajectory[-1]
100
+ speed_score = round(max(0.0, 1.0 - total_steps / max_steps), 4) \
101
+ if last_step.done and total_steps < max_steps else 0.0
 
 
 
 
 
 
102
 
103
  # ── Final Weighted Score ──────────────────────────────────────────────────
104
+ # Hospital (45%) is the primary constraint β€” system collapse is catastrophic.
105
+ # Containment (30%) β€” keeping infection below dangerous levels.
106
+ # Efficiency (15%) β€” quality of resource allocation decisions.
107
+ # Speed (10%) β€” tiebreaker rewarding proactive early containment.
108
+ final_score = round(min(1.0, max(0.0,
109
+ containment_score * 0.30 +
110
+ hospital_score * 0.45 +
111
+ efficiency_score * 0.15 +
112
+ speed_score * 0.10
113
+ )), 4)
 
 
 
114
 
 
 
115
  districts_contained = sum(
116
+ 1 for d in trajectory[-1].city_state.districts
117
  if d.true_infection_rate < SAFE_THRESHOLD
118
  )
119
 
 
129
  )
130
 
131
 
132
+ def grade_task(trajectory: List[TrajectoryStep], task_name: str) -> float:
133
+ return grade_trajectory(trajectory, task_name).final_score
 
 
 
 
 
 
 
 
 
 
server/tasks/task_easy.py CHANGED
@@ -1,6 +1,5 @@
1
  # server/tasks/task_easy.py
2
- import sys
3
- import os
4
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
5
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
6
 
@@ -11,7 +10,6 @@ from server.constants import TASK_CONFIG
11
 
12
 
13
  class EasyTask(BaseTask):
14
-
15
  name = "easy"
16
  num_districts = TASK_CONFIG["easy"]["num_districts"]
17
  max_steps = TASK_CONFIG["easy"]["max_steps"]
@@ -19,10 +17,12 @@ class EasyTask(BaseTask):
19
  data_lag_days = TASK_CONFIG["easy"]["data_lag_days"]
20
 
21
  def build_initial_state(self) -> CityState:
22
- # D0 has a visible outbreak in WARNING-CRITICAL boundary.
23
- # Seed is high enough that one allocation cannot instantly contain it β€”
24
- # agent must sustain 3+ allocations while also managing D1's growth.
25
- seed_infections = [0.45, 0.06]
 
 
26
 
27
  return CityState(
28
  day = 0,
 
1
  # server/tasks/task_easy.py
2
+ import sys, os
 
3
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
4
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
5
 
 
10
 
11
 
12
  class EasyTask(BaseTask):
 
13
  name = "easy"
14
  num_districts = TASK_CONFIG["easy"]["num_districts"]
15
  max_steps = TASK_CONFIG["easy"]["max_steps"]
 
17
  data_lag_days = TASK_CONFIG["easy"]["data_lag_days"]
18
 
19
  def build_initial_state(self) -> CityState:
20
+ # D0 starts at the danger threshold β€” one district with a visible outbreak.
21
+ # D1 is very clean, grows slowly through spillover only.
22
+ # With TREATMENT_REDUCTION=0.05 and good strategy, agent contains both
23
+ # districts in 6-8 steps, earning a speed bonus. Requires sustained focus
24
+ # on D0 first before D1 grows above safe threshold.
25
+ seed_infections = [0.40, 0.04]
26
 
27
  return CityState(
28
  day = 0,
server/tasks/task_medium.py CHANGED
@@ -1,6 +1,5 @@
1
  # server/tasks/task_medium.py
2
- import sys
3
- import os
4
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
5
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
6
 
@@ -11,7 +10,6 @@ from server.constants import TASK_CONFIG
11
 
12
 
13
  class MediumTask(BaseTask):
14
-
15
  name = "medium"
16
  num_districts = TASK_CONFIG["medium"]["num_districts"]
17
  max_steps = TASK_CONFIG["medium"]["max_steps"]
@@ -19,11 +17,14 @@ class MediumTask(BaseTask):
19
  data_lag_days = TASK_CONFIG["medium"]["data_lag_days"]
20
 
21
  def build_initial_state(self) -> CityState:
22
- # D0 and D2 seeded with outbreaks (non-adjacent).
23
- # D1 and D3 start clean but will grow into critical within 3-4 steps.
24
- # 8 total resources for 4 districts creates genuine triage pressure β€”
25
- # agent cannot save all districts and must choose strategically.
26
- seed_infections = [0.28, 0.05, 0.25, 0.05]
 
 
 
27
 
28
  return CityState(
29
  day = 0,
 
1
  # server/tasks/task_medium.py
2
+ import sys, os
 
3
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
4
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
5
 
 
10
 
11
 
12
  class MediumTask(BaseTask):
 
13
  name = "medium"
14
  num_districts = TASK_CONFIG["medium"]["num_districts"]
15
  max_steps = TASK_CONFIG["medium"]["max_steps"]
 
17
  data_lag_days = TASK_CONFIG["medium"]["data_lag_days"]
18
 
19
  def build_initial_state(self) -> CityState:
20
+ # D0 and D2 seeded with active outbreaks (non-adjacent boroughs).
21
+ # D1 and D3 start with low infections that grow into danger within
22
+ # 4-6 steps through spillover and their own spread rates.
23
+ # With only 8 resources for 4 districts over 15 steps, the agent
24
+ # cannot contain all districts simultaneously β€” genuine triage required.
25
+ # A dumb agent scores ~0.35, a smart agent with good prioritisation
26
+ # scores 0.60-0.70.
27
+ seed_infections = [0.35, 0.08, 0.32, 0.08]
28
 
29
  return CityState(
30
  day = 0,