From 7065b88269af1331a1fcb096191e7e6356c5a11a Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Thu, 23 Jul 2026 12:21:31 +0800 Subject: [PATCH] refactor: move flat modules into inbound_verify package (Tier 1, behavior-identical) Relocate 12 root .py modules into inbound_verify/ (sites/, cli/ subpackages). Rewrite all internal imports to package-qualified; drop the site_ prefix on the 5 site modules and their 28 call sites. Fix paths.py BASE_DIR to anchor at the project root. Add main() entry wrappers (cli/router, cli/server, store). No behavior change. Co-Authored-By: Claude --- .../cli/router.py | 41 +++++++------- server.py => inbound_verify/cli/server.py | 21 +++++--- .../expected_undelivered.py | 37 +++++++++---- paths.py => inbound_verify/paths.py | 3 +- runtime.py => inbound_verify/runtime.py | 54 +++++++++---------- .../sites/anneng.py | 27 ++++++---- .../sites/baishi.py | 14 +++-- .../sites/shunxin.py | 6 +-- .../sites/yunda.py | 6 +-- site_zto.py => inbound_verify/sites/zto.py | 6 +-- .../state_store.py | 2 +- db_store.py => inbound_verify/store.py | 10 ++-- 12 files changed, 134 insertions(+), 93 deletions(-) rename main_router.py => inbound_verify/cli/router.py (93%) rename server.py => inbound_verify/cli/server.py (93%) rename expected_undelivered.py => inbound_verify/expected_undelivered.py (95%) rename paths.py => inbound_verify/paths.py (86%) rename runtime.py => inbound_verify/runtime.py (93%) rename site_anneng.py => inbound_verify/sites/anneng.py (98%) rename site_baishi.py => inbound_verify/sites/baishi.py (97%) rename site_shunxin.py => inbound_verify/sites/shunxin.py (99%) rename site_yunda.py => inbound_verify/sites/yunda.py (99%) rename site_zto.py => inbound_verify/sites/zto.py (99%) rename state_store.py => inbound_verify/state_store.py (99%) rename db_store.py => inbound_verify/store.py (98%) diff --git a/main_router.py b/inbound_verify/cli/router.py similarity index 93% rename from main_router.py rename to inbound_verify/cli/router.py index 475f229..92ea662 100644 --- a/main_router.py +++ b/inbound_verify/cli/router.py @@ -11,8 +11,8 @@ import time import yaml -from paths import CONFIG_PATH -from runtime import ( +from inbound_verify.paths import CONFIG_PATH +from inbound_verify.runtime import ( APP_SITES, HEARTBEAT_INTERVAL, dispatch_task, @@ -20,15 +20,11 @@ from runtime import ( run_heartbeat, ) -import state_store +from inbound_verify import state_store # 各站点模块(自动化测试 + 比对用;任务派发在 runtime) -import site_shunxin -import site_baishi -import site_zto -import site_yunda -import site_anneng -import expected_undelivered +from inbound_verify.sites import shunxin, baishi, zto, yunda, anneng +from inbound_verify import expected_undelivered def run_undelivered_compare(): @@ -57,20 +53,20 @@ def run_automation_test(pages_map): # 不被模块内部"失败→重置→重试"机制掩盖。 flow_table = { "顺心": { - "expected": ("应到", site_shunxin.shunxin_expected_download_impl), - "actual": ("实到", site_shunxin.shunxin_actual_download_impl), + "expected": ("应到", shunxin.shunxin_expected_download_impl), + "actual": ("实到", shunxin.shunxin_actual_download_impl), }, "中通": { - "expected": ("应到", site_zto.zto_expected_download_impl), - "actual": ("实到", site_zto.zto_actual_download_impl), + "expected": ("应到", zto.zto_expected_download_impl), + "actual": ("实到", zto.zto_actual_download_impl), }, "韵达": { - "expected": ("应到", site_yunda.yunda_expected_download_impl), - "actual": ("实到", site_yunda.yunda_actual_download_impl), + "expected": ("应到", yunda.yunda_expected_download_impl), + "actual": ("实到", yunda.yunda_actual_download_impl), }, "安能": { - "expected": ("应到", site_anneng.anneng_expected_download_impl), - "actual": ("实到", site_anneng.anneng_actual_download_impl), + "expected": ("应到", anneng.anneng_expected_download_impl), + "actual": ("实到", anneng.anneng_actual_download_impl), }, } @@ -83,7 +79,7 @@ def run_automation_test(pages_map): continue for sx_idx, sx_page in enumerate(pages_map["顺心"], start=1): try: - tag = site_shunxin.shunxin_belonging(sx_page) + tag = shunxin.shunxin_belonging(sx_page) except Exception: tag = f"账号{sx_idx}" # 读不到归属地时用序号占位,不阻断测试 for flow_key in CROSS_TEST_SEQUENCE: @@ -102,7 +98,7 @@ def run_automation_test(pages_map): ( "百世", "应到未到", - site_baishi.baishi_download_undelivered_data_impl, + baishi.baishi_download_undelivered_data_impl, pages_map["百世"], "", ) @@ -343,5 +339,10 @@ def run_multi_site_daemon(): print("程序已退出。") -if __name__ == "__main__": +def main(): + """交互菜单模式入口。""" run_multi_site_daemon() + + +if __name__ == "__main__": + main() diff --git a/server.py b/inbound_verify/cli/server.py similarity index 93% rename from server.py rename to inbound_verify/cli/server.py index 6ade86f..3fd88ec 100644 --- a/server.py +++ b/inbound_verify/cli/server.py @@ -26,9 +26,9 @@ from fastapi import FastAPI, HTTPException from fastapi.responses import FileResponse from pydantic import BaseModel -from paths import DOWNLOAD_DIR, OUTPUT_DIR -import state_store -from runtime import ( +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, @@ -64,7 +64,9 @@ def _worker_loop(): # 【P1-2 重启自愈】worker 就绪后清理上轮遗留的 pending/running 僵尸任务 cleaned = state_store.fail_stale_tasks() if cleaned: - print(f">> [worker] 自愈:清理 {cleaned} 条遗留任务(pending/running → failed)") + print( + f">> [worker] 自愈:清理 {cleaned} 条遗留任务(pending/running → failed)" + ) print(">> [worker] 各站就绪,开始接收任务 ...") except Exception as e: worker_state["error"] = str(e) @@ -161,7 +163,9 @@ def create_task(req: TaskRequest): """提交任务 {site, kind} → 入队,返回 task_id。""" # 【P0】后端未就绪时直接拒绝,避免任务在 worker 启动前入队卡死 if not worker_state["ready"]: - raise HTTPException(status_code=409, detail="后端尚未就绪,请等待各站点登录完成后再操作") + raise HTTPException( + status_code=409, detail="后端尚未就绪,请等待各站点登录完成后再操作" + ) if (req.site, req.kind) not in TASK_HANDLERS: raise HTTPException(status_code=400, detail=f"无效任务: {req.site}/{req.kind}") task_id = state_store.create_task(req.site, req.kind) @@ -287,5 +291,10 @@ def download_data(filename: str): return FileResponse(path, filename=filename) +def main(): + """服务模式入口。传字符串导入路径(规范写法;不开 reload/workers 时进程内 import,行为等价)。""" + uvicorn.run("inbound_verify.cli.server:app", host="0.0.0.0", port=8000) + + if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=8000) + main() diff --git a/expected_undelivered.py b/inbound_verify/expected_undelivered.py similarity index 95% rename from expected_undelivered.py rename to inbound_verify/expected_undelivered.py index 7c36ebd..1b9fc2f 100644 --- a/expected_undelivered.py +++ b/inbound_verify/expected_undelivered.py @@ -63,7 +63,7 @@ def arrived_pieces_zhongtong(df): for v in df["运单号"]: v = str(v).strip() if len(v) > 8 and v[-4:].isdigit(): - res[v[:-8]].add(v) # 以完整复合串作为“已到单号”存入 + res[v[:-8]].add(v) # 以完整复合串作为“已到单号”存入 return res @@ -72,6 +72,7 @@ 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]): @@ -79,6 +80,7 @@ def arrived_pieces_by_cols(wb_col, piece_col): if m and s: res[m].add(s) return res + return parse @@ -87,9 +89,9 @@ STATIONS = [ "name": "中通", "exp": "中通-应到货物数据.xlsx", "act": "中通-实到货物数据.xlsx", - "exp_qty": "交接件数", # 应到件数口径:交接件数(非录单件数) - "exp_wb": "运单号", # 应到表运单号列(兼作去重键) - "exp_jd": "交接单号", # 未到数据需展示的交接单号 + "exp_qty": "交接件数", # 应到件数口径:交接件数(非录单件数) + "exp_wb": "运单号", # 应到表运单号列(兼作去重键) + "exp_jd": "交接单号", # 未到数据需展示的交接单号 "arrived_pieces": arrived_pieces_zhongtong, "columns": ["交接单号", "运单号", "总件数"], }, @@ -174,7 +176,7 @@ def process(name): # 实到件数(新口径)= 实到表单号去重数量(分组 运单->已到单号集合) arrived = cfg["arrived_pieces"](df_act) - act_pieces = sum(len(s) for s in arrived.values()) # 全局去重单号数 + act_pieces = sum(len(s) for s in arrived.values()) # 全局去重单号数 # 未到:逐运单比较,列出实际已到的单号(顺序号乱序,无法反推缺件序号) rows = [] @@ -301,7 +303,9 @@ def process_baishi(): # 应到/实到基数取自「扫描综合查询」应扫/已扫(到/接件扫描→当日), # 由 baishi_download_undelivered_data_impl 在同次导航里抓取并落 site_settings。 # 未抓取过则 get_setting 返回 "" → 视为无基数(报表显示「—」)。 - import state_store # 与 _read_business_dates 一致:比对模块纯离线,懒加载 + from inbound_verify import ( + state_store, + ) # 与 _read_business_dates 一致:比对模块纯离线,懒加载 def _to_int(v): v = (v or "").strip().replace(",", "") @@ -348,7 +352,7 @@ def _read_business_dates(include): """从状态库读各站业务日期(dispatch 下载成功时快照写入),供报告「数据日期」列。 4 站取 expected_business_date(报告按应到口径);百世取 undelivered_business_date。 从未下过的站返回空串(诚实留空,不反推)。""" - import state_store # lazy import:比对模块本身保持纯离线 + from inbound_verify import state_store # lazy import:比对模块本身保持纯离线 status = state_store.get_all_status() dates = {} @@ -534,8 +538,16 @@ def build_summary(ws, results, generated_at, dates=None): # 百世:未到明细已知;若已抓取应到/实到基数(扫描综合查询应扫/已扫)则填真实值 if s["应到件"] is not None and s["已到件"] is not None: srate = (s["未到件"] / s["应到件"]) if s["应到件"] else 0 - vals = [name, s["运单数"], s["应到件"], s["已到件"], - s["未到件"], srate, "—", "—"] + vals = [ + name, + s["运单数"], + s["应到件"], + s["已到件"], + s["未到件"], + srate, + "—", + "—", + ] else: vals = [name, s["运单数"], "—", "—", s["未到件"], "—", "—", "—"] else: @@ -564,7 +576,12 @@ def build_summary(ws, results, generated_at, dates=None): cell.fill = PatternFill("solid", fgColor=ZEBRA) if isinstance(v, (int, float)): cell.number_format = "0.0%" if i == 5 else "#,##0" - if i == 5 and s is not None and isinstance(v, (int, float)) and not isinstance(v, bool): + if ( + i == 5 + and s is not None + and isinstance(v, (int, float)) + and not isinstance(v, bool) + ): cell.fill = PatternFill("solid", fgColor=heat(srate)) ws.row_dimensions[r].height = 19 r += 1 diff --git a/paths.py b/inbound_verify/paths.py similarity index 86% rename from paths.py rename to inbound_verify/paths.py index 8d8c255..22083f4 100644 --- a/paths.py +++ b/inbound_verify/paths.py @@ -6,7 +6,8 @@ import os # 项目根目录(以本文件所在位置为基准,与从哪个目录启动脚本无关) -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +# __file__ = /inbound_verify/paths.py → 上两级 = 项目根 +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 统一的下载 / 输出目录 DOWNLOAD_DIR = os.path.join(BASE_DIR, "downloads") diff --git a/runtime.py b/inbound_verify/runtime.py similarity index 93% rename from runtime.py rename to inbound_verify/runtime.py index 1b8cd86..c8bd56c 100644 --- a/runtime.py +++ b/inbound_verify/runtime.py @@ -21,22 +21,18 @@ from datetime import datetime, timedelta import yaml from playwright.sync_api import sync_playwright -from paths import DOWNLOAD_DIR, CONFIG_PATH +from inbound_verify.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 任务用 +from inbound_verify import state_store +from inbound_verify.sites import shunxin, baishi, zto, yunda, anneng +from inbound_verify import expected_undelivered # dispatch 的 compare 任务用 # 各网页站点首页 URL(单一来源,取自各站点模块 HOME_URL) SITES_CONFIG = { - "顺心": site_shunxin.HOME_URL, - "百世": site_baishi.HOME_URL, - "中通": site_zto.HOME_URL, - "韵达": site_yunda.HOME_URL, + "顺心": shunxin.HOME_URL, + "百世": baishi.HOME_URL, + "中通": zto.HOME_URL, + "韵达": yunda.HOME_URL, } # 站点就绪特征:登录成功进入工作台后的标志性控件 @@ -114,8 +110,10 @@ def launch_anneng(app_path): anneng_env.pop("NODE_OPTIONS", None) port = _find_free_port() print(f">> 以调试模式启动【安能】应用(端口 {port}):{app_path}") - proc = subprocess.Popen([app_path, f"--remote-debugging-port={port}"], env=anneng_env) - site_anneng.set_cdp_port(port) + proc = subprocess.Popen( + [app_path, f"--remote-debugging-port={port}"], env=anneng_env + ) + anneng.set_cdp_port(port) if not _wait_cdp_up(port): raise RuntimeError( f"安能应用调试端口 {port} 未就绪——可能应用已在运行(单实例)," @@ -136,7 +134,7 @@ def probe_site_login(site_name, pages_map): if site_name not in pages_map: return False if site_name == "安能": - return site_anneng.anneng_ready() + return anneng.anneng_ready() if site_name == "顺心": return all( pg.locator(READY_SELECTORS["顺心"]).is_visible(timeout=500) @@ -333,7 +331,7 @@ def launch_and_prepare(debug_mode=False, debug_target=""): if "韵达" in pages_map: try: pages_map["韵达"].bring_to_front() - site_yunda.yunda_login(pages_map["韵达"]) + yunda.yunda_login(pages_map["韵达"]) except Exception as e: print(f" ⚠️ 韵达前置自动登录模块发生波动: {e}") @@ -403,9 +401,9 @@ def _dismiss_initial_popups(pages_map): bs_page = pages_map["百世"] bs_page.bring_to_front() print(">> 正在处理【百世】初始弹窗(阅读消息 / 配置检查 / 优惠券广告)...") - # 委托给 site_baishi 的专用清理:优惠券广告是全屏居中 modal, + # 委托给 baishi 的专用清理:优惠券广告是全屏居中 modal, # 关闭键为 .ant-modal-close(纯图标无文字),必须点它才能真正关掉。 - site_baishi.dismiss_baishi_popups(bs_page) + baishi.dismiss_baishi_popups(bs_page) print(" ✅ 【百世】初始弹窗处理完成。") except Exception: pass @@ -414,7 +412,7 @@ def _dismiss_initial_popups(pages_map): yd_page = pages_map["韵达"] yd_page.bring_to_front() print(">> 正在检查【韵达】音频设备授权提示...") - site_yunda.dismiss_audio_prompt(yd_page) + yunda.dismiss_audio_prompt(yd_page) except Exception: pass @@ -460,20 +458,20 @@ def _site_undelivered_handler(site): TASK_HANDLERS = { - ("顺心", "expected"): _web_handler("顺心", site_shunxin.shunxin_expected_download), - ("顺心", "actual"): _web_handler("顺心", site_shunxin.shunxin_actual_download), + ("顺心", "expected"): _web_handler("顺心", shunxin.shunxin_expected_download), + ("顺心", "actual"): _web_handler("顺心", shunxin.shunxin_actual_download), ("顺心", "undelivered"): _site_undelivered_handler("顺心"), ("百世", "undelivered"): _web_handler( - "百世", site_baishi.baishi_download_undelivered_data + "百世", baishi.baishi_download_undelivered_data ), - ("中通", "expected"): _web_handler("中通", site_zto.zto_expected_download), - ("中通", "actual"): _web_handler("中通", site_zto.zto_actual_download), + ("中通", "expected"): _web_handler("中通", zto.zto_expected_download), + ("中通", "actual"): _web_handler("中通", zto.zto_actual_download), ("中通", "undelivered"): _site_undelivered_handler("中通"), - ("韵达", "expected"): _web_handler("韵达", site_yunda.yunda_expected_download), - ("韵达", "actual"): _web_handler("韵达", site_yunda.yunda_actual_download), + ("韵达", "expected"): _web_handler("韵达", yunda.yunda_expected_download), + ("韵达", "actual"): _web_handler("韵达", yunda.yunda_actual_download), ("韵达", "undelivered"): _site_undelivered_handler("韵达"), - ("安能", "expected"): lambda ctx: site_anneng.anneng_expected_download(), - ("安能", "actual"): lambda ctx: site_anneng.anneng_actual_download(), + ("安能", "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), } diff --git a/site_anneng.py b/inbound_verify/sites/anneng.py similarity index 98% rename from site_anneng.py rename to inbound_verify/sites/anneng.py index e37f94a..3539441 100644 --- a/site_anneng.py +++ b/inbound_verify/sites/anneng.py @@ -1,4 +1,4 @@ -# site_anneng.py +# sites/anneng.py # # 安能全网门户(Electron 应用)—— 应到货物数据下载。 # @@ -7,7 +7,7 @@ # 本模块通过该端口的 CDP 驱动它;「进站交接单查询」「导出下载」等右侧 tab 是 # **独立 webContents**(远程网页),在 /json 里是独立目标。 # 也可脱离 main_router 独立运行(此时用默认端口 9222,需已自行启动应用): -# .venv/Scripts/python.exe site_anneng.py +# python -m inbound_verify.sites.anneng expected # 或 actual # # 应到 = 运单信息导出(每张交接单下应到的运单明细)。整体流程: # 主页(重庆鱼洞镇) → 运营管理 → 进站管理 → 进站交接单查询(tab) @@ -39,8 +39,8 @@ import yaml # Windows 控制台默认 GBK,打印中文/emoji 会崩,强制 UTF-8。 sys.stdout.reconfigure(encoding="utf-8") -from paths import DOWNLOAD_DIR, CONFIG_PATH -import state_store +from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH +from inbound_verify import state_store def with_retry(site_name, label, flow, reset, max_attempts=3): @@ -73,7 +73,7 @@ def with_retry(site_name, label, flow, reset, max_attempts=3): return False -CDP_PORT = 9222 # 默认端口(独立运行 site_anneng.py 时用);main_router 启动时会用 set_cdp_port 覆盖 +CDP_PORT = 9222 # 默认端口(独立运行(python -m inbound_verify.sites.anneng)时用);main_router 启动时会用 set_cdp_port 覆盖 POLL_INTERVAL = 0.5 DEFAULT_TIMEOUT = 25.0 @@ -973,6 +973,7 @@ def set_scan_date(cdp, placeholder, value, target_ymd=None): 返回 True 表示写入并校验成功,否则 False。 """ import re as _re + ph = json.dumps(placeholder) val_json = json.dumps(value) if target_ymd is None: @@ -998,7 +999,9 @@ def set_scan_date(cdp, placeholder, value, target_ymd=None): "(() => {const inp=[...document.querySelectorAll('input')]" f".find(i=>i.placeholder==={ph}); if(!inp) return false; inp.focus();" "let sel=true; try{ sel=document.execCommand('selectAll'); }catch(e){ try{inp.select();}catch(_){ sel=false; } }" - "let done=false; try{ done=document.execCommand('insertText',false," + val_json + "); }catch(e){ done=false; }" + "let done=false; try{ done=document.execCommand('insertText',false," + + val_json + + "); }catch(e){ done=false; }" "if(!done){ const s=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;" f" s.call(inp,{val_json}); inp.dispatchEvent(new Event('input',{{bubbles:true}})); }}" "return inp.value;})()" @@ -1016,7 +1019,9 @@ def set_scan_date(cdp, placeholder, value, target_ymd=None): return True time.sleep(0.4) # 全部尝试失败:告警,避免静默下成「今天」 - print(f" ⚠ set_scan_date 未能将 {placeholder} 设为目标日期 {target_ymd}(请检查 DatePicker 是否就绪)") + print( + f" ⚠ set_scan_date 未能将 {placeholder} 设为目标日期 {target_ymd}(请检查 DatePicker 是否就绪)" + ) return False @@ -1158,8 +1163,12 @@ def wait_scan_form_ready(cdp, timeout=20.0): f".find(i=>i.placeholder==={json.dumps(ph)}); if(!inp) return false; inp.focus(); return true;}})()" ) try: - wait_until(cdp, "!!document.querySelector('.ant-picker-panel')", - "预热面板打开", timeout=4.0) + wait_until( + cdp, + "!!document.querySelector('.ant-picker-panel')", + "预热面板打开", + timeout=4.0, + ) except Exception: pass cdp.eval( diff --git a/site_baishi.py b/inbound_verify/sites/baishi.py similarity index 97% rename from site_baishi.py rename to inbound_verify/sites/baishi.py index 64e1a86..91bec64 100644 --- a/site_baishi.py +++ b/inbound_verify/sites/baishi.py @@ -1,11 +1,11 @@ -# site_baishi.py +# sites/baishi.py import os import yaml from playwright.sync_api import sync_playwright -from paths import DOWNLOAD_DIR, CONFIG_PATH -import state_store +from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH +from inbound_verify import state_store def with_retry(site_name, label, flow, reset, max_attempts=3): @@ -181,8 +181,12 @@ def baishi_download_undelivered_data_impl(page): # 10(应扫) 11(已扫) 12(未扫) 13(率)。(与下方 nth(12) 未扫同源) try: _first_row = page.locator(".ant-table-tbody > tr").first - _exp_txt = _first_row.locator("td").nth(10).inner_text().strip().replace(",", "") - _arr_txt = _first_row.locator("td").nth(11).inner_text().strip().replace(",", "") + _exp_txt = ( + _first_row.locator("td").nth(10).inner_text().strip().replace(",", "") + ) + _arr_txt = ( + _first_row.locator("td").nth(11).inner_text().strip().replace(",", "") + ) def _to_int(v): try: diff --git a/site_shunxin.py b/inbound_verify/sites/shunxin.py similarity index 99% rename from site_shunxin.py rename to inbound_verify/sites/shunxin.py index 53cca49..e4f9645 100644 --- a/site_shunxin.py +++ b/inbound_verify/sites/shunxin.py @@ -1,4 +1,4 @@ -# site_shunxin.py +# sites/shunxin.py import os import re @@ -7,8 +7,8 @@ import yaml from datetime import datetime, timedelta import pandas as pd -from paths import DOWNLOAD_DIR, CONFIG_PATH -import state_store +from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH +from inbound_verify import state_store def with_retry(site_name, label, flow, reset, max_attempts=3): diff --git a/site_yunda.py b/inbound_verify/sites/yunda.py similarity index 99% rename from site_yunda.py rename to inbound_verify/sites/yunda.py index 8f70a3a..0306bba 100644 --- a/site_yunda.py +++ b/inbound_verify/sites/yunda.py @@ -1,4 +1,4 @@ -# site_yunda.py +# sites/yunda.py import os import re @@ -7,8 +7,8 @@ import yaml from datetime import datetime, timedelta import pandas as pd -from paths import DOWNLOAD_DIR, CONFIG_PATH -import state_store +from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH +from inbound_verify import state_store def with_retry(site_name, label, flow, reset, max_attempts=3): diff --git a/site_zto.py b/inbound_verify/sites/zto.py similarity index 99% rename from site_zto.py rename to inbound_verify/sites/zto.py index fa27cf5..4c78b95 100644 --- a/site_zto.py +++ b/inbound_verify/sites/zto.py @@ -1,4 +1,4 @@ -# site_zto.py +# sites/zto.py import os import re @@ -7,8 +7,8 @@ import yaml from datetime import datetime import pandas as pd -from paths import DOWNLOAD_DIR, CONFIG_PATH -import state_store +from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH +from inbound_verify import state_store def with_retry(site_name, label, flow, reset, max_attempts=3): diff --git a/state_store.py b/inbound_verify/state_store.py similarity index 99% rename from state_store.py rename to inbound_verify/state_store.py index 603946a..097a898 100644 --- a/state_store.py +++ b/inbound_verify/state_store.py @@ -9,7 +9,7 @@ import os import sqlite3 from datetime import datetime -from paths import STATE_DB_PATH +from inbound_verify.paths import STATE_DB_PATH # 登录态枚举 LOGIN_UNKNOWN = "unknown" # 尚未探测过 diff --git a/db_store.py b/inbound_verify/store.py similarity index 98% rename from db_store.py rename to inbound_verify/store.py index 3510ef0..451485c 100644 --- a/db_store.py +++ b/inbound_verify/store.py @@ -28,9 +28,11 @@ import psycopg import yaml from psycopg.types.json import Jsonb -from paths import BASE_DIR, CONFIG_PATH, DOWNLOAD_DIR +from inbound_verify.paths import BASE_DIR, CONFIG_PATH, DOWNLOAD_DIR -import expected_undelivered as eu # 复用站点 / 文件名 / 列映射 / 基号口径(单一来源) +from inbound_verify import ( + expected_undelivered as eu, +) # 复用站点 / 文件名 / 列映射 / 基号口径(单一来源) SCHEMA_PATH = os.path.join(BASE_DIR, "schema.sql") @@ -376,7 +378,7 @@ def ingest(site=None): # ============================== 命令行 ============================== -def _cli(): +def main(): cmd = sys.argv[1] if len(sys.argv) > 1 else "all" site = sys.argv[2] if len(sys.argv) > 2 else None if cmd == "createdb": @@ -395,4 +397,4 @@ def _cli(): if __name__ == "__main__": - _cli() + main()