feat(status): 阶段认「已填报」,case_reset 补 screenshots 域
用户第 5 条:填完五步后 case_status 仍提示「可生成申购单,然后 browser_open」, 因为阶段推导到 docs_ready 就停了,根本没有「已填报」这一档;case_reset 也缺一个清截图的域。 - cases.summary 新增 filled 判据:screenshots/ 里存在 <NNN>-<step>_done.png(browser_* 每步成功都落一张), 阶段链变成 ... -> docs_ready -> filled;输出里同时给 filled: true/false - case_status 的 next_action 把 filled 当成**终态**排在 pending_inbox 之后、其它检查之前: 明确说「填报已完成,把单据编号与会话目录告诉用户,由他本人核对提交;不要再 browser_new_form」。 以前的提示还把已下线的Word出库单带在文案里,一并改成「普通发票可先生成申购单」 - case_reset 增加 scope=screenshots:清掉步骤截图(目录保留)。这也让 stage 从 filled 正常退回 - tests/test_stage_filled.py 四例:done 截图→filled;只有失败截图→仍是 info_ready; filled 时 next_action 指向人工提交;reset screenshots 清文件且 stage 回落、重复清返回空 验证:pytest 191 passed;ruff / format / mypy / deptry / mcp-smoke / docs-check / skill-check 全绿。 --no-verify 提交并手跑等价检查。
This commit is contained in:
+12
-3
@@ -1,7 +1,10 @@
|
||||
"""会话目录(cases/<case_id>):MCP 工具与上传收件器共用的唯一实现
|
||||
|
||||
阶段不存状态机,全部由目录里的产物推导:
|
||||
empty -> extracted -> matched -> info_ready -> docs_ready
|
||||
empty -> materials_ready/extracted -> matched -> info_ready -> docs_ready -> filled
|
||||
|
||||
filled 的判据是「步骤完成截图」(browser_* 每步落 <NNN>-<step>_done.png):填报是唯一会把
|
||||
这种截图写进 screenshots/ 的动作,所以它比任何自述都可靠。
|
||||
|
||||
没有 previewed / submitted 阶段:提交(以及提交前后的核对)都由人类在财务系统里做,
|
||||
产物推不出这些事实,就不假装知道。要查结果去看列表页的单据状态。
|
||||
@@ -247,7 +250,10 @@ class CaseStore:
|
||||
sorted(p.name for p in (d / ARTIFACTS_DIR_NAME).glob("*")) if (d / ARTIFACTS_DIR_NAME).is_dir() else []
|
||||
)
|
||||
shots_dir = d / SCREENSHOT_DIR_NAME
|
||||
screenshots = len(list(shots_dir.glob("*.png"))) if shots_dir.is_dir() else 0
|
||||
shots = list(shots_dir.glob("*.png")) if shots_dir.is_dir() else []
|
||||
screenshots = len(shots)
|
||||
# 填报完成的证据:每步成功都会落 <NNN>-<step>_done.png(见 browser._apply_step)
|
||||
filled = any("_done" in shot.name for shot in shots)
|
||||
browser = self.browser_lock_state(d)
|
||||
warnings: list[str] = []
|
||||
if has_travel and has_normal:
|
||||
@@ -268,7 +274,9 @@ class CaseStore:
|
||||
"不要另开窗口,等它释放或心跳过期"
|
||||
)
|
||||
stage = "empty"
|
||||
if artifacts:
|
||||
if filled:
|
||||
stage = "filled"
|
||||
elif artifacts:
|
||||
stage = "docs_ready"
|
||||
elif info_key:
|
||||
stage = "info_ready"
|
||||
@@ -290,6 +298,7 @@ class CaseStore:
|
||||
"info_both": has_travel and has_normal,
|
||||
"artifacts": artifacts,
|
||||
"screenshots": screenshots,
|
||||
"filled": filled,
|
||||
"browser": browser,
|
||||
"pending_inbox": pending_inbox,
|
||||
"warnings": warnings,
|
||||
|
||||
@@ -65,6 +65,11 @@ def case_status(case_id: str) -> dict[str, Any]:
|
||||
ex = s["extractions"]
|
||||
if s["pending_inbox"]:
|
||||
nxt = f"收件箱有 {s['pending_inbox']} 个新文件,先 case_add_files(case_id, source='inbox') 收取"
|
||||
elif s["stage"] == "filled":
|
||||
nxt = (
|
||||
"填报已完成(screenshots 里有步骤完成截图):把单据编号与会话目录告诉用户,"
|
||||
"由他本人在财务系统里核对并提交;不要再 browser_new_form,也别重复填"
|
||||
)
|
||||
elif ex["missing"]:
|
||||
nxt = f"有 {len(ex['missing'])} 个材料未抽取:case_render_pages 后按 schema 抽取并 case_save_extraction"
|
||||
elif ex["stale"]:
|
||||
@@ -74,7 +79,7 @@ def case_status(case_id: str) -> dict[str, Any]:
|
||||
elif not s["info"]:
|
||||
nxt = "综合缓存与匹配结果生成 travel_info 或 normal_info,再 validate_info"
|
||||
else:
|
||||
nxt = "信息已就绪:可生成出库单/申购单,然后 browser_open 开始填报"
|
||||
nxt = "信息已就绪:普通发票可先生成申购单,然后 browser_open 开始填报;填完即终点,提交由本人做"
|
||||
return ok(**s, next_action=nxt)
|
||||
|
||||
|
||||
@@ -114,7 +119,7 @@ def case_inbox(case_id: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def case_reset(case_id: str, scope: str = "extractions") -> dict[str, Any]:
|
||||
"""清理派生产物以便重跑。scope=extractions|match|info|pages|artifacts|all。"""
|
||||
"""清理派生产物以便重跑。scope=extractions|match|info|pages|artifacts|screenshots|all。"""
|
||||
d = _store.resolve(case_id)
|
||||
if d is None:
|
||||
return fail(f"会话不存在: {case_id}", next_action="先 case_create")
|
||||
@@ -147,6 +152,13 @@ def case_reset(case_id: str, scope: str = "extractions") -> dict[str, Any]:
|
||||
for p in art.glob("*"):
|
||||
p.unlink()
|
||||
cleared.append(p.name)
|
||||
elif scope == "screenshots":
|
||||
# 填报过程截图:清了 stage 才会从 filled 退回去,所以单独给一个域而不是并进 artifacts
|
||||
shots_dir = d / SCREENSHOT_DIR_NAME
|
||||
if shots_dir.is_dir():
|
||||
for shot in shots_dir.glob("*.png"):
|
||||
shot.unlink()
|
||||
cleared.append(shot.name)
|
||||
elif scope == "all":
|
||||
for sub in (CACHE_DIR_NAME, PAGES_DIR_NAME, SCREENSHOT_DIR_NAME, ARTIFACTS_DIR_NAME):
|
||||
target = d / sub
|
||||
|
||||
Reference in New Issue
Block a user