# -*- 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.81.1' # ★ 版本只改这里 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.81.1', date='2026-10-04', level='patch', title='「振动 CMS」页数值全部按窗/CMS 实时(并修 portal_cms 未触发取数)', note='用户令:「振动 CMS」页要**按窗实时值**。① 窗口侧实时:当前时间窗(`d.win`)、需关注/未见异常(`d.fus.kpi` 候选观察/全场,随 `/api/fleet?win=` 变)、**窗口内报警工单条数**(`d.fus.事件.主轴.工单.总条数`);② CMS 侧实时(`/api/vibcms`):采集窗 + 时间范围(`窗`/`时间范围`)、裁决分布(`grades` 统计优秀/良好/报警)、行动项条数(`action`)、数据时点(`date`);③ 移除门户里**写死的**「TCM 六采集窗 · 320 万记录 · 160 万谱」——该记录/谱计数**接口并不提供**,故改为用接口可得的实时项替代(不编造)。④ **实逮并修**:按需取数只对 `cms` 触发,`portal_cms` 未触发 ⇒ CMS 侧 chip 全为 0;已改为两者都触发。双窗验证:窗口`2026年` ⇒「当前时间窗 · 2026年」「需关注 35 台」「窗口内报警工单 · 11 条」「CMS 采集窗 w0316 · 2026-03-16 → 2026-04-21」「CMS 裁决 · 优秀 32 / 良好 3 / 报警 3」「行动项 … 条 · 数据时点 2026-09-29」;切到`全程` ⇒ 时间窗与需关注/未见异常随之变化 ✓。'), dict(version='2.81.0', date='2026-10-04', level='minor', title='门户「振动·CMS」迁入「振动融合分析」为并列子选项卡「振动 CMS」(受时间窗影响)', note='用户令:把门户的「振动·CMS」迁到「振动融合分析」里,成为与"融合矩阵/端到端闭环/逐台情况/报警工单/振动分析"**并列的第 6 个子选项卡「振动 CMS」**,且**受时间窗影响**。实现:① 从 v2.9.2 门户 shell 抽出 `#cms` 节(1.6 KB:hero + 状态 chip + 入口)为 `src/lib/portalCms.ts`;② `lib/vib.ts` 的 `FUS_SUBS` 增 `portal_cms`(6 项),新增 `portalCmsHtml(d)`:把门户里的**静态数字**替换为**按当前时间窗实时算**的值 —— 「需关注 N 台」「未见异常 M 台」取自 `d.fus.kpi`(候选观察/全场,二者随 `/api/fleet?win=` 变化)、「证据窗末」取 `d.fus.证据窗末`,并**追加一个「当前时间窗 · 」chip**(直接体现窗口);③ i18n 增 `fus.sub.portal_cms`(zh:振动 CMS / en:Vibration CMS);④ `Vibration.vue` 接 `portal_cms` 分支。**实测(真应用 jsdom 点击 + 两个窗口对比)**:子选项卡为 ["融合矩阵","端到端闭环","逐台情况","报警工单","振动分析","振动 CMS"](6 个);点「振动 CMS」出门户内容(HAS_HERO true:振动与 SCADA 联合分析 / 六层模型…);**随窗变化**:窗口`2026年` ⇒ chips ["当前时间窗 · 2026年","需关注 35 台","未见异常 3 台",…];窗口`全程` ⇒ ["当前时间窗 · 全程","需关注 9 台","未见异常 29 台",…] ✓。远端已部署 36 件。'), dict(version='2.80.2', date='2026-10-04', level='patch', title='修「振动融合分析」点击子选项卡页面不变(弃用 v-unwrap)', note='用户反馈:「振动融合分析」里点「端到端闭环 / 逐台情况 / 报警工单」页面没有变化。**根因(实逮)**:这些子视图的 HTML 挂在 `v-html` + **`v-unwrap`** 上,而 `v-unwrap` 只在 `mounted` **解包一次**(把宿主替换为子节点后把宿主移出文档)⇒ 之后 Vue 每次 `v-html` 更新都写进**已脱离文档的宿主** ⇒ 页面上永远是最初那一屏(表现为"点了没反应")。中途我曾把指令改成"注释锚点 + updated 同步",但在**数据异步到达 / 重复渲染**下锚点与节点归属会错位(实测元素数出现堆积与错位:matrix 10、units 4150 等),机制本身过于脆弱。**最终处置**:**弃用 `v-unwrap`**(`Assistant.vue` / `Decision.vue` / `Vibration.vue` 三处),改为保留宿主 `div.unwrap-host` —— 先核实**没有任何 CSS 依赖 `#root` 的直接子结构**(扫描 0 条命中),故该层 div 无样式副作用;对拍工具本就把它按"透明"处理,**10/10 页签结构一致性不受影响**。**修复后真应用点击实测**(点 5 个子选项卡各一次,数 `#root` 元素数):matrix **1126**(基线 1126)· loop **13**(基线 13)· units 4151(基线 3398)· events **626**(基线 626)· cms 558(基线 543)—— 前三+events 与基线**逐字相同**,units/cms 因"真应用按真实数据/真实交互路径渲染"与SSR 对拍口径不同而略有出入(非堆积,多次点击不再增长)。'), dict(version='2.80.1', date='2026-10-04', level='patch', title='振动融合分析(含 5 个子页面)与 v2.9.2 差异核定;修 cms 子页面未取 /api/vibcms', note='用户令:核定「振动融合分析」中所有页面(含子页面)与 v2.9.2 的差异并修改。① **判据补强**:`tools/struct_diff.mjs` 增加**子视图支持**(`tab:sub`:基线点 `#root [data-fsub=]` 停稳后比骨架,Vue 侧把 `sub` 传给 `renderTab`),于是 5 个子页面都进了结构对拍。② **结构**:vibration `matrix/loop/units/events/cms` = **5/5 一致**(1126 / 13 / 3398 / 626 / 543 个元素)。③ **内容**:同 5 项文本/表格/图数字 = **5/5 一致**(561 / 9 / 1126 / 492 / 158 项文本;表 1/0/2/9/2 张;含 1 条已登记例外:后端"页面按窗缓存 vs 实时 /api/fleet"的措辞差异,非前端)。④ **实逮到一处真实差异(工具此前掩盖)**:旧页在选中 cms 子视图时会**按需异步取 `/api/vibcms`**(`app.js:980`),而 Vue 应用**从不取该接口**(只在契约里声明路径、靠父组件传 `cms` 而无人传)⇒ **真实打开「振动分析」时无数据**;我的对拍脚本会替它喂数据,所以此前判据是"绿的"却与实际不符。已修:`Vibration.vue` 选中 cms 时按需 `fetch(\'/api/vibcms\')`(prop 优先,保持对拍同源口径 + `cmsErr` 兜底)。**真行为验证**(真应用 + 远端真实 `/api/vibcms` 6,297 B 夹具):点击「振动分析」⇒ 实际发出 `["/api/fleet","/api/vibcms"]`、`VIB_CMS_CALLED true`、渲染出表格 **39 行** ✓。'), dict(version='2.80.0', date='2026-10-04', level='minor', title='页签精简(去「架构与资料/门户」、数据重算并入系统维护)+ 删除页头模式按钮(修布局乱)', note='用户两项:① **页签精简** —— 工作台页签由 14 减为 **11**:去掉「架构与资料」(其首个子选项卡即"系统架构")与「门户」;「数据重算」不再单列页签,改为并入 **系统维护** 页签内的一个区块(`System.vue` 内渲染 ``)。现页签为:总览 · 电量算账 · 部件问题 · 振动融合分析 · 发电性能 · 故障统计 · 检修决策 · 检修助手 · 汇报定制 · 系统维护 · 交付文档。② **修布局乱** —— **实逮**根因:页头里还残留一组"模式按钮"(工作台 / 契约自检 / 关于本前端),它们用的容器也是 `