Nexus-Grid / models.py
Abineshsdata's picture
Add manifest.json endpoint, update dashboard and app
74965f9
Raw
History Blame Contribute Delete
10.6 kB
"""
NexusGrid-CyberPhysEnv Data Models.
Typed Pydantic models for the national power grid defense environment.
All fields have explicit range contracts via Field(ge=, le=, description=).
"""
from __future__ import annotations
from enum import Enum
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------
class NodeType(str, Enum):
HYDRO = "hydro"
SOLAR = "solar"
GAS = "gas"
BATTERY = "battery"
LOAD = "load"
class EdgeStatus(str, Enum):
LIVE = "LIVE"
TRIPPED = "TRIPPED"
class ActionType(str, Enum):
DISPATCH_GENERATION = "dispatch_generation"
TOGGLE_CIRCUIT_BREAKER = "toggle_circuit_breaker"
RUN_STATE_ESTIMATION = "run_state_estimation"
QUARANTINE_SCADA_NODE = "quarantine_scada_node"
INJECT_COUNTER_SIGNAL = "inject_counter_signal"
ADVANCE_TICK = "advance_tick"
# ---------------------------------------------------------------------------
# Topology sub-models
# ---------------------------------------------------------------------------
class GridNode(BaseModel):
"""A substation node in the power grid."""
id: str = Field(..., description="Unique node identifier, e.g. NODE_01")
region: str = Field(..., description="Geographic region name")
node_type: NodeType = Field(..., description="Type of substation")
capacity_mw: float = Field(..., ge=0, le=5000, description="Maximum generation/load capacity in MW")
peak_load_mw: float = Field(0.0, ge=0, le=5000, description="Peak demand for load nodes in MW")
critical: bool = Field(False, description="True if this is critical infrastructure (hospital, water)")
phase_angle_deg: float = Field(0.0, ge=-180.0, le=180.0, description="AC phase angle in degrees")
class GridEdge(BaseModel):
"""A transmission line edge in the power grid."""
id: str = Field(..., description="Unique edge identifier, e.g. LINE_01")
source: str = Field(..., description="Source node ID")
target: str = Field(..., description="Target node ID")
capacity_mw: float = Field(..., ge=50, le=5000, description="Line thermal capacity in MW")
current_load_mw: float = Field(0.0, ge=0, le=5000, description="Current power flow on line in MW")
status: EdgeStatus = Field(EdgeStatus.LIVE, description="Line status: LIVE or TRIPPED")
# ---------------------------------------------------------------------------
# Telemetry sub-models
# ---------------------------------------------------------------------------
class NodeTelemetry(BaseModel):
"""Per-node telemetry reading (may be spoofed)."""
node_id: str = Field(..., description="Node this reading belongs to")
voltage_kv: float = Field(345.0, ge=0.0, le=765.0, description="Voltage in kV (nominal 345)")
frequency_hz: float = Field(60.0, ge=58.0, le=62.0, description="Local frequency in Hz (nominal 60)")
generation_mw: float = Field(0.0, ge=0.0, le=5000.0, description="Generation output in MW")
consumption_mw: float = Field(0.0, ge=0.0, le=5000.0, description="Consumption demand in MW")
class WeatherZone(BaseModel):
"""Weather data for a geographic zone."""
zone: str = Field(..., description="Zone identifier")
solar_irradiance: float = Field(0.5, ge=0.0, le=1.0, description="Solar irradiance [0-1]")
wind_speed_ms: float = Field(5.0, ge=0.0, le=30.0, description="Wind speed in m/s")
cloud_cover: float = Field(0.3, ge=0.0, le=1.0, description="Cloud cover fraction [0-1]")
class PacketLog(BaseModel):
"""A simulated SCADA network packet log entry."""
timestamp: float = Field(..., ge=0.0, description="Simulated timestamp")
source_node: str = Field(..., description="Source node ID")
dest_node: str = Field(..., description="Destination node ID")
latency_ms: float = Field(5.0, ge=0.0, le=500.0, description="Packet latency in ms")
anomaly_flag: bool = Field(False, description="True if latency > 50ms for 2+ consecutive packets")
# ---------------------------------------------------------------------------
# State estimation result
# ---------------------------------------------------------------------------
class StateEstimationResult(BaseModel):
"""Result from run_state_estimation action."""
consistent: bool = Field(..., description="True if Kirchhoff's laws hold for the subgraph")
violation_node: Optional[str] = Field(None, description="Node ID where violation was detected")
estimated_true_mw: float = Field(0.0, description="Estimated true power at violation node")
# ---------------------------------------------------------------------------
# Observation — what the agent sees
# ---------------------------------------------------------------------------
class GridObservation(BaseModel):
"""
Complete observation from the NexusGrid environment.
The agent sees topology (never spoofed), telemetry (may be spoofed),
weather, SCADA packet logs, and the true grid frequency.
"""
model_config = {"extra": "allow"}
topology_graph: Dict[str, Any] = Field(
default_factory=dict,
description="Dict with 'nodes' and 'edges' keys — the immutable physical map, never spoofed"
)
telemetry_stream: List[List[Dict[str, Any]]] = Field(
default_factory=list,
description="Per-tick telemetry history (last 10 ticks). Each tick is a list of node readings. May be spoofed."
)
weather_forecast_matrix: List[Dict[str, Any]] = Field(
default_factory=list,
description="24-hour per-zone weather forecast"
)
network_packet_logs: List[Dict[str, Any]] = Field(
default_factory=list,
description="Recent SCADA packet logs with anomaly flags"
)
grid_frequency_hz: float = Field(
60.0, ge=58.0, le=62.0,
description="Instantaneous national grid frequency from truth engine. Cannot be spoofed."
)
tick: int = Field(0, ge=0, description="Current simulation tick")
task_id: int = Field(0, ge=0, le=5, description="Current task ID")
done: bool = Field(False, description="Whether episode has ended")
reward: float = Field(0.0, description="Reward for this step")
last_action_error: Optional[str] = Field(None, description="Error message from last action, if any")
last_state_estimation: Optional[Dict[str, Any]] = Field(
None,
description="Result of most recent run_state_estimation call"
)
weather_summary: str = Field("", description="Natural language weather summary")
metadata: Dict[str, Any] = Field(
default_factory=dict,
description="Auxiliary environment metadata such as reward/rubric breakdowns."
)
# ---------------------------------------------------------------------------
# Action — what the agent does
# ---------------------------------------------------------------------------
class GridAction(BaseModel):
"""
Action for the NexusGrid environment.
The agent selects an action_type and provides relevant parameters.
Unused parameters for a given action_type should be left as None.
"""
action_type: ActionType = Field(
...,
description="Type of action to perform"
)
node_id: Optional[str] = Field(
None,
description="Target node ID (for dispatch_generation, quarantine_scada_node, inject_counter_signal)"
)
edge_id: Optional[str] = Field(
None,
description="Target edge ID (for toggle_circuit_breaker)"
)
mw: Optional[float] = Field(
None, ge=-5000, le=5000,
description="Megawatts to dispatch (for dispatch_generation)"
)
status: Optional[str] = Field(
None,
description="OPEN or CLOSED (for toggle_circuit_breaker)"
)
subgraph: Optional[List[str]] = Field(
None,
description="List of node IDs to check (for run_state_estimation)"
)
hz_offset: Optional[float] = Field(
None, ge=-5.0, le=5.0,
description="Frequency offset for counter-signal injection (for inject_counter_signal)"
)
duration: Optional[int] = Field(
None, ge=1, le=20,
description="Duration in ticks for counter-signal (for inject_counter_signal)"
)
# ---------------------------------------------------------------------------
# Reward — breakdown of reward signals
# ---------------------------------------------------------------------------
class GridReward(BaseModel):
"""Breakdown of reward signals for transparency."""
fault_isolation: float = Field(0.0, description="Reward for isolating transmission faults")
cyber_detection: float = Field(0.0, description="Reward for detecting spoofed sensors")
frequency_stable: float = Field(0.0, description="Reward for keeping frequency in nominal band")
proactive_dispatch: float = Field(0.0, description="Reward for proactive (early) dispatch")
reasoning_order: float = Field(0.0, description="Reward for correct investigation order")
stability_bonus: float = Field(0.0, description="Bonus for tight frequency control (±0.1Hz)")
penalties: float = Field(0.0, description="Sum of all penalties (negative value)")
total: float = Field(0.0, description="Total reward for this tick")
# ---------------------------------------------------------------------------
# State — full internal state for debugging
# ---------------------------------------------------------------------------
class GridState(BaseModel):
"""Full internal environment state for debugging and reproducibility."""
episode_id: str = Field("", description="Unique episode identifier")
episode_seed: int = Field(42, description="Seed used for this episode")
task_id: int = Field(0, ge=0, le=5, description="Current task")
tick: int = Field(0, ge=0, description="Current tick")
grid_frequency_hz: float = Field(60.0, ge=58.0, le=62.0, description="Current grid frequency")
done: bool = Field(False, description="Whether episode is finished")
total_reward: float = Field(0.0, description="Accumulated reward")
active_spoofs: List[str] = Field(default_factory=list, description="Currently spoofed node IDs")
quarantined_nodes: List[str] = Field(default_factory=list, description="Quarantined node IDs")
state_estimation_run: bool = Field(False, description="Whether state estimation has been run this episode")
action_history: List[Dict[str, Any]] = Field(default_factory=list, description="History of all actions taken")