# Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. # # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. """ Data models for the Dispatch Triage Environment. The agent receives natural-language incident descriptions and a pool of available units. It must infer urgency and correct unit type from context alone — no severity numbers or incident type labels are exposed. """ from typing import List, Literal, Optional from openenv.core.env_server.types import Action, Observation, State from pydantic import BaseModel, Field # --------------------------------------------------------------------------- # Domain constants (internal use only — never sent to the agent) # --------------------------------------------------------------------------- UnitType = Literal["fire_truck", "ambulance", "police"] Difficulty = Literal["easy", "medium", "hard"] # Correct unit-type mapping used internally for scoring. # The agent must infer this from the incident description. CORRECT_UNIT: dict = { "fire": "fire_truck", "cardiac_arrest": "ambulance", "car_crash": "police", "gas_leak": "fire_truck", } # Penalty added to mismatch_penalties when an agent dispatches an incident # whose dependency has not yet been resolved (hard-mode cascade). # This is a PENALTY subtracted from the running score, not a severity boost. CASCADE_SEVERITY_BOOST: int = 4 # --------------------------------------------------------------------------- # Sub-models # --------------------------------------------------------------------------- class Incident(BaseModel): """ A single emergency call visible to the agent. Severity and incident type are intentionally hidden — the agent must reason about urgency and appropriate unit from the natural-language `description` alone. """ id: int description: str # Plain-English caller report — only signal the agent gets location: str # Human-readable label, e.g. "Block 4A" assigned_unit_id: Optional[int] = None # None until dispatched dispatch_rank: Optional[int] = None # 1 = dispatched first this episode resolved: bool = False depends_on: List[int] = Field(default_factory=list) # Hard mode: ids of blocking incidents class Unit(BaseModel): """An emergency unit in the dispatch pool.""" id: int type: UnitType available: bool = True # --------------------------------------------------------------------------- # Action / Observation / State # --------------------------------------------------------------------------- class DispatchTriageAction(Action): """ The agent's decision each step: send unit `unit_id` to handle `incident_id`. Repeated until all units are deployed or all incidents are resolved. """ incident_id: int unit_id: int class DispatchTriageObservation(Observation): """ Everything the agent can see after each step. Inherited from Observation base: done: bool reward: Optional[float] """ incidents: List[Incident] units: List[Unit] dispatch_count: int message: str score_so_far: float class DispatchTriageState(State): """ Internal state persisted across steps. Inherited from State base: episode_id: Optional[str] step_count: int """ difficulty: Difficulty = "easy" total_incidents: int = 0 total_units: int = 0 max_possible_score: float = 1.0