Files
InboundVerify/docs/superpowers/plans/2026-07-23-tier1-package-move.md
Misaka_Company a07b9b435b docs: update README/CLAUDE.md for package layout; add Tier 1 spec and plan
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>
2026-07-23 12:21:45 +08:00

23 KiB
Raw Permalink Blame History

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 name inbound-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 InboundVerify git 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_verify package + 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__.py
  • inbound_verify/sites/__init__.py
  • inbound_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 .py modules → package locations (see Step 1)
  • Modify: inbound_verify/paths.py (anchor), and import lines + call sites in every moved module
  • Modify: entry main() in cli/router.py, cli/server.py, store.py

Interfaces:

  • Consumes: the scaffold from Task 1 (package importable + editable install active).
  • Produces: a fully functional inbound_verify package invokable via python -m inbound_verify.cli.router, python -m inbound_verify.cli.server, python -m inbound_verify.store, or the three console_scripts. Old root .py files 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 by py_compile of 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 ~1431):

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 ~3349) 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交互菜单/ serverFastAPI 服务)
├── 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 ~5871), 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.pypython -m inbound_verify.cli.router (or inbound-verify)
  • server.pypython -m inbound_verify.cli.server (or inbound-verify-server)
  • db_store.py createdb|init|ingest|allpython -m inbound_verify.store createdb|init|ingest|all (or inbound-verify-db ...)
  • site_anneng.py expected|actualpython -m inbound_verify.sites.anneng expected|actual
  • black/py_compile targets → 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 39; §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.