Spaces:
Sleeping
Sleeping
File size: 6,850 Bytes
1091ce2 50e3063 1091ce2 50e3063 1091ce2 50e3063 1091ce2 50e3063 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | """DispatchPulse data models.
Two layers:
1. **OpenEnv interface models** — ``DispatchPulseAction``, ``DispatchPulseObservation``,
``DispatchPulseState``. These inherit directly from openenv-core base classes
and form the wire format the server/client/grader exchange.
2. **Internal simulation models** — ``Position``, ``EmergencyType``, ``Severity``,
``UnitType``, ``UnitStatus``, ``EmergencyCall``, ``EmergencyUnit``, ``Hospital``,
``WorldConfig``, ``Reward``. These are plain Pydantic models the simulation
engine uses internally; they never cross the OpenEnv boundary directly.
"""
from __future__ import annotations
from enum import Enum
from typing import List, Optional
from pydantic import BaseModel, Field
# ---------------------------------------------------------------------------
# OpenEnv base classes
# ---------------------------------------------------------------------------
from openenv.core.env_server.types import Action, Observation, State
# ===========================================================================
# OpenEnv-facing wire types
# ===========================================================================
class DispatchPulseAction(Action):
"""A single dispatcher action.
The agent supplies ``action_type`` plus optional fields. The simplest
possible interface for an LLM is the ``text`` field — the server will
parse it as a command line like ``"dispatch CALL-001 ALS-1 H1"``.
Supported action types:
- ``dispatch`` : send a unit to a call (call_id, unit_id, hospital_id?)
- ``classify`` : reclassify a call's severity (call_id, severity)
- ``callback`` : phone the caller back (call_id, message)
- ``wait`` : skip ahead in simulation time (minutes)
- ``view`` : free inspection (no time cost)
"""
action_type: str = Field(
..., description="One of: dispatch, classify, callback, wait, view"
)
text: str = Field(
default="",
description="Free-text representation of the action (e.g. 'dispatch CALL-001 ALS-1 H1')",
)
call_id: Optional[str] = Field(default=None)
unit_id: Optional[str] = Field(default=None)
hospital_id: Optional[str] = Field(default=None)
severity: Optional[int] = Field(default=None, ge=1, le=5)
message: Optional[str] = Field(default=None)
minutes: Optional[int] = Field(default=None, ge=1, le=5)
class DispatchPulseObservation(Observation):
"""What the dispatcher sees each turn.
The ``text`` field is the human-readable dispatch center view that the
LLM agent reads. The structured fields underneath are useful for
programmatic agents and grading.
"""
text: str = Field(default="", description="Formatted dispatch center view for the agent")
current_time: int = Field(default=0, description="Simulation minute")
time_limit: int = Field(default=30, description="Episode time limit (minutes)")
calls_pending: int = Field(default=0, description="Number of calls waiting for dispatch")
units_available: int = Field(default=0, description="Number of free units")
calls_completed: int = Field(default=0)
calls_timed_out: int = Field(default=0)
total_calls: int = Field(default=0)
last_action_error: Optional[str] = Field(
default=None, description="Error message from the last action, or None"
)
info_message: Optional[str] = Field(
default=None, description="Free-text message describing what just happened"
)
class DispatchPulseState(State):
"""Internal state snapshot exposed via ``GET /state``."""
current_time: int = Field(default=0)
episode_done: bool = Field(default=False)
total_calls: int = Field(default=0)
calls_dispatched: int = Field(default=0)
calls_completed: int = Field(default=0)
calls_timed_out: int = Field(default=0)
calls_pending: int = Field(default=0)
units_available: int = Field(default=0)
running_reward: float = Field(default=0.0)
task_name: str = Field(default="easy")
# ===========================================================================
# Internal simulation models (plain Pydantic, never cross OpenEnv boundary)
# ===========================================================================
class Position(BaseModel):
"""A 2D coordinate on the city grid (km)."""
x: float = Field(..., ge=0.0)
y: float = Field(..., ge=0.0)
class EmergencyType(str, Enum):
CARDIAC_ARREST = "cardiac_arrest"
TRAUMA = "trauma"
STROKE = "stroke"
FIRE = "fire"
MINOR_INJURY = "minor_injury"
BREATHING = "breathing_difficulty"
MENTAL_HEALTH = "mental_health_crisis"
class Severity(int, Enum):
CRITICAL = 1
URGENT = 2
MODERATE = 3
LOW = 4
FALSE_ALARM = 5
class UnitType(str, Enum):
ALS_AMBULANCE = "als_ambulance"
BLS_AMBULANCE = "bls_ambulance"
FIRE_ENGINE = "fire_engine"
POLICE = "police"
class UnitStatus(str, Enum):
AVAILABLE = "available"
EN_ROUTE = "en_route"
ON_SCENE = "on_scene"
RETURNING = "returning"
class EmergencyCall(BaseModel):
call_id: str
timestamp: int
caller_description: str
location: Position
true_type: EmergencyType
true_severity: Severity
reported_type: Optional[EmergencyType] = None
reported_severity: Optional[Severity] = None
requires_unit_types: List[UnitType] = Field(default_factory=list)
optimal_unit_type: UnitType
active: bool = True
dispatched_unit_id: Optional[str] = None
response_time: Optional[float] = None
outcome_score: Optional[float] = None
delivered_hospital_id: Optional[str] = None
class EmergencyUnit(BaseModel):
unit_id: str
unit_type: UnitType
position: Position
base_position: Position
status: UnitStatus = UnitStatus.AVAILABLE
speed_kmh: float = Field(..., gt=0)
assigned_call_id: Optional[str] = None
assigned_hospital_id: Optional[str] = None
busy_until: Optional[int] = None
capabilities: List[EmergencyType] = Field(default_factory=list)
class Hospital(BaseModel):
hospital_id: str
name: str
position: Position
capacity: int = Field(..., ge=0)
available_beds: int = Field(..., ge=0)
has_trauma_center: bool = False
has_cardiac_unit: bool = False
has_stroke_unit: bool = False
on_diversion: bool = False
class WorldConfig(BaseModel):
grid_size_km: float = 10.0
time_limit_minutes: int = 30
step_duration_minutes: int = 1
call_timeout_minutes: int = 20
max_wait_step_minutes: int = 5
class Reward(BaseModel):
"""Final episode reward, all components in [0.0, 1.0]."""
total: float = Field(..., ge=0.0, le=1.0)
survival_score: float
efficiency_score: float
triage_accuracy: float
penalty: float
details: str = ""
|