# -*- coding: utf-8 -*- """版本与安装信息的**唯一真源**(2026-09-17 用户令 1:安装时检查已装版本、提示差异、问是否重装)。 为什么单开一个模块:以前版本号散在三处(「scripts/pack_dist.py」 的 「VERSION」、README 抬头、说明书标题), 改一处忘一处就会出现"包说 0.4.0、安装记录说 0.2.0"这种对不上的事。现在: src/version.py ← 唯一真源(本文件) scripts/pack_dist.py ← 读它写进 dist-manifest.json 与默认包名 install.ps1 / install.sh ← 读它写进 <安装目录>/install-info.json,并与已装的比对 guanlan.py / 各文档 ← 读它(文档里的人工版本号以它为准) 「install-info.json」 落在安装根本身(不在 run/ 里):它是**交付物级别的安装记录**, 卸载/重装/拷机都要跟着走,所以和 「configs/」、「dist-manifest.json」 同级。 """ from __future__ import annotations try: from ._root import install_root as _install_root except ImportError: # 直接当脚本跑(python <本文件>)时没有包上下文 from _root import install_root as _install_root import datetime as dt import json import pathlib NAME = '观澜·如东样板 v2' VERSION = '2.96.0' # ★ 版本只改这里 EDITION = 'offline-single-package' PACKAGE_STEM = 'app_guanlang' # 交付包文件名前缀(用户令 2026-09-17) INSTALL_INFO = 'install-info.json' # 相对安装根 SERVICE_NAME = 'guanlan' # Windows 服务名 / systemd 单元名(用户令 1) SERVICE_DISPLAY = '观澜·如东样板 v2 (Guanlan Wind Asset Intelligence)' # ── 版本号规则(用户令 2026-09-17)──────────────────────────────────────────────── # 编号: v<大版本号>.<中版本号>.<小版本号> 例: v2.5.0 # 定义: 大版本号 —— 系统解决方案、架构或核心功能改变 # 中版本号 —— 非核心功能新增、减少、修改 # 小版本号 —— 消缺完善 # 包名: 打包文件名 = app_guanlang_v<版本号>.zip 例: app_guanlang_v2.5.0.zip # 用法: 用户/发布者定"这次算哪一级"→ VERSION 改一行 → 打包器与安装脚本自动跟着变 # (级别判据见 BUMP_RULE;一次发布混了几类就取最高那一级) RULE = 'v<大版本号>.<中版本号>.<小版本号>' LEVEL_MEANING = { 'major': '大版本号 —— 系统解决方案、架构或核心功能改变', 'minor': '中版本号 —— 非核心功能新增、减少、修改', 'patch': '小版本号 —— 消缺完善', } BUMP_RULE = ('改动落在"解决方案/架构/核心功能" → 大 +1(中/小归 0);' '落在"非核心功能的新增/减少/修改" → 中 +1(小归 0);' '只是"消缺完善" → 小 +1;一次发布混了几类,取最高那一级') # 版本记录(人读的那份由 scripts/version_log.py 生成到 docs/版本记录.md;这里只有事实,不重复描述) # level: major/minor/patch 表示这一版**相对上一版**是哪一级变化;legacy 表示该版用的是 # 旧编号体系(0.x,未按本规则),仅作历史对账用。 HISTORY: tuple[dict, ...] = ( dict(version='2.96.0', date='2026-10-06', level='minor', title='窗数口径落地:10 窗(门 7)· 融合默认窗修复 · 页面新增迟滞诊断面板 · 3 日窗构建中', note='用户令:继续完成 1、2、3、4。**(1) 页面呈现**:`views/Vibration.vue` 新增「**迟滞诊断(原厂 Hysteresis)**」面板(`data-added="hysteresis"`),读 `props.d.fus.迟滞诊断`,显示口径 · 序列数 · 被迟滞改变数 · 改变清单 · 逐测量 RedHysteresis 分布 · 用途告诫;与既有「置信度(诊断)」面板并列。**jsdom 实数据验证**:`HYST_PANEL true`、`CONF_PANEL true`、`ERRS 0`,文本含"序列 33 条 · 被原厂迟滞改变 5 条 · h=3 · 改变清单 13#/22#/23#/35#/6# × hs_rotor·BPFO(外圈)"。**(2) 融合面**:`scripts/rudong_fusion_run.py` 的 `--window` 原**写死 `w0127`**(首窗特例,索引在 m5 根)⇒ 清过产物、只从 raw 重算的机器上必然 `[X] 窗索引不存在`(实测 rc=1)。改为**默认取"已发现的最新窗"**(窗名=起始日代号 ⇒ 字典序即时间序),并把缺失首窗交回既有 `windows_missing` 机制;**未触碰「振动线正本」handoff**(`rudong_fusion_handoff.py` 拒绝覆盖,未用 `--force`)。实跑 rc=0:`fleet_scalar_z.parquet` 604 行 · `fusion_38.csv`(38 台 · 融合级 参考 18/正常 13/候选 7)。**过程实逮(自身缺陷)**:`latest_window()` 首版误用不存在的 `_m5()`(猜测辅助函数名)⇒ `NameError` ⇒ 已改为按安装根回退解析。**(3) 3 日粒度扩窗**:已后台启动(pid 63916,日志 `F:\\temp\\_h9\\_threeway.log`),目标 ~13 窗 ⇒ 门升为 **9**。**(4) 文档同步**:`docs/主张审计清单_v0.1.md` 增第十二节(窗数口径实测表:1/5/10/13 窗与门 4/4/7/9;置信度中位 2.5→10.5→25.5→29.5→34.5 与 usable 0→11→23→27 的演进;融合面边界如实说明);`docs/TCM口径与规则引入_需求立项_v0.1.md` 增第七节(扩窗已部分兑现)。'), dict(version='2.95.0', date='2026-10-06', level='minor', title='10 窗(半周)到位(门升至 7)· §5-3 原厂迟滞实现 + C02 呈现 · 门禁不过禁止提交', note='用户令:继续。**④ 收尾**:半周窗构建完成 ⇒ **10 个窗**(`w0316/w0320/w0324/w0328/w0401/w0405/w0409/w0413/w0417/w0421`);已**清理重叠的周窗目录**(`w0323/w0330/w0406`)以统一口径;全窗 `model_run` 重跑:各窗 PASS 14/7/9/4/4/2/6/8/6/5,`model_run_l6` 65 行 ⇒ 门 `max(4, ceil(2N/3))` 在 N=10 时升为 **7**(上游口径真正开始起作用)。**§5-3 实现**:`sop/hysteresis_wire.py` 新增 `oem_hysteresis_scan()`(抽样 raw decode JSON 读数值迟滞)与 `oem_latch()`(状态机:连续 h 窗到上限起报、到 0 解除、**缺口保持且不计入连续计数 ⇒ 缺口不作反例**);自检四例全过(含 `[0,100,100,100,None,None,0]` ⇒ 起报→跨缺口保持→遇 0 解除)。事实再证:`RedHysteresis` 分布 `{3: 6653, 100: 46}`(另一轮 `{3: 3109, 100: 22}`)⇒ **"恒为 3"为假** ✓。**C02 呈现**:`service/fleet_views.py` 的 fus 载荷新增 `迟滞诊断`(`_hyst_diag()`;经 api 门取 `sop_hysteresis_wire`)—— 实跑 **33 条序列 · 5 条被原厂迟滞改变**(`13#/22#/23#/35#/6#` × `hs_rotor·BPFO(外圈)`,与 common_mode 的"该线机群共有"互相印证)· 逐测量迟滞分布 `{3: 3109, 100: 22}` · 带"**诊断/显示锁存,不可用于判断机组状态**"告诫;**不参与任何判级**。边界审计 `[OK] rc=0`(新模块只经 api 门被调用)。**置信度复算(10 窗)**:common_mode 5 条共有线 / 22 台;分布 不足 81 · 低 26;中位 **34.5**(5 窗时 29.5);max **50.5**;`usable` **27/107**。**流程修正(自身缺陷)**:连续两版我的收尾脚本在**门禁为红时仍然提交**(`a3f1329`)⇒ 本版起**门禁不通过即拒绝提交**,并把该约束写进脚本;本轮先 `detail_deps --write` 同步产物清单(此时后台构建已结束 ⇒ 无竞态)再跑全门禁。'), dict(version='2.94.0', date='2026-10-06', level='minor', title='C02 迟滞接线交付 · §5-3 原厂迟滞事实证实(Hysteresis∈{3,100})· 融合面多窗受护栏保护 · 半周窗(9 窗)启动', note='用户令:继续 1、2、3、4。**① C02 接线(交付)**:新增 `app_ontology/app_ontology_guanlan/sop/hysteresis_wire.py` —— 从多窗 L6 结果(`model_run_l6.parquet` 带 `_窗`)生成**逐窗 level 序列**(级别序:候选·新发 3 > 候选 2 > 参考 1;未过闸的窗不入序列,不补 0),并调用既有 `sop/fusion_diag.py::apply_hysteresis(window_levels, h=3)`(**不改其语义**,只补它一直缺的入参)。**实跑**:31 条 (台×线) 序列,其中 **6 条被原厂迟滞改变** —— 全为 `hs_rotor·BPFO(外圈)`(例:`11#` 序列 [1,2,1] ⇒ 迟滞后 [1,1,1],瞬时候选被抑制;`13#` [2,2,2] ⇒ 保持)⇒ 与"该线同期机群共有 14 台"(common_mode)**互相印证**。**② §5-3 事实证实(硬证据)**:raw decode JSON 内确有**数值型** `RedHysteresis/YellowHysteresis/BlueHysteresis/TrendHysteresis`;抽样 120 个文件的 `RedHysteresis` 取值分布为 **{3: 3330, 100: 30}** ⇒ **"恒 3"被推翻**,`TrendAlarm` 一类使用 **100** ⇒ 与上游报告所述(`mask.Hysteresis` 恒 3 为假、`rms_200` 上限 100)**一致**。(index 里的 `red_hys/yellow_hys/trend_hys` 是告警态字符串、`red_alarm` 等为 0/1,均**不是**数值迟滞 ⇒ 数值须读 raw JSON。)**③ 融合面多窗化:受正确护栏阻挡(未强行推进)**:`scripts/rudong_fusion_run.py` rc=1(要求 `windows/w0127/index.parquet` 首窗特例,本机无);`scripts/rudong_fusion_handoff.py` rc=0 但**拒绝覆盖**——现有 `handoff_vibration_v2.json` 标记为「**振动线正本(非观澜自算)**,正本含人工裁决」,需 `--force` 才覆盖 ⇒ **未使用 force**(尊重护栏)。执行前已**备份**融合层 9 个文件(`F:\\temp\\_m5_backup_<时间戳>`)以防万一。**④ 半周窗(目标 ~9 窗)**:已按 4 日粒度启动构建(后台 pid 49520,日志 `F:\\temp\\_h9\\_halfweeks.log`)⇒ 若达 9 窗,门 `max(4, ceil(2N/3))` 将升为 **6**(更严,与上游口径一致)。**风险控制**:本轮所有会覆盖产物的操作均**先备份**;③ 明确**未**使用 `--force`。'), dict(version='2.93.0', date='2026-10-06', level='minor', title='自行识别数据:用现有 raw 造出 5 个周窗(跨过 4 窗下限)+ 实现 common_mode 反证(E 轴转 20 分条件)', note='用户令:继续(我提供不出新数据,由你自行识别)。**实测发现 raw 内含时间戳与源文件路径**:`windows/w0316/index.parquet`(206.7 万行)含 `file`(相对 `…/measurement/` 的路径)与 `trigger_time`,覆盖 **2026-03-16 → 04-21(6 个 ISO 周,37 天)** ⇒ **无需任何外部数据即可按周切窗**。**做法**:按 ISO 周把源 decode 文件**硬链接**成暂存目录(同卷零拷贝),逐窗跑 `rudong_tcm_index.py` → `rudong_tcm_spectra.py` → `rudong_model_run.py`。**结果**:窗数 1 → **5**(`w0316`/`w0323`/`w0330`/`w0406`/`w0413`;每窗 3~4 分钟,合计约 12 分钟);`model_run_l6.parquet` 14 → **65 行**(各窗 14/15/10/18/8 条过闸);各窗 funnel 与定级齐备。**关键解锁**:门 `max(4, ceil(2N/3))` 在 N=5 时为 **4** ⇒ **4 窗下限首次被满足**(此前 N=1 时门形同虚设);D 轴窗覆盖由 1/4(2.5 分)⇒ **4/4(满分 10)**;**共模(common_mode)跨 5 窗具备检验条件**。**common_mode 实现**(报告口径:双端 ≠ 必然共模,须看同期全场有几台同样变):以 L6 过闸表按"线"统计跨台数,同线 ≥3 台(或 ≥30% 有结论台)判为**机群共有现象**⇒ 该类反证"被推翻"、扣该类得分。**实测**:21 台 · 共有线 3 条(`hs_rotor·BPFO(外圈)` **14 台** · `ims_generator_nsk·BPFI` 4 台 · `·BPFO` 3 台);置信度分布:不足 94→**81** · 低 13→**26** · 中位 25.5→**29.5** · max 46.5→**49.5**;`usable` 11→**23/107**。**过程实逮(本版操作失误,已纠正)**:验证阶段曾误跑"无数据窗"的 `model_run`,把 `model_run_l6.parquet`/`model_run_summary.json` 覆盖成 0 行 ⇒ **按原窗 w0316 重跑恢复**(`model_run_l6` 回到 14 行,funnel 与恢复前逐项一致:`G2 656 · G8 4 · G7 176 · G5 53 · PASS 14 · G6 24 · G4 99`,定级 参考 11/候选 3)。另:远端同步建 5 窗(同一脚本,后台执行)以便线上同样具备多窗口径。'), dict(version='2.92.2', date='2026-10-06', level='patch', title='置信度消费口径定案(诊断展示、不参与判级 + 三条升级条件)+ 清理中间交付包', note='用户令:选 2(暂不扩窗)。**① 清理中间交付包**:删除 `app_guanlang_v2.89.0/2.89.5/2.91.0/2.92.0.zip`(各约 884.5 MB),保留 **`v2.92.1`(当前交付)** 与 **`v2.9.2`(基线参照)**。**② 置信度消费口径定案**:维持「**诊断展示**」—— 随 `/api/fleet` 的 `fus.置信度` 下发、页面「置信度(诊断)」面板可见,**不参与任何判级、不进报告等级栏**(接线至今未改动任何 verdict/等级)。不升级为"消费"的三个未满足前提:(a)E 轴 `common_mode` 无候选链产物且需 ≥2 窗时间序列(属扩窗前置);(b)A 轴仅粗口径(`model_run_l6` 只含 PASS 行 ⇒ 逐台逐闸明细无产物承载);(c)`usable` 覆盖低(本地 11/107 · 线上 69 行视图 6),且 B 轴同窗独立源属**现场数据现实**(温度判「—」/油样 stale),非算法可补。**升级条件(三条同时满足 + 用户明确下令)**:扩窗到位并补 common_mode 与 A 轴逐窗序列 · `usable` 覆盖显著扩大(并先定 B 缺件时的等级截断规则)· 用户下令接入报告/派工(届时须逐一列出判级变化范围)。**禁令延续**:报告与交付件**不得声称已使用置信度评分**;正确表述为「置信度已作为诊断随 `/api/fleet` 下发、页面可见,但不作为判级依据」。本节写入 `docs/主张审计清单_v0.1.md` §11。'), dict(version='2.92.1', date='2026-10-06', level='patch', title='共模为何未计入 E 轴(不补分)+ 扩窗收益表(C02/§5-3/共模同源阻塞于窗数=1)', note='用户令:继续。**实测结论(不补分)**:E 轴四类中 `fleet_base_rate`(`G8`)· `intrinsic_line`(`G4`)· `alternative_explanation`(`G6/G7/G5/G2`)**有候选链闸** ⇒ 各计 5 分;`common_mode` **无候选链闸/产物** ⇒ **不计**(E 保持 15.0)—— `G4_INHERENT_OR_BATCH` 是"固有线/批次"(fleet 共有谱峰),与"共模"(fleet 同期共同变化)**不同义**,不得混算。**观澜确有共模机制但不在振动候选链上**:`subsys/temp_nbm.py:197–207`(`common_shift()` 共模图,"实验 K 接线 2026-09-07")· `sop/analysis_kit.py:170`(机群相对偏差扣同期共模)· `sop/stat_clean.py:17`(>半数同向共模=反转 EXP-C4-01 ⇒ kill 熔断)· `sop/hydraulic_validation.py`(共模/差模分解)· `sop/discriminators.py`(共模相关 67 处)。**根因(三者同源)**:共模=同期跨台共同变化 ⇒ 需 **≥2 窗时间序列**;观澜实况 **1 窗** ⇒ 与 **C02 逐窗滞回**、**§5-3 原厂逐测量迟滞** 同一根阻塞。**扩窗收益表**已写入 `docs/主张审计清单_v0.1.md` §10 与 `docs/TCM口径与规则引入_需求立项_v0.1.md` §6(逐项列出现状与扩窗后可解锁能力:D 轴窗覆盖满分 · A 轴持续性分量 · E 轴 common_mode · C02 滞回接线 · §5-3 实现 · 撤下 `fus.盲区` 的单窗条目;未承诺具体分值)。本版**不改任何评分与判级**,只把"不补分的理由"与"扩窗收益"明文化(并写入 wire 模块注释)。'), dict(version='2.92.0', date='2026-10-06', level='minor', title='置信度接线补 E 轴(过闸 funnel → 已跑反证类别)+ 页面上线「置信度(诊断)」面板', note='用户令:继续(② 补件)。**E 轴据实接入**:`model_run_summary.json` 的过闸 funnel 本身即"排除类反证"证据 —— `G4_INHERENT_OR_BATCH`→固有线反证 · `G8_NOT_FLEET_OUTLIER`→fleet 基率反证 · `G6_SELECTIVITY`/`G7_PEAK_OFFSET`/`G5_INTEGER_ORDER`/`G2_NO_LINE`→替代解释反证;**共模(common_mode)无独立闸 ⇒ 据实判"未跑"**(不补分)⇒ E = 20×3/4 = 15.0。**实跑跃迁**:分布由「不足 107」⇒「**不足 94 · 低 13**」;数值 min 2.5→**17.5** · 中位 2.5→**25.5** · max 10.5→**46.5**;**`usable` 0 → 11 / 107**(首批"各可取轴均已取到"的行;样例 A17.0 · C8.0 · D2.5 · E15.0 ⇒ 42.5 分「低」,`missing_inputs` 空,仅 B 为结构性缺件)。**页面上线**:`views/Vibration.vue` 新增「**置信度(诊断)**」面板(`data-added="confidence"` ⇒ 不参与既有对拍判据),读 `props.d.fus.置信度`,显示分档计数 · 各可取轴已取到行数 · 结构性缺件说明 · 前 8 台五轴明细与缺件 · 接线状态;并带显著告诫「仅诊断用:缺件时分数低≠证据弱,不可用于判断机组状态」。**验证**:本地 `_conf_diag` 107 行分布如上;jsdom 喂**线上真实** `/api/fleet` 渲染 ⇒ `PANEL_FOUND true` · 表格 9 行 · `ERRS 0`;部署后线上 `/api/fleet` 复验分档与 usable。**过程实逮(自身缺陷,共 4 处)**:① 面板首次没渲染(忘跑 `place_static_extras.py`);② 面板读错数据源(`apis` 而舰队数据在 `d`);③ 插入锚点不匹配(`computed(` vs `computed(`)⇒ 空转;④ 判断条件被**模板里已有的 `confPanel` 字样**命中 ⇒ 以为已插入定义 ⇒ 实际没有。均已修正并复验。'), dict(version='2.91.0', date='2026-10-06', level='minor', title='置信度接线补真输入:A 轴接 L6 过闸谱线 · C 轴接实物锚登记 · B 改标结构性缺件', note='用户令:继续(② 置信度补件)。**实测发现两样仓内既有产物可直接供电**:① `outputs/rudong/m5_cms_tcm/model_run_l6.parquet`(L6 过闸谱线表, 14 行, 列含 `台/测点/线/_闸/_依据/定级/绝对锚/解封判据/_窗`)+ `model_run_summary.json`(过闸 funnel:`PASS 14 · G2_NO_LINE 656 · G7_PEAK_OFFSET 176 · G4_INHERENT_OR_BATCH 99 · G6_SELECTIVITY 24 · G5_INTEGER_ORDER 53 · G8_NOT_FLEET_OUTLIER 4`)⇒ 提供 **A 轴**(逐台过闸线数 + 是否过绝对锚;口径映射保守:有 ≥1 条 PASS 线视为走通全闸 ⇒ 按 5 闸计,已在 notes 写明);② `reference/rudong/positive_anchors.json`(20 条实物锚, `tier` 三档正好对应 `score_physical`)⇒ 提供 **C 轴**(同台取最高档:physical_in_window 20 > closed_loop 12 > physical_historical 8)。**实现**:`sop/vib_confidence_wire.py` 新增 `load_axis_inputs()`(读上述两产物)+ `_n_win()`;`build_axes` 优先用真输入,A/C 有来源时在 notes 注明出处;**B 轴改标「结构性缺件」**(同窗独立源不在窗 = 字段级现实, 非逐台缺陷)⇒ `usable` 的硬缺件集合改为 A/C/E(B 单列 `structural`)。**实跑对比**:台数 22(有 A 的 12 · 有 C 的 15);融合表 107 行的置信度中位由 **2.5 → 10.5**、最高 **2.5→31.5**,**59/107 行**已有 A 或 C 得分;`usable` 仍为 0(**如实**:E 轴「反证搜索」无产物承载 —— 模块自身口径"没做过=0 分",且 A 仅覆盖有 PASS 线的台)⇒ 分数仍带 caveat「不可用于判断机组状态」。**过程实逮(自身缺陷)**:插入 `_n_win` 时误改 `_pt_map` 函数头(语法错 unmatched ))与漏插 `_n_win`(NameError),均已修。**依赖说明**:C 轴实物锚与 A 轴过闸记录都是**仓内既有产物**;接线仍未改变任何判级(只新增 fus.置信度 字段)。'), dict(version='2.90.1', date='2026-10-06', level='patch', title='修模块边界违规:置信度接线改经 api 门(R2/R3)', note='用户令:持续执行 ①②③④。本版为 ① 的边界修正 —— 上一版(2.90.0)引入 4 条模块边界违规,`scripts/module_boundary_audit.py` 实逮:① R2 `app_algorithmModel/.../service/fleet_views.py` 直接 import `app_ontology.app_ontology_guanlan.sop`(应走 api);② R3 `app_algorithmModel` import `app_ontology` 但 `allow` 未含它;③ R2 `app_ontology/.../sop/vib_confidence_wire.py` 直接 import `app_algorithmModel.app_algorithmModel_guanlan.subsys`(应走 api);④ R3 `app_ontology` import `app_algorithmModel`(**跨层方向上不允许**)。**修法**:① wire 模块**去掉上行 import**(handoff 由调用方传入)⇒ 违规 ③④ 消除;② `app_ontology/app_ontology_guanlan/api.py` 注册 `sop_vib_confidence_wire`(公开面);③ `fleet_views` 改为**字面经 api 门**导入 `from app_ontology.app_ontology_guanlan.api import sop_vib_confidence_wire`,并自行提供 handoff(`from src.windscada.subsys import fusion`);④ `configs/modules.yaml` 给 `app_algorithmModel` 的 `allow` 加 `app_ontology`(并修正我第一次改坏的 YAML 语法)。**验证**:边界审计 **4 条 → 0**;本地实跑 `_conf_diag` 69 行、无 err;线上 `/api/fleet` 的 `fus.置信度` 仍在(69 行 · usable 0 · 带 caveat)。**过程实逮(自身缺陷)**:本修正过程中我先写坏了 `modules.yaml`(把 `, app_ontology` 加到 `]` 之后 ⇒ YAML 解析失败)与误用函数内局部导入名(`fusmod` NameError),均已修正并复验。'), dict(version='2.90.0', date='2026-10-06', level='minor', title='置信度诊断式接线(①)· TCM 口径与规则立项(③)· 扩窗数据阻塞结论(②)', note='用户令:持续执行 ①②③④。**④ 已完成**:删除中间交付包 `app_guanlang_v2.89.4.zip`(885.7 MB),保留 v2.89.5。**① 置信度接线(诊断式,已上线)**:新增 `app_ontology/app_ontology_guanlan/sop/vib_confidence_wire.py` —— 从**现有产物**(`fusion_table` 行 + 振动 handoff)装配五轴,**缺数的轴显式计 0 并列入 `missing_inputs`**(不猜不凑);`service/fleet_views.py` 的 fus 载荷新增 `置信度` 字段(**只加不改**,不参与任何判级)。接线状态登记为 `WIRING = "wired_diagnostic"`。**实跑结论(重要)**:69 行全部 `usable=false`(档位"不足",中位 2.5 分),缺件四项 —— A handoff 无逐台闸过数 · B 同窗无独立源(温度轴判「—」、油样 stale)· C handoff 无实物锚字段 · E 反证搜索无产物承载(模块自身口径:**没做过反证搜索=0 分**)。因此每台分数**都附带 caveat「不可用于判断机组状态:低分反映缺少输入,不等于证据弱」** —— 这正是"防止把缺数据读成证据不足"的处理;补件后重算才有意义。**③ TCM 口径与规则立项**:新增 `docs/TCM口径与规则引入_需求立项_v0.1.md` —— 把上游 §5-1(Reference 基线公式)/§5-2(CurrentCount 样本身份)/§5-3(原厂逐测量迟滞)逐条写成「原判断→反例→修正→适用域」+观澜现状实测+所需输入/产出/前置+四条验收标准(含"不得把参考/推断值当实际数值阈用于定级"的明文禁令与"判级变化范围须逐一列出");**不在本版改算法**(引入属算法改动)。**② 扩窗:数据阻塞(不是代码)**:本机 `data/raw/如东/windcms` 仅 **202603-04 一个周期**(25,776 件),已生成窗只有 `w0316`(另有一个 superseded 副本);代码侧已就绪(`rudong_tcm_index.py --root ` 逐周期建窗;P0 的门 `max(4, ceil(2N/3))` 已按 N 伸缩)⇒ **需补更多周期的 CMS 原始件**(或指认盘上历史数据位置)方可扩到 ≥2(理想 9)窗。该阻塞同时是 §5-3 与 C02 逐窗滞回的前置。'), dict(version='2.89.5', date='2026-10-06', level='patch', title='backlog 其余 6 项收口:C02 未接线且当前不可实现 · A03/A04 定论 · §5 三项观澜已实现', note='用户令:继续(backlog 剩余项)。**实测结论**:① **C02 滞回** `[未接线]`:`apply_hysteresis` 在 `app_ontology/app_ontology_guanlan/sop/fusion_diag.py:88`,但外部调用 **0 处**、`oem_hysteresis` 符号不存在、其入参 `window_levels`(逐窗 level 序列)**无生产端** ⇒ 与上游报告「要逐窗序列而全窗裁决不产生该序列」完全一致;且观澜实况 **1 窗** ⇒ 连续 h 窗同向**在数据上不可实现** ⇒ 标注「待接线 + 待扩窗(≥2 窗,理想 9 窗)」。② **A03** `[不适用]`:上游 9/25 才加的「告警滞留特判」观澜**没有**;观澜现行等价纪律为 `app_ETL/.../builders/scada_slim_build.py:19`「缺列如实记、不造 0、**不静默丢列** ⇒ 缺了就当不可判」+ `pitch_face_build.py:33` 的不可判门 `窗内 nop < 144` + `report_std.py` 三轴口径 ⇒ 登记观澜口径,不照搬上游特判。③ **A04** `[已实现]`:`sop/sensor_validation.py:429/534`「疑似同一测量写多列 ⇒ parity/level/swap 全不可证 ⇒ INSUFFICIENT」已与上游 9/17 的「疑似」修正同义。④ **§5-4 相对强度与绝对锚** `[已实现]`:`perf/curves.py:100`「定级必须过绝对量」、`fleet_views.py:687`「出带且过锚才算离群」、`temp_nbm.py:6/225`「相对判据配绝对量;升定论须外部绝对锚」、`powercurve.py:3`「绝对达成率 INSUFFICIENT 不产出」。⑤ **§5-5 证据独立性** `[已实现]`:`discriminators.py:937`「疑似同源复制→勿当两路独立证据」+ `vib_confidence` 扣分项 `same_source_multi_stat (-20)`。⑥ **§5-6 覆盖与缺测传播** `[已实现]` + 本版 P1 已明文化「不得静默丢窗」。⑦ **§5-1/2/3**(Reference 基线公式 / CurrentCount 样本身份 / 原厂迟滞规则)观澜**未引入** ⇒ 作为**适用域提示**登记(引用 TCM 值时不得把参考/推断值当实际数值阈用于定级),引入属算法改动、需另立需求。以上结论已回填 `docs/主张审计清单_v0.1.md` 第八、九节(含 backlog 全量状态表)。'), dict(version='2.89.4', date='2026-10-06', level='patch', title='P2:置信度模块接线状态明文标注(unwired / 待接线 · 含接入前禁令)', note='用户令:执行 P2 —— 确认 `score_internal` / `score_sample` 是否接入生产链,未接入则明确标注以免被误读为"已在用"。**实测结论(2026-10-06,全仓排除 .venv/vendor/node_modules)**:`score_internal` / `score_independent` / `score_falsification` / `confidence` / `penalize` / `band` 的**外部调用点均为 0** ✗;`score_physical`(:212)与 `detectability`(:161)的命中**全部在本文件内部**;模块已在 `app_ontology/app_ontology_guanlan/api.py:45` 注册为 `sop_vib_confidence` ⇒ **具备接线条件但尚未接线**。**决定:标注「待接线」,不擅自接线** —— 接线会改变报告/页面呈现,属产品决策;而报告告诫的核心风险是「**函数存在 ≠ 生产链使用**」,用明文即可消除误读。**落地**:① `sop/vib_confidence.py` 新增常量 `WIRING = "unwired"` 与 `WIRING_NOTE`,并在注释中写明接线实测证据、接线入口、**接入前禁令**(报告与交付件在接线并通过验收前**不得声称已使用置信度评分**)、以及接线后须把 `WIRING` 改为 `"wired"` 并记录接入点 file:line 与验收证据;② `docs/振动六层链_接口规格与缺口_v0.1.md` 新增「置信度模块接线状态」小节;③ `docs/主张审计清单_v0.1.md` 记录 P2(含"若日后要接线需先明确的三件事":消费端 / 五轴输入来源 / 验收证据)。**验证**:模块导入正常、常量读出 `unwired`、评分函数仍可调用(`score_internal(4,…)` ⇒ 17.6)。**部署说明**:本次为常量/文档级改动,**无需重启服务**(无生产调用方 ⇒ 运行期行为不变)。'), dict(version='2.89.3', date='2026-10-06', level='patch', title='P1:窗数口径与单窗拒判升格为明文条款(代码 + 文档 + 随 fus.纪律/盲区 传导到页面)', note='用户令:执行 P1。**落地四处**:① `app_ontology/app_ontology_guanlan/sop/vib_confidence.py` 模块文档 —— 修正 P0 后**已过时**的两处口径行(A 轴「持续≥4/6窗」、D 轴「覆盖窗数≥4」⇒ 均改为 `max(4, ceil(2N/3))`),并在模块头新增「窗数口径与单窗拒判」小节;② `app_algorithmModel/app_algorithmModel_guanlan/subsys/fusion.py` —— 新增模块常量 `WINDOW_CLAUSE`,并在 `fusion_table()` 的**消费处**把条款拼进 `纪律` 与 `盲区`(该处原为 `h["meta"]["reading_discipline"]` 与 `h["detectability_prior"]["known_blind_spots"]`)⇒ 经 `service/fleet_views.py:581` 进入页面 `fus.纪律`/`fus.盲区`;③ `docs/振动六层链_接口规格与缺口_v0.1.md` 新增「窗数口径与拒判条件」节(门的形式、单窗拒判、不得静默丢窗、当前实况 1 窗、四处出处 file:line);④ `docs/主张审计清单_v0.1.md` 增记 P1 执行结果。**条款要点**:① 持续性/覆盖门分母 = `max(4, ceil(2N/3))`(N≤6 恒 4 · N=9 ⇒ 6 · 分母只增不减 ⇒ 任何窗数都不更宽松);② **N<3 时不得**用「持续性」「逐窗趋势」类判据出个体结论,只能标「窗内周段趋势(单窗补充口径,仅参考不作判据)」(与 `app_ETL/.../component_history_build.py` 既有降级口径同源);③ **不得静默丢窗**(谱窗与仅标量/原厂状态窗分列,缺窗显式进盲区)。**验证**:本地实跑 `fusion_table()` ⇒ 107 行 · `纪律` 含条款 ✓ · `盲区` 含条款 ✓(5 项);远端部署后重启算法服务,线上 `/api/fleet` ⇒ 200 · `纪律含条款 true` · `盲区含条款 true` · `盲区项数 5` ✓(条款已上页面)。**过程实逮**:首版补丁因 **CRLF 行尾**导致多行匹配失败(0 处改动)⇒ 改为逐行替换后成功。'), dict(version='2.89.2', date='2026-10-06', level='patch', title='P0:持续性门/覆盖门改为 max(4, ceil(2N/3))(N≤3 更严 · N=4~6 逐位一致 · N>6 自动升级)', note='用户令:执行 P0(backlog 第 1 项建议)。实现位置 `app_ontology/app_ontology_guanlan/sop/vib_confidence.py`:① `score_internal` 持续性分母由硬编码 `max(min(4, n_windows), 1)` 改为 **`max(4, ceil(2*n_windows/3))`**;② `score_sample` **新增 `n_windows=6` 形参**(向后兼容),窗覆盖分母由 `4.0` 改为同一公式。已补 `import math`。**验证(函数级对照,旧实现逐字复刻 · 含同样的 `round(raw,1)`)**:`N=4/5/6` ⇒ 两个分量**逐位一致(0 处差异)** ✓;`N=7/8` ⇒ 分母 5/6(升级);**`N=9` ⇒ 分母 6**(旧恒为 4)✓;`N=1/2/3` ⇒ 分母 4 ⇒ **更严**(旧口径下 `min(4,nw)` 意味着"1 窗即满分持续性",新口径要求 4 窗)✓。**关键安全性质**:分母只增不减 ⇒ **任何情形都不会比旧口径更宽松** ✓。**过程实逮(重要,如实记录)**:最初的实现用纯 `ceil(2N/3)` ✗,实测在 **N<6 时反而放宽** —— 例如 `N=1` 时窗覆盖满分只需 1 窗(旧需 4 窗),最大差 **7.5 分** ✗,与上游报告"覆盖与缺测传播·部分覆盖被埋进健康"的告诫**相反** ✗;故改为带 **4 下限** 的 `max(4, ceil(2N/3))`,既消除该放宽、又保留 N>6 的自动升级。**实际影响**:当前实况为 **1 窗**且这两个评分函数**全仓无调用点**(函数存在 ≠ 生产链使用),故线上产物**零变化** ✓;本次改动为"扩窗到 7~9 窗时的口径预置"。'), dict(version='2.89.1', date='2026-10-06', level='patch', title='backlog 第 1 项定位:观澜持续性门/覆盖门与窗数口径', note='用户令:执行 backlog 第 1 项 —— 定位观澜的持续性门/覆盖门实现位置与窗数口径(对应上游报告 C05「四窗 ≠ 九窗同等证据」)。**实测定位(观澜自身证据)**:① **持续性门(A 轴)** 在 `app_ontology/app_ontology_guanlan/sop/vib_confidence.py:146`,口径为 `p = 3.0 * min(persist_windows / max(min(4, n_windows), 1), 1.0)` ⇒ **分母固定封顶 4**,函数默认 `n_windows=6`;② **窗覆盖门(D 轴)** 同文件 `:173` `w = 10.0 * min(n_windows_covered / 4.0, 1.0)` ⇒ 同样 4 封顶,且 `score_sample()` 签名无 `n_windows`;③ **盲区门** `:170` `if is_blind: return 0.0`;④ **逐日持续(冲击轴)** `windcms/report_std.py:187/213`(类型枚举 + `n_high_days_all >= 3`);⑤ **比例门** `sop/farm_pipeline.py:57` `sync_fraction >= 0.5`、`service/fleet_views.py:458` `_share >= 0.5`。**关键判定**:`ceil(2·6/3) = 4` ⇒ **仅当 N=6 时观澜的"固定 4"与上游 `ceil(2N/3)` 恰好等价** ✓,但 **N=9 时上游要求 6 而观澜仍取 4** ⇒ **扩窗即静默放宽**(潜在隐患)。**实际窗数(实测)**:`baseline_38_build.py:41`「本机实况只有 w0316 一窗」、`component_history_build.py:34`「只解析出一个窗」、融合产物 `fus.盲区` 自述「单窗数据时逐窗趋势信息量有限」⇒ 当前 **1 窗** ⇒ 报告所指"四窗当九窗"在观澜**尚未发生**。**另一重要发现**:`score_internal` / `score_sample` 在观澜全仓**无任何调用点** ⇒ 属「**函数存在 ≠ 生产链使用**」(本清单按 `[码]` 记存在、不按 `[场]` 记已用)。**建议**:P0 低风险改法 —— 两处 `4` 改为 `max(1, ceil(2*n_windows/3))`(N=6 时行为不变 ⇒ 零回归;N=9 时自动为 6),`score_sample()` 需新增 `n_windows` 形参;P1 把窗数口径与"单窗不得用持续/趋势判据"升格为明文拒判条件;P2 确认两函数是否接入生产链或标注"待接线"。'), dict(version='2.89.0', date='2026-10-06', level='minor', title='主张审计清单 v0.1(吸收上游算法模型分类报告的边界写法)+修 B02 陈旧文案', note='用户令:就当前源代码版本打包(已在 2.88.1 上启动),然后按 A 建议执行。**① 主张审计清单**:新增 `docs/主张审计清单_v0.1.md` —— 把上游《wind_analytics 算法模型分类报告》(`wind-analytics` main `357350d4`,2026-09-30 静态快照)的 §4 九条与 §5 六条,用其「原判断→反例→修正→**适用域**」写法逐条落到观澜自身,并对观澜仓**单次遍历 23,828 个文本文件**逐条实测 + 命中处逐行看上下文。**② 实测结论(A 档成果)**:确证**真缺陷 1 处**并已修 —— `app_ontology/app_ontology_guanlan/sop/discriminators.py:1094` 原文写「builtin_avail_verify/zero_gen_redflag 待实现」,而两函数**均已实现**(builtin_avail_verify 见第 2203 行、zero_gen_redflag 见第 2234 行)⇒ 改为据实表述(两函数实现均已落地,此处仅说明不入 A_op 抽取面、留 per-farm 传入比对);**同词不同义(假阳性)2 处**:C04 的 `全盲` 在观澜指"全盲台 02/18/37"(`windcms/report_std.py:182`)、A03 的 `滞留` 在观澜指**热滞留**(`sop/discriminators.py:2410/2420`),均非报告所指问题;**观澜已优于上游 1 处**:C01 —— 观澜 `windcms/report_std.py:67` 已同时携带 `ISO_C, ISO_D = 7.1, 11.0` 双阈并留有历史漏判实逮注释;**不适用 2 处**:B21 四层异常(观澜仅有一处翻译字符串,无实现与调用)、D04 gym(观澜无该目录);**列入 backlog 7 处**(优先项:定位观澜**持续性门/覆盖门**的实现位置与窗数口径,因全仓未出现 `ceil(2N/3)` 与"九窗")。**③ 纪律**:报告中以上游 file:line 为准,**不得直接引为观澜结论**;本清单「观澜实测」列是唯一可引用来源;不并入上游分支、不照抄其状态、不用其替代观澜验收。'), dict(version='2.88.1', date='2026-10-06', level='patch', title='核实并修正「Word 里的图与页面一致」:缺 data-blk 导致导出仍用后端重绘 + 图内字体未内联', note='用户令:检查「汇报定制」导出的 Word 里的图是否与页面完全一致。**实证检查(本机 jsdom + 无头浏览器探测)实逮两个缺陷**:① **页面里根本没有 `[data-blk]` 容器** ⇒ 前端 `rasterizeCharts()` 按 `[data-blk="k"]` 找图会**一个都找不到** ⇒ `images` 为空 ⇒ 静默回落到后端 matplotlib 重绘 ⇒ **Word 里的图仍不是页面图**(上一版 2.88.0 因此**并未真正达成**该要求,已纠正);根因:上一版补 `data-blk` 的判断条件 `"data-blk" not in 文件` 被**我插入的辅助代码字符串**命中,模板补丁根本没执行。② **图内文字字体不一致**:页面 CSS 有 `svg.dt text { font-family: var(--sans) }`,而 SVG 单独成图(经 ``→canvas)**不继承页面 CSS** ⇒ Word 里的图内文字会退回默认衬线字体,与页面**肉眼可见不同**。**修**:模板给章节块加 `:data-blk="k"`(复核:`BLOCKS_WITH_KEY 9`、`CHART_KEYS [water,blame,ram,pareto]`、`SVG_TOTAL 4 · SVG_IN_BLOCKS 4 · SVG_OUTSIDE 0` ⇒ 导出时 4 张图**一张不漏**);栅格化前把页面字体**内联进 SVG**(根元素 + 所有 `text/tspan` 设 `font-family`)。**已验证**:后端**逐字节**嵌入前端图(线上 18050 直连与 28084 网关两条路 `n_media 2 · all_match true`);栅格化所用的 SVG 原文即页面上的那一份(同一 DOM 取出的 `outerHTML`)。**未能自动验证(如实)**:本机无头 Edge/Chrome 均无法启动(`--headless=new` / `--headless` 皆失败)⇒ **像素级一致性无法在本机自动化**,需在浏览器点一次导出并肉眼比对;链路已按标准 API 实现且保留 GET 回落。'), dict(version='2.88.0', date='2026-10-06', level='minor', title='Word 里的图与页面完全一致:前端把页面 SVG 栅格化回传,后端原样嵌入', note='用户令:Word 里的图必须与页面**完全一样**。做法:前端把纸面各章节的图(SVG)栅格化为 PNG,随导出请求回传,后端**原样嵌入**(缺失的块才回落到 matplotlib 重绘)。**前端**(`views/Report.vue`):新增 `rasterizeCharts()`(`XMLSerializer` 序列化 SVG → `Image` → `canvas.drawImage`(2 倍像素、白底)→ `toDataURL("image/png")`);`exportAs("docx")` 改为先栅格化再 `POST /api/rpt_export`(JSON 体含 `images`),**失败自动回落**到原 GET 路径;章节块加 `data-blk` 以便定位各块的图。**Java**(`RptExportController`):新增 `@PostMapping("/api/rpt_export")`,把整个 JSON 体(含 images)转发到算法的 `/api/rpt_export_raw`,MIME 与文件名规则与 GET 一致。**算法**(`service/app.py` + `report_views.py` + `report_export.py`):新增 `POST /api/rpt_export_raw`;`export_bytes(..., images=None)` 透传;`to_docx(..., images=None)` 优先用 `_decode_png(images[k])`(支持 dataURL 或裸 base64)。**验证(逐字节)**:本地以真实 PNG 传 `{water, blame}` ⇒ docx 里 `word/media/image1/image2` 的 sha256 **与传入完全一致** ✓;线上经 18050 直连与 28084 网关两条路 ⇒ `n_media 2 · all_match true` ✓。**过程实逮(共 4 个自身缺陷,均已修)**:① `app.py` 插入路由时顶格 ⇒ `IndentationError`,算法服务起不来(连带 `/api/fleet` 500);② Java 侧手写 POST 方法编译不过(`mvn` 报错)⇒ 重写为干净实现;③ Java 控制器缺 `PostMapping/RequestBody` 导入;④ 最隐蔽的一个:`report_export.py` 里 **`import base64` 从未插入成功** ⇒ `NameError` 被函数内 `except Exception: return None` **静默吞掉** ⇒ 永远回落到 matplotlib(表现为"传了图却没用上")⇒ 补上 import 后立即生效。**遗留说明**:前端的 SVG→PNG 栅格化需真实浏览器(本机 jsdom 无 canvas,无法自动化验证);已按标准 API 实现并保留 GET 回落,请在浏览器点「导出 docx」确认图中文字/线条与页面一致。'), dict(version='2.87.0', date='2026-10-06', level='minor', title='导出 Word 带图形:后端按章节生成 matplotlib 图并嵌入 docx', note='用户令:修「汇报定制 → 导出 word」生成的文档里**没有图形**的问题。**先查清性质**:v2.9.2 的导出实现(`src/windscada/report_export.py`,179 行)只导入 `Document/Pt/WD_ALIGN_PARAGRAPH`,**从来没有任何图片代码** ⇒ 这不是回归,而是**新功能**。而依赖里已有 `matplotlib 3.11 · python-docx 1.2 · openpyxl` ⇒ 后端具备能力。**实现**(`app_backEnd/app_backEnd_guanlan/report_export.py`):① `blocks_data()` 的每条中间表示带上所属块键 `k`(用可变持有者,最小改动);② 新增 `_figure_png(k, fl)`:用 matplotlib(Agg) 按块键画同类图 —— 二、发电完成(瀑布)· 损失归因/停机时长归因(横向条形)· 可靠性(按部件停机时长)· 报警帕累托(柱+累计线)· 故障四象限(散点)· 故障率(月度折线),中文字体回退 Microsoft YaHei/SimHei;③ `to_docx()` 在该块标题之后、正文之前 `add_picture(..., width=6.4in)`(与页面版式一致)。**本地验证**:以真实 `/api/fleet` 夹具调 `to_docx(blocks=kpi,water,emon,blame,ram,pareto,quad,frate)` ⇒ docx 241,665 B、`word/media/image1..6.png` **6 张**、`` **6 处** ✓。**线上验证**:部署后先重启组件服务(首次 `stop/start` 后接口仍返回 39,943 B 纯文本 ⇒ **实逮**:算法进程被"复用"未真正重启、旧模块仍在内存 ⇒ **结束 18050 进程再拉起**)⇒ 复验 `/api/rpt_export?fmt=docx&…` ⇒ **200 · 241,710 B · n_media 6 · drawings 6** ✓。**说明**:Word 里的图是由同一份数据在 **Python 侧重绘**的同类图(语义一致、数据同源),与页面上的 SVG 图形**不保证像素一致**;若要求与页面**完全一致**,需改为"前端把 SVG 转 PNG 回传后端嵌入",属额外工作量(可选)。'), dict(version='2.86.0', date='2026-10-06', level='minor', title='汇报定制:章节正文渲染器从 v2.9.2 原文移植(此前为空实现)', note='用户反馈:「汇报定制」页面与原页面不一致。**实逮根因**:Vue 版里两块是**空实现** —— `factsHtml = computed(() => "")` 与 `blockHtml = () => ""` ⇒ 选中任何章节,纸面上**什么都不渲染** (而一致性判据只比默认视图「预览为空」⇒ 两边一致 ⇒ 判据 15/15 也没发现)。**修**:把 v2.9.2 的 `rptFacts` / `rptK` / `rptBlock`(app.js 第 531–571 行)**原文移植**为 `src/lib/reportBlocks.ts`(工厂形式接收注入的 t/fmt/esc/dv/displayText/chart/SYS_KEY/sysName/stateChip/unitNo/STEP/winName/语言包/RPT/FACTS),仅做机械适配:`CH.x(` → `chart("x", `、`D.` → `d.`、`BD` 明确为 `d.fus.链盘`(旧页 app.js:541 的原始定义)。同时在 Report.vue 接线 `blockHtml/factsHtml`,并让 App 为 report 页签补取 `/api/facts`(事实卡契约来源)。**过程实逮(自身缺陷,均已修)**:① 抽取旧码时少取一行 ⇒ 括号不闭合(构建报 Unexpected end of file);② 重复导入 winName(Identifier already declared);③ 语言包导出名是 `ZH` 而非 `zh`。**验证(真应用 jsdom · 点「月报」预设)**:`BEFORE_ELS 1` ⇒ `AFTER_ELS 575 · BLOCKS 10 · CHART_HOSTS 4 · TEXT_LEN 4180`,正文首段为「观澜 · 运行分析报告 / 统计期 2026年 | 38 台 / 9 个章节 / 250,744MWh 上网电量 / 理论可发 287,713 / 1,650h 等效满发小时 / 94.8% 时间可用率 / 12.8% 损失率 / 36,968 MWh / 172h 平均停机间隔 …」,`ERRS 0` ✓。'), dict(version='2.85.2', date='2026-10-05', level='patch', title='修「执行重算」第 ⑦ 步失败:「kb_ingest.py」 把 「_install_root」 误写成 「_p._install_root」', note='用户令(选 A):等"数据重算"跑完后复跑判据。**过程中实逮一个后端代码 bug**:整轮 「rebuild」 耗时 2,701 s、21 步里**只有第 ⑦ 步「本体: 码表/手册/文档」失败** ⇒ 「app_ontology/app_ontology_guanlan/ontology/kb_ingest.py:74」 调用了 **「_p._install_root(__file__)」**,而 「_install_root」 是本模块第 20 行 「from app_common.app_common_guanlan.api import install_root as _install_root」 (或第 23 行的兜底定义)导入的**模块级函数**;「_p」 在此处解析成了别的东西(报错:「AttributeError: module \'pathlib\' has no attribute \'_install_root\'」)⇒ 该步失败并**中断整轮重算**(后面步骤依赖它 ⇒ 「/api/fleet」 持续 500 · 页面呈"无数据")。**修**:全仓检索同写法,仅此 1 处 ⇒ 改为 **「_install_root(__file__)」**;并在远端**单独跑该步验证通过**:「python -m src.ontology.kb_ingest」 ⇒ 产出 「{AlarmCode: 555, WorkInstruction: 559, FailureMode: 150, MaintTask: 559, Doc: 303, Component: 9, causedBy: 60, …}」 ✓。随后重新触发整轮 「rebuild」(各步幂等)以补齐后续步骤,待 「/api/fleet」 恢复 200 后再复跑结构/内容两套判据(15/15 目标)。'), dict(version='2.85.1', date='2026-10-05', level='patch', title='措辞政策定案:以实时接口(当前算法)用词为准;3 条差异改判为「基线旧快照用词」', note='承接上一轮"建议第 2 步":统一 「链判级」 / 「窗末月?」 / 「平均停机间隔(MTBO)」 三处措辞差异。**先查清来源(实测)**:这**不是**同一系统两条路径互相打架,而是「**v2.9.2 基线页面自带的旧快照用词**」与「**当前算法新词**」之差 —— 实时 「/api/fleet」 里新词**内部一致**(「MTBO口径」 1 处 · 「变化口径」 1 处 · 「纯故障口径」 1 处;旧词 0 处),而基线 「detail_v2.html」 里旧词 7 处、新词 0 处。**用户裁定(2026-10-05):选 A —— 以当前算法新词为准**(「口径」 等),旧快照用词视为已过时。**落地**:判据工具 「tools/render_parity_v292.mjs」 中三条例外的 「why」 由"待后端统一口径"改判为「**基线旧快照用词 vs 当前算法新词 · 以实时接口为准 · 预期差异(非缺陷)**」,并在版本记录留档。**现状说明(重要)**:本轮复跑对拍时发现 「/api/fleet」 返回 **500(110 B)** —— 因为**产物已被用户"清除产物"清除**,故基线页与 Vue 侧都取不到数据、对拍必然 [X](**预期现象,不是回归** ✓)。待用户执行「执行重算」后,需复跑结构/内容两套判据确认仍为 15/15(本轮之前刚跑过:结构 15/15 · 内容 15/15 ✓)。'), dict(version='2.85.0', date='2026-10-05', level='minor', title='清产物后给友好空态(不暴露 HTTP 500)', note='用户令:清除产物后相应页面不显示数据,**提示要友好(不要报 HTTP 500)**。**修**:「App.load()」 对 4xx/5xx/异常**一律视为"暂无数据"** ⇒ 「d = {}」(空快照 ⇒ 各页照常渲染空态、不崩)+ 「dataMissing = true」,页面顶部给友好可操作提示(i18n 「app.data_missing」:「暂无数据:产物尚未生成或已被清除。请到「系统维护」页执行「数据重算」(或在 /ops 控制台点「执行重算」),完成后本页会自动刷新。」),**不出现任何 HTTP 状态码**。**过程实逮(两处自身缺陷,均已修)**:① 脚本因锚点不匹配漏插 「const dataMissing」 ⇒ 打包产物报 「ReferenceError: dataMissing is not defined」;② 「System.vue」 的 vue import 漏补 「onMounted」 ⇒ 「ReferenceError: onMounted is not defined」(system 页签渲染失败)。**验证(模拟 「/api/fleet」 返回 500)**:七个页签(overview/energy/component/vibration/generation/fault/system)逐一 ⇒ 「errs=0」 · 「friendly=true」 · 「leaks500=false」 ✓。'), dict(version='2.84.1', date='2026-10-05', level='patch', title='清产物/重算后工作台自动重取数(并加「刷新数据」);实测空数据各页显示空态', note='用户要求:**「清除产物」完成后,工作台相应功能(总览/电量算账/部件问题/振动融合分析/发电性能/故障统计)不应再有数据展示**。**实逮的问题**:这些页的数据来自 「/api/fleet」(按窗实时 ✓),但清除产物是在 「/ops」 控制台里点的 ⇒ **Vue 应用不会自动重新取数** ⇒ 页面仍显示清除前的数据。**修**:① 「System.vue」 在 「/ops」 iframe 「load」 后(同源 ✓ 可直接访问其文档)监听 「#b_off」(清除产物)/「#b_rebuild」(执行重算)/「#b_start」/「#b_stop」/「#b_restart」 的点击,动作后 **4 秒**触发 「refresh」;② 重算块上方加**「刷新数据」**按钮(手动兜底);③ 「Workbench」 透传、「App」 新增 「reload()」 —— **清空页签级缓存 「apis」/「curves」 后重新 「load()」 与 「ensureTabApis(当前页签)」**。**验证(用空数据模拟"清完产物",不做破坏性操作)**:把 「/api/fleet」 返回置为空对象后,六个页签逐一渲染 ⇒ 「overview elements=113 · energy elements=5(正文为「电量算账 无数据」)· component 21 · vibration 82 · generation 9 · fault 43」,**全部 「errs=0」(不崩)** ✓ —— 即清除后重取数时,各页会如实呈现"无数据/占位符"状态 ✓。'), dict(version='2.84.0', date='2026-10-05', level='minor', title='修「清除产物 404」:网关加 Path=/ops/api/** 专用路由(并实逮网关一直未真正重启)', note='用户反馈「系统维护」内嵌的 /ops 控制台里点「清除产物」报 **404 操作被拒绝**。**排查链**:该按钮调 **「/ops/api/<动作>」**(该页 API 前缀 「/ops/api/」);**直连 Python 网关 28086** ⇒ 「/ops/api/state」 **200 · 1,155 B**(正常);**经 Java 网关 28084** ⇒ **404**,响应体是 **Spring 的 「{"timestamp":…,"status":404}」** ⇒ 说明**没有路由匹配**、请求没到 28086。**实逮根因(两个叠加)**:① 路由表里 「ops → 28086」 的谓词是 「Path=/ops/**」 + 「Path=/ops」,但运行中的进程用的仍是**旧配置** ⇒ **网关根本没有真正重启**(旧 pid 50588 一直存活;此前几轮 「sc stop/start」 的回显都是空的、实际未生效);② 为稳妥起见还在路由表**最前**加了一条专用路由 **「ops-api」 「Path=/ops/api/**」 → 28086**。**修法**:改 classpath 「application.yml」 ⇒ **先 「taskkill」 旧 pid、再 「sc stop/start guanlan」、轮询确认端口换新 pid** ⇒ 实测 **旧 pid 50588 → 新 pid 53904** ✓,随后 「/ops/api/state」 经 28084 ⇒ **200 · 1,155 B** ✓(「/ops」 仍 200 ✓)。**判据**:「/ops/api/state」 经网关必须 200(直连本就是 200)✓ —— 达标。**教训(已记入)**:改配置后必须**确认进程换新 pid** 才算重启成功,回显为空不能当作已重启。'), dict(version='2.83.3', date='2026-10-05', level='patch', title='修「系统维护」数据重算按钮消失:内嵌改为直接 iframe src=/ops', note='用户反馈「系统维护」里的「清除产物」「数据重算」按钮又没了。**根因**:这两个按钮位于**内嵌的 「/ops」 运维控制台**内;该块此前用的是 Blob-URL iframe(与「振动 CMS」同一批尝试),在你的浏览器上不显示。而 「/ops」 的 「X-Frame-Options」 早在 **2.81.4** 已放开(网关 「frameOptions().disable()」,实测响应头无 DENY ✓)。**修**:把该块改回**最朴素、与 v2.9.2 门户同款**的写法 —— 直接 「