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