from __future__ import annotations from math import hypot from typing import Sequence from .models import GridCellModel, GridDescriptor class GridPlanner: """Helper that picks a grid cell using simple heuristics.""" def select_cell( self, descriptor: GridDescriptor, preferred_label: str | None = None ) -> GridCellModel | None: if not descriptor.cells: return None if preferred_label: match = self._match_label(descriptor.cells, preferred_label) if match: return match center_point = self._grid_center(descriptor) return min(descriptor.cells, key=lambda cell: self._distance(self._cell_center(cell), center_point)) def describe(self, descriptor: GridDescriptor) -> str: cell_count = len(descriptor.cells) return ( f"Grid {descriptor.grid_id} is {descriptor.rows}x{descriptor.columns} with {cell_count} cells." ) def _grid_center(self, descriptor: GridDescriptor) -> tuple[float, float]: width = descriptor.metadata.get("width", 0) height = descriptor.metadata.get("height", 0) return (width / 2, height / 2) def _cell_center(self, cell: GridCellModel) -> tuple[float, float]: left, top, right, bottom = cell.bounds return ((left + right) / 2, (top + bottom) / 2) def _distance( self, first: tuple[float, float], second: tuple[float, float] ) -> float: return hypot(first[0] - second[0], first[1] - second[1]) def _match_label( self, cells: Sequence[GridCellModel], label: str ) -> GridCellModel | None: lowered = label.lower() for cell in cells: if cell.label and lowered in cell.label.lower(): return cell return None