windscada_overview_build.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. r"""`windscada/index.html`(总览「全场状态」页)的**观澜自算**生成端(用户令 2026-09-19)。
  4. ## 为什么需要它
  5. `/detail/v2` 的「全场状态总览」是一个 `<iframe src="/static/index.html">`,而该路径直接落到
  6. `outputs/<场>/windscada/index.html`。这件此前是**随包件**(全库 0 处写入方)⇒ 清过产物、只从
  7. `data/raw` 重算的机器上它必然不在位,页面里那块就显示"产物不在位"的说明页 —— 用户报的
  8. "某一块没有计算输出"就有它。
  9. 用户令「所有的计算均要形成观澜的源代码,确保别的电脑装完系统运行正常」⇒ 用**包内既有**的
  10. 快照烘焙器把这一页由观澜自己算出来(同一套取数函数 + 同一套前端,不是另写一份页面):
  11. src/windscada/ui/snapshot.py::bake() ← 组件服务 `/v2/snapshot` 用的就是它
  12. ctx 里的取数函数 = scripts/windscada_serve.py 的 fleet_view/curve_view/vibcms_results/…
  13. 产出仍是一张**单文件**页面(接口响应烤进页面 + 运行时拦截 fetch 优先快照兜底),所以离线也能开。
  14. 用法:
  15. python scripts/windscada_overview_build.py # 生成并自登记
  16. python scripts/windscada_overview_build.py --status # 只报现状
  17. python scripts/windscada_overview_build.py --out <路径>
  18. """
  19. from __future__ import annotations
  20. import argparse
  21. import json
  22. import pathlib
  23. import sys
  24. import time
  25. ROOT = pathlib.Path(__file__).resolve().parents[1]
  26. sys.path.insert(0, str(ROOT))
  27. sys.path.insert(0, str(ROOT / 'scripts'))
  28. from src import paths as P # noqa: E402
  29. def status(farm: str | None = None) -> int:
  30. p = P.store(farm) / 'index.html'
  31. if not p.is_file():
  32. print(json.dumps(dict(path=P.rel(p), exists=False,
  33. note='不在位 —— /detail/v2 的「全场状态总览」iframe 会显示"产物不在位"; '
  34. '跑本器可由观澜自算生成'), ensure_ascii=False))
  35. return 0
  36. st = p.stat()
  37. print(json.dumps(dict(path=P.rel(p), exists=True, bytes=st.st_size,
  38. mtime=time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(st.st_mtime))),
  39. ensure_ascii=False))
  40. return 0
  41. def build(farm: str | None = None, out: str | None = None) -> int:
  42. # ★P13:旧后端已删 ⇒ 取数改指算法服务同一套实现
  43. from app_algorithmModel.app_algorithmModel_guanlan.service import fleet_views as S
  44. # ★P13:单文件烘焙页随旧页 /detail 退役(快照器已删)
  45. from src.ontology import mcp_server as _omc
  46. def _codes():
  47. db = json.loads(pathlib.Path(_omc.OBJ_PATH).read_text(encoding='utf-8'))
  48. return sorted(k.split('/')[-1] for k in db if k.startswith('alarmcode/'))
  49. ctx = dict(fleet_view=S.fleet_view, curve_view=S.curve_view, vibcms_results=S.vibcms_results,
  50. survey=__import__('src.ontology.maintenance', fromlist=['survey']).survey,
  51. ont=_omc, alarm_codes=_codes, turbine_problems=S.turbine_problems,
  52. single_problem=S.single_problem, _redact=S._redact, _jclean=S._jclean,
  53. _proj=S._proj, _REQ=S._REQ)
  54. print("[i] 单文件烘焙页已随旧页退役:本生成端不再产出 outputs/<场>/windscada/index.html;"
  55. "页面数据请走在线接口(Vue 工作台 / 契约 /api/*)。")
  56. return 0
  57. dest = pathlib.Path(out) if out else (P.store(farm) / 'index.html')
  58. dest.parent.mkdir(parents=True, exist_ok=True)
  59. dest.write_text(html, encoding='utf-8')
  60. print(f'已写 {P.rel(dest)}: {len(html):,} B · 烤入 {rep.get("keys", rep)}')
  61. try:
  62. from src import derived_manifest as DM
  63. DM.record(P.out_root(farm), {dest.relative_to(P.out_root(farm)).as_posix():
  64. 'scripts/windscada_overview_build.py (与组件 /v2/snapshot 同一烘焙器: '
  65. 'fleet/curves/vibcms/survey/ontology 响应烤进单文件页)'},
  66. by='windscada_overview_build')
  67. print(' 已自登记 → _derived_manifest.json')
  68. except Exception as e:
  69. print(f' [i] 自登记跳过: {type(e).__name__}: {e}')
  70. return 0
  71. def main() -> int:
  72. ap = argparse.ArgumentParser(description='总览「全场状态」页 (windscada/index.html) 的观澜自算生成端')
  73. ap.add_argument('--farm', default=None)
  74. ap.add_argument('--out', default=None)
  75. ap.add_argument('--status', action='store_true')
  76. a = ap.parse_args()
  77. if a.status:
  78. return status(a.farm)
  79. return build(a.farm, a.out)
  80. if __name__ == '__main__':
  81. for _s in (sys.stdout, sys.stderr):
  82. try:
  83. _s.reconfigure(errors='replace')
  84. except Exception:
  85. pass
  86. sys.exit(main())