""" 数据流结构分析 — CLI 入口 用法: python scripts/ops/analyze_dataflow.py python scripts/ops/analyze_dataflow.py --date-from 2025-01-01 --date-to 2025-01-15 python scripts/ops/analyze_dataflow.py --limit 100 --tables settlement_records,payment_transactions """ from __future__ import annotations import argparse import os from datetime import datetime from pathlib import Path def build_parser() -> argparse.ArgumentParser: """ 构造 CLI 参数解析器。 参数: --date-from 数据获取起始日期 (YYYY-MM-DD) --date-to 数据获取截止日期 (YYYY-MM-DD) --limit 每端点最大记录数 (默认 200) --tables 要分析的表名列表 (逗号分隔,缺省=全部) """ parser = argparse.ArgumentParser( description="数据流结构分析 — 采集 API JSON 和 DB 表结构", ) parser.add_argument( "--date-from", type=str, default=None, help="数据获取起始日期 (YYYY-MM-DD)", ) parser.add_argument( "--date-to", type=str, default=None, help="数据获取截止日期 (YYYY-MM-DD)", ) parser.add_argument( "--limit", type=int, default=200, help="每端点最大记录数 (默认 200)", ) parser.add_argument( "--tables", type=str, default=None, help="要分析的表名列表 (逗号分隔,缺省=全部)", ) return parser def resolve_output_dir() -> Path: """ 确定输出目录: 1. 优先读取环境变量 SYSTEM_ANALYZE_ROOT 2. 回退到 docs/reports/ 3. 确保目录存在(自动创建) """ env_root = os.environ.get("SYSTEM_ANALYZE_ROOT") if env_root: out = Path(env_root) else: out = Path("docs/reports") out.mkdir(parents=True, exist_ok=True) return out def generate_output_filename(dt: "datetime") -> str: """生成输出文件名:dataflow_YYYY-MM-DD_HHMMSS.md""" return f"dataflow_{dt.strftime('%Y-%m-%d_%H%M%S')}.md" def main() -> None: """ 串联采集流程: 1. 解析 CLI 参数 2. 加载环境变量(.env 分层叠加) 3. 构造 AnalyzerConfig 4. 调用 collect_all_tables() 执行采集 5. 调用 dump_collection_results() 落盘 6. 输出采集摘要到 stdout """ from datetime import date as _date, datetime as _datetime from dotenv import load_dotenv # ── 1. 解析 CLI 参数 ── parser = build_parser() args = parser.parse_args() # ── 2. 加载环境变量(分层叠加:根 .env < ETL .env < 环境变量) ── # override=False 保证后加载的不覆盖先加载的环境变量 # 先加载根 .env(最低优先级) load_dotenv(Path(".env"), override=False) # 再加载 ETL 专属 .env(中优先级) load_dotenv(Path("apps/etl/connectors/feiqiu/.env"), override=False) # 真实环境变量(最高优先级)已自动存在于 os.environ # ── 3. 构造 AnalyzerConfig ── date_from = _date.fromisoformat(args.date_from) if args.date_from else None date_to = _date.fromisoformat(args.date_to) if args.date_to else None tables = [t.strip() for t in args.tables.split(",")] if args.tables else None output_dir = resolve_output_dir() from dataflow_analyzer import AnalyzerConfig, ODS_SPECS, collect_all_tables, dump_collection_results config = AnalyzerConfig( date_from=date_from, date_to=date_to, limit=args.limit, tables=tables, output_dir=output_dir, pg_dsn=os.environ.get("DATABASE_URL") or os.environ.get("PG_DSN", ""), api_base=os.environ.get("API_BASE", ""), api_token=os.environ.get("API_TOKEN", ""), store_id=os.environ.get("STORE_ID", ""), ) # ── 4. 执行采集(使用本模块的 ODS_SPECS) ── results = collect_all_tables(config, specs=ODS_SPECS) # ── 5. 落盘 ── paths = dump_collection_results(results, output_dir) # ── 6. 输出采集摘要 ── now = _datetime.now() filename = generate_output_filename(now) ok = sum(1 for r in results if r.error is None) fail = len(results) - ok total_records = sum(r.record_count for r in results) print(f"\n{'='*60}") print(f"数据流结构分析完成") print(f"{'='*60}") print(f" 输出目录: {output_dir}") print(f" 报告文件名: {filename}") print(f" 分析表数: {len(results)} ({ok} 成功, {fail} 失败)") print(f" 总记录数: {total_records}") print(f" 落盘路径:") for category, p in paths.items(): print(f" {category}: {p}") print(f"{'='*60}") if __name__ == "__main__": main()