Upload folder using huggingface_hub
Browse files- app/environment/core.py +12 -3
- app/environment/validation.py +9 -1
- app/models/observation.py +8 -2
- app/models/state.py +24 -3
- app/utils/calculations.py +21 -13
- data/learning_archive.json +235 -0
- data/learning_memory.json +16 -16
- data/trajectory_history.jsonl +8 -0
- inference.py +25 -17
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 =
|
| 1100 |
else:
|
| 1101 |
-
normalized_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 =
|
| 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 =
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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
|
| 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
|
| 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 =
|
| 67 |
specialization_component = 1.0 if specialization_match else 0.0
|
| 68 |
-
delay_penalty =
|
| 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
|
| 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 |
-
|
| 90 |
if survival_score is not None
|
| 91 |
else (1.0 if survived else 0.0)
|
| 92 |
)
|
| 93 |
-
time_efficiency =
|
| 94 |
specialization_component = (
|
| 95 |
-
|
| 96 |
if capability_score is not None
|
| 97 |
else (1.0 if specialization_match else 0.0)
|
| 98 |
)
|
| 99 |
-
delay_penalty =
|
| 100 |
adapt_component = (
|
| 101 |
-
|
| 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 =
|
| 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":
|
| 4 |
-
"fail":
|
| 5 |
-
"avg": 0.
|
| 6 |
-
"accepted":
|
| 7 |
-
"rejected":
|
| 8 |
},
|
| 9 |
"H6": {
|
| 10 |
"success": 50,
|
| 11 |
-
"fail":
|
| 12 |
-
"avg": 0.
|
| 13 |
"accepted": 50,
|
| 14 |
-
"rejected":
|
| 15 |
},
|
| 16 |
"H5": {
|
| 17 |
-
"success":
|
| 18 |
-
"fail":
|
| 19 |
-
"avg": 0.
|
| 20 |
-
"accepted":
|
| 21 |
-
"rejected":
|
| 22 |
},
|
| 23 |
"H1": {
|
| 24 |
-
"success":
|
| 25 |
"fail": 117,
|
| 26 |
-
"avg": 0.
|
| 27 |
-
"accepted":
|
| 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
|
| 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 =
|
| 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 =
|
| 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 =
|
| 462 |
if confidence_score < 0.2:
|
| 463 |
-
effective_memory_score =
|
| 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":
|
| 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"] =
|
| 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"] =
|
| 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"] =
|
| 532 |
else:
|
| 533 |
for item in scored:
|
| 534 |
-
item["policy_score"] =
|
| 535 |
|
| 536 |
# Remove hard-zero scores and normalize to probability-like values.
|
| 537 |
for item in scored:
|
| 538 |
-
if item["policy_score"] <=
|
| 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 |
|