|
|
@@ -0,0 +1,521 @@
|
|
|
+# -*- 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.11.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.11.1', date='2026-09-22', level='patch',
|
|
|
+ title='模块化重构 P1:公共层九个平台件实体迁移(旧路径留兼容转发壳,零行为变化)',
|
|
|
+ note='重构推进(源码组织层)⇒ 小版本 +1。落地: '
|
|
|
+ '① 九个平台件(paths/version/logfile/proc/entry_refs/console/opsjob/derived_manifest/tabfmt)'
|
|
|
+ '实体迁入 app_common/common/app_common_guanlan/; '
|
|
|
+ '② 安装根推算由 __file__.parents[1] 改为 _root.install_root()(按 configs/ 与 guanlan.py 标记'
|
|
|
+ '向上查找, 位置无关; 移深三层后仍算得对); '
|
|
|
+ '③ 旧路径 src/<件>.py 变**兼容转发壳**(sys.modules 别名, 含私有名)⇒ '
|
|
|
+ 'from src import paths as P / from src.proc import NO_WINDOW / importlib.import_module(src.version) '
|
|
|
+ '三种写法全部照旧, 全仓约 120 处引用无需一次性改完; '
|
|
|
+ '④ config_audit 的「配置取用口豁免」由按旧路径判改为**按取用口所在文件**判(位置无关), '
|
|
|
+ '以后 paths.py 再搬也不会误报 R6。'
|
|
|
+ '验证: 旧路径导入与安装根实测正确(P.ROOT/P.RAW_ROOT/P.store/P.config 全对, 模块身份同一); '
|
|
|
+ 'raw_scan --check rc=0、rebuild_all --dry-run rc=0(23 步计划不变)、三份交付文档重渲通过; '
|
|
|
+ 'guanlan.py check、config_audit、pages_audit、chain_gap、version_log、detail_deps、'
|
|
|
+ 'check_portability、module_boundary_audit 全 rc=0。'),
|
|
|
+ dict(version='2.11.0', date='2026-09-22', level='minor',
|
|
|
+ title='源码模块化重构 P0:七个模块目录 + 接口层 + 模块边界门(每模块只经 api 调用,可插拔)',
|
|
|
+ note='源码组织层重构 ⇒ 中版本 +1(运行形态、交付形态与核心功能未变)。'
|
|
|
+ '用户令: 「对观澜的源代码,按算法、数据接入管理、前端、后端 等系统模块进行重构,每个模块一个目录;'
|
|
|
+ '遵循组件化、高内聚低耦合、可复用、可扩展(支持热插拔)、兼容性、性能优化、高可用、分布式、'
|
|
|
+ '面向对象、代码精简;系统组件: TiDB community、MinIO、Redis、Nginx」。'
|
|
|
+ '本轮按用户选择执行:**只重构目录与接口**(暂不引入四个组件,只留接入点)、'
|
|
|
+ '**零行为变化、逐版本可回滚**。落地: '
|
|
|
+ '① 七个模块目录(每模块一个目录): app_common(公共层)/ app_ETL(数据接入管理)/ '
|
|
|
+ 'app_algorithmModel(算法)/ app_ontology(本体与知识层)/ app_backEnd(后端)/ app_frontEnd(前端)/ '
|
|
|
+ 'app_qualityGate(质量门与审计 + 打包安装);每个模块下 `common/app_<模块>_guanlan/` 放观澜实现,'
|
|
|
+ '`api.py` 是**唯一对外公开面**(PEP 562 惰性转发到既有实现,因此行为与重构前一致)。'
|
|
|
+ '② `configs/modules.yaml` 模块登记表(职责/允许依赖/实现落点/迁移阶段/组件接入点)。'
|
|
|
+ '③ 新增 `scripts/module_boundary_audit.py` 并接入 `guanlan.py check`: 查结构齐、模块间只经 api、'
|
|
|
+ '依赖方向合规(不得反向成环)、公共层不依赖业务模块、api 转发目标真实存在;并统计迁移进度。'
|
|
|
+ '④ 四个系统组件的接入点先立**接口契约**(无实现,默认仍走本地/进程内): '
|
|
|
+ 'TiDB community ↔ app_ETL 的标准仓读写接口、MinIO ↔ 源件与产物对象存储接口、'
|
|
|
+ 'Redis ↔ app_backEnd 的按时间窗缓存与作业状态接口、Nginx ↔ 统一入口与路由表接口。'
|
|
|
+ '⑤ 设计说明新增「源码模块化布局与边界」一节(模块表 + 目录树 + 边界规则 + 分阶段迁移 P1–P7),'
|
|
|
+ '方案全文见 `docs/重构方案_模块化_v0.1.md`。'
|
|
|
+ '⑥ 三份交付文档随版本号改名重渲(内容口径不变)。'
|
|
|
+ '验证: 边界审计 rc=0;旧路径(src/**、scripts/**)与页面/CLI/产物零变化;'
|
|
|
+ '`guanlan.py check`、配置统一、页面归口、反向呼应、可移植性、可转移、文档三检 全绿。'),
|
|
|
+ dict(version='2.10.4', date='2026-09-22', level='patch',
|
|
|
+ title='数据要求说明补测点:安全链与数字输入、执行器与热管理、计数账、CMS 采集参数、位号字典',
|
|
|
+ note='纯交付物修订 ⇒ 小版本 +1。用户令: 「测点遗漏体检的方案补进数据要求说明」。'
|
|
|
+ '体检口径(六组真源): 现场件表头 595 个 10 分钟通道(9 前缀)· 标准仓契约 164 登记项(10 中文域)·'
|
|
|
+ '窄仓 48 列 · 1 分钟转发层 76 列 · CMS 振动 71 项 · 状态与告警与工单与整定值 44 字段;'
|
|
|
+ '逐列判定(参考/覆盖/缺失/待判): 595/325/151/119、164/117/31/16、48/46/0/2、76/61/11/4、71/50/19/2、44/33/6/5。'
|
|
|
+ '三块结构性遗漏: ① din_ 数字输入 93 列几乎整片未登记(烟雾/急停/外部停机/UPS/油位/滤芯/接地刀闸/能见度);'
|
|
|
+ '② dot_ 执行器 69 列整片为空(泵/阀/加热器/冷却器/通风),而文档把「热链与冷却」列为支撑功能;'
|
|
|
+ '③ cnt_ 计数账 69 列只登记 6 条(外部错误小时= IEC 责任剥离并列账、OK 与可用小时、各类停机小时、寿命小时)。'
|
|
|
+ '补齐内容: 10 分钟节新增「数字输入与安全链」「执行器命令与热管理」两域,扩充计数与统计及 tur/flg/int/prs/grd;'
|
|
|
+ '秒级节补 11 条;CMS 节新增「采集参数与溯源要求」小节(键相/转速脉冲、窗函数、采样频率、量程与过载、'
|
|
|
+ '序列号与配置、触发时刻、标称频率等 —— 没有键相无法做真阶次跟踪,窗函数在振动侧两侧都缺);'
|
|
|
+ '第 5 章增「位号释义」质量要求;第 8 章收资清单增「测点与位号字典」一项'
|
|
|
+ '(119 条待判通道没有位号释义就永远只能存而不解)。'
|
|
|
+ '口径不变: 只写中文业务含义名 + 度量单位 + 必须性,不出现英文列名/文件名/落盘位置/机组编号/样本场现状;'
|
|
|
+ '单位只取自已取证口径(canonical 词典 unit_majority、机型契约 unit),查不到写「未取证」;'
|
|
|
+ '中文业务名取自 src/windscada/i18n.py 的 STEM 中译(源西门子 WTC-3 IO 表)与产物列名。'
|
|
|
+ '验证: 严格模式(英文标识/扩展名/路径/机组编号/现状字样)0 命中;去标识化 0 命中;'
|
|
|
+ '21 类逐类仍有四列测点表;表号引用闭合;guanlan.py check 全绿;三份 docx 重渲到 2.10.4。'),
|
|
|
+ dict(version='2.10.3', date='2026-09-22', level='patch',
|
|
|
+ title='交付件定名:设计说明改为「系统设计说明」(与需求分析、数据要求说明命名对齐)',
|
|
|
+ note='纯交付物修订(文档内容未变,只改交付件名称与随之而来的引用)⇒ 小版本 +1。'
|
|
|
+ '用户令: 「设计说明_观澜 word 文档,改名为:系统设计说明_观澜_[版本号].docx」。做法: '
|
|
|
+ '① 渲染器的交付件定义改名 —— `docs/设计说明_观澜_<版本>.docx` → '
|
|
|
+ '`docs/系统设计说明_观澜_<版本>.docx`(封面标题、页眉、markdown 源件名一并改为「系统设计说明」);'
|
|
|
+ '② 三份稿里指向该交付件的交叉引用与「编写依据」路径同步改名(`《设计说明_观澜_…》`→'
|
|
|
+ '`《系统设计说明_观澜_…》`、`docs/src/设计说明_观澜_….md`→`docs/src/系统设计说明_观澜_….md`),'
|
|
|
+ '以及"三份交付文档(需求分析、设计说明、数据要求说明)"这类并列清单;'
|
|
|
+ '③ **仓库内的内部文档 `docs/系统设计说明.md` 不受影响**(替换模式带 `_观澜_` 或书名号,'
|
|
|
+ '不会误伤同名内部件);④ 三份 markdown 源件随批次改名到 2.10.3,正文"当前版本"口径'
|
|
|
+ '(系统版本行、包名、本文版本、变更级别、版本表新增 2.10.3 一行、HISTORY 计数 15 条)同步更新,'
|
|
|
+ '历史行保留。文档内容与图表未变。'
|
|
|
+ '**2026-09-22 用户复核补记**: 用户对交付的《数据要求说明》做了**一处口径修改** —— 秒级 SCADA 的采样频率由「1 秒或 5 秒(二选一)」放宽为「**1 秒至 30 秒之间**」(共 7 个落点: 第 3.2 节正文、第 5 章质量要求、数据分类总表、必须性分档表、收资 21 项对照表、面向现场的收资清单、附录 B 收资原文第 2 项)。已按「相关内容以后照此描述」并入源件,并把该口径写进 docs/系统设计说明.md §17 的描述口径清单;附录 B 的标题与说明同步改为「第 2 项采样频率按现场最新口径」,不再声称逐字。**验证**: 三份 markdown 与 docx 重渲染后 `delivery_docs_build --verify` rc=0(目录/页码域、'
|
|
|
+ '去标识化、数据稿去实现细节、表号引用闭合);新文件名含当前版本号,`guanlan.py check` 的'
|
|
|
+ '「交付文档三件」门按新名核验通过;包内只带 2.10.3 三份 docx,无同名旧版残留。'),
|
|
|
+ dict(version='2.10.2', date='2026-09-22', level='patch',
|
|
|
+ title='数据要求说明改成纯数据需求规格(去现状/去实现细节)+ 新增 SCADA 秒级数据要求',
|
|
|
+ note='纯交付物修订 ⇒ 小版本 +1。用户令: 「数据要求说明不要体现样本场的接入情况、英文测点名称、'
|
|
|
+ '机组名称、文件名称、落盘位置,只体现测点真实业务含义名称;每类数据的测点以列表呈现'
|
|
|
+ '(测点/字段名、业务含义、度量单位、是否必须);增加 SCADA 秒级数据的要求」。做法: '
|
|
|
+ '① **删掉一切样本场现状**: 到位情况/缺口/催缴状态、件数体量、行数、时间覆盖、日期全部不再出现'
|
|
|
+ '(该文只写"对数据的要求",不描述任何具体场站);机组范围改写为「全场机组(按场实际台数)」。'
|
|
|
+ '② **去实现细节**: 正文与表格不再出现英文测点名(snake_case)、文件名与扩展名、目录与落盘位置、'
|
|
|
+ '机组编号;测点一律用中文业务含义名称(有功功率、齿轮箱油温、机舱风速…),单位取自'
|
|
|
+ '`configs/canonical/dictionary.yaml` 的 unit_majority 与机型—场站契约的 unit,查不到就写「未取证」。'
|
|
|
+ '③ **测点列表化**: 每类数据一节 + 一张四列表(测点/字段名 | 业务含义 | 度量单位 | 是否必须,'
|
|
|
+ '分级只在必须/建议/可选三档),另加"测点汇总索引"章。'
|
|
|
+ '④ **新增 SCADA 秒级数据要求**: 1 s 或 5 s、至少 3 个月、全场全测点,并给出必需测点清单'
|
|
|
+ '(功率、风速风向、发电机/主轴/叶轮转速、三叶桨距与一致性、发电机绕组与齿轮箱温度、机舱与环境温度、'
|
|
|
+ '偏航角度与压力、液压与蓄能压力、运行状态位、限电与功率给定、振动触发量…),写清它与 10 分钟数据、'
|
|
|
+ '故障录波、机舱振动数据的分工与配合。'
|
|
|
+ '⑤ **机器硬门**: `delivery_docs_build.py` 为该文加 `strict_terms` 检查(snake_case 标识/文件扩展名/'
|
|
|
+ '目录路径/机组编号/现状字样),`--check`(扫源件)与 `--verify`(扫渲染后的 docx)命中即 FAIL;'
|
|
|
+ '该文不再配插图(原四张是样本场现状图,按用户令不再体现),图表器移除 dat 组与对应四张 PNG。'
|
|
|
+ '验证: 新稿 markdown 与 docx 对上述各类 0 命中,21 类每类都有四列测点表,秒级节含测点表;'
|
|
|
+ '`guanlan.py check` 全绿;三份 docx 重渲(req/des 内容不变,仅版本号与文件名随批次升到 2.10.2)。'),
|
|
|
+ dict(version='2.10.1', date='2026-09-22', level='patch',
|
|
|
+ title='交付文档对外版:去样本场标识(全文不体现具体风电场)+ 新增多场适用性章节',
|
|
|
+ note='纯交付物修订(系统功能未变)⇒ 小版本 +1。用户令: 「修改三份文档,内容参考如东风电场,'
|
|
|
+ '但不体现如东风电场,且具有不同风电场适用性」。做法: '
|
|
|
+ '① **去标识化**(中文与罗马化形态一并去): 场名/业主/地域/OEM/第三方机构/系统名里的样本场代号'
|
|
|
+ '一律换成中性表述(样本风电场 / 本场 / 业主单位(从略)/ 主机制造商(OEM,名称从略)/ '
|
|
|
+ '4.0 MW 级海上机组(机型代号从略)/ 观澜 v2(风电场智能分析系统));'
|
|
|
+ '**路径写成模板**(`data/raw/<场站>/…`、`outputs/<场>/…`、`reference/<场>/…`、'
|
|
|
+ '`configs/contracts/<机型>_<场>.yaml`、`configs/farms/<场>.yaml`、`WINDSCADA_FARM=<场>`),'
|
|
|
+ '并在「编写依据」表下写明占位符替换规则;实测数字与结论全部保留,标注「样本场实测(2026-09-22)」。'
|
|
|
+ '② **多场适用性**(用户令第二句): 需求分析新增第 11 章(FR-44…FR-52 + NFR-12: 场配置化、'
|
|
|
+ '机组与机型可替换、数据源形态可适配、阈值按场标定、术语与单位可配、跨场统一口径、按场裁剪收资、'
|
|
|
+ '换场验收检查表);设计说明新增第 16 章(场抽象层与配置 schema、场无关引擎 vs 场相关参数分层表、'
|
|
|
+ '换场作业单与检查表、接新 OEM/新数据形态的扩展点、换场会失效的假设如实列);'
|
|
|
+ '数据要求说明新增第 12 章(通用必选/可选判定规则、场配置字段对照、数据源形态适配、'
|
|
|
+ '换场收资差异清单、按场裁剪步骤)。'
|
|
|
+ '③ **去标识化做成机器可查的硬门**(不靠人自觉): `scripts/delivery_docs_build.py` 增 `FORBIDDEN` '
|
|
|
+ '词表并在 `--check/--verify` 里逐份扫描(命中即报 FAIL),`guanlan.py check` 的「交付文档三件」'
|
|
|
+ '门同步带上该扫描;图表器里出现的样本场字样一并中性化(图题写「样本场实测」)。'
|
|
|
+ '验证: 三份 markdown 源件与 docx 对 12 个禁用词 0 命中;`delivery_docs_build --verify` rc=0;'
|
|
|
+ '`guanlan.py check` 全绿;三份 docx 与插图重渲,图表数字仍与正文同源(图解析文档表)。'),
|
|
|
+ dict(version='2.10.0', date='2026-09-22', level='minor',
|
|
|
+ title='交付文档三件(需求分析 / 设计说明 / 数据要求说明)与源代码化生成器',
|
|
|
+ note='非核心功能新增(交付物)⇒ 中版本 +1。用户令: 「整理观澜的需求/设计/数据接入,各写一份 Word 文档,'
|
|
|
+ '要求区分章节目录、文表图并茂、字体字号分类统一;数据要求说明结合现场《数据分析收资要求-v3.docx》」。'
|
|
|
+ '① 新增三份交付文档(落 `docs/`,文件名带本版本号): '
|
|
|
+ '`需求分析_观澜_2.10.0.docx`(需求来源与演进、角色场景、功能需求 FR、非功能需求 NFR、'
|
|
|
+ '页面与信息架构、验收门、需求跟踪矩阵)、'
|
|
|
+ '`设计说明_观澜_2.10.0.docx`(总体架构、目录与路径真源、重算链、判级与算法、时间窗口径、'
|
|
|
+ '服务与前端、本体与模型、运维编排、安装与版本、质量保证、安全与边界、可移植性)、'
|
|
|
+ '`数据要求说明_观澜_2.10.0.docx`(数据分类总表、逐类要求、核心测点/字段/单位/必须性、'
|
|
|
+ '质量与对齐、落位流程、收资要求 v3 的 21 项逐条对照、缺失与替代、核对锚点、面向现场的收资清单); '
|
|
|
+ '② 两份源件均**源代码化**(用户令「所有的计算均要形成观澜的源代码」): '
|
|
|
+ '`scripts/delivery_docs_build.py` 把 `docs/src/*.md` 渲染成 docx(Word 域目录 + 标题/正文/表格/图题'
|
|
|
+ '四级字体字号分类统一: 黑体标题 + 宋体正文小四 + 表格五号 + 图题五号居中, 页眉页脚页码, 附录排版规范); '
|
|
|
+ '`scripts/delivery_docs_figures.py` 生成 14 张插图, 每张图的数字都从真件取: `src/version.py` 的 HISTORY、'
|
|
|
+ '`configs/portal_pages.yaml`、`configs/serve.json`、`data/raw/<场>/**`、`outputs/<场>/**`、'
|
|
|
+ '并落 `guanlan.py check` 与 `rebuild_all.py --dry-run` 的实跑底稿到 `docs/src/_*.txt`(可复核); '
|
|
|
+ '③ 文档口径遵循用户令 §17: 含"窗"且指时间窗口写全"时间窗"、影响机组写"影响机组数"、'
|
|
|
+ '英文简写写"中文(英文简写)"(平均无故障间隔(MTBF)/平均停机间隔(MTBO)/单次停机时长(MDT)); '
|
|
|
+ '④ 不确定与缺件一律如实写(测风塔零交付、m5_cms_tcm 正本缺失由观澜自算件顶上、故障录波仅 4 台、'
|
|
|
+ '远端未装 Ollama),不编造; 每份文档附「编写依据」表逐章列出源文件; '
|
|
|
+ '⑤ `guanlan.py check` 增一行「交付文档三件 · v<版本>」: 校验三份 docx 在位**且文件名含当前版本号**、'
|
|
|
+ 'markdown 源件与所引插图都在位 —— 否则升一次版本号, 旧文档名就悄悄过期(同名旧版会跟着进包); '
|
|
|
+ '★2026-09-22 用户令「三份文档不同步到远端服务器」⇒ 该行先看 `docs/src` 在不在: 不在 = 本机是'
|
|
|
+ '**不随文档的部署**(远端演示机即此),只报提示行、不 FAIL,且在 import python-docx 之前分流'
|
|
|
+ '(那台机未装该依赖,直接进渲染器会炸成 ModuleNotFoundError)。'),
|
|
|
+ dict(version='2.9.2', date='2026-09-22', level='patch',
|
|
|
+ title='升级后验收补缺:版本/文档漂移有了自动拦截点(重算链增 ⑧d 版本记录门)',
|
|
|
+ note='纯消缺 ⇒ 小版本 +1。检查: 远端 2.9.1 升级 + 重算完成后跑 `guanlan.py check`,'
|
|
|
+ '出现两处 FAIL —— ① `docs/版本记录.md` 与 `src/version.py` 不一致(version_log rc=6);'
|
|
|
+ '② 安装根多出 `_pre290_backup/`(16 件扁平旧副本)让 config_audit rc=8。'
|
|
|
+ '根因: 这次升级的差异盘点只覆盖 `src/`+`scripts/`+`configs/`(`remote_src_inv.py`),'
|
|
|
+ '**文档从不在盘点面上** ⇒ 代码升到 2.9.1、文档留在旧版(版本记录 13,277 vs 21,284 字节、'
|
|
|
+ '系统设计说明缺 §17);而**重算链里没有版本门**(rebuild_all 只到 ⑧c 页面归口审计),'
|
|
|
+ '于是 24/24 步全 OK、控制台写"重算完成",漂移照样静默过关 —— 没有任何自动拦截点。'
|
|
|
+ '解决: ① `scripts/rebuild_all.py` 增「⑧d 版本记录一致性」(`version_log.py --check`,'
|
|
|
+ 'tolerate=(6,) —— 与 ⑧c 同口径: 缺的是"文档同步"这一路,报出来但不打断整条链);'
|
|
|
+ '② 远端补齐 4 份文档(版本记录/系统设计说明/输入数据放置指导/detail 页面依赖台账),'
|
|
|
+ '逐件 sha256 与本地一致;③ 远端把 `_pre290_backup` 移出安装根到 '
|
|
|
+ '`D:\\产品\\_pre290_backup_20260921`(备份保留,不删除)。'
|
|
|
+ '验证: 远端 `version_log.py --check` rc=0、`config_audit.py` rc=0;'
|
|
|
+ '本地 `guanlan.py check` 全绿 + 反向审计/页面归口/chain_gap 全 rc=0。'
|
|
|
+ '遗留(如实记录,未处理): 远端**没有装 Ollama**(无二进制、PATH 无、11434 拒绝连接)'
|
|
|
+ '⇒ 本机模型探针在远端必然 FAIL,问答/升档类功能在远端不可用;'
|
|
|
+ '要不要在远端装 Ollama 与拉哪几档模型,等用户定。'),
|
|
|
+ dict(version='2.9.1', date='2026-09-22', level='patch',
|
|
|
+ title='人工测试 7 项消缺(描述口径 + 依据空白 + 问题页空 + 门户链接 404 + 闭环文案 + Ollama 档位)',
|
|
|
+ note='纯消缺(无功能增减)⇒ 小版本 +1。逐项「检查→根因→解决→验证」: '
|
|
|
+ '① 描述口径: 「窗」确实指时间窗者一律写「时间窗」(预设窗→预设时间窗、判级窗→判级时间窗、'
|
|
|
+ '窗内→时间窗内、证据窗→证据时间窗、随所选窗→随所选时间窗…;天气窗/作业窗/预览窗/观测窗'
|
|
|
+ '属领域词,保持原样);「影响台」→「影响机组」(含报告构建器表头);英文简写改「中文(英文简写)」'
|
|
|
+ '(平均停机间隔(MTBO)/平均无故障间隔(MTBF)/单次停机时长(MDT)),英文映射键同步; '
|
|
|
+ '② 需要关注「查看完整依据」空白: 根因=行的来源是**融合面链盘**(d.fus.链盘.rows),依据却只从 '
|
|
|
+ 'SCADA 的 d.watch 查 ⇒ 两边机组集合不同时 why[t] 为空(实测 WTG09 齿轮箱 bad 台)。'
|
|
|
+ '改为按本行字段自组句(部件/链路进度/卡在哪步/证据源),永不空; '
|
|
|
+ '③ 报警机组「该问题页」打开空白 + ⑤ 命中系统链接: 根因=链接传**slug**(pitch)而页面/接口按'
|
|
|
+ '**中文系统名**取数 ⇒ /api/problem 返回 0 条,页面还写"该系统在判级窗内无非常态"(对报警机组是假陈述)。'
|
|
|
+ '改为: 链接传中文名 + 服务端 `sys_norm()` 双向归一(slug/中文/大小写都认)+ 页面区分'
|
|
|
+ '"系统标识无法识别 / 本时间窗无异常"两者,绝不把认不出说成正常; '
|
|
|
+ '④ /turbine 页「返回观澜门户」→ 原写死 `http://127.0.0.1:18084/观澜_如东样板_门户_单文件.html`'
|
|
|
+ '(用户机器上等于访问自己的 loopback,且该文件服务端没有)⇒ 网关回 not found。'
|
|
|
+ '修法: 用根相对 `/`,并给**网关加 `data-abs="1"` 豁免**(作者显式声明某链接不按组件前缀改写,'
|
|
|
+ '否则根相对链接一律被改写成 /detail/… 永远回不到门户),普通链接前缀行为不变(有单测); '
|
|
|
+ '⑥ 振动「端到端闭环」文案: 去掉内部黑话 handoff,改为'
|
|
|
+ '「暂无端到端闭环证据:现场提交的振动数据包里没有「报警→检修→复测」的成对记录」; '
|
|
|
+ '⑦ 观澜↔本机 Ollama: 链路实测可用(探针 OK、/api/ask 提交成功、8B 出答但**未过校闸**→触发升档),'
|
|
|
+ '但升档目标**硬编码**在 `src/ontology/fast_agent.py` 的 `ESCALATE`(写的是本机没装的 `qwen3.8:27b`,'
|
|
|
+ '`configs/models.json` 只是同处笔误的另一份)⇒ 升档 digest=None、ask 终态 error(用户只看到"复核中")。'
|
|
|
+ '修法: ESCALATE 改 `qwen3:32b` + 新增 `installed_models()`/`escalate_target()` —— 升档目标按'
|
|
|
+ '「配置档位且**确已安装**」解析,没装则**响亮回落**并打日志,不再把答案静默卡死;'
|
|
|
+ '初答文案里的"27B"改为实际目标名;BUDGET_S 随档位更新(32B 给 120 s)。'
|
|
|
+ '验证: `escalate_target("qwen3:8b") → qwen3:32b`(本机实装 4 档实测);'
|
|
|
+ '另: 远端 106.120.102.238 与本地各跑一遍 HTTP 抽样(门户链接=/、slug 归一出 1 条问题、'
|
|
|
+ '文案含时间窗/影响机组/(MTBO)、闭环新文案、WTG09 依据不再空),网关改写 `data-abs` 豁免单测通过,'
|
|
|
+ 'config_audit/pages_audit/反向审计/raw_scan selftest 全 rc=0,`guanlan.py check` 全绿'),
|
|
|
+ dict(version='2.9.0', date='2026-09-21', level='minor',
|
|
|
+ title='时间窗真正生效(用户令):「部件问题」与「发电性能」随所选窗重算,并新增自定义起止日期窗',
|
|
|
+ note='功能增减改 ⇒ 中版本 +1。要点: '
|
|
|
+ '① 窗口词表新增**自定义起止日期**(`YYYY-MM-DD~YYYY-MM-DD`,**含两端**):'
|
|
|
+ '`months_of()` 取相交月、新增 `win_range()/in_win()` 供日粒度件做含端过滤;'
|
|
|
+ 'v2 顶栏加两个日期输入框 + 应用(校验 a≤b),月度件按所跨月取整、日粒度件按日精确,页上写明; '
|
|
|
+ '② 判级四轴按窗重算:`taxonomy.system_matrix(span=…)` 把窗透到 变桨(日粒度件切片, 精确)/'
|
|
|
+ '偏航/蓄能/温度(走新窄仓重算);`reliability.overview(span=…)` 让部件可靠性表按窗;'
|
|
|
+ '温度面的 ref/ref_sm 对照窗**仍固定**(季节解耦基线,跟着漂就失去对照意义); '
|
|
|
+ '③ 发电性能按窗:`/api/curves?win=` 七镜头按窗重算 + 7 张月度时序图按窗过滤;'
|
|
|
+ '选到 2025-07~2025-12(限电前干净判别窗)时直接用正式产物不重算;'
|
|
|
+ '④ 新产物 `windscada/slim10min/*`(公共列子集,不裁剪行)作**按窗重算底座**:'
|
|
|
+ '一次全量扫≈2 分钟,把"每换一个窗重读 15 GB"降到秒级;已进重算链 ③b 并登记反向审计族表; '
|
|
|
+ '④b **M9 控制参数一致性**(就摆在发电性能页上)此前仍钉死 2025-H2 ⇒ `control.registry(span=…)` 按窗重算'
|
|
|
+ '(窄仓补 `grd_wtc_ActPower_max`/`tur_wtc_GenRpm_max` 两列 ⇒ 48 列;≈0.6 s/窗,P封顶中位 4190(干净窗) → 4181.7(2026年) / 4177.5(2026Q1)),'
|
|
|
+ 'fleet 响应加 `m9_pending/win_pending` 并由页面轮询(5 s)—— 至此判级四轴 + 可靠性 + 控制参数 + 七镜头 + 7 张月度图**全部随窗**;'
|
|
|
+ '远端两窗对比只剩 all_months/note/*_pending 三个元数据键不变; '
|
|
|
+ '⑤ 按窗重算一律"后台算+进程缓存"(判级≈25 s/窗、镜头≈10~45 s/窗):命中即回,'
|
|
|
+ '未命中先回 `pending/building` 并由页面轮询,**不静默拿旧口径当新窗**;'
|
|
|
+ '启动时后台预热 6 个预设窗(判级+默认窗镜头); '
|
|
|
+ '⑥ 实测(本机 38 台): 判级逐系统报警台数随窗变(变桨 15/13/10、偏航 15/11/5、齿轮箱 7/4/4 对应'
|
|
|
+ '2026年/2025H2/2026-07),曲线图注窗=所选窗且样本量随窗变;验收: 反向审计 3548 件 0 未归类、'
|
|
|
+ 'pages_audit/detail_deps/config_audit/raw_scan(selftest,rc=0)/chain_gap 全绿;'
|
|
|
+ '⑦ 远端部署(D:\\产品\\app,2026-09-21)实逮两处并已修: (a) 预热与请求各自起线程 ⇒ 同时算两份'
|
|
|
+ '按窗中间帧,改为**重活全局串行**(`_HEAVY_LOCK`)并打印可用内存;(b) 退化窗("近30日"=2026-09'
|
|
|
+ '只有 1 个节拍 ⇒ 比档件为空)时 `groupby().apply()` 返回空表 ⇒ `.rename("dev_K")` 抛 TypeError 把'
|
|
|
+ '整面判级打断,改为**如实"不可判(样本不足)"**;另: 远端进程须由 Windows 服务 `guanlan` 托管'
|
|
|
+ '(SSH 会话里起的进程会随会话关闭而死,实测全端口消失 ⇒ 已装服务并 RUNNING),'
|
|
|
+ '远端核验: fleet 两窗 pending→就位、变桨报警 15→7 / 偏航 15→3、rel 窗随窗、'
|
|
|
+ 'curves 图注"窗 2026-07-01 ~ 2026-07-31 (随所选窗)"、v2 页含自定义起止控件'),
|
|
|
+ dict(version='2.8.2', date='2026-09-21', level='patch',
|
|
|
+ title='消缺:扫描器的"变化"判据被两类**非摄入件**常年占住 —— 现场按类目交付的月度通道组库'
|
|
|
+ '(`scada_mdb/<年>年/<月>月/<年>-<月>-<类>.mdb`)被当成"数据没人读·缺口",用户指定的按月提取件'
|
|
|
+ '(`<场站>/scada_10min_<YYYYMM>.csv`)被当成"未归类";两者每次扫描都计一次变化 ⇒ '
|
|
|
+ '`raw_scan --check` 永远 rc=4,`rebuild_all.py --auto` 与 raw_watch 会**每轮都以为来了新数据**',
|
|
|
+ note='纯消缺(无功能变化)⇒ 小版本 +1。要点: '
|
|
|
+ '① `CONSUME` 新增 `upstream` 口径(信息级):类目库是 10min 同台补充件的**上游**,'
|
|
|
+ '取数层不直接读它 ⇒ 不再算缺口、不计变化,改按"上游归档件(要先转换才被消费)"列出并写清转换口径; '
|
|
|
+ '② 新增 `IGNORE_PATTERNS`:`scada_\\d+min_\\d{6}\\.csv` 是按月提取/核对件(位置由用户指定、'
|
|
|
+ '不是摄入源)⇒ 不报未归类、不计变化;'
|
|
|
+ '③ 实测(2026-09-21 落位 7-8 月交付后):修前 `--check` rc=4 且每轮都报"2 处变化",'
|
|
|
+ '修后 `--check` rc=0「与上次快照一致,没有新数据」,`--selftest` 通过,24 件类目库如实以信息级列出; '
|
|
|
+ '④ 口径写进 `docs/输入数据放置指导_v0.1.md` §4.1(含 12 组中文目录↔类目码、9 类↔主件 595 通道一一对应、'
|
|
|
+ 'float32+ISO 对齐、以及"类目库→同台补充件"的落位转换)'),
|
|
|
+ dict(version='2.8.1', date='2026-09-20', level='patch',
|
|
|
+ title='消缺:重算链末步「台账等价验收」在全新机器上必然 rc=5(交付包不含产物 ⇒ 没有随包基线),'
|
|
|
+ '原先把它判成失败 ⇒ 门户上写"重算失败"(实测服务器 2.8.0 一轮 24/24 步全 OK 却整轮 rc=1)',
|
|
|
+ note='纯消缺(无功能变化)⇒ 小版本 +1。要点: '
|
|
|
+ '① `rebuild_all.py` 的 ⑧ 台账等价验收由 `tolerate=(4,)` 改成 `tolerate=(4, 5)` —— '
|
|
|
+ 'rc=5 = "找不到随包基线 ⇒ 没做验收",与 ④ 的口径对齐(④ 一直容忍 5 并写明"跳过等价验收≠通过");'
|
|
|
+ '步名与步说明都改写清楚"rc=4 = 有需人工看的差异 / rc=5 = 没基线 ⇒ 没验收(不是失败,也不是通过)",'
|
|
|
+ '并指路 `scripts/set_baseline.py`(用已核实的当前产物快照登记基线,之后这一项才会真比对); '
|
|
|
+ '② 远端复核(服务器 D:\\产品\\app): 该轮 24 个实质步骤全绿(①b 扫描 53.7s · ② 164s · ③ 1025s · '
|
|
|
+ '④ 99s · ④b 2118s · ④c 385s · ④d 41.6s · ④e 15.3s · ⑤b/⑤c/⑤a/⑤ · ⑥ · ⑦×6 · ⑦b 78s · ⑧ 7.1s · '
|
|
|
+ '⑧b 3s · ⑧c 9.5s),只有第 25 步判失败;且同批数据已真进产物:`temp_monthly` 21 个月、末四格 '
|
|
|
+ '2026-06/07/**08**/09、2026-08 = 1,026 行(服务器 `scada_10min` 也有 76 件含 38 个 `WTG??-B2.csv` '
|
|
|
+ '同台补充件,说明步骤 B 的同台多件合并已在服务器上生效); '
|
|
|
+ '③ 已把修好的 `scripts/rebuild_all.py`(+ `src/version.py` / `docs/版本记录.md`)单文件部署到服务器,'
|
|
|
+ '无需重装整包。交付包 app_guanlang_v2.8.1.zip'),
|
|
|
+ dict(version='2.8.0', date='2026-09-20', level='minor',
|
|
|
+ title='输入数据自动扫描识别:<安装目录>/data/raw 逐族指纹 → 发现新增/变化 → 指明该跑哪几步;'
|
|
|
+ '重算链加 ①b 步 + `rebuild_all.py --auto`(新数据不被 --skip-* 漏掉)+ 服务侧可选看门狗',
|
|
|
+ note='非核心功能新增 ⇒ 中版本 +1(无架构改动)。要点: '
|
|
|
+ '① 新增 `scripts/raw_scan.py`(用户令 2026-09-19「对 <安装目录>/data/raw 目录下的接入数据处理,'
|
|
|
+ '增加自动扫描识别机制,以能发现新增数据,并纳入重算」):逐族指纹 = 件数/体积/最新落盘时间/'
|
|
|
+ '清单摘要(`--deep` 再叠内容哈希),与快照 `outputs/<场>/_raw_scan.json` 比;'
|
|
|
+ 'rc: 0 无变化 · 4 有新增/变化 · 5 还没有基线; `--write` 记基线, `--json` 给机器读, '
|
|
|
+ '`--selftest` 对账"族表覆盖反查族表里所有 raw 输入 + 步骤名都能在链上找到"(防两处漂移); '
|
|
|
+ '② 族表把每个 raw 子目录映射到链上步骤(故障报警/工单/油样→②;scada_10min→③④④c⑤b;'
|
|
|
+ 'scada_1min→④c;scada_mdb→③④④c;windcms→④b④d④e;m5_cms_tcm→④b;西门子4.0技术资料→⑦),'
|
|
|
+ '`data/raw/<场>/` 下出现族表没有的目录则如实报"未归类、没有已知消费者"(不猜); '
|
|
|
+ '③ 进重算链作 **①b 步**(`--check --write`,容忍 rc=4/5,并在步说明里写清"4 = 有新数据不是失败"); '
|
|
|
+ '④ `rebuild_all.py --auto`:先扫一遍,新数据落在被 `--skip-scada`/`--skip-vib` 跳过的族里就'
|
|
|
+ '**自动取消跳过**(实测: scada_10min 新增 1 件 → 计划从 22 步变 24 步并打印取消原因); '
|
|
|
+ '⑤ 服务侧看门狗(`scripts/service_worker.py::raw_watch`,配置 `configs/serve.json` 的 `raw_watch`,'
|
|
|
+ '**默认关**):每 N 分钟扫一次,有新数据写 logs/service.log 并列出该跑的步;`auto_rebuild=true` '
|
|
|
+ '时顺手发起一次重算(走 ops 同一条路;`run/ops_job.json` 显示在跑则不重复发起)。'
|
|
|
+ '★2026-09-20 按用户令「对 data/raw(**含嵌套子目录**)下文件的增减做到监听;重算要按该目录'
|
|
|
+ '**最新的变化**算」逐条查证并补了三处: '
|
|
|
+ '⑥ 扫描改成**递归统计全部文件**(不限扩展名; 白名单命中的另记 matched, 差额单列为"信息,不计变化")'
|
|
|
+ '+ **子目录清单进指纹**(新建空目录也发现) + 未归类改成**全树逐件**找(不再只看一层); '
|
|
|
+ '⑦ **产物 = 当前源件的函数**(删/换源件后, 那些行不再产出并大声报出"哪几件不在盘、少多少行、'
|
|
|
+ '涉及哪些月"): 报警/工单/油样三个摄入器原先是"只替换本次涉及的件、其余原样保留" ⇒ 源件删了行还在; '
|
|
|
+ '⑧★报警台账改**键集合并集**去重(键=Name/Alarmcode/TimeOn, 归属取最窄的源件)—— 原先按"逐件累加、'
|
|
|
+ '没新键就跳过"的贪心判快照, 实测**删一个源件反而让总行数从 39,211 涨到 56,593**(被跳过的大快照件'
|
|
|
+ '整件摄入, 重复计数) ⇒ 既不是源集的函数也会虚增; 现在 39,211 键稳定、重复 3.6 万行如实去掉; '
|
|
|
+ '⑨ 振动窗: 同名窗已存在时原来一律改名 `_reimport`(被 EXCLUDE_DEFAULT 排除 ⇒ **补进来的 CMS 数据'
|
|
|
+ '根本进不了生产集**), 现在默认**替换同名窗**(旧窗留档 `_superseded_<时分>`, 已加入排除表; '
|
|
|
+ '要旧行为用 `--reimport-as-new`)。'
|
|
|
+ '★另按用户令「按你的建议执行 先 C 再 B」补完"同台多件 10min 进不来"这条链: '
|
|
|
+ '⑩ **步骤 C(让"放了没人读"当场可见)**:`raw_scan.py` 新增 `CONSUME` 表(逐族写清**消费者真实'
|
|
|
+ '取数口径**),判据从"扩展名白名单没命中"改成"**全部文件里没被消费者读的**"—— 实测 `data/raw/'
|
|
|
+ '如东/scada_10min/WTG01-B2.csv`(856 列 / 2026-08 数据)扩展名 `*.csv` 命中白名单,旧判据'
|
|
|
+ '**永远发现不了**它;现在报成**缺口级**(计入变化 rc=4 + ★[缺口] 块写明消费者口径与件例),'
|
|
|
+ '附件类(.rar/.jpg/厂商软件)仍只列信息;建议行也分流说明"缺口类重算也纳入不了"。'
|
|
|
+ '`raw_data_check.py` 同步: 同台补充件不再误报"文件名不是本场机组",改为 info 行。'
|
|
|
+ '⑪ **步骤 B(真正把新月份的 10min 件吃进来)**:`scada_source.load` 取数从"只认 `<台号>.csv`"'
|
|
|
+ '扩到 **`<台号>.csv` + 同台补充件 `<台号>-*.csv`/`<台号>_*.csv`**:按列名对齐(新导出把类型前缀'
|
|
|
+ '去掉了: `din_wtc_HydLevel_timeon` → `wtc_HydLevel_timeon`,实测老件 598 列里 597 列能这样对上)、'
|
|
|
+ '按时间戳排序去重(**主件优先**,重叠条数写进 `attrs.scada_overlap` 并打印)、来路件名与各件行数'
|
|
|
+ '写进 `attrs.scada_files/scada_rows`。实测 WTG01:主件 77,551 行 + 补充件 5,820 行 → 合并 83,353 行'
|
|
|
+ '(重叠 18 行去重),时间范围由 2026-07-07 延到 **2026-09-01**,2026-08 的 5,819 行关键通道'
|
|
|
+ '非空率 77%(**空白是源件自带的**:该月 144 行/日齐全但约 23% 行的通道为空,如实保留 NaN)。'
|
|
|
+ '交付包 app_guanlang_v2.8.0.zip'),
|
|
|
+ dict(version='2.7.0', date='2026-09-19', level='minor',
|
|
|
+ title='远程部署消缺:振动窗发布被杀软/索引占用不再打断整条重算链(+ --publish-only 恢复通道);'
|
|
|
+ '门户可对外监听(public_host,组件仍只本机);GBK 控制台打印不炸',
|
|
|
+ note='一次消缺 + 一项非核心功能新增(对外监听)⇒ 按规则取最高一级(minor)。要点: '
|
|
|
+ '① ★远端实逮(服务器 D:\\产品\\app):索引 719 s + 谱 894 s 落进 windows\\_staging_ingest 后,'
|
|
|
+ '`staging.rename(final)` 回 `PermissionError: [WinError 5] 拒绝访问`(360 实时扫描/索引器持着'
|
|
|
+ '刚写的 1710 件小文件的句柄,Windows 的目录改名要求树里无打开句柄)⇒ ④b 失败 ⇒ 链停在第 4 步,'
|
|
|
+ '页面表现为「需要关注 0 台 + 融合面缺件」。修法:发布改 `_move_tree()`(整目录 rename 退避重试 '
|
|
|
+ '→ 逐件搬,rename 不行就 copy+删 → 仍锁住的逐条如实报出);并加 `--publish-only`:已算完的暂存窗'
|
|
|
+ '可直接发布,不必白等 25 分钟重跑索引/谱; '
|
|
|
+ '② 用户令「观澜改为监听所有 IP」:`configs/serve.json` 增 `public_host`(空=跟 host=只本机;'
|
|
|
+ '设 0.0.0.0=所有网卡),**只有门户网关**用它,detail/cms/viewer/sim 仍绑 127.0.0.1 ⇒ 对外只有 '
|
|
|
+ '28084 一个入口;启动时打印对外地址并提示"页面无鉴权,请在防火墙侧限来源"; '
|
|
|
+ '③ 消缺: 新生成端在 GBK 控制台打印 m/s² / ⇒ 时抛 UnicodeEncodeError(远端 baseline_38 整件没出)'
|
|
|
+ '⇒ 四个生成端加 stdout 守卫;trend_ingest 的"台账止 2024-11"过期断言改实话;'
|
|
|
+ 'CMS 三层基线卡的"健康期窗 N"重复措辞; '
|
|
|
+ '④ 部署口径: 用 SSH 起的服务在会话断开时会被一起收掉 ⇒ 现场改注册 Windows 服务(`guanlan`)'
|
|
|
+ '(`scripts/service_ctl.py install`),开机自启、SCM 崩溃重启。交付包 app_guanlang_v2.7.0.zip'),
|
|
|
+ dict(version='2.6.0', date='2026-09-19', level='minor',
|
|
|
+ title='「所有的计算均要形成观澜的源代码」:融合面/总览页/事实契约/发布层/掩码阈值/六层链两步/'
|
|
|
+ '变桨面/振动在升与三层基线 全部落成观澜自算;SCADA 接入兼容 CSV+MDB;'
|
|
|
+ '交付包带输入数据目录结构(不含文件)',
|
|
|
+ note='功能新增为主、夹带多项消缺 ⇒ 按规则取最高一级(minor)。要点: '
|
|
|
+ '① 六层链 model_run/fusion 两步按口径重建(报告的"融合级"列从此有值)+ energy_share 步补上 + '
|
|
|
+ '峰值拾取口径定案(观澜口径,四件 *_freq_scan 形式不再复刻); '
|
|
|
+ '② 融合面 handoff 与总览页 windscada/index.html 由观澜自算生成(原为"包内无生成端",'
|
|
|
+ '新机器上 /detail/v2 的"需要关注/全场状态"必空白); '
|
|
|
+ '③ 事实契约改为**从重算台账生成 claim**(门户结论段/问答/报告随重算刷新,/api/facts 不再 503); '
|
|
|
+ '④ 本体发布层 r1/r2、TCM 掩码阈值(观澜自算口径,非厂商)分别落成生成端; '
|
|
|
+ '⑤ SCADA 接入统一取数层兼容 CSV 与 MDB(含老库列位错位如实拒绝;37 个月库按修好的建库脚本'
|
|
|
+ '重建并逐分片对拍); '
|
|
|
+ '⑥ 变桨面 pitch/** 落成生成端(10min 开关量/压力锯齿 + 1min 桨距角;零位口径按用户令'
|
|
|
+ '**停机段/满发段分列**,并据实测反推补上**运行段同工况分档**这一真正的判据轴 —— 它能把'
|
|
|
+ '现场确诊的 19# 单独拎出来); '
|
|
|
+ '⑦ 振动 component_history.json(在升/换件闭环)与 baseline_38.json(自/机群/绝对三层基线)'
|
|
|
+ '落成生成端(窗数不足时如实空表并写明数据边界); '
|
|
|
+ '⑧ 打包:**带输入数据目录结构**(30 个空目录, 含 data/raw/西门子4.0技术资料)+ 放置说明文件随包, '
|
|
|
+ '仍不含数据文件; ⑨ 消缺: report 步踩可选包 tabulate 致整链中断、语言包闸拦下的 /v2 500、'
|
|
|
+ '振动页看不出数据区间与"3 月数据消失"(趋势按日聚合)、账目单位错(kWh/MW)、'
|
|
|
+ 'Access 锁文件误报、日志保留策略等。交付包 app_guanlang_v2.6.0.zip'),
|
|
|
+ dict(version='2.5.0', date='2026-09-17', level='minor',
|
|
|
+ title='卸载闭环(uninstall.bat / uninstall.sh)+ 版本管理与打包命名规则 + 版本记录',
|
|
|
+ note='二代架构(大版本 2,与 NAME 里的 v2 对齐)的第 5 次功能性发布,无消缺项;'
|
|
|
+ '交付包 app_guanlang_v2.5.0.zip'),
|
|
|
+ dict(version='0.4.0', date='2026-09-17', level='legacy',
|
|
|
+ title='打包默认不含 输入数据/产物/日志/临时文件;无窗口启动(去掉 VBScript);统一配置与日志;'
|
|
|
+ '页面归口审计;输入数据放置检查;安装时版本检查与服务化',
|
|
|
+ note='旧编号体系;交付包 app_guanlan_v2_0.4.0.zip(= guanlan-v0.4.0_dist_20260917.zip)'),
|
|
|
+ dict(version='0.2.0', date='2026-09-01', level='legacy',
|
|
|
+ title='含产物的旧交付基线(历史上用作"包内无生成端"那批产物的补救源;该用途 2026-09-19 起已失效)',
|
|
|
+ note='旧编号体系;交付包 guanlan-rudong-v2_0.2.0_test_win64.zip —— ★2026-09-19 用户令从工作树'
|
|
|
+ '清理(943 MB):① 「所有的计算均要形成观澜的源代码」落地后无生成端件 = 0(反向呼应审计 '
|
|
|
+ '1,800 件全部 raw-derived)⇒ 它作为"补救源"的用途消失;② 该包仍留在 git 历史里'
|
|
|
+ '(blob 可随时 git 取回),需要时用 git show 恢复即可'),
|
|
|
+)
|
|
|
+
|
|
|
+
|
|
|
+def package_name(version: str | None = None, ext: str = 'zip') -> str:
|
|
|
+ """交付包文件名(用户令 2026-09-17 的命名规则):`app_guanlang_v<版本号>.zip`。"""
|
|
|
+ return f'{PACKAGE_STEM}_v{version or VERSION}.{ext}'
|
|
|
+
|
|
|
+
|
|
|
+def level_of(old: str, new: str = VERSION) -> str:
|
|
|
+ """两次版本之间是"哪一级"的变化 → major / minor / patch / same / ?(版本管理的机器判据)。"""
|
|
|
+ a, b = _parts(old), _parts(new)
|
|
|
+ if a == b:
|
|
|
+ return 'same'
|
|
|
+ if len(a) < 3 or len(b) < 3:
|
|
|
+ return '?'
|
|
|
+ if b[0] != a[0]:
|
|
|
+ return 'major'
|
|
|
+ if b[1] != a[1]:
|
|
|
+ return 'minor'
|
|
|
+ return 'patch'
|
|
|
+
|
|
|
+
|
|
|
+def rule_lines() -> list[str]:
|
|
|
+ """把版本规则印出来(README / 版本记录 / `guanlan.py version` 共用同一份措辞)。"""
|
|
|
+ return [f'编号: {RULE} 例: v{VERSION}',
|
|
|
+ *[f' {LEVEL_MEANING[k]}' for k in ('major', 'minor', 'patch')],
|
|
|
+ f'包名: 打包文件名 = {package_name()}']
|
|
|
+
|
|
|
+
|
|
|
+def info_path(root: pathlib.Path) -> pathlib.Path:
|
|
|
+ return pathlib.Path(root) / INSTALL_INFO
|
|
|
+
|
|
|
+
|
|
|
+def read_installed(root: pathlib.Path) -> dict | None:
|
|
|
+ """→ 已安装记录 dict;没装过就是 None(`install-info.json` 缺失或坏了都算没装)。
|
|
|
+
|
|
|
+ ★ 用 `utf-8-sig` 读:本器写的是无 BOM 的 UTF-8,但**别的工具/手改**过这份 json 时可能带 BOM
|
|
|
+ (实测 PowerShell 的 `Set-Content -Encoding UTF8` 就带 BOM)。带 BOM 而按 utf-8 读会抛异常,
|
|
|
+ 于是"明明装了却报没装过" —— 版本检查直接失灵,所以这里按最宽的方式读。
|
|
|
+ """
|
|
|
+ p = info_path(root)
|
|
|
+ if not p.is_file():
|
|
|
+ return None
|
|
|
+ try:
|
|
|
+ d = json.loads(p.read_text(encoding='utf-8-sig'))
|
|
|
+ return d if isinstance(d, dict) else None
|
|
|
+ except Exception:
|
|
|
+ return None
|
|
|
+
|
|
|
+
|
|
|
+def write_installed(root: pathlib.Path, python: str = '', mode: str = 'manual', extra: dict | None = None) -> dict:
|
|
|
+ """写安装记录(安装脚本在装完时调用)。mode: manual | windows-service | systemd | startup-entry。"""
|
|
|
+ root = pathlib.Path(root)
|
|
|
+ rec = dict(name=NAME, version=VERSION, edition=EDITION, installed_at=dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
|
+ python=python or '', mode=mode, host=str(root))
|
|
|
+ rec.update(extra or {})
|
|
|
+ info_path(root).write_text(json.dumps(rec, ensure_ascii=False, indent=1) + '\n', encoding='utf-8')
|
|
|
+ return rec
|
|
|
+
|
|
|
+
|
|
|
+def _parts(v: str) -> tuple:
|
|
|
+ out = []
|
|
|
+ for seg in str(v).strip().lstrip('vV').split('.'):
|
|
|
+ num = ''.join(ch for ch in seg if ch.isdigit())
|
|
|
+ out.append(int(num) if num else 0)
|
|
|
+ return tuple(out)
|
|
|
+
|
|
|
+
|
|
|
+def compare(installed: dict | None, pkg_version: str = VERSION) -> dict:
|
|
|
+ """安装检查的核心:已装版本 vs 本包版本 → 判断该提醒什么。
|
|
|
+
|
|
|
+ → dict(state, installed_version, pkg_version, message, action)
|
|
|
+ state: none(没装过)· same(同版本)· older(已装更旧,本包是升级)·
|
|
|
+ newer(已装更新,本包更旧 —— 要特别提醒别降级)· unknown(老安装没写版本记录)
|
|
|
+ action: install(照常装)· ask(问用户是否重装)· warn(要用户明确确认)
|
|
|
+ """
|
|
|
+ cur = (installed or {}).get('version') or ''
|
|
|
+ if not installed:
|
|
|
+ return dict(state='none', installed_version='', pkg_version=pkg_version,
|
|
|
+ message=f'未检测到已安装的{NAME}(将全新安装 v{pkg_version})', action='install')
|
|
|
+ if not cur:
|
|
|
+ return dict(state='unknown', installed_version='', pkg_version=pkg_version,
|
|
|
+ message=f'检测到安装目录已有部署,但没有版本记录(老版本安装)'
|
|
|
+ f'—— 本包是 v{pkg_version},将按"重装/升级"处理', action='ask')
|
|
|
+ if _parts(cur) == _parts(pkg_version):
|
|
|
+ return dict(state='same', installed_version=cur, pkg_version=pkg_version,
|
|
|
+ message=f'已安装 {NAME} v{cur},与本包 v{pkg_version} **版本相同**', action='ask')
|
|
|
+ if _parts(cur) < _parts(pkg_version):
|
|
|
+ return dict(state='older', installed_version=cur, pkg_version=pkg_version,
|
|
|
+ message=f'已安装 v{cur} → 本包 v{pkg_version}(**升级**)', action='ask')
|
|
|
+ return dict(state='newer', installed_version=cur, pkg_version=pkg_version,
|
|
|
+ message=f'已安装 v{cur},而本包是 v{pkg_version}(**本包更旧 / 降级**)'
|
|
|
+ f'—— 除非确知原因,不要用旧包覆盖新装', action='warn')
|
|
|
+
|
|
|
+
|
|
|
+def summary_lines(cmp_res: dict, installed: dict | None) -> list[str]:
|
|
|
+ """给人看的几行(安装脚本与 guanlan.py check 共用同一份措辞)。"""
|
|
|
+ out = [cmp_res['message']]
|
|
|
+ if installed:
|
|
|
+ out.append(f" 现有安装: 装在 {installed.get('installed_at', '?')}"
|
|
|
+ + (f",运行方式 {installed.get('mode')}" if installed.get('mode') else '')
|
|
|
+ + (f",解释器 {installed.get('python')}" if installed.get('python') else ''))
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ import sys
|
|
|
+ print(f'{NAME} v{VERSION} ({EDITION})')
|
|
|
+ print(f'服务名: {SERVICE_NAME} · 显示名: {SERVICE_DISPLAY}')
|
|
|
+ root = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else _install_root(__file__)
|
|
|
+ ins = read_installed(root)
|
|
|
+ c = compare(ins)
|
|
|
+ print(f'安装记录: {info_path(root)}')
|
|
|
+ for ln in summary_lines(c, ins):
|
|
|
+ print(' ' + ln)
|
|
|
+ print(f" → state={c['state']} action={c['action']}")
|