Skip to content
24 changes: 24 additions & 0 deletions README/WHATS_NEW_zh-CN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# 本次更新 — AutoControl

## 本次更新 (2026-06-24) — OCR 行的段落与列表分组

把 OCR 行分组成段落,并检测项目符号 / 编号列表。完整参考:[`docs/source/Zh/doc/new_features/v174_features_doc.rst`](../docs/source/Zh/doc/new_features/v174_features_doc.rst)。

- **`group_paragraphs` / `detect_lists`**(`AC_group_paragraphs`、`AC_detect_lists`):`text_regions` 把字形并成行,但没有功能把那些行分组成段落或检测列表;`ocr/structure` 止于平面行。`group_paragraphs` 在垂直间距超过 `line_gap_factor` × 中位行高处开始新段落;`detect_lists` 识别项目符号(`•`/`-`/`*`)或序号(`1.`/`2)`/`a.`)项目,返回 `{text, marker, indent, box}`。纯标准库,作用于行字典;重用 `table_grid_fill` 的框读取器;不导入 `PySide6`。

## 本次更新 (2026-06-24) — 列感知阅读顺序(XY-Cut)

读多列布局时*完整读完*每一列,而非交错。完整参考:[`docs/source/Zh/doc/new_features/v173_features_doc.rst`](../docs/source/Zh/doc/new_features/v173_features_doc.rst)。

- **`flow_order` / `xy_cut` / `to_blocks`**(`AC_flow_order`、`AC_xy_cut`):`element_parse.reading_order` 是平面上到下排序,会交错列(读作 A1, B1, A2, B2…)。本功能以递归 XY-cut 还原正确顺序——在最宽留白谷切分(垂直 → 列、水平 → 行),故两列页面读作 A1, A2, B1, B2。`flow_order` 返回与 `reading_order` 相同的 `index` 标记契约(列感知的直接升级,且命名不遮蔽它);`xy_cut` 暴露区域树;`to_blocks` 列出叶区块。纯标准库;不导入 `PySide6`。

## 本次更新 (2026-06-24) — Grounding 自我一致性(提案共识)

把多个 grounding 提案融合成单一一致目标,并附 agreement 分数。完整参考:[`docs/source/Zh/doc/new_features/v172_features_doc.rst`](../docs/source/Zh/doc/new_features/v172_features_doc.rst)。

- **`consensus_point` / `consensus_element` / `is_confident`**(`AC_consensus_point`、`AC_consensus_element`):一个目标可同时以多种方式 grounding(set-of-marks / OCR / 模板 / a11y / 模型 N 次采样)而未必一致。`ab_locator`/`element_scoring` 依历史排序*策略*;`snap_to_element` 只贴*单一*坐标——两者都不融合*同时*的提案。本功能将候选点聚类(或对候选元素投票),返回一致的 `point` + `agreement` 比例 + `spread`,而 `is_confident` 标记低一致度目标,让代理改为放大 / 询问而非盲目点击。纯标准库;不导入 `PySide6`。

## 本次更新 (2026-06-24) — 亚像素模板匹配精修

把匹配中心精修到像素的分数位,供拖曳 / 滑块 / 高 DPI 精度。完整参考:[`docs/source/Zh/doc/new_features/v171_features_doc.rst`](../docs/source/Zh/doc/new_features/v171_features_doc.rst)。

- **`match_subpixel` / `refine_peak`**(`AC_match_subpixel`):每个匹配器都从 `cv2.minMaxLoc` 返回*整数*坐标——对拖曳把手、细滑块或高 DPI 显示器,这种舍入是主要的点击落点误差。本功能以抛物线拟合峰值周围的 3×3 分数邻域(x/y 各自独立,标准 NCC 亚像素法),返回带浮点 `cx`/`cy` 与套用的 `offset_x`/`offset_y` 的 `SubPixelMatch`。重用 `visual_match._score_map`;`haystack` 可注入;不导入 `PySide6`。

## 本次更新 (2026-06-24) — 失败 / 无效果动作的修复策略引擎

当动作没效果时选择下一个修复战术——并驱动重试循环。完整参考:[`docs/source/Zh/doc/new_features/v170_features_doc.rst`](../docs/source/Zh/doc/new_features/v170_features_doc.rst)。
Expand Down
24 changes: 24 additions & 0 deletions README/WHATS_NEW_zh-TW.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# 本次更新 — AutoControl

## 本次更新 (2026-06-24) — OCR 行的段落與清單分組

把 OCR 行分組成段落,並偵測項目符號 / 編號清單。完整參考:[`docs/source/Zh/doc/new_features/v174_features_doc.rst`](../docs/source/Zh/doc/new_features/v174_features_doc.rst)。

- **`group_paragraphs` / `detect_lists`**(`AC_group_paragraphs`、`AC_detect_lists`):`text_regions` 把字形併成行,但沒有功能把那些行分組成段落或偵測清單;`ocr/structure` 止於平面列。`group_paragraphs` 在垂直間距超過 `line_gap_factor` × 中位行高處開始新段落;`detect_lists` 辨識項目符號(`•`/`-`/`*`)或序號(`1.`/`2)`/`a.`)項目,回傳 `{text, marker, indent, box}`。純標準函式庫,作用於行字典;重用 `table_grid_fill` 的框讀取器;不匯入 `PySide6`。

## 本次更新 (2026-06-24) — 欄位感知閱讀順序(XY-Cut)

讀多欄版面時*完整讀完*每一欄,而非交錯。完整參考:[`docs/source/Zh/doc/new_features/v173_features_doc.rst`](../docs/source/Zh/doc/new_features/v173_features_doc.rst)。

- **`flow_order` / `xy_cut` / `to_blocks`**(`AC_flow_order`、`AC_xy_cut`):`element_parse.reading_order` 是平面上到下排序,會交錯欄位(讀作 A1, B1, A2, B2…)。本功能以遞迴 XY-cut 還原正確順序——在最寬留白谷切分(垂直 → 欄、水平 → 列),故兩欄頁面讀作 A1, A2, B1, B2。`flow_order` 回傳與 `reading_order` 相同的 `index` 標記契約(欄位感知的直接升級,且命名不遮蔽它);`xy_cut` 暴露區域樹;`to_blocks` 列出葉區塊。純標準函式庫;不匯入 `PySide6`。

## 本次更新 (2026-06-24) — Grounding 自我一致性(提案共識)

把多個 grounding 提案融合成單一一致目標,並附 agreement 分數。完整參考:[`docs/source/Zh/doc/new_features/v172_features_doc.rst`](../docs/source/Zh/doc/new_features/v172_features_doc.rst)。

- **`consensus_point` / `consensus_element` / `is_confident`**(`AC_consensus_point`、`AC_consensus_element`):一個目標可同時以多種方式 grounding(set-of-marks / OCR / 樣板 / a11y / 模型 N 次抽樣)而未必一致。`ab_locator`/`element_scoring` 依歷史排序*策略*;`snap_to_element` 只貼*單一*座標——兩者都不融合*同時*的提案。本功能將候選點聚類(或對候選元素投票),回傳一致的 `point` + `agreement` 比例 + `spread`,而 `is_confident` 標記低一致度目標,讓代理改為放大 / 詢問而非盲目點擊。純標準函式庫;不匯入 `PySide6`。

## 本次更新 (2026-06-24) — 次像素樣板比對精修

把比對中心精修到像素的分數位,供拖曳 / 滑桿 / 高 DPI 精度。完整參考:[`docs/source/Zh/doc/new_features/v171_features_doc.rst`](../docs/source/Zh/doc/new_features/v171_features_doc.rst)。

- **`match_subpixel` / `refine_peak`**(`AC_match_subpixel`):每個比對器都從 `cv2.minMaxLoc` 回傳*整數*座標——對拖曳把手、細滑桿或高 DPI 顯示器,這種捨入是主要的點擊落點誤差。本功能以拋物線擬合峰值周圍的 3×3 分數鄰域(x/y 各自獨立,標準 NCC 次像素法),回傳帶浮點 `cx`/`cy` 與套用的 `offset_x`/`offset_y` 的 `SubPixelMatch`。重用 `visual_match._score_map`;`haystack` 可注入;不匯入 `PySide6`。

## 本次更新 (2026-06-24) — 失敗 / 無效果動作的修復策略引擎

當動作沒效果時選擇下一個修復戰術——並驅動重試迴圈。完整參考:[`docs/source/Zh/doc/new_features/v170_features_doc.rst`](../docs/source/Zh/doc/new_features/v170_features_doc.rst)。
Expand Down
24 changes: 24 additions & 0 deletions WHATS_NEW.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# What's New — AutoControl

## What's new (2026-06-24) — Paragraph & List Grouping of OCR Lines

Group OCR lines into paragraphs and detect bulleted / numbered lists. Full reference: [`docs/source/Eng/doc/new_features/v174_features_doc.rst`](docs/source/Eng/doc/new_features/v174_features_doc.rst).

- **`group_paragraphs` / `detect_lists`** (`AC_group_paragraphs`, `AC_detect_lists`): `text_regions` merges glyphs into lines but nothing grouped those lines into paragraphs or detected lists; `ocr/structure` stops at flat rows. `group_paragraphs` starts a new paragraph wherever the vertical gap exceeds `line_gap_factor` × the median line height; `detect_lists` recognises bullet (`•`/`-`/`*`) or ordinal (`1.`/`2)`/`a.`) items, returning `{text, marker, indent, box}`. Pure-stdlib over line dicts; reuses `table_grid_fill`'s box reader; no `PySide6`.

## What's new (2026-06-24) — Column-Aware Reading Order (XY-Cut)

Read multi-column layouts down each column instead of interleaving them. Full reference: [`docs/source/Eng/doc/new_features/v173_features_doc.rst`](docs/source/Eng/doc/new_features/v173_features_doc.rst).

- **`flow_order` / `xy_cut` / `to_blocks`** (`AC_flow_order`, `AC_xy_cut`): `element_parse.reading_order` is a flat top-to-bottom sort that interleaves columns (reads A1, B1, A2, B2…). This recovers the correct order with recursive XY-cut — split at the widest whitespace valley (vertical → columns, horizontal → rows), so a two-column page reads A1, A2, B1, B2. `flow_order` returns the same `index`-tagged contract as `reading_order` (a drop-in column-aware upgrade, named to not shadow it); `xy_cut` exposes the region tree; `to_blocks` lists the leaf blocks. Pure-stdlib; no `PySide6`.

## What's new (2026-06-24) — Grounding Self-Consistency (Consensus Over Proposals)

Fuse several grounding proposals into one agreed target with an agreement score. Full reference: [`docs/source/Eng/doc/new_features/v172_features_doc.rst`](docs/source/Eng/doc/new_features/v172_features_doc.rst).

- **`consensus_point` / `consensus_element` / `is_confident`** (`AC_consensus_point`, `AC_consensus_element`): a target can be grounded several ways at once (set-of-marks / OCR / template / a11y / N model samples) and they don't always agree. `ab_locator`/`element_scoring` rank *strategies* by history; `snap_to_element` snaps a *single* coordinate — neither fuses *simultaneous* proposals. This clusters candidate points (or votes candidate elements), returns the agreed `point` + an `agreement` fraction + `spread`, and `is_confident` flags low-agreement targets so the agent zooms / asks instead of clicking blind. Pure-stdlib; no `PySide6`.

## What's new (2026-06-24) — Sub-Pixel Template-Match Refinement

Refine a match's centre to a fraction of a pixel for drag / slider / high-DPI precision. Full reference: [`docs/source/Eng/doc/new_features/v171_features_doc.rst`](docs/source/Eng/doc/new_features/v171_features_doc.rst).

- **`match_subpixel` / `refine_peak`** (`AC_match_subpixel`): every matcher returns *integer* coordinates from `cv2.minMaxLoc` — for a drag handle, fine slider or high-DPI display that rounding is the dominant click-placement error. This fits a parabola to the 3×3 score neighbourhood around the peak (independently on x/y, the standard NCC sub-pixel method) and returns a `SubPixelMatch` with float `cx`/`cy` + the applied `offset_x`/`offset_y`. Reuses `visual_match._score_map`; injectable `haystack`; no `PySide6`.

## What's new (2026-06-24) — Repair-Tactic Policy for Failed / No-Effect Actions

Pick the next repair tactic when an action does nothing — and drive the retry loop. Full reference: [`docs/source/Eng/doc/new_features/v170_features_doc.rst`](docs/source/Eng/doc/new_features/v170_features_doc.rst).
Expand Down
40 changes: 40 additions & 0 deletions docs/source/Eng/doc/new_features/v171_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Sub-Pixel Template-Match Refinement
===================================

Every matcher (``match_template`` / ``match_rotated`` / ``match_masked``) returns *integer*
pixel coordinates straight from ``cv2.minMaxLoc``. For a drag handle, a fine slider, a
sub-pixel-rendered target, or a high-DPI display, that integer rounding is the dominant
click-placement error. ``subpixel_match`` refines the peak to a fraction of a pixel by fitting
a parabola to the 3x3 score neighbourhood around the integer maximum — independently on x and y
(the standard NCC sub-pixel method).

It reuses ``visual_match._score_map`` (the full ``matchTemplate`` surface the public matchers
discard); no matching code is duplicated. The ``haystack`` is injectable; the analysis is
unit-testable on synthetic arrays. Imports no ``PySide6``.

Headless API
------------

.. code-block:: python

from je_auto_control import match_subpixel, refine_peak

hit = match_subpixel("slider_thumb.png", min_score=0.8)
if hit:
# cx / cy are floats — pass to a sub-pixel-aware drag / move
move_to(hit.cx, hit.cy)

# the fitting primitive over any score surface
offset_x, offset_y = refine_peak(score_map, (peak_x, peak_y))

``match_subpixel`` returns a ``SubPixelMatch`` (integer ``x`` / ``y`` / ``width`` / ``height`` /
``score`` plus float ``cx`` / ``cy`` and the ``offset_x`` / ``offset_y`` applied), or ``None``
below ``min_score``. ``refine_peak`` returns the ``[-0.5, 0.5]`` quadratic-fit offset of a peak
from its neighbours — usable on any correlation surface.

Executor command
----------------

``AC_match_subpixel`` (``template`` / ``min_score`` / ``region`` / ``method`` →
``{found, match}``) is exposed as the MCP tool ``ac_match_subpixel`` (read-only) and as the
Script Builder command **Match Template (sub-pixel)** under **Image**.
44 changes: 44 additions & 0 deletions docs/source/Eng/doc/new_features/v172_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Grounding Self-Consistency (Consensus Over Proposals)
=====================================================

A target can be grounded several ways at once — set-of-marks, OCR, template match, the a11y
tree, or N samples from a model — and they don't always agree. ``ab_locator`` /
``element_scoring`` rank locator *strategies* by historical reliability but never fuse
*simultaneous* proposals into one consensus point with a dispersion metric, and
``action_grounding.snap_to_element`` snaps a *single* coordinate to the nearest element with no
notion of disagreement. ``grounding_consensus`` clusters the candidate points (or votes
candidate elements), returns the agreed target plus an *agreement* fraction and *spread*, and
flags low-agreement targets so the caller can zoom / ask a human instead of clicking blind.

Pure-stdlib geometry; deterministic and unit-testable with no device. Imports no ``PySide6``.

Headless API
------------

.. code-block:: python

from je_auto_control import consensus_point, consensus_element, is_confident

proposals = [[480, 260], [484, 258], [477, 263], [900, 100]] # one outlier
result = consensus_point(proposals, cluster_radius=24)
if is_confident(result, min_agreement=0.6):
click(*result.point)
else:
print("proposals disagree:", result.agreement, result.spread)

# or vote proposals onto a known element list
element, agreement = consensus_element(proposals, elements)

``consensus_point`` clusters ``[x, y]`` or ``[x, y, weight]`` candidates and returns a
``ConsensusResult`` (``point`` / ``agreement`` — winning cluster weight over total / ``spread``
/ ``n_clusters``), or ``None`` when empty. ``consensus_element`` votes each proposal to its
nearest element and returns ``(element, agreement)``. ``is_confident`` thresholds the agreement.

Executor commands
-----------------

``AC_consensus_point`` (``candidates`` / ``cluster_radius`` → ``{found, result}``) and
``AC_consensus_element`` (``candidates`` / ``elements`` → ``{found, element, agreement}``). They
are exposed as the MCP tools ``ac_consensus_point`` / ``ac_consensus_element`` (read-only) and
as the Script Builder commands **Grounding Consensus Point** / **Grounding Consensus Element**
under **Native UI**.
41 changes: 41 additions & 0 deletions docs/source/Eng/doc/new_features/v173_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Column-Aware Reading Order (XY-Cut)
===================================

``element_parse.reading_order`` is a flat top-to-bottom / left-to-right sort — it interleaves
columns on any multi-column page (the well-documented naive-sort failure: it reads row 1 of
column A, then row 1 of column B, then row 2 of A …). ``reading_flow`` recovers the correct
order with recursive **XY-cut**: it repeatedly splits the boxes at the widest whitespace valley
(a vertical gutter → columns, a horizontal gutter → rows / blocks), so a two-column layout is
read *down* column A fully, then column B.

The public flattener is named ``flow_order`` to sit *beside* — not shadow —
``element_parse.reading_order``; it returns the same ``index``-tagged element contract, so it is
a drop-in column-aware upgrade. Pure-stdlib geometry over plain box dicts (no image, no OCR
engine); reuses ``table_grid_fill``'s box-bounds reader. Imports no ``PySide6``.

Headless API
------------

.. code-block:: python

from je_auto_control import flow_order, xy_cut, to_blocks

# two columns, two rows each: reads A1, A2, B1, B2 — not A1, B1, A2, B2
for element in flow_order(ocr_boxes, min_gap=12):
print(element["index"], element["text"])

tree = xy_cut(ocr_boxes, min_gap=12) # {type, axis, children|boxes}
blocks = to_blocks(tree) # leaf blocks in reading order

``flow_order`` returns the boxes in column-aware reading order, each tagged with an ``index``.
``xy_cut`` returns the recursive region tree (each node is a ``split`` on ``axis`` ``"x"`` /
``"y"`` or a ``leaf`` of boxes). ``to_blocks`` flattens the tree to its leaf blocks in order.
``min_gap`` is the smallest whitespace valley treated as a column / row break.

Executor commands
-----------------

``AC_flow_order`` (``boxes`` / ``min_gap`` → ``{count, elements}``) and ``AC_xy_cut``
(``boxes`` / ``min_gap`` → ``{tree}``). They are exposed as the MCP tools ``ac_flow_order`` /
``ac_xy_cut`` (read-only) and as the Script Builder commands **Reading Order (column-aware)** /
**XY-Cut Region Tree** under **OCR**.
38 changes: 38 additions & 0 deletions docs/source/Eng/doc/new_features/v174_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
Paragraph & List Grouping of OCR Lines
======================================

``text_regions.find_text_lines`` merges glyphs into lines, but nothing groups those *lines*
into paragraphs or detects lists — ``ocr/structure`` stops at flat rows. ``text_blocks`` adds
that: ``group_paragraphs`` splits lines into paragraphs wherever the vertical gap exceeds a
multiple of the median line height (the standard whitespace-grouping heuristic), and
``detect_lists`` recognises bulleted / numbered items by their leading marker and left indent.

Pure-stdlib over plain line dicts (text + bbox); fully unit-testable with no image and no OCR
engine. Reuses ``table_grid_fill``'s box-bounds reader. Imports no ``PySide6``.

Headless API
------------

.. code-block:: python

from je_auto_control import group_paragraphs, detect_lists

for para in group_paragraphs(ocr_lines, line_gap_factor=1.6):
print(para["n_lines"], para["text"])

for item in detect_lists(ocr_lines):
print(item["marker"], item["indent"], item["text"])

``group_paragraphs`` returns paragraph dicts (``left`` / ``top`` / ``right`` / ``bottom`` /
``text`` / ``n_lines``) — a new paragraph begins when the gap from the previous line's bottom
exceeds ``line_gap_factor`` times the median line height. ``detect_lists`` returns the lines
whose text starts with a bullet (``•`` / ``-`` / ``*``) or an ordinal (``1.`` / ``2)`` /
``a.``), each as ``{text, marker, indent, box}`` (``indent`` is the left x, for nesting).

Executor commands
-----------------

``AC_group_paragraphs`` (``lines`` / ``line_gap_factor`` → ``{count, paragraphs}``) and
``AC_detect_lists`` (``lines`` → ``{count, items}``). They are exposed as the MCP tools
``ac_group_paragraphs`` / ``ac_detect_lists`` (read-only) and as the Script Builder commands
**Group Paragraphs** / **Detect Lists** under **OCR**.
4 changes: 4 additions & 0 deletions docs/source/Eng/eng_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ Comprehensive guides for all AutoControl features.
doc/new_features/v168_features_doc
doc/new_features/v169_features_doc
doc/new_features/v170_features_doc
doc/new_features/v171_features_doc
doc/new_features/v172_features_doc
doc/new_features/v173_features_doc
doc/new_features/v174_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
Loading
Loading