| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657 |
- # -*- 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.34.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.34.0', date='2026-09-30', level='minor',
- title='/api/maint_framework 逐值一致 ⇒ 计分板 7/17',
- note='① 新增算法服务模块 `service/maint_views.py`:把详情层 `maint_framework_view()`(serve.py:361-394)**搬进目标栈**(读同一份 objects.json、调同一批 `maint/framework.py` 函数、保留英文化分支),不是重写 ⇒ 输出与详情层应当逐值一致;登记表新增 `maint_framework`(可选 `lang`)。② 新增 `MaintFrameworkController`(Java):代理算法服务 + 对外脱敏。③ 实测:**一致 7/17** · 有差异 0 · 未实现 9 · 跳过 1;`--require` 七条门禁通过。④ "搬"这一手让数据/规则类端点的迁移成本显著下降:只要 Python 侧逻辑能整体搬进算法服务包,Java 就只是一层代理 + 脱敏,一致性天然成立;真正的重写只留给纯展示/组装逻辑。⑤ 下一批:`maint_std`(同一手法)· `channels` · 算数大头 `fleet`(10 个页签取数源)· `curves` · LLM 类 `ask`/`ask_status`/`ask_models`/`rpt_compose`。'),
- dict(version='2.33.0', date='2026-09-30', level='minor',
- title='/api/ont_chain 逐值一致 ⇒ 计分板 6/17(本体三端点齐活)',
- note='① 算法服务 `ontology_views.ont_chain()` **逐字照抄**详情层五路分派(`fault`/`prev`/`tree`/`plan`/`doc`)与错误形状(含"未知链 …"与异常包装"本体查询失败: …");登记表新增 `ont_chain`(6 个可选参数:kind/code/system/top/keyword/dkind)。② `OntController` 增加 `/api/ont_chain`(代理 + 对外脱敏)。③ 实测:**一致 6/17**(facts · dq_findings · maint_survey · ont_list · ont_obj · ont_chain)· 有差异 0 · 未实现 10 · 跳过 1;`--require` 六条门禁通过。④ 下一批:`channels`(需先摸清 ETL `contract(CFG)` 的输入是否可直读)· `maint_std`(四判据 + 成熟度)· `maint_framework`(`maint/framework.py` 六个函数)· 算数大头 `fleet`(10 个页签取数源)· `curves` · LLM 类 `ask`/`ask_status`/`ask_models`/`rpt_compose`(先给裁判补 POST 请求体构造)。'),
- dict(version='2.32.0', date='2026-09-30', level='minor',
- title='本体两端点(/api/ont_list、/api/ont_obj)逐值一致 ⇒ 计分板 5/17;算法服务通用参数透传',
- note='① 算法服务新增适配模块 `service/ontology_views.py`(**逐字照抄**详情层包装口径:`ont_list` → `{rows: ont_query(...).data}`;`ont_obj` → `{obj, links, err}`),登记表新增两条。② 算法服务通用 handler 实逮并改造:原先参数写死为 `span/since/month_from/all_turbines`,新增端点(`ont_list` 的 `type/t/limit`、`ont_obj` 的 `id`)只能改代码 ⇒ 改为**按登记表声明的参数从查询串透传**(类型校验 str/int/bool,固定值参数不接受外部传入),登记表自此真正成为"唯一需要维护的清单"。③ 新增 `OntController`(Java):`/api/ont_list`、`/api/ont_obj` 代理算法服务 + 对外脱敏(`Redact`)。④ 实测:**一致 5/17**(facts · dq_findings · maint_survey · ont_list · ont_obj)· 有差异 0 · 未实现 11 · 跳过 1;`--require` 五条门禁通过。⑤ 下一批:`ont_chain`(5 种 kind 分派)· `maint_framework` · `channels` · `maint_std` · 算数大头 `fleet` / `curves` · LLM 类 `ask*` / `rpt_compose`。'),
- dict(version='2.31.0', date='2026-09-30', level='minor',
- title='/api/maint_survey 逐值一致(3/17):定下数据类端点迁移范式 + Java 复刻对外脱敏',
- note='① **范式定下**(数据类 detail_api 端点都这么做):重算/读 parquet 这类数据活归**算法服务**(用户三栈令 ③ 的目标栈 = Python + FastAPI,已满足),Java 后端实现同名业务端点、经 `/algorithm/...` 取数、再按契约组装 —— 两侧最终走同一段 Python 实现,一致性由裁判逐值验收。② 算法服务登记表新增 `maint_survey`(`app_ontology…maintenance.survey`,只读)。③ ★**Java 侧复刻对外脱敏** `redact/Redact.java`(与 Python 详情层 `_redact` 逐条对齐):Hz 前数字 → `▪▪`;`≥/≤` 后数字 → `≥▪`(保留时间口径 h/天/日、台数计数 台/次/条/起/个、无单位小整数);判据门槛(前 8 字含 z/σ/×/倍/dev/resid/选择性/峰/结构度/ratio)必脱;含 `0.593`/贝兹/Betz 只做 Hz 脱敏;`WINDSCADA_INTERNAL=1` 全程不脱。**这是"行为与重构前一致"的必答项** —— 本轮实逮:Java 直接回算法服务的原值给 `≥1.3`,而线上 Python 给 `≥▪`,正是缺了这一步。④ 实测:**一致 3/17**(`/api/facts`、`/api/dq_findings`、`/api/maint_survey`)· 有差异 0 · 未实现 13 · 跳过 1;`--require` 三条门禁通过。⑤ 下一批:`maint_framework`(依赖 maint/framework.py 六个函数)· `ont_list/ont_obj/ont_chain`(依赖 ontology MCP)· `channels` · 算数大头 `fleet`/`curves`· LLM 类 `ask*`/`rpt_compose`。'),
- dict(version='2.30.1', date='2026-09-30', level='patch',
- title='Java 迁移范围澄清:契约 42 条中属 Java 后端的是 17 条(detail_api),算法 13 条本就是目标栈',
- note='★重要澄清(改口径,不做事后美化):冻契约 42 条按 `kind` 分为 —— **detail_api 17 条**(业务后端,= Java 迁移范围)· **algorithm 13 条**(算法服务,**目标栈本就是 Python+FastAPI**,用户三栈令 ③ 已满足,Java 只需经 `/algorithm` 调用)· gateway 7 条(页面/网关路由)· cms 4 条 · detail_page 1 条(页面)。① 因此裁判 `scripts/java_contract_parity.py` 增加 `--java-kinds`(默认 `detail_api`):只对 Java 范围内的端点计分,范围外单列;计分板由 y/42 改为更如实的 **2/17**。② 实测:`一致 2/17`(`/api/facts`、`/api/dq_findings`)· 有差异 0 · 未实现 14 · 跳过 1(`/api/ask` 为 POST,需先给裁判补"构造请求体"的能力)· 范围外 25 条单列。③ 下一批仍按难易推进:`channels`(依赖 ETL 侧 `contract(CFG)` + i18n `ch_cn`,需先摸清输入)→ `maint_framework`(依赖 `maint/framework.py` 的 6 个函数)→ `ont_list/ont_obj/ont_chain`(依赖 ontology MCP)→ 算数类 `fleet`/`curves`/`maint_survey`/`maint_std` → LLM 类 `ask*/rpt_compose`。'),
- dict(version='2.30.0', date='2026-09-30', level='minor',
- title='Java 第二批端点 /api/dq_findings 逐值一致(计分板 2/42)',
- note='① 新增 `DqController`:与 Python `dq_findings_view()`(serve.py:394-446)**逐字对齐** —— 读**同一份**对象库产物 `outputs/rudong/ontology/objects.json`,取四族前缀 `finding/{data_quality,caliber,system_coverage,tier_trend}/` 投影成 rows,按 `(kind != data_quality, id)` 排序,空时补 note;两侧 JSON 深度比对**完全一致**。② 路径解析踩过的点:Python 的 `objects_json()` 是 `outputs/rudong/ontology/objects.json`(**不是** `derived/` 下),故 Java 侧以 `guanlan.objects-json` 配置注入并从工作目录/仓库根两处探测;产物一律"读同一份、不重算",与方法学一致。③ 实测计分板:**一致 2/42**(`/api/facts`、`/api/dq_findings`)· 有差异 1(根路径 `/`)· Java 未实现 37 · 跳过 2(POST 类);`--require /api/facts,/api/dq_findings` **门禁通过**。④ 下一批(同型:纯产物投影/读取)拟按 `maint_framework` → `channels` → `ont_list/ont_obj/ont_chain` 推进,再做算数类 `fleet/curves/matrix/availability_summary`。'),
- dict(version='2.29.1', date='2026-09-30', level='patch',
- title='Java 迁移裁判修正:未实现只按 HTTP 状态判;澄清 /api/facts/claim 不在 42 条契约内',
- note='① 裁判 `scripts/java_contract_parity.py` 修正:原先"未实现"判定里带了"正文含 404 字样"这一条,会把**已实现但正文含该字样**的端点误判 ⇒ 改为**只按 HTTP 状态**(0/404)。② 事实澄清:`/api/facts/claim` **不在冻契约的 42 条里**(契约只列 `/api/facts`)—— 我上一轮用 `--require /api/facts/claim` 要求它,属于**用法错误**(拿契约外的路径当门禁项);该端点 Java 侧已实现且两侧直连/经网关均 200,作为"契约外增量"记录在案,不计入 42 条计分板。③ 当前计分板(如实):**一致 1/42**(`/api/facts`)· 有差异 1(根路径 `/`,Java 未按契约同构返回)· Java 未实现 38 · 跳过 2(POST 类需构造请求体)。④ 下一轮开始按分层逐条实现,先用**纯产物读取类**(channels / dq_findings / maint_framework / maint_std / ont_*)打开局面。'),
- dict(version='2.29.0', date='2026-09-30', level='minor',
- title='Java 后端迁移开工:逐条比对裁判 + 同一源站灰度路由 + 首批端点 /api/facts 已一致',
- note='用户令「Java 后端按冻契约 42 条端点逐条迁移并灰度切换」⇒ 中版本 +1。① 新增**裁判** `scripts/java_contract_parity.py`:按冻契约逐条真问两侧(Java 走 `/java`、Python 走 `/detail`),JSON 归一化后深度比对,输出「一致 / 有差异 / Java 未实现」计分板,并支持 `--require` 作门禁。② 灰度路由:Python 网关 `_DEFAULT_ROUTES` 实逮**根本没有 `/java`**(此前 `/java/*` 是走 Java 网关 28085 或直连 28120)⇒ 新增 `("/java", 28120, …)` 与 `_PORT_KEY`/`serve.json` 登记,实现"同一源站两条路并存"。③ 首批端点:`FactsController` 落地 `/api/facts`(原样返回 `derived/detail_cards.json`,与 Python 同源同字节)与 `/api/facts/claim`(从 `qa_refs.json` 取 claim 引用);新增 `app_backEnd/backend-java/run_java.py` 启动器。④ 实测:`/api/facts` **已一致**(计分板 1/42,`--require` 通过),`/api/facts/claim` 见门禁输出。⑤ 下一轮:按计分板逐条实现其余端点(先做纯产物读取类:channels / dq_findings / maint_std / maint_framework / ont_* / facts 系;再做算数类:fleet / curves / matrix / availability_summary;最后 LLM 类:ask / rpt_compose),每条都以裁判过关为准;42 条全绿后切入口并一次性删 Python 源码。'),
- dict(version='2.28.1', date='2026-09-29', level='patch',
- title='灰度切换第一步就绪:Vue 产物经网关可作并列入口(新增产物级冒烟门)',
- note='用户令「先做灰度切换,Java 后端迁完再一次性删 Python 源码」⇒ 小版本 +1。① 新增 `app_frontEnd/web/tools/spa_smoke.mjs` 与仓库入口 `scripts/spa_smoke.py`:校验 `/web/index.html` 可达且引用构建产物、产物内含**全部 10 个页签标签与 10 个视图组件**、SPA 依赖的接口面(`/detail/api/fleet`、`/algorithm/healthz`)经网关可达;实测**全绿**。② 如实标注覆盖边界:本机无浏览器自动化、jsdom 也不能执行 Vite 的 ESM 包 ⇒ 该门是**产物级**验证;"呈现一致"仍由 10 页签 SSR 严格对拍承担,浏览器内交互回归不在覆盖内。③ 冒烟里的页签标签**取自现网语言包**(`window.__L` 的 `tab.<id>`)而不是硬编码 —— 实逮一次"期望文案猜错"的假失败(真实标签是 总览/电量算账/部件问题/故障统计/振动融合分析/发电性能/检修决策/检修助手/汇报定制/系统维护)。④ 灰度路线(按用户令):先并列入口(本轮)→ Java 后端按冻契约 42 条端点逐条迁移与灰度 → 最后一次性删除 Python 前端+后端源码,重打包并开箱验收。'),
- dict(version='2.28.0', date='2026-09-29', level='minor',
- title='report 页签迁移完成 ⇒ 10/10 页签全部通过严格整页签对拍(Vue 前端迁完)',
- note='用户令「直到 generation → decision → assistant → report → system 完成」达成 ⇒ 中版本 +1。① `report`(报告组装)逐字转写旧 `report()`:控制面板(5 组章节库按钮带勾选态/图标/名称 · 预设 · 受众 · 打印/导出 · 需求文本 + 模型下拉 + 组装按钮 + 提示行)+ 纸面骨架(标题/副题/章节数 · 页脚);`RPT_PRESETS`/`RPT_BLOCKS`/`RPT_ICON`/`MDL_LOC` 一律**从 app.js 原样抽出**。对拍 **48 条文本一致,0 例外**。② 实逮并修:与 decision 同法 —— 旧 `bindReport` 取到模型清单后把 `RPT.model` 置为**清单第一个键**,于是提示行会带";数据不出本地";我留空 ⇒ 少一段文案。③ **10/10 页签通过严格整页签对拍**:overview 138 · energy 51+图11/11 · component 260 · fault 133(例外1) · vibration 561(例外1) · generation 122 · decision 26 · assistant 40 · system 165(例外1) · report 48。④ 口径与边界(如实记):对拍对象是**首屏呈现**(各块正文 `rptBlock` 仅在用户往章节库里加块时才出现,属交互路径,本版未覆盖);3 条登记例外均为**后端两条数据来源的文案不一致**(非前端偏差),待后端统一后删除。⑤ 下一步(目标剩余部分):按纪律删除对应 Python 前端源码 → Java 后端按冻契约 42 条端点逐条迁移并灰度切换 → 随包 JRE + jar 的离线单包与开箱验收。'),
- dict(version='2.27.0', date='2026-09-29', level='minor',
- title='assistant 与 system 两个页签迁移完成 ⇒ 已迁页签 9/10(仅余 report)',
- note='中版本 +1。① `assistant`(三类查询助手)逐字转写旧 `assistant()`:故障码纠正(输入 + 常用码链接)· 预防性维护(系统下拉)· 故障树(系统下拉),各自"查询"按钮与输出区;对拍 **40 条文本一致,0 例外**。② `system`(维护巡检)逐字转写旧 `system()`:`/api/maint_survey` 数据按"数据/机理"两层出两张表(存在标记 · 覆盖 · 条数 · 末次更新 · 频率 · 责任 · 位置/摄入/说明);对拍 **165 条文本 + 2 张表一致(例外 1)**。③ 该例外如实登记:`/api/maint_survey` 的"说明"字段在**页面缓存**与**回放快照**间文案不同("确诊链判级…" vs "定谳链判级…"),与 fault/vibration 两例同类(数据两条来源),非前端偏差;待确认数据来源后删除。④ 顺带把 `zh.ts` 重新从现网语言包生成,消除快照漂移。⑤ 进度:**九个页签过对拍**(overview/energy/component/fault/vibration/generation/decision/assistant/system),仅余 `report`。'),
- dict(version='2.26.0', date='2026-09-29', level='minor',
- title='decision 页签迁移完成(AI 决策问答,对拍全绿)⇒ 已迁页签 7/10',
- note='中版本 +1。① `decision`(AI 决策问答)逐字转写旧 `decision()`:CATS 四类 pill · 该类常用问题三条 · 提问输入框 · 模型下拉(唯一来源 `/api/ask_models`)· 越界开关 · 输出区。② 修掉的三处偏差:a) 旧 `bindDecision`(app.js:435-436)拿到清单后把 `ASK.model` 置为**清单第一个键**,我留空 ⇒ 少一条档位说明并连带错位;b) 旧模板 `SNAP ? \'\' : <p>…</p>` 的**经典版入口提示**本页确实渲染(我按"v2 是快照站"想当然省略);c) `MDL_LOC`(模型档位表)我手抄缺项 ⇒ 档位后缀" · 云端"丢失,改为**从 app.js 原样抽出**。③ 对拍工具新增**通用接口快照**:把页面请求过的 `/api/*` 首次响应按路径记下(`state.apis`),经 `renderTab(..., apis)` → Workbench `apis` → 组件取用 —— 供 decision 及后续 assistant/report/system 复用。④ 进度:**overview · energy · component · fault · vibration · generation · decision 七个页签过对拍**;余 assistant/report/system 三个。'),
- dict(version='2.25.0', date='2026-09-29', level='minor',
- title='generation 页签迁移完成(对拍 122 条文本一致)⇒ 已迁页签 6/10',
- note='中版本 +1。① `generation` 在严格整页签口径下与重构前一致:**122 条文本全同**、0 例外。② 最后一处偏差是"我把旧图的**中位数注解**塞进了 `__data__`"(对拍报"Vue 有 1 个数字不在旧图里:4212.5")——按已定规则(`__data__` 只放"我画的数据且旧图会写成文字"的数字)三张图应为空:旧 `capAxis`/`betaBar`/`rulerBar` 只把**刻度与注解**写成 SVG 文字,散点/条形即数据本身。③ 关键工程改进:把"等 `/api/curves` 出图"做成对拍工具的**显式前置条件**(加载页面前先追到 `figs` 非空,最多 20×3 秒,超时**报错**而不是退化成骨架屏)—— 此前同一轮对拍会在 56/122 条之间飘,改一处飘一次、无法收敛;现在可复现。④ 本轮还修:候选机列表整串 `v-html`(分隔符粘前项尾部)、曲线图组补 HTML 图例(系列名)、图例色块内联数组(常量声明没落地导致 SSR 取到 undefined 而崩)。⑤ 进度:**overview · energy · component · fault · vibration · generation 六个页签过对拍**;余 decision/assistant/report/system 四个。'),
- dict(version='2.24.2', date='2026-09-29', level='patch',
- title='generation 继续推进:补曲线图组 HTML 图例、候选机整串渲染;对拍仍受 /api/curves 建设态影响',
- note='仍未过对拍 ⇒ 不列入 DONE,小版本 +1 记进度。① 本轮修:候选机列表必须**整串拼 HTML**(旧模板 `join(\' | \')` 把分隔符粘在前一项尾部;我原先用 `<span v-if>` 插在项前 ⇒ 文本节点边界不同、第 47 项起全错位)→ 改 `v-html`;曲线图组补**HTML 图例**(旧 `multiline` 返回的 `.legend`,系列名如"全场中位"/"29# +123.7kW")。② 实逮的稳定性问题:`/api/curves` 首次常是 `building`(服务端后台算),同一轮对拍里基准可能抽到骨架屏(56 条文本)、也可能抽到出图后(122 条)⇒ 对拍**不可复现**。已让桩"追到算出图的那一份再回放"(最多 8 次 × 3 秒),但服务端算得慢时仍会退化。③ 下一轮:把"等出图"做成对拍工具的**显式前置条件**(先把 /api/curves 追到 figs 再加载页面),再逐条收剩下的文本差异。④ 已迁 5 页签复验仍全过。'),
- dict(version='2.24.1', date='2026-09-29', level='patch',
- title='generation 页签迁移进行中(结构/三图/曲线图组已落地,对拍尚差 25 条文本)',
- note='未过对拍 ⇒ 不列入 DONE,版本按小版本 +1 记进度。① 已落地:`Generation.vue` 逐字转写旧 `generation()`(固定窗提示 · m9 控制面板 · 候选机列表 · 曲线图组),`capAxis`/`betaBar`/`rulerBar`/`multiline` 四图改 **ECharts**(`__data__` 只放旧图写成文字的数字:两行中位/β 中位/规范线 3.7);`/api/curves?win=…` 与 cms 同法接进对拍(新增"追到算出图的那一份再回放",因为首次常是 building)并透传 `renderTab(..., curves)`。② 实逮并修:`Generation.vue` 误从 `lib/render` 导入 `dv`/`esc`(rollup 报 not exported)⇒ 改从 `lib/vib`;β 注里的机组链接必须 `v-html`(否则尖括号被转义、节点边界与旧页不同)。③ 当前对拍:`generation` **97 ≠ 122 条文本**、图数字 220/4(子集满足)。缺口集中在 **β 偏置图的 HTML 注解**("38# +7.184kNm"、"虚线=物理绝对锚 ±0.25kNm … | 样本 179")与**曲线图组的图题/单位**("L2 功率-桨距 (控制律/削峰)"、"(°)")—— 下一轮按定点取证逐条补齐。④ 已迁 5 页签复验仍全过。'),
- dict(version='2.24.0', date='2026-09-29', level='minor',
- title='vibration 页签迁移完成(5 个子视图全过对拍)⇒ 已迁页签 5/10',
- note='用户令「vibration(含 5 子视图)→ generation → … 直到全部完成」的第 1 个页签落地 ⇒ 中版本 +1。① 五个子视图**全部**通过严格对拍:`matrix` 561 文本+1 表(例外 1)· `loop` 9 文本(该子视图数据多为空,走 `loop_none` 分支)· `units` 1126 文本+2 表 · `events` 492 文本+9 表 · `cms` 158 文本+2 表。② `cms` 的数据来自**按需异步** `/api/vibcms`:对拍工具新增"记录并回放该响应"(与 fleet 同法:只认第一次,并喂给 Vue 的 `renderTab(d, tab, sub, cms)` → Workbench `cms` prop),基准侧点 cms pill 后多等 4 秒;实逮一次"改了桩但 baseline() 没把 cms 返回出去"(Vue 侧一直显示"载入振动评估结果…")⇒ 已修。③ vibration 列入 `src/lib/tabs.ts` 的 DONE;界面不再标"迁移中"。④ 进度:**overview · energy · component · fault · vibration 五个页签过对拍**;余 generation/decision/assistant/report/system 五个。'),
- dict(version='2.23.3', date='2026-09-29', level='patch',
- title='vibration 的 events 子视图迁移完成(对拍 492 条文本 + 9 张表一致)',
- note='逐子视图推进 ⇒ 小版本 +1。① 旧 `fusEvents`(app.js:946-958)逐字转写为 `eventsHtml(F, d)`:按部件出"报警码统计 + 近况明细"与"工单明细"两张表。② 同时把 `cms` 子视图的构件(`gc`/`chainStates`/`evLadder`)与 `cmsHtml(V)` 一并落地(cms 的数据来自按需异步 `/api/vibcms`,**尚未对拍**)。③ 本轮排障记录(已全部解决):"原样抽取"把下一行一起带进来 + 多次追写叠加,导致 `gc`/`evLadder` 重复声明、并留下孤立的缩进行(esbuild 报 symbol already declared / Unexpected export)—— 逐条定位删除后 SSR 构建通过。④ 对拍:`vibration:events` **492 条文本 + 9 张表一致**,0 例外;`vibration:units` 复验 1126 条仍一致。⑤ 进度:vibration 5 子视图 **matrix ✓ loop ✓ units ✓ events ✓**,余 `cms`(需把 `/api/vibcms` 的按需数据接进对拍);`vibration` 仍未列入 DONE。'),
- dict(version='2.23.2', date='2026-09-29', level='patch',
- title='vibration 的 units 子视图迁移完成(对拍 1126 条文本 + 2 张表一致)',
- note='逐子视图推进 ⇒ 小版本 +1。① 逐字转写旧 `fusUnits`(app.js:924-944)为 `unitsHtml(F, filter)`,并把它用到的构件 `devBar`/`winBar`/`oilPill`/`stateChip` 一并落地;`devBar`/`winBar`/`oilPill` 与它依赖的 `CHAIN7` 常量一律**从 app.js 原样抽出**(不手抄)。② 实逮三处并修:`displayText` 未定义(旧 app.js 的显示文案函数,中文口径=原样);`CHAIN7` 未定义;追写叠加导致 `chainMini` 重复声明(且 CHAIN7 抽取把后一行 `const chainMini` 一并带进来)⇒ 去重并删杂散片段。③ 对拍:`vibration:units` **1126 条文本 + 2 张表一致**,0 例外。④ 进度:vibration 5 子视图 **matrix ✓ loop ✓ units ✓**,余 `events`/`cms`;`vibration` 仍未列入 DONE。'),
- dict(version='2.23.1', date='2026-09-29', level='patch',
- title='vibration 的 loop 子视图迁移完成(对拍 9 条文本一致)',
- note='逐子视图推进 ⇒ 小版本 +1。① `vib.ts` 追写 `baBar`/`chainMini`(chainMini 的整串正则替换**从 app.js 原样抽出**)与 `loopHtml`(旧 `fusLoop` 逐字转写);`Vibration.vue` 接上 `loop` 子视图。② 对拍:`vibration:loop` **9 条文本一致**(说明:本机该子视图的数据多为空 —— 走的是 `fus.loop_none` 分支,所以证据量小;结构与文案一致性仍由对拍保证)。③ 进度:vibration 5 子视图中 **matrix ✓ loop ✓**,余 `units`/`events`/`cms`;`vibration` 仍未列入 DONE。'),
- dict(version='2.23.0', date='2026-09-29', level='minor',
- title='vibration 的 matrix 子视图迁移完成(5 子视图中的第 1 个),对拍 561 条文本一致',
- note='逐子视图推进 ⇒ 中版本 +1。① `Vibration.vue` + `src/lib/vib.ts`:把旧 `fusMatrix` 与它的构件(`spark`/`famBar`/`duo`/`tierWord`/`shortOf`/`cell`/状态色表)**逐字转写**,用 v-html 渲染 —— 旧代码本就是拼 HTML 字符串,转写后 DOM 与文本节点边界天然一致。② `VIBJARGON`(技术速记→人话,6 行长正则数组)改为**从 app.js 原样抽出**(`src/lib/vibjargon.ts`),避免手抄漏项。③ pill 键名实逮:旧用 `fus.sub.<s>`(我写成 `fus.s.<s>` ⇒ 5 个 pill 全显键名)。④ 对拍结果:`vibration:matrix` **561 条文本 + 1 张表一致**(含 1 条登记例外);既有四页签重跑仍全过。⑤ **vibration 仍未列入 DONE** —— 余下 4 个子视图(loop/units/events/cms)未迁;cms 还需按需异步取 `/api/vibcms`。⑥ 对拍门增强:例外清单改为"包含匹配"并**同时作用于文本与表格**。'),
- dict(version='2.22.1', date='2026-09-29', level='patch',
- title='对拍门支持子视图(页签:子视图);vibration 侦察结论入档',
- note='进行中 ⇒ 小版本 +1。① 对拍门新增**子视图**口径:`node tools/render_parity.mjs <网关> vibration:matrix` —— 基准侧加载后**点一下**对应 pill(旧页面 `data-fsub`)再抽锚点,Vue 侧把 `sub` 传进组件;`renderTab(d, tab, sub)`、Workbench 增 `sub` prop、页签文案比对含 `[data-fsub]`。② vibration 侦察结论(本轮实测):该页签内部分 **5 个子视图**(matrix/loop/units/events/cms,旧 `FUS.sub`),**不含图表**(无需 ECharts),但依赖约 10 个 SVG/HTML 小构件(`spark`/`famBar`/`baBar`/`winBar`/`devBar`/`chainMini`/`chainStates`/`oilPill`/`evLadder`/`gc`),且 `cms` 子视图按需异步取 `/api/vibcms` ⇒ 需逐子视图迁并对拍。③ 四个已迁页签重跑仍全过(overview 138 · energy 51+图11/11 · component 260 · fault 133 含 1 例外)。'),
- dict(version='2.22.0', date='2026-09-29', level='minor',
- title='fault 页签迁移完成(4/10 过对拍);对拍门加“显式例外”并记下后端一处口径文案不一致',
- note='用户令「迁 energy/component/fault 三页签」的第三个落地 ⇒ 中版本 +1。① `fault`(故障统计)在**严格整页签**口径下与重构前呈现一致:文本 133 条(含 1 条登记例外)、2 张表全同、图数据 132 ⊆ 旧图。本轮补齐的关键三处:**Pareto 图的 HTML 摘要**("关键少数: 前 N 项占 80%"、"前三: <b>码</b> 名 · …"、note —— 旧 `paretoChart` 返回的 HTML 而非 SVG,用定点取证 `tools/probe_baseline.mjs` 查明)、**季节性图的 HTML 图例**(旧 `multiline` 的 `.legend`,4 条系列名)、**rate 示例的守卫条件**(旧代码 `i0>=0 && mo.per_day` 即便值为 null 也照样渲染)。② 图数据子集规则下把 `__data__` 收敛到"旧图真的写成 SVG 文字的数字"(柱状/条形图的值;散点/折线只标刻度故为空),并改正两处口径:`ramChart` 旧为 **MTBF×MDT 散点**、`attribBar` 旧分组为 **ok=总−无记录−未归类 / unk / un2**。③ 对拍门加**显式例外清单**(ALLOW,每条必须写理由并在结论里报数):已登记 1 条 —— `m8.mtbf.口径` 在后端**两条路径**上文案不一致(页面的按窗缓存给"(MTBO统计方法)/(纯故障统计方法)",实时 `/api/fleet` 给"(MTBO口径)/(纯故障口径)");两侧同字段同模板 ⇒ 非前端偏差,**待后端统一口径后删除该例外**(已作为遗留项记录)。④ 顺带实逮并修正对拍工具:页面有 pending 轮询,桩若记录"最后一次"响应,两侧数据版本会不同 ⇒ 改为"记录页面**第一次**拿到的 /api/fleet 并回放给后续请求"。⑤ 进度:**overview · energy · component · fault 四页签过对拍**;余 vibration/generation/decision/assistant/report/system 六个。'),
- dict(version='2.21.2', date='2026-09-29', level='patch',
- title='fault 对拍推进:Pareto 图内 HTML 摘要逐字复现(含前三),图数据改用原值',
- note='进行中 ⇒ 小版本 +1。① 定点取证 `tools/probe_baseline.mjs`(在旧页面 DOM 里按文本找父元素)实逮:旧 `paretoChart` 除 SVG 外还返回一段 HTML —— `<b>关键少数: 前 N 项占 80%</b> (共 M 项, 合计 X unit)<br>前三: <b>码</b> 名 · …<br>note`;本版按同构 v-html 复现(三处 Pareto:次数/时长/停机),文本差异由 133≠103 收敛到 **133≠129**。② 图数据比对:`__data__` 改用**原图打印的原值**(旧图 `txt(..., v)` 直接写值,不四舍五入);并把 ramChart(旧为 MTBF×MDT 散点)与 attribBar(旧分组 ok=总−无记录−未归类 / unk=无记录 / un2=未归类)的口径改正 —— 此前我给的是"停机时长柱/设备·外部·无记录",属**口径用错**。③ 仍差 4 条文本:停机 Pareto 摘要"前三"的第 2/3 项与其分隔符(“1020 / 就地操作模式 · / 2700 / 塔筒频率外部允许窗口”)与一处注文顺序;`fault` **仍在 IN_PROGRESS**,不列入 DONE。'),
- dict(version='2.21.1', date='2026-09-29', level='patch',
- title='fault 页签迁移进行中(未过对拍,如实标注)+ 对拍门两处口径修正',
- note='进行中 ⇒ 小版本 +1。① `Fault.vue` 已按旧 `fault()` 实现全部区块(警报面:月度故障率 · 次数/时长 Pareto · 可靠性×可维修性象限 · 逐台故障表 · 季节性;停机面:RAM 分解 · 停机 Pareto · 停机台次表),图表用 ECharts;但**严格整页签对拍尚未通过**(当前 133≠103 文本、图数字子集不满足),故不列入 DONE,界面标"迁移中"(新增 `IN_PROGRESS` 常量)。② 本轮对拍门两处口径修正:(a) 图数据比对由"集合相等"改为**子集**(旧图把数据与坐标刻度都写成 SVG 文本,ECharts 刻度在画布上取不到;子集规则抓"画错/画多",不苛求刻度);(b) 文本比对明确要求**文本节点边界**一致(旧模板一个插值里拼多段 = 一个节点)。③ 本轮已修:`winBanner` 的 " | " 与 span 分开、`flt.rate_example` 取值、pareto 图的 HTML 摘要("关键少数…"/"(共 N 项, 合计 X)")。④ 已过对拍的仍是 overview/energy/component 三页签。'),
- dict(version='2.21.0', date='2026-09-29', level='minor',
- title='三页签通过严格整页签对拍(overview/energy/component)· ECharts 图表数据级一致',
- note='用户令「迁 energy/component/fault 三页签并逐页签对拍」的收口:**overview / energy / component 三页签在严格整页签口径下与重构前呈现一致** ⇒ 中版本 +1。① 对拍门(`app_frontEnd/web/tools/render_parity.mjs`)本版实测:overview 138 条文本全同;energy 51 条文本 + 1 张表 + **图表数字集合 11/11 一致**;component 260 条文本 + 2 张表全同。② 逐条修掉的偏差(每条都由对拍指出,不是靠眼看):a) 页头窗徽应为 `t(win.label) + " · " + winName(win)`(我写成 `win.l`+值);b) 旧 `CH.blameBar` 的图例是 **HTML 三段**(名称/数值/说明)不是画布 ⇒ 必须照渲,否则少 9 条文本;c) 瀑布图数字集合口径 = theo · act · Σitems · 各 item · 组占比%(ECharts `__data__` 按同一公式给数);d) 文本**节点边界**也要一致:KPI「报警台数」旧模板是 `<b>28 <small>/ 38</small></b>` 两个节点(用 v-html 复现)、九系统卡计数三段拼成**一个**插值、命中系统格用 v-html 复现"锚+文本分隔符+锚";e) 块顺序:旧页面是 KPI → 需要关注 → 全场状态 → **九系统**(我原先把九系统放在状态条之前);f) `cmp.rel_meta` 传**原始值**不 fmt;`rel_note` 与 `cmp.fusion_n`("(107 台次收录)")漏了要补;g) 徽章前空格会被 Vue 模板空白压缩吃掉 ⇒ 用 `{{ \' \' }}` 显式给。③ fault 页签**仍未迁移**(下一步);总览/电量/部件三页签的 Python 前端源码**暂不删**(需全 10 页签通过)。④ 客户端产物 release/web 重建;契约冒烟 42/42、HTTP 契约门 42/42、模块边界门 OK。'),
- dict(version='2.20.0', date='2026-09-29', level='minor',
- title='ECharts 引入 + Java 网关/OAuth2/Security 落地;呈现对拍门升级为“严格整页签”',
- note='用户令 2026-09-29(第二轮):引入 ECharts;补齐 spring-cloud-gateway / oauth2 / security 构件;迁 energy/component/fault 三页签并补打四个模块包。⇒ 中版本 +1。① **构件获取(实逮纠正)**:上一轮判"Maven Central 不可达"是**瞬时探测失败** —— 实际 repo.maven.apache.org / maven.aliyun.com / repo.huaweicloud.com / mirrors.cloud.tencent.com 均可达。新增 settings-online.xml(阿里云 public 镜像)把 spring-cloud-starter-gateway 2021.0.9、spring-boot-starter-security、spring-boot-starter-oauth2-resource-server、spring-security-oauth2-jose、nacos-discovery 2021.0.5.0 **下到用户本地仓**(`mvn -s settings-online.xml compile` 成功)。② **离线可复现**:settings-offline.xml 补与联机**同名**的 mirror/repository id,并加 `-Dmaven.legacyLocalRepo=true`(实逮:来源仓库 id 不一致会让 `mvn -o` 误报"不可达");Nacos 依赖显式钉版本(离线时 BOM 导入不参与解析)。③ **Java 网关独立成工程** `app_backEnd/backend-gateway/`(实逮:Spring Cloud Gateway 走 WebFlux,与本工程 MVC+springfox 同进程不兼容):Spring Cloud Gateway + OAuth2 资源服务器 + Nacos(可关);端口 28085,与现 Python 网关 28084 并存、按前缀灰度。实测经它 `/algorithm/healthz`、`/web/index.html`、`/detail/api/facts` 全 200。④ **OAuth2+JWT 闭环**(业务后端):`/java/token` 自签 HS256(无 IdP 的离线口径)、`/java/secure/whoami` 带 token 200(authenticated=true, SCOPE_guanlan.read)、**不带 token 401**;Swagger UI 仍 200。实逮两坑并已修:Nacos 在 classpath 就抢连(需 `spring.cloud.nacos.discovery.enabled=false`)、application.yml 出现两个 `guanlan:` 顶层键导致 snakeyaml DuplicateKey。⑤ **前端(进行中,未完成)**:引入 ECharts 6.1.0 与 `EChart.vue` 包装;`Energy.vue`(4 KPI + 水位图/责任分账两图 + 工况态表)、`Component.vue`(九系统卡 + 可靠性表 + 跨系统关注台表 + 系统详情)已迁;`fault` 未开始。语言包改为**全量**搬(845 键,此前只挑前缀导致 `en.*/cmp.*` 取不到词)。⑥ **对拍门升级**:`tools/render_parity.mjs` 从"分块锚点"改为**整页签**比对(`#root` 文本序列 + 表格逐行 + 图表数据数字集合;Vue 侧摘掉页签导航单独比),页签由 hash 驱动。当前它**如实报出未通过**:overview 138≠95 文本、energy 51≠42 文本与 11≠16 图数字、component 260≠243 文本 + 一处空格差异("运维操作 外部")。⇒ 上一轮"总览 11 组一致"更正为"总览前三个区块一致、整页签仍有差异"。⑦ 四个模块包已按本版补打(app_frontEnd/app_ETL/app_algorithmModel/app_backEnd)。'),
- dict(version='2.19.0', date='2026-09-29', level='minor',
- title='Vue 迁移第一个页签(总览)+ 呈现对拍门:jsdom 跑旧页面 vs Vue SSR,锚点逐项一致',
- note='三栈重写(前端)⇒ 中版本 +1。① 迁移:`app_frontEnd/web/src/views/{Workbench,Overview}.vue` —— 页签清单与重构前 app.js 的 TABS **逐字一致(10 个,含 system)**;总览页签按 app.js 的 `overview(d)` 逐块搬出 KPI 卡(6)、需要关注卡(含"查看完整依据"与卡在哪一步)、全场状态条(38 格 + 台号 + 静默台数);渲染口径函数(状态枚举/台号/数字格式/台级状态取最严)原样搬到 `src/lib/render.ts`,文案搬到 `src/i18n/zh.ts`(105 键,逐字取自现网语言包,由对拍复核)。② **呈现对拍门**(本轮核心):`app_frontEnd/web/tools/render_parity.mjs` —— 因为 `/detail/v2` 是浏览器里现渲的单页(HTML 只有壳 + 内联 app.js + 语言包),所以基线不能靠 curl,必须**让旧代码真跑一遍**:jsdom 加载现网页面 + 在 beforeParse 装 fetch 桩转发到真实网关(实逮:桩晚装一句就 `fetch is not defined`、基线全空)+ 等 `.kpis` 出现;再用**同一份数据**(桩记录下的 /api/fleet 响应)把 Vue 组件 SSR 出来,抽同一批锚点比对:KPI 值/文案/类名、需关注卡的 id/部件/状态词、状态条格子的类名/data-u/台号、静默文案、页签文案 —— 11 组全部一致。③ 附带:类名比较按**排序后的集合**(旧页 `kpi warn` / Vue `warn kpi` 只是属性顺序,实逮后归一)。④ 未迁移的 9 个页签在界面上如实标"迁移中",不造假界面;`tools/contract_smoke.mjs` 仍 42/42 一致。'),
- dict(version='2.18.0', date='2026-09-29', level='minor',
- title='后端 Java 化(P12 起步):Spring Boot 2.7.18 + springfox Swagger 离线可编可跑,与算法服务打通',
- note='三栈重写第三步(用户令 2026-09-29:后端完全 Java + Spring Boot + Swagger,与算法模块经接口交互;用户指定用本机 JDK 1.8 + Maven)⇒ 中版本 +1。① 工程 `app_backEnd/backend-java/`:Spring Boot 2.7.18 + spring-boot-starter-web + springfox-boot-starter 3.0.0 (Swagger UI + OpenAPI 3.0)+ RestTemplate 调算法服务;控制器 `/java/healthz`、`/java/algorithm/endpoints`、`/java/algorithm/{name}`(13 条只读白名单,与算法服务登记表同源);`application.yml` 端口 28120(与现有 Python 后端并存,迁移期两套同跑、逐条对拍)。② 离线实逮(本机仓残缺,逐条绕开并记在 README):`mvn package` 打不出 jar(缺 maven-archiver、plexus-utils 1.1);surefire 缺运行期依赖;缺 spring-boot-loader-tools ⇒ 不打 fat jar;springfox 运行期要 mapstruct(未传递);Boot 2.6+ 需 ant_path_matcher。因此改走 `mvn -o compile` + 自组装 `target/lib`(53~54 jar / 26.4 MB),由 `build_offline.py` 一键完成(环境变量优先、随工程带 settings-offline.xml,仓里不写死本机路径)。③ 实测证据:`/java/healthz` 200 且 `algorithm_ok=true`(Java→FastAPI 通)、`/java/algorithm/endpoints` 返回 11 个端点、`/swagger-ui/index.html` 200、`/v3/api-docs` 200(OpenAPI 3.0.3)。④ 交付与打包:Java 构建输出 `target/` 明确不进包(pack_dist EXCLUDE_DIRS);交付形态 = 随包 JRE + classes + lib。⑤ 迁移纪律:按冻契约 42 条端点逐条搬、逐条对拍(Python 门 + Node 冒烟),通过后才删对应 Python 源码;Gateway/Nacos/OAuth2 待依赖到位再接。'),
- dict(version='2.17.0', date='2026-09-29', level='minor',
- title='前端 Vue 化(P11 起步):Vue3+Vite+TS 工程落 release/web、经网关 /web 同源伺服、契约冒烟两侧一致',
- note='三栈重写第二步(用户令 2026-09-29:前端完全 Vue.js + Node.js,与后端结构交互;用户选定"先冻契约 → 前端 Vue 优先")⇒ 中版本 +1。① 工程 `app_frontEnd/web/`:Vue 3 + Vite 6 + TypeScript + Node 24(依赖取自可达的 registry.npmmirror.com,`npm install` 50 包 13 秒);`npm run build` → `release/web/`(index.html + assets,gzip 后约 28 KB);取数入口 `src/api/contract.ts` 只走**相对路径**(/api、/algorithm),与冻契约 `http_api_v1.json` 的路径一一对应。② 运行体系接入:`configs/serve.json` 加 `web=28110`;`cli.services()` 加 web 静态组件;网关加路由 `/web` → 28110 —— 于是 Vue 页面与后端/算法**同源**,页面里的相对取数直接可用。③ 两侧对拍:`scripts/http_contract_audit.py`(Python 门,已在 guanlan.py check 里)与 `app_frontEnd/web/tools/contract_smoke.mjs`(Node,走同一网关同一批路径)—— 实测均 **42/42 一致**,Vue 前端 `/web/index.html` 200 且挂载点就位。④ 契约支持"异步构建态":`/api/curves` 记为双形状(`json_keys_any`)—— 产物仓在位返回 figs/physics、后台构建中返回 building/months/span/win,两者皆合法(node 侧冒烟曾据此实逮误报)。⑤ v2 工作台逐页签迁移与"删对应 Python 前端源码"留待下一阶段(对拍通过后再删)。'),
- dict(version='2.16.0', date='2026-09-29', level='minor',
- title='冻契约:现有 HTTP 面固化成契约正本 + 在线复核门(并入 guanlan.py check)',
- note='三栈重写第一步(用户令 2026-09-29:前端 Vue / 后端 Java / 算法 FastAPI,"确保与重构前一致";用户选定"先冻契约 → 前端 Vue 优先")⇒ 中版本 +1。① 抽面:从 serve.py / windcms serve.py / gateway.py / 算法登记表 / 壳与 JS 里抽出 53 个候选路径;② 逐路径**双方法探活**(GET+POST),据实定方法/状态/形状 —— 42 条至少一种方法返回 200;③ 契约正本 `app_backEnd/app_backEnd_guanlan/contract/http_api_v1.json`(42 条端点:类别/路径/方法/状态/JSON 顶层键或 HTML 标题)+ 人读版 `docs/接口契约_前后端_v0.1.md`;④ 复核门 `app_qualityGate/.../audits/http_contract_audit.py`(CLI 壳 scripts/http_contract_audit.py)并入 `guanlan.py check`:方法/状态码/JSON 顶层键必须与契约一致(值随数据变不比),实测 **42/42 一致**、check 全绿。它是 Vue/Java/FastAPI 三条线的对拍口 —— 任何一条偏离都会在这里报出来。'),
- dict(version='2.15.2', date='2026-09-29', level='patch',
- title='算法服务随 serve 默认起(并入网关 /algorithm,端口取 configs/serve.json)',
- note='用户令 2026-09-29「算法服务随 guanlan.py serve 默认起」⇒ 小版本 +1。① `cli.services()` 增加 algorithm 组件(端口取 `configs/serve.json` 的 algorithm,缺省 18050)—— `guanlan.py serve` / start.bat / 服务化启动都会连带起算法服务;② 统一网关加路由 `/algorithm` → 上游 18050(探活 /healthz):前端与后端(各栈)都从**一个源站**调算法,不必直连端口;③ `ops.py` 的 DEFAULT_PORTS 加 algorithm(运维控制台兜底端口表);④ `scripts/guanlan_offline_up.sh` 加 18050 启动行;⑤ `configs/registry.yaml` 里 serve.json 的 schema 加 algorithm 键。验收:`guanlan.py stop` → `guanlan_start_hidden.py`(= serve)后,直连 http://127.0.0.1:18050/healthz 与经网关 http://127.0.0.1:28084/algorithm/healthz 均 200,`/algorithm/api/endpoints` 返回 11 个端点;门户与其它组件不受影响;全门禁与 `guanlan.py check` 全绿。'),
- dict(version='2.15.1', date='2026-09-29', level='patch',
- title='算法服务消缺:日志纳入统一格式(logs/algorithm.log)+ 端点登记表按实测收口 + 模块入口可起服务',
- note='缺陷修复 ⇒ 小版本 +1(2.15.0 同日)。① 日志:算法服务原先用裸 stdout 重定向,`guanlan.py check` 的"日志统一"门禁实逮 FAIL(logs/algorithm_service.log 末尾 104 行都不符合 "时间戳 级别 组件 消息");现改为入口调 logfile.prefix_stdout(\'algorithm\')(与 detail/cms/gateway/static_server 同一套设施),日志落 logs/algorithm.log,门禁转全绿。② 端点收口(逐值对拍时实逮):撤掉 availability(=perf.availability.build() 只写盘返回 None,不是接口);撤掉 matrix/pitch/curves/reliability 的 span 参数(这些函数的 span 是**跨度对象**不是窗口字符串,传字符串必炸:Given date string "2" not likely a datetime / too many values to unpack);保留实测可用的 reliability.since、fusion.all_turbines、availability_summary.month_from。③ 模块入口:bootstrap 的 app_algorithmModel 增加 service 入口(run.bat service / sh run.sh service → 起 FastAPI 服务,默认 18050),app_algorithmModel/README.MD 补"算法服务"一节(端点表 + 离线依赖 + 对拍口径)。验收:11 个端点 + 3 个带参抽查逐值对拍一致;guanlan.py check 全绿;总包开箱 5/5 + 卸载核验;算法模块包自检通过。'),
- dict(version='2.15.0', date='2026-09-29', level='minor',
- title='算法服务化(P10):算法层包成 FastAPI 服务(11 个只读端点 + OpenAPI/Swagger),离线依赖随包',
- note='功能新增(目标技术栈第一步)⇒ 中版本 +1。用户令 2026-09-29:前端 Vue/TS/Node · 后端 Java + Spring Cloud Alibaba(Nacos/Gateway/OAuth+JWT)+ Spring Boot + Swagger + MyBatis-Plus · **算法 Python + FastAPI**;经确认本期只做算法一步。① 实现 app_algorithmModel/app_algorithmModel_guanlan/service/{app,registry,jsonable,deps}.py:FastAPI 应用 + 端点登记表(判级矩阵/对账/可靠性/问题清单/可用率摘要/融合面/交接件/变桨面/曲线仓/曲线检查/维度字典)+ 确定性 JSON 归一化 + 依赖兜底;入口 scripts/algorithm_service.py(端口 18050,登记 configs/serve.json 的 algorithm)。② 只读:带写盘副作用的参数在登记表里固定成只读值(curves.write=False)。③ 离线依赖实逮:目标机离线且 wheels/ 无 FastAPI 轮子,而开发机 .venv 是 include-system-site-packages (fastapi/uvicorn 来自系统 Python)⇒ 把运行栈整目录随包 vendor/pyfastapi/(15.9 MB,含 dist-info),ensure_fastapi() 在导入失败时插 sys.path,并已按"纯 stdlib + 该目录"验证可导入。④ 验收:逐值对拍 —— /api/<端点>/raw 的确定性 JSON 与进程内直调逐字节一致(sha256 相等),11 个端点 + 3 个带参抽查全部一致(曲线仓 69.4 万字节 81 秒、判级矩阵 2.1 万字节)。⑤ 零行为变化:不改现有进程内调用链,页面/CLI/产物不变;是否随 serve 默认启动留待后续。文档:设计说明新增 §3.5 算法服务化。'),
- dict(version='2.14.0', date='2026-09-29', level='minor',
- title='分别打包:前端 / 后端 / 算法 / 数据接入与管理 各自成包(含依赖闭包 + 独立 install/run/uninstall)',
- note='功能新增(打包形态)⇒ 中版本 +1。用户令 2026-09-28「前端、后端、算法、数据接入及管理 支持分别打包」。① 打包器加 `--module <名>`:产物 app_guanlang_v<版本>_<模块>.zip;**模块包 = 该模块 + 它依赖的模块(按 configs/modules.yaml 的 allow 递归闭包)+ 公共层 + 共享支撑件**(src 兼容壳 / scripts / configs 单件 / wheels·vendor 离线依赖 / reference;前端与后端另带 release·resources,算法带 resources)。② 包根写 MODULE_PACKAGE.json(模块 + 闭包 + 版本),模块目录里的 install/run/uninstall(bat+sh)改**双模**:总包内调安装根统一入口;独立模块包走新增的 app_common/.../bootstrap.py(自建 .venv、离线装依赖、自检、按模块起服务或跑链、卸载)。③ bootstrap 自检覆盖:目录与 6 个入口脚本、版本一致、配置单源、公开面可导入;run 支持 --cmd(后端 serve/check/status/stop;前端 portal 静态服务;ETL/算法按脚本名跑,ETL 默认 --dry-run);list 列出各模块可运行入口。④ 新增门:根 VERSION 镜像文件必须与 version.py 同号(version_log --check 里查 —— 此前它是 0.2.0 的历史遗留,与真源脱节)。⑤ 打包验证加 `--verify <zip> --module <名>`:解压 → 入口引用闭合与编码守则 → bootstrap 自检 → 可运行入口列举。验收:四个模块包各自出包并验证通过;总包开箱 5/5 + 卸载核验;全门禁 rc=0;guanlan.py check 全绿。'),
- dict(version='2.13.0', date='2026-09-28', level='minor',
- title='P9 源码目录结构变更:实现包上提模块根 + 每模块 common/configs/data/README/入口脚本 + 配置双根',
- note='用户令 2026-09-28 变更重构要求(架构布局变更)⇒ 中版本 +1。① 实现包上提:app_<模块>/common/app_<模块>_guanlan/ → app_<模块>/app_<模块>_guanlan/(7 模块),全仓点号 540 处 + 实路径 190 处一次改写(src/** 壳、scripts/** 壳、api.py、安装器、打包器、门禁、文档)。② 旧前缀兼容:common/app_<模块>_guanlan/__init__.py 用 sys.meta_path 前缀重定向(sys.modules[旧名] is sys.modules[新名],不产生两份模块对象),保留一个版本。③ 每模块补齐 common/(模块内通用组件)、configs/(模块专属配置)、data/(数据件预留)、README.MD(含安装部署与运行)、install/uninstall/run(bat+sh,内部调安装根统一入口;纳入入口引用闭合,92 条)。④ 配置双根:app_ETL/configs/{canonical,contracts,farms}(164 件)与 app_frontEnd/configs/terms(3 件)下放;安装根 configs/ 只留共用单件(serve.json/models.json/portal_pages.yaml/modules.yaml/registry.yaml);P.config()/P.config_dir() 多根查找 + 单源校验;登记表加 owner,config_audit 按归属根校验。⑤ 门禁联动:模块边界 R1/R2/R5 改新布局(并修 _TARGETS 带注解写法漏检);uninstall --all 纳入模块目录。验收:189 个模块文件新旧前缀同体导入;全门禁 rc=0;guanlan.py check 全绿;起服务逐页(/detail/v2 唯一差异为页内注释里的配置路径 +13 字节);打包开箱 5/5 + 卸载核验通过。'),
- dict(version='2.12.1', date='2026-09-28', level='patch',
- title='源码梳理与死代码清理:删 cms_availability(275 行,另有实现)+ 清本地残留 15.4 MB',
- note='清理类改动(不改行为口径)⇒ 小版本 +1。用户令「梳理源代码,及清理无用源代码程序」。新增 docs/源代码梳理_v0.1.md(761 受管件 / 437 .py / 72,787 行 / 158 兼容壳的规模与分层、十条根入口、23 个质量门、依赖纪律 R1–R8,以及按"引用计数 + 出处"取证的五类无用件清单)。① 删死代码: app_ontology/.../sop/cms_availability.py(275 行)与壳 src/sop/cms_availability.py(23 行)——全仓无调用,L0 数据可用性判据的实际实现在 scripts/rudong_model_run.py::l0_layer;并从 app_ontology/api.py 摘掉 sop_cms_availability 公开名。④ 清本地残留: scripts/sim_hub/frames/(37 张 PNG,8.7 MB,本就 .gitignore)+ 全仓 46 个 __pycache__ 与散落 .pyc,共释放约 15.4 MB(.venv 未动)。②③ 两批(8 件零引用一次性生成器、33 个无人使用的旧路径壳)经用户裁示保留;C 类接入点契约(MinIO objectstore / Redis cache)保留。验收: 全门禁 rc=0(模块边界 R1–R8 / 配置统一 / 页面归口 / 产物反向呼应 / 链路缺口 / 可移植 / 版本记录 / 依赖台账)+ guanlan.py check 全绿 + 起服务逐页探活 + 打包开箱 5/5 + 卸载核验。'),
- dict(version='2.12.0', date='2026-09-28', level='minor',
- title='模块化重构收官 P7:质量门与打包安装迁移(23 件 / 7,426 行)——七个模块齐备',
- note='重构收官的架构里程碑(七个模块目录 + 公开面 + 兼容壳齐备)⇒ 中版本 +1。P7 把质量门与打包安装 23 件 / 7,426 行迁入 app_qualityGate/app_qualityGate_guanlan/{audits,docs,pack}/;旧路径 scripts/<件>.py 留 CLI 壳(公开名同面转发 + main(),guanlan.py check 的 importlib 载入照旧)。**根引导脚本 install.ps1/install.sh/uninstall.*/check.bat/pack.*/start|stop.bat 原地不动**("解压即用"引导层,其 Python 实现已迁入模块)。实逮: ① 子包内 __file__ 取根(parents[1])失效 22 处 ⇒ 统一 install_root();② 文件间 bare import 4 处改相对导入;③ audit_chinese_terms 的 here.parent 是仓库根语义 ⇒ 换 install_root();④ 补 P4 遗留:该审计器被审件 scripts/windscada_serve.py 早已是壳 ⇒ 改读实现位置。验收: 8 条质量门 stdout 逐字节等于迁移前 + 23 个 CLI 壳 importlib 公开名同面 + 新路径 -m 可跑 + 打包器从新位置出包并开箱验证 5/5 + 卸载核验通过 + guanlan.py check 全绿。至此 P0–P7 全部完成:公共/数据接入/算法/本体/前端/后端/质量门各一个目录与 api 公开面,旧路径全部留兼容壳。'),
- dict(version='2.11.9', date='2026-09-28', level='patch',
- title='模块化重构 P6:本体与知识层实体迁移(58 模块 / 19,775 行)',
- note='重构推进(源码组织层)⇒ 小版本 +1。本体 20 件 + SOP 38 件迁入 app_ontology/app_ontology_guanlan/{ontology,sop}/;旧路径 src/ontology/<件>.py、src/sop/<件>.py 全部留兼容壳 (模块别名 + `python -m` 经 runpy 重跑实现,重算链 ⑦ 步命令不变)。实逮: ① 包内 `from .. import paths`(23 处)改绝对路径;② 19 处 __file__ 取根改 install_root();③ `-m` 入口必须由壳 runpy.run_module(run_name="__main__") 兜住;④ 按源码路径读本层的审计器与登记表(audit_chinese_terms、check_portability 的 ALLOW 键、products_reverse_audit 的生成端、configs/{registry,terms/jargon_rules}.yaml)同步改位置。验收: 58/58 模块新旧路径同体导入 + 取根抽查正确 + 十条本体/SOP 路由响应体逐字节等于迁移前 + `-m` 旧入口冒烟通过 + 全门禁 rc=0 + 开箱验证 5/5。'),
- dict(version='2.11.8', date='2026-09-28', level='patch',
- title='消缺:guanlan.py stop 在 pids.json 读不动/删不掉时抛异常中断',
- note='消缺(不改行为口径,只修坏掉的路径)⇒ 小版本 +1。根因: `cli.py::cmd_stop()` 直接 `PIDS.unlink()`,而**服务宿主与启动器并存**时 run/pids.json 可能已被清掉、被句柄占用或读到一半;2026-09-28 远端同步 2.11.7 时实逮:`guanlan.py stop` 以 FileNotFoundError 中断,组件其实已停掉。修法: 读记录失败按空记录处理(如实报一句);删记录失败只报一句,不影响停机结果与退出码;`v.get("pid")` 容忍记录缺字段。回归: pids.json 不存在 / 是目录(读+删都失败)/ 内容损坏 三种情形均不抛异常且 rc=0。验收: 本机 `guanlan.py stop` 正常停六个组件并删记录、再跑一次给友好提示 rc=0;三情形回归 3/3 通过;全门禁 rc=0。'),
- dict(version='2.11.7', date='2026-09-28', level='patch',
- title='模块化重构 P5:前端层实体迁移(13 件;经典页模板从 serve.py 抽出)',
- note='重构推进(源码组织层)⇒ 小版本 +1。前端层迁入 app_frontEnd/app_frontEnd_guanlan/:assets/{app.js,app.css,charts.js,favicon.svg,classic_chart.js}、pages/{shell.html,build.py,snapshot.py,classic.py,classic_*.html}、design.py、lang.py、terms.py(经用户裁:语言包与显示层术语归前端)、builders/{portal_build,portal_fix_anchors,portal_inject_claims}.py。serve.py 的经典页模板(CHART_JS/PAGE_FLEET/PAGE_PROBLEM/PAGE_TURBINE,约 246 KB)抽成真资源件 + 只读装载器,serve.py 5549 → 2707 行,模板值逐字节一致。旧路径全部留壳(5 个模块别名壳 + 3 个 CLI 壳,python -m 照旧)。实逮:① 资源件无壳可留 ⇒ 六个按路径读它们的审计器/登记表同步改;② 子目录取根层数变 ⇒ 统一 install_root();③ 模板装载必须 newline=""(否则换行翻译改掉字节);④ 搬迁中被工具改写的换行风格要按 HEAD blob 对齐,避免整文件伪 diff。验收:build.render 四组合与 design 两段 CSS 逐字节等于迁移前;四个模板常量与三张经典页渲染逐字节一致;portal_build --verify 与 20.19 MB 门户逐字节一致;服务重启后门户//detail/v2//ops//cms/ 页面体 sha256 一致;全门禁 rc=0;开箱验证 5/5。'),
- dict(version='2.11.6', date='2026-09-28', level='patch',
- title='消缺:服务 restart 的等待判据把 STOP_PENDING 当成已停(远端实测吃 1056)',
- note='消缺(承 2.11.5,同一动作的判据收紧)⇒ 小版本 +1。2.11.5 把 `sc.exe restart` 拆成"停 → 等 → 起"后,等待判据写成"状态不是 RUNNING 就算停稳"—— 而 `sc.exe stop` 是异步的,SCM 先回 **STOP_PENDING**,该判据立刻为真,于是 start 抢跑,远端 Windows 服务上实测报 1056(服务的一个实例已在运行),网关/仿真/三维端口被停掉约一分钟(站点短暂不可用)。修法: 新增 `win_state()`(按 STOP_PENDING / START_PENDING / RUNNING / STOPPED 顺序认状态字)与 `wait_state("STOPPED", 120s)`,必须**等于 STOPPED** 才继续;start 再给三次机会(间隔 5 s)兜 SCM 状态刚落地的偶发 1056/1058。补回归测试: 用真实 `sc query` 的四种输出喂 `win_state()`,并断言 STOP_PENDING 下 `wait_state("STOPPED")` 不会提前为真。验收: 本地回归 6/6 + `restart --dry-run` 三步全打印 + 远端 Windows 服务用新 restart 真重启成功(服务 RUNNING、六个端口重新监听、七条路由全 200)。'),
- dict(version='2.11.5', date='2026-09-28', level='patch',
- title='消缺:Windows 服务 restart 是无效命令(sc.exe 无 restart 子命令)',
- note='消缺(不改行为口径,只修坏掉的动作)⇒ 小版本 +1。根因: `scripts/service_ctl.py restart` 在 Windows 上落成 `sc.exe restart guanlan`,而 sc.exe 没有 restart 子命令 ⇒ 实际只打印用法帮助并以非零码退出,服务从未被重启(此前只能用 stop + start 两步绕过)。修法: 新增 `win_restart()` / `wait_stopped()` —— 先 `sc.exe stop`,轮询 `sc.exe query` 直到不再是 RUNNING(最长 90 s),再 `sc.exe start`;因为 stop 是异步的,不等停稳就 start 会吃 1056/1058 错。`--dry-run` 现在会把"停 / 等停稳 / 起"三步都打印出来。Linux 侧 `systemctl restart` 本来就对,不动。验收: `restart --dry-run` 打出 stop+start 两条;真跑到远端 Windows 服务上又逮到"等待判据只看不是 RUNNING"⇒ STOP_PENDING 抢跑、紧接着 start 吃 1056(服务的一个实例已在运行),网关等端口被停掉 ⇒ 见 2.11.6。'),
- dict(version='2.11.4', date='2026-09-28', level='patch',
- title='模块化重构 P4:后端层实体迁移(21 文件 / 10,482 行,服务与入口留壳)',
- note='重构推进(源码组织层)⇒ 小版本 +1。后端层(cli/serve/gateway/ops/service/start_hidden/ops_*/windcms 服务侧 4 件/config/terms/i18n/lang/report_export/deid*)迁入 app_backEnd/app_backEnd_guanlan/;根 guanlan.py 与 scripts/<9 件> 留 CLI 壳、src/<库件> 留模块别名壳;网关—Nginx 接入点契约改名 gateway_contract.py 避免与真实现撞名;13 处 __file__ 安装根推算改为公共层 install_root()。实逮并修掉: ① CLI 壳 sys.path 插错层;② serve/gateway 无 main()(入口在 __main__ 守卫)⇒ 壳改 runpy 兜底;③ P0 接口契约同名挡住 git mv;④ 审计器 detail_deps 按旧路径读 serve 源码(改读新位置);⑤ 网关按同目录兄弟名找 guanlan_ops.py(改找同包 ops.py)。验收: 服务起停 + 六条路由全 200(/、/detail/v2、/detail/api/fleet、/ops、/ops/api/state、/cms/、/sim/)+ 全门禁 rc=0 + 开箱验证 5/5。'),
- dict(version='2.11.3', date='2026-09-22', level='patch',
- title='模块化重构 P3:算法层实体迁移(26 文件 / 3,858 行,逐值对拍零变化)',
- note='重构推进(源码组织层)⇒ 小版本 +1。落地: 算法层(taxonomy、audit、perf/*、subsys/* 与随迁的 windcms 分析件 6 个)迁入 app_algorithmModel/app_algorithmModel_guanlan/;旧路径留兼容转发壳;跨模块依赖改经公开面(数据层 app_ETL…api,本轮为它补函数级公开名 load_10min/contracted_cols 与 slim;安装根 app_common…api)。验收=逐值对拍: 同一时间窗下 25 项算法输出(判级矩阵/七镜头曲线/M9/可靠性/四轴 registry/限电/故障/趋势/功率曲线),迁移前后 24 项逐字节一致,唯一差异为 trend.attribution_router 报出的缺件名(快照自身写产物所致);同状态重跑两次 25/25 一致(同一总哈希 ee80df59…→77319d18…→77319d18…)。实逮并修掉: ① 导入改写规则顺序误伤 windcms 侧 .data/.config(5 处);② from . import X 类模块对象导入未覆盖;③ 函数级公开名需在 api 显式暴露。全门禁 rc=0(含模块边界 R2/R3/R6/R7/R8),开箱验证 5/5。'),
- dict(version='2.11.2', date='2026-09-22', level='patch',
- title='模块化重构 P2:数据接入管理实体迁移(4 库模块 + 12 构建器,scripts 留 CLI 壳)',
- note='重构推进(源码组织层)⇒ 小版本 +1。落地: ① 四个库模块(data/scada_source/slim/mdb_names)与十二个构建器(rebuild_from_raw、rebuild_all、scada_slim_build、windscada_monthly_build、vib_raw_build、raw_scan、raw_data_check、place_raw_data、pitch_face_build、baseline_38_build、component_history_build、csv_to_mdb)迁入 app_ETL/app_ETL_guanlan/;② scripts/<同名>.py 留 CLI 兼容壳(导入实现并原样返回退出码)—— scripts/<构建器>.py 这条路径被链、审计器与文档引用约 269 处,必须保留;③ 构建器安装根推算改用公共层公开面 install_root(不再用 __file__.parents[1]);④ 公共层 api.py 公开 install_root,满足「模块间只经 api 调用」的边界规则。实逮并修掉: 包内相对导入(from .config、from . import scada_source、from .mdb_names 共 6 处)在换包后指向不存在的模块。验证: 12/12 构建器可导入;raw_scan --check、scada_slim_build --check、rebuild_all --dry-run(26 步 / --skip-scada 23 步)全部 rc=0;库模块旧路径别名与模块身份实测一致;guanlan.py check、config_audit、pages_audit、chain_gap、version_log、detail_deps、check_portability、products_reverse_audit、module_boundary_audit 全 rc=0;开箱验证 5/5。'),
- 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/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。'
- '⚠ P1 附带逮到并修掉的**入口耦合缺陷**: 安装器原先按 src/version.py 的**字面量**读包版本,该文件变转发壳后读不到 ⇒ 回落到读 dist-manifest.json,而 PS 5.1 的 Get-Content 默认按 ANSI 解码无 BOM 的 UTF-8 ⇒ 中文乱码、ConvertFrom-Json 抛异常 ⇒ **安装中断**(开箱验证从 5/5 掉到 0)。修法: install.ps1 与 install.sh 改为**按候选路径解析**(公共层新路径优先,旧路径兜底),且 JSON 一律按 UTF-8 读;并新增两条门防复发 —— R6「入口与布局解耦」(安装器必须能在当前布局下解析出版本)与 R7「入口读 JSON 必须指定 UTF-8」,都并入 module_边界审计。 '
- '验证: 旧路径导入与安装根实测正确(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)、文件名与扩展名、目录与落盘位置、'
- '机组编号;测点一律用中文业务含义名称(有功功率、齿轮箱油温、机舱风速…),单位取自'
- '`app_ETL/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/<场>/…`、'
- '`app_ETL/configs/contracts/<机型>_<场>.yaml`、`app_ETL/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']}")
|