"""State models for search and planning results.""" from dataclasses import dataclass, field from typing import List, Optional, Tuple from .grid import Grid from .entities import Store, Destination, Tunnel @dataclass class SearchState: """Represents the complete state for a delivery search problem.""" grid: Grid stores: List[Store] destinations: List[Destination] tunnels: List[Tunnel] def get_tunnel_at(self, pos: Tuple[int, int]) -> Optional[Tunnel]: """Get tunnel with entrance at given position.""" for tunnel in self.tunnels: if tunnel.has_entrance_at(pos): return tunnel return None def to_dict(self) -> dict: return { "grid": self.grid.to_dict(), "stores": [s.to_dict() for s in self.stores], "destinations": [d.to_dict() for d in self.destinations], "tunnels": [t.to_dict() for t in self.tunnels], } @dataclass class PathResult: """Result of finding a path from start to goal.""" plan: str # Comma-separated actions: "up,down,left,right,tunnel" cost: float # Total traffic cost nodes_expanded: int # Number of nodes expanded during search path: List[Tuple[int, int]] = field( default_factory=list ) # Actual positions in path def to_string(self) -> str: """Format as required: plan;cost;nodesExpanded""" return f"{self.plan};{self.cost};{self.nodes_expanded}" def to_dict(self) -> dict: return { "plan": self.plan, "cost": self.cost, "nodes_expanded": self.nodes_expanded, "path": [{"x": p[0], "y": p[1]} for p in self.path], } @dataclass class DeliveryAssignment: """Assignment of a destination to a store/truck.""" store_id: int destination_id: int path_result: PathResult def to_dict(self) -> dict: return { "store_id": self.store_id, "destination_id": self.destination_id, "path": self.path_result.to_dict(), } @dataclass class PlanResult: """Result of the complete delivery planning.""" assignments: List[DeliveryAssignment] total_cost: float total_nodes_expanded: int def to_string(self) -> str: """Format output as specified.""" parts = [] for assignment in self.assignments: parts.append( f"({assignment.store_id},{assignment.destination_id}):{assignment.path_result.to_string()}" ) return ";".join(parts) def to_dict(self) -> dict: return { "assignments": [a.to_dict() for a in self.assignments], "total_cost": self.total_cost, "total_nodes_expanded": self.total_nodes_expanded, } @dataclass class SearchStep: """Represents a single step in the search process for visualization.""" step_number: int current_node: Tuple[int, int] action: Optional[str] frontier: List[Tuple[int, int]] explored: List[Tuple[int, int]] current_path: List[Tuple[int, int]] path_cost: float def to_dict(self) -> dict: return { "stepNumber": self.step_number, "currentNode": {"x": self.current_node[0], "y": self.current_node[1]}, "action": self.action, "frontier": [{"x": p[0], "y": p[1]} for p in self.frontier], "explored": [{"x": p[0], "y": p[1]} for p in self.explored], "currentPath": [{"x": p[0], "y": p[1]} for p in self.current_path], "pathCost": self.path_cost, } @dataclass class SearchMetrics: """Performance metrics for a search execution.""" runtime_ms: float memory_kb: float cpu_percent: float nodes_expanded: int path_cost: float path_length: int def to_dict(self) -> dict: return { "runtime_ms": self.runtime_ms, "memory_kb": self.memory_kb, "cpu_percent": self.cpu_percent, "nodes_expanded": self.nodes_expanded, "path_cost": self.path_cost, "path_length": self.path_length, }