71 lines
2.3 KiB
Python
71 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
from math import hypot
|
|
from typing import Sequence
|
|
|
|
from .models import ActionPayload, ActionType, 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 build_payload(
|
|
self,
|
|
descriptor: GridDescriptor,
|
|
action: ActionType = ActionType.CLICK,
|
|
preferred_label: str | None = None,
|
|
text: str | None = None,
|
|
comment: str | None = None,
|
|
) -> ActionPayload:
|
|
target = self.select_cell(descriptor, preferred_label)
|
|
return ActionPayload(
|
|
grid_id=descriptor.grid_id,
|
|
action=action,
|
|
target_cell=target.cell_id if target else None,
|
|
text=text,
|
|
comment=comment,
|
|
)
|
|
|
|
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
|