refactor: rename expected_undelivered to compare

git mv expected_undelivered.py -> compare.py; update the 3 importers (store/runtime/router) to import compare. All public names (main, write_site_file, _read_business_dates) unchanged. Also add the Tier 2 plan doc.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-07-24 08:40:04 +08:00
parent 91ef8970b8
commit 23042c272b
5 changed files with 412 additions and 11 deletions

View File

@@ -0,0 +1,403 @@
# Tier 2: domain extract + rename + path dedup — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Three targeted, behavior-preserving cleanups of the compare/config layer: (1) make `expected_undelivered` use `paths.DOWNLOAD_DIR/OUTPUT_DIR` instead of its own duplicate anchor; (2) extract shared site/file/column config into a new `domain` module; (3) rename `expected_undelivered``compare`.
**Architecture:** Pure refactor — move definitions, rewire imports, no logic change. Removes the duplicate path anchor that caused the Tier 1 hotfix bug (`17293be`), and the `db_store → expected_undelivered` coupling where the DB layer imported the whole compare engine just to read site config.
**Tech Stack:** Python ≥3.10, package `inbound_verify`, pandas, openpyxl, psycopg3.
## Global Constraints
- **Python ≥ 3.10**, package import name `inbound_verify`.
- **No behavior change** — pure refactor. Any logic change is a defect.
- **No test suite** (user decision). Per-task verification = `compileall` + **import smoke** (fresh process, no backend needed) + grep-for-stale-refs. NOT pytest. (The running backend is unaffected until a restart; a final end-to-end re-confirm is done once at the end of Tier 2.)
- **No auto-commit** (project rule). Each task's commit step runs only after the user says "提交". Commit messages in English.
- **Black-format** every changed `.py` (global rule).
- All changes inside the `InboundVerify` submodule; commits local on `dev`.
- Venv interpreter: `.venv/Scripts/python.exe` (absolute: `D:/projects/LogisticsHubIPA/InboundVerify/.venv/Scripts/python.exe`). Run commands from `D:/projects/LogisticsHubIPA/InboundVerify/`.
**Reference spec:** `docs/superpowers/specs/2026-07-23-package-restructure-design.md` §8 Tier 2.
**Out of scope (deferred):** `config.py` centralization (consolidating the 6 `open(CONFIG_PATH)+yaml.safe_load` sites in store/runtime×2/anneng/router). It is the churniest Tier 2 item (6 files) for the least marginal value — pure DRY, no pain addressed — and under no-tests more churn = more risk. Reconsider after the rest of Tier 2 lands.
---
## File Structure
```
inbound_verify/
├── domain.py # NEW (Task 2): shared site/file/column config — leaf module
├── paths.py # unchanged (already the single anchor)
├── expected_undelivered.py# Task 1: use paths.* ; Task 2: import config from domain ; Task 3: renamed → compare.py
├── compare.py # (after Task 3) was expected_undelivered.py — compare engine + report
├── store.py # Task 2: import config from domain (not eu); Task 3: import _read_business_dates from compare
├── runtime.py # Task 2: SITE_UNDELIVERED_FILE from domain; Task 3: write_site_file from compare
└── cli/router.py # Task 3: compare.main()
```
**`domain.py` responsibility:** pure data — `ALL_REPORT_SITES`, `SITE_UNDELIVERED_FILE`, `BAISHI_FILE`, `BAISHI_COLUMNS`, `arrived_pieces_zhongtong`, `arrived_pieces_by_cols`, `STATIONS`, `_site_cfg`. No `state_store` dependency, no file I/O. Leaf module.
---
## Task 1: Path dedup — expected_undelivered uses paths.DOWNLOAD_DIR/OUTPUT_DIR
**Files:**
- Modify: `inbound_verify/expected_undelivered.py` (lines 43-48 defs; usages at 146,147,298,338,374,400,669,675,676,690)
**Interfaces:**
- Consumes: `paths.DOWNLOAD_DIR`, `paths.OUTPUT_DIR` (already exist, anchored at project root).
- Produces: `expected_undelivered` no longer defines `BASE/DOWNLOADS/OUTPUT`; keeps `OUTFILE` (now `= join(OUTPUT_DIR, "应到未到数据.xlsx")`). The 6 `DOWNLOADS` and 1 `OUTPUT` usages point at the `paths.*` constants — same resolved values as the current hotfixed anchor, so behavior identical.
- [ ] **Step 1: Add the paths import to the top import block**
In `inbound_verify/expected_undelivered.py`, after the existing `import os` (top of file), add a line importing the two path constants. (The file currently has no `paths` import — it used its own BASE.)
Add:
```python
from inbound_verify.paths import DOWNLOAD_DIR, OUTPUT_DIR
```
(place it alongside the other imports near the top, e.g. right after `import os`.)
- [ ] **Step 2: Replace the self-anchored BASE/DOWNLOADS/OUTPUT/OUTFILE block**
Replace this block (currently lines ~43-48):
```python
# 包内文件:上两级 = 项目根(与 paths.BASE_DIR 一致;站点下载落在 <root>/downloads
# 注:本模块自带锚点是 paths.py 的重复Tier 2 计划改为直接引用 paths.DOWNLOAD_DIR/OUTPUT_DIR。
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DOWNLOADS = os.path.join(BASE, "downloads")
OUTPUT = os.path.join(BASE, "output")
OUTFILE = os.path.join(OUTPUT, "应到未到数据.xlsx")
```
with:
```python
# 比对报表输出文件(路径锚定统一走 paths.py
OUTFILE = os.path.join(OUTPUT_DIR, "应到未到数据.xlsx")
```
- [ ] **Step 3: Replace all `DOWNLOADS` usages with `DOWNLOAD_DIR`**
Apply `DOWNLOADS``DOWNLOAD_DIR` (6 occurrences, at lines 146, 147, 298, 338, 669, 675, 676). Use replace-all on the token `DOWNLOADS`.
- [ ] **Step 4: Replace the remaining `OUTPUT` usage (the makedirs line)**
At line ~374, replace:
```python
os.makedirs(OUTPUT, exist_ok=True)
```
with:
```python
os.makedirs(OUTPUT_DIR, exist_ok=True)
```
(`OUTFILE` at lines 400/690 is unchanged — it's a different token, already redefined in Step 2.)
- [ ] **Step 5: Verify syntax + no stale BASE/DOWNLOADS/OUTPUT**
```bash
cd /d/projects/LogisticsHubIPA/InboundVerify
.venv/Scripts/python.exe -m py_compile inbound_verify/expected_undelivered.py
grep -nE "\b(BASE|DOWNLOADS|OUTPUT)\b" inbound_verify/expected_undelivered.py || echo "no stale BASE/DOWNLOADS/OUTPUT OK"
```
Expected: py_compile silent; grep prints `no stale BASE/DOWNLOADS/OUTPUT OK` (OUTFILE is a different token, won't match).
- [ ] **Step 6: Import smoke + paths equivalence**
```bash
.venv/Scripts/python.exe -c "from inbound_verify import expected_undelivered as eu; from inbound_verify.paths import DOWNLOAD_DIR, OUTPUT_DIR; import os; print('OUTFILE dir matches OUTPUT_DIR:', os.path.dirname(eu.OUTFILE)==OUTPUT_DIR)"
.venv/Scripts/python.exe -c "import inbound_verify.cli.router, inbound_verify.cli.server, inbound_verify.store, inbound_verify.runtime; print('import smoke OK')"
```
Expected: `OUTFILE dir matches OUTPUT_DIR: True` and `import smoke OK`.
- [ ] **Step 7: Black + commit (after user confirms)**
```bash
.venv/Scripts/python.exe -m black inbound_verify/expected_undelivered.py
git add inbound_verify/expected_undelivered.py
git commit -m "refactor: expected_undelivered uses paths.DOWNLOAD_DIR/OUTPUT_DIR (Tier 2)
Co-Authored-By: Claude <noreply@anthropic.com>"
```
---
## Task 2: Extract `domain.py` (shared site/file/column config)
**Files:**
- Create: `inbound_verify/domain.py`
- Modify: `inbound_verify/expected_undelivered.py` (remove the moved block, import from domain)
- Modify: `inbound_verify/store.py` (config from domain, not eu)
- Modify: `inbound_verify/runtime.py` (SITE_UNDELIVERED_FILE from domain)
**Interfaces:**
- Consumes: nothing new (moves existing definitions verbatim).
- Produces: `inbound_verify.domain` exposing `ALL_REPORT_SITES`, `SITE_UNDELIVERED_FILE`, `BAISHI_FILE`, `BAISHI_COLUMNS`, `arrived_pieces_zhongtong(df)`, `arrived_pieces_by_cols(wb_col, piece_col)`, `STATIONS` (list of dicts), `_site_cfg(name)`. These are the exact same objects that lived in `expected_undelivered` lines 50-135.
- [ ] **Step 1: Create `inbound_verify/domain.py`**
Create `inbound_verify/domain.py` with this exact content (moved verbatim from expected_undelivered.py lines 50-135, plus the `defaultdict` import it needs):
```python
# -*- coding: utf-8 -*-
"""domain.py — 站点 / 文件名 / 列映射的共享配置(单一来源)。
比对compare与入库store都依赖这套配置抽出独立 leaf 模块,
让 store 不必为读配置而依赖整个比对引擎。纯数据,无 state_store / 文件 IO 依赖。
"""
from collections import defaultdict
# 汇总报表覆盖的全部站点4 站在前、百世在末;汇总页图表只取 4 站)
ALL_REPORT_SITES = ["顺心", "中通", "韵达", "安能", "百世"]
# 4 站单站未到明细文件名(百世未到文件由站点直接产出,名为 BAISHI_FILE
SITE_UNDELIVERED_FILE = "{name}-未到数据.xlsx"
BAISHI_FILE = "百世-应到未到货物数据.xlsx"
BAISHI_COLUMNS = ["类型", "子单号", "运单号", "最新扫描记录"]
def arrived_pieces_zhongtong(df):
"""中通实到「运单号」为复合串H + 运单号(12) + 总数(4) + 顺序(4))。
基号 = v[:-8](与应到表运单号对齐),单件 = 整串(每串即一件)。"""
res = defaultdict(set)
for v in df["运单号"]:
v = str(v).strip()
if len(v) > 8 and v[-4:].isdigit():
res[v[:-8]].add(v) # 以完整复合串作为“已到单号”存入
return res
def arrived_pieces_by_cols(wb_col, piece_col):
"""顺心 / 韵达 / 安能:按干净运单列分组,单件 = 子单号 / 扫描单号。
wb_col实到表中与应到运单号对齐的干净列
(顺心=运单号 / 韵达=主单号 / 安能=所属单号)
piece_col实到表中每件货物的单号列子单号 / 扫描单号)"""
def parse(df):
res = defaultdict(set)
for m, s in zip(df[wb_col], df[piece_col]):
m, s = str(m).strip(), str(s).strip()
if m and s:
res[m].add(s)
return res
return parse
STATIONS = [
{
"name": "中通",
"exp": "中通-应到货物数据.xlsx",
"act": "中通-实到货物数据.xlsx",
"exp_qty": "交接件数", # 应到件数口径:交接件数(非录单件数)
"exp_wb": "运单号", # 应到表运单号列(兼作去重键)
"exp_jd": "交接单号", # 未到数据需展示的交接单号
"arrived_pieces": arrived_pieces_zhongtong,
"columns": ["交接单号", "运单号", "总件数"],
},
{
"name": "顺心",
"exp": "顺心-应到货物数据.xlsx",
"act": "顺心-实到货物数据.xlsx",
"exp_qty": "交接件数",
"exp_wb": "运单号",
"exp_jd": "交接单号",
"arrived_pieces": arrived_pieces_by_cols("运单号", "子单号"),
"columns": ["交接单号", "运单号", "总件数"],
},
{
"name": "韵达",
"exp": "韵达-应到货物数据.xlsx",
"act": "韵达-实到货物数据.xlsx",
"exp_qty": "交接件数",
"exp_wb": "运单号",
"exp_jd": "交接单号",
"arrived_pieces": arrived_pieces_by_cols("主单号", "子单号"),
"columns": ["交接单号", "运单号", "总件数"],
},
{
"name": "安能",
"exp": "安能-应到货物数据.xlsx",
"act": "安能-实到货物数据.xlsx",
"exp_qty": "交接件数",
"exp_wb": "运单号",
"exp_jd": "交接单号",
"arrived_pieces": arrived_pieces_by_cols("所属单号", "扫描单号"),
"columns": ["交接单号", "运单号", "总件数"],
},
]
def _site_cfg(name):
"""按名称取 4 站配置(百世不在 STATIONS返回 None"""
return next((c for c in STATIONS if c["name"] == name), None)
```
- [ ] **Step 2: Remove the moved block from expected_undelivered.py and import from domain**
In `inbound_verify/expected_undelivered.py`:
- Add to the top import block:
```python
from inbound_verify.domain import (
ALL_REPORT_SITES,
BAISHI_COLUMNS,
BAISHI_FILE,
SITE_UNDELIVERED_FILE,
STATIONS,
_site_cfg,
arrived_pieces_by_cols,
arrived_pieces_zhongtong,
)
```
- Delete the now-duplicated definitions (the block from `ALL_REPORT_SITES = ...` through the end of `_site_cfg`, i.e. old lines ~50-135 — the comment header `# 汇总报表...` through `return next(...)`). These now live in domain.py. The `from collections import defaultdict` import in expected_undelivered.py can stay (harmless) or be removed if unused — check with grep after.
- [ ] **Step 3: store.py — take config from domain, _read_business_dates still from eu**
In `inbound_verify/store.py`:
- Add to imports:
```python
from inbound_verify.domain import BAISHI_FILE, _site_cfg
```
- Replace `cfg = eu._site_cfg(site)` (2 occurrences, lines 264 and 296) → `cfg = _site_cfg(site)`.
- Replace `eu.BAISHI_FILE` (2 occurrences, lines 335 and 337) → `BAISHI_FILE`.
- Leave `eu._read_business_dates(...)` (line 213) unchanged — that's compare behavior, stays accessed via the compare module (`eu`). The `from inbound_verify import expected_undelivered as eu` import stays for this.
- [ ] **Step 4: runtime.py — SITE_UNDELIVERED_FILE from domain**
In `inbound_verify/runtime.py`:
- Add to imports:
```python
from inbound_verify.domain import SITE_UNDELIVERED_FILE
```
- Replace (line ~448):
```python
DOWNLOAD_DIR, expected_undelivered.SITE_UNDELIVERED_FILE.format(name=site)
```
with:
```python
DOWNLOAD_DIR, SITE_UNDELIVERED_FILE.format(name=site)
```
- Leave `expected_undelivered.write_site_file(site)` (line ~446) and `expected_undelivered.main()` (line ~476) unchanged (compare behavior).
- [ ] **Step 5: Verify — compileall + import smoke + domain is leaf**
```bash
cd /d/projects/LogisticsHubIPA/InboundVerify
.venv/Scripts/python.exe -m compileall -q inbound_verify
.venv/Scripts/python.exe -c "import inbound_verify.domain as d; print('STATIONS:', [s['name'] for s in d.STATIONS]); print('_site_cfg(中通):', d._site_cfg('中通')['exp']); print('BAISHI_FILE:', d.BAISHI_FILE)"
.venv/Scripts/python.exe -c "import inbound_verify.cli.router, inbound_verify.cli.server, inbound_verify.store, inbound_verify.runtime, inbound_verify.expected_undelivered; print('import smoke OK')"
```
Expected: STATIONS lists `['中通', '顺心', '韵达', '安能']`; `_site_cfg('中通')['exp']` = `中通-应到货物数据.xlsx`; `BAISHI_FILE` = `百世-应到未到货物数据.xlsx`; `import smoke OK`.
- [ ] **Step 6: Black + commit (after user confirms)**
```bash
.venv/Scripts/python.exe -m black inbound_verify/domain.py inbound_verify/expected_undelivered.py inbound_verify/store.py inbound_verify/runtime.py
git add inbound_verify/domain.py inbound_verify/expected_undelivered.py inbound_verify/store.py inbound_verify/runtime.py
git commit -m "refactor: extract domain.py (shared site/file/colmap config) from expected_undelivered
Co-Authored-By: Claude <noreply@anthropic.com>"
```
---
## Task 3: Rename `expected_undelivered.py` → `compare.py`
**Files:**
- Move: `inbound_verify/expected_undelivered.py``inbound_verify/compare.py` (git mv)
- Modify: `inbound_verify/store.py`, `inbound_verify/runtime.py`, `inbound_verify/cli/router.py` (import + call-site rewrites)
**Interfaces:**
- Consumes: Task 2's `domain` (compare still uses it).
- Produces: module is `inbound_verify.compare`; all public names (`main`, `write_site_file`, `_read_business_dates`) unchanged. `expected_undelivered` no longer exists as a module name.
- [ ] **Step 1: git mv the module (history preserved)**
```bash
cd /d/projects/LogisticsHubIPA/InboundVerify
git mv inbound_verify/expected_undelivered.py inbound_verify/compare.py
```
- [ ] **Step 2: store.py — import compare instead of expected_undelivered**
In `inbound_verify/store.py`, replace:
```python
from inbound_verify import expected_undelivered as eu
```
with:
```python
from inbound_verify import compare
```
and replace the one call site (line ~213):
```python
return eu._read_business_dates(ALL_SITES + ["百世"]) or {}
```
with:
```python
return compare._read_business_dates(ALL_SITES + ["百世"]) or {}
```
(`_site_cfg` and `BAISHI_FILE` already come from `domain` after Task 2 — no change there.)
- [ ] **Step 3: runtime.py — import compare, fix write_site_file/main**
In `inbound_verify/runtime.py`, replace:
```python
from inbound_verify import expected_undelivered
```
with:
```python
from inbound_verify import compare
```
Replace `expected_undelivered.write_site_file(site)` (line ~446) → `compare.write_site_file(site)`.
Replace `(expected_undelivered.main() or True)` (line ~476) → `(compare.main() or True)`.
- [ ] **Step 4: cli/router.py — import compare, fix main()**
In `inbound_verify/cli/router.py`, replace:
```python
from inbound_verify import expected_undelivered
```
with:
```python
from inbound_verify import compare
```
Replace `expected_undelivered.main()` (line ~34, inside `run_undelivered_compare`) → `compare.main()`.
- [ ] **Step 5: Verify — no stale references + import smoke**
```bash
cd /d/projects/LogisticsHubIPA/InboundVerify
.venv/Scripts/python.exe -m compileall -q inbound_verify
grep -rn "expected_undelivered" inbound_verify || echo "no stale expected_undelivered refs OK"
.venv/Scripts/python.exe -c "import inbound_verify.compare, inbound_verify.cli.router, inbound_verify.cli.server, inbound_verify.store, inbound_verify.runtime; print('import smoke OK')"
```
Expected: compileall silent; grep prints `no stale expected_undelivered refs OK`; `import smoke OK`.
- [ ] **Step 6: Black + commit (after user confirms)**
```bash
.venv/Scripts/python.exe -m black inbound_verify
git add -A inbound_verify
git commit -m "refactor: rename expected_undelivered to compare
Co-Authored-By: Claude <noreply@anthropic.com>"
```
---
## Final end-to-end re-confirm (once, after all 3 tasks; requires backend restart + re-login)
After Task 3 commits, the running backend still has the old modules loaded. To confirm end-to-end behavior is unchanged on the refactored code:
- [ ] **Restart backend** (TaskStop current → clean ms-playwright/anneng orphans → `python -m inbound_verify.cli.server`), re-login all sites.
- [ ] **Re-run the gate from the Tier 1 manual test:** trigger `undelivered` for 顺心/中通/韵达/安能 via API → expect 4× success + 4× `*-未到数据.xlsx`; trigger `__compare__` → expect `output/应到未到数据.xlsx`; run `python -m inbound_verify.store ingest` → expect rows UPSERTed. All green = Tier 2 behavior-identical, done.
> If you want to skip the re-login cost: the per-task import-smoke gates already prove the import graph is correct and the changes are behavior-preserving moves. The e2e re-confirm is belt-and-suspenders.
---
## Self-Review (completed)
- **Spec coverage:** spec §8 Tier 2 — path dedup → Task 1; domain extract → Task 2; rename → compare → Task 3. config.py explicitly deferred (noted with rationale). All spec items addressed or consciously deferred.
- **Placeholder scan:** none — every step has exact code or exact commands with expected output. The domain.py content is the verbatim extracted block.
- **Type/name consistency:** `_site_cfg`, `BAISHI_FILE`, `SITE_UNDELIVERED_FILE`, `STATIONS`, `write_site_file`, `main`, `_read_business_dates` referenced consistently across tasks. Task 2 routes config to `domain` and leaves behavior (`_read_business_dates`, `write_site_file`, `main`) in compare — verified against store.py/runtime.py/router.py usages. Task 3 renames the module but preserves all public names.

View File

@@ -24,14 +24,14 @@ from inbound_verify import state_store
# 各站点模块(自动化测试 + 比对用;任务派发在 runtime # 各站点模块(自动化测试 + 比对用;任务派发在 runtime
from inbound_verify.sites import shunxin, baishi, zto, yunda, anneng from inbound_verify.sites import shunxin, baishi, zto, yunda, anneng
from inbound_verify import expected_undelivered from inbound_verify import compare
def run_undelivered_compare(): def run_undelivered_compare():
"""应到未到比对(全站点):调用 expected_undelivered,读 downloads/ 下的应到/实到 """应到未到比对(全站点):调用 compare读 downloads/ 下的应到/实到
数据,生成 output/应到未到数据.xlsx汇总报表 + 各站明细)。""" 数据,生成 output/应到未到数据.xlsx汇总报表 + 各站明细)。"""
print("\n▶ 开始执行【应到未到比对(全站点)】任务 ...") print("\n▶ 开始执行【应到未到比对(全站点)】任务 ...")
expected_undelivered.main() compare.main()
# ==================================================================== # ====================================================================

View File

@@ -26,7 +26,7 @@ from inbound_verify.domain import SITE_UNDELIVERED_FILE
from inbound_verify import state_store from inbound_verify import state_store
from inbound_verify.sites import shunxin, baishi, zto, yunda, anneng from inbound_verify.sites import shunxin, baishi, zto, yunda, anneng
from inbound_verify import expected_undelivered # dispatch 的 compare 任务用 from inbound_verify import compare # dispatch 的 compare 任务用
# 各网页站点首页 URL单一来源取自各站点模块 HOME_URL # 各网页站点首页 URL单一来源取自各站点模块 HOME_URL
SITES_CONFIG = { SITES_CONFIG = {
@@ -444,7 +444,7 @@ def _site_undelivered_handler(site):
(TASK_HANDLERS[(site, "actual")](ctx) is not False) if exp_ok else False (TASK_HANDLERS[(site, "actual")](ctx) is not False) if exp_ok else False
) )
if exp_ok and act_ok: if exp_ok and act_ok:
return expected_undelivered.write_site_file(site) return compare.write_site_file(site)
stale = os.path.join(DOWNLOAD_DIR, SITE_UNDELIVERED_FILE.format(name=site)) stale = os.path.join(DOWNLOAD_DIR, SITE_UNDELIVERED_FILE.format(name=site))
if os.path.exists(stale): if os.path.exists(stale):
os.remove(stale) os.remove(stale)
@@ -472,7 +472,7 @@ TASK_HANDLERS = {
("安能", "expected"): lambda ctx: anneng.anneng_expected_download(), ("安能", "expected"): lambda ctx: anneng.anneng_expected_download(),
("安能", "actual"): lambda ctx: anneng.anneng_actual_download(), ("安能", "actual"): lambda ctx: anneng.anneng_actual_download(),
("安能", "undelivered"): _site_undelivered_handler("安能"), ("安能", "undelivered"): _site_undelivered_handler("安能"),
("__compare__", "compare"): lambda ctx: (expected_undelivered.main() or True), ("__compare__", "compare"): lambda ctx: (compare.main() or True),
} }

View File

@@ -34,9 +34,7 @@ from inbound_verify.domain import (
_site_cfg, _site_cfg,
) # 站点 / 文件名配置(单一来源) ) # 站点 / 文件名配置(单一来源)
from inbound_verify import ( from inbound_verify import compare # _read_business_dates比对侧业务日期读取
expected_undelivered as eu,
) # _read_business_dates比对侧业务日期读取
SCHEMA_PATH = os.path.join(BASE_DIR, "schema.sql") SCHEMA_PATH = os.path.join(BASE_DIR, "schema.sql")
@@ -111,7 +109,7 @@ def init_schema():
# ============================== 解析辅助 ============================== # ============================== 解析辅助 ==============================
# 实到单号列 / 基号列映射(口径取自 expected_undelivered # 实到单号列 / 基号列映射(口径取自 domain与 STATIONS 对齐
# piece = 实到表里「每件」的单号列(扫描单号 / 子单号 / 复合串) # piece = 实到表里「每件」的单号列(扫描单号 / 子单号 / 复合串)
# waybill = 与应到运单号对齐的干净列(中通无干净列,由 piece 复合串 v[:-8] 推导) # waybill = 与应到运单号对齐的干净列(中通无干净列,由 piece 复合串 v[:-8] 推导)
# scan_time = 扫描时间列(缺失则不填,原始值仍在 raw # scan_time = 扫描时间列(缺失则不填,原始值仍在 raw
@@ -214,7 +212,7 @@ def _raw_row(row):
def _read_business_dates(): def _read_business_dates():
"""从状态库读各站本次业务日期(与报告口径一致;读不到返回空 dict""" """从状态库读各站本次业务日期(与报告口径一致;读不到返回空 dict"""
try: try:
return eu._read_business_dates(ALL_SITES + ["百世"]) or {} return compare._read_business_dates(ALL_SITES + ["百世"]) or {}
except Exception as e: except Exception as e:
print(f">> [warn] 读取业务日期失败(不影响入库): {e}") print(f">> [warn] 读取业务日期失败(不影响入库): {e}")
return {} return {}