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>
23 KiB
Tier 1: Package Move 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: Move all 12 flat root-level Python modules into an installable inbound_verify/ package (sites/, cli/ subpackages), rewrite every internal import to package-qualified form, and expose three console_script entry points — with zero behavior change.
Architecture: Pure mechanical relocation (git mv preserves history) + import rewrite + paths.py anchor fix + entry-point main() wrappers + pyproject.toml packaging. No logic changes. Module names kept for heavily-referenced modules (state_store, expected_undelivered, runtime, paths) to avoid call-site churn; only leaf entries (db_store→store, main_router→cli/router, server→cli/server) and site files (drop site_ prefix) are renamed.
Tech Stack: Python ≥3.10, setuptools (PEP 517/621), pip editable install, psycopg3, FastAPI/uvicorn, Playwright.
Global Constraints
- Python ≥ 3.10 (
requires-python = ">=3.10"in pyproject). - Package import name
inbound_verify; distribution nameinbound-verify. - Dependency floors (verbatim from spec):
pandas>=2.0.0,playwright>=1.40.0,openpyxl>=3.1.0,PyYAML>=6.0,websocket-client>=1.0.0,fastapi>=0.110.0,uvicorn>=0.27.0,apscheduler>=3.10.0,psycopg[binary]>=3.1. - No test suite (user decision). Verification =
compileall+ import smoke + grep-for-stale-refs + DB connectivity. No pytest. - Behavior must not change in Tier 1 — pure move.
- No auto-commit/push. Every commit step below runs ONLY after the user explicitly says "提交/commit". Commit messages in English.
- Black-format every changed
.py(global rule). - All changes are inside the
InboundVerifygit submodule; the parent repo pointer bump is a separate parent-repo step, out of scope. - All commands run from the
InboundVerify/directory using the venv interpreter.venv/Scripts/python.exe(Windows; no activation needed).
Reference spec: docs/superpowers/specs/2026-07-23-package-restructure-design.md (§3 mapping table, §4 paths anchor, §5 import rules, §6 entry/packaging, §7 verification gate).
File Structure (what each file becomes responsible for)
inbound_verify/
├── __init__.py # empty (package marker)
├── paths.py # path anchors → PROJECT ROOT (one dir above package)
├── runtime.py # orchestration core (unchanged logic)
├── state_store.py # SQLite state (unchanged; name kept)
├── expected_undelivered.py# offline compare (unchanged; name kept — Tier 2 renames to compare)
├── store.py # PostgreSQL persist + main() (was db_store.py)
├── sites/
│ ├── __init__.py # empty
│ ├── shunxin.py # (was site_shunxin.py)
│ ├── baishi.py # (was site_baishi.py)
│ ├── zto.py # (was site_zto.py)
│ ├── yunda.py # (was site_yunda.py)
│ └── anneng.py # (was site_anneng.py)
└── cli/
├── __init__.py # empty
├── router.py # interactive menu + main() (was main_router.py)
└── server.py # FastAPI service + main() (was server.py)
Root keeps: pyproject.toml (new), config.yaml, config.example.yaml, schema.sql, requirements.txt, README.md, CLAUDE.md, docs/, downloads/, output/, state/.
Task 1: Package scaffold + pyproject + editable install
Files:
- Create:
inbound_verify/__init__.py,inbound_verify/sites/__init__.py,inbound_verify/cli/__init__.py - Create:
pyproject.toml
Interfaces:
-
Produces: an importable (near-empty)
inbound_verifypackage + console_script registration. The flat root scripts remain 100% functional after this task (untouched). -
Step 1: Create package marker files
Create three empty files:
inbound_verify/__init__.pyinbound_verify/sites/__init__.pyinbound_verify/cli/__init__.py
Each is a single comment line:
# inbound_verify package
- Step 2: Write pyproject.toml
Create pyproject.toml:
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "inbound-verify"
version = "0.1.0"
description = "物流到货数据自动下载与应到未到核对工具"
requires-python = ">=3.10"
dependencies = [
"pandas>=2.0.0",
"playwright>=1.40.0",
"openpyxl>=3.1.0",
"PyYAML>=6.0",
"websocket-client>=1.0.0",
"fastapi>=0.110.0",
"uvicorn>=0.27.0",
"apscheduler>=3.10.0",
"psycopg[binary]>=3.1",
]
[project.scripts]
inbound-verify = "inbound_verify.cli.router:main"
inbound-verify-server = "inbound_verify.cli.server:main"
inbound-verify-db = "inbound_verify.store:main"
[tool.setuptools.packages.find]
include = ["inbound_verify*"]
- Step 3: Editable-install into the existing venv
Run:
.venv/Scripts/python.exe -m pip install -e .
Expected: Successfully installed inbound-verify-0.1.0 (deps already satisfied from earlier install — no network needed).
- Step 4: Verify package imports
Run:
.venv/Scripts/python.exe -c "import inbound_verify, inbound_verify.sites, inbound_verify.cli; print('package OK')"
Expected output: package OK
- Step 5: Commit (only after user confirms)
git add inbound_verify/__init__.py inbound_verify/sites/__init__.py inbound_verify/cli/__init__.py pyproject.toml
git commit -m "chore: scaffold inbound_verify package and pyproject
Co-Authored-By: Claude <noreply@anthropic.com>"
Do NOT commit until the user says to.
Task 2: Atomic move — relocate, rewrite imports, fix anchor, wire mains, verify
Files:
- Move (git mv): all 12 root
.pymodules → package locations (see Step 1) - Modify:
inbound_verify/paths.py(anchor), and import lines + call sites in every moved module - Modify: entry
main()incli/router.py,cli/server.py,store.py
Interfaces:
- Consumes: the scaffold from Task 1 (package importable + editable install active).
- Produces: a fully functional
inbound_verifypackage invokable viapython -m inbound_verify.cli.router,python -m inbound_verify.cli.server,python -m inbound_verify.store, or the three console_scripts. Old root.pyfiles are gone. The flat root scripts no longer exist — invocation switches to package form.
Why this is one task: in a flat-import codebase, moving
paths.py(imported by everyone) immediately breaks every importer until ALL moves + rewrites are complete. There is no intermediate state that imports cleanly, so the whole move is one atomic unit verified by the gate at the end. Each file-edit step below is followed bypy_compileof that file to catch syntax errors as we go.
- Step 1: Relocate all 12 modules with git mv (history preserved)
From the InboundVerify/ directory:
git mv paths.py inbound_verify/paths.py
git mv runtime.py inbound_verify/runtime.py
git mv state_store.py inbound_verify/state_store.py
git mv expected_undelivered.py inbound_verify/expected_undelivered.py
git mv db_store.py inbound_verify/store.py
git mv main_router.py inbound_verify/cli/router.py
git mv server.py inbound_verify/cli/server.py
git mv site_shunxin.py inbound_verify/sites/shunxin.py
git mv site_baishi.py inbound_verify/sites/baishi.py
git mv site_zto.py inbound_verify/sites/zto.py
git mv site_yunda.py inbound_verify/sites/yunda.py
git mv site_anneng.py inbound_verify/sites/anneng.py
After this the tree is temporarily broken (imports unresolved) — expected. Continue.
- Step 2: Fix paths.py anchor to point at project root
In inbound_verify/paths.py, replace:
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
with:
# __file__ = <root>/inbound_verify/paths.py → 上两级 = 项目根
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
(DOWNLOAD_DIR/OUTPUT_DIR/CONFIG_PATH/STATE_DB_PATH lines stay unchanged — they derive from BASE_DIR.)
Verify syntax:
.venv/Scripts/python.exe -m py_compile inbound_verify/paths.py
Expected: no output (success).
- Step 3: Rewrite imports in state_store.py
In inbound_verify/state_store.py, replace:
from paths import STATE_DB_PATH
with:
from inbound_verify.paths import STATE_DB_PATH
Verify: .venv/Scripts/python.exe -m py_compile inbound_verify/state_store.py → no output.
- Step 4: Rewrite imports + call sites in runtime.py
In inbound_verify/runtime.py, replace the import block:
from paths import DOWNLOAD_DIR, CONFIG_PATH
import state_store
import site_shunxin
import site_baishi
import site_zto
import site_yunda
import site_anneng
import expected_undelivered # dispatch 的 compare 任务用
with:
from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH
from inbound_verify import state_store
from inbound_verify.sites import shunxin, baishi, zto, yunda, anneng
from inbound_verify import expected_undelivered # dispatch 的 compare 任务用
Then drop the site_ prefix at every call site (28 references — 18 in this file). Apply these 5 replacements (all occurrences):
site_shunxin.→shunxin.site_baishi.→baishi.site_zto.→zto.site_yunda.→yunda.site_anneng.→anneng.
Affected lines in runtime.py (for reference, all must change): 36, 37, 38, 39, 118, 139, 336, 408, 417, 463, 464, 467, 469, 470, 472, 473, 475, 476.
state_store. and expected_undelivered. call sites stay UNCHANGED (names kept). Confirm the key handler block now reads:
TASK_HANDLERS = {
("顺心", "expected"): _web_handler("顺心", shunxin.shunxin_expected_download),
("顺心", "actual"): _web_handler("顺心", shunxin.shunxin_actual_download),
("顺心", "undelivered"): _site_undelivered_handler("顺心"),
("百世", "undelivered"): _web_handler(
"百世", baishi.baishi_download_undelivered_data
),
("中通", "expected"): _web_handler("中通", zto.zto_expected_download),
("中通", "actual"): _web_handler("中通", zto.zto_actual_download),
("中通", "undelivered"): _site_undelivered_handler("中通"),
("韵达", "expected"): _web_handler("韵达", yunda.yunda_expected_download),
("韵达", "actual"): _web_handler("韵达", yunda.yunda_actual_download),
("韵达", "undelivered"): _site_undelivered_handler("韵达"),
("安能", "expected"): lambda ctx: anneng.anneng_expected_download(),
("安能", "actual"): lambda ctx: anneng.anneng_actual_download(),
("安能", "undelivered"): _site_undelivered_handler("安能"),
("__compare__", "compare"): lambda ctx: (expected_undelivered.main() or True),
}
Verify: .venv/Scripts/python.exe -m py_compile inbound_verify/runtime.py → no output.
- Step 5: Rewrite imports in expected_undelivered.py
This module has TWO lazy import state_store statements (inside functions). Replace each occurrence of:
import state_store
with:
from inbound_verify import state_store
(There is no from paths import here — the module has its own BASE/DOWNLOADS/OUTPUT constants; that dedup is Tier 2, not now.)
Verify: .venv/Scripts/python.exe -m py_compile inbound_verify/expected_undelivered.py → no output.
- Step 6: Rewrite imports + rename _cli→main in store.py
In inbound_verify/store.py, replace:
from paths import BASE_DIR, CONFIG_PATH, DOWNLOAD_DIR
with:
from inbound_verify.paths import BASE_DIR, CONFIG_PATH, DOWNLOAD_DIR
Replace:
import expected_undelivered as eu # 复用站点 / 文件名 / 列映射 / 基号口径(单一来源)
with:
from inbound_verify import expected_undelivered as eu # 复用站点 / 文件名 / 列映射 / 基号口径(单一来源)
(eu. call sites stay unchanged.)
Rename the CLI entry: replace the function definition:
def _cli():
with:
def main():
And at the bottom replace:
if __name__ == "__main__":
_cli()
with:
if __name__ == "__main__":
main()
Verify: .venv/Scripts/python.exe -m py_compile inbound_verify/store.py → no output.
- Step 7: Rewrite imports in all 5 site modules
In each of inbound_verify/sites/{shunxin,baishi,zto,yunda,anneng}.py, replace:
from paths import DOWNLOAD_DIR, CONFIG_PATH
import state_store
with:
from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH
from inbound_verify import state_store
(Each site module has exactly these two internal imports; state_store. call sites unchanged.)
Verify all five:
.venv/Scripts/python.exe -m py_compile inbound_verify/sites/shunxin.py inbound_verify/sites/baishi.py inbound_verify/sites/zto.py inbound_verify/sites/yunda.py inbound_verify/sites/anneng.py
Expected: no output.
- Step 8: Rewrite imports + call sites + add main() in cli/router.py
In inbound_verify/cli/router.py, replace the import block (lines ~14–31):
from paths import CONFIG_PATH
from runtime import (
APP_SITES,
HEARTBEAT_INTERVAL,
dispatch_task,
launch_and_prepare,
run_heartbeat,
)
import state_store
# 各站点模块(自动化测试 + 比对用;任务派发在 runtime)
import site_shunxin
import site_baishi
import site_zto
import site_yunda
import site_anneng
import expected_undelivered
with:
from inbound_verify.paths import CONFIG_PATH
from inbound_verify.runtime import (
APP_SITES,
HEARTBEAT_INTERVAL,
dispatch_task,
launch_and_prepare,
run_heartbeat,
)
from inbound_verify import state_store
# 各站点模块(自动化测试 + 比对用;任务派发在 runtime)
from inbound_verify.sites import shunxin, baishi, zto, yunda, anneng
from inbound_verify import expected_undelivered
Drop the site_ prefix at the 10 call sites (apply the same 5 replacements as Step 4). Affected lines: 60, 61, 64, 65, 68, 69, 72, 73, 86, 105. (expected_undelivered.main() at line 38 stays unchanged.)
Add an entry function and update the __main__ guard. Replace:
if __name__ == "__main__":
run_multi_site_daemon()
with:
def main():
"""交互菜单模式入口。"""
run_multi_site_daemon()
if __name__ == "__main__":
main()
Verify: .venv/Scripts/python.exe -m py_compile inbound_verify/cli/router.py → no output.
- Step 9: Rewrite imports + add main() in cli/server.py
In inbound_verify/cli/server.py, replace:
from paths import DOWNLOAD_DIR, OUTPUT_DIR
import state_store
from runtime import (
HEARTBEAT_INTERVAL,
TASK_HANDLERS,
dispatch_task,
launch_and_prepare,
run_heartbeat,
)
with:
from inbound_verify.paths import DOWNLOAD_DIR, OUTPUT_DIR
from inbound_verify import state_store
from inbound_verify.runtime import (
HEARTBEAT_INTERVAL,
TASK_HANDLERS,
dispatch_task,
launch_and_prepare,
run_heartbeat,
)
Replace the bottom entry block:
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
with:
def main():
"""服务模式入口。传字符串导入路径(规范写法;不开 reload/workers 时进程内 import,行为等价)。"""
uvicorn.run("inbound_verify.cli.server:app", host="0.0.0.0", port=8000)
if __name__ == "__main__":
main()
Verify: .venv/Scripts/python.exe -m py_compile inbound_verify/cli/server.py → no output.
- Step 10: Clean up stale filename comments
Cosmetic but keeps grep clean (Step 12 depends on it). In each inbound_verify/sites/*.py, update the line-1 header # site_xxx.py → # sites/xxx.py. In inbound_verify/sites/anneng.py, update the standalone-run comment near the top:
# .venv/Scripts/python.exe site_anneng.py
→
# python -m inbound_verify.sites.anneng expected # 或 actual
And the comment at the CDP_PORT line referencing "独立运行 site_anneng.py" — update to "独立运行(python -m inbound_verify.sites.anneng)".
- Step 11: Black-format all changed files
.venv/Scripts/python.exe -m black inbound_verify
Expected: reformatted ... / left unchanged lines, exit 0.
- Step 12: VERIFICATION GATE — run all five checks
12a. compileall (syntax across whole package):
.venv/Scripts/python.exe -m compileall inbound_verify
Expected: no errors.
12b. Import smoke (catches every wrong import path / missed rewrite):
.venv/Scripts/python.exe -c "import inbound_verify.cli.router, inbound_verify.cli.server, inbound_verify.store, inbound_verify.runtime, inbound_verify.state_store; print('import smoke OK')"
Expected: import smoke OK. (The three entries transitively import sites + expected_undelivered.)
12c. No stale site_ references:
grep -rn "site_shunxin\|site_baishi\|site_zto\|site_yunda\|site_anneng" inbound_verify || echo "no stale site_ refs OK"
Expected: no stale site_ refs OK.
12d. No stale bare flat imports:
grep -rnE "^from paths import|^from runtime import|^import site_|^import state_store$|^import expected_undelivered$" inbound_verify || echo "no stale flat imports OK"
Expected: no stale flat imports OK.
12e. paths anchor points at project root + DB still connects:
.venv/Scripts/python.exe -c "from inbound_verify.paths import BASE_DIR; print('BASE_DIR', BASE_DIR)"
.venv/Scripts/python.exe -c "from inbound_verify.store import _connect, _load_pg_config; c=_load_pg_config(); conn=_connect(c['dbname']); print('DB OK', conn.info.server_version); conn.close()"
Expected: BASE_DIR prints the InboundVerify project root (the dir containing config.yaml); DB OK <pg version>.
If 12b fails with ModuleNotFoundError for a site module, run
.venv/Scripts/python.exe -m pip install -e .again (editable finder refresh) and retry. If 12d still shows a line, that import was missed — rewrite it per Step 4/8 rules.
- Step 13: Commit (only after user confirms)
git add -A inbound_verify
git commit -m "refactor: move flat modules into inbound_verify package (Tier 1, behavior-identical)
- relocate 12 root .py into inbound_verify/ (sites/, cli/ subpackages)
- rewrite all internal imports to package-qualified
- fix paths.py BASE_DIR to anchor at project root
- add main() entry wrappers; register console_scripts
- drop site_ prefix on site modules; keep state_store/expected_undelivered names
Co-Authored-By: Claude <noreply@anthropic.com>"
Do NOT commit until the user says to. This commit is the safety baseline; the manual end-to-end test (spec §7 gate) runs against this state before any Tier 2/Tier 3 work.
Task 3: Docs sync (README + CLAUDE.md)
Files:
- Modify:
README.md(§二 directory tree, §三 env prep, §五 run) - Modify:
CLAUDE.md(常用命令 section)
Interfaces:
-
Consumes: the completed package from Task 2 (docs must describe the real new layout/commands).
-
Produces: documentation matching the new invocation model. No code impact.
-
Step 1: Update README §二 directory tree
Replace the tree block (README lines ~33–49) with the actual new layout:
InboundVerify/
├── pyproject.toml # 打包 + 依赖 + console_scripts
├── inbound_verify/ # 源码包
│ ├── paths.py runtime.py state_store.py expected_undelivered.py store.py
│ ├── sites/ shunxin / baishi / zto / yunda / anneng
│ └── cli/ router(交互菜单)/ server(FastAPI 服务)
├── config.example.yaml / config.yaml
├── schema.sql
├── requirements.txt # pyproject 的静态镜像
├── downloads/ output/ state/
└── docs/
- Step 2: Update README §三 env prep — add editable install
In the env-prep command block (README lines ~58–71), after pip install -r requirements.txt, add:
# 4. 以可编辑模式安装本包(注册 inbound-verify 等命令)
pip install -e .
(renumber the subsequent cp config.example.yaml config.yaml step).
- Step 3: Update README §五 run — new commands
Replace python main_router.py with:
# 交互菜单(任选其一)
python -m inbound_verify.cli.router
# 或装包后:inbound-verify
- Step 4: Update CLAUDE.md 常用命令
In the 常用命令 section, change every .venv/Scripts/python.exe <module>.py to the package form:
main_router.py→python -m inbound_verify.cli.router(orinbound-verify)server.py→python -m inbound_verify.cli.server(orinbound-verify-server)db_store.py createdb|init|ingest|all→python -m inbound_verify.store createdb|init|ingest|all(orinbound-verify-db ...)site_anneng.py expected|actual→python -m inbound_verify.sites.anneng expected|actualblack/py_compiletargets → package paths (e.g.-m black inbound_verify)
Add a one-liner near the top of that section: 首次/拉取新代码后需 .venv/Scripts/python.exe -m pip install -e .。
- Step 5: Verify docs render + commands are real
grep -n "main_router.py\|python server.py\|python db_store.py\|site_anneng.py\|site_shunxin" README.md CLAUDE.md || echo "no stale old-path commands OK"
Expected: no stale old-path commands OK (every old invocation updated).
- Step 6: Commit (only after user confirms)
git add README.md CLAUDE.md
git commit -m "docs: update README and CLAUDE.md for package layout and console_scripts
Co-Authored-By: Claude <noreply@anthropic.com>"
Self-Review (completed)
- Spec coverage: spec §3 (layout) → Task 1+2; §4 (paths anchor) → Task 2 Step 2; §5 (import rules) → Task 2 Steps 3–9; §6 (entry/packaging) → Task 1 Step 2 + Task 2 Steps 6/8/9; §7 (verification gate) → Task 2 Step 12; §10 (docs) → Task 3. All covered.
- Placeholder scan: none — every step has exact code or an exact command with expected output. The 28 call-site rewrites are given as a deterministic prefix-drop rule + enumerated line numbers + verification grep (complete, not a placeholder).
- Type/name consistency: kept-module names (
state_store,expected_undelivered,runtime,paths) used consistently across all import rewrites and call sites; renamed entries (store,cli/router,cli/server) consistent with pyproject[project.scripts].main()signature consistent across router/server/store and console_scripts.