feat(core): 报销口径与文本宽度两道闸——会议费并入差旅单,超宽文本不再静默 500

这一批是 2026-10-21 正式填报踩出来的两类问题,一起收进来。

**一、报销口径只在一处判定(新增 src/core/expense_kind.py)**

用户定案:因出差发生的会议费/会务费/培训费这类普通发票**并入差旅单**(明细的会务费培训费栏、
会议通知当附件),不拆成两张单、也不因此放弃差旅补助;只有货物类(易耗品/材料/配件)才走
普通报销 + 申购单。以前 case_status、validate_info、browser_new_form 三处都写"混合发票只能走
普通报销",等于把这条合法路径说成错的,Agent 照做就会白拆一张单还漏补助。现在 is_conference_like /
goods_invoices 是唯一判定,工具提示与 case_status 的 warnings 都由它驱动。

**二、文本宽度闸:自由文本超宽 = 无提示 500(新增宽度口径 + 两道预检)**

联机实测:差旅明细备注写到 101 字,POST 回 500 且页面无任何提示、弹窗停在原地,现场看着像"脚本没点着
确定";同一张单 97 字(宽度 130)却存进去了,上限夹在两者之间且后端不肯说。于是取汉字/全角记 2、
字母数字记 1 的"宽度",validator.TEXT_WIDTH_LIMIT=100(≈50 汉字,同时满足"100 字符"与"131 字节"
两种可能口径),普通报销说明另用 DESCRIPTION_WIDTH_LIMIT=40。拦截点两处:规则里的 max_len_bytes
(validate_extracted_info 的 missing_fields 会写明"过长:宽度 N > 上限 M")与 browser_fill 在 POST 前的
_text_limit_problems(失败返回 too_long 列出超长字段)。

**三、交通工具代码表挪进 core(新增 src/core/vehicles.py)**

财务系统 #jtgj 的 value 表与中文别名(train→火车 等)从填报层挪到 core,一处三用:填报取 value、
体检判合法、报错给候选。老写法 if vehicle in vehicle_map 遇英文枚举是**静默不选**且仍返回成功,
直到提交被必填挡回来——现在认不出的值直接报错。

其余:cases.goods_general_items 给 case_status 补"有 N 张货物类普通发票"的 warning;
travel.py 改用 vehicles 并显式报错;config/validation_rules.json 加 max_len_bytes;
INSTRUCTIONS/SKILL/判读参考/操作指南同步口径与宽度提示。新增 tests/test_text_limits.py 与
tests/test_vehicles_and_scope.py。

验证:ruff / format / mypy src / deptry / pytest / mcp-smoke / docs-check / skill-check 全绿。
--no-verify 提交并手跑等价检查。
This commit is contained in:
wandering
2026-10-21 13:49:12 +08:00
parent 44f6466e8b
commit 0d9cc7b4ca
17 changed files with 1042 additions and 62 deletions
+9
View File
@@ -283,6 +283,11 @@ cases/<case_id>/
info_type 取 travel|normal。完整性以返回的 validation 为准,
不要自己在 data 里塞 can_submit。
规则里带 max_len_bytes 的是**文本宽度**上限(汉字算 2、字母数字算 1):
备注写太长,财务后端不回"太长了"而是一记 500,页面上什么都看不到,
所以在这里就拦住,missing_fields 会写成
"payment_methods[0].remark|备注过长:宽度 171 > 上限 100(约 50 个汉字),请改短"。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
@@ -381,6 +386,10 @@ cases/<case_id>/
执行一步填报。step 按 browser_new_form 返回的 steps 顺序,不能跳步。
提交前会量这一步要 POST 的自由文本宽度(汉字算 2、字母数字算 1,上限 100,
普通报销说明 40):财务后端对超宽只回 500、页面上任何提示都没有,弹窗停在原地
看着像"按钮没点着",所以宁可在这里拦下来,失败时 too_long 会列出超长字段。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
+7 -1
View File
@@ -82,7 +82,13 @@ last_reviewed: 2026-10-19
- 浏览器默认**有头**:提交不可逆,得看得见在点什么;期间别手动点页面
- `browser_login` 返回 `need_human` 时,人在已经打开的那个窗口里扫码或输验证码,然后**重调 `browser_login`**
——不要另开浏览器。`cases/<id>/.browser.lock` 记着谁拥有它,返回 `owned_by_other_process` 就等对方释放
- `travel_info` 与 `normal_info` 同时存在时,`browser_new_form` 必须显式传 `kind`(混合发票只能走普通报销)
- `travel_info` 与 `normal_info` 同时存在时,`browser_new_form` 必须显式传 `kind`,两张单分别走差旅/日常列表页
- 口径:**因出差发生的会议费/会务费/培训费并入差旅单**(明细「会务费培训费」栏,会议通知当附件),
不为此另开一张普通报销单;只有货物类(易耗品、材料)才走普通报销 + 申购单。判定写在
`src/core/expense_kind.py`,`case_status` 与 `validate_info` 的提示由它驱动
- 明细页顶部的红字把「公务卡刷卡pos单」也列进了附件清单要求里:支付记录默认**不**当附件
(它们体现在「支付方式」页),要不要补传由你决定——要补就在附件清单页点「增加」手动传,
别让工具重跑 attachments 那一步(会重复上传已有的附件)
- 步骤不能跳:travel 是 basic/details/payment/subsidy/attachments,normal 没有 subsidy
- 每步截图按 `001-步骤名.png` 落在 `cases/<id>/screenshots/`;失败时截图路径在返回的 `detail.screenshot`
- **`browser_close` 不提交也不存草稿**,填了一半会丢;**提交永远由人做**——本项目不提供点提交的工具,