Weighted Candidate Scoring

anchor_locator filters by a single spatial relation and sorts by distance, and ab_locator races whole strategies and picks by elapsed time — neither is a weighted multi-signal scorer that ranks ambiguous candidates by combining a role match, a fuzzy name similarity, proximity to an anchor and enabled state into one confidence. That is exactly what self-healing / grounding needs when several boxes could be the target. The name similarity is injectable (defaulting to the project’s fuzzy_ratio), so no new string-distance code is added.

Pure-stdlib over plain element dicts (role / name / x / y / width / height / optional enabled), fully unit-testable. Imports no PySide6.

Headless API

from je_auto_control import score_candidates, best_candidate

ranked = score_candidates(candidates, want_role="button", want_name="Save",
                          anchor=(960, 540))
for c in ranked:
    print(round(c.score, 3), c.element["name"], c.matched_on)

pick = best_candidate(candidates, want_role="button", want_name="Save")
if pick:
    click(*[pick.element["x"], pick.element["y"]])

score_candidates returns a list of ScoredCandidate (element / score / matched_on breakdown), best-first; each active signal contributes 0..1 and the score is their mean. want_role scores 1 on an exact role match, want_name runs name_similarity (default fuzzy_ratio), anchor adds a proximity term, and prefer_enabled rewards enabled elements. best_candidate returns the top one (or None).

Executor commands

AC_score_candidates (candidates / want_role / want_name / anchor{count, scored}) and AC_best_candidate (same inputs → {found, best}). They are exposed as the MCP tools ac_score_candidates / ac_best_candidate and as Script Builder commands under Native UI.