|
@@ -0,0 +1,130 @@
|
|
|
|
|
+#!/usr/bin/env python3
|
|
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
|
|
+r"""一条命令重算全部 (2026-09-12) —— 把散在各脚本里的重算链按依赖顺序串起来。
|
|
|
|
|
+
|
|
|
|
|
+## 为什么要有它
|
|
|
|
|
+
|
|
|
|
|
+"从零重算"原本是六七条命令、顺序还不能错 (产物齐了本体层才跑得动, 本体层跑完才能拷随包件…),
|
|
|
|
|
+手工执行容易漏步、也说不清"到底重算了什么"。本脚本把顺序、依赖、验收锚点全写死在这里。
|
|
|
|
|
+
|
|
|
|
|
+## 顺序 (为什么是这个顺序)
|
|
|
|
|
+
|
|
|
|
|
+ ① 放数据 place_raw_data.py --scope full (给了 --src 才跑; 同尺寸自动跳过)
|
|
|
|
|
+ ② 三门台账 rebuild_from_raw.py 报警/工单/油样
|
|
|
|
|
+ ③ SCADA 侧 rebuild_from_raw.py --scada 10 个构建器, 逐台读 ~14 GB, 约 15 分钟
|
|
|
|
|
+ ④ 月度派生件 windscada_monthly_build.py 能从 raw 重算的月表(与随包件逐值对齐才落盘)
|
|
|
|
|
+ ⑤ 补齐缺件 products_restore_missing.py 包内没有生成端的那批(只补缺, 不覆盖重算件)
|
|
|
|
|
+ ↑ 必须在 ⑥ 之前: 本体层的 populate/chain_ingest 要吃这些 L1 产物
|
|
|
|
|
+ ⑥ 重启服务 guanlan.py stop/serve chain_ingest 要从 /api/fleet 取链盘
|
|
|
|
|
+ ⑦ 本体层 kb_ingest → populate → chain_ingest → trend_ingest → retrieval → refresh_params
|
|
|
|
|
+ ⑧ 验收 ontology.audit + rebuild_from_raw --verify (可选) + 页面对照
|
|
|
|
|
+
|
|
|
|
|
+## 用法
|
|
|
|
|
+
|
|
|
|
|
+ python scripts/rebuild_all.py # 全套 (会跑 SCADA 那 15 分钟)
|
|
|
|
|
+ python scripts/rebuild_all.py --skip-scada # 只换台账时的常用档
|
|
|
|
|
+ python scripts/rebuild_all.py --src F:\temp\如东风场数据 # 连"放数据"一起
|
|
|
|
|
+ python scripts/rebuild_all.py --no-restart # 不碰服务(自己管)
|
|
|
|
|
+ python scripts/rebuild_all.py --with-verify # 末尾加等价验收(需随包基线在位)
|
|
|
|
|
+ python scripts/rebuild_all.py --dry-run # 只打印计划
|
|
|
|
|
+"""
|
|
|
|
|
+from __future__ import annotations
|
|
|
|
|
+
|
|
|
|
|
+import argparse
|
|
|
|
|
+import os
|
|
|
|
|
+import pathlib
|
|
|
|
|
+import subprocess
|
|
|
|
|
+import sys
|
|
|
|
|
+import time
|
|
|
|
|
+
|
|
|
|
|
+ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
|
|
|
+PY = sys.executable
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def step_cmd(name, cmd, tolerate=()):
|
|
|
|
|
+ """tolerate: 允许的退出码 (不算失败)。"""
|
|
|
|
|
+ return (name, cmd, tuple(tolerate))
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def build_plan(a) -> list:
|
|
|
|
|
+ plan = []
|
|
|
|
|
+ if a.src:
|
|
|
|
|
+ plan.append(step_cmd('① 放数据', [PY, 'scripts/place_raw_data.py', '--src', a.src, '--scope', 'full']))
|
|
|
|
|
+ plan.append(step_cmd('② 三门台账', [PY, 'scripts/rebuild_from_raw.py']))
|
|
|
|
|
+ if not a.skip_scada:
|
|
|
|
|
+ plan.append(step_cmd('③ SCADA 侧 10 个构建器 (约 15 分钟)', [PY, 'scripts/rebuild_from_raw.py', '--scada']))
|
|
|
|
|
+ plan.append(step_cmd('④ 月度派生件', [PY, 'scripts/windscada_monthly_build.py']))
|
|
|
|
|
+ plan.append(step_cmd('⑤ 补齐"包内没有生成端"的产物', [PY, 'scripts/products_restore_missing.py']))
|
|
|
|
|
+ if not a.no_restart:
|
|
|
|
|
+ plan.append(step_cmd('⑥ 重启服务 (stop)', [PY, 'guanlan.py', 'stop']))
|
|
|
|
|
+ # guanlan.py serve 在"有模块 DOWN"时**故意返回 1**(degraded 是正常状态: 如本机 Ollama 没模型),
|
|
|
|
|
+ # 所以这一步单独允许退出码 1, 由 tolerate={...} 标注。
|
|
|
|
|
+ plan.append(step_cmd('⑥ 重启服务 (serve)', [PY, 'guanlan.py', 'serve'], tolerate=(1,)))
|
|
|
|
|
+ plan.append(step_cmd('⑦ 本体: 码表/手册/文档', [PY, '-m', 'src.ontology.kb_ingest']))
|
|
|
|
|
+ plan.append(step_cmd('⑦ 本体: 铺开判级与台账', [PY, '-m', 'src.ontology.populate']))
|
|
|
|
|
+ plan.append(step_cmd('⑦ 本体: 决策链进度', [PY, '-m', 'src.ontology.chain_ingest']))
|
|
|
|
|
+ plan.append(step_cmd('⑦ 本体: 在升/闭环证据', [PY, '-m', 'src.ontology.trend_ingest']))
|
|
|
|
|
+ plan.append(step_cmd('⑦ 本体: 检索索引', [PY, '-c', 'from src.ontology import retrieval as R; R.build(use_vec=False)']))
|
|
|
|
|
+ plan.append(step_cmd('⑦ 本体: 实机参数表', [PY, '-c', 'from src.ontology.maintenance import refresh_params as f; print(f())']))
|
|
|
|
|
+ plan.append(step_cmd('⑧ 本体审计 (期望 0 问题)', [PY, '-m', 'src.ontology.audit']))
|
|
|
|
|
+ if a.with_verify:
|
|
|
|
|
+ # 退出码 4 = 有"无法归类"的差异; 本包已知的差异是"油样 102 行源件不在现场包里"(属人工项),
|
|
|
|
|
+ # 所以容忍 4 但把完整报告打给用户看 —— 真出现新差异时报告里会点名。
|
|
|
|
|
+ plan.append(step_cmd('⑧ 台账等价验收 (rc=4 表示有需人工看的差异, 属已知)',
|
|
|
|
|
+ [PY, 'scripts/rebuild_from_raw.py', '--verify'], tolerate=(4,)))
|
|
|
|
|
+ return plan
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def main() -> int:
|
|
|
|
|
+ ap = argparse.ArgumentParser()
|
|
|
|
|
+ ap.add_argument('--src', default=None, help='现场数据包目录 (给了就先跑 place_raw_data --scope full)')
|
|
|
|
|
+ ap.add_argument('--skip-scada', action='store_true', help='跳过 SCADA 侧 10 个构建器 (没换 10min 数据时用)')
|
|
|
|
|
+ ap.add_argument('--no-restart', action='store_true', help='不自动重启服务')
|
|
|
|
|
+ ap.add_argument('--with-verify', action='store_true', help='末尾加等价验收')
|
|
|
|
|
+ ap.add_argument('--dry-run', action='store_true', help='只打印计划')
|
|
|
|
|
+ a = ap.parse_args()
|
|
|
|
|
+
|
|
|
|
|
+ plan = build_plan(a)
|
|
|
|
|
+ print(f'重算全部 · {len(plan)} 步 · 根目录 {ROOT}')
|
|
|
|
|
+ print(f' 跳过 SCADA: {a.skip_scada} | 重启服务: {not a.no_restart} | 等价验收: {a.with_verify}\n')
|
|
|
|
|
+ if a.dry_run:
|
|
|
|
|
+ for name, cmd, tol in plan:
|
|
|
|
|
+ print(f' {name:34s} {" ".join(cmd[1:])[:90]}')
|
|
|
|
|
+ print('\n(dry-run, 未执行)')
|
|
|
|
|
+ return 0
|
|
|
|
|
+
|
|
|
|
|
+ # 子进程环境: 与 guanlan.py 给服务的一致 —— 中文 Windows 控制台是 GBK, 不设的话
|
|
|
|
|
+ # 子脚本 print 里的 ✔/✗ 会抛 UnicodeEncodeError, 明明干成了却以退出码 1 结束。
|
|
|
|
|
+ env = dict(os.environ, PYTHONUTF8='1', PYTHONIOENCODING='utf-8')
|
|
|
|
|
+ results, t_all = [], time.time()
|
|
|
|
|
+ for i, (name, cmd, tol) in enumerate(plan, 1):
|
|
|
|
|
+ print(f'\n{"=" * 78}\n[{i}/{len(plan)}] {name}\n$ {" ".join(cmd)}\n{"=" * 78}', flush=True)
|
|
|
|
|
+ t0 = time.time()
|
|
|
|
|
+ rc = subprocess.run(cmd, cwd=str(ROOT), env=env).returncode
|
|
|
|
|
+ dt = time.time() - t0
|
|
|
|
|
+ ok = (rc == 0) or (rc in tol)
|
|
|
|
|
+ results.append((name, rc, dt, ok, tol))
|
|
|
|
|
+ if not ok:
|
|
|
|
|
+ print(f'\n[X] 第 {i} 步失败 (退出码 {rc}): {name} —— 后面的步骤依赖它, 先停下。')
|
|
|
|
|
+ break
|
|
|
|
|
+ if rc != 0:
|
|
|
|
|
+ print(f'\n (退出码 {rc} 属预期: {name} 的 degraded 是正常状态 —— 例如本机模型未启动)')
|
|
|
|
|
+
|
|
|
|
|
+ print(f'\n{"=" * 78}\n汇总 (总耗时 {time.time() - t_all:.0f}s)')
|
|
|
|
|
+ for name, rc, dt, ok, tol in results:
|
|
|
|
|
+ tag = 'OK' if rc == 0 else (f'OK(rc={rc})' if ok else 'FAIL')
|
|
|
|
|
+ print(f' [{tag:9s}] {name:34s} {dt:7.1f}s')
|
|
|
|
|
+ n_fail = sum(1 for *_, ok, _ in results if not ok)
|
|
|
|
|
+ if n_fail:
|
|
|
|
|
+ print(f'\n结论: {n_fail} 步失败 —— 按上面的日志处理后再重跑本命令 (各步都是幂等的)')
|
|
|
|
|
+ return 1
|
|
|
|
|
+ print('\n结论: 全部完成。核验: .venv\\Scripts\\python.exe -m src.ontology.maintenance '
|
|
|
|
|
+ '(数据层/机理层的条数与末次更新); 浏览器 http://127.0.0.1:28084/detail/')
|
|
|
|
|
+ return 0
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == '__main__':
|
|
|
|
|
+ for _s in (sys.stdout, sys.stderr):
|
|
|
|
|
+ try: _s.reconfigure(errors='replace')
|
|
|
|
|
+ except Exception: pass
|
|
|
|
|
+ sys.exit(main())
|