Skip to content

Commit 3e8aeaa

Browse files
committed
Add weighted candidate scoring (role + name + proximity)
1 parent a637dd3 commit 3e8aeaa

15 files changed

Lines changed: 381 additions & 1 deletion

File tree

README/WHATS_NEW_zh-CN.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-23) — 加权候选评分
4+
5+
以信心分数排序模棱两可的元素候选。完整参考:[`docs/source/Zh/doc/new_features/v156_features_doc.rst`](../docs/source/Zh/doc/new_features/v156_features_doc.rst)
6+
7+
- **`score_candidates` / `best_candidate`**(`AC_score_candidates``AC_best_candidate`):`anchor_locator` 是单一关系 + 距离排序、`ab_locator` 依耗时竞赛整个策略——两者都不以*加权*混合(角色匹配 + 模糊名称相似度 + 锚点邻近 + 启用状态)排序模棱候选。本功能返回最佳优先的 `ScoredCandidate` 并含 `matched_on` 明细;名称相似度可注入(默认 `fuzzy_ratio`,重用——不新增字符串距离代码)。纯标准库,作用于元素字典;在多个框都可能是目标时驱动自我修复 / grounding。可无头测试。
8+
39
## 本次更新 (2026-06-23) — 几何感知的元素差异与稳定 ID
410

511
以重叠跨帧追踪元素,并给予稳定 ID。完整参考:[`docs/source/Zh/doc/new_features/v155_features_doc.rst`](../docs/source/Zh/doc/new_features/v155_features_doc.rst)

README/WHATS_NEW_zh-TW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-23) — 加權候選評分
4+
5+
以信心分數排序模稜兩可的元素候選。完整參考:[`docs/source/Zh/doc/new_features/v156_features_doc.rst`](../docs/source/Zh/doc/new_features/v156_features_doc.rst)
6+
7+
- **`score_candidates` / `best_candidate`**(`AC_score_candidates``AC_best_candidate`):`anchor_locator` 是單一關係 + 距離排序、`ab_locator` 依耗時競賽整個策略——兩者都不以*加權*混合(角色匹配 + 模糊名稱相似度 + 錨點鄰近 + 啟用狀態)排序模稜候選。本功能回傳最佳優先的 `ScoredCandidate` 並含 `matched_on` 明細;名稱相似度可注入(預設 `fuzzy_ratio`,重用——不新增字串距離程式)。純標準函式庫,作用於元素字典;在多個框都可能是目標時驅動自我修復 / grounding。可無頭測試。
8+
39
## 本次更新 (2026-06-23) — 幾何感知的元素差異與穩定 ID
410

511
以重疊跨影格追蹤元素,並給予穩定 ID。完整參考:[`docs/source/Zh/doc/new_features/v155_features_doc.rst`](../docs/source/Zh/doc/new_features/v155_features_doc.rst)

WHATS_NEW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# What's New — AutoControl
22

3+
## What's new (2026-06-23) — Weighted Candidate Scoring
4+
5+
Rank ambiguous element candidates by a confidence score. Full reference: [`docs/source/Eng/doc/new_features/v156_features_doc.rst`](docs/source/Eng/doc/new_features/v156_features_doc.rst).
6+
7+
- **`score_candidates` / `best_candidate`** (`AC_score_candidates`, `AC_best_candidate`): `anchor_locator` is a single relation + distance sort and `ab_locator` races whole strategies by elapsed time — neither ranks ambiguous candidates by a *weighted* mix of role match + fuzzy name similarity + anchor proximity + enabled-state. This returns `ScoredCandidate`s best-first with a `matched_on` breakdown; the name similarity is injectable (default `fuzzy_ratio`, reused — no new string-distance code). Pure-stdlib over element dicts; powers self-heal / grounding when several boxes could be the target. Headless-testable.
8+
39
## What's new (2026-06-23) — Geometry-Aware Element Diff & Stable IDs
410

511
Track elements across frames by overlap, with stable IDs. Full reference: [`docs/source/Eng/doc/new_features/v155_features_doc.rst`](docs/source/Eng/doc/new_features/v155_features_doc.rst).
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
Weighted Candidate Scoring
2+
==========================
3+
4+
``anchor_locator`` filters by a single spatial relation and sorts by distance, and
5+
``ab_locator`` races *whole strategies* and picks by elapsed time — neither is a
6+
*weighted multi-signal scorer* that ranks ambiguous candidates by combining a role
7+
match, a fuzzy name similarity, proximity to an anchor and enabled state into one
8+
confidence. That is exactly what self-healing / grounding needs when several boxes
9+
could be the target. The name similarity is injectable (defaulting to the project's
10+
``fuzzy_ratio``), so no new string-distance code is added.
11+
12+
Pure-stdlib over plain element dicts (``role`` / ``name`` / ``x`` / ``y`` / ``width`` /
13+
``height`` / optional ``enabled``), fully unit-testable. Imports no ``PySide6``.
14+
15+
Headless API
16+
------------
17+
18+
.. code-block:: python
19+
20+
from je_auto_control import score_candidates, best_candidate
21+
22+
ranked = score_candidates(candidates, want_role="button", want_name="Save",
23+
anchor=(960, 540))
24+
for c in ranked:
25+
print(round(c.score, 3), c.element["name"], c.matched_on)
26+
27+
pick = best_candidate(candidates, want_role="button", want_name="Save")
28+
if pick:
29+
click(*[pick.element["x"], pick.element["y"]])
30+
31+
``score_candidates`` returns a list of ``ScoredCandidate`` (``element`` / ``score`` /
32+
``matched_on`` breakdown), best-first; each active signal contributes 0..1 and the
33+
score is their mean. ``want_role`` scores 1 on an exact role match, ``want_name`` runs
34+
``name_similarity`` (default ``fuzzy_ratio``), ``anchor`` adds a proximity term, and
35+
``prefer_enabled`` rewards enabled elements. ``best_candidate`` returns the top one (or
36+
``None``).
37+
38+
Executor commands
39+
-----------------
40+
41+
``AC_score_candidates`` (``candidates`` / ``want_role`` / ``want_name`` / ``anchor`` →
42+
``{count, scored}``) and ``AC_best_candidate`` (same inputs → ``{found, best}``). They
43+
are exposed as the MCP tools ``ac_score_candidates`` / ``ac_best_candidate`` and as
44+
Script Builder commands under **Native UI**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ Comprehensive guides for all AutoControl features.
178178
doc/new_features/v153_features_doc
179179
doc/new_features/v154_features_doc
180180
doc/new_features/v155_features_doc
181+
doc/new_features/v156_features_doc
181182
doc/ocr_backends/ocr_backends_doc
182183
doc/observability/observability_doc
183184
doc/operations_layer/operations_layer_doc
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
加權候選評分
2+
============
3+
4+
``anchor_locator`` 以單一空間關係過濾、依距離排序,``ab_locator`` 競賽*整個策略*並依耗時挑選——兩者都不是把角色
5+
匹配、模糊名稱相似度、對錨點的鄰近度與啟用狀態合成單一信心的*加權多訊號評分器*。當多個框都可能是目標時,
6+
自我修復 / grounding 正需要這個。名稱相似度可注入(預設為專案的 ``fuzzy_ratio``),因此不新增字串距離程式。
7+
8+
純標準函式庫,作用於純元素字典(``role`` / ``name`` / ``x`` / ``y`` / ``width`` / ``height`` / 選用 ``enabled``),
9+
完全可單元測試。不匯入 ``PySide6``。
10+
11+
無頭 API
12+
--------
13+
14+
.. code-block:: python
15+
16+
from je_auto_control import score_candidates, best_candidate
17+
18+
ranked = score_candidates(candidates, want_role="button", want_name="Save",
19+
anchor=(960, 540))
20+
for c in ranked:
21+
print(round(c.score, 3), c.element["name"], c.matched_on)
22+
23+
pick = best_candidate(candidates, want_role="button", want_name="Save")
24+
if pick:
25+
click(*[pick.element["x"], pick.element["y"]])
26+
27+
``score_candidates`` 回傳 ``ScoredCandidate`` 清單(``element`` / ``score`` / ``matched_on`` 明細),最佳優先;每個
28+
啟用的訊號貢獻 0..1,分數為其平均。``want_role`` 在角色精確匹配時得 1、``want_name`` 執行 ``name_similarity``
29+
(預設 ``fuzzy_ratio``)、``anchor`` 加入鄰近項、``prefer_enabled`` 獎勵啟用元素。``best_candidate`` 回傳最佳者
30+
(或 ``None``)。
31+
32+
執行器命令
33+
----------
34+
35+
``AC_score_candidates``(``candidates`` / ``want_role`` / ``want_name`` / ``anchor`` → ``{count, scored}``)與
36+
``AC_best_candidate``(相同輸入 → ``{found, best}``)。它們以 MCP 工具 ``ac_score_candidates`` / ``ac_best_candidate``
37+
以及 Script Builder 中 **Native UI** 分類下的命令提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ AutoControl 所有功能的完整使用指南。
178178
doc/new_features/v153_features_doc
179179
doc/new_features/v154_features_doc
180180
doc/new_features/v155_features_doc
181+
doc/new_features/v156_features_doc
181182
doc/ocr_backends/ocr_backends_doc
182183
doc/observability/observability_doc
183184
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,10 @@
381381
from je_auto_control.utils.element_diff import (
382382
assign_stable_ids, match_elements,
383383
)
384+
# Weighted candidate scoring (role + name similarity + proximity + enabled)
385+
from je_auto_control.utils.element_scoring import (
386+
ScoredCandidate, best_candidate, score_candidates,
387+
)
384388
# CI workflow annotations (GitHub Actions)
385389
from je_auto_control.utils.ci_annotations import (
386390
emit_annotations, format_annotation,
@@ -1271,6 +1275,9 @@ def start_autocontrol_gui(*args, **kwargs):
12711275
"replay_trace",
12721276
"match_elements",
12731277
"assign_stable_ids",
1278+
"score_candidates",
1279+
"best_candidate",
1280+
"ScoredCandidate",
12741281
"emit_annotations", "format_annotation",
12751282
"ClipboardHistory", "default_clipboard_history",
12761283
"analyze_heal_log", "heal_stats", "scan_secrets",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2987,6 +2987,30 @@ def _add_set_of_marks_specs(specs: List[CommandSpec]) -> None:
29872987
),
29882988
description="Tag elements with IDs carried across frames by overlap.",
29892989
))
2990+
specs.append(CommandSpec(
2991+
"AC_score_candidates", "Native UI", "Score Candidates",
2992+
fields=(
2993+
FieldSpec("candidates", FieldType.STRING,
2994+
placeholder='[{"role":"button","name":"OK","x":..,"y":..}]'),
2995+
FieldSpec("want_role", FieldType.STRING, optional=True),
2996+
FieldSpec("want_name", FieldType.STRING, optional=True),
2997+
FieldSpec("anchor", FieldType.STRING, optional=True,
2998+
placeholder="[x, y]"),
2999+
),
3000+
description="Rank candidate elements by role / name / proximity confidence.",
3001+
))
3002+
specs.append(CommandSpec(
3003+
"AC_best_candidate", "Native UI", "Best Candidate",
3004+
fields=(
3005+
FieldSpec("candidates", FieldType.STRING,
3006+
placeholder='[{"role":"button","name":"OK","x":..,"y":..}]'),
3007+
FieldSpec("want_role", FieldType.STRING, optional=True),
3008+
FieldSpec("want_name", FieldType.STRING, optional=True),
3009+
FieldSpec("anchor", FieldType.STRING, optional=True,
3010+
placeholder="[x, y]"),
3011+
),
3012+
description="The single highest-scoring candidate element.",
3013+
))
29903014
specs.append(CommandSpec(
29913015
"AC_mark_screen", "Native UI", "Set-of-Marks: Number Elements",
29923016
fields=(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Weighted candidate scoring (role + name similarity + proximity + enabled)."""
2+
from je_auto_control.utils.element_scoring.element_scoring import (
3+
ScoredCandidate, best_candidate, score_candidates,
4+
)
5+
6+
__all__ = ["ScoredCandidate", "best_candidate", "score_candidates"]

0 commit comments

Comments
 (0)