codes.py 14 KB

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