docs: align docs with post-restructure package layout

Update README tree (compare.py + domain.py), CLAUDE.md module refs, and the three docs/ deep-dives to the current inbound_verify package (compare/domain/sites/cli). Correct the statistics review report's methodology to the current 口径 (应到=交接件数, 实到=直接数单号去重, 未到=应到−实到) per compare.py docstring; remove ghost-script references in the CDP guide.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-07-24 10:12:40 +08:00
parent 79c84a5a0c
commit f61f52bcbe
5 changed files with 44 additions and 42 deletions

View File

@@ -104,12 +104,12 @@ playwright install chromium
**去重校验**(两账号同归属地则报错中止,防数据翻倍),再顺序对各账号跑一遍 **去重校验**(两账号同归属地则报错中止,防数据翻倍),再顺序对各账号跑一遍
`xxx_download_impl(page, out_tag=归属地)`(产物 `顺心-{归属}-{应到/实到}货物数据.xlsx` `xxx_download_impl(page, out_tag=归属地)`(产物 `顺心-{归属}-{应到/实到}货物数据.xlsx`
最后 `shunxin_merge_final` 把两份 `pd.concat` 成统一的 `顺心-{应到/实到}货物数据.xlsx` 最后 `shunxin_merge_final` 把两份 `pd.concat` 成统一的 `顺心-{应到/实到}货物数据.xlsx`
并删中间文件。比对层 `expected_undelivered` **零改动**(仍读同名文件)。 并删中间文件。比对层 `compare` **零改动**(仍读同名文件)。
- 导出队列不串扰:双账号**顺序执行**,账号 A 走完完整下载流程(远超 40s后 B 才提交, - 导出队列不串扰:双账号**顺序执行**,账号 A 走完完整下载流程(远超 40s后 B 才提交,
配合每账号独立 `export_times` + ≤40s 容差B 不会误匹配 A 的任务。 配合每账号独立 `export_times` + ≤40s 容差B 不会误匹配 A 的任务。
### 比对 ### 比对
`inbound_verify.expected_undelivered`(菜单 [9])纯离线:读 `downloads/` 下各站应到/实到 xlsx `inbound_verify.compare`(菜单 [9])纯离线:读 `downloads/` 下各站应到/实到 xlsx
比对生成 `output/应到未到数据.xlsx`(汇总 + 各站明细)。 比对生成 `output/应到未到数据.xlsx`(汇总 + 各站明细)。
### 路径 ### 路径

View File

@@ -37,7 +37,8 @@ InboundVerify/
│ ├── paths.py # 统一路径锚点(以项目目录为基准,不依赖 cwd │ ├── paths.py # 统一路径锚点(以项目目录为基准,不依赖 cwd
│ ├── runtime.py # 两种模式共享核心(启动 / 就绪 / 任务派发 / 心跳) │ ├── runtime.py # 两种模式共享核心(启动 / 就绪 / 任务派发 / 心跳)
│ ├── state_store.py # SQLite 状态持久化state/state.db │ ├── state_store.py # SQLite 状态持久化state/state.db
│ ├── expected_undelivered.py# 站点应到未到离线比对,输出 output/应到未到数据.xlsx │ ├── domain.py # 站点 / 文件名 / 列映射共享配置单一来源leaf
│ ├── compare.py # 全站点应到未到离线比对,输出 output/应到未到数据.xlsx
│ ├── store.py # DB CLI 入口createdb|init|ingest|all │ ├── store.py # DB CLI 入口createdb|init|ingest|all
│ ├── sites/ # 各站点模块(流程 + 重置 + 重试,自洽) │ ├── sites/ # 各站点模块(流程 + 重置 + 重试,自洽)
│ │ ├── shunxin.py # 顺心(含双账号) │ │ ├── shunxin.py # 顺心(含双账号)

View File

@@ -80,7 +80,7 @@ Protocol error (Browser.setDownloadBehavior): Browser context management is not
``` ```
- `type == "page"` 的才是普通页面(另有 `background_page``service_worker``webview` 等,按需过滤)。 - `type == "page"` 的才是普通页面(另有 `background_page``service_worker``webview` 等,按需过滤)。
- **`id` / `webSocketDebuggerUrl` 每次启动都会变**,所以一定要动态发现,**绝不能硬编码**(早期 `site_anneng.py` 硬编码 WS URL重启就失效 - **`id` / `webSocketDebuggerUrl` 每次启动都会变**,所以一定要动态发现,**绝不能硬编码**(早期 `sites/anneng.py` 硬编码 WS URL重启就失效
-`title``url` 来挑选你真正要操作的那一页。 -`title``url` 来挑选你真正要操作的那一页。
### 3.2 CDP 消息往返 ### 3.2 CDP 消息往返
@@ -408,8 +408,8 @@ def wait_for(self, selector, timeout=10, interval=0.3):
- 所有 Python 脚本在项目 `.venv` 内运行(`python -m venv .venv`,激活后安装依赖)。 - 所有 Python 脚本在项目 `.venv` 内运行(`python -m venv .venv`,激活后安装依赖)。
- 依赖:`websocket-client`(已装)、`black`(格式化,已装)、可选 `pychrome` - 依赖:`websocket-client`(已装)、`black`(格式化,已装)、可选 `pychrome`
- 相关脚本:`_probe_anneng.py`(探查)、`site_anneng.py`(菜单导航)、`download_clean.py`(免对话框下载)。 - 相关脚本:`inbound_verify/sites/anneng.py`(菜单导航 + CDP 驱动;早期探查脚本 `_probe_anneng.py`、免对话框下载脚本 `download_clean.py` 已并入此模块,不再单独存在)。
- 运行:`.venv/Scripts/python.exe download_clean.py` - 运行:`.venv/Scripts/python.exe -m inbound_verify.sites.anneng expected|actual`
--- ---

View File

@@ -1,7 +1,7 @@
# 后端各站点「应到件数 / 已到件数」统计逻辑审查报告 # 后端各站点「应到件数 / 已到件数」统计逻辑审查报告
> 审查范围:`InboundVerify` 后端 > 审查范围:`InboundVerify` 后端
> 核心模块:`expected_undelivered.py`(比对统计)、`runtime.py`(调度/下载路由)、`site_*.py`(各站数据采集)、`server.py`(对外接口) > 核心模块:`compare.py`(比对统计)、`domain.py`(站点/文件/列配置)、`runtime.py`(调度/下载路由)、`sites/*.py`(各站数据采集)、`cli/server.py`(对外接口)
> 审查重点:每个站点的「应到件数」和「已到件数」如何统计、数据来源、口径与潜在歧义。 > 审查重点:每个站点的「应到件数」和「已到件数」如何统计、数据来源、口径与潜在歧义。
--- ---
@@ -12,60 +12,60 @@
| 层 | 模块 | 职责 | 产物 | | 层 | 模块 | 职责 | 产物 |
|---|---|---|---| |---|---|---|---|
| **① 数据采集层** | `site_顺心/中通/韵达/安能.py``site_百世.py` | 用 Playwright安能用 CDP登录各承运商后台导出 Excel 到 `downloads/` | `downloads/<站>-应到货物数据.xlsx``downloads/<站>-实到货物数据.xlsx``downloads/<站>-未到数据.xlsx` | | **① 数据采集层** | `sites/{顺心,中通,韵达,安能,百世}.py` | 用 Playwright安能用 CDP登录各承运商后台导出 Excel 到 `downloads/` | `downloads/<站>-应到货物数据.xlsx``downloads/<站>-实到货物数据.xlsx``downloads/<站>-未到数据.xlsx` |
| **② 比对统计层** | `expected_undelivered.py` | 读 `downloads/` 源文件,两表比对,算出应到/到/未到件数并出报表 | `output/应到未到数据.xlsx`(汇总+各站明细) | | **② 比对统计层** | `compare.py`(站点/文件/列配置取自 `domain.py` | 读 `downloads/` 源文件,两表比对,算出应到/到/未到件数并出报表 | `output/应到未到数据.xlsx`(汇总+各站明细) |
调度入口:`runtime.py``TASK_HANDLERS` 调度入口:`runtime.py``TASK_HANDLERS`
- 4 站:`expected` 下载 + `actual` 下载 + `undelivered`(先下应到+实到,再调 `write_site_file` 算出未到)。 - 4 站:`expected` 下载 + `actual` 下载 + `undelivered`(先下应到+实到,再调 `write_site_file` 算出未到)。
- 百世:只有 `undelivered`(直接导「当日未扫」明细,无应到/实到两份基表)。 - 百世:只有 `undelivered`(直接导「当日未扫」明细,无应到/实到两份基表)。
- `__compare__`(菜单[9]):调 `expected_undelivered.main()`,用 `downloads/` 现有文件生成全站汇总报表。 - `__compare__`(菜单[9]):调 `compare.main()`,用 `downloads/` 现有文件生成全站汇总报表。
--- ---
## 二、核心统计公式4 站统一口径) ## 二、核心统计公式4 站统一口径)
`expected_undelivered.process(name)` 是 4 站统计的唯一实现: `compare.process(name)` 是 4 站统计的唯一实现:
``` ```
对每一条应到运单(按运单号去重,保留首条): 应到(按运单号去重,保留首条):
应到件数 N = 应到数据中的「录单件数」(输出列名「总件数」 应到件数 N = 应到数据中的「交接件数」 # 注意:用「交接件数」,不是「录单件数」
应到序号集 = {1, 2, …, N} (录单件数只是该单号总录单量,并非真正到站量)
已到序号集 = 从「实到货物数据.xlsx」解析该运单实际到货的子单序号 应到件数 += N # 站点级应到 = Σ N
未到序号集 = {1…N} 已到序号集
未到件数 += len(未到序号集) # 每缺 1 个子单 → 1 行未到明细
应到件数 += N
站点级 实到(直接数,不再由「应到−未到」倒推)
应到件数 = Σ N所有应到运单 按「运单号」把实到表的「单号/子单号/扫描单号」分组,组内去重计数
未到件数 = Σ len(未到序号集) → 每个运单的实到件数;站点级实到 = Σ 各运单实到件数
已到件数 = 应到件数 未到件数 ← 注意:是「减出来」的,不是直接数实到
未到:
未到件数 = max(0, 应到件数 实到件数) # 站点级
未到率 = 未到件数 ÷ 应到件数 未到率 = 未到件数 ÷ 应到件数
完全未到运单 = 未到序号集 == {1…N} 的运单 短少运单 = 实到件数 < 应到件数 N 的运单
部分未到运单 = 0 < 未到 < N 的运单 (未到明细 downloads/<站>-未到数据.xlsx 只列这些短少运单
每行:交接单号 | 运单号 | 总件数(=N) | 已到单号1 | 已到单号2 | …)
``` ```
**关键事实**`到件数` `应到件数 到件数` 推算出来的(`exp_pieces - len(rows)`),并非独立去数实到表。它等价于 `Σ|已到序号集|`,前提是实到解析正确且已到序号 ⊆ 应到序号 **关键事实**`到件数`直接数实到表「单号」、按运单号分组去重得到的(**不再由「应到−未到」倒推**`未到件数 = 应到件数 到件数`。前提是实到单号能正确按运单号分组(分组规则见下表各站 `arrived_pieces_*`
--- ---
## 三、各站点统计明细 ## 三、各站点统计明细
| 站点 | 应到件数来源 | 到件数来源 | 实到序号解析规则(`arrived` | 源文件(`downloads/` | | 站点 | 应到件数来源 | 到件数来源 | 单号→运单号 分组规则(`arrived_pieces_*` | 源文件(`downloads/` |
|---|---|---|---|---| |---|---|---|---|---|
| **中通** | `中通-应到货物数据.xlsx` 的「录单件数」 | 应到−未到(推算 | `运单号`是复合串 = 运单号 + 总数(4) + 顺序(4)取右 8 位,前 4 为总数、后 4 为顺序号 | `中通-应到货物数据.xlsx` / `中通-实到货物数据.xlsx` | | **中通** | `中通-应到货物数据.xlsx` 的「交接件数」 | 实到表单号去重(直接数 | 实到`运单号`是复合串 = 运单号 + 总数(4) + 顺序(4)`v[:-8]` 归并到应到运单号,每条复合串即 1 件 | `中通-应到货物数据.xlsx` / `中通-实到货物数据.xlsx` |
| **顺心** | `顺心-应到货物数据.xlsx` 的「录单件数」 | 应到−未到(推算 | `运单号`(干净) + `子单号` = 运单号 + 顺序号(3位);后缀(3位)即顺序号 | `顺心-应到货物数据.xlsx` / `顺心-实到货物数据.xlsx` | | **顺心** | `顺心-应到货物数据.xlsx` 的「交接件数」 | 实到表单号去重(直接数 | `运单号`分组,`子单号`=每件(一件一个子单号) | `顺心-应到货物数据.xlsx` / `顺心-实到货物数据.xlsx` |
| **韵达** | `韵达-应到货物数据.xlsx` 的「录单件数」 | 应到−未到(推算 | `主单号`=运单号`子单号`=主单号 + 顺序号(4位);后缀(4位)即顺序号 | `韵达-应到货物数据.xlsx` / `韵达-实到货物数据.xlsx` | | **韵达** | `韵达-应到货物数据.xlsx` 的「交接件数」 | 实到表单号去重(直接数 | `主单号`分组`子单号`=每件 | `韵达-应到货物数据.xlsx` / `韵达-实到货物数据.xlsx` |
| **安能** | `安能-应到货物数据.xlsx` 的「录单件数」 | 应到−未到(推算 | `所属单号`=运单号`扫描单号`=所属单号 + 总数(4位) + 顺序号(4位)`has_total=True`,取末 4 位为顺序号 | `安能-应到货物数据.xlsx` / `安能-实到货物数据.xlsx` | | **安能** | `安能-应到货物数据.xlsx` 的「交接件数」 | 实到表单号去重(直接数 | `所属单号`分组`扫描单号`=每件 | `安能-应到货物数据.xlsx` / `安能-实到货物数据.xlsx` |
| **百世** | **无**(站点只给未到) | **无(显示「—」)** | 不适用(无实到基表) | 仅 `百世-应到未到货物数据.xlsx`= 当日未扫明细,本身就是未到结果) | | **百世** | **无**(站点只给未到) | **无(显示「—」)** | 不适用(无实到基表) | 仅 `百世-应到未到货物数据.xlsx`= 当日未扫明细,本身就是未到结果) |
> 4 站合计/图表口径:`expected_undelivered.build_summary` 只累加 4 站(`应到−实到`口径),**百世不计入合计**(无应到基数)。百世在表中单列,未到件数 = 其明细行数。 > 4 站合计/图表口径:`compare.build_summary` 只累加 4 站(`应到−实到`口径),**百世不计入合计**(无应到基数)。百世在表中单列,未到件数 = 其明细行数。
--- ---
## 四、百世的特殊口径(务必注意) ## 四、百世的特殊口径(务必注意)
百世是唯一「无应到/实到基数」的站点: 百世是唯一「无应到/实到基数」的站点:
- 它的数据来自后台「扫描综合查询 → 到/接件扫描 → 当日 → 未扫」,导出即「当日未扫」明细(`site_baishi.py`)。 - 它的数据来自后台「扫描综合查询 → 到/接件扫描 → 当日 → 未扫」,导出即「当日未扫」明细(`sites/baishi.py`)。
- 因此 `process_baishi()` 只能给 `未到件数 = 行数``应到件数 = None``已到件数 = None`、完全/部分未到 = None。 - 因此 `process_baishi()` 只能给 `未到件数 = 行数``应到件数 = None``已到件数 = None`、完全/部分未到 = None。
- 报表里百世的应到/已到列显示「—」,未到率无法计算。 - 报表里百世的应到/已到列显示「—」,未到率无法计算。
- **含义**:百世统计的是「今天还没扫到的件」,不是「相对应到总量的缺件率」。与 4 站口径不可直接相加比较。 - **含义**:百世统计的是「今天还没扫到的件」,不是「相对应到总量的缺件率」。与 4 站口径不可直接相加比较。
@@ -97,8 +97,8 @@
## 七、潜在歧义与风险点(审查结论) ## 七、潜在歧义与风险点(审查结论)
1. **已到件数是减出来的,不是数出来的**:依赖实到子单号解析正确。一旦某站 `子单号` 格式与解析规则(`arrived_*`)不匹配,该序号进不了「已到序号集」→ 误判为未到 → 已到件数被低估、未到率虚高。解析规则硬编码,承运商改版号段即失准。 1. **实到依赖单号→运单号分组正确**:实到件数靠把实到表「单号」按运单号分组去重计数(`arrived_pieces_*`)。一旦某站单号格式与分组规则不匹配(如复合串切分错),该件归不到对应运单 → 实到被低估、未到率虚高。规则硬编码,承运商改版号段即失准。
2. **应到件数依赖「录单件数」**:若应到数据某运单 `录单件数` 缺失/为 0/非数字,该运单被 `continue` 跳过,既不计入应到也不计入未到 → 静默漏统(应到总量被低估,但该运单出现在实到中也因不在应到循环而永不计入已到,方向一致)。 2. **应到件数依赖「交接件数」**:若应到数据某运单 `交接件数` 缺失/为 0/非数字,该运单被跳过,既不计入应到也不计入未到 → 静默漏统(应到总量被低估该运单即便出现在实到中也因不在应到循环而无处抵扣)。
3. **应到按运单号去重keep first**:同一运单多条交接记录只取首条 `录单件数`。若重复行的件数不同,取首条,可能与实际不符。 3. **应到按运单号去重keep first**:同一运单多条交接记录只取首条 `录单件数`。若重复行的件数不同,取首条,可能与实际不符。
4. **百世不可并入合计**4 站合计的「已到总件数」不含百世;跨站看「已到」时别把百世当成有应到基数的站。 4. **百世不可并入合计**4 站合计的「已到总件数」不含百世;跨站看「已到」时别把百世当成有应到基数的站。
5. **应到/实到业务日期可能错位**(见第五节):两表不同步下载或偏移不一致时,比对口径失真。 5. **应到/实到业务日期可能错位**(见第五节):两表不同步下载或偏移不一致时,比对口径失真。
@@ -111,9 +111,10 @@
| 文件 | 角色 | | 文件 | 角色 |
|---|---| |---|---|
| `expected_undelivered.py` | 统计核心:`process()`4站比对`process_baishi()`(百世)、`build_summary()`(汇总报表)`STATIONS`(各站解析配置) | | `compare.py` | 统计核心:`process()`4站比对`process_baishi()`(百世)、`build_summary()`(汇总报表) |
| `domain.py` | 站点/文件名/列映射配置:`STATIONS`(各站解析配置)、`arrived_pieces_*`(实到单号→运单号分组)、`_site_cfg` |
| `runtime.py` | `TASK_HANDLERS`(下载/比对路由)、`_site_undelivered_handler`4站先下应到+实到再算未到)、`_record_business_date`(业务日期/偏移写入) | | `runtime.py` | `TASK_HANDLERS`(下载/比对路由)、`_site_undelivered_handler`4站先下应到+实到再算未到)、`_record_business_date`(业务日期/偏移写入) |
| `site_中通/顺心/韵达/安能.py` | 各站 `expected_download` / `actual_download`Playwright/CDP 导出源表) | | `sites/{中通,顺心,韵达,安能}.py` | 各站 `expected_download` / `actual_download`Playwright/CDP 导出源表) |
| `site_百世.py` | `baishi_download_undelivered_data`(直接导「当日未扫」) | | `sites/baishi.py` | `baishi_download_undelivered_data`(直接导「当日未扫」) |
| `state_store.py` | `site_status`ready/business_date`site_config`offset/schedule`get_offset` | | `state_store.py` | `site_status`ready/business_date`site_config`offset/schedule`get_offset` |
| `server.py` | `/status``/report``/data/{filename}` 接口 | | `cli/server.py` | `/status``/report``/data/{filename}` 接口 |

View File

@@ -1,11 +1,11 @@
# 韵达 / 安能 应到未到计算逻辑梳理(基于 expected_undelivered.py 重构版代码) # 韵达 / 安能 应到未到计算逻辑梳理(基于 inbound_verify/compare.py 重构版代码)
> 用途:供审核两站「应到件数 / 实到件数 / 未到件数」的实现逻辑与关联键。 > 用途:供审核两站「应到件数 / 实到件数 / 未到件数」的实现逻辑与关联键。
> 代码基准:`InboundVerify/expected_undelivered.py`(重构版)。行号对应本次读取。 > 代码基准:`InboundVerify/inbound_verify/compare.py`(重构版)。
--- ---
## 、两站共用的计算骨架process 函数,第 134216 行 ## 、两站共用的计算骨架process 函数)
无论韵达还是安能,最终都走同一个 `process(name)` 无论韵达还是安能,最终都走同一个 `process(name)`