entry-server.js 230 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124
  1. import { defineComponent, ref, onMounted, unref, useSSRContext, onUnmounted, computed, watch, onBeforeUnmount, mergeProps, reactive, createSSRApp } from "vue";
  2. import { ssrRenderAttrs, ssrInterpolate, ssrRenderStyle, ssrRenderList, ssrRenderAttr, ssrIncludeBooleanAttr, ssrRenderClass, ssrRenderComponent, renderToString } from "vue/server-renderer";
  3. const ZH = {
  4. "app.brand": "观澜",
  5. "app.build": "最新完成构建",
  6. "app.lang_en": "英文",
  7. "app.lang_zh": "中文",
  8. "app.subtitle": "海上风电场",
  9. "app.title": "观澜 · 风电数据分析与决策支持",
  10. "ask.common_q": "{cat} · 常用问题 (点即问, 或下方自由输入)",
  11. "ask.done_meta": "模型 {m} · 用时 {s} 秒 · {n} 步",
  12. "ask.elapsed": "{s} 秒",
  13. "ask.empty": "问题为空",
  14. "ask.en_qa_failed": "英文质检未通过: 已拦截这次模型回答, 请重新提问。",
  15. "ask.error": "出错: {e}",
  16. "ask.go": "提问",
  17. "ask.grounding": "答案句句带 [对象id] 溯源 + 数据回抓校验核验; 判级只转述本体原文带审级, 模型零判级权。",
  18. "ask.idle": "空闲 — 点常用问题或输入后提问。串行一次一问。",
  19. "ask.local_down": "本机模型未启动 (Ollama 127.0.0.1:11434 未响应); 离线版不转云",
  20. "ask.model": "模型",
  21. "ask.no_models": "无可用模型",
  22. "ask.offline": "离线版: 模型问答需连接系统。",
  23. "ask.partial": "初答 · 27B 复核中",
  24. "ask.placeholder": "自由提问 ({cat})",
  25. "ask.question": "问题: {q}",
  26. "ask.refined": "已复核 (默认档 {m} 未过成文校验, 由更强模型重答)",
  27. "ask.refs.none": "无匹配契约条目 (契约 {sha}): 本回答未被结构化事实契约背书, 按模型输出对待",
  28. "ask.refs.title": "契约引用 ({n} 条, 契约 {sha}) — 按问题/回答匹配的可交付结论, 引文来自 qa_refs",
  29. "ask.review": "交叉审核 · {m}",
  30. "ask.review_err": "审核未完成: {e}",
  31. "ask.review_note": "由另一模型基于同一份证据复核上述答案。溯源核验仅校验数字与机组号是否越界, 本项校验结论是否有证据支撑。",
  32. "ask.running": "推理中…",
  33. "ask.verify_bad": "溯源核验未全过 (引用 {n})",
  34. "ask.verify_ok": "溯源核验 {n}/{n} 全真",
  35. "ask.xrev": "交叉审核",
  36. "ask.xrev_note": "(DeepSeek 14B 互审, +约 1 分钟)",
  37. "ask.xrev_title": "由另一模型基于同一份证据复核; 同一模型自审无法构成独立校验",
  38. "asst.alarm_type": "告警类型 {t}",
  39. "asst.cause_counts": "手册 {r} 次 · 工单 {w} 次 · 涉码 {c}",
  40. "asst.causes": "← 可能原因 (手册排查方向 + 工单实证)",
  41. "asst.check": "查什么",
  42. "asst.code_placeholder": "报警码, 如 3225 / 3130 / 13902",
  43. "asst.common": "常见",
  44. "asst.corrective": "抢修",
  45. "asst.corrective_flow": "报警码 → 失效模式 → 手册分步处置 → 历史工单",
  46. "asst.drawings": "对应图纸",
  47. "asst.failmodes": "指向的失效模式",
  48. "asst.faulttree": "故障树",
  49. "asst.faulttree_flow": "按证据量排序的系统失效模式 + 部件级成因",
  50. "asst.fm_counts": "该类本场报警 {a} 条 / 工单 {w} 条",
  51. "asst.head": "检修助手 — 确定性本体查询",
  52. "asst.head_note": "结果逐字取自手册与工单, 不经模型",
  53. "asst.identity": "系统 {sys} · 本场 {n} 条",
  54. "asst.interval": "周期 {i}",
  55. "asst.known_tags": "该系统手册出现过的位号 (对图纸时参考):",
  56. "asst.no_causes": "(手册无该类排查条目 — 原因层待补)",
  57. "asst.no_name": "(码表未收录)",
  58. "asst.no_steps": "本码无手册处置条目 (手册覆盖本场 161/322 码) — 需查图纸或联系厂商支持。",
  59. "asst.node": "节点",
  60. "asst.nodes": "应检节点 · {c}",
  61. "asst.nodes_note": "手册此步未给位号 → 按回路构成展开, 逐点查",
  62. "asst.prev_empty": "(该系统无匹配的年度维护条目 — 维护作业指导书按位置代号编排, 未必逐系统对齐)",
  63. "asst.prev_fm": "失效模式 (按本场报警量)",
  64. "asst.prev_head": "{sys} 预防链 · 失效模式 {f} 类 · 预防性任务 {n} 项",
  65. "asst.prev_tasks": "年度任务",
  66. "asst.preventive": "预防性维护",
  67. "asst.preventive_flow": "系统 → 失效模式 → 年度任务 → 作业指导步骤",
  68. "asst.qty": "量: {q}",
  69. "asst.query_failed": "查询失败: {e}",
  70. "asst.querying": "查询中…",
  71. "asst.spec_name": "名称",
  72. "asst.spec_no": "参数号",
  73. "asst.spec_range": "允许范围",
  74. "asst.spec_val": "整定值",
  75. "asst.specs": "判定规范 (实机参数表)",
  76. "asst.step_n": "第 {n} 步",
  77. "asst.steps": "现场处置 {n} 步",
  78. "asst.steps_note": "节点部件 = 手册原文元件位号 (确定); 判定规范 = 实机参数表按物理量匹配的候选判据, 非厂商绑定 — 现场以铭牌与图纸复核。",
  79. "asst.tags": "节点部件",
  80. "asst.tree_codes": "征兆码 {c}",
  81. "asst.tree_counts": "本场报警 {a} 条 · 工单 {w} 条",
  82. "asst.tree_empty": "该系统无失效模式记录",
  83. "asst.tree_head": "{sys} 故障树 · {n} 个失效模式",
  84. "asst.tree_note": "一层 = 系统 × 失效类 (归纳自本场码表与工单) · 二层 = 手册排查方向归一 + 工单原因计数; 条长按实证量 (报警条数 + 工单 × 10), 因果来自手册与工单原文非推断, 树深两层不外推。",
  85. "asst.unassigned": "未归属",
  86. "asst.wi_steps": "WI {n} 步",
  87. "asst.wo_action": "处置",
  88. "asst.wo_fault": "故障",
  89. "asst.wo_head": "现场实际怎么修的 ({sys} 历史工单 {n} 条, 台账 2020~2024)",
  90. "asst.wo_note": '工单时间窗止 2024-11-21; 2025–26 现场未提供 ⇒ "0 条" 是记录缺失非未修过。',
  91. "attr.变桨与液压": "变桨与液压",
  92. "attr.塔架/结构": "塔架/结构",
  93. "attr.无记录停机": "无记录停机",
  94. "attr.未归类": "未归类",
  95. "attr.电网/供电(外部)": "电网/供电(外部)",
  96. "attr.轮毂/叶片": "轮毂/叶片",
  97. "attr.运维操作": "运维操作",
  98. "attr.风况(外部)": "风况(外部)",
  99. "cat.equip": "设备类",
  100. "cat.equip.hint": "设备状态 / 机制 / 判级 — 从机组档案、对象查询与机制检索取证",
  101. "cat.equip.q1": "29号机组现在什么状态、为什么、该怎么办?",
  102. "cat.equip.q2": "全场哪些机组有报警级或定论级判级?逐台列出并注明审级",
  103. "cat.equip.q3": "失效机制库里有哪些机制族?各涉及哪些机组、状态如何?",
  104. "cat.loss": "损失分析",
  105. "cat.loss.hint": "当前 3 个月损失 / 未来损失 — ont_loss / ont_scenario 取证, 金额带电价区间统计方法",
  106. "cat.loss.q1": "近3个月全场损失结构如何?分状态和分机组说, 电量与金额都给",
  107. "cat.loss.q2": "29号如果拖到主轴承失效, 未来损失量级是多少?与现在修对比",
  108. "cat.loss.q3": "限电常态化对检修机会成本有什么影响?",
  109. "cat.risk": "安全风险预测",
  110. "cat.risk.hint": "风险敞口 / 劣化趋势 / 盲区 — ont_risk / ont_mechanism_search 取证, 未列 ≠ 正常",
  111. "cat.risk.q1": "当前哪些机组风险在上升需要重点盯?联动监视读数如何?",
  112. "cat.risk.q2": '监测有哪些盲区?哪些"没报警"不等于正常?',
  113. "cat.risk.q3": "29号轴承正在承受金属接触, 风险敞口怎么评估?",
  114. "cat.sched": "检修排程决策",
  115. "cat.sched.hint": "排期沙盘 / 天气窗 / 验收树 — ont_scenario / ont_get 取证",
  116. "cat.sched.q1": "29号修泵什么时候做最合适?对比各情景的损失与风险",
  117. "cat.sched.q2": "未来几个月哪些月份适合安排CTV检修和吊装作业?依据历史可作业窗",
  118. "cat.sched.q3": "29号修好之后按什么判据验收?三分支分别是什么条件?",
  119. "ch.attrib.none": "无记录 {h}h ({p}%)",
  120. "ch.attrib.ok": "已归因到部件 {h}h ({p}%)",
  121. "ch.attrib.uncls": "未归类 {h}h",
  122. "ch.beta.median": "全场中位 {v}°",
  123. "ch.blame.d1": "限电与调度令, 不进设备责任",
  124. "ch.blame.d2": "故障与检修停机, 唯一进设备考核",
  125. "ch.blame.d3": "正常发电欠发与低风待机",
  126. "ch.blame.n1": "交易与调度侧",
  127. "ch.blame.n2": "设备侧",
  128. "ch.blame.n3": "运行侧",
  129. "ch.blame.title": "损失 {tot} MWh 按责任归属",
  130. "ch.cap.legend": "圆内数字 = 台数, 圆面积 ∝ 台数",
  131. "ch.cap.median": "中位 {v}",
  132. "ch.cap.p": "满发封顶",
  133. "ch.cap.w": "转速封顶",
  134. "ch.ml.legend": "— {s}",
  135. "ch.pareto.cum80": "累计 80%",
  136. "ch.pareto.cumtip": "{k} · 累计 {p}%",
  137. "ch.pareto.key": "关键少数: 前 {k} 项占 80%",
  138. "ch.pareto.none": "时间窗内无数据",
  139. "ch.pareto.top3": "前三: ",
  140. "ch.pareto.total": "共 {n} 项, 合计 {tot}{unit}",
  141. "ch.pareto.xlab": "报警码 (悬停看全名)",
  142. "ch.quad.few": "时间窗内码数不足, 不作四象限",
  143. "ch.quad.q1": "低频长时 · 硬故障",
  144. "ch.quad.q1s": "备件与检修窗",
  145. "ch.quad.q2": "高频长时 · 重点治理",
  146. "ch.quad.q2s": "优先派工",
  147. "ch.quad.q3": "低频短时 · 本底",
  148. "ch.quad.q3s": "不单独处置",
  149. "ch.quad.q4": "高频短时 · 重复触发",
  150. "ch.quad.q4s": "调整参数或屏蔽, 非硬件故障",
  151. "ch.quad.tip": "{n} 条 · 单次中位 {med}s · 累计 {h}h · {nt} 台",
  152. "ch.quad.xlab": "发生条数 (对数)",
  153. "ch.quad.ylab": "单次中位时长 (对数)",
  154. "ch.ram.area": "圆面积 = 累计停机时长",
  155. "ch.ram.equip": "设备类",
  156. "ch.ram.ext": "外部类",
  157. "ch.ram.few": "部件数不足, 不作象限",
  158. "ch.ram.median": "虚线 = 本场中位分界",
  159. "ch.ram.q1": "常坏且难修",
  160. "ch.ram.q1s": "最高优先级",
  161. "ch.ram.q2": "罕发难修",
  162. "ch.ram.q2s": "备件与作业窗",
  163. "ch.ram.q3": "频发易修",
  164. "ch.ram.q3s": "查根因, 换件无用",
  165. "ch.ram.q4": "少发好修",
  166. "ch.ram.q4s": "维持",
  167. "ch.ram.tip": "{p}: 平均无故障间隔(MTBF){mtbf}h · 单次停机时长(MDT){mdt}h · 停机 {h}h · {n}起 · {u}台",
  168. "ch.ram.tip_ext": " · 外部类不计设备可靠性",
  169. "ch.ram.xlab": "平均无故障间隔(MTBF,h,对数)→ 越右越可靠",
  170. "ch.ram.ylab": "单次停机时长(MDT,h)→ 越上越难修",
  171. "ch.rate.days": "仅{d}天",
  172. "ch.rate.month": "{m}月",
  173. "ch.rate.n": "{n}条",
  174. "ch.rate.tip": "{m}: {v} 条/日 (总 {n} 条, 数据覆盖 {cov}%)",
  175. "ch.rate.ylab": "条/日",
  176. "ch.ruler.norm": "正态期望 3.7σ",
  177. "ch.ruler.row": "{r}σ σ = {s}",
  178. "ch.wf.act": "实际上网",
  179. "ch.wf.theo": "理论可发",
  180. "ch.wf.tip": "{nm}: 损失 {loss} MWh, 占理论 {pct}%, 时长 {h}h",
  181. "chain.gb": "齿轮箱",
  182. "chain.gde": "发电机DE",
  183. "chain.gen": "发电机",
  184. "chain.gnde": "发电机NDE",
  185. "chain.hss": "高速",
  186. "chain.ims": "中间",
  187. "chain.mba": "主轴承后",
  188. "chain.mbf": "主轴承前",
  189. "chain.ps": "行星",
  190. "cmp.alarm_n": "报警 {a}",
  191. "cmp.alarm_units": "报警机组",
  192. "cmp.basis": "依据",
  193. "cmp.by_system": "按系统查看",
  194. "cmp.cards_note": "点系统卡 → 该系统全场机组与依据; 九系统 = 交付九分册一一对应; 叶片与叶根 / 塔架与基础无 SCADA 轴 (分册蓝本取 CMS / 巡检)",
  195. "cmp.counts": "报警 {a} · 不可判 {u} · 良好 {g} · 优秀 {e}",
  196. "cmp.cross_eyebrow": "多系统交叉机组",
  197. "cmp.detail": "详情",
  198. "cmp.detail_head": "{sys} · 全场 {n} 台",
  199. "cmp.external": "外部",
  200. "cmp.fleet_n": " · 全场 {n} 台",
  201. "cmp.fusion_moved": "振动线结论与四源融合已移至独立板块 →",
  202. "cmp.fusion_n": "({n} 台次收录)",
  203. "cmp.good_units": "良好 {n} 台 (轻微偏离, 周期观察 — 点开看逐台依据)",
  204. "cmp.grading_pending": "正在按所选时间窗重算判级…",
  205. "cmp.grading_pending_why": "时间窗 {span}:变桨/偏航/蓄能/温度四个判级面正按该时间窗重算(首次约 25 秒),下面的系统卡暂时还是上一份统计方法 —— 算完会自动刷新,不会静默混用。",
  206. "cmp.hit_sys": "命中系统",
  207. "cmp.n_sys": "系统数",
  208. "cmp.n_units": "({n} 台)",
  209. "cmp.none": "本系统全场无非常态。",
  210. "cmp.problem_page": "该问题页 →",
  211. "cmp.rel_ev": "事件",
  212. "cmp.rel_eyebrow": "部件可靠性 · 时间窗 {win}",
  213. "cmp.rel_fm": "典型失效模式 (按时长)",
  214. "cmp.rel_h": "停机时长",
  215. "cmp.rel_head": "部件停机贡献与可靠性",
  216. "cmp.rel_mdt": "单次停机时长(MDT,h)",
  217. "cmp.rel_meta": "总停机 {t}h = 设备类 {a} + 外部类 {b}",
  218. "cmp.rel_mtbo": "平均停机间隔(MTBO,h)",
  219. "cmp.rel_note": '统计方法 ≥1h 停机段, 不含调度令 — 与"电量算账"里的停机时长{h} 不同, 差额是短于 1h 的停机段 (本页按段做可靠性指标须成段, 能量账按逐条工况态算电量): 两个数都对, 不可互相相减。',
  220. "cmp.rel_part": "部件",
  221. "cmp.rel_units": "影响机组",
  222. "cmp.unk_n": " · 不可判 {u}",
  223. "cmp.unk_units": "不可判 (监测/数据不足, ≠ 正常)",
  224. "cmp.watch_head": "关注机组 (≥2 系统交叉, {n} 台; 点机组号 → 该台独立页)",
  225. "dec.classic": "维护标准 / 维护框架 / 数据质量三块尚未迁入新版 —",
  226. "dec.head": "检修决策 · 本体问答 (推理无界, 结论须过校验)",
  227. "en.balance": "电量平衡",
  228. "en.blame": "损失责任分账",
  229. "en.blame_note": '最大一笔是限电不是故障。进设备考核的只有"故障与检修停机" {n} MWh; 限电属交易与调度侧, 2026 起交易策略主导, 少发未必是弃风。',
  230. "en.cap_fail": "不通过",
  231. "en.cap_note": "能量守恒核 · 理论 {op} 全额定上限 {cap}",
  232. "en.cap_pass": "通过",
  233. "en.kpi_act": "实际上网 · 等效满发 {h} h",
  234. "en.kpi_loss": "损失率 · {v} MWh",
  235. "en.kpi_theo": "理论可发 (实际+损失)",
  236. "en.loss_mwh": "损失 MWh",
  237. "en.share": "时长占比",
  238. "en.state": "工况态",
  239. "en.states": "工况态明细",
  240. "en.states_note": "控制参数一致性与曲线欠发筛查在固定干净窗判, 已迁入「发电性能」页 — 本页只出当期电量账。",
  241. "en.win": "窗 {win}",
  242. "fig.fleet_median": "全场中位",
  243. "fig.point": "测点",
  244. "fig.quartile": "四分位",
  245. "fig.this_unit": "本台",
  246. "fig.trend": "趋势",
  247. "fig.vibbar_note": "条为本台与全场中位之比; 色按 z (稳健离群度): 绿 <2 · 橙 2~3 · 红 ≥3",
  248. "fig.x_median": "×中位",
  249. "flt.alarm_face": "报警面",
  250. "flt.alarm_meta": "{n} 条 · {c} 个码 · 时间窗 {win}",
  251. "flt.attrib_note": '归因覆盖: "无记录停机" {h}h 是全场最大单项, 因不给 MTBF 而在上图中不出现 — 它不是"没有故障", 是"没归上因"。统计方法为段起点 ±30min 首发码。缺口成因待定: 此前写的"98% 的段在前 72h 内有报警 ⇒ 长停远窗"不成立 — 前 72h 有报警不等于该报警与本段停机有因果 (双模审 2026-08-28 逮)。要定成因须改判"段起止是否落在某条报警的持续区间内", 未做之前只报缺口不报原因。',
  252. "flt.block_err": "本块未算出 ({k}): {e}",
  253. "flt.eyebrow": "故障统计与可靠性",
  254. "flt.pareto_d": "帕累托 · 按持续时长",
  255. "flt.pareto_d_note": "时长 = 闩锁统计方法 (复位 − 激活), 只作排序参考不作停机时长",
  256. "flt.pareto_n": "帕累托 · 按发生条数",
  257. "flt.pareto_n_note": "条数 = 发生频次, 不反映影响程度 — 高频短时报警码 (信号抖动或重复触发) 排序靠前",
  258. "flt.per_t": "台级分布 · 报警条数 top{n}",
  259. "flt.per_t_note": "全场合计 {n} 条 · 单台占比高须先核查是否为单一报警码重复触发",
  260. "flt.per_t_share": "占全场",
  261. "flt.quad": "故障模式四象限 · 条数 × 单次时长",
  262. "flt.quad_note": "时间窗内共 {n} 个码, 图上为条数前 {m} 个。",
  263. "flt.quad_note2": "点面积表示影响机组数, 大点为批次共性、小点为单台; 竖分界取条数中位, 横分界取 10min (一个 SCADA 采样格)。右下角的码不要按条数排优先级 — 属重复触发, 非硬件故障。上面两张帕累托对照看即此图的两条投影。",
  264. "flt.quad_partial": "窗含不满月 {m} (覆盖 {c}%), 条数为原始累计未按天归一。",
  265. "flt.ram": "可靠性 × 可维修性 · 部件象限",
  266. "flt.ram_note": '按停机时长排序的表看不出这层结构: 同样 500h 的停机, "频发易修"要查根因、"罕发难修"要备件, 处置相反。设备类停机 {a}h / 外部类 {b}h (外部 = 电网·风况·运维操作, 不计设备可靠性)。海上机组 MDT 普遍偏高是交通窗口决定的, 不等于维修本身慢 — 跨场比 MDT 须同为海上。',
  267. "flt.rate": "月度故障率",
  268. "flt.rate_example": "实测: {m} 总数最低但日均 {v} 条/日 = 全时间窗前列。",
  269. "flt.rate_full": "各月覆盖完整。",
  270. "flt.rate_note": "柱高 = 日均条数, 不是月总数",
  271. "flt.rate_partial": "本时间窗 {m} 数据不满月 (灰虚线柱), 直接比月总数会读反。",
  272. "flt.seasonal": "故障季节性 · top 报警族 × 月",
  273. "flt.seasonal_note": "全程 19 个月 (跨 2 冬 1 夏); 未做 χ² 显著性检验前为【参考级】, 只看形态不作季节归因",
  274. "flt.seasonal_unit": "条/月",
  275. "flt.seasonal_x": "月",
  276. "flt.stop_face": "停机面",
  277. "flt.stop_h": "停机时长",
  278. "flt.stop_meta": "{h}h · {n} 起 · 统计方法 ≥1h 段, 不含调度令",
  279. "flt.stop_n": "次数",
  280. "flt.stop_pareto": "停机归因帕累托",
  281. "flt.stop_pareto_note": "按停机时长; 事件次数见悬停",
  282. "flt.stop_units": "台级停机 top{n}",
  283. "flt.stop_units_note": "停机时长高 ≠ 设备差 — 须先看归因里外部类占比 (调度令已剔除)",
  284. "fus.alarm_n": "{n} 条",
  285. "fus.alarm_rank": " · 第 {r}/{t}",
  286. "fus.burst": "⚠ {p}% 是码 {c} 抖动",
  287. "fus.burst_tip": "⚠ 单码 {c} 占 {p}%, 中位 {m} 秒/次 — 条数被抖动灌满",
  288. "fus.clear": "× 清除",
  289. "fus.cms_act": "行动",
  290. "fus.cms_action": "需行动机组 (报警 / 不可判)",
  291. "fus.cms_basis": "统计方法说明",
  292. "fus.cms_basis_note": "本表 = windcms 状态管理判级 (含标量趋势与数据可判性); 融合矩阵 / 逐台结论 = 振动线确诊链转录 (候选以上)。两统计方法差异台: 仅本表列 {a} (多为标量发作 / 数据不可判, 未到确诊链); 仅确诊链列 {b} (预警级在察, 状态管理未升)。",
  293. "fus.cms_chain": "部件状态链",
  294. "fus.cms_chain_note": "(主承前 · 主承后 · 齿轮箱 · 发电机; 悬停看逐件等级)",
  295. "fus.cms_evidence": "证据强度",
  296. "fus.cms_focus": "状态焦点",
  297. "fus.cms_focus_note": "(传动链: 主承前 · 主承后 · 齿轮箱 · 发电机两端)",
  298. "fus.cms_grades": "38 台部件等级一览",
  299. "fus.cms_head": "振动状态评估 (windcms 报告 {d} 结果转录)",
  300. "fus.cms_link": "谱图 · 波形 · 判据 · 工作台等分析功能在独立 windcms 系统 →",
  301. "fus.cms_loading": "载入振动评估结果…",
  302. "fus.cms_note": "状态管理判级, 不代表确诊数量; 判据/谱线/计算过程不在本页 — 深潜走底部独立系统。",
  303. "fus.cms_open": "打开 windcms ↗",
  304. "fus.cms_overall": "综合",
  305. "fus.cms_static": "(未启动时用静态版)",
  306. "fus.cms_window": '数据窗 {r} —— 本时间窗**实算**覆盖区间 (窗名只是起始日代号); 页面上的图谱与"末窗"状态取自时间窗内最新记录, 故多显示末期日期。',
  307. "fus.col.alarm": "报警",
  308. "fus.col.claim": "证据时间窗",
  309. "fus.col.claim_axis": "26-01 → 09",
  310. "fus.col.display_only": " · 只展示",
  311. "fus.col.fixed": " · 不随本时间窗",
  312. "fus.col.lub": "润滑柱塞",
  313. "fus.col.oil": "油样",
  314. "fus.col.part": "部件",
  315. "fus.col.signal": "振动信号",
  316. "fus.col.state_concl": "设备状态 / 振动结论",
  317. "fus.col.temp": "温度",
  318. "fus.col.tempaxis": "温度轴",
  319. "fus.col.tempaxis_note": "±6 K",
  320. "fus.col.vib": "振动",
  321. "fus.col.wo": "工单",
  322. "fus.ev_code": "码",
  323. "fus.ev_dur": "累计时长 h",
  324. "fus.ev_head": "{c} — 报警 {an} 条 / {au} 台 · 工单 {wn} 条 / {wu} 台 (时间窗内)",
  325. "fus.ev_min": "时长 min",
  326. "fus.ev_no_alarm": "时间窗内无该部件报警",
  327. "fus.ev_no_wo": "时间窗内无该部件工单 (工单数据止 2024-11, 2026 类窗必空 = 数据边界)",
  328. "fus.ev_recent": "最近 {n} 条明细",
  329. "fus.ev_text": "报警内容",
  330. "fus.ev_time": "时间",
  331. "fus.ev_top": "台分布 top: ",
  332. "fus.ev_units": "台数",
  333. "fus.ev_win": "时间窗 {win} · 顶部选择器可换",
  334. "fus.ev_wo_act": "处理",
  335. "fus.ev_wo_cause": "原因",
  336. "fus.ev_wo_name": "故障名称",
  337. "fus.eval_head": "评价体系 (规范化定义 — 两轴并存, 不可互相替代)",
  338. "fus.eval_meaning": "含义",
  339. "fus.eval_note": '设备状态答"这台设备现在怎么样" (危险 > 报警 > 良好 > 优秀 > 不可判); 证据状态答"这个判断有多可信" (定论 → INSUFFICIENT)。设备状态一律取自振动线的判级栏这一个来源。本页判级为转录, 不产新判级; 标注为参照同型机组/频谱信号级 (未定位到具体零件)/测量链/未对应到具体部件的行不作部件级结论消费; claim_window = 该结论的证据时间窗, 窗外状态该结论不背书。',
  340. "fus.eval_state": "设备状态 (业主统计方法)",
  341. "fus.eval_tier": "证据状态 (可信度)",
  342. "fus.eyebrow": "振动与 SCADA 证据视图 · 展示振动结论、SCADA 指标、报警与工单线索",
  343. "fus.fam_tip": "{n} 条 · 全场最多 {m} 条",
  344. "fus.legend_note": '两轴不可互相替代: "候选"说的是可信度不是设备好坏; "良好"说的是设备状态不是证据强度。设备状态只取自振动线的判级栏这一个来源。',
  345. "fus.legend_state": "格子颜色 = 设备状态 (业主统计方法, 与 windcms 报告同一套):",
  346. "fus.legend_tier": "徽章 = 证据状态 (可信度, windcms 九枚举):",
  347. "fus.loop_ba": "换前 → 换后 (同标尺)",
  348. "fus.loop_bound": "数据边界: {b}",
  349. "fus.loop_closed": "已闭环 · 换件后恢复 ({n} 例)",
  350. "fus.loop_date": "换件日",
  351. "fus.loop_el": "早期 → 近期 (同标尺)",
  352. "fus.loop_head": "维修后验证案例 (检出 → 换件 → 恢复)",
  353. "fus.loop_item": "事项",
  354. "fus.loop_neg": "负面对照: 工单后持平 {n} 例 (换件 ≠ 必然回落, 说明回落非巧合)。",
  355. "fus.loop_none": "暂无端到端闭环证据:现场提交的振动数据包里没有「报警→检修→复测」的成对记录",
  356. "fus.loop_note": "有换件记录且同一统计方法指标回落, 可作为检测链已闭合的案例; 指标仍在上升但缺少可匹配工单的机组, 需现场或台账确认后再关闭。",
  357. "fus.loop_ratio": "恢复比",
  358. "fus.loop_rising": "未闭环 · 指标上升待现场/工单确认 ({n} 台)",
  359. "fus.loop_state": "状态",
  360. "fus.loop_verdict": "判",
  361. "fus.loop_x": "倍比",
  362. "fus.lub_now": "润滑柱塞现状",
  363. "fus.lv.bad": "定论/报警级",
  364. "fus.lv.note": "候选/监视/观察级",
  365. "fus.lv.ok": "销案/无新证",
  366. "fus.lv.warn": "预警级",
  367. "fus.m.bad": "异常严重, 建议尽快停机检查",
  368. "fus.m.bad_e": "证据: 已定案",
  369. "fus.m.ex": "未见异常, 保持常规监视",
  370. "fus.m.ex_e": "证据: 仅记录 / 已取消",
  371. "fus.m.ok": "轻微偏离, 记录观察",
  372. "fus.m.ok_e": "证据: 待现场核实 / 已记为基线下窗观察",
  373. "fus.m.unk": "数据不足或监测中断 — 不等于正常",
  374. "fus.m.unk_e": "证据: 数据不足",
  375. "fus.m.warn": "存在需现场核实或处置的异常",
  376. "fus.m.warn_e": "证据: 基本确认 / 新出现待核实",
  377. "fus.matrix_foot": '润滑柱塞各台设定本就不同 (全场单次循环 22~961 秒), 所以此列只展示不判级, 只有已定案的机制链才标色; 油液多数台末次取样距今超 12 个月, 此列看的是"样够不够新", 不代表当前状态。',
  378. "fus.matrix_head": "全场融合矩阵 (38 台 × 6 个证据面)",
  379. "fus.matrix_note1": '各列取数窗口不同 (见列头) — 不同窗的两格同时发红不等于"同时发生", 不可当共时印证。',
  380. "fus.matrix_note2": '每格右侧曲线 = 逐月记忆轨迹 ({n} 个月, {m0} 起; 实心点 = 现窗) — 现窗快照会丢形态, 轨迹才看得见"塌→回升→翻转"类机制签名。',
  381. "fus.no_alarm": "时间窗内无报警",
  382. "fus.none_word": "无",
  383. "fus.oil_fresh": "近共时",
  384. "fus.oil_none": "无样",
  385. "fus.oil_stale": "stale > 12 个月",
  386. "fus.rule1": "① 同一失效机制的多个观测面 ≠ 多源印证 — 融合禁按源数加权 (29# 柱塞/温度/振动稳态/振动瞬态 = 四个观测面一条机制链)。",
  387. "fus.rule2": '② "没报警" ≠ "正常" — 在已知盲区上, 缺报警只说明监测面无效, 不说明设备无恙。',
  388. "fus.rules_head": "两条判读纪律 (随板块常显)",
  389. "fus.short.nodata": "数据不足",
  390. "fus.short.normal": "正常",
  391. "fus.short.nosample": "未取样",
  392. "fus.short.stale": "样龄过期",
  393. "fus.short.uneval": "未评估",
  394. "fus.short.unlisted": "未收录",
  395. "fus.sub.cms": "振动分析",
  396. "fus.sub.events": "报警工单",
  397. "fus.sub.loop": "端到端闭环",
  398. "fus.sub.matrix": "融合矩阵",
  399. "fus.sub.units": "逐台情况",
  400. "fus.temp_now": "温度轴现状",
  401. "fus.tip_state": " — 设备状态 {s}",
  402. "fus.tip_tier": " / 证据状态 {e}",
  403. "fus.unavailable": "融合面数据不可用: {e}",
  404. "fus.units_filter": " · 筛选: {l}",
  405. "fus.units_head": '逐台情况 (全场 {u} 台 / {n} 台次{f}; 深入谱图/波形 → "振动分析")',
  406. "fus.unlisted_head": "未列机组 {n} 台 (振动线未收录 — 未列 ≠ 正常)",
  407. "fus.unlisted_link": "未列 {n} 台",
  408. "fus.win_n": "{n} 个窗段",
  409. "fus.win_none": "未标窗",
  410. "fus.wo_n": "{n} 单",
  411. "fus.wo_none": "时间窗内无传动链工单",
  412. "gen.beta": "桨距调度同档差",
  413. "gen.beta_note": "同一功率档下的桨角与全场中位之差。{units} 偏离达中位的 {k} 倍 → {v}",
  414. "gen.cands": "曲线欠发筛查",
  415. "gen.cands_meta": "{note} · σ = {s}%",
  416. "gen.cap": "封顶参数落位",
  417. "gen.cap_note": "台数落在几个离散位置上, 不是连成一片 — 这是配置/版本差异的形状; 磨损或工况引起的会是连续散布。两轴各自成组且分组不同台 ⇒ 参数级差异, 成因须配置台账。",
  418. "gen.ctrl": "控制参数一致性",
  419. "gen.ctrl_win": "窗 {win}",
  420. "gen.eyebrow": "特性曲线与控制参数",
  421. "gen.figs": "七个镜头 · 逐档中位与同型机群残差",
  422. "gen.fixed_win": "随上方时间窗 · 按时间窗重算",
  423. "gen.loading": "正在计算七个镜头 (剥限电 · 逐档中位 · 同型机群残差)…",
  424. "gen.no_cands": "无候选",
  425. "gen.recomputing": "正在按所选时间窗重算七镜头…",
  426. "gen.recomputing_short": "正在按窗重算…",
  427. "gen.ruler": "尺子巡检 · 极差 ÷ σ",
  428. "gen.ruler_note": "判据自身的分辨力。38 台正态抽样的极差期望约 3.7σ, 明显超出即分布非单峰 — 与左图的分组互为印证; 落在期望附近的轴 (额定桨角) 则是连续分布。",
  429. "gen.win_range": "时间窗 {span}",
  430. "gen.win_why": '七镜头与月度时序都按所选时间窗重算(含自定义起止日期)。提示:2025-07 ~ 2025-12 是"限电前干净判别窗"——2026 起交易策略主导,曲线含策略性少发,作设备判据时须注意;选到含 2026 的窗时该影响存在但不再遮掩。',
  431. "kpi.alarm_units": "九系统有报警的台数 / 全场",
  432. "kpi.alarm_units.hint": "统计方法: 九系统 (叶片与叶根/变桨/偏航/齿轮箱/发电机/主轴承/变流器/塔架与基础/主控与传感网) 中任一被判「报警」即计一台。与下方「全场状态」格的统计方法不同 —— 那里按每台 振动/温度/润滑/油液/报警/工单 六列取最严一级, 所以两处数字可以不同, 不是矛盾。",
  433. "kpi.avail": "时间可用率 (不含调度令) · {win}",
  434. "kpi.curtail": "限电·绑定 电量损失",
  435. "kpi.downtime": "停机时长 · {n} 起",
  436. "kpi.loss": "停机电量损失 · {win}",
  437. "kpi.loss_dispatch": "另调度令 {n}",
  438. "kpi.mtbo": "平均停机间隔(MTBO;非行业 MTBF)",
  439. "loss.curtail_bound": "限电 (命令与实际功率对得上)",
  440. "loss.curtail_unbound": "限电命令期 · 功率未被压制",
  441. "loss.dispatch": "调度令停机",
  442. "loss.lowwind": "低风待机",
  443. "loss.normal": "正常发电时的欠发",
  444. "loss.stop": "故障与检修停机",
  445. "mdl.cloud": "云端",
  446. "mdl.deepseek-chat.nm": "DeepSeek",
  447. "mdl.deepseek-chat.t": "约 3 到 10 秒",
  448. "mdl.deepseek-r1:14b.nm": "本机 DeepSeek 14B · 审核档",
  449. "mdl.deepseek-r1:14b.t": "约 1 到 3 分钟",
  450. "mdl.deepseek-r1:32b.nm": "本机 DeepSeek 32B",
  451. "mdl.deepseek-r1:32b.t": "约 3 到 8 分钟",
  452. "mdl.deepseek-v4-pro.nm": "云端 DS",
  453. "mdl.deepseek-v4-pro.t": "约 2 到 6 分钟",
  454. "mdl.kimi-fast.nm": "Kimi",
  455. "mdl.kimi-fast.t": "约 5 到 20 秒",
  456. "mdl.local": "本地",
  457. "mdl.outbound_warn": "内容会外发到模型厂商, 场名与台号已去标识化",
  458. "mdl.qwen-plus.nm": "通义千问",
  459. "mdl.qwen-plus.t": "约 5 到 20 秒",
  460. "mdl.qwen3.8:27b.nm": "标准 27B · 复杂题",
  461. "mdl.qwen3.8:27b.t": "约 2 到 4 分钟",
  462. "mdl.qwen3:30b-a3b.nm": "快 30B (a3b)",
  463. "mdl.qwen3:30b-a3b.t": "约 20 到 60 秒",
  464. "mdl.qwen3:8b.nm": "轻量 8B · 默认",
  465. "mdl.qwen3:8b.t": "约 30 到 60 秒",
  466. "mdl.unknown_warn": "未知档位, 按内容会外发处理",
  467. "mdl.zero_outbound": "全程零外发",
  468. "mt.cov": "当前覆盖",
  469. "mt.eyebrow": "系统维护 · 数据输入与更新端口",
  470. "mt.fail": "盘点失败: {e}",
  471. "mt.freq": "频率",
  472. "mt.head": "数据维护面 · {n} 个源",
  473. "mt.how": "补数据怎么做",
  474. "mt.last": "末次更新",
  475. "mt.layer_data": "数据层",
  476. "mt.layer_data_note": "随运营滚动 — 新数据到位后跑摄入, 本体与判级自动刷新",
  477. "mt.layer_mech": "机理层",
  478. "mt.layer_mech_note": "相对稳定 — 换厂商资料或现场反馈修正时才动; 它是判据的地基, 变更须过审",
  479. "mt.note": '每个源给出: 当前覆盖 / 末次更新 / 约定位置 / 摄入命令 / 更新频率 / 责任方。要补数据就把新文件放到"约定位置"再跑"摄入命令"。',
  480. "mt.put": "放到:",
  481. "mt.resp": "责任",
  482. "mt.rows_n": " ({n} 条)",
  483. "mt.run": "跑:",
  484. "mt.src": "源",
  485. "mt.surveying": "盘点中…",
  486. "ov.all_steps_done": "已走完(待验收)",
  487. "ov.attention": "需要关注",
  488. "ov.attention_n": "{n} 台",
  489. "ov.basis_fusion": "本台依据来自融合面(九系统判级无「报警」行):部件 {part} · 链路走到第 {done}/{n} 步 · 卡在「{stuck}」 · 证据源 {srcs}",
  490. "ov.caveat": "限定",
  491. "ov.evidence": "查看完整依据",
  492. "ov.fus_gap_hint": "这一面(需要关注 / 全场状态)取数来自融合面;缺件时如实为空,不从交付包补齐。在本机放好原始件后跑一次重算即会由观澜自己的生成端补上(scripts/rudong_fusion_handoff.py,见 docs 的「源代码化清单」)。",
  493. "ov.illustrative": "示意数据",
  494. "ov.no_srcs": "无(该台无外部证据源)",
  495. "ov.quiet": "其余 {n} 台未进入优先关注清单;设备状态请查看全场状态评估。",
  496. "ov.quiet_all": "当前优先关注清单为空;{n} 台机组的设备状态请查看全场状态评估。",
  497. "ov.ranked_by": "按 设备状态 × 证据量 排序",
  498. "ov.strip": "全场状态",
  499. "ov.strip_hint": "一格一台 · 点格定位",
  500. "ov.systems": "九系统",
  501. "ov.systems_hint": "点系统看逐台依据",
  502. "pop.acts": "动作统计",
  503. "pop.close": "关闭",
  504. "pop.loading": "载入中…",
  505. "pop.none": "本时间窗无记录",
  506. "pop.o_act": "处置",
  507. "pop.o_cause": "原因",
  508. "pop.o_check": "排查",
  509. "pop.o_date": "日期",
  510. "pop.o_fault": "故障",
  511. "pop.o_kind": "类别",
  512. "pop.orders": "工单 {n} 单",
  513. "pop.orders_none": "时间窗内无工单",
  514. "pop.orders_one": "工单 1 单",
  515. "pop.problem": "{u} · {sys}",
  516. "pop.unit": "{u} · 全部问题",
  517. "rpt.attrib.note": '"无记录停机"不是没有故障, 是没归上因。',
  518. "rpt.attrib.title": "归因覆盖",
  519. "rpt.aud_in": "场站内部 (全量)",
  520. "rpt.aud_up": "上报集团 (脱敏)",
  521. "rpt.b.attrib.d": "已归因 vs 无记录停机的占比 — 无记录不等于没有故障",
  522. "rpt.b.attrib.nm": "归因覆盖",
  523. "rpt.b.blame.d": '交易调度侧 / 设备侧 / 运行侧 三段占比 — 回答"谁的责任"',
  524. "rpt.b.blame.nm": "损失责任分账",
  525. "rpt.b.caliber.d": "统计期 / 残月 / 工单时间窗 / 判级性质 — 建议常驻, 没有边界的数字堆不能作汇报依据",
  526. "rpt.b.caliber.nm": "统计方法与适用边界",
  527. "rpt.b.chain.d": "证据→机制→判级→排期→动作→验收 六步的瓶颈分布",
  528. "rpt.b.chain.nm": "决策链进度",
  529. "rpt.b.emon.d": "逐月上网 / 损失 / 损失率 / 等效满发, 含数据覆盖率",
  530. "rpt.b.emon.nm": "逐月发电表",
  531. "rpt.b.frate.d": "按天归一的日均条数, 数据不满月单独标注",
  532. "rpt.b.frate.nm": "月度故障率",
  533. "rpt.b.kpi.d": "六个核心数: 上网电量 / 等效满发 / 可用率 / 损失率 / 停机间隔 / 需行动台",
  534. "rpt.b.kpi.nm": "一级指标",
  535. "rpt.b.loop.d": "检出 → 换件 → 复测回落的历史例证, 证明这条链是通的",
  536. "rpt.b.loop.nm": "闭环实证",
  537. "rpt.b.narr.d": "模型基于实测数据生成的运行总结 — 为图表补充文字结论, 供汇报使用",
  538. "rpt.b.narr.nm": "运行总结",
  539. "rpt.b.pareto.d": "按条数降序 + 累计 80% 线, 找关键少数",
  540. "rpt.b.pareto.nm": "故障帕累托",
  541. "rpt.b.parts.d": "逐部件停机时长 / 事件数 / 平均无故障间隔(MTBF)/ 单次停机时长(MDT)/ 影响机组数",
  542. "rpt.b.parts.nm": "部件停机表",
  543. "rpt.b.plan.d": "由数据推出的行动项, 不是套话",
  544. "rpt.b.plan.nm": "下期建议",
  545. "rpt.b.quad.d": "条数 × 单次时长, 区分重复触发与实际停机 — 按条数排序会误定优先级",
  546. "rpt.b.quad.nm": "故障四象限",
  547. "rpt.b.ram.d": '平均无故障间隔(MTBF)× 单次停机时长(MDT)部件象限, 分出"频发易修"与"罕发难修", 处置方向相反',
  548. "rpt.b.ram.nm": "可靠性象限",
  549. "rpt.b.units.d": "需跟踪台的部件 / 证据源 / 六步进度 / 卡在哪一步",
  550. "rpt.b.units.nm": "重点机组",
  551. "rpt.b.water.d": "理论可发 → 逐段扣损 → 实际上网, 用于定位电量流向",
  552. "rpt.b.water.nm": "能量瀑布",
  553. "rpt.blame.cal": "工况态分解 + 限电命令面绑定判别; 平均出力=实发÷台时, 用作风资源代理量",
  554. "rpt.blame.note": '进设备考核的只有"故障与检修停机"; 限电属交易与调度侧。',
  555. "rpt.blame.note2": '限电不是"低风时段收到的空令": 限电·绑定时段平均出力 1.435 MW, 正常发电时段 1.412 MW, 比值 1.02 — 风况相同而出力被压, 说明限电是真压制; 但"限电命令期·功率未被压制"那一档平均出力仅 0.724 MW (18% 额定), 确属低风时段收令。',
  556. "rpt.blame.title": "损失责任分账",
  557. "rpt.cal.deid": "脱敏",
  558. "rpt.cal.deid_d": "上报版隐去判据门槛与特征频率",
  559. "rpt.cal.denom": "可用率与损失率分母不同",
  560. "rpt.cal.denom_d": '可用率分母剔除低风待机与调度令(问"该发时能不能发"), 损失率分母含全部时段(问"理论能发多少"); 同一低风时段在可用率里不计时间、在损失率里计入基数 — 这是两个指标的设计差异不是错, 但两者不可直接相乘或互推。低风待机对损失的贡献仅 118 MWh(0.5%)',
  561. "rpt.cal.partial": " — {m} 数据不满月(覆盖 {c}%), 月度已按天归一",
  562. "rpt.cal.period": "统计期",
  563. "rpt.cal.rating": "判级",
  564. "rpt.cal.rating_d": "由振动/温度/润滑/油液四源融合得出; 行动等级由综合报告出具",
  565. "rpt.cal.stop": "停机",
  566. "rpt.cal.stop_d": "可靠性统计方法 ≥1h 段, 电量账为逐条工况态 — 两个数都对, 不可相减",
  567. "rpt.cal.title": "统计方法与数据边界",
  568. "rpt.cal.wo": "工单",
  569. "rpt.cal.wo_d": "台账 2020~2024-11, 本报告不对本期检修执行下结论",
  570. "rpt.caliber_lbl": "统计方法: ",
  571. "rpt.chain.cal": "「动作」一列不可判 — {b}",
  572. "rpt.chain.note": "六步: 证据 → 机制 → 判级 → 排期 → 动作 → 验收。当前 {s}。",
  573. "rpt.chain.title": "决策链进度",
  574. "rpt.chapters": "{n} 个章节",
  575. "rpt.cloud_note": "; 内容会外发到模型厂商",
  576. "rpt.compose": "生成",
  577. "rpt.compose_fail": "生成失败: {e}",
  578. "rpt.composed": "已按需求编排 {n} 块{narr} · {m} 用时 {s}s",
  579. "rpt.composing": "模型编排中… 挑组件 + 写总结",
  580. "rpt.emon.act": "上网 MWh",
  581. "rpt.emon.cal": "覆盖率<50% 的月不参与环比",
  582. "rpt.emon.cov": "覆盖",
  583. "rpt.emon.eflh": "等效满发 h",
  584. "rpt.emon.loss": "损失 MWh",
  585. "rpt.emon.m": "月",
  586. "rpt.emon.partial": " 不满月",
  587. "rpt.emon.pct": "损失率",
  588. "rpt.emon.title": "逐月发电",
  589. "rpt.empty_canvas": "画布是空的 — 先从组件库选内容",
  590. "rpt.export_docx": "导出 Word",
  591. "rpt.export_xlsx": "导出 Excel",
  592. "rpt.eyebrow": "汇报定制 · 画布",
  593. "rpt.facts.agg": "聚合层",
  594. "rpt.facts.sub": "{n} 条, 契约 {sha}; 数据/时间窗/结论只经契约更新",
  595. "rpt.facts.sys": "系统",
  596. "rpt.facts.text": "结论",
  597. "rpt.facts.title": "可交付级结论 · 契约生成",
  598. "rpt.facts.verdict": "裁决",
  599. "rpt.foot_asof": "出件 {d}",
  600. "rpt.foot_deid": "判据门槛与特征频率已脱敏",
  601. "rpt.foot_src": "数据源 SCADA 10min · 报警事件 · 工单台账 · CMS 振动 · 油液化验",
  602. "rpt.foot_trace": "结论可回溯至本体对象",
  603. "rpt.frate.cal": "不满月按天归一",
  604. "rpt.frate.note": "柱高=日均条数, 不是月总数。",
  605. "rpt.frate.title": "月度故障率",
  606. "rpt.g.end": "收尾",
  607. "rpt.g.flt": "故障",
  608. "rpt.g.gen": "发电",
  609. "rpt.g.rel": "可靠性",
  610. "rpt.g.unit": "机组",
  611. "rpt.grounded": "数字全部接地",
  612. "rpt.hint_idle": "悬停查看组件说明 · 已选组件再次点击移出 · 右侧 ↑↓ 调整顺序",
  613. "rpt.kpi.act": "上网电量",
  614. "rpt.kpi.action": "需行动机组",
  615. "rpt.kpi.action_n": "定论 {a} · 预警 {b} · 候选 {c}",
  616. "rpt.kpi.annual": "年化约 {v} h",
  617. "rpt.kpi.avail": "时间可用率",
  618. "rpt.kpi.avail_n": "不含调度令与低风待机",
  619. "rpt.kpi.eflh": "等效满发小时",
  620. "rpt.kpi.loss": "损失率",
  621. "rpt.kpi.mom": "环比 {v}%",
  622. "rpt.kpi.mom_na": "环比不可比(末月不满)",
  623. "rpt.kpi.mtbo": "平均停机间隔",
  624. "rpt.kpi.mtbo_n": "停机 {n} 起 · 单次均 {h}h",
  625. "rpt.kpi.theo": "理论可发 {v}",
  626. "rpt.library": "组件库",
  627. "rpt.library_note": "点击加入报告, 再次点击移出 · 画布里已有 {n} 块",
  628. "rpt.local_note": "; 数据不出本地",
  629. "rpt.loop.cal": "振动标量前后对比",
  630. "rpt.loop.comp": "部件",
  631. "rpt.loop.date": "换件日期",
  632. "rpt.loop.note": "这几台正是算法当初的检出台 ⇒ 故障前能看见、换件后能看见恢复。",
  633. "rpt.loop.post": "换后",
  634. "rpt.loop.pre": "换前",
  635. "rpt.loop.ratio": "比值",
  636. "rpt.loop.title": "端到端闭环实证",
  637. "rpt.narr.cal": "模型 {m} 生成 · 数字取自系统实测摘要 · 数据回抓校验{g}",
  638. "rpt.narr.empty": "还没有生成 — 在上面的交互窗输入报告要求。",
  639. "rpt.narr.g_n": "报 {n} 处待核",
  640. "rpt.narr.g_ok": "全通过",
  641. "rpt.narr.title": "运行总结",
  642. "rpt.narr.ungr": "⚠ 以下数值由模型自行推算, 不在原始数据摘要中, 引用前须核对: {v}",
  643. "rpt.narr_plus": " + 一段运行总结",
  644. "rpt.need_empty": "先输入报告要求",
  645. "rpt.need_head": "输入报告要求",
  646. "rpt.need_hint": "模型依据需求选取组件, 并基于实测数据生成运行总结",
  647. "rpt.need_ph": "例如: 上报集团月报, 侧重设备可靠性与重点机组",
  648. "rpt.no_compose": "本部署未启用编排接口, 画布仍可手动拼装",
  649. "rpt.no_models": "本部署未配置任何模型密钥, 编排不可用",
  650. "rpt.paper_sub": "统计期 {win} | {n} 台",
  651. "rpt.paper_title": "运行分析报告",
  652. "rpt.pareto.cal": "报警事件表",
  653. "rpt.pareto.title": "故障帕累托",
  654. "rpt.parts.cal": "归因=段起点±30min 首发码",
  655. "rpt.parts.ev": "事件",
  656. "rpt.parts.ext": " (外部)",
  657. "rpt.parts.h": "停机h",
  658. "rpt.parts.mdt": "单次停机时长(MDT,h)",
  659. "rpt.parts.mtbf": "平均无故障间隔(MTBF,h)",
  660. "rpt.parts.part": "部件",
  661. "rpt.parts.title": "部件停机",
  662. "rpt.parts.units": "影响机组",
  663. "rpt.plan.1": '补机制定性 — {n} 台停在"判了级但根因未定", 是当前最大单一瓶颈',
  664. "rpt.plan.2": "回收 25~26 工单 — 台账止 2024-11, 本期检修执行与闭环无法自动判定",
  665. "rpt.plan.3": "油液采样节奏 — 四源里油液长期 stale, 只能作回测锚",
  666. "rpt.plan.4": "限电侧 — 损失六成在交易与调度侧, 设备侧改善空间有限",
  667. "rpt.plan.cal": "建议级 — 排期与动作须经审批",
  668. "rpt.plan.title": "下期计划与建议",
  669. "rpt.preset.blank": "空白画布",
  670. "rpt.preset.month": "月度运行分析",
  671. "rpt.preset.topic": "设备专题",
  672. "rpt.preset.week": "周报",
  673. "rpt.preset.year": "年度总结",
  674. "rpt.presets": "预设",
  675. "rpt.preview_empty": "文档预览窗 — 从上面的组件库点选内容, 此处显示报告预览",
  676. "rpt.print": "打印 / 存 PDF",
  677. "rpt.quad.cal": "纵界=10min(一个SCADA采样格)",
  678. "rpt.quad.note": "右下角报警码属重复触发, 不应按条数排定优先级。",
  679. "rpt.quad.title": "故障模式四象限",
  680. "rpt.ram.cal": "SCADA ≥1h 停机段, 不含调度令; 海上 MDT 含交通窗口",
  681. "rpt.ram.note": "设备类 {a}h / 外部类 {b}h (电网·风况·运维操作不计设备可靠性)",
  682. "rpt.ram.title": "可靠性 × 可维修性",
  683. "rpt.ungrounded_n": "{n} 个数字待核",
  684. "rpt.units.at": "卡在",
  685. "rpt.units.cal": "四源融合判级",
  686. "rpt.units.clear": "全通",
  687. "rpt.units.ev": "部件与证据",
  688. "rpt.units.head": "需跟踪 {n} 台 / {tot} | 瓶颈 ",
  689. "rpt.units.prog": "进度",
  690. "rpt.units.rising": "在升 {r}×",
  691. "rpt.units.state": "状态",
  692. "rpt.units.stuck": "{n} 台卡在「{s}」",
  693. "rpt.units.title": "重点机组与处置",
  694. "rpt.up_note": "上报版 — 判据门槛与特征频率已脱敏",
  695. "rpt.up_tag": "上报版",
  696. "rpt.water.cal": "SCADA 10min 工况态分解; 损失=同工况理论出力与实测之差。量级自检为防虚高的下限校验, 不是风资源判据",
  697. "rpt.water.cap_bad": "量级自检: 理论 ≥ 全额定上限 {cap} MWh ✗ 数字不可信",
  698. "rpt.water.cap_ok": "量级自检: 理论低于全额定上限 {cap} MWh ✔",
  699. "rpt.water.note": "理论可发 {theo} = 上网 {act} + 损失 {loss} MWh ({pct}%)",
  700. "rpt.water.title": "发电完成情况",
  701. "rv.advci": "AdvCI",
  702. "rv.dual": "双模型审",
  703. "rv.lite": "lite",
  704. "rv.none": "未审",
  705. "st.bad": "危险",
  706. "st.excellent": "优秀",
  707. "st.ok": "良好",
  708. "st.ok_excellent": "良好 / 优秀",
  709. "st.unk": "不可判",
  710. "st.warn": "报警",
  711. "step.acceptance": "验收",
  712. "step.action": "动作",
  713. "step.evidence": "证据",
  714. "step.mechanism": "机制",
  715. "step.rating": "判级",
  716. "step.schedule": "排期",
  717. "step.site_records": "现场记录",
  718. "step.stuck_at": "卡在「{step}」",
  719. "sys.blade": "叶片与叶根",
  720. "sys.control": "主控与传感网",
  721. "sys.converter": "变流器",
  722. "sys.gearbox": "齿轮箱",
  723. "sys.generator": "发电机",
  724. "sys.main_bearing": "主轴承",
  725. "sys.other": "其他",
  726. "sys.pitch": "变桨",
  727. "sys.tower": "塔架与基础",
  728. "sys.yaw": "偏航",
  729. "tab.assistant": "检修助手",
  730. "tab.component": "部件问题",
  731. "tab.decision": "检修决策",
  732. "tab.energy": "电量算账",
  733. "tab.fault": "故障统计",
  734. "tab.generation": "发电性能",
  735. "tab.ontology": "本体",
  736. "tab.overview": "总览",
  737. "tab.report": "汇报定制",
  738. "tab.system": "系统维护",
  739. "tab.vibration": "振动融合分析",
  740. "tier.candidate": "候选",
  741. "tier.candidate.s": "候选",
  742. "tier.candidate_baseline": "候选·记基线",
  743. "tier.candidate_baseline.s": "候选·记基线",
  744. "tier.candidate_new": "候选·新发",
  745. "tier.candidate_new.s": "候选·新发",
  746. "tier.confirmed": "定论",
  747. "tier.confirmed.s": "定论",
  748. "tier.detail.不可判": "不可判",
  749. "tier.detail.仲裁输入": "仲裁输入",
  750. "tier.detail.优": "优",
  751. "tier.detail.优秀": "优秀",
  752. "tier.detail.传感器项": "传感器项",
  753. "tier.detail.候选": "候选",
  754. "tier.detail.候选·监视(复发)": "候选·监视(复发)",
  755. "tier.detail.候选·跟踪": "候选·跟踪",
  756. "tier.detail.危险": "危险",
  757. "tier.detail.可降": "可降",
  758. "tier.detail.复发候选(两轴合并)": "复发候选(两轴合并)",
  759. "tier.detail.定论": "定论",
  760. "tier.detail.弱同向新证": "弱同向新证",
  761. "tier.detail.报": "报",
  762. "tier.detail.报警": "报警",
  763. "tier.detail.本体无新证": "本体无新证",
  764. "tier.detail.正常": "正常",
  765. "tier.detail.正样本": "正样本",
  766. "tier.detail.温度单轴保留": "温度单轴保留",
  767. "tier.detail.监视": "监视",
  768. "tier.detail.监视(不开箱)": "监视(不开箱)",
  769. "tier.detail.良": "良",
  770. "tier.detail.良好": "良好",
  771. "tier.detail.蓄能失效候选(定向查)": "蓄能失效候选(定向查)",
  772. "tier.detail.观察": "观察",
  773. "tier.detail.观察(发电机侧)": "观察(发电机侧)",
  774. "tier.detail.记基线观察": "记基线观察",
  775. "tier.detail.销案(定论)": "销案(定论)",
  776. "tier.detail.降观察": "降观察",
  777. "tier.detail.预警": "预警",
  778. "tier.detail.预警·处置升急": "预警·处置升急",
  779. "tier.detail.预警·处置规划": "预警·处置规划",
  780. "tier.insufficient": "不足",
  781. "tier.insufficient.s": "不足",
  782. "tier.near": "准定论·预警",
  783. "tier.near.s": "准定论",
  784. "tier.normal": "正常",
  785. "tier.normal.s": "正常",
  786. "tier.reference": "参考",
  787. "tier.reference.s": "参考",
  788. "tier.withdrawn": "撤回",
  789. "tier.withdrawn.s": "撤回",
  790. "ui.all": "全部",
  791. "ui.back_portal": "← 返回门户",
  792. "ui.back_to": "← 返回{tab}",
  793. "ui.collapse": "收起",
  794. "ui.count": "条数",
  795. "ui.data_hidden": "({n} 处数据文本尚无英文,需复核)",
  796. "ui.date": "日期",
  797. "ui.empty": "无数据",
  798. "ui.error": "出错了",
  799. "ui.expand": "展开",
  800. "ui.loading": "载入中…",
  801. "ui.not_ported": "本页尚未迁入新版。",
  802. "ui.not_recorded": "台账未填",
  803. "ui.open": "查看 →",
  804. "ui.open_classic": "在经典版打开 →",
  805. "ui.query": "查询",
  806. "ui.reselect": "重新选择机组 / 测点",
  807. "ui.retry": "重试",
  808. "ui.system": "系统",
  809. "ui.unit": "机组",
  810. "ui.units": "机组",
  811. "ui.verbatim": "原始记录 (中文)",
  812. "ui.verbatim_note": "{n} 处为手册/工单原文, 无英文译本, 按原文转录。",
  813. "unit.h": "h",
  814. "unit.n": " 条",
  815. "unit.units": "台",
  816. "vp.(振动线未收录)": "(振动线未收录)",
  817. "vp.主轴承(后)+集中润滑泵": "主轴承(后)+集中润滑泵",
  818. "vp.发电机(引用同族, 见族主台)": "发电机(无本台独立证据, 参照同型机组)",
  819. "vp.发电机(未映射)": "发电机(未对应到具体部件)",
  820. "vp.发电机DE": "发电机DE",
  821. "vp.发电机DE+NDE(两端)": "发电机DE+NDE(两端)",
  822. "vp.发电机NDE": "发电机NDE",
  823. "vp.齿轮箱(未映射)": "齿轮箱(未对应到具体部件)",
  824. "vp.齿轮箱(测量链问题, 非部件)": "齿轮箱(测量链问题, 非部件)",
  825. "vp.齿轮箱(谱线级, 未映射到部件)": "齿轮箱(频谱信号级 (未定位到具体零件), 未对应到具体部件)",
  826. "vp.齿轮箱中间轴": "齿轮箱中间轴",
  827. "vp.齿轮箱二级行星内齿圈": "齿轮箱二级行星内齿圈",
  828. "vp.齿轮箱高速轴": "齿轮箱高速轴",
  829. "win.2025H2": "2025H2",
  830. "win.2026": "2026年",
  831. "win.2026H1": "2026H1",
  832. "win.30d": "近30日",
  833. "win.90d": "近90日",
  834. "win.all": "全程",
  835. "win.apply": "应用",
  836. "win.bad_order": "开始日期不能晚于结束日期",
  837. "win.banner": "时间窗 {win} · 本页全部数字随上方时间窗切换",
  838. "win.custom": "自定义",
  839. "win.custom_hint": "自定义起止日期(含两端)。日粒度件(判级/停机台账/曲线)按日精确;月度聚合件按所跨月份取整,图上会写明。",
  840. "win.custom_sel": "自定义 {a} ~ {b}",
  841. "win.from": "开始日期(含)",
  842. "win.label": "时间窗",
  843. "win.months": "按月",
  844. "win.need_both": "请把开始与结束日期都选上",
  845. "win.partial": "时间窗内 {m} 数据不满月 (覆盖 {c}%), 月度图已按天归一",
  846. "win.presets": "预设时间窗",
  847. "win.through": "至{date}",
  848. "win.to": "结束日期(含)",
  849. "tab.documents": "交付文档",
  850. "tab.recalc": "数据重算",
  851. "doc.eyebrow": "交付文档",
  852. "doc.loading": "正在读取交付文档…",
  853. "doc.head": "交付文档 ({n})",
  854. "doc.note": "交付文档随包提供;下载后可离线查阅。",
  855. "doc.name": "文档",
  856. "doc.size": "大小",
  857. "doc.get": "获取",
  858. "doc.download": "下载",
  859. "rc.eyebrow": "数据重算",
  860. "rc.loading": "正在读取重算状态…",
  861. "rc.running": "重算进行中",
  862. "rc.idle": "当前空闲",
  863. "rc.note": "重算链按步执行;本页每 5 秒刷新状态与日志尾部。",
  864. "rc.since": "开始于",
  865. "rc.start": "开始重算",
  866. "rc.log": "重算日志(尾部)",
  867. "rc.empty": "(暂无日志)",
  868. "tab.portal": "门户栏目",
  869. "pt.eyebrow": "门户栏目",
  870. "pt.head": "重构前门户上的栏目(已迁移 / 可达)",
  871. "pt.note": "总览、振动·CMS、交付文档、数据重算已迁为页签;系统架构/方法/经验发现/案例·液压 见「架构与资料」页签;下列两项为在线服务入口。",
  872. "pt.col": "栏目",
  873. "pt.what": "说明",
  874. "pt.open": "打开",
  875. "pt.kind_portal": "门户存档页(release/portal.html 经网关 /portal 伺服)",
  876. "pt.kind_svc": "在线服务(经网关)",
  877. "tab.portal_sec": "架构与资料",
  878. "ps.eyebrow": "系统架构 · 方法 · 经验发现 · 案例·液压",
  879. "ps.missing": "该节内容未抽取到(请检查 v2.9.2 门户原文)。",
  880. "tab.portal_src": "门户",
  881. "pv.eyebrow": "观澜门户(Vue 装配 · v2.9.2 原文外壳与内嵌件)",
  882. "pv.loading": "正在装配门户(外壳 + 内嵌件)…",
  883. "pv.assembled": "门户已装配:内嵌件 {n} 件(外壳与内嵌件均取自 v2.9.2 原文)"
  884. };
  885. function t(key, vars) {
  886. let s = ZH[key] ?? key;
  887. if (vars) {
  888. for (const [k, v] of Object.entries(vars)) s = s.split(`{${k}}`).join(String(v));
  889. }
  890. return s;
  891. }
  892. const TABS = [
  893. "overview",
  894. "energy",
  895. "component",
  896. "vibration",
  897. "generation",
  898. "fault",
  899. "decision",
  900. "assistant",
  901. "report",
  902. "system",
  903. "documents",
  904. "recalc",
  905. "portal_sec",
  906. "portal_src"
  907. ];
  908. const DONE = ["overview", "energy", "component", "fault", "vibration", "generation", "decision", "assistant", "system", "report", "documents", "recalc", "portal_sec", "portal_src"];
  909. const _sfc_main$h = /* @__PURE__ */ defineComponent({
  910. __name: "Documents",
  911. __ssrInlineRender: true,
  912. setup(__props) {
  913. const docs = ref(null);
  914. const err = ref("");
  915. const size = (n) => n >= 1048576 ? (n / 1048576).toFixed(1) + " MB" : Math.max(1, Math.round(n / 1024)) + " KB";
  916. onMounted(async () => {
  917. try {
  918. const r = await fetch("/api/delivery_docs");
  919. const j = await r.json();
  920. if (j && j.error) err.value = String(j.error);
  921. else docs.value = j && j.docs || [];
  922. } catch (e) {
  923. err.value = String(e && e.message || e);
  924. }
  925. });
  926. return (_ctx, _push, _parent, _attrs) => {
  927. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("doc.eyebrow"))}</div>`);
  928. if (err.value) {
  929. _push(`<div class="panel"><p class="small" style="${ssrRenderStyle({ "color": "var(--critical)" })}">${ssrInterpolate(err.value)}</p></div>`);
  930. } else if (!docs.value) {
  931. _push(`<div class="panel"><p class="small muted">${ssrInterpolate(unref(t)("doc.loading"))}</p></div>`);
  932. } else {
  933. _push(`<!--[--><div class="panel mb"><h3>${ssrInterpolate(unref(t)("doc.head", { n: docs.value.length }))}</h3><p class="small muted">${ssrInterpolate(unref(t)("doc.note"))}</p></div><div class="panel mb"><div class="tblwrap"><table style="${ssrRenderStyle({ "margin-top": "4px" })}"><tr><th style="${ssrRenderStyle({ "width": "60%" })}">${ssrInterpolate(unref(t)("doc.name"))}</th><th style="${ssrRenderStyle({ "width": "16%" })}">${ssrInterpolate(unref(t)("doc.size"))}</th><th style="${ssrRenderStyle({ "width": "24%" })}">${ssrInterpolate(unref(t)("doc.get"))}</th></tr><!--[-->`);
  934. ssrRenderList(docs.value, (d) => {
  935. _push(`<tr><td class="small">${ssrInterpolate(d.name)}</td><td class="small num">${ssrInterpolate(size(d.size || 0))}</td><td class="small"><a${ssrRenderAttr("href", d.url)}${ssrRenderAttr("download", d.name)}>${ssrInterpolate(unref(t)("doc.download"))}</a></td></tr>`);
  936. });
  937. _push(`<!--]--></table></div></div><!--]-->`);
  938. }
  939. _push(`</section>`);
  940. };
  941. }
  942. });
  943. const _sfc_setup$h = _sfc_main$h.setup;
  944. _sfc_main$h.setup = (props, ctx) => {
  945. const ssrContext = useSSRContext();
  946. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Documents.vue");
  947. return _sfc_setup$h ? _sfc_setup$h(props, ctx) : void 0;
  948. };
  949. const _sfc_main$g = /* @__PURE__ */ defineComponent({
  950. __name: "Recalc",
  951. __ssrInlineRender: true,
  952. setup(__props) {
  953. const st = ref(null);
  954. const err = ref("");
  955. const busy = ref(false);
  956. let timer = null;
  957. const load = async () => {
  958. try {
  959. const r = await fetch("/api/recalc");
  960. st.value = await r.json();
  961. } catch (e) {
  962. err.value = String(e && e.message || e);
  963. }
  964. };
  965. onMounted(() => {
  966. load();
  967. timer = setInterval(load, 5e3);
  968. });
  969. onUnmounted(() => {
  970. if (timer) clearInterval(timer);
  971. });
  972. return (_ctx, _push, _parent, _attrs) => {
  973. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("rc.eyebrow"))}</div>`);
  974. if (err.value) {
  975. _push(`<div class="panel"><p class="small" style="${ssrRenderStyle({ "color": "var(--critical)" })}">${ssrInterpolate(err.value)}</p></div>`);
  976. } else if (!st.value) {
  977. _push(`<div class="panel"><p class="small muted">${ssrInterpolate(unref(t)("rc.loading"))}</p></div>`);
  978. } else {
  979. _push(`<!--[--><div class="panel mb"><h3>${ssrInterpolate(st.value.running ? unref(t)("rc.running") : unref(t)("rc.idle"))}</h3><p class="small muted">${ssrInterpolate(unref(t)("rc.note"))}</p>`);
  980. if (st.value.started_at) {
  981. _push(`<p class="small">${ssrInterpolate(unref(t)("rc.since"))} <code>${ssrInterpolate(st.value.started_at)}</code></p>`);
  982. } else {
  983. _push(`<!---->`);
  984. }
  985. _push(`<button class="pill"${ssrIncludeBooleanAttr(busy.value || st.value.running) ? " disabled" : ""}>${ssrInterpolate(unref(t)("rc.start"))}</button></div><div class="panel mb"><h3>${ssrInterpolate(unref(t)("rc.log"))}</h3><pre class="small" style="${ssrRenderStyle({ "max-height": "340px", "overflow": "auto", "margin": "0" })}">${ssrInterpolate(st.value.log || unref(t)("rc.empty"))}</pre></div><!--]-->`);
  986. }
  987. _push(`</section>`);
  988. };
  989. }
  990. });
  991. const _sfc_setup$g = _sfc_main$g.setup;
  992. _sfc_main$g.setup = (props, ctx) => {
  993. const ssrContext = useSSRContext();
  994. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Recalc.vue");
  995. return _sfc_setup$g ? _sfc_setup$g(props, ctx) : void 0;
  996. };
  997. const _sfc_main$f = /* @__PURE__ */ defineComponent({
  998. __name: "Portal",
  999. __ssrInlineRender: true,
  1000. setup(__props) {
  1001. const ENTRIES = [
  1002. { id: "sim", label: "仿真", href: "/sim/", note: "仿真与回放(仿真台 18791)", kind: "svc" },
  1003. { id: "admin", label: "状态", href: "/ops", note: "系统状态(运维台)", kind: "svc" },
  1004. {
  1005. id: "classic",
  1006. label: "经典",
  1007. href: "/web/detail_v2.html",
  1008. note: "经典版页面(v2.9.2 原文原样:布局/样式/字体/图文表一致)",
  1009. kind: "svc"
  1010. }
  1011. ];
  1012. return (_ctx, _push, _parent, _attrs) => {
  1013. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("pt.eyebrow"))}</div><div class="panel mb"><h3>${ssrInterpolate(unref(t)("pt.head"))}</h3><p class="small muted">${ssrInterpolate(unref(t)("pt.note"))}</p></div><div class="panel mb"><div class="tblwrap"><table style="${ssrRenderStyle({ "margin-top": "4px" })}"><tr><th style="${ssrRenderStyle({ "width": "22%" })}">${ssrInterpolate(unref(t)("pt.col"))}</th><th style="${ssrRenderStyle({ "width": "56%" })}">${ssrInterpolate(unref(t)("pt.what"))}</th><th>${ssrInterpolate(unref(t)("pt.open"))}</th></tr><!--[-->`);
  1014. ssrRenderList(ENTRIES, (e) => {
  1015. _push(`<tr><td class="small"><b>${ssrInterpolate(e.note)}</b></td><td class="small muted">${ssrInterpolate(e.kind === "portal" ? unref(t)("pt.kind_portal") : unref(t)("pt.kind_svc"))}</td><td class="small"><a${ssrRenderAttr("href", e.href)}>${ssrInterpolate(unref(t)("pt.open"))}</a></td></tr>`);
  1016. });
  1017. _push(`<!--]--></table></div></div></section>`);
  1018. };
  1019. }
  1020. });
  1021. const _sfc_setup$f = _sfc_main$f.setup;
  1022. _sfc_main$f.setup = (props, ctx) => {
  1023. const ssrContext = useSSRContext();
  1024. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Portal.vue");
  1025. return _sfc_setup$f ? _sfc_setup$f(props, ctx) : void 0;
  1026. };
  1027. const SEC_ARCHITECTURE = '</div><div class="pg" id="architecture" data-title="系统架构"><section class="hero"><div class="wrap"><div class="kicker">技术白皮书 · 系统架构</div><h1>从证据分析到处置建议</h1><p class="lead">五个相连的系统层:保留来源身份、落位设备上下文、检验机制、准备可问责的决定,并从检修结果中学习。</p></div></section>\n<section><div class="wrap"><div class="chips"><span class="chip">十分钟 SCADA</span><span class="chip">秒级数据</span><span class="chip">CMS</span><span class="chip">报警</span><span class="chip">工单</span><span class="chip">油样与检查</span><span class="chip">技术文档</span></div>\n<div class="rail" style="--n:5;margin-top:26px"><div class="node"><i>01</i><b>数据地基</b><small>每条记录能被辨识和解释吗?</small></div><div class="node"><i>02</i><b>设备本体</b><small>每条记录指向哪个物理对象与配置?</small></div><div class="node on"><i>03</i><b>证据引擎</b><small>证据支持某个具体机制吗?</small></div><div class="node"><i>04</i><b>决策服务</b><small>现在有什么动作是有依据的?</small></div><div class="node"><i>05</i><b>检修学习</b><small>动作改变了预期指标吗?</small></div></div>\n<div class="grid g2" style="margin-top:22px"><div class="callout"><b>验证过的现场结果</b> → 本体 + 证据史 → 下一轮分析</div><div class="callout warn"><b>必需证据缺失</b> → 点名采集任务 → 停止升级</div></div>\n<p class="sub" style="margin-top:12px">身份、来源完整性或必需证据未解决时,系统失败关闭;验证过的检修结果成为可复用的证据。</p></div></section>\n<section><div class="wrap"><div class="kicker">01 · 数据地基 · 登记与契约</div><h2>每条记录能被辨识和解释吗?</h2><div class="grid g2" style="margin-top:14px"><div class="card"><p>保留原始证据,定义每个通道:登记来源位置、指纹、时间覆盖与格式,不改原文件;十分钟与秒级数据核单位、采样间隔、时间戳、哨兵值、通道活性与运行状态。</p></div><div class="card"><div class="k">输出</div><p>来源登记簿、通道定义、可用时段与排除项。</p></div></div></div></section><section><div class="wrap"><div class="kicker">02 · 设备本体 · 落位</div><h2>每条记录指向哪个物理对象与配置?</h2><div class="grid g2" style="margin-top:14px"><div class="card"><p>通过受控的工程图谱连接证据:资产、系统、总成、部件、测点与通道、告警、规程、工单、文档相连;机型、配置、有效期与来源约束每一条映射,含糊的对象停在未分配。</p></div><div class="card"><div class="k">输出</div><p>按机型限定的设备对象、带类型的关系与出处。</p></div></div></div></section><section><div class="wrap"><div class="kicker">03 · 证据引擎 · 检验机制</div><h2>证据支持某个具体机制吗?</h2><div class="grid g2" style="margin-top:14px"><div class="card"><p>先验测量链与物理界,再比较绝对水平、同工况机群残差、变化、分布形状与合格谱证据;证据族分开,相互矛盾的记录保持可见。</p></div><div class="card"><div class="k">输出</div><p>有界的发现、疑似机制、支持证据、矛盾项,或「需更多数据」。</p></div></div></div></section><section><div class="wrap"><div class="kicker">04 · 决策服务 · 限定动作</div><h2>现在有什么动作是有依据的?</h2><div class="grid g2" style="margin-top:14px"><div class="card"><p>写一个可问责的决定对象:状态、机制、置信、后果、选项、责任人、到期日与闭合证据放在一起;系统准备监视/计划/动作三种选项,授权工程师批准现场与高成本动作。</p></div><div class="card"><div class="k">输出</div><p>可审阅的决定、批准人与执行边界。</p></div></div></div></section><section><div class="wrap"><div class="kicker">05 · 检修学习 · 验证与复用</div><h2>动作改变了预期指标吗?</h2><div class="grid g2" style="margin-top:14px"><div class="card"><p>工作包写明部件、规程、检查点与必需记录;检修后在同工况下比较同一指标,改善支持闭合,无改善则重开机制;验证过的结果挂回本体,供下一个相似案例。</p></div><div class="card"><div class="k">输出</div><p>现场记录、修后比较、闭合或重开的调查。</p></div></div></div></section>\n<section><div class="wrap"><div class="kicker">受保护的内容</div><h2>资产专属参考保持在受保护工作区内</h2><p class="sub">特征频率、业主与场站标识、私有技术文档与台级证据留在受保护工作区;公开图只展示架构。</p></div></section>\n</div>';
  1028. const SEC_METHOD = '</div><div class="pg" id="method" data-title="方法"><section class="hero"><div class="wrap"><div class="kicker">工程方法</div><h1>六步分析与决策依据</h1><p class="lead">观澜把运行数据、设备上下文与检修历史连在一起,不隐藏不确定性。每个结果保留来源、时段与下一步验证。</p></div></section>\n<section><div class="wrap"><div class="kicker">输入</div><h2>十分钟 SCADA + 秒级数据</h2><div class="grid g3"><div class="card"><div class="k">十分钟 SCADA</div><h3>运行状态、热行为、负荷与机群比较</h3><p></p></div><div class="card"><div class="k">秒级数据</div><h3>快速暂态、控制响应与事件序列</h3><p></p></div><div class="card"><div class="k">CMS</div><h3>状态监测标量与合格谱</h3><p></p></div><div class="card"><div class="k">报警</div><h3>码按适用的整机厂码表解析</h3><p></p></div><div class="card"><div class="k">检修</div><h3>工单、检查、油样与换件</h3><p></p></div><div class="card"><div class="k">技术来源</div><h3>按机型限定的图纸、手册、规程与配置记录</h3><p></p></div></div>\n<div class="callout" style="margin-top:16px">单位、采样间隔、时间戳、哨兵值与通道活性在使用前核验。<b>有这一列不等于有活信号。</b></div></div></section>\n<section><div class="wrap"><div class="kicker">执行路径</div><h2>六步</h2><div class="rail" style="--n:6"><div class="node"><i>01</i><b>登记</b><small>来源:指纹与覆盖</small></div><div class="node"><i>02</i><b>落位</b><small>身份:机型与配置</small></div><div class="node"><i>03</i><b>验证</b><small>测量:单位、时间与活性</small></div><div class="node"><i>04</i><b>比较</b><small>状态:绝对与同工况对照</small></div><div class="node on"><i>05</i><b>检验</b><small>机制:支持与矛盾</small></div><div class="node"><i>06</i><b>发布</b><small>决定:动作、责任人与闭合</small></div></div></div></section>\n<section><div class="wrap"><div class="kicker">四道控制</div><h2>诊断结论的验证要求</h2><div class="grid g4"><div class="card"><div class="k">01</div><h3>先验测量链</h3><p>算特征之前先查静态偏置、缺失或默认值、时间戳对齐与真实采样行为。</p></div><div class="card"><div class="k">02</div><h3>匹配状态与时段</h3><p>同工况、同证据窗比较;检修与配置变更开启新时段。</p></div><div class="card"><div class="k">03</div><h3>证据保持独立</h3><p>同一传感器的多个特征仍是一族证据;共享预处理或共同测量链不构成互证。</p></div><div class="card"><div class="k">04</div><h3>缺必需证据就停</h3><p>缺必需证据只产出&quot;需更多数据&quot;,点名采集任务,禁止升级到更强的结论。</p></div></div></div></section>\n<section><div class="wrap"><div class="kicker">决定与检修</div><h2>从发现到验证过的现场结果</h2><div class="grid g3"><div class="card"><div class="k">发现</div><h3>陈述状态</h3><p>部件、疑似机制、幅度、证据、矛盾与置信。</p></div><div class="card"><div class="k">决定</div><h3>选择 监视 / 计划 / 动作</h3><p>给出后果、选项、责任人、到期日、批准与闭合证据。</p></div><div class="card"><div class="k">现场结果</div><h3>看变了什么</h3><p>同工况下比较同一指标;闭合、继续或重开机制。</p></div></div><div class="callout" style="margin-top:16px">系统准备可审阅的工作范围与闭合所需证据;<b>不签发工作许可</b>,授权工程师批准现场与高成本动作。</div></div></section>\n<section><div class="wrap"><div class="kicker">边界保持可见</div><h2>记录决定边界</h2><div class="grid g2"><div class="card"><div class="k">十分钟数据</div><h3>分不开秒级作用的机制</h3><p>秒级数据改善时序证据,但本身不建立因果。</p></div><div class="card"><div class="k">相对偏差</div><h3>只是筛查结果</h3><p>直到有绝对物理量与适用边界支持为止。</p></div><div class="card"><div class="k">无确认阳性的阈值</div><h3>可排序、可筛查</h3><p>不能确认诊断。</p></div><div class="card"><div class="k">语言模型</div><h3>解释受控对象并引用记录</h3><p>不能发明关系、补缺输入或批准检修。</p></div></div></div></section>\n</div>';
  1029. const SEC_FINDINGS = '</div><div class="pg" id="findings" data-title="经验发现"><section class="hero"><div class="wrap"><div class="kicker">现场经验</div><h1>现场经验与工程判断</h1><p class="lead">在 1.5 到 6 MW 机群上的工作,形成了一套鉴定证据、检验机制、支持检修决定的实用方法。</p><div class="chips"><span class="chip ev">59 项内部检查 → 8 条公开教训</span></div></div></section>\n<section><div class="wrap"><div class="grid g4"><div class="card"><div class="k">分析足迹</div><h3>60+ 个风场</h3><p>陆上 + 海上</p></div><div class="card"><div class="k">容量范围</div><h3>1.5–6 MW</h3><p></p></div><div class="card"><div class="k">架构</div><h3>双馈 · 直驱 · 半直驱</h3><p></p></div><div class="card"><div class="k">平台</div><h3>远景 · 金风 · 明阳 · 西门子</h3><p></p></div></div><p class="sub" style="margin-top:12px">项目身份、业主、场站、机组标识与资产专属诊断参数不公开。</p></div></section>\n<section><div class="wrap"><div class="kicker">八条反复出现的教训</div><h2>会改变工程结论的检查</h2><div class="grid g4" style="margin-top:14px"><div class="card"><div class="k">A · 采样</div><h3>让样本匹配问题</h3><p>比较机组或机群之前,先声明独立单位、聚合层级、对照组、分母与时间窗。</p></div><div class="card"><div class="k">B · 归因</div><h3>要机制,也要时间顺序</h3><p>共现与相关只能筛查假设;因果归因还需要物理机制、预期方向与事件级先后。</p></div><div class="card"><div class="k">C · 测量</div><h3>先验测量链</h3><p>有值的字段、固定偏置或安静的通道未必是活信号;先查单位、时间戳、采样、哨兵、偏置与传感器状态。</p></div><div class="card"><div class="k">D · 聚合</div><h3>先保留事件再汇总</h3><p>平均会抹掉极端、持续时长与受影响比例;出机群统计前先看点级形状、缺口与边界。</p></div><div class="card"><div class="k">E · 判据</div><h3>写明每条判据的适用域</h3><p>单位、阈值、方差、运行状态、控制时长、必需输入与边界附近的情形都随结果一起给出。</p></div><div class="card"><div class="k">F · 守护</div><h3>验证守护真的在跑</h3><p>文档里的检查在触发条件与生产调用路径确认前不提供任何保护,且失败必须可见。</p></div><div class="card"><div class="k">G · 管线</div><h3>让检查器失败来测它</h3><p>向隔离的样例注入已知错误,证明真实入口拒绝它,再恢复有效输入证明通过。</p></div><div class="card"><div class="k">H · 决定</div><h3>筛查与动作分开</h3><p>技术发现只有在范围、不确定性、后果、选项、责任人与批准明确后,才成为检修决定。</p></div></div></div></section>\n<section><div class="wrap"><div class="kicker">经验怎么用</div><h2>从异常发现到检修建议</h2><div class="rail" style="--n:3"><div class="node"><i>01</i><b>鉴定证据</b><small>现有数据能支持什么、不能支持什么、哪项检查补上缺口</small></div><div class="node"><i>02</i><b>落位设备上下文</b><small>通过本体映射到正确的机型、配置、部件、测点与疑似机制</small></div><div class="node on"><i>03</i><b>准备决定</b><small>给出监视/计划/动作选项、后果、现场范围、必需记录、批准人与闭合证据</small></div></div>\n<div class="callout" style="margin-top:22px"><b>经验边界。</b>经验横跨陆上与海上;换环境或换不熟悉的配置,复用任何判据或结论前都要重新验证控制、测量链、部件结构与运行制度。</div></div></section>\n<section id="contract-claims" data-contract="362a00a7649889ea79c3df3108c8b21f439da2537c93af8517da5bd92c855bba" data-version="v0"><div class="wrap"><div class="kicker">本场结论 · 契约生成</div><h2>定论与准定论·预警 (0 条)</h2><p class="sub">由结构化事实契约 362a00a7649889ea 渲染 (共 6 条); 数据、时间窗、结论只经契约更新, 本段禁手改 (release-manifest.content_contract)。</p><div style="overflow:auto"><table><tr><th>claim</th><th>裁决</th><th>结论</th><th>系统</th><th>窗 (天)</th></tr></table></div><h2 style="margin-top:22px">候选 · 参考 · 证据不足 (6 条, 按系统)</h2><details><summary>报警面 · 1 条 (候选 1)</summary><table><tr><th>claim</th><th>裁决</th><th>结论</th><th>缺项</th></tr><tr><td><code>RD-LEDGER-ALM-01</code></td><td>候选</td><td>报警集中: WTG08 5148 条(机群中位 616 条的 8.4 倍)</td><td>报警条数不等于缺陷严重度: 单码抖动(同一码反复报)会抬高条数; 未做同码基线归一。</td></tr></table></details><details><summary>传动链 · 1 条 (候选 1)</summary><table><tr><th>claim</th><th>裁决</th><th>结论</th><th>缺项</th></tr><tr><td><code>RD-LEDGER-WO-01</code></td><td>候选</td><td>重复检修: WTG33 的「偏航单元」累计 81 次(窗内)</td><td>检修台账记录的是&quot;做过什么&quot;, 不等于&quot;坏过几次&quot;: 同一部件的例行检查也会计次; 未做计划性/故障性停机区分(缺 work_type 字段)。</td></tr></table></details><details><summary>发电量 · 1 条 (参考 1)</summary><table><tr><th>claim</th><th>裁决</th><th>结论</th><th>缺项</th></tr><tr><td><code>RD-LEDGER-LOSS-01</code></td><td>参考</td><td>停机损失集中: WTG05 占全场停机损失 10%(2,874 MWh, 全场停机 29,368 MWh)</td><td>损失是&quot;损失了多少电&quot;, 不是&quot;为什么损失&quot;; 含调度令停机(非缺陷)与限电, 需与故障类分开看。</td></tr></table></details><details><summary>温度面 · 1 条 (候选 1)</summary><table><tr><th>claim</th><th>裁决</th><th>结论</th><th>缺项</th></tr><tr><td><code>RD-LEDGER-TEMP-01</code></td><td>候选</td><td>温度相对离群: WTG01 的 tmp_wtc_TrafoTMa_mean 在 3750.0 功率档偏离机群中位 18.0K(工况窗 ref)</td><td>同工况中位比对的是&quot;机群&quot;, 若全机群共因升温和(环境/冷却水温)则判据失效; 未接绝对限值(无 ISO/厂商温度锚)。</td></tr></table></details><details><summary>发电量, 功率曲线 · 1 条 (参考 1)</summary><table><tr><th>claim</th><th>裁决</th><th>结论</th><th>缺项</th></tr><tr><td><code>RD-LEDGER-PC-01</code></td><td>参考</td><td>功率曲线偏离: WTG03 同风速下功率偏差 -147 kW(判别: 风速计偏高候选(A类)); 全场判别分布 {&#x27;欠发候选(筛查级)&#x27;: 3, &#x27;风速计偏高候选(A类)&#x27;: 1}</td><td>同风速残差受机位风资源(尾流/扇区)影响; 未做机位修正, 故只作&quot;待核&quot;不作缺陷结论。</td></tr></table></details><details><summary>振动面 · 1 条 (候选 1)</summary><table><tr><th>claim</th><th>裁决</th><th>结论</th><th>缺项</th></tr><tr><td><code>RD-LEDGER-VIB-01</code></td><td>候选</td><td>振动面过闸线: 3 条特征线达「候选」及以上(13#·hs_rotor·BPFO(外圈), 35#·hs_rotor·BPFO(外圈), 6#·hs_rotor·BPFO(外圈))</td><td>机制未定 ⇒ **不命名部件**(滚道/圈侧须实物或换件闭环); 峰值拾取为本器口径(目标频率 ±2 bin 取最大), 无正样本锚 ⇒ 封顶「候选」, 不出「</td></tr></table></details><h2 style="margin-top:22px">已撤回 (0 条)</h2><ul></ul></div></section><!--/contract-claims--></div>';
  1030. const SEC_CASE_HYDRAULIC = '</div><div class="pg" id="case_hydraulic" data-title="案例·液压"><section class="hero"><div class="wrap"><div class="kicker">发现案例 · 系统① 液压 · 变桨回路与高速轴制动回路</div><h1>八台机组变桨蓄能器失效</h1><p class="lead">如东海上 38 台 SWT-4.0-130。通过泵加载特征评估 70 号蓄能器储能能力,形成逐台复核与处置建议。证据状态以各项记录为准。</p>\n<div class="chips"><span class="chip s gd">健康 26 台</span><span class="chip s al">气容丧失 8 台</span><span class="chip s ob">边界 / 参数侧 5 台</span><span class="chip ev">证据窗 运行 → 2026-07-07</span><span class="chip ev">已处置 1 台 (4#) · 恢复基准待核对</span></div></div></section>\n<section><div class="wrap"><div class="kicker">01 · 问</div><h2>评估对象、监测量与适用条件</h2><div class="grid g5"><div class="card"><div class="k">系统</div><h3>液压 · 变桨回路</h3><p>一个油箱、两条回路:变桨大站 400 L / 245 bar,HS 制动器由大站经 P BRAKE 分出。</p></div><div class="card"><div class="k">监测量</div><h3>泵单次加载输出量 V_cyc</h3><p>加载时长 × 排量 36.8 L/min,来自 10-min 与秒级 SCADA 的泵启停信号。</p></div><div class="card"><div class="k">方向</div><h3>V_cyc 降低:单次进油量减少</h3><p>蓄能器气容丧失后,每次加载只能推进很少的油。</p></div><div class="card"><div class="k">评估限制</div><h3>缺 Δp 或未逐台落值</h3><p>横坐标缺失的台不外推;档案给组区间的台按区间画,不当单点。</p></div><div class="card"><div class="k">缺失资料</div><h3>轮毂 106 号无连续通道</h3><p>只能靠机组自检容量检查记录(235 bar 起测)。</p></div></div></div></section>\n<section><div class="wrap"><div class="kicker">02 · 证</div><h2>两组蓄能器,分工完全不同,判据只对 70 号有效</h2><div class="grid g2"><div class="card"><div class="k">70 号 · 泵站近端缓冲</div><h3>10 L / 180 bar</h3><p>活塞式,经 ø7 节流直连泵压主管,全额参与蓄能循环;A 类失效全在此件。系统总容变按仿真标定式分解:C = C管路 + C70 + 0.095·C106;预充 180 bar 由 38 台实测容变反算证实 (0.0839 ≈ 实测 0.087 L/bar)。</p></div><div class="card"><div class="k">106 号 · 轮毂顺桨主源</div><h3>6 × 30 L / 125 bar</h3><p>无连续 SCADA 通道,经长管 + 旋转接头 + 118 单向阀,对蓄能循环仅弱耦合 (系数 0.095)。有自检容量检查可查;最近记录 140 bar @20 ℃ = 正常。</p></div></div>\n<div class="grid g2" style="margin-top:16px"><div class="card"><div class="k">健康 · 26 台</div><h3>3.1 – 7.2 L</h3><p>全部机群带中位 4.4 L。独立核验:按变桨活动带标定的仿真健康点 4.9 L 落在带内,与中位差 12%,仿真结果与运行统计对照;独立性需另行核查。同期泵周期 96 s、加载 6 次/10 min、带宽 50 bar、油温 38 ℃、超压 0%。</p></div><div class="card"><div class="k">气容丧失 · 8 台</div><h3>0.52 L</h3><p>A 类中位 0.85 秒,为健康中位的 1/8.5。本质是系统总容变 C 由 0.087 塌到 0.0037 L/bar = 23 倍(剩下的只有纯管路柔度)。8# 实测同期:周期 2.6 s、加载 228 次/10 min、带宽 135、油温 51 ℃、超压占时 98.3%。</p></div></div></div></section>\n<section><div class="wrap"><div class="kicker">03 · 裁</div><h2>失效机理与鉴别依据</h2><div class="grid g3"><div class="card"><div class="k">阶跃变化的机理解释</div><h3>循环尖峰 → 铁片抬升 → 118 单向阀闭 → 轮毂 106 退出循环</h3><p>C 直接塌到纯管路 ≈0.003(对齐 8# 实测 0.0037)。一旦越过这个连锁点,指标阶跃下落,没有中间态。</p></div><div class="card"><div class="k">为什么不是漏油</div><h3>外漏只增每循环消耗,不改系统容变 C</h3><p>25# 泵输出 1.46× 而容变 C 仍在健康区 0.62 ⇒ 主导因素在消耗侧,先查漏点。但 C 已非满值,不能断言蓄能器完好,该台已补&quot;同次测预充&quot;。</p></div><div class="card"><div class="k">为什么不是泵磨损</div><h3>排量下降会拉长单次加载而不改频次</h3><p>实测 8# 是频次由 6 升到 228 次/10 min(38×)且单次时长反缩短,该特征不支持单独以泵磨损解释,仍需结合现场检查。</p></div></div><div class="callout" style="margin-top:16px">口径边界:反算出的 C 绝对值依赖假定排量 36.8 L/min;分型与健康带用的是实测 V_cyc,不受此影响。</div></div></section>\n<section><div class="wrap"><div class="kicker">04 · 分型</div><h2>V_cyc × Δp 二维判别:分型与处置参考</h2><div class="grid g4"><div class="card"><div class="k">右下</div><h3>换 70 号蓄能器</h3><p>压力波动范围扩大,单次进油量降低。7#、10#、8#、3#、20#、2#、35#。</p></div><div class="card"><div class="k">右上</div><h3>不换蓄能器</h3><p>压力波动范围扩大,但单次进油量保持 ⟹ 漏油或参数。25#。</p></div><div class="card"><div class="k">左上</div><h3>正常 · 30 台</h3><p>Δp 锁定 52,进油量满。</p></div><div class="card"><div class="k">左下 / 未落值</div><h3>参数侧 · 先测</h3><p>Δp 过窄属参数侧;Δp 未落值的台横坐标无处安放,未测 ≠ 正常。7#、10# 档案无 Δp,不因缺少坐标而取消现场复核。</p></div></div></div></section>\n<section><div class="wrap"><div class="kicker">05 · 改</div><h2>逐台处置建议</h2><table><tr><th>台</th><th>V_cyc (L/次) 原 → 现</th><th>Δp</th><th>判别</th><th>处置</th></tr><tr><td>7#</td><td class="n">9.61 → 1.8</td><td>—</td><td>气容丧失 · 全场衰减最快</td><td>换 70 号蓄能器</td></tr><tr><td>10#</td><td class="n">2.9 → 0.47</td><td>—</td><td>气容丧失 · 最新一起阶跃下降</td><td>换 70 号蓄能器</td></tr><tr><td>3# / 20# / 2# / 35#</td><td class="n">0.47–0.63 (组区间)</td><td>宽</td><td>气容丧失 · 档案未逐台落值</td><td>换 70 号蓄能器 (逐台落值后下单)</td></tr><tr><td>8#</td><td class="n">1.45 → 0.5</td><td>宽</td><td>气容丧失 · 频次 6 → 228 次/10 min</td><td>换 70 号蓄能器</td></tr><tr><td>21# / 28# / 19#</td><td class="n">0.98 / 1.15 / 0.52</td><td>宽</td><td>边界台</td><td>先测预充定型, 再定</td></tr><tr><td>25#</td><td class="n">4.33</td><td>宽 (泵输出 1.46×)</td><td>漏油或参数, 不是蓄能器</td><td>先现场核, 不换蓄能器</td></tr><tr><td>14# / 32#</td><td class="n">3.4–4.4 (健康带内)</td><td>窄</td><td>压力带宽问题</td><td>参数侧核</td></tr><tr><td>4#</td><td class="n">0.53 → 4.84</td><td>—</td><td>已处置 · 恢复基准待核对</td><td>验收:核对基准后同工况复核</td></tr></table><div class="callout warn" style="margin-top:14px">备件:70 号蓄能器 物资编码 A9B10051506(10 L / 180 bar 活塞式),编码取自备件系统「物资编码」字段;库存列表无导出日期,下单前必核 ERP 现存量,禁按本表采购。凡标「件号待确认」的条目,采购前联系现场核实。</div></div></section>\n<section><div class="wrap"><div class="kicker">06 · 顺桨安全</div><h2>顺桨储能与自检证据</h2><div class="grid g3"><div class="card"><div class="k">70 号失效</div><h3>SCADA 循环特征变化明显</h3><p>失电顺桨主源是轮毂 106(103 → 121 路),70 号不在此路。数据窗内 4 次全场停电全部安全顺桨、零超速。</p></div><div class="card"><div class="k">106 失氮(假设情形)</div><h3>SCADA 循环指标识别能力有限</h3><p>循环侧只有中度变化(吞吐约 2 L),与个体基线难分辨;能看见它的只有机组自检容量检查(UP03284 组,从轮毂 235 bar 起测)。后果最重:失电顺桨失去能量源。</p></div><div class="card"><div class="k">两者同时失效</div><h3>SCADA 上与单独 70 号失效不可分辨</h3><p>而实际后果是顺桨失败 ⇒ 不能仅凭 SCADA 排除液压失效。两套监测互补、同时关注。</p></div></div><div class="callout" style="margin-top:16px">将现有自检记录按月归档,用于跟踪 106 号蓄能器的容量变化。</div></div></section>\n<section><div class="wrap"><div class="kicker">07 · 量</div><h2>修后验证</h2><div class="grid g3"><div class="card"><div class="k">同工况复核</div><h3>同一指标 V_cyc,同一变桨活动带</h3><p>4# 已处置:0.53 → 4.84 L。4.84 L 低于此处所列 5.0–11.7 L 下限,与页面另一处 3.1–7.2 L 基准不一致。须核对工况与基准来源,当前不能据此确认修后恢复。</p></div><div class="card"><div class="k">阴性对照</div><h3>换蓄能器不该改变能量产出</h3><p>4# 换件在 E1 阴性对照池中(优化窗 22 天 &lt; 28 → INSUFFICIENT,未出数),不夸大收益。</p></div><div class="card"><div class="k">记录归档</div><h3>工单 → 复核 → 台账</h3><p>每台的处置进检修任务,复核结果挂回本体;106 号自检记录进月度台账。</p></div></div><div class="chips" style="margin-top:14px"></div><p class="sub" style="margin-top:12px">依据:U6_变桨液压仿真台(SWT-4.0-130,图纸 1034384 / 1034386;参数标定自本场 38 台实测与原厂图纸,2026-08-13 现场证实)。本页数字全部来自《如东_液压系统治理清单_v1.2》,未重跑。<a href="#sim" data-nav="sim">仿真台在"仿真与回放"页可交互 →</a></p></div></section>\n</div>';
  1031. const _sfc_main$e = /* @__PURE__ */ defineComponent({
  1032. __name: "PortalSections",
  1033. __ssrInlineRender: true,
  1034. setup(__props) {
  1035. const SUBS = [
  1036. { id: "architecture", label: "系统架构", html: SEC_ARCHITECTURE },
  1037. { id: "method", label: "方法", html: SEC_METHOD },
  1038. { id: "findings", label: "经验发现", html: SEC_FINDINGS },
  1039. { id: "case_hydraulic", label: "案例·液压", html: SEC_CASE_HYDRAULIC }
  1040. ];
  1041. const cur = ref("architecture");
  1042. const body = computed(() => (SUBS.find((s) => s.id === cur.value) || SUBS[0]).html);
  1043. const missing = computed(() => !body.value);
  1044. return (_ctx, _push, _parent, _attrs) => {
  1045. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("ps.eyebrow"))}</div><div class="panel mb" style="${ssrRenderStyle({ "display": "flex", "gap": "8px", "flex-wrap": "wrap" })}"><!--[-->`);
  1046. ssrRenderList(SUBS, (s) => {
  1047. _push(`<button class="${ssrRenderClass([{ on: s.id === cur.value }, "pill"])}">${ssrInterpolate(s.label)}</button>`);
  1048. });
  1049. _push(`<!--]--></div>`);
  1050. if (missing.value) {
  1051. _push(`<div class="panel"><p class="small muted">${ssrInterpolate(unref(t)("ps.missing"))}</p></div>`);
  1052. } else {
  1053. _push(`<div class="ps-embed">${body.value ?? ""}</div>`);
  1054. }
  1055. _push(`</section>`);
  1056. };
  1057. }
  1058. });
  1059. const _sfc_setup$e = _sfc_main$e.setup;
  1060. _sfc_main$e.setup = (props, ctx) => {
  1061. const ssrContext = useSSRContext();
  1062. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/PortalSections.vue");
  1063. return _sfc_setup$e ? _sfc_setup$e(props, ctx) : void 0;
  1064. };
  1065. const BASE = "/web/portal/";
  1066. const _sfc_main$d = /* @__PURE__ */ defineComponent({
  1067. __name: "PortalView",
  1068. __ssrInlineRender: true,
  1069. setup(__props) {
  1070. const frame = ref(null);
  1071. const state = ref("loading");
  1072. const info = ref("");
  1073. async function text(url) {
  1074. const r = await fetch(url);
  1075. if (!r.ok) throw new Error(url + " " + r.status);
  1076. return await r.text();
  1077. }
  1078. onMounted(async () => {
  1079. try {
  1080. let shell = await text(BASE + "shell.html");
  1081. const ids = Array.from(shell.matchAll(/<!--@TEMPLATE:([^>]+?)-->/g)).map((m) => m[1]);
  1082. const parts = await Promise.all(ids.map(async (id) => {
  1083. try {
  1084. return await text(BASE + "templates/" + encodeURIComponent(id) + ".html");
  1085. } catch {
  1086. return "";
  1087. }
  1088. }));
  1089. ids.forEach((id, i) => {
  1090. shell = shell.replace("<!--@TEMPLATE:" + id + "-->", parts[i]);
  1091. });
  1092. try {
  1093. const gs = await text(BASE + "governance_sources.json");
  1094. shell = shell.replace(
  1095. "<!--@GOVERNANCE_SOURCES-->",
  1096. '<script type="application/json" id="__governance_sources">' + gs.replace(/</g, "\\u003c") + "<\/script>"
  1097. );
  1098. } catch {
  1099. }
  1100. info.value = t("pv.assembled", { n: ids.length });
  1101. const blob = new Blob([shell], { type: "text/html;charset=utf-8" });
  1102. if (frame.value) frame.value.src = URL.createObjectURL(blob);
  1103. state.value = "ready";
  1104. } catch (e) {
  1105. info.value = String(e && e.message || e);
  1106. state.value = "error";
  1107. }
  1108. });
  1109. return (_ctx, _push, _parent, _attrs) => {
  1110. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("pv.eyebrow"))}</div>`);
  1111. if (state.value !== "ready") {
  1112. _push(`<div class="panel mb"><p class="small muted">${ssrInterpolate(state.value === "loading" ? unref(t)("pv.loading") : info.value)}</p></div>`);
  1113. } else {
  1114. _push(`<div class="panel mb"><p class="small muted">${ssrInterpolate(info.value)}</p></div>`);
  1115. }
  1116. _push(`<iframe class="portal-frame" title="观澜门户" style="${ssrRenderStyle({ "width": "100%", "height": "calc(100vh - 210px)", "border": "0" })}"></iframe></section>`);
  1117. };
  1118. }
  1119. });
  1120. const _sfc_setup$d = _sfc_main$d.setup;
  1121. _sfc_main$d.setup = (props, ctx) => {
  1122. const ssrContext = useSSRContext();
  1123. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/PortalView.vue");
  1124. return _sfc_setup$d ? _sfc_setup$d(props, ctx) : void 0;
  1125. };
  1126. const ST$1 = { 危险: "bad", 报警: "warn", 不可判: "unk", 良好: "ok", 优秀: "ex" };
  1127. const ST_ALIAS$1 = {
  1128. critical: "bad",
  1129. alarm: "warn",
  1130. undeterminable: "unk",
  1131. indeterminate: "unk",
  1132. "not evaluable": "unk",
  1133. good: "ok",
  1134. excellent: "ex",
  1135. warn: "warn",
  1136. bad: "bad",
  1137. ok: "ok",
  1138. unk: "unk",
  1139. ex: "ex"
  1140. };
  1141. const stateKey$1 = (value) => ST$1[String(value)] || ST_ALIAS$1[String(value)];
  1142. const STEP = {
  1143. 证据: "evidence",
  1144. 机制: "mechanism",
  1145. 判级: "rating",
  1146. 排期: "schedule",
  1147. 动作: "action",
  1148. 验收: "acceptance"
  1149. };
  1150. const stKey$1 = (k) => "st." + (k === "ex" ? "excellent" : k);
  1151. const unitNo = (wtg) => String(wtg).replace("WTG", "").replace(/^0/, "") + "#";
  1152. const fmt = (n, d = 0) => {
  1153. if (n == null || typeof n === "number" && isNaN(n)) return "—";
  1154. const num = Number(n);
  1155. if (isNaN(num)) return "—";
  1156. return num.toLocaleString("zh-CN", { maximumFractionDigits: d, minimumFractionDigits: d });
  1157. };
  1158. const stWord$1 = (zh) => {
  1159. const k = stateKey$1(zh);
  1160. return k ? t(stKey$1(k)) : String(zh ?? "");
  1161. };
  1162. const COLS = ["振动", "温度", "润滑", "油液", "报警", "工单"];
  1163. function unitState(row2) {
  1164. const ks = COLS.map((c) => stateKey$1((row2?.[c] || {}).ostate)).filter(Boolean);
  1165. if (!ks.length) return "unk";
  1166. if (ks.includes("bad")) return "bad";
  1167. if (ks.includes("warn")) return "warn";
  1168. const seen = ks.filter((k) => k !== "unk");
  1169. if (!seen.length) return "unk";
  1170. return seen.includes("ok") ? "ok" : "ex";
  1171. }
  1172. const SEV = {
  1173. bad: "critical",
  1174. warn: "warning",
  1175. unk: "unknown",
  1176. ok: "good",
  1177. ex: "excellent"
  1178. };
  1179. const winName = (w) => String(w ?? "");
  1180. const SYS9 = ["叶片与叶根", "变桨", "偏航", "齿轮箱", "发电机", "主轴承", "变流器", "塔架与基础", "主控与传感网"];
  1181. const SYS_KEY = {
  1182. 叶片与叶根: "blade",
  1183. 变桨: "pitch",
  1184. 偏航: "yaw",
  1185. 齿轮箱: "gearbox",
  1186. 发电机: "generator",
  1187. 主轴承: "main_bearing",
  1188. 变流器: "converter",
  1189. 塔架与基础: "tower",
  1190. 主控与传感网: "control",
  1191. 主轴: "main_bearing",
  1192. 其他: "other"
  1193. };
  1194. const SYS_ZH = (() => {
  1195. const out = {};
  1196. for (const zh of SYS9) out[SYS_KEY[zh]] = zh;
  1197. for (const [zh, k] of Object.entries(SYS_KEY)) if (!(k in out)) out[k] = zh;
  1198. return out;
  1199. })();
  1200. const sysSlug = (value) => SYS_KEY[String(value)] || String(value);
  1201. const sysFromSlug = (value) => SYS_ZH[String(value)] || String(value);
  1202. const sysName = (value) => {
  1203. const key = SYS_KEY[String(value)] || String(value);
  1204. return SYS_ZH[key] ? t("sys." + key) : String(value ?? "");
  1205. };
  1206. const partName = (p) => sysName(p);
  1207. const _sfc_main$c = /* @__PURE__ */ defineComponent({
  1208. __name: "Component",
  1209. __ssrInlineRender: true,
  1210. props: {
  1211. d: {},
  1212. sys: {}
  1213. },
  1214. setup(__props) {
  1215. const props = __props;
  1216. const SD = computed(() => props.d?.sysdist || {});
  1217. const rel = computed(() => props.d?.rel && !props.d.rel.err ? props.d.rel : null);
  1218. const sysv = computed(() => props.sys ? sysFromSlug(props.sys) : null);
  1219. const cards = computed(() => SYS9.filter((s) => SD.value[s]).map((s) => {
  1220. const dist = SD.value[s];
  1221. const a = dist["报警"] || 0;
  1222. const u = dist["不可判"] || 0;
  1223. const N = Object.values(dist).reduce((x, y) => x + y, 0) || 1;
  1224. const col = a >= 8 ? "var(--critical)" : a >= 3 ? "var(--warning)" : a ? "var(--accent)" : "var(--good)";
  1225. return { s, dist, a, u, N, col };
  1226. }));
  1227. function hitHtml(w) {
  1228. return (w.syss || []).map((x) => `<a href="/problem/${w.t}/${encodeURIComponent(sysFromSlug(x) || x)}">${sysName(x)}</a>`).join(" · ");
  1229. }
  1230. const relEh = computed(() => {
  1231. const items = ((props.d?.control || {}).energy || {}).items || [];
  1232. const hit = items.filter((x) => x.k === "停机").map((x) => x.h)[0];
  1233. return hit == null ? null : hit;
  1234. });
  1235. const relRows = computed(() => {
  1236. const tb = rel.value?.表 || [];
  1237. const maxH = Math.max(1, ...tb.map((r) => r.停机时长h));
  1238. return tb.map((r) => ({
  1239. r,
  1240. maxH,
  1241. sev: r.部件 === "无记录停机" ? "var(--unknown)" : r.外部 ? "var(--accent)" : r.占停机 >= 0.06 ? "var(--critical)" : r.占停机 >= 0.03 ? "var(--warning)" : "var(--good)"
  1242. }));
  1243. });
  1244. const detail = computed(() => {
  1245. const s = sysv.value;
  1246. if (!s) return null;
  1247. const rows = (props.d?.systems || {})[s] || [];
  1248. const dist = SD.value[s] || {};
  1249. const gd = (props.d?.systems || {})[s + "·良好"] || [];
  1250. const cnt = (k) => dist[k] || 0;
  1251. const N = Object.values(dist).reduce((a, b) => a + b, 0) || 1;
  1252. return {
  1253. s,
  1254. N,
  1255. gd,
  1256. cnt,
  1257. al: rows.filter((r) => r.st === "报警"),
  1258. un: rows.filter((r) => r.st === "不可判"),
  1259. dist
  1260. };
  1261. });
  1262. return (_ctx, _push, _parent, _attrs) => {
  1263. if (detail.value) {
  1264. _push(`<section${ssrRenderAttrs(_attrs)}><p class="small"><a href="#tab=component" data-back="1">${ssrInterpolate(unref(t)("ui.back_to", { tab: unref(t)("tab.component") }))}</a></p><div class="panel mb"><h3>${ssrInterpolate(unref(t)("cmp.detail_head", { sys: unref(sysName)(detail.value.s), n: detail.value.N }))}</h3><div style="${ssrRenderStyle({ "display": "flex", "height": "12px", "border-radius": "4px", "overflow": "hidden", "margin": "8px 0 4px" })}"><!--[-->`);
  1265. ssrRenderList([["报警", "critical"], ["不可判", "unknown"], ["良好", "good"], ["优秀", "excellent"]], ([k, c]) => {
  1266. _push(`<i style="${ssrRenderStyle(`width:${(detail.value.cnt(k) / detail.value.N * 100).toFixed(1)}%;background:var(--${c})`)}"></i>`);
  1267. });
  1268. _push(`<!--]--></div><div class="small muted">${ssrInterpolate(unref(t)("cmp.counts", { a: detail.value.cnt("报警"), u: detail.value.cnt("不可判"), g: detail.value.cnt("良好"), e: detail.value.cnt("优秀") }))}</div></div><!--[-->`);
  1269. ssrRenderList([detail.value.al, detail.value.un], (list, kind) => {
  1270. _push(`<div class="panel mb" style="${ssrRenderStyle(list.length ? null : { display: "none" })}"><h3>${ssrInterpolate(kind === 0 ? unref(t)("cmp.alarm_units") : unref(t)("cmp.unk_units"))} ${ssrInterpolate(unref(t)("cmp.n_units", { n: list.length }))}</h3><div class="tblwrap"><table><tr><th style="${ssrRenderStyle({ "width": "74px" })}">${ssrInterpolate(unref(t)("ui.unit"))}</th><th>${ssrInterpolate(unref(t)("cmp.why"))}</th><th class="r" style="${ssrRenderStyle({ "width": "96px" })}">${ssrInterpolate(unref(t)("cmp.detail"))}</th></tr><!--[-->`);
  1271. ssrRenderList(list, (r) => {
  1272. _push(`<tr><td><a${ssrRenderAttr("href", `/turbine/${r.t}`)}><b>${ssrInterpolate(unref(unitNo)(r.t))}</b></a></td><td class="small">${ssrInterpolate(r.why)}</td><td class="r"><a class="small"${ssrRenderAttr("href", `/problem/${r.t}/${encodeURIComponent(detail.value.s)}`)}>${ssrInterpolate(unref(t)("cmp.detail_link"))}</a></td></tr>`);
  1273. });
  1274. _push(`<!--]--></table></div></div>`);
  1275. });
  1276. _push(`<!--]-->`);
  1277. if (detail.value.gd.length) {
  1278. _push(`<details class="panel mb"><summary style="${ssrRenderStyle({ "cursor": "pointer", "font-weight": "600" })}">${ssrInterpolate(unref(t)("cmp.good_units", { n: detail.value.gd.length }))}</summary><div class="tblwrap"><table style="${ssrRenderStyle({ "margin-top": "10px" })}"><tr><th style="${ssrRenderStyle({ "width": "74px" })}">${ssrInterpolate(unref(t)("ui.unit"))}</th><th>${ssrInterpolate(unref(t)("cmp.why"))}</th></tr><!--[-->`);
  1279. ssrRenderList(detail.value.gd, (r) => {
  1280. _push(`<tr><td><b>${ssrInterpolate(unref(unitNo)(r.t))}</b></td><td class="small">${ssrInterpolate(r.why)}</td></tr>`);
  1281. });
  1282. _push(`<!--]--></table></div></details>`);
  1283. } else {
  1284. _push(`<!---->`);
  1285. }
  1286. if (!detail.value.al.length && !detail.value.un.length && !detail.value.gd.length) {
  1287. _push(`<div class="panel"><p class="small">${ssrInterpolate(unref(t)("cmp.none"))}</p></div>`);
  1288. } else {
  1289. _push(`<!---->`);
  1290. }
  1291. _push(`</section>`);
  1292. } else {
  1293. _push(`<!--[-->`);
  1294. if (__props.d.sysmx_pending) {
  1295. _push(`<section><div class="panel mb small" style="${ssrRenderStyle({ "border-left": "3px solid var(--accent)" })}"><b>${ssrInterpolate(unref(t)("cmp.grading_pending"))}</b> ${ssrInterpolate(unref(t)("cmp.grading_pending_why", { span: __props.d.sysmx_span || "" }))}</div></section>`);
  1296. } else {
  1297. _push(`<!---->`);
  1298. }
  1299. _push(`<section><div class="eyebrow">${ssrInterpolate(unref(t)("cmp.by_system"))}</div><div class="panel mb"><div style="${ssrRenderStyle({ "display": "grid", "grid-template-columns": "repeat(auto-fit,minmax(170px,1fr))", "gap": "10px" })}"><!--[-->`);
  1300. ssrRenderList(cards.value, (c) => {
  1301. _push(`<div class="sysc" role="button" tabindex="0"${ssrRenderAttr("data-sys", unref(sysSlug)(c.s))} style="${ssrRenderStyle(`cursor:pointer;border-left:3px solid ${c.col}`)}"><div class="t"><b>${ssrInterpolate(unref(sysName)(c.s))}</b></div><div class="bar"><!--[-->`);
  1302. ssrRenderList([["报警", "critical"], ["不可判", "unknown"], ["良好", "good"], ["优秀", "excellent"]], ([k, col]) => {
  1303. _push(`<i style="${ssrRenderStyle([
  1304. `width:${(c.dist[k] / c.N * 100).toFixed(1)}%;background:var(--${col})`,
  1305. c.dist[k] ? null : { display: "none" }
  1306. ])}"></i>`);
  1307. });
  1308. _push(`<!--]--></div><div class="small muted"><span>${unref(t)("cmp.alarm_n", { a: `<b style="color:${c.col}">${c.a}</b>` }) ?? ""}</span>${ssrInterpolate((c.u ? unref(t)("cmp.unk_n", { u: c.u }) : "") + unref(t)("cmp.fleet_n", { n: c.N }))}</div></div>`);
  1309. });
  1310. _push(`<!--]--></div><p class="small muted" style="${ssrRenderStyle({ "margin-top": "8px" })}">${ssrInterpolate(unref(t)("cmp.cards_note"))}</p></div></section>`);
  1311. if (__props.d.rel && __props.d.rel.err) {
  1312. _push(`<section><div class="panel mb" style="${ssrRenderStyle({ "border-left": "3px solid var(--critical)" })}"><p class="small">${ssrInterpolate(unref(t)("flt.block_err", { k: "rel", e: __props.d.rel.err }))}</p></div></section>`);
  1313. } else if (rel.value) {
  1314. _push(`<section><div class="eyebrow">${ssrInterpolate(unref(t)("cmp.rel_eyebrow", { win: rel.value.窗 }))}</div><div class="panel mb"><h3>${ssrInterpolate(unref(t)("cmp.rel_head"))} <span class="small muted" style="${ssrRenderStyle({ "font-weight": "400" })}">${ssrInterpolate(unref(t)("cmp.rel_meta", { t: rel.value.总停机h, a: rel.value.设备类停机h, b: rel.value.外部类停机h }))}</span></h3><p class="small muted" style="${ssrRenderStyle({ "margin": "-2px 0 8px" })}">${ssrInterpolate(unref(t)("cmp.rel_note", { h: relEh.value == null ? "" : " " + unref(fmt)(relEh.value) + "h" }))}</p><div class="tblwrap"><table><tr><th>${ssrInterpolate(unref(t)("cmp.rel_part"))}</th><th>${ssrInterpolate(unref(t)("cmp.rel_h"))}</th><th class="r">${ssrInterpolate(unref(t)("cmp.rel_ev"))}</th><th class="r">${ssrInterpolate(unref(t)("cmp.rel_mtbo"))}</th><th class="r">${ssrInterpolate(unref(t)("cmp.rel_mdt"))}</th><th class="r">${ssrInterpolate(unref(t)("cmp.rel_units"))}</th><th>${ssrInterpolate(unref(t)("cmp.rel_fm"))}</th></tr><!--[-->`);
  1315. ssrRenderList(relRows.value, (x) => {
  1316. _push(`<tr><td><b>${ssrInterpolate(unref(partName)(x.r.部件))}</b>`);
  1317. if (x.r.外部) {
  1318. _push(`<span>${ssrInterpolate(" ")}<span class="badge">${ssrInterpolate(unref(t)("cmp.external"))}</span></span>`);
  1319. } else {
  1320. _push(`<!---->`);
  1321. }
  1322. _push(`</td><td><i class="bar" style="${ssrRenderStyle(`width:${Math.round(x.r.停机时长h / x.maxH * 72)}px;background:${x.sev}`)}"></i><span class="num small">${ssrInterpolate(x.r.停机时长h)}h · ${ssrInterpolate((x.r["占停机"] * 100).toFixed(1))}%</span></td><td class="r">${ssrInterpolate(x.r.事件数)}</td><td class="r">${ssrInterpolate(x.r.外部 ? "—" : x.r.MTBF_h || "—")}</td><td class="r">${ssrInterpolate(x.r.MDT_h)}</td><td class="r">${ssrInterpolate(x.r.影响台数)}</td><td class="small">${ssrInterpolate(x.r.典型失效模式 || "—")}</td></tr>`);
  1323. });
  1324. _push(`<!--]--></table></div><p class="small muted">${ssrInterpolate(rel.value.口径)}</p></div></section>`);
  1325. } else {
  1326. _push(`<!---->`);
  1327. }
  1328. if (__props.d.fus && !__props.d.fus.err) {
  1329. _push(`<section><div class="panel mb"><p class="small">${ssrInterpolate(unref(t)("cmp.fusion_moved"))} <a href="#tab=vibration"><b>${ssrInterpolate(unref(t)("tab.vibration"))}</b></a> ${ssrInterpolate(unref(t)("cmp.fusion_n", { n: (__props.d.fus.表 || []).length }))}</p></div></section>`);
  1330. } else {
  1331. _push(`<!---->`);
  1332. }
  1333. _push(`<section><div class="eyebrow">${ssrInterpolate(unref(t)("cmp.cross_eyebrow"))}</div><div class="panel mb"><h3>${ssrInterpolate(unref(t)("cmp.watch_head", { n: (__props.d.watch || []).length }))}</h3><div class="tblwrap"><table><tr><th>${ssrInterpolate(unref(t)("ui.unit"))}</th><th class="r">${ssrInterpolate(unref(t)("cmp.n_sys"))}</th><th>${ssrInterpolate(unref(t)("cmp.hit_sys"))}</th><th>${ssrInterpolate(unref(t)("cmp.basis"))}</th></tr><!--[-->`);
  1334. ssrRenderList(__props.d.watch || [], (w) => {
  1335. _push(`<tr><td><a${ssrRenderAttr("href", `/turbine/${w.t}`)}><b>${ssrInterpolate(unref(unitNo)(w.t))}</b></a></td><td class="r">${ssrInterpolate(w.n)}</td><td class="small">${hitHtml(w) ?? ""}</td><td class="small">${ssrInterpolate(w.why)}</td></tr>`);
  1336. });
  1337. _push(`<!--]--></table></div></div></section><!--]-->`);
  1338. }
  1339. };
  1340. }
  1341. });
  1342. const _sfc_setup$c = _sfc_main$c.setup;
  1343. _sfc_main$c.setup = (props, ctx) => {
  1344. const ssrContext = useSSRContext();
  1345. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Component.vue");
  1346. return _sfc_setup$c ? _sfc_setup$c(props, ctx) : void 0;
  1347. };
  1348. const _sfc_main$b = /* @__PURE__ */ defineComponent({
  1349. __name: "EChart",
  1350. __ssrInlineRender: true,
  1351. props: {
  1352. option: {},
  1353. height: {}
  1354. },
  1355. setup(__props) {
  1356. const props = __props;
  1357. const el = ref(null);
  1358. let chart2 = null;
  1359. async function draw() {
  1360. if (!el.value) return;
  1361. const echarts = await import("echarts");
  1362. if (!chart2) chart2 = echarts.init(el.value, void 0, { renderer: "canvas" });
  1363. chart2.setOption(props.option, true);
  1364. }
  1365. onMounted(draw);
  1366. watch(() => props.option, draw, { deep: true });
  1367. onBeforeUnmount(() => {
  1368. if (chart2) {
  1369. chart2.dispose();
  1370. chart2 = null;
  1371. }
  1372. });
  1373. return (_ctx, _push, _parent, _attrs) => {
  1374. _push(`<div${ssrRenderAttrs(mergeProps({
  1375. class: "echart",
  1376. "data-chart": JSON.stringify(__props.option),
  1377. style: `height:${__props.height || "260px"}`,
  1378. ref_key: "el",
  1379. ref: el
  1380. }, _attrs))} data-v-999bee15></div>`);
  1381. };
  1382. }
  1383. });
  1384. const _export_sfc = (sfc, props) => {
  1385. const target = sfc.__vccOpts || sfc;
  1386. for (const [key, val] of props) {
  1387. target[key] = val;
  1388. }
  1389. return target;
  1390. };
  1391. const _sfc_setup$b = _sfc_main$b.setup;
  1392. _sfc_main$b.setup = (props, ctx) => {
  1393. const ssrContext = useSSRContext();
  1394. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/components/EChart.vue");
  1395. return _sfc_setup$b ? _sfc_setup$b(props, ctx) : void 0;
  1396. };
  1397. const EChart = /* @__PURE__ */ _export_sfc(_sfc_main$b, [["__scopeId", "data-v-999bee15"]]);
  1398. const _sfc_main$a = /* @__PURE__ */ defineComponent({
  1399. __name: "Energy",
  1400. __ssrInlineRender: true,
  1401. props: {
  1402. d: {}
  1403. },
  1404. setup(__props) {
  1405. const props = __props;
  1406. const E = computed(() => (props.d?.control || {}).energy);
  1407. const S = computed(() => (props.d?.control || {}).states || []);
  1408. const LOSS = {
  1409. "限电·绑定": "loss.curtail_bound",
  1410. "限电命令·未绑定": "loss.curtail_unbound",
  1411. "停机(调度令)": "loss.dispatch",
  1412. 停机: "loss.stop",
  1413. 正常发电: "loss.normal",
  1414. 低风待机: "loss.lowwind"
  1415. };
  1416. const lossName = (k) => LOSS[k] ? t(LOSS[k]) : k;
  1417. const kpi = computed(() => {
  1418. const e = E.value || {};
  1419. return [
  1420. { v: fmt(e.theo), u: "MWh", l: t("en.kpi_theo"), k: "" },
  1421. { v: fmt(e.act), u: "MWh", l: t("en.kpi_act", { h: fmt(e.eflh) }), k: "ok" },
  1422. { v: fmt(e.loss_pct, 1), u: "%", l: t("en.kpi_loss", { v: fmt(e.loss) }), k: e.loss_pct >= 10 ? "warn" : "" },
  1423. {
  1424. v: e.cap_ok ? t("en.cap_pass") : t("en.cap_fail"),
  1425. u: "",
  1426. l: t("en.cap_note", { op: e.cap_ok ? "<" : "≥", cap: fmt(e.cap) }),
  1427. k: e.cap_ok ? "ok" : "bad"
  1428. }
  1429. ];
  1430. });
  1431. const blameCat = (k) => k.indexOf("限电") >= 0 || k.indexOf("调度") >= 0 ? 0 : k === "停机" ? 1 : 2;
  1432. const blameGroups = computed(() => {
  1433. const g = [0, 0, 0];
  1434. for (const x of E.value?.items || []) g[blameCat(x.k)] += x.loss;
  1435. return g;
  1436. });
  1437. const waterfallOption = computed(() => {
  1438. const e = E.value || {};
  1439. const items = (e.items || []).filter((x) => x.loss > 0).map((x) => ({ name: x.k, value: Math.round(x.loss) }));
  1440. const groups = blameGroups.value;
  1441. const tot = groups.reduce((a, b) => a + b, 0) || 1;
  1442. const pct = groups.map((v) => Math.round(v / tot * 100)).filter((p, i) => groups[i] / tot * 500 > 46);
  1443. return {
  1444. title: { text: t("en.balance"), left: "center", textStyle: { fontSize: 13 } },
  1445. tooltip: { trigger: "axis" },
  1446. grid: { left: 70, right: 20, top: 44, bottom: 40 },
  1447. xAxis: { type: "category", data: items.map((x) => x.name) },
  1448. yAxis: { type: "value", name: "MWh" },
  1449. series: [{ type: "bar", data: items.map((x) => x.value), itemStyle: { color: "#0b5fa5" } }],
  1450. // 与旧图数字集合同源:theo · act · Σitems · 各 item · 组占比%
  1451. __data__: [
  1452. Math.round(e.theo),
  1453. Math.round(e.act),
  1454. Math.round(items.reduce((a, x) => a + x.value, 0)),
  1455. ...items.map((x) => x.value),
  1456. ...pct
  1457. ]
  1458. };
  1459. });
  1460. const blameOption = computed(() => {
  1461. const groups = blameGroups.value;
  1462. return {
  1463. grid: { left: 90, right: 24, top: 20, bottom: 30 },
  1464. xAxis: { type: "value", name: "MWh" },
  1465. yAxis: { type: "category", data: [t("ch.blame.n1"), t("ch.blame.n2"), t("ch.blame.n3")] },
  1466. series: [{ type: "bar", data: groups.map((v) => Math.round(v)), itemStyle: { color: "#2f7dbf" } }],
  1467. __data__: []
  1468. };
  1469. });
  1470. const mxp = computed(() => Math.max(1, ...S.value.map((x) => x.pct)));
  1471. return (_ctx, _push, _parent, _attrs) => {
  1472. if (!E.value) {
  1473. _push(`<section${ssrRenderAttrs(_attrs)}><div class="panel"><p class="small muted">${ssrInterpolate(unref(t)("ui.empty"))}</p></div></section>`);
  1474. } else {
  1475. _push(`<section${ssrRenderAttrs(_attrs)}><div class="kpis" style="${ssrRenderStyle({ "margin-bottom": "12px" })}"><!--[-->`);
  1476. ssrRenderList(kpi.value, (k, i) => {
  1477. _push(`<div class="${ssrRenderClass([k.k, "kpi"])}"><b>${ssrInterpolate(k.v)}`);
  1478. if (k.u) {
  1479. _push(`<small>${ssrInterpolate(k.u)}</small>`);
  1480. } else {
  1481. _push(`<!---->`);
  1482. }
  1483. _push(`</b><span>${ssrInterpolate(k.l)}</span></div>`);
  1484. });
  1485. _push(`<!--]--></div><div class="grid2"><div class="panel span2"><h3>${ssrInterpolate(unref(t)("en.balance"))} <span class="small muted" style="${ssrRenderStyle({ "font-weight": "400" })}">${ssrInterpolate(unref(t)("en.win", { win: unref(winName)(__props.d.win) }))}</span></h3>`);
  1486. _push(ssrRenderComponent(EChart, {
  1487. option: waterfallOption.value,
  1488. height: "280px"
  1489. }, null, _parent));
  1490. _push(`</div><div class="panel"><h3>${ssrInterpolate(unref(t)("en.blame"))}</h3>`);
  1491. _push(ssrRenderComponent(EChart, {
  1492. option: blameOption.value,
  1493. height: "240px"
  1494. }, null, _parent));
  1495. _push(`<!--[-->`);
  1496. ssrRenderList(blameGroups.value, (v, i) => {
  1497. _push(`<div style="${ssrRenderStyle({ "display": "flex", "align-items": "baseline", "gap": "7px", "margin-top": "5px" })}"><i style="${ssrRenderStyle(`flex:none;width:9px;height:9px;border-radius:2px;background:${["var(--accent)", "var(--critical)", "var(--muted)"][i]};opacity:.55`)}"></i><span style="${ssrRenderStyle({ "font-weight": "600" })}">${ssrInterpolate(unref(t)(["ch.blame.n1", "ch.blame.n2", "ch.blame.n3"][i]))}</span><span class="num">${ssrInterpolate(unref(fmt)(Math.round(v)))} MWh</span><span class="small muted">${ssrInterpolate(unref(t)(["ch.blame.d1", "ch.blame.d2", "ch.blame.d3"][i]))}</span></div>`);
  1498. });
  1499. _push(`<!--]--><p class="small muted" style="${ssrRenderStyle({ "margin-top": "6px" })}">${ssrInterpolate(unref(t)("en.blame_note", { n: unref(fmt)(Math.round(((E.value.items || []).find((x) => x.k === "停机") || {}).loss || 0)) }))}</p></div><div class="panel"><h3>${ssrInterpolate(unref(t)("en.states"))} <span class="small muted" style="${ssrRenderStyle({ "font-weight": "400" })}">${ssrInterpolate(unref(t)("en.win", { win: unref(winName)(__props.d.win) }))}</span></h3><div class="tblwrap"><table><tr><th>${ssrInterpolate(unref(t)("en.state"))}</th><th>${ssrInterpolate(unref(t)("en.share"))}</th><th class="r" style="${ssrRenderStyle({ "width": "90px" })}">${ssrInterpolate(unref(t)("en.loss_mwh"))}</th></tr><!--[-->`);
  1500. ssrRenderList(S.value, (x) => {
  1501. _push(`<tr><td class="small">${ssrInterpolate(lossName(x.k))}</td><td><i class="bar" style="${ssrRenderStyle(`width:${Math.round(x.pct / mxp.value * 84)}px;background:${x.k.indexOf("停机") >= 0 && x.k.indexOf("调度") < 0 ? "var(--critical)" : "var(--accent)"}`)}"></i><span class="num small">${ssrInterpolate(x.pct)}% · ${ssrInterpolate(unref(fmt)(Math.round(x.h)))}h</span></td><td class="r small">${ssrInterpolate(unref(fmt)(Math.round(x.loss)))}</td></tr>`);
  1502. });
  1503. _push(`<!--]--></table></div><p class="small muted" style="${ssrRenderStyle({ "margin-top": "6px" })}">${ssrInterpolate(__props.d.control.anchors || "")}<br>${ssrInterpolate(unref(t)("en.states_note"))}</p></div></div></section>`);
  1504. }
  1505. };
  1506. }
  1507. });
  1508. const _sfc_setup$a = _sfc_main$a.setup;
  1509. _sfc_main$a.setup = (props, ctx) => {
  1510. const ssrContext = useSSRContext();
  1511. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Energy.vue");
  1512. return _sfc_setup$a ? _sfc_setup$a(props, ctx) : void 0;
  1513. };
  1514. const _sfc_main$9 = /* @__PURE__ */ defineComponent({
  1515. __name: "LegacyChart",
  1516. __ssrInlineRender: true,
  1517. props: {
  1518. html: {}
  1519. },
  1520. setup(__props) {
  1521. return (_ctx, _push, _parent, _attrs) => {
  1522. _push(`<div${ssrRenderAttrs(mergeProps({ class: "legacy-chart" }, _attrs))}>${__props.html ?? ""}</div>`);
  1523. };
  1524. }
  1525. });
  1526. const _sfc_setup$9 = _sfc_main$9.setup;
  1527. _sfc_main$9.setup = (props, ctx) => {
  1528. const ssrContext = useSSRContext();
  1529. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/components/LegacyChart.vue");
  1530. return _sfc_setup$9 ? _sfc_setup$9(props, ctx) : void 0;
  1531. };
  1532. const __G_SHIM__ = typeof window !== "undefined" ? window : globalThis;
  1533. __G_SHIM__.__charts = function(ctx) {
  1534. const { t: t2, fmt: fmt2, esc: esc2, dv: dv2, EN } = ctx;
  1535. const CJK = /[㐀-鿿]/;
  1536. const isV = (x) => EN && CJK.test(String(x == null ? "" : x));
  1537. const plain = (x) => {
  1538. x = ctx.displayText ? ctx.displayText(x) : x == null ? "" : String(x);
  1539. if (isV(x) && ctx.verb) ctx.verb();
  1540. return x;
  1541. };
  1542. const ttl = (x) => {
  1543. const label = plain(x);
  1544. return `<title${isV(label) ? ' class="verbatim"' : ""}>${esc2(label)}</title>`;
  1545. };
  1546. const svg = (W, H, body) => `<svg class="dt" viewBox="0 0 ${W} ${H}" style="width:${W}px;max-width:100%;height:auto" role="img">${body}</svg>`;
  1547. const txt = (x, y, s, o = {}) => `<text x="${(+x).toFixed(1)}" y="${(+y).toFixed(1)}" font-size="${o.fs || 9}" fill="${o.fill || "var(--muted)"}"${o.anchor ? ` text-anchor="${o.anchor}"` : ""}${o.op ? ` opacity="${o.op}"` : ""}${o.w ? ` font-weight="${o.w}"` : ""}${o.tr ? ` transform="${o.tr}"` : ""}${o.cls ? ` class="${o.cls}"` : ""}>${s}</text>`;
  1548. const dtxt = (x, y, raw, o = {}) => {
  1549. const p = plain(raw);
  1550. return txt(x, y, esc2(p), isV(p) ? Object.assign({}, o, { cls: "verbatim" }) : o);
  1551. };
  1552. const LOSS = { "限电·绑定": "loss.curtail_bound", "限电命令·未绑定": "loss.curtail_unbound", "停机(调度令)": "loss.dispatch", "停机": "loss.stop", "正常发电": "loss.normal", "低风待机": "loss.lowwind" };
  1553. const lossName = (k) => LOSS[k] ? t2(LOSS[k]) : plain(k);
  1554. function quadChart(items) {
  1555. const it = (items || []).filter((x) => x && x.n > 0 && x.med > 0);
  1556. if (it.length < 3) return `<p class="small muted">${t2("ch.quad.few")}</p>`;
  1557. const W = 720, H = 330, ML = 58, MR = 16, MB = 44, MT = 16;
  1558. const lx = (x) => Math.log10(x), xs = it.map((d) => lx(d.n)), ys = it.map((d) => lx(d.med));
  1559. const x0 = Math.min(...xs) - 0.15, x1 = Math.max(...xs) + 0.15, y0 = Math.min(...ys) - 0.15, y1 = Math.max(...ys) + 0.15;
  1560. const PX = (v) => ML + (lx(v) - x0) / (x1 - x0) * (W - ML - MR), PY = (v) => H - MB - (lx(v) - y0) / (y1 - y0) * (H - MT - MB);
  1561. const srt = it.map((d) => d.n).sort((a, b) => a - b), nmid = srt[Math.floor(srt.length / 2)];
  1562. const cx = PX(nmid), cy = PY(600), mt = Math.max(...it.map((d) => d.nt));
  1563. const pts = it.map((d) => {
  1564. const r = 3 + Math.sqrt(d.nt / mt) * 7, hot = d.n >= nmid && d.med >= 600, c = hot ? "var(--critical)" : "var(--accent)";
  1565. return `<circle cx="${PX(d.n).toFixed(1)}" cy="${PY(d.med).toFixed(1)}" r="${r.toFixed(1)}" fill="${c}" fill-opacity=".5" stroke="${c}" stroke-width="1">${ttl(`${d.code} ${plain(d.k)}
  1566. ` + t2("ch.quad.tip", { n: d.n, med: d.med, h: d.h, nt: d.nt }))}</circle>`;
  1567. }).join("");
  1568. const lab = it.filter((d) => d.n >= nmid || d.med >= 600).slice(0, 8).map((d) => txt(PX(d.n) + 9, PY(d.med) + 3, esc2(d.code))).join("");
  1569. const qn = (x, y, a, b) => txt(x, y, a, { fs: 10, op: 0.85 }) + txt(x, y + 12, b, { fs: 8.5, op: 0.6 });
  1570. const tick = (v) => v >= 3600 ? v / 3600 + "h" : v >= 60 ? v / 60 + "min" : v + "s";
  1571. const yt = [10, 60, 600, 3600, 36e3].filter((v) => lx(v) >= y0 && lx(v) <= y1).map((v) => `<line x1="${ML}" y1="${PY(v).toFixed(1)}" x2="${W - MR}" y2="${PY(v).toFixed(1)}" stroke="var(--line)" stroke-opacity=".4"/>` + txt(ML - 6, PY(v) + 3, tick(v), { anchor: "end" })).join("");
  1572. const xt = [1, 10, 100, 1e3, 1e4].filter((v) => lx(v) >= x0 && lx(v) <= x1).map((v) => txt(PX(v), H - MB + 14, v, { anchor: "middle" })).join("");
  1573. const mid = MT + (H - MB - MT) / 2;
  1574. return svg(W, H, `${yt}${xt}
  1575. <line x1="${cx.toFixed(1)}" y1="${MT}" x2="${cx.toFixed(1)}" y2="${H - MB}" stroke="var(--muted)" stroke-dasharray="3 3" stroke-opacity=".5"/>
  1576. <line x1="${ML}" y1="${cy.toFixed(1)}" x2="${W - MR}" y2="${cy.toFixed(1)}" stroke="var(--muted)" stroke-dasharray="3 3" stroke-opacity=".5"/>
  1577. ${qn(ML + 6, MT + 12, t2("ch.quad.q1"), t2("ch.quad.q1s"))}${qn(W - MR - 130, MT + 12, t2("ch.quad.q2"), t2("ch.quad.q2s"))}
  1578. ${qn(ML + 6, H - MB - 18, t2("ch.quad.q3"), t2("ch.quad.q3s"))}${qn(W - MR - 130, H - MB - 18, t2("ch.quad.q4"), t2("ch.quad.q4s"))}
  1579. ${pts}${lab}
  1580. ${txt(ML + (W - MR - ML) / 2, H - 6, t2("ch.quad.xlab"), { fs: 9.5, anchor: "middle" })}
  1581. ${txt(14, mid, t2("ch.quad.ylab"), { fs: 9.5, anchor: "middle", tr: `rotate(-90 14 ${mid.toFixed(1)})` })}`);
  1582. }
  1583. function rateChart(mo) {
  1584. const ms = mo.months || [], vs = mo.vals || [], cov = mo.cov || ms.map(() => 1), pd = mo.per_day || [];
  1585. const W = 720, H = 250, ML = 48, MR = 14, MB = 52, MT = 18, n = ms.length || 1;
  1586. const mx = Math.max(...pd.filter((x) => x != null), 1), bw = (W - ML - MR) / n;
  1587. let bars = "", lab = "";
  1588. ms.forEach((m, i) => {
  1589. const v = pd[i], part = cov[i] < 0.5;
  1590. if (v == null) return;
  1591. const hh = v / mx * (H - MT - MB), bx = ML + i * bw + bw * 0.16, by = H - MB - hh, cxm = bx + bw * 0.34;
  1592. bars += `<rect x="${bx.toFixed(1)}" y="${by.toFixed(1)}" width="${(bw * 0.68).toFixed(1)}" height="${hh.toFixed(1)}" rx="2" fill="${part ? "var(--muted)" : "var(--accent)"}" fill-opacity="${part ? 0.32 : 0.8}" stroke="${part ? "var(--warning)" : "none"}" stroke-dasharray="${part ? "3 2" : ""}"><title>${t2("ch.rate.tip", { m, v, n: vs[i], cov: (cov[i] * 100).toFixed(0) })}</title></rect>`;
  1593. lab += txt(cxm, by - 4, v, { anchor: "middle" }) + txt(cxm, H - MB + 13, t2("ch.rate.month", { m: m.slice(5) }), { anchor: "middle" }) + txt(cxm, H - MB + 25, t2("ch.rate.n", { n: vs[i] }), { fs: 8, op: 0.65, anchor: "middle" }) + (part ? txt(cxm, H - MB + 37, t2("ch.rate.days", { d: Math.round(cov[i] * 30) }), { fs: 8, fill: "var(--warning)", anchor: "middle" }) : "");
  1594. });
  1595. const yt = [0, 0.5, 1].map((f) => txt(ML - 6, H - MB - f * (H - MT - MB) + 3, Math.round(mx * f), { anchor: "end" })).join("");
  1596. const mid = MT + (H - MB - MT) / 2;
  1597. return svg(W, H, `<line x1="${ML}" y1="${H - MB}" x2="${W - MR}" y2="${H - MB}" stroke="var(--line)"/><line x1="${ML}" y1="${MT}" x2="${ML}" y2="${H - MB}" stroke="var(--line)"/>
  1598. ${bars}${lab}${yt}${txt(14, mid, t2("ch.rate.ylab"), { fs: 9.5, anchor: "middle", tr: `rotate(-90 14 ${mid.toFixed(1)})` })}`);
  1599. }
  1600. function waterfall(e) {
  1601. if (!e || !e.theo) return "";
  1602. const W = 720, H = 300, ML = 8, MR = 8, MT = 30, MB = 64, items = (e.items || []).filter((x) => x.loss > 0);
  1603. const cols = items.length + 2, cw = (W - ML - MR) / cols, sc = (H - MT - MB) / e.theo;
  1604. let run = e.theo, h = "", i = 0;
  1605. const bar = (idx, y, hh, fill, op) => `<rect x="${(ML + idx * cw + cw * 0.14).toFixed(1)}" y="${y.toFixed(1)}" width="${(cw * 0.72).toFixed(1)}" height="${Math.max(hh, 1.2).toFixed(1)}" rx="2" fill="${fill}" fill-opacity="${op}"/>`;
  1606. const lab = (idx, y, nm, v, c) => txt(ML + idx * cw + cw * 0.5, y - 6, v, { fs: 10, fill: c || "var(--ink)", anchor: "middle", w: 600 }) + txt(ML + idx * cw + cw * 0.5, H - MB + 14, nm, { fs: 8.5, anchor: "middle" });
  1607. h += bar(0, H - MB - e.theo * sc, e.theo * sc, "var(--accent)", 0.45) + lab(0, H - MB - e.theo * sc, t2("ch.wf.theo"), fmt2(e.theo));
  1608. items.forEach((x) => {
  1609. i++;
  1610. const top = H - MB - run * sc, hh = x.loss * sc;
  1611. run -= x.loss;
  1612. const nm = lossName(x.k);
  1613. h += bar(i, top, hh, "var(--critical)", 0.55) + `<line x1="${(ML + (i - 1) * cw + cw * 0.86).toFixed(1)}" y1="${top.toFixed(1)}" x2="${(ML + i * cw + cw * 0.14).toFixed(1)}" y2="${top.toFixed(1)}" stroke="var(--muted)" stroke-dasharray="2 2" stroke-opacity=".6"/>` + lab(i, top, esc2(nm.length > 12 ? nm.slice(0, 11) + "…" : nm), "−" + fmt2(x.loss), "var(--critical)") + ttl(t2("ch.wf.tip", { nm, loss: fmt2(x.loss), pct: (x.loss / e.theo * 100).toFixed(1), h: fmt2(x.h) }));
  1614. });
  1615. h += bar(cols - 1, H - MB - e.act * sc, e.act * sc, "var(--good)", 0.6) + lab(cols - 1, H - MB - e.act * sc, t2("ch.wf.act"), fmt2(e.act), "var(--good)");
  1616. return svg(W, H, `<line x1="${ML}" y1="${H - MB}" x2="${W - MR}" y2="${H - MB}" stroke="var(--line)"/>${txt(ML, 14, "MWh", { fs: 9.5 })}${h}`);
  1617. }
  1618. function ramChart(tb, partName2) {
  1619. const it = (tb || []).filter((x) => x.MTBF_h && x.MDT_h);
  1620. if (it.length < 3) return `<p class="small muted">${t2("ch.ram.few")}</p>`;
  1621. const W = 720, H = 340, ML = 56, MR = 112, MB = 48, MT = 16;
  1622. const lg = (v) => Math.log10(v), xs = it.map((d) => lg(d.MTBF_h)), ys = it.map((d) => d.MDT_h);
  1623. const x0 = Math.min(...xs) - 0.08, x1 = Math.max(...xs) + 0.08, y0 = 0, y1 = Math.max(...ys) * 1.12;
  1624. const PX = (v) => ML + (lg(v) - x0) / (x1 - x0) * (W - ML - MR), PY = (v) => H - MB - (v - y0) / (y1 - y0) * (H - MT - MB);
  1625. const md = (a) => {
  1626. const z = a.slice().sort((p, q) => p - q);
  1627. return z[Math.floor(z.length / 2)];
  1628. };
  1629. const mMT = md(it.map((d) => d.MTBF_h)), mMD = md(ys), cx = PX(mMT), cy = PY(mMD), mh = Math.max(...it.map((d) => d.停机时长h));
  1630. const nm = (d) => plain(partName2 ? partName2(d.部件) : d.部件);
  1631. const pts = it.map((d) => {
  1632. const r = 4 + Math.sqrt(d.停机时长h / mh) * 13, ext = d.外部, hot = d.MTBF_h < mMT && d.MDT_h >= mMD, c = ext ? "var(--muted)" : hot ? "var(--critical)" : "var(--accent)";
  1633. return `<circle cx="${PX(d.MTBF_h).toFixed(1)}" cy="${PY(d.MDT_h).toFixed(1)}" r="${r.toFixed(1)}" fill="${c}" fill-opacity="${ext ? 0.3 : 0.42}" stroke="${c}" stroke-width="1.2" stroke-dasharray="${ext ? "3 2" : ""}">${ttl(t2("ch.ram.tip", { p: nm(d), mtbf: d.MTBF_h, mdt: d.MDT_h, h: d.停机时长h, n: d.事件数, u: d.影响台数 }) + (ext ? t2("ch.ram.tip_ext") : ""))}</circle>` + dtxt(PX(d.MTBF_h) + r + 4, PY(d.MDT_h) + 3.5, plain(nm(d)).slice(0, EN ? 14 : 7), { fs: 9.5, fill: ext ? "var(--muted)" : "currentColor" });
  1634. }).join("");
  1635. const qn = (x, y, a, s1, s2) => txt(x, y, s1, { fs: 10, anchor: a, op: 0.9 }) + txt(x, y + 12, s2, { fs: 8.5, anchor: a, op: 0.62 });
  1636. const xt = [1e3, 2e3, 5e3, 1e4, 2e4].filter((v) => lg(v) >= x0 && lg(v) <= x1).map((v) => `<line x1="${PX(v).toFixed(1)}" y1="${MT}" x2="${PX(v).toFixed(1)}" y2="${H - MB}" stroke="var(--line)" stroke-opacity=".3"/>` + txt(PX(v), H - MB + 14, v >= 1e3 ? v / 1e3 + "k" : v, { anchor: "middle" })).join("");
  1637. const yt = [0, 5, 10, 15, 20].filter((v) => v <= y1).map((v) => txt(ML - 6, PY(v) + 3, v, { anchor: "end" })).join("");
  1638. const mid = MT + (H - MB - MT) / 2;
  1639. return svg(W, H, `${xt}${yt}<line x1="${ML}" y1="${H - MB}" x2="${W - MR}" y2="${H - MB}" stroke="var(--line)"/><line x1="${ML}" y1="${MT}" x2="${ML}" y2="${H - MB}" stroke="var(--line)"/>
  1640. <line x1="${cx.toFixed(1)}" y1="${MT}" x2="${cx.toFixed(1)}" y2="${H - MB}" stroke="var(--muted)" stroke-dasharray="3 3" stroke-opacity=".45"/>
  1641. <line x1="${ML}" y1="${cy.toFixed(1)}" x2="${W - MR}" y2="${cy.toFixed(1)}" stroke="var(--muted)" stroke-dasharray="3 3" stroke-opacity=".45"/>
  1642. ${qn(ML + 6, MT + 12, "start", t2("ch.ram.q1"), t2("ch.ram.q1s"))}${qn(W - MR - 6, MT + 12, "end", t2("ch.ram.q2"), t2("ch.ram.q2s"))}
  1643. ${qn(ML + 6, H - MB - 16, "start", t2("ch.ram.q3"), t2("ch.ram.q3s"))}${qn(W - MR - 6, H - MB - 16, "end", t2("ch.ram.q4"), t2("ch.ram.q4s"))}
  1644. ${pts}${txt(ML + (W - MR - ML) / 2, H - 8, t2("ch.ram.xlab"), { fs: 9.5, anchor: "middle" })}
  1645. ${txt(13, mid, t2("ch.ram.ylab"), { fs: 9.5, anchor: "middle", tr: `rotate(-90 13 ${mid.toFixed(1)})` })}
  1646. <circle cx="${W - MR + 16}" cy="${MT + 40}" r="5" fill="var(--accent)" fill-opacity=".42" stroke="var(--accent)"/>${txt(W - MR + 26, MT + 44, t2("ch.ram.equip"))}
  1647. <circle cx="${W - MR + 16}" cy="${MT + 58}" r="5" fill="var(--muted)" fill-opacity=".3" stroke="var(--muted)" stroke-dasharray="3 2"/>${txt(W - MR + 26, MT + 62, t2("ch.ram.ext"))}
  1648. ${txt(W - MR + 6, MT + 84, t2("ch.ram.area"), { fs: 8.5 })}${txt(W - MR + 6, MT + 98, t2("ch.ram.median"), { fs: 8.5 })}`);
  1649. }
  1650. function attribBar(tb, tot) {
  1651. const nore = (tb || []).find((x) => x.部件 === "无记录停机"), unk = nore ? nore.停机时长h : 0;
  1652. const un2 = (tb || []).filter((x) => x.部件 === "未归类").reduce((a, b) => a + b.停机时长h, 0), ok = tot - unk - un2, W = 720, H = 54;
  1653. const seg = (x, w, c, op) => `<rect x="${x.toFixed(1)}" y="14" width="${Math.max(w, 1).toFixed(1)}" height="22" rx="3" fill="${c}" fill-opacity="${op}"/>`;
  1654. const w1 = ok / tot * W, w2 = un2 / tot * W, w3 = unk / tot * W;
  1655. return svg(W, H, `${seg(0, w1, "var(--accent)", 0.55)}${seg(w1, w2, "var(--warning)", 0.5)}${seg(w1 + w2, w3, "var(--critical)", 0.5)}
  1656. ${txt(4, 10, t2("ch.attrib.ok", { h: Math.round(ok), p: (ok / tot * 100).toFixed(0) }))}
  1657. ${txt(w1 + w2 + w3 - 2, 10, t2("ch.attrib.none", { h: Math.round(unk), p: (unk / tot * 100).toFixed(0) }), { fill: "var(--critical)", anchor: "end" })}
  1658. ${txt(w1 + w2 / 2, 50, t2("ch.attrib.uncls", { h: Math.round(un2) }), { fill: "var(--warning)", anchor: "middle" })}`);
  1659. }
  1660. function paretoChart(items, unit, note) {
  1661. const it = (items || []).filter((x) => x && x.v > 0);
  1662. if (!it.length) return `<p class="small muted">${t2("ch.pareto.none")}</p>`;
  1663. const W = 520, H = 236, ML = 46, MR = 40, MB = 40, MT = 14, n = it.length, tot = it.reduce((a, x) => a + x.v, 0), mx = Math.max(...it.map((x) => x.v)), bw = (W - ML - MR) / n;
  1664. const short = (k) => {
  1665. k = plain(k);
  1666. const m = String(k).match(/^([0-9]+)/);
  1667. return m ? m[1] : String(k).slice(0, 4);
  1668. };
  1669. let cum = 0, bars = "", line = [], dots = "", k80 = null;
  1670. it.forEach((x, i) => {
  1671. const hh = x.v / mx * (H - MT - MB), bx = ML + i * bw + bw * 0.14, by = H - MB - hh;
  1672. bars += `<rect x="${bx.toFixed(1)}" y="${by.toFixed(1)}" width="${(bw * 0.72).toFixed(1)}" height="${hh.toFixed(1)}" rx="2" fill="var(--accent)" fill-opacity="${i < 3 ? 0.85 : 0.45}">${ttl(`${plain(x.k)}: ${fmt2(x.v)}${unit}`)}</rect>`;
  1673. cum += x.v;
  1674. const pct = cum / tot;
  1675. if (k80 === null && pct >= 0.8) k80 = i + 1;
  1676. const px = ML + i * bw + bw / 2, py = MT + (1 - pct) * (H - MT - MB);
  1677. line.push(`${px.toFixed(1)},${py.toFixed(1)}`);
  1678. dots += `<circle cx="${px.toFixed(1)}" cy="${py.toFixed(1)}" r="2.4" fill="var(--warning)">${ttl(t2("ch.pareto.cumtip", { k: plain(x.k), p: (pct * 100).toFixed(0) }))}</circle>`;
  1679. });
  1680. const y80 = MT + 0.2 * (H - MT - MB);
  1681. const xt = it.map((x, i) => dtxt(ML + i * bw + bw / 2, H - MB + 13, short(x.k), { anchor: "middle" })).join("");
  1682. const yt = [0, 0.5, 1].map((f) => txt(ML - 5, H - MB - f * (H - MT - MB) + 3, fmt2(Math.round(mx * f)), { anchor: "end" })).join("") + [0, 0.5, 1].map((f) => txt(W - MR + 5, MT + (1 - f) * (H - MT - MB) + 3, Math.round(f * 100) + "%", { fill: "var(--warning)" })).join("");
  1683. const top3 = it.slice(0, 3).map((x) => `<b>${isV(short(x.k)) ? `<span class="verbatim">${esc2(plain(short(x.k)))}</span>` : esc2(short(x.k))}</b> ${dv2(plain(x.k).replace(/^[0-9]+ */, "").slice(0, 18))}`).join(" · ");
  1684. return svg(W, H, `<line x1="${ML}" y1="${H - MB}" x2="${W - MR}" y2="${H - MB}" stroke="var(--line)"/><line x1="${ML}" y1="${MT}" x2="${ML}" y2="${H - MB}" stroke="var(--line)"/>${bars}
  1685. <line x1="${ML}" y1="${y80.toFixed(1)}" x2="${W - MR}" y2="${y80.toFixed(1)}" stroke="var(--warning)" stroke-dasharray="4 3" stroke-opacity=".7"/>
  1686. ${txt(W - MR - 3, y80 - 4, t2("ch.pareto.cum80"), { fill: "var(--warning)", anchor: "end" })}
  1687. <polyline points="${line.join(" ")}" fill="none" stroke="var(--warning)" stroke-width="1.5"/>${dots}${xt}${yt}
  1688. ${txt(ML + (W - MR - ML) / 2, H - 3, t2("ch.pareto.xlab"), { fs: 8.5, anchor: "middle" })}`) + `<p class="small muted" style="margin-top:5px;line-height:1.6">${k80 ? `<b>${t2("ch.pareto.key", { k: k80 })}</b> (${t2("ch.pareto.total", { n, tot: fmt2(Math.round(tot)), unit })})` : t2("ch.pareto.total", { n, tot: fmt2(Math.round(tot)), unit })}<br>${t2("ch.pareto.top3")}${top3}${note ? "<br>" + note : ""}</p>`;
  1689. }
  1690. function blameBar(e) {
  1691. const cat = (k) => k.indexOf("限电") >= 0 || k.indexOf("调度") >= 0 ? 0 : k === "停机" ? 1 : 2;
  1692. const NM = [t2("ch.blame.n1"), t2("ch.blame.n2"), t2("ch.blame.n3")], NOTE = [t2("ch.blame.d1"), t2("ch.blame.d2"), t2("ch.blame.d3")], CL = ["var(--accent)", "var(--critical)", "var(--muted)"];
  1693. const g = [0, 0, 0];
  1694. (e.items || []).forEach((x2) => {
  1695. g[cat(x2.k)] += x2.loss;
  1696. });
  1697. const tot = g.reduce((a, b) => a + b, 0) || 1, W = 500, H = 44;
  1698. let x = 0, seg = "";
  1699. g.forEach((v, i) => {
  1700. const w = v / tot * W;
  1701. seg += `<rect x="${x.toFixed(1)}" y="12" width="${Math.max(w, 1).toFixed(1)}" height="26" rx="3" fill="${CL[i]}" fill-opacity=".55"><title>${NM[i]}: ${fmt2(Math.round(v))} MWh</title></rect>` + (w > 46 ? txt(x + w / 2, 29, (v / tot * 100).toFixed(0) + "%", { fs: 10.5, fill: "currentColor", anchor: "middle", w: 600 }) : "");
  1702. x += w;
  1703. });
  1704. const leg = g.map((v, i) => `<div style="display:flex;align-items:baseline;gap:7px;margin-top:5px"><i style="flex:none;width:9px;height:9px;border-radius:2px;background:${CL[i]};opacity:.55"></i><span style="font-weight:600">${NM[i]}</span><span class="num">${fmt2(Math.round(v))} MWh</span><span class="small muted">${NOTE[i]}</span></div>`).join("");
  1705. return svg(W, H, `${txt(0, 8, t2("ch.blame.title", { tot: fmt2(Math.round(tot)) }))}${seg}`) + leg;
  1706. }
  1707. function multiline(f) {
  1708. const xs = f.months || f.x || [], n = xs.length;
  1709. if (!n) return "";
  1710. const W = 620, H = 250, ML = 52, MB = 34, cols = ["var(--accent)", "#D48A1E", "#2E8B6A", "#7C5295"];
  1711. const all = [];
  1712. (f.series || []).forEach((s) => s.vals.forEach((v) => {
  1713. if (v != null && v === v) all.push(v);
  1714. }));
  1715. (f.thresholds || []).forEach((th) => {
  1716. if (th && th.v != null) all.push(th.v);
  1717. });
  1718. if (!all.length) return "";
  1719. let lo = f.zero === false ? Math.min(...all) : Math.min(0, ...all), hi = Math.max(...all);
  1720. if (lo === hi) {
  1721. lo -= 1;
  1722. hi += 1;
  1723. }
  1724. const pad = (hi - lo) * 0.08;
  1725. hi += pad;
  1726. if (f.zero === false) lo -= pad;
  1727. const X = (i) => ML + i / Math.max(n - 1, 1) * (W - ML - 12), Y = (v) => H - MB - (v - lo) / (hi - lo) * (H - MB - 14);
  1728. const line = (vals, c) => {
  1729. let segs = [], cur = [];
  1730. vals.forEach((v, i) => {
  1731. if (v == null || v !== v) {
  1732. if (cur.length) segs.push(cur);
  1733. cur = [];
  1734. } else cur.push(`${X(i).toFixed(1)},${Y(v).toFixed(1)}`);
  1735. });
  1736. if (cur.length) segs.push(cur);
  1737. return segs.map((sg) => `<polyline points="${sg.join(" ")}" fill="none" stroke="${c}" stroke-width="1.8"/>`).join("");
  1738. };
  1739. let body = (f.series || []).map((s, k) => line(s.vals, cols[k % 4])).join("");
  1740. const step = Math.max(1, Math.ceil(n / 8));
  1741. let xt = "";
  1742. for (let i = 0; i < n; i += step) xt += txt(X(i), H - 10, String(xs[i]).includes("-") ? String(xs[i]).slice(2, 7) : String(xs[i]), { fs: 10, anchor: "middle" });
  1743. let yt = "";
  1744. for (let k = 0; k < 5; k++) {
  1745. const v = lo + (hi - lo) * k / 4;
  1746. yt += txt(ML - 6, Y(v) + 3, v.toFixed(Math.abs(hi) < 10 ? 1 : 0), { fs: 10, anchor: "end" }) + `<line x1="${ML}" x2="${W - 12}" y1="${Y(v).toFixed(1)}" y2="${Y(v).toFixed(1)}" stroke="var(--line)"/>`;
  1747. }
  1748. (f.thresholds || []).forEach((th) => {
  1749. if (!th || th.v == null) return;
  1750. body += `<line x1="${ML}" x2="${W - 12}" y1="${Y(th.v).toFixed(1)}" y2="${Y(th.v).toFixed(1)}" stroke="var(--muted)" stroke-dasharray="5 4"/>` + (th.label ? dtxt(ML + 6, Y(th.v) - 4, th.label, { fs: 10 }) : "");
  1751. });
  1752. if (f.xlab) body += dtxt((ML + W) / 2, H - 1, f.xlab, { fs: 10, anchor: "middle" });
  1753. const leg = `<div class="legend" style="margin-top:6px;flex-wrap:wrap">` + (f.series || []).map((s, k) => `<span><i style="background:${cols[k % 4]}"></i>${isV(s.name) ? `<span class="verbatim">${esc2(plain(s.name))}</span>` : esc2(s.name)}</span>`).join("") + `</div>`;
  1754. return svg(W, H, `${yt}${body}${xt}`) + leg;
  1755. }
  1756. function capAxis(P, W9) {
  1757. const W = 500, H = 158, ML = 54, MR = 34;
  1758. const row2 = (y, spec, label) => {
  1759. const gs = Object.entries(spec.分组 || {}).map(([k, v]) => ({ v: parseFloat(k), n: v, raw: k }));
  1760. if (!gs.length) return "";
  1761. const lo = Math.min(...gs.map((g) => g.v)), hi = Math.max(...gs.map((g) => g.v)), pad = (hi - lo) * 0.22 || 1, x0 = lo - pad, x1 = hi + pad, PX = (v) => ML + (v - x0) / (x1 - x0) * (W - ML - MR), mn = Math.max(...gs.map((g) => g.n)), med = spec["中位"];
  1762. return txt(ML - 6, y + 3, label, { fs: 9.5, anchor: "end" }) + `<line x1="${ML}" y1="${y}" x2="${W - MR}" y2="${y}" stroke="var(--line)"/><line x1="${PX(med).toFixed(1)}" y1="${y - 24}" x2="${PX(med).toFixed(1)}" y2="${y + 18}" stroke="var(--muted)" stroke-dasharray="3 3" stroke-opacity=".55"/>` + txt(PX(med), y + 28, t2("ch.cap.median", { v: med }), { fs: 8.5, anchor: "middle" }) + gs.map((g) => {
  1763. const r = 5 + Math.sqrt(g.n / mn) * 9;
  1764. return `<circle cx="${PX(g.v).toFixed(1)}" cy="${y}" r="${r.toFixed(1)}" fill="var(--accent)" fill-opacity=".42" stroke="var(--accent)"/>` + txt(PX(g.v), y - r - 5, esc2(g.raw), { anchor: "middle", fill: "currentColor" }) + txt(PX(g.v), y + 3.5, g.n, { anchor: "middle", fill: "var(--panel)", w: 600 });
  1765. }).join("");
  1766. };
  1767. return svg(W, H, `${row2(44, P || {}, t2("ch.cap.p"))}${row2(120, W9 || {}, t2("ch.cap.w"))}${txt(W - MR, 12, t2("ch.cap.legend"), { fs: 8.5, anchor: "end" })}`);
  1768. }
  1769. function betaBar(b, unitNo2) {
  1770. const outs = b.离群 || [], med = b["全场同档差中位"] || 0, W = 500, H = 34 + outs.length * 26, ML = 52, MR = 58;
  1771. const mx = Math.max(med, ...outs.map((o) => o.平均偏)) * 1.18 || 1, PX = (v) => ML + v / mx * (W - ML - MR);
  1772. const rows = outs.map((o, i) => {
  1773. const y = 30 + i * 26;
  1774. return txt(ML - 6, y + 10, esc2(unitNo2(o.t)), { fs: 9.5, anchor: "end", fill: "currentColor" }) + `<rect x="${ML}" y="${y + 1}" width="${(PX(o.平均偏) - ML).toFixed(1)}" height="13" rx="2" fill="var(--warning)" fill-opacity=".62"/>` + txt(PX(o.平均偏) + 5, y + 11, o.平均偏 + "°", { fill: "var(--warning)" });
  1775. }).join("");
  1776. return svg(W, H, `<line x1="${PX(med).toFixed(1)}" y1="18" x2="${PX(med).toFixed(1)}" y2="${H - 4}" stroke="var(--good)" stroke-dasharray="3 3"/>${txt(PX(med), 12, t2("ch.beta.median", { v: med }), { fs: 8.5, fill: "var(--good)" })}<line x1="${ML}" y1="18" x2="${ML}" y2="${H - 4}" stroke="var(--line)"/>${rows}`);
  1777. }
  1778. function rulerBar(rl) {
  1779. const ks = Object.entries(rl || {}), W = 500, H = 26 + ks.length * 30, ML = 96, MR = 76, NORM = 3.7;
  1780. const mxr = Math.max(NORM * 1.35, ...ks.map(([, v]) => v.极差 / v.σ)), PX = (v) => ML + v / mxr * (W - ML - MR);
  1781. const rows = ks.map(([k, v], i) => {
  1782. const y = 22 + i * 30, ratio = v.极差 / v.σ, wide = ratio > NORM;
  1783. return dtxt(ML - 6, y + 11, k, { fs: 9.5, anchor: "end", fill: "currentColor" }) + `<rect x="${ML}" y="${y + 2}" width="${(PX(ratio) - ML).toFixed(1)}" height="12" rx="2" fill="${wide ? "var(--warning)" : "var(--accent)"}" fill-opacity=".55"/>` + txt(PX(ratio) + 5, y + 11, t2("ch.ruler.row", { r: ratio.toFixed(1), s: v.σ }));
  1784. }).join("");
  1785. return svg(W, H, `<line x1="${PX(NORM).toFixed(1)}" y1="14" x2="${PX(NORM).toFixed(1)}" y2="${H - 4}" stroke="var(--good)" stroke-dasharray="3 3"/>${txt(PX(NORM), 10, t2("ch.ruler.norm"), { fs: 8.5, fill: "var(--good)", anchor: "middle" })}${rows}`);
  1786. }
  1787. function fig(f) {
  1788. const k = f.kind;
  1789. if (k === "vib") return `<p class="small">${isV(f.text || f.comp) ? `<span class="verbatim">${esc2(plain(f.text || f.comp))}</span>` : esc2(f.text || f.comp || "")}</p>`;
  1790. if (k === "vibbar") {
  1791. const rows = f.rows || [];
  1792. if (!rows.length) return "";
  1793. const sig = (v) => {
  1794. const a = Math.abs(v);
  1795. return a >= 100 ? v.toFixed(0) : a >= 10 ? v.toFixed(1) : a >= 1 ? v.toFixed(2) : a >= 0.01 ? v.toFixed(3) : Number(v).toExponential(1);
  1796. };
  1797. const byM = {};
  1798. rows.forEach((r) => {
  1799. (byM[r.指标] = byM[r.指标] || []).push(r);
  1800. });
  1801. let h = `<p class="small muted">${t2("fig.vibbar_note")}</p>`;
  1802. for (const [mk, rs] of Object.entries(byM)) {
  1803. h += `<div class="tblwrap" style="margin-top:8px"><b class="small muted">${dv2(mk)}</b><table style="margin-top:2px"><tr><th>${t2("fig.point")}</th><th class="r">${t2("fig.x_median")}</th><th class="r">${t2("fig.this_unit")}</th><th class="r">${t2("fig.fleet_median")}</th><th class="r">z</th><th class="r">${t2("fig.trend")}</th></tr>` + rs.map((r) => {
  1804. const col = Math.abs(r.z) >= 3 ? "var(--critical)" : Math.abs(r.z) >= 2 ? "var(--warning)" : "var(--good)";
  1805. return `<tr><td class="small">${dv2(r.测点)}</td><td class="r" style="color:${col};font-weight:600">${r.倍中位 != null ? r.倍中位 + "×" : "—"}</td><td class="r small">${sig(r.本台)}</td><td class="r small muted">${sig(r.全场中位)}</td><td class="r small" style="color:${Math.abs(r.z) >= 3 ? "var(--critical)" : "var(--muted)"}">${r.z}</td><td class="r small" style="color:${r.六窗比 > 1.15 ? "var(--warning)" : "var(--muted)"}">${r.六窗比 != null ? r.六窗比 + "×" : "—"}</td></tr>`;
  1806. }).join("") + `</table></div>`;
  1807. }
  1808. return h;
  1809. }
  1810. const xs = f.months || f.x || [], n = xs.length;
  1811. if (!n) return "";
  1812. const W = 620, H = 250, ML = 52, MB = 34, MT = 14;
  1813. const all = [];
  1814. if (k === "band") [f.self, f.med, f.q1, f.q3].forEach((a) => (a || []).forEach((v) => {
  1815. if (v != null && v === v) all.push(v);
  1816. }));
  1817. else if (k === "bars") {
  1818. all.push(0);
  1819. (f.vals || []).forEach((v) => {
  1820. if (v != null && v === v) all.push(v);
  1821. });
  1822. } else if (k === "xy") [f.self, f.med].forEach((a) => (a || []).forEach((v) => {
  1823. if (v != null && v === v) all.push(v);
  1824. }));
  1825. else (f.series || []).forEach((s) => (s.vals || []).forEach((v) => {
  1826. if (v != null && v === v) all.push(v);
  1827. }));
  1828. (f.thresholds || []).forEach((th) => {
  1829. if (th && th.v != null) all.push(th.v);
  1830. });
  1831. if (!all.length) return "";
  1832. let lo = Math.min(...all), hi = Math.max(...all);
  1833. if (lo === hi) {
  1834. lo -= 1;
  1835. hi += 1;
  1836. }
  1837. const pad = (hi - lo) * 0.08;
  1838. lo -= pad;
  1839. hi += pad;
  1840. const X = (i) => ML + i / Math.max(n - 1, 1) * (W - ML - 12), Y = (v) => H - MB - (v - lo) / (hi - lo) * (H - MB - MT);
  1841. const line = (vals, c, w) => {
  1842. let segs = [], cur = [];
  1843. (vals || []).forEach((v, i) => {
  1844. if (v == null || v !== v) {
  1845. if (cur.length) segs.push(cur);
  1846. cur = [];
  1847. } else cur.push(`${X(i).toFixed(1)},${Y(v).toFixed(1)}`);
  1848. });
  1849. if (cur.length) segs.push(cur);
  1850. return segs.map((sg) => `<polyline points="${sg.join(" ")}" fill="none" stroke="${c}" stroke-width="${w || 1.8}"/>`).join("");
  1851. };
  1852. let body = "";
  1853. if (k === "band" && f.q1 && f.q3) {
  1854. const up = [], dn = [];
  1855. f.q3.forEach((v, i) => {
  1856. if (v != null) up.push(`${X(i).toFixed(1)},${Y(v).toFixed(1)}`);
  1857. });
  1858. for (let i = f.q1.length - 1; i >= 0; i--) if (f.q1[i] != null) dn.push(`${X(i).toFixed(1)},${Y(f.q1[i]).toFixed(1)}`);
  1859. if (up.length && dn.length) body += `<polygon points="${up.join(" ")} ${dn.join(" ")}" fill="var(--unknown)" fill-opacity=".18"/>`;
  1860. }
  1861. if (k === "bars") {
  1862. const bw = Math.max(3, (W - ML - 20) / n * 0.6), y0 = Y(Math.max(lo, 0));
  1863. body += (f.vals || []).map((v, i) => v == null || v !== v ? "" : `<rect x="${(X(i) - bw / 2).toFixed(1)}" y="${Y(v).toFixed(1)}" width="${bw.toFixed(1)}" height="${Math.max(y0 - Y(v), 0.8).toFixed(1)}" rx="2" fill="var(--accent)" fill-opacity=".75"/>`).join("");
  1864. }
  1865. if (f.med) body += line(f.med, "var(--muted)", 1.7);
  1866. if (f.self) body += line(f.self, "var(--critical)", 2.1) + (f.self || []).map((v, i) => v == null || v !== v ? "" : `<circle cx="${X(i).toFixed(1)}" cy="${Y(v).toFixed(1)}" r="2.4" fill="var(--critical)"/>`).join("");
  1867. const cols = ["var(--accent)", "#D48A1E", "#2E8B6A", "#7C5295"];
  1868. (f.series || []).forEach((s, i) => {
  1869. body += line(s.vals, cols[i % 4]);
  1870. });
  1871. (f.thresholds || []).forEach((th) => {
  1872. if (th == null || th.v == null) return;
  1873. body += `<line x1="${ML}" x2="${W - 12}" y1="${Y(th.v).toFixed(1)}" y2="${Y(th.v).toFixed(1)}" stroke="var(--muted)" stroke-dasharray="5 4"/>` + (th.label ? dtxt(ML + 6, Y(th.v) - 4, th.label, { fs: 10 }) : "");
  1874. });
  1875. const step = Math.max(1, Math.ceil(n / 8));
  1876. let xt = "";
  1877. for (let i = 0; i < n; i += step) xt += txt(X(i), H - 10, String(xs[i]).includes("-") ? String(xs[i]).slice(2, 7) : String(xs[i]), { fs: 10, anchor: "middle" });
  1878. let yt = "";
  1879. for (let i = 0; i < 5; i++) {
  1880. const v = lo + (hi - lo) * i / 4;
  1881. yt += txt(ML - 6, Y(v) + 3, v.toFixed(Math.abs(hi) < 10 ? 1 : 0), { fs: 10, anchor: "end" }) + `<line x1="${ML}" x2="${W - 12}" y1="${Y(v).toFixed(1)}" y2="${Y(v).toFixed(1)}" stroke="var(--line)"/>`;
  1882. }
  1883. const leg = k === "band" ? txt(W - 190, 12, t2("fig.this_unit"), { fill: "var(--critical)", fs: 10 }) + txt(W - 110, 12, t2("fig.fleet_median"), { fs: 10 }) + txt(W - 40, 12, t2("fig.quartile"), { fs: 10 }) : (f.series || []).length && (f.series || []).length <= 2 ? f.series.map((s, i) => txt(W - 12 - (f.series.length - 1 - i) * 118, 12, "— " + esc2(plain(s.name)), { fill: cols[i % 4], fs: 10, anchor: "end" })).join("") : "";
  1884. const legHtml = (f.series || []).length > 2 ? `<div class="legend" style="margin-top:6px;flex-wrap:wrap">` + f.series.map((s, i) => `<span><i style="background:${cols[i % 4]}"></i>${isV(s.name) ? `<span class="verbatim">${esc2(plain(s.name))}</span>` : esc2(s.name)}</span>`).join("") + `</div>` : "";
  1885. return svg(W, H, `${yt}${body}${xt}${leg}`) + legHtml;
  1886. }
  1887. return { quadChart, rateChart, waterfall, ramChart, attribBar, paretoChart, blameBar, lossName, multiline, capAxis, betaBar, rulerBar, fig };
  1888. };
  1889. const VIBJARGON = [
  1890. [/DE[/]NDE/g, "驱动端与非驱动端"],
  1891. [/GenDE/g, "发电机驱动端"],
  1892. [/GenNDE/g, "发电机非驱动端"],
  1893. [/HS_gen2?/g, "高速轴发电机侧"],
  1894. [/HS_rot/g, "高速轴转子侧"],
  1895. [/IMS/g, "中间轴"],
  1896. [/(?<![A-Za-z])NDE(?![A-Za-z])/g, "非驱动端"],
  1897. [/(?<![A-Za-z])DE(?![A-Za-z])/g, "驱动端"],
  1898. [/内圈BPFI/g, "内圈特征频率"],
  1899. [/外圈BPFO/g, "外圈特征频率"],
  1900. [/BPFI/g, "内圈特征频率"],
  1901. [/BPFO/g, "外圈特征频率"],
  1902. [/FTF/g, "保持架特征频率"],
  1903. [/BSF/g, "滚动体特征频率"],
  1904. [/TD_1R/g, "时域单转指标"],
  1905. [/Peak/g, "冲击峰值"],
  1906. [/Rms/gi, "有效值"],
  1907. [/([0-9]+(?:[.][0-9]+)?)[ ]*阶/g, "$1 倍轴频"],
  1908. [/[(]o([0-9]+(?:[.][0-9]+)?)[)]/g, "(轴频 $1 倍)"],
  1909. [/×p90/g, "×全场90分位"],
  1910. [/×fleet/g, "×全场中位"],
  1911. [/六窗/g, "六个观测窗"],
  1912. [/([0-9])窗/g, "$1 个观测窗"],
  1913. [/宽带/g, "宽频带总量"],
  1914. [/台阶/g, "阶跃上升"],
  1915. [/缓升/g, "缓慢上升"],
  1916. [/回落/g, "回落至"],
  1917. [/全净|六窗全净/g, "各窗均干净"],
  1918. [/清洁/g, "干净"],
  1919. [/回归/g, "回到常态"],
  1920. [/同族/g, "同批次共性 (见该批主台)"],
  1921. [/族第([^ ]+)台/g, "同批次第$1台"],
  1922. [/族/g, "批次共性组"],
  1923. [/gain异常/g, "测点增益异常"],
  1924. [/gain/g, "测点增益"],
  1925. [/[(]销[)]/g, "(已销案)"],
  1926. [/梯度/g, "沿链梯度最高点"],
  1927. [/周期主导/g, "周期成分主导"],
  1928. [/急性事件/g, "突发事件"],
  1929. [/慢性/g, "长期"],
  1930. [/主异常在/g, "主要异常位于"]
  1931. ];
  1932. const esc = (s) => String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
  1933. const displayText = (s) => String(s == null ? "" : s);
  1934. const dv = (s) => esc(s);
  1935. const ST = { 危险: "bad", 报警: "warn", 不可判: "unk", 良好: "ok", 优秀: "ex" };
  1936. const ST_ALIAS = {
  1937. critical: "bad",
  1938. alarm: "warn",
  1939. undeterminable: "unk",
  1940. indeterminate: "unk",
  1941. "not evaluable": "unk",
  1942. good: "ok",
  1943. excellent: "ex",
  1944. warn: "warn",
  1945. bad: "bad",
  1946. ok: "ok",
  1947. unk: "unk",
  1948. ex: "ex"
  1949. };
  1950. const stateKey = (v) => ST[String(v)] || ST_ALIAS[String(v)];
  1951. const STC = { bad: "var(--critical)", warn: "var(--warning)", unk: "var(--unknown)", ok: "var(--good)", ex: "var(--excellent)" };
  1952. const CST = { 优秀: "var(--excellent)", 良好: "var(--good)", 报警: "var(--warning)", 危险: "var(--critical)", 不可判: "var(--unknown)", 监测不可用: "var(--unknown)", 无监测: "var(--unknown)" };
  1953. const CLV = { bad: "var(--critical)", warn: "var(--warning)", note: "var(--muted)", ok: "var(--good)", unlisted: "var(--muted)", stale: "var(--muted)", meta: "var(--muted)" };
  1954. const TIER = {
  1955. 定论: "confirmed",
  1956. "准定论·预警": "near",
  1957. "候选·新发": "candidate_new",
  1958. 候选: "candidate",
  1959. "候选·记基线": "candidate_baseline",
  1960. 参考: "reference",
  1961. INSUFFICIENT: "insufficient",
  1962. 正常: "normal",
  1963. 撤回: "withdrawn"
  1964. };
  1965. const stKey = (k) => "st." + (k === "ex" ? "excellent" : k);
  1966. const stWord = (zh) => {
  1967. const k = stateKey(zh);
  1968. return k ? t(stKey(k)) : dv(zh);
  1969. };
  1970. const tierWord = (zh) => {
  1971. const k = TIER[String(zh)];
  1972. const key = "tier." + k;
  1973. return k ? t(key) : String(zh) === "—" ? "—" : dv(zh);
  1974. };
  1975. const duo = (st, ev, col) => `<span class="duo" style="--c:${col}"><span class="st">${st}</span><span class="ev">${ev}</span></span>`;
  1976. const vibPlain = (x) => {
  1977. let s = String(x == null ? "" : x);
  1978. for (const [a, b] of VIBJARGON) s = s.replace(a, b);
  1979. return s;
  1980. };
  1981. const attrPlain = (x) => vibPlain(x);
  1982. const spark = (hist, color) => {
  1983. if (!hist || hist.filter((x) => x != null).length < 3) return "";
  1984. const vs = hist.filter((x) => x != null);
  1985. const mn = Math.min(...vs), mx = Math.max(...vs), sp = mx - mn || 1;
  1986. const W = 84, H = 22, n = hist.length;
  1987. let d = "", last = null;
  1988. hist.forEach((v, i) => {
  1989. if (v == null) return;
  1990. const x = i / (n - 1) * W, y = H - 2 - (v - mn) / sp * (H - 4);
  1991. d += (d ? "L" : "M") + x.toFixed(1) + " " + y.toFixed(1);
  1992. last = [x, y];
  1993. });
  1994. return `<svg width="${W}" height="${H}" style="vertical-align:middle;margin-left:6px"><path d="${d}" fill="none" stroke="${color}" stroke-width="1.2" opacity=".75"/><circle cx="${last[0]}" cy="${last[1]}" r="2.4" fill="${color}"/></svg>`;
  1995. };
  1996. const FAMCO = { 齿轮箱: "#8A6D3B", 发电机: "#4A6FA5", 主轴承: "#2E7D6B", 变桨叶片: "#4A6FA5", 偏航: "#2E7D6B", 测风: "#7C5295", 其他: "#5A6470" };
  1997. const famBar = (fam, n, maxN) => {
  1998. if (!n) return `<span class="small muted">${t("fus.no_alarm")}</span>`;
  1999. const W = 118, w = Math.max(6, Math.round(n / Math.max(maxN || n, 1) * W));
  2000. return `<div title="${esc(t("fus.fam_tip", { n, m: maxN || n }))}" style="display:flex;height:9px;border-radius:3px;overflow:hidden;width:${w}px">${(fam || []).map((f) => `<i title="${esc(sysName(f.k))} ${f.n}" style="width:${(f.n / n * 100).toFixed(1)}%;background:${FAMCO[f.k] || "#5A6470"}"></i>`).join("")}</div><div class="small muted" style="margin-top:3px">${(fam || []).slice(0, 2).map((f) => sysName(f.k) + " " + f.n).join(" · ")}</div>`;
  2001. };
  2002. function matrixHtml(F) {
  2003. const months = (F.列窗 || {}).months || [];
  2004. let h = `<div class="panel mb" style="border-left:3px solid var(--warning)"><b>${t("fus.rules_head")}</b><p class="small" style="margin-top:4px">${t("fus.rule1")}<br>${t("fus.rule2")}</p></div>`;
  2005. h += `<div class="panel mb"><h3>${t("fus.matrix_head")}</h3><p class="small muted">${t("fus.matrix_note1")}</p><p class="small">${t("fus.matrix_note2", { n: months.length, m0: months[0] || "?" })}</p>
  2006. <div class="small muted" style="margin:6px 0 12px;line-height:2"><div><b>${t("fus.legend_state")}</b> ${["bad", "warn", "ok", "ex", "unk"].map((k) => `<span style="display:inline-flex;align-items:center;gap:4px;margin-right:12px"><i style="width:10px;height:10px;border-radius:2px;background:${STC[k]}"></i>${t(stKey(k))}</span>`).join("")}</div>
  2007. <div><b>${t("fus.legend_tier")}</b> ${["定论", "准定论·预警", "候选·新发", "候选", "候选·记基线", "参考", "INSUFFICIENT"].map((k) => `<span style="display:inline-flex;align-items:center;gap:4px;margin-right:12px"><i style="width:10px;height:10px;border-radius:2px;background:${(F.色标 || {})[k] || "#ccc"}"></i>${tierWord(k)}</span>`).join("")}</div><div>${t("fus.legend_note")}</div></div>`;
  2008. const shortWin = (w) => {
  2009. const s = String(w || "");
  2010. const m = s.match(/20[0-9][0-9]-[0-9][0-9](?:~20[0-9][0-9]-[0-9][0-9])?|至[ ]*20[0-9][0-9]-[0-9][0-9]|20[0-9][0-9]~20[0-9][0-9]/);
  2011. return m ? m[0].startsWith("至") ? t("win.through", { date: m[0].replace(/^至\s*/, "") }) : m[0] : s.slice(0, 14);
  2012. };
  2013. const th = (name, win, extra) => `<th title="${esc(win || "")}${extra || ""}"><div>${name}</div><div class="small muted" style="font-weight:400">${dv(shortWin(win))}${extra || ""}</div></th>`;
  2014. const LW = F.列窗 || {};
  2015. h += `<div class="tblwrap"><table class="fmx" style="table-layout:fixed;width:100%;min-width:900px"><colgroup><col style="width:7%">${'<col style="width:15.5%">'.repeat(6)}</colgroup><tr><th>${t("ui.unit")}</th>${th(t("fus.col.vib"), LW.振动, t("fus.col.fixed"))}${th(t("fus.col.temp"), LW.温度)}${th(t("fus.col.lub"), LW.润滑, t("fus.col.display_only"))}${th(t("fus.col.oil"), LW.油液)}${th(t("fus.col.alarm"), LW.报警)}${th(t("fus.col.wo"), LW.工单)}</tr>`;
  2016. const mat = F.矩阵 || [];
  2017. const MAXAL = Math.max(1, ...mat.map((r) => (r.报警 || {}).n || 0));
  2018. const MAXWO = Math.max(1, ...mat.map((r) => (r.工单 || {}).n || 0));
  2019. const WC = F.色标 || {};
  2020. const shortOf = (c) => {
  2021. if (c.ostate && c.ostate !== "不可判") return stWord(c.ostate);
  2022. const n = String(c.note || "");
  2023. if (/数据不足|不可判|nsufficient/.test(n)) return t("fus.short.nodata");
  2024. if (/未评估|ot assessed/.test(n)) return t("fus.short.uneval");
  2025. if (/无样|未取样|o sample/.test(n)) return t("fus.short.nosample");
  2026. if (/样龄|stale/.test(n)) return t("fus.short.stale");
  2027. const m = { ok: t("fus.short.normal"), unlisted: t("fus.short.unlisted"), stale: t("fus.short.stale") };
  2028. return m[c.level] || "—";
  2029. };
  2030. const cell = (c0) => {
  2031. const c = c0 || {};
  2032. const col = CST[c.ostate] || WC[c.wlevel] || CLV[c.level] || "var(--muted)";
  2033. const full = vibPlain(c.note);
  2034. const isProb = ["bad", "warn", "note"].includes(c.level);
  2035. const tip = esc(full) + t("fus.tip_state", { s: stWord(c.ostate || "?") });
  2036. if (!isProb) return `<td class="small" title="${tip}" style="border-left:3px solid ${col};color:var(--muted)">${shortOf(c)}</td>`;
  2037. const parts = full.split(/[::]/);
  2038. const brief = (parts.length > 1 ? parts.slice(1).join(":") : full).trim();
  2039. const cleanBrief = /^[ ]*(评估报告|两口径差异|⚠|ssessment)/.test(brief) || brief.length > 16 ? "" : brief;
  2040. const shortCut = cleanBrief ? dv(cleanBrief) : c.ostate && c.ostate !== "不可判" ? "" : shortOf(c);
  2041. const ev = c.wlevel && c.wlevel !== "—" && c.wlevel !== "正常" && c.ostate ? duo(stWord(c.ostate), tierWord(c.wlevel), col) : "";
  2042. return `<td class="small" title="${tip}${t("fus.tip_tier", { e: tierWord(c.wlevel || "?") })}" style="border-left:3px solid ${col};overflow:hidden"><div><div style="color:${col};font-weight:${["bad", "warn"].includes(c.level) ? 600 : 400}">${shortCut}</div>${ev}</div>${spark(c.hist, c.level === "bad" ? "var(--critical)" : c.level === "warn" ? "var(--warning)" : "var(--muted)")}</td>`;
  2043. };
  2044. mat.forEach((r) => {
  2045. const al = r.报警 || { n: 0, fam: [] }, wo = r.工单 || { n: 0, acts: [] };
  2046. const alc = al.rank && al.rank <= 5 ? "var(--warning)" : "var(--line)";
  2047. const bu = al.burst;
  2048. const altip = esc((al.items || []).map((x) => `${x.code} ${vibPlain(x.text)} — ${x.n} (${x.first}~${x.last})`).join("\n")) + (bu ? "\n" + esc(t("fus.burst_tip", { c: bu.code, p: (bu.share * 100).toFixed(0), m: bu.med_s })) : "");
  2049. h += `<tr><td><a href="/turbine/${r.turbine}"><b>${unitNo(r.turbine)}</b></a></td>${cell(r.振动)}${cell(r.温度)}${cell(r.润滑)}${cell(r.油液)}
  2050. <td title="${altip}" style="border-left:3px solid ${bu ? "var(--unknown)" : alc}"><b class="small num" style="color:${al.rank && al.rank <= 10 ? "var(--ink)" : "var(--muted)"}">${t("fus.alarm_n", { n: al.n })}</b>${al.rank ? `<span class="small muted">${t("fus.alarm_rank", { r: al.rank, t: al.tot })}</span>` : ""}${bu ? `<div class="small" style="color:var(--unknown)">${t("fus.burst", { p: (bu.share * 100).toFixed(0), c: bu.code })}</div>` : ""}${al.rank && al.rank <= 10 || bu ? famBar(al.fam, al.n, MAXAL) : ""}</td>
  2051. <td title="${esc((wo.items || []).map((x) => `${x.date} ${attrPlain(x.name)} → ${attrPlain(x.act)}${x.part ? " " + attrPlain(x.part) : ""}`).join("\n") || t("fus.wo_none"))}" style="min-width:110px">${wo.n ? `<b class="small">${t("fus.wo_n", { n: wo.n })}</b>${wo.last ? `<span class="small muted"> · ${esc(wo.last)}</span>` : ""}<div style="height:7px;border-radius:3px;background:var(--muted);opacity:.5;margin-top:4px;width:${Math.max(6, Math.round(wo.n / MAXWO * 90))}px"></div><div class="small muted" style="margin-top:3px">${(wo.acts || []).slice(0, 2).map((a) => dv(a.k) + "×" + a.n).join(" · ")}</div>` : `<span class="small muted">—</span>`}</td></tr>`;
  2052. });
  2053. return h + `</table></div><p class="small muted" style="margin-top:8px">${t("fus.matrix_foot")}</p></div>`;
  2054. }
  2055. const baBar = (pre, post, col) => {
  2056. const M = Math.max(pre, post, 7), W = 120, sc = (v) => Math.max(2, v / M * (W - 4));
  2057. return `<svg class="dt" viewBox="0 0 ${W} 26" style="width:${W}px;height:26px"><rect x="2" y="2" width="${sc(pre).toFixed(1)}" height="9" rx="2" fill="var(--muted)" fill-opacity=".55"/><rect x="2" y="15" width="${sc(post).toFixed(1)}" height="9" rx="2" fill="${col}" fill-opacity=".9"/></svg>`;
  2058. };
  2059. const chainMini = (comp, col) => {
  2060. const c = String(comp || "").replace(/main bearing[, ]*[(]?(?:rear|back)[)]?/ig, "主轴承后").replace(/main bearing[, ]*[(]?(?:front|forward)[)]?/ig, "主轴承前").replace(/main bearing/ig, "主轴承").replace(/generator[, ]*(?:NDE|non-drive[- ]end)/ig, "发电机NDE").replace(/generator[, ]*(?:DE|drive[- ]end)/ig, "发电机DE").replace(/generator/ig, "发电机").replace(/gearbox/ig, "齿轮箱").replace(/planetary/ig, "行星").replace(/intermediate[- ](?:speed[- ])?shaft/ig, "中间轴").replace(/high[- ]speed shaft/ig, "高速轴"), gball = /齿轮箱/.test(c) && !/行星|中间轴|中速|高速轴/.test(c);
  2061. return `<span class="chain" title="${esc(displayText(comp))}">${CHAIN7.map(([k, re]) => {
  2062. const on = re.test(c) || gball && ["ps", "ims", "hss"].includes(k);
  2063. return `<i class="${on ? "on" : ""}" style="${on ? `background:${col};border-color:${col}` : ""}" title="${t("chain." + k)}"></i>`;
  2064. }).join("")}</span>`;
  2065. };
  2066. function loopHtml(F) {
  2067. const LP = F.能力 && F.能力.loop && F.能力.loop["换件闭环 (检出→换件→恢复)"] ? F.能力.loop : null;
  2068. if (!LP) return `<div class="panel mb"><p class="small">${t("fus.loop_none")}</p></div>`;
  2069. const cl = LP["换件闭环 (检出→换件→恢复)"] || [];
  2070. const up = LP["当前在升 (无工单可对)"] || [];
  2071. const neg = LP["负面对照 (工单后持平)"];
  2072. return `<div class="panel mb"><h3>${t("fus.loop_head")}</h3><p class="small">${t("fus.loop_note")}</p>
  2073. <h3 style="margin-top:8px">${t("fus.loop_closed", { n: cl.length })}</h3><div class="tblwrap"><table><tr><th>${t("ui.unit")}</th><th>${t("fus.col.part")}</th><th>${t("fus.loop_item")}</th><th>${t("fus.loop_date")}</th><th>${t("fus.loop_ba")}</th><th class="r">${t("fus.loop_ratio")}</th><th>${t("fus.loop_verdict")}</th></tr>
  2074. ${cl.map((r) => `<tr><td><b>${unitNo(r.turbine)}</b></td><td>${chainMini(r.component + (r.title || ""), "var(--good)")} <span class="small">${partName(r.component)}</span></td><td class="small">${dv(r.title)}</td><td>${esc(r.replace_date)}</td><td>${baBar(r.pre, r.post, "var(--good)")}<span class="small muted">${r.pre} → ${r.post}</span></td><td class="r"><b style="color:var(--good)">${r.ratio}×</b></td><td><span class="chip ok">${dv(r.verdict)}</span></td></tr>`).join("")}</table></div>
  2075. <p class="small" style="margin-top:6px">${t("fus.loop_neg", { n: neg })}</p>
  2076. <h3 style="margin-top:14px">${t("fus.loop_rising", { n: up.length })}</h3><div class="tblwrap"><table><tr><th>${t("ui.unit")}</th><th>${t("fus.col.part")}</th><th>${t("fus.loop_el")}</th><th class="r">${t("fus.loop_x")}</th><th>${t("fus.loop_state")}</th></tr>
  2077. ${up.map((r) => `<tr><td><b>${unitNo(r.turbine)}</b></td><td>${chainMini(r.component, "var(--warning)")} <span class="small">${partName(r.component)}</span></td><td>${baBar(r.early, r.late, "var(--warning)")}<span class="small muted">${r.early} → ${r.late}</span></td><td class="r"><b style="color:var(--warning)">${r.ratio}×</b></td><td><span class="chip warn">${t("rpt.units.rising", { r: r.ratio })}</span></td></tr>`).join("")}</table></div>
  2078. <p class="small muted" style="margin-top:8px">${t("fus.loop_bound", { b: dv(String(LP["★数据边界"] || "").split("**").join("")) })}</p></div>`;
  2079. }
  2080. const devBar = (ch, dev, vd, raw) => {
  2081. if (dev == null) return `<span class="small">${dv(displayText(raw).slice(0, 30))}</span>`;
  2082. const M = 6, W = 104, mid = W / 2, k = Math.max(-1, Math.min(1, dev / M)), bw = Math.abs(k) * (mid - 8), strong = Math.abs(dev) >= 3;
  2083. const col = !strong ? "var(--muted)" : /候选|andidate/.test(vd || "") ? "var(--warning)" : /记基线|aseline/.test(vd || "") ? "var(--accent)" : /不可判|返服|ndeterm/.test(vd || "") ? "var(--unknown)" : "var(--muted)", over = Math.abs(dev) > M;
  2084. return `<div title="${esc(raw || "")}" style="display:flex;align-items:center;gap:7px"><svg class="dt" viewBox="0 0 ${W} 18" style="width:${W}px;height:18px;flex:none"><line x1="4" y1="9" x2="${W - 4}" y2="9" stroke="var(--line)" stroke-opacity=".7"/><line x1="${mid}" y1="1" x2="${mid}" y2="17" stroke="var(--muted)" stroke-opacity=".6"/><rect x="${dev >= 0 ? mid : mid - bw}" y="4" width="${Math.max(bw, 1.5)}" height="10" rx="2" fill="${col}" fill-opacity=".9"/>${over ? `<path d="M${dev > 0 ? W - 5 : 5},9 l${dev > 0 ? -5 : 5},-4 v8 z" fill="${col}"/>` : ""}</svg><b class="small num" style="color:${col}">${dev > 0 ? "+" : ""}${dev}K</b></div><div class="small muted" style="margin-top:1px">${esc(ch || "")}</div>`;
  2085. };
  2086. const winBar = (spans, raw) => {
  2087. if (!spans || !spans.length) return `<span class="small" title="${esc(raw || "")}">${t("fus.win_none")}</span>`;
  2088. const T0 = Date.parse("2026-01-01"), T1 = Date.parse("2026-09-01"), W = 118, px = (d) => 3 + Math.max(0, Math.min(1, (Date.parse(d) - T0) / (T1 - T0))) * (W - 6);
  2089. return `<div title="${esc(raw || "")}"><svg class="dt" viewBox="0 0 ${W} 18" style="width:${W}px;height:18px"><rect x="3" y="4" width="${W - 6}" height="10" rx="2" fill="var(--line)" fill-opacity=".5"/>${spans.map(([a, b]) => {
  2090. const x1 = px(a), x2 = px(b);
  2091. return `<rect x="${x1.toFixed(1)}" y="4" width="${Math.max(x2 - x1, 2.5).toFixed(1)}" height="10" rx="2" fill="var(--accent)" fill-opacity=".8"/>`;
  2092. }).join("")}</svg><div class="small muted">${t("fus.win_n", { n: spans.length })}</div></div>`;
  2093. };
  2094. const oilPill = (last, fresh, raw) => `<div title="${esc(raw || "")}" style="display:flex;align-items:center;gap:6px"><i style="width:9px;height:9px;border-radius:50%;flex:none;background:${fresh ? "var(--good)" : "var(--unknown)"}"></i><span class="small num">${esc(last || t("fus.oil_none"))}</span></div><div class="small muted">${last ? fresh ? t("fus.oil_fresh") : t("fus.oil_stale") : "—"}</div>`;
  2095. const stateChip = (zh, cls) => {
  2096. const k = stateKey(zh);
  2097. return k ? `<span class="chip ${k} ${""}">${t(stKey(k))}</span>` : zh ? `<span class="chip">${esc(zh)}</span>` : "";
  2098. };
  2099. function unitsHtml(F, filter) {
  2100. const tb = F.表 || [], mat = F.矩阵 || [];
  2101. let rows = tb;
  2102. const filt = "";
  2103. let h = `<div class="panel mb"><h3>${t("fus.units_head", { u: new Set(tb.map((x) => x.turbine)).size, n: rows.length + "", f: filt })}</h3>
  2104. <div style="display:flex;gap:8px;margin:8px 0 12px;flex-wrap:wrap">${["bad", "warn", "note", "ok"].map((k) => `<button class="pill ${filter === k ? "on" : ""}" data-ffilter="${k}">${t("fus.lv." + k)}</button>`).join("")}<button class="pill ${""}" data-ffilter="unlisted">${t("fus.unlisted_link", { n: mat.filter((r) => (r.振动 || {}).level === "unlisted").length })}</button></div>
  2105. <div class="panel" style="border-left:3px solid var(--accent);margin-bottom:12px"><b class="small">${t("fus.eval_head")}</b><div class="tblwrap"><table style="margin-top:8px"><tr><th style="width:30%">${t("fus.eval_state")}</th><th style="width:30%">${t("fus.eval_tier")}</th><th>${t("fus.eval_meaning")}</th></tr>
  2106. <tr><td>${stateChip("危险")}</td><td><span class="tier confirmed">${t("tier.confirmed.s")}</span></td><td class="small">${t("fus.m.bad")} <span class="muted">(${t("fus.m.bad_e")})</span></td></tr>
  2107. <tr><td>${stateChip("报警")}</td><td><span class="tier near">${t("tier.near")}</span> / <span class="tier candidate">${t("tier.candidate_new")}</span></td><td class="small">${t("fus.m.warn")} <span class="muted">(${t("fus.m.warn_e")})</span></td></tr>
  2108. <tr><td>${stateChip("良好")}</td><td><span class="tier candidate">${t("tier.candidate")}</span> / <span class="tier candidate">${t("tier.candidate_baseline")}</span></td><td class="small">${t("fus.m.ok")} <span class="muted">(${t("fus.m.ok_e")})</span></td></tr>
  2109. <tr><td>${stateChip("优秀")}</td><td><span class="tier reference">${t("tier.reference")}</span> / <span class="tier reference">${t("tier.normal")}</span> / <span class="tier withdrawn">${t("tier.withdrawn")}</span></td><td class="small">${t("fus.m.ex")} <span class="muted">(${t("fus.m.ex_e")})</span></td></tr>
  2110. <tr><td>${stateChip("不可判")}</td><td><span class="tier insufficient">${t("tier.insufficient")}</span></td><td class="small">${t("fus.m.unk")} <span class="muted">(${t("fus.m.unk_e")})</span></td></tr></table></div><p class="small muted" style="margin-top:7px">${t("fus.eval_note")}</p></div>
  2111. <div class="tblwrap"><table><tr><th>${t("ui.unit")}</th><th>${t("fus.col.part")}</th><th>${t("fus.col.state_concl")}</th><th>${t("fus.col.claim")} <span class="small muted" style="font-weight:400">${t("fus.col.claim_axis")}</span></th><th>${t("fus.col.signal")}</th><th>${t("fus.col.tempaxis")} <span class="small muted" style="font-weight:400">${t("fus.col.tempaxis_note")}</span></th><th>${t("fus.col.oil")}</th></tr>`;
  2112. rows.forEach((r) => {
  2113. const sev = CLV[r.级] || "var(--accent)", col = CST[r.设备状态] || "var(--muted)";
  2114. h += `<tr><td><a href="/turbine/${r.turbine}"><b>${unitNo(r.turbine)}</b></a></td><td style="min-width:200px">${chainMini(r.部件, sev)} <span class="small">${partName(r.部件)}</span></td>
  2115. <td title="${esc(vibPlain(r.振动结论))}">${duo(stWord(r.设备状态 || "—"), dv(r.振动结论), col)}</td><td style="min-width:130px">${winBar(r.窗区间, r.claim_window)}</td>
  2116. <td class="small" title="${esc(r.振动信号 || "")}">${dv(vibPlain(r.振动信号))}</td><td style="min-width:158px">${devBar(r.温度通道, r.温度dev, r.温度判, r.温度轴)}</td><td style="min-width:112px">${oilPill(r.油末样, r.油新鲜, r.油样轴)}</td></tr>`;
  2117. });
  2118. return h + `</table></div></div>`;
  2119. }
  2120. const CHAIN7 = [["mbf", /主轴承[((]前|主轴承前|主轴承[++]|主轴承(?![((前后])/], ["mba", /主轴承[((]后|主轴承后/], ["ps", /行星|二级行星/], ["ims", /中间轴|中速/], ["hss", /高速轴|高速(?!.*发电)/], ["gde", /发电机DE|发电机[((]|发电机(?!NDE)/], ["gnde", /发电机NDE|NDE/]];
  2121. const gc = (st) => STC[stateKey(st)] || null;
  2122. const chainStates = (map) => `<span class="chain">${Object.entries(map).map(([k, col]) => `<i class="${col ? "on" : ""}" style="${col ? `background:${col};border-color:${col}` : ""}" title="${t("chain." + k)}"></i>`).join("")}</span>`;
  2123. const evLadder = (x) => {
  2124. const T = String(x || ""), n = /准定论|near[- ](?:conf|establish)/i.test(T) ? 3 : /确诊|onfirmed/.test(T) ? 4 : /定论|ettled|stablished/.test(T) ? 3 : /候选|andidate/.test(T) ? 2 : /参考|eference/.test(T) ? 1 : 0, col = n >= 4 ? "var(--critical)" : n === 3 ? "var(--warning)" : n === 2 ? "var(--accent)" : n === 1 ? "var(--muted)" : "var(--unknown)";
  2125. return `<div style="display:flex;align-items:center;gap:7px"><svg class="dt" viewBox="0 0 62 20" style="width:62px;height:20px;flex:none">${[0, 1, 2, 3].map((i) => `<rect x="${2 + i * 15}" y="${12 - i * 3}" width="11" height="${6 + i * 3}" rx="1.5" fill="${i < n ? col : "none"}" fill-opacity=".9" stroke="${col}" stroke-opacity="${i < n ? 1 : 0.4}" ${i >= n ? 'stroke-dasharray="2 1.5"' : ""}/>`).join("")}</svg><span class="small" style="color:${col}">${dv(T)}</span></div>`;
  2126. };
  2127. function eventsHtml(F, d) {
  2128. if (!F.事件) return `<div class="panel mb"><p class="small muted">${t("ui.empty")}</p></div>`;
  2129. let h = `<div class="panel mb"><p class="small muted"><b>${t("fus.ev_win", { win: winName(d.win) })}</b></p></div>`;
  2130. for (const [comp, E0] of Object.entries(F.事件)) {
  2131. const E = E0;
  2132. const A = E.报警 || {}, W = E.工单 || {};
  2133. h += `<div class="panel mb"><h3>${t("fus.ev_head", { c: sysName(comp), an: A.总条数 || 0, au: A.台数 || 0, wn: W.总条数 || 0, wu: W.台数 || 0 })}</h3>`;
  2134. if (A.总条数 > 0) {
  2135. h += `<div class="tblwrap"><table><tr><th>${t("fus.ev_code")}</th><th>${t("fus.ev_text")}</th><th class="r">${t("ui.count")}</th><th class="r">${t("fus.ev_units")}</th><th class="r">${t("fus.ev_dur")}</th></tr>${(A.码统计 || []).map((c) => `<tr><td>${esc(c.code)}</td><td class="small">${dv(vibPlain(c.text))}</td><td class="r">${c.条数}</td><td class="r">${c.台数}</td><td class="r">${c.时长h}</td></tr>`).join("")}</table></div>
  2136. <p class="small">${t("fus.ev_top")}${Object.entries(A.top台 || {}).map(([u, n]) => `<a href="/turbine/${u}">${unitNo(u)}</a>×${n}`).join(" · ")}</p>
  2137. <details><summary class="small">${t("fus.ev_recent", { n: (A.近况 || []).length })}</summary><div class="tblwrap"><table><tr><th>${t("ui.unit")}</th><th>${t("fus.ev_time")}</th><th>${t("fus.ev_code")}</th><th>${t("fus.ev_text")}</th><th class="r">${t("fus.ev_min")}</th></tr>${(A.近况 || []).map((x) => `<tr><td>${unitNo(x.t)}</td><td class="small">${esc(x.on)}</td><td>${esc(x.code)}</td><td class="small">${dv(vibPlain(x.text))}</td><td class="r">${x.dur_min ?? "—"}</td></tr>`).join("")}</table></div></details>`;
  2138. } else h += `<p class="small">${t("fus.ev_no_alarm")}</p>`;
  2139. if (W.总条数 > 0) h += `<div class="tblwrap"><table style="margin-top:8px"><tr><th>${t("ui.unit")}</th><th>${t("ui.date")}</th><th>${t("fus.ev_wo_name")}</th><th>${t("fus.ev_wo_act")}</th><th>${t("fus.ev_wo_cause")}</th></tr>${(W.明细 || []).map((x) => `<tr><td>${unitNo(x.t)}</td><td class="small">${esc(x.date)}</td><td class="small">${dv(x.name)}</td><td class="small">${dv(x.act)}</td><td class="small">${dv(x.cause)}</td></tr>`).join("")}</table></div>`;
  2140. else h += `<p class="small">${t("fus.ev_no_wo")}</p>`;
  2141. h += `</div>`;
  2142. }
  2143. return h;
  2144. }
  2145. function cmsHtml(V, err) {
  2146. if (!V) return `<div class="panel mb"><p class="small muted">${t("fus.cms_loading")}</p></div>`;
  2147. if (V.error) return `<div class="panel mb"><p class="small" style="color:var(--critical)">${dv(V.error)}</p></div>`;
  2148. const CK = {
  2149. "主轴承前": "mbf",
  2150. "主轴承后": "mba",
  2151. "齿轮箱": "gb",
  2152. "发电机": "gen",
  2153. "发电机DE": "gde",
  2154. "发电机NDE": "gnde",
  2155. "Main bearing, front": "mbf",
  2156. "Main bearing, rear": "mba",
  2157. "Gearbox": "gb",
  2158. "Generator": "gen",
  2159. "Generator DE": "gde",
  2160. "Generator NDE": "gnde"
  2161. };
  2162. return `<div class="panel mb"><h3>${t("fus.cms_head", { d: esc(V.date || "") })}</h3>
  2163. ${V["时间范围"] ? `<p class="small" style="margin:2px 0 6px">${t("fus.cms_window", { r: esc(V["时间范围"]) })}</p>` : ""}
  2164. <p class="small">${dv(V.overall || "")} ${t("fus.cms_note")}</p>
  2165. <h3 style="margin-top:10px">${t("fus.cms_action")}</h3><div class="tblwrap"><table><tr><th>${t("ui.unit")}</th><th>${t("fus.loop_state")}</th><th>${t("fus.cms_focus")} <span class="small muted" style="font-weight:400">${t("fus.cms_focus_note")}</span></th><th>${t("fus.cms_evidence")}</th><th>${t("fus.cms_act")}</th></tr>
  2166. ${(V.action || []).map((r) => {
  2167. const cm = {};
  2168. String(r[2]).split(/[;;]/).forEach((seg) => {
  2169. const m = seg.split(/[::]/);
  2170. if (m.length > 1) {
  2171. const k = CK[m[0].trim()] || null;
  2172. if (k) cm[k] = gc(m[1].trim());
  2173. }
  2174. });
  2175. return `<tr><td><b>${unitNo(r[0])}</b></td><td>${stateChip(r[1])}</td><td title="${esc(r[2])}">${chainStates(cm)} <span class="small muted">${dv(r[2])}</span></td><td style="min-width:150px">${evLadder(r[3])}</td><td>${/P0/.test(r[4]) ? `<b style="color:var(--critical)">${esc(r[4])}</b>` : esc(r[4])}</td></tr>`;
  2176. }).join("")}</table></div>
  2177. <h3 style="margin-top:14px">${t("fus.cms_grades")}</h3><div class="tblwrap"><table><tr><th>${t("ui.unit")}</th><th>${t("fus.cms_chain")} <span class="small muted" style="font-weight:400">${t("fus.cms_chain_note")}</span></th><th>${t("fus.cms_overall")}</th><th>${t("fus.cms_act")}</th></tr>
  2178. ${(V.grades || []).map((g) => `<tr><td><b>${unitNo(g.t)}</b></td><td title="${t("chain.mbf")} ${stWord(g.主轴承前)} · ${t("chain.mba")} ${stWord(g.主轴承后)} · ${t("chain.gb")} ${stWord(g.齿轮箱)} · ${t("chain.gen")} ${stWord(g.发电机)}">${chainStates({ mbf: gc(g.主轴承前), mba: gc(g.主轴承后), gb: gc(g.齿轮箱), gen: gc(g.发电机) })}</td><td>${stateChip(g.综合)}</td><td class="small">${esc(g.行动)}</td></tr>`).join("")}</table></div>
  2179. <div class="small" style="margin-top:12px;padding:10px;border:1px solid var(--line);border-radius:8px"><b>${t("fus.cms_basis")}</b> ${t("fus.cms_basis_note", { a: ((V.diff || {}).only_cms || []).map(unitNo).join(", ") || t("fus.none_word"), b: ((V.diff || {}).only_vibration_package || (V.diff || {})["only_handoff"] || []).map(unitNo).join(", ") || t("fus.none_word") })}</div>
  2180. <p class="small muted" style="margin-top:10px">${t("fus.cms_link")} <a href="http://localhost:8030/index.html" target="_blank" rel="noopener">${t("fus.cms_open")}</a> <a href="/windcms/index.html" target="_blank" rel="noopener">${t("fus.cms_static")}</a></p></div>`;
  2181. }
  2182. const FUS_SUBS = ["matrix", "loop", "units", "events", "cms"];
  2183. const G = typeof window !== "undefined" ? window : globalThis;
  2184. const factory = G.__charts;
  2185. const CH = factory ? factory({ t, fmt, esc, dv, EN: false, displayText: (x) => x == null ? "" : String(x), verb: () => {
  2186. } }) : {};
  2187. function chart(name, ...args) {
  2188. const fn = CH[name];
  2189. if (typeof fn !== "function") return "";
  2190. try {
  2191. return fn(...args);
  2192. } catch (e) {
  2193. return "";
  2194. }
  2195. }
  2196. const _sfc_main$8 = /* @__PURE__ */ defineComponent({
  2197. __name: "Fault",
  2198. __ssrInlineRender: true,
  2199. props: {
  2200. d: {}
  2201. },
  2202. setup(__props) {
  2203. const props = __props;
  2204. const F = computed(() => props.d?.faults || {});
  2205. const pm = computed(() => F.value.partial || []);
  2206. const M = computed(() => props.d?.m8 && !props.d.m8.err ? props.d.m8.mtbf : null);
  2207. const mo = computed(() => F.value.monthly || { months: [], vals: [] });
  2208. const perDay = computed(() => mo.value.per_day || []);
  2209. const exampleIdx = computed(() => pm.value.length ? (mo.value.months || []).indexOf(pm.value[0]) : -1);
  2210. const rateTail = computed(() => {
  2211. const head = pm.value.length ? t("flt.rate_partial", { m: pm.value.join("/") }) : t("flt.rate_full");
  2212. const hasExample = exampleIdx.value >= 0 && !!mo.value.per_day;
  2213. return head + (hasExample ? " " + t("flt.rate_example", { m: pm.value[0], v: exampleVal.value }) : "");
  2214. });
  2215. const exampleVal = computed(() => {
  2216. const i = exampleIdx.value;
  2217. return i >= 0 && mo.value.per_day ? mo.value.per_day[i] : null;
  2218. });
  2219. const partialWarnTxt = computed(() => pm.value.length ? t("win.partial", { m: pm.value.join("/"), c: Math.round((F.value.cov_min || 0) * 100) }) : "");
  2220. const basic = {
  2221. grid: { left: 60, right: 20, top: 24, bottom: 46 },
  2222. tooltip: { trigger: "axis" },
  2223. yAxis: { type: "value" }
  2224. };
  2225. computed(() => ({
  2226. ...basic,
  2227. xAxis: { type: "category", data: mo.value.months || [] },
  2228. series: [{ type: "bar", data: perDay.value.map((v) => v == null ? null : Math.round(v * 100) / 100), itemStyle: { color: "#0b5fa5" } }],
  2229. __data__: perDay.value.filter((v) => v != null)
  2230. }));
  2231. function pareto(items) {
  2232. const it = (items || []).filter((x) => x && x.v > 0);
  2233. return {
  2234. ...basic,
  2235. xAxis: { type: "category", data: it.map((x) => x.k) },
  2236. series: [{ type: "bar", data: it.map((x) => x.v), itemStyle: { color: "#2f7dbf" } }],
  2237. __data__: []
  2238. // 旧 Pareto 只把坐标刻度写成 SVG 文字(数据靠柱高/悬停),故不给数
  2239. };
  2240. }
  2241. const shortK = (k) => {
  2242. const m = String(k).match(/^([0-9]+)/);
  2243. return m ? m[1] : String(k).slice(0, 4);
  2244. };
  2245. const esc2 = (s) => String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
  2246. function paretoSummaryHtml(items, unit, note) {
  2247. const it = (items || []).filter((x) => x && x.v > 0);
  2248. if (!it.length) return esc2(t("ch.pareto.none"));
  2249. const tot = it.reduce((a, x) => a + x.v, 0);
  2250. let cum = 0;
  2251. let k80 = null;
  2252. for (let i = 0; i < it.length; i++) {
  2253. cum += it[i].v;
  2254. if (k80 === null && tot && cum / tot >= 0.8) k80 = i + 1;
  2255. }
  2256. const top3 = it.slice(0, 3).map((x) => `<b>${esc2(shortK(x.k))}</b> ${esc2(String(x.k).replace(/^[0-9]+ */, "").slice(0, 18))}`).join(" · ");
  2257. const total = t("ch.pareto.total", { n: it.length, tot: fmt(Math.round(tot)), unit });
  2258. const head = k80 ? `<b>${esc2(t("ch.pareto.key", { k: k80 }))}</b> (${esc2(total)})` : esc2(total);
  2259. return head + "<br>" + esc2(t("ch.pareto.top3")) + top3 + (note ? "<br>" + esc2(note) : "");
  2260. }
  2261. const paretoNSummary = computed(() => paretoSummaryHtml(F.value.pareto_n || [], t("unit.n"), t("flt.pareto_n_note")));
  2262. const paretoDSummary = computed(() => paretoSummaryHtml(F.value.pareto_d || [], " h", t("flt.pareto_d_note")));
  2263. const stopParetoSummary = computed(() => paretoSummaryHtml(stopPareto.value, " h", t("flt.stop_pareto_note")));
  2264. computed(() => pareto(F.value.pareto_n || []));
  2265. computed(() => pareto(F.value.pareto_d || []));
  2266. function k80Of(items) {
  2267. const it = (items || []).filter((x) => x && x.v > 0);
  2268. if (!it.length) return null;
  2269. const tot = it.reduce((a, x) => a + x.v, 0);
  2270. let cum = 0;
  2271. for (const x of it) {
  2272. cum += x.v;
  2273. if (tot && cum / tot >= 0.8) return { k: String(x.k), n: it.length, tot };
  2274. }
  2275. return { k: String(it[it.length - 1].k), n: it.length, tot };
  2276. }
  2277. computed(() => k80Of(F.value.pareto_n || []));
  2278. computed(() => k80Of(F.value.pareto_d || []));
  2279. computed(() => {
  2280. const it = (F.value.quad || []).filter((x) => x && x.n > 0 && x.med > 0);
  2281. return {
  2282. grid: { left: 60, right: 24, top: 24, bottom: 46 },
  2283. xAxis: { type: "log", name: "MTBF(h)" },
  2284. yAxis: { type: "value", name: "MDT(h)" },
  2285. series: [{
  2286. type: "scatter",
  2287. data: it.map((x) => [x.mtbf ?? x.n, x.med]),
  2288. symbolSize: (v) => Math.min(28, 6 + Math.sqrt(Math.max(v[0] || 1, 1))),
  2289. itemStyle: { color: "#b42318", opacity: 0.6 }
  2290. }],
  2291. __data__: []
  2292. // 象限图是散点:坐标即数据,旧图不把数值写成文字
  2293. };
  2294. });
  2295. const pt = computed(() => F.value.per_t || []);
  2296. const maxT = computed(() => Math.max(1, ...pt.value.map((x) => x.v)));
  2297. const seasonal = computed(() => M.value && props.d?.m8?.seasonal ? props.d.m8.seasonal : []);
  2298. const ML_COLS = ["var(--accent)", "#D48A1E", "#2E8B6A", "#7C5295"];
  2299. const seasonalNames = computed(() => seasonal.value.slice(0, 4).map((x) => String(x.name).slice(0, 16)));
  2300. computed(() => {
  2301. const s = seasonal.value.slice(0, 4);
  2302. const months = s.length ? s[0].months : [];
  2303. return {
  2304. ...basic,
  2305. xAxis: { type: "category", data: months },
  2306. legend: { top: 0, textStyle: { fontSize: 10 } },
  2307. series: s.map((x) => ({ name: String(x.name).slice(0, 16), type: "line", data: x.vals, connectNulls: false })),
  2308. __data__: []
  2309. // 旧 multiline 只标刻度;系列值不写成文字
  2310. };
  2311. });
  2312. const RT = computed(() => props.d?.rel && !props.d.rel.err ? props.d.rel : null);
  2313. computed(() => {
  2314. const rows = RT.value?.表 || [];
  2315. return {
  2316. grid: { left: 60, right: 24, top: 24, bottom: 40 },
  2317. xAxis: { type: "value", name: "MTBF(h)" },
  2318. yAxis: { type: "value", name: "MDT(h)" },
  2319. series: [{
  2320. type: "scatter",
  2321. data: rows.filter((r) => !r.外部 && r.MTBF_h).map((r) => [r.MTBF_h, r.MDT_h]),
  2322. symbolSize: 8,
  2323. itemStyle: { color: "#2f7dbf", opacity: 0.5 }
  2324. }],
  2325. __data__: []
  2326. // 旧 ramChart 也是散点(MTBF×MDT),数值不写成文字
  2327. };
  2328. });
  2329. const attribGroups = computed(() => {
  2330. const rows = RT.value?.表 || [];
  2331. const unk = (rows.find((r) => r.部件 === "无记录停机") || {}).停机时长h || 0;
  2332. const un2 = rows.filter((r) => r.部件 === "未归类").reduce((a, r) => a + r.停机时长h, 0);
  2333. const tot = RT.value?.总停机h || 0;
  2334. const ok = tot - unk - un2;
  2335. return { ok, unk, un2 };
  2336. });
  2337. computed(() => {
  2338. const g = attribGroups.value;
  2339. return {
  2340. xAxis: { type: "value" },
  2341. yAxis: { type: "category", data: [t("ch.attrib.ok", { h: Math.round(g.ok), p: "" }), t("ch.attrib.none", { h: Math.round(g.unk), p: "" }), t("ch.attrib.uncls", { h: Math.round(g.un2) })] },
  2342. series: [{ type: "bar", data: [Math.round(g.ok), Math.round(g.unk), Math.round(g.un2)], itemStyle: { color: "#0b5fa5" } }],
  2343. __data__: [Math.round(g.ok), Math.round(g.unk), Math.round(g.un2)]
  2344. };
  2345. });
  2346. const stopPareto = computed(() => (props.d?.m8?.stop_pareto || []).map((x) => ({
  2347. k: String(x.k).split(";")[0].slice(0, 26),
  2348. v: x.h
  2349. })));
  2350. computed(() => pareto(stopPareto.value));
  2351. const partPlain = (k) => partName(k);
  2352. const seasonalRaw = computed(() => ({
  2353. months: seasonal.value[0] && seasonal.value[0].months || [],
  2354. series: seasonal.value.slice(0, 4).map((x) => ({ name: String(x.name), vals: x.vals }))
  2355. }));
  2356. const top = computed(() => M.value?.台级top || []);
  2357. const mxTop = computed(() => Math.max(1, ...top.value.map((x) => x.时长h)));
  2358. return (_ctx, _push, _parent, _attrs) => {
  2359. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("flt.eyebrow"))}</div><div class="panel mb small" style="${ssrRenderStyle({ "border-left": "3px solid var(--accent)" })}"><b>${ssrInterpolate(unref(t)("win.banner", { win: unref(winName)(__props.d.win) }))}</b>`);
  2360. if (partialWarnTxt.value) {
  2361. _push(`<!--[--> | <span style="${ssrRenderStyle({ "color": "var(--warning)" })}">${ssrInterpolate(partialWarnTxt.value)}</span><!--]-->`);
  2362. } else {
  2363. _push(`<!---->`);
  2364. }
  2365. _push(`</div><div class="grid2"><div class="eyebrow" style="${ssrRenderStyle({ "grid-column": "1/-1", "margin-top": "4px" })}">${ssrInterpolate(unref(t)("flt.alarm_face"))} <span style="${ssrRenderStyle({ "text-transform": "none", "letter-spacing": "0", "color": "var(--muted)" })}">${ssrInterpolate(unref(t)("flt.alarm_meta", { n: unref(fmt)(F.value.total), c: F.value.n_codes, win: unref(winName)(__props.d.win) }))}</span></div><div class="panel span2"><h3>${ssrInterpolate(unref(t)("flt.rate"))}</h3>`);
  2366. _push(ssrRenderComponent(_sfc_main$9, {
  2367. html: unref(chart)("rateChart", mo.value)
  2368. }, null, _parent));
  2369. _push(`<p class="small muted" style="${ssrRenderStyle({ "margin-top": "6px" })}"><b>${ssrInterpolate(unref(t)("flt.rate_note"))}</b> — ${ssrInterpolate(rateTail.value)}</p></div><div class="panel"><h3>${ssrInterpolate(unref(t)("flt.pareto_n"))}</h3>`);
  2370. _push(ssrRenderComponent(_sfc_main$9, {
  2371. html: unref(chart)("paretoChart", F.value.pareto_n || [], unref(t)("unit.n"), unref(t)("flt.pareto_n_note"))
  2372. }, null, _parent));
  2373. _push(`<p class="small muted" style="${ssrRenderStyle({ "margin-top": "5px", "line-height": "1.6" })}">${paretoNSummary.value ?? ""}</p></div><div class="panel"><h3>${ssrInterpolate(unref(t)("flt.pareto_d"))}</h3>`);
  2374. _push(ssrRenderComponent(_sfc_main$9, {
  2375. html: unref(chart)("paretoChart", F.value.pareto_d || [], " h", unref(t)("flt.pareto_d_note"))
  2376. }, null, _parent));
  2377. _push(`<p class="small muted" style="${ssrRenderStyle({ "margin-top": "5px", "line-height": "1.6" })}">${paretoDSummary.value ?? ""}</p></div><div class="panel span2"><h3>${ssrInterpolate(unref(t)("flt.quad"))}</h3>`);
  2378. _push(ssrRenderComponent(_sfc_main$9, {
  2379. html: unref(chart)("quadChart", F.value.quad || [])
  2380. }, null, _parent));
  2381. _push(`<p class="small muted" style="${ssrRenderStyle({ "margin-top": "6px" })}">${ssrInterpolate(unref(t)("flt.quad_note", { n: F.value.n_codes, m: (F.value.quad || []).length }))} `);
  2382. if (pm.value.length) {
  2383. _push(`<span style="${ssrRenderStyle({ "color": "var(--warning)" })}">${ssrInterpolate(unref(t)("flt.quad_partial", { m: pm.value.join("/"), c: Math.round((F.value.cov_min || 0) * 100) }))}</span>`);
  2384. } else {
  2385. _push(`<!---->`);
  2386. }
  2387. _push(` ${ssrInterpolate(unref(t)("flt.quad_note2"))}</p></div><div class="panel"><h3>${ssrInterpolate(unref(t)("flt.per_t", { n: pt.value.length }))}</h3><div class="tblwrap"><table><tr><th style="${ssrRenderStyle({ "width": "70px" })}">${ssrInterpolate(unref(t)("ui.unit"))}</th><th>${ssrInterpolate(unref(t)("ui.count"))}</th><th class="r" style="${ssrRenderStyle({ "width": "80px" })}">${ssrInterpolate(unref(t)("flt.per_t_share"))}</th></tr><!--[-->`);
  2388. ssrRenderList(pt.value, (x) => {
  2389. _push(`<tr><td><b>${ssrInterpolate(unref(unitNo)(x.k))}</b></td><td><i class="bar" style="${ssrRenderStyle(`width:${Math.round(x.v / maxT.value * 90)}px;background:${x.v / maxT.value > 0.6 ? "var(--critical)" : "var(--accent)"}`)}"></i><span class="num small">${ssrInterpolate(unref(fmt)(x.v))}</span></td><td class="r small">${ssrInterpolate((x.v / F.value.total * 100).toFixed(1))}%</td></tr>`);
  2390. });
  2391. _push(`<!--]--></table></div><p class="small muted" style="${ssrRenderStyle({ "margin-top": "6px" })}">${ssrInterpolate(unref(t)("flt.per_t_note", { n: unref(fmt)(F.value.total) }))}</p></div>`);
  2392. if (__props.d.m8 && __props.d.m8.err) {
  2393. _push(`<div class="panel" style="${ssrRenderStyle({ "border-left": "3px solid var(--critical)" })}"><p class="small">${ssrInterpolate(unref(t)("flt.block_err", { k: "m8", e: __props.d.m8.err }))}</p></div>`);
  2394. } else {
  2395. _push(`<!---->`);
  2396. }
  2397. if (M.value && seasonal.value.length) {
  2398. _push(`<div class="panel"><h3>${ssrInterpolate(unref(t)("flt.seasonal"))} <span class="small muted" style="${ssrRenderStyle({ "font-weight": "400" })}">(${ssrInterpolate(unref(t)("flt.seasonal_unit"))})</span></h3>`);
  2399. _push(ssrRenderComponent(_sfc_main$9, {
  2400. html: unref(chart)("multiline", seasonalRaw.value)
  2401. }, null, _parent));
  2402. _push(`<div class="legend" style="${ssrRenderStyle({ "margin-top": "6px", "flex-wrap": "wrap" })}"><!--[-->`);
  2403. ssrRenderList(seasonalNames.value, (nm, i) => {
  2404. _push(`<span><i style="${ssrRenderStyle(`background:${ML_COLS[i % 4]}`)}"></i>${ssrInterpolate(nm)}</span>`);
  2405. });
  2406. _push(`<!--]--></div><p class="small muted" style="${ssrRenderStyle({ "margin-top": "6px" })}">${ssrInterpolate(unref(t)("flt.seasonal_note"))}</p></div>`);
  2407. } else {
  2408. _push(`<!---->`);
  2409. }
  2410. if (M.value) {
  2411. _push(`<!--[--><div class="eyebrow" style="${ssrRenderStyle({ "grid-column": "1/-1", "margin-top": "4px" })}">${ssrInterpolate(unref(t)("flt.stop_face"))} <span style="${ssrRenderStyle({ "text-transform": "none", "letter-spacing": "0", "color": "var(--muted)" })}">${ssrInterpolate(unref(t)("flt.stop_meta", { h: unref(fmt)(M.value.停机总时长h), n: unref(fmt)(M.value.停机事件数) }))}</span></div>`);
  2412. if (RT.value) {
  2413. _push(`<div class="panel span2"><h3>${ssrInterpolate(unref(t)("flt.ram"))}</h3>`);
  2414. _push(ssrRenderComponent(_sfc_main$9, {
  2415. html: unref(chart)("ramChart", RT.value && RT.value.表 || [], partPlain)
  2416. }, null, _parent));
  2417. _push(`<p class="small muted" style="${ssrRenderStyle({ "margin-top": "6px" })}">${ssrInterpolate(unref(t)("flt.ram_note", { a: RT.value.设备类停机h, b: RT.value.外部类停机h }))}</p><div style="${ssrRenderStyle({ "margin-top": "12px" })}">`);
  2418. _push(ssrRenderComponent(_sfc_main$9, {
  2419. html: unref(chart)("attribBar", RT.value && RT.value.表 || [], RT.value && RT.value.总停机h || 0)
  2420. }, null, _parent));
  2421. _push(`</div><p class="small muted" style="${ssrRenderStyle({ "margin-top": "2px" })}">${ssrInterpolate(unref(t)("flt.attrib_note", { h: ((RT.value.表 || []).find((x) => x.部件 === "无记录停机") || {}).停机时长h || 0 }))}</p></div>`);
  2422. } else {
  2423. _push(`<!---->`);
  2424. }
  2425. _push(`<div class="panel"><h3>${ssrInterpolate(unref(t)("flt.stop_pareto"))}</h3>`);
  2426. _push(ssrRenderComponent(_sfc_main$9, {
  2427. html: unref(chart)("paretoChart", stopPareto.value, " h", unref(t)("flt.stop_pareto_note"))
  2428. }, null, _parent));
  2429. _push(`<p class="small muted" style="${ssrRenderStyle({ "margin-top": "5px", "line-height": "1.6" })}">${stopParetoSummary.value ?? ""}</p><p class="small muted" style="${ssrRenderStyle({ "margin-top": "4px" })}">${ssrInterpolate(M.value.口径)}</p></div><div class="panel"><h3>${ssrInterpolate(unref(t)("flt.stop_units", { n: top.value.length }))}</h3><div class="tblwrap"><table><tr><th style="${ssrRenderStyle({ "width": "64px" })}">${ssrInterpolate(unref(t)("ui.unit"))}</th><th>${ssrInterpolate(unref(t)("flt.stop_h"))}</th><th class="r" style="${ssrRenderStyle({ "width": "56px" })}">${ssrInterpolate(unref(t)("flt.stop_n"))}</th></tr><!--[-->`);
  2430. ssrRenderList(top.value, (x) => {
  2431. _push(`<tr><td><b>${ssrInterpolate(unref(unitNo)(x.t))}</b></td><td><i class="bar" style="${ssrRenderStyle(`width:${Math.round(x.时长h / mxTop.value * 90)}px;background:${x.时长h / mxTop.value > 0.6 ? "var(--critical)" : "var(--accent)"}`)}"></i><span class="num small">${ssrInterpolate(unref(fmt)(x.时长h))}</span>h</td><td class="r small">${ssrInterpolate(x.次)}</td></tr>`);
  2432. });
  2433. _push(`<!--]--></table></div><p class="small muted" style="${ssrRenderStyle({ "margin-top": "6px" })}">${ssrInterpolate(unref(t)("flt.stop_units_note"))}</p></div><!--]-->`);
  2434. } else {
  2435. _push(`<!---->`);
  2436. }
  2437. _push(`</div></section>`);
  2438. };
  2439. }
  2440. });
  2441. const _sfc_setup$8 = _sfc_main$8.setup;
  2442. _sfc_main$8.setup = (props, ctx) => {
  2443. const ssrContext = useSSRContext();
  2444. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Fault.vue");
  2445. return _sfc_setup$8 ? _sfc_setup$8(props, ctx) : void 0;
  2446. };
  2447. const _sfc_main$7 = /* @__PURE__ */ defineComponent({
  2448. __name: "Vibration",
  2449. __ssrInlineRender: true,
  2450. props: {
  2451. d: {},
  2452. sub: {},
  2453. cms: {}
  2454. },
  2455. emits: ["sub"],
  2456. setup(__props, { emit: __emit }) {
  2457. const props = __props;
  2458. const sub = computed(() => props.sub || "matrix");
  2459. const F = computed(() => props.d?.fus);
  2460. const bodyHtml = computed(() => {
  2461. const f = F.value;
  2462. if (!f || f.err) return `<section><div class="panel"><p>${t("fus.unavailable", { e: String(f && f.err || "") })}</p></div></section>`;
  2463. if (sub.value === "matrix") return matrixHtml(f);
  2464. if (sub.value === "loop") return loopHtml(f);
  2465. if (sub.value === "units") return unitsHtml(f, null);
  2466. if (sub.value === "events") return eventsHtml(f, props.d);
  2467. if (sub.value === "cms") return cmsHtml(props.cms ?? null);
  2468. return `<div class="panel mb"><p class="small muted">子视图「${t("fus.sub." + sub.value)}」尚未迁移到 Vue(迁移中)。</p></div>`;
  2469. });
  2470. return (_ctx, _push, _parent, _attrs) => {
  2471. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("fus.eyebrow"))}</div><div style="${ssrRenderStyle({ "display": "flex", "gap": "8px", "margin-bottom": "14px", "flex-wrap": "wrap" })}"><!--[-->`);
  2472. ssrRenderList(unref(FUS_SUBS), (s) => {
  2473. _push(`<button class="${ssrRenderClass([{ on: s === sub.value }, "pill"])}"${ssrRenderAttr("data-fsub", s)}>${ssrInterpolate(unref(t)("fus.sub." + s))}</button>`);
  2474. });
  2475. _push(`<!--]--></div><div>${bodyHtml.value ?? ""}</div></section>`);
  2476. };
  2477. }
  2478. });
  2479. const _sfc_setup$7 = _sfc_main$7.setup;
  2480. _sfc_main$7.setup = (props, ctx) => {
  2481. const ssrContext = useSSRContext();
  2482. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Vibration.vue");
  2483. return _sfc_setup$7 ? _sfc_setup$7(props, ctx) : void 0;
  2484. };
  2485. const _sfc_main$6 = /* @__PURE__ */ defineComponent({
  2486. __name: "Generation",
  2487. __ssrInlineRender: true,
  2488. props: {
  2489. d: {},
  2490. curves: {},
  2491. pending: { type: Boolean }
  2492. },
  2493. setup(__props) {
  2494. const props = __props;
  2495. const M9 = computed(() => props.d?.m9 && !props.d.m9.err ? props.d.m9 : null);
  2496. const F = computed(() => props.d?.control || null);
  2497. const GEN = computed(() => props.curves || null);
  2498. const sp = computed(() => (props.d?.sysmx_span || []).join(" ~ ") || "");
  2499. const stats = (spec) => {
  2500. const gs = Object.entries(spec?.分组 || {}).map(([k, v]) => ({ v: parseFloat(k), n: v, raw: k }));
  2501. if (!gs.length) return null;
  2502. const vals = gs.map((g) => g.v).sort((a, b) => a - b);
  2503. const med = vals.length % 2 ? vals[(vals.length - 1) / 2] : (vals[vals.length / 2 - 1] + vals[vals.length / 2]) / 2;
  2504. return { gs, med };
  2505. };
  2506. const capStats = computed(() => ({ p: stats(M9.value?.P封顶), w: stats(M9.value?.ω封顶) }));
  2507. const capOption = computed(() => {
  2508. const P = capStats.value.p, W9 = capStats.value.w;
  2509. const series = [];
  2510. if (P) series.push({ name: t("ch.cap.p"), type: "scatter", data: P.gs.map((g) => [g.v, 1]), symbolSize: 10, itemStyle: { color: "#0b5fa5", opacity: 0.6 } });
  2511. if (W9) series.push({ name: t("ch.cap.w"), type: "scatter", data: W9.gs.map((g) => [g.v, 0]), symbolSize: 10, itemStyle: { color: "#b45309", opacity: 0.6 } });
  2512. return {
  2513. grid: { left: 56, right: 36, top: 20, bottom: 24 },
  2514. xAxis: { type: "value" },
  2515. yAxis: { type: "value", min: -0.5, max: 1.5, show: false },
  2516. series,
  2517. // 旧 capAxis 只把刻度与中位注解写成 SVG 文字(那是图自身算的注解)⇒ 不计入我画的数据
  2518. __data__: []
  2519. };
  2520. });
  2521. const betaOuts = computed(() => M9.value?.β_schedule?.离群 || []);
  2522. const betaMed = computed(() => M9.value?.β_schedule?.["全场同档差中位"] || 0);
  2523. const betaOption = computed(() => ({
  2524. grid: { left: 56, right: 60, top: 20, bottom: 24 },
  2525. xAxis: { type: "value" },
  2526. yAxis: { type: "category", data: betaOuts.value.map((o) => unitNo(o.t)) },
  2527. series: [{ type: "bar", data: betaOuts.value.map((o) => o.平均偏), itemStyle: { color: "#0b5fa5" } }],
  2528. __data__: []
  2529. // 同上:β 图中位是注解
  2530. }));
  2531. const candsHtml = computed(() => {
  2532. const cs = F.value?.curve?.cands || [];
  2533. return cs.length ? cs.map((c) => `<a href="/turbine/${c.t}">${unitNo(c.t)}</a> ${c.dev}% ${dv(c.判)}`).join(" | ") : t("gen.no_cands");
  2534. });
  2535. const betaNoteHtml = computed(() => t("gen.beta_note", {
  2536. units: betaOuts.value.map((x) => `<a href="/turbine/${x.t}">${unitNo(x.t)}</a>`).join(" / "),
  2537. k: betaOuts.value.length ? Math.round(Math.max(...betaOuts.value.map((x) => x.平均偏)) / Math.max(betaMed.value || 0, 0.01)) : "—",
  2538. v: dv(M9.value?.β_schedule?.判)
  2539. }));
  2540. const rulerRows = computed(() => Object.entries(M9.value?.尺子 || {}).map(([k, v]) => ({ k, r: v.极差 / v.σ })));
  2541. const rulerOption = computed(() => ({
  2542. grid: { left: 100, right: 80, top: 20, bottom: 24 },
  2543. xAxis: { type: "value" },
  2544. yAxis: { type: "category", data: rulerRows.value.map((r) => r.k) },
  2545. series: [{ type: "bar", data: rulerRows.value.map((r) => r.r), itemStyle: { color: "#0b5fa5" } }],
  2546. __data__: []
  2547. // 同上:规范线是注解
  2548. }));
  2549. const figs = computed(() => GEN.value?.figs || []);
  2550. function figOption(f) {
  2551. const months = f.months || f.x || [];
  2552. return {
  2553. grid: { left: 56, right: 20, top: 28, bottom: 40 },
  2554. legend: { top: 0, textStyle: { fontSize: 10 } },
  2555. xAxis: { type: "category", data: months },
  2556. yAxis: { type: "value", scale: true },
  2557. series: (f.series || []).map((s) => ({ name: String(s.name), type: "line", data: s.vals, connectNulls: false })),
  2558. __data__: []
  2559. };
  2560. }
  2561. return (_ctx, _push, _parent, _attrs) => {
  2562. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("gen.eyebrow"))}</div><div class="panel mb small" style="${ssrRenderStyle({ "border-left": "3px solid var(--accent)" })}"><b>${ssrInterpolate(unref(t)("gen.fixed_win"))}</b> ${ssrInterpolate(sp.value ? unref(t)("gen.win_range", { span: sp.value }) : "")} <div class="muted" style="${ssrRenderStyle({ "margin-top": "3px" })}">${ssrInterpolate(unref(t)("gen.win_why"))}</div></div>`);
  2563. if (__props.d.m9 && __props.d.m9.err) {
  2564. _push(`<div class="panel mb" style="${ssrRenderStyle({ "border-left": "3px solid var(--critical)" })}"><p class="small">${ssrInterpolate(unref(t)("flt.block_err", { k: "m9", e: unref(dv)(__props.d.m9.err) }))}</p></div>`);
  2565. } else {
  2566. _push(`<!---->`);
  2567. }
  2568. if (M9.value) {
  2569. _push(`<div class="panel mb" style="${ssrRenderStyle(__props.pending ? "border-left:3px solid var(--accent)" : "")}"><h3>${ssrInterpolate(unref(t)("gen.ctrl"))} <span class="small muted" style="${ssrRenderStyle({ "font-weight": "400" })}">${ssrInterpolate(unref(t)("gen.ctrl_win", { win: unref(dv)(M9.value.窗) }))}</span>`);
  2570. if (__props.pending) {
  2571. _push(`<span class="chip">${ssrInterpolate(unref(t)("gen.recomputing_short"))}</span>`);
  2572. } else {
  2573. _push(`<!---->`);
  2574. }
  2575. _push(`</h3><div style="${ssrRenderStyle({ "display": "flex", "flex-wrap": "wrap", "gap": "18px" })}"><div style="${ssrRenderStyle({ "flex": "1 1 300px", "min-width": "280px" })}"><div class="small" style="${ssrRenderStyle({ "font-weight": "600", "margin-bottom": "4px" })}">${ssrInterpolate(unref(t)("gen.cap"))}</div>`);
  2576. _push(ssrRenderComponent(EChart, {
  2577. option: capOption.value,
  2578. height: "170px"
  2579. }, null, _parent));
  2580. _push(`<p class="small muted" style="${ssrRenderStyle({ "margin-top": "4px" })}">${ssrInterpolate(unref(t)("gen.cap_note"))}</p></div><div style="${ssrRenderStyle({ "flex": "1 1 300px", "min-width": "280px" })}"><div class="small" style="${ssrRenderStyle({ "font-weight": "600", "margin-bottom": "4px" })}">${ssrInterpolate(unref(t)("gen.beta"))}</div>`);
  2581. _push(ssrRenderComponent(EChart, {
  2582. option: betaOption.value,
  2583. height: "170px"
  2584. }, null, _parent));
  2585. _push(`<p class="small muted" style="${ssrRenderStyle({ "margin-top": "4px" })}">${betaNoteHtml.value ?? ""}</p><div class="small" style="${ssrRenderStyle({ "font-weight": "600", "margin": "12px 0 4px" })}">${ssrInterpolate(unref(t)("gen.ruler"))}</div>`);
  2586. _push(ssrRenderComponent(EChart, {
  2587. option: rulerOption.value,
  2588. height: "170px"
  2589. }, null, _parent));
  2590. _push(`<p class="small muted" style="${ssrRenderStyle({ "margin-top": "4px" })}">${ssrInterpolate(unref(t)("gen.ruler_note"))}</p></div></div></div>`);
  2591. } else {
  2592. _push(`<!---->`);
  2593. }
  2594. if (F.value && F.value.curve) {
  2595. _push(`<div class="panel mb"><h3>${ssrInterpolate(unref(t)("gen.cands"))} <span class="small muted" style="${ssrRenderStyle({ "font-weight": "400" })}">${ssrInterpolate(unref(t)("gen.cands_meta", { note: unref(dv)(F.value.curve.note || ""), s: __props.d.sigma || F.value.curve.sigma }))}</span></h3><p class="small">${candsHtml.value ?? ""}</p></div>`);
  2596. } else {
  2597. _push(`<!---->`);
  2598. }
  2599. _push(`<div class="eyebrow">${ssrInterpolate(unref(t)("gen.figs"))}</div>`);
  2600. if (GEN.value && GEN.value.err) {
  2601. _push(`<div class="panel" style="${ssrRenderStyle({ "border-left": "3px solid var(--critical)" })}"><p class="small">${ssrInterpolate(unref(esc)(GEN.value.err))}</p></div>`);
  2602. } else if (GEN.value && GEN.value.building) {
  2603. _push(`<div class="panel" style="${ssrRenderStyle({ "border-left": "3px solid var(--accent)" })}"><p class="small"><b>${ssrInterpolate(unref(t)("gen.recomputing"))}</b> ${ssrInterpolate(unref(dv)(GEN.value.curves?.note || ""))}</p><div class="skel" style="${ssrRenderStyle({ "width": "60%", "margin-top": "10px" })}"></div><div class="skel" style="${ssrRenderStyle({ "width": "80%", "margin-top": "8px" })}"></div></div>`);
  2604. } else if (!GEN.value) {
  2605. _push(`<div class="panel"><p class="small muted">${ssrInterpolate(unref(t)("gen.loading"))}</p><div class="skel" style="${ssrRenderStyle({ "width": "60%", "margin-top": "10px" })}"></div><div class="skel" style="${ssrRenderStyle({ "width": "80%", "margin-top": "8px" })}"></div></div>`);
  2606. } else {
  2607. _push(`<div class="grid2"><!--[-->`);
  2608. ssrRenderList(figs.value, (f, i) => {
  2609. _push(`<div class="panel"><h3>${ssrInterpolate(unref(dv)(f.title || ""))}`);
  2610. if (f.unit) {
  2611. _push(`<span><span class="small muted" style="${ssrRenderStyle({ "font-weight": "400" })}">(${ssrInterpolate(unref(dv)(f.unit))})</span></span>`);
  2612. } else {
  2613. _push(`<!---->`);
  2614. }
  2615. _push(`</h3>`);
  2616. _push(ssrRenderComponent(EChart, {
  2617. option: figOption(f),
  2618. height: "260px"
  2619. }, null, _parent));
  2620. _push(`<div class="legend" style="${ssrRenderStyle({ "margin-top": "6px", "flex-wrap": "wrap" })}"><!--[-->`);
  2621. ssrRenderList(f.series || [], (s, k) => {
  2622. _push(`<span><i style="${ssrRenderStyle(`background:${["var(--accent)", "#D48A1E", "#2E8B6A", "#7C5295"][k % 4]}`)}"></i>${ssrInterpolate(String(s.name))}</span>`);
  2623. });
  2624. _push(`<!--]--></div>`);
  2625. if (f.note) {
  2626. _push(`<p class="small muted" style="${ssrRenderStyle({ "margin-top": "6px" })}">${ssrInterpolate(unref(dv)(f.note))}</p>`);
  2627. } else {
  2628. _push(`<!---->`);
  2629. }
  2630. _push(`</div>`);
  2631. });
  2632. _push(`<!--]--></div>`);
  2633. }
  2634. _push(`</section>`);
  2635. };
  2636. }
  2637. });
  2638. const _sfc_setup$6 = _sfc_main$6.setup;
  2639. _sfc_main$6.setup = (props, ctx) => {
  2640. const ssrContext = useSSRContext();
  2641. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Generation.vue");
  2642. return _sfc_setup$6 ? _sfc_setup$6(props, ctx) : void 0;
  2643. };
  2644. const _sfc_main$5 = /* @__PURE__ */ defineComponent({
  2645. __name: "Decision",
  2646. __ssrInlineRender: true,
  2647. props: {
  2648. d: {},
  2649. apis: {}
  2650. },
  2651. setup(__props) {
  2652. const props = __props;
  2653. const CATS = [
  2654. ["equip", "设备类"],
  2655. ["sched", "检修排程决策"],
  2656. ["loss", "损失分析"],
  2657. ["risk", "安全风险预测"]
  2658. ];
  2659. const MDL_LOC = { "qwen3:8b": "local", "qwen3.8:27b": "local", "qwen3:30b-a3b": "local", "deepseek-r1:14b": "local", "deepseek-r1:32b": "local", "deepseek-chat": "cloud", "kimi-fast": "cloud", "qwen-plus": "cloud", "deepseek-v4-pro": "cloud" };
  2660. const cat = ref("equip");
  2661. const q = ref("");
  2662. const model = ref("");
  2663. const out = ref("");
  2664. const askModels = computed(() => props.apis?.["/detail/api/ask_models"] ?? props.apis?.["/api/ask_models"] ?? null);
  2665. const models = computed(() => askModels.value ? askModels.value.models || {} : null);
  2666. const localDown = computed(() => !!askModels.value?.offline || !!askModels.value?.LOCAL_AI_DOWN);
  2667. const catName = computed(() => t("cat." + cat.value));
  2668. const pills = computed(() => CATS.map(([id]) => `<button class="pill ${id === cat.value ? "on" : ""}" data-cat="${id}">${t("cat." + id)}</button>`).join(""));
  2669. const qs = computed(() => [1, 2, 3].map((i) => `<div class="qitem" data-q="${esc(t("cat." + cat.value + ".q" + i))}">${t("cat." + cat.value + ".q" + i)}</div>`).join(""));
  2670. const mdlKey = (id, f) => t("mdl." + id + "." + f);
  2671. const mdlLabel = (id) => (mdlKey(id, "nm") || id) + (MDL_LOC[id] ? " · " + t("mdl." + MDL_LOC[id]) : "") + (mdlKey(id, "t") ? " · " + mdlKey(id, "t") : "");
  2672. const mdlNote = (id) => !MDL_LOC[id] ? `<span style="color:var(--warning)">${t("mdl.unknown_warn")}</span>` : MDL_LOC[id] === "local" ? t("mdl.zero_outbound") : `<span style="color:var(--warning)">${t("mdl.outbound_warn")}</span>`;
  2673. const effectiveModel = computed(() => {
  2674. const m = models.value;
  2675. if (m === null) return model.value;
  2676. const ks = Object.keys(m);
  2677. return !model.value || !ks.includes(model.value) ? ks[0] || "" : model.value;
  2678. });
  2679. const opts = computed(() => {
  2680. const m = models.value;
  2681. if (m === null) return '<option value="">…</option>';
  2682. return Object.keys(m).length ? Object.keys(m).map((k) => `<option value="${esc(k)}" ${k === effectiveModel ? "selected" : ""}>${esc(mdlLabel(k))}</option>`).join("") : `<option value="">${localDown.value ? t("ask.local_down") : t("ask.no_models")}</option>`;
  2683. });
  2684. const classicHref = computed(() => `/?lang=zh&win=${encodeURIComponent(String(props.d?.win || ""))}`);
  2685. const mdlNoteHtml = computed(() => effectiveModel.value ? mdlNote(effectiveModel.value) : "");
  2686. return (_ctx, _push, _parent, _attrs) => {
  2687. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("dec.head"))}</div><p class="small muted">${ssrInterpolate(unref(t)("dec.classic"))} <a${ssrRenderAttr("href", classicHref.value)}>${ssrInterpolate(unref(t)("ui.open_classic"))}</a></p><div style="${ssrRenderStyle({ "display": "flex", "gap": "8px", "margin": "10px 0 12px", "flex-wrap": "wrap" })}">${pills.value ?? ""}</div><div class="panel mb"><h3>${ssrInterpolate(unref(t)("ask.common_q", { cat: catName.value }))}</h3><p class="small muted">${ssrInterpolate(unref(t)("cat." + cat.value + ".hint"))}${ssrInterpolate("。")}${ssrInterpolate(unref(t)("ask.grounding"))}</p><div>${qs.value ?? ""}</div><div style="${ssrRenderStyle({ "display": "flex", "gap": "8px", "flex-wrap": "wrap", "margin": "10px 0", "align-items": "center" })}"><input id="askq" type="text"${ssrRenderAttr("placeholder", unref(esc)(unref(t)("ask.placeholder", { cat: catName.value })))}${ssrRenderAttr("value", q.value)} style="${ssrRenderStyle({ "flex": "1", "min-width": "280px" })}"><label class="small muted" for="askm">${ssrInterpolate(unref(t)("ask.model"))}</label><select id="askm">${opts.value ?? ""}</select><span id="mdlnote" class="small">${mdlNoteHtml.value ?? ""}</span><label class="small" style="${ssrRenderStyle({ "cursor": "pointer", "white-space": "nowrap" })}"${ssrRenderAttr("title", unref(esc)(unref(t)("ask.xrev_title")))}><input type="checkbox" id="askxrev"> ${ssrInterpolate(unref(t)("ask.xrev"))} <span class="muted">${ssrInterpolate(unref(t)("ask.xrev_note"))}</span></label><button class="btn" id="askgo">${ssrInterpolate(unref(t)("ask.go"))}</button></div><div id="askout" class="small">${ssrInterpolate(out.value || unref(t)("ask.idle"))}</div></div></section>`);
  2688. };
  2689. }
  2690. });
  2691. const _sfc_setup$5 = _sfc_main$5.setup;
  2692. _sfc_main$5.setup = (props, ctx) => {
  2693. const ssrContext = useSSRContext();
  2694. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Decision.vue");
  2695. return _sfc_setup$5 ? _sfc_setup$5(props, ctx) : void 0;
  2696. };
  2697. const row = "display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:8px";
  2698. const _sfc_main$4 = /* @__PURE__ */ defineComponent({
  2699. __name: "Assistant",
  2700. __ssrInlineRender: true,
  2701. setup(__props) {
  2702. const SYSOPT = ["变桨", "偏航", "主轴承", "齿轮箱", "发电机", "变流器", "塔架与基础", "主控与传感网", "叶片与叶根"];
  2703. const HOT = ["3225", "3130", "13902", "10105", "3224"];
  2704. const ASST = reactive({ fault: "", prev: "pitch", tree: "pitch", out: { fault: "", prev: "", tree: "" } });
  2705. const selOptions = (id) => SYSOPT.map((s) => `<option value="${sysSlug(s)}" ${ASST[id] === sysSlug(s) ? "selected" : ""}>${sysName(s)}</option>`).join("");
  2706. const hotHtml = computed(() => HOT.map((c) => `<a href="#" data-code="${c}">${c}</a>`).join(" · "));
  2707. return (_ctx, _push, _parent, _attrs) => {
  2708. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("asst.head"))} — ${ssrInterpolate(unref(t)("asst.head_note"))}</div><div class="panel mb"><h3>${ssrInterpolate(unref(t)("asst.corrective"))} <span class="small muted" style="${ssrRenderStyle({ "font-weight": "400" })}">${ssrInterpolate(unref(t)("asst.corrective_flow"))}</span></h3><div style="${ssrRenderStyle(row)}"><input id="asstIn_fault"${ssrRenderAttr("placeholder", unref(esc)(unref(t)("asst.code_placeholder")))}${ssrRenderAttr("value", ASST.fault)} style="${ssrRenderStyle({ "flex": "1 1 220px" })}"><button class="btn" data-asst="fault">${ssrInterpolate(unref(t)("ui.query"))}</button><span class="small muted">${ssrInterpolate(unref(t)("asst.common"))} <span>${hotHtml.value ?? ""}</span></span></div><div id="asstOut_fault" style="${ssrRenderStyle({ "margin-top": "10px" })}">${ssrInterpolate(ASST.out.fault)}</div></div><div class="panel mb"><h3>${ssrInterpolate(unref(t)("asst.preventive"))} <span class="small muted" style="${ssrRenderStyle({ "font-weight": "400" })}">${ssrInterpolate(unref(t)("asst.preventive_flow"))}</span></h3><div style="${ssrRenderStyle(row)}"><select id="asstIn_prev" style="${ssrRenderStyle({ "flex": "1 1 200px" })}">${selOptions("prev") ?? ""}</select><button class="btn" data-asst="prev">${ssrInterpolate(unref(t)("ui.query"))}</button></div><div id="asstOut_prev" style="${ssrRenderStyle({ "margin-top": "10px" })}">${ssrInterpolate(ASST.out.prev)}</div></div><div class="panel mb"><h3>${ssrInterpolate(unref(t)("asst.faulttree"))} <span class="small muted" style="${ssrRenderStyle({ "font-weight": "400" })}">${ssrInterpolate(unref(t)("asst.faulttree_flow"))}</span></h3><div style="${ssrRenderStyle(row)}"><select id="asstIn_tree" style="${ssrRenderStyle({ "flex": "1 1 200px" })}">${selOptions("tree") ?? ""}</select><button class="btn" data-asst="tree">${ssrInterpolate(unref(t)("ui.query"))}</button></div><div id="asstOut_tree" style="${ssrRenderStyle({ "margin-top": "10px" })}">${ssrInterpolate(ASST.out.tree)}</div></div></section>`);
  2709. };
  2710. }
  2711. });
  2712. const _sfc_setup$4 = _sfc_main$4.setup;
  2713. _sfc_main$4.setup = (props, ctx) => {
  2714. const ssrContext = useSSRContext();
  2715. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Assistant.vue");
  2716. return _sfc_setup$4 ? _sfc_setup$4(props, ctx) : void 0;
  2717. };
  2718. const _sfc_main$3 = /* @__PURE__ */ defineComponent({
  2719. __name: "System",
  2720. __ssrInlineRender: true,
  2721. props: {
  2722. apis: {}
  2723. },
  2724. setup(__props) {
  2725. const props = __props;
  2726. const publicMeta = (x) => String(x == null ? "" : x);
  2727. const md = computed(() => props.apis?.["/detail/api/maint_survey"] ?? props.apis?.["/api/maint_survey"] ?? null);
  2728. const LAYERS = [
  2729. [["数据", "Data"], "mt.layer_data", "mt.layer_data_note", "var(--accent)"],
  2730. [["机理", "Domain model", "Mechanism"], "mt.layer_mech", "mt.layer_mech_note", "var(--warning)"]
  2731. ];
  2732. const rows = computed(() => md.value?.rows || []);
  2733. const groups = computed(() => LAYERS.map(([lvs, kn, kd, col]) => ({ kn, kd, col, rs: rows.value.filter((r) => lvs.includes(String(r.层))) })).filter((g) => g.rs.length));
  2734. return (_ctx, _push, _parent, _attrs) => {
  2735. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("mt.eyebrow"))}</div>`);
  2736. if (!md.value) {
  2737. _push(`<div class="panel"><p class="small muted">${ssrInterpolate(unref(t)("mt.surveying"))}</p></div>`);
  2738. } else if (md.value.error) {
  2739. _push(`<div class="panel"><p class="small" style="${ssrRenderStyle({ "color": "var(--critical)" })}">${ssrInterpolate(unref(dv)(md.value.error))}</p></div>`);
  2740. } else {
  2741. _push(`<!--[--><div class="panel mb"><h3>${ssrInterpolate(unref(t)("mt.head", { n: rows.value.length }))}</h3><p class="small muted">${ssrInterpolate(unref(t)("mt.note"))}</p></div><!--[-->`);
  2742. ssrRenderList(groups.value, (g) => {
  2743. _push(`<div class="panel mb" style="${ssrRenderStyle(`border-left:3px solid ${g.col}`)}"><h3>${ssrInterpolate(unref(t)(g.kn))} (${ssrInterpolate(g.rs.length)})</h3><p class="small muted">${ssrInterpolate(unref(t)(g.kd))}</p><div class="tblwrap"><table style="${ssrRenderStyle({ "margin-top": "10px" })}"><tr><th style="${ssrRenderStyle({ "width": "18%" })}">${ssrInterpolate(unref(t)("mt.src"))}</th><th style="${ssrRenderStyle({ "width": "20%" })}">${ssrInterpolate(unref(t)("mt.cov"))}</th><th style="${ssrRenderStyle({ "width": "13%" })}">${ssrInterpolate(unref(t)("mt.last"))}</th><th style="${ssrRenderStyle({ "width": "10%" })}">${ssrInterpolate(unref(t)("mt.freq"))}</th><th style="${ssrRenderStyle({ "width": "11%" })}">${ssrInterpolate(unref(t)("mt.resp"))}</th><th>${ssrInterpolate(unref(t)("mt.how"))}</th></tr><!--[-->`);
  2744. ssrRenderList(g.rs, (r, i) => {
  2745. _push(`<tr><td class="small"><b>${ssrInterpolate(r.存在 ? "" : "✗ ")}${ssrInterpolate(publicMeta(r.名))}</b></td><td class="small num">${ssrInterpolate(publicMeta(r.覆盖))}`);
  2746. if (r.条数) {
  2747. _push(`<span class="muted">${ssrInterpolate(unref(t)("mt.rows_n", { n: unref(fmt)(r.条数) }))}</span>`);
  2748. } else {
  2749. _push(`<!---->`);
  2750. }
  2751. _push(`</td><td class="small muted">${ssrInterpolate(unref(esc)(r.末次更新 || "—"))}</td><td class="small">${ssrInterpolate(publicMeta(r.频率))}</td><td class="small">${ssrInterpolate(publicMeta(r.责任))}</td><td class="small"><div class="muted">${ssrInterpolate(unref(t)("mt.put"))} <code>${ssrInterpolate(publicMeta(r.位置))}</code></div><div style="${ssrRenderStyle({ "margin-top": "3px" })}">${ssrInterpolate(unref(t)("mt.run"))} <code>${ssrInterpolate(publicMeta(r.摄入))}</code></div><div class="muted" style="${ssrRenderStyle({ "margin-top": "3px" })}">${ssrInterpolate(publicMeta(r.说明))}</div></td></tr>`);
  2752. });
  2753. _push(`<!--]--></table></div></div>`);
  2754. });
  2755. _push(`<!--]--><!--]-->`);
  2756. }
  2757. _push(`</section>`);
  2758. };
  2759. }
  2760. });
  2761. const _sfc_setup$3 = _sfc_main$3.setup;
  2762. _sfc_main$3.setup = (props, ctx) => {
  2763. const ssrContext = useSSRContext();
  2764. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/System.vue");
  2765. return _sfc_setup$3 ? _sfc_setup$3(props, ctx) : void 0;
  2766. };
  2767. const _sfc_main$2 = /* @__PURE__ */ defineComponent({
  2768. __name: "Report",
  2769. __ssrInlineRender: true,
  2770. props: {
  2771. d: {},
  2772. apis: {}
  2773. },
  2774. setup(__props) {
  2775. const props = __props;
  2776. const RPT_PRESETS = { month: ["kpi", "water", "emon", "blame", "ram", "pareto", "units", "plan", "caliber"], week: ["kpi", "frate", "units", "plan", "caliber"], topic: ["units", "chain", "ram", "loop", "caliber"], year: ["kpi", "water", "emon", "ram", "loop", "caliber"], blank: [] };
  2777. const RPT_BLOCKS = { kpi: "gen", water: "gen", emon: "gen", blame: "gen", ram: "rel", parts: "rel", attrib: "rel", pareto: "flt", quad: "flt", frate: "flt", units: "unit", chain: "unit", loop: "unit", plan: "end", caliber: "end", narr: "end" };
  2778. const RPT_ICON = { kpi: "▦", water: "▤", emon: "▥", blame: "◧", ram: "◈", parts: "▤", attrib: "▬", pareto: "▮", quad: "◇", frate: "▪", units: "☰", chain: "⛓", loop: "✓", plan: "✎", caliber: "⚑", narr: "✍" };
  2779. const MDL_LOC = { "qwen3:8b": "local", "qwen3.8:27b": "local", "qwen3:30b-a3b": "local", "deepseek-r1:14b": "local", "deepseek-r1:32b": "local", "deepseek-chat": "cloud", "kimi-fast": "cloud", "qwen-plus": "cloud", "deepseek-v4-pro": "cloud" };
  2780. const RPT_GROUPS = ["gen", "rel", "flt", "unit", "end"];
  2781. const RPT = reactive({
  2782. canvas: [],
  2783. aud: "in",
  2784. need: "",
  2785. model: "",
  2786. models: null,
  2787. compose: null,
  2788. narr: null,
  2789. out: ""
  2790. });
  2791. const askModels = computed(() => props.apis?.["/detail/api/ask_models"] ?? props.apis?.["/api/ask_models"] ?? null);
  2792. const models = computed(() => askModels.value ? askModels.value.models || {} : RPT.models);
  2793. const localDown = computed(() => !!askModels.value?.offline);
  2794. const mdlKey = (id, f) => t("mdl." + id + "." + f);
  2795. const mdlLabel = (id) => (mdlKey(id, "nm") || id) + (MDL_LOC[id] ? " · " + t("mdl." + MDL_LOC[id]) : "") + (mdlKey(id, "t") ? " · " + mdlKey(id, "t") : "");
  2796. const on = (k) => RPT.canvas.includes(k);
  2797. const canCompose = computed(() => RPT.compose === true && !!models.value && Object.keys(models.value).length > 0);
  2798. const opts = computed(() => {
  2799. const m = models.value;
  2800. if (m === null) return '<option value="">…</option>';
  2801. return Object.keys(m).length ? Object.keys(m).map((k) => `<option value="${esc(k)}" ${k === effectiveModel ? "selected" : ""}>${esc(mdlLabel(k))}</option>`).join("") : `<option value="">${localDown.value ? t("ask.local_down") : t("ask.no_models")}</option>`;
  2802. });
  2803. const libraryHtml = computed(() => RPT_GROUPS.map((g) => `<div class="small muted" style="text-align:right;white-space:nowrap">${t("rpt.g." + g)}</div><div style="display:flex;gap:6px;flex-wrap:wrap">` + Object.keys(RPT_BLOCKS).filter((k) => RPT_BLOCKS[k] === g).map((k) => `<button class="pill ${on(k) ? "on" : ""}" data-block="${k}" style="border-radius:7px;padding:5px 11px;font-size:12.5px;opacity:${on(k) ? 1 : 0.8}">${RPT_ICON[k]} ${t("rpt.b." + k + ".nm")}${on(k) ? " ✓" : ""}</button>`).join("") + `</div>`).join(""));
  2804. const factsHtml = computed(() => "");
  2805. const blockHtml = (_k) => "";
  2806. const effectiveModel = computed(() => {
  2807. const m = models.value;
  2808. if (m === null) return RPT.model;
  2809. const ks = Object.keys(m);
  2810. return !RPT.model || !ks.includes(RPT.model) ? ks[0] || "" : RPT.model;
  2811. });
  2812. const needHint = computed(() => t("rpt.need_hint") + (effectiveModel.value ? MDL_LOC[effectiveModel.value] === "local" ? t("rpt.local_note") : `<span style="color:var(--warning)">${t("rpt.cloud_note")}</span>` : ""));
  2813. const outText = computed(() => {
  2814. if (RPT.out) return RPT.out;
  2815. if (RPT.compose === false) return t("rpt.no_compose");
  2816. if (models.value && !Object.keys(models.value).length) return localDown.value ? t("ask.local_down") : t("rpt.no_models");
  2817. return "";
  2818. });
  2819. const asof = computed(() => (String(props.d?.fus?.数据时点 || "").match(/\d{4}-\d{2}-\d{2}/) || [""])[0]);
  2820. return (_ctx, _push, _parent, _attrs) => {
  2821. _push(`<section${ssrRenderAttrs(_attrs)}><div class="eyebrow">${ssrInterpolate(unref(t)("rpt.eyebrow"))}</div><div class="panel mb noprint"><div style="${ssrRenderStyle({ "display": "flex", "gap": "10px", "align-items": "center", "flex-wrap": "wrap", "margin-bottom": "10px" })}"><span class="small" style="${ssrRenderStyle({ "font-weight": "600" })}">${ssrInterpolate(unref(t)("rpt.presets"))}</span><!--[-->`);
  2822. ssrRenderList(Object.keys(RPT_PRESETS), (k) => {
  2823. _push(`<button class="pill"${ssrRenderAttr("data-preset", k)}>${ssrInterpolate(unref(t)("rpt.preset." + k))}</button>`);
  2824. });
  2825. _push(`<!--]--><span style="${ssrRenderStyle({ "flex": "1" })}"></span><select id="rptaud"><option value="in"${ssrIncludeBooleanAttr(RPT.aud === "in") ? " selected" : ""}>${ssrInterpolate(unref(t)("rpt.aud_in"))}</option><option value="up"${ssrIncludeBooleanAttr(RPT.aud === "up") ? " selected" : ""}>${ssrInterpolate(unref(t)("rpt.aud_up"))}</option></select><button class="btn quiet" id="rptprint">${ssrInterpolate(unref(t)("rpt.print"))}</button><button class="btn quiet" data-export="docx">${ssrInterpolate(unref(t)("rpt.export_docx"))}</button><button class="btn quiet" data-export="xlsx">${ssrInterpolate(unref(t)("rpt.export_xlsx"))}</button>`);
  2826. if (RPT.aud === "up") {
  2827. _push(`<span class="small" style="${ssrRenderStyle({ "color": "var(--warning)", "flex-basis": "100%" })}">${ssrInterpolate(unref(t)("rpt.up_note"))}</span>`);
  2828. } else {
  2829. _push(`<!---->`);
  2830. }
  2831. _push(`</div><div class="small" style="${ssrRenderStyle({ "font-weight": "600", "margin-bottom": "7px" })}">${ssrInterpolate(unref(t)("rpt.library"))} <span style="${ssrRenderStyle({ "font-weight": "400", "color": "var(--muted)" })}">${ssrInterpolate(unref(t)("rpt.library_note", { n: RPT.canvas.length }))}</span></div><div style="${ssrRenderStyle({ "display": "grid", "grid-template-columns": "auto 1fr", "gap": "6px 12px", "align-items": "center" })}">${libraryHtml.value ?? ""}</div><div id="rpthint" class="small muted" style="${ssrRenderStyle({ "margin-top": "9px", "padding-top": "8px", "border-top": "1px solid var(--line)", "min-height": "19px" })}">${ssrInterpolate(unref(t)("rpt.hint_idle"))}</div><div style="${ssrRenderStyle({ "margin-top": "11px", "padding-top": "10px", "border-top": "1px solid var(--line)" })}"><div class="small" style="${ssrRenderStyle({ "font-weight": "600", "margin-bottom": "6px" })}">${ssrInterpolate(unref(t)("rpt.need_head"))} <span id="rptmdlnote" style="${ssrRenderStyle({ "font-weight": "400", "color": "var(--muted)" })}">${needHint.value ?? ""}</span></div><div style="${ssrRenderStyle({ "display": "flex", "gap": "8px", "align-items": "center", "flex-wrap": "wrap" })}"><input id="rptneed"${ssrRenderAttr("placeholder", unref(esc)(unref(t)("rpt.need_ph")))}${ssrRenderAttr("value", RPT.need)} style="${ssrRenderStyle({ "flex": "1 1 300px", "min-width": "220px" })}"><select id="rptmdl">${opts.value ?? ""}</select><button class="btn" id="rptgo"${ssrIncludeBooleanAttr(!canCompose.value) ? " disabled" : ""}>${ssrInterpolate(unref(t)("rpt.compose"))}</button></div><div id="rptout" class="small muted" style="${ssrRenderStyle({ "margin-top": "7px" })}">${ssrInterpolate(outText.value)}</div></div></div>`);
  2832. if (!RPT.canvas.length) {
  2833. _push(`<div class="paper" style="${ssrRenderStyle({ "text-align": "center", "padding": "56px 40px" })}"><div class="small muted">${ssrInterpolate(unref(t)("rpt.preview_empty"))}</div></div>`);
  2834. } else {
  2835. _push(`<div class="paper"><div class="paper-hd"><div><div class="paper-ti">${ssrInterpolate(unref(t)("app.brand"))} · ${ssrInterpolate(unref(t)("rpt.paper_title"))}</div><div class="small muted" style="${ssrRenderStyle({ "margin-top": "3px" })}">${ssrInterpolate(unref(t)("rpt.paper_sub", { win: unref(winName)(__props.d.win), n: (__props.d.kpi || {}).全场 || 38 }))}${ssrInterpolate(RPT.aud === "up" ? " | " + unref(t)("rpt.up_tag") : "")}</div></div><div class="small muted" style="${ssrRenderStyle({ "text-align": "right" })}">${ssrInterpolate(unref(t)("rpt.chapters", { n: RPT.canvas.length }))}</div></div><!--[-->`);
  2836. ssrRenderList(RPT.canvas, (k, i) => {
  2837. _push(`<!--[-->`);
  2838. if (RPT_BLOCKS[k]) {
  2839. _push(`<div class="noprint" style="${ssrRenderStyle({ "display": "flex", "gap": "3px", "justify-content": "flex-end", "margin-bottom": "-6px", "position": "relative", "z-index": "2" })}"><button class="pill" data-mv="up"${ssrRenderAttr("data-k", k)}${ssrIncludeBooleanAttr(i === 0) ? " disabled" : ""} style="${ssrRenderStyle({ "padding": "1px 8px", "font-size": "11px", "opacity": ".7" })}">↑</button><button class="pill" data-mv="down"${ssrRenderAttr("data-k", k)}${ssrIncludeBooleanAttr(i === RPT.canvas.length - 1) ? " disabled" : ""} style="${ssrRenderStyle({ "padding": "1px 8px", "font-size": "11px", "opacity": ".7" })}">↓</button><button class="pill" data-mv="del"${ssrRenderAttr("data-k", k)} style="${ssrRenderStyle({ "padding": "1px 8px", "font-size": "11px", "opacity": ".7" })}">×</button></div>`);
  2840. } else {
  2841. _push(`<!---->`);
  2842. }
  2843. if (RPT_BLOCKS[k]) {
  2844. _push(`<div>${blockHtml()}</div>`);
  2845. } else {
  2846. _push(`<!---->`);
  2847. }
  2848. _push(`<!--]-->`);
  2849. });
  2850. _push(`<!--]--><div>${factsHtml.value ?? ""}</div><div class="paper-ft"><span>${ssrInterpolate(unref(t)("rpt.foot_src"))}${ssrInterpolate(asof.value ? " | " + unref(t)("rpt.foot_asof", { d: asof.value }) : "")}</span><span>${ssrInterpolate(unref(t)("rpt.foot_trace"))}${ssrInterpolate(RPT.aud === "up" ? " | " + unref(t)("rpt.foot_deid") : "")}</span></div></div>`);
  2851. }
  2852. _push(`</section>`);
  2853. };
  2854. }
  2855. });
  2856. const _sfc_setup$2 = _sfc_main$2.setup;
  2857. _sfc_main$2.setup = (props, ctx) => {
  2858. const ssrContext = useSSRContext();
  2859. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Report.vue");
  2860. return _sfc_setup$2 ? _sfc_setup$2(props, ctx) : void 0;
  2861. };
  2862. const _sfc_main$1 = /* @__PURE__ */ defineComponent({
  2863. __name: "Overview",
  2864. __ssrInlineRender: true,
  2865. props: {
  2866. d: {}
  2867. },
  2868. setup(__props) {
  2869. const props = __props;
  2870. const F = computed(() => props.d?.fus || {});
  2871. const M = computed(() => props.d?.m8?.mtbf || {});
  2872. const C = computed(() => props.d?.control || {});
  2873. const LS = computed(() => {
  2874. const out = {};
  2875. for (const x of C.value.states || []) out[x.k] = x.loss;
  2876. return out;
  2877. });
  2878. const kpi = computed(() => {
  2879. const c = C.value;
  2880. const lossLabel = t("kpi.loss", { win: winName(props.d?.win) }) + (LS.value["停机(调度令)"] != null ? ` · ${t("kpi.loss_dispatch", { n: fmt(LS.value["停机(调度令)"]) })}` : "");
  2881. return [
  2882. { v: fmt(c.avail, 1), u: "%", l: t("kpi.avail", { win: winName(props.d?.win) }), k: c.avail >= 95 ? "ok" : "warn", ti: "" },
  2883. { v: fmt(M.value.停机总时长h), u: "h", l: t("kpi.downtime", { n: fmt(M.value.停机事件数) }), k: "warn", ti: "" },
  2884. { v: fmt(M.value.MTBF_h), u: "h", l: t("kpi.mtbo"), k: "", ti: "" },
  2885. { v: fmt(LS.value["停机"]), u: "MWh", l: lossLabel, k: "", ti: "" },
  2886. { v: fmt(LS.value["限电·绑定"]), u: "MWh", l: t("kpi.curtail"), k: "", ti: "" },
  2887. {
  2888. // 旧模板 `<b>${fmt(报警台)} <small>/ ${fmt(全场)}</small></b>` —— `vHtml` 保留 <small> 元素与节点边界
  2889. vRaw: `${fmt(props.d?.kpi?.报警台)} <small>/ ${fmt(props.d?.kpi?.全场)}</small>`,
  2890. u: "",
  2891. l: t("kpi.alarm_units"),
  2892. k: props.d?.kpi?.报警台 ? "warn" : "ok",
  2893. ti: t("kpi.alarm_units.hint"),
  2894. vHtml: true
  2895. }
  2896. ];
  2897. });
  2898. const why = computed(() => {
  2899. const out = {};
  2900. for (const w of props.d?.watch || []) out[w.t] = w.why;
  2901. return out;
  2902. });
  2903. const rows = computed(() => ((F.value.链盘 || {}).rows || []).filter((r) => r.lvl === "bad" || r.lvl === "warn"));
  2904. function basis(r) {
  2905. const own = (why.value[r.t] || "").trim();
  2906. if (own) return own;
  2907. const stuckKey = STEP[r.stuck] || String(r.stuck || "").toLowerCase();
  2908. const stuckStep = stuckKey === "evidence" ? t("step.site_records") : t("step." + (stuckKey || "evidence"));
  2909. return t("ov.basis_fusion", {
  2910. part: r.part || "—",
  2911. done: r.done == null ? "—" : r.done,
  2912. n: (r.steps || []).length || "—",
  2913. stuck: r.stuck ? stuckStep : t("ov.all_steps_done"),
  2914. srcs: (r.srcs || []).join(" / ") || t("ov.no_srcs")
  2915. });
  2916. }
  2917. function stuckText(r) {
  2918. if (!r.stuck) return "";
  2919. const stuckKey = STEP[r.stuck] || String(r.stuck || "").toLowerCase();
  2920. const stuckStep = stuckKey === "evidence" ? t("step.site_records") : t("step." + (stuckKey || "evidence"));
  2921. return t("step.stuck_at", { step: stuckStep });
  2922. }
  2923. const SYS_SEG = [["危险", "critical"], ["报警", "warning"], ["不可判", "unknown"], ["良好", "good"], ["优秀", "excellent"]];
  2924. const systems = computed(() => {
  2925. const dist = props.d?.sysdist || {};
  2926. return SYS9.map((s) => {
  2927. const c = dist[s] || {};
  2928. const n = Object.values(c).reduce((a, b) => a + b, 0) || 1;
  2929. return { name: sysName(s), slug: sysSlug(s), dist: c, n, seg: SYS_SEG.filter(([k]) => c[k]).map(([k, v]) => ({ k, v })) };
  2930. });
  2931. });
  2932. const mat = computed(() => F.value.矩阵 || []);
  2933. const hot = computed(() => new Set(rows.value.map((r) => r.t)));
  2934. const TIER_MAP = { "定论": "confirmed", "准定论·预警": "near", "候选·新发": "candidate_new", "候选": "candidate", "候选·记基线": "candidate_baseline", "参考": "reference", "INSUFFICIENT": "insufficient", "正常": "normal", "撤回": "withdrawn" };
  2935. const TIER_CLS = { candidate_new: "candidate", candidate_baseline: "candidate", normal: "reference" };
  2936. const tierCls = (zh) => {
  2937. const k = TIER_MAP[String(zh ?? "")];
  2938. if (!k) return "";
  2939. return TIER_CLS[k] || k;
  2940. };
  2941. return (_ctx, _push, _parent, _attrs) => {
  2942. _push(`<!--[--><section><div class="kpis"><!--[-->`);
  2943. ssrRenderList(kpi.value, (k, i) => {
  2944. _push(`<div class="${ssrRenderClass([k.k, "kpi"])}"${ssrRenderAttr("title", k.ti || void 0)}>`);
  2945. if (k.vHtml) {
  2946. _push(`<b>${k.vRaw ?? ""}</b>`);
  2947. } else {
  2948. _push(`<b>${ssrInterpolate(k.v)}`);
  2949. if (k.u) {
  2950. _push(`<small>${ssrInterpolate(k.u)}</small>`);
  2951. } else {
  2952. _push(`<!---->`);
  2953. }
  2954. _push(`</b>`);
  2955. }
  2956. _push(`<span>${ssrInterpolate(k.l)}</span></div>`);
  2957. });
  2958. _push(`<!--]--></div></section><section><div class="sechead"><h2>${ssrInterpolate(unref(t)("ov.attention"))}</h2><span class="meta">${ssrInterpolate(unref(t)("ov.attention_n", { n: rows.value.length }))}</span><span class="meta">${ssrInterpolate(unref(t)("ov.ranked_by"))}</span></div>`);
  2959. if (F.value.gap) {
  2960. _push(`<div class="card mb" style="${ssrRenderStyle({ "margin-bottom": "10px" })}"><p class="small" style="${ssrRenderStyle({ "color": "var(--warning)" })}">${ssrInterpolate(F.value.gap)}</p><p class="small muted">${ssrInterpolate(unref(t)("ov.fus_gap_hint"))}</p></div>`);
  2961. } else {
  2962. _push(`<!---->`);
  2963. }
  2964. _push(`<div class="cards"><!--[-->`);
  2965. ssrRenderList(rows.value, (r) => {
  2966. _push(`<div class="card"${ssrRenderAttr("id", `u-${r.t}`)} style="${ssrRenderStyle(`--sev:var(--${unref(SEV)[unref(stateKey$1)(r.ostate) || (r.lvl === "bad" ? "bad" : "warn")]})`)}"><div class="head"><a class="unit"${ssrRenderAttr("href", `/turbine/${r.t}`)}>${ssrInterpolate(unref(unitNo)(r.t))}</a><span class="sys">${ssrInterpolate(r.part || "")}</span><span class="${ssrRenderClass([unref(stateKey$1)(r.ostate) || (r.lvl === "bad" ? "bad" : "warn"), "chip"])}">${ssrInterpolate(unref(stWord$1)(r.ostate || (r.lvl === "bad" ? "危险" : "报警")))}</span></div><details class="evidence-detail"><summary>${ssrInterpolate(unref(t)("ov.evidence"))}</summary><div class="why">${ssrInterpolate(basis(r))}</div></details><div class="evid">`);
  2967. if (r.estate) {
  2968. _push(`<span class="${ssrRenderClass([tierCls(r.estate), "tier"])}"${ssrRenderAttr("title", unref(t)("tier." + (TIER_MAP[r.estate] || "unknown")))}>${ssrInterpolate(unref(stWord$1)(r.estate))}</span>`);
  2969. } else {
  2970. _push(`<!---->`);
  2971. }
  2972. if (r.srcs && r.srcs.length) {
  2973. _push(`<span class="rv">${ssrInterpolate(r.srcs.length)} sources</span>`);
  2974. } else {
  2975. _push(`<!---->`);
  2976. }
  2977. _push(`</div>`);
  2978. if (stuckText(r) || r.next) {
  2979. _push(`<div class="caveat"><b>${ssrInterpolate(unref(t)("ov.caveat"))}</b>${ssrInterpolate(stuckText(r))}${ssrInterpolate(stuckText(r) && r.next ? " — " : "")}${ssrInterpolate(r.next || "")}</div>`);
  2980. } else {
  2981. _push(`<!---->`);
  2982. }
  2983. _push(`</div>`);
  2984. });
  2985. _push(`<!--]--></div></section><section><div class="sechead"><h2>${ssrInterpolate(unref(t)("ov.strip"))}</h2><span class="meta">${ssrInterpolate(unref(t)("ov.strip_hint"))}</span><div class="legend"><!--[-->`);
  2986. ssrRenderList(["bad", "warn", "unk", "ok", "ex"], (k) => {
  2987. _push(`<span><i style="${ssrRenderStyle(`background:var(--${unref(SEV)[k]})`)}"></i>${ssrInterpolate(unref(t)(unref(stKey$1)(k)))}</span>`);
  2988. });
  2989. _push(`<!--]--></div></div><div class="strip" style="${ssrRenderStyle(`--n:${mat.value.length || 38}`)}"><div class="cells">`);
  2990. if (mat.value.length) {
  2991. _push(`<!--[-->`);
  2992. ssrRenderList(mat.value, (r) => {
  2993. _push(`<div class="${ssrRenderClass([[unref(unitState)(r), hot.value.has(r.turbine) ? "hot" : ""], "cell"])}"${ssrRenderAttr("data-u", r.turbine)}${ssrRenderAttr("title", `${unref(unitNo)(r.turbine)} ${unref(t)(unref(stKey$1)(unref(unitState)(r)))}`)}></div>`);
  2994. });
  2995. _push(`<!--]-->`);
  2996. } else {
  2997. _push(`<div class="quiet" style="${ssrRenderStyle({ "padding": "10px" })}">${ssrInterpolate(F.value.gap || unref(t)("ov.fus_gap_hint"))}</div>`);
  2998. }
  2999. _push(`</div><div class="nums"><!--[-->`);
  3000. ssrRenderList(mat.value, (r) => {
  3001. _push(`<span class="${ssrRenderClass(hot.value.has(r.turbine) ? "hot" : "")}">${ssrInterpolate(unref(unitNo)(r.turbine).replace("#", ""))}</span>`);
  3002. });
  3003. _push(`<!--]--></div><div class="quiet">${ssrInterpolate(rows.value.length ? unref(t)("ov.quiet", { n: Math.max(0, (mat.value.length || 38) - rows.value.length) }) : unref(t)("ov.quiet_all", { n: mat.value.length || 38 }))}</div></div></section><section><div class="sechead"><h2>${ssrInterpolate(unref(t)("ov.systems"))}</h2><span class="meta">${ssrInterpolate(unref(t)("ov.systems_hint"))}</span></div><div class="systems"><!--[-->`);
  3004. ssrRenderList(systems.value, (s) => {
  3005. _push(`<div class="sysc"><div class="t"><b>${ssrInterpolate(s.name)}</b><a${ssrRenderAttr("href", `#tab=component&sys=${encodeURIComponent(s.slug)}`)}>${ssrInterpolate(unref(t)("ui.open"))}</a></div><div class="bar"><!--[-->`);
  3006. ssrRenderList(s.seg, (k) => {
  3007. _push(`<i style="${ssrRenderStyle(`width:${(s.dist[k.k] / s.n * 100).toFixed(1)}%;background:var(--${k.v})`)}"></i>`);
  3008. });
  3009. _push(`<!--]--></div><div class="cnt"><!--[-->`);
  3010. ssrRenderList(s.seg, (k) => {
  3011. _push(`<span><i style="${ssrRenderStyle(`background:var(--${k.v})`)}"></i>${ssrInterpolate(s.dist[k.k])}</span>`);
  3012. });
  3013. _push(`<!--]--></div></div>`);
  3014. });
  3015. _push(`<!--]--></div></section><!--]-->`);
  3016. };
  3017. }
  3018. });
  3019. const _sfc_setup$1 = _sfc_main$1.setup;
  3020. _sfc_main$1.setup = (props, ctx) => {
  3021. const ssrContext = useSSRContext();
  3022. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Overview.vue");
  3023. return _sfc_setup$1 ? _sfc_setup$1(props, ctx) : void 0;
  3024. };
  3025. const _sfc_main = /* @__PURE__ */ defineComponent({
  3026. __name: "Workbench",
  3027. __ssrInlineRender: true,
  3028. props: {
  3029. d: {},
  3030. tab: {},
  3031. sys: {},
  3032. sub: {},
  3033. cms: {},
  3034. curves: {},
  3035. apis: {}
  3036. },
  3037. emits: ["tab", "sub"],
  3038. setup(__props, { emit: __emit }) {
  3039. const props = __props;
  3040. const emit = __emit;
  3041. const pending = computed(() => !DONE.includes(props.tab));
  3042. return (_ctx, _push, _parent, _attrs) => {
  3043. _push(`<!--[--><nav class="wb-tabs" data-v-bef855f5><!--[-->`);
  3044. ssrRenderList(unref(TABS), (id) => {
  3045. _push(`<button class="${ssrRenderClass({ on: id === __props.tab })}" data-v-bef855f5>${ssrInterpolate(unref(t)("tab." + id))}`);
  3046. if (!unref(DONE).includes(id)) {
  3047. _push(`<sup class="todo" data-v-bef855f5>迁移中</sup>`);
  3048. } else {
  3049. _push(`<!---->`);
  3050. }
  3051. _push(`</button>`);
  3052. });
  3053. _push(`<!--]--></nav><div class="page-head" data-v-bef855f5><h1 data-v-bef855f5>${ssrInterpolate(unref(t)("tab." + __props.tab))}</h1><span class="window-badge" data-v-bef855f5>${ssrInterpolate(unref(t)("win.label"))} · ${ssrInterpolate(unref(winName)(__props.d?.win))}</span></div>`);
  3054. if (__props.tab === "overview") {
  3055. _push(ssrRenderComponent(_sfc_main$1, { d: __props.d }, null, _parent));
  3056. } else if (__props.tab === "energy") {
  3057. _push(ssrRenderComponent(_sfc_main$a, { d: __props.d }, null, _parent));
  3058. } else if (__props.tab === "component") {
  3059. _push(ssrRenderComponent(_sfc_main$c, {
  3060. d: __props.d,
  3061. sys: __props.sys
  3062. }, null, _parent));
  3063. } else if (__props.tab === "fault") {
  3064. _push(ssrRenderComponent(_sfc_main$8, { d: __props.d }, null, _parent));
  3065. } else if (__props.tab === "vibration") {
  3066. _push(ssrRenderComponent(_sfc_main$7, {
  3067. d: __props.d,
  3068. sub: __props.sub,
  3069. cms: __props.cms,
  3070. onSub: ($event) => emit("sub", $event)
  3071. }, null, _parent));
  3072. } else if (__props.tab === "generation") {
  3073. _push(ssrRenderComponent(_sfc_main$6, {
  3074. d: __props.d,
  3075. curves: __props.curves,
  3076. pending: !!__props.d.m9_pending
  3077. }, null, _parent));
  3078. } else if (__props.tab === "decision") {
  3079. _push(ssrRenderComponent(_sfc_main$5, {
  3080. d: __props.d,
  3081. apis: __props.apis
  3082. }, null, _parent));
  3083. } else if (__props.tab === "assistant") {
  3084. _push(ssrRenderComponent(_sfc_main$4, { d: __props.d }, null, _parent));
  3085. } else if (__props.tab === "system") {
  3086. _push(ssrRenderComponent(_sfc_main$3, { apis: __props.apis }, null, _parent));
  3087. } else if (__props.tab === "report") {
  3088. _push(ssrRenderComponent(_sfc_main$2, {
  3089. d: __props.d,
  3090. apis: __props.apis
  3091. }, null, _parent));
  3092. } else if (__props.tab === "documents") {
  3093. _push(ssrRenderComponent(_sfc_main$h, null, null, _parent));
  3094. } else if (__props.tab === "recalc") {
  3095. _push(ssrRenderComponent(_sfc_main$g, null, null, _parent));
  3096. } else if (__props.tab === "portal") {
  3097. _push(ssrRenderComponent(_sfc_main$f, null, null, _parent));
  3098. } else if (__props.tab === "portal_sec") {
  3099. _push(ssrRenderComponent(_sfc_main$e, null, null, _parent));
  3100. } else if (__props.tab === "portal_src") {
  3101. _push(ssrRenderComponent(_sfc_main$d, null, null, _parent));
  3102. } else if (pending.value) {
  3103. _push(`<section class="card" data-v-bef855f5><h2 data-v-bef855f5>${ssrInterpolate(unref(t)("tab." + __props.tab))} <small data-v-bef855f5>(本页签尚未迁移到 Vue)</small></h2><p class="hint" data-v-bef855f5> 迁移纪律:页签逐个搬,每搬一个就用 <code data-v-bef855f5>tools/render_parity.mjs</code> 与重构前页面 (现网 <code data-v-bef855f5>/detail/v2</code>)对拍文案/数值/类名序列,**通过后**才删对应的 Python 前端源码。 当前已迁移:${ssrInterpolate(unref(DONE).join("、"))}。 </p></section>`);
  3104. } else {
  3105. _push(`<!---->`);
  3106. }
  3107. _push(`<!--]-->`);
  3108. };
  3109. }
  3110. });
  3111. const _sfc_setup = _sfc_main.setup;
  3112. _sfc_main.setup = (props, ctx) => {
  3113. const ssrContext = useSSRContext();
  3114. (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/Workbench.vue");
  3115. return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
  3116. };
  3117. const Workbench = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-bef855f5"]]);
  3118. async function renderTab(d, tab = "overview", sub, cms, curves, apis) {
  3119. const app = createSSRApp(Workbench, { d, tab, sub: sub ?? null, cms: cms ?? null, curves: curves ?? null, apis: apis ?? null });
  3120. return await renderToString(app);
  3121. }
  3122. export {
  3123. renderTab
  3124. };