windscada_overview_build.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  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. import windscada_serve as S # 组件模块: 取数函数与在线页**同一实现**
  43. from src.windscada.ui import snapshot as _snap
  44. from src.ontology import mcp_server as _omc
  45. def _codes():
  46. db = json.loads(pathlib.Path(_omc.OBJ_PATH).read_text(encoding='utf-8'))
  47. return sorted(k.split('/')[-1] for k in db if k.startswith('alarmcode/'))
  48. ctx = dict(fleet_view=S.fleet_view, curve_view=S.curve_view, vibcms_results=S.vibcms_results,
  49. survey=__import__('src.ontology.maintenance', fromlist=['survey']).survey,
  50. ont=_omc, alarm_codes=_codes, turbine_problems=S.turbine_problems,
  51. single_problem=S.single_problem, _redact=S._redact, _jclean=S._jclean,
  52. _proj=S._proj, _REQ=S._REQ)
  53. html, rep = _snap.bake('zh', ctx, noindex=False)
  54. dest = pathlib.Path(out) if out else (P.store(farm) / 'index.html')
  55. dest.parent.mkdir(parents=True, exist_ok=True)
  56. dest.write_text(html, encoding='utf-8')
  57. print(f'已写 {P.rel(dest)}: {len(html):,} B · 烤入 {rep.get("keys", rep)}')
  58. try:
  59. from src import derived_manifest as DM
  60. DM.record(P.out_root(farm), {dest.relative_to(P.out_root(farm)).as_posix():
  61. 'scripts/windscada_overview_build.py (与组件 /v2/snapshot 同一烘焙器: '
  62. 'fleet/curves/vibcms/survey/ontology 响应烤进单文件页)'},
  63. by='windscada_overview_build')
  64. print(' 已自登记 → _derived_manifest.json')
  65. except Exception as e:
  66. print(f' [i] 自登记跳过: {type(e).__name__}: {e}')
  67. return 0
  68. def main() -> int:
  69. ap = argparse.ArgumentParser(description='总览「全场状态」页 (windscada/index.html) 的观澜自算生成端')
  70. ap.add_argument('--farm', default=None)
  71. ap.add_argument('--out', default=None)
  72. ap.add_argument('--status', action='store_true')
  73. a = ap.parse_args()
  74. if a.status:
  75. return status(a.farm)
  76. return build(a.farm, a.out)
  77. if __name__ == '__main__':
  78. for _s in (sys.stdout, sys.stderr):
  79. try:
  80. _s.reconfigure(errors='replace')
  81. except Exception:
  82. pass
  83. sys.exit(main())