autotrain/docs/screen-adaptation.md
xinxin6623 7a01ed4db9 feat: add invoice batch wrap-up (name extraction + roster annotation) and web ui automation improvements
- add scripts/annotate_roster_invoices.py: match passenger names to invoice PDFs, write status + relative encoded hyperlinks into roster xlsx
- add scripts/add_invoice_passenger_name.py, scripts/calibrate.py
- add .opencode/skills/invoice-wrapup skill
- improve invoice_tool.py / web_ui.py / vision_fallback.py automation
- add device/screen/windows adaptation docs
- ignore out/ (delivered invoice PDFs contain sensitive data)
2026-07-10 00:11:45 +08:00

252 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 屏幕适配 · 识图点与坐标依赖清单
> 目的:梳理 H5 开票流程中所有"关键识图点"及其输出,标明哪些依赖具体屏幕分辨率,为适配其他手机做准备。
>
**当前手机分辨率1260 x 2800**PNG 头实测,`_screenshot_size` 读取)
## 一、识图点全链路(按处理顺序)
每个乘客的完整处理流程经过以下识图点。标注 **[屏幕依赖]** 的点换手机必须改。
### 阶段 0 · 进入列表页(仅 precheck 不在列表时触发)
| # | 识图点 | 输入 | 输出给后续环节 | 屏幕依赖 |
|---|---|---|---|---|
| 0a | `_vision_find_button("扫码开票")` | 截屏 | 像素坐标 (x,y) or None → tap 进扫码 | 否0-1000 归一化) |
| 0b | `_vision_find_button("订单")` | 截屏 | 像素坐标 → tap | 否 |
| 0c | `_vision_find_button("电子发票")` | 截屏 | 像素坐标 → tap | 否 |
| 0d | `_vision_find_button("扫码开票")` | 截屏 | 像素坐标 → tap | 否 |
| 0e | XML 找"相册"按钮 | uiautomator dump | bounds → click | 否(系统返回) |
| 0f | `_vision_find_qr_thumbnail()` | 相册截屏 | QR 缩略图坐标 → tap 选图 | 否0-1000 归一化) |
### 阶段 1 · 列表页识行(每轮截屏)
| # | 识图点 | 输入 | 输出给后续环节 | 屏幕依赖 |
|---|---|---|---|---|
| 1 | `vision_list_rows()` | 截屏 | `rows[{masked_name, id_first, id_last, button_bbox:[x1,y1,x2,y2], tap_point:[x,y]}]`, `scroll_position`, `page_ok` → 后续用 tap_point 点击行 | 否0-1000 归一化后 denormalize |
**防熄屏 tap [屏幕依赖]**:每轮开头 `tap "630" "1400"``invoice_tool.py:2013`)— 硬编码像素,换屏幕要改。
### 阶段 2 · 点击乘客行 → 子流程
用阶段 1 的 `tap_point` tap进入核验页。
### 阶段 3 · 等待页面状态 `wait_for_page_transition`
| # | 识图点 | 输入 | 输出给后续环节 | 屏幕依赖 |
|---|---|---|---|---|
| 3a | `xml_page_kind(nodes)` | uiautomator dump | page kind: `verify`/`invoice_info`/`invoice_confirm`/`no_ticket`/`success`/`other` → 状态机分发 | 否 |
| 3b | `vision_detect_page()`(仅 `screenshot_wait=True` | 截屏 | `{page, summary}` → 补充页面判断 | 否 |
### 阶段 4 · 核验页 - 输入身份证后 8 位
| # | 识图点 | 输入 | 输出给后续环节 | 屏幕依赖 |
|---|---|---|---|---|
| 4 | XML 找 EditText 输入框 | uiautomator dump | bounds → click 输入框 → 输入 id8 | 否 |
### 阶段 5 · 点核验按钮
| # | 识图点 | 输入 | 输出给后续环节 | 屏幕依赖 |
|---|---|---|---|---|
| 5a | XML 找"核验"按钮 | uiautomator dump | bounds → click | 否 |
| 5b | `_vision_find_button("核验")`XML 找不到时 fallback | 截屏 | 像素坐标 → tap | 否 |
### 阶段 6 · 等待 after_verifynetwork_sensitive2s + 12s
输出 page kind。超时返回 `other` → 进阶段 7。
### 阶段 7 · [after_verify 超时 other] 发票管理页纠偏
| # | 识图点 | 输入 | 输出给后续环节 | 屏幕依赖 |
|---|---|---|---|---|
| 7a | XML 找"查看"按钮 | uiautomator dump | 无(找不到) | 否 |
| 7b | `_vision_find_button("查看")` | 截屏 | found:false → 触发探测 | 否 |
| 7c | `vision_detect_page()` | 截屏 | `v_page=invoice_info`, `summary` → 判定为误判 | 否 |
| 7d | `_vision_find_button("开具")` | 截屏 | 像素坐标 (1071,1047) → tap 开具 | 否 |
### 阶段 8 · 发票信息页 - 点提交
| # | 识图点 | 输入 | 输出给后续环节 | 屏幕依赖 |
|---|---|---|---|---|
| 8a | XML 找"提交"按钮 | uiautomator dump | bounds `[31,2418][1228,2576]` → click | 否 |
| 8b | `_vision_find_button("提交")`fallback | 截屏 | 像素坐标 → tap | 否 |
### 阶段 9 · 等待 after_submit_invoice_info → page=invoice_confirm
### 阶段 10 · 发票确认页 - 点确认
| # | 识图点 | 输入 | 输出给后续环节 | 屏幕依赖 |
|---|---|---|---|---|
| 10a | XML 找"确认"按钮 | uiautomator dump | 无H5 页面常无 text | 否 |
| 10b | `_vision_find_button("确认")` | 截屏 | 像素坐标 (850,2153) → tap | 否 |
### 阶段 11 · 等待 after_invoice_confirmnetwork_sensitive→ 经过 loading → page=success
### 阶段 12 · 成功页 - 下载发票
| # | 识图点 | 输入 | 输出给后续环节 | 屏幕依赖 |
|---|---|---|---|---|
| 12a | XML 找"发票预览/下载" | uiautomator dump | bounds `[31,1942][1228,2096]` → click | 否 |
| 12b | XML 找"下载" | uiautomator dump | bounds `[52,1372][1207,1526]` → click → `download_pulled` | 否 |
### 阶段 13 · 成功页 - 找继续开票按钮回列表
| # | 识图点 | 输入 | 输出给后续环节 | 屏幕依赖 |
|---|---|---|---|---|
| 13a | XML 找"继续开票" | uiautomator dump | 无 | 否 |
| 13b | `_vision_find_button("继续开票")` | 截屏 | found:false → sub_step_error | 否 |
| 13c | 兜底XML 找"确定" | uiautomator dump | bounds `[192,1591][1067,1724]` → click | 否 |
| 13d | 兜底XML 找"继续开票" | uiautomator dump | bounds `[28,2128][1232,2289]` → click | 否 |
### 阶段 14 · 回列表
`keyevent 4` (back) × N -> 列表页。back 后 H5 页 scroll 重置到顶部,每轮重新截屏。
---
## 二、滑动设计详解([屏幕依赖] · 换屏必改)
> 滑动是**最大的屏幕依赖点**:起止 y 坐标 + 幅度 + duration 都绑定 1260×2800换屏不改会导致翻页异常跳过行 / 重复行 / 超出屏幕)。
### 2.1 滑动参数全量H5 主线 `android-h5-page-run`
| 参数 | 当前默认值 | 占屏比例 (2800 高) | 用途 | 代码位置 |
|---|---|---|---|---|
| `--h5-scroll-x` | `630` | 50%(中线) | 滑动 x 坐标(起止相同,垂直滑动) | `:3182` |
| `--h5-scroll-start-y` | `2400` | **85.7%**(屏幕下方) | 向下滚动时的起点 / 回顶时的终点 | `:3183` |
| `--h5-scroll-end-y` | `600` | **21.4%**(屏幕上方) | 向下滚动时的终点 / 回顶时的起点 | `:3184` |
| `--h5-scroll-duration-ms` | `800` | - | 单次滑动耗时ms越长越慢越可控 | `:3185` |
| `--h5-scroll-after` | `2.0` | - | 滑动后等待页面稳定(秒) | `:3186` |
| `--h5-scroll-top-burst` | `5` | - | 回顶时先连续下拉次数(不等识图) | `:3179` |
| `--h5-scroll-top-max` | `12` | - | 回顶时带签名确认的最大下拉次数 | `:3180` |
### 2.2 滑动方向与幅度
**向下滚动**`_h5_scroll_down``:2306`)——看更多下方内容:
```
swipe (x, start_y=2400) -> (x, end_y=600) # 手指上滑
幅度 = 2400 - 600 = 1800px = 屏高的 64.3%
```
**回顶下拉**`h5_scroll_to_top``:2233`)——回到顶部:
```
swipe (x, end_y=600) -> (x, start_y=2400) # 手指下滑
幅度 = 同 1800px方向相反
先连续 5 次burst再带 vision_list_rows 签名确认到顶
```
### 2.3 幅度为什么是屏幕相关的
| 屏幕 | 屏高 | 幅度 1800px 占比 | 问题 |
|---|---|---|---|
| 当前 1260×2800 | 2800 | 64.3% | 正常 |
| 小屏 1080×2400 | 2400 | 75.0% | **滑太多**可能跳过行start_y=2400 已贴底) |
| 大屏 1440×3200 | 3200 | 56.3% | **滑太少**,翻页慢,同屏签名重复判定到底 |
| 超大 1080×3120 | 3120 | 57.7% | 同上 |
**根因**`start_y` / `end_y` 是绝对像素,不随屏高缩放。换屏后:
- `start_y=2400` 在 2400 高的屏幕上 = 最底部swipe 起点贴边可能无效
- 幅度 1800px 在小屏占比过大 -> 一次滑过太多行 -> 跳过乘客
- 幅度 1800px 在大屏占比过小 -> 每次只滑一点 -> 效率低 + 签名去重误判
### 2.4 duration 也受影响
`duration_ms=800` 对 1800px 幅度 = 2.25 px/ms。换屏后若幅度变大比如 2400px同样 800ms 会更快 -> Android 可能触发 fling惯性滚动不可控。应保持**速度恒定**`duration = 幅度 / 目标速度`。
### 2.5 原生列表滑动(`android-vision-page-run`,非主线但保留)
| 参数 | 当前默认 | 占屏比例 | 代码位置 |
|---|---|---|---|
| `--swipe-x` | `630` | 50% | `:3146` |
| `--swipe-start-y` | `2300` | 82.1% | `:3147` |
| `--swipe-end-y` | `200` | 7.1% | `:3148` |
| `--swipe-duration-ms` | `700` | - | `:3149` |
| `--after-swipe` | `1.5` | - | `:3150` |
幅度 = 2300-200 = 2100px = 75%,比 H5 更大。同样的问题。
---
## 三、[屏幕依赖] 硬编码坐标清单(换手机必须改)
> 这些是**像素字面量**,绑定 1260×2800 分辨率。换屏幕不改就会 tap/swipe 到错误位置。
| 位置 | 当前值 | 用途 | 代码位置 |
|---|---|---|---|
| 防熄屏 tap | `(630, 1400)` | 每轮开头轻点唤醒屏幕 | `invoice_tool.py:2013` |
| 截图尺寸 fallback | `1260 × 2800` | PNG 头读取失败时的兜底尺寸(极少触发) | `invoice_tool.py:1280` |
| H5 滑动 x / start_y / end_y | `630 / 2400 / 600` | H5 列表页滑动(见第二节详解) | `--h5-scroll-*` 默认 `:3182-3184` |
| 原生滑动 x / start_y / end_y | `630 / 2300 / 200` | 原生列表页滑动 | `--swipe-*` 默认 `:3146-3148` |
**滑动幅度是最大风险点**:换屏幕后 start_y 可能超出屏高,或幅度比例不对导致跳行/重复/翻页异常。
---
## 四、[屏幕无关] 自动适配的识图点
以下点使用 **0-1000 归一化坐标系**Qwen 返回归一化坐标后,用**实际截图尺寸** denormalize 到像素,自动适配不同分辨率:
- `_vision_find_button(text)` → 截图宽高 × (Qwen 返回的 0-1000 值 / 1000)
- `vision_list_rows()` → 同上button_bbox 和 tap_point 都已 denormalize
- `_vision_find_qr_thumbnail()` → 同上
- XML bounds → 来自 `uiautomator dump`,系统返回的本身就是当前屏幕正确坐标
- `vision_detect_page()` → 只返回页面类型,无坐标
**结论**:视觉识图点本身是屏幕无关的。问题集中在**硬编码像素坐标**(防熄屏 tap + 滑动参数)。
---
## 五、适配方案建议
### 5.1 启动时读屏幕分辨率(一次性)
```python
# adb shell wm size -> "Physical size: 1260x2800"
SCREEN_W, SCREEN_H = read_screen_size() # 缓存,全流程复用
```
### 5.2 防熄屏 tap 改为比例
`(630, 1400)` -> `(SCREEN_W // 2, SCREEN_H // 2)`
### 5.3 滑动坐标 + 幅度改为比例(核心)
当前值反推比例(以 2800 高为基准):
| 参数 | 当前像素 | 比例 | 动态计算 |
|---|---|---|---|
| `scroll_x` | 630 | W×50% | `SCREEN_W // 2` |
| `start_y` | 2400 | H×85.7% | `int(SCREEN_H * 0.857)` |
| `end_y` | 600 | H×21.4% | `int(SCREEN_H * 0.214)` |
| **幅度** | 1800 | H×64.3% | `start_y - end_y`(自动随比例缩放) |
换屏后幅度自动适配:
- 2400 高 -> 幅度 = 2400×(0.857-0.214) = 1543px仍是 64.3%,行为一致)
- 3200 高 -> 幅度 = 3200×0.643 = 2058px同比例
### 5.4 duration 按幅度等比缩放(保持滑动速度恒定)
当前:幅度 1800px / duration 800ms = **2.25 px/ms**
换屏后若仍用 800ms 但幅度变了,速度会变 -> 可能触发 fling。应保持速度恒定
```python
SWIPE_SPEED = 2.25 # px/ms, 当前调好的值
duration_ms = max(300, int(幅度 / SWIPE_SPEED)) # 下限 300ms 防过快
```
- 2400 高 -> 幅度 1543 -> duration = 686ms
- 3200 高 -> 幅度 2058 -> duration = 915ms
### 5.5 截图尺寸 fallback 改用动态值
`return 1260, 2800` -> `return SCREEN_W, SCREEN_H`(启动时读的值)
### 5.6 保留 argparse override
默认值改用动态计算(比例 × 屏幕尺寸),但 `--h5-scroll-*` / `--swipe-*` 参数仍保留,特殊机型可手动覆盖动态值。
### 5.7 原生列表滑动同理
`--swipe-*` 参数start_y=2300→82.1%, end_y=200→7.1%)也按同样方式改比例 + duration 缩放。