Anchor Ordinal & Locate-All

anchor_locate finds a target by spatial relation to an anchor but always returned the single nearest match — there was no way to pick “the 2nd row below the header” or to enumerate every matching row. This adds an ordinal selector and a list-returning anchor_locate_all().

Both build on a shared ranking helper (pure: filter by relation, sort by distance) so the selection logic is unit-testable by injecting candidate boxes. Headless and Qt-free.

Headless API

from je_auto_control import (
    anchor_locate, anchor_locate_all, ocr_locator, image_locator,
)

header = ocr_locator("Name")
row = image_locator("row_handle.png")

anchor_locate(anchor=header, target=row, relation="below")             # nearest
anchor_locate(anchor=header, target=row, relation="below", ordinal=2)  # 2nd row
rows = anchor_locate_all(anchor=header, target=row, relation="below")  # all rows

ordinal is 1-based (ordinal=1 is the nearest, the previous behaviour, so this is backward-compatible); an out-of-range ordinal returns a not-found outcome. anchor_locate_all returns a list of found AnchorOutcome ordered by distance — the building block for table / list-row selection.

Executor commands

AC_anchor_locate gains an ordinal parameter; AC_anchor_locate_all returns {count, matches}. Both are exposed as MCP tools (ac_anchor_locate with ordinal / ac_anchor_locate_all).