Spaces:
Running
Running
| from __future__ import annotations | |
| from enum import Enum | |
| from math import isclose | |
| from typing import Annotated, Any, Literal, Optional, Union | |
| from pydantic import BaseModel, ConfigDict, Field, model_validator | |
| class EvacBaseModel(BaseModel): | |
| model_config = ConfigDict(extra="forbid") | |
| ScoreValue = Annotated[float, Field(gt=0.0, lt=1.0)] | |
| class DisasterType(str, Enum): | |
| fire = "fire" | |
| flood = "flood" | |
| gas = "gas" | |
| structural = "structural" | |
| active_threat = "active_threat" | |
| multi_cascade = "multi_cascade" | |
| class HazardType(str, Enum): | |
| fire = "fire" | |
| flood = "flood" | |
| gas = "gas" | |
| structural = "structural" | |
| threat = "threat" | |
| class ActionType(str, Enum): | |
| route_civilians = "route_civilians" | |
| evacuate_floor = "evacuate_floor" | |
| prioritize_room = "prioritize_room" | |
| block_route = "block_route" | |
| call_elevator = "call_elevator" | |
| open_exit = "open_exit" | |
| lockdown_room = "lockdown_room" | |
| request_render = "request_render" | |
| wait = "wait" | |
| class ExitType(str, Enum): | |
| ground = "ground" | |
| rooftop = "rooftop" | |
| emergency_window = "emergency_window" | |
| class TerminationReason(str, Enum): | |
| all_saved = "all_saved" | |
| all_routes_cut = "all_routes_cut" | |
| max_steps = "max_steps" | |
| all_lost = "all_lost" | |
| class EventType(str, Enum): | |
| gas_rupture = "gas_rupture" | |
| stairwell_collapse = "stairwell_collapse" | |
| power_outage = "power_outage" | |
| fire_ignition = "fire_ignition" | |
| structural_collapse = "structural_collapse" | |
| flood_rise = "flood_rise" | |
| gas_spread = "gas_spread" | |
| explosion = "explosion" | |
| threat_move = "threat_move" | |
| civilian_loss = "civilian_loss" | |
| class Rect(EvacBaseModel): | |
| x: int | |
| y: int | |
| w: int | |
| h: int | |
| class EdgeRef(EvacBaseModel): | |
| from_id: str | |
| to_id: str | |
| edge_type: Literal["corridor", "stairwell", "elevator"] | |
| class ElevatorRequest(EvacBaseModel): | |
| floor_id: int | |
| direction: Literal["up", "down"] | |
| class RewardWeights(EvacBaseModel): | |
| civilian_saved: float = 2.0 | |
| civilian_lost: float = -5.0 | |
| hazard_avoidance: float = 0.3 | |
| vulnerable_bonus: float = 0.5 | |
| efficiency: float = 0.2 | |
| invalid_action: float = -0.5 | |
| idle: float = -0.1 | |
| time_step: float = -0.1 | |
| completion: float = 5.0 | |
| class ThreatState(EvacBaseModel): | |
| current_room_id: str | |
| target_room_id: Optional[str] = None | |
| steps_since_move: int | |
| movement_interval: int = 2 | |
| class ActionRecord(EvacBaseModel): | |
| step: int | |
| action_type: ActionType | |
| raw_action: dict[str, Any] | |
| valid: bool | |
| reward_total: float | |
| class IncidentOutcomes(EvacBaseModel): | |
| safe: int = 0 | |
| mild_injury: int = 0 | |
| severe_injury: int = 0 | |
| deaths: int = 0 | |
| def total(self) -> int: | |
| return self.safe + self.mild_injury + self.severe_injury + self.deaths | |
| class EpisodeMetrics(EvacBaseModel): | |
| actions_by_type: dict[str, int] = Field(default_factory=dict) | |
| invalid_actions: int = 0 | |
| render_requests: int = 0 | |
| civilians_saved_mobile: int = 0 | |
| civilians_saved_injured: int = 0 | |
| civilians_saved_impaired: int = 0 | |
| civilians_lost_mobile: int = 0 | |
| civilians_lost_injured: int = 0 | |
| civilians_lost_impaired: int = 0 | |
| incident_safe: int = 0 | |
| incident_mild_injury: int = 0 | |
| incident_severe_injury: int = 0 | |
| incident_deaths: int = 0 | |
| cumulative_hazard_exposure: float = 0.0 | |
| elapsed_ms: float = 0.0 | |
| class MetricsDelta(EvacBaseModel): | |
| civilians_saved_delta: int = 0 | |
| civilians_lost_delta: int = 0 | |
| hazard_exposure_delta: float = 0.0 | |
| class EventSummary(EvacBaseModel): | |
| event_id: str | |
| event_type: EventType | |
| target_id: str | |
| description: str | |
| class BaselineRun(EvacBaseModel): | |
| task_id: str | |
| seed: int | |
| episode_id: str | |
| steps: int | |
| score: ScoreValue | |
| total_reward: float | |
| termination_reason: Optional[str] = None | |
| class TaskSpecPublic(EvacBaseModel): | |
| task_id: str | |
| name: str | |
| difficulty: str | |
| disaster_type: DisasterType | |
| goal: str | |
| description: str | |
| max_steps: int | |
| expected_score_range: list[ScoreValue] | |
| class Occupancy(EvacBaseModel): | |
| mobile: int = 0 | |
| injured: int = 0 | |
| mobility_impaired: int = 0 | |
| def total(self) -> int: | |
| return self.mobile + self.injured + self.mobility_impaired | |
| class HazardState(EvacBaseModel): | |
| hazard_type: Optional[HazardType] = None | |
| severity: float = 0.0 | |
| smoke: float = 0.0 | |
| water_level: float = 0.0 | |
| structural_integrity: float = 1.0 | |
| passable: bool = True | |
| class Room(EvacBaseModel): | |
| room_id: str | |
| floor_id: int | |
| room_type: Literal["office", "lab", "hall", "utility", "shelter"] = "office" | |
| geometry: Rect | |
| capacity: int = 20 | |
| occupancy: Occupancy = Field(default_factory=Occupancy) | |
| hazard: HazardState = Field(default_factory=HazardState) | |
| visibility: float = 1.0 | |
| accessible: bool = True | |
| adjacent_node_ids: list[str] = Field(default_factory=list) | |
| class Corridor(EvacBaseModel): | |
| corridor_id: str | |
| from_node_id: str | |
| to_node_id: str | |
| length_cost: int = 1 | |
| hazard: HazardState = Field(default_factory=HazardState) | |
| class Stairwell(EvacBaseModel): | |
| stairwell_id: str | |
| floor_ids: list[int] | |
| capacity_per_step: int = 5 | |
| blocked: bool = False | |
| entry_room_ids: dict[int, str] = Field(default_factory=dict) | |
| class Elevator(EvacBaseModel): | |
| elevator_id: str | |
| floor_ids: list[int] | |
| current_floor: int = 0 | |
| target_floor: Optional[int] = None | |
| operational: bool = True | |
| capacity: int = 6 | |
| travel_time_per_floor: int = 2 | |
| queue: list[ElevatorRequest] = Field(default_factory=list) | |
| class Exit(EvacBaseModel): | |
| exit_id: str | |
| floor_id: int | |
| exit_type: ExitType = ExitType.ground | |
| adjacent_room_id: str | |
| blocked: bool = False | |
| requires_open_action: bool = False | |
| class Floor(EvacBaseModel): | |
| floor_id: int | |
| width: int = 800 | |
| height: int = 400 | |
| rooms: list[Room] = Field(default_factory=list) | |
| corridors: list[Corridor] = Field(default_factory=list) | |
| stairwells: list[Stairwell] = Field(default_factory=list) | |
| exits: list[Exit] = Field(default_factory=list) | |
| elevators: list[Elevator] = Field(default_factory=list) | |
| class Building(EvacBaseModel): | |
| building_id: str | |
| seed: int | |
| floors: list[Floor] = Field(default_factory=list) | |
| graph_edges: list[EdgeRef] = Field(default_factory=list) | |
| disaster_zones: list[str] = Field(default_factory=list) | |
| class TransitGroup(EvacBaseModel): | |
| transit_id: str | |
| source_node_id: str | |
| target_node_id: str | |
| occupancy: Occupancy | |
| incident_outcomes: IncidentOutcomes = Field(default_factory=IncidentOutcomes) | |
| path_kind: Literal["corridor", "stairwell", "elevator"] | |
| carrier_id: Optional[str] = None | |
| steps_remaining: int = 1 | |
| class ScheduledEvent(EvacBaseModel): | |
| event_id: str | |
| trigger_step: int | |
| event_type: EventType | |
| target_id: str | |
| payload: dict[str, Any] = Field(default_factory=dict) | |
| triggered: bool = False | |
| class SummaryObservation(EvacBaseModel): | |
| disaster_type: DisasterType | |
| disaster_origin: str | |
| goal: str | |
| total_civilians: int | |
| civilians_saved: int | |
| civilians_lost: int | |
| civilians_in_transit: int | |
| elevators_operational: int | |
| incident_outcomes: IncidentOutcomes = Field(default_factory=IncidentOutcomes) | |
| class RoomObservation(EvacBaseModel): | |
| room_id: str | |
| floor_id: int | |
| occupancy: Occupancy | |
| hazard_type: Optional[HazardType] = None | |
| hazard_severity: float = 0.0 | |
| smoke: float = 0.0 | |
| water_level: float = 0.0 | |
| structural_integrity: float = 1.0 | |
| passable: bool = True | |
| accessible: bool = True | |
| connected_rooms: list[str] = Field(default_factory=list) | |
| class EdgeObservation(EvacBaseModel): | |
| corridor_id: str | |
| from_node_id: str | |
| to_node_id: str | |
| hazard_severity: float = 0.0 | |
| passable: bool = True | |
| class StairwellObservation(EvacBaseModel): | |
| stairwell_id: str | |
| floor_ids: list[int] | |
| blocked: bool = False | |
| capacity_per_step: int = 5 | |
| class ExitObservation(EvacBaseModel): | |
| exit_id: str | |
| floor_id: int | |
| exit_type: ExitType | |
| blocked: bool = False | |
| requires_open_action: bool = False | |
| class ElevatorObservation(EvacBaseModel): | |
| elevator_id: str | |
| current_floor: int | |
| target_floor: Optional[int] = None | |
| operational: bool = True | |
| class TransitGroupObservation(EvacBaseModel): | |
| transit_id: str | |
| source_node_id: str | |
| target_node_id: str | |
| occupancy: Occupancy | |
| steps_remaining: int | |
| class RenderObservation(EvacBaseModel): | |
| floor_id: int | |
| image_base64: str | |
| class Observation(EvacBaseModel): | |
| episode_id: str | |
| task_id: str | |
| step: int | |
| max_steps: int | |
| summary: SummaryObservation | |
| rooms: list[RoomObservation] | |
| corridors: list[EdgeObservation] | |
| stairwells: list[StairwellObservation] | |
| exits: list[ExitObservation] | |
| render: Optional[RenderObservation] = None | |
| class BaseAction(EvacBaseModel): | |
| episode_id: str | |
| expected_step: int | |
| action_type: ActionType | |
| class RouteCiviliansAction(BaseAction): | |
| action_type: Literal[ActionType.route_civilians] = ActionType.route_civilians | |
| from_node_id: str | |
| to_node_id: str | |
| occupancy: Occupancy | |
| preference: Literal["fastest", "safest", "injured_first"] = "fastest" | |
| class EvacuateFloorAction(BaseAction): | |
| action_type: Literal[ActionType.evacuate_floor] = ActionType.evacuate_floor | |
| floor_id: int | |
| preferred_exit_id: Optional[str] = None | |
| class PrioritizeRoomAction(BaseAction): | |
| action_type: Literal[ActionType.prioritize_room] = ActionType.prioritize_room | |
| room_id: str | |
| priority: Literal["injured_first", "all", "mobile_only"] = "all" | |
| class BlockRouteAction(BaseAction): | |
| action_type: Literal[ActionType.block_route] = ActionType.block_route | |
| edge_id: str | |
| class CallElevatorAction(BaseAction): | |
| action_type: Literal[ActionType.call_elevator] = ActionType.call_elevator | |
| elevator_id: str | |
| source_floor: int | |
| target_floor: int | |
| class OpenExitAction(BaseAction): | |
| action_type: Literal[ActionType.open_exit] = ActionType.open_exit | |
| exit_id: str | |
| class LockdownRoomAction(BaseAction): | |
| action_type: Literal[ActionType.lockdown_room] = ActionType.lockdown_room | |
| room_id: str | |
| class RequestRenderAction(BaseAction): | |
| action_type: Literal[ActionType.request_render] = ActionType.request_render | |
| floor_id: int | |
| class WaitAction(BaseAction): | |
| action_type: Literal[ActionType.wait] = ActionType.wait | |
| reason: Optional[str] = None | |
| Action = Annotated[ | |
| Union[ | |
| RouteCiviliansAction, | |
| EvacuateFloorAction, | |
| PrioritizeRoomAction, | |
| BlockRouteAction, | |
| CallElevatorAction, | |
| OpenExitAction, | |
| LockdownRoomAction, | |
| RequestRenderAction, | |
| WaitAction, | |
| ], | |
| Field(discriminator="action_type"), | |
| ] | |
| class Reward(EvacBaseModel): | |
| total: float | |
| civilians_saved_delta: int = 0 | |
| civilians_lost_delta: int = 0 | |
| hazard_avoidance_bonus: float = 0.0 | |
| vulnerable_group_bonus: float = 0.0 | |
| efficiency_bonus: float = 0.0 | |
| invalid_action_penalty: float = 0.0 | |
| idle_penalty: float = 0.0 | |
| completion_bonus: float = 0.0 | |
| def validate_total(self) -> Reward: | |
| expected_total = ( | |
| self.civilians_saved_delta | |
| + self.civilians_lost_delta | |
| + self.hazard_avoidance_bonus | |
| + self.vulnerable_group_bonus | |
| + self.efficiency_bonus | |
| + self.invalid_action_penalty | |
| + self.idle_penalty | |
| + self.completion_bonus | |
| ) | |
| if not isclose(self.total, expected_total, rel_tol=1e-9, abs_tol=1e-9): | |
| raise ValueError( | |
| f"Reward.total must equal the sum of components ({expected_total})" | |
| ) | |
| return self | |
| class StepInfo(EvacBaseModel): | |
| termination_reason: Optional[TerminationReason] = None | |
| invalid_action: bool = False | |
| invalid_reason: Optional[str] = None | |
| triggered_events: list[EventSummary] = Field(default_factory=list) | |
| metrics_delta: MetricsDelta = Field(default_factory=MetricsDelta) | |
| class ErrorResponse(EvacBaseModel): | |
| error_code: str | |
| message: str | |
| episode_id: Optional[str] = None | |
| class StateView(EvacBaseModel): | |
| episode_id: str | |
| task_id: str | |
| step: int | |
| max_steps: int | |
| done: bool | |
| termination_reason: Optional[TerminationReason] = None | |
| summary: SummaryObservation | |
| rooms: list[RoomObservation] | |
| corridors: list[EdgeObservation] | |
| stairwells: list[StairwellObservation] | |
| exits: list[ExitObservation] | |
| elevators: list[ElevatorObservation] = Field(default_factory=list) | |
| transit_groups: list[TransitGroupObservation] = Field(default_factory=list) | |
| blocked_route_ids: list[str] = Field(default_factory=list) | |
| class TaskSpec(EvacBaseModel): | |
| task_id: str | |
| name: str | |
| difficulty: Literal["easy", "medium", "medium_hard", "hard", "expert"] | |
| disaster_type: DisasterType | |
| building_profile: str | |
| success_criteria: str | |
| goal: str | |
| max_steps: int | |
| evaluation_seeds: list[int] = Field(default_factory=lambda: [42, 123, 456]) | |
| reward_weights: RewardWeights = Field(default_factory=RewardWeights) | |
| description: str = "" | |
| expected_score_range: list[ScoreValue] = Field( | |
| default_factory=lambda: [0.001, 0.999] | |
| ) | |
| class EpisodeStateInternal(EvacBaseModel): | |
| model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True) | |
| episode_id: str | |
| task: TaskSpec | |
| building: Building | |
| seed: int | |
| step: int = 0 | |
| done: bool = False | |
| termination_reason: Optional[TerminationReason] = None | |
| civilians_saved: Occupancy = Field(default_factory=Occupancy) | |
| civilians_lost: Occupancy = Field(default_factory=Occupancy) | |
| total_civilians: Occupancy = Field(default_factory=Occupancy) | |
| civilians_in_transit: list[TransitGroup] = Field(default_factory=list) | |
| scheduled_events: list[ScheduledEvent] = Field(default_factory=list) | |
| panic_timers: dict[str, int] = Field(default_factory=dict) | |
| threat_state: Optional[ThreatState] = None | |
| action_history: list[ActionRecord] = Field(default_factory=list) | |
| metrics: EpisodeMetrics = Field(default_factory=EpisodeMetrics) | |
| resolved_incident_outcomes: IncidentOutcomes = Field(default_factory=IncidentOutcomes) | |
| room_incident_outcomes: dict[str, IncidentOutcomes] = Field(default_factory=dict) | |
| prioritized_rooms: set[str] = Field(default_factory=set) | |
| blocked_routes: set[str] = Field(default_factory=set) | |
| # -- Building Layout Views (for Unity 3D client) ------------------------ | |
| class RoomLayoutView(BaseModel): | |
| room_id: str | |
| floor_id: int | |
| room_type: str | |
| geometry: Rect | |
| capacity: int | |
| connected_node_ids: list[str] | |
| class CorridorLayoutView(BaseModel): | |
| corridor_id: str | |
| from_node_id: str | |
| to_node_id: str | |
| length_cost: int | |
| class StairwellLayoutView(BaseModel): | |
| stairwell_id: str | |
| floor_ids: list[int] | |
| capacity_per_step: int | |
| entry_room_ids: dict[int, str] | |
| class ElevatorLayoutView(BaseModel): | |
| elevator_id: str | |
| floor_ids: list[int] | |
| capacity: int | |
| travel_time_per_floor: int | |
| entry_room_ids: dict[int, str] | |
| class ExitLayoutView(BaseModel): | |
| exit_id: str | |
| floor_id: int | |
| exit_type: str | |
| adjacent_room_id: str | |
| requires_open_action: bool | |
| class FloorLayoutView(BaseModel): | |
| floor_id: int | |
| width: int | |
| height: int | |
| rooms: list[RoomLayoutView] | |
| corridors: list[CorridorLayoutView] | |
| class BuildingLayoutView(BaseModel): | |
| episode_id: str | |
| building_id: str | |
| seed: int | |
| floors: list[FloorLayoutView] | |
| stairwells: list[StairwellLayoutView] | |
| elevators: list[ElevatorLayoutView] | |
| exits: list[ExitLayoutView] | |