set_baseline.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. r"""重建/查看"台账等价验收"的**基线**(2026-09-18 现场问题驱动)。
  4. ## 为什么需要它
  5. `rebuild_all.py` 最后一步跑 `rebuild_from_raw.py --verify`:拿当前产物与**基线**逐值比对,
  6. 证明"重算能复现同一批数字"。基线是 `outputs/<场>/windscada/_pre_rebuild_20260911/` 下的 4 件
  7. (`alarms` / `workorders` / `oil_samples_index` / `temp_monthly`)。
  8. **2026-09-17 用户令「清除产物」把它一并真删了**(该命令不留备份)⇒ 之后每次重算最后一步都报
  9. `rc=5 找不到随包基线 ⇒ 无法做等价验收("没验收"不是"通过")`,门户上就显示成"重算失败"。
  10. 本器把"重建基线"变成一条命令:在当前产物**已核实无误**的前提下快照这 4 件,并写清来历。
  11. ## 用法
  12. python scripts/set_baseline.py --check # 看基线在不在(不看盘面以外的推断)
  13. python scripts/set_baseline.py # 用**当前在位产物**快照成基线(同路径, 覆盖)
  14. 退出码: 0 成功/在位 · 2 缺源件
  15. """
  16. from __future__ import annotations
  17. import argparse
  18. import datetime as dt
  19. import json
  20. import pathlib
  21. import shutil
  22. import sys
  23. ROOT = pathlib.Path(__file__).resolve().parents[1]
  24. sys.path.insert(0, str(ROOT))
  25. from src import paths as P # noqa: E402
  26. FILES = ('alarms.parquet', 'workorders.parquet', 'oil_samples_index.parquet', 'temp_monthly.parquet')
  27. BASE_NAME = '_pre_rebuild_20260911'
  28. def base_dir(farm: str | None = None) -> pathlib.Path:
  29. return P.store(farm) / BASE_NAME
  30. def main() -> int:
  31. ap = argparse.ArgumentParser(description='重建/查看 台账等价验收基线')
  32. ap.add_argument('--check', action='store_true', help='只看基线在不在')
  33. ap.add_argument('--farm', default=None)
  34. a = ap.parse_args()
  35. farm = a.farm or P.farm()
  36. store = P.store(farm)
  37. b = base_dir(farm)
  38. have = {n: (b / n) for n in FILES}
  39. print(f'== 等价验收基线 · {P.rel(b)} ==')
  40. for n, p in have.items():
  41. print(f' [{"OK " if p.is_file() else "缺 "}] {n:28s}'
  42. + (f' {p.stat().st_size:>12,} B' if p.is_file() else ''))
  43. if a.check:
  44. ok = all(p.is_file() for p in have.values())
  45. print(f' 结论: {"基线在位, 重算最后一步可做等价验收" if ok else "基线缺失 ⇒ 重算最后一步会 rc=5(没验收); 跑本命令(不带 --check)重建"}')
  46. return 0 if ok else 2
  47. miss = [n for n, p in have.items() if not (store / n).is_file()]
  48. if miss:
  49. print(f' [X] 当前产物里缺 {miss} —— 先重算产出它们, 再建基线')
  50. return 2
  51. b.mkdir(parents=True, exist_ok=True)
  52. for n in FILES:
  53. shutil.copy2(store / n, b / n)
  54. (b / '_来源说明.json').write_text(json.dumps(dict(
  55. why='rebuild_all 最后一步「台账等价验收」要一份标准答案做逐值比对; 基线目录被「清除产物」真删后, '
  56. '该步 rc=5「没验收」⇒ 门户上显示重算失败。',
  57. what='本目录 4 件 = 快照时**在位且已核实**的产物(alarms/workorders/oil_samples_index/temp_monthly)。',
  58. nature='★ 这是**回归基线**(证明将来的重算能复现同一批数字),不是历史交付基线。',
  59. built_at=dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  60. rebuilt_by='scripts/set_baseline.py',
  61. verify_by='python scripts/rebuild_from_raw.py --verify(全部通过即基线有效)',
  62. ), ensure_ascii=False, indent=1) + '\n', encoding='utf-8')
  63. print(f' 已重建基线: {P.rel(b)}(4 件 + _来源说明.json)')
  64. print(' 复验: python scripts/rebuild_from_raw.py --verify')
  65. return 0
  66. if __name__ == '__main__':
  67. for _s in (sys.stdout, sys.stderr):
  68. try:
  69. _s.reconfigure(errors='replace')
  70. except Exception:
  71. pass
  72. sys.exit(main())