系统设计说明.md 60 KB

观澜 · 如东样板 v2 · 系统设计说明

版本 v0.2.0 · 2026-09-16 · 适用 <安装目录>(安装目录) 本文是系统设计正本: ① 产物全景(类型/功用/输出路径/生成端/消费端);② 路径与进程的统一约定; ③ 输入 ↔ 产物呼应关系;④ 重算的两种运行状态与"产物即时进页面"的实现;⑤ 启动与门户。 与本文配套、可复跑的机器校验: python scripts/inventory_products.py(清点 + 呼应校验,--check 出退出码)。


1. 系统是什么(一页)

现场数据 data/raw/<场站>/  ──摄入──▶  outputs/<场>/(产物仓)  ──现读──▶  页面/接口
     ▲  7 类约定目录                        ▲  9 个产物仓                  ▲
     │  scada_10min / scada_1min /           │  windscada  L0 标准仓        │  网关 :28084(门户/运维控制台)
     │  故障报警 / 风机故障记录 /             │  ontology   本体对象库        │  detail :18033(分析工作台)
     │  油样报告 / windcms / m5_cms_tcm      │  windcms   CMS 振动           │  cms    :18020(振动诊断)
     └─ 西门子4.0技术资料(机理层,不在场站目录下)│  m5_cms_tcm 振动线出件/窗     │  sim :18791 · sim_sys :18792 · viewer :64292
                                            │  tcm_compatible_replay      │
                                            │  sop / guanlan / pitch / paradigm_r1

四条设计铁律(贯穿全部代码,改代码时先读这四条):

  1. 路径唯一真源 src/paths.py —— 代码/配置里只写 ROOT 相对路径,运行期由助手解析成绝对路径(基准是安装根,不是 cwd)。
  2. 产物只落在 outputs/<场>/;原始件只读 data/raw/<场站>/;release/ 是交付静态层。三者不互相写。
  3. 判断在代码、模型只转述;每个产物都要能说清"谁生成、谁消费、来源是 raw 重算还是随包补齐"(_provenance.json)。
  4. 窗口/进程统一口径 src/proc.py —— 子进程一律 CREATE_NO_WINDOW,日志落 logs/,不弹命令窗口。

2. 产物全景(自动生成,勿手改)

由 python scripts/inventory_products.py --write-doc 生成;数据源是盘上的实际文件 + outputs/<场>/_provenance.json(逐件来源台账)+ outputs/<场>/_derived_manifest.json(构建脚本自登记)。

自动生成于 2026-09-16 16:26;数据源: outputs/rudong/_provenance.json + _derived_manifest.json + 实际文件

产物仓 输出路径 (相对安装根) 功用 生成端 消费端 件数 大小 来源(raw 重算/随包)
windscada outputs/rudong/windscada/ L0 标准仓: SCADA/台账/派生分析的全部 parquet (页面主取数处) rebuild_from_raw.py (三门台账) + --scada (10 个构建器) + windscada_monthly_build.py scripts/windscada_serve.py 各视图 · src/windscada/taxonomy · subsys/fusion 115 4.5 MB 17 / 98
ontology outputs/rudong/ontology/ 本体对象库: 码表/手册/工单展开/失效树 + 检索索引 + 实机参数 python -m src.ontology.kb_ingest → populate → chain_ingest → trend_ingest → retrieval.build 脚本 windscada_serve.py 本体页/问答 · scripts/guanlan_facts_contract.py 25 82.9 MB 3 / 22
windcms outputs/rudong/windcms/ CMS 振动诊断产物: 状态评估报告/逐台页/工作台页/知识库 + 厂家报告转录 scripts/windcms.py report/kb · scripts/vib_reports_build.py 自服务 :18020 (每请求现读) · taxonomy.system_matrix (转录设备状态) 56 30.1 MB 2 / 54
m5_cms_tcm outputs/rudong/m5_cms_tcm/ 振动线出件与窗级分析: handoff 接口 + 窗索引/谱库 + TCM 兼容件 scripts/vib_raw_build.py (窗索引/谱) · 振动线出件 (handoff, 随包快照) src/windscada/subsys/fusion.py · src/windcms/data.py · scripts/windcms.py report 1808 3329.7 MB 1718 / 90
tcm_compatible_replay outputs/rudong/tcm_compatible_replay/ TCM 兼容链回放资产 (模型表/掩码阈值/裁决记录) 随包快照 (无生成端) src/windcms/report*.py · config.mask_thresholds 59 32.7 MB 0 / 59
sop outputs/rudong/sop/ SOP 中间件/评审/台账与事实契约底稿 随包快照 (无生成端) scripts/guanlan_facts_contract.py · 门户结论段 210 15.8 MB 0 / 210
guanlan outputs/rudong/guanlan/ 事实契约与对外派生 (可上云面孔) scripts/guanlan_facts_contract.py 门户 #findings · /api/facts 5 0.5 MB 0 / 5
pitch outputs/rudong/pitch/ 变桨侧派生件 (零位/日粒度) 随包快照 + rebuild_from_raw --scada 脚本 windscada_serve.py 变桨面 2 0.3 MB 0 / 2
paradigm_r1 outputs/rudong/paradigm_r1/ 范式实验件 (E3/E5/E8 底稿, 事实契约输入) 随包快照 (无生成端) scripts/guanlan_facts_contract.py 29 0.5 MB 0 / 29

合计 2309 件 / 3496.9 MB;其中 raw 重算 1740 件、随包补齐 571 件。

2.1 逐件来源口径(_provenance.json)

来源 含义 判据
raw-derived 由 data/raw 重算出来的 该件在 RAW_DERIVED 表里,或由构建脚本自登记(src/derived_manifest.py)
shipped 包内没有生成端 / 规则未复现,用随包件补齐 既不在上表、也无自登记,且件在随包快照里存在

自登记(2026-09-16 新增): 振动侧的产物名随"窗名/分片序号"变化,写不进精确路径表,而按名字通配会误伤 同名旧件(报告_CMS振动状态评估报告_*.md 既有随包/自产的、也有厂家报告转录的)。改成谁算的谁登记: vib_raw_build.py / vib_reports_build.py 落盘后把相对路径写进 _derived_manifest.json, products_restore_missing.py 生成台账时据此记为 raw-derived。漏登记可用 python scripts/vib_raw_build.py --register-only 幂等补登记。


3. 路径约定(唯一真源 src/paths.py)

3.1 全部路径助手(写新代码时只用这些)

助手 返回 用途
P.ROOT <安装目录> 一切解析的基准(WINDSCADA_ROOT 可覆盖,冻结构建时由启动器设)
P.RAW_ROOT data/raw 现场原始件根(WINDSCADA_RUDONG_SRC / serve.json.raw_dir 可覆盖)
P.station_dir(name) data/raw/<场站名称> 兜底约定位置(权威值来自 config.raw_station_dir() 的扫描辨识)
P.out_root(name) outputs/<场> 产物仓根
P.store(name) outputs/<场>/windscada L0 标准仓(页面主取数处)
P.ont(name) outputs/<场>/ontology 本体对象库
P.objects_json(name) …/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) outputs/<场>/guanlan 事实契约与对外派生
P.pitch(name) outputs/<场>/pitch 变桨侧派生件
P.paradigm(name) outputs/<场>/paradigm_r1 范式实验件(E3/E5/E8 底稿)2026-09-16 新增
P.report_dir(name) outputs/<场>/report 报告交付件(交接单/现场单)2026-09-16 新增
P.cloud(name) outputs/<场>/guanlan/cloud 可上云面孔(脱敏后的契约/派生/页面)2026-09-16 新增
P.contract(name) reference/<场>/windscada_contract.yaml 机型判据契约(属 reference 侧,不在 outputs)
P.rel(p) / P.disp(p) / P.disp_dir(p) 相对 POSIX 串 / 显示串 / 目录显示串 写进产物用 rel();给人看(页面「位置」列)用 disp()
P.resolve(p) 绝对路径 把"可能是相对"的值按 ROOT(不是 cwd)解析
P.venv_python() / P.python_exe() 解释器路径 跨平台探测 .venv/Scripts/python.exe 或 .venv/bin/python

3.2 2026-09-16 统一掉的路径问题(源码已改)

# 位置 原样 改成 为什么要改
1 scripts/products_restore_missing.py P.STORE if hasattr(P,'STORE') else ROOT/'outputs'/'rudong' P.out_root() P.STORE 不存在 ⇒ 恒落到写死的 rudong;多场部署会把 rudong 的随包件补进别的场(静默串场)
2 scripts/ingest_ops_2025.py pathlib.Path('outputs/rudong/ontology/objects.json') + 裸 write_text _P.objects_json() + Store(…).save() cwd 相对 + 写死场名 + 绕过 store 的全库校闸与原子写;且该脚本缺 import os 一直 NameError,属"上膛但没响"的凶器
3 scripts/ontology_p2_verify.py ROOT/'outputs/rudong/ontology/objects.json'、…/guanlan/facts_contract_v0.json P.objects_json()、P.guanlan() 写死场名
4 scripts/guanlan_facts_contract.py 4 条 ROOT/"outputs/rudong/…" P.guanlan()/P.sop()/P.paradigm() 同上(事实契约的输入/输出全在这里)
5 src/ontology/maintenance.py 自写 ROOT=parents[2]、_RAW_STR、disp() P.ROOT / P.RAW_ROOT / P.disp 同一文件里三处"影子真源"(显示规则两个实现)
6 src/sop/wrapup.py …/cleaned/turbine.parquet(硬编码) 读 clean_gate.json 的 section → 否则取目录内唯一 parquet → 都取不到则响亮打印 写侧落的是 cleaned/<section>.parquet(section 可变)⇒ 读侧硬编码时静默 None,柱2 悄悄降级 INSUFFICIENT
7 src/windscada/subsys/pitch.py P.store()/'alarms.parquet' cfg['store'](registry(cfg) 透传) P.store() 跟环境变量,cfg['store'] 跟显式选定的场 ⇒ 多场下跨场串数据且不报错
8 scripts/check_transferable.py 门户期望字节数写死 20225828 运行时从主实例现量 门户外壳一改常量即过期,核验会打印永远不成立的"与主包不同"
9 src/windscada/taxonomy.py、subsys/fusion.py、scripts/windscada_serve.py 读侧硬绑 P.cms() 新增 windcms.config.cms_out(),读侧与写侧同一解析口 WINDCMS_OUT 只有写侧认 ⇒ 设了它就"写到旁路、页面读生产",表现为"重算完了页面还是旧数"且不报错(冻结构建自检正是这个组合)
10 src/windcms/pipeline.py::ingest 任何 .zip/.rar/.7z 都调 rudong_tcm_ingest_raw.py(未随包) 先判包内容:含 *_decode.json → 按"已解码导出"走 rudong_tcm_index/spectra(支持 zip 当 root);否则报明确错误并给两条可行路径 把 CMS 导出 zip 直接指过来时原本必崩 FileNotFoundError,而其实不需要那个脚本

3.3 仍然存在的不统一(如实列出,未擅自大改)

类别 具体 影响 处置建议
一次性/历史脚本自拼路径 scripts/guanlan_cloud_{face,page,qa}.py、guanlan_portal_inject_claims.py、guanlan_baseline_manifest.py、llm_known_answer.py 里仍有 outputs/rudong/… 字面量 只影响这些一次性派生/云端面孔脚本;多场时需手改 换场前把这几处改走 P.guanlan()/P.cloud()/P.store()(P.paradigm()/P.report_dir()/P.cloud() 已备好)
历史随包留档 scripts/sim_hub/*(含 /Users/yuanying/... mac 路径)、outputs/rudong/sop/*.py 非运行期(装配脚本留档) 已在 check_portability.py 具名登记;随包只为"可重放",不参与运行
多场机制未接通 src/windscada/config.py::available() glob configs/farms/*.json,而磁盘上 11 个场配置是 *.yaml 现在只有内置 rudong 可用;cfg['store'] 恒等于 P.store('rudong') 二选一:把 available() 同时认 *.yaml,或把场配置转成 json
store seam 三种写法 cfg['store'](14 处写点)/ P.store()(1 处,已修)/ ROOT/'outputs'/…(4 处,已修 3 处) 多场前无实际差异,属"潜在缺陷" 新代码一律 cfg['store'] 或 P.store(name)
配置/依赖指向不存在的件 configs/scenario_registry.yaml、configs/<场>/value_assumptions.yaml、configs/analysis_lock*.yaml、scripts/sop_check.py、scripts/analysis_lock_check.py、docs/审核规则_经验固化_v1.md、docs/振动诊断模型_六层_v1.md、.claude/skills/…、m5_cms_tcm/model_run_l6.parquet、m5_cms_tcm/fusion_38.csv SOP 场景模块读注册表必抛;锁闸恒空转;wrapup 电价只能走"假定锚";CMS 报告不能重生成(会掉内容) 见 §7 缺口表;这批是"随包没发全",要么补件要么显式降级(wrapup 已改为响亮说明)
被引用但未随包的脚本(10 个) rudong_tcm_ingest_raw.py(已加前置判断,见 §3.2-10)、rudong_tcm_oem_scan.py、rudong_line_energy_share.py、rudong_model_run.py、rudong_fusion_run.py、rudong_build_baseline.py、dsh_learn_page.py、intake_scan.py、guanlan_cloud_sims.py、deploy_gate_check.py 前五个属振动六层链(见 §7);其余是开发/部署辅助脚本的引用 逐个二选一:补件,或在调用处加"缺失即明确报错 + 替代路径"(pipeline.ingest 已按此改)
有读无写的"孤儿产物"(12 件) genbearing_monthly / mblub_monthly / yaw_dynamic_monthly / yaw1min_liveness / yaw_err_clean / sector_power / duty_monthly / pc_monthly_bins / thermal_monthly / control_monthly / structure.parquet / watch_channels_monthly 趋势件、热链、扇区、偏航/润滑/控制面 维持随包件;要"自己算"须研发给口径(照 temp_monthly 的办法反推 + 逐值验证)
有意重复落盘(不是缺陷,但要知道) ① spectra_meta.parquet 同时写 <窗>/spectra/ 与 <窗>/(约 4.6 MB/窗)—— 为兼容 data.spectra_meta() 的两条读取约定;② 报告_CMS振动状态评估报告_<期>.md 有两个来源(report_std.py 自算 vs vib_reports_build.py 厂家转录),靠日期口径区分(自算=当天、转录=报告期月末) 磁盘占用翻倍(仅 meta,非谱数据);命名空间共享 已在此登记;data.py 优先读 <窗>/spectra_meta.parquet,两份内容逐字节一致
P.VIEWER 常量全仓 0 引用 src/paths.py 定义了 VIEWER,实际取 viewer_dir 走 configs/serve.json 无功能影响 下次统一时二选一(用起来或删掉),避免"看着像真源其实没人用"

4. 进程与命令窗口(统一口径 src/proc.py)

问题: Windows 上"无控制台的父进程 + 裸 spawn 一个控制台程序" = 系统给子进程新建一个可见控制台窗口。 本系统里无控制台的父进程很多(网关、运维动作进程),于是"点页面按钮"会闪窗、跑重算会挂一个 20 分钟的黑窗、 /ops 每 2 s 轮询 tasklist 会反复闪窗。

统一写法: src/proc.py

API 用途
proc.NO_WINDOW / proc.flags() CREATE_NO_WINDOW(Windows)/ 0(POSIX)。★与 DETACHED_PROCESS 互斥,只认这一种
proc.spawn(cmd, log=…, env=…, cwd=…, **popen_kw) 后台起无窗口子进程;log= 落日志,也可自己传 stdout=/stderr= 句柄(透传,不覆盖)
proc.run(cmd, **kw) / proc.run_text(cmd) 前台等待的无窗口子进程(tasklist / git / 短命令);run_text 默认 text=True, errors='replace'(控制台输出是 GBK,严格解码会得到 None)

已收敛的调用点(凡"父进程无可见控制台"的入口都必须走这里): guanlan.py(组件 spawn、tasklist、taskkill)、 scripts/_ops_launch.py、scripts/_ops_run.py(整套重算的宿主进程)、scripts/guanlan_ops.py(启动器调用 + 每 2 s 的 tasklist 轮询)、 scripts/guanlan_gateway.py(git,每次 /api/version)、scripts/_ops_start_and_open.py(起 serve)、 src/windcms/plugins.py(CMS 页面「分析」按钮起的全链)、scripts/windscada_serve.py(详情工作台云端档问答)。

判定口径(新加子进程前先问一句): 我的父进程有没有可见控制台?有(用户从终端跑的 CLI)→ 让它继承,用户能看见进度; 没有(服务/网关/运维动作进程)→ 必须走 src/proc.py,否则 Windows 会给子进程新建一个可见窗口。

4.1 三个"静默故障"坑(都实炸过,已写进 guanlan.py check 作回归守卫)

# 坑 现场表现 处置
1 CREATE_NO_WINDOW 新建的控制台会把子进程标准句柄吸走 从页面点"执行重算":_ops_run 自己那行进了日志,rebuild_all.py 及之后一个字都没有,任务随后挂住(0 CPU / 0 I/O / 1 线程);页面只剩"运行中" src/proc.py::_inherit_stdio() 把父进程当前 sys.stdout/stderr 显式交给子进程(STARTF_USESTDHANDLES)
2 capture_output=True 与显式 stdout= 互斥,同给抛 ValueError 异常被 guanlan_ops.job_running() 的 except 吞成 alive=False ⇒ 任何在跑的任务都被立刻改写成"被强杀",页面显示完成、重算按钮重新可点(可能并发起两个重算) _inherit_stdio 先让路(有 capture_output 就什么都不加);自检加一条"run_text(capture_output) 可用"
3 内嵌版 /ops/recalc 裁掉了「服务」卡,但 JS 仍 $('#b_start').onclick=… 元素为 null → 抛 TypeError → 后面所有按钮绑定与 refresh() 全不执行 ⇒ 用户看到的正是"清除产物/执行重算点了没反应" 所有元素访问与绑定走 set()/on() 容错包装;并加 try/catch 把脚本错误显示在页面上(不再静默)

服务端配套: /ops* 响应现在带 Cache-Control: no-store, must-revalidate —— 否则页面 HTML/脚本更新后浏览器仍用旧版, 表现同样是"改了没反应"(guanlan_gateway.py::_bytes(no_store=True))。

4.2 用户怎么启动(不弹命令窗口)

★ 2026-09-16 用户令"把 VBScript 替换掉":原先靠 start_hidden.vbs(WScript.Shell.Run(..., 0, False)) 隐藏启动。VBScript/Windows 脚本宿主(WSH)已被微软列为弃用特性,且服务器上常被组策略整体禁用, 所以现在包里没有任何 .vbs,无窗口启动由 pythonw.exe(无控制台子系统的解释器)+ Python 启动器承担:

入口 行为 窗口
启动观澜.lnk(推荐;install.ps1 安装时生成,根目录一份 + 桌面一份) 快捷方式直接指向 .venv\Scripts\pythonw.exe + scripts\guanlan_start_hidden.py(工作目录=安装根,窗口样式 7) 一个窗口都不出现(不经过 cmd.exe,也不经过 WSH)
start.bat(双击) 默认 start "" /min ".venv\Scripts\pythonw.exe" "scripts\guanlan_start_hidden.py" 会闪一下(.bat 是控制台程序,Windows 必定先开窗口,脚本内无法消除);start.bat console = 旧前台模式(排障看实时日志,关窗=停服务)
scripts/guanlan_start_hidden.py 启动器本体:端口已在 → 只开浏览器(幂等);否则无窗口起 guanlan.py serve、等 /healthz、开浏览器 无窗口(--selftest 自证:GetConsoleWindow()==0)
运维控制台/门户「数据重算」 动作进程同样无窗口;进度与日志尾巴在页面里看 无窗口

日志去哪了: logs/serve.log(无窗口模式下 serve 的输出)、logs/start_hidden.log(启动器轨迹:窗口状态/就绪耗时/失败原因/是否已开浏览器)、 各组件 logs/<name>.log、运维动作 logs/ops_<动作>_<时间>.log。失败时启动器还会弹一个消息框(无窗口模式下唯一能让人看见的通道)。

实测(2026-09-16): 全停后可见窗口 0;start.bat 2.3 s 返回;启动后可见窗口 0,6 个服务端口全开, 7 s 内 /healthz 就绪。VBScript 移除后复测(2026-09-17,就在这一版源码上): ① 全新副本 install.ps1 rc=0(172 s),安装后目录里生成 启动观澜.lnk(1,683 B); ② pythonw.exe scripts\guanlan_start_hidden.py --selftest → {"console_hwnd": 0, "windowless": true}; ③ 全停后按快捷方式的方式启动(pythonw.exe scripts\guanlan_start_hidden.py):命令立即返回, logs/start_hidden.log 记下 窗口状态: 无控制台窗口 (= 无窗口启动成立),7 s 内 /healthz 就绪, /·/detail/·/ops·/ops/recalc 全部 200; ④ 自动化核验用 install.ps1 -NoDesktopShortcut,避免在桌面留一个指向临时副本的死链。


5. 页面取数与"产物即时进页面"

5.1 产品数据的读取口径

服务 读法 产物改了要不要重启
detail :18033(scripts/windscada_serve.py) 首次请求把产物读进内存 _CACHE 不需要:每次取数前比产物指纹(products_stamp() = 相关产物文件的 (路径, mtime_ns, size) 摘要,TTL 2 s),指纹变了自动重载
cms :18020(src/windcms/serve.py) 报告/页面/文件每请求现读 不需要
cms 的谱元数据(src/windcms/data.py::spectra_meta) 进程内缓存,缓存键含各窗 index/spectra_meta 指纹 不需要(新摄入/重算后自动失效)
门户(网关 :28084) portal.html 按 (mtime, size) 缓存改写结果 不需要(重建门户即生效)
网关的 /ops 页面模块 guanlan_ops 模块级缓存 需要重启网关(改了这个文件才需要)

实测证据: 只改一个产物文件的 mtime,logs/detail.log 立刻出现 [reload] 产物指纹变化 b15e2652… → f0b457f7…, 重载,页面随即用新数(无需重启)。 另有显式入口 GET /detail/api/reload(清缓存并回显新旧指纹)。

5.2 门户菜单

顶部菜单: 总览 | 系统架构 | 方法 | 经验发现 | 案例·液压 | 振动·CMS | 仿真与回放 | 交付文档 | 系统状态 | 数据重算 | 登录 (2026-09-16 用户令: 「数据重算」落在「交付文档」与「登录」之间)。

  • 门外壳正本 release/portal_src/shell.html → 装配 release/portal.html(scripts/portal_build.py)。 改外壳后流程: --check(看漂移)→ --rebaseline(有意改动后重设基准)→ 装配 → --check 应"全部一致 ✔"。
  • #recalc 段落用 iframe 懒加载 http://127.0.0.1:28084/ops/recalc(同源;切到本页才挂 src, 否则隐藏时它还会每 2 s 轮询 /ops/api/state)。
  • /ops/recalc 是 /ops 的内嵌版(只保留 重算 + 产物 + 动作进度;服务卡与页头由门户承担)。 走独立路由而不是 ?embed=:网关转给 guanlan_ops.handle() 的是 u.path,查询串会被丢掉 —— 用查询串 会变成"看起来支持、实际不生效"的静默坑。原 /ops 整页保留不变。

6. 输入 ↔ 产物呼应 与 重算

6.1 呼应关系(机器可校验)

python scripts/inventory_products.py --check 逐类输入算跨度/条数,与它喂出的产物对拍(退出码 5 = 有问题)。 2026-09-16 实测呼应正常:

输入类 输入跨度 件数 对拍产物 产物跨度/条数
scada_10min 2025-01-01 ~ 2026-07-07 38 temp_monthly.parquet / loss_monthly.parquet / powercurve_bins.parquet 2025-01 ~ 2026-07 / 19494 · 3729 行
故障报警 2025 ~ 2026(文件名年粒度) 16 alarms.parquet 2025-01-01 ~ 2026-07-15 / 39211 行
风机故障记录 2021 ~ 2026(年粒度) 135 workorders.parquet 2020-01-03 ~ 2026-07-08 / 5876 行
油样报告 2024-11-19 ~ 2025-08-29 404 oil_samples_index.parquet 同跨度 / 404 行
windcms(CMS 原始导出) 2026-03 ~ 2026-04 25693 m5_cms_tcm/windows/w0316/index.parquet 2026-03-16 ~ 2026-04-21 / 2,066,686 行

★ 年粒度输入(报警/工单的"年度汇总表")不参与"产物落后"判定 —— 用文件名年份推出来的跨度天然是年粒度, 拿它当"输入到 2026-12"会造出假缺口(本脚本第一版就这么误报过 2 条)。

6.2 重算的两种运行状态(都实测过)

状态 入口 说明
系统运行中 门户「数据重算」或 /ops 的「执行重算」按钮;等价命令行 python scripts/rebuild_all.py 动作经 _ops_launch.py 二次启动(不挂网关的父子树,避免 taskkill /T 把自己杀掉);跑完不需要重启就能在页面看到新数(§5.1 指纹重载)。重算期间按钮全灰、并发动作被后端拒(HTTP 409)
系统未运行 同一套命令行(先 guanlan.py stop 或本就关机状态) 全部构建器都是普通 CLI,不依赖服务;跑完再双击 启动观澜 快捷方式(或 start.bat)起来,页面直接读新产物。实测: 全停后跑 rebuild_from_raw.py rc=0、inventory_products.py --check rc=0

一键顺序(rebuild_all.py,14 步): ① 放数据(--src 才跑)/② 三门台账/③ SCADA 侧 10 构建器/④ 月度派生件/ ④b 振动侧摄入/⑤ 补齐随包件(★2026-09-16 用户令"清除产物不留备份"之后, 随包件不再有 _products_off/ 暂存区 ⇒ 该步固定返回 6 跳过并说明; 要补齐须显式给交付包 products_restore_missing.py --stash <交付包.zip>, 不再打断整条链)/ ⑥ 重启组件服务/ ⑦ 本体六步/⑧ 本体审计(+可选等价验收)。


7. 缺口与边界(如实列)

缺口 影响的产物 依据/证据 补齐判据
振动六层链四步脚本未随包(rudong_tcm_oem_scan / rudong_line_energy_share / rudong_model_run / rudong_fusion_run) oem_frequency_scan、gear_freq_scan、blade_1p_*、model_run_l6.parquet、fusion_38.csv 等 outputs/<场>/m5_cms_tcm/vib_raw_manifest.json 的 missing_chain 给脚本或口径;源件已在 data/raw/<场>/windcms/
厂商月度报告 12 份 PDF 是纯扫描件 「CMS 振动评估报告」的厂商侧 pypdf 实测 12 页 12 图、extract_text() 长度 0 现场给电子件(docx/xlsx),或上 OCR(本包不装)
handoff_vibration_v2.json / component_history.json 是随包快照 融合面判级、/cms/ 该件含人工裁决/校准更新,不是测量数据的函数 现场给正本,放 data/raw/<场>/m5_cms_tcm/
随包件"暂存区"按设计不再存在(2026-09-16 用户令"清除产物不留备份") 第⑤步"补齐随包件"固定返回 6 跳过 旧设计把清掉的产物挪到 _products_off/ 以便还原;现口径 = 真删除 要补回"包内没有生成端"的随包件: python scripts/products_restore_missing.py --stash <交付包.zip>(从交付件按需补齐, 不在安装目录里留备份)。products_state.py --off 需显式 --yes;--on 已移除
台账等价验收基线 outputs/<场>/windscada/_pre_rebuild_20260911/ 曾缺失 第④步与 rebuild_from_raw.py --verify 的逐值比对(缺基线时 ④ 返回 5) 该目录在打包时被"清除产物"挪进了暂存区, 而暂存区随后被清掉 ⇒ 机器上无标准答案 2026-09-16 已重建: 从当天 dist 包里取 4 件(alarms/workorders/oil_samples_index/temp_monthly)+ _来源说明.json 标明来历。★链已加固: ④ 容忍 rc=5 —— 缺基线只跳过"等价验收", 不再打断整条重算链。注意: 「清除产物」会把这个目录一并删掉(它在 windscada/ 里), 届时需按同样办法重建
油样 2026-07 批 102 行(华标合并报告) 数据层「油液化验」 源件 BG-2026-07-YP013 ….pdf 不在现场包 补那份 PDF 后 rebuild_from_raw.py --verify 无人工项
无生成端的组级产物(pc_monthly_bins / duty_monthly / thermal_monthly / sector_power / yaw_* / genbearing_monthly / mblub_monthly / pitch_daily 等) 趋势件、热链、扇区、偏航/润滑面 全库只有读取方、0 处写入方 研发补口径(照 temp_monthly 的办法反推 + 逐值验证)
configs/farms/*.yaml 无代码读者;available() 只认 *.json 多场部署 src/windscada/config.py::available() 二选一(见 §3.3)
门户/交付件引用的产物树 不在本包:cleaned/、gearbox_life/、yaw_verify/、structured/ 传动链回放面板、四系统判据面板、控制律仪表台、如东取数单 scripts/pages_audit.py 报 19 条「引用悬空(已知)」;本包在位的是 outputs/<场>/{windscada,m5_cms_tcm,sop,ontology,paradigm_r1,pitch,guanlan,tcm_compatible_replay,windcms} 六层链/结构化产物随包,或把页面里的引用改到本包在位路径(需研发确认是同一件东西;不改交付件正文)
2 件数据派生页面没有生成端:release/如东/全场状态一览_脱敏_20260825.html(1.5 MB 场站状态快照)、release/如东/如东取数单_2026-08-21.html 交付文档页里这两个子页 全仓搜不到生成端;页面里也没有 source/指纹 ⇒ 无法做陈旧检测(§10) 补生成端并写成带 source_sha256 的产物(推荐);或明确降级为 frozen-delivery 并接受它会过期(需用户裁定)
3 件内嵌快照缺指纹:标准面板、覆盖度 ch0、如东取数单 门户内嵌页 只有「生成 2026-09-05 16:28」这类时间戳;覆盖度页的源 e1_base.parquet 不在本包 生成时把 source + source_sha256 写进页面(机制已就绪,见 §10 的 data-derived 规则)

8. 变更记录

日期 变更
2026-09-16 本文建立(用户令 1): 产物全景(自动生成清单)+ 路径唯一真源与本次统一的 8 处 + 未统一项如实列表 + 进程无窗口口径 src/proc.py + 产物指纹热重载 + 输入↔产物呼应 + 重算两种状态 + 门户「数据重算」
2026-09-16 用户令 2/3/4/5 的落地: inventory_products.py(清点+呼应校验,--check 出码);⑤步不再打断重算链(StashMissing rc=6);start_hidden.vbs + start.bat 默认无窗口;门户菜单新增「数据重算」+ /ops/recalc 内嵌版;portal_build.py --rebaseline
2026-09-16 振动侧接入(详见 docs/振动数据接入_v0.1.md): data/raw/<场>/{windcms,m5_cms_tcm} 两类源件、rudong_tcm_index.py/rudong_tcm_spectra.py/vib_raw_build.py/vib_reports_build.py、窗 w0316
2026-09-16 用户令"清除产物不留备份": products_state.py --off --yes 改为真删除(不再产生 _products_off*/)、--on 与门户「恢复产物」按钮移除;随包件的唯一来源改为交付包 zip(products_restore_missing.py --stash <交付包.zip>);derived_manifest.prune() 清掉陈旧自登记(raw-derived 台账 3444 → 1740 件,回到真实)
2026-09-16 用户令"打包不含 输入数据/产物/日志" → 交付包 v0.4.0(见 §9): pack_dist.py 增 --no-products、VERSION='0.4.0'、dist-manifest.json 记 no_data/no_products/no_logs 与逐条排除理由;guanlan.py check 读该清单,产物缺失显示 [--] 待重算 而非 FAIL
2026-09-16 现场实炸一次: 目标机 D:\产品\app 双击 start.bat 报"无法找到脚本文件 start_hidden.vbs" —— 打包器 INCLUDE_FILES 是手工清单,新加的根目录文件(无窗口启动的 .vbs)忘了写进去。修法不是"再补一行",而是从引用反推:新增 src/entry_refs.py,凡入口脚本(.bat/.ps1/.sh)引用的、本机真实存在的文件,打包时自动补入、打包后对着包内条目复核(缺一个就删掉这个包并 rc=1)、开箱验证(--verify 第①b 步)与装机自检(guanlan.py check 一行)各再查一遍。同时修正 size_of() 把 __pycache__/*.pyc 也数进"预演件数"的虚报(src 少报 129 件)
2026-09-16 用户令"把 VBScript 替换掉"(见 §4.2): 删除 start_hidden.vbs;无窗口启动改为 pythonw.exe + scripts/guanlan_start_hidden.py(不经过 wscript/WSH —— 服务器上 WSH 常被组策略禁用/未安装);install.ps1 安装时用 PowerShell 原生 COM 建 启动观澜.lnk(根目录 + 桌面,带 -NoDesktopShortcut 供自动化核验);start.bat 默认改为 start "" /min pythonw.exe …(.bat 会闪一次,快捷方式才是零窗口);guanlan_start_hidden.py --selftest 用 GetConsoleWindow()==0 自证无窗口;打包器新增 *.lnk(安装时才产生、内含绝对路径)与顶层 *.zip 排除(后者原先只写在文档里没实现)
2026-09-17 入口脚本的换行/编码事故两连 —— 这类问题都不会在开发机上暴露,只在目标机双击时炸:
① install.ps1 的 UTF-8 BOM 被编辑工具吃掉(我在规范化之后又改了一次文件)⇒ PS 5.1 按 GBK 解码 ⇒ 中文乱码 + 级联 ParserError,目标机 install 1 秒即退出。开箱验证当场逮到(install 退出码 1, 耗时 1s + The '<' operator is reserved for future use)。
② install.sh 从写出来那天起就是 CRLF(git HEAD 的 blob 就是 CRLF,不是某次编辑造成的)⇒ POSIX 语义下每个词尾粘 \r:set -e 变非法选项、RT="" 变 RT="\r" 让后续 -n "$RT" 判断翻面 ⇒ 此前所有交付包的 Linux/macOS 安装脚本都是坏的。
加固:src/entry_refs.py::encoding_problems() 成文守则(.ps1 = UTF-8 BOM + CRLF;.bat = CRLF 无 BOM;.sh = LF),在打包、开箱验证第①b 步、装机自检三处强制执行(违反就不出包 / 直接判 FAIL);guanlan.py check 增一行;新增 .gitattributes 把 checkout 也钉死(*.bat/*.ps1 eol=crlf、*.sh eol=lf)。变异测试:去掉 BOM / 改 CRLF / .bat 存成 LF 三种改法都被逐条报出
2026-09-17 打包后对着 zip 条目逐件复核(3,733 件全部与工作树逐字节相同)+ 开箱验证:解压 3,734 件 → ①b 闭合与编码守则通过 → 离线安装 rc=0(172 s)→ ②b 快捷方式在位 + console_hwnd=0 无窗口成立 + 包内无 .vbs → 页面 4/5(/ 与主包同字节;/cms/ 503 属无产物预期)
2026-09-17 用户令 1「页面是否属于产物」的落地(见 §10):新建登记表 configs/portal_pages.yaml(20 个页面/子页 + 五类 kind + 逐条判定依据)与检查器 scripts/pages_audit.py(五条机器规则;陈旧检测 rc=7:内嵌快照的 source_sha256 与当前产物不符即报"数据变了页面没变");guanlan.py check 增一行;rebuild_all.py 增 ⑧b「重装门户(把新产物灌进门户结论段)」与 ⑧c「页面归口审计」;文档增 §10(含用户问的三页逐页判定)。结论:#findings 静态、非产物;#sim 本体是图纸派生的冻结资料包、其中"实际运行回放"面板属产物;#documents 里治理清单/报告是冻结交付件,而脱敏状态一览与取数单属产物且缺生成端与溯源(已入 §7 缺口表)
2026-09-17 用户令 2「统一配置与日志」的落地(见 §11):① 配置——src/paths.py 增唯一取用口 P.config()/P.config_dir()/P.farm_config(),9 个模块不再手拼 configs 路径;新增登记表 configs/registry.yaml 与检查器 scripts/config_audit.py(R1–R8);修掉三处真问题:10 个场定义 YAML 因 available() 只 glob *.json 而"配了看不见"(并查明它们其实是另一条链的机型/物理约束 profile,逐件登记在册)、坏模板 _模板.json.example(GBK 乱码 + 写死 /Volumes/WINDDATA)重写为 _模板.yaml.example、sop/wrapup.py 拼了不存在的 configs/<场>/ 路径;删除 serve.json.bak-bomfix;5 处"代码引用但包里没有"的配置登进 known_missing 并写明影响。② 日志——新增 src/logfile.py(目录/命名/行格式唯一口径 + prefix_stdout() 流包装 + 动作日志保留 20 份/30 天 + logs/build 归位口)与 scripts/log_audit.py(L1–L5);6 个服务 + 启动器 + 运维动作全部接入统一格式(viewer/sim 改走新增的 scripts/static_server.py);把 39 个躺在产物目录里的构建日志搬到 logs/build/ 并同步重算产物台账(2309→2270 件,呼应校验 仍 rc=0)、22 份历史动作日志与 8 个服务日志的旧内容归档 logs/legacy/;guanlan.py check 增两行
2026-09-17 用户令 3「输入数据放置检查 + 指导」的落地(见 docs/输入数据放置指导_v0.1.md):新增 scripts/raw_data_check.py(R1–R8:顶层/子目录约定、各源类后缀与结构、TCM *_decode.json 按摄入脚本真实取法抽样(body.body[时间戳]=[{Record:…}],机组取 Record.Location)、CSV 编码表头抽样、增量三清单(新增/相同跳过/冲突)、时间空洞、体量提示、按新增源类给重算命令);place_raw_data.py 落位前自动体检并拦下冲突(--force 才覆盖,--no-check 可跳过);新增指导文档(正确目录树、逐类规则、增量语义、放完跑什么、常见错误对照表,含自动生成的体检结论块);guanlan.py check 增一行。实测:真实树 26,325 件/177.6 GB 结构合规、TCM 抽样 12/12 通过;合成增量包(同尺寸/改过/新台号各一)三清单齐全、冲突拦下 rc=6

9. 交付包组成(v0.4.0,2026-09-16 用户令:不含 输入数据 / 产物 / 日志)

打包器:scripts/pack_dist.py。默认文件名 <父目录>/guanlan-v<VERSION>_dist_<日期>.zip,可用 --out 指定。

进包 内容
src/ scripts/ guanlan.py 程序与全部构建/运维脚本(含 src/entry_refs.py 入口引用闭合检查)
configs/ resources/ reference/ 配置(含 serve.json 端口真源、场站/通道台账)
release/ 门户、仿真页、三维资产 viewer/、治理清单交付件 release/如东/(客户交付物,勿外传)
docs/ README_先读我.txt 测试须知.txt 交付文档与说明书
wheels/win_amd64/(42 件)、vendor/python/(3 平台便携运行时) 离线安装件:Windows 完全离线可装;Linux/macOS 走联网安装(要离线就把轮子放进 wheels/linux_x86_64 / wheels/macos_arm64)
install.bat/.ps1/.sh、check/start/stop.bat、requirements.txt 安装与起停入口(无 .vbs:start.bat 默认调 pythonw.exe + scripts/guanlan_start_hidden.py;install.ps1 安装时另建 启动观澜.lnk 供零窗口启动)
不进包 理由(同时写进包内 dist-manifest.json 的 excluded)
data/ 现场原始输入件;约定"原始件不随包分发"(要带用 --with-data)
outputs/ 用户令:产物不进包(--no-products)。目标机放数据后 rebuild_all.py 重算;或缺"无生成端"的随包件时用 products_restore_missing.py --stash <交付包.zip>
logs/ run/ 用户令:日志不进包(run/pids.json 里的 PID 到新机器上是无效引用)
.venv/ .git/ .github/ venv 换机必失效(安装时重建);版本库不随交付件分发
_products_off*/ 旧设计的"清除产物"暂存档(2026-09-16 起清除=真删,不再产生;老机器上若有可手工删)
*.lnk 安装时建的快捷方式:内含绝对路径,换机必失效,由目标机的 install.ps1 重建
__pycache__/ *.pyc、顶层 *.zip 解释器缓存、旧的交付压缩包(避免包中包)

开箱验证(一条命令给出"能不能装、能不能跑"的证据):python scripts/pack_dist.py --verify <zip> —— 解压到 临时目录 → ①b 入口引用闭合核对 → 离线安装 → 起服务核验 /·/detail/·/cms/·/ops·/healthz → 收尾清理, 要求 ≥4 个页面可用。 ★ 核验要求组件端口(18033/18020/18791/18792/64292)空闲,验证前先 guanlan.py stop,否则整段核验被跳过(打印 [!])。

9.1 入口引用闭合(src/entry_refs.py,2026-09-16 实炸后加)

现场反馈:目标机 D:\产品\app 双击 start.bat → 无法找到脚本文件 "D:\产品\app\start_hidden.vbs"。 根因是打包器的 INCLUDE_FILES 是手工清单,新加根目录文件时漏了一项 —— 这类清单必然漏,所以改成从引用反推:

凡是入口脚本 (.bat/.ps1/.sh) 里出现的一个路径, 且该路径在本机源码树里真实存在 → 它必须在包里
(本机不存在的 `.venv\Scripts\pythonw.exe` / 系统 exe / `%~dp0` 前缀残片, 按"装机后才有的东西"忽略)

四处落地:打包时自动补入(plan() 打印 [+] 自动补入 (被入口脚本引用))→ 打包后对着 zip 条目复核(缺一个就删包 + rc=1,不留"看起来能用"的坏包)→ --verify 第①b 步在解压目录再查 → guanlan.py check 输出一行 入口脚本引用闭合 (24 条: start.bat→guanlan_start_hidden.py 等),装机后 check.bat 也能看出来。

★ 那个 .vbs 已按用户令删除(§4.2),但这份守卫保留:它守的是"入口引用的文件必须齐全"这条性质,与用什么语言实现无关 —— 现在它盯的是 start.bat/install.ps1 → scripts\guanlan_start_hidden.py 这类引用(这次替换正是它自动发现的:去掉 .vbs 后入口列表变为 6 个、引用 24 条,全部在位)。


10. 页面归口(哪些页面是产物、该不该随数据变)

用户问的三页,结论(依据见下表与 configs/portal_pages.yaml):

页面 该不该随输入数据变 算不算产物 现状与处置
#findings 经验发现 不该 不是 外壳里的静态叙述(2,322 B,0 iframe/fetch/产物引用),内容是公司级方法论而非本场站数据的函数 ⇒ 留在受管外壳。若将来要放本场站计数,必须改成 data-derived 并登记 source
#sim 仿真与回放 页面本体不该 本体不是 叙述+6 个链接:仿真台是图纸/机型派生的冻结资料包(release/如东SWT40_控制律仿真台_20260906.zip,由 release/sim_sys_server.py 从 zip 现读),三维工作台是图纸派生。但其中"实际运行回放"面板(门户内嵌 tpl-如东传动链实际运行诊断_单文件版)把 outputs/<场>/cleaned/turbine_1min.parquet 连 source_sha256 一起烘进了页面 ⇒ 那一件属于产物
#documents 交付文档 交付件按版本变,不随数据自动变 治理清单/报告不是(冻结交付件);取数单与脱敏状态一览是 iframe 指向的治理清单分册自带版本号+日期(v1.2_2026-09-01 / v4.5_2026-09-06)⇒ 冻结交付件。但 全场状态一览_脱敏_20260825.html(1.5 MB 场站状态快照)与 如东取数单(明列产物路径)是数据派生的 ⇒ 属产物,且现在既无生成端也无溯源

五种 kind 与机器规则(登记表头部有完整说明):

kind 该随数据变? 机器查什么
static 否 正文不得引用产物(引用了说明分类错了)
live 是(永远一致) iframe/链接的端口必须在 configs/serve.json 的已知服务里
data-citing 否(正文不变) 被引用的产物路径必须存在(悬空引用 = 挂羊头)
data-derived 是 必须有 source + (source_sha256 或生成时间);有 sha 就跟当前产物逐字节比对 ⇒ 陈旧检测
frozen-delivery 否(按交付版本变) 文件名或正文必须能读到版本号 + 日期

"纳入产物管理"落到本系统的四件事:① 登记(configs/portal_pages.yaml:kind/来源/生成端/依据); ② 溯源(source + source_sha256 或生成时间);③ 陈旧检测(scripts/pages_audit.py,rc=7 = 数据换了页面没换); ④ 进链(rebuild_all.py ⑧b 重装门户把新产物灌进结论段、⑧c 页面归口审计;guanlan.py check 一行)。 实时读产物的页面(/detail/、/cms/、/ops)不需要快照,靠 §5.1 的产物指纹热重载即时更新。

当前如实状态:19 条已知缺口(登记表里逐条写了理由,不装作通过)—— 都是"页面引用了本包没有的产物树" (cleaned/、gearbox_life/、yaw_verify/、structured/,属未随包的六层链/结构化产物线,见 §7), 因此这些页面拿不到当前值做比对;另有 2 件数据派生页面没有生成端(脱敏状态一览、取数单), 以及 3 件内嵌快照缺指纹只能按生成时间判断(标准面板、覆盖度 ch0、取数单)。这三类都记在 §7 缺口表里。

10.1 门户页面归口(自动生成,勿手改)

判据与说明见 configs/portal_pages.yaml 头部;检查器 scripts/pages_audit.py。

页面/子页 kind 随输入数据变? 依据(为什么这么判)
index 总览 static 否 外壳 #index 段 2,519 B: 0 iframe / 0 fetch / 0 产物引用; 正文是产品承诺与入口导航
architecture 系统架构 static 否 外壳 #architecture 段 3,037 B: 无数据引用; 描述分层架构
method 方法 static 否 外壳 #method 段 2,959 B: 无数据引用; 描述方法论
findings 经验发现 static 否 ★用户问的三页之一。外壳 #findings 段 2,322 B: 0 iframe / 0 fetch / 0 产物引用; 正文是公司级经验叙述(59 项内部检查 → 8 条公开教训, 60+ 个风场), 不是本场站数据的函数 ⇒ 不该随输入数据变, 也不是产物。若将来要放本场站计数, 必须改成 data-derived 并登记 source
case_hydraulic 案例·液压 static 否 外壳 #case_hydraulic 段 6,187 B: 无数据引用; 案例叙述
cms 振动·CMS live 是 外壳 #cms 段里 1 个 iframe + 2 个链接指向组件 :18020 (src/windcms/serve.py), 组件按需读产物 ⇒ 页面本身不存快照, 数据变了刷新即变
recalc 数据重算 live 是 外壳 #recalc 段只有 1 个到 /ops 的链接(网关提供, 后端即真实状态)
login 登录 live 是 外壳 #login 段链接到工作台 :18033/v2
admin 系统状态 live 是 外壳 #admin 段由外壳 JS 拉网关 /healthz 渲染(段内无静态数字)
sim 仿真与回放 static 否 ★用户问的三页之一。外壳 #sim 段 50,767 B 是静态叙述(机理/图纸说明), 段内 0 iframe / 0 fetch; 6 个链接指向 :18792 仿真台页与 :64292 三维工作台。仿真台的输入是图纸/机型参数(configs/machine_packs), 不是 data/raw ⇒ 页面本体不该随输入数据变
  └ 控制律仿真台资料包 (5 页) frozen-delivery 否 release/sim_sys_server.py 直接从 zip 里读这 5 页 ⇒ 冻结资料包(日期在包名里); 内容为图纸/机理仿真, 不随输入数据变
  └ 三维拆装工作台 static 否 release/viewer/** 由图纸/三维模型构建(build-*.py + esbuild), 与 data/raw 无关; rev=hub-review-v1 是评审版本号
  └ 控制律仪表台面板 (门户内嵌) data-citing 否 面板正文是固定的判据/仪表说明, 只把产物当脚注引用('判据与证伪条件全量见 outputs/rudong/yaw_verify/findings.json') ⇒ 正文不随数据变, 但被引用的产物必须在位
  └ 四系统判据面板 (门户内嵌, 4 份) data-citing 否 同仪表台: 静态正文 + findings 引用脚注
documents 交付文档 static 否 ★用户问的三页之一。外壳 #documents 段 5,690 B 是导航页(0 fetch), 1 个 iframe 指向网关下发的治理清单 HTML(release/如东/**)。段内不含数据快照
  └ 治理清单交付包 (分册 + 全册 + 正式报告) frozen-delivery 否 文件名自带版本号与日期(如 如东_液压系统治理清单_v1.2_2026-09-01.html / 全册_v4.5_2026-09-06.html) ⇒ 客户交付件, 随交付版本变; 内容虽由分析产出, 但冻结发布, 不该随输入数据自动改
  └ 全场状态一览 (脱敏) data-derived 是 ★这是问题所在: 1.5 MB 的场站状态快照(脱敏版), 内容是实际运行状态的函数 ⇒ 属于产物, 但页面里没有任何指纹/生成时间/来源登记, 全仓也搜不到生成端 ⇒ 目前游离在产物管理之外(无溯源、无陈旧检测、无重算入口)
  └ 如东取数单 data-derived 是 取数单是「要哪些数据」的清单页, 明列产物路径 ⇒ 随产物结构变; 现在只有日期没有指纹, 也没有生成端随包
embed_replay_chain 传动链实际运行回放 (门户内嵌单文件版) data-derived 是 内嵌 window.RUDONG_ACTUAL_REPLAY 带 source=outputs/rudong/cleaned/turbine_1min.parquet 与 source_sha256 ⇒ 明明白白的产物烘入快照; 已可做陈旧检测(产物在位时)
embed_standard_panel 标准面板 (门户内嵌) data-derived 是 正文带 '生成 2026-09-05 16:28' 与产物引用 ⇒ 某次生成的数据快照; 缺 source_sha ⇒ 只能按生成时间+引用在位判断
embed_coverage_ch0 覆盖度报告 ch0 (门户内嵌) data-derived 是 正文写 '生成 2026-09-05 16:28 · 源: e1_base.parquet e3b531b8ed">@e3b531b8ed (10-min, 2,973,638 格, 38 台)' ⇒ 数据派生; 但 e1_base.parquet 不在本包 ⇒ 溯源链断在包外
embed_u6_sim U6 变桨液压仿真台 (门户内嵌) static 否 图纸派生的物理回路仿真台面板; 无内嵌数据/指纹/产物引用
embed_u6_health U6 液压公共站健康报告 (客户版, 门户内嵌) frozen-delivery 否 984 KB 客户交付报告; 文件名标 '客户版'; 无数据指纹 ⇒ 按交付件冻结(缺版本号/日期, 见 audit 提示)
embed_gearbox_report 整机综合诊断与风险评估 (主轴冲击深挖 V2.1, 门户内嵌) frozen-delivery 否 3.7 MB 综合诊断报告, 文件名带修订版号 V2.1; 报告类交付件按版本发布
embed_reports_4 分系统评估报告 (主轴承/齿轮箱/发电机/变桨, 门户内嵌 4 份) frozen-delivery 否 4 份系统评估报告(0.7–3.4 MB), 无内嵌数据/指纹; 属交付报告(缺版本号/日期登记)
embed_governance_12 治理清单分册 (门户内嵌 12 份) frozen-delivery 否 12 份治理分册(0–11), 与 release/如东 下的分册同源; 冻结交付件
embed_sc1_local SC1 本地占位 (门户内嵌) static 否 0.4 KB 占位面板

11. 配置与日志的统一口径(2026-09-17 用户令 2)

11.1 配置目录与配置文件

唯一取用口:代码只许用 src/paths.py 的 P.config(...) / P.config_dir(...) / P.farm_config(...), 不许再手拼 "configs" / …(统一前实测 9 个模块各拼各的:llm_gate、windscada/terms、sop/analysis_lock、 sop/scenarios、sop/wrapup、guanlan.py、guanlan_ops、page_fingerprint、check_transferable, 另有 audit_chinese_terms.py 要试三个位置才找得到 terms 库)。

位置 放什么 谁读
configs/serve.json 运行期单件:端口/路径真源(install 脚本写) guanlan.py、网关、运维控制台、指纹、移植检查
configs/models.json 运行期单件:本机模型档与 pull 命令 guanlan.py check、src/ontology/llm_gate.py
configs/portal_pages.yaml 运行期单件:门户页面归口登记表(§10) scripts/pages_audit.py
configs/registry.yaml 配置登记表:每个域是什么、谁读、缺哪些 scripts/config_audit.py
configs/canonical/(71 别名 + 3 决策) 通道/测点 canonical 字典与别名字典 src/sop/contract_gate.py、discriminators.py;别名表由未随包的分析链消费
configs/contracts/(74) 机型契约(判据参数)与机型-场站对照表 scripts/sim_hub/wake_*.py;(历史)分析链
configs/farms/(15) 场定义(YAML 为准,历史 .json 兼容) src/windscada/config.py
configs/terms/(3) 显示层术语映射、OEM 术语基准、行话规则 src/windscada/terms.py、ui/build.py、audit_chinese_terms.py

本次修掉的三处真问题:

  1. 场定义"配了看不见":configs/farms/ 下 10 个 YAML 里 0 个能被 available() 列出(它只 glob *.json), farm('FUSHAN') 直接报"未知场"。现在 yaml/json 都认、约定 YAML,并且——更重要的——查清了那 10 个 YAML 其实不是场定义而是另一条链的"机型/场站物理约束 profile"(顶层 meta + physical_constraints), 于是 available() 只列能加载的场、foreign_farm_files() 把 profile 逐件报出来(登记表里已声明), 并把 farm() 的报错写得能看懂("该文件存在但不是场定义…")。
  2. 配置模板是坏的:configs/farms/_模板.json.example 是 GBK 乱码("绀轰緥椋庣數鍦?")且写死 /Volumes/WINDDATA, 与实物格式(YAML)相反。已重写为 configs/farms/_模板.yaml.example(UTF-8、YAML、<场站名称> 占位), 并同步 docs/数据目录结构与落位约定_v0.2.md。
  3. 散件与悬空引用:删掉 configs/serve.json.bak-bomfix(历史备份垃圾);sop/wrapup.py 原来拼的是 configs/<场>/value_assumptions.yaml(那个目录根本不存在)→ 改为 configs/value_assumptions/<场>.yaml。 另有 5 处代码引用的配置不在包里(analysis_lock*、scenario_registry、discriminator_registry、 analysis_modules、value_assumptions/<场>),逐条登进 registry.yaml::known_missing 并写明影响 (例如场景注册表缺失 ⇒ SOP 场景解析不可用,但本包无调用方,故不影响页面)。

机器规则(scripts/config_audit.py):R1 顶层只许放登记过的运行期单件配置;R2 无备份垃圾(*.bak* 等); R3 命名(空格判错、中文/大写只提示);R4 内容不得含本机绝对路径——区分字段:说明性字段/注释里记的 "当时数据在哪台机器"算溯源信息(提示),被读取的字段里出现 C:\、/Volumes/… 才是失配(判错); R5 代码引用的配置必须存在(未登记的按悬空引用判错,登记过的记 i);R6 不许手拼 configs 路径; R7 每个域必须声明 consumers 或写明"无人读";R8 configs/farms/ 每个文件要么是能加载的场定义, 要么在登记表里声明为 profile/CSV 数据表。现状:171 件配置、5 个域、1 个场定义、10 件机型 profile、 0 不一致(14 条已知缺口、7 条提示)。

11.2 日志目录、命名与内容格式

统一前(实测,不是推测):logs/ 顶层堆了 34 个文件,其中 22 个是历史动作日志(无保留策略); 39 个构建日志躺在产物目录里(outputs/<场>/windscada/*.log 等),还被 _provenance.json 登记成 shipped 随包件——产物台账里混着日志;detail.log 首行是 b'windscada serve :18033\r\n'(CRLF), cms.log 首行是一条历史 SyntaxWarning;行里没有时间戳也没有级别,import logging 的文件数 = 0。

位置 放什么 命名
logs/<组件>.log 长驻服务与启动器(组件 = serve.json 的键 + gateway/serve/start_hidden) gateway.log、detail.log、cms.log、sim.log、sim_sys.log、viewer.log、serve.log、start_hidden.log
logs/ops/ 运维动作(自动保留最近 20 份 / 30 天,写前清理) <动作>_<YYYYmmdd-HHMMSS>.log
logs/audit/ 机器审计流水(一行一条 JSON) llm_audit.jsonl、cloud_qa.jsonl、terms_audit.json
logs/build/<场>/… 构建/摄入脚本的日志(按产物子路径归档) 沿用原相对路径
logs/legacy/ 统一格式之前的日志(启动器与动作的历史留档,可随时删) 原名 + 时间戳

行格式(每一行都要满足,正则 src/logfile.py::LINE_RE): YYYY-MM-DD HH:MM:SS LEVEL 组件 消息,级别 ∈ DEBUG/INFO/WARN/ERROR;UTF-8 无 BOM、行尾 LF、无 ANSI 颜色码。

怎么保证 6 个服务都合规:不要求各服务改写自己的 print —— 入口处调一次 src/logfile.py::prefix_stdout('<组件>'),之后 stdout/stderr 的每一行自动带前缀(含把 stderr 与含 error/traceback/失败 字样的行标成 ERROR)。viewer/sim 原先是 python -m http.server(Common Log 格式,挂不上前缀),改用新增的 scripts/static_server.py(同样带统一格式)。运维动作日志改由 logfile.action_log() 生成并带保留策略;本体模型闸与云问答审计改落 logs/audit/。

本次的实际搬迁(都做了台账/留档,不是悄悄删):

  • 39 个构建日志 outputs/**/*.log → logs/build/<场>/<原相对路径>;同时把 _provenance.json 里这 39 条 删掉并重算计数(2309 → 2270 件;shipped 569 → 530;raw-derived 1740 不变),台账里写明"因日志归位移出"。 搬迁后 inventory_products.py --check 仍 rc=0 呼应正常。
  • 22 份历史动作日志(ops_*.log,2026-09-12 那批)与 _proc_reg.log → logs/legacy/;
  • 8 个服务日志里"统一格式之前"的内容 → logs/legacy/<名>.<时间戳>.log,现场文件只保留合规行 (服务以 append 持句柄,原地截断不影响继续写);logs/llm_audit.jsonl → logs/audit/。

机器规则(scripts/log_audit.py):L1 运行日志只在 logs/ 下(白名单:产物附件 analyze_stdout.log、 第三方工具缓存 .npm-cache/、.qa-profile/、node_modules/,各写了理由);L2 命名;L3 按末尾 200 行校验 行格式(启用前的旧行不算数);L4 行尾/颜色(历史行只提示);L5 logs/ops/ 保留策略 + logs/audit/*.jsonl 每行必须是合法 JSON。现状:8 服务日志 + 39 构建日志 + 1 审计流水,0 不一致(39 条"历史构建日志"提示, 写它们的人不在本包);guanlan.py check 各报一行。