Rewrite run commands to python -m inbound_verify.* (and console_script aliases); add pip install -e . to env prep; refresh the directory tree. Also commit the design spec and Tier 1 implementation plan under docs/superpowers/. Co-Authored-By: Claude <noreply@anthropic.com>
9.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
项目概览
自动登录 5 家物流承运商工作台,下载"应到 / 实到"货物数据,离线比对出应到未到 异常运单并汇总成 Excel。4 个网页站点 + 1 个 Electron 应用(安能)。
两种运行模式(阶段1 起):
- 交互模式
inbound_verify.cli.router:人工调试 / 操作,交互菜单(登录、触发下载、[12] 状态盘)。 - 服务模式
inbound_verify.cli.server:常驻 + FastAPI,客户端经 HTTP 触发任务、查状态、下载数据(API 文档/docs)。 - 两者共享
inbound_verify.runtime(启动 / 就绪 / 任务派发 / 心跳)与inbound_verify.state_store(SQLite 状态持久化)。
常用命令
所有 Python 一律在项目虚拟环境 .venv 中运行(Windows 下可直接用
.venv/Scripts/python.exe,无需激活)。首次 / 拉取新代码后需
.venv/Scripts/python.exe -m pip install -e .(以可编辑模式注册
inbound-verify 等命令)。
# 安装依赖(含安能 CDP 驱动所需的 websocket-client)+ 以可编辑模式注册命令
pip install -r requirements.txt
pip install -e .
playwright install chromium
# 运行主程序(交互式菜单,详见 inbound_verify.cli.router 的 run_multi_site_daemon)
.venv/Scripts/python.exe -m inbound_verify.cli.router
# 装包后也可直接用命令:inbound-verify
# 服务模式(常驻 + FastAPI,客户端经 HTTP 触发;默认 :8000,API 文档见 /docs)
.venv/Scripts/python.exe -m inbound_verify.cli.server
# 或:inbound-verify-server
# DB CLI:建库 / 初始化 / 灌数据 / 全流程
.venv/Scripts/python.exe -m inbound_verify.store createdb # 或 init | ingest | all
# 或:inbound-verify-db createdb|init|ingest|all
# 单站点联调:在 config.yaml 设 debug.enabled=true + debug.target_site=顺心|百世|中通|韵达|安能
# 网页站:只挂载该站;安能:只启动 Electron 应用。
# 安能独立运行(需先以 --remote-debugging-port=9222 启动「安能全网门户.exe」并手动登录)
.venv/Scripts/python.exe -m inbound_verify.sites.anneng expected # 或 actual
# 格式化(全局规范:改完 Python 必须 Black)
.venv/Scripts/python.exe -m black inbound_verify
# 语法自检
.venv/Scripts/python.exe -m py_compile inbound_verify
没有 pytest 测试套件。 "测试"指 inbound_verify.cli.router 菜单 [8] 自动化测试
(run_automation_test,按 CROSS_TEST_SEQUENCE 交叉跑通各站点流程)。
架构(big picture)
运行模式与共享核心(阶段0/1 重构)
inbound_verify.runtime:两种模式共享的核心——launch_and_prepare(启动 Playwright + 各站就绪 + 弹窗 + 心跳初值,阻塞至就绪)、dispatch_task(ctx, {site,kind})(派发任务,掉登录直接判 failed)、run_heartbeat、probe_site_login/probe_data_file、RuntimeContext.stop()。常量SITES_CONFIG/READY_SELECTORS/APP_SITES/HEARTBEAT_INTERVAL/DATA_FILENAMES在此。inbound_verify.state_store:SQLite 状态持久化(state/state.db)。site_status(登录态 + 数据态 + 时间戳,心跳刷新)、task_history(任务记录)。重启不丢。inbound_verify.cli.router:交互模式,菜单循环(input 后台线程 +_await_command+dispatch_task+ 心跳)。inbound_verify.cli.server:服务模式。FastAPI(主线程)+ Playwright worker(独立线程)——主线程处理 HTTP(绝不碰 Playwright),worker 独占 page 操作,经task_queue+state_store通信。API:POST/GET /tasks、GET /status、GET /data/{file}。- 关键线程约束:Playwright sync 对象绑定创建它的线程;
launch_and_prepare(含sync_playwright().start())必须在持有 Playwright 的线程调用(交互=主线程,服务=worker 线程)。FastAPI 路由绝不访问 page。 - 站点模块
inbound_verify.sites.*的with_retry返回True/False(成功 / 放弃),供dispatch_task判成败。
两套驱动模态 —— 这是理解全局的关键
- 网页 4 站(顺心/百世/中通/韵达):
inbound_verify.cli.router用 Playwright 开 chromium, 每站一个page,流程函数签名为xxx_download_impl(page)。例外:顺心是双账号 ——同一窗口开两个标签页(两个归属地账号),pages_map["顺心"]存为 page 列表,shunxin_download(pages)接收列表(详见下文「顺心双账号」)。 - 安能:Electron 桌面应用,不走 Playwright。
inbound_verify.cli.router以--remote-debugging-port=<动态空闲端口>启动 exe(launch_anneng), 通过inbound_verify.sites.anneng.set_cdp_port告知模块;inbound_verify.sites.anneng用裸 CDP(websocket) 驱动,业务 tab 是独立 webContents。这也是playwright-cli接管不了安能的原因 (Electron 19 / Chrome 102 不支持 Playwright 要的 setDownloadBehavior)。
分层:路由纯调度,站点模块自洽
inbound_verify.cli.router只调度:启动浏览器/安能、就绪轮询、登录检测、菜单分发。 菜单项直接调sites.xxx_download(page)(顺心传 page 列表),不关心重试/重置。- 每个
inbound_verify.sites.*模块对外只暴露"把任务做了"的入口,内部自洽:xxx_download(...)—— 公开入口,=with_retry(站点, 标签, xxx_download_impl, xxx_reset)xxx_download_impl(...)—— 单次执行、无重试(自动化测试刻意调它以探测原始失败)xxx_reset(...)—— 重置回初始态(网页 =page.goto(HOME_URL);安能 = 关业务 tab + 收菜单)with_retry(...)—— 重试逻辑内联在每个站点模块(不抽公共组件,现阶段刻意不优化结构); 失败→重置→重试,最多 3 次(含首次),每次失败都重置(含最终放弃那次清场)HOME_URL—— 站点首页 URL,runtime.SITES_CONFIG引用它(单一来源)
导出任务队列模式(顺心/中通/韵达/安能-应到 共用)
这些站的下载是异步的:提交导出(记 export_times 时间戳)→ 跳"导出任务管理"页轮询 →
匹配本批任务 → 下载 → 合并多个临时 xlsx。
- 匹配只按时间容差 ≤40s,不校验任务标题/模块名(标题可能被站点改动;单账号无并发, 时间窗内的任务必为本批所建)。不要把标题校验加回来。
- 轮询循环都有 300s deadline:超时
raise→ 流程返回 False → 触发with_retry重试。 - 例外:百世是同步下载(
expect_download,无队列);安能-实到页面无导出按钮, 直接抓表格 DOM 翻页。
顺心双账号(双归属地)
顺心业务上要同时处理两个归属地网点(两个账号)。程序在同一窗口开两个标签页, 人工分别登录两个账号(顺心站点支持同浏览器双账号并存,无需独立 context/窗口)。
runtime启动时为顺心开 2 个context.new_page(),pages_map["顺心"]为列表; 就绪轮询要求两个标签页都进主页才算就绪;初始弹窗对两个标签页各处理一遍。shunxin_expected_download(pages)/shunxin_actual_download(pages)接收 page 列表: 先用shunxin_belonging(page)读各账号归属地(首页「切换网点」控件.site___3o7nH), 去重校验(两账号同归属地则报错中止,防数据翻倍),再顺序对各账号跑一遍xxx_download_impl(page, out_tag=归属地)(产物顺心-{归属}-{应到/实到}货物数据.xlsx), 最后shunxin_merge_final把两份pd.concat成统一的顺心-{应到/实到}货物数据.xlsx并删中间文件。比对层expected_undelivered零改动(仍读同名文件)。- 导出队列不串扰:双账号顺序执行,账号 A 走完完整下载流程(远超 40s)后 B 才提交,
配合每账号独立
export_times+ ≤40s 容差,B 不会误匹配 A 的任务。
比对
inbound_verify.expected_undelivered(菜单 [9])纯离线:读 downloads/ 下各站应到/实到 xlsx,
比对生成 output/应到未到数据.xlsx(汇总 + 各站明细)。
路径
inbound_verify.paths 把 DOWNLOAD_DIR / CONFIG_PATH / BASE_DIR 全部锚定到项目目录,
不依赖运行时 cwd——别用相对路径或 os.getcwd()。
重要约定 / 易踩坑
- 登录是手动的:
inbound_verify.cli.router启动后会停在就绪轮询(READY_SELECTORS/anneng_ready), 直到检测到所有站点进入工作台才进菜单。仅韵达支持凭config.yaml凭据自动登录。 顺心需登录两个账号:同一窗口的两个标签页分别登录两个不同归属地账号,两个标签页 都进主页后才算就绪(顺心站点支持同浏览器双账号并存,故用同 context 标签页而非独立窗口)。 - 安能单实例:启动前必须先关闭已打开的安能窗口,否则调试端口起不来。
- 安能重置绝不能用
Page.reload:reload 会让已开业务 webContents 失去 app 引用、 变成Target.closeTarget/window.close都杀不掉的僵尸。重置只能走 tab 条 X 关 tab + 收菜单。 config.yaml已 gitignore,存放凭据 /query_days/debug/anneng.app_path, 切勿提交,也别把真实凭据写进config.example.yaml。- 不要自动提交 / 推送:本仓库约定改动后等用户明确说"提交"再 commit/push (覆盖全局 CLAUDE.md 的 auto-push 默认)。
- 改完 Python 文件必须跑 Black(全局规范)。