paramjitbaral commited on
Commit
b9755d7
·
verified ·
1 Parent(s): 4e42a7f

Upload folder using huggingface_hub

Browse files
app/environment/core.py CHANGED
@@ -15,6 +15,15 @@ from app.utils.calculations import compute_speed_kmh, compute_travel_time_minute
15
  from app.utils.randomizer import SeededRandomizer
16
 
17
 
 
 
 
 
 
 
 
 
 
18
  TASKS = {
19
  "acde_easy": {
20
  "difficulty": "easy",
@@ -1096,9 +1105,9 @@ class EmergencyEnv:
1096
 
1097
  total = entry.success + entry.fail
1098
  if total == 1:
1099
- entry.avg = max(0.0, min(1.0, reward))
1100
  else:
1101
- normalized_reward = max(0.0, min(1.0, reward))
1102
  entry.avg = ((entry.avg * (total - 1)) + normalized_reward) / total
1103
 
1104
  memory[hospital_id] = entry
@@ -1124,7 +1133,7 @@ class EmergencyEnv:
1124
  reward_component = max(MIN_REWARD, min(MAX_REWARD, self.state_data.reward))
1125
  total_steps = max(1, len(self.trajectory))
1126
  rejected_steps = sum(1 for item in self.trajectory if item.get("outcome_status") == "rejected")
1127
- route_quality = max(0.0, 1.0 - (rejected_steps / total_steps))
1128
  score = (0.45 * reward_component) + (0.40 * progress_component) + (0.15 * route_quality)
1129
  return max(MIN_REWARD, min(MAX_REWARD, max(0.25, min(0.99, score))))
1130
 
 
15
  from app.utils.randomizer import SeededRandomizer
16
 
17
 
18
+ # Strict clamping to open interval (0, 1)
19
+ _FLOOR = 0.001
20
+ _CEIL = 0.999
21
+
22
+ def _clamp(v: float) -> float:
23
+ """Clamp a score to the open interval (0, 1)."""
24
+ return max(_FLOOR, min(_CEIL, v))
25
+
26
+
27
  TASKS = {
28
  "acde_easy": {
29
  "difficulty": "easy",
 
1105
 
1106
  total = entry.success + entry.fail
1107
  if total == 1:
1108
+ entry.avg = _clamp(reward)
1109
  else:
1110
+ normalized_reward = _clamp(reward)
1111
  entry.avg = ((entry.avg * (total - 1)) + normalized_reward) / total
1112
 
1113
  memory[hospital_id] = entry
 
1133
  reward_component = max(MIN_REWARD, min(MAX_REWARD, self.state_data.reward))
1134
  total_steps = max(1, len(self.trajectory))
1135
  rejected_steps = sum(1 for item in self.trajectory if item.get("outcome_status") == "rejected")
1136
+ route_quality = _clamp(1.0 - (rejected_steps / total_steps))
1137
  score = (0.45 * reward_component) + (0.40 * progress_component) + (0.15 * route_quality)
1138
  return max(MIN_REWARD, min(MAX_REWARD, max(0.25, min(0.99, score))))
1139
 
app/environment/validation.py CHANGED
@@ -7,6 +7,14 @@ Outcomes are based on difficulty level, hospital capacity, patient suitability,
7
  from app.models.state import ArrivalOutcome, HospitalValidationDetails, HospitalState
8
  from app.utils.randomizer import SeededRandomizer
9
 
 
 
 
 
 
 
 
 
10
 
11
  class HospitalValidator:
12
  """Performs hidden validation checks on arrival and returns outcome."""
@@ -190,7 +198,7 @@ class HospitalValidator:
190
  # Add difficulty-based noise
191
  if difficulty == "hard":
192
  noise = self.rng.uniform(-0.15, 0.15)
193
- suitability = max(0.0, min(1.0, suitability + noise))
194
 
195
  return suitability
196
 
 
7
  from app.models.state import ArrivalOutcome, HospitalValidationDetails, HospitalState
8
  from app.utils.randomizer import SeededRandomizer
9
 
10
+ # Strict clamping to open interval (0, 1)
11
+ _FLOOR = 0.001
12
+ _CEIL = 0.999
13
+
14
+ def _clamp(v: float) -> float:
15
+ """Clamp a score to the open interval (0, 1)."""
16
+ return max(_FLOOR, min(_CEIL, v))
17
+
18
 
19
  class HospitalValidator:
20
  """Performs hidden validation checks on arrival and returns outcome."""
 
198
  # Add difficulty-based noise
199
  if difficulty == "hard":
200
  noise = self.rng.uniform(-0.15, 0.15)
201
+ suitability = _clamp(suitability + noise)
202
 
203
  return suitability
204
 
app/models/observation.py CHANGED
@@ -1,6 +1,6 @@
1
  from typing import Literal
2
 
3
- from pydantic import BaseModel, Field
4
 
5
 
6
  class HospitalObservation(BaseModel):
@@ -15,7 +15,13 @@ class ArrivalOutcomeObservation(BaseModel):
15
  """What happened when ambulance arrived at hospital"""
16
  status: Literal["accepted", "partial", "rejected"]
17
  reason: str
18
- suitability_score: float = Field(ge=0.0, le=1.0)
 
 
 
 
 
 
19
 
20
 
21
  class Observation(BaseModel):
 
1
  from typing import Literal
2
 
3
+ from pydantic import BaseModel, Field, field_validator
4
 
5
 
6
  class HospitalObservation(BaseModel):
 
15
  """What happened when ambulance arrived at hospital"""
16
  status: Literal["accepted", "partial", "rejected"]
17
  reason: str
18
+ suitability_score: float = Field(ge=0.001, le=0.999)
19
+
20
+ @field_validator("suitability_score", mode="before")
21
+ @classmethod
22
+ def clamp_suitability(cls, v: float) -> float:
23
+ """Clamp to open interval (0, 1)."""
24
+ return max(0.001, min(0.999, v))
25
 
26
 
27
  class Observation(BaseModel):
app/models/state.py CHANGED
@@ -1,14 +1,29 @@
1
  from typing import Literal
2
 
3
- from pydantic import BaseModel, Field
 
 
 
 
 
 
 
 
 
4
 
5
 
6
  class LearningEntry(BaseModel):
7
  success: int = Field(default=0, ge=0)
8
  fail: int = Field(default=0, ge=0)
9
- avg: float = Field(default=0.0, ge=0.0, le=1.0)
10
  accepted: int = Field(default=0, ge=0)
11
  rejected: int = Field(default=0, ge=0)
 
 
 
 
 
 
12
 
13
 
14
  class HospitalValidationDetails(BaseModel):
@@ -17,7 +32,13 @@ class HospitalValidationDetails(BaseModel):
17
  doctor_available: bool
18
  equipment_functional: bool
19
  overload_status: Literal["clear", "moderate", "severe"]
20
- patient_suitability: float = Field(ge=0.0, le=1.0) # 0=unsuitable, 1=ideal
 
 
 
 
 
 
21
 
22
 
23
  class ArrivalOutcome(BaseModel):
 
1
  from typing import Literal
2
 
3
+ from pydantic import BaseModel, Field, field_validator
4
+
5
+
6
+ # Strict clamping to open interval (0, 1)
7
+ _FLOOR = 0.001
8
+ _CEIL = 0.999
9
+
10
+ def _clamp(v: float) -> float:
11
+ """Clamp a score to the open interval (0, 1)."""
12
+ return max(_FLOOR, min(_CEIL, v))
13
 
14
 
15
  class LearningEntry(BaseModel):
16
  success: int = Field(default=0, ge=0)
17
  fail: int = Field(default=0, ge=0)
18
+ avg: float = Field(default=0.001, ge=0.001, le=0.999)
19
  accepted: int = Field(default=0, ge=0)
20
  rejected: int = Field(default=0, ge=0)
21
+
22
+ @field_validator("avg", mode="before")
23
+ @classmethod
24
+ def clamp_avg(cls, v: float) -> float:
25
+ """Clamp to open interval (0, 1)."""
26
+ return _clamp(v)
27
 
28
 
29
  class HospitalValidationDetails(BaseModel):
 
32
  doctor_available: bool
33
  equipment_functional: bool
34
  overload_status: Literal["clear", "moderate", "severe"]
35
+ patient_suitability: float = Field(ge=0.001, le=0.999) # 0=unsuitable, 1=ideal
36
+
37
+ @field_validator("patient_suitability", mode="before")
38
+ @classmethod
39
+ def clamp_suitability(cls, v: float) -> float:
40
+ """Clamp to open interval (0, 1)."""
41
+ return _clamp(v)
42
 
43
 
44
  class ArrivalOutcome(BaseModel):
app/utils/calculations.py CHANGED
@@ -1,5 +1,13 @@
1
  from app.models.state import LearningEntry
2
 
 
 
 
 
 
 
 
 
3
  TRAFFIC_FACTOR = {
4
  "low": 1.0,
5
  "medium": 0.6,
@@ -18,7 +26,7 @@ def compute_travel_time_minutes(distance_km: float, speed_kmh: float) -> float:
18
 
19
 
20
  def score_distance(distance_km: float, max_distance_km: float = 20.0) -> float:
21
- return max(0.0, min(1.0, 1.0 - (distance_km / max_distance_km)))
22
 
23
 
24
  def score_traffic(traffic: str) -> float:
@@ -26,7 +34,7 @@ def score_traffic(traffic: str) -> float:
26
 
27
 
28
  def score_icu(display_icu: str) -> float:
29
- return 1.0 if display_icu == "available" else 0.55
30
 
31
 
32
  def score_memory(entry: LearningEntry | None) -> float:
@@ -38,7 +46,7 @@ def score_memory(entry: LearningEntry | None) -> float:
38
  success_rate = entry.success / total
39
  fail_bias = max(0.0, (entry.fail - entry.success) / total)
40
  raw = (0.7 * entry.avg) + (0.3 * success_rate) - (0.4 * fail_bias)
41
- return max(0.0, min(1.0, raw))
42
 
43
 
44
  def decision_score(
@@ -53,7 +61,7 @@ def decision_score(
53
  + (traffic_score * 0.2)
54
  + (memory_score * 0.3)
55
  )
56
- return max(0.0, min(1.0, weighted / 1.2))
57
 
58
 
59
  def compute_reward(
@@ -63,9 +71,9 @@ def compute_reward(
63
  specialization_match: bool,
64
  ) -> float:
65
  survival_component = 1.0 if survived else 0.0
66
- time_efficiency = max(0.0, min(1.0, critical_limit / max(critical_limit + travel_time, 1e-6)))
67
  specialization_component = 1.0 if specialization_match else 0.0
68
- delay_penalty = max(0.0, min(1.0, travel_time / max(critical_limit + travel_time, 1e-6)))
69
 
70
  reward = (
71
  (survival_component * 0.45)
@@ -73,7 +81,7 @@ def compute_reward(
73
  + (specialization_component * 0.2)
74
  - (delay_penalty * 0.1)
75
  )
76
- return max(0.0, min(1.0, reward))
77
 
78
 
79
  def compute_reward_with_breakdown(
@@ -86,19 +94,19 @@ def compute_reward_with_breakdown(
86
  adaptability_score: float | None = None,
87
  ) -> tuple[float, dict[str, float]]:
88
  survival_component = (
89
- max(0.0, min(1.0, survival_score))
90
  if survival_score is not None
91
  else (1.0 if survived else 0.0)
92
  )
93
- time_efficiency = max(0.0, min(1.0, critical_limit / max(critical_limit + travel_time, 1e-6)))
94
  specialization_component = (
95
- max(0.0, min(1.0, capability_score))
96
  if capability_score is not None
97
  else (1.0 if specialization_match else 0.0)
98
  )
99
- delay_penalty = max(0.0, min(1.0, travel_time / max(critical_limit + travel_time, 1e-6)))
100
  adapt_component = (
101
- max(0.0, min(1.0, adaptability_score))
102
  if adaptability_score is not None
103
  else 0.5
104
  )
@@ -110,7 +118,7 @@ def compute_reward_with_breakdown(
110
  + (adapt_component * 0.2)
111
  - (delay_penalty * 0.12)
112
  )
113
- reward = max(0.0, min(1.0, reward))
114
  return reward, {
115
  "survival_component": survival_component,
116
  "time_efficiency_component": time_efficiency,
 
1
  from app.models.state import LearningEntry
2
 
3
+ # Strict clamping to open interval (0, 1)
4
+ _FLOOR = 0.001
5
+ _CEIL = 0.999
6
+
7
+ def _clamp(v: float) -> float:
8
+ """Clamp a score to the open interval (0, 1)."""
9
+ return max(_FLOOR, min(_CEIL, v))
10
+
11
  TRAFFIC_FACTOR = {
12
  "low": 1.0,
13
  "medium": 0.6,
 
26
 
27
 
28
  def score_distance(distance_km: float, max_distance_km: float = 20.0) -> float:
29
+ return _clamp(1.0 - (distance_km / max_distance_km))
30
 
31
 
32
  def score_traffic(traffic: str) -> float:
 
34
 
35
 
36
  def score_icu(display_icu: str) -> float:
37
+ return _clamp(1.0 if display_icu == "available" else 0.55)
38
 
39
 
40
  def score_memory(entry: LearningEntry | None) -> float:
 
46
  success_rate = entry.success / total
47
  fail_bias = max(0.0, (entry.fail - entry.success) / total)
48
  raw = (0.7 * entry.avg) + (0.3 * success_rate) - (0.4 * fail_bias)
49
+ return _clamp(raw)
50
 
51
 
52
  def decision_score(
 
61
  + (traffic_score * 0.2)
62
  + (memory_score * 0.3)
63
  )
64
+ return _clamp(weighted / 1.2)
65
 
66
 
67
  def compute_reward(
 
71
  specialization_match: bool,
72
  ) -> float:
73
  survival_component = 1.0 if survived else 0.0
74
+ time_efficiency = _clamp(critical_limit / max(critical_limit + travel_time, 1e-6))
75
  specialization_component = 1.0 if specialization_match else 0.0
76
+ delay_penalty = _clamp(travel_time / max(critical_limit + travel_time, 1e-6))
77
 
78
  reward = (
79
  (survival_component * 0.45)
 
81
  + (specialization_component * 0.2)
82
  - (delay_penalty * 0.1)
83
  )
84
+ return _clamp(reward)
85
 
86
 
87
  def compute_reward_with_breakdown(
 
94
  adaptability_score: float | None = None,
95
  ) -> tuple[float, dict[str, float]]:
96
  survival_component = (
97
+ _clamp(survival_score)
98
  if survival_score is not None
99
  else (1.0 if survived else 0.0)
100
  )
101
+ time_efficiency = _clamp(critical_limit / max(critical_limit + travel_time, 1e-6))
102
  specialization_component = (
103
+ _clamp(capability_score)
104
  if capability_score is not None
105
  else (1.0 if specialization_match else 0.0)
106
  )
107
+ delay_penalty = _clamp(travel_time / max(critical_limit + travel_time, 1e-6))
108
  adapt_component = (
109
+ _clamp(adaptability_score)
110
  if adaptability_score is not None
111
  else 0.5
112
  )
 
118
  + (adapt_component * 0.2)
119
  - (delay_penalty * 0.12)
120
  )
121
+ reward = _clamp(reward)
122
  return reward, {
123
  "survival_component": survival_component,
124
  "time_efficiency_component": time_efficiency,
data/learning_archive.json CHANGED
@@ -8723,6 +8723,197 @@
8723
  "best_scenario_name": "Mass Cardiac Event (Overload)",
8724
  "best_difficulty": "hard",
8725
  "best_required_specialization": "cardiac"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8726
  }
8727
  },
8728
  "episodes": [
@@ -10910,6 +11101,50 @@
10910
  "H2"
10911
  ],
10912
  "timestamp": "2026-04-09T08:48:36.666940+00:00"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10913
  }
10914
  ]
10915
  }
 
8723
  "best_scenario_name": "Mass Cardiac Event (Overload)",
8724
  "best_difficulty": "hard",
8725
  "best_required_specialization": "cardiac"
8726
+ },
8727
+ "917229607|acde_easy": {
8728
+ "attempts": 1,
8729
+ "best_score": 0.9038000000000002,
8730
+ "best_actions": [
8731
+ "H1"
8732
+ ],
8733
+ "best_steps": 1,
8734
+ "step_stats": {
8735
+ "1": {
8736
+ "H1": {
8737
+ "count": 1,
8738
+ "success": 1,
8739
+ "accepted": 1,
8740
+ "partial": 0,
8741
+ "rejected": 0,
8742
+ "total_reward": 0.8870000000000001,
8743
+ "avg_reward": 0.8870000000000001,
8744
+ "last_status": "ACCEPTED",
8745
+ "last_reason": "Patient stabilized after delayed admission",
8746
+ "success_rate": 1.0
8747
+ }
8748
+ }
8749
+ },
8750
+ "last_score": 0.9038000000000002,
8751
+ "last_success": true,
8752
+ "last_run_at": "2026-04-09T09:44:05.035745+00:00",
8753
+ "last_actions": [
8754
+ "H1"
8755
+ ],
8756
+ "last_required_specialization": "general",
8757
+ "last_scenario_type": "fire",
8758
+ "last_scenario_name": "Apartment Fire (Smoke Inhalation)",
8759
+ "best_success": true,
8760
+ "best_scenario_name": "Apartment Fire (Smoke Inhalation)",
8761
+ "best_difficulty": "easy",
8762
+ "best_required_specialization": "general"
8763
+ },
8764
+ "917229608|acde_medium": {
8765
+ "attempts": 1,
8766
+ "best_score": 0.483,
8767
+ "best_actions": [
8768
+ "H5",
8769
+ "H2",
8770
+ "H5"
8771
+ ],
8772
+ "best_steps": 3,
8773
+ "step_stats": {
8774
+ "1": {
8775
+ "H5": {
8776
+ "count": 1,
8777
+ "success": 0,
8778
+ "accepted": 0,
8779
+ "partial": 1,
8780
+ "rejected": 0,
8781
+ "total_reward": 0.499,
8782
+ "avg_reward": 0.499,
8783
+ "last_status": "PARTIAL",
8784
+ "last_reason": "Admitted with delays: patient worsened during transfer",
8785
+ "success_rate": 0.0
8786
+ }
8787
+ },
8788
+ "2": {
8789
+ "H2": {
8790
+ "count": 1,
8791
+ "success": 0,
8792
+ "accepted": 0,
8793
+ "partial": 0,
8794
+ "rejected": 1,
8795
+ "total_reward": 0.039,
8796
+ "avg_reward": 0.039,
8797
+ "last_status": "REJECTED",
8798
+ "last_reason": "Hospital cannot admit: Hospital overloaded",
8799
+ "success_rate": 0.0
8800
+ }
8801
+ },
8802
+ "3": {
8803
+ "H5": {
8804
+ "count": 1,
8805
+ "success": 1,
8806
+ "accepted": 1,
8807
+ "partial": 0,
8808
+ "rejected": 0,
8809
+ "total_reward": 0.5336,
8810
+ "avg_reward": 0.5336,
8811
+ "last_status": "ACCEPTED",
8812
+ "last_reason": "Patient stabilized after critical delay",
8813
+ "success_rate": 1.0
8814
+ }
8815
+ }
8816
+ },
8817
+ "last_score": 0.483,
8818
+ "last_success": true,
8819
+ "last_run_at": "2026-04-09T09:44:06.199323+00:00",
8820
+ "last_actions": [
8821
+ "H5",
8822
+ "H2",
8823
+ "H5"
8824
+ ],
8825
+ "last_required_specialization": "cardiac",
8826
+ "last_scenario_type": "medical",
8827
+ "last_scenario_name": "Heart Attack (Unstable)",
8828
+ "best_success": true,
8829
+ "best_scenario_name": "Heart Attack (Unstable)",
8830
+ "best_difficulty": "medium",
8831
+ "best_required_specialization": "cardiac"
8832
+ },
8833
+ "917229609|acde_hard": {
8834
+ "attempts": 1,
8835
+ "best_score": 0.1684125,
8836
+ "best_actions": [
8837
+ "H2",
8838
+ "H6",
8839
+ "H2",
8840
+ "H2"
8841
+ ],
8842
+ "best_steps": 4,
8843
+ "step_stats": {
8844
+ "1": {
8845
+ "H2": {
8846
+ "count": 1,
8847
+ "success": 0,
8848
+ "accepted": 0,
8849
+ "partial": 0,
8850
+ "rejected": 1,
8851
+ "total_reward": 0.139,
8852
+ "avg_reward": 0.139,
8853
+ "last_status": "REJECTED",
8854
+ "last_reason": "Hospital cannot admit: No specialist available, Hospital overloaded",
8855
+ "success_rate": 0.0
8856
+ }
8857
+ },
8858
+ "2": {
8859
+ "H6": {
8860
+ "count": 1,
8861
+ "success": 0,
8862
+ "accepted": 0,
8863
+ "partial": 0,
8864
+ "rejected": 1,
8865
+ "total_reward": 0.001,
8866
+ "avg_reward": 0.001,
8867
+ "last_status": "REJECTED",
8868
+ "last_reason": "Hospital cannot admit: No specialist available",
8869
+ "success_rate": 0.0
8870
+ }
8871
+ },
8872
+ "3": {
8873
+ "H2": {
8874
+ "count": 1,
8875
+ "success": 0,
8876
+ "accepted": 0,
8877
+ "partial": 1,
8878
+ "rejected": 0,
8879
+ "total_reward": 0.148,
8880
+ "avg_reward": 0.148,
8881
+ "last_status": "PARTIAL",
8882
+ "last_reason": "Admitted with significant risk: ICU unavailable",
8883
+ "success_rate": 0.0
8884
+ }
8885
+ },
8886
+ "4": {
8887
+ "H2": {
8888
+ "count": 1,
8889
+ "success": 0,
8890
+ "accepted": 0,
8891
+ "partial": 0,
8892
+ "rejected": 1,
8893
+ "total_reward": 0.001,
8894
+ "avg_reward": 0.001,
8895
+ "last_status": "REJECTED",
8896
+ "last_reason": "Hospital cannot admit: Hospital overloaded",
8897
+ "success_rate": 0.0
8898
+ }
8899
+ }
8900
+ },
8901
+ "last_score": 0.1684125,
8902
+ "last_success": false,
8903
+ "last_run_at": "2026-04-09T09:44:07.652831+00:00",
8904
+ "last_actions": [
8905
+ "H2",
8906
+ "H6",
8907
+ "H2",
8908
+ "H2"
8909
+ ],
8910
+ "last_required_specialization": "trauma",
8911
+ "last_scenario_type": "accident",
8912
+ "last_scenario_name": "Bridge Crash (Infrastructure Blocked)",
8913
+ "best_success": false,
8914
+ "best_scenario_name": "Bridge Crash (Infrastructure Blocked)",
8915
+ "best_difficulty": "hard",
8916
+ "best_required_specialization": "trauma"
8917
  }
8918
  },
8919
  "episodes": [
 
11101
  "H2"
11102
  ],
11103
  "timestamp": "2026-04-09T08:48:36.666940+00:00"
11104
+ },
11105
+ {
11106
+ "seed": 917229607,
11107
+ "task_id": "acde_easy",
11108
+ "difficulty": "easy",
11109
+ "required_specialization": "general",
11110
+ "scenario_name": "Apartment Fire (Smoke Inhalation)",
11111
+ "score": 0.9038000000000002,
11112
+ "success": true,
11113
+ "actions": [
11114
+ "H1"
11115
+ ],
11116
+ "timestamp": "2026-04-09T09:44:05.035745+00:00"
11117
+ },
11118
+ {
11119
+ "seed": 917229608,
11120
+ "task_id": "acde_medium",
11121
+ "difficulty": "medium",
11122
+ "required_specialization": "cardiac",
11123
+ "scenario_name": "Heart Attack (Unstable)",
11124
+ "score": 0.483,
11125
+ "success": true,
11126
+ "actions": [
11127
+ "H5",
11128
+ "H2",
11129
+ "H5"
11130
+ ],
11131
+ "timestamp": "2026-04-09T09:44:06.199323+00:00"
11132
+ },
11133
+ {
11134
+ "seed": 917229609,
11135
+ "task_id": "acde_hard",
11136
+ "difficulty": "hard",
11137
+ "required_specialization": "trauma",
11138
+ "scenario_name": "Bridge Crash (Infrastructure Blocked)",
11139
+ "score": 0.1684125,
11140
+ "success": false,
11141
+ "actions": [
11142
+ "H2",
11143
+ "H6",
11144
+ "H2",
11145
+ "H2"
11146
+ ],
11147
+ "timestamp": "2026-04-09T09:44:07.652831+00:00"
11148
  }
11149
  ]
11150
  }
data/learning_memory.json CHANGED
@@ -1,30 +1,30 @@
1
  {
2
  "H2": {
3
- "success": 111,
4
- "fail": 214,
5
- "avg": 0.3359261538461545,
6
- "accepted": 111,
7
- "rejected": 214
8
  },
9
  "H6": {
10
  "success": 50,
11
- "fail": 145,
12
- "avg": 0.291038461538462,
13
  "accepted": 50,
14
- "rejected": 145
15
  },
16
  "H5": {
17
- "success": 116,
18
- "fail": 179,
19
- "avg": 0.38248847457627094,
20
- "accepted": 116,
21
- "rejected": 179
22
  },
23
  "H1": {
24
- "success": 111,
25
  "fail": 117,
26
- "avg": 0.401454385964912,
27
- "accepted": 111,
28
  "rejected": 117
29
  },
30
  "H3": {
 
1
  {
2
  "H2": {
3
+ "success": 112,
4
+ "fail": 216,
5
+ "avg": 0.3334268292682933,
6
+ "accepted": 112,
7
+ "rejected": 216
8
  },
9
  "H6": {
10
  "success": 50,
11
+ "fail": 146,
12
+ "avg": 0.28955867346938824,
13
  "accepted": 50,
14
+ "rejected": 146
15
  },
16
  "H5": {
17
+ "success": 118,
18
+ "fail": 180,
19
+ "avg": 0.3825694630872481,
20
+ "accepted": 118,
21
+ "rejected": 180
22
  },
23
  "H1": {
24
+ "success": 112,
25
  "fail": 117,
26
+ "avg": 0.4035746724890827,
27
+ "accepted": 112,
28
  "rejected": 117
29
  },
30
  "H3": {
data/trajectory_history.jsonl CHANGED
@@ -383,3 +383,11 @@
383
  {"seed": 468098217, "task": "acde_hard", "difficulty": "hard", "step": 2, "state": {"patient_condition": "critical", "remaining_time_minutes": 12.0, "failed_hospitals": ["H2"], "visited_hospitals": ["H2"], "ambulance_status": "rerouting"}, "action": {"hospital_id": "H3", "policy_score": 0.29411764705882354, "strategy": "risk-aware policy"}, "outcome": {"status": "REJECTED", "reason": "Hidden mismatch at arrival (wrong risky guess). Rerouting required."}, "reward": 0.001}
384
  {"seed": 468098217, "task": "acde_hard", "difficulty": "hard", "step": 3, "state": {"patient_condition": "critical", "remaining_time_minutes": 12.0, "failed_hospitals": ["H2", "H3"], "visited_hospitals": ["H2", "H3"], "ambulance_status": "rerouting"}, "action": {"hospital_id": "H5", "policy_score": 0.039999999999999994, "strategy": "risk-aware policy"}, "outcome": {"status": "REJECTED", "reason": "Hidden mismatch at arrival (wrong risky guess). Rerouting required."}, "reward": 0.001}
385
  {"seed": 468098217, "task": "acde_hard", "difficulty": "hard", "step": 4, "state": {"patient_condition": "critical", "remaining_time_minutes": 12.0, "failed_hospitals": ["H2", "H3", "H1"], "visited_hospitals": ["H2", "H3", "H1"], "ambulance_status": "rerouting"}, "action": {"hospital_id": "H2", "policy_score": 0.29411764705882354, "strategy": "risk-aware policy + immediate-retry override"}, "outcome": {"status": "REJECTED", "reason": "Hospital cannot admit: ICU unavailable, No specialist available"}, "reward": 0.001}
 
 
 
 
 
 
 
 
 
383
  {"seed": 468098217, "task": "acde_hard", "difficulty": "hard", "step": 2, "state": {"patient_condition": "critical", "remaining_time_minutes": 12.0, "failed_hospitals": ["H2"], "visited_hospitals": ["H2"], "ambulance_status": "rerouting"}, "action": {"hospital_id": "H3", "policy_score": 0.29411764705882354, "strategy": "risk-aware policy"}, "outcome": {"status": "REJECTED", "reason": "Hidden mismatch at arrival (wrong risky guess). Rerouting required."}, "reward": 0.001}
384
  {"seed": 468098217, "task": "acde_hard", "difficulty": "hard", "step": 3, "state": {"patient_condition": "critical", "remaining_time_minutes": 12.0, "failed_hospitals": ["H2", "H3"], "visited_hospitals": ["H2", "H3"], "ambulance_status": "rerouting"}, "action": {"hospital_id": "H5", "policy_score": 0.039999999999999994, "strategy": "risk-aware policy"}, "outcome": {"status": "REJECTED", "reason": "Hidden mismatch at arrival (wrong risky guess). Rerouting required."}, "reward": 0.001}
385
  {"seed": 468098217, "task": "acde_hard", "difficulty": "hard", "step": 4, "state": {"patient_condition": "critical", "remaining_time_minutes": 12.0, "failed_hospitals": ["H2", "H3", "H1"], "visited_hospitals": ["H2", "H3", "H1"], "ambulance_status": "rerouting"}, "action": {"hospital_id": "H2", "policy_score": 0.29411764705882354, "strategy": "risk-aware policy + immediate-retry override"}, "outcome": {"status": "REJECTED", "reason": "Hospital cannot admit: ICU unavailable, No specialist available"}, "reward": 0.001}
386
+ {"seed": 917229607, "task": "acde_easy", "difficulty": "easy", "step": 1, "state": {"patient_condition": "serious", "remaining_time_minutes": 18.0, "failed_hospitals": [], "visited_hospitals": [], "ambulance_status": "en_route"}, "action": {"hospital_id": "H1", "policy_score": 0.330194953786115, "strategy": "safe policy"}, "outcome": {"status": "ACCEPTED", "reason": "Patient stabilized after delayed admission"}, "reward": 0.8870000000000001}
387
+ {"seed": 917229608, "task": "acde_medium", "difficulty": "medium", "step": 1, "state": {"patient_condition": "critical", "remaining_time_minutes": 14.0, "failed_hospitals": [], "visited_hospitals": [], "ambulance_status": "en_route"}, "action": {"hospital_id": "H5", "policy_score": 0.49333881522073353, "strategy": "safe policy + critical triage"}, "outcome": {"status": "PARTIAL", "reason": "Admitted with delays: patient worsened during transfer"}, "reward": 0.499}
388
+ {"seed": 917229608, "task": "acde_medium", "difficulty": "medium", "step": 2, "state": {"patient_condition": "critical", "remaining_time_minutes": 14.0, "failed_hospitals": [], "visited_hospitals": ["H5"], "ambulance_status": "in_transit"}, "action": {"hospital_id": "H2", "policy_score": 0.39089664384926215, "strategy": "balanced policy + critical triage"}, "outcome": {"status": "REJECTED", "reason": "Hospital cannot admit: Hospital overloaded"}, "reward": 0.039}
389
+ {"seed": 917229608, "task": "acde_medium", "difficulty": "medium", "step": 3, "state": {"patient_condition": "critical", "remaining_time_minutes": 14.0, "failed_hospitals": ["H2"], "visited_hospitals": ["H5", "H2"], "ambulance_status": "rerouting"}, "action": {"hospital_id": "H5", "policy_score": 0.47656977550308066, "strategy": "risk-aware policy + guided-exploration + anti-stupidity guard"}, "outcome": {"status": "ACCEPTED", "reason": "Patient stabilized after critical delay"}, "reward": 0.5336}
390
+ {"seed": 917229609, "task": "acde_hard", "difficulty": "hard", "step": 1, "state": {"patient_condition": "critical", "remaining_time_minutes": 13.0, "failed_hospitals": [], "visited_hospitals": [], "ambulance_status": "en_route"}, "action": {"hospital_id": "H2", "policy_score": 0.49956106431243036, "strategy": "safe policy"}, "outcome": {"status": "REJECTED", "reason": "Hospital cannot admit: No specialist available, Hospital overloaded"}, "reward": 0.139}
391
+ {"seed": 917229609, "task": "acde_hard", "difficulty": "hard", "step": 2, "state": {"patient_condition": "critical", "remaining_time_minutes": 13.0, "failed_hospitals": ["H5"], "visited_hospitals": ["H5"], "ambulance_status": "rerouting"}, "action": {"hospital_id": "H6", "policy_score": 0.025524969981599324, "strategy": "risk-aware policy + immediate-retry override"}, "outcome": {"status": "REJECTED", "reason": "Hospital cannot admit: No specialist available"}, "reward": 0.001}
392
+ {"seed": 917229609, "task": "acde_hard", "difficulty": "hard", "step": 3, "state": {"patient_condition": "critical", "remaining_time_minutes": 13.0, "failed_hospitals": ["H5", "H6"], "visited_hospitals": ["H5", "H6"], "ambulance_status": "rerouting"}, "action": {"hospital_id": "H2", "policy_score": 0.4, "strategy": "risk-aware policy"}, "outcome": {"status": "PARTIAL", "reason": "Admitted with significant risk: ICU unavailable"}, "reward": 0.148}
393
+ {"seed": 917229609, "task": "acde_hard", "difficulty": "hard", "step": 4, "state": {"patient_condition": "critical", "remaining_time_minutes": 13.0, "failed_hospitals": ["H5", "H6"], "visited_hospitals": ["H5", "H6", "H2"], "ambulance_status": "in_transit"}, "action": {"hospital_id": "H2", "policy_score": 0.4, "strategy": "balanced policy + critical triage + anti-stupidity guard"}, "outcome": {"status": "REJECTED", "reason": "Hospital cannot admit: Hospital overloaded"}, "reward": 0.001}
inference.py CHANGED
@@ -42,6 +42,14 @@ DEFAULT_API_BASE_URL = "https://api-inference.huggingface.co/v1"
42
  DEFAULT_MODEL_NAME = "Qwen/Qwen2.5-72B-Instruct"
43
  REQUIRED_ENV_VARS = ("HF_TOKEN",)
44
 
 
 
 
 
 
 
 
 
45
 
46
  def parse_args() -> argparse.Namespace:
47
  parser = argparse.ArgumentParser(description="EmergencyEnv agent runner")
@@ -315,7 +323,7 @@ def memory_score_for_hospital(
315
  if recent_failed:
316
  value -= 0.3
317
 
318
- return max(0.0, min(1.0, value))
319
 
320
 
321
  def score_hospitals(observation: dict, learning_profile: dict | None = None) -> list[dict]:
@@ -348,8 +356,8 @@ def score_hospitals(observation: dict, learning_profile: dict | None = None) ->
348
  speed_kmh = BASE_SPEED_KMH * traffic_factor
349
  travel_time = (hospital["distance_km"] / max(speed_kmh, 1e-6)) * 60.0
350
 
351
- distance_score = max(0.0, min(1.0, 1.0 - hospital["distance_km"] / 20.0))
352
- icu_score = 1.0 if hospital["icu"] == "available" else 0.55
353
  mem_score = memory_score_for_hospital(
354
  hospital["hospital_id"],
355
  memory_snapshot,
@@ -454,13 +462,13 @@ def score_hospitals(observation: dict, learning_profile: dict | None = None) ->
454
  memory_weight = 0.1
455
  current_score_weight = 0.9
456
  base_current_score = score
457
- confidence_score = max(0.0, min(1.0, base_current_score))
458
  effective_memory_score = mem_score
459
  in_best_route = hospital["hospital_id"] in preferred_route
460
  if in_best_route and confidence_score < 0.6:
461
- effective_memory_score = 0.0
462
  if confidence_score < 0.2:
463
- effective_memory_score = 0.0
464
 
465
  score = (current_score_weight * base_current_score) + (memory_weight * effective_memory_score)
466
 
@@ -473,7 +481,7 @@ def score_hospitals(observation: dict, learning_profile: dict | None = None) ->
473
  "specialization": hospital["specialization"],
474
  "travel_time": travel_time,
475
  "memory_score": mem_score,
476
- "policy_score": max(0.0, min(1.0, score)),
477
  "specialization_match": spec_match,
478
  "tie_break_score": (
479
  (distance_score * 0.35)
@@ -500,7 +508,7 @@ def score_hospitals(observation: dict, learning_profile: dict | None = None) ->
500
  )
501
  jitter_rng = random.Random(jitter_seed)
502
  normalized *= jitter_rng.uniform(0.3, 0.7)
503
- item["policy_score"] = max(0.0, min(1.0, normalized))
504
  elif max_score > 0:
505
  for item in scored:
506
  normalized = item["policy_score"] / max_score
@@ -512,7 +520,7 @@ def score_hospitals(observation: dict, learning_profile: dict | None = None) ->
512
  )
513
  jitter_rng = random.Random(jitter_seed)
514
  normalized *= jitter_rng.uniform(0.3, 0.7)
515
- item["policy_score"] = max(0.0, min(1.0, normalized))
516
  else:
517
  tie_min = min(item.get("tie_break_score", 0.0) for item in scored)
518
  tie_max = max(item.get("tie_break_score", 0.0) for item in scored)
@@ -528,14 +536,14 @@ def score_hospitals(observation: dict, learning_profile: dict | None = None) ->
528
  )
529
  jitter_rng = random.Random(jitter_seed)
530
  normalized *= jitter_rng.uniform(0.3, 0.7)
531
- item["policy_score"] = max(0.0, min(1.0, normalized))
532
  else:
533
  for item in scored:
534
- item["policy_score"] = 0.0
535
 
536
  # Remove hard-zero scores and normalize to probability-like values.
537
  for item in scored:
538
- if item["policy_score"] <= 0.0:
539
  jitter_seed = (
540
  int(observation.get("seed", 0))
541
  + (step_number * 173)
@@ -544,11 +552,11 @@ def score_hospitals(observation: dict, learning_profile: dict | None = None) ->
544
  jitter_rng = random.Random(jitter_seed)
545
  if critical_patient and required_specialization != "general":
546
  if item.get("specialization") == required_specialization:
547
- item["policy_score"] = jitter_rng.uniform(0.08, 0.18)
548
  else:
549
- item["policy_score"] = jitter_rng.uniform(0.001, 0.01)
550
  else:
551
- item["policy_score"] = jitter_rng.uniform(0.05, 0.15)
552
 
553
  total_score = sum(item["policy_score"] for item in scored)
554
  if total_score > 0:
@@ -584,8 +592,8 @@ def score_hospitals(observation: dict, learning_profile: dict | None = None) ->
584
  + sum(ord(ch) for ch in item["hospital_id"])
585
  )
586
  jitter_rng = random.Random(jitter_seed)
587
- normalized_score = jitter_rng.uniform(0.01, 0.03)
588
- item["policy_score"] = normalized_score
589
 
590
  scored.sort(key=lambda item: item["policy_score"], reverse=True)
591
 
 
42
  DEFAULT_MODEL_NAME = "Qwen/Qwen2.5-72B-Instruct"
43
  REQUIRED_ENV_VARS = ("HF_TOKEN",)
44
 
45
+ # Strict clamping to open interval (0, 1)
46
+ _FLOOR = 0.001
47
+ _CEIL = 0.999
48
+
49
+ def _clamp(v: float) -> float:
50
+ """Clamp a score to the open interval (0, 1)."""
51
+ return max(_FLOOR, min(_CEIL, v))
52
+
53
 
54
  def parse_args() -> argparse.Namespace:
55
  parser = argparse.ArgumentParser(description="EmergencyEnv agent runner")
 
323
  if recent_failed:
324
  value -= 0.3
325
 
326
+ return _clamp(value)
327
 
328
 
329
  def score_hospitals(observation: dict, learning_profile: dict | None = None) -> list[dict]:
 
356
  speed_kmh = BASE_SPEED_KMH * traffic_factor
357
  travel_time = (hospital["distance_km"] / max(speed_kmh, 1e-6)) * 60.0
358
 
359
+ distance_score = _clamp(1.0 - hospital["distance_km"] / 20.0)
360
+ icu_score = _clamp(1.0 if hospital["icu"] == "available" else 0.55)
361
  mem_score = memory_score_for_hospital(
362
  hospital["hospital_id"],
363
  memory_snapshot,
 
462
  memory_weight = 0.1
463
  current_score_weight = 0.9
464
  base_current_score = score
465
+ confidence_score = _clamp(base_current_score)
466
  effective_memory_score = mem_score
467
  in_best_route = hospital["hospital_id"] in preferred_route
468
  if in_best_route and confidence_score < 0.6:
469
+ effective_memory_score = _FLOOR
470
  if confidence_score < 0.2:
471
+ effective_memory_score = _FLOOR
472
 
473
  score = (current_score_weight * base_current_score) + (memory_weight * effective_memory_score)
474
 
 
481
  "specialization": hospital["specialization"],
482
  "travel_time": travel_time,
483
  "memory_score": mem_score,
484
+ "policy_score": _clamp(score),
485
  "specialization_match": spec_match,
486
  "tie_break_score": (
487
  (distance_score * 0.35)
 
508
  )
509
  jitter_rng = random.Random(jitter_seed)
510
  normalized *= jitter_rng.uniform(0.3, 0.7)
511
+ item["policy_score"] = _clamp(normalized)
512
  elif max_score > 0:
513
  for item in scored:
514
  normalized = item["policy_score"] / max_score
 
520
  )
521
  jitter_rng = random.Random(jitter_seed)
522
  normalized *= jitter_rng.uniform(0.3, 0.7)
523
+ item["policy_score"] = _clamp(normalized)
524
  else:
525
  tie_min = min(item.get("tie_break_score", 0.0) for item in scored)
526
  tie_max = max(item.get("tie_break_score", 0.0) for item in scored)
 
536
  )
537
  jitter_rng = random.Random(jitter_seed)
538
  normalized *= jitter_rng.uniform(0.3, 0.7)
539
+ item["policy_score"] = _clamp(normalized)
540
  else:
541
  for item in scored:
542
+ item["policy_score"] = _FLOOR
543
 
544
  # Remove hard-zero scores and normalize to probability-like values.
545
  for item in scored:
546
+ if item["policy_score"] <= _FLOOR:
547
  jitter_seed = (
548
  int(observation.get("seed", 0))
549
  + (step_number * 173)
 
552
  jitter_rng = random.Random(jitter_seed)
553
  if critical_patient and required_specialization != "general":
554
  if item.get("specialization") == required_specialization:
555
+ item["policy_score"] = _clamp(jitter_rng.uniform(0.08, 0.18))
556
  else:
557
+ item["policy_score"] = _clamp(jitter_rng.uniform(0.001, 0.01))
558
  else:
559
+ item["policy_score"] = _clamp(jitter_rng.uniform(0.05, 0.15))
560
 
561
  total_score = sum(item["policy_score"] for item in scored)
562
  if total_score > 0:
 
592
  + sum(ord(ch) for ch in item["hospital_id"])
593
  )
594
  jitter_rng = random.Random(jitter_seed)
595
+ normalized_score = _clamp(jitter_rng.uniform(0.01, 0.03))
596
+ item["policy_score"] = _clamp(normalized_score)
597
 
598
  scored.sort(key=lambda item: item["policy_score"], reverse=True)
599