# 系统设计说明 · 观澜 v2 风电场智能分析系统 · 版本 2.32.0 ## 1 文档说明 > **本文用途(2026-09-22 用户说明)**:本文供**开发团队后续迭代观澜**时使用,目标是让系统对不同风电场、不同机型尽可能**适用与通用**。本文**以观澜当前实现为基础**编写(口径取自现有代码、配置与产物),但**它不是对当前实现的描述,也不是改造现有系统的要求**:凡本文列出而当前实现尚未覆盖的条目,只作为**后续迭代的候选需求**,不得据此改动现有系统的行为与产物。 ### 1.1 目的与范围 本文是"观澜 v2(风电场智能分析系统)"(海上风电场智能分析离线系统)的设计说明(下称"本系统"),面向版本 2.32.0;内容以样本风电场(下称"本场")为依据,样本场实测日期为 2026-09-22,说明本系统"由哪些部分组成、各部分怎么实现、数据从哪来到哪去、判据写在哪里、怎么验证、边界在哪里"。 本文覆盖十五个设计面:总体架构与分层、目录结构与路径真源、数据接入与重算链、判级与算法、时间窗口径、服务与前端、本体与知识层、本机模型接入、运维控制台与重算编排、安装与服务化与版本管理、质量保证、安全与离线边界、可移植性与资源占用、多场适用性与换场迁移、已知边界与未实现。 本文不重复需求条目本身(那是《需求分析_观澜_2.32.0.docx》的职责),也不重复操作步骤的逐步手册(那是 docs/重算操作手册_v0.1.md 与《使用说明书》v0.2(随包 docs/)的职责);本文只回答"设计上为什么这样、落在哪个文件的哪一处、用什么机器守卫保证它不漂移"。 ### 1.2 读者与用法 现场运维与检修人员可看第 3 章、第 7 章、第 8 章、第 11 章,了解自己能点到的页面背后读的是什么口径;场站管理人员与换场交付人员可看第 6 章、第 7 章、第 16 章、第 17 章,了解判级与可靠性指标的口径与边界,以及换场时要重新标定哪些参数;研发与交付人员应通读全文,重点是第 4 章、第 5 章、第 12 章、第 13 章;验收方可直接按第 13 章的质量门与附录 A 的编写依据逐条复核。 ### 1.3 与《需求分析_观澜_2.32.0.docx》的对应关系 需求分析写"要什么、为谁、优先级与验收门",设计说明写"怎么实现、落在哪、如何自证"。两文的章节对应关系如表 1-1 所示;与《数据要求说明_观澜_2.32.0.docx》的数据侧口径去向见 16.6 节。 | 本文章节 | 需求分析对应章 | 对应关系说明 | |---|---|---| | 2 设计目标与原则 | 2 设计目标与约束 | 需求给目标,本文给落地机制与机器守卫 | | 3 总体架构 | 4 系统架构需求 | 需求要分层与统一入口,本文给分层图与组件端口表 | | 4 目录结构与路径真源 | 6 非功能需求(可移植性) | 需求要换机可用,本文给路径唯一真源与助手表 | | 5 数据接入与重算链 | 3 数据与功能需求 | 需求列功能,本文给逐步重算链与退出码容忍 | | 6 判级与算法 | 3 数据与功能需求(判级部分) | 需求要"带证据的判级",本文给判据、阈值与产物 | | 7 时间窗口径 | 3 数据与功能需求(时间窗) | 需求要随所选时间窗变化,本文给词表、缓存与 pending 契约 | | 8 服务与前端 | 5 页面与信息架构 | 需求给页面清单,本文给路由族与链接契约 | | 9 本体与知识层 | 3 数据与功能需求(知识与问答) | 需求要机制链与决策台,本文给六步摄入与只读工具表 | | 10 本机模型接入 | 6 非功能需求(离线模型) | 需求要离线问答,本文给档位、校闸、升档与审计 | | 11 运维控制台与重算编排 | 5 页面与信息架构(数据重算) | 需求要一个按钮一件事,本文给动作、状态文件与冲突码 | | 12 安装服务化与版本管理 | 7 交付与安装需求 | 需求要可安装可卸载可升级,本文给步骤与三重守卫 | | 13 质量保证 | 8 验收门 | 需求给验收门,本文给审计器矩阵与退出码语义 | | 14 安全合规与离线边界 | 6 非功能需求(安全与离线) | 需求要无外网与脱敏,本文给边界与降级路径 | | 15 可移植性与资源占用 | 6 非功能需求(可移植) | 需求要零配置启动,本文给离线件与实测体积 | | 16 多场适用性与换场迁移 | 11 多场适用性与换场迁移 | 需求要按场配置化并可换场交付,本文给场抽象层、场无关与场相关分层、换场作业单与扩展点 | | 17 已知边界与未实现 | 8 验收门(遗留项) | 需求要求如实留白,本文逐条给影响与处置建议 | ### 1.4 口径与依据 本文所有数字来自仓库文件或命令的实跑输出,不采用估算与推测;查不到、未实现的,一律写"未取证"或"未实现",并在第 17 章汇总。本文中"本次实测"与各表"实测"列一律指样本场实测(2026-09-22):即在样本风电场的一套实例上的一次实跑,换场后这些数字会变、方法与口径不变。版本号的唯一真源是 src/version.py 的 VERSION 常量,本版为 2.32.0;打包文件名由 src/version.py 的 package_name() 给出,为 app_guanlang_v2.32.0.zip。 本文遵循四条写作口径:含"窗"且确实指时间窗口的,一律写全"时间窗"(天气窗、作业窗、预览窗、观测窗属领域词,保持原样);影响的是风电机组时写"影响机组"或"影响机组数";使用英文简写时必须写成"中文(英文简写)"形式,如平均无故障间隔(MTBF)、平均停机间隔(MTBO)、单次停机时长(MDT);全文用简体中文与半角数字与单位。 本文用到的取证命令与样本场实测(2026-09-22)的输出要点如表 1-2 所示,全部为秒级只读或自检命令(重算与构建类耗时命令未执行)。 | 命令 | 用途 | 本次输出要点 | |---|---|---| | python scripts/rebuild_all.py --dry-run | 取重算链实际步骤 | 输出"重算全部 · 26 步";加 --skip-scada 为 23 步 | | python guanlan.py check | 装机自检(40 余行结论) | 结论"全绿, 可 serve";底稿落在 docs/src/_guanlan_check.txt | | python scripts/products_reverse_audit.py --check | 反向呼应审计 | 呼应成立 3,548 件、不成立 0 件、未归类 0 件、rc=0 | | python scripts/pages_audit.py --check | 页面归口审计 | 2 项一致、19 项已知缺口、0 项要处理、rc=0 | | python scripts/config_audit.py | 配置统一审计 | 171 件配置、5 个域、0 不一致、14 已知缺口、rc=0 | | python scripts/raw_scan.py --check | 输入数据指纹比对 | "与上次快照一致,没有新数据"、rc=0 | | python scripts/log_audit.py | 日志口径审计 | 0 不一致、87 条提示、rc=0 | | python scripts/chain_gap_check.py | 振动六层链完整性 | 四步脚本齐全且已落地、rc=0 | | python scripts/version_log.py --check | 版本记录一致性 | 版本记录与代码一致、rc=0 | | python scripts/service_ctl.py status | 服务化状态 | 本机"已注册=False 运行中=False",服务名为 guanlan | *** ## 2 设计目标与原则 ### 2.1 六条设计原则 **离线单包**:整套系统在一台不接外网的机器上可安装、可运行、可重算、可出报告。程序与依赖随包(wheels/win_amd64 42 件、vendor/python 三平台便携运行时),模型为可选项(未装即降级并如实标注)。落地证据:install.ps1 第 3 步"装依赖(优先包内离线轮子 wheels\win_amd64)",包内无轮子才改为联网安装。 **单一真源**:路径真源是 src/paths.py,版本真源是 src/version.py 的 VERSION,端口与路径真源是 configs/serve.json,页面归口真源是 configs/portal_pages.yaml,配置登记真源是 configs/registry.yaml。落地证据:src/version.py 注释写明"改版本只改这里",打包器 scripts/pack_dist.py 不再自带版本常量而改为读 VERSION。 **产物自算**:页面与接口要用到的每一件数据,都应由包内源代码从 data/raw 算出来,而不是靠随包快照顶上。落地证据:重算链的 ④b 会生成振动时间窗索引与谱库、④c 生成变桨面、④d 生成振动在升与换件闭环、④e 生成三层基线、⑤b 由重算台账生成事实契约的 claim、⑦b 自算烘焙总览页;反向呼应审计当前 rc=0,3,548 件产物全部能指到生成端与输入。 **口径先于结论**:判据写在代码里、模型只做转述;相对判据封顶"候选";不同观测面不因为数量多就当作多源印证;外部原因造成的结果不写成设备不可靠。落地证据:app_ontology/.../ontology/mcp_server.py 头部写明"全部工具只读(L1)——模型零写权;判级零生成——只转述对象里的 verdict 原文与审级";src/windscada/subsys/fusion.py 明确禁止按源数加权;src/windscada/perf/reliability.py 把运维操作、风况(外部)、电网/供电(外部)单列为外部类并写明"外部类不计设备可靠性"。 **缺件如实**:缺数据、缺配置、缺模型时,页面与日志要明确写出缺哪一件、属于哪条产物线、怎么补,不允许静默降级或伪造内容。落地证据:工作台与 CMS 在无产物时返回 HTTP 200 的结构化缺件页(而非裸 404 或直接掐断连接);src/windscada/subsys/temp_nbm.py 在样本不足时判"不可判(样本不足/返服嫌疑)"而不是判正常。 **可审计**:每一条结论都能回抓到源记录,每一件产物都能说明谁生成、谁消费、来源是原始件重算还是随包补齐。落地证据:outputs/场站/_provenance.json 逐件记来源(raw-derived 或 shipped),outputs/场站/_derived_manifest.json 由构建脚本自登记,模型调用逐条写 logs/audit/llm_audit.jsonl。 ### 2.2 原则与机器守卫的对应关系 原则不靠人记,靠检查器与退出码守住。对应关系如表 2-1 所示。 | 设计原则 | 落地机制 | 守卫或检查器 | 违反时的表现 | |---|---|---|---| | 离线单包 | 包内轮子与便携运行时、可选模型 | install.ps1 第 3 步、guanlan.py check 依赖项 | 依赖安装失败即 exit 3,不继续启动 | | 单一真源 | paths.py、version.py、serve.json、registry.yaml | config_audit.py R1 至 R8、version_log.py --check | 手拼配置路径或版本不一致即报错(rc=6) | | 产物自算 | 构建器 + 自登记清单 | products_reverse_audit.py --check | 有件无生成端或无输入即报 rc=5 | | 口径先于结论 | 判据在 taxonomy 与 perf 与 subsys 内 | 反向可逆性矩阵、llm 校闸(校闸未过不出文) | 引用不到契约条目时明写"未被契约背书" | | 缺件如实 | 结构化缺件页、缺件说明字段 | pages_audit.py、guanlan.py check | 缺件被登记为已知缺口而不是装作通过 | | 可审计 | _provenance.json、llm_audit.jsonl | products_reverse_audit.py、log_audit.py L1 至 L5 | 台账查不到来路即报"溯源缺失"(rc=6) | ### 2.3 关键设计取舍 **判断在代码、模型只转述**:把判级放进模型会让同一份数据在不同机器上给出不同结论,也会让结论无法回溯到证据。因此模型只在问答、解释、引用三个位置出现,且必须过接地闸:app_ontology/.../ontology/mcp_server.py 的 claim_check 工具要求"最终回答前必须调用;台号与数字须来自本会话工具事实,越界即拒"。 **慢就慢在算,不慢在等**:按所选时间窗重算一次判级矩阵约 25 秒、七镜头约 9 至 44 秒每时间窗。若同步等待,HTTP 请求会挂几十秒(对外访问还要过网关)。因此统一为"后台算加进程内缓存,未命中先回上一份口径并明确标注 pending",而不是静默等待或静默给旧口径。 **缺件不静默降级**:缺 handoff 正本时由观澜自算件顶上并在 meta.from 标明"观澜自算";缺振动时间窗索引时按缺件如实返回空表并写明数据边界;缺模型时页面提示"本机模型未启动"(HTTP 503),绝不改用别的来源。 **运行期不从交付包补齐**:2026-09-17 的口径是"运行链只报账、不搬运"——缺件要么放原始件后重算,要么由研发补生成端。交付包默认不含产物,因此"旁边有个含产物的压缩包"不能成为隐式依赖。 *** ## 3 总体架构 ### 3.1 分层结构 系统分为输入层、摄入构建层、产物层、服务层、前端层,另有贯穿的本体与模型层和两套贯穿全链的守卫(单一真源与审计门),如图 2-1 所示。 ![图 2-1 观澜总体架构](figures/fig-des-01-总体架构.png) 输入层是 data/raw/场站名称/ 下的七类约定目录(scada_10min、scada_1min、scada_mdb、故障报警、风机故障记录、油样报告、windcms,另有 m5_cms_tcm 与场站并列的共享技术资料目录),原始件只读、不随包分发。 摄入构建层是 scripts/rebuild_all.py 串起来的重算链(默认 26 步),把原始件算成产物,并把每一步的日志、退出码、耗时写进日志目录与任务状态文件。 产物层是 outputs/场站/ 下的九个产物仓(windscada、ontology、windcms、m5_cms_tcm、tcm_compatible_replay、sop、guanlan、pitch、paradigm_r1),另有 report 与 guanlan/cloud 作为报告交付与可上云面孔的落点。 服务层是六个常驻进程加一个可选的本机模型服务:网关 28084、分析工作台 18033、振动诊断 18020、仿真与回放 18791、仿真合页 18792、三维工作台 64292,以及可选的 Ollama 11434。 前端层是门户(release/portal.html)、工作台单页(/detail/v2)、CMS 页面、仿真页与三维页,全部通过网关的同一前缀与同一入口访问。 ### 3.2 组件清单 组件、端口、入口与职责如表 3-1 所示;端口取自 configs/serve.json(第 4 至 10 行),入口脚本取自 guanlan.py 的组件启动表。 | 组件 | 端口 | 入口 | 职责 | |---|---|---|---| | 门户网关 | 28084 | scripts/guanlan_gateway.py | 统一入口;前缀改写;data-abs 豁免;/ops 与 /release 只读暴露;门户静态件按 mtime 缓存 | | 综合详细分析工作台 | 18033 | scripts/windscada_serve.py | 判级矩阵、部件问题、该问题页、发电性能、可靠性、本体页、问答、报告导出 | | CMS 振动诊断 | 18020 | scripts/windcms.py serve | 六层振动模型逐台判读、报告与逐台页、谱图与标量 | | 仿真与回放 | 18791 | scripts/static_server.py | 静态服务 release 与 resources 下的仿真页 | | 仿真·四系统合页 | 18792 | release/sim_sys_server.py | 从资料包 zip 现读四系统合页与 5 页资料 | | 三维拆装工作台 | 64292 | scripts/static_server.py | 三维资产页(release/viewer) | | 本机模型(可选) | 11434 | Ollama 本体进程 | 只服务问答、解释与初筛;未装即降级 | 三处部署形态说明:组件(分析、振动、仿真、三维)出厂只绑 127.0.0.1(configs/serve.json 的 host),只有门户网关使用 public_host(出厂 0.0.0.0);因此对外只有 28084 一个入口,且页面没有鉴权,配置注释明确提示"对外监听请同时限来源(防火墙白名单或反代认证),别直接挂公网"。本次实测监听状态与之一致:网关在所有网卡的 28084 上监听,而工作台 18033、振动 18020、仿真 18791、四系统合页 18792、三维 64292 与本机模型 11434 都只在 127.0.0.1 上监听。 ### 3.3 端口与进程拓扑 端口与进程拓扑如图 3-2 所示。网关按前缀把请求转给内部组件,组件端口可被 configs/serve.json 的同名键覆盖(scripts/guanlan_gateway.py 的上游端口表 _DEFAULT_ROUTES 与 _upstream_ports())。 ![图 3-2 端口与进程拓扑(依据 configs/serve.json)](figures/fig-des-02-端口与进程.png) 网关的前缀路由表(依据 scripts/guanlan_gateway.py 第 31 至 38 行)为:/detail 转 18033 默认路径 /v2;/cms 转 18020 默认路径 /?theme=light;/sim/sys 转 18792 默认路径 /0_四系统合页.html;/sim 转 18791 默认路径 /sc1_sim_demo.html;/viewer 转 64292 默认路径 /unit-workbench.html;/local-ai 转 11434 探活路径 /api/tags。另有 /healthz、/api/version、/ops、/release/ 与门户根路径由网关自己处理。 运行态实测(run/pids.json,本机一次运行中)如表 3-2 所示。该文件是"当前跑着谁"的唯一凭据,服务停止时删除;它换一台机器就是无效引用,因此交付包默认排除 run 目录。 | 组件键 | 端口 | 进程号(实测) | 说明 | |---|---|---|---| | gateway | 28084 | 16448 | 对外唯一入口 | | detail | 18033 | 35436 | 工作台 | | cms | 18020 | 56720 | 振动诊断 | | sim | 18791 | 21780 | 仿真与回放 | | sim_sys | 18792 | 25936 | 四系统合页 | | viewer | 64292 | 58488 | 三维工作台 | ### 3.4 产物仓的生成端与消费端 九个产物仓的生成端与消费端如表 3-3 所示(依据 docs/系统设计说明.md 的产物全景块与本次盘上实测件数)。本次实测 outputs/<场> 合计 3,549 件、7,157.5 MB。 | 产物仓 | 件数(实测) | 体积(实测) | 生成端 | 主要消费端 | |---|---|---|---|---| | windscada | 57 | 569.7 MB | rebuild_from_raw.py、10 个 SCADA 构建器、windscada_monthly_build.py、scada_slim_build.py | 工作台各视图、taxonomy、subsys/fusion | | m5_cms_tcm | 3,417 | 6,555.0 MB | scripts/<场>_tcm_index.py、scripts/<场>_tcm_spectra.py、vib_raw_build.py、scripts/<场>_model_run.py、scripts/<场>_fusion_run.py、scripts/<场>_fusion_handoff.py、component_history_build.py、baseline_38_build.py | subsys/fusion.py、windcms/data.py、报告构建器 | | ontology | 9 | 15.2 MB | src.ontology.kb_ingest 至 maintenance.refresh_params 六步 | 本体页、问答、事实契约 | | windcms | 49 | 15.2 MB | scripts/windcms.py report 与 kb、vib_reports_build.py | CMS 自服务 18020、taxonomy 转录列 | | pitch | 3 | 0.4 MB | scripts/pitch_face_build.py | 变桨面判级、零位卡片 | | guanlan | 5 | 0.0 MB | scripts/guanlan_facts_contract.py | 门户结论段、/api/facts、报告摘要 | | sop | 1 | 0.0 MB | scripts/sop_findings_from_ledger.py | 事实契约输入 | | paradigm_r1 | 3 | 0.0 MB | scripts/sop_findings_from_ledger.py | 事实契约输入 | | tcm_compatible_replay | 2 | 0.6 MB | scripts/tcm_mask_thresholds_build.py | 报告构建器、掩码阈值 | *** ### 3.4 源码模块化布局与边界(2026-09-22 用户令;P9 结构更新 2026-09-28) 源码按**变化的原因**分为七个模块,**每个模块一个目录**(P9 结构,2026-09-28 用户令): ``` app_<模块>/ common/ 模块内通用插件/组件(跨模块复用的公共层是 app_common) configs/ 模块**专属**配置域(共用单件仍留安装根 configs/ 单源) data/ 模块相关数据件(运行期数据在安装根 data/、outputs/) app_<模块>_guanlan/ 观澜在该模块的实现包;其中 api.py 是该模块**唯一的对外公开面** README.MD 职责 / 公开面 / 安装部署与运行 install.bat / install.sh · uninstall.bat / uninstall.sh · run.bat / run.sh (入口包装:内部调安装根统一入口) ``` | 模块目录 | 职责 | 允许依赖 | 迁移阶段 | 组件接入点 | |---|---|---|---|---| | `app_common/` | 公共层:路径与版本真源、日志、进程、入口引用、控制台输出、运行态作业文件 | 无(不得依赖业务模块) | P1 | — | | `app_ETL/` | 数据接入管理:源件摄入、放置与增量体检、标准仓、机型与场站契约、canonical 词典 | 公共层 | P2 | **TiDB community**(标准仓读写)、**MinIO**(源件与产物对象存储) | | `app_algorithmModel/` | 算法:七系统判级、七镜头曲线、可靠性、控制参数一致性、融合面四源、振动与温度面、趋势 | 公共层、数据接入 | P3 | — | | `app_ontology/` | 本体与知识层:对象库、机制链、决策台、检索、实机参数表、SOP 与验收状态机、模型闸 | 公共层 | P6 | — | | `app_backEnd/` | 后端:组件服务、统一网关、运维控制台与重算编排、作业状态、CLI | 公共层、数据接入、算法、本体 | P4 | **Redis**(按时间窗缓存与作业状态)、**Nginx**(统一入口与路由表) | | `app_frontEnd/` | 前端:工作台单页与交互、图表库、经典页模板、门户与静态件、设计系统与界面文案 | 公共层 | **P5 已完成(2026-09-28)** | — | | `app_qualityGate/` | 质量门与审计 + 打包安装:审计器、交付文档三检、打包与开箱验证、安装卸载 | 全部模块(只读审计,不参与业务调用链) | P7 | — | **边界规则(机器可查,不靠人自觉)**:① 模块之间**只许经对方 `api.py`** 调用;② 依赖方向不得反向、不得成环; ③ 公共层不得依赖任何业务模块;④ 每个 `api.py` 的转发目标必须真实存在。四条由 `scripts/module_boundary_audit.py` 逐条检查,并接入 `guanlan.py check`;模块职责、允许依赖与迁移阶段登记在 `configs/modules.yaml`。 **本轮(P0)只建目录与接口**:实现仍在既有路径(`src/**`、`scripts/**`),因此页面、CLI、产物与门禁**零行为变化**; P1–P7 分阶段实体迁移,每阶段独立提交、随时可回退,方案与验收口径见 `docs/重构方案_模块化_v0.1.md`。 **P1 已落地(公共层实体迁移,2026-09-22)**:`src/` 下的九个平台件(路径真源、版本真源、日志、进程、入口引用、控制台输出、运行态作业文件、产物派生台账、表格式)已实体迁入 `app_common/app_common_guanlan/`;安装根推算由 `__file__.parents[1]` 改为**按 `configs/` 与 `guanlan.py` 标记向上查找**(位置无关,再搬也不会算错);旧路径 `src/<件>.py` 保留为**兼容转发壳**(`sys.modules` 别名,含私有名),因此脚本、审计器与文档里既有的 `src/paths.py`、`src/version.py` 一类引用**照旧可用**,不必一次性改完。P2–P7 按同一方式推进,每阶段独立提交、随时回退。 **P2 已落地(数据接入管理实体迁移,2026-09-22)**:`data`/`scada_source`/`slim`/`mdb_names` 四个库模块与 12 个构建器(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` 路径引用不变。构建器的安装根推算一律经公共层公开面 `app_common.app_common_guanlan.api.install_root`(不再用 `__file__.parents[1]`)。本轮实逮并修掉一类搬迁陷阱:包内相对导入(`from .config`、`from . import scada_source`、`from .mdb_names` 共 6 处)在模块换包后会指向不存在的模块 —— 迁移后必须逐条核对相对导入(本条已写进本节的换场/换目录检查表)。 **P3 已落地(算法实体迁移,2026-09-22)**:`taxonomy`、`audit`、`perf/*`(availability、control、curtail、curves、faults、powercurve、reliability、trend)、`subsys/*`(fusion、hydraulic、pitch、structure、temp_nbm、thermal_chain、workorder、yaw)与随迁的振动分析件(report_std、tcm、knowledge、cross_review、audit_rules、pipeline)共 26 文件 / 3,858 行迁入 `app_algorithmModel/app_algorithmModel_guanlan/`;旧路径留兼容转发壳。跨模块依赖改为**经公开面调用**:数据层经 `app_ETL…api`(本轮为它补了函数级公开名 load_10min/contracted_cols 与 slim 模块),安装根经 `app_common…api`。**验收用逐值对拍**:同一时间窗下 25 项算法输出,迁移前后 24 项逐字节一致,唯一差异是缺件报错文本(快照自身写产物所致);同状态重跑两次 25/25 一致。本轮实逮的搬迁陷阱(已写进换目录检查表):改写导入规则时**子包同名模块要先按包判定**(windcms 的 `.data`/`.config` 与 windscada 的同名模块语义不同),以及 `from . import X` 这类"模块对象导入"必须单独覆盖。 **P4 已落地(后端实体迁移,2026-09-22)**:`cli`(原根 `guanlan.py`)、`serve`(分析组件)、`gateway`、`ops`、`service`、`start_hidden`、四个 `ops_*`、`windcms/{serve,orchestrator,llm,agent}` 与 `config/terms/i18n/lang/report_export/deid*` 共 21 文件 / 10,482 行迁入 `app_backEnd/app_backEnd_guanlan/`。**入口与旧路径全部留兼容壳**:根 `guanlan.py`、`scripts/<9 件>`(CLI 壳)与 `src/windscada/*`、`src/windcms/*`(模块别名壳);网关—Nginx 的接入点契约改名为 `gateway_contract.py` 以免与真实现 `gateway.py` 撞名。后端件一律经公共层 `install_root()` 取安装根(本轮修 13 处 `__file__` 推算)。**验收**:服务起停 + 六条路由全 200(门户、`/detail/v2`、`/detail/api/fleet`、`/ops`、`/ops/api/state`、`/cms/`、`/sim/`)+ 全门禁 + 开箱验证 5/5。 本轮实逮的搬迁陷阱(已并入换目录检查表):① CLI 壳的 `sys.path` 要插**安装根**(`scripts/` 的上一层)而不是自身目录;② 服务类脚本常无 `main()`(入口写在 `__main__` 守卫里)—— 壳需 `runpy.run_path(..., run_name="__main__")` 兜底;③ **新模块目录里已存在的同名接口契约文件会挡住 git mv**,迁移前必须先盘点目标目录(本轮 `gateway.py` 即为此);④ 审计器/校验器按旧路径读源码的(`detail_deps` 读 `scripts/windscada_serve.py` 找路由符号)要改读新位置。 **P5 已落地(前端实体迁移,2026-09-28)**:工作台单页资源(`app.js`/`app.css`/`charts.js`/`favicon.svg`)、页面装配(`shell.html`/`build.py`/`snapshot.py`)、经典页模板(`CHART_JS`/`PAGE_FLEET`/`PAGE_PROBLEM`/`PAGE_TURBINE`,约 246 KB,其中图表 JS 176 KB)、设计系统 `design.py`、双语文案语言包 `lang.py`、显示层术语 `terms.py` 与三个门户装配器,共 13 件迁入 `app_frontEnd/app_frontEnd_guanlan/`(`assets/`、`pages/`、`builders/`)。旧路径全部留兼容壳:`src/windscada/ui/{build,snapshot}.py`、`src/windscada/{design,lang,terms}.py`(模块别名壳,`python -m` 照旧)与 `scripts/{portal_build,guanlan_portal_fix_anchors,guanlan_portal_inject_claims}.py`(CLI 壳)。**验收**:出页(`build.render` 四组合)与设计系统两段 CSS 逐字节等于迁移前;`serve.py` 四个经典页模板常量与三张经典页渲染逐字节一致;`portal_build --verify` 装配结果与 20.19 MB 门户逐字节一致;服务重启后门户、`/detail/v2`、`/ops`、`/cms/` 页面体 sha256 与迁移前一致;全门禁 rc=0。本轮实逮:① 资源件(js/css/svg/html)**无壳可留** ⇒ 按路径读它们的审计器要同步改(`detail_deps`、`pages_audit`、`guanlan_baseline_manifest`、`audit_chinese_terms`、`check_portability` 的 ALLOW 键、`configs/registry.yaml` 的 consumers);② 迁入子目录后 `__file__` 上跳层数变了 ⇒ 取根统一走公共层 `install_root()`;③ 模板抽成真资源件时,装载必须 `newline=""`,否则换行翻译会改掉模板字节。 **P6 已落地(本体与知识层实体迁移,2026-09-28)**:本体 20 件(对象库、码表、摄入六步、检索、MCP 只读工具、离线问答代理与模型闸)与 SOP 38 件(契约闸、判别器、调查与案例、台账与日期口径、各类校验、编排)共 58 个模块 / 19,775 行迁入 `app_ontology/app_ontology_guanlan/{ontology,sop}/`。**旧路径全部留兼容壳**:`src/ontology/<件>.py`、`src/sop/<件>.py`(模块别名壳;`python -m src.ontology.kb_ingest` 这类重算链入口经壳 `runpy` 按 `__main__` 重跑,命令一字不改)。正文里 `app_ontology/.../ontology/<件>.py` 即 `app_ontology/app_ontology_guanlan/ontology/<件>.py`(`sop/` 同理)。**验收**:58/58 模块新旧路径均可导入且为同一模块对象;9 处取根抽查全部等于安装根;门户 / `/ops` / `/cms/` / `/api/ont_list` / `/api/ont_chain` / `/api/maint_survey` / `/api/maint_std` / `/api/maint_framework` / `/api/dq_findings` / `/api/facts` 十条路由响应体 sha256 与迁移前**逐字节一致**;`-m src.ontology.mcp_server`、`-m src.sop.cases` 等旧入口冒烟通过;全门禁 rc=0。本轮实逮:① 包内 `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`)要同步改,否则门禁会读壳或误报。 **P7 已落地(质量门与打包安装实体迁移,2026-09-28;重构收官)**:23 个工具 / 7,426 行迁入 `app_qualityGate/app_qualityGate_guanlan/{audits,docs,pack}/` —— 审计器 17 件(配置统一、页面归口、产物反向呼应、链路缺口、可移植、日志、安全扫描、中文表达、模块边界、页面指纹、产物清点与补缺、依赖台账)、交付文档三检与版本记录 3 件、打包器与开箱验证与卸载器 3 件。旧路径 `scripts/<件>.py` 全部留 CLI 壳(公开名同面转发 + `main()`;`guanlan.py check` 用 importlib 载入这些路径,照旧可用)。**根引导脚本(`install.ps1`/`install.sh`/`uninstall.*`/`check.bat`/`pack.bat|sh`/`start|stop.bat`)原地不动**:它们是"解压即用"的引导层,被 README、入口引用闭合门与 `.gitattributes` 的换行/编码约定钉着;其 Python 实现迁入模块后,引导脚本与既有命令行一字不改。**验收**:8 条质量门 stdout 与迁移前**逐字节一致**(config / pages / chain_gap / products_reverse / detail_deps / version_log / docs_verify / check_transferable),模块边界与可移植性 rc=0;23 个壳经 importlib 载入且公开名同面;新路径 `python -m app_qualityGate....audits.<件>` 可跑;**打包器从新位置出包,开箱验证页面 5/5 + 卸载核验通过**;`guanlan.py check` 全绿。本轮实逮:① 子包内 `__file__` 取根(`parents[1]`)失效(22 处)⇒ 统一 `install_root()`;② 文件间 bare import(4 处)改相对导入;③ `audit_chinese_terms` 的 `here.parent` 是"仓库根"语义 ⇒ 换 `install_root()`;④ 顺手补 P4 遗留:该审计器的被审件 `scripts/windscada_serve.py` 早已是壳 ⇒ 改读 `app_backEnd/app_backEnd_guanlan/serve.py`。 **P9 已落地(源码目录结构变更,2026-09-28 用户令)**:按新要求把每个模块的实现包**上提一层** (`app_<模块>/common/app_<模块>_guanlan/` → `app_<模块>/app_<模块>_guanlan/`),并给每个模块补齐 `common/`、`configs/`、`data/`、`README.MD` 与 `install/uninstall/run` 入口包装脚本。① **引用改写**:全仓 540 处点号引用 + 190 处实路径一次改到新布局(含 `src/**` 旧路径壳、`scripts/**` CLI 壳、各模块 `api.py`、安装器、打包器、门禁与文档)。② **旧导入前缀留一轮兼容**:`app_<模块>/common/app_<模块>_guanlan/__init__.py` 用 `sys.meta_path` 前缀重定向到新包,做到 `sys.modules[旧名] is sys.modules[新名]`(同名同体,不产生两份模块对象),下版删除。③ **配置双根**:模块专属配置下放 —— `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`,配置门禁按"归属根"校验。④ **每模块入口包装**:`install/uninstall/run`(bat + sh)内部调安装根统一入口,一套安装不变;入口引用闭合检查纳入这 42 个脚本(92 条入口)。**验收**:189 个模块文件新旧前缀均可导入且同体;全门禁 rc=0;`guanlan.py check` 全绿;起服务后门户 / `/ops` / `/cms/` / `/api/ont_list` / `/api/maint_survey` / `/api/facts` 响应体 sha256 与迁移前一致(`/detail/v2` 唯一差异是页内 JS 注释里的配置路径由 `configs/terms/…` 更新为 `app_frontEnd/configs/terms/…`,+13 字节,非行为变化);打包开箱 5/5 + 卸载核验。 **四个系统组件(TiDB community / MinIO / Redis / Nginx)**:本轮只建立**接口契约**(标准仓读写、对象存储、 按时间窗缓存与作业状态、统一入口与路由表),默认实现仍是本地目录与进程内缓存 —— 所以离线单包交付形态与现有 验收链保持不变;后续接实现时只换实现、不动上层调用。 ## 4 目录结构与路径真源 ### 4.1 路径唯一真源与助手表 src/paths.py 是全部路径的唯一真源。硬约束有三条:代码与配置里只写相对路径(相对安装根),不写机器相关绝对路径;运行时由该模块解析成绝对路径,解析基准是安装根而不是当前工作目录;需要写进产物与清单的路径字符串用 POSIX 相对形式(rel()),只在给人看的显示场景用本机分隔符(disp() 与 disp_dir())。安装根由环境变量 WINDSCADA_ROOT 决定,未设置时按 src/paths.py 的位置回溯上一级,因此整个安装目录可以整体拷到别的电脑或别的盘而不改任何路径。 路径助手的功能如表 4-1 所示。 | 助手 | 返回 | 用途 | |---|---|---| | P.ROOT | 安装根 | 一切解析的基准(WINDSCADA_ROOT 可覆盖) | | P.RAW_ROOT | data/raw | 现场原始件根(可用环境变量 `WINDSCADA_<代号>_SRC` 或 serve.json 的 raw_dir 覆盖;该变量名里**写死了样本场代号**,换场须一并改名 —— 见 16.5 节) | | P.station_dir(name) | data/raw/场站名称 | 兜底约定位置,权威值来自场配置的扫描辨识 | | P.out_root(name) | outputs/场站 | 产物仓根 | | P.store(name) | outputs/场站/windscada | L0 标准仓,页面主取数处 | | P.ont(name) 与 P.objects_json(name) | outputs/场站/ontology 与其 objects.json | 本体对象库 | | P.cms(name) | outputs/场站/windcms | CMS 振动诊断产物 | | P.m5(name) | outputs/场站/m5_cms_tcm | 振动线出件、时间窗索引、谱库 | | P.tcm_replay(name) | outputs/场站/tcm_compatible_replay | TCM 兼容链回放资产 | | P.sop(name) | outputs/场站/sop | SOP 中间件与评审落盘 | | P.guanlan(name) 与 P.cloud(name) | outputs/场站/guanlan 与其 cloud | 事实契约与可上云面孔 | | P.pitch(name) | outputs/场站/pitch | 变桨侧派生件 | | P.paradigm(name) | outputs/场站/paradigm_r1 | 范式实验件(E3/E5/E8 底稿) | | P.report_dir(name) | outputs/场站/report | 报告交付件 | | P.contract(name) | reference/场站/windscada_contract.yaml | 机型判据契约(属 reference 侧) | | P.config() 与 P.config_dir() 与 P.farm_config() | configs 下的配置路径 | 配置的唯一取用口 | | P.rel(p) 与 P.disp(p) 与 P.disp_dir(p) | 相对 POSIX 串与显示串 | 写进产物用 rel(),给人看用 disp() | | P.resolve(p) | 绝对路径 | 把可能是相对的值按安装根解析 | | P.venv_python() 与 P.python_exe() | 解释器路径 | 跨平台探测 .venv/Scripts/python.exe 或 .venv/bin/python | ### 4.2 安装根逐目录说明 安装根下的目录分工如表 4-2 所示,体积与件数为本次递归实测值。 | 目录 | 件数(实测) | 体积(实测) | 放什么 | |---|---|---|---| | src | 281 | 6.4 MB | 分析代码(windscada、windcms、ontology、sop)与通用模块(paths、version、proc、logfile、entry_refs) | | scripts | 223 | 13.9 MB | 服务与工具脚本、构建器、审计器、打包器 | | configs | 171 件配置 | 见下 | 配置:serve.json、models.json、portal_pages.yaml、registry.yaml 与四个域目录 | | data | 26,504 | 224.67 GB | 原始件(data/raw/<场站> 与 data/raw/<机型>技术资料),只读输入 | | outputs | 3,549 | 7,157.5 MB | 产物仓,只由构建器写 | | release | 2,898 | 708.4 MB | 门户 portal.html、仿真服务与资料包、三维 viewer 资产、治理清单交付件 | | resources | 30 | 5.2 MB | 仿真回放资产(resources/oem__sc1_<场>2014) | | wheels | 42 | 169.4 MB | 离线依赖轮子(wheels/win_amd64) | | vendor | 4 | 81.4 MB | 三平台便携 Python 运行时(3.12.14)与 vendor/MANIFEST.json | | docs | 32 | 2.6 MB | 交付文档与设计正本(含本文) | | logs | 155 | 3.3 MB | 长驻服务日志、运维动作日志、构建日志、审计流水、历史留档 | | run | 2 | 0.0 MB | 运行态:pids.json 与 ops_job.json | | .venv | 17,779 | 688.1 MB | 虚拟环境(换机必失效,安装时重建) | ### 4.3 配置、日志、运行态与交付件的落点 四类运行期文件的落点与命名是唯一口径,如表 4-3 所示。配置只允许经 src/paths.py 的 config() 与 config_dir() 取路径,不许手拼 configs 字符串(config_audit.py 的 R6 规则专门查这一条)。 | 类别 | 落点 | 命名或字段 | 谁读 | |---|---|---|---| | 运行期单件配置 | configs/serve.json | host、public_host、gateway、detail、cms、sim、sim_sys、viewer、ollama、release_dir、viewer_dir、sim_dir、python、raw_watch、raw_dir | guanlan.py、网关、运维控制台、指纹、移植检查 | | 运行期单件配置 | configs/models.json | runtime、endpoint、tiers、profiles、pull_commands | guanlan.py check、app_ontology/.../ontology/llm_gate.py、fast_agent.py | | 运行期单件配置 | configs/portal_pages.yaml | 25 条主条目与 7 条子条目的 id 与 kind | scripts/pages_audit.py | | 配置登记表 | configs/registry.yaml | top_level、domains、known_missing | scripts/config_audit.py | | 长驻服务日志 | logs 组件名.log | gateway、detail、cms、sim、sim_sys、viewer、serve、start_hidden、service | 现场排障、log_audit.py | | 运维动作日志 | logs/ops/ | ops_动作_YYYYmmdd-HHMMSS.log,保留最近 20 份或 30 天 | 运维控制台页面、现场排障 | | 构建日志 | logs/build/场站/原相对路径 | 沿用原相对路径 | 构建排障 | | 审计流水 | logs/audit/ | llm_audit.jsonl、cloud_qa.jsonl、terms_audit.json | 审计与复核 | | 运行态 | run/ | pids.json(组件到进程号与端口)、ops_job.json(当前任务) | guanlan.py stop、运维控制台 | | 交付件 | release/ 与 outputs/场站/report 与 outputs/场站/guanlan/cloud | 门户、治理清单、报告交付件、可上云面孔 | 门户、网关 /release 只读暴露 | 日志行格式统一为"时间戳 级别 组件 消息"(级别取值 DEBUG、INFO、WARN、ERROR),UTF-8 无 BOM、行尾 LF、无 ANSI 颜色码。六个服务不需要各自改写打印语句,入口处调用一次 src/logfile.py 的 prefix_stdout() 即可让每一行自动带前缀。 ### 4.4 已统一的路径问题(选列) 2026-09-16 曾集中统一十处路径问题,选列五处如表 4-4 所示,用以说明"为什么必须只有一个真源"。 | 位置 | 原样 | 改成 | 为什么要改 | |---|---|---|---| | scripts/products_restore_missing.py | 判断不存在的常量后落到写死的样本场名 | P.out_root() | 多场部署会把本场的随包件补进别的场(静默串场) | | app_ontology/.../ontology/maintenance.py | 自写安装根回溯与自己的显示规则 | P.ROOT 与 P.RAW_ROOT 与 P.disp | 同一文件里出现三处影子真源,显示规则两个实现 | | app_ontology/.../sop/wrapup.py | 硬编码 cleaned/turbine.parquet | 读 clean_gate.json 的 section,取不到则目录内唯一 parquet,都没有则响亮报错 | 写侧落的是 cleaned/节名.parquet,读侧硬编码时静默取空并悄悄降级 | | src/windscada/subsys/pitch.py | P.store() 跟环境变量 | 配置透传的 store 键 | 多场部署下跨场串数据且不报错 | | src/windcms/pipeline.py 的 ingest | 任何压缩包都调未随包的脚本 | 先判断包内容:含解码导出则按已解码路线走,否则报明确错误并给两条可行路径 | 把 CMS 导出包直接指过来时原本必然崩,而其中一条路径并不需要那个脚本 | *** ## 5 数据接入与重算链设计 ### 5.1 输入数据族与落位约定 输入层是 data/raw/场站名称/ 下的约定目录,多套一层包名目录等于没放(摄入脚本按"源类目录再往下一层就是文件"读)。本次实测各族的件数与体积如表 5-1 所示(data/raw/<场站> 合计 26,504 件、224.67 GB;guanlan.py check 报 26,503 件源件,差额 1 件是目录内的放置说明文件 README_把原始数据放这里.txt)。 | 源类 | 件数(实测) | 体积(实测) | 结构要求 | 消费该族的重算步 | |---|---|---|---|---| | scada_10min | 114 | 15.60 GB | 平铺,每台一个主件(台号形态如 WTG01.csv 等;均为样本场内部编号,换场按实际台号替换),可含同台补充件 | ③ 与 ③b 与 ④ 与 ④c | | scada_1min | 38 | 12.77 GB | 平铺,名字是内部台号(样本场形如 01E…;均为样本场内部编号,换场按实际台号替换) | ④c | | scada_mdb | 100 | 44.93 GB | 按年与月分目录的月度通道组库 | 上游归档(转换后才被消费) | | windcms | 25,693 | 150.15 GB | 含解码导出 json,可套包名与 measurement 层 | ④b 与 ④d 与 ④e | | 故障报警 | 16 | 0.02 GB | 年度或季度 xls 与 XML 导出 | ② | | 风机故障记录 | 137 | 0.14 GB | 年目录加月度汇总表,另含压缩包与现场照片 | ② | | 油样报告 | 404 | 0.16 GB | 两级:台号目录下部件目录内的 pdf | ② | | m5_cms_tcm | 1 | 0.02 GB | handoff 正本与厂家报告 | ④b 与融合面 | | 主机技术资料(4.0 MW 级海上机组;主机制造商(OEM,名称从略)与机型代号从略;与场站并列) | 318 | 2,800.7 MB | 技术资料与四个台账 | ⑦ 本体六步 | ### 5.2 重算链逐步说明 重算链由 scripts/rebuild_all.py 写死顺序、依赖与退出码容忍。默认口径(不含 --src、不含 --with-verify)实跑为 26 步;加 --skip-scada 为 23 步(少了 SCADA 侧的 ③、③b、④c 三步);加 --src 为 27 步;再加 --with-verify 为 28 步。重算链的步骤关系如图 5-1 所示,该图按 --skip-scada 口径绘制,因此图中的步骤数为 23 步。 ![图 5-1 重算链(依据 scripts/rebuild_all.py --dry-run)](figures/fig-des-03-重算链.png) 逐步说明如表 5-2 所示(步号顺序即执行顺序;⑦ 的六个子步合并为一行列出)。 | 步号 | 脚本 | 输入 | 输出 | 退出码容忍 | |---|---|---|---|---| | ① 放数据(仅 --src 时) | scripts/place_raw_data.py --src 包目录 --scope full | 现场包 | data/raw/场站/ | 0 | | ①b 输入数据扫描 | scripts/raw_scan.py --check --write | data/raw 逐族指纹 | outputs/场站/_raw_scan.json 快照 | 容忍 4 与 5(4 有新数据不是失败;5 首次无快照顺手记一份) | | ② 三门台账 | scripts/rebuild_from_raw.py | 故障报警、风机故障记录、油样报告 | alarms.parquet、workorders.parquet、oil_samples_index.parquet | 0 | | ③ SCADA 侧 10 个构建器 | scripts/rebuild_from_raw.py --scada | scada_10min 逐台约 14 GB | 温度、功率曲线、损失、控制、偏航、热链、停机事件等 parquet | 0,实测约 15 分钟 | | ③b SCADA 10min 窄仓 | scripts/scada_slim_build.py | 同上,一次全量 | windscada/slim10min/台号.parquet 与 _manifest.json | 0,实测一次扫约 2 分钟 | | ④ 月度派生件 | scripts/windscada_monthly_build.py | raw 与随包基线 | temp_monthly 等同族月表 | 容忍 5(找不到随包基线即跳过等价验收,不是通过) | | ④b 振动侧摄入与报告与标量 | scripts/vib_raw_build.py | windcms 解码导出 | 时间窗索引、谱库、CMS 报告与逐台页、融合 z | 0;现场无振动原始件时空跑退出 0 | | ④c 变桨面 | scripts/pitch_face_build.py | scada_10min 与 scada_1min | pitch/pitch_daily.parquet 与 pitch_zero_monthly.parquet | 容忍 4(缺 SCADA 原始件按缺件如实报) | | ④d 振动在升与换件闭环 | scripts/component_history_build.py | 振动时间窗索引 | m5_cms_tcm/component_history.json | 容忍 4 | | ④e 三层基线 | scripts/baseline_38_build.py | 振动时间窗索引 | m5_cms_tcm/baseline_38.json | 容忍 4 | | ⑤b 事实契约 claim 生成 | scripts/sop_findings_from_ledger.py | 重算台账 | sop/findings.json 与 paradigm_r1 三份底稿 | 0 | | ⑤c 事实契约构建与渲染 | scripts/guanlan_facts_contract.py build | findings 与底稿 | guanlan/facts_contract_v0.json 与 derived 四件 | 容忍 2(契约自检不过需人看) | | ⑤a 逐件来源台账 | scripts/products_restore_missing.py --refresh | 盘上产物与族表 | outputs/场站/_provenance.json | 0;只记账不搬件 | | ⑤ 反向呼应审计 | scripts/products_reverse_audit.py --check | 产物与生成端 | 审计清单(只报账) | 容忍 5 | | ⑥ 重启组件服务(停) | scripts/_ops_stop_keep_gateway.py | run/pids.json | 组件停、网关留 | 0 | | ⑥ 重启组件服务(起) | guanlan.py serve | configs/serve.json | run/pids.json 与六个服务 | 容忍 1(有模块 DOWN 属 degraded 正常) | | ⑦ 本体六步 | src.ontology.kb_ingest、populate、chain_ingest、trend_ingest、retrieval.build、maintenance.refresh_params | 上级产物与技术资料 | objects.json、检索索引、turbine_params.parquet | 0 | | ⑦b 全场状态总览页 | scripts/windscada_overview_build.py | 重算产物与 objects.json | windscada/index.html | 0,缺产物时响亮报错不产出半张页 | | ⑧ 本体审计 | src.ontology.audit | objects.json | 审计结论(期望 0 问题) | 0 | | ⑧b 重装门户 | scripts/portal_build.py | 契约产物 | release/portal.html | 容忍 1(缺契约产物时门户保留原样) | | ⑧c 页面归口审计 | scripts/pages_audit.py --check | 页面登记表与产物 | 陈旧检测结论 | 容忍 5 与 6 与 7 | | ⑧d 版本记录一致性 | scripts/version_log.py --check | 版本记录与 version.py 的 HISTORY | 一致性结论 | 容忍 6(属文档同步这一路,不打断整条链) | | ⑧ 台账等价验收(仅 --with-verify) | scripts/rebuild_from_raw.py --verify | 随包基线 | 差异分类 | 容忍 4 与 5(4 有需人工看的差异;5 没基线即没验收) | 退出码容忍的语义汇总如表 5-3 所示。主循环的判定是"退出码为 0 或落在该步容忍集合内即视为通过",非容忍的非零退出立即中断整条链。 | 退出码 | 语义 | 典型来源 | |---|---|---| | 0 | 通过 | 各步正常完成 | | 1 | 有模块未起(degraded 正常态)或门户缺契约产物 | ⑥ 重启服务、⑧b 重装门户 | | 2 | 契约自检不过(sha 或脱敏或枚举) | ⑤c | | 4 | 有新数据,或缺现场原始件而按缺件如实报 | ①b、④c、④d、④e、⑧台账等价验收 | | 5 | 随包件这一路缺失(无基线、有未归类、被引产物不在位) | ④、⑤、⑧c | | 6 | 溯源缺失,或版本记录与代码不一致 | ⑧c、⑧d | | 7 | 数据派生页面陈旧(内嵌快照指纹与当前产物不一致) | ⑧c | ### 5.3 幂等与可重入 链上各步都是普通命令行程序,幂等且可整条重跑:② 与 ③ 与 ④b 按"产物是当前原始件的函数"重建(源件删了行也随之消失并大声报出缺了哪件、少了多少行、涉及哪些月);③b 全量重写窄仓;⑤a 由盘上账目重算台账;⑦ 的六个子步幂等覆盖。重算可以在系统运行中做(门户的「数据重算」或运维控制台按钮),也可以在系统全停时做(先 guanlan.py stop,跑完再启动,页面直接读新产物)。 为让"新数据必进重算"不靠人记,链上有两条设计:①b 步每次扫描 data/raw 的逐族指纹并把本次指纹记成新基线;rebuild_all.py --auto 会先扫一遍,发现新数据落在被 --skip-scada 或 --skip-vib 跳过的族里就自动取消跳过并打印原因(实测示例:scada_10min 新增 1 件时计划从 22 步变 24 步)。 ### 5.4 等价验收与基线 ④ 步与可选的 ⑧ 台账等价验收都以"随包基线"为标准答案,逐值比对随包件与自算件。基线目录是 outputs/场站/windscada/_pre_rebuild_20260911/,缺基线时 ④ 返回 5(跳过等价验收,不是通过);全新机器上交付包按口径不含产物,因此 ⑧ 在全新机器上必然返回 5,这与"重算失败"是两件事,链上如实标出退出码并写明"这不是通过"。要真做验收,用 scripts/set_baseline.py 把已核实的当前产物快照登记成基线,之后这一项才会真比对。 ### 5.5 raw_scan 指纹与增量 逐族指纹的构成是:递归统计该族目录下全部文件(不限扩展名)的件数、体积、最新落盘时间,加上清单摘要与子目录清单;每件按"相对路径、字节数、微秒级修改时间"排序串联后取 sha1,默认不叠内容哈希(--deep 才对不大于 8 MiB 的件逐件加内容 sha1)。快照落在 outputs/场站/_raw_scan.json,退出码为 0 无变化、4 有新增或变化、5 还没有基线。指纹字段与含义如表 5-4 所示。 | 字段 | 含义 | |---|---| | files、bytes、newest | 该族递归文件数、总字节、最新落盘时间 | | matched | 扩展名白名单命中的件数(差额单列为"信息,不计变化") | | dirs | 子目录清单(新建空目录也能被发现) | | unmatched、unmatched_n | 族表之外的件与件数(按"未归类、没有已知消费者"如实报出,不猜) | | consumed、consume_how | 被消费者读取的件数与消费者的真实取数口径 | | upstream、upstream_how | 上游归档件(要先转换才被消费)与其转换口径 | | gaps、gaps_n | 缺口级件(放了但没有任何消费者读) | | digest、top | 整族指纹与样例件 | "增量"在放置侧另有三清单口径(scripts/raw_data_check.py 与 scripts/place_raw_data.py):目标不存在为新增;目标存在且大小相同为"相同(跳过)"(源件是只读输入,同尺寸视为同一份);目标存在但大小不同为冲突,默认拒绝落位并列出,必须人确认后加 --force 才覆盖,体检器的退出码语义是 0 合规、5 结构或命名违例、6 增量冲突、7 缺源类或时间空洞、8 仅提示。 ### 5.6 slim10min 窄仓为什么存在 按所选时间窗重算的真实卡点,是每换一个时间窗就要重读 15 GB 级的 10min CSV(实测约 2 分钟每面)。而各面真正需要的列只是公共子集。scripts/scada_slim_build.py 把这份公共列子集抽成逐台一份的窄仓,一次全量扫后,任何时间窗的重算都退化为"窄仓过滤加分组"的秒级操作。 窄仓的实测口径是 48 列(23 条核心列加 25 条温度 NBM 的均值列),分七个分组:功率、风况、转速、变桨、温度 NBM、偏航、润滑液压;不裁剪行(时间范围等于该台原始件全量),缺列如实记进 columns_missing 而不造 0。本次实测 outputs/<场>/windscada/slim10min 下 39 件(38 台加一份 _manifest.json),_manifest.json 记 columns_n 为 48,构建行合计 3,300,822 行,无缺列。窄仓是 v2.9.0 补入 grd_wtc_ActPower_max 与 tur_wtc_GenRpm_max 两列之后的口径,因此文档中旧的"46 列、温度 27 列"写法已过期,实测为 48 列。 ### 5.7 scada_mdb 类目库是上游归档 现场会按"月份乘通道组"打包交付 SCADA 归档,例如某月目录下的通道组库,共 12 个类目(中文目录名与类目码对应关系是:风机数据对应 tur、温度数据对应 tmp、压力数据对应 prs、电网数据对应 grd、DigiIn 数据对应 din、DigiOut 数据对应 dot、计算数据对应 cnt、标志数据对应 flg、内部数据对应 int、统计数据对应 scd、标准数据对应 std、汇总数据对应 sum)。 取数层不直接读类目库(它只认按月分片的主件与同台补充件),因此类目库是上游归档:扫描器按信息级列出、不计变化、不假装被摄入(这是 v2.8.2 的消缺点,修前 raw_scan --check 会永远返回 4)。要让它进产物,需按时间戳与机组标识把逐台类目表对齐合成同台 10min 补充件 scada_10min/台号-YYYYMM.csv(列与主件逐列同名同序、时间戳为 ISO 形式),之后 ③ 与 ④ 与 ④c 会自动纳入。实测 9 个逐台类目与主件的前缀块一一对应(tur 91、din 93、dot 69、cnt 69、tmp 116、prs 52、grd 50、flg 42、int 13,合计 595 通道,0 缺 0 多),精度与格式按 float32 加 ISO 对齐(实测重叠 19,555,865 格,两侧都有值却不相等的为 0 格)。 SCADA 形态的接纳规则是"CSV 优先、该台没有 CSV 才回落 MDB",并把这次从哪种形态取的记在返回表的属性里供台账与页面说明来路,不静默换源。MDB 侧有两个硬限制:单表不超过 255 列,因此库内按 250 列拆表;2026-09-19 之前建的老库缺机组列,10min 老库还能用源件自带的机组列兜底,1min 老库救不了(源件没有机组标识列),需重跑 scripts/csv_to_mdb.py 重建。 *** ## 6 判级与算法设计 ### 6.1 判级矩阵与四维证据 判级矩阵覆盖九个系统(src/windscada/taxonomy.py 的 SYSTEMS):变桨、偏航、主轴承、齿轮箱、发电机、变流器、主控与传感网、叶片与叶根、塔架与基础。其中叶片与叶根、塔架与基础不参与 SCADA 侧判级(代码里列为无 SCADA 数据的两个系统),因此页面上常见的"七系统判级矩阵"指的是可由 SCADA 判级的七个系统;液压(蓄能)并入变桨,温度 NBM 是方法而不是系统,这两点写在代码注释里。 判级词表有五态:报警、良好、不可判、优秀、无数据;取严合并的权重是报警最严、其次良好、再次不可判、优秀与无数据并列(src/windscada/taxonomy.py)。子系统面另用四键取严(报警、良好、优秀、不可判),偏航面先过滤不可判再取三键。七系统判级矩阵与四源融合数据流如图 6-1 所示。 ![图 6-1 判级矩阵与四源融合数据流](figures/fig-des-04-判级与融合.png) 各系统的判级函数与证据维度如表 6-1 所示。四个子判定面各自有四个维度(四维证据),逐维度给出判词,再按取严序归到系统级。 | 系统 | 判级函数 | 四维证据或依据 | 判词示例 | |---|---|---|---| | 变桨 | src/windscada/subsys/pitch.py 的 registry | 蓄能与压力调节、变桨轴承与轮毂润滑、执行与位置反馈、油路与密封 | 蓄能失效候选(定向查)、记基线观察 | | 偏航 | src/windscada/subsys/yaw.py 的 registry | 偏航轴承与润滑、驱动装置、液压制动、对风控制 | 报警、良好、优秀 | | 主轴承与齿轮箱与发电机 | 温度通道经 temp_nbm.registry,再按通道到系统的映射归口 | 温度 NBM 同工况比档与相对离群 | 温度候选(定向查)、传感器候选(A 类,非热)、记基线观察 | | 变流器 | 可靠性归因码到部件的映射与状态统计 | 停机段归因、故障码统计 | 报警、良好 | | 主控与传感网 | 报警台账与系统辅助件 | 多报警台、传感器偏差、通信错误 | 报警、良好 | | 叶片与叶根、塔架与基础 | src/windscada/subsys/structure.py 的 registry | 塔筒频率、塔筒振动、塔筒湿度、基础与法兰;叶片 A 与 B 与 C、叶根螺栓 | 报警、不可判 | 温度判词到矩阵级的映射是:温度候选与传感器不可信(A 类)归为报警,记基线观察归为良好,不可判(返服嫌疑)归为不可判。融合面的矩阵级枚举是 ok、meta、bad、warn、note 五种,关闭类最先判,取严权重为 bad 最重、其次 warn、再次 note、ok 最轻。 ### 6.2 温度 NBM 与同油温档带宽 温度 NBM 的口径是"同一功率档内比同族温度",功率档按 500 kW 一档划分(0 至 4500 kW)。样本门是同一单元样本数不少于 18;台级样本少于 500 行时判"不可判(样本不足或返服嫌疑)"。偏差与稳健 z 值为:偏差等于实测中位减去基准中位,稳健标准差取 1.4826 倍中位绝对偏差并从下限 0.3 起算,z 等于偏差除以该稳健标准差。判级阈值为:偏差不小于 15 K 判 A 类(传感器不可信,非热);偏差不小于 4.0 K 且 z 不小于 3.0 判温度候选;偏差不小于 2.5 K 且 z 不小于 2.0 判记基线观察。 同油温档带宽的判据在蓄能(液压)面而不是温度 NBM 文件里:按油温 5 K 一档分箱(发电态功率大于 100 kW 的行),同一档内样本数不少于 24 时计算压力带宽(段内最大值减最小值);带宽比值不小于 1.5 时入轴,轴数不少于 2 时判"蓄能失效候选(定向查)"。热链面另按高油温档统计占空:档位不小于 45 摄氏度且样本数不少于 30 才出值。 温度面还有两个故意固定的对照时间窗:ref 是 2025-07-01 至 2026-01-01(限电前干净时间窗),ref_sm 是同历月早一年;它们不随用户所选时间窗漂移,因为季节解耦的对照基准一旦跟着漂就失去对照意义,页面上如实标注这一点。 ### 6.3 七镜头曲线与限电前干净时间窗 七镜头曲线的定义与口径在 src/windscada/perf/curves.py:L1 风速与功率、L2 功率与桨距(含三叶互比)、L3 功率与发电机转速、L4 转矩与转速(转矩等于功率除以发电机角速度,理论形态为转速平方族)、L5 风速与风轮转速(叶尖速比)、L6 功率与功率系数、L7 发电机转速与风轮转速之比。机型常数是风轮半径 65.0 米、扫风面积 13,273 平方米、铭牌齿比 119.752、贝兹极限 0.593。 分箱边界为:风速 3.0 至 15.5 米每秒、步长 0.5;功率 0 至 4250 kW、步长 250;转速比 600 至 1760、步长 40。显著门共八项按绝对值锚定;离群判据是稳健 z 绝对值不小于 3 且残差不小于对应门限;工况段闸是功率分箱不小于 250 kW、转速比分箱不小于 900。另有三条物理硬闸:功率系数不得超过贝兹极限、齿比恒等(偏差超过 1.5 判离群)、叶尖速比须呈平台。列活性铁律要求同一列取值数大于 1、卡死比例小于 0.5 且落在物理域内,否则该列不参与镜头。 限电前干净时间窗是 2025-07-01 至 2026-01-01(2025-H2),代码里把它写成常量并注明这是"限电前干净判别时间窗",与七镜头曲线的判别时间窗一致。选到 2025-07 至 2025-12 这一段时,发电性能页直接使用正式产物而不重算。 ### 6.4 M9 控制参数一致性 M9 是控制参数一致性检查,摆在发电性能页上,口径在 src/windscada/perf/control.py。四个判据如表 6-2 所示。P 封顶取有功功率上限通道的 99.5 百分位,ω 封顶取发电机转速上限通道的 99.5 百分位,只取有功功率均值大于 100 kW 的行参与统计。 | 判据 | 输入列 | 方法 | 阈值 | |---|---|---|---| | C1 双封顶 | grd_wtc_ActPower_max、tur_wtc_GenRpm_max | 按容差分组(容差 25 kW 与 8 rpm),组内计数不少于 2,逐台与组中位比 | 偏差超过 3 倍容差即离群,判"版本或参数差异·准定论载体" | | C2 K 聚类 | 有功功率均值与发电机转速均值 | 中载区 1200 至 2800 kW 内计算 K 等于功率除以转速立方,取中位乘 100 万 | 离群门为 5 倍 1.4826 倍中位绝对偏差,稳健标准差下限为百万分之一 | | C3 桨距调度 | 功率分箱与桨距角 | 同功率档内比桨距角 | 同档差门 0.5 度 | | C4 尺子 | 满发功率 99.5 百分位、转速封顶、额定桨角 | 满发段取功率大于 3800 kW 且样本不少于 50 | 出 pitch_rated,供交叉核对 | M9 按所选时间窗重算(control.registry 接受 span 参数),产物是 control_profile.parquet(逐台的 P 封顶、ω 封顶、样本数、K 中位、额定桨角)与 control_schedule.parquet(逐台逐功率档的桨距)。实测(38 台):P 封顶中位从干净时间窗的 4190 kW 变为 2026 年的 4181.7 kW 与 2026 年一季度的 4177.5 kW。 ### 6.5 可靠性指标 部件可靠性表的口径在 src/windscada/perf/reliability.py 与 src/windscada/perf/faults.py。停机段门是"连续 10min 行不少于 6 行"(即不少于 1 小时),段结束时间是末行加 10 分钟。归因有两级:T1 紧归因时间窗取段起点前后 30 分钟内的首发故障码,T2 宽归因时间窗取前 6 小时内的最近一条;实测紧归因时间窗未命中的段里有 53% 在段前 6 小时内能找到报警,中位段长 2.5 小时、最长 174 小时。调度令 1007 与 1008 判为远程停机(业主单位(从略)与主机制造商(OEM,名称从略)),不计入设备责任。 三项指标的口径如表 6-3 所示。外部类(运维操作、风况(外部)、电网或供电(外部))单列统计并明确"外部类不计设备可靠性"。 | 指标 | 公式或口径 | 注意点 | |---|---|---| | 平均无故障间隔(MTBF) | 台时减去停机小时再除以事件数;无记录停机不给值 | 部件可靠性模块的口径,台时目前用理想日历 | | 平均停机间隔(MTBO) | 在上一项基础上再扣除调度令停机后除以事件数 | 停机归因模块的口径,台时已改为实测台时 | | 单次停机时长(MDT) | 停机段时长的均值;就地与切入边界类码按口径剔除 | 两个模块同口径 | 台时口径的实测对比很重要:2026 年 1 至 7 月理想日历台时为 194,050,实测台时为 165,475(差 17%),原因是尾月只有 6 天数据;若沿用理想日历,平均停机间隔会虚高 18%(239 小时实为 203 小时)。因此 faults.py 已改为用实测台时(由 loss_monthly 的行数除以每小时 6 个节拍得到),并在不可得时才回退理想日历。需要如实指出:reliability.py 的部件可靠性表仍使用理想日历台时,两处口径目前不一致,属待统一项(见第 17 章)。 ### 6.6 融合面四源与逐台判级 融合面的四源是振动、温度、润滑、油液,口径写在 src/windscada/subsys/fusion.py 的模块头。融合面读取的输入件包括:handoff_vibration_v2.json、oil_samples_index.parquet、watch_channels_monthly.parquet、由 temp_nbm.registry 出的温度比档结果、mblub_monthly.parquet、alarms.parquet、workorders.parquet、油样合并报告 json,以及 CMS 报告转录件。 融合纪律有两条:一是禁止按源数加权(同一机制的多个观测面不等于多源印证,源多不代表结论更强);二是 claim 时间窗对齐(振动数据到 2026-08、SCADA 数据到 2026-07-07、油样到 2025-08),避免拿不同时点的证据互相印证。逐台判级的产物字段包含台号、部件、振动结论、语义类、级别、设备状态、证据状态、振动信号、温度通道、温度偏差、温度判词、时间窗区间、末次油样、油样新鲜度、claim 时间窗等。 四源的权重不是数值表而是优先级:关闭类最先判(销案、正样本、无新证、可降、降观察、无异常先归为正常),其余按取严合并;振动侧判 bad 或 warn 且非 meta 时,对应系统升级为报警;windcms 报告三方对拍也取严(危险与报警抬级,不可判且本层无异常证据则判不可判,良好与优秀不动)。系统关键词映射表把部件名归到系统,未收录的单元写"未收录"而不是写正常。 ### 6.7 残月识别 "产物是当前原始件的函数"这条原则要求尾月样本很少时也如实出值,而不是加最小样本门去猜。同时,样本严重不足的月份必须能被识别出来,否则环比与均值会被拉偏。残月识别的实现在服务层:覆盖度等于该月行数合计除以每小时 6 个节拍再除以机组台数再除以当月日历小时;日条数等于该月条数除以覆盖度与当月天数的乘积,仅当覆盖度大于 0.02 才出值;覆盖度小于 0.5 的月份列为残月。 实测的典型残月是 2026-07:只有 6 天数据,覆盖度 19.4%。页面上残月以灰虚线柱表示,并写"仅 N 天"(N 等于覆盖度乘 30 后取整);报告导出侧对覆盖度小于 0.5 的月份写"(不满月)",并声明环比不纳入残月。融合面与温度面也在各自口径里写明"6 天时间窗"的样本事实(例如温度面记"应有 288 个节拍、实测 145 个")。 ### 6.8 算法到输入列到判据到产物 各算法与输入列、判据、产物的对应关系如表 6-4 所示,用以支撑"每条判据都能指到输入列"这一要求。 | 算法或模块 | 关键输入列 | 判据要点 | 产物 | |---|---|---|---| | 温度比档(subsys/temp_nbm.py) | 各温度通道的均值列(按 500 kW 功率档分箱) | 偏差与稳健 z 双门限;样本门 18 | windscada/temp_bins.parquet,注册表出判词与依据 | | 蓄能带宽(subsys/hydraulic.py) | 液压油温均值、液压压力最大最小均值 | 5 K 油温档内带宽比不小于 1.5 且轴数不少于 2 | windscada/hydraulic_accum.parquet | | 变桨面(subsys/pitch.py) | 开关量 on 秒、柱塞动作次数、压力、三叶桨距角与有功与转速 | 停机顺桨段与满发段与运行段同工况分档的零位偏差 | pitch/pitch_daily.parquet、pitch/pitch_zero_monthly.parquet | | 偏航面(subsys/yaw.py) | 偏航压力均值、偏航润滑与柱塞时间、偏航计数、风偏差 | 四维取严;偏差清洗与相关位置 | windscada/yaw_daily.parquet、yaw_err_clean.parquet | | 七镜头(perf/curves.py) | 有功功率均值、限电命令、风速、发电机转速、风轮转速、三叶桨距角、环境温度 | 八项显著门;离群 z 与残差双门;三条物理硬闸 | windscada/curve_lenses.parquet、curve_liveness.parquet | | M9(perf/control.py) | 有功功率上限、发电机转速上限、有功功率均值、转速均值 | C1 至 C4 四判据 | windscada/control_profile.parquet、control_schedule.parquet | | 停机与归因(perf/faults.py) | 停机事件、报警台账、调度令码 | 段不少于 1 小时;T1 与 T2 两级归因 | windscada/stop_events.parquet | | 损失与可用率(perf/availability.py) | 风速、有功功率、功率曲线分箱 | 理论功率插值后算损失;风速低于 3.0 米每秒记 0 | windscada/loss_monthly.parquet | | 融合面(subsys/fusion.py) | 振动 handoff、温度比档、润滑月度、油样索引、报警与工单 | 关闭类最先判加取严;禁按源数加权 | 内存逐台表(融合面链盘)与 handoff 出件 | | 系统矩阵(taxonomy.py) | 各面上级产物与 windcms 报告转录 | 取严合并;未收录不判正常 | system_aux.parquet 与内存矩阵 | *** ## 7 时间窗口径设计 ### 7.1 时间窗词表 2026-09-21 的用户令要求"部件问题与发电性能随所选时间窗变化",并新增自定义起止日期(含两端)。实现的词表如表 7-1 所示(scripts/windscada_serve.py 的 WINDOWS 与 months_of 与 win_range)。 | 时间窗写法 | 含义 | 对月度件的作用 | 对日粒度件的作用(含两端) | |---|---|---|---| | 近30日 | 数据末端 1 个月 | 该 1 个月 | 该段月首至月末 | | 近90日 | 数据末端 3 个月 | 该 3 个月 | 该段月首至月末 | | 2026年 | 预设段 | 2026 年 1 月起至数据末端 | 段首月首至段末月末 | | 2026H1 | 预设段 | 2026-01 至 2026-06 | 2026-01-01 至 2026-06-30 | | 2025H2 | 预设段 | 2025-07 至 2025-12 | 2025-07-01 至 2025-12-31 | | 全程 | 全部月份 | 全部 | 0001-01-01 至 9999-12-31 | | YYYY-MM | 单月 | 该月 | 该月 1 日至月末 | | YYYY-MM~YYYY-MM | 月区间 | 区间内各月 | 起月 1 日至止月月末 | | YYYY-MM-DD~YYYY-MM-DD | 自定义起止(含两端) | 与之相交的月 | 原样,含两端 | 含两端的实现方式是:win_range 原样返回日期串并按起不大于止排序,日粒度过滤在 in_win 里用"上界加 1 天再减 1 秒"来含住止日当天。月度件按所跨月份取整,日粒度件按日精确,页面上写明这句,避免读者以为月度均值能切到天。默认时间窗是 2026年(服务端取参数默认值与前端一致),工作台 v2 顶栏提供两个日期输入框与「应用」按钮,校验起不大于止,非法输入拒绝并提示。 ### 7.2 哪些随所选时间窗、哪些不随 随与不随的划分如表 7-2 所示。需要强调的是:不随时间窗变化的内容不是"忘了改",而是各有自己的数据时点或对照意义。 | 内容 | 是否随时间窗 | 机制 | |---|---|---| | 报障统计、五态控制策略、温度月轨迹、停机台账 | 随 | 月度件按 months_of 过滤 | | 判级四轴(变桨、偏航、蓄能、温度) | 随 | taxonomy.system_matrix 接受 span,各面 registry 接受 span;变桨用日粒度件精确切片,偏航与蓄能与温度走窄仓重算 | | 部件可靠性表(平均无故障间隔(MTBF)、单次停机时长(MDT)、停机时长) | 随 | reliability.overview 接受 span;停机事件与报警都是日粒度 | | 发电性能的 7 张月度时序图 | 随 | 按 months_of 过滤(月度件按月取整) | | 发电性能的七镜头曲线 | 随 | /api/curves 带时间窗参数,按时间窗重算分箱;选到 2025-07 至 2025-12 时直接用正式产物 | | M9 控制参数一致性 | 随 | control.registry 接受 span,走窄仓 | | 温度面的 ref 与 ref_sm 对照时间窗 | 不随(故意固定) | 季节解耦的对照基准,跟着漂就失去对照意义 | | 振动判级与厂家报告转录与工单台账 | 不随 | 各有自己的数据时点;页面上如实标注"不随本时间窗" | ### 7.3 按时间窗缓存与 pending 契约 按所选时间窗重算一律走"后台算加进程内缓存":缓存分三类(判级矩阵、曲线、M9),键由起止日期组成;命中即回,未命中则起后台线程并把"正在算"的状态回给页面,页面轮询再取。三类缓存的状态变量是 sysmx_pending、m9_pending 与 win_pending(win_pending 等于前两者之一为真),曲线另用 building 标记。缓存是进程内常驻字典,没有过期时间也没有容量上限;另有产物指纹缓存,过期时间为 2 秒,用于产物更新后自动重载。 同一时间窗只算一次靠"在算集合"去重;算失败会记名并保留错误摘要,供页面与日志查看。为避免"预热线程与请求线程同时算两份中间帧"把内存吃光(远端实测问题),重活加了一把全局串行锁(_HEAVY_LOCK),并在拿锁时打印可用内存。服务启动末尾会起一个名为 win-warm 的守护线程做后台预热:先算六个预设时间窗的判级矩阵(每时间窗等待上限 180 秒),再算默认时间窗的七镜头(等待上限 240 秒);M9 不预热。预热可用环境变量关闭。 pending 契约的关键是"绝不静默把旧口径当新时间窗":在算期间页面在脸上写明"下面这几张卡还是上一份口径",并在轮询到缓存就位后整段替换。时间窗与 pending 契约的时序如图 7-1 所示。 ![图 7-1 时间窗按窗重算与 pending 契约](figures/fig-des-05-时间窗与pending.png) 前后端的等待参数如表 7-3 所示。 | 环节 | 参数 | 实测或口径值 | |---|---|---| | 判级矩阵与曲线按时间窗重算 | 预估耗时 | 判级约 25 秒每时间窗;七镜头约 9 至 44 秒每时间窗 | | M9 控制参数按时间窗重算 | 预估耗时 | 服务注释约 5 秒每时间窗,版本记录约 0.6 秒每时间窗(两处不一致,待统一) | | 前端判级 pending 轮询 | 间隔与次数 | 每 5 秒一次,至多 40 次 | | 前端曲线 building 轮询 | 间隔与次数 | 每 6 秒一次,至多 40 次 | | 前端问答轮询 | 间隔 | 每 2 秒一次 | | 后台预热 | 覆盖范围 | 六个预设时间窗的判级加默认时间窗的镜头(不含 M9) | | 运维控制台 | 轮询间隔 | 每 2 秒一次 | ### 7.4 时间窗口径下的实测对比 按所选时间窗重算生效后,逐系统报警机组数会随时间窗变化,这是"判级真的按所选时间窗重算"的直接证据。本机实测(38 台):变桨报警为 15 与 13 与 10 台,偏航报警为 15 与 11 与 5 台,齿轮箱报警为 7 与 4 与 4 台,分别对应 2026年、2025H2、2026-07 三个时间窗;曲线图注随所选时间窗走且样本量随时间窗变化。远端复核(2026-09-21):变桨报警从 15 台变 7 台、偏航从 15 台变 3 台,曲线图注写"时间窗 2026-07-01 至 2026-07-31(随所选时间窗)"。 *** ## 8 服务与前端设计 ### 8.1 HTTP 服务与路由族 工作台服务用 Python 标准库的线程化 HTTP 服务实现(scripts/windscada_serve.py 引入 ThreadingHTTPServer 与 BaseHTTPRequestHandler),首请求把产物读进内存,之后每次取数前比对产物指纹(相关文件的路径、微秒级修改时间与大小的摘要,过期时间 2 秒),指纹变了自动重载,因此产物更新后不需要重启服务。路由族如表 8-1 所示。 | 路由族 | 代表路由 | 说明 | |---|---|---| | 页面族 | / 与 /v2 与 /v2/snapshot 与 /turbine/台号 与 /problem/台号/系统 | 经典总览、v2 单页、快照下载、逐台页、单问题页 | | 取数族 | /api/fleet 与 /api/curves 与 /api/problem 与 /api/turbine 与 /api/vibcms 与 /api/maint_survey 与 /api/maint_std 与 /api/maint_framework 与 /api/dq_findings | 判级矩阵与链盘、七镜头、单问题、逐台、振动融合、检修四链与数据质量 | | 本体族 | /api/ontology 与 /api/ont_list 与 /api/ont_obj 与 /api/ont_chain 与 /api/channels | 本体页与机制链;v2 实际只懒取 /api/ont_chain | | 模型与报告族 | POST /api/ask 与 /api/ask_status 与 /api/ask_models 与 /api/rpt_compose 与 /api/rpt_export | 问答、状态轮询、档位列表、汇报纸组装与导出 | | 快照与事实与运维族 | /api/snapshot 与 /api/facts 与 /api/facts/claim 与 /api/reload | 快照、事实契约与单条 claim、显式清缓存并回显新旧指纹 | | 静态件族 | /static/* 与 /turbines/* 与 /windcms/* | 总览页与逐台静态件、振动页面件(限目录只读) | | 兜底 | 其余路径 | 缺件走结构化缺件页(HTTP 200),其它异常返回 500 页并记日志 | ### 8.2 网关前缀改写与 data-abs 豁免 网关把组件页里的根相对链接按前缀改写,否则组件页里的 / 会指向网关根而不是组件根。改写规则如表 8-2 所示(scripts/guanlan_gateway.py 的 rewrite())。 | 改写对象 | 规则 | 例外 | |---|---|---| | 属性类 | href、src、action、content 后紧跟单个斜杠的根相对写法加前缀 | 双斜杠协议相对与 data: 不动 | | 样式类 | url() 内的单个斜杠根相对写法加前缀 | 同上 | | 脚本调用类 | fetch、open、EventSource、WebSocket、assign、replace 后字符串字面量里的根相对写法加前缀 | 同上 | | 赋值类 | 裸 location 赋值的根相对写法加前缀 | location.href 已被属性类规则覆盖 | | 字符串常量类 | 字符串里出现的 /api/ 加前缀(避免拼接出的接口地址漏改) | 同上 | | 绝对地址类 | 指向本机旧端口与新端口的绝对地址归到对应前缀 | 旧端口映射表保留在代码里 | | data-abs 豁免 | 标签上标 data-abs="1" 的 href 先换成哨兵、改写完再摘掉,即这条链接不被前缀改写 | 两种书写顺序(href 在前或 data-abs 在前)都认 | data-abs 豁免的实际用途是"组件页要指回网关根":工作台页面里的「返回观澜门户」用根相对写法并在标签上标 data-abs="1",否则它会被改写成 /detail/ 而永远回不到门户。此外网关对 /ops 系列的响应加 no-store 缓存头(否则页面脚本更新后浏览器仍用旧版,表现为"改了没反应"),门户静态件按修改时间与大小缓存,/release/ 提供只读暴露且不合并主库。 ### 8.3 页面烘焙与静态件 除实时取数外,系统还有一条"烘焙"路线:把接口响应烤进单个 HTML 文件,使其离线也能打开。烘焙器是 `app_frontEnd/app_frontEnd_guanlan/pages/snapshot.py`(旧路径 `src/windscada/ui/snapshot.py` 留兼容壳)的 bake(),注入 snap 标记、六个预设时间窗与语言对等文件指针,并放行问答与报告导出类接口走网络。总览「全场状态」页就是这条路线:重算链的 ⑦b 步用组件同一套取数函数生成 outputs/场站/windscada/index.html,工作台 v2 的 iframe 指向它。 静态件落点是:/static/* 从 L0 标准仓读,/turbines/* 从同一仓读并注入浅色主题,/windcms/* 只读振动产物目录并限制在本目录内。三者缺件时都返回结构化缺件页(HTTP 200),写明缺哪一件、属于哪条产物线、怎么补,并给出运维控制台与自检入口。 ### 8.4 前端单页、轮询、中英切换与链接契约 工作台 v2 是单页应用(`app_frontEnd/app_frontEnd_guanlan/assets/app.js`,133 KB,另有同目录 charts.js 30.8 KB 与 app.css 24.2 KB;旧路径 `src/windscada/ui/` 留壳)。首屏只取 /api/fleet(判级矩阵与链盘),其余标签按需懒取;待算状态按第 7 章的间隔轮询。中英切换用查询参数 ?lang=en(语言来源是页面的 lang 属性,服务端据此投影权威英文名),localStorage 中只有一个键 guanlan.portal,用于记住门户来路;静态快照模式下查询参数不生效,改用对等语言的真实文件指针。前端契约如表 8-3 所示。 | 契约 | 写法 | 说明 | |---|---|---| | 标签切换 | #tab=标签名 | 十个工作台标签;切换触发 hashchange | | 系统下钻 | #tab=component 与 sys=系统 slug | 从系统格下钻到该系统的问题清单 | | 逐台页 | /turbine/台号 | 独立页,含返回门户链接(标 data-abs) | | 单问题页 | /problem/台号/系统 | 系统标识经 sys_norm 双向归一(中文名、前端 slug、大小写都认) | | 回经典页 | / 加 lang 与 win 参数 | 从 v2 回到经典总览并保留语言与时间窗 | | 系统标识认不出时 | 页面写"系统标识无法识别" | 绝不写"该系统在判级时间窗内无非常态"(对报警机组那是假陈述) | v2 页面还有一条硬要求:可展开的「查看完整依据」不得为空。该类卡片的行来自融合面链盘,而 SCADA 侧只覆盖多报警机组,两边集合不同时旧的查表会取空;现在的口径是缺依据时按本行字段自组句(部件、链路走到第几步、卡在哪一步、证据源),永不空。 *** ## 9 本体与知识层设计 ### 9.1 六个摄入步与对象库规模 本体层的构建是重算链 ⑦ 的六个子步,逐步说明如表 9-1 所示。本体层"零判级权":它只转录 L1 正本(判级矩阵与融合面)的结论,每条判级对象带审级与 claim 时间窗,证据指回 L1 产物,同部件多源判级并存不合并。 | 顺序 | 命令 | 输入 | 输出 | |---|---|---|---| | 1 | python -m src.ontology.kb_ingest | 4.0 MW 级海上机组的技术资料、四个台账、码表与手册 | 码表、作业指导、故障树、预防性任务等对象 | | 2 | python -m src.ontology.populate | 上述全部产物与判级矩阵 | 铺开判级与台账对象(含证据与判级对象) | | 3 | python -m src.ontology.chain_ingest | 运行中的链盘(/api/fleet) | 逐台一个决策对象,记六步状态与卡点与下一步 | | 4 | python -m src.ontology.trend_ingest | component_history.json 与链盘 | 在升与闭环趋势证据(挂到机组与部件) | | 5 | python -m src.ontology.retrieval 的 build(use_vec=False) | 对象库 | retrieval_index.json 检索索引 | | 6 | src.ontology.maintenance 的 refresh_params | 技术资料与契约 | turbine_params.parquet 实机参数表 | 对象库实测规模如表 9-2 所示(outputs/<场>/ontology/objects.json,4,452,520 字节,对象结构为 id、type、props、links 四个字段)。 | 对象类型前缀 | 对象数(实测) | 说明 | |---|---|---| | workorder | 5,567 | 检修工单展开 | | component | 580 | 部件与机群级系统对象 | | wi | 559 | 作业指导步骤 | | task | 559 | 预防性任务 | | alarmcode | 555 | 报警码(含中文码义) | | verdict | 411 | 判级对象(带审级与证据) | | evidence | 411 | 证据对象(指回 L1 产物) | | oilsample | 404 | 油样索引对象 | | catalog | 207 | 目录与分册 | | doc | 303 | 文档与图纸索引 | | failmode | 150 | 失效模式 | | turbine | 38 | 机组 | | decision | 39 | 决策链进度(一台一个) | | mechanism | 8 | 失效机制 | | 合计 | 9,791 | 对象库当前对象数 | 按对象自身的类型字段统计(与上表按顶层键前缀统计口径略有差异,差异来自 Doc 类含文档与目录两种前缀):工单 5,567、部件 580、预防性任务 559、作业指导 559、报警码 555、文档类 510、判级 411、证据 411、油样 404、失效模式 150、决策 39、机组 38、失效机制 8。对比参考:docs/系统设计说明.md 记录的 2026-09-17 时点为 9,613 个对象,本次实测为 9,791 个(对象库文件修改时间 2026-09-21),两个数字都如实保留并注明口径日期;检索索引文件 2,842,037 字节,实机参数表 58,397 字节。本体审计(python -m src.ontology.audit)的期望值是 0 问题,判级时间窗口径常量为"近 90 日固定时间窗(至 2026-07-07,曲线轴为 2025H2)"。 ### 9.2 MCP 纯函数只读复用 本体层对外暴露一组只读工具(app_ontology/.../ontology/mcp_server.py),全部为纯函数、模型零写权、判级零生成,只转述对象里的判级原文与审级。工具共 18 个,另有 1 个接地闸,如表 9-3 所示。 | 工具 | 用途 | |---|---| | ont_stats | 本体库概览与判读纪律(全场级问题开局) | | ont_fleet_verdicts | 全场判级速查(一次给全 38 台的台号、部件、级别、审级) | | ont_turbine_brief | 台级汇总(判级带审级、机制、工单、油样、决策) | | ont_brief_pack | 一站式取证包(台级问题首选,一轮取齐) | | ont_get | 按 id 取对象全文(props 与 links) | | ont_query | 按类型、台号、关键词过滤查询 | | ont_links | 对象的正向链接与反向被引 | | ont_search | 混合检索(术语加语义召回相关对象 id) | | ont_mechanism_search | 失效机制库检索 | | ont_scenario | 排期沙盘推演结果(建议级) | | ont_loss | 损失分析(近 N 月分态与台级损失结构) | | ont_risk | 风险跟踪面(监视读数、机制状态、验收状态机、盲区纪律) | | ont_workorder | 检修工单查询(按台、系统、关键词) | | ont_chain_fault | 链二抢修:报警码到码义到失效模式到手册分步处置 | | ont_chain_preventive | 链一预防:系统或部件到失效模式到预防性任务到作业指导步骤 | | ont_fault_tree | 链三故障树:系统失效模式按实证量排序加征兆码 | | ont_maint_plan | 链四计划:按风险级与命中数排序的检修排程建议 | | ont_doc_search | 技术支持:图纸、原理、手册检索并返回现场可调阅路径 | | claim_check | 接地闸:最终回答前必须调用,台号与数字须来自本会话工具事实,越界即拒 | 这组工具同时被多处复用,且"只读"不是口头声明而是可核事实:整份模块里文件写入、目录创建、进程调用与网络请求四类接口零命中;对象库缺失时直接响亮报错并提示先跑铺开步,不静默降级;读库按对象库文件的修改时间失效重载,避免读到静默陈旧的库。复用方有五处:离线问答代理(同进程直连,工具与接地闸零复制)、工作台本体页的三个服务端函数、工作台机制链接口的按类派发、问答服务的取证调用,以及总览页烘焙时的本体数据注入。工作台侧的回答会自动附结构化事实契约引文块,引不到契约条时明写"未被契约背书"。 ### 9.3 机制链、决策台与对象浏览器 机制链(chain_ingest)把逐台处置进度落成决策对象,记六个步骤的状态、卡点与下一步;它不是证据对象,而是系统推出来的处置状态,因此需要服务在跑(它从工作台的链盘接口取数)。本体层不设独立页面标签,承载页是工作台的第 7 个标签「检修决策」(口径原话:"检修决策等于本体展现的窗口"),页内四段分别是「机制链视图 · 29 号范例(证据到机制到判级到沙盘到动作到验收)」「失效机制库」「决策台(决策捕获加月度滚动)」「对象浏览器」。这四段的取数接口是本体概览、对象列表、对象详情与机制链派发(按故障、预防、故障树、计划、文档五类分别派给对应的只读工具);这些接口在生成单文件快照时也会被逐个烘焙进页面,因此离线打开也能看。页面页脚写着一句纪律:本体是只读语义层,判级权在 L1 加审级,模型与网页均零判级权。 事实契约是把结论对外化的那一层:每条结论由 scripts/guanlan_facts_contract.py 生成,包含 claim 编号、脱敏来源引用(文件名加 sha16 与章节)、本条 sha256、时间窗、聚合层级与裁决(六枚举之一),并派生出四类消费者件(门户结论段、工作台卡片、问答引用、报告摘要)。当前口径是"由重算台账生成 claim"(⑤b 步),因此门户结论段、问答与报告都随重算刷新;claim 六族是报警集中、重复检修、停机损失集中、温度相对离群、功率曲线偏离、振动面过闸线,相对判据一律封顶"候选"。 *** ## 10 本机模型接入设计 ### 10.1 档位与配置 模型接入完全可选:判断在代码,模型只做问答、解释与初筛。档位与显存需求写在 configs/models.json,如表 10-1 所示;配置注释明确"换模型改这里加 ollama pull,不改结论"。 | 档位键 | 模型 | 显存需求(写入配置) | 用途 | |---|---|---|---| | default_qa | qwen3:8b | 6 GB | 默认问答,关闭思考模式;校闸不过自动升档一次 | | escalate_qa | qwen3:32b | 22 GB | 复杂题升档(2026-09-22 由本机未装的旧档名改为本机实装档) | | review | deepseek-r1:14b | 10 GB | 交叉审核初筛票(非独立审级) | | embed | bge-m3 | 2 GB | 检索向量 | 配置文件另给出三套显存档位组合(24 GB 及以上四档、12 至 16 GB 三档、纯 CPU 两档)与四条拉取命令,运行地址是本机 11434。本机实测四档全部可用(自检逐档打印档位与模型名)。 ### 10.2 问答校闸与升档 问答链路(app_ontology/.../ontology/fast_agent.py)的口径是"校闸不过不出正文":回答里引用的对象编号必须存在、引用判级对象的句子必须写出该对象的判级词、句中的系统名必须与所引对象一致;拦截原因会明确写给用户(换问法、点名机组号或结论编号、查看契约结论清单)。慢档(32B 级)单轮改写超过 60 秒时只给一次改写机会,避免为一个不过闸的答案烧掉十几分钟。 升档的规则是:默认档被闸拦下则用升档目标重答一次;升档也拦则照旧不出文,但把两次拦截原因都带回。升档目标不再硬编码为单一档名,而是按"配置档位且确已安装"解析(先取 configs/models.json 的 escalate_qa 且验证本机确有该模型,再回落到代码内的映射表,都没有则返回空并响亮回落同时打日志)。这一改动来自 2026-09-22 的人工测试消缺:原先升档目标写的是本机未安装的档名,导致升档调用拿不到摘要、问答终态为 error,用户只看到"复核中"。 ### 10.3 耗时预算 耗时预算按档位给出(app_ontology/.../ontology/fast_agent.py 的 BUDGET_S):8B 档 30 秒、另一轻档 30 秒、32B 档 120 秒(32B 权重约 18.8 GB,单轮可达数分钟,属"离线慢审")。超预算后不再改写,直接交给升档或不出文。实测参考:本机自检的 Ollama 探针耗时 171.8 秒(同一底稿另一次记录为 114.09 秒),审计流水里也有探针超时 90 秒的失败记录,说明本机模型可用但慢,属纯 CPU 或显存不足场景。 ### 10.4 审计日志 每次模型调用写一条 JSON 到 logs/audit/llm_audit.jsonl,字段如表 10-2 所示(依据该文件当前 40,751 字节内容与末两条实样)。 | 字段 | 含义 | |---|---| | ts | 调用时间(ISO 形式) | | purpose | 用途(如 probe、ask、review) | | model | 模型名 | | digest | 模型摘要指纹 | | prompt_sha16 | 提示指纹前 16 位 | | options | 参数(温度、随机种子、生成长度上限) | | think | 是否开启思考模式 | | tools | 是否带工具 | | attempt | 第几次尝试 | | latency_s | 耗时(秒) | | status | 结果(ok 或 fail) | | error | 失败原因(异常类型与消息,截断保存) | | output_sha16 | 输出指纹前 16 位 | | out_chars | 输出字数(可用于判断是否"零字成文") | 审计流水与模型闸的其他纪律(固定随机种子与温度使同问同证据同答、同一时刻只有一个生成请求、瞬断自动重试一次、连续三次失败熔断 60 秒并明示、探针成功即清熔断)共同保证"模型部分可复核"。两条如实记录的观测:一是本机的审计流水在服务运行期间持续追加(本次只读观测中从 108 行增至 114 行),因此它是"活的"证据而不是静态文件;二是成功记录里会残留上一次尝试的失败原因(重试成功的那一行仍带超时字样),这是字段清理不彻底的小瑕疵,已列入待统一项。 ### 10.5 未装模型时的降级与如实标注 未装模型时的降级分两层,口径不同。检索层是"真降级":向量检索不可用时自动改为纯词法检索并打印说明,绝不让检索层因为缺模型而整个不可用;重算链因此刻意只建词法索引(不建向量索引),所以离线机器上的对象库、机制链、对象浏览器与图纸检索全部照常可用。模型层是"响亮报错、不静默回落":模型闸在连续三次失败后熔断 60 秒并明确报"本机模型未就绪(调用方应报 503 或明示,不得静默回落)";页面把连接被拒一类的底层错误改写成"本机模型未启动(Ollama 127.0.0.1:11434 拒连);离线版不转云,请先启动 Ollama",问答接口返回 503,档位列表接口给出本机可用档位,自检里的模型探针判 FAIL 并记录原因。远端部署机(2026-09-22)实测没有装 Ollama(无二进制、命令不在 PATH、11434 端口拒绝连接),因此远端的问答与升档功能不可用,其余页面不受影响;这一点已记入版本记录的遗留项,处置建议是"装 Ollama 并把有网机器上的模型目录整体拷到目标机同位置",是否安装由用户决定。 *** ## 11 运维控制台与重算编排 ### 11.1 运维控制台与动作 运维控制台由网关提供两个入口:整页 /ops 与内嵌版 /ops/recalc(只保留重算、产物与动作进度三块,是门户「数据重算」页的 iframe 目标)。接口是 GET /ops/api/state(真实状态:各服务端口通不通、产物在不在、有没有任务在跑、上次结果)与 POST /ops/api/动作。动作只有 POST,其它方法返回 405(避免链接被预取或刷新时误触发),未知动作返回 404。四个动作如表 11-1 所示。 | 动作 | 语义 | 语义冲突时的返回 | |---|---|---| | stop_services | 停止组件服务(保留控制台本身) | 组件都已停止时回 409 | | start_services | 启动组件服务并打开门户 | 组件都在运行时回 409 | | rebuild | 执行重算(等价于 python scripts/rebuild_all.py) | 已有任务在跑时回 409 | | products_off | 清除产物(真删除,不留备份) | 产物已清空时回 409 | 按钮的禁用只是提示,后端一律再校验一次;直接打接口也绕不过并发与语义前置。页面每 2 秒轮询一次状态。 ### 11.2 任务为什么必须脱离网关的父子树 若网关直接用子进程方式起重算,任务就是网关的子进程;而停止服务用的是"按进程号并沿父子树强杀",会连带把正在执行的重算一起杀掉(实测日志停在第六步"重启组件服务(停)"那一行,之后什么都没有,服务也停在半路)。因此运维动作经一个只做一件事的启动器二次拉起执行器后立即退出:执行器的父进程变成一个已退出进程,于是它不再是网关的子孙,沿树强杀不会再连带杀掉它。启动器把执行器的进程号打到标准输出的最后一行,供父进程记账。 与此配套,重算链的第六步只重启组件服务、保留网关,理由有二:控制台页面本身由网关提供,连它一起停会把用户正在看的界面弄没;重算本身可能就是从控制台起的任务,停网关会沿父子树把重算一起杀掉。 ### 11.3 状态文件、心跳与日志 任务状态写在 run/ops_job.json,字段与本次实测内容如表 11-2 所示。执行器每 15 秒更新该文件的心跳;网关侧超过 150 秒没有心跳就把运行中收尾为完成并写"心跳停了 N 秒,任务多半被强杀,未拿到退出码";判失败前会重读一次状态文件,防止轮询竞态把退出码覆写。 | 字段 | 含义 | 本次实测值 | |---|---|---| | kind | 动作类型 | rebuild | | cmd | 实际命令 | scripts/rebuild_all.py --with-verify | | pid | 执行器进程号 | 54848 | | log | 动作日志路径 | logs/ops/ops_rebuild_20260919-095635.log | | status | 任务状态 | done | | rc | 退出码 | 1 | | started、finished | 起止时间 | 2026-09-19 09:56:35 至 2026-09-19 10:43:31 | | seconds | 耗时(秒) | 2815.5 | | note | 备注 | 空 | 动作日志落在 logs/ops/,命名为 ops_动作_YYYYmmdd-HHMMSS.log,保留最近 20 份或 30 天(写前清理);本次实测该目录恰有 20 个文件,符合保留策略。运行态文件 run/pids.json 与 run/ops_job.json 是"当前跑着谁、当前在做什么"的唯一凭据,交付包默认排除它们。 ### 11.4 服务化重启与端口占用处置 停止与启动的口径是:停止用按进程号并沿树强杀(Windows)或按进程组发信号(类 Unix),成功后删除运行态文件;启动按配置起六个组件并等待健康检查就绪(40 次 1 秒探测),全部就绪返回 0,有模块未起返回 1(属预期降级态),健康检查 60 秒未就绪返回 2。端口占用时的处置顺序是:先停服务再启动;仍被占用则在运维控制台看端口状态与进程,按端口清理占用进程;仍不行则改 configs/serve.json 的端口并同步网关路由表(网关的上游端口表默认值与 serve.json 可覆盖的键名一一对应)。 *** ## 12 安装、服务化与版本管理设计 ### 12.1 安装与卸载 Windows 安装入口是 install.bat 与 install.ps1,步骤号写死在输出里,如表 12-1 所示(依据 install.ps1 的实际打印文案)。类 Unix 入口是 install.sh,口径与 Windows 侧同构(选解释器、建虚拟环境、装依赖、写配置、可选装 systemd 单元、写安装记录、跑自检)。卸载入口是 uninstall.bat 与 uninstall.sh,共用同一份实现 scripts/guanlan_uninstall.py。 | 步骤 | 做什么 | 失败时的处置 | |---|---|---| | 0/7 安装前检查 | 读 src/version.py 取本包版本,读安装根下的安装记录比版本差异(同版本、升级、降级、老安装无记录四类),交互式询问是否重装 | 交互式答非 y 即取消且不改任何文件;加 -Force 跳过询问;非交互自动继续 | | 1/7 选 Python | 需 3.11 及以上(依赖 pandas 3,3.10 及以下没有轮子);机器上没有则解包内便携运行时 | 两者都没有则打印两条办法后退出 | | 2/7 建虚拟环境 | 建 .venv 并升级 pip | 失败即停 | | 3/7 装依赖 | 优先用包内离线轮子 wheels/win_amd64,无轮子才联网安装 | 依赖安装失败退出码 3,并提示先解决再启动 | | 4/7 写配置 | 把 configs/serve.json 的 python 字段写成相对安装根的 POSIX 路径 | 写配置失败退出码 4 | | 5/7 建启动快捷方式 | 建"启动观澜.lnk"(中文名给人)与"start_guanlan.lnk"(ASCII 名给脚本)与桌面快捷方式,指向无窗口启动器 | 建不出来只告警不影响使用,并给手工建法与 start.bat 替代 | | 6/7 服务化(可选) | 调 services/service_ctl.py install 注册 Windows 服务,或加 --user-login 写登录自启 | 注册失败只告警,并提示需要管理员权限与 --dry-run 预览 | | 7/7 模型(可选) | 包内有离线安装包则静默安装 Ollama,并提示模型目录的拷贝办法 | 未装且包内无离线包时明确写"问答不可用",其余页面不受影响 | | 收尾 | 写安装记录 install-info.json 并跑一次自检 | 自检输出作为装机结论 | 安装时间为整轮口径"首次约 10 分钟"(说明书),逐步耗时无实测证据。卸载的默认口径是"只拆本程序装上去的东西":停服务、注销服务、撤开机自启、删快捷方式、清运行态、删安装记录;默认保留 data、outputs、logs、.venv 与程序目录,加 --purge 才删数据(会再问一次),加 --all 才删程序本体。之所以默认不删程序目录,是因为 Windows 上正在运行的 python.exe 就在 .venv 里,"删自己必然只删一半",留个删了一半的目录比留着整棵目录更难查;卸载脚本因此先切到临时目录再调 Python。 ### 12.2 服务化形态 两平台的服务化形态如表 12-2 所示。两平台共用同一个工作体(起等于 guanlan.py serve、守护等于每 15 秒巡检同样命令、停等于 guanlan.py stop),因此"服务起的"与"手工起的"口径完全一致,不会出现两套行为。 | 平台 | 形态 | 服务体 | 注册器 | 前置条件 | |---|---|---|---|---| | Windows | Windows 服务(SCM 托管,独立进程) | scripts/win_service.py(用标准库直连服务控制管理器,不依赖第三方库) | scripts/service_ctl.py install(内部调系统命令创建、描述、配置失败重启、启动) | 管理员 | | Linux | systemd 单元 guanlan.service(简单类型加始终重启) | scripts/service_main.py(前台守护) | scripts/service_ctl.py install(写单元文件并启用) | root | | Windows 备选 | 当前用户登录自启(不需要管理员) | scripts/guanlan_start_hidden.py(无窗口启动器) | scripts/service_ctl.py install --user-login | 无 | 服务名与显示名是固定值(guanlan 与"观澜 v2(风电场智能分析系统)"),服务失败重启策略是 24 小时复位、重启延迟 60 秒。本机实测(appraisal):service_ctl.py status 报"已注册=False 运行中=False",即本机没有注册服务(本机以手工方式在跑,六个端口处于运行中);Windows 服务的真注册与 systemd 的真装在本次环境未取证(非管理员环境只有 --dry-run 与免权限守护自证两条路径,其输出与设计说明一致),属如实留白项。 ### 12.3 安装记录与版本三守卫 安装记录 install-info.json 是"这台机器装的是哪一版"的唯一凭据,字段如表 12-3 所示。装完写、卸载时删(删掉等于这台机器回到"没装过")。需要如实指出:本机该文件记录的版本是 2.5.0(装于 2026-09-17),落后于当前代码版本 2.32.0,因此它正是"安装前检查会提示版本差异"的活样本;重装或升级后会随之更新。 | 字段 | 含义 | |---|---| | name | 系统名 | | version | 已装版本(唯一凭据) | | edition | 版本性质(离线单包) | | installed_at | 安装时间 | | python | 安装时使用的解释器绝对路径 | | mode | 运行方式(手工、Windows 服务、systemd、登录自启) | | host | 安装根 | 版本号只写在 src/version.py 的 VERSION 一行,改动自动传导到各处;三条守卫如表 12-4 所示,都是"对不上就报错",不靠人记得。 | 守卫 | 读哪里比对 | 违反时的表现 | |---|---|---| | 版本真源守卫 | scripts/pack_dist.py 读 src/version.py 的 VERSION 写进包清单并拼默认包名 | 打包器不再自带版本常量,避免"包说一个版本、安装记录说另一个版本" | | 版本记录守卫 | scripts/version_log.py --check 比对 docs/版本记录.md 的自动块与 src/version.py 的 HISTORY | 不一致即 rc=6,提示重生成;重算链 ⑧d 步容忍 6 但报出来 | | 安装前检查守卫 | install.ps1 与 install.sh 从 src/version.py 取版本并与安装记录比对 | 同版本、升级、降级、无记录四类分别提示,降级会明确警告 | 另有一条与版本同级的守卫是入口引用闭合与入口脚本编码守则(见 13.3 节):打包、开箱验证、装机自检三处都查,缺一个文件就不出包。 ### 12.4 打包器与开箱验证 打包器 scripts/pack_dist.py 的默认口径是"一条命令、零开关":程序与脚本、配置、发布件、文档、离线依赖件与入口脚本进包;输入数据、产物、运行日志与临时文件不进包(例外用 --with-data 与 --with-products),另排除装机产物(安装记录与快捷方式,因为它们只在本机有效)。包内清单 dist-manifest.json 逐条记版本、构建时间、包含项、逐条排除理由与四类标志(不含数据、不含产物、不含日志、不含临时文件)。 打包实测口径:一次打包报 3,430 件、1,032.0 MB(该数字来自变更记录,非本次实测;旧口径含缓存与日志为 3,741 件、1,091 MB)。开箱验证一条命令给出"能不能装、能不能跑"的证据,步骤如表 12-5 所示。 | 步骤 | 核验内容 | 判定 | |---|---|---| | ① 解压 | 解压到临时目录并打印件数与耗时 | 解压失败即失败 | | ①b 闭合与编码 | 入口脚本引用闭合、编码守则核对 | 任一不通过即 rc=1 | | ①c 数据骨架 | 输入数据目录骨架逐个在位,且除放置说明外无混入文件 | 不通过即失败 | | ② 离线安装 | 在副本上真跑安装 | 非 0 即失败 | | ②b 无窗口与快捷方式 | 两份快捷方式在位、无窗口自证通过、包内无脚本宿主脚本 | 不通过即失败 | | ③ 起服务核验 | 核验门户根、工作台、振动、运维控制台、健康检查五个地址 | 要求至少 4 个可用 | | ③b 卸载核验 | 在刚装好的副本上真跑卸载,断言记录、运行态、快捷方式都拆掉且数据与产物一件不少 | 不通过即失败 | | ④ 收尾 | 只停副本自己的进程、清理临时目录 | 副本端口无残留 | 一项现场教训写进了设计:开箱验证原先必须占用默认组件端口,于是验证期间用户正在用的工作台与振动页是断的(用户点门户「登录」正好撞上)。现在网关的上游端口表可被 configs/serve.json 覆盖,验证改为副本整套换到空闲端口(例如网关 28700、组件 28800 起),主实例照常运行;找不到空闲端口才降级为"跳过启动核验"并打印提示。 *** ## 13 质量保证设计 ### 13.1 审计脚本矩阵 系统的质量保证是"一条链加重算里的审计门加一个装机自检"。审计器矩阵如表 13-1 所示,退出码与本次实测结论一并列出。 | 审计器 | 查什么 | 退出码语义 | 本次实测 | |---|---|---|---| | scripts/products_reverse_audit.py | 逐件回溯"输出到功能与算法到输入",族表匹配与人工件识别 | 0 全部成立;5 有未归类或判据失败 | rc=0,呼应成立 3,548 件、不成立 0、人工件 0、未归类 0 | | scripts/pages_audit.py | 页面归口五条机器规则(静态页不得引产物、实时页端口须在端口表内、引用产物须在位、数据派生页须有来源与指纹、冻结交付件须有版本与日期) | 0 一致;5 登记缺项或文件缺失;6 溯源缺失;7 数据派生页面陈旧;8 交付件缺版本号;9 分类错误 | rc=0,检查 21 项、2 项一致、19 项已知缺口、0 项要处理 | | scripts/config_audit.py | 配置登记表 R1 至 R8(顶层只放登记过的运行期单件、无备份垃圾、命名、内容不得含本机绝对路径、代码引用的配置必须存在、不许手拼配置路径、每域须声明消费者、场目录每件要么是场定义要么登记为 profile) | 0 通过;5 结构问题;6 悬空引用;7 内容含本机绝对路径;8 手拼路径;9 场配置不合约定 | rc=0,171 件配置、5 个域、1 个场定义、10 件机型 profile、0 不一致、14 已知缺口、7 提示 | | scripts/log_audit.py | 日志 L1 至 L5(落点唯一、命名、按末尾 200 行校验行格式、行尾与颜色、保留策略与审计流水每行合法 JSON) | 0 通过;5 日志跑到 logs 之外;6 命名不合口径;7 行格式不合规;8 行尾或颜色问题;9 保留策略超限 | rc=0,0 不一致、87 条提示(历史日志与旧行只提示) | | scripts/raw_scan.py | 输入数据逐族指纹比对与增量识别 | 0 无变化;4 有新增或变化;5 还没有基线 | rc=0,"与上次快照一致,没有新数据";另 --selftest rc=0(族表覆盖反向族表的 7 个输入、10 个步骤标签都能在链上找到) | | scripts/chain_gap_check.py | 振动六层链四步脚本是否齐全、关键产物是否在位、能否逐值对拍 | 0 齐全;5 缺料 | rc=0,四步脚本齐全;两件关键产物按口径重建但无标准答案可对拍 | | scripts/version_log.py | 版本记录自动块与代码 HISTORY 是否逐字一致 | 0 一致或已写;2 文件缺失;6 需重生成 | rc=0 | | scripts/detail_deps.py | 工作台各页面与接口的产物依赖台账(页面到接口到产物件到来源与生成端) | 0 通过;5 文档与现场不一致或证据核对不通过 | rc=0,清单 10 页签、16 接口、缺口 0 件、产物仓 3,549 件,文档与现场一致 | | scripts/inventory_products.py | 产物清点与输入到产物呼应校验(产物跨度必须落在输入跨度内) | 0 呼应正常;5 有 problems | rc=0(抽样:振动输入 25,693 件对时间窗索引 2,066,686 行、跨度 2026-03-16 至 2026-04-21) | | scripts/check_portability.py | 换机可移植性门禁三条硬规则(不许机器相关绝对路径、不许工作目录相对路径字面量、不许用系统分隔符拼库存字符串)加三条提示规则 | 0 通过;1 有严重项 | rc=0,通过(无机器相关绝对路径、无工作目录相对路径、未用系统分隔符拼串),例外登记 16 项加 2 类 | | scripts/check_transferable.py | 移植性大扫描五项(全类型文件的机器相关路径、虚拟环境解释器指向、各平台轮子是否备齐、便携运行时与清单、配置与端口是否相对) | 0 通过;1 有机器相关路径 | rc=1,290 处机器相关路径(HTML 烘入绝对路径 0 处,文档旧口径写 350 处)、提到网关端口的文本文件 43 个;轮子与便携运行时两项判通过 | | scripts/audit_chinese_terms.py | 中文表达审核(OEM 术语基准与行话规则),机器给候选人裁 | 0 通过;2 仅严格模式下有已裁改条目仍命中 | rc=0,扫 5 个文件与 7,322 条中文串,规则命中 123 条,其中显示层已覆盖 111 条、内部面豁免 42 条、仍待处理 0 条;无根词 3,902 条 | | guanlan.py check | 装机自检(约 40 行结论:解释器、源码可编译、语言包成对、子进程口径、入口闭合与编码、版本、卸载入口、反向呼应、页面归口、配置、日志、输入数据、依赖、产物与本体与契约与门户、六个端口、模型探针与四档、模型可用性) | 0 全绿;2 有 FAIL | rc=0,结论"全绿, 可 serve";底稿 docs/src/_guanlan_check.txt | 自检底稿里的几个可核对数字:Python 3.12.10;源码 229 个文件可编译、无非法转义或语法错;语言包前后端 zh 与 en 成对 845 条;入口脚本引用闭合 48 条;入口脚本编码守则通过;反向呼应 3,548 件;页面归口检查 21 项;配置与日志无一致性问题;输入数据 26,503 件源件结构合规;依赖九项(numpy、pandas、pyarrow、polars、yaml、matplotlib、plotly、jinja2、docx)全部就位;L0 与 L1 产物、本体对象库、findings、事实契约、门户、仿真合页服务与资料包、三维资产、仿真回放资产、治理清单交付件全部在位;六个端口处于运行中;本机模型探针通过(耗时 171.8 秒)。 有一处过程值得记录:本轮取证期间,工作台依赖台账门与可移植性门禁各自返回过一次"未通过"(一个是文档清单与现场不一致,一个是运行期源码里用系统分隔符拼串),随后两处都按同一口径修好并复跑转绿(可移植性门禁复跑输出"通过,路径可移植:只有相对路径加经路径模块解析",依赖台账复跑输出"文档与现场一致")。这正好说明"每条门都能独立重跑并给出退出码"的价值:问题是被跑出来的,不是被猜出来的。 ### 13.2 退出码语义与质量门闭环 各审计器的退出码语义汇总如表 13-2 所示,语义与重算链里的容忍集合一一对应(见 5.2 与 5.3 节)。 | 退出码 | 含义 | 谁在用 | |---|---|---| | 0 | 通过或无变化 | 全部审计器与构建器 | | 1 | 有严重可移植性问题;或链级失败汇总 | 可移植性检查器;重算链汇总非零即返回 1 | | 2 | 自检有 FAIL;契约自检不过;非交互下需要确认;文件缺失;严格模式下术语命中 | guanlan.py check、事实契约构建、卸载器、版本记录、中文表达审核 | | 3 | 权限不足 | 安装脚本与服务化注册器、卸载器 | | 4 | 有新数据;或按缺件如实报 | 输入数据扫描、变桨面、振动历史与基线、等价验收 | | 5 | 随包件这一路缺失(无基线、有未归类、被引产物不在位、文档与现场不一致、缺料、结构问题) | 月度派生件、反向呼应审计、页面归口审计、等价验收、链完整性检查、工作台依赖台账、产物清点、配置审计、日志审计 | | 6 | 溯源缺失;文档与代码版本记录不一致;增量冲突;悬空引用;命名不合口径 | 页面归口审计、版本记录一致性、输入数据结构体检、配置审计、日志审计 | | 7 | 数据派生页面陈旧;内容含本机绝对路径;行格式不合规;缺源类或时间空洞 | 页面归口审计、配置审计、日志审计、输入数据结构体检 | | 8 | 交付件缺版本号;手拼配置路径;行尾或颜色问题;输入结构仅提示 | 页面归口审计、配置审计、日志审计、输入数据结构体检 | | 9 | 分类错误;场配置不合约定;保留策略超限 | 页面归口审计、配置审计、日志审计 | 需要如实说明的是:多数审计器只定义了自己会用到的那几个码,未列出的码在对应脚本里并未定义。例如反向呼应审计与链完整性检查只定义 0 与 5,版本记录只定义 0 与 2 与 6,装机自检只定义 0 与 2,可移植性门禁只定义 0 与 1。读退出码时应同时看该脚本自己的说明,不要假设"非零都一样"。 质量门形成闭环:改代码或改配置后,重算链在⑤、⑧、⑧b、⑧c、⑧d 五处设审计门;装机时 guanlan.py check 再统一体检一遍;打包时入口闭合与编码守则在打包、开箱验证、装机自检三处强制执行;出包以后在目标机上安装再由自检复核。闭环如图 13-1 所示。 ![图 13-1 质量门与审计闭环](figures/fig-des-06-质量门闭环.png) ### 13.3 入口脚本的字符守则 三条编码守则由 src/entry_refs.py 实现,并在打包、开箱验证第①b 步、装机自检三处强制执行,违反就不出包或直接判 FAIL,如表 13-3 所示。 | 文件类型 | 守则 | 违反的后果(现场实炸过) | |---|---|---| | .bat | 纯 ASCII、CRLF、无 BOM、不含连续小于号、大于号后必须是重定向目标 | 批处理注释里的中文被命令行解释器当成命令执行;连续小于号直接报重定向错 | | .ps1 | UTF-8 带 BOM 加 CRLF | PowerShell 5.1 按本地代码页解码导致中文乱码与级联语法错,安装一秒即退出 | | .sh | 行尾 LF | 每个词尾粘回车,set -e 变非法选项,变量判空翻转,脚本从一开始就是坏的 | 与此配套的一条纪律是"入口脚本引用的文件必须齐全":凡入口脚本里出现的路径且在本机源码树真实存在,打包时自动补入,打包后对着包内条目复核(缺一个就删包并返回非 0),开箱验证与装机自检各再查一遍。这条守卫的价值在替换无窗口启动实现时体现过:去掉脚本宿主方案后入口列表与引用条目自动重算且全部在位。 ### 13.4 文档纪律 文档与代码同级受管:版本记录由 scripts/version_log.py 从代码里的 HISTORY 生成,自动块禁止手改;重算链的 ⑧d 步以 rc=6 容忍但不静默的方式盯"文档与代码版本一致"。这条门的由来是一次实逮:远端升级的差异盘点只覆盖源码、脚本与配置,文档不在盘点面上,于是代码升到新版本而版本记录与系统设计说明留在旧版,而重算链当时只到 ⑧c,导致 24 步全绿、控制台写"重算完成",文档漂移没有任何自动拦截点。现在的口径是"升级差异盘点必须包含 docs",并由 ⑧d 步兜底。 本文本身也按同一纪律编写:每章的事实都能在附录 A 找到来源文件,数字都能按第 1.4 节的命令复跑复现;本版同时新增三份交付文档(需求分析、系统设计说明、数据要求说明)与其生成端(文档渲染器与图表构建器),图表里的数字全部从真件取数而不是手画。 *** ## 14 安全、合规与离线边界 ### 14.1 离线边界与只读输入 系统的安全边界是四个方面,如表 14-1 所示。 | 边界 | 设计口径 | 落地证据 | |---|---|---| | 只读输入 | 原始件只读,摄入器不写 data/raw;放置工具的增量落位对"同名不同大小"默认拒绝并拦下,必须显式确认才覆盖 | scripts/place_raw_data.py 默认拒绝冲突;产物是"当前原始件的函数"(源件删了行也随之消失并报出) | | 默认离线运行 | 全部计算在本机;依赖随包离线安装;模型可选且指向本机 | wheels/win_amd64 42 件、vendor/python 三平台便携运行时;模型端点是本机 11434 | | 唯一外发通道且强制脱敏 | 代码内保留一条显式的"可上云问答"通道(三个兼容云端接口,需环境变量提供密钥);未配置密钥时回落本机模型;外发前强制对每条消息做脱敏,脱敏模块不可用时直接拒绝外发而不是照发 | 离线模型代理模块的外发分支与脱敏调用;脱敏实体映射 16 条加三条正则;产物脱敏词表 32 条加 11 类回扫 | | 监听范围 | 组件只绑本机回环地址,只有门户网关使用 public_host(出厂为所有网卡) | configs/serve.json 的 host 与 public_host 及注释;启动时打印对外地址并提示"页面无鉴权,请在防火墙侧限来源";实测网关在所有网卡监听,其余六个进程只在回环监听 | | 无鉴权风险如实告知 | 页面没有登录与权限体系,对外监听等于对同网开放 | 配置注释与启动提示;处置建议是防火墙白名单或反向代理认证,不要直接挂公网 | ### 14.2 脱敏与可上云面孔 对外披露的内容走独立的一层:可上云面孔落在 outputs/场站/guanlan/cloud,由专门的面孔与页面与问答脚本生成;事实契约的每条结论带脱敏来源引用(文件名加内容摘要前 16 位与章节)与本条 sha256,因此"这句话出自哪份文件的哪一节、当时那份文件是什么内容"都可核对。脱敏实现分布在若干模块,口径可以概括为四层:一是外发消息的实体映射(16 条,把业主集团、整机厂、部件供应商、场站名与机型泛化,另有本机路径、电价与机群规模三条正则);二是产物与交付侧的公开词表(32 条替换词加 11 类独立回扫规则,替换与回扫分开写,并要求交叉自检"回扫认识每个替换词、替换后回扫干净",有残留即抛错拒绝出件);三是振动产物里唯一的字段级黑名单(8 个频率类键整值遮蔽,另有 17 条阈值或频率正则),交付件使用的是不看开关的强制脱敏版本;四是机组代号化(按定序摘要映射,不可由编号反推)。对外面孔生成后仍要回扫一次,命中本机绝对路径、邮箱、手机、身份证、精确坐标、密钥格式等任一类即返回 2 不出件;云问答出口同一口径,脱敏模块导入失败即拒绝启动。脱敏的边界是"对外面孔这一层做,内部产物不脱敏",两层由目录与生成端区分;机组号与机群台数在对外面孔里只报不拦,是否代号化由使用方裁决并写进面孔清单。需要如实说明的是,交付前另有一个安全核查器(密钥、个人信息、金额与业主名、内部件四类规则),但它目前尚未接入打包链,属手工步骤。 ### 14.3 日志与审计 日志是审计的载体:长驻服务与启动器的日志落在 logs 组件名.log 并按统一行格式写;运维动作落在 logs/ops/ 并按保留策略清理;构建与摄入日志落在 logs/build/场站/ 下按产物子路径归档;机器审计流水落在 logs/audit/(模型调用流水 llm_audit.jsonl、云问答流水 cloud_qa.jsonl、术语审计 terms_audit.json)。统一格式之前的日志全部搬到 logs/legacy/ 留档,不悄悄删除。 ### 14.4 权限不足时的降级路径 权限不足时不抛"访问被拒绝",而是给出可执行的替代路径,如表 14-2 所示。 | 场景 | 无权限时的行为 | 可用替代 | |---|---|---| | 注册 Windows 服务 | 打印前置检查结果、逐条将执行的命令,返回码 3 | 管理员重试;先用 --dry-run 看它要做什么;改用登录自启(--user-login) | | 注销服务(卸载第②步) | 缺权限时照做其余步骤并打印手工命令,返回码 3 | 管理员/root 执行注销命令 | | 注册 systemd 单元 | 打印将写入的单元全文,不写入 | root 重试 | | 端口被占用 | 不静默重试,给出占用排查与改端口路径 | stop 后重启;按端口清理;改配置端口并同步网关路由表 | | 缺产物或缺模型 | 返回结构化缺件页或明确的降级提示,不伪造内容 | 放原始件后重算;补齐生成端;安装并拉取模型 | *** ## 15 可移植性与资源占用 ### 15.1 另一台机器零配置启动 迁移到另一台机器的步骤如表 15-1 所示。要点是"程序与配置路径无关(全部相对安装根),但虚拟环境不能直接搬"——虚拟环境的配置文件绑定基础解释器路径,因此换机后跑一次安装即离线自足。 | 步骤 | Windows | Linux 或 macOS | |---|---|---| | 1 解包 | 解压交付包到安装目录(目录不要带空格与中文) | 解包到目标目录(同样避免空格与中文) | | 2 安装 | 双击 install.bat(或调用 install.ps1) | sh install.sh(可用 GUANLAN_PY 指定解释器,--service 装 systemd) | | 3 自检 | 双击 check.bat 或跑 guanlan.py check | .venv/bin/python guanlan.py check | | 4 放数据 | 把原始件放进 data/raw/场站名/ 下并按源类目录落位 | 同上 | | 5 重算 | guanlan.py serve 后从门户「数据重算」触发,或直接跑 rebuild_all.py | 同上 | | 6 启动 | 双击"启动观澜"快捷方式(无窗口)或 start.bat | .venv/bin/python guanlan.py serve | 安装根可用环境变量覆盖(路径模块优先读 WINDSCADA_ROOT),原始件根可用环境变量 `WINDSCADA_<代号>_SRC`(**变量名内写死样本场代号**,换场须改名,见 16.5 节)覆盖,场名可用 `WINDSCADA_FARM=<场>` 覆盖;配置里的路径一律写相对安装根的形式,因此换盘、换机不需要改任何路径。 ### 15.2 Python 与依赖 wheel 解释器要求是 3.11 及以上(依赖 pandas 3,3.10 及以下没有轮子);机器上没有 3.11 时使用包内便携运行时(3.12.14,三个平台各一份)。依赖清单共 19 条固定版本:polars 1.42.0、pandas 3.0.3、pyarrow 24.0.0、numpy 2.5.0、pyyaml 6.0.3、python-frontmatter 1.3.0、matplotlib 3.11.0、plotly 6.8.0、jinja2 3.1.6、python-docx 1.2.0、docxtpl 0.20.2、python-dotenv 1.2.2、requests 2.34.2、rich 15.0.0、tqdm 4.68.3、openpyxl 3.1.5、scipy 1.18.0、xlrd 2.0.2,另有一条不锁版本的 tzdata(Windows 无系统时区库,而 pandas 3 硬依赖它)。离线件如表 15-2 所示(件数与体积为本次实测)。 | 离线件目录 | 件数(实测) | 体积(实测) | 覆盖平台 | |---|---|---|---| | wheels/win_amd64 | 42 | 169.4 MB | Windows 64 位完全离线可装 | | vendor/python | 3 | 81.4 MB(含清单文件共 4 件) | Windows x86_64、Linux x86_64、Linux aarch64 便携运行时 3.12.14 | Linux 与 macOS 侧包内没有轮子时,安装脚本自动改为联网安装;要完全离线需把对应平台的轮子放进相应目录。模型离线件(Ollama 安装包与模型目录)单独交付,不在主包内;未装时问答不可用,其余页面不受影响。 ### 15.3 磁盘、内存与算力估算 资源需求与实测体积如表 15-3 所示。内存与磁盘的硬性要求来自说明书(最低内存 16 GB、推荐 32 GB;磁盘最低 5 GB 加三维资产 1 GB,另加模型 30 至 60 GB);目录体积为本次递归实测。 | 项 | 数值 | 出处 | |---|---|---| | 内存最低或推荐 | 16 GB 或 32 GB | 《使用说明书》v0.2(随包 docs/) | | 磁盘最低 | 5 GB 加三维资产 1 GB | 同上 | | 模型占用(可选) | 30 至 60 GB(固态盘) | 同上 | | 显存(只影响问答与本地审核) | 12 GB 及以上可跑默认档;24 GB 及以上可开升档;无显卡走纯 CPU 慢档 | 同上 | | data(原始件,本机实测) | 26,504 件、224.67 GB(其中振动原始导出 25,693 件、150.15 GB) | 本次递归实测 | | outputs(产物,本机实测) | 3,549 件、7,157.5 MB | 本次递归实测 | | release(发布件,本机实测) | 2,898 件、708.4 MB | 本次递归实测 | | 虚拟环境(本机实测) | 17,779 件、688.1 MB | 本次递归实测 | | 日志与配置(本机实测) | 日志 154 件、3.3 MB;配置 171 件、1.6 MB | 本次递归实测 | | 全仓不含版本库(本机实测) | 52,049 件、241,707.5 MB | 本次递归实测 | | 运行内存(本机实测快照) | 七个监听进程工作集合计 568 MB(网关 238.9、工作台 198.6、振动 53.8、本机模型 33.7、三维 15.4、四系统合页 13.9、仿真 13.7 兆字节);同批私有提交合计 3,293.7 MB | 本次实测快照,非稳态峰值,仅供量级参考 | | 交付包(口径) | 3,430 件、1,032.0 MB | 变更记录,非本次实测 | | 重算耗时(口径) | SCADA 侧 10 个构建器约 15 分钟;振动摄入按现场导出体量约 14 分钟(另有 25 分钟口径);变桨面约 10 至 12 分钟;窄仓一次扫约 2 至 3 分钟 | 代码注释与手册,多口径未统一 | | 单次完整重算(实测) | 本机一次带等价验收的重算 2,815.5 秒(约 47 分钟),该次以退出码 1 结束(末步等价验收因无随包基线一路属预期) | run/ops_job.json | | 按所选时间窗重算 | 判级约 25 秒每时间窗;七镜头约 9 至 44 秒每时间窗 | 服务注释 | 算力方面的判断是:系统的重活集中在重算链(一次性、可后台跑),页面侧的实时计算只有按所选时间窗的分组与过滤,最重的两类已经用窄仓与进程内缓存压到秒级;模型问答是唯一可能占用数分钟的交互,且已按档位给预算并在超时后明确不出文。 ### 15.4 Linux 适配现状 Linux 侧的适配程度如表 15-4 所示,全部为"已有实现但在本次环境未实测"或"已在本机验证"两类之一,不含估计。 | 项 | 现状 | |---|---| | 安装脚本 | install.sh 已有,逻辑与 Windows 同构(选解释器、建虚拟环境、装依赖、写配置、可选装 systemd、写安装记录、自检) | | 便携运行时 | vendor/python 下已含 Linux x86_64 与 aarch64 两份 3.12.14 | | 依赖轮子 | 包内只有 win_amd64 轮子;Linux 需联网安装或自行放轮子 | | 服务化 | systemd 单元 guanlan.service 由注册器写入(简单类型、始终重启、停止超时 120 秒、混合杀进程模式、环境变量强制 UTF-8) | | 路径与换行 | 路径统一用 pathlib 与 POSIX 相对串;入口脚本按类型守行尾(.sh 用 LF) | | 进程与日志 | 无窗口子进程的口径在类 Unix 上是 0 标志位;日志行格式与保留策略平台无关 | | 未取证项 | Linux 安装、systemd 真装、国产化发行版首装均未在本次环境实测(历史交付件的 Linux 安装脚本曾有过行尾问题,已由编码守则与打包校验拦住) | *** ## 16 多场适用性与换场迁移 ### 16.1 场抽象层 本系统的"场"不是散落在代码里的常量,而是一个可加载、可校验、可切换的配置对象。场抽象层分三层落地:配置层是场定义文件 app_ETL/configs/farms/<场>.yaml;路径层是 src/paths.py 的 farm() 与 farm_config() 与 contract();语义层是 src/windscada/config.py 的场定义加载与校验、场站目录扫描辨识与场切换。三层的关系是:配置层给"这个场长什么样",路径层给"这个场的件写到哪、从哪读",语义层给"凭什么认定 data/raw 下的这个目录就是本场"。本章数字均为样本场实测(2026-09-22)。 #### 16.1.1 场定义的 schema 场定义是一个 YAML 文件(历史 JSON 仍可加载,新场一律写 YAML),落在 app_ETL/configs/farms/ 下,文件名去掉后缀即场名。加载器要求七个必填键齐全,缺任何一个都在加载时响亮报错并以非零退出终止,而不是等到分析中途才崩。字段、必填性与模板写法如表 16-1 所示。 | 键 | 必填性 | 含义 | 模板写法 | |---|---|---|---| | name | 必填 | 场站中文全名,用于页面与服务显示 | `<场站中文名>` | | n_turbines | 必填 | 机组台数 | `25` | | turbines | 必填 | 机组号列表,或生成式字符串(加载时展开) | `WTG{:02d}:1-25` 或 `["WTG01", "WTG02"]` | | src_10min | 必填 | 10 分钟 SCADA 原始件目录 | `data/raw/<场站>/scada_10min` | | src_alarm | 必填 | 故障报警原始件目录 | `data/raw/<场站>/故障报警` | | store | 必填 | L0 标准仓落点(页面主取数处) | `outputs/<场>/windscada` | | rated_kw | 必填 | 单机额定功率,单位 kW | `3000` | | raw_station | 建议 | data/raw 下的首选目录名(辨识第一顺位) | `` | | src_farm_names | 选填 | 原始件里的场站名写法(别名),用于台账与报警筛本场行 | `[<别名1>, <别名2>]` | | contract | 选填 | 机型判据契约落点 | `reference/<场>/windscada_contract.yaml` | | src_1min 与 src_workorder 与 src_oil 与 src_windcms 与 src_m5 与 src_mdb | 选填 | 其余源类目录 | `data/raw/<场站>/<源类目录>` | 三条与 schema 配套的加载规则:一是 turbines 的生成式写法是"前缀加占位加起止",加载时展开成列表,n_turbines 不给时按列表长度补齐;二是 src 开头的键与 store 若显式给出即以其为准,留空才由 data/raw 的扫描结果派生——扫描只填空白,不覆盖声明;三是场定义里的相对路径一律按安装根解析成绝对路径,因此安装目录整体拷到别的盘不需要改场定义。 模板文件是 app_ETL/configs/farms/_模板.yaml.example,以底线开头的文件不被当成场。同一个目录里还混着另一类文件:机型或场站的物理约束档(顶层是 meta 加 physical_constraints),schema 与场定义不同,不是场定义;加载到这类文件时加载器会明确报出"该文件存在但不是场定义",并由 foreign_farm_files() 登记在册、由配置审计记账。 #### 16.1.2 场名解析、运行期切换与落点规则 当前场名的解析顺序固定为三条:显式传入的场名,然后环境变量 WINDSCADA_FARM=<场>,最后是内置默认场名。因此同一份代码换一个场名,就能把 outputs 与 reference 两侧的读写整体迁到新场目录,调用点一行不改。 - farm(name):取场配置对象;不带参数时用当前场;结果进进程内缓存,refresh=True 可强制重扫场站目录(放了新数据又不想重启服务时用)。 - set_current(name):先试着加载该场,加载不通过就报错并列出可用场;通过则把它设为进程内当前场。current() 读回当前场名。 - available():可用场清单,内置场标"内置",其余来自能加载的场定义文件。 - P.farm(name):路径层的场名真源,返回场名本身(解析顺序同上);所有按场分目录的落点都以它为入参。 - P.store(name):返回 outputs/<场>/windscada,即 L0 标准仓;场定义里显式给了 store 时以场定义为准。 - P.contract(name):返回 reference/<场>/windscada_contract.yaml,即机型判据契约;场定义里显式给了 contract 时以场定义为准。 路径层的三个落点助手 P.farm() 与 P.store() 与 P.contract() 都以"场名"为唯一入参;场名解析函数 farm() 与运行期切换函数 set_current() 与可用场函数 available() 三者共同构成场抽象层对外的一小组接口。 落点规则可以概括成一句:场名决定 outputs/<场>/ 与 reference/<场>/ 两条树,场定义决定原始件根 data/raw/<场站>/;其余固定位置(configs、release、resources、docs、logs、run)不含场名,换场不动。 #### 16.1.3 raw_station 与 src_farm_names 的别名匹配 原始件目录名与场定义里的场站名常常对不上:现场交付的原始件根目录可能写的是场站的另一个名字,而集团口径的月度与年度台账里用的又是另一个写法。加载器因此用四段式辨识,并把"凭什么认出这个场站目录"写进结论供页面与命令行显示: - raw_station:data/raw 下一级目录名与场定义的 raw_station 完全相同(第一顺位,最可靠)。 - alias:目录名与 src_farm_names 里的任一别名互为子串(原始件里的场站名写法)。 - single:data/raw 下只有一个场站目录(单站部署),直接采用它。 - none:data/raw 下有多个目录但一个都对不上——不猜,页面显示无数据,并写明本场的 raw_station 与别名,让人把目录名或场定义改对。 辨识结论落在场配置的 raw_station_dir 与 station_how 与 station_note 三个字段上;raw_station_dir() 取到辨识结果就用它,辨识不到则回落到约定位置 data/raw/,这样维护页仍能指着正确的地方让人补数据,而不是给一个空路径。实测(样本场实测(2026-09-22)):本场的月度汇总台账里混着十来个其它场站的行,必须靠 src_farm_names 别名把本场行筛出来,否则报警与工单台账会串进别的场的记录。 #### 16.1.4 is_farm_def() 与 foreign_farm_files() 的守门作用 - farm_files():列出 app_ETL/configs/farms/ 下的候选文件(YAML 与 YML 与 JSON),跳过以底线开头的模板。 - is_farm_def(f):这个文件能不能加载成一份场定义(七个必填键齐全);解析失败也算不是。 - available():只列"真能加载"的场,因此把另一种 schema 的文件列进可用场这种事被挡住。 - foreign_farm_files():列出目录下"不是场定义"的文件及其依据(顶层键名或解析失败类型),登记在册但不参与场加载。 这几个函数合起来解决一个真实坑:配置目录里放着另一种 schema 的文件时,若"可用场"只按文件名后缀列,使用方在切换场时就会撞上加载报错。现在的口径是"能加载的才叫场,不能加载的登记为 profile 并如实报出来",由配置审计的场目录规则(每件要么是场定义要么登记为 profile)与加载报错信息两处守住。实测(样本场实测(2026-09-22)):配置审计结论为 171 件配置、5 个域、1 个场定义、10 件机型 profile、0 不一致、14 已知缺口。 ### 16.2 场无关与场相关的分层 换场之所以可能,是因为系统把"怎么算"和"按什么算"分开了:判级与算法的结构、服务与页面、审计门属引擎侧,换场不动;阈值、机型参数、时间窗锚点、机组清单、术语库与代码表属参数侧,换场必须重新给或重新标定。分层如表 16-2 所示。 | 设计面 | 场无关(引擎侧,落在代码里,换场不改) | 场相关(参数侧,换场必给或必标定) | |---|---|---| | 判级矩阵结构 | 五态词表与取严权重、四键取严(报警与良好与优秀与不可判)、系统到问题的归口、未收录不判正常 | 参与判级的系统清单与通道到系统的映射、系统关键词映射表、各面的阈值 | | 曲线七镜头 | 七镜头定义(风速与功率、功率与桨距、功率与发电机转速、转矩与转速、风速与风轮转速、功率与功率系数、转速比)、分箱结构、三条物理硬闸、列活性铁律 | 机型常数(风轮半径、扫风面积、铭牌齿比、贝兹极限)、分箱边界与步长、八项显著门与离群门限 | | 温度同工况比档 | 同一功率档内比同族温度的算法、稳健标准差与稳健 z 的公式、样本门结构、返服嫌疑与样本不足的判词 | 功率档宽、台级样本门、三级偏差阈值与 z 门限、温度通道清单与均值列集合 | | 蓄能与热链 | 按油温档分箱算带宽比的算法、档内样本门结构、轴数与判定链 | 油温档宽、档内样本门、带宽比门限、轴数门、高油温档位与占空样本门 | | M9 控制参数一致性 | 四个判据的结构(双封顶、K 聚类、桨距调度、尺子) | 分组容差、中载区区间、离群倍数与稳健标准差下限、满发段门 | | 可靠性口径 | 停机段门与段结束规则、两级归因的结构、外部类不计设备可靠性、三项指标公式 | 调度令码表(哪些码算远程停机)、归因时间窗长度、台时口径(理想日历或实测) | | 融合四源 | 四源集合与关闭类最先判、禁止按源数加权、claim 时间窗对齐纪律、取严合并 | 各源的 claim 时间窗锚点、系统关键词映射、handoff 正本与厂家报告转录的件名 | | 本体与检索 | 六步摄入结构与对象 schema、只读工具与接地闸、只读与模型零写权的守门 | 术语库、场站故障代码表(样本场实测 2,793 条)、作业指导与中间件正本、机型技术资料 | | 服务与前端 | 路由族、pending 契约、缓存与产物指纹策略、网关前缀改写、缺件如实页 | 端口与监听地址配置、页面上的场名与台数展示、逐台页与单问题页的台号 | | 审计门与质量门 | 审计器矩阵与退出码语义、入口引用闭合、编码守则、质量门闭环节奏 | 场定义与机型 profile 的登记、页面归口登记、按场裁剪的收资项 | | 数据落位与摄入 | 源类目录名的接口约定(八个约定子目录)、形态接纳规则(CSV 优先、该台没有 CSV 才回落归档库)、逐族指纹与增量三清单 | 机组清单与台号形态、逐台主件命名、场站坐标件(现场交付件)、原始件里的场站名写法 | | 时间窗与口径 | 时间窗词表与含两端的实现、缓存键结构、残月识别公式 | 时间窗锚点(各源数据末端)、限电前干净时间窗与对照时间窗常量、判级时间窗口径常量 | 分层的判据是可机器核对的:引擎侧的东西没有任何一处读场定义里的阈值或机组数,参数侧的东西没有任何一处写死样本场的取值(个别写死的样本场代号属遗留,见 16.5 节)。因此换场的改动面被压到"补一份场定义加重新标定阈值加重新摄入语义件"三件事。 ### 16.3 换场作业单 #### 16.3.1 逐步作业单 每一步都给命令与退出码语义;退出码 0 才算过,落在容忍集合内的非零按"如实报出"处理(容忍集合见 5.2 与 5.3 节)。命令一律在安装根下执行。 1. 准备场配置。把 app_ETL/configs/farms/_模板.yaml.example 复制成 app_ETL/configs/farms/<场>.yaml,填齐七个必填键(name、n_turbines、turbines、src_10min、src_alarm、store、rated_kw),并给出 raw_station 与 src_farm_names。校验命令是 `python scripts/config_audit.py`,退出码 0 通过、9 表示场配置不合约定(例如把不是场定义的文件当成场,或场定义缺键)。 2. 放数据。按八个约定子目录落位,命令是 `python scripts/place_raw_data.py --src <现场包目录> --scope full`,退出码 0 为落位成功;同名不同大小默认拒绝覆盖并列出冲突,必须人确认后加 --force 才覆盖。 3. 体检。两条命令:`python scripts/raw_scan.py --check --write` 取逐族指纹快照,退出码 0 无变化、4 有新增或变化、5 还没有基线(首次顺手记一份,不是失败);`python scripts/raw_data_check.py` 做放置体检,退出码 0 合规、5 结构或命名违例、6 增量冲突、7 缺源类或时间空洞、8 仅提示。 4. 重算。先 `python scripts/rebuild_all.py --dry-run` 看本次计划的步数(默认 26 步,加 --skip-scada 为 23 步),再 `python scripts/rebuild_all.py` 真跑;退出码 0 通过,1 表示有模块未起(属降级正常态),落在该步容忍集合内的非零(2 与 4 与 5 与 6 与 7)视为通过并如实记入日志,容忍集之外的非零立即中断整条链。要做等价验收时用 `python scripts/rebuild_all.py --with-verify`;随包基线缺失时该步返回 5,链上会写明"这不是通过"。 5. 校准阈值。阈值不在引擎里,在机型判据契约与场定义里:契约落 reference/<场>/windscada_contract.yaml(对外交付件的机型契约落 app_ETL/configs/contracts/<机型>_<场>.yaml),场相关阈值与机组清单落场定义。改完复跑 `python scripts/products_reverse_audit.py --check`(退出码 0 全部成立、5 有未归类或判据失败)与 `python scripts/pages_audit.py --check`(0 一致、5 登记缺项或文件缺失、6 溯源缺失、7 数据派生页面陈旧、8 交付件缺版本号、9 分类错误)。 6. 核对锚点。锚点指"数据末端"与"干净与对照时间窗"两件。命令 `python scripts/inventory_products.py` 校验产物跨度必须落在输入跨度内(退出码 0 呼应正常、5 有 problems);`python scripts/detail_deps.py` 校验页面到接口到产物到来源的依赖台账(0 通过、5 文档与现场不一致或证据核对不通过)。锚点对不上,说明场定义或契约里的时间常量还留着旧场的值。 7. 页面与门禁验收。逐条跑:`python guanlan.py check` 装机自检(退出码 0 全绿、2 有 FAIL)、`python scripts/check_portability.py` 可移植性门禁(0 通过、1 有严重项)、`python scripts/log_audit.py` 日志口径(0 通过,5 至 9 对应各类问题)、`python scripts/chain_gap_check.py` 振动六层链完整性(0 齐全、5 缺料)、`python scripts/version_log.py --check` 版本记录一致(0 一致或已写、2 文件缺失、6 需重生成)。起服务的命令是 `python guanlan.py serve`,退出码 0 表示六个组件全部就绪、1 表示有模块未起(属降级)、2 表示健康检查 60 秒未就绪。 #### 16.3.2 换场检查表 检查表如表 16-3 所示,每一条都能用一条命令或一次页面查看判定,不通过时同时给出常见原因。 | 检查项 | 判据 | 不通过时的常见原因 | |---|---|---| | 场定义可加载 | available() 列出新场,farm('<场>') 不报错 | 七个必填键缺项,或文件名与场名不一致 | | 场站目录辨识 | station_how 为 raw_station 或 alias | 目录名与 raw_station 和 src_farm_names 都对不上(落 none) | | 机组清单 | 场定义的 turbines 与现场台数一致,逐台主件齐全 | 台号形态不同,内部台号与主件命名不对应 | | 源类目录齐全 | 八个约定子目录在位,件数与体积与收资清单一致 | 多套一层包名目录,或缺源类 | | 指纹与增量 | raw_scan 退出码 0 或 4;raw_data_check 退出码 0 | 结构或命名违例,同名不同大小冲突 | | 重算整链 | rebuild_all 退出码落在容忍集合内,无容忍外中断 | 缺原始件,缺基线,契约自检不过 | | 阈值标定 | 温度与蓄能与 M9 与曲线的门限经新场数据复核 | 沿用旧场阈值,导致大面积误报或漏报 | | 时间窗锚点 | inventory_products 的产物跨度落在输入跨度内 | 干净时间窗与对照时间窗常量仍是旧场的日期 | | 代码表与术语库 | 本体对象库按新场交付件重新摄入 | 沿用旧场代码表,问答引不到契约条目 | | 坐标与页面 | 场站坐标件在位,页面场名与台数正确 | 坐标件缺失,页面仍显示旧场名 | | 服务与入口 | guanlan.py serve 退出码 0,门户与工作台可开 | 端口占用,缺产物 | | 门禁全绿 | guanlan.py check 全绿 | 有 FAIL 项,文档未随版本更新 | ### 16.4 扩展点 换场之外,系统还有六类需要动刀的扩展;每一类的落点与改动面如表 16-4 所示。 | 扩展类型 | 落点 | 改动面 | |---|---|---| | 接新主机制造商(OEM) | 机型判据契约 reference/<场>/windscada_contract.yaml;主机技术资料目录(与场站目录并列);本体摄入第一步 | 契约条目与该机型的判据阈值;曲线七镜头的机型常数;通道到系统的映射与系统关键词映射;术语库;重跑本体六步 | | 接新数据形态(CSV 转发层) | 取数层的形态接纳(CSV 优先、该台没有 CSV 才回落归档库),形态来源记在返回表属性里供台账与页面说明来路 | 只加形态识别与回落规则,引擎与判级不动;新形态要能被逐族指纹扫描认到,否则"新数据必进重算"的自动取消跳过会失效 | | 接月度类目归档库 | 上游归档件;先按时间戳与机组标识对齐合成为同台 10min 补充件 scada_10min/台号-YYYYMM.csv(列与主件逐列同名同序、时间戳为 ISO 形式) | 只加一层转换与对齐;转换后 ③ 与 ④ 与 ④c 自动纳入;类目库本身只做信息级列出,不计变化、不假装被摄入 | | 接其它导出 | 场站目录下的约定子目录接口(八个约定子目录,改名等于换接口) | 约定子目录名要同步源类表、README 与维护页;摄入脚本按"源类目录再往下一层就是文件"读 | | 接新判级面 | 子系统的判级函数注册表加进系统集合 | 新增注册表,补四维证据与取严序,加进系统集合,补契约条目,补页面归口登记,补审计器期望 | | 接新机型契约 | 契约文件即判据阈值的来源 | 改契约等于改判据,须同步阈值标定与等价验收;旧产物的判词会随契约变化,属预期 | ### 16.5 已知未接通与边界 本节如实列出多场机制当前的缺口与换场时会失效的假设,不含估计。 - 多场机制在样本场之外的实测缺口:可用场清单当前只有内置的一个场,配置目录里其余同名文件不是场定义;16.3 节的作业单是按代码路径与守卫退出码推导出的可执行清单,不是在第二个场上跑通的现场记录。因此"可换场复用"目前有设计证据与单场实测证据,缺第二场的端到端证据。 - 原生归档库未装配:本机未装配原生的归档库读取驱动(既无该形态的 Python 驱动,也无命令行工具),读月度归档库走系统提供的数据访问组件加命令行(与写库同一套);目标机未装该组件时该形态不可读,且未在无该组件的机器上实测。 - 原始件根的环境变量名是代码侧遗留:原始件根默认 data/raw,可由 configs/serve.json 的 raw_dir 覆盖,启动器把它导出成一个"内嵌样本场代号"的环境变量,形如 WINDSCADA_<场>_SRC。该变量名与仿真回放资产目录名里各有一处写死的样本场代号,属去标识化未覆盖的代码侧遗留;真正接第二场时这两处需一并中性化或改为按场名派生。 - 门禁在无文档部署上的口径:交付文档三件不在成品包里时,版本记录一致性门与文档类门禁按"文档这一路缺失"容忍(重算链 ⑧d 步容忍 6,版本记录脚本只定义 0 与 2 与 6),即在只带程序的部署上门禁不会因为"没有文档"而判失败;代价是它也不会替使用方发现文档漂移。 - 换场后失效的假设之一,阈值标定:温度同工况比档的三级偏差阈值与稳健 z 门限、蓄能带宽比门限、M9 的分组容差与离群倍数、曲线八项显著门与分箱边界,全部是在样本场按该场机型的实测分布标定的。换场后不重新标定,门限过紧会把正常差异判成候选(表现为大面积报警),门限过松会把真实异常漏在门限之下;两者都是标定问题,不是算法问题。 - 换场后失效的假设之二,锚点与时间窗常量:残月识别与台时口径依赖"每小时 6 个节拍"的 10min 节拍假设;温度面的对照时间窗与同历月早一年对照时间窗、限电前干净时间窗、本体判级时间窗口径(近 90 日固定时间窗)都是与样本场数据末端绑定的常量。换场后必须按新场的数据起止与限电史重设,否则"干净时间窗"不再干净,判级会带上限电造成的功率与转速偏置。 - 换场后失效的假设之三,术语库与代码表:系统关键词映射、本体对象库的部件名与失效机制、作业指导与中间件正本、场站故障代码表、场站坐标件都是从样本场的交付件摄入的。换场后不重新摄入,本体页、机制链与问答会成片引不到契约条目并显示"未被契约背书";故障代码对不上时,按代码归因的判级面会整体落到不可判。 - 换场后失效的假设之四,机组清单与台号形态:机组清单、内部台号形态(样本场形如 01E…)、逐台主件命名、窄仓的缺列记录都按台号走;换场后台数变了,页面上"全场 N 台"一类表述与已烘焙进快照件的台数是生成时刻的场相关值,需要重算并重烘焙总览页。 ### 16.6 与同批交付文档的对应关系 本章对应《需求分析_观澜_2.32.0.docx》第 11 章(场配置化、机组与机型可替换、数据源形态可适配、阈值按场标定、术语与单位可配、跨场统一口径、按场裁剪收资、换场验收检查表)与《数据要求说明_观澜_2.32.0.docx》第 12 章(通用必选与可选的判定规则、场配置字段对照、数据源形态适配、换场收资差异清单、按场裁剪步骤);本章给的是设计侧的落点、换场作业单与检查表,数据侧的收资口径与字段要求以数据要求说明为准,需求侧的验收项以需求分析为准。 *** ## 17 已知边界与未实现 ### 17.1 逐条边界与处置建议 本系统坚持"缺件如实"。边界、影响与处置建议如表 17-1 所示,每条的处置建议都是可执行的一条命令或一次催缴动作。 | 边界 | 影响 | 处置建议 | |---|---|---| | 本机未装配原生 MDB 驱动 | 本机无 pyodbc 与 mdbtools,读 MDB 走 ACE 提供程序加 PowerShell(与写库同一套);若目标机未装该提供程序,MDB 形态不可读(未在无 ACE 的机器上实测) | 目标机安装 ACE 提供程序;或让现场导出 CSV(取数层本就 CSV 优先) | | 测风塔数据零交付 | 风资源与尾流类分析缺外部对照(收资要求第 3 项记为未到位) | 按收资要求催缴测风塔数据;到位后重算对应族 | | 油样 2026-07 批 102 行缺源件 | 油样索引少该批,等价验收会报"有需人工看的差异" | 现场补该份合并报告 PDF 后重算 ② 步 | | 远端未装 Ollama | 远端问答与升档不可用,模型探针必然 FAIL;其余页面不受影响 | 安装 Ollama 并把有网机器上的模型目录整体拷到目标机同位置;是否安装由用户决定 | | SOP 相关配置悬空 6 条 | 分析锁校验不可用;场景解析不可用(本包无调用方,不影响页面);案例判别器外键校验与模块键校验缺单源;经济性换算退回内置假设并在结果里标注 | 需要哪一场就建对应配置;或明确删除该模块的入口引用;逐条影响已登记在配置登记表的 known_missing | | 振动六层链扫描件与正本类产物 | 四步脚本齐全且关键两件已按口径重建,但"无样件可对拍"(链完整性检查自述) | 由研发给口径或提供样件做逐值对拍 | | 数据派生页面无生成端 | 脱敏状态一览与取数单是客户手里的交付件,性质上随运行状态变但没有指纹与生成端,重算不会刷新它们 | 已按冻结交付件管理并把"会变旧"写进登记理由;将来若要随数据变,再补生成端与来源指纹 | | 页面引用的四棵产物树不在包内 | 传动链回放、四系统判据、控制律仪表台、取数单等页面存在 19 条已知的引用悬空 | 保留记账不删(删了门户裂口、交付缺件);待该产物线随包后复检 | | 3 件内嵌快照缺指纹 | 只能按生成时间判断新旧,无法做逐字节陈旧检测 | 下次生成时把来源与来源摘要写进页面(机制已就绪) | | 无生成端的历史组级产物清单 | 趋势件、热链、扇区、偏航与润滑面等族的历史随包件没有生成端;当前反向审计显示盘上 3,548 件全部呼应成立、未归类 0,但清单本身仍在文档里留档 | 由研发补口径(照月度派生件的办法反推并逐值验证);或承认其为人工件不按产物管 | | 收资要求其余未到位项 | 故障录波仅 4 台、原生秒级高频数据未见交付、保护定值未成册、可行性研究与微观选址未交付、周围已建风电场报表未交付、大部件更换台账不完整 | 按收资要求逐项催缴;到位后按族重算 | | 本轮曾出现的两处门禁未通过(已修复留档) | 取证期间出现两个未通过:工作台依赖台账门返回 5(文档清单与现场不一致)、运行期可移植性门禁返回 1(源码里用系统分隔符拼库存字符串) | 两处都已按原口径修好并复跑转绿(依赖台账输出"文档与现场一致"、可移植性门禁输出"通过");留档的目的是提醒:这两处都不是靠人记得,而是被检查器跑出来的 | | 移植性大扫描仍报机器相关路径 | 第二个可移植性扫描器返回 1:290 处机器相关路径(HTML 烘入绝对路径 0 处),均在交付留档脚本、历史文档与注释里,不在运行路径上;同一份扫描同时确认虚拟环境绑定本机基础解释器路径,换机必须重跑一次安装 | 出包前把该扫描器纳入打包前置检查,或把这份清单当具名白名单维护并逐条写明理由;不要靠"人工记得它不影响运行" | | 交付前安全核查器未接入打包链 | 该脚本有四类规则(密钥、个人信息、金额与业主名、内部件)与严格模式退出码,但打包器、打包壳脚本里都没有调用它 | 在打包器里加一步调用(严格模式),命中即删包并返回非零;否则它只是手工步骤 | | 包内离线轮子只覆盖 Windows | wheels 下只有 win_amd64 一个平台目录(42 件、169.4 MB);Linux 与 macOS 安装会自动改为联网 | 要在国产化平台上完全离线安装,需在有网同类机器上取件后放进对应平台目录 | | 文档引用的模型离线包不在包内 | vendor 目录实测只有 4 件(清单文件加三份便携运行时),没有模型安装包目录;而说明书与安装脚本多处引用该目录 | 现场需要模型时单独交付模型安装包与模型目录;或在文档中明确"该目录随单独交付件提供" | | Windows 服务与 systemd 未在本次环境实测 | 本机未注册服务(状态报已注册为假、运行中为假),Windows 服务真注册与 Linux 单元真装未取证 | 用 --dry-run 预览、用免权限守护自证;具备管理员或 root 权限时执行注册并复核状态 | ### 17.2 口径冲突与待统一项 系统里仍有多处文档或代码口径不一致,如实列出如表 17-2 所示。这些不是"功能坏了",而是同一件事有两种写法,读者与维护方必须知道以哪一处为准。 | 冲突项 | 两种口径 | 以哪一处为准 | |---|---|---| | 重算链步数 | 系统设计说明写 14 步、重算操作手册写 8 步、移植文档写 12 步,实测默认 26 步(加 --skip-scada 为 23 步) | 以 scripts/rebuild_all.py --dry-run 的实跑输出为准 | | 窄仓列数 | 文档写 46 列(温度均值 27 列),实测 48 列(温度均值 25 列,另补两列控制通道) | 以脚本 --check 输出与 _manifest.json 的 columns_n 为准 | | 窄仓一次扫耗时 | 一处写约 2 分钟,一处写约 3 分钟 | 以现场实测为准,两处都保留为口径区间 | | 变桨面耗时 | 一处写约 10 分钟,手册写约 12 分钟 | 同上 | | 振动摄入耗时 | 一处写约 14 分钟,放置指导写约 25 分钟(对应 153 GB 导出) | 同上(体量不同) | | M9 按时间窗耗时 | 服务注释约 5 秒每时间窗,版本记录约 0.6 秒每时间窗 | 以本机实测为准;两处已记入待统一 | | 平均无故障间隔(MTBF)台时口径 | 停机归因模块已改为实测台时,部件可靠性表仍用理想日历台时 | 需统一到实测台时(否则同一指标两个值) | | 网关离线守卫端口 | 守卫里硬编码判断旧端口 8033,而端口表里工作台是 18033 | 以 configs/serve.json 为准,守卫待同步 | | 前端一处外链端口 | 工作台一处外链硬编码 8030,该端口不在端口表内 | 以 configs/serve.json 为准,外链待改 | | 多场机制只在样本场跑通 | 场配置目录下有多件 YAML 不是场定义(实为另一条链的机型物理约束档,由 foreign_farm_files() 登记为 profile);场定义加载器已同时认 YAML 与 JSON,但样本场之外没有第二个场做过端到端实跑(见 16.5 节) | 以场配置加载函数的实行为准;新场按 16.3 节作业单实跑一遍并按 16.3 节检查表验收 | | 重算操作手册的操作表 | 手册的步骤表仍写"默认不重生成 CMS 报告",而现行链自 2026-09-18 起默认生成报告(要旧行为用 --no-vib-report);手册第⑤步仍写旧的补齐脚本,而现行链第⑤步是反向呼应审计 | 以 scripts/rebuild_all.py 为准,手册待同步 | | 运维日志路径写法 | 一处写 logs/ops_动作_时间.log,实际是 logs/ops/ops_动作_时间.log | 以日志口径模块与实盘为准 | | 问答档位表 | 工作台服务端保留着旧的档位名(一个未安装的中档与一个混合专家档),配置文件的升档档位已经是本机实装的 32B 档 | 以 configs/models.json 为准(它同时是升档解析的第一顺位),服务端展示表待同步 | | 审计流水的字段清理 | 重试成功的那条记录里仍带着上一次尝试的失败原因 | 以字段语义为准(成功行不应带错误文本),生成端待修正 | | 检索索引文件形态 | 一处注释写索引落 json 与 npz 两种,而代码只写 json 与向量文件 npy,盘上也没有 npz | 以代码与盘上实物为准,注释待改 | | 本体对象库规模 | 设计文档写 9,613 个对象(2026-09-17 时点),盘上实测 9,791 个(2026-09-21 修改时间) | 引用规模时须注明口径日期,或重测 | | 安装说明的口径 | 说明书里仍写"所有服务只监听本机",与后来"门户监听所有网卡"的口径不一致;同一份说明书一处的"不含重算链"与另一处的"重算链已进包"互相矛盾 | 以 configs/serve.json 与 scripts/rebuild_all.py 的现状为准,说明书待修订 | ### 17.3 未实现项 以下三项是"设计上这样定,但功能未实现",如实列出:按所选时间窗的缓存没有过期时间与容量上限(进程内常驻、无淘汰、无持久化),长时间运行且频繁换时间窗时内存会持续增长;后台预热不覆盖 M9(只预热判级与默认时间窗的镜头);时间窗词表没有"今日、近 7 日、本月、本年"这类词(只有六个预设加单月、月区间、自定义起止)。此外判级矩阵本身只在内存里返回,不落 parquet 产物,因此"某一时刻的判级矩阵"没有独立留档件,只能靠当时的页面快照与日志。 *** ## 附录 A 编写依据 本文每章的主要来源文件如表 A-1 所示(章节与来源文件)。 | 章节 | 来源文件 | |---|---| | 1 文档说明 | src/version.py(VERSION 与 HISTORY 的 2.32.0 条目)、docs/需求分析_观澜_2.32.0.docx、scripts/delivery_docs_figures.py | | 2 设计目标与原则 | docs/系统设计说明.md(四条设计铁律)、app_ontology/.../ontology/mcp_server.py 头部、src/windscada/subsys/fusion.py 头部、src/windscada/perf/reliability.py | | 3 总体架构 | configs/serve.json、scripts/guanlan_gateway.py、guanlan.py(组件启动表)、run/pids.json、docs/系统设计说明.md(产物全景) | | 4 目录结构与路径真源 | src/paths.py、configs/registry.yaml、docs/系统设计说明.md(路径约定与统一记录)、本次目录实测 | | 5 数据接入与重算链 | scripts/rebuild_all.py(--dry-run 实跑)、scripts/scada_slim_build.py 与 slim10min/_manifest.json、scripts/raw_scan.py、scripts/raw_data_check.py、scripts/place_raw_data.py、docs/输入数据放置指导_v0.1.md、src/windscada/scada_source.py、scripts/csv_to_mdb.py | | 6 判级与算法 | src/windscada/taxonomy.py、src/windscada/perf/curves.py、src/windscada/perf/control.py、src/windscada/perf/reliability.py、src/windscada/perf/faults.py、src/windscada/perf/availability.py、src/windscada/subsys/pitch.py、src/windscada/subsys/yaw.py、src/windscada/subsys/hydraulic.py、src/windscada/subsys/temp_nbm.py、src/windscada/subsys/thermal_chain.py、src/windscada/subsys/structure.py、src/windscada/subsys/fusion.py、src/windcms/report_std.py、src/windscada/ui_en.py | | 7 时间窗口径 | scripts/windscada_serve.py(词表、按时间窗缓存、pending、预热)、app_frontEnd/app_frontEnd_guanlan/assets/app.js、src/version.py(2.9.0 条目) | | 8 服务与前端设计 | scripts/windscada_serve.py(路由与烘焙调用)、scripts/guanlan_gateway.py(改写与豁免)、app_frontEnd/app_frontEnd_guanlan/pages/(build/snapshot/classic)、app_frontEnd/app_frontEnd_guanlan/assets/app.js、scripts/windscada_overview_build.py | | 9 本体与知识层 | app_ontology/.../ontology/kb_ingest.py、populate.py、chain_ingest.py、trend_ingest.py、retrieval.py、maintenance.py、mcp_server.py、scripts/guanlan_facts_contract.py、scripts/sop_findings_from_ledger.py、outputs/<场>/ontology(实测规模) | | 10 本机模型接入 | configs/models.json、app_ontology/.../ontology/fast_agent.py、app_ontology/.../ontology/llm_gate.py、logs/audit/llm_audit.jsonl、docs/版本记录.md(2.9.1 与 2.9.2 条目) | | 11 运维控制台与重算编排 | scripts/guanlan_ops.py、scripts/_ops_launch.py、scripts/_ops_run.py、scripts/_ops_stop_keep_gateway.py、run/ops_job.json、logs/ops/、src/logfile.py | | 12 安装服务化与版本管理 | install.ps1、install.sh、uninstall.bat、uninstall.sh、scripts/guanlan_uninstall.py、scripts/service_ctl.py、scripts/win_service.py、scripts/service_worker.py、scripts/service_main.py、install-info.json、src/version.py、scripts/pack_dist.py、scripts/version_log.py、docs/版本记录.md | | 13 质量保证 | scripts/products_reverse_audit.py、pages_audit.py、config_audit.py、log_audit.py、raw_scan.py、chain_gap_check.py、version_log.py、detail_deps.py、inventory_products.py、check_portability.py、audit_chinese_terms.py、src/entry_refs.py、guanlan.py check(底稿 docs/src/_guanlan_check.txt) | | 14 安全合规与离线边界 | scripts/security_scan.py、scripts/deliver_desensitize.py、src/windscada/deid.py、src/windscada/deid_public.py、src/windcms/redact.py、configs/serve.json 注释 | | 15 可移植性与资源占用 | docs/移植与独立运行_v0.1.md、《使用说明书》v0.2(随包 docs/)、requirements.txt、wheels/ 与 vendor/ 实测、install.sh、scripts/service_ctl.py、本次目录实测 | | 16 多场适用性与换场迁移 | src/windscada/config.py(场抽象层与七个必填键)、app_ETL/configs/farms/_模板.yaml.example、src/paths.py 的 farm() 与 farm_config() 与 contract()、scripts/config_audit.py、本次场配置与场站目录实测 | | 17 已知边界与未实现 | docs/系统设计说明.md(缺口与边界、变更记录)、docs/源代码化清单_v0.1.md、docs/重算缺口与补件清单_v0.1.md、configs/registry.yaml(known_missing)、src/windscada/scada_source.py、docs/版本记录.md(遗留项) | | 附录 B 与附录 C | 仓库实测路径与模块清单、docs/系统设计说明.md 的术语用法 | 表 A-1 里的场相关路径与文件名一律写成占位符模板,替换规则是:`<场站>` 换成 data/raw 下的场站目录名(即场定义的 raw_station,辨识不到时用配置值);`<场>` 换成场名(app_ETL/configs/farms/ 下场定义文件的主名,同时是 outputs 与 reference 下的目录名,也是 WINDSCADA_FARM 的取值);`<机型>` 换成机型代号;`` 换成主机制造商代号;`<源类目录>` 换成八个约定子目录之一(scada_10min、scada_1min、scada_mdb、故障报警、风机故障记录、油样报告、windcms、m5_cms_tcm)。本文正文与表中一律称样本风电场(下称本场),按上述规则逐条替换即可得到目标场的实例路径;少数被引文档名本身含样本场字样,表中已按对外名写法归一(如 docs/说明书_观澜v2_v0.2.md)。 ## 附录 B 关键模块清单 仓库相对路径到职责的对应关系如表 B-1 所示。 | 仓库相对路径 | 职责 | |---|---| | guanlan.py | 启动器与自检:check 与 serve 与 status 与 stop 与 open 与 qa 与 version 七个子命令 | | src/paths.py | 路径唯一真源:安装根解析、各产物仓与配置与解释器助手;场名解析与按场落点(farm 与 store 与 contract) | | src/windscada/config.py | 场抽象层:场定义加载与必填键校验、场站目录扫描辨识与别名匹配、可用场与场切换 | | src/version.py | 版本唯一真源:VERSION 与版本规则与 HISTORY 与包名与安装记录读写 | | src/proc.py | 子进程统一口径:无窗口标志、后台起进程、前台短命令 | | src/logfile.py | 日志唯一口径:目录与命名与行格式与保留策略与标准输出包装 | | src/entry_refs.py | 入口引用闭合与入口脚本编码守则 | | src/derived_manifest.py | 由构建脚本自登记的派生产物清单 | | scripts/rebuild_all.py | 重算链编排(默认 26 步)与逐步退出码容忍 | | scripts/rebuild_from_raw.py | 三门台账与 SCADA 侧 10 个构建器的入口 | | scripts/scada_slim_build.py | 10min 公共列子集窄仓(按所选时间窗重算的底座) | | scripts/windscada_monthly_build.py | 月度派生件(与随包基线逐值对齐后才落盘) | | scripts/vib_raw_build.py | 振动侧一键摄入:时间窗索引与谱库与报告与逐台页与融合标量 | | scripts/pitch_face_build.py | 变桨面生成端(日粒度液压润滑柱塞与零位三口径月表) | | scripts/component_history_build.py 与 baseline_38_build.py | 振动在升与换件闭环、三层基线 | | scripts/sop_findings_from_ledger.py | 由重算台账生成事实契约 claim 族 | | scripts/guanlan_facts_contract.py | 事实契约构建与四类消费者派生件 | | scripts/products_restore_missing.py | 逐件来源台账维护(只记账不搬件) | | scripts/products_reverse_audit.py | 反向呼应审计与可逆性矩阵 | | scripts/pages_audit.py 与 configs/portal_pages.yaml | 页面归口登记与五条机器规则 | | scripts/config_audit.py 与 configs/registry.yaml | 配置登记与八条机器规则 | | scripts/raw_scan.py 与 raw_data_check.py 与 place_raw_data.py | 输入数据指纹扫描、放置体检与增量落位 | | scripts/windscada_serve.py | 工作台服务:路由族、按所选时间窗重算、pending 契约、页面烘焙调用 | | scripts/guanlan_gateway.py | 门户网关:前缀改写、data-abs 豁免、只读发布层、运维控制台 | | scripts/guanlan_ops.py 与 _ops_launch.py 与 _ops_run.py | 运维控制台后端、动作启动器与重算执行器 | | scripts/windcms.py 与 src/windcms | 振动诊断服务与报告、逐台页、工作台 | | src/windscada/taxonomy.py | 判级矩阵与系统归口与取严合并 | | src/windscada/perf 与 subsys | 曲线、控制、可靠性、停机、可用率、变桨、偏航、蓄能、温度、热链、结构、融合各面算法 | | app_ontology/.../ontology | 本体摄入六步、对象库、检索、MCP 只读工具、离线问答代理与校闸 | | install.ps1 与 install.sh 与 uninstall.bat 与 uninstall.sh | 安装与卸载入口 | | scripts/service_ctl.py 与 win_service.py 与 service_main.py 与 service_worker.py | 服务化注册器与服务体 | | scripts/pack_dist.py 与 pack.bat 与 pack.sh | 打包器、预演与开箱验证入口 | | scripts/delivery_docs_build.py 与 delivery_docs_figures.py | 交付文档渲染器与图表构建器(本版新增) | ## 附录 C 术语与缩写 本文使用的术语与缩写如表 C-1 所示。 | 术语或缩写 | 含义 | |---|---| | 观澜 | 本系统的产品名(观澜 v2 风电场智能分析系统);样板指以样本风电场为依据的第一套落地实例 | | 产物 | 由原始件算出来、供页面与接口消费的件(parquet、json、md、html 等),只落在 outputs/场站/ 下 | | 产物仓 | outputs/场站/ 下的九个顶层目录(windscada、ontology、windcms、m5_cms_tcm、tcm_compatible_replay、sop、guanlan、pitch、paradigm_r1) | | 原始件 | 现场交付的输入数据,只读,落在 data/raw/场站/ 下 | | 重算链 | scripts/rebuild_all.py 串起来的构建步骤序列(默认 26 步) | | 窄仓 | 10min 公共列子集的逐台 parquet 仓,用于把按所选时间窗重算从分钟级降到秒级 | | 判级矩阵 | 逐台乘各系统的判级表,由各面判级取严合并得到 | | 四维证据 | 变桨与偏航等面内部用于取严合并的四个判据维度 | | 融合面 | 把振动、温度、润滑、油液四源对齐后逐台判级的分析面 | | 残月 | 样本覆盖度低于 0.5 的月份(如只有 6 天数据的月份),页面与报告均如实标注 | | 平均无故障间隔(MTBF) | 日历或实测台时扣除停机后除以事件数得到的平均无故障间隔 | | 平均停机间隔(MTBO) | 进一步扣除调度令停机后除以事件数得到的平均停机间隔 | | 单次停机时长(MDT) | 停机段时长的平均值 | | 时间窗 | 页面顶部可选的数据范围(预设段、单月、月区间、自定义起止日期,含两端) | | pending 契约 | 按所选时间窗重算未完成时,先回上一份口径并用 pending 标记明示,页面轮询再取的约定 | | 单一真源 | 同一事实只允许一处权威定义(路径、版本、端口、页面归口、配置登记) | | 质量门 | 重算链与自检里的审计检查点,每处都能独立重跑并给出退出码 | | 开箱验证 | 打包器对交付包做解压、安装、无窗口启动、页面可用、卸载的整链核验 | | 骨架 | 交付包里只带目录结构不带文件的输入数据目录骨架 | | MCP | 本体层的只读工具接口,供离线模型按纯函数取证 | | claim | 事实契约里的一条结论,带来源引用、本条摘要、时间窗与裁决 | | 校闸 | 问答结果的接地核查,不过闸不出正文 | | 升档 | 默认模型被校闸拦下后改用更强档位重答一次 | | 中间件 | SOP 中间件与评审落盘件(outputs/场站/sop) | | 冻结交付件 | 按交付版本发布、不随输入数据自动变的客户交付件 | | 场定义 | app_ETL/configs/farms/<场>.yaml,描述一个场的机组清单、源类目录与产物落点的配置对象(七个必填键) | | 场抽象层 | 把"场"做成可加载、可校验、可切换的配置对象的机制:配置层加路径层加语义层 | | 场无关引擎 | 换场不需要改动的部分:判级与算法结构、融合纪律、本体与检索、服务与前端、审计门 | | 场相关参数 | 换场必须重新给或重新标定的部分:阈值、机型参数、时间窗锚点、机组清单、术语库与代码表、坐标件 | | 换场作业单 | 从准备场配置到页面与门禁验收的逐步命令清单,每步带命令与退出码语义 | | 场站目录辨识 | 用 raw_station 与 src_farm_names 把 data/raw 下的目录认成本场的四段式判定(raw_station 与 alias 与 single 与 none) | ### 3.5 算法服务化(P10,2026-09-29 用户令) 用户令给出目标技术栈:前端 JavaScript/TypeScript + HTML/CSS + Vue.js + Node.js,后端 Java + Spring Cloud Alibaba(Nacos / Gateway / OAuth+JWT)+ Spring Boot + Swagger + MyBatis-Plus,**算法 Python + FastAPI**。 本期只落地**算法这一步**(P10):把算法层既有实现包成 HTTP 服务,作为后端各栈与前端调用算法的统一入口。 **形态**:`app_algorithmModel/app_algorithmModel_guanlan/service/` 是 FastAPI 应用,入口 `python scripts/algorithm_service.py --host 127.0.0.1 --port 18050`(端口登记在 `configs/serve.json` 的 `algorithm`)。**只读**:带写盘副作用的参数在登记表里被固定成只读值(如曲线仓的 `write=False`)。 | 端点 | 取数 | 参数 | |---|---|---| | `GET /healthz` | 存活与版本 | — | | `GET /api/endpoints` | 端点自述(登记表) | — | | `GET /api/systems` | 判级维度字典(七系统 + 温度通道→系统) | — | | `GET /api/matrix` | 七系统判级矩阵(与 `/detail/api/fleet` 的 `sysmx` 同源) | — | | `GET /api/reconcile` | 判级与台账对账 | — | | `GET /api/reliability` | 可靠性总览 | `since` | | `GET /api/problems` | 当前问题清单 | — | | `GET /api/availability_summary` | 可用率摘要 | `month_from` | | `GET /api/fusion` | 融合面四源表 | `all_turbines` | | `GET /api/handoff` | 振动融合交接件 | — | | `GET /api/pitch` | 变桨面(日粒度 + 零位三口径) | — | | `GET /api/curves` | 曲线仓(只读) | — | | `GET /api/curves_check` | 曲线物理性检查 | — | | `GET /api/{端点}/raw` | 该端点的确定性 JSON 文本(对拍用) | — | | `/docs` · `/openapi.json` | Swagger UI 与 OpenAPI 文档 | — | **不改行为的证明(逐值对拍)**:`/api/{端点}/raw` 返回的确定性 JSON 与进程内直调同一函数的 JSON **逐字节相同**(sha256 相等),`/api/{端点}` 的 JSON 响应经同一归一化后也相同。2026-09-29 实测 11 个端点 全部一致(含曲线仓 69.4 万字节 / 81 秒、判级矩阵 2.1 万字节),带参抽查 `reliability.since`、 `fusion.all_turbines`、`availability_summary.month_from` 同样一致。 **离线依赖(实逮)**:目标机离线,`wheels/` 里没有 FastAPI 的轮子;而开发机的 `.venv` 是 `include-system-site-packages=true`(fastapi/uvicorn 来自**系统** Python),"开发机能跑"不等于"随包能跑"。 因此把 FastAPI 运行栈按发行包整目录随包在 `vendor/pyfastapi/`(15.9 MB,含 `*.dist-info`), `service/deps.py::ensure_fastapi()` 在 `import fastapi` 失败时把它插进 `sys.path`, 并已用"纯 stdlib + 该目录"验证可导入(fastapi 0.141.1 / uvicorn 0.52.4 / pydantic 2.13.4)。 **随 serve 默认起(2026-09-29 用户令)**:`cli.services()` 已把算法服务列为组件(端口取 `configs/serve.json` 的 `algorithm`,缺省 18050),网关加路由 `/algorithm` → 18050,因此 `guanlan.py serve` / start.bat 会连带起它,前端与后端(各栈)**从网关一个源站**调算法;现有进程内调用链与页面呈现不变。后端 Java 化(P12)如何经网关消费本服务,留待后续阶段。 ### 3.6 前端 Vue 化(P11 起步,2026-09-29 用户令) 目标栈:**JavaScript/TypeScript + HTML/CSS + Vue.js + Node.js**,呈现与重构前一致、与后端按结构交互。 | 项 | 内容 | |---|---| | 工程 | `app_frontEnd/web/`(Vue 3 + Vite 6 + TypeScript;`npm run dev` / `npm run build` / `npm run typecheck`) | | 构建产物 | `release/web/`(`index.html` + `assets/*`,≈28 KB gzip)——由静态组件伺服、经网关 `/web` 前缀暴露 | | 取数入口 | `src/api/contract.ts`:只写相对路径(`/api/...`、`/algorithm/...`),与冻契约 `app_backEnd/.../contract/http_api_v1.json` 一一对应 | | 同源 | `configs/serve.json` 的 `web=28110` + `cli.services()` 的 web 组件 + 网关 `/web` 路由 ⇒ 页面与后端/算法同源,前端代码不带 host | | 对拍 | Python 门 `scripts/http_contract_audit.py`(并入 `guanlan.py check`)· Node 冒烟 `app_frontEnd/web/tools/contract_smoke.mjs` —— 同网关同路径,实测各 42/42 一致;`/web/index.html` 200 且挂载点就位 | | 迁移纪律 | v2 工作台**逐页签**迁移,每页签与重构前页面对拍通过后才删对应 Python 源码(用户令) | | **进度(2.21.0)** | **已过严格整页签对拍**:`overview`(138 条文本)· `energy`(51 条文本 + 1 表 + 图表数字 11/11)· `component`(260 条文本 + 2 表)。**未迁移**:`vibration`·`generation`·`fault`·`decision`·`assistant`·`report`·`system`(7 个) | **迁移进度(2026-09-29 第二轮前端工作)**:已迁移 **总览(overview)** 页签,并对拍通过。 | 机制 | 说明 | |---|---| | 基线怎么来 | `/detail/v2` 是**浏览器里现渲**的单页(HTML 只有壳 + 内联 app.js + 语言包,无 `window.__DATA__`)⇒ 基线必须**让旧代码真跑一遍**:`tools/render_parity.mjs` 用 jsdom 加载现网页面、在 `beforeParse` 装 fetch 桩转发到真实网关、等 `.kpis` 出现后从 DOM 抽锚点 | | 迁移版怎么来 | 用**同一份数据**(fetch 桩记录下的 `/api/fleet` 响应)把 Vue 的 `Overview.vue` 经 `vite build --ssr` + `vue/server-renderer` 渲成 HTML,抽同一批锚点 | | 比什么 | KPI 值/文案/类名(6)· 需关注卡的 id/部件/状态词(3)· 状态条格子的类名/`data-u`/台号(38)· 静默文案 · 页签文案(10)—— 共 11 组 | | 实测 | 11 组**全部一致**(类名按排序后的集合比;实逮旧页 `kpi warn` 与 Vue `warn kpi` 只是属性顺序) | | 口径同源 | 渲染小函数(状态枚举映射、台号、数字格式、台级状态取最严)搬到 `web/src/lib/render.ts`;文案搬到 `web/src/i18n/zh.ts`(105 键,逐字取自现网语言包,由对拍复核) | | 命令 | `cd app_frontEnd/web && npm run build:ssr && npm run parity`(另有 `npm run smoke` 复核契约 42 条) | **2026-09-29 第二轮(ECharts + Java 网关/OAuth2)**: | 项 | 实况 | |---|---| | 构件 | **纠正上一轮结论**:Maven Central 与阿里云/华为/腾讯镜像**可达**(上轮为瞬时失败)。新增 `settings-online.xml` 下回 gateway/security/oauth2/nacos;离线侧补同名 mirror id + `-Dmaven.legacyLocalRepo=true` 才能用(实逮:来源 id 不一致会误报不可达) | | Java 网关 | 独立工程 `app_backEnd/backend-gateway/`(Gateway 走 WebFlux,与 MVC+springfox 同进程不兼容)· 端口 28085 · 与 Python 网关 28084 并存灰度;实测 `/algorithm/healthz`、`/web/index.html`、`/detail/api/facts` 经它全 200 | | OAuth2+JWT | `/java/token` 自签 HS256;`/java/secure/whoami` 带 token 200(SCOPE_guanlan.read)、**无 token 401**;Swagger UI 200。实逮:Nacos 在 classpath 会抢连(须 `spring.cloud.nacos.discovery.enabled=false`)、yml 两个 `guanlan:` 顶层键致 DuplicateKey | | 前端 | ECharts 6.1.0 + `EChart.vue`;`Energy.vue`/`Component.vue` 已迁(进行中);`fault` 未开始;语言包改全量搬(845 键) | | 对拍门 | 升级为**整页签**(文本序列 + 表格逐行 + 图表数据数字集合);当前**如实未通过**:overview 138≠95、energy 51≠42 与图数字 11≠16、component 260≠243(含一处空格) | 本版落地的是"链路 + 契约自检页":证明前端这条栈在本机可用、可构建、可同源取数,并给出自动化对拍口; 各页签的界面搬迁在后续阶段按同一验收方式推进。 ### 3.7 后端 Java 化(P12 起步,2026-09-29 用户令) 目标栈:**Java + Spring Cloud Alibaba + Spring Boot + Swagger + MyBatis-Plus**(本期先落地单体 Spring Boot 2.7.18 + springfox Swagger;Gateway/Nacos/OAuth2 待依赖到位)。工具链按用户指定用本机 JDK 1.8.0_202 + Maven 3.8.9。 | 项 | 内容 | |---|---| | 工程 | `app_backEnd/backend-java/`(Maven 单模块;`GuanlanBackendApplication` + `DiagnosticController` + `AlgorithmClient` + `SwaggerConfig`) | | 端口 | 28120(与现有 Python 后端并存:detail 18033 / 网关 28084 / 算法 18050 / Vue 28110)——迁移期两套同跑 | | 与算法交互 | `AlgorithmClient` 经 HTTP 调算法服务(11 个只读端点),**不重复实现任何判据**;`/java/algorithm/{name}` 按白名单代理 | | 接口文档 | springfox 3:Swagger UI `/swagger-ui/index.html`、OpenAPI `/v3/api-docs`(OpenAPI 3.0.3) | | 构建 | `python app_backEnd/backend-java/build_offline.py`(`mvn -o compile` + 自组装 `target/lib`);随工程带 `settings-offline.xml`(`${user.home}/.m2/repository` + offline),仓里不写死本机盘符 | | 交付 | 随包 JRE 1.8 + `classes` + `lib/`(53~54 jar / 26.4 MB);`target/` 明确不进交付包 | | 验收(实测) | `/java/healthz` 200 且 `algorithm_ok=true` · `/java/algorithm/endpoints` 返回 11 端点 · Swagger UI 200 · `/v3/api-docs` 200 | | 待办 | 按 `http_api_v1.json` 的 42 条端点逐条迁移 + 逐条对拍;网关前缀灰度切换;对拍通过后删对应 Python 源码 | **离线实逮(本机 Maven 仓是残缺的,逐条记在 `app_backEnd/backend-java/README.MD`)**: `mvn package` 打不出 jar(缺 `maven-archiver` 与 `plexus-utils:1.1`);`surefire` 缺运行期依赖 ⇒ 本项目把 `default-test` 绑到 `phase=none`;缺 `spring-boot-loader-tools` ⇒ 不打 fat jar;springfox 3 运行期要 `mapstruct`(未被传递,需显式声明);Spring Boot 2.6+ 需 `spring.mvc.pathmatch.matching-strategy=ant_path_matcher`。 到有网/有内网 Maven 源的环境,这些绕法应逐步换回标准做法。