Files
InboundVerify/CLAUDE.md
Misaka_Company 5e4889e845 docs: sync docs with current package layout and auto-ingest hook
- config.example.yaml: replace stale site_yunda/main_router with new
  module paths (sites.yunda, runtime)
- CLAUDE.md: add ingest-one to DB CLI list; new subsection documenting
  the download->PostgreSQL auto-ingest hook (_persist_to_db, ingest_task,
  ingest_state, /status.ingest, auto_ingest config)
- README.md: store tree comment lists all 5 CLI commands (add ingest-one)
- docs: /status row notes the ingest field; anneng CDP guide snippet
  gets timeout=15
- cli/server.py: docstring run command -> python -m inbound_verify.cli.server

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 12:35:54 +08:00

145 lines
11 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.
# 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` 等命令)。
```bash
# 安装依赖(含安能 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 触发;默认 :8000API 文档见 /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 | ingest-one <site> <kind> | all
# 或inbound-verify-db createdb|init|ingest|ingest-one|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绝不碰 Playwrightworker 独占 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` 用裸 CDPwebsocket
驱动,业务 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`
并删中间文件。比对层 `compare` **零改动**(仍读同名文件)。
- 导出队列不串扰:双账号**顺序执行**,账号 A 走完完整下载流程(远超 40s后 B 才提交,
配合每账号独立 `export_times` + ≤40s 容差B 不会误匹配 A 的任务。
### 比对
`inbound_verify.compare`(菜单 [9])纯离线:读 `downloads/` 下各站应到/实到 xlsx
比对生成 `output/应到未到数据.xlsx`(汇总 + 各站明细)。
### 自动入库(下载成功后 → PostgreSQL
`dispatch_task` 下载成功后,在 `_record_business_date` 旁挂一个**尽力而为**钩子
`_persist_to_db(site, kind)``runtime`):懒导入 `store`,调 `store.ingest_task(site, kind)`
按 kind 幂等 UPSERT 进 PostgreSQLexpected/actual 各入其列undelivered——百世入未到、
4 站连入 expected+actual。**绝不影响下载任务的成功判定**:所有写库/写状态都包 try/except
失败只告警。
- 开关 `postgres.auto_ingest`(默认开)+ `connect_timeout_seconds`(兜 cpolar 抖动)。
- 可见性:结果写 `state_store.ingest_state`(每站每类 ok/count/ingested_at/error
`GET /status``ingest` 字段暴露。
- 手动:`store` CLI `ingest-one <site> <kind>` 用同套路由单测(脱离下载)。
- 注意:跑在 Playwright 线程、同步阻塞cpolar 慢/断靠超时 + try/except 降级,不重试不补入。
### 路径
`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**(全局规范)。