# -*- coding: utf-8 -*- """本体码表 — 枚举层的**单一正本** (2026-08-31)。 ## 为什么有这个文件 英文版返工三轮的根因不在渲染层, 在这里: `schema.py` 原先把枚举定义成中文字面量 (`FM_CLASSES={'磨损',...}` / `FACETS={'因侧执行链',...}`), 于是**校验闸强制所有生成器写中文** — 中文不是"漏译", 是被约束系统要求写进去的。产物端再怎么翻译都是治标, 且翻译自由文本必然产出 "Verification 状态机" / "scroll 元Data" 这类混杂串 (2026-08-31 实测, 已作废)。 ## 分层 (docs/本体数据标准.md) | 层 | 存什么 | 语言 | |---|---|---| | 标识层 id/key | `mechanism/WTG29/lub_starvation` | 英文 snake_case, **永不翻译** | | 枚举层 | `wear` / `cause_actuation` / `L2_reversible` | **英文码**, 显示时查 LABELS | | 数值层 | 3.07, "2026-07" | 无语言 | | 文本层 | name / note / decision 正文 | `_zh` + `_en` **双写**, 不靠词表替换 | **枚举与文本的分界是硬的**: 有限集合 ⇒ 码化(机器判, 一劳永逸); 自由文本 ⇒ 双写(人或模型写一次固化)。把自由文本当枚举翻译, 就是那三轮返工。 ## 兼容 `normalize(field, value)` 把历史中文值映射回码。**只读方向兼容, 不反向写** — 存量 7178 个对象保持原样(中文版在用, 不动), 新写入一律走码。 """ # ── 枚举码表: code → (zh, en) ────────────────────────────────────────── # 新增枚举值必须同时给 zh 与 en; 缺一不许进表 (缺了就会在渲染时回落成另一语言 = 残留复发) LABELS = { 'fm_class': { # ISO 14224 风格失效分类 'wear': ('磨损', 'Wear'), 'fatigue': ('疲劳', 'Fatigue'), 'lubrication_failure': ('润滑失效', 'Lubrication failure'), 'corrosion': ('腐蚀', 'Corrosion'), 'overheating': ('过热', 'Overheating'), 'fastener_failure': ('松动/紧固失效', 'Loosening / fastener failure'), 'contamination': ('污染/异物', 'Contamination / foreign object'), 'electrical_failure': ('电气失效', 'Electrical failure'), 'control_software': ('控制/软件', 'Control / software'), 'seal_failure': ('密封失效', 'Seal failure'), 'structural_damage': ('结构损伤', 'Structural damage'), 'measurement_failure': ('测量链失效', 'Measurement chain failure'), 'hydraulic_pneumatic': ('液压/气动失效', 'Hydraulic / pneumatic failure'), 'undetermined': ('未定', 'Undetermined'), }, 'facet': { # 观测面 (C3: 源数按面去重, 非按证据条数) 'cause_actuation': ('因侧执行链', 'Cause side — actuation chain'), 'effect_temperature': ('果侧温度', 'Effect side — temperature'), 'effect_vib_steady': ('果侧振动稳态', 'Effect side — vibration, steady state'), 'effect_vib_transient':('果侧振动瞬态', 'Effect side — vibration, transient'), 'effect_oil': ('果侧油液', 'Effect side — oil'), 'effect_electrical': ('果侧电气', 'Effect side — electrical'), 'external_record': ('外部记录', 'External record'), 'measurement_chain': ('测量链面', 'Measurement chain'), 'structural': ('结构面', 'Structural'), 'performance': ('性能面', 'Performance'), }, 'crosscheck': { # C4: 禁把"不可判"写成阴性 'supports': ('支持', 'Supports'), 'excludes': ('排除', 'Excludes'), 'indeterminate': ('不可判', 'Indeterminate'), }, 'reversibility': { # C6: 可逆性三级 'L1_read_only': ('L1只读', 'L1 read-only'), 'L2_reversible': ('L2可逆写', 'L2 reversible write'), 'L3_approval': ('L3待批', 'L3 pending approval'), }, 'review_tier': { # SOP §6.1 RV-1 审级 'lite': ('lite 单独立审', 'Lite — single independent review'), 'dual_model': ('双模审', 'Dual-model review'), 'advci_pr': ('AdvCI-PR', 'AdvCI-PR'), 'user_ruling': ('用户裁', 'User ruling'), 'field_exec': ('现场执行', 'Field execution'), }, 'closure_type': { # C16 (2026-09-07, Claude 裁/用户授权 "你来定"): Verdict 归宿类 (章程铁律 1 出生带归宿; E3 四类 + E10 两格); 码 = 章程原词 '现场验证单': ('现场验证单', 'Field verification order'), '补数据': ('补数据', 'Acquire data'), '下轮Measure': ('下轮Measure', 'Next Measure round'), '被后续替代': ('被后续替代', 'Superseded by later claim'), '已闭环/不适用': ('已闭环/不适用', 'Closed / not applicable'), '已有裁决/处置中': ('已有裁决/处置中', 'Already ruled / in progress'), }, 'decision_status': { # C16: Decision 生命周期 (机器提议 → 人裁 → 关闭/替代) 'proposed': ('提议 (机器)', 'Proposed (machine)'), 'proposed_pending_human': ('提议待人裁', 'Proposed, pending human ruling'), 'decided': ('已裁', 'Decided'), 'closed': ('已关闭', 'Closed'), 'superseded': ('已被替代', 'Superseded'), }, 'decision_kind': { # C16: Release 层 Decision 子类 (E9/E10) 'closure_proposal': ('归宿提议', 'Closure proposal'), 'closure_migrated': ('归宿迁移', 'Closure migrated'), 'screening_policy': ('普查策略', 'Screening policy'), }, 'detection_method': { # C15 检出方式 — 这次故障是怎么被发现的 # ★为什么必须有这一张表: 它是检出率/误报率/监测面有效性的**唯一直接来源**。 # 没有它, "高速轴点蚀在振动上不可见、靠内窥镜发现" 这类结论只能靠翻台账原文 # 事后对拍去得到 — 既贵又不可复现。 # ★故意做短 (10 个)。现场在塔上填表, 选项超过十个就会开始乱选。 'scada_alarm': ('SCADA 报警', 'SCADA alarm'), 'cms_vibration': ('CMS 振动', 'CMS vibration'), 'temp_trend': ('温度趋势', 'Temperature trend'), 'oil_lab': ('油液化验', 'Oil laboratory analysis'), 'routine_check': ('例行巡检', 'Routine inspection'), 'borescope': ('内窥镜', 'Borescope'), 'incidental': ('修他件时发现', 'Found while servicing something else'), 'sensory': ('外观/异响', 'Visual or audible'), 'owner_report': ('业主指出', 'Reported by the owner'), # ★"不确定"必须是一个**值**而不是留空。留空分不出"没查出来"(真实诊断结论, # 该进检出率分母) 与"忘了填"(数据质量问题, 该剔除) — 混在一起检出率永远算不准, # 而检出率正是这整套体系最终要产出的那个数。 'unknown': ('不确定', 'Not determined'), }, 'task_kind': { # C10 检修任务类型 'preventive': ('预防性', 'Preventive'), 'corrective': ('抢修', 'Corrective / breakdown'), 'inspection': ('检查', 'Inspection'), 'rectify': ('整改', 'Rectification'), }, 'part': { # 部件码 (台账部位名 → 码; 标识层用它拼 id) 'bolt': ('螺栓', 'Bolt'), 'yaw_brake_pad': ('偏航刹车片', 'Yaw brake pad'), 'brake_pad': ('刹车片', 'Brake pad'), 'yaw_motor': ('偏航电机', 'Yaw motor'), 'yaw_inverter': ('偏航变频器', 'Yaw inverter'), 'main_breaker': ('主断路器', 'Main circuit breaker'), 'slip_ring': ('滑环', 'Slip ring'), 'electrical_slip_ring': ('电气滑环', 'Electrical slip ring'), 'capacitor': ('电容', 'Capacitor'), 'power_module': ('电源模块', 'Power module'), 'stic_processor': ('STIC处理器', 'STIC processor'), 'cooling_fan': ('散热风扇', 'Cooling fan'), 'fan': ('风扇', 'Fan'), 'oil_pipe': ('油管', 'Oil pipe'), 'anemometer': ('风速仪', 'Anemometer'), 'igbt': ('IGBT', 'IGBT'), # 2025-2026 工单接入后新出现的部件 (闸拦下来才补的, 不是预先猜的) 'generator_lube_pump': ('发电机润滑泵', 'Generator lubrication pump'), 'motor': ('电机', 'Motor'), }, 'maturity_dim': { # 运维成熟度四维 (docs/运维可靠性方法论.md) 'evidence_completeness': ('证据完整', 'Evidence completeness'), 'closure_verification': ('闭环验证', 'Closure verification'), 'recurrence_control': ('复发控制', 'Recurrence control'), 'commonality_identification': ('共性识别', 'Commonality identification'), }, 'maturity_stage': { # 成熟度五级阶梯 'reactive': ('1 被动抢修', '1 — Reactive'), 'planned': ('2 计划维修', '2 — Planned'), 'condition': ('3 状态维修', '3 — Condition-based'), 'predictive': ('4 预测维修', '4 — Predictive'), 'reliability':('5 可靠性驱动', '5 — Reliability-driven'), }, # ★grade 是**结论级别** (这条结论有多硬), 不是设备状态 (机器多健康)。 # 设备状态的英文按 ISO 17359 / ISO 20816 走, 见 src/windscada/ui_en.py 的说明; # 两者混用会让"预警"这类词在两种语境下指不同的东西。 'grade': { # SOP §0.2 结论六枚举 'established': ('定论', 'Established'), 'early_warning': ('准定论·预警', 'Near-established — early warning'), 'candidate': ('候选', 'Candidate'), 'reference': ('参考', 'Reference'), 'insufficient': ('INSUFFICIENT', 'Insufficient evidence'), 'withdrawn': ('撤回', 'Withdrawn'), }, # ★2026-08-31 增: count 的计数单位。 # 为什么必须码化 —— C12 早就强制"带 count 必须声明 count_basis", 但**只查有没有, # 不查填了什么**。结果是这个字段被填成"手边最近的那个词": 实测 5 条 finding 里 3 条错标 # (35 台/37 台被标成 work_orders, 59 个长停事件被标成 work_sessions)。 # **一个必填但不校验取值的字段, 等于没设防** —— 它给了"已声明口径"的假象, # 而 C12 存在的全部意义就是防"同一事实两个数"。 'count_basis': { 'work_orders': ('工单条数', 'work orders'), 'work_sessions': ('作业次数', 'work sessions'), 'turbines': ('机组台数', 'turbines'), 'events': ('事件数', 'events'), 'records': ('记录条数', 'records'), 'samples': ('样本数', 'samples'), 'documents': ('文档份数', 'documents'), }, } # ── 历史中文值 → 码 (只读兼容; 存量对象不改写) ──────────────────────── _ALIAS = {(f, zh): c for f, m in LABELS.items() for c, (zh, _en) in m.items()} _ALIAS.update({ ('review_tier', 'lite'): 'lite', # 原枚举里本就是英文的两个 ('review_tier', 'AdvCI-PR'): 'advci_pr', ('grade', 'INSUFFICIENT'): 'insufficient', ('grade', '准定论'): 'early_warning', ('grade', '预警'): 'early_warning', # 历史截短写法 (旧 ingest 存进库的是 '处理器', 判据实际输出 'STIC处理器') ('part', '处理器'): 'stic_processor', }) CODES = {f: set(m) for f, m in LABELS.items()} def normalize(field, value): """历史中文值 → 码; 已是码则原样返回; 无法映射返回 None (响亮, 不静默塞回原值)。""" if value is None: return None if field in CODES and value in CODES[field]: return value hit = _ALIAS.get((field, value)) if hit: return hit # ★2026-08-31 增三档归一。存量值实测有三类"其实能映射但差一点"的: # ① 尾随/前导空格 ('IGBT ' ×10) ② 大小写 ('stic'/'STIC' ×6) # ③ 码前缀简写 ('L1' → 'L1_read_only' ×26, 可逆性三档) # 这三类不是新语义, 是**同一个值的书写变体**; 不归一就白白落进"无法映射", # 让英文投影退回中文。归一只做这三档, 不做模糊匹配 —— 模糊匹配会静默塞错码。 v = str(value).strip() if v != value: hit = normalize(field, v) if hit: return hit if field in CODES: low = v.lower() for c in CODES[field]: if c.lower() == low: return c if '_' in c and c.split('_', 1)[0].lower() == low: # L1 → L1_read_only return c for c, (zh, _en) in LABELS.get(field, {}).items(): if zh.strip().lower() == low: return c return None def label(field, code, lang='zh'): """码 → 显示文本。**查不到不回落到另一语言** —— 回落会让缺口永远不被发现, 正是英文版残留三轮没被逮住的机制 (docs/本体数据标准.md §4)。""" m = LABELS.get(field, {}).get(code) if not m: return f'[{field}:{code}]' return m[0] if lang == 'zh' else m[1] def text(props, key, lang='zh'): """文本层取值: 优先 `key_zh`/`key_en`, 回落裸 `key`(存量对象)。 英文侧取不到 `_en` 时返回 `[translation pending]` 而非中文原文 —— 同上, 缺口必须可见。""" v = props.get(f'{key}_{lang}') if v: return v bare = props.get(key) if lang == 'zh': return bare return '[translation pending]' if bare else None