# -*- 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/ · /problem//) ────── # 静态站上那两条路径是 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