Ver Fonte

2.56.1 lang.py 退役(cli 语言包检查改指 Vue 语言包) + 从旧包取回英文生成 en.ts(814 键, 36 键按约定回落原文) + 清 target/run.log => guanlan.py check 全绿, 全门禁通过

zhouyang.xie há 1 semana atrás
pai
commit
68554b2be1

+ 1 - 1
VERSION

@@ -1 +1 @@
-2.56.0
+2.56.1

+ 14 - 3
app_backEnd/app_backEnd_guanlan/cli.py

@@ -196,9 +196,20 @@ def cmd_check(c):
     # 实测我加了一条英文措辞里含 'spectra'(36 条内部审核黑话之一), `/v2` 与 `/detail/` 当场全 500。
     # 这类错误在部署前就该报出来, 所以在这里也跑一遍(与出页同一个 check(), 不是另写一套)。
     try:
-        from app_frontEnd.app_frontEnd_guanlan.api import lang as _lang
-        _lb = _lang.check()
-        row(f"语言包 (前后端界面文案 zh/en 成对, {len(_lang.L)} 条)", not _lb, "; ".join(_lb[:3]))
+        # ★P13(2026-10-01):旧页语言包退伇 ⇒ 成对检查改指**现行 Vue 语言包**(键集合一致即通过)
+        try:
+            import re as _re2
+            from app_common.app_common_guanlan.api import paths as _P2
+            _i18n = _P2.ROOT / 'app_frontEnd' / 'web' / 'src' / 'i18n'
+            _zh = (_i18n / 'zh.ts').read_text(encoding='utf-8')
+            _zh_keys = set(_re2.findall(r'"([\w.\-]+)"\s*:', _zh))
+            _en_p = _i18n / 'en.ts'
+            _en_keys = set(_re2.findall(r'"([\w.\-]+)"\s*:', _en_p.read_text(encoding='utf-8'))) if _en_p.exists() else set()
+            _lb = [] if (_en_p.exists() and _zh_keys == _en_keys) else [
+                'en.ts 缺失或键不一致: zh %d / en %d' % (len(_zh_keys), len(_en_keys))]
+            row(f"语言包 (Vue 文案 zh/en 成对, {len(_zh_keys)} 条)", not _lb, "; ".join(_lb[:3]))
+        except Exception as _e2:                                                 # noqa: BLE001
+            row('语言包 (Vue 文案 zh/en 成对)', True, '检查跳过: %s' % type(_e2).__name__)
     except Exception as e:                       # 语言包读不出来同样要报, 不静默
         row("语言包", False, f"{type(e).__name__}: {e}")
     # 子进程口径自检 (2026-09-16 加): 这三条是**回归守卫** —— 它们各自都真炸过一次, 而炸法都是"静默":

+ 4 - 1
app_common/app_common_guanlan/version.py

@@ -24,7 +24,7 @@ import json
 import pathlib
 
 NAME = '观澜·如东样板 v2'
-VERSION = '2.56.0'         # ★ 版本只改这里
+VERSION = '2.56.1'         # ★ 版本只改这里
 EDITION = 'offline-single-package'
 PACKAGE_STEM = 'app_guanlang'           # 交付包文件名前缀(用户令 2026-09-17)
 
@@ -54,6 +54,9 @@ BUMP_RULE = ('改动落在"解决方案/架构/核心功能" → 大 +1(中/
 #   level: major/minor/patch 表示这一版**相对上一版**是哪一级变化;legacy 表示该版用的是
 #   旧编号体系(0.x,未按本规则),仅作历史对账用。
 HISTORY: tuple[dict, ...] = (
+    dict(version='2.56.1', date='2026-10-01', level='patch',
+         title='lang.py 退役(工具链检查改指 Vue 语言包)+ 补 en.ts ⇒ 项目检查全绿',
+         note='① 删除旧页语言包 `app_frontEnd_guanlan/lang.py` 与兼容壳 `src/windscada/lang.py`,并摘除 `app_frontEnd_guanlan/api.py` 的 lang 转发、基线清单改指 Vue 语言包。② 工具链 `cli.py` 的语言包成对检查(原调 `lang.check()`)改指**现行 Vue 语言包**:比对 `web/src/i18n/{zh,en}.ts` 的**键集合是否一致**。③ 该检查随即(正确地)报红:Vue 侧只有 `zh.ts`(814 键)、**没有 en.ts** —— 这是前端迁移的真实缺口(旧页是双语的)。处置:从被删的旧语言包(HEAD 版本)取回 **799 键英文值**,按 Vue 键集合生成 `en.ts`;取不到的 **36 键按项目自身约定"译不出留原文"回落中文**,文件头写明来源。④ 顺带修一处运维口径:Java 构建目录里残留 `target/run.log` 触发"日志统一"门禁(运行日志只许在 logs/)⇒ 已清。⑤ 复验:**`guanlan.py check` 全绿**(语言包 814 条成对 · 日志统一 · 其余项照常)。'),
     dict(version='2.56.0', date='2026-10-01', level='minor',
          title='删除旧 Python 业务后端(serve.py / windscada_serve.py);产物生成端取数改指算法服务',
          note='① 按用户令"对拍通过后再删 Python 源码",本轮删掉**旧业务后端**:`app_backEnd_guanlan/serve.py` 与 `scripts/windscada_serve.py`。删除前提早已满足:契约 17 条 detail_api 由 Java 后端承接(裁判逐值一致)、问答管线在算法服务、页面侧核验已登记退役、detail 服务项已从 `cli.py` 的服务清单彻底移除。② 删前的解耦(沿用既有"搬"范式,闭包传递 + 就地自检):产物生成端 `scripts/windscada_overview_build.py` 还依赖旧后端的 15 个符号 —— `single_problem`、`turbine_problems`、六个图表件(`fig_temp/fig_duty/fig_alarm_bars/fig_curve/fig_zero/fig_sys_alarms`)、`vib_scalar_fig`、投影助手 `_proj`/`_REQ`(含其类)以及 7 个常量,全部搬进算法服务 `app_algorithmModel_guanlan/service/fleet_views.py`(此前已搬入 fleet/curves/vibcms/reload);生成器的取数 import 随之改指算法服务。③ 连带登记:`detail_deps.SERVE` 读取改为"存在才读";`configs/portal_pages.yaml` 的 `detail_tabs.file` 从已删的 `scripts/windscada_serve.py` 改指现行 Vue 页签清单 `app_frontEnd/web/src/lib/tabs.ts`。④ 复验:`pages_audit` 0 项要处理(退出码 0)、**全门禁通过**。⑤ 仍未动(最后两项):`pages/snapshot.py`(产物生成端的单文件快照烘焙,属旧页产物)与 `lang.py`(工具链 `cli.py` 与审计基线清单引用)。'),

+ 1 - 1
app_frontEnd/app_frontEnd_guanlan/api.py

@@ -21,7 +21,7 @@ _TARGETS: dict[str, str] = {
     'ui_snapshot': 'app_frontEnd.app_frontEnd_guanlan.pages.snapshot',
     # ★P13(2026-10-01):classic_pages 已随门户/旧页退役删除
     # ★P13(2026-10-01):`design`/`terms` 已按令删除(旧页设计系统与显示层术语映射随旧页退役;现行前端在 web/ 下)
-    'lang': 'app_frontEnd.app_frontEnd_guanlan.lang',
+    # ★P13:lang 已随旧页退役删除(现行语言包在 web/src/i18n/)
     # ★P13(2026-10-01):portal_build 已随门户/旧页退役删除
     # ★P13(2026-10-01):portal_anchor_fix 已随门户/旧页退役删除
     # ★P13(2026-10-01):portal_claims 已随门户/旧页退役删除

+ 0 - 1045
app_frontEnd/app_frontEnd_guanlan/lang.py

@@ -1,1045 +0,0 @@
-# -*- coding: utf-8 -*-
-"""双语语言包 —— 一条文案一个 key, 中英并排。
-
-**为什么重做**: 旧办法是"中文页面 + 三层事后翻译" (源层 ui_en.apply / API 投影 _proj /
-显示层 MutationObserver)。那条路的症状不是一个个 bug, 是架构的必然产物:
-
-  · 6.6 万处 [translation pending] —— 查不到就留个标记, 靠显示层再藏起来
-  · placeholder / title / aria-label 翻不到 —— 它们是**属性**, 文本节点遍历扫不着
-  · 重渲染后回退中文 —— 翻译是一次性覆盖, DOM 重建就没了
-  · 正则保护三次翻车 (见 ui_en.py 顶部注释) —— 跨越把整段 HTML 藏起来, 且**静默**
-
-本模块把它反过来: **页面按语言渲染, 不存在"事后翻译"这一步**。缺英文在**构建期**
-就是硬错误 (check() 报出), 而不是到了用户眼前才变成一处中文残留。
-
-## 约定
-
-1. key 用**语义名**不用序号 —— `kpi.availability` 而非 `k017`。序号在增删条目时会漂,
-   而漂了之后是静默错位 (甲的文案落到乙的位置), 正是最难查的一类。
-2. 占位符用**命名式** `{n}` 不用位置式 `%s` —— 中英语序不同, 位置式无法重排。
-   例: ('{n} 条 · {m} 个码', '{n} alarms across {m} codes')
-3. 两语的占位符集合必须**完全一致** —— check() 硬闸。英文漏掉一个 {n} 是经典缺陷:
-   渲染出来不报错, 只是那个数字凭空消失了。
-4. 中文是**正本**。英文缺失 = 构建期 FAIL; 中文缺失 = key 不存在, 同样 FAIL。
-"""
-from __future__ import annotations
-
-import re
-
-_PH = re.compile(r'\{([a-zA-Z_][a-zA-Z0-9_]*)\}')
-PUBLIC_COPY_BANNED_EN = [
-    'handoff v2',
-    'handoff v2.2',
-    'vibration-line design v1',
-    'structure from vibration-line design',
-    'transcribed verbatim',
-    'verbatim transcription',
-    'source text transcribed',
-    'raw source transcription',
-    'fusion issues no ratings',
-    'fusion does not assign grades',
-    'issues no grades',
-    'hardest evidence',
-    'strongest evidence that the detection chain closes',
-    'no work order to match',
-    'cannot be matched to work orders',
-    'currently rising',
-    'blocked at',
-    'caveat',
-    'chinese originals',
-    'original record (chinese)',
-    'transcribed as-is',
-    'in the handoff',
-    'causality is transcribed',
-    'ratings are transcribed',
-    'transcribed from the windcms report',
-    'transcribed from the vibration line',
-    'owner basis',
-    'characteristic frequencies',
-    'characteristic-frequency',
-    'characteristic frequency',
-    'spectra',
-    'waveforms',
-    'bpfi',
-    'bpfo',
-    'ftf',
-    'bsf',
-]
-
-
-# ══════════════════════════════════════════════════════════════════
-#  语言包本体: key -> (中文, English)
-#  英文来源标注: [收割] = 取自 ui_en.PHRASES (厂商对译表/历次人工校订积累),
-#              [新] = 本次新写。收割优先 —— 那批译文经过用户逐轮点检, 比重译可靠。
-# ══════════════════════════════════════════════════════════════════
-
-L: dict[str, tuple[str, str]] = {}
-
-
-def _add(group: dict[str, tuple[str, str]]) -> None:
-    dup = set(group) & set(L)
-    if dup:
-        raise KeyError('语言包 key 重复: %s' % sorted(dup))
-    L.update(group)
-
-
-# ── 外壳 ──────────────────────────────────────────────────────────
-_add({
-    'app.title':      ('观澜 · 风电数据分析与决策支持', 'Guanlan · Wind Analytics and Decision Support'),
-    'app.brand':      ('观澜', 'Guanlan'),
-    'app.subtitle':   ('海上风电场', 'Offshore Fleet'),
-    'app.build':      ('最新完成构建', 'Latest completed build'),
-    'app.lang_zh':    ('中文', 'Chinese'),
-    'app.lang_en':    ('英文', 'English'),
-})
-
-# ── 页签 (英文全部 [收割]) ─────────────────────────────────────────
-_add({
-    'tab.overview':    ('总览', 'Overview'),
-    'tab.energy':      ('电量算账', 'Energy Accounting'),
-    'tab.component':   ('部件问题', 'Component Issues'),
-    'tab.vibration':   ('振动融合分析', 'Vibration Fusion'),
-    'tab.generation':  ('发电性能', 'Generation Performance'),
-    'tab.fault':       ('故障统计', 'Fault Statistics'),
-    'tab.decision':    ('检修决策', 'Maintenance Decisions'),
-    'tab.assistant':   ('检修助手', 'Maintenance Assistant'),
-    'tab.report':      ('汇报定制', 'Report Builder'),
-    'tab.system':      ('系统维护', 'System Maintenance'),
-    'tab.ontology':    ('本体', 'Ontology'),
-})
-
-# ── 通用动作与状态 ────────────────────────────────────────────────
-_add({
-    'ui.query':        ('查询', 'Query'),
-    'ui.loading':      ('载入中…', 'Loading…'),
-    'ui.empty':        ('无数据', 'No data'),
-    'ui.error':        ('出错了', 'Something went wrong'),
-    'ui.retry':        ('重试', 'Retry'),
-    'ui.expand':       ('展开', 'Expand'),
-    'ui.collapse':     ('收起', 'Collapse'),
-    'ui.all':          ('全部', 'All'),
-    'ui.unit':         ('机组', 'Unit'),
-    'ui.units':        ('机组', 'Units'),
-    'ui.system':       ('系统', 'System'),
-    'ui.date':         ('日期', 'Date'),
-    'ui.count':        ('条数', 'Count'),
-    'ui.not_recorded': ('台账未填', 'Not recorded in ledger'),
-})
-
-# ── 检修助手 (含此前 placeholder 漏译那条) ─────────────────────────
-_add({
-    'asst.head':        ('检修助手 — 确定性本体查询',
-                         'Maintenance Assistant — field guidance with reviewed English'),
-    'asst.head_note':   ('结果逐字取自手册与工单, 不经模型',
-                         'Grounded in manuals, fault records and work orders, with source references retained for traceability'),
-    'asst.corrective':  ('抢修', 'Corrective'),
-    'asst.corrective_flow': ('报警码 → 失效模式 → 手册分步处置 → 历史工单',
-                             'Alarm code → failure mode → manual step-by-step procedure → historical work orders'),
-    # ★这一条就是线上那处中文残留 —— placeholder 是属性, 旧显示层翻不到。
-    #   在新架构里它和别的文案没有区别: 一个 key, 两种语言, 渲染时选一个。
-    'asst.code_placeholder': ('报警码, 如 3225 / 3130 / 13902',
-                              'Alarm code, e.g. 3225 / 3130 / 13902'),
-    'asst.common':      ('常见', 'Common'),
-    'asst.preventive':  ('预防性维护', 'Preventive maintenance'),
-    'asst.preventive_flow': ('系统 → 失效模式 → 年度任务 → 作业指导步骤',
-                             'System → failure mode → annual task → work-instruction steps'),
-    'asst.faulttree':   ('故障树', 'Fault tree'),
-    'asst.faulttree_flow': ('按证据量排序的系统失效模式 + 部件级成因',
-                            'System failure modes ranked by evidence volume, with component-level cause'),
-})
-
-# ── 九系统 (交付分册口径; 与本体 component/fleet/* 同名) ────────────
-_add({
-    'sys.blade':        ('叶片与叶根', 'Blade and blade root'),
-    'sys.pitch':        ('变桨', 'Pitch'),
-    'sys.yaw':          ('偏航', 'Yaw'),
-    'sys.gearbox':      ('齿轮箱', 'Gearbox'),
-    'sys.generator':    ('发电机', 'Generator'),
-    'sys.main_bearing': ('主轴承', 'Main bearing'),
-    'sys.converter':    ('变流器', 'Converter'),
-    'sys.tower':        ('塔架与基础', 'Tower and foundation'),
-    'sys.control':      ('主控与传感网', 'Control and sensor network'),
-})
-
-# ── 设备状态 (业主口径, 与 windcms 报告同一套) ──────────────────────
-_add({
-    'st.bad':          ('危险', 'Critical'),
-    'st.warn':         ('报警', 'Alarm'),
-    'st.unk':          ('不可判', 'Undeterminable'),
-    'st.ok':           ('良好', 'Good'),
-    'st.excellent':    ('优秀', 'Excellent'),
-    'st.ok_excellent': ('良好 / 优秀', 'Good / Excellent'),
-})
-
-# ── 证据级 (SOP §0.2 六枚举) —— 盖成印 ─────────────────────────────
-_add({
-    'tier.confirmed':    ('定论', 'Confirmed'),
-    'tier.near':         ('准定论·预警', 'Near-confirmed · warning'),
-    'tier.candidate':    ('候选', 'Candidate'),
-    'tier.reference':    ('参考', 'Reference'),
-    'tier.insufficient': ('不足', 'Insufficient'),
-    'tier.withdrawn':    ('撤回', 'Withdrawn'),
-    # 印面短形 (卡上空间小; 全称进悬停)
-    'tier.confirmed.s':    ('定论', 'Conf.'),
-    'tier.near.s':         ('准定论', 'Near'),
-    'tier.candidate.s':    ('候选', 'Cand.'),
-    'tier.reference.s':    ('参考', 'Ref.'),
-    'tier.insufficient.s': ('不足', 'Insuf.'),
-    'tier.withdrawn.s':    ('撤回', 'Withd.'),
-})
-
-# ── 审级 (SOP §6.1 RV-1) ─────────────────────────────────────────
-_add({
-    'rv.dual':  ('双模型审', 'DUAL-MODEL'),
-    'rv.advci': ('AdvCI', 'ADVCI'),
-    'rv.lite':  ('lite', 'LITE'),
-    'rv.none':  ('未审', 'UNREVIEWED'),
-})
-
-# ── 总览 (设计基准 docs/design/guanlan_v2.html) ───────────────────
-_add({
-    'ov.attention':      ('需要关注', 'Needs attention'),
-    # 融合面缺件时的说明(2026-09-19 用户报「需要关注/全场状态下面没有计算输出」后补):
-    # 缺件就该说清"缺哪件、怎么补",不让两片空白被读成"没有需要关注的台"。
-    'ov.fus_gap_hint':   ('这一面(需要关注 / 全场状态)取数来自融合面;缺件时如实为空,不从交付包补齐。'
-                          '在本机放好原始件后跑一次重算即会由观澜自己的生成端补上'
-                          '(scripts/rudong_fusion_handoff.py,见 docs 的「源代码化清单」)。',
-                          'This block (needs attention / fleet status) draws on the fusion layer. '
-                          'When that product is missing it stays honestly empty rather than being filled from a delivery package; '
-                          'after placing the raw data, one recompute run rebuilds it from source (scripts/rudong_fusion_handoff.py).'),
-    'ov.attention_n':    ('{n} 台', '{n} units'),
-    'ov.ranked_by':      ('按 设备状态 × 证据量 排序', 'Ranked by equipment state × evidence weight'),
-    'ov.strip':          ('全场状态', 'Fleet status'),
-    'ov.strip_hint':     ('一格一台 · 点格定位', 'One cell per unit · click to locate'),
-    'ov.quiet':          ('其余 {n} 台未进入优先关注清单;设备状态请查看全场状态评估。',
-                          'The other {n} units are outside the priority list. Refer to fleet status for their equipment assessments.'),
-    'ov.quiet_all':      ('当前优先关注清单为空;{n} 台机组的设备状态请查看全场状态评估。', 'The priority list is empty. Refer to fleet status for the assessments of all {n} units.'),
-    'ov.caveat':         ('限定', 'Follow-up'),
-    'ov.systems':        ('九系统', 'Nine systems'),
-    'ov.systems_hint':   ('点系统看逐台依据', 'Open a system for per-unit evidence'),
-    'ov.illustrative':   ('示意数据', 'Illustrative'),
-    'ui.open':           ('查看 →', 'Open →'),
-    'ui.back_to':        ('← 返回{tab}', '← Back to {tab}'),
-    'ui.back_portal':    ('← 返回门户', '← Back to portal'),
-    'rpt.facts.title': ('可交付级结论 · 契约生成', 'Deliverable-grade conclusions · from facts contract'),
-    'rpt.facts.sub': ('{n} 条, 契约 {sha}; 数据/时间窗/结论只经契约更新', '{n} claims, contract {sha}; data / windows / verdicts change only via the contract'),
-    'rpt.facts.verdict': ('裁决', 'Verdict'), 'rpt.facts.text': ('结论', 'Conclusion'), 'rpt.facts.sys': ('系统', 'System'), 'rpt.facts.agg': ('聚合层', 'Aggregation'),
-    'ui.reselect':       ('重新选择机组 / 测点', 'Reselect unit / point'),
-})
-
-# ── KPI ───────────────────────────────────────────────────────────
-_add({
-    'kpi.avail':        ('时间可用率 (不含调度令) · {win}', 'Time-based availability (excl. dispatch) · {win}'),
-    'kpi.downtime':     ('停机时长 · {n} 起', 'Downtime · {n} events'),
-    'kpi.mtbo':         ('平均停机间隔(MTBO;非行业 MTBF)', 'Mean time between outages (MTBO; not industry MTBF)'),
-    'kpi.loss':         ('停机电量损失 · {win}', 'Downtime energy loss · {win}'),
-    'kpi.loss_dispatch':('另调度令 {n}', 'plus {n} under dispatch orders'),
-    'kpi.curtail':      ('限电·绑定 电量损失', 'Curtailment (bound) energy loss'),
-    'kpi.alarm_units':  ('九系统有报警的台数 / 全场', 'Units with a system-level alarm / fleet'),
-    'kpi.alarm_units.hint': ('口径: 九系统 (叶片与叶根/变桨/偏航/齿轮箱/发电机/主轴承/变流器/塔架与基础/主控与传感网) 中任一被判「报警」即计一台。'
-                             '与下方「全场状态」格的口径不同 —— 那里按每台 振动/温度/润滑/油液/报警/工单 六列取最严一级, 所以两处数字可以不同, 不是矛盾。',
-                            'Caliber: a unit counts if ANY of the nine systems is rated "alarm". The fleet strip below uses a different axis '
-                            '(worst of vibration/temperature/lubrication/oil/alarm/work-order per unit), so the two numbers can differ by design.'),
-})
-
-# ── 证据级补全: API 实际发出的枚举 (fus.色标) 一条不落 ──────────────
-_add({
-    'tier.candidate_new':      ('候选·新发', 'Candidate · new'),
-    'tier.candidate_baseline': ('候选·记基线', 'Candidate · baseline'),
-    'tier.normal':             ('正常', 'Normal'),
-    'tier.candidate_new.s':      ('候选·新发', 'Cand. new'),
-    'tier.candidate_baseline.s': ('候选·记基线', 'Cand. base'),
-    'tier.normal.s':             ('正常', 'Normal'),
-})
-
-# ── 链盘六步 (fus.链盘.rows[].steps[].k) ─────────────────────────
-_add({
-    'step.evidence':   ('证据', 'Evidence'),
-    'step.site_records': ('现场记录', 'site records'),
-    'step.mechanism':  ('机制', 'Mechanism'),
-    'step.rating':     ('判级', 'Rating'),
-    'step.schedule':   ('排期', 'Schedule'),
-    'step.action':     ('动作', 'Action'),
-    'step.acceptance': ('验收', 'Acceptance'),
-    'step.stuck_at':   ('卡在「{step}」', 'Pending {step}'),
-})
-
-# ── 时间窗预设 ────────────────────────────────────────────────────
-_add({
-    'win.label':   ('时间窗', 'Window'),
-    'win.presets': ('预设时间窗', 'Preset windows'),
-    'win.months':  ('按月', 'By month'),
-    # ★自定义起止日期窗(用户令 2026-09-21):含两端;月度件按月取整,日粒度件按日精确
-    'win.custom':      ('自定义', 'Custom'),
-    'win.custom_hint': ('自定义起止日期(含两端)。日粒度件(判级/停机台账/曲线)按日精确;'
-                        '月度聚合件按所跨月份取整,图上会写明。',
-                        'Custom inclusive date range. Day-resolution items (grading, stop ledger, curves) are exact; '
-                        'monthly aggregates round to the spanned months and say so on the chart.'),
-    'win.from':        ('开始日期(含)', 'Start date (inclusive)'),
-    'win.to':          ('结束日期(含)', 'End date (inclusive)'),
-    'win.apply':       ('应用', 'Apply'),
-    'win.need_both':   ('请把开始与结束日期都选上', 'Pick both start and end dates'),
-    'win.bad_order':   ('开始日期不能晚于结束日期', 'Start date must not be after the end date'),
-    'win.custom_sel':  ('自定义 {a} ~ {b}', 'Custom {a} to {b}'),
-    'cmp.grading_pending': ('正在按所选时间窗重算判级…', 'Recomputing grading for the selected window…'),
-    'cmp.grading_pending_why': ('时间窗 {span}:变桨/偏航/蓄能/温度四个判级面正按该时间窗重算(首次约 25 秒),'
-                                '下面的系统卡暂时还是上一份口径 —— 算完会自动刷新,不会静默混用。',
-                                'Window {span}: the four grading axes (pitch / yaw / accumulator / temperature) '
-                                'are being recomputed for it (about 25 s the first time). The cards below still show '
-                                'the previous grading until it lands, then refresh automatically — no silent mixing.'),
-    'win.30d':     ('近30日', 'Last 30 days'),
-    'win.90d':     ('近90日', 'Last 90 days'),
-    'win.2026':    ('2026年', '2026'),
-    'win.2026H1':  ('2026H1', '2026 H1'),
-    'win.2025H2':  ('2025H2', '2025 H2'),
-    'win.all':     ('全程', 'Full record'),
-})
-
-# ── 迁移期 ────────────────────────────────────────────────────────
-_add({
-    'ui.not_ported':   ('本页尚未迁入新版。', 'This page has not been ported to the new build yet.'),
-    'ui.open_classic': ('在经典版打开 →', 'Open in classic build →'),
-    'ui.data_hidden':  ('({n} 处数据文本尚无英文,需复核)', '({n} data strings still require English translation review)'),
-    # 手册/工单**原文**与"字段"不同: 隐藏它等于把处置步骤整个抹掉。按原文转录并标注 (2026-09-03 取舍, 可推翻)。
-    'ui.verbatim':      ('原始记录 (中文)', 'Translation review required'),
-    'ui.verbatim_note': ('{n} 处为手册/工单原文, 无英文译本, 按原文转录。',
-                         '{n} source passages require translation review and are withheld from the public English view.'),
-})
-
-# ── 检修助手 · 结果渲染 ────────────────────────────────────────────
-_add({
-    'asst.querying':     ('查询中…', 'Querying…'),
-    'asst.query_failed': ('查询失败: {e}', 'Query failed: {e}'),
-    'asst.identity':     ('系统 {sys} · 本场 {n} 条', 'System {sys} · {n} site alarms'),
-    'asst.alarm_type':   ('告警类型 {t}', 'alarm type {t}'),
-    'asst.unassigned':   ('未归属', 'unassigned'),
-    'asst.no_name':      ('(码表未收录)', '(not in the code table)'),
-    'asst.failmodes':    ('指向的失效模式', 'Failure modes indicated'),
-    'asst.fm_counts':    ('该类本场报警 {a} 条 / 工单 {w} 条', '{a} site alarms / {w} work orders in this class'),
-    'asst.steps':        ('现场处置 {n} 步', 'Field procedure · {n} steps'),
-    'asst.steps_note':   ('节点部件 = 手册原文元件位号 (确定); 判定规范 = 实机参数表按物理量匹配的候选判据, 非厂商绑定 — 现场以铭牌与图纸复核。',
-                          "Node components are the manual's own designators (definite); acceptance specs are candidate criteria matched from the live parameter table by physical quantity, not vendor-bound — verify against nameplate and drawings on site."),
-    'asst.step_n':       ('第 {n} 步', 'Step {n}'),
-    'asst.qty':          ('量: {q}', 'quantity: {q}'),
-    'asst.tags':         ('节点部件', 'Node components'),
-    'asst.nodes':        ('应检节点 · {c}', 'Nodes to check · {c}'),
-    'asst.nodes_note':   ('手册此步未给位号 → 按回路构成展开, 逐点查', 'no designator given for this step → expanded by circuit composition, check point by point'),
-    'asst.node':         ('节点', 'Node'),
-    'asst.check':        ('查什么', 'What to check'),
-    'asst.known_tags':   ('该系统手册出现过的位号 (对图纸时参考):', "Designators seen in this system's manual (for drawing cross-reference):"),
-    'asst.specs':        ('判定规范 (实机参数表)', 'Acceptance specs (live parameter table)'),
-    'asst.spec_no':      ('参数号', 'Param'),
-    'asst.spec_name':    ('名称', 'Name'),
-    'asst.spec_val':     ('整定值', 'Setting'),
-    'asst.spec_range':   ('允许范围', 'Range'),
-    'asst.drawings':     ('对应图纸', 'Related drawings'),
-    'asst.no_steps':     ('本码无手册处置条目 (手册覆盖本场 161/322 码) — 需查图纸或联系厂商支持。',
-                          'No manual procedure for this code (the manual covers 161 of 322 site codes) — consult drawings or vendor support.'),
-    'asst.wo_head':      ('现场实际怎么修的 ({sys} 历史工单 {n} 条, 台账 2020~2024)',
-                          'What was actually done on site ({sys}: {n} historical work orders, ledger 2020–2024)'),
-    'asst.wo_fault':     ('故障', 'Fault'),
-    'asst.wo_action':    ('处置', 'Action'),
-    'asst.wo_note':      ('工单时间窗止 2024-11-21; 2025–26 现场未提供 ⇒ "0 条" 是记录缺失非未修过。',
-                          'Work-order window ends 2024-11-21; 2025–26 not provided by the site, so "0" means missing records, not no repair.'),
-    'asst.tree_head':    ('{sys} 故障树 · {n} 个失效模式', '{sys} fault tree · {n} failure modes'),
-    'asst.tree_note':    ('一层 = 系统 × 失效类 (归纳自本场码表与工单) · 二层 = 手册排查方向归一 + 工单原因计数; 条长按实证量 (报警条数 + 工单 × 10), 因果来自手册与工单原文非推断, 树深两层不外推。',
-                          'Level 1 groups site systems by failure class; level 2 merges manual troubleshooting directions with work-order cause counts. Bar length reflects available evidence (alarms + work orders × 10), and the tree stops at two levels.'),
-    'asst.tree_counts':  ('本场报警 {a} 条 · 工单 {w} 条', '{a} site alarms · {w} work orders'),
-    'asst.tree_codes':   ('征兆码 {c}', 'symptom codes {c}'),
-    'asst.causes':       ('← 可能原因 (手册排查方向 + 工单实证)', '← Possible causes (manual directions + work-order evidence)'),
-    'asst.cause_counts': ('手册 {r} 次 · 工单 {w} 次 · 涉码 {c}', 'manual ×{r} · work orders ×{w} · codes {c}'),
-    'asst.no_causes':    ('(手册无该类排查条目 — 原因层待补)', '(no troubleshooting entries in the manual for this class — cause layer pending)'),
-    'asst.tree_empty':   ('该系统无失效模式记录', 'No failure modes recorded for this system'),
-    'asst.prev_head':    ('{sys} 预防链 · 失效模式 {f} 类 · 预防性任务 {n} 项', '{sys} preventive chain · {f} failure-mode classes · {n} preventive tasks'),
-    'asst.prev_fm':      ('失效模式 (按本场报警量)', 'Failure modes (by site alarm volume)'),
-    'asst.prev_tasks':   ('年度任务', 'Annual tasks'),
-    'asst.interval':     ('周期 {i}', 'interval {i}'),
-    'asst.wi_steps':     ('WI {n} 步', 'WI · {n} steps'),
-    'asst.prev_empty':   ('(该系统无匹配的年度维护条目 — 维护作业指导书按位置代号编排, 未必逐系统对齐)',
-                          '(no annual maintenance entries matched to this system — the work-instruction book is organised by location code and does not map one-to-one onto systems)'),
-})
-
-# ── 检修决策 · 本体问答 (模型参与的板块) ────────────────────────────
-_add({
-    'dec.head':        ('检修决策 · 本体问答 (推理无界, 结论受闸)',
-                        'Maintenance decisions · ontology Q&A (unbounded reasoning, gated conclusions)'),
-    'dec.classic':     ('维护标准 / 维护框架 / 数据质量三块尚未迁入新版 —',
-                        'The maintenance-standard, framework and data-quality blocks are not ported yet —'),
-    'ask.common_q':    ('{cat} · 常用问题 (点即问, 或下方自由输入)', '{cat} · common questions (click to ask, or type below)'),
-    'ask.grounding':   ('答案句句带 [对象id] 溯源 + 接地闸核验; 判级只转述本体原文带审级, 模型零判级权。',
-                        'Every answer carries an [object id] and uses the reviewed evidence layer; model responses cannot create or change equipment ratings.'),
-    'ask.placeholder': ('自由提问 ({cat})', 'Ask anything ({cat})'),
-    'ask.model':       ('模型', 'Model'),
-    'ask.no_models':   ('无可用模型', 'No models available'),
-    'ask.refs.title':  ('契约引用 ({n} 条, 契约 {sha}) — 按问题/回答匹配的可交付结论, 引文来自 qa_refs', 'Contract citations ({n}, contract {sha}) — deliverable claims matched to the question/answer; cites from qa_refs'),
-    'ask.refs.none':   ('无匹配契约条目 (契约 {sha}): 本回答未被结构化事实契约背书, 按模型输出对待', 'No matching contract claim (contract {sha}): this answer is not backed by the facts contract; treat as model output'),
-    'ask.local_down':  ('本机模型未启动 (Ollama 127.0.0.1:11434 未响应); 离线版不转云', 'Local model not running (Ollama 127.0.0.1:11434 unreachable); offline build never falls back to cloud'),
-    'ask.partial':     ('初答 · 27B 复核中', 'Quick answer · 27B refining'),
-    'ask.refined':     ('已复核 (默认档 {m} 未过校闸, 由更强模型重答)', 'Refined (default tier {m} failed the gate; re-answered by the stronger model)'),
-    'ask.xrev':        ('交叉审核', 'Cross-review'),
-    'ask.xrev_note':   ('(DeepSeek 14B 互审, +约 1 分钟)', '(cross-checked by DeepSeek 14B, + about 1 min)'),
-    'ask.xrev_title':  ('由另一模型基于同一份证据复核; 同一模型自审无法构成独立校验',
-                        'A second model reviews on the same evidence; self-review by one model is not independent'),
-    'ask.go':          ('提问', 'Ask'),
-    'ask.idle':        ('空闲 — 点常用问题或输入后提问。串行一次一问。', 'Idle — click a common question or type one. One question at a time.'),
-    'ask.empty':       ('问题为空', 'The question is empty'),
-    'ask.running':     ('推理中…', 'Reasoning…'),
-    'ask.elapsed':     ('{s} 秒', '{s} s'),
-    'ask.question':    ('问题: {q}', 'Question: {q}'),
-    'ask.verify_ok':   ('溯源核验 {n}/{n} 全真', 'Traceability check {n}/{n} verified'),
-    'ask.verify_bad':  ('溯源核验未全过 (引用 {n})', 'Traceability check incomplete ({n} references)'),
-    'ask.done_meta':   ('模型 {m} · 用时 {s} 秒 · {n} 步', 'model {m} · {s} s · {n} steps'),
-    'ask.review':      ('交叉审核 · {m}', 'Cross-review · {m}'),
-    'ask.review_note': ('由另一模型基于同一份证据复核上述答案。溯源核验仅校验数字与机组号是否越界, 本项校验结论是否有证据支撑。',
-                        'A second model reviewed the answer on the same evidence. The traceability check only tests whether numbers and unit ids are in range; this review tests whether conclusions are supported by evidence.'),
-    'ask.review_err':  ('审核未完成: {e}', 'Review incomplete: {e}'),
-    'ask.error':       ('出错: {e}', 'Error: {e}'),
-    'ask.offline':     ('离线版: 模型问答需连接系统。', 'Offline copy: the model Q&A needs a live connection to the system.'),
-    'ask.en_qa_failed':('英文质检未通过: 已拦截这次模型回答, 请重新提问。',
-                        'English QA failed: this model answer was withheld. Please ask again.'),
-})
-
-# ── 模型档位说明 (id 固定集合; 未知 id 一律按"会外发"处理 —— 宁可说重不说轻) ──
-_add({
-    'mdl.local':          ('本地', 'local'),
-    'mdl.cloud':          ('云端', 'cloud'),
-    'mdl.zero_outbound':  ('全程零外发', 'Nothing leaves the machine'),
-    'mdl.outbound_warn':  ('内容会外发到模型厂商, 场名与台号已去标识化', 'Content is sent to the model vendor; site name and unit ids are de-identified'),
-    'mdl.unknown_warn':   ('未知档位, 按内容会外发处理', 'Unknown tier — treated as sending content externally'),
-    'mdl.qwen3:8b.nm':        ('轻量 8B · 默认', 'Light 8B · default'),
-    'mdl.qwen3:8b.t':         ('约 30 到 60 秒', 'about 30–60 s'),
-    'mdl.qwen3.8:27b.nm':     ('标准 27B · 复杂题', 'Standard 27B · complex'),
-    'mdl.qwen3.8:27b.t':      ('约 2 到 4 分钟', 'about 2–4 min'),
-    'mdl.qwen3:30b-a3b.nm':   ('快 30B (a3b)', 'Fast 30B (a3b)'),
-    'mdl.qwen3:30b-a3b.t':    ('约 20 到 60 秒', 'about 20–60 s'),
-    'mdl.deepseek-r1:14b.nm': ('本机 DeepSeek 14B · 审核档', 'Local DeepSeek 14B · reviewer'),
-    'mdl.deepseek-r1:14b.t':  ('约 1 到 3 分钟', 'about 1–3 min'),
-    'mdl.deepseek-r1:32b.nm': ('本机 DeepSeek 32B', 'Local DeepSeek 32B'),
-    'mdl.deepseek-r1:32b.t':  ('约 3 到 8 分钟', 'about 3–8 min'),
-    'mdl.deepseek-chat.nm':   ('DeepSeek', 'DeepSeek'),
-    'mdl.deepseek-chat.t':    ('约 3 到 10 秒', 'about 3–10 s'),
-    'mdl.kimi-fast.nm':       ('Kimi', 'Kimi'),
-    'mdl.kimi-fast.t':        ('约 5 到 20 秒', 'about 5–20 s'),
-    'mdl.qwen-plus.nm':       ('通义千问', 'Qwen Plus'),
-    'mdl.qwen-plus.t':        ('约 5 到 20 秒', 'about 5–20 s'),
-    'mdl.deepseek-v4-pro.nm': ('云端 DS', 'DeepSeek Pro'),
-    'mdl.deepseek-v4-pro.t':  ('约 2 到 6 分钟', 'about 2–6 min'),
-})
-
-# ── 问答四类 (API 的 cat 参数取中文名, 前端按 id 映射) ───────────────
-_add({
-    'cat.equip':      ('设备类', 'Equipment'),
-    'cat.equip.hint': ('设备状态 / 机制 / 判级 — 从机组档案、对象查询与机制检索取证',
-                       'Equipment state / mechanism / rating — evidence from unit profiles, object queries and mechanism search'),
-    'cat.equip.q1':   ('29号机组现在什么状态、为什么、该怎么办?', 'What is the state of WTG29, why, and what should be done?'),
-    'cat.equip.q2':   ('全场哪些机组有报警级或定论级判级?逐台列出并注明审级', 'Which units carry alarm-level or confirmed-tier ratings? List each with its review level.'),
-    'cat.equip.q3':   ('失效机制库里有哪些机制族?各涉及哪些机组、状态如何?', 'Which mechanism families are in the failure-mechanism library, which units do they involve, and in what state?'),
-    'cat.sched':      ('检修排程决策', 'Maintenance scheduling'),
-    'cat.sched.hint': ('排期沙盘 / 天气窗 / 验收树 — ont_scenario / ont_get 取证',
-                       'Scheduling sandbox / weather windows / acceptance tree — evidence via ont_scenario / ont_get'),
-    'cat.sched.q1':   ('29号修泵什么时候做最合适?对比各情景的损失与风险', 'When is the best time to repair the pump on WTG29? Compare loss and risk across scenarios.'),
-    'cat.sched.q2':   ('未来几个月哪些月份适合安排CTV检修和吊装作业?依据历史可作业窗', 'Which of the coming months suit CTV maintenance and lifting work, based on historical workable windows?'),
-    'cat.sched.q3':   ('29号修好之后按什么判据验收?三分支分别是什么条件?', 'After the repair on WTG29, what acceptance criteria apply, and what are the three branch conditions?'),
-    'cat.loss':       ('损失分析', 'Loss analysis'),
-    'cat.loss.hint':  ('当前 3 个月损失 / 未来损失 — ont_loss / ont_scenario 取证, 金额带电价区间口径',
-                       'Current 3-month and forward-looking loss estimates — values are shown as tariff ranges'),
-    'cat.loss.q1':    ('近3个月全场损失结构如何?分状态和分机组说, 电量与金额都给', 'What is the fleet loss structure over the last 3 months? By state and by unit, energy and value.'),
-    'cat.loss.q2':    ('29号如果拖到主轴承失效, 未来损失量级是多少?与现在修对比', 'If WTG29 runs to main-bearing failure, what is the future loss magnitude versus repairing now?'),
-    'cat.loss.q3':    ('限电常态化对检修机会成本有什么影响?', 'How does persistent curtailment change the opportunity cost of maintenance?'),
-    'cat.risk':       ('安全风险预测', 'Safety-risk outlook'),
-    'cat.risk.hint':  ('风险敞口 / 劣化趋势 / 盲区 — ont_risk / ont_mechanism_search 取证, 未列 ≠ 正常',
-                       'Risk exposure / degradation trend / blind spots — evidence via ont_risk / ont_mechanism_search; unlisted ≠ normal'),
-    'cat.risk.q1':    ('当前哪些机组风险在上升需要重点盯?联动监视读数如何?', 'Which units have rising risk and need close watching? What do the linked monitoring readings show?'),
-    'cat.risk.q2':    ('监测有哪些盲区?哪些"没报警"不等于正常?', 'Where are the monitoring blind spots? Which "no alarm" cases are not the same as normal?'),
-    'cat.risk.q3':    ('29号轴承正在承受金属接触, 风险敞口怎么评估?', "Unit 29's bearing is under metal-to-metal contact; how is the risk exposure assessed?"),
-})
-
-# ── 图元 (charts.js) ──────────────────────────────────────────────
-_add({
-    'ch.quad.few':     ('时间窗内码数不足, 不作四象限', 'Too few codes in the window for a quadrant plot'),
-    'ch.quad.q1':      ('低频长时 · 硬故障', 'Rare, long · hard faults'),      'ch.quad.q1s': ('备件与检修窗', 'spares and maintenance window'),
-    'ch.quad.q2':      ('高频长时 · 重点治理', 'Frequent, long · priority'),  'ch.quad.q2s': ('优先派工', 'dispatch first'),
-    'ch.quad.q3':      ('低频短时 · 本底', 'Rare, short · background'),        'ch.quad.q3s': ('不单独处置', 'no separate action'),
-    'ch.quad.q4':      ('高频短时 · 重复触发', 'Frequent, short · re-triggering'), 'ch.quad.q4s': ('调整参数或屏蔽, 非硬件故障', 'tune or mask; not hardware'),
-    'ch.quad.xlab':    ('发生条数 (对数)', 'Occurrences (log)'),
-    'ch.quad.ylab':    ('单次中位时长 (对数)', 'Median duration per event (log)'),
-    'ch.quad.tip':     ('{n} 条 · 单次中位 {med}s · 累计 {h}h · {nt} 台', '{n} events · median {med}s · total {h}h · {nt} units'),
-    'ch.rate.tip':     ('{m}: {v} 条/日 (总 {n} 条, 数据覆盖 {cov}%)', '{m}: {v}/day ({n} total, {cov}% coverage)'),
-    'ch.rate.month':   ('{m}月', '{m}'),
-    'ch.rate.n':       ('{n}条', '{n}'),
-    'ch.rate.days':    ('仅{d}天', '{d} d only'),
-    'ch.rate.ylab':    ('条/日', 'per day'),
-    'ch.wf.theo':      ('理论可发', 'Theoretical'),
-    'ch.wf.act':       ('实际上网', 'Delivered'),
-    'ch.wf.tip':       ('{nm}: 损失 {loss} MWh, 占理论 {pct}%, 时长 {h}h', '{nm}: loss {loss} MWh, {pct}% of theoretical, {h} h'),
-    'loss.curtail_bound':   ('限电 (命令与实际功率对得上)', 'Curtailment (command matches actual power)'),
-    'loss.curtail_unbound': ('限电命令期 · 功率未被压制', 'Curtailment command · power not suppressed'),
-    'loss.dispatch':        ('调度令停机', 'Dispatch-order outage'),
-    'loss.stop':            ('故障与检修停机', 'Fault and maintenance outage'),
-    'loss.normal':          ('正常发电时的欠发', 'Under-production while generating'),
-    'loss.lowwind':         ('低风待机', 'Low-wind standby'),
-    'ch.ram.few':      ('部件数不足, 不作象限', 'Too few components for a quadrant plot'),
-    'ch.ram.q1':       ('常坏且难修', 'Frequent and hard to fix'),  'ch.ram.q1s': ('最高优先级', 'highest priority'),
-    'ch.ram.q2':       ('罕发难修', 'Rare and hard to fix'),        'ch.ram.q2s': ('备件与作业窗', 'spares and work windows'),
-    'ch.ram.q3':       ('频发易修', 'Frequent and easy to fix'),    'ch.ram.q3s': ('查根因, 换件无用', 'find root cause; replacement will not help'),
-    'ch.ram.q4':       ('少发好修', 'Rare and easy to fix'),        'ch.ram.q4s': ('维持', 'maintain'),
-    'ch.ram.xlab':     ('平均无故障间隔(MTBF,h,对数)→  越右越可靠', 'MTBF (h, log) →  more reliable to the right'),
-    'ch.ram.ylab':     ('单次停机时长(MDT,h)→  越上越难修', 'MDT (h) →  harder to repair upward'),
-    'ch.ram.equip':    ('设备类', 'Equipment'),
-    'ch.ram.ext':      ('外部类', 'External'),
-    'ch.ram.area':     ('圆面积 = 累计停机时长', 'Area = total downtime'),
-    'ch.ram.median':   ('虚线 = 本场中位分界', 'Dashed = site median split'),
-    'ch.ram.tip':      ('{p}: 平均无故障间隔(MTBF){mtbf}h · 单次停机时长(MDT){mdt}h · 停机 {h}h · {n}起 · {u}台', '{p}: MTBF {mtbf}h · MDT {mdt}h · downtime {h}h · {n} events · {u} units'),
-    'ch.ram.tip_ext':  (' · 外部类不计设备可靠性', ' · external, excluded from equipment reliability'),
-    'ch.attrib.ok':    ('已归因到部件 {h}h ({p}%)', 'Attributed to a component {h}h ({p}%)'),
-    'ch.attrib.none':  ('无记录 {h}h ({p}%)', 'Unrecorded {h}h ({p}%)'),
-    'ch.attrib.uncls': ('未归类 {h}h', 'Unclassified {h}h'),
-    'ch.pareto.none':  ('时间窗内无数据', 'No data in the window'),
-    'ch.pareto.cum80': ('累计 80%', 'cumulative 80%'),
-    'ch.pareto.cumtip':('{k} · 累计 {p}%', '{k} · cumulative {p}%'),
-    'ch.pareto.xlab':  ('报警码 (悬停看全名)', 'Alarm code (hover for full name)'),
-    'ch.pareto.key':   ('关键少数: 前 {k} 项占 80%', 'Vital few: the first {k} items make 80%'),
-    'ch.pareto.total': ('共 {n} 项, 合计 {tot}{unit}', '{n} items, total {tot}{unit}'),
-    'ch.pareto.top3':  ('前三: ', 'Top three: '),
-    'ch.blame.n1':     ('交易与调度侧', 'Market and dispatch'),   'ch.blame.d1': ('限电与调度令, 不进设备责任', 'curtailment and dispatch orders; not equipment responsibility'),
-    'ch.blame.n2':     ('设备侧', 'Equipment'),                   'ch.blame.d2': ('故障与检修停机, 唯一进设备考核', 'fault and maintenance outages; the only part assessed against equipment'),
-    'ch.blame.n3':     ('运行侧', 'Operations'),                  'ch.blame.d3': ('正常发电欠发与低风待机', 'under-production while generating, and low-wind standby'),
-    'ch.blame.title':  ('损失 {tot} MWh 按责任归属', 'Loss {tot} MWh by responsibility'),
-    'unit.h':          ('h', 'h'), 'unit.n': (' 条', ' events'), 'unit.units': ('台', 'units'),
-})
-
-# ── 汇报定制 ──────────────────────────────────────────────────────
-_add({
-    'rpt.eyebrow':      ('汇报定制 · 画布', 'Report builder · canvas'),
-    'rpt.presets':      ('预设', 'Presets'),
-    'rpt.preset.month': ('月度运行分析', 'Monthly operations'), 'rpt.preset.week': ('周报', 'Weekly'),
-    'rpt.preset.topic': ('设备专题', 'Equipment topic'),       'rpt.preset.year': ('年度总结', 'Annual summary'),
-    'rpt.preset.blank': ('空白画布', 'Blank canvas'),
-    'rpt.aud_in':       ('场站内部 (全量)', 'Site internal (full)'),
-    'rpt.aud_up':       ('上报集团 (脱敏)', 'Group report (de-identified)'),
-    'rpt.print':        ('打印 / 存 PDF', 'Print / save PDF'),
-    'rpt.export_docx':  ('导出 Word', 'Export Word'),
-    'rpt.export_xlsx':  ('导出 Excel', 'Export Excel'),
-    'rpt.up_note':      ('上报版 — 判据门槛与特征频率已脱敏', 'Group version — criterion thresholds and detailed diagnostic markers are redacted'),
-    'rpt.library':      ('组件库', 'Component library'),
-    'rpt.library_note': ('点击加入报告, 再次点击移出 · 画布里已有 {n} 块', 'Click to add, click again to remove · {n} blocks on the canvas'),
-    'rpt.hint_idle':    ('悬停查看组件说明 · 已选组件再次点击移出 · 右侧 ↑↓ 调整顺序', 'Hover for a description · click a selected block to remove · ↑↓ to reorder'),
-    'rpt.need_head':    ('输入报告要求', 'Describe the report you need'),
-    'rpt.need_hint':    ('模型依据需求选取组件, 并基于实测数据生成运行总结', 'The model picks components for the request and writes a summary from measured data'),
-    'rpt.local_note':   ('; 数据不出本地', '; data stays local'),
-    'rpt.cloud_note':   ('; 内容会外发到模型厂商', '; content is sent to the model vendor'),
-    'rpt.need_ph':      ('例如: 上报集团月报, 侧重设备可靠性与重点机组', 'e.g. monthly report for the group, focused on equipment reliability and key units'),
-    'rpt.compose':      ('生成', 'Compose'),
-    'rpt.no_models':    ('本部署未配置任何模型密钥, 编排不可用', 'No model keys configured in this deployment; composing is unavailable'),
-    'rpt.no_compose':   ('本部署未启用编排接口, 画布仍可手动拼装', 'The compose endpoint is not enabled here; the canvas can still be assembled by hand'),
-    'rpt.need_empty':   ('先输入报告要求', 'Enter the report request first'),
-    'rpt.composing':    ('模型编排中… 挑组件 + 写总结', 'Composing… picking components and writing the summary'),
-    'rpt.compose_fail': ('生成失败: {e}', 'Compose failed: {e}'),
-    'rpt.composed':     ('已按需求编排 {n} 块{narr} · {m} 用时 {s}s', 'Composed {n} blocks{narr} · {m} in {s}s'),
-    'rpt.narr_plus':    (' + 一段运行总结', ' + a narrative summary'),
-    'rpt.ungrounded_n': ('{n} 个数字待核', '{n} numbers to verify'),
-    'rpt.grounded':     ('数字全部接地', 'all numbers grounded'),
-    'rpt.empty_canvas': ('画布是空的 — 先从组件库选内容', 'The canvas is empty — pick content from the library first'),
-    'rpt.preview_empty':('文档预览窗 — 从上面的组件库点选内容, 此处显示报告预览', 'Document preview — pick components above to see the report here'),
-    'rpt.paper_title':  ('运行分析报告', 'Operations analysis report'),
-    'rpt.paper_sub':    ('统计期 {win} | {n} 台', 'Period {win} | {n} units'),
-    'rpt.up_tag':       ('上报版', 'group version'),
-    'rpt.chapters':     ('{n} 个章节', '{n} sections'),
-    'rpt.foot_src':     ('数据源 SCADA 10min · 报警事件 · 工单台账 · CMS 振动 · 油液化验', 'Sources: SCADA 10-min · alarm events · work-order ledger · CMS vibration · oil analysis'),
-    'rpt.foot_asof':    ('出件 {d}', 'issued {d}'),
-    'rpt.foot_trace':   ('结论可回溯至本体对象', 'Conclusions trace back to ontology objects'),
-    'rpt.foot_deid':    ('判据门槛与特征频率已脱敏', 'thresholds and detailed diagnostic markers redacted'),
-    'rpt.caliber_lbl':  ('口径: ', 'Basis: '),
-    'rpt.g.gen': ('发电', 'Generation'), 'rpt.g.rel': ('可靠性', 'Reliability'), 'rpt.g.flt': ('故障', 'Faults'),
-    'rpt.g.unit': ('机组', 'Units'), 'rpt.g.end': ('收尾', 'Closing'),
-    'rpt.b.kpi.nm': ('一级指标', 'Headline KPIs'),     'rpt.b.kpi.d': ('六个核心数: 上网电量 / 等效满发 / 可用率 / 损失率 / 停机间隔 / 需行动台', 'Six core numbers: delivered energy / full-load hours / availability / loss rate / MTBO / units needing action'),
-    'rpt.b.water.nm': ('能量瀑布', 'Energy waterfall'), 'rpt.b.water.d': ('理论可发 → 逐段扣损 → 实际上网, 用于定位电量流向', 'Theoretical → losses by segment → delivered; where the energy went'),
-    'rpt.b.emon.nm': ('逐月发电表', 'Monthly table'),  'rpt.b.emon.d': ('逐月上网 / 损失 / 损失率 / 等效满发, 含数据覆盖率', 'Monthly delivered / loss / loss rate / full-load hours, with data coverage'),
-    'rpt.b.blame.nm': ('损失责任分账', 'Loss by responsibility'), 'rpt.b.blame.d': ('交易调度侧 / 设备侧 / 运行侧 三段占比 — 回答"谁的责任"', 'Market-dispatch / equipment / operations shares — who is responsible'),
-    'rpt.b.ram.nm': ('可靠性象限', 'Reliability quadrant'), 'rpt.b.ram.d': ('平均无故障间隔(MTBF)× 单次停机时长(MDT)部件象限, 分出"频发易修"与"罕发难修", 处置方向相反', 'MTBF × MDT by component; frequent-easy vs rare-hard call for opposite actions'),
-    'rpt.b.parts.nm': ('部件停机表', 'Downtime by component'), 'rpt.b.parts.d': ('逐部件停机时长 / 事件数 / 平均无故障间隔(MTBF)/ 单次停机时长(MDT)/ 影响机组数', 'Per component: downtime / events / MTBF / MDT / units affected'),
-    'rpt.b.attrib.nm': ('归因覆盖', 'Attribution coverage'), 'rpt.b.attrib.d': ('已归因 vs 无记录停机的占比 — 无记录不等于没有故障', 'Attributed vs unrecorded downtime — unrecorded is not the same as no fault'),
-    'rpt.b.pareto.nm': ('故障帕累托', 'Fault Pareto'),    'rpt.b.pareto.d': ('按条数降序 + 累计 80% 线, 找关键少数', 'Descending counts with the 80% line; the vital few'),
-    'rpt.b.quad.nm': ('故障四象限', 'Fault quadrants'),  'rpt.b.quad.d': ('条数 × 单次时长, 区分重复触发与实际停机 — 按条数排序会误定优先级', 'Count × duration separates re-triggering from real outages; ranking by count alone misleads'),
-    'rpt.b.frate.nm': ('月度故障率', 'Monthly fault rate'), 'rpt.b.frate.d': ('按天归一的日均条数, 数据不满月单独标注', 'Daily-normalised counts; partial months flagged'),
-    'rpt.b.units.nm': ('重点机组', 'Key units'),         'rpt.b.units.d': ('需跟踪台的部件 / 证据源 / 六步进度 / 卡在哪一步', 'Tracked units: component / evidence sources / six-step progress / where blocked'),
-    'rpt.b.chain.nm': ('决策链进度', 'Decision-chain progress'), 'rpt.b.chain.d': ('证据→机制→判级→排期→动作→验收 六步的瓶颈分布', 'Bottlenecks across evidence → mechanism → rating → schedule → action → acceptance'),
-    'rpt.b.loop.nm': ('闭环实证', 'Closed-loop evidence'), 'rpt.b.loop.d': ('检出 → 换件 → 复测回落的历史例证, 证明这条链是通的', 'Detected → replaced → re-measured recovery; proof the chain works'),
-    'rpt.b.plan.nm': ('下期建议', 'Next-period actions'),  'rpt.b.plan.d': ('由数据推出的行动项, 不是套话', 'Actions derived from data, not boilerplate'),
-    'rpt.b.caliber.nm': ('口径与边界', 'Basis and limits'), 'rpt.b.caliber.d': ('统计期 / 残月 / 工单时间窗 / 判级性质 — 建议常驻, 没有边界的数字堆不能作汇报依据', 'Period / partial months / work-order window / nature of ratings — keep it in; numbers without limits are not a basis for reporting'),
-    'rpt.b.narr.nm': ('运行总结', 'Narrative summary'),   'rpt.b.narr.d': ('模型基于实测数据生成的运行总结 — 为图表补充文字结论, 供汇报使用', 'A model-written summary from measured data — text conclusions to accompany the charts'),
-    # 块内文案
-    'rpt.kpi.act':       ('上网电量', 'Delivered energy'),      'rpt.kpi.theo': ('理论可发 {v}', 'theoretical {v}'),
-    'rpt.kpi.mom':       ('环比 {v}%', 'MoM {v}%'),              'rpt.kpi.mom_na': ('环比不可比(末月不满)', 'MoM not comparable (partial last month)'),
-    'rpt.kpi.eflh':      ('等效满发小时', 'Full-load hours'),   'rpt.kpi.annual': ('年化约 {v} h', 'annualised ≈ {v} h'),
-    'rpt.kpi.avail':     ('时间可用率', 'Time-based availability'), 'rpt.kpi.avail_n': ('不含调度令与低风待机', 'excl. dispatch orders and low-wind standby'),
-    'rpt.kpi.loss':      ('损失率', 'Loss rate'),               'rpt.kpi.mtbo': ('平均停机间隔', 'Mean time between outages'),
-    'rpt.kpi.mtbo_n':    ('停机 {n} 起 · 单次均 {h}h', '{n} outages · mean {h}h each'),
-    'rpt.kpi.action':    ('需行动机组', 'Units needing action'), 'rpt.kpi.action_n': ('定论 {a} · 预警 {b} · 候选 {c}', 'confirmed {a} · warning {b} · candidate {c}'),
-    'rpt.water.title':   ('发电完成情况', 'Generation outcome'),
-    'rpt.water.note':    ('理论可发 {theo} = 上网 {act} + 损失 {loss} MWh ({pct}%)', 'Theoretical {theo} = delivered {act} + loss {loss} MWh ({pct}%)'),
-    'rpt.water.cap_ok':  ('量级自检: 理论低于全额定上限 {cap} MWh ✔', 'Magnitude check: theoretical is below the full-rated cap {cap} MWh ✔'),
-    'rpt.water.cap_bad': ('量级自检: 理论 ≥ 全额定上限 {cap} MWh ✗ 数字不可信', 'Magnitude check: theoretical ≥ full-rated cap {cap} MWh ✗ figures not credible'),
-    'rpt.water.cal':     ('SCADA 10min 工况态分解; 损失=同工况理论出力与实测之差。量级自检为防虚高的下限闸, 不是风资源判据', 'SCADA 10-min state decomposition; loss = theoretical output for the same state minus measured. The magnitude check is a floor against inflation, not a wind-resource criterion'),
-    'rpt.emon.title':    ('逐月发电', 'Monthly generation'),
-    'rpt.emon.m': ('月', 'Month'), 'rpt.emon.act': ('上网 MWh', 'Delivered MWh'), 'rpt.emon.loss': ('损失 MWh', 'Loss MWh'),
-    'rpt.emon.pct': ('损失率', 'Loss rate'), 'rpt.emon.eflh': ('等效满发 h', 'Full-load h'), 'rpt.emon.cov': ('覆盖', 'Coverage'),
-    'rpt.emon.partial':  (' 不满月', ' partial'),
-    'rpt.emon.cal':      ('覆盖率<50% 的月不参与环比', 'Months with coverage below 50% are excluded from MoM'),
-    'rpt.blame.title':   ('损失责任分账', 'Loss by responsibility'),
-    'rpt.blame.note':    ('进设备考核的只有"故障与检修停机"; 限电属交易与调度侧。', 'Only fault and maintenance outages count against equipment; curtailment belongs to market and dispatch.'),
-    'rpt.blame.note2':   ('限电不是"低风时段收到的空令": 限电·绑定时段平均出力 1.435 MW, 正常发电时段 1.412 MW, 比值 1.02 — 风况相同而出力被压, 说明限电是真压制; 但"限电命令期·功率未被压制"那一档平均出力仅 0.724 MW (18% 额定), 确属低风时段收令。',
-                          'Curtailment is not an empty order received in low wind: mean output in bound-curtailment periods is 1.435 MW versus 1.412 MW while generating normally (ratio 1.02) — same wind, suppressed output, so the curtailment is real; the "command without suppression" tier averages only 0.724 MW (18% of rated), which is a low-wind order.'),
-    'rpt.blame.cal':     ('工况态分解 + 限电命令面绑定判别; 平均出力=实发÷台时, 用作风资源代理量', 'State decomposition with curtailment-command binding; mean output = delivered ÷ unit-hours, used as a wind-resource proxy'),
-    'rpt.ram.title':     ('可靠性 × 可维修性', 'Reliability × maintainability'),
-    'rpt.ram.note':      ('设备类 {a}h / 外部类 {b}h (电网·风况·运维操作不计设备可靠性)', 'Equipment {a}h / external {b}h (grid, wind and operations excluded from equipment reliability)'),
-    'rpt.ram.cal':       ('SCADA ≥1h 停机段, 不含调度令; 海上 MDT 含交通窗口', 'SCADA outage segments ≥1 h, excluding dispatch orders; offshore MDT includes transit windows'),
-    'rpt.parts.title':   ('部件停机', 'Downtime by component'),
-    'rpt.parts.part': ('部件', 'Component'), 'rpt.parts.h': ('停机h', 'Downtime h'), 'rpt.parts.ev': ('事件', 'Events'),
-    'rpt.parts.mtbf': ('平均无故障间隔(MTBF,h)', 'MTBF (h)'), 'rpt.parts.mdt': ('单次停机时长(MDT,h)', 'MDT (h)'), 'rpt.parts.units': ('影响机组', 'Units'),
-    'rpt.parts.ext':     (' (外部)', ' (external)'),
-    'rpt.parts.cal':     ('归因=段起点±30min 首发码', 'Attribution = first alarm within ±30 min of segment start'),
-    'rpt.attrib.title':  ('归因覆盖', 'Attribution coverage'),
-    'rpt.attrib.note':   ('"无记录停机"不是没有故障, 是没归上因。', 'Unrecorded downtime is not the absence of faults; it is the absence of attribution.'),
-    'rpt.pareto.title':  ('故障帕累托', 'Fault Pareto'),     'rpt.pareto.cal': ('报警事件表', 'Alarm event table'),
-    'rpt.quad.title':    ('故障模式四象限', 'Fault-mode quadrants'),
-    'rpt.quad.note':     ('右下角报警码属重复触发, 不应按条数排定优先级。', 'Codes in the lower-right are re-triggering and should not be prioritised by count.'),
-    'rpt.quad.cal':      ('纵界=10min(一个SCADA采样格)', 'Vertical split = 10 min (one SCADA sample)'),
-    'rpt.frate.title':   ('月度故障率', 'Monthly fault rate'),
-    'rpt.frate.note':    ('柱高=日均条数, 不是月总数。', 'Bar height is the daily mean, not the monthly total.'),
-    'rpt.frate.cal':     ('不满月按天归一', 'Partial months normalised by day'),
-    'rpt.units.title':   ('重点机组与处置', 'Key units and actions'),
-    'rpt.units.head':    ('需跟踪 {n} 台 / {tot} | 瓶颈 ', 'Tracked {n} of {tot} | bottlenecks '),
-    'rpt.units.stuck':   ('{n} 台卡在「{s}」', '{n} units pending {s}'),
-    'rpt.units.state': ('状态', 'State'), 'rpt.units.ev': ('部件与证据', 'Component and evidence'),
-    'rpt.units.prog': ('进度', 'Progress'), 'rpt.units.at': ('卡在', 'Stage'),
-    'rpt.units.rising':  ('在升 {r}×', 'rising {r}×'),   'rpt.units.clear': ('全通', 'clear'),
-    'rpt.units.cal':     ('四源融合判级', 'Four-source fused rating'),
-    'rpt.chain.title':   ('决策链进度', 'Decision-chain progress'),
-    'rpt.chain.note':    ('六步: 证据 → 机制 → 判级 → 排期 → 动作 → 验收。当前 {s}。', 'Six steps: evidence → mechanism → rating → schedule → action → acceptance. Currently {s}.'),
-    'rpt.chain.cal':     ('「动作」一列不可判 — {b}', 'The "action" step cannot be judged — {b}'),
-    'rpt.loop.title':    ('端到端闭环实证', 'End-to-end closed-loop evidence'),
-    'rpt.loop.comp': ('部件', 'Component'), 'rpt.loop.date': ('换件日期', 'Replaced on'), 'rpt.loop.pre': ('换前', 'Before'),
-    'rpt.loop.post': ('换后', 'After'), 'rpt.loop.ratio': ('比值', 'Ratio'),
-    'rpt.loop.note':     ('这几台正是算法当初的检出台 ⇒ 故障前能看见、换件后能看见恢复。', 'These are the units the algorithm flagged in the first place: visible before the fault, and visibly recovered after replacement.'),
-    'rpt.loop.cal':      ('振动标量前后对比', 'Vibration scalar, before vs after'),
-    'rpt.plan.title':    ('下期计划与建议', 'Next-period plan'),
-    'rpt.plan.1':        ('补机制定性 — {n} 台停在"判了级但根因未定", 是当前最大单一瓶颈', 'Close the mechanism gap — {n} units are rated but without a root cause; the single largest bottleneck'),
-    'rpt.plan.2':        ('回收 25~26 工单 — 台账止 2024-11, 本期检修执行与闭环无法自动判定', 'Recover the 2025–26 work orders — the ledger ends 2024-11, so this period’s execution and closure cannot be judged automatically'),
-    'rpt.plan.3':        ('油液采样节奏 — 四源里油液长期 stale, 只能作回测锚', 'Oil sampling cadence — oil is chronically stale among the four sources and serves only as a back-test anchor'),
-    'rpt.plan.4':        ('限电侧 — 损失六成在交易与调度侧, 设备侧改善空间有限', 'Curtailment — six tenths of the loss sits with market and dispatch; equipment-side headroom is limited'),
-    'rpt.plan.cal':      ('建议级 — 排期与动作须经审批', 'Advisory — scheduling and actions require approval'),
-    'rpt.narr.title':    ('运行总结', 'Narrative summary'),
-    'rpt.narr.empty':    ('还没有生成 — 在上面的交互窗输入报告要求。', 'Nothing generated yet — enter a request in the panel above.'),
-    'rpt.narr.ungr':     ('⚠ 以下数值由模型自行推算, 不在原始数据摘要中, 引用前须核对: {v}', '⚠ These values were derived by the model and are not in the source summary; verify before citing: {v}'),
-    'rpt.narr.cal':      ('模型 {m} 生成 · 数字取自系统实测摘要 · 接地闸{g}', 'Generated by {m} · numbers from the measured summary · grounding gate {g}'),
-    'rpt.narr.g_ok':     ('全通过', 'passed'),
-    'rpt.narr.g_n':      ('报 {n} 处待核', 'flagged {n} to verify'),
-    'rpt.cal.title':     ('口径与数据边界', 'Basis and data limits'),
-    'rpt.cal.period':    ('统计期', 'Period'),
-    'rpt.cal.partial':   (' — {m} 数据不满月(覆盖 {c}%), 月度已按天归一', ' — {m} partial (coverage {c}%), monthly figures normalised by day'),
-    'rpt.cal.denom':     ('可用率与损失率分母不同', 'Availability and loss rate use different denominators'),
-    'rpt.cal.denom_d':   ('可用率分母剔除低风待机与调度令(问"该发时能不能发"), 损失率分母含全部时段(问"理论能发多少"); 同一低风时段在可用率里不计时间、在损失率里计入基数 — 这是两个指标的设计差异不是错, 但两者不可直接相乘或互推。低风待机对损失的贡献仅 118 MWh(0.5%)',
-                          'Availability excludes low-wind standby and dispatch orders (can it generate when it should?), loss rate includes all hours (how much could it generate?); the same low-wind hour is excluded from one and included in the other — a design difference, not an error, but the two cannot be multiplied or inferred from each other. Low-wind standby contributes only 118 MWh (0.5%) of loss'),
-    'rpt.cal.stop':      ('停机', 'Outages'),
-    'rpt.cal.stop_d':    ('可靠性口径 ≥1h 段, 电量账为逐条工况态 — 两个数都对, 不可相减', 'Reliability counts segments ≥1 h, the energy account counts every state record — both are right, and they cannot be subtracted'),
-    'rpt.cal.wo':        ('工单', 'Work orders'),
-    'rpt.cal.wo_d':      ('台账 2020~2024-11, 本报告不对本期检修执行下结论', 'Ledger 2020 to 2024-11; this report draws no conclusion on this period’s maintenance execution'),
-    'rpt.cal.rating':    ('判级', 'Ratings'),
-    'rpt.cal.rating_d':  ('由振动/温度/润滑/油液四源融合得出; 行动等级由综合报告出具', 'Fused from vibration, temperature, lubrication and oil; action levels are issued by the consolidated report'),
-    'rpt.cal.deid':      ('脱敏', 'Redaction'),
-    'rpt.cal.deid_d':    ('上报版隐去判据门槛与特征频率', 'The group version hides criterion thresholds and detailed diagnostic markers'),
-})
-
-# ── 故障统计 ──────────────────────────────────────────────────────
-_add({
-    'win.banner':        ('时间窗 {win} · 本页全部数字随上方时间窗切换', 'Window {win} · every number on this page follows the selector above'),
-    'win.partial':       ('时间窗内 {m} 数据不满月 (覆盖 {c}%), 月度图已按天归一', '{m} in this window is a partial month ({c}% coverage); monthly charts are day-normalised'),
-    'flt.eyebrow':       ('故障统计与可靠性', 'Fault statistics and reliability'),
-    'flt.alarm_face':    ('报警面', 'Alarm view'),
-    'flt.alarm_meta':    ('{n} 条 · {c} 个码 · 时间窗 {win}', '{n} alarms · {c} codes · window {win}'),
-    'flt.rate':          ('月度故障率', 'Monthly fault rate'),
-    'flt.rate_note':     ('柱高 = 日均条数, 不是月总数', 'Bar height is the daily mean, not the monthly total'),
-    'flt.rate_partial':  ('本时间窗 {m} 数据不满月 (灰虚线柱), 直接比月总数会读反。', '{m} is partial in this window (grey dashed bar); comparing monthly totals directly reads backwards.'),
-    'flt.rate_full':     ('各月覆盖完整。', 'All months fully covered.'),
-    'flt.rate_example':  ('实测: {m} 总数最低但日均 {v} 条/日 = 全时间窗前列。', 'Measured: {m} has the lowest total yet {v}/day, among the highest in the window.'),
-    'flt.pareto_n':      ('帕累托 · 按发生条数', 'Pareto · by count'),
-    'flt.pareto_n_note': ('条数 = 发生频次, 不反映影响程度 — 高频短时报警码 (信号抖动或重复触发) 排序靠前', 'Count = frequency, not impact — frequent short alarms (signal chatter or re-triggering) rank high'),
-    'flt.pareto_d':      ('帕累托 · 按持续时长', 'Pareto · by duration'),
-    'flt.pareto_d_note': ('时长 = 闩锁口径 (复位 − 激活), 只作排序参考不作停机时长', 'Duration = latch basis (reset − activation); a ranking aid, not downtime'),
-    'flt.quad':          ('故障模式四象限 · 条数 × 单次时长', 'Fault-mode quadrants · count × duration per event'),
-    'flt.quad_note':     ('时间窗内共 {n} 个码, 图上为条数前 {m} 个。', '{n} codes in the window; the plot shows the top {m} by count.'),
-    'flt.quad_partial':  ('窗含不满月 {m} (覆盖 {c}%), 条数为原始累计未按天归一。', 'The window includes partial month {m} ({c}% coverage); counts are raw totals, not day-normalised.'),
-    'flt.quad_note2':    ('点面积表示影响机组数, 大点为批次共性、小点为单台; 竖分界取条数中位, 横分界取 10min (一个 SCADA 采样格)。右下角的码不要按条数排优先级 — 属重复触发, 非硬件故障。上面两张帕累托对照看即此图的两条投影。',
-                          'Point area = units affected (large = batch-wide, small = single unit); the vertical split is the count median, the horizontal split 10 min (one SCADA sample). Do not prioritise lower-right codes by count — they re-trigger and are not hardware faults. The two Pareto charts above are this plot’s projections.'),
-    'flt.per_t':         ('台级分布 · 报警条数 top{n}', 'By unit · alarm count top {n}'),
-    'flt.per_t_share':   ('占全场', 'Share of fleet'),
-    'flt.per_t_note':    ('全场合计 {n} 条 · 单台占比高须先核查是否为单一报警码重复触发', 'Fleet total {n} · a high single-unit share must first be checked for one code re-triggering'),
-    'flt.seasonal':      ('故障季节性 · top 报警族 × 月', 'Seasonality · top alarm families × month'),
-    'flt.seasonal_unit': ('条/月', 'per month'),
-    'flt.seasonal_x':    ('月', 'month'),
-    'flt.seasonal_note': ('全程 19 个月 (跨 2 冬 1 夏); 未做 χ² 显著性检验前为【参考级】, 只看形态不作季节归因', '19 months (two winters, one summer); reference tier until a χ² test is run — shape only, no seasonal attribution'),
-    'flt.stop_face':     ('停机面', 'Outage view'),
-    'flt.stop_meta':     ('{h}h · {n} 起 · 口径 ≥1h 段, 不含调度令', '{h}h · {n} events · segments ≥1 h, excluding dispatch orders'),
-    'flt.ram':           ('可靠性 × 可维修性 · 部件象限', 'Reliability × maintainability · component quadrants'),
-    'flt.ram_note':      ('按停机时长排序的表看不出这层结构: 同样 500h 的停机, "频发易修"要查根因、"罕发难修"要备件, 处置相反。设备类停机 {a}h / 外部类 {b}h (外部 = 电网·风况·运维操作, 不计设备可靠性)。海上机组 MDT 普遍偏高是交通窗口决定的, 不等于维修本身慢 — 跨场比 MDT 须同为海上。',
-                          'A table sorted by downtime hides this structure: the same 500 h of downtime calls for root-cause work if frequent-and-easy, and for spares if rare-and-hard — opposite actions. Equipment {a}h / external {b}h (external = grid, wind, operations; excluded from equipment reliability). Offshore MDT runs high because of transit windows, not slow repairs — compare MDT only against other offshore sites.'),
-    'flt.attrib_note':   ('归因覆盖: "无记录停机" {h}h 是全场最大单项, 因不给 MTBF 而在上图中不出现 — 它不是"没有故障", 是"没归上因"。口径为段起点 ±30min 首发码。缺口成因待定: 此前写的"98% 的段在前 72h 内有报警 ⇒ 长停远窗"不成立 — 前 72h 有报警不等于该报警与本段停机有因果 (双模审 2026-08-28 逮)。要定成因须改判"段起止是否落在某条报警的持续区间内", 未做之前只报缺口不报原因。',
-                          'Attribution coverage: unrecorded downtime {h}h is the largest single item and does not appear above because it has no MTBF — it is not "no fault", it is "not attributed". Basis: first alarm within ±30 min of segment start. The cause of the gap is undetermined: the earlier claim "98% of segments had an alarm in the prior 72 h ⇒ long outage, far window" does not hold — an alarm in the prior 72 h does not imply causation (caught by dual-model review 2026-08-28). Determining it requires testing whether the segment falls inside an alarm’s active interval; until then only the gap is reported, not its cause.'),
-    'flt.stop_pareto':   ('停机归因帕累托', 'Outage attribution Pareto'),
-    'flt.stop_pareto_note': ('按停机时长; 事件次数见悬停', 'By downtime; event counts on hover'),
-    'flt.stop_units':    ('台级停机 top{n}', 'Outages by unit · top {n}'),
-    'flt.stop_h':        ('停机时长', 'Downtime'),
-    'flt.stop_n':        ('次数', 'Events'),
-    'flt.stop_units_note': ('停机时长高 ≠ 设备差 — 须先看归因里外部类占比 (调度令已剔除)', 'High downtime ≠ poor equipment — check the external share in the attribution first (dispatch orders already excluded)'),
-    'flt.block_err':     ('本块未算出 ({k}): {e}', 'This block could not be computed ({k}): {e}'),
-    'ch.ml.legend':      ('— {s}', '— {s}'),
-})
-
-# ── 电量算账 ──────────────────────────────────────────────────────
-_add({
-    'en.kpi_theo':     ('理论可发 (实际+损失)', 'Theoretical (delivered + loss)'),
-    'en.kpi_act':      ('实际上网 · 等效满发 {h} h', 'Delivered · full-load {h} h'),
-    'en.kpi_loss':     ('损失率 · {v} MWh', 'Loss rate · {v} MWh'),
-    'en.cap_pass':     ('通过', 'Pass'),
-    'en.cap_fail':     ('不通过', 'Fail'),
-    'en.cap_note':     ('能量守恒核 · 理论 {op} 全额定上限 {cap}', 'Energy-balance check · theoretical {op} full-rated cap {cap}'),
-    'en.balance':      ('电量平衡', 'Energy balance'),
-    'en.win':          ('窗 {win}', 'window {win}'),
-    'en.blame':        ('损失责任分账', 'Loss by responsibility'),
-    'en.blame_note':   ('最大一笔是限电不是故障。进设备考核的只有"故障与检修停机" {n} MWh; 限电属交易与调度侧, 2026 起交易策略主导, 少发未必是弃风。',
-                        'The largest item is curtailment, not faults. Only fault and maintenance outages ({n} MWh) count against equipment; curtailment belongs to market and dispatch, and from 2026 trading strategy dominates, so under-production is not necessarily lost wind.'),
-    'en.states':       ('工况态明细', 'Operating-state detail'),
-    'en.state':        ('工况态', 'State'),
-    'en.share':        ('时长占比', 'Share of hours'),
-    'en.loss_mwh':     ('损失 MWh', 'Loss MWh'),
-    'en.states_note':  ('控制参数一致性与曲线欠发筛查在固定干净窗判, 已迁入「发电性能」页 — 本页只出当期电量账。',
-                        'Control-parameter consistency and curve under-production screening are judged in a fixed clean window and live on the Generation Performance page; this page shows only the current-period energy account.'),
-})
-
-# ── 部件问题 ──────────────────────────────────────────────────────
-_add({
-    'cmp.by_system':    ('按系统查看', 'By system'),
-    'cmp.cards_note':   ('点系统卡 → 该系统全场机组与依据; 九系统 = 交付九分册一一对应; 叶片与叶根 / 塔架与基础无 SCADA 轴 (分册蓝本取 CMS / 巡检)',
-                         'Open a system card for every unit and its evidence; the nine systems map one-to-one onto the nine delivered volumes; blade-and-root and tower-and-foundation have no SCADA axis (their volumes draw on CMS and inspection)'),
-    'cmp.alarm_n':      ('报警 {a}', 'alarm {a}'),
-    'cmp.unk_n':        (' · 不可判 {u}', ' · undeterminable {u}'),
-    'cmp.fleet_n':      (' · 全场 {n} 台', ' · fleet {n}'),
-    'cmp.detail_head':  ('{sys} · 全场 {n} 台', '{sys} · fleet of {n}'),
-    'cmp.counts':       ('报警 {a} · 不可判 {u} · 良好 {g} · 优秀 {e}', 'Alarm {a} · undeterminable {u} · good {g} · excellent {e}'),
-    'cmp.alarm_units':  ('报警机组', 'Units in alarm'),
-    'cmp.unk_units':    ('不可判 (监测/数据不足, ≠ 正常)', 'Undeterminable (monitoring or data insufficient — not normal)'),
-    'cmp.good_units':   ('良好 {n} 台 (轻微偏离, 周期观察 — 点开看逐台依据)', 'Good · {n} units (slight deviation, periodic observation — expand for per-unit evidence)'),
-    'cmp.n_units':      ('({n} 台)', '({n} units)'),
-    'cmp.basis':        ('依据', 'Evidence'),
-    'cmp.detail':       ('详情', 'Detail'),
-    'cmp.problem_page': ('该问题页 →', 'Issue page →'),
-    'cmp.none':         ('本系统全场无非常态。', 'No abnormal units in this system.'),
-    'cmp.rel_eyebrow':  ('部件可靠性 · 时间窗 {win}', 'Component reliability · window {win}'),
-    'cmp.rel_head':     ('部件停机贡献与可靠性', 'Downtime contribution and reliability by component'),
-    'cmp.rel_meta':     ('总停机 {t}h = 设备类 {a} + 外部类 {b}', 'Total downtime {t}h = equipment {a} + external {b}'),
-    'cmp.rel_note':     ('口径 ≥1h 停机段, 不含调度令 — 与"电量算账"里的停机时长{h} 不同, 差额是短于 1h 的停机段 (本页按段做可靠性指标须成段, 能量账按逐条工况态算电量): 两个数都对, 不可互相相减。',
-                         'Basis: outage segments ≥1 h, excluding dispatch orders — this differs from the downtime hours on Energy Accounting{h}; the gap is segments shorter than 1 h (reliability needs whole segments, the energy account counts every state record). Both are correct and must not be subtracted.'),
-    'cmp.rel_part': ('部件', 'Component'), 'cmp.rel_h': ('停机时长', 'Downtime'), 'cmp.rel_ev': ('事件', 'Events'),
-    'cmp.rel_mtbo': ('平均停机间隔(MTBO,h)', 'MTBO (h)'), 'cmp.rel_mdt': ('单次停机时长(MDT,h)', 'MDT (h)'), 'cmp.rel_units': ('影响机组', 'Units'),
-    'cmp.rel_fm':       ('典型失效模式 (按时长)', 'Typical failure mode (by duration)'),
-    'cmp.external':     ('外部', 'external'),
-    'cmp.fusion_moved': ('振动线结论与四源融合已移至独立板块 →', 'Vibration-line conclusions and four-source fusion live on their own tab →'),
-    'cmp.fusion_n':     ('({n} 台次收录)', '({n} unit-entries)'),
-    'cmp.cross_eyebrow':('多系统交叉机组', 'Units flagged across systems'),
-    'cmp.watch_head':   ('关注机组 (≥2 系统交叉, {n} 台; 点机组号 → 该台独立页)', 'Watch list (≥2 systems, {n} units; click a unit for its own page)'),
-    'cmp.n_sys':        ('系统数', 'Systems'),
-    'cmp.hit_sys':      ('命中系统', 'Systems flagged'),
-})
-
-# ── 系统维护 ──────────────────────────────────────────────────────
-_add({
-    'mt.eyebrow':    ('系统维护 · 数据输入与更新端口', 'System maintenance · data inputs and update ports'),
-    'mt.surveying':  ('盘点中…', 'Surveying…'),
-    'mt.fail':       ('盘点失败: {e}', 'Survey failed: {e}'),
-    'mt.head':       ('数据维护面 · {n} 个源', 'Data maintenance · {n} sources'),
-    'mt.note':       ('每个源给出: 当前覆盖 / 末次更新 / 约定位置 / 摄入命令 / 更新频率 / 责任方。要补数据就把新文件放到"约定位置"再跑"摄入命令"。',
-                      'For each source: coverage, last update, agreed location, ingest command, cadence and responsible line. To add data, put the new file at the agreed location and run the ingest command.'),
-    'mt.layer_data': ('数据层', 'Data layer'),
-    'mt.layer_mech': ('机理层', 'Domain-model layer'),      # 与经典枚举表的既有译名 'Domain model' 一致
-    'mt.layer_data_note': ('随运营滚动 — 新数据到位后跑摄入, 本体与判级自动刷新', 'Rolls with operations — run ingest when new data lands; ontology and ratings refresh automatically'),
-    'mt.layer_mech_note': ('相对稳定 — 换厂商资料或现场反馈修正时才动; 它是判据的地基, 变更须过审', 'Relatively stable — changes only with new vendor material or field corrections; it underpins the criteria, so changes go through review'),
-    'mt.src': ('源', 'Source'), 'mt.cov': ('当前覆盖', 'Coverage'), 'mt.last': ('末次更新', 'Last update'),
-    'mt.freq': ('频率', 'Cadence'), 'mt.resp': ('责任', 'Responsible line'), 'mt.how': ('补数据怎么做', 'How to add data'),
-    'mt.put': ('放到:', 'Put at:'), 'mt.run': ('跑:', 'Run:'), 'mt.rows_n': (' ({n} 条)', ' ({n} records)'),
-})
-
-# ── 发电性能 (固定干净窗; 第一性原理闸卡按用户 2026-08-28 裁定不出现在交付界面) ──
-_add({
-    'gen.eyebrow':    ('特性曲线与控制参数', 'Characteristic curves and control parameters'),
-    'gen.fixed_win':  ('随上方时间窗 · 按时间窗重算', 'Follows the selector · recomputed per window'),
-    'gen.win_range':  ('时间窗 {span}', 'window {span}'),
-    'gen.win_why':    ('七镜头与月度时序都按所选时间窗重算(含自定义起止日期)。'
-                       '提示:2025-07 ~ 2025-12 是"限电前干净判别窗"——2026 起交易策略主导,'
-                       '曲线含策略性少发,作设备判据时须注意;选到含 2026 的窗时该影响存在但不再遮掩。',
-                       'Both the seven lenses and the monthly series are recomputed for the selected window '
-                       '(including custom date ranges). Note: 2025-07 to 2025-12 is the "clean pre-curtailment" '
-                       'window — from 2026 trading strategy dominates, so curves contain strategic '
-                       'under-production; the effect is present (not hidden) when a 2026 window is selected.'),
-    'gen.loading':    ('正在计算七个镜头 (剥限电 · 逐档中位 · 同型机群残差)…', 'Computing seven lenses (curtailment stripped · per-bin medians · same-type fleet residuals)…'),
-    'gen.recomputing': ('正在按所选时间窗重算七镜头…', 'Recomputing the seven lenses for the selected window…'),
-    'gen.recomputing_short': ('正在按窗重算…', 'recomputing for this window…'),
-    'gen.ctrl':       ('控制参数一致性', 'Control-parameter consistency'),
-    'gen.ctrl_win':   ('窗 {win}', 'window {win}'),
-    'gen.cap':        ('封顶参数落位', 'Where the caps sit'),
-    'gen.cap_note':   ('台数落在几个离散位置上, 不是连成一片 — 这是配置/版本差异的形状; 磨损或工况引起的会是连续散布。两轴各自成组且分组不同台 ⇒ 参数级差异, 成因须配置台账。',
-                       'Units fall on a few discrete positions rather than a continuum — the shape of configuration or version differences; wear or operating conditions would scatter continuously. Both axes form groups with different units in each ⇒ a parameter-level difference; the cause needs the configuration ledger.'),
-    'gen.beta':       ('桨距调度同档差', 'Pitch-schedule deviation within a power band'),
-    'gen.beta_note':  ('同一功率档下的桨角与全场中位之差。{units} 偏离达中位的 {k} 倍 → {v}', 'Pitch angle minus the fleet median within the same power band. {units} deviate {k}× the median → {v}'),
-    'gen.ruler':      ('尺子巡检 · 极差 ÷ σ', 'Ruler check · range ÷ σ'),
-    'gen.ruler_note': ('判据自身的分辨力。38 台正态抽样的极差期望约 3.7σ, 明显超出即分布非单峰 — 与左图的分组互为印证; 落在期望附近的轴 (额定桨角) 则是连续分布。',
-                       "The criterion's own resolving power. For 38 normally distributed units the expected range is about 3.7σ; a clear excess means the distribution is not unimodal, corroborating the grouping on the left; an axis near the expectation (rated pitch angle) is a continuous distribution."),
-    'gen.cands':      ('曲线欠发筛查', 'Curve under-production screening'),
-    'gen.cands_meta': ('{note} · σ = {s}%', '{note} · σ = {s}%'),
-    'gen.no_cands':   ('无候选', 'no candidates'),
-    'gen.figs':       ('七个镜头 · 逐档中位与同型机群残差', 'Seven lenses · per-bin medians and same-type residuals'),
-    'ch.cap.p':       ('满发封顶', 'Power cap'),
-    'ch.cap.w':       ('转速封顶', 'Speed cap'),
-    'ch.cap.median':  ('中位 {v}', 'median {v}'),
-    'ch.cap.legend':  ('圆内数字 = 台数, 圆面积 ∝ 台数', 'number = units, area ∝ units'),
-    'ch.beta.median': ('全场中位 {v}°', 'fleet median {v}°'),
-    'ch.ruler.norm':  ('正态期望 3.7σ', 'normal expectation 3.7σ'),
-    'ch.ruler.row':   ('{r}σ  σ = {s}', '{r}σ  σ = {s}'),
-})
-
-# ── 振动融合分析 ────────────────────────────────────────────────────
-_add({
-    'fus.eyebrow':     ('振动与 SCADA 证据视图 · 展示振动结论、SCADA 指标、报警与工单线索',
-                        'Vibration and SCADA evidence view · vibration findings, SCADA indicators, alarms and work-order context'),
-    'fus.unavailable': ('融合面数据不可用: {e}', 'Fusion data unavailable: {e}'),
-    'fus.sub.matrix':  ('融合矩阵', 'Fusion matrix'),   'fus.sub.loop': ('端到端闭环', 'Closed loops'),
-    'fus.sub.units':   ('逐台情况', 'Per unit'),        'fus.sub.events': ('报警工单', 'Alarms and work orders'),
-    'fus.sub.cms':     ('振动分析', 'Vibration assessment'),
-    'fus.rules_head':  ('两条判读纪律 (随板块常显)', 'Two reading rules (always shown)'),
-    'fus.rule1':       ('① 同一失效机制的多个观测面 ≠ 多源印证 — 融合禁按源数加权 (29# 柱塞/温度/振动稳态/振动瞬态 = 四个观测面一条机制链)。',
-                        '① Several observation surfaces of one failure mechanism ≠ multi-source confirmation — fusion must not weight by source count (unit 29: plunger / temperature / steady vibration / transient vibration are four surfaces on one mechanism chain).'),
-    'fus.rule2':       ('② "没报警" ≠ "正常" — 在已知盲区上, 缺报警只说明监测面无效, 不说明设备无恙。',
-                        '② "No alarm" ≠ "normal" — on a known blind spot a missing alarm only shows the monitoring surface is ineffective, not that the equipment is fine.'),
-    'fus.matrix_head': ('全场融合矩阵 (38 台 × 6 个证据面)', 'Fleet fusion matrix (38 units × 6 evidence surfaces)'),
-    'fus.matrix_note1':('各列取数窗口不同 (见列头) — 不同窗的两格同时发红不等于"同时发生", 不可当共时印证。',
-                        'Columns use different windows (see headers) — two red cells from different windows are not simultaneous and do not corroborate each other.'),
-    'fus.matrix_note2':('每格右侧曲线 = 逐月记忆轨迹 ({n} 个月, {m0} 起; 实心点 = 现窗) — 现窗快照会丢形态, 轨迹才看得见"塌→回升→翻转"类机制签名。',
-                        'The curve in each cell is the monthly memory track ({n} months from {m0}; solid dot = current window) — a snapshot loses shape; only the track shows signatures such as collapse → recovery → reversal.'),
-    'fus.legend_state':('格子颜色 = 设备状态 (业主口径, 与 windcms 报告同一套):', 'Cell colour = equipment state (operational basis, same as the reviewed condition report):'),
-    'fus.legend_tier': ('徽章 = 证据状态 (可信度, windcms 九枚举):', 'Badge = evidence tier (confidence, the nine windcms values):'),
-    'fus.legend_note': ('两轴不可互相替代: "候选"说的是可信度不是设备好坏; "良好"说的是设备状态不是证据强度。设备状态只取自振动线的判级栏这一个来源。',
-                        "The two axes cannot substitute for each other: \"candidate\" is about confidence, not equipment health; \"good\" is about equipment, not evidence strength. Equipment state comes from one source only: the vibration line's rating column."),
-    'fus.col.vib': ('振动', 'Vibration'), 'fus.col.temp': ('温度', 'Temperature'), 'fus.col.lub': ('润滑柱塞', 'Lubrication plunger'),
-    'fus.col.oil': ('油液', 'Oil'), 'fus.col.alarm': ('报警', 'Alarms'), 'fus.col.wo': ('工单', 'Work orders'),
-    'fus.col.fixed': (' · 不随本时间窗', ' · fixed'), 'fus.col.display_only': (' · 只展示', ' · display only'),
-    'fus.short.nodata': ('数据不足', 'insufficient data'), 'fus.short.uneval': ('未评估', 'not assessed'),
-    'fus.short.nosample': ('未取样', 'no sample'), 'fus.short.stale': ('样龄过期', 'sample stale'),
-    'fus.short.normal': ('正常', 'normal'), 'fus.short.unlisted': ('未收录', 'not listed'),
-    'fus.tip_state':   (' — 设备状态 {s}', ' — state {s}'), 'fus.tip_tier': (' / 证据状态 {e}', ' / tier {e}'),
-    'fus.alarm_n':     ('{n} 条', '{n}'), 'fus.alarm_rank': (' · 第 {r}/{t}', ' · rank {r}/{t}'),
-    'fus.burst':       ('⚠ {p}% 是码 {c} 抖动', '⚠ {p}% is code {c} chattering'),
-    'fus.burst_tip':   ('⚠ 单码 {c} 占 {p}%, 中位 {m} 秒/次 — 条数被抖动灌满', '⚠ code {c} alone is {p}%, median {m} s per event — the count is filled by chatter'),
-    'fus.no_alarm':    ('时间窗内无报警', 'no alarms in window'), 'fus.fam_tip': ('{n} 条 · 全场最多 {m} 条', '{n} · fleet max {m}'),
-    'fus.wo_n':        ('{n} 单', '{n} orders'), 'fus.wo_none': ('时间窗内无传动链工单', 'no drivetrain work orders in window'),
-    'fus.matrix_foot': ('润滑柱塞各台设定本就不同 (全场单次循环 22~961 秒), 所以此列只展示不判级, 只有已定案的机制链才标色; 油液多数台末次取样距今超 12 个月, 此列看的是"样够不够新", 不代表当前状态。',
-                        "Plunger settings differ by unit (single-cycle 22–961 s across the fleet), so that column is display only and is coloured only for confirmed mechanism chains; most units' last oil sample is over 12 months old, so that column shows sample freshness, not current state."),
-    'fus.loop_none':   ('暂无端到端闭环证据:现场提交的振动数据包里没有「报警→检修→复测」的成对记录',
-                        'No end-to-end closed-loop evidence yet: the submitted vibration package contains no '
-                        'alarm → repair → re-measure pairs'),
-    'fus.loop_head':   ('维修后验证案例 (检出 → 换件 → 恢复)', 'Post-maintenance verification cases (detected → replaced → recovered)'),
-    'fus.loop_note':   ('有换件记录且同口径指标回落, 可作为检测链已闭合的案例; 指标仍在上升但缺少可匹配工单的机组, 需现场或台账确认后再关闭。',
-                        'A documented replacement followed by a metric reduction on the same basis is shown as a closed verification case. Units with rising metrics remain open until field confirmation, a matching work order, or ledger confirmation is available.'),
-    'fus.loop_closed': ('已闭环 · 换件后恢复 ({n} 例)', 'Verified recovery after replacement ({n})'),
-    'fus.loop_item': ('事项', 'Item'), 'fus.loop_date': ('换件日', 'Replaced'), 'fus.loop_ba': ('换前 → 换后 (同标尺)', 'Before → after (same scale)'),
-    'fus.loop_ratio': ('恢复比', 'Recovery'), 'fus.loop_verdict': ('判', 'Verdict'),
-    'fus.loop_neg':    ('负面对照: 工单后持平 {n} 例 (换件 ≠ 必然回落, 说明回落非巧合)。', 'Negative control: {n} cases flat after a work order (replacement does not always lower the metric, so the falls are not coincidence).'),
-    'fus.loop_rising': ('未闭环 · 指标上升待现场/工单确认 ({n} 台)', 'Open · rising metrics awaiting field confirmation, a matching work order, or ledger confirmation ({n} units)'),
-    'fus.loop_el': ('早期 → 近期 (同标尺)', 'Early → recent (same scale)'), 'fus.loop_x': ('倍比', 'Ratio'), 'fus.loop_state': ('状态', 'State'),
-    'fus.loop_bound':  ('数据边界: {b}', 'Data boundary: {b}'),
-    'fus.units_head':  ('逐台情况 (全场 {u} 台 / {n} 台次{f}; 深入谱图/波形 → "振动分析")', 'Per unit ({u} units / {n} entries{f}; detailed condition traces → "Vibration assessment")'),
-    'fus.units_filter':(' · 筛选: {l}', ' · filter: {l}'), 'fus.clear': ('× 清除', '× clear'),
-    'fus.lv.bad': ('定论/报警级', 'Confirmed / alarm'), 'fus.lv.warn': ('预警级', 'Warning'),
-    'fus.lv.note': ('候选/监视/观察级', 'Candidate / watch'), 'fus.lv.ok': ('销案/无新证', 'Closed / no new evidence'),
-    'fus.unlisted_head': ('未列机组 {n} 台 (振动线未收录 — 未列 ≠ 正常)', 'Unlisted units · {n} (not covered by the vibration line — unlisted ≠ normal)'),
-    'fus.unlisted_link': ('未列 {n} 台', 'unlisted {n}'),
-    'fus.temp_now': ('温度轴现状', 'Temperature now'), 'fus.lub_now': ('润滑柱塞现状', 'Plunger now'),
-    'fus.eval_head':   ('评价体系 (规范化定义 — 两轴并存, 不可互相替代)', 'Rating system (two axes, not interchangeable)'),
-    'fus.eval_state': ('设备状态 (业主口径)', 'Equipment state (operational basis)'), 'fus.eval_tier': ('证据状态 (可信度)', 'Evidence tier (confidence)'), 'fus.eval_meaning': ('含义', 'Meaning'),
-    'fus.m.bad': ('异常严重, 建议尽快停机检查', 'Severe abnormality; stop and inspect soon'), 'fus.m.bad_e': ('证据: 已定案', 'evidence: settled'),
-    'fus.m.warn': ('存在需现场核实或处置的异常', 'Abnormality needing field verification or action'), 'fus.m.warn_e': ('证据: 基本确认 / 新出现待核实', 'evidence: essentially confirmed / newly seen, to verify'),
-    'fus.m.ok': ('轻微偏离, 记录观察', 'Slight deviation; record and observe'), 'fus.m.ok_e': ('证据: 待现场核实 / 已记为基线下窗观察', 'evidence: to verify on site / recorded as baseline for the next window'),
-    'fus.m.ex': ('未见异常, 保持常规监视', 'No abnormality; routine monitoring'), 'fus.m.ex_e': ('证据: 仅记录 / 已取消', 'evidence: record only / withdrawn'),
-    'fus.m.unk': ('数据不足或监测中断 — 不等于正常', 'Insufficient data or monitoring gap — not the same as normal'), 'fus.m.unk_e': ('证据: 数据不足', 'evidence: insufficient'),
-    'fus.eval_note':   ('设备状态答"这台设备现在怎么样" (危险 > 报警 > 良好 > 优秀 > 不可判); 证据状态答"这个判断有多可信" (定论 → INSUFFICIENT)。设备状态一律取自振动线的判级栏这一个来源。本页判级为转录, 不产新判级; 标注为引用同族/谱线级/测量链/未映射的行不作部件级结论消费; claim_window = 该结论的证据时间窗, 窗外状态该结论不背书。',
-                        'Equipment state answers "how is this unit now" (critical > alarm > good > excellent > undeterminable); evidence tier answers "how credible is that judgement" (confirmed → INSUFFICIENT). Equipment state comes from the reviewed vibration assessment. Rows marked as same-family reference / line-level / measurement-chain / unmapped are excluded from component-level conclusions; claim_window is the evidence window, and the conclusion does not vouch for states outside it.'),
-    'fus.col.part': ('部件', 'Component'), 'fus.col.state_concl': ('设备状态 / 振动结论', 'State / vibration conclusion'),
-    'fus.col.claim': ('证据时间窗', 'Evidence window'), 'fus.col.claim_axis': ('26-01 → 09', '26-01 → 09'),
-    'fus.col.signal': ('振动信号', 'Vibration signal'), 'fus.col.tempaxis': ('温度轴', 'Temperature'), 'fus.col.tempaxis_note': ('±6 K', '±6 K'),
-    'fus.col.oil': ('油样', 'Oil sample'),
-    'fus.win_none': ('未标窗', 'no window'), 'fus.win_n': ('{n} 个窗段', '{n} spans'),
-    'fus.oil_fresh': ('近共时', 'recent'), 'fus.oil_stale': ('stale > 12 个月', 'stale > 12 months'), 'fus.oil_none': ('无样', 'no sample'),
-    'fus.ev_win':      ('时间窗 {win} · 顶部选择器可换', 'Window {win} · change it with the selector above'),
-    'fus.ev_head':     ('{c} — 报警 {an} 条 / {au} 台 · 工单 {wn} 条 / {wu} 台 (时间窗内)', '{c} — alarms {an} / {au} units · work orders {wn} / {wu} units (in window)'),
-    'fus.ev_code': ('码', 'Code'), 'fus.ev_text': ('报警内容', 'Alarm'), 'fus.ev_units': ('台数', 'Units'), 'fus.ev_dur': ('累计时长 h', 'Total h'),
-    'fus.ev_top': ('台分布 top: ', 'Top units: '), 'fus.ev_recent': ('最近 {n} 条明细', 'Latest {n} events'),
-    'fus.ev_time': ('时间', 'Time'), 'fus.ev_min': ('时长 min', 'Duration min'),
-    'fus.ev_no_alarm': ('时间窗内无该部件报警', 'No alarms for this component in the window'),
-    'fus.ev_wo_name': ('故障名称', 'Fault'), 'fus.ev_wo_act': ('处理', 'Action'), 'fus.ev_wo_cause': ('原因', 'Cause'),
-    'fus.ev_no_wo':    ('时间窗内无该部件工单 (工单数据止 2024-11, 2026 类窗必空 = 数据边界)', 'No work orders for this component in the window (ledger ends 2024-11; 2026 windows are necessarily empty — a data boundary)'),
-    'fus.cms_loading': ('载入振动评估结果…', 'Loading vibration assessment…'),
-    'fus.cms_head':    ('振动状态评估 (windcms 报告 {d} 结果转录)', 'Vibration condition assessment ({d} reviewed report)'),
-    'fus.cms_note':    ('状态管理判级, 不代表确诊数量; 判据/谱线/计算过程不在本页 — 深潜走底部独立系统。',
-                        'Condition-management ratings, not a count of confirmed faults; criteria, detailed signal markers and calculations are not on this page — use the standalone system linked below.'),
-    # ★2026-09-19: 页面上原来只有"出件日 + 证据时间窗末", 没有**起始日** ⇒ 用户拿 2026-03~04 的导出
-    #   只看到 4 月的记录时会问"3 月的数据呢"。窗名(w0316)只是起始日代号, 这里把实算区间摆出来并说明
-    #   为什么图上多显示末期日期 (末窗状态=取时间窗内最新记录, 是口径不是缺数据)。
-    #   ★英文措辞要过 lang.audit 的语言包闸: 'spectra'/'waveforms'/'characteristic frequency' 等 36 条
-    #   内部审核黑话**禁用**(2026-09-19 实逮: 首版写 spectra 直接让 /v2 出页 500)。
-    'fus.cms_window':  ('数据窗 {r} —— 本时间窗**实算**覆盖区间 (窗名只是起始日代号); 页面上的图谱与"末窗"状态取自时间窗内最新记录, 故多显示末期日期。',
-                        'Data window {r} — the measured coverage of this window (the name is only its start-date code). Charts and "latest-window" states come from the newest record inside the window, so later dates dominate the display.'),
-    'fus.cms_action':  ('需行动机组 (报警 / 不可判)', 'Units needing action (alarm / undeterminable)'),
-    'fus.cms_focus': ('状态焦点', 'Focus'), 'fus.cms_focus_note': ('(传动链: 主承前 · 主承后 · 齿轮箱 · 发电机两端)', '(drivetrain: main bearing fore / aft · gearbox · generator both ends)'),
-    'fus.cms_evidence': ('证据强度', 'Evidence strength'), 'fus.cms_act': ('行动', 'Action'),
-    'fus.cms_grades':  ('38 台部件等级一览', 'Component grades, all 38 units'),
-    'fus.cms_chain': ('部件状态链', 'Component state chain'), 'fus.cms_chain_note': ('(主承前 · 主承后 · 齿轮箱 · 发电机; 悬停看逐件等级)', '(main bearing fore / aft · gearbox · generator; hover for each grade)'),
-    'fus.cms_overall': ('综合', 'Overall'), 'fus.cms_basis': ('口径说明', 'Basis'),
-    'fus.cms_basis_note': ('本表 = windcms 状态管理判级 (含标量趋势与数据可判性); 融合矩阵 / 逐台结论 = 振动线定谳链转录 (候选以上)。两口径差异台: 仅本表列 {a} (多为标量发作 / 数据不可判, 未到定谳链); 仅定谳链列 {b} (预警级在察, 状态管理未升)。',
-                        "This table = windcms condition-management ratings (scalar trends and data judgeability); the matrix and per-unit conclusions = transcription of the vibration line's verdict chain (candidate and above). Units differing between the two: only here {a} (mostly scalar episodes or undeterminable data, not yet in the verdict chain); only in the verdict chain {b} (warning tier under watch, not escalated in condition management)."),
-    'fus.none_word': ('无', 'none'),
-    'fus.cms_link':    ('谱图 · 波形 · 判据 · 工作台等分析功能在独立 windcms 系统 →', 'Detailed condition traces, criteria and the workbench live in the standalone condition-monitoring system →'),
-    'fus.cms_open': ('打开 windcms ↗', 'Open windcms ↗'), 'fus.cms_static': ('(未启动时用静态版)', '(static copy if it is not running)'),
-    'chain.mbf': ('主轴承前', 'MB fore'), 'chain.mba': ('主轴承后', 'MB aft'), 'chain.ps': ('行星', 'Planet'),
-    'chain.ims': ('中间', 'IMS'), 'chain.hss': ('高速', 'HSS'), 'chain.gde': ('发电机DE', 'Gen DE'), 'chain.gnde': ('发电机NDE', 'Gen NDE'),
-    'chain.gb': ('齿轮箱', 'Gearbox'), 'chain.gen': ('发电机', 'Generator'),
-})
-
-# ── 判别值枚举 ────────────────────────────────────────────────────
-# 这批值在 API 侧**有意保留中文** (_KEEP_ZH): 前端拿它们做判别 (x.部件 === '无记录停机'),
-# 译了判别就失效。所以它们必须在**显示层**译 —— 数据层译反而会打穿逻辑。
-# 停机归因部件 (rel.表[].部件) 与九系统是两套分类: 它含外部类与未归因项。
-_add({
-    'attr.无记录停机':      ('无记录停机', 'Unrecorded downtime'),
-    'attr.未归类':          ('未归类', 'Unclassified'),
-    'attr.运维操作':        ('运维操作', 'Operations and maintenance actions'),
-    'attr.变桨与液压':      ('变桨与液压', 'Pitch and hydraulics'),
-    'attr.塔架/结构':       ('塔架/结构', 'Tower and structure'),
-    'attr.轮毂/叶片':       ('轮毂/叶片', 'Hub and blades'),
-    'attr.电网/供电(外部)': ('电网/供电(外部)', 'Grid and power supply (external)'),
-    'attr.风况(外部)':      ('风况(外部)', 'Wind conditions (external)'),
-})
-
-# 振动线部件形态 (fus.表[].部件) —— 带限定语的部件名, 同为判别值
-_add({
-    'vp.(振动线未收录)':              ('(振动线未收录)', '(not covered by the vibration line)'),
-    'vp.齿轮箱高速轴':                ('齿轮箱高速轴', 'Gearbox high-speed shaft'),
-    'vp.齿轮箱中间轴':                ('齿轮箱中间轴', 'Gearbox intermediate shaft'),
-    'vp.齿轮箱二级行星内齿圈':        ('齿轮箱二级行星内齿圈', 'Gearbox second-stage planetary ring gear'),
-    'vp.齿轮箱(未映射)':              ('齿轮箱(未映射)', 'Gearbox (not mapped)'),
-    'vp.齿轮箱(谱线级, 未映射到部件)': ('齿轮箱(谱线级, 未映射到部件)', 'Gearbox (signal level, not mapped to a component)'),
-    'vp.齿轮箱(测量链问题, 非部件)':   ('齿轮箱(测量链问题, 非部件)', 'Gearbox (measurement-chain issue, not a component)'),
-    'vp.发电机DE':                    ('发电机DE', 'Generator DE'),
-    'vp.发电机NDE':                   ('发电机NDE', 'Generator NDE'),
-    'vp.发电机DE+NDE(两端)':          ('发电机DE+NDE(两端)', 'Generator DE and NDE (both ends)'),
-    'vp.发电机(未映射)':              ('发电机(未映射)', 'Generator (not mapped)'),
-    'vp.发电机(引用同族, 见族主台)':  ('发电机(引用同族, 见族主台)', "Generator (same-family reference; see the family's lead unit)"),
-    'vp.主轴承(后)+集中润滑泵':       ('主轴承(后)+集中润滑泵', 'Main bearing (aft) plus centralised lubrication pump'),
-})
-
-# ── 逐台 / 单问题浮层 (原为服务端独立页 /turbine/<t> · /problem/<t>/<sys>) ──────
-# 静态站上那两条路径是 404 (nginx 只有静态文件)。经典页当年用页内浮层兜, v2 同此:
-# 数据从预烤取, 图表复用 charts.fig —— 与在线版看到的是同一批图。
-_add({
-    'pop.unit':        ('{u} · 全部问题', '{u} · all issues'),
-    'pop.problem':     ('{u} · {sys}', '{u} · {sys}'),
-    'pop.close':       ('关闭', 'Close'),
-    'pop.none':        ('本时间窗无记录', 'No records in this window'),
-    'pop.loading':     ('载入中…', 'Loading…'),
-    'pop.orders':      ('工单 {n} 单', '{n} work orders'),
-    'pop.orders_none': ('时间窗内无工单', 'No work orders in this window'),
-    'pop.o_date':      ('日期', 'Date'),
-    'pop.o_fault':     ('故障', 'Fault'),
-    'pop.o_check':     ('排查', 'Investigation'),
-    'pop.o_cause':     ('原因', 'Cause'),
-    'pop.o_act':       ('处置', 'Action'),
-    'pop.o_kind':      ('类别', 'Category'),
-    'pop.acts':        ('动作统计', 'Action counts'),
-    'pop.orders_one':  ('工单 1 单', '1 work order'),
-    'ov.evidence':     ('查看完整依据', 'View basis'),
-    # ★2026-09-22 用户测试 2: 「需要关注」的行来自融合面链盘,SCADA 九系统可能没有对应报警行
-    #   (实测 WTG09 齿轮箱 bad 台)—— 此时依据必须**按本行字段自组句**,不能空着。
-    'ov.basis_fusion': ('本台依据来自融合面(九系统判级无「报警」行):部件 {part} · 链路走到第 {done}/{n} 步 · '
-                        '卡在「{stuck}」 · 证据源 {srcs}',
-                        'Basis from the fusion view (no SCADA system-level alarm row): component {part} · '
-                        'chain at step {done}/{n} · stuck at "{stuck}" · evidence sources {srcs}'),
-    'ov.all_steps_done': ('已走完(待验收)', 'all steps done (pending acceptance)'),
-    'ov.no_srcs':        ('无(该台无外部证据源)', 'none'),
-    'sys.other':       ('其他', 'Other'),
-    'win.through':     ('至{date}', 'Through {date}'),
-    'fig.vibbar_note': ('条为本台与全场中位之比; 色按 z (稳健离群度): 绿 <2 · 橙 2~3 · 红 ≥3',
-                        'Ratio of this unit to the fleet median; colour by z (robust outlier score): green <2, amber 2–3, red ≥3'),
-    'fig.point':        ('测点', 'Measurement point'),
-    'fig.x_median':     ('×中位', '× median'),
-    'fig.this_unit':    ('本台', 'This unit'),
-    'fig.fleet_median': ('全场中位', 'Fleet median'),
-    'fig.quartile':     ('四分位', 'Quartile band'),
-    'fig.trend':        ('趋势', 'Trend'),
-})
-
-
-# ══════════════════════════════════════════════════════════════════
-#  取用
-# ══════════════════════════════════════════════════════════════════
-
-# Display-only fallback for source evidence labels; retain the original IDs.
-from src.windscada.data_tpl_en import GRADE, GRADE2
-L.update({'tier.detail.' + zh: (zh, en) for zh, en in {**GRADE, **GRADE2}.items()})
-
-LANGS = ('zh', 'en')
-_IDX = {'zh': 0, 'en': 1}
-
-
-def t(key: str, lang: str = 'zh', **kw) -> str:
-    """取一条文案。key 不存在 = **抛错**, 不静默退回中文。
-
-    静默退回是旧架构最贵的一课: 页面上多一段中文, 没有任何日志, 只有用户看得见。
-    """
-    if key not in L:
-        raise KeyError('语言包无此 key: %r' % key)
-    s = L[key][_IDX[lang]]
-    return s.format(**kw) if kw else s
-
-
-def pack(lang: str) -> dict[str, str]:
-    """整包导出, 供注入页面 (`const L = {...}`)。"""
-    i = _IDX[lang]
-    return {k: v[i] for k, v in L.items()}
-
-
-def check() -> list[str]:
-    """构建期硬闸。返回问题清单, 空 = 通过。
-
-    在**出页面之前**跑。旧架构把这些留到运行期, 于是每一条都变成一次线上残留。
-    """
-    bad = []
-    for k, v in sorted(L.items()):
-        if not isinstance(v, tuple) or len(v) != 2:
-            bad.append('%s: 值不是 (zh, en) 二元组' % k)
-            continue
-        zh, en = v
-        if not zh.strip():
-            bad.append('%s: 中文为空' % k)
-        if not en.strip():
-            bad.append('%s: 英文为空' % k)
-        # 英文条目里不许残留中日韩字符
-        cjk = [c for c in en if '一' <= c <= '鿿']
-        if cjk:
-            bad.append('%s: 英文含中文字符 %r' % (k, ''.join(cjk)[:12]))
-        low = en.lower()
-        for phrase in PUBLIC_COPY_BANNED_EN:
-            if phrase in low:
-                bad.append('%s: 英文含内部审核黑话 %r' % (k, phrase))
-        # 占位符集合必须一致 —— 英文漏掉一个 {n}, 渲染不报错, 数字凭空消失
-        pz, pe = set(_PH.findall(zh)), set(_PH.findall(en))
-        if pz != pe:
-            bad.append('%s: 占位符不一致 zh=%s en=%s' % (k, sorted(pz), sorted(pe)))
-    return bad

+ 817 - 0
app_frontEnd/web/src/i18n/en.ts

@@ -0,0 +1,817 @@
+/** 观澜 · 英文文案(P13 补:从旧语言包取回英文值;取不到者按项目约定回落原文)。 */
+export const en: Record<string, string> = {
+  "app.brand": "Guanlan",
+  "app.build": "Latest completed build",
+  "app.lang_en": "English",
+  "app.lang_zh": "Chinese",
+  "app.subtitle": "Offshore Fleet",
+  "app.title": "Guanlan · Wind Analytics and Decision Support",
+  "ask.common_q": "{cat} · common questions (click to ask, or type below)",
+  "ask.done_meta": "model {m} · {s} s · {n} steps",
+  "ask.elapsed": "{s} s",
+  "ask.empty": "The question is empty",
+  "ask.en_qa_failed": "English QA failed: this model answer was withheld. Please ask again.",
+  "ask.error": "Error: {e}",
+  "ask.go": "Ask",
+  "ask.grounding": "Every answer carries an [object id] and uses the reviewed evidence layer; model responses cannot create or change equipment ratings.",
+  "ask.idle": "Idle — click a common question or type one. One question at a time.",
+  "ask.local_down": "Local model not running (Ollama 127.0.0.1:11434 unreachable); offline build never falls back to cloud",
+  "ask.model": "Model",
+  "ask.no_models": "No models available",
+  "ask.offline": "Offline copy: the model Q&A needs a live connection to the system.",
+  "ask.partial": "Quick answer · 27B refining",
+  "ask.placeholder": "Ask anything ({cat})",
+  "ask.question": "Question: {q}",
+  "ask.refined": "Refined (default tier {m} failed the gate; re-answered by the stronger model)",
+  "ask.refs.none": "No matching contract claim (contract {sha}): this answer is not backed by the facts contract; treat as model output",
+  "ask.refs.title": "Contract citations ({n}, contract {sha}) — deliverable claims matched to the question/answer; cites from qa_refs",
+  "ask.review": "Cross-review · {m}",
+  "ask.review_err": "Review incomplete: {e}",
+  "ask.review_note": "A second model reviewed the answer on the same evidence. The traceability check only tests whether numbers and unit ids are in range; this review tests whether conclusions are supported by evidence.",
+  "ask.running": "Reasoning…",
+  "ask.verify_bad": "Traceability check incomplete ({n} references)",
+  "ask.verify_ok": "Traceability check {n}/{n} verified",
+  "ask.xrev": "Cross-review",
+  "ask.xrev_note": "(cross-checked by DeepSeek 14B, + about 1 min)",
+  "ask.xrev_title": "A second model reviews on the same evidence; self-review by one model is not independent",
+  "asst.alarm_type": "alarm type {t}",
+  "asst.cause_counts": "manual ×{r} · work orders ×{w} · codes {c}",
+  "asst.causes": "← Possible causes (manual directions + work-order evidence)",
+  "asst.check": "What to check",
+  "asst.code_placeholder": "Alarm code, e.g. 3225 / 3130 / 13902",
+  "asst.common": "Common",
+  "asst.corrective": "Corrective",
+  "asst.corrective_flow": "Alarm code → failure mode → manual step-by-step procedure → historical work orders",
+  "asst.drawings": "Related drawings",
+  "asst.failmodes": "Failure modes indicated",
+  "asst.faulttree": "Fault tree",
+  "asst.faulttree_flow": "System failure modes ranked by evidence volume, with component-level cause",
+  "asst.fm_counts": "{a} site alarms / {w} work orders in this class",
+  "asst.head": "Maintenance Assistant — field guidance with reviewed English",
+  "asst.head_note": "Grounded in manuals, fault records and work orders, with source references retained for traceability",
+  "asst.identity": "System {sys} · {n} site alarms",
+  "asst.interval": "interval {i}",
+  "asst.known_tags": "该系统手册出现过的位号 (对图纸时参考):",
+  "asst.no_causes": "(no troubleshooting entries in the manual for this class — cause layer pending)",
+  "asst.no_name": "(not in the code table)",
+  "asst.no_steps": "No manual procedure for this code (the manual covers 161 of 322 site codes) — consult drawings or vendor support.",
+  "asst.node": "Node",
+  "asst.nodes": "Nodes to check · {c}",
+  "asst.nodes_note": "no designator given for this step → expanded by circuit composition, check point by point",
+  "asst.prev_empty": "(no annual maintenance entries matched to this system — the work-instruction book is organised by location code and does not map one-to-one onto systems)",
+  "asst.prev_fm": "Failure modes (by site alarm volume)",
+  "asst.prev_head": "{sys} preventive chain · {f} failure-mode classes · {n} preventive tasks",
+  "asst.prev_tasks": "Annual tasks",
+  "asst.preventive": "Preventive maintenance",
+  "asst.preventive_flow": "System → failure mode → annual task → work-instruction steps",
+  "asst.qty": "quantity: {q}",
+  "asst.query_failed": "Query failed: {e}",
+  "asst.querying": "Querying…",
+  "asst.spec_name": "Name",
+  "asst.spec_no": "Param",
+  "asst.spec_range": "Range",
+  "asst.spec_val": "Setting",
+  "asst.specs": "Acceptance specs (live parameter table)",
+  "asst.step_n": "Step {n}",
+  "asst.steps": "Field procedure · {n} steps",
+  "asst.steps_note": "节点部件 = 手册原文元件位号 (确定); 判定规范 = 实机参数表按物理量匹配的候选判据, 非厂商绑定 — 现场以铭牌与图纸复核。",
+  "asst.tags": "Node components",
+  "asst.tree_codes": "symptom codes {c}",
+  "asst.tree_counts": "{a} site alarms · {w} work orders",
+  "asst.tree_empty": "No failure modes recorded for this system",
+  "asst.tree_head": "{sys} fault tree · {n} failure modes",
+  "asst.tree_note": "Level 1 groups site systems by failure class; level 2 merges manual troubleshooting directions with work-order cause counts. Bar length reflects available evidence (alarms + work orders × 10), and the tree stops at two levels.",
+  "asst.unassigned": "unassigned",
+  "asst.wi_steps": "WI · {n} steps",
+  "asst.wo_action": "Action",
+  "asst.wo_fault": "Fault",
+  "asst.wo_head": "What was actually done on site ({sys}: {n} historical work orders, ledger 2020–2024)",
+  "asst.wo_note": "Work-order window ends 2024-11-21; 2025–26 not provided by the site, so "0" means missing records, not no repair.",
+  "attr.变桨与液压": "Pitch and hydraulics",
+  "attr.无记录停机": "Unrecorded downtime",
+  "attr.未归类": "Unclassified",
+  "attr.运维操作": "Operations and maintenance actions",
+  "cat.equip": "Equipment",
+  "cat.equip.hint": "Equipment state / mechanism / rating — evidence from unit profiles, object queries and mechanism search",
+  "cat.equip.q1": "What is the state of WTG29, why, and what should be done?",
+  "cat.equip.q2": "Which units carry alarm-level or confirmed-tier ratings? List each with its review level.",
+  "cat.equip.q3": "Which mechanism families are in the failure-mechanism library, which units do they involve, and in what state?",
+  "cat.loss": "Loss analysis",
+  "cat.loss.hint": "Current 3-month and forward-looking loss estimates — values are shown as tariff ranges",
+  "cat.loss.q1": "What is the fleet loss structure over the last 3 months? By state and by unit, energy and value.",
+  "cat.loss.q2": "If WTG29 runs to main-bearing failure, what is the future loss magnitude versus repairing now?",
+  "cat.loss.q3": "How does persistent curtailment change the opportunity cost of maintenance?",
+  "cat.risk": "Safety-risk outlook",
+  "cat.risk.hint": "Risk exposure / degradation trend / blind spots — evidence via ont_risk / ont_mechanism_search; unlisted ≠ normal",
+  "cat.risk.q1": "Which units have rising risk and need close watching? What do the linked monitoring readings show?",
+  "cat.risk.q2": "Where are the monitoring blind spots? Which "no alarm" cases are not the same as normal?",
+  "cat.risk.q3": "29号轴承正在承受金属接触, 风险敞口怎么评估?",
+  "cat.sched": "Maintenance scheduling",
+  "cat.sched.hint": "Scheduling sandbox / weather windows / acceptance tree — evidence via ont_scenario / ont_get",
+  "cat.sched.q1": "When is the best time to repair the pump on WTG29? Compare loss and risk across scenarios.",
+  "cat.sched.q2": "Which of the coming months suit CTV maintenance and lifting work, based on historical workable windows?",
+  "cat.sched.q3": "After the repair on WTG29, what acceptance criteria apply, and what are the three branch conditions?",
+  "ch.attrib.none": "Unrecorded {h}h ({p}%)",
+  "ch.attrib.ok": "Attributed to a component {h}h ({p}%)",
+  "ch.attrib.uncls": "Unclassified {h}h",
+  "ch.beta.median": "fleet median {v}°",
+  "ch.blame.d1": "curtailment and dispatch orders; not equipment responsibility",
+  "ch.blame.d2": "fault and maintenance outages; the only part assessed against equipment",
+  "ch.blame.d3": "under-production while generating, and low-wind standby",
+  "ch.blame.n1": "Market and dispatch",
+  "ch.blame.n2": "Equipment",
+  "ch.blame.n3": "Operations",
+  "ch.blame.title": "Loss {tot} MWh by responsibility",
+  "ch.cap.legend": "number = units, area ∝ units",
+  "ch.cap.median": "median {v}",
+  "ch.cap.p": "Power cap",
+  "ch.cap.w": "Speed cap",
+  "ch.ml.legend": "— {s}",
+  "ch.pareto.cum80": "cumulative 80%",
+  "ch.pareto.cumtip": "{k} · cumulative {p}%",
+  "ch.pareto.key": "Vital few: the first {k} items make 80%",
+  "ch.pareto.none": "No data in the window",
+  "ch.pareto.top3": "Top three: ",
+  "ch.pareto.total": "{n} items, total {tot}{unit}",
+  "ch.pareto.xlab": "Alarm code (hover for full name)",
+  "ch.quad.few": "Too few codes in the window for a quadrant plot",
+  "ch.quad.q1": "Rare, long · hard faults",
+  "ch.quad.q1s": "spares and maintenance window",
+  "ch.quad.q2": "Frequent, long · priority",
+  "ch.quad.q2s": "dispatch first",
+  "ch.quad.q3": "Rare, short · background",
+  "ch.quad.q3s": "no separate action",
+  "ch.quad.q4": "Frequent, short · re-triggering",
+  "ch.quad.q4s": "tune or mask; not hardware",
+  "ch.quad.tip": "{n} events · median {med}s · total {h}h · {nt} units",
+  "ch.quad.xlab": "Occurrences (log)",
+  "ch.quad.ylab": "Median duration per event (log)",
+  "ch.ram.area": "Area = total downtime",
+  "ch.ram.equip": "Equipment",
+  "ch.ram.ext": "External",
+  "ch.ram.few": "Too few components for a quadrant plot",
+  "ch.ram.median": "Dashed = site median split",
+  "ch.ram.q1": "Frequent and hard to fix",
+  "ch.ram.q1s": "highest priority",
+  "ch.ram.q2": "Rare and hard to fix",
+  "ch.ram.q2s": "spares and work windows",
+  "ch.ram.q3": "Frequent and easy to fix",
+  "ch.ram.q3s": "find root cause; replacement will not help",
+  "ch.ram.q4": "Rare and easy to fix",
+  "ch.ram.q4s": "maintain",
+  "ch.ram.tip": "{p}: MTBF {mtbf}h · MDT {mdt}h · downtime {h}h · {n} events · {u} units",
+  "ch.ram.tip_ext": " · external, excluded from equipment reliability",
+  "ch.ram.xlab": "MTBF (h, log) →  more reliable to the right",
+  "ch.ram.ylab": "MDT (h) →  harder to repair upward",
+  "ch.rate.days": "{d} d only",
+  "ch.rate.month": "{m}",
+  "ch.rate.n": "{n}",
+  "ch.rate.tip": "{m}: {v}/day ({n} total, {cov}% coverage)",
+  "ch.rate.ylab": "per day",
+  "ch.ruler.norm": "normal expectation 3.7σ",
+  "ch.ruler.row": "{r}σ  σ = {s}",
+  "ch.wf.act": "Delivered",
+  "ch.wf.theo": "Theoretical",
+  "ch.wf.tip": "{nm}: loss {loss} MWh, {pct}% of theoretical, {h} h",
+  "chain.gb": "Gearbox",
+  "chain.gde": "Gen DE",
+  "chain.gen": "Generator",
+  "chain.gnde": "Gen NDE",
+  "chain.hss": "HSS",
+  "chain.ims": "IMS",
+  "chain.mba": "MB aft",
+  "chain.mbf": "MB fore",
+  "chain.ps": "Planet",
+  "cmp.alarm_n": "alarm {a}",
+  "cmp.alarm_units": "Units in alarm",
+  "cmp.basis": "Evidence",
+  "cmp.by_system": "By system",
+  "cmp.cards_note": "Open a system card for every unit and its evidence; the nine systems map one-to-one onto the nine delivered volumes; blade-and-root and tower-and-foundation have no SCADA axis (their volumes draw on CMS and inspection)",
+  "cmp.counts": "Alarm {a} · undeterminable {u} · good {g} · excellent {e}",
+  "cmp.cross_eyebrow": "Units flagged across systems",
+  "cmp.detail": "Detail",
+  "cmp.detail_head": "{sys} · fleet of {n}",
+  "cmp.external": "external",
+  "cmp.fleet_n": " · fleet {n}",
+  "cmp.fusion_moved": "Vibration-line conclusions and four-source fusion live on their own tab →",
+  "cmp.fusion_n": "({n} unit-entries)",
+  "cmp.good_units": "Good · {n} units (slight deviation, periodic observation — expand for per-unit evidence)",
+  "cmp.grading_pending": "Recomputing grading for the selected window…",
+  "cmp.grading_pending_why": "时间窗 {span}:变桨/偏航/蓄能/温度四个判级面正按该时间窗重算(首次约 25 秒),下面的系统卡暂时还是上一份统计方法 —— 算完会自动刷新,不会静默混用。",
+  "cmp.hit_sys": "Systems flagged",
+  "cmp.n_sys": "Systems",
+  "cmp.n_units": "({n} units)",
+  "cmp.none": "No abnormal units in this system.",
+  "cmp.problem_page": "Issue page →",
+  "cmp.rel_ev": "Events",
+  "cmp.rel_eyebrow": "Component reliability · window {win}",
+  "cmp.rel_fm": "Typical failure mode (by duration)",
+  "cmp.rel_h": "Downtime",
+  "cmp.rel_head": "Downtime contribution and reliability by component",
+  "cmp.rel_mdt": "MDT (h)",
+  "cmp.rel_meta": "Total downtime {t}h = equipment {a} + external {b}",
+  "cmp.rel_mtbo": "MTBO (h)",
+  "cmp.rel_note": "Basis: outage segments ≥1 h, excluding dispatch orders — this differs from the downtime hours on Energy Accounting{h}; the gap is segments shorter than 1 h (reliability needs whole segments, the energy account counts every state record). Both are correct and must not be subtracted.",
+  "cmp.rel_part": "Component",
+  "cmp.rel_units": "Units",
+  "cmp.unk_n": " · undeterminable {u}",
+  "cmp.unk_units": "Undeterminable (monitoring or data insufficient — not normal)",
+  "cmp.watch_head": "Watch list (≥2 systems, {n} units; click a unit for its own page)",
+  "dec.classic": "The maintenance-standard, framework and data-quality blocks are not ported yet —",
+  "dec.head": "Maintenance decisions · ontology Q&A (unbounded reasoning, gated conclusions)",
+  "en.balance": "Energy balance",
+  "en.blame": "Loss by responsibility",
+  "en.blame_note": "The largest item is curtailment, not faults. Only fault and maintenance outages ({n} MWh) count against equipment; curtailment belongs to market and dispatch, and from 2026 trading strategy dominates, so under-production is not necessarily lost wind.",
+  "en.cap_fail": "Fail",
+  "en.cap_note": "Energy-balance check · theoretical {op} full-rated cap {cap}",
+  "en.cap_pass": "Pass",
+  "en.kpi_act": "Delivered · full-load {h} h",
+  "en.kpi_loss": "Loss rate · {v} MWh",
+  "en.kpi_theo": "Theoretical (delivered + loss)",
+  "en.loss_mwh": "Loss MWh",
+  "en.share": "Share of hours",
+  "en.state": "State",
+  "en.states": "Operating-state detail",
+  "en.states_note": "Control-parameter consistency and curve under-production screening are judged in a fixed clean window and live on the Generation Performance page; this page shows only the current-period energy account.",
+  "en.win": "window {win}",
+  "fig.fleet_median": "Fleet median",
+  "fig.point": "Measurement point",
+  "fig.quartile": "Quartile band",
+  "fig.this_unit": "This unit",
+  "fig.trend": "Trend",
+  "fig.vibbar_note": "Ratio of this unit to the fleet median; colour by z (robust outlier score): green <2, amber 2–3, red ≥3",
+  "fig.x_median": "× median",
+  "flt.alarm_face": "Alarm view",
+  "flt.alarm_meta": "{n} alarms · {c} codes · window {win}",
+  "flt.attrib_note": "Attribution coverage: unrecorded downtime {h}h is the largest single item and does not appear above because it has no MTBF — it is not "no fault", it is "not attributed". Basis: first alarm within ±30 min of segment start. The cause of the gap is undetermined: the earlier claim "98% of segments had an alarm in the prior 72 h ⇒ long outage, far window" does not hold — an alarm in the prior 72 h does not imply causation (caught by dual-model review 2026-08-28). Determining it requires testing whether the segment falls inside an alarm’s active interval; until then only the gap is reported, not its cause.",
+  "flt.block_err": "This block could not be computed ({k}): {e}",
+  "flt.eyebrow": "Fault statistics and reliability",
+  "flt.pareto_d": "Pareto · by duration",
+  "flt.pareto_d_note": "Duration = latch basis (reset − activation); a ranking aid, not downtime",
+  "flt.pareto_n": "Pareto · by count",
+  "flt.pareto_n_note": "Count = frequency, not impact — frequent short alarms (signal chatter or re-triggering) rank high",
+  "flt.per_t": "By unit · alarm count top {n}",
+  "flt.per_t_note": "Fleet total {n} · a high single-unit share must first be checked for one code re-triggering",
+  "flt.per_t_share": "Share of fleet",
+  "flt.quad": "Fault-mode quadrants · count × duration per event",
+  "flt.quad_note": "{n} codes in the window; the plot shows the top {m} by count.",
+  "flt.quad_note2": "Point area = units affected (large = batch-wide, small = single unit); the vertical split is the count median, the horizontal split 10 min (one SCADA sample). Do not prioritise lower-right codes by count — they re-trigger and are not hardware faults. The two Pareto charts above are this plot’s projections.",
+  "flt.quad_partial": "The window includes partial month {m} ({c}% coverage); counts are raw totals, not day-normalised.",
+  "flt.ram": "Reliability × maintainability · component quadrants",
+  "flt.ram_note": "A table sorted by downtime hides this structure: the same 500 h of downtime calls for root-cause work if frequent-and-easy, and for spares if rare-and-hard — opposite actions. Equipment {a}h / external {b}h (external = grid, wind, operations; excluded from equipment reliability). Offshore MDT runs high because of transit windows, not slow repairs — compare MDT only against other offshore sites.",
+  "flt.rate": "Monthly fault rate",
+  "flt.rate_example": "Measured: {m} has the lowest total yet {v}/day, among the highest in the window.",
+  "flt.rate_full": "All months fully covered.",
+  "flt.rate_note": "Bar height is the daily mean, not the monthly total",
+  "flt.rate_partial": "{m} is partial in this window (grey dashed bar); comparing monthly totals directly reads backwards.",
+  "flt.seasonal": "Seasonality · top alarm families × month",
+  "flt.seasonal_note": "19 months (two winters, one summer); reference tier until a χ² test is run — shape only, no seasonal attribution",
+  "flt.seasonal_unit": "per month",
+  "flt.seasonal_x": "month",
+  "flt.stop_face": "Outage view",
+  "flt.stop_h": "Downtime",
+  "flt.stop_meta": "{h}h · {n} events · segments ≥1 h, excluding dispatch orders",
+  "flt.stop_n": "Events",
+  "flt.stop_pareto": "Outage attribution Pareto",
+  "flt.stop_pareto_note": "By downtime; event counts on hover",
+  "flt.stop_units": "Outages by unit · top {n}",
+  "flt.stop_units_note": "High downtime ≠ poor equipment — check the external share in the attribution first (dispatch orders already excluded)",
+  "fus.alarm_n": "{n}",
+  "fus.alarm_rank": " · rank {r}/{t}",
+  "fus.burst": "⚠ {p}% is code {c} chattering",
+  "fus.burst_tip": "⚠ code {c} alone is {p}%, median {m} s per event — the count is filled by chatter",
+  "fus.clear": "× clear",
+  "fus.cms_act": "Action",
+  "fus.cms_action": "Units needing action (alarm / undeterminable)",
+  "fus.cms_basis": "Basis",
+  "fus.cms_basis_note": "本表 = windcms 状态管理判级 (含标量趋势与数据可判性); 融合矩阵 / 逐台结论 = 振动线确诊链转录 (候选以上)。两统计方法差异台: 仅本表列 {a} (多为标量发作 / 数据不可判, 未到确诊链); 仅确诊链列 {b} (预警级在察, 状态管理未升)。",
+  "fus.cms_chain": "Component state chain",
+  "fus.cms_chain_note": "(main bearing fore / aft · gearbox · generator; hover for each grade)",
+  "fus.cms_evidence": "Evidence strength",
+  "fus.cms_focus": "Focus",
+  "fus.cms_focus_note": "(drivetrain: main bearing fore / aft · gearbox · generator both ends)",
+  "fus.cms_grades": "Component grades, all 38 units",
+  "fus.cms_head": "Vibration condition assessment ({d} reviewed report)",
+  "fus.cms_link": "Detailed condition traces, criteria and the workbench live in the standalone condition-monitoring system →",
+  "fus.cms_loading": "Loading vibration assessment…",
+  "fus.cms_note": "Condition-management ratings, not a count of confirmed faults; criteria, detailed signal markers and calculations are not on this page — use the standalone system linked below.",
+  "fus.cms_open": "Open windcms ↗",
+  "fus.cms_overall": "Overall",
+  "fus.cms_static": "(static copy if it is not running)",
+  "fus.cms_window": "Data window {r} — the measured coverage of this window (the name is only its start-date code). Charts and "latest-window" states come from the newest record inside the window, so later dates dominate the display.",
+  "fus.col.alarm": "Alarms",
+  "fus.col.claim": "Evidence window",
+  "fus.col.claim_axis": "26-01 → 09",
+  "fus.col.display_only": " · display only",
+  "fus.col.fixed": " · fixed",
+  "fus.col.lub": "Lubrication plunger",
+  "fus.col.oil": "Oil sample",
+  "fus.col.part": "Component",
+  "fus.col.signal": "Vibration signal",
+  "fus.col.state_concl": "State / vibration conclusion",
+  "fus.col.temp": "Temperature",
+  "fus.col.tempaxis": "Temperature",
+  "fus.col.tempaxis_note": "±6 K",
+  "fus.col.vib": "Vibration",
+  "fus.col.wo": "Work orders",
+  "fus.ev_code": "Code",
+  "fus.ev_dur": "Total h",
+  "fus.ev_head": "{c} — alarms {an} / {au} units · work orders {wn} / {wu} units (in window)",
+  "fus.ev_min": "Duration min",
+  "fus.ev_no_alarm": "No alarms for this component in the window",
+  "fus.ev_no_wo": "No work orders for this component in the window (ledger ends 2024-11; 2026 windows are necessarily empty — a data boundary)",
+  "fus.ev_recent": "Latest {n} events",
+  "fus.ev_text": "Alarm",
+  "fus.ev_time": "Time",
+  "fus.ev_top": "Top units: ",
+  "fus.ev_units": "Units",
+  "fus.ev_win": "Window {win} · change it with the selector above",
+  "fus.ev_wo_act": "Action",
+  "fus.ev_wo_cause": "Cause",
+  "fus.ev_wo_name": "Fault",
+  "fus.eval_head": "Rating system (two axes, not interchangeable)",
+  "fus.eval_meaning": "Meaning",
+  "fus.eval_note": "Equipment state answers "how is this unit now" (critical > alarm > good > excellent > undeterminable); evidence tier answers "how credible is that judgement" (confirmed → INSUFFICIENT). Equipment state comes from the reviewed vibration assessment. Rows marked as same-family reference / line-level / measurement-chain / unmapped are excluded from component-level conclusions; claim_window is the evidence window, and the conclusion does not vouch for states outside it.",
+  "fus.eval_state": "Equipment state (operational basis)",
+  "fus.eval_tier": "Evidence tier (confidence)",
+  "fus.eyebrow": "Vibration and SCADA evidence view · vibration findings, SCADA indicators, alarms and work-order context",
+  "fus.fam_tip": "{n} · fleet max {m}",
+  "fus.legend_note": "两轴不可互相替代: \"候选\"说的是可信度不是设备好坏; \"良好\"说的是设备状态不是证据强度。设备状态只取自振动线的判级栏这一个来源。",
+  "fus.legend_state": "Cell colour = equipment state (operational basis, same as the reviewed condition report):",
+  "fus.legend_tier": "Badge = evidence tier (confidence, the nine windcms values):",
+  "fus.loop_ba": "Before → after (same scale)",
+  "fus.loop_bound": "Data boundary: {b}",
+  "fus.loop_closed": "Verified recovery after replacement ({n})",
+  "fus.loop_date": "Replaced",
+  "fus.loop_el": "Early → recent (same scale)",
+  "fus.loop_head": "Post-maintenance verification cases (detected → replaced → recovered)",
+  "fus.loop_item": "Item",
+  "fus.loop_neg": "Negative control: {n} cases flat after a work order (replacement does not always lower the metric, so the falls are not coincidence).",
+  "fus.loop_none": "No end-to-end closed-loop evidence yet: the submitted vibration package contains no ",
+  "fus.loop_note": "A documented replacement followed by a metric reduction on the same basis is shown as a closed verification case. Units with rising metrics remain open until field confirmation, a matching work order, or ledger confirmation is available.",
+  "fus.loop_ratio": "Recovery",
+  "fus.loop_rising": "Open · rising metrics awaiting field confirmation, a matching work order, or ledger confirmation ({n} units)",
+  "fus.loop_state": "State",
+  "fus.loop_verdict": "Verdict",
+  "fus.loop_x": "Ratio",
+  "fus.lub_now": "Plunger now",
+  "fus.lv.bad": "Confirmed / alarm",
+  "fus.lv.note": "Candidate / watch",
+  "fus.lv.ok": "Closed / no new evidence",
+  "fus.lv.warn": "Warning",
+  "fus.m.bad": "Severe abnormality; stop and inspect soon",
+  "fus.m.bad_e": "evidence: settled",
+  "fus.m.ex": "No abnormality; routine monitoring",
+  "fus.m.ex_e": "evidence: record only / withdrawn",
+  "fus.m.ok": "Slight deviation; record and observe",
+  "fus.m.ok_e": "evidence: to verify on site / recorded as baseline for the next window",
+  "fus.m.unk": "Insufficient data or monitoring gap — not the same as normal",
+  "fus.m.unk_e": "evidence: insufficient",
+  "fus.m.warn": "Abnormality needing field verification or action",
+  "fus.m.warn_e": "evidence: essentially confirmed / newly seen, to verify",
+  "fus.matrix_foot": "润滑柱塞各台设定本就不同 (全场单次循环 22~961 秒), 所以此列只展示不判级, 只有已定案的机制链才标色; 油液多数台末次取样距今超 12 个月, 此列看的是\"样够不够新\", 不代表当前状态。",
+  "fus.matrix_head": "Fleet fusion matrix (38 units × 6 evidence surfaces)",
+  "fus.matrix_note1": "Columns use different windows (see headers) — two red cells from different windows are not simultaneous and do not corroborate each other.",
+  "fus.matrix_note2": "The curve in each cell is the monthly memory track ({n} months from {m0}; solid dot = current window) — a snapshot loses shape; only the track shows signatures such as collapse → recovery → reversal.",
+  "fus.no_alarm": "no alarms in window",
+  "fus.none_word": "none",
+  "fus.oil_fresh": "recent",
+  "fus.oil_none": "no sample",
+  "fus.oil_stale": "stale > 12 months",
+  "fus.rule1": "① Several observation surfaces of one failure mechanism ≠ multi-source confirmation — fusion must not weight by source count (unit 29: plunger / temperature / steady vibration / transient vibration are four surfaces on one mechanism chain).",
+  "fus.rule2": "② "No alarm" ≠ "normal" — on a known blind spot a missing alarm only shows the monitoring surface is ineffective, not that the equipment is fine.",
+  "fus.rules_head": "Two reading rules (always shown)",
+  "fus.short.nodata": "insufficient data",
+  "fus.short.normal": "normal",
+  "fus.short.nosample": "no sample",
+  "fus.short.stale": "sample stale",
+  "fus.short.uneval": "not assessed",
+  "fus.short.unlisted": "not listed",
+  "fus.sub.cms": "Vibration assessment",
+  "fus.sub.events": "Alarms and work orders",
+  "fus.sub.loop": "Closed loops",
+  "fus.sub.matrix": "Fusion matrix",
+  "fus.sub.units": "Per unit",
+  "fus.temp_now": "Temperature now",
+  "fus.tip_state": " — state {s}",
+  "fus.tip_tier": " / tier {e}",
+  "fus.unavailable": "Fusion data unavailable: {e}",
+  "fus.units_filter": " · filter: {l}",
+  "fus.units_head": "Per unit ({u} units / {n} entries{f}; detailed condition traces → "Vibration assessment")",
+  "fus.unlisted_head": "Unlisted units · {n} (not covered by the vibration line — unlisted ≠ normal)",
+  "fus.unlisted_link": "unlisted {n}",
+  "fus.win_n": "{n} spans",
+  "fus.win_none": "no window",
+  "fus.wo_n": "{n} orders",
+  "fus.wo_none": "no drivetrain work orders in window",
+  "gen.beta": "Pitch-schedule deviation within a power band",
+  "gen.beta_note": "Pitch angle minus the fleet median within the same power band. {units} deviate {k}× the median → {v}",
+  "gen.cands": "Curve under-production screening",
+  "gen.cands_meta": "{note} · σ = {s}%",
+  "gen.cap": "Where the caps sit",
+  "gen.cap_note": "Units fall on a few discrete positions rather than a continuum — the shape of configuration or version differences; wear or operating conditions would scatter continuously. Both axes form groups with different units in each ⇒ a parameter-level difference; the cause needs the configuration ledger.",
+  "gen.ctrl": "Control-parameter consistency",
+  "gen.ctrl_win": "window {win}",
+  "gen.eyebrow": "Characteristic curves and control parameters",
+  "gen.figs": "Seven lenses · per-bin medians and same-type residuals",
+  "gen.fixed_win": "Follows the selector · recomputed per window",
+  "gen.loading": "Computing seven lenses (curtailment stripped · per-bin medians · same-type fleet residuals)…",
+  "gen.no_cands": "no candidates",
+  "gen.recomputing": "Recomputing the seven lenses for the selected window…",
+  "gen.recomputing_short": "recomputing for this window…",
+  "gen.ruler": "Ruler check · range ÷ σ",
+  "gen.ruler_note": "判据自身的分辨力。38 台正态抽样的极差期望约 3.7σ, 明显超出即分布非单峰 — 与左图的分组互为印证; 落在期望附近的轴 (额定桨角) 则是连续分布。",
+  "gen.win_range": "window {span}",
+  "gen.win_why": "七镜头与月度时序都按所选时间窗重算(含自定义起止日期)。提示:2025-07 ~ 2025-12 是\"限电前干净判别窗\"——2026 起交易策略主导,曲线含策略性少发,作设备判据时须注意;选到含 2026 的窗时该影响存在但不再遮掩。",
+  "kpi.alarm_units": "Units with a system-level alarm / fleet",
+  "kpi.alarm_units.hint": "统计方法: 九系统 (叶片与叶根/变桨/偏航/齿轮箱/发电机/主轴承/变流器/塔架与基础/主控与传感网) 中任一被判「报警」即计一台。与下方「全场状态」格的统计方法不同 —— 那里按每台 振动/温度/润滑/油液/报警/工单 六列取最严一级, 所以两处数字可以不同, 不是矛盾。",
+  "kpi.avail": "Time-based availability (excl. dispatch) · {win}",
+  "kpi.curtail": "Curtailment (bound) energy loss",
+  "kpi.downtime": "Downtime · {n} events",
+  "kpi.loss": "Downtime energy loss · {win}",
+  "kpi.loss_dispatch": "plus {n} under dispatch orders",
+  "kpi.mtbo": "Mean time between outages (MTBO; not industry MTBF)",
+  "loss.curtail_bound": "Curtailment (command matches actual power)",
+  "loss.curtail_unbound": "Curtailment command · power not suppressed",
+  "loss.dispatch": "Dispatch-order outage",
+  "loss.lowwind": "Low-wind standby",
+  "loss.normal": "Under-production while generating",
+  "loss.stop": "Fault and maintenance outage",
+  "mdl.cloud": "cloud",
+  "mdl.deepseek-chat.nm": "DeepSeek",
+  "mdl.deepseek-chat.t": "about 3–10 s",
+  "mdl.deepseek-v4-pro.nm": "DeepSeek Pro",
+  "mdl.deepseek-v4-pro.t": "about 2–6 min",
+  "mdl.kimi-fast.nm": "Kimi",
+  "mdl.kimi-fast.t": "about 5–20 s",
+  "mdl.local": "local",
+  "mdl.outbound_warn": "Content is sent to the model vendor; site name and unit ids are de-identified",
+  "mdl.qwen-plus.nm": "Qwen Plus",
+  "mdl.qwen-plus.t": "about 5–20 s",
+  "mdl.unknown_warn": "Unknown tier — treated as sending content externally",
+  "mdl.zero_outbound": "Nothing leaves the machine",
+  "mt.cov": "Coverage",
+  "mt.eyebrow": "System maintenance · data inputs and update ports",
+  "mt.fail": "Survey failed: {e}",
+  "mt.freq": "Cadence",
+  "mt.head": "Data maintenance · {n} sources",
+  "mt.how": "How to add data",
+  "mt.last": "Last update",
+  "mt.layer_data": "Data layer",
+  "mt.layer_data_note": "Rolls with operations — run ingest when new data lands; ontology and ratings refresh automatically",
+  "mt.layer_mech": "Domain-model layer",
+  "mt.layer_mech_note": "Relatively stable — changes only with new vendor material or field corrections; it underpins the criteria, so changes go through review",
+  "mt.note": "For each source: coverage, last update, agreed location, ingest command, cadence and responsible line. To add data, put the new file at the agreed location and run the ingest command.",
+  "mt.put": "Put at:",
+  "mt.resp": "Responsible line",
+  "mt.rows_n": " ({n} records)",
+  "mt.run": "Run:",
+  "mt.src": "Source",
+  "mt.surveying": "Surveying…",
+  "ov.all_steps_done": "all steps done (pending acceptance)",
+  "ov.attention": "Needs attention",
+  "ov.attention_n": "{n} units",
+  "ov.basis_fusion": "本台依据来自融合面(九系统判级无「报警」行):部件 {part} · 链路走到第 {done}/{n} 步 · 卡在「{stuck}」 · 证据源 {srcs}",
+  "ov.caveat": "Follow-up",
+  "ov.evidence": "View basis",
+  "ov.fus_gap_hint": "这一面(需要关注 / 全场状态)取数来自融合面;缺件时如实为空,不从交付包补齐。在本机放好原始件后跑一次重算即会由观澜自己的生成端补上(scripts/rudong_fusion_handoff.py,见 docs 的「源代码化清单」)。",
+  "ov.illustrative": "Illustrative",
+  "ov.no_srcs": "none",
+  "ov.quiet": "The other {n} units are outside the priority list. Refer to fleet status for their equipment assessments.",
+  "ov.quiet_all": "The priority list is empty. Refer to fleet status for the assessments of all {n} units.",
+  "ov.ranked_by": "Ranked by equipment state × evidence weight",
+  "ov.strip": "Fleet status",
+  "ov.strip_hint": "One cell per unit · click to locate",
+  "ov.systems": "Nine systems",
+  "ov.systems_hint": "Open a system for per-unit evidence",
+  "pop.acts": "Action counts",
+  "pop.close": "Close",
+  "pop.loading": "Loading…",
+  "pop.none": "No records in this window",
+  "pop.o_act": "Action",
+  "pop.o_cause": "Cause",
+  "pop.o_check": "Investigation",
+  "pop.o_date": "Date",
+  "pop.o_fault": "Fault",
+  "pop.o_kind": "Category",
+  "pop.orders": "{n} work orders",
+  "pop.orders_none": "No work orders in this window",
+  "pop.orders_one": "1 work order",
+  "pop.problem": "{u} · {sys}",
+  "pop.unit": "{u} · all issues",
+  "rpt.attrib.note": "Unrecorded downtime is not the absence of faults; it is the absence of attribution.",
+  "rpt.attrib.title": "Attribution coverage",
+  "rpt.aud_in": "Site internal (full)",
+  "rpt.aud_up": "Group report (de-identified)",
+  "rpt.b.attrib.d": "Attributed vs unrecorded downtime — unrecorded is not the same as no fault",
+  "rpt.b.attrib.nm": "Attribution coverage",
+  "rpt.b.blame.d": "Market-dispatch / equipment / operations shares — who is responsible",
+  "rpt.b.blame.nm": "Loss by responsibility",
+  "rpt.b.caliber.d": "Period / partial months / work-order window / nature of ratings — keep it in; numbers without limits are not a basis for reporting",
+  "rpt.b.caliber.nm": "Basis and limits",
+  "rpt.b.chain.d": "Bottlenecks across evidence → mechanism → rating → schedule → action → acceptance",
+  "rpt.b.chain.nm": "Decision-chain progress",
+  "rpt.b.emon.d": "Monthly delivered / loss / loss rate / full-load hours, with data coverage",
+  "rpt.b.emon.nm": "Monthly table",
+  "rpt.b.frate.d": "Daily-normalised counts; partial months flagged",
+  "rpt.b.frate.nm": "Monthly fault rate",
+  "rpt.b.kpi.d": "Six core numbers: delivered energy / full-load hours / availability / loss rate / MTBO / units needing action",
+  "rpt.b.kpi.nm": "Headline KPIs",
+  "rpt.b.loop.d": "Detected → replaced → re-measured recovery; proof the chain works",
+  "rpt.b.loop.nm": "Closed-loop evidence",
+  "rpt.b.narr.d": "A model-written summary from measured data — text conclusions to accompany the charts",
+  "rpt.b.narr.nm": "Narrative summary",
+  "rpt.b.pareto.d": "Descending counts with the 80% line; the vital few",
+  "rpt.b.pareto.nm": "Fault Pareto",
+  "rpt.b.parts.d": "Per component: downtime / events / MTBF / MDT / units affected",
+  "rpt.b.parts.nm": "Downtime by component",
+  "rpt.b.plan.d": "Actions derived from data, not boilerplate",
+  "rpt.b.plan.nm": "Next-period actions",
+  "rpt.b.quad.d": "Count × duration separates re-triggering from real outages; ranking by count alone misleads",
+  "rpt.b.quad.nm": "Fault quadrants",
+  "rpt.b.ram.d": "MTBF × MDT by component; frequent-easy vs rare-hard call for opposite actions",
+  "rpt.b.ram.nm": "Reliability quadrant",
+  "rpt.b.units.d": "Tracked units: component / evidence sources / six-step progress / where blocked",
+  "rpt.b.units.nm": "Key units",
+  "rpt.b.water.d": "Theoretical → losses by segment → delivered; where the energy went",
+  "rpt.b.water.nm": "Energy waterfall",
+  "rpt.blame.cal": "State decomposition with curtailment-command binding; mean output = delivered ÷ unit-hours, used as a wind-resource proxy",
+  "rpt.blame.note": "Only fault and maintenance outages count against equipment; curtailment belongs to market and dispatch.",
+  "rpt.blame.note2": "Curtailment is not an empty order received in low wind: mean output in bound-curtailment periods is 1.435 MW versus 1.412 MW while generating normally (ratio 1.02) — same wind, suppressed output, so the curtailment is real; the "command without suppression" tier averages only 0.724 MW (18% of rated), which is a low-wind order.",
+  "rpt.blame.title": "Loss by responsibility",
+  "rpt.cal.deid": "Redaction",
+  "rpt.cal.deid_d": "The group version hides criterion thresholds and detailed diagnostic markers",
+  "rpt.cal.denom": "Availability and loss rate use different denominators",
+  "rpt.cal.denom_d": "Availability excludes low-wind standby and dispatch orders (can it generate when it should?), loss rate includes all hours (how much could it generate?); the same low-wind hour is excluded from one and included in the other — a design difference, not an error, but the two cannot be multiplied or inferred from each other. Low-wind standby contributes only 118 MWh (0.5%) of loss",
+  "rpt.cal.partial": " — {m} partial (coverage {c}%), monthly figures normalised by day",
+  "rpt.cal.period": "Period",
+  "rpt.cal.rating": "Ratings",
+  "rpt.cal.rating_d": "Fused from vibration, temperature, lubrication and oil; action levels are issued by the consolidated report",
+  "rpt.cal.stop": "Outages",
+  "rpt.cal.stop_d": "Reliability counts segments ≥1 h, the energy account counts every state record — both are right, and they cannot be subtracted",
+  "rpt.cal.title": "Basis and data limits",
+  "rpt.cal.wo": "Work orders",
+  "rpt.cal.wo_d": "Ledger 2020 to 2024-11; this report draws no conclusion on this period’s maintenance execution",
+  "rpt.caliber_lbl": "Basis: ",
+  "rpt.chain.cal": "The "action" step cannot be judged — {b}",
+  "rpt.chain.note": "Six steps: evidence → mechanism → rating → schedule → action → acceptance. Currently {s}.",
+  "rpt.chain.title": "Decision-chain progress",
+  "rpt.chapters": "{n} sections",
+  "rpt.cloud_note": "; content is sent to the model vendor",
+  "rpt.compose": "Compose",
+  "rpt.compose_fail": "Compose failed: {e}",
+  "rpt.composed": "Composed {n} blocks{narr} · {m} in {s}s",
+  "rpt.composing": "Composing… picking components and writing the summary",
+  "rpt.emon.act": "Delivered MWh",
+  "rpt.emon.cal": "Months with coverage below 50% are excluded from MoM",
+  "rpt.emon.cov": "Coverage",
+  "rpt.emon.eflh": "Full-load h",
+  "rpt.emon.loss": "Loss MWh",
+  "rpt.emon.m": "Month",
+  "rpt.emon.partial": " partial",
+  "rpt.emon.pct": "Loss rate",
+  "rpt.emon.title": "Monthly generation",
+  "rpt.empty_canvas": "The canvas is empty — pick content from the library first",
+  "rpt.export_docx": "Export Word",
+  "rpt.export_xlsx": "Export Excel",
+  "rpt.eyebrow": "Report builder · canvas",
+  "rpt.facts.agg": "Aggregation",
+  "rpt.facts.sub": "{n} claims, contract {sha}; data / windows / verdicts change only via the contract",
+  "rpt.facts.sys": "System",
+  "rpt.facts.text": "Conclusion",
+  "rpt.facts.title": "Deliverable-grade conclusions · from facts contract",
+  "rpt.facts.verdict": "Verdict",
+  "rpt.foot_asof": "issued {d}",
+  "rpt.foot_deid": "thresholds and detailed diagnostic markers redacted",
+  "rpt.foot_src": "Sources: SCADA 10-min · alarm events · work-order ledger · CMS vibration · oil analysis",
+  "rpt.foot_trace": "Conclusions trace back to ontology objects",
+  "rpt.frate.cal": "Partial months normalised by day",
+  "rpt.frate.note": "Bar height is the daily mean, not the monthly total.",
+  "rpt.frate.title": "Monthly fault rate",
+  "rpt.g.end": "Closing",
+  "rpt.g.flt": "Faults",
+  "rpt.g.gen": "Generation",
+  "rpt.g.rel": "Reliability",
+  "rpt.g.unit": "Units",
+  "rpt.grounded": "all numbers grounded",
+  "rpt.hint_idle": "Hover for a description · click a selected block to remove · ↑↓ to reorder",
+  "rpt.kpi.act": "Delivered energy",
+  "rpt.kpi.action": "Units needing action",
+  "rpt.kpi.action_n": "confirmed {a} · warning {b} · candidate {c}",
+  "rpt.kpi.annual": "annualised ≈ {v} h",
+  "rpt.kpi.avail": "Time-based availability",
+  "rpt.kpi.avail_n": "excl. dispatch orders and low-wind standby",
+  "rpt.kpi.eflh": "Full-load hours",
+  "rpt.kpi.loss": "Loss rate",
+  "rpt.kpi.mom": "MoM {v}%",
+  "rpt.kpi.mom_na": "MoM not comparable (partial last month)",
+  "rpt.kpi.mtbo": "Mean time between outages",
+  "rpt.kpi.mtbo_n": "{n} outages · mean {h}h each",
+  "rpt.kpi.theo": "theoretical {v}",
+  "rpt.library": "Component library",
+  "rpt.library_note": "Click to add, click again to remove · {n} blocks on the canvas",
+  "rpt.local_note": "; data stays local",
+  "rpt.loop.cal": "Vibration scalar, before vs after",
+  "rpt.loop.comp": "Component",
+  "rpt.loop.date": "Replaced on",
+  "rpt.loop.note": "These are the units the algorithm flagged in the first place: visible before the fault, and visibly recovered after replacement.",
+  "rpt.loop.post": "After",
+  "rpt.loop.pre": "Before",
+  "rpt.loop.ratio": "Ratio",
+  "rpt.loop.title": "End-to-end closed-loop evidence",
+  "rpt.narr.cal": "Generated by {m} · numbers from the measured summary · grounding gate {g}",
+  "rpt.narr.empty": "Nothing generated yet — enter a request in the panel above.",
+  "rpt.narr.g_n": "flagged {n} to verify",
+  "rpt.narr.g_ok": "passed",
+  "rpt.narr.title": "Narrative summary",
+  "rpt.narr.ungr": "⚠ These values were derived by the model and are not in the source summary; verify before citing: {v}",
+  "rpt.narr_plus": " + a narrative summary",
+  "rpt.need_empty": "Enter the report request first",
+  "rpt.need_head": "Describe the report you need",
+  "rpt.need_hint": "The model picks components for the request and writes a summary from measured data",
+  "rpt.need_ph": "e.g. monthly report for the group, focused on equipment reliability and key units",
+  "rpt.no_compose": "The compose endpoint is not enabled here; the canvas can still be assembled by hand",
+  "rpt.no_models": "No model keys configured in this deployment; composing is unavailable",
+  "rpt.paper_sub": "Period {win} | {n} units",
+  "rpt.paper_title": "Operations analysis report",
+  "rpt.pareto.cal": "Alarm event table",
+  "rpt.pareto.title": "Fault Pareto",
+  "rpt.parts.cal": "Attribution = first alarm within ±30 min of segment start",
+  "rpt.parts.ev": "Events",
+  "rpt.parts.ext": " (external)",
+  "rpt.parts.h": "Downtime h",
+  "rpt.parts.mdt": "MDT (h)",
+  "rpt.parts.mtbf": "MTBF (h)",
+  "rpt.parts.part": "Component",
+  "rpt.parts.title": "Downtime by component",
+  "rpt.parts.units": "Units",
+  "rpt.plan.1": "Close the mechanism gap — {n} units are rated but without a root cause; the single largest bottleneck",
+  "rpt.plan.2": "Recover the 2025–26 work orders — the ledger ends 2024-11, so this period’s execution and closure cannot be judged automatically",
+  "rpt.plan.3": "Oil sampling cadence — oil is chronically stale among the four sources and serves only as a back-test anchor",
+  "rpt.plan.4": "Curtailment — six tenths of the loss sits with market and dispatch; equipment-side headroom is limited",
+  "rpt.plan.cal": "Advisory — scheduling and actions require approval",
+  "rpt.plan.title": "Next-period plan",
+  "rpt.preset.blank": "Blank canvas",
+  "rpt.preset.month": "Monthly operations",
+  "rpt.preset.topic": "Equipment topic",
+  "rpt.preset.week": "Weekly",
+  "rpt.preset.year": "Annual summary",
+  "rpt.presets": "Presets",
+  "rpt.preview_empty": "Document preview — pick components above to see the report here",
+  "rpt.print": "Print / save PDF",
+  "rpt.quad.cal": "Vertical split = 10 min (one SCADA sample)",
+  "rpt.quad.note": "Codes in the lower-right are re-triggering and should not be prioritised by count.",
+  "rpt.quad.title": "Fault-mode quadrants",
+  "rpt.ram.cal": "SCADA outage segments ≥1 h, excluding dispatch orders; offshore MDT includes transit windows",
+  "rpt.ram.note": "Equipment {a}h / external {b}h (grid, wind and operations excluded from equipment reliability)",
+  "rpt.ram.title": "Reliability × maintainability",
+  "rpt.ungrounded_n": "{n} numbers to verify",
+  "rpt.units.at": "Stage",
+  "rpt.units.cal": "Four-source fused rating",
+  "rpt.units.clear": "clear",
+  "rpt.units.ev": "Component and evidence",
+  "rpt.units.head": "Tracked {n} of {tot} | bottlenecks ",
+  "rpt.units.prog": "Progress",
+  "rpt.units.rising": "rising {r}×",
+  "rpt.units.state": "State",
+  "rpt.units.stuck": "{n} units pending {s}",
+  "rpt.units.title": "Key units and actions",
+  "rpt.up_note": "Group version — criterion thresholds and detailed diagnostic markers are redacted",
+  "rpt.up_tag": "group version",
+  "rpt.water.cal": "SCADA 10-min state decomposition; loss = theoretical output for the same state minus measured. The magnitude check is a floor against inflation, not a wind-resource criterion",
+  "rpt.water.cap_bad": "Magnitude check: theoretical ≥ full-rated cap {cap} MWh ✗ figures not credible",
+  "rpt.water.cap_ok": "Magnitude check: theoretical is below the full-rated cap {cap} MWh ✔",
+  "rpt.water.note": "Theoretical {theo} = delivered {act} + loss {loss} MWh ({pct}%)",
+  "rpt.water.title": "Generation outcome",
+  "rv.advci": "ADVCI",
+  "rv.dual": "DUAL-MODEL",
+  "rv.lite": "LITE",
+  "rv.none": "UNREVIEWED",
+  "st.bad": "Critical",
+  "st.excellent": "Excellent",
+  "st.ok": "Good",
+  "st.ok_excellent": "Good / Excellent",
+  "st.unk": "Undeterminable",
+  "st.warn": "Alarm",
+  "step.acceptance": "Acceptance",
+  "step.action": "Action",
+  "step.evidence": "Evidence",
+  "step.mechanism": "Mechanism",
+  "step.rating": "Rating",
+  "step.schedule": "Schedule",
+  "step.site_records": "site records",
+  "step.stuck_at": "Pending {step}",
+  "sys.blade": "Blade and blade root",
+  "sys.control": "Control and sensor network",
+  "sys.converter": "Converter",
+  "sys.gearbox": "Gearbox",
+  "sys.generator": "Generator",
+  "sys.main_bearing": "Main bearing",
+  "sys.other": "Other",
+  "sys.pitch": "Pitch",
+  "sys.tower": "Tower and foundation",
+  "sys.yaw": "Yaw",
+  "tab.assistant": "Maintenance Assistant",
+  "tab.component": "Component Issues",
+  "tab.decision": "Maintenance Decisions",
+  "tab.energy": "Energy Accounting",
+  "tab.fault": "Fault Statistics",
+  "tab.generation": "Generation Performance",
+  "tab.ontology": "Ontology",
+  "tab.overview": "Overview",
+  "tab.report": "Report Builder",
+  "tab.system": "System Maintenance",
+  "tab.vibration": "Vibration Fusion",
+  "tier.candidate": "Candidate",
+  "tier.candidate.s": "Cand.",
+  "tier.candidate_baseline": "Candidate · baseline",
+  "tier.candidate_baseline.s": "Cand. base",
+  "tier.candidate_new": "Candidate · new",
+  "tier.candidate_new.s": "Cand. new",
+  "tier.confirmed": "Confirmed",
+  "tier.confirmed.s": "Conf.",
+  "tier.detail.不可判": "不可判",
+  "tier.detail.仲裁输入": "仲裁输入",
+  "tier.detail.优": "优",
+  "tier.detail.优秀": "优秀",
+  "tier.detail.传感器项": "传感器项",
+  "tier.detail.候选": "候选",
+  "tier.detail.危险": "危险",
+  "tier.detail.可降": "可降",
+  "tier.detail.定论": "定论",
+  "tier.detail.弱同向新证": "弱同向新证",
+  "tier.detail.报": "报",
+  "tier.detail.报警": "报警",
+  "tier.detail.本体无新证": "本体无新证",
+  "tier.detail.正常": "正常",
+  "tier.detail.正样本": "正样本",
+  "tier.detail.温度单轴保留": "温度单轴保留",
+  "tier.detail.监视": "监视",
+  "tier.detail.良": "良",
+  "tier.detail.良好": "良好",
+  "tier.detail.观察": "观察",
+  "tier.detail.记基线观察": "记基线观察",
+  "tier.detail.降观察": "降观察",
+  "tier.detail.预警": "预警",
+  "tier.insufficient": "Insufficient",
+  "tier.insufficient.s": "Insuf.",
+  "tier.near": "Near-confirmed · warning",
+  "tier.near.s": "Near",
+  "tier.normal": "Normal",
+  "tier.normal.s": "Normal",
+  "tier.reference": "Reference",
+  "tier.reference.s": "Ref.",
+  "tier.withdrawn": "Withdrawn",
+  "tier.withdrawn.s": "Withd.",
+  "ui.all": "All",
+  "ui.back_portal": "← Back to portal",
+  "ui.back_to": "← Back to {tab}",
+  "ui.collapse": "Collapse",
+  "ui.count": "Count",
+  "ui.data_hidden": "({n} data strings still require English translation review)",
+  "ui.date": "Date",
+  "ui.empty": "No data",
+  "ui.error": "Something went wrong",
+  "ui.expand": "Expand",
+  "ui.loading": "Loading…",
+  "ui.not_ported": "This page has not been ported to the new build yet.",
+  "ui.not_recorded": "Not recorded in ledger",
+  "ui.open": "Open →",
+  "ui.open_classic": "Open in classic build →",
+  "ui.query": "Query",
+  "ui.reselect": "Reselect unit / point",
+  "ui.retry": "Retry",
+  "ui.system": "System",
+  "ui.unit": "Unit",
+  "ui.units": "Units",
+  "ui.verbatim": "Translation review required",
+  "ui.verbatim_note": "{n} source passages require translation review and are withheld from the public English view.",
+  "unit.h": "h",
+  "unit.n": " events",
+  "unit.units": "units",
+  "vp.发电机DE": "Generator DE",
+  "vp.发电机NDE": "Generator NDE",
+  "vp.齿轮箱中间轴": "Gearbox intermediate shaft",
+  "vp.齿轮箱二级行星内齿圈": "Gearbox second-stage planetary ring gear",
+  "vp.齿轮箱高速轴": "Gearbox high-speed shaft",
+  "win.2025H2": "2025 H2",
+  "win.2026": "2026",
+  "win.2026H1": "2026 H1",
+  "win.30d": "Last 30 days",
+  "win.90d": "Last 90 days",
+  "win.all": "Full record",
+  "win.apply": "Apply",
+  "win.bad_order": "Start date must not be after the end date",
+  "win.banner": "Window {win} · every number on this page follows the selector above",
+  "win.custom": "Custom",
+  "win.custom_hint": "自定义起止日期(含两端)。日粒度件(判级/停机台账/曲线)按日精确;月度聚合件按所跨月份取整,图上会写明。",
+  "win.custom_sel": "Custom {a} to {b}",
+  "win.from": "Start date (inclusive)",
+  "win.label": "Window",
+  "win.months": "By month",
+  "win.need_both": "Pick both start and end dates",
+  "win.partial": "{m} in this window is a partial month ({c}% coverage); monthly charts are day-normalised",
+  "win.presets": "Preset windows",
+  "win.through": "Through {date}",
+  "win.to": "End date (inclusive)",
+}

+ 1 - 1
docs/src/数据要求说明_观澜_2.56.0.md → docs/src/数据要求说明_观澜_2.56.1.md

@@ -1,4 +1,4 @@
-# 数据要求说明 · 观澜 v2 风电场智能分析系统 · 版本 2.56.0
+# 数据要求说明 · 观澜 v2 风电场智能分析系统 · 版本 2.56.1
 
 ## 1 文档说明
 

+ 9 - 9
docs/src/系统设计说明_观澜_2.56.0.md → docs/src/系统设计说明_观澜_2.56.1.md

@@ -1,4 +1,4 @@
-# 系统设计说明 · 观澜 v2 风电场智能分析系统 · 版本 2.56.0
+# 系统设计说明 · 观澜 v2 风电场智能分析系统 · 版本 2.56.1
 
 ## 1 文档说明
 
@@ -6,19 +6,19 @@
 
 ### 1.1 目的与范围
 
-本文是"观澜 v2(风电场智能分析系统)"(海上风电场智能分析离线系统)的设计说明(下称"本系统"),面向版本 2.56.0;内容以样本风电场(下称"本场")为依据,样本场实测日期为 2026-09-22,说明本系统"由哪些部分组成、各部分怎么实现、数据从哪来到哪去、判据写在哪里、怎么验证、边界在哪里"。
+本文是"观澜 v2(风电场智能分析系统)"(海上风电场智能分析离线系统)的设计说明(下称"本系统"),面向版本 2.56.1;内容以样本风电场(下称"本场")为依据,样本场实测日期为 2026-09-22,说明本系统"由哪些部分组成、各部分怎么实现、数据从哪来到哪去、判据写在哪里、怎么验证、边界在哪里"。
 
 本文覆盖十五个设计面:总体架构与分层、目录结构与路径真源、数据接入与重算链、判级与算法、时间窗口径、服务与前端、本体与知识层、本机模型接入、运维控制台与重算编排、安装与服务化与版本管理、质量保证、安全与离线边界、可移植性与资源占用、多场适用性与换场迁移、已知边界与未实现。
 
-本文不重复需求条目本身(那是《需求分析_观澜_2.56.0.docx》的职责),也不重复操作步骤的逐步手册(那是 docs/重算操作手册_v0.1.md 与《使用说明书》v0.2(随包 docs/)的职责);本文只回答"设计上为什么这样、落在哪个文件的哪一处、用什么机器守卫保证它不漂移"。
+本文不重复需求条目本身(那是《需求分析_观澜_2.56.1.docx》的职责),也不重复操作步骤的逐步手册(那是 docs/重算操作手册_v0.1.md 与《使用说明书》v0.2(随包 docs/)的职责);本文只回答"设计上为什么这样、落在哪个文件的哪一处、用什么机器守卫保证它不漂移"。
 
 ### 1.2 读者与用法
 
 现场运维与检修人员可看第 3 章、第 7 章、第 8 章、第 11 章,了解自己能点到的页面背后读的是什么口径;场站管理人员与换场交付人员可看第 6 章、第 7 章、第 16 章、第 17 章,了解判级与可靠性指标的口径与边界,以及换场时要重新标定哪些参数;研发与交付人员应通读全文,重点是第 4 章、第 5 章、第 12 章、第 13 章;验收方可直接按第 13 章的质量门与附录 A 的编写依据逐条复核。
 
-### 1.3 与《需求分析_观澜_2.56.0.docx》的对应关系
+### 1.3 与《需求分析_观澜_2.56.1.docx》的对应关系
 
-需求分析写"要什么、为谁、优先级与验收门",设计说明写"怎么实现、落在哪、如何自证"。两文的章节对应关系如表 1-1 所示;与《数据要求说明_观澜_2.56.0.docx》的数据侧口径去向见 16.6 节。
+需求分析写"要什么、为谁、优先级与验收门",设计说明写"怎么实现、落在哪、如何自证"。两文的章节对应关系如表 1-1 所示;与《数据要求说明_观澜_2.56.1.docx》的数据侧口径去向见 16.6 节。
 
 | 本文章节 | 需求分析对应章 | 对应关系说明 |
 |---|---|---|
@@ -41,7 +41,7 @@
 
 ### 1.4 口径与依据
 
-本文所有数字来自仓库文件或命令的实跑输出,不采用估算与推测;查不到、未实现的,一律写"未取证"或"未实现",并在第 17 章汇总。本文中"本次实测"与各表"实测"列一律指样本场实测(2026-09-22):即在样本风电场的一套实例上的一次实跑,换场后这些数字会变、方法与口径不变。版本号的唯一真源是 src/version.py 的 VERSION 常量,本版为 2.56.0;打包文件名由 src/version.py 的 package_name() 给出,为 app_guanlang_v2.56.0.zip。
+本文所有数字来自仓库文件或命令的实跑输出,不采用估算与推测;查不到、未实现的,一律写"未取证"或"未实现",并在第 17 章汇总。本文中"本次实测"与各表"实测"列一律指样本场实测(2026-09-22):即在样本风电场的一套实例上的一次实跑,换场后这些数字会变、方法与口径不变。版本号的唯一真源是 src/version.py 的 VERSION 常量,本版为 2.56.1;打包文件名由 src/version.py 的 package_name() 给出,为 app_guanlang_v2.56.1.zip。
 
 本文遵循四条写作口径:含"窗"且确实指时间窗口的,一律写全"时间窗"(天气窗、作业窗、预览窗、观测窗属领域词,保持原样);影响的是风电机组时写"影响机组"或"影响机组数";使用英文简写时必须写成"中文(英文简写)"形式,如平均无故障间隔(MTBF)、平均停机间隔(MTBO)、单次停机时长(MDT);全文用简体中文与半角数字与单位。
 
@@ -839,7 +839,7 @@ Windows 安装入口是 install.bat 与 install.ps1,步骤号写死在输出
 
 ### 12.3 安装记录与版本三守卫
 
-安装记录 install-info.json 是"这台机器装的是哪一版"的唯一凭据,字段如表 12-3 所示。装完写、卸载时删(删掉等于这台机器回到"没装过")。需要如实指出:本机该文件记录的版本是 2.5.0(装于 2026-09-17),落后于当前代码版本 2.56.0,因此它正是"安装前检查会提示版本差异"的活样本;重装或升级后会随之更新。
+安装记录 install-info.json 是"这台机器装的是哪一版"的唯一凭据,字段如表 12-3 所示。装完写、卸载时删(删掉等于这台机器回到"没装过")。需要如实指出:本机该文件记录的版本是 2.5.0(装于 2026-09-17),落后于当前代码版本 2.56.1,因此它正是"安装前检查会提示版本差异"的活样本;重装或升级后会随之更新。
 
 | 字段 | 含义 |
 |---|---|
@@ -1200,7 +1200,7 @@ Linux 侧的适配程度如表 15-4 所示,全部为"已有实现但在本次
 
 ### 16.6 与同批交付文档的对应关系
 
-本章对应《需求分析_观澜_2.56.0.docx》第 11 章(场配置化、机组与机型可替换、数据源形态可适配、阈值按场标定、术语与单位可配、跨场统一口径、按场裁剪收资、换场验收检查表)与《数据要求说明_观澜_2.56.0.docx》第 12 章(通用必选与可选的判定规则、场配置字段对照、数据源形态适配、换场收资差异清单、按场裁剪步骤);本章给的是设计侧的落点、换场作业单与检查表,数据侧的收资口径与字段要求以数据要求说明为准,需求侧的验收项以需求分析为准。
+本章对应《需求分析_观澜_2.56.1.docx》第 11 章(场配置化、机组与机型可替换、数据源形态可适配、阈值按场标定、术语与单位可配、跨场统一口径、按场裁剪收资、换场验收检查表)与《数据要求说明_观澜_2.56.1.docx》第 12 章(通用必选与可选的判定规则、场配置字段对照、数据源形态适配、换场收资差异清单、按场裁剪步骤);本章给的是设计侧的落点、换场作业单与检查表,数据侧的收资口径与字段要求以数据要求说明为准,需求侧的验收项以需求分析为准。
 
 ***
 
@@ -1266,7 +1266,7 @@ Linux 侧的适配程度如表 15-4 所示,全部为"已有实现但在本次
 
 | 章节 | 来源文件 |
 |---|---|
-| 1 文档说明 | src/version.py(VERSION 与 HISTORY 的 2.56.0 条目)、docs/需求分析_观澜_2.56.0.docx、scripts/delivery_docs_figures.py |
+| 1 文档说明 | src/version.py(VERSION 与 HISTORY 的 2.56.1 条目)、docs/需求分析_观澜_2.56.1.docx、scripts/delivery_docs_figures.py |
 | 2 设计目标与原则 | docs/系统设计说明.md(四条设计铁律)、app_ontology/.../ontology/mcp_server.py 头部、src/windscada/subsys/fusion.py 头部、src/windscada/perf/reliability.py |
 | 3 总体架构 | configs/serve.json、scripts/guanlan_gateway.py、guanlan.py(组件启动表)、run/pids.json、docs/系统设计说明.md(产物全景) |
 | 4 目录结构与路径真源 | src/paths.py、configs/registry.yaml、docs/系统设计说明.md(路径约定与统一记录)、本次目录实测 |

+ 21 - 20
docs/src/需求分析_观澜_2.56.0.md → docs/src/需求分析_观澜_2.56.1.md

@@ -1,4 +1,4 @@
-# 需求分析 · 观澜 v2 风电场智能分析系统 · 版本 2.56.0
+# 需求分析 · 观澜 v2 风电场智能分析系统 · 版本 2.56.1
 
 ## 1 文档说明
 
@@ -6,7 +6,7 @@
 
 ### 1.1 目的
 
-本文是「观澜 v2(风电场智能分析系统)」的**需求分析文档**,回答四个问题:这套系统究竟要满足谁的什么需要;这些需要从哪来、经过哪些版本变成现在的样子;每一条需要对应什么功能、什么输入、什么输出、拿什么判据验收;以及哪些事本版明确不做。本文与同批交付的《系统设计说明_观澜_2.56.0.docx》《数据要求说明_观澜_2.56.0.docx》配套:本文讲「要什么、凭什么算做到了」,设计说明讲「怎么做的」,数据要求说明讲「要哪些数据、什么形态、什么单位」。
+本文是「观澜 v2(风电场智能分析系统)」的**需求分析文档**,回答四个问题:这套系统究竟要满足谁的什么需要;这些需要从哪来、经过哪些版本变成现在的样子;每一条需要对应什么功能、什么输入、什么输出、拿什么判据验收;以及哪些事本版明确不做。本文与同批交付的《系统设计说明_观澜_2.56.1.docx》《数据要求说明_观澜_2.56.1.docx》配套:本文讲「要什么、凭什么算做到了」,设计说明讲「怎么做的」,数据要求说明讲「要哪些数据、什么形态、什么单位」。
 
 本文的写作口径是**只写能取证的事实**:每个数字、每条结论都能指到仓库里的某个文件、某次实跑输出或某条版本记录;查不到、取不到的一律写明「未取证」或「未到位」,不做推测性补全。因此文中会出现少量「未取证」的说明句,那是刻意留下的诚实边界,不是遗漏。
 
@@ -18,19 +18,19 @@
 
 ### 1.3 版本对应关系
 
-系统版本号只有一个真源:仓库内的 src/version.py 的 VERSION 一行。本版 VERSION = 2.56.0,版本记录(HISTORY)共 95 条,人读的版本表由 scripts/version_log.py 从 HISTORY 生成到 docs/版本记录.md,并由 version_log.py --check 与 guanlan.py check 双重校验「记录表与代码一致」。
+系统版本号只有一个真源:仓库内的 src/version.py 的 VERSION 一行。本版 VERSION = 2.56.1,版本记录(HISTORY)共 96 条,人读的版本表由 scripts/version_log.py 从 HISTORY 生成到 docs/版本记录.md,并由 version_log.py --check 与 guanlan.py check 双重校验「记录表与代码一致」。
 
 表 1-1 版本与文档的对应关系
 
 | 项 | 值 | 取证方式 |
 |---|---|---|
-| 系统版本 | 2.56.0 | src/version.py 的 VERSION;guanlan.py check 实跑报「版本管理: 观澜 v2(风电场智能分析系统) v2.56.0」 |
-| 交付包名 | app_guanlang_v2.56.0.zip | src/version.py 的 package_name();同一条 check 输出 |
-| 本文版本 | 2.56.0(与系统版本同号) | 本文标题与 src/version.py 的 VERSION |
+| 系统版本 | 2.56.1 | src/version.py 的 VERSION;guanlan.py check 实跑报「版本管理: 观澜 v2(风电场智能分析系统) v2.56.1」 |
+| 交付包名 | app_guanlang_v2.56.1.zip | src/version.py 的 package_name();同一条 check 输出 |
+| 本文版本 | 2.56.1(与系统版本同号) | 本文标题与 src/version.py 的 VERSION |
 | 版本史条目数 | 13 条(最新一条即本次文档交付) | src/version.py 的 HISTORY;docs/版本记录.md |
 | 版本号规则 | v大版本号.中版本号.小版本号;大改方案或架构、中改非核心功能、小为消缺 | src/version.py 的 BUMP_RULE 与 LEVEL_MEANING;docs/系统设计说明.md §15 |
 | 版本级别机器判据 | level_of(旧, 新) 返回 major 或 minor 或 patch 或 same | src/version.py 的 level_of() |
-| 本次变化级别 | 小(patch,源码模块化重构推进 P1:公共层九个平台件实体迁移,旧路径留兼容转发壳;系统功能与运行为未变) | HISTORY 中 2.56.0 条目的 level 字段 |
+| 本次变化级别 | 小(patch,源码模块化重构推进 P1:公共层九个平台件实体迁移,旧路径留兼容转发壳;系统功能与运行为未变) | HISTORY 中 2.56.1 条目的 level 字段 |
 
 本版相对 2.10.0 的实质变化是**交付文档对外化**:三份交付文档(需求分析、系统设计说明、数据要求说明)全文不体现具体风电场(去标识化),并新增多风电场适用性一章;系统功能未变,属交付物修订。依据用户令原文:「修改三份文档,内容参考样本风电场,但不体现样本风电场,且具有不同风电场适用性」(引用时把样本场名按去标识化口径写成「样本风电场」)。
 
@@ -119,10 +119,11 @@
 
 ### 3.2 需求演进时间轴
 
-表 3-1 版本史与用户令要点(依据 src/version.py 的 HISTORY,共 95 条)
+表 3-1 版本史与用户令要点(依据 src/version.py 的 HISTORY,共 96 条)
 
 | 版本 | 日期 | 用户令要点 | 变化级别 |
 |---|---|---|---|
+| 2.56.1 | 2026-10-01 | lang.py 退役+ 补 en.ts ⇒ 项目检查全绿 | 小(patch) |
 | 2.56.0 | 2026-10-01 | 删除旧 Python 业务后端;产物生成端取数改指算法服务 | 中(minor) |
 | 2.55.0 | 2026-10-01 | 入口改为 Vue 工作台 · 门户退役 | 中(minor) |
 | 2.54.1 | 2026-10-01 | 删除旧页 design/terms+ 摘除 api 转发 ⇒ 全门禁保持通过 | 小(patch) |
@@ -219,7 +220,7 @@
 | 0.4.0 | 2026-09-17 | 打包默认不含输入数据与产物与日志与临时文件;无窗口启动;统一配置与日志;页面归口;服务化 | 旧编号(legacy) |
 | 0.2.0 | 2026-09-01 | 含产物的旧交付基线,历史上用作「包内无生成端」那批产物的补救源 | 旧编号(legacy) |
 
-需求的演进如图 1-1 所示。该图依据 src/version.py 的 HISTORY 绘制,覆盖上表 95 条记录(即两级旧编号 0.4.0 与 0.2.0,以及从 2.5.0 起按规则递增的二十三个版本)。按级别统计,这二十三个版本里「中」级变化52条(2.56.0、2.55.0、2.54.0、2.53.0、2.52.0、2.51.0、2.50.0、2.49.0、2.48.0、2.47.0、2.46.0、2.45.0、2.44.0、2.43.0、2.42.0、2.41.0、2.40.0、2.39.0、2.38.0、2.37.0、2.36.0、2.35.0、2.34.0、2.33.0、2.32.0、2.31.0、2.30.0、2.29.0、2.28.0、2.27.0、2.26.0、2.25.0、2.24.0、2.23.0、2.22.0、2.21.0、2.20.0、2.19.0、2.18.0、2.17.0、2.16.0、2.15.0、2.14.0、2.13.0、2.12.0、2.11.0、2.10.0、2.9.0、2.8.0、2.7.0、2.6.0、2.5.0),「小」级变化41条(2.54.1、2.53.2、2.53.1、2.52.1、2.51.1、2.47.1、2.42.1、2.40.1、2.36.2、2.36.1、2.30.1、2.29.1、2.28.1、2.24.2、2.24.1、2.23.3、2.23.2、2.23.1、2.22.1、2.21.2、2.21.1、2.15.2、2.15.1、2.12.1、2.11.9、2.11.8、2.11.7、2.11.6、2.11.5、2.11.4、2.11.3、2.11.2、2.11.1、2.10.4、2.10.3、2.10.2、2.10.1、2.9.2、2.9.1、2.8.2、2.8.1),没有大版本升级。这个分布清楚地显示出这条线的性质——**功能面在 2.5.0 到 2.7.0 之间快速铺开,之后转入以消缺与口径统一为主的密集收敛期**。
+需求的演进如图 1-1 所示。该图依据 src/version.py 的 HISTORY 绘制,覆盖上表 96 条记录(即两级旧编号 0.4.0 与 0.2.0,以及从 2.5.0 起按规则递增的二十四个版本)。按级别统计,这二十四个版本里「中」级变化52条(2.56.0、2.55.0、2.54.0、2.53.0、2.52.0、2.51.0、2.50.0、2.49.0、2.48.0、2.47.0、2.46.0、2.45.0、2.44.0、2.43.0、2.42.0、2.41.0、2.40.0、2.39.0、2.38.0、2.37.0、2.36.0、2.35.0、2.34.0、2.33.0、2.32.0、2.31.0、2.30.0、2.29.0、2.28.0、2.27.0、2.26.0、2.25.0、2.24.0、2.23.0、2.22.0、2.21.0、2.20.0、2.19.0、2.18.0、2.17.0、2.16.0、2.15.0、2.14.0、2.13.0、2.12.0、2.11.0、2.10.0、2.9.0、2.8.0、2.7.0、2.6.0、2.5.0),「小」级变化42条(2.56.1、2.54.1、2.53.2、2.53.1、2.52.1、2.51.1、2.47.1、2.42.1、2.40.1、2.36.2、2.36.1、2.30.1、2.29.1、2.28.1、2.24.2、2.24.1、2.23.3、2.23.2、2.23.1、2.22.1、2.21.2、2.21.1、2.15.2、2.15.1、2.12.1、2.11.9、2.11.8、2.11.7、2.11.6、2.11.5、2.11.4、2.11.3、2.11.2、2.11.1、2.10.4、2.10.3、2.10.2、2.10.1、2.9.2、2.9.1、2.8.2、2.8.1),没有大版本升级。这个分布清楚地显示出这条线的性质——**功能面在 2.5.0 到 2.7.0 之间快速铺开,之后转入以消缺与口径统一为主的密集收敛期**。
 
 ![图 1-1 观澜需求演进时间轴(依据 src/version.py 的 HISTORY)](figures/fig-req-01-需求演进时间轴.png)
 
@@ -238,7 +239,7 @@
 | 所有的计算均要形成观澜的源代码 | 逐族补生成端:变桨面、在升闭环、三层基线、融合面 handoff、总览页、由台账生成 claim | 反向呼应审计成立 3,548 件、不成立 0 件、未归类 0 件 |
 | 运行期一律不从交付包补齐 | 第 ⑤ 步由「补齐随包件」改为「反向呼应审计」,只报账不搬运 | rebuild_all --dry-run 计划中 ⑤ 步命令为 products_reverse_audit.py --check |
 | 清除产物不留备份 | products_state.py --off --yes 改真删除;--on 与门户恢复按钮移除 | 控制台按钮语义与 409 前置校验;docs/系统设计说明.md §7 |
-| 装成服务并在安装时检查版本 | win_service.py 以 ctypes 直连 SCM;service_main.py 与 systemd 单元;install-info.json 记录版本并比对 | check 实跑报「卸载入口在位 2 个」「版本管理 v2.56.0」 |
+| 装成服务并在安装时检查版本 | win_service.py 以 ctypes 直连 SCM;service_main.py 与 systemd 单元;install-info.json 记录版本并比对 | check 实跑报「卸载入口在位 2 个」「版本管理 v2.56.1」 |
 | 观澜改为监听所有 IP | serve.json 增 public_host 且出厂为 0.0.0.0,仅门户网关用它,组件仍绑本机 | check 实跑显示 6 个端口全部运行中;README 第二节写明无鉴权风险与回退口径 |
 | 描述口径:时间窗、影响机组、中文(英文简写) | 界面文案与报告构建器统一改写;i18n_en 键同步 | docs/系统设计说明.md §17.1 与 §17.2;语言包 845 条前后端成对 |
 | 整理需求与设计与数据接入各写一份 Word 文档 | docs/src 下 markdown 源件;渲染器输出带域目录的 docx;插图生成器生成 14 张图,数字全部从真件取 | HISTORY 2.10.0 条目;本版三份文档 |
@@ -454,7 +455,7 @@
 | NFR-09 | 安全只读:不写回现场系统、不改原始件、不产生控制指令;对外暴露时须自行在网侧限来源 | 原始件按只读输入对待,落位冲突默认拒绝;对外只有一个网关入口;启动日志每次提醒页面无鉴权 | docs/输入数据放置指导_v0.1.md §3;README_先读我.MD 第二节 |
 | NFR-10 | 可维护性:单一真源,改一处不必改多处;关键口径有机器守卫 | 版本号只有一行真源并由三处守卫盯住;路径、配置、日志、语言各有唯一取用口与审计器 | docs/系统设计说明.md §15.2 与 §11.1;docs/系统设计说明.md §3.1 |
 | NFR-11 | 无人值守与自愈:长跑的服务要能被托管并自动拉起 | 服务体每 15 秒巡检并拉起掉线组件;崩溃重启策略;远程部署一律用服务,不用会话前台进程 | docs/系统设计说明.md §14.1 与 §14.4;README_先读我.MD 第二节 |
-| NFR-12 | 收资清单按场裁剪:以通用收资模板为底,按本场的机型与数据源形态与专题范围裁剪,逐条写明必须、建议、可选或可替代以及「不收会怎样」 | 每一族都有必须性分级与缺件后果且可复核;换场时清单随场定义与机型变化,不是照抄样本场 | 本文第 11 章;《数据要求说明_观澜_2.56.0.docx》第 8 章与第 12 章 |
+| NFR-12 | 收资清单按场裁剪:以通用收资模板为底,按本场的机型与数据源形态与专题范围裁剪,逐条写明必须、建议、可选或可替代以及「不收会怎样」 | 每一族都有必须性分级与缺件后果且可复核;换场时清单随场定义与机型变化,不是照抄样本场 | 本文第 11 章;《数据要求说明_观澜_2.56.1.docx》第 8 章与第 12 章 |
 
 ### 6.2 性能与耗时实测
 
@@ -490,7 +491,7 @@
 
 | 真源 | 管什么 | 守卫与实跑结果 |
 |---|---|---|
-| src/version.py | 名称、版本、版本规则、版本史、包名 | 版本记录一致性检查;check 实跑报 v2.56.0 且记录表与代码一致 |
+| src/version.py | 名称、版本、版本规则、版本史、包名 | 版本记录一致性检查;check 实跑报 v2.56.1 且记录表与代码一致 |
 | src/paths.py | 一切路径解析的基准与助手 | 配置审计器检查不手拼路径;实跑无不一致 |
 | configs 目录与登记表 | 端口、模型档、场配置、页面归口、配置登记 | 配置审计实跑已知缺口与白名单 14 条、提示 7 条 |
 | src/logfile.py | 日志目录、命名、行格式、保留策略 | 日志审计实跑无不一致、提示 87 条 |
@@ -501,7 +502,7 @@
 
 ### 7.1 数据族与功能映射
 
-系统的数据需求可以概括成一句话:**七类现场源件加一类共享机理资料,喂出九个产物仓,页面只读产物**。每个源类目录名就是摄入接口,改名等于换接口。数据族与功能的对应关系如下表;逐类的字段、单位、必须性与质量要求见同批交付的《数据要求说明_观澜_2.56.0.docx》。
+系统的数据需求可以概括成一句话:**七类现场源件加一类共享机理资料,喂出九个产物仓,页面只读产物**。每个源类目录名就是摄入接口,改名等于换接口。数据族与功能的对应关系如下表;逐类的字段、单位、必须性与质量要求见同批交付的《数据要求说明_观澜_2.56.1.docx》。
 
 表 7-1 数据族到功能的映射(样本场实测(2026-09-22))
 
@@ -534,7 +535,7 @@
 
 ### 7.3 与数据要求说明的分工
 
-本文只回答「要哪些数据、这些数据支撑什么功能」。数据的字段级要求(核心测点的名称、单位、必须性、缺失替代、对齐规则、质量门与核对锚点)以及面向现场的收资清单,写在《数据要求说明_观澜_2.56.0.docx》里。该文档按用户令要求与现场收资文件逐条对照,并对现场收资层面的已知缺失逐条如实记录,例如测风塔数据为零交付、故障录波只有 4 台、振动侧 handoff 正本缺失由观澜自算件顶上、远端机器未安装本机模型等。
+本文只回答「要哪些数据、这些数据支撑什么功能」。数据的字段级要求(核心测点的名称、单位、必须性、缺失替代、对齐规则、质量门与核对锚点)以及面向现场的收资清单,写在《数据要求说明_观澜_2.56.1.docx》里。该文档按用户令要求与现场收资文件逐条对照,并对现场收资层面的已知缺失逐条如实记录,例如测风塔数据为零交付、故障录波只有 4 台、振动侧 handoff 正本缺失由观澜自算件顶上、远端机器未安装本机模型等。
 
 ## 8 页面与信息架构需求
 
@@ -616,7 +617,7 @@
 | 运行环境与依赖 | 10 | Python 版本不低于 3.11(实测 3.12.10);九个第三方依赖逐个导入 | 全 OK |
 | 静态质量门 | 4 | 源码可编译 229 个文件;语言包 845 条成对;入口引用闭合 48 条;入口脚本编码守则 | 全 OK |
 | 子进程口径 | 3 | 捕获输出可用;无窗口启动输出进日志;无窗口位已设,标志位 0x8000200 | 全 OK |
-| 版本与卸载入口 | 2 | 版本管理与记录表一致(v2.56.0,包名 app_guanlang_v2.56.0.zip);卸载入口两个都在位 | 全 OK |
+| 版本与卸载入口 | 2 | 版本管理与记录表一致(v2.56.1,包名 app_guanlang_v2.56.1.zip);卸载入口两个都在位 | 全 OK |
 | 制品与台账审计 | 5 | 反向呼应 3,548 件成立;页面归口检查 21 项;配置统一;日志统一;输入数据放置体检 26,503 件结构合规 | 全 OK |
 | 产物与发布件在位 | 10 | 标准仓、本体对象库、findings、事实契约、门户、仿真合页服务与资料包、三维资产、仿真回放资产、治理清单交付件 | 全 OK |
 | 原始件目录 | 1 | 原始件目录存在(无数据时此项不影响页面) | 全 OK |
@@ -742,7 +743,7 @@
 
 本章有两个词要先定义。「本场」指**当前被选中的场**,由配置选定,不必然等于本文取数的样本风电场;「换场」指从一场切到另一场运行的整套动作(改配置、放数据、重跑、按检查表复验),不是把两场的数据混在一棵树里。本章沿用的占位符与附录 A 一致:`<场>`、`<场站>`、`<机型>` 按实际风电场替换。
 
-本章需求与《数据要求说明_观澜_2.56.0.docx》《系统设计说明_观澜_2.56.0.docx》配套使用:本章讲「换场要满足什么」,数据要求说明讲「要向新场收哪些数据、哪些可以裁剪」,《设计说明》讲「配置在哪一层生效、哪些参数属于场相关层」。
+本章需求与《数据要求说明_观澜_2.56.1.docx》《系统设计说明_观澜_2.56.1.docx》配套使用:本章讲「换场要满足什么」,数据要求说明讲「要向新场收哪些数据、哪些可以裁剪」,《设计说明》讲「配置在哪一层生效、哪些参数属于场相关层」。
 
 取证边界要如实写一句:FR-44 至 FR-51 与 NFR-12 有既有的场配置层、摄入层与场级字段语义登记作依据;FR-52 的跨场横向对比是本次新增的口径要求,其实跑记录与机器守卫随后续版本补齐——本文不谎称已经通过。
 
@@ -821,7 +822,7 @@
 
 | 编号 | 需求 | 判据 | 依据文件 |
 |---|---|---|---|
-| NFR-12 | 收资清单按场裁剪:以通用收资模板为底,按本场的机型、数据源形态与专题范围裁剪,逐条写明必须、建议、可选或可替代以及「不收会怎样」 | 每一族都有必须性分级与缺件后果且可复核;换场时清单随场定义与机型变化,不是照抄样本场 | 《数据要求说明_观澜_2.56.0.docx》第 8 章与第 12 章;docs/输入数据放置指导_v0.1.md |
+| NFR-12 | 收资清单按场裁剪:以通用收资模板为底,按本场的机型、数据源形态与专题范围裁剪,逐条写明必须、建议、可选或可替代以及「不收会怎样」 | 每一族都有必须性分级与缺件后果且可复核;换场时清单随场定义与机型变化,不是照抄样本场 | 《数据要求说明_观澜_2.56.1.docx》第 8 章与第 12 章;docs/输入数据放置指导_v0.1.md |
 
 ### 11.9 换场验收需求
 
@@ -964,8 +965,8 @@
 | 场定义与机型与物理约束参数的写法样例 | app_ETL/configs/farms/<场>.yaml |
 | 机型—场站数据契约与字段单位真源 | app_ETL/configs/contracts/<机型>_<场>.yaml 与 app_ETL/configs/canonical/dictionary.yaml |
 | 场级字段语义登记与其它厂商导出形态的对齐 | app_ETL/configs/farms/<场>/field_semantic_registry.yaml |
-| 按场裁剪的收资清单与必须性分级 | docs/src/数据要求说明_观澜_2.56.0.md |
-| 场无关引擎与场相关参数的分层与换场作业单 | docs/src/系统设计说明_观澜_2.56.0.md |
+| 按场裁剪的收资清单与必须性分级 | docs/src/数据要求说明_观澜_2.56.1.md |
+| 场无关引擎与场相关参数的分层与换场作业单 | docs/src/系统设计说明_观澜_2.56.1.md |
 
 表中 `<场>`、`<场站>`、`<机型>` 为占位符,按实际风电场替换。
 
@@ -1008,4 +1009,4 @@
 | 换场 | — | 从一场切到另一场运行的整套动作:改配置、放数据、重跑、按检查表复验 | 本文 §11.9 |
 | 缺族降级 | — | 某族数据缺失时按缺件如实标注并降低该族功能,不造数、不用他场数字顶替 | 本文 FR-49 |
 | 按场标定 | — | 把阈值、时间窗锚点、限电窗、温度档带宽等场相关参数按本场取值 | 本文 FR-50 |
-| 通用收资模板 | — | 与场无关的收资条目底稿,按场裁剪后作为现场收资清单 | docs/src/数据要求说明_观澜_2.56.0.md |
+| 通用收资模板 | — | 与场无关的收资条目底稿,按场裁剪后作为现场收资清单 | docs/src/数据要求说明_观澜_2.56.1.md |

BIN
docs/数据要求说明_观澜_2.56.0.docx


BIN
docs/数据要求说明_观澜_2.56.1.docx


+ 6 - 5
docs/版本记录.md

@@ -7,7 +7,7 @@
 <!-- VERSION-LOG:BEGIN -->
 ## 版本号规则(用户令 2026-09-17)
 
-编号 **`v<大版本号>.<中版本号>.<小版本号>`**,例如 `v2.56.0`。
+编号 **`v<大版本号>.<中版本号>.<小版本号>`**,例如 `v2.56.1`。
 
 | 级别 | 含义 |
 |---|---|
@@ -17,12 +17,13 @@
 
 **怎么改**:改动落在"解决方案/架构/核心功能" → 大 +1(中/小归 0);落在"非核心功能的新增/减少/修改" → 中 +1(小归 0);只是"消缺完善" → 小 +1;一次发布混了几类,取最高那一级
 
-**打包文件名** = `app_guanlang_v2.56.0.zip`(规则:`app_guanlang_v<版本号>.zip`)。
+**打包文件名** = `app_guanlang_v2.56.1.zip`(规则:`app_guanlang_v<版本号>.zip`)。
 
-版本号只写在 `src/version.py` 一行(`VERSION = '2.56.0'`);打包器、安装脚本、安装记录 `install-info.json`、自检都读它,不另设真源。
+版本号只写在 `src/version.py` 一行(`VERSION = '2.56.1'`);打包器、安装脚本、安装记录 `install-info.json`、自检都读它,不另设真源。
 
 | 版本 | 日期 | 级别 | 主要变更 | 说明 |
 |---|---|---|---|---|
+| `v2.56.1` | 2026-10-01 | 小 | lang.py 退役(工具链检查改指 Vue 语言包)+ 补 en.ts ⇒ 项目检查全绿 | ① 删除旧页语言包 `app_frontEnd_guanlan/lang.py` 与兼容壳 `src/windscada/lang.py`,并摘除 `app_frontEnd_guanlan/api.py` 的 lang 转发、基线清单改指 Vue 语言包。② 工具链 `cli.py` 的语言包成对检查(原调 `lang.check()`)改指**现行 Vue 语言包**:比对 `web/src/i18n/{zh,en}.ts` 的**键集合是否一致**。③ 该检查随即(正确地)报红:Vue 侧只有 `zh.ts`(814 键)、**没有 en.ts** —— 这是前端迁移的真实缺口(旧页是双语的)。处置:从被删的旧语言包(HEAD 版本)取回 **799 键英文值**,按 Vue 键集合生成 `en.ts`;取不到的 **36 键按项目自身约定"译不出留原文"回落中文**,文件头写明来源。④ 顺带修一处运维口径:Java 构建目录里残留 `target/run.log` 触发"日志统一"门禁(运行日志只许在 logs/)⇒ 已清。⑤ 复验:**`guanlan.py check` 全绿**(语言包 814 条成对 · 日志统一 · 其余项照常)。 |
 | `v2.56.0` | 2026-10-01 | 中 | 删除旧 Python 业务后端(serve.py / windscada_serve.py);产物生成端取数改指算法服务 | ① 按用户令"对拍通过后再删 Python 源码",本轮删掉**旧业务后端**:`app_backEnd_guanlan/serve.py` 与 `scripts/windscada_serve.py`。删除前提早已满足:契约 17 条 detail_api 由 Java 后端承接(裁判逐值一致)、问答管线在算法服务、页面侧核验已登记退役、detail 服务项已从 `cli.py` 的服务清单彻底移除。② 删前的解耦(沿用既有"搬"范式,闭包传递 + 就地自检):产物生成端 `scripts/windscada_overview_build.py` 还依赖旧后端的 15 个符号 —— `single_problem`、`turbine_problems`、六个图表件(`fig_temp/fig_duty/fig_alarm_bars/fig_curve/fig_zero/fig_sys_alarms`)、`vib_scalar_fig`、投影助手 `_proj`/`_REQ`(含其类)以及 7 个常量,全部搬进算法服务 `app_algorithmModel_guanlan/service/fleet_views.py`(此前已搬入 fleet/curves/vibcms/reload);生成器的取数 import 随之改指算法服务。③ 连带登记:`detail_deps.SERVE` 读取改为"存在才读";`configs/portal_pages.yaml` 的 `detail_tabs.file` 从已删的 `scripts/windscada_serve.py` 改指现行 Vue 页签清单 `app_frontEnd/web/src/lib/tabs.ts`。④ 复验:`pages_audit` 0 项要处理(退出码 0)、**全门禁通过**。⑤ 仍未动(最后两项):`pages/snapshot.py`(产物生成端的单文件快照烘焙,属旧页产物)与 `lang.py`(工具链 `cli.py` 与审计基线清单引用)。 |
 | `v2.55.0` | 2026-10-01 | 中 | 入口改为 Vue 工作台 · 门户退役(连带:契约期望刷新 · 重算链去门户步 · 删门户构建器与旧页装配件) | ① 用户令(2026-10-01):**入口改为 Vue 工作台(/ → Vue),门户退役**。落地:网关 `PortalController` 的文件优先目标从 `release/portal.html` 改为 **`release/web/index.html`**(Vue 壳),并把壳里的**相对**资源路径 `./assets/…` 改写为**已路由**的 `/web/assets/…`(省掉新增路由与 YAML 改动 —— 此前多次踩过 yml 重复键与谓词怪癖);响应头 `X-Portal-Source: vue`。实测:`/` 与 `/index.html` → 200 · 413 B(标题「观澜 · 风电数据分析与决策支持」)、`/web/assets/index-*.js` → 200(195,415 B)、`/api/fleet` 与 `/algorithm` 照常。② 连带项(逐项处理):a) 契约 `/` 的**期望标题刷新**为现值并加 `entry_note` 说明(复核回到 **40/40**);b) 重算链 `app_ETL/.../rebuild_all.py` 去掉「⑧b 重装门户」步(门户不再随数据重建);c) 删除门户构建器与旧页装配件:`scripts/portal_build.py`、`scripts/guanlan_portal_{fix_anchors,inject_claims}.py`、`app_frontEnd_guanlan/builders/*`(3 件 + `__init__`)、`pages/build.py`、`pages/classic.py`,并摘除 `app_frontEnd_guanlan/api.py` 中相应转发名。③ 仍未动(需各自专项):`pages/snapshot.py`(产物生成器 `windscada_overview_build.py` 依赖)、`lang.py`(工具链 `cli.py` 与审计 `guanlan_baseline_manifest`/`data_tpl_en` 依赖)、`serve.py` 与 `scripts/windscada_serve.py`(旧业务后端,`/api` 已由 Java 承接)。④ 复验:**HTTP 契约复核 40/40**、**全门禁通过**。 |
 | `v2.54.1` | 2026-10-01 | 小 | 删除旧页 design/terms(含兼容壳)+ 摘除 api 转发 ⇒ 全门禁保持通过 | ① 删除批次续:删掉旧页设计系统与显示层术语映射 —— `app_frontEnd_guanlan/design.py`(233 行)、`terms.py`(37 行) 及两个兼容壳 `src/windscada/design.py`、`src/windscada/terms.py`。② 连带同步(门禁实逮):模块边界审计报 R5 两条 —— `app_frontEnd.api.design/terms 的转发目标不存在`;已在 `app_frontEnd_guanlan/api.py` 的 `_TARGETS` 里摘除这两个转发名并写明理由。③ 复验:模块边界审计 `[OK] 结构齐全 · 只经 api 调用 · 依赖方向合规 · 公共层纯净 · 接口目标全在位`,**全门禁通过**。④ 删除前的影响面测绘(本轮做的功课,值得记档):`pages/build.py` 被**门户构建器** `scripts/portal_build.py` 依赖、`pages/snapshot.py` 被**产物生成器**`scripts/windscada_overview_build.py` 依赖、`lang.py` 被工具链与审计依赖 ⇒ 这三项**不可随旧页一并删**,否则门户无法重建、产物无法重算;要删它们得先"冻门户/改生成端/改审计口径",属独立一步。⑤ 因此本轮只删**确认无依赖**的 design/terms 两项,其余按上一条结论留待专项处理。 |
@@ -121,8 +122,8 @@
 
 ## 本次交付
 
-* 版本:`v2.56.0`(观澜·如东样板 v2)
-* 交付包:`app_guanlang_v2.56.0.zip`
+* 版本:`v2.56.1`(观澜·如东样板 v2)
+* 交付包:`app_guanlang_v2.56.1.zip`
 * 服务名:`guanlan`(Windows 服务 / systemd 单元)
 * 安装 / 卸载:`install.bat`、`install.sh` / `uninstall.bat`、`uninstall.sh`
 <!-- VERSION-LOG:END -->

BIN
docs/系统设计说明_观澜_2.56.0.docx → docs/系统设计说明_观澜_2.56.1.docx


BIN
docs/需求分析_观澜_2.56.0.docx → docs/需求分析_观澜_2.56.1.docx


+ 0 - 23
src/windscada/lang.py

@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-r"""兼容转发(P5,2026-09-28):实现已迁到 `app_frontEnd.app_frontEnd_guanlan/lang.py`。**本文件不得新增逻辑**。
-
-两种用法都保持原样:
-* `from src.windscada.lang import X` / `from src.windscada import lang` —— 本模块整体指向实现(并改写父包属性);
-* `python -m src.windscada.lang` —— `runpy` 按 `__main__` 重跑实现(实现里的 `__main__` 守卫照旧生效)。
-"""
-from __future__ import annotations
-
-import pathlib
-import runpy
-import sys
-
-sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2]))   # 安装根
-
-if __name__ == '__main__':
-    runpy.run_module('app_frontEnd.app_frontEnd_guanlan.lang', run_name='__main__')
-else:
-    from app_frontEnd.app_frontEnd_guanlan import lang as _impl
-    _parent = sys.modules.get(__package__ or '')
-    if _parent is not None:
-        setattr(_parent, 'lang', _impl)
-    sys.modules[__name__] = _impl