# 观澜 · 如东样板 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/
.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/.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) | | **清过产物 + 包不随产物 ⇒ "无生成端"的那批产物不可恢复** | `pitch_daily`/`pc_monthly_bins`/`duty_monthly`/`thermal_monthly`/`sector_power`/`yaw_*`/`genbearing_monthly`/`mblub_monthly`/`structure.parquet`/`watch_channels_monthly`、振动 `handoff_vibration_v2.json`/`component_history.json`、CMS 页面件等 | 2026-09-17 实测:13:05 执行「清除产物」(真删、不留备份),随后重算产出 1,727 件(**有生成端**的那批,锚点 39,211/5,876/404/19,494 全部对齐、呼应校验正常),但这批无生成端的件**没有任何来源可补** —— 交付包按用户令不含产物,本机也没有旧包 | 需要这批件时**打一个带产物的包**(`scripts/pack_dist.py --with-products`)留档,再用 `products_restore_missing.py --stash <该包.zip>` 补回;或由研发补生成端(`docs/重算缺口与补件清单_v0.1.md`) | | 重算链在**缺这批件**时曾整步中断 | ⑦ 本体层 | 2026-09-17 实测:`pitch_daily` 缺 → `pitch.registry()` 抛 `FileNotFoundError`;`handoff` 缺 → `fusion.load_handoff()` 按设计抛错("不可静默降级")⇒ `populate` rc=1,⑧ 全不执行 | **已修**:`pitch.load/registry`、`taxonomy.system_matrix`(新增 `_cell()` 逐格兜底 + `_缺件说明` 写进结果)、`fusion.load_handoff/fusion_table` 改为**响亮降级**(空结构 + 缺件说明,判级不造数);实测缺这两件时 ⑦ 全部步骤跑完、`ontology.audit` **rc=0**(对象 9,613,含缺件说明) | | 门户/交付件引用的产物树 **不在本包**:`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}` | 六层链/结构化产物随包,**或**把页面里的引用改到本包在位路径(需研发确认是同一件东西;不改交付件正文)。★**2026-09-17 用户裁决「确定无用的才删」→ 本项不删**:四棵目录逐个 `Test-Path` = False,即引用**确实**悬空,记账是实话;被引的页面又都是客户手里的冻结交付件/门户内嵌件(删了门户裂口、交付缺件),而登记表的 `known_gap` 一旦删掉,这些引用就变成"没人管的悬空引用"(`pages_audit` 反而会按 rc=5 报出来)。⇒ **保留记账,待该产物线随包后复检**;`configs/portal_pages.yaml` 的 5 条 `known_gap.why` 已逐条写上裁决语 | | 2 件数据派生页面**没有生成端**:`release/如东/全场状态一览_脱敏_20260825.html`(1.5 MB 场站状态快照)、`release/如东/如东取数单_2026-08-21.html` | 交付文档页里这两个子页 | 全仓搜不到生成端;页面里也没有 `source`/指纹 ⇒ 无法做陈旧检测(§10) | ★**2026-09-17 已裁决(用户令"确定无用的才删")**:两页都是**有真实内容的交付件**,不是无用件 ⇒ **不删**,改按**冻结交付件**管:`kind: frozen-delivery` + `version:`(与"`release/如东/**/*.html` 一律冻结交付"的统一口径对齐,消除"同一页既是 data-derived 又是 frozen-delivery"的自相矛盾),并把"性质是数据派生、无指纹无生成端、重算链不会刷新它、会随时间变旧"如实留在 `known_gap.why` 里。若将来要它们随数据变,再补生成端 + `source_sha256`(机制已就绪,见 §10 的 `data-derived` 规则) | | 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 | **现场实炸一次**: 目标机 `<安装目录>` 双击 `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 | **用户令 1「装成服务 + 安装时检查版本」的落地(见 §14)**:新增版本单一真源 `src/version.py`;安装脚本增"步骤 0"——读 `install-info.json` 比对已装版本(相同/升级/降级/老安装无记录四类提示),交互式询问是否重装(`-Force`/`--force` 跳过,非交互自动继续),装完写安装记录(version/时间/解释器/运行方式);服务化:**Windows 服务**(`scripts/win_service.py` **ctypes 直连 SCM,不依赖 pywin32**)+ **systemd 单元**(`scripts/service_main.py` + `guanlan.service`)+ 免管理员的"登录自启"备选;两平台共用工作体 `scripts/service_worker.py`(起/守护/停都走现成的 `guanlan.py serve|stop`,口径与手工一致);注册器 `scripts/service_ctl.py`(status/install/uninstall/start/stop/restart + `--dry-run` 打印逐条命令与单元全文;无权限时给三条可执行建议而不是抛 Access denied)。另:修掉网关轮询与执行器"写回退出码"的**竞态**(实测 14:58 清产物 rc=0 却被页面显示成"未拿到退出码")。本机非管理员 ⇒ 已用 `--dry-run`/`--debug --once`/直跑服务体报 1063 三条路径自证,见 §14.3 | | 2026-09-17 | **清产物 → 重算链走通(并暴露"包不随产物"的连带后果)**:用户 13:05 执行「清除产物」(真删、无备份),之后重算在 ⑦ 本体层失败 —— `pitch_daily.parquet`/`handoff_vibration_v2.json` 属"包内无生成端"的件,而交付包按用户令已不含产物 ⇒ **目标机/清空后的机器上必然缺**,代码却按"缺了就抛"处理,把整条链打断(实测 2267 s 后 rc=1)。修法见 §7/§11.4:`windcms`(空表 + 结构化无产物页,不再掐连接)、`pitch`、`taxonomy.system_matrix`(`_cell()` 逐格兜底 + `_缺件说明`)、`fusion`(响亮降级而非抛错);修完把 ⑦⑧ 跑完:对象 9,613、`ontology.audit` rc=0,SCADA 锚点 39,211/5,876/404/19,494 全对,呼应校验正常。另把开箱验证改为**不打扰运行中的实例**(见上一条),实测验证期间主实例 6/7 全程在跑、副本 5/5 页面可用。⚠ **后果如实记**:这批无生成端的产物已无可恢复来源(包不含产物、本机无旧包),要保留请用 `--with-products` 打一份带产物的包存档 | | 2026-09-17 | **现场事故二连(清产物后登录报"模块不可用")**:① `windcms` 在**无产物**时自毁 —— `data.load_scalars` 的 `pd.concat([])` 抛 `ValueError` 带走进程、`serve.py` 读不到产物页 `index.html` 直接掐断连接;改为返回空表 + 结构化「无产物」页(HTTP 200,页面写清"缺哪件/怎么补"),实测 CMS 在 `outputs/` 为空时 `GET /` → 200 / 48 KB。② `pack_dist.py --verify` 原先**必须**占用默认组件端口 ⇒ 要先停掉用户正在跑的服务(用户点「登录」正好撞上验证窗口);把 `guanlan_gateway.ROUTES` 的上游端口改为可被 `configs/serve.json` 覆盖(端口单一真源),验证改为**副本整套换端口**(网关 28700 + 组件 28800+),不再打扰主实例。见 §11.4 | | 2026-09-17 | **用户令「实现打包功能」的落地(见 §9)**:打包器默认口径改为**一条命令、零开关** —— `outputs/`(产物/输出数据)由"默认打进包"改为**默认不含**(例外用 `--with-products`,旧开关 `--no-products` 保留兼容),并新增两类排除:**运行日志兜底**(`logs/` `run/` 之外,任何位置的 `*.log`/`*.jsonl` 都不进包)与**临时文件**(`*_cache/ __pycache__ .pytest_cache .npm-cache .qa-profile`、`*.tmp *.temp *.bak* *.old *.orig *.rej *.swp *~ ~$*`、`.DS_Store Thumbs.db desktop.ini` 等);`dist-manifest.json` 增 `no_temp` 并保留逐条排除理由;新增**双击可用**的打包入口 `pack.bat`(`dry`/`verify` 子命令)与 `pack.sh`、`scripts/pack_verify_entry.py`(自动找最新 zip);`pack.bat`/`pack.sh` 纳入入口引用闭合检查。同时按用户令把 `README_先读我.txt` **改名为 `README_先读我.MD`** 并在其中新增第五节「打包」(命令/输出位置/四类排除逐条理由/打完自查);`.bat` 全为纯 ASCII(§11.3)。实测:`pack.bat dry` 报 3,430 件 / 1,032.0 MB (含缓存与日志的旧口径是 3,741 件 / 1,091 MB) | | 2026-09-17 | **现场再炸一次: `start.bat` 的注释被 cmd 当命令执行**(`'本身是控制台程序' 不是内部或外部命令`)—— 中文写在批处理里不可靠(代码页 + cmd 解析),我自己还写错过 `echo <>`(`<`/`>` 是重定向符)。处置见 §11.3:包里 `.bat` 一律纯 ASCII,中文说明移入 README/docs;`install.ps1` 增设 ASCII 名快捷方式 `start_guanlan.lnk`;编码守则新增「`.bat` 纯 ASCII / 不含 `<<` / `>` 后须是重定向目标」三条,变异测试两类都能拦下 | | 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 |:① 配置——`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 | **现场报"重算之后两处没数据"的根因与修复(见 §11.4 / §7)**:用户在页面上点「数据重算」,1367 s 后 `rc=1`。查根因**不是重算坏了**,而是三件事叠加:① 那批"包内没有生成端"的随包件在用户清产物时被真删,交付包按用户令又不含产物 ⇒ 总览「全场状态」的 iframe 正本 `outputs/<场>/windscada/index.html` 与振动融合面要的 `m5_cms_tcm/{handoff_vibration_v2,component_history,baseline_38}.json`、`tcm_index.parquet`、`windcms/` 页面件全都不在位(iframe 404 = 一块空白;振动页无数据);② 重算链第 16 步 ⑧c 页面归口审计因这些缺件判 rc=5 → **整条链中断**,连最后的 ⑧台账等价验收都没跑(⑧c 原先只容忍 rc=7),于是"重算失败"这件事本身也是被缺件放大的;③ 第 ⑤ 步"补齐随包件"实际只找 `_products_off*/` 暂存区,而含产物的包**就在仓库根目录**却没人去看 ⇒ 打印"跳过",缺件补不回来。修复:**从含产物的交付包补齐 567 件**(`products_restore_missing.py --stash guanlan-rudong-v2_0.2.0_test_win64.zip`,来源逐件记进 `_provenance.json` 的 `shipped`)⇒ `pages_audit` 由 rc=5 **转 rc=0 全部一致**,`/detail/static/index.html` 由 404 转 HTTP 200 / 94 KB,`/detail/api/vibcms` 恢复真实数据(全场 38 台判级);`products_restore_missing.stash_dir()` 增**自动找含产物的包**(本仓根与上一级 `.zip` 里挑真含 `windscada/temp_monthly.parquet` 的那个,实测自动命中 586 件);`rebuild_all` 的 ⑧c 改为容忍 rc=5 并写明它意味着什么(口径与 ④/⑤/⑥/⑧b 一致:缺的是"随包件"这一路,不是重算本身;缺件仍由 `guanlan.py check` 报 FAIL);`/static/*`、`/turbines/*` 缺件改回结构化缺件页(见 §11.4)。另修两个打包器缺陷:**`install-info.json` 被原样打进包**(自动补入把 `install.ps1` 读它当成了"入口引用的文件"⇒ 目标机首次安装会误报"已安装、版本相同",版本检查失灵)与 **`--with-products`/`--with-data` 是摆设**(`keep()` 无条件套用 `EXCLUDE_GLOBS`,实测开关打出来的包与默认包同为 3441 件;修后 3440 → 5697 件 / 4.7 GB)。两条都加了机器守卫:打包后复查"包内不得有机产物"(有则删包 rc=1) | | 2026-09-17 | **用户令「增加卸载脚本 + 打包版本管理」的落地(见 §14.5 / §15)**:① 卸载 —— 新增入口 `uninstall.bat`(纯 ASCII/CRLF)+ `uninstall.sh`(LF),共用 `scripts/guanlan_uninstall.py`;默认只拆"本程序装上去的东西"(停服务 → 注销服务 → 撤开机自启 → 删快捷方式 → 清运行态 → 删安装记录),**默认保留 data/ outputs/ logs/ .venv/ 与程序目录**,`--purge` 才删数据(再问一次)、`--all` 才删程序本体;`--dry-run` 只打印、`--selftest` 断言"计划齐全 + dry-run 无副作用"。`uninstall.bat` 先 `cd /d "%TEMP%"` 再调 Python(Windows 不允许删"活进程的当前目录"),实测 `--all` 能连安装根一起删掉。开箱验证新增第 ③b 步:在**刚装好的副本**上真跑卸载并断言"记录/运行态/快捷方式都拆掉 + 数据与产物一件不少 + 端口已释放"。顺带把 `read_installed` 改为按 `utf-8-sig` 读(带 BOM 的 install-info.json 原先会让版本检查"明明装了却报没装过")。② 版本管理 —— `src/version.py` 增版本号规则(`v<大>.<中>.<小>`:大=方案/架构/核心功能、中=非核心功能增减改、小=消缺)、`level_of()` 机器判据、`package_name()` 与 `HISTORY`;`VERSION` 升至 **2.5.0**(二代架构第 5 次功能性发布);打包器删掉自带的 `VERSION = '0.4.0'` 常量改读真源,默认包名从 `guanlan-v<版本>_dist_<日期>.zip` 改为 **`app_guanlang_v<版本号>.zip`**;新增 `scripts/version_log.py` 由 `HISTORY` 生成 `docs/版本记录.md` 并支持 `--check`(`guanlan.py check` 有一行盯"记录表与代码一致");`guanlan.py version` 打印版本与规则;`uninstall.bat/sh` 纳入入口引用闭合与编码守则 | | 2026-09-17 | **用户令「仍未裁决的 3 项,确定无用的可以删除」的裁决落地**:逐项核证后**只删确定无用的**,其余如实保留并把裁决语写进登记表与本节 —— ① **19 条引用悬空 ⇒ 不删**(四棵被引目录逐个 `Test-Path`=False,引用确实悬空;被引页面是客户冻结交付件/门户内嵌件,登记表条目一删就成"没人管的悬空引用"反而按 rc=5 报错)⇒ 5 条 `known_gap.why` 逐条补写裁决语;② **2 件无生成端的取数页面 ⇒ 不删**,改 `kind: frozen-delivery` + 补 `version:`(与"`release/如东/**/*.html` 一律冻结交付"口径对齐,消除同页两种 kind 的矛盾),残留风险(会变旧、重算不刷新)留在 `known_gap.why`;③ **`release/viewer/.npm-cache` + `.qa-profile` ⇒ 真删**(64+250 件 / 56.1 MB;非 git 跟踪、无运行时代码读取、本就被打包器排除);④ 顺带核了两处"像死代码"的:`scripts/ingest_ops_2025.py`(被 `check_portability.py` 引用)与 `configs/contracts/*.yaml`(被 `src/paths.py` 与 registry 引用)**都在链上,不删**。复检:`pages_audit` 已知缺口 21 → 18 条(两条不再按"溯源缺失"重复报),rc=6 仅剩两条 —— `page_product_cms` / `page_product_vib_raw_manifest` 要的 `m5_cms_tcm/windows/w0316/index.parquet` 与 `m5_cms_tcm/vib_raw_manifest.json` 不在位(用户 14:58 亲手清产物所致,属"检查器在正常工作",补齐走 `scripts/vib_raw_build.py` 或门户「数据重算」,**未擅自重建**) | | 2026-09-17 | **用户令 1「运行不要从含产物的交付包补齐」+ 用户令 2「无生成端的那批能否逆向工程推导」的落地(见 §13.6)**:① **运行期与交付包解耦** —— `rebuild_all.py` 第 ⑤ 步由"补齐随包件"改为"**反向呼应审计**(只报账不搬运)";本会话里我加过的"自动找旁边的 zip"整段删掉(那会把"旁边有个包"变成隐式依赖);运行期一切面向人的提示(工作台缺件页 `_gap_page`、`err=no_products` 说明、CMS「无产物」页、`taxonomy._缺件说明`、运维控制台清产物提示、`products_state --status/--on`、打包器文字与 README)统一改成"**放原始件后重算,或由研发补生成端**";`products_restore_missing.py` 降级为**离线人工补救**(新增 `--find` 仅供人工诊断,无 `--stash` 时不再自动找包)。② **逆向工程可逆性矩阵(机器证据而非印象)** —— 判据两条:生成端在不在包内 + 产物里有没有**人工判断字段**(按字段名匹配,不拿整篇子串;页面 `.html` 不计入),并区分测量形态件与文本·判断件。**512 件的实测分布**:`混合(测量件可反推/文本件需人定) 71`(振动线出件:23 测量形态 + 48 人工件)· `半可逆(需人工裁定) 207`(sop 评审底稿)· `不可逆(人工判断) 144`(tcm 回放 58 · windscada 评审 47 · paradigm_r1 29 · 本体发布层 5 · 事实契约 5)· `可逆(页面可再生) 52`(windcms 页面件)· `可逆(需反推口径) 2`(pitch 变桨件)· `应移出产物仓 21` · `需人工裁定 15`(谱图) ⇒ **答复**:能从原件**推**出来的是"内容只是原件的函数"那部分(约 125 件),做法照 `temp_monthly`(反推口径 → 逐值对拍,一致才敢用);含人工裁决/评审/领域正本的 351 件**推不出来**——逆向工程对它们只能"重做那一步人工判断",正确处置是承认它们是人工件(不该按自动产物管)或由研发补口径 | --- ## 9. 交付包组成(v0.4.0;2026-09-17 用户令:默认不含 输入数据 / 产物 / 运行日志 / 临时文件) 打包器:`scripts/pack_dist.py`。**一条命令、不需要开关**:`python scripts/pack_dist.py`(或 Windows 双击 `pack.bat`、Linux/macOS `sh pack.sh`;`pack.bat dry` 只预演、`pack.bat verify` 开箱验证)。 默认文件名 `<安装目录的上一级>/guanlan-v_dist_<日期>.zip`,可用 `--out` 指定。 | 进包 | 内容 | |---|---| | `src/` `scripts/` `guanlan.py` | 程序与全部构建/运维脚本(含 `src/entry_refs.py` 入口引用闭合检查、`scripts/pack_dist.py` 打包器自身) | | `configs/` `resources/` `reference/` | 配置(含 `serve.json` 端口真源、`portal_pages.yaml` 页面归口、`registry.yaml` 配置登记表) | | `release/` | 门户、仿真页、三维资产 `viewer/`、治理清单交付件 `release/如东/`(客户交付物,勿外传) | | `docs/` `README_先读我.MD` `测试须知.txt` | 交付文档与说明书(README 已按用户令由 `.txt` 改名为 `.MD`) | | `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`、**`pack.bat` `pack.sh`**、`requirements.txt` | 安装、起停与**打包**入口(**无 `.vbs`**:`start.bat` 默认调 `pythonw.exe` + `scripts/guanlan_start_hidden.py`;`install.ps1` 建两份快捷方式 —— 中文名给人双击、`start_guanlan.lnk` 供控制台/脚本引用) | | 不进包(默认口径) | 理由(同时写进包内 `dist-manifest.json` 的 `excluded`) | |---|---| | ① `data/` | **输入数据**:现场原始件,约定不随包分发(例外要带用 `--with-data`) | | ② `outputs/` | **产物/输出数据**:目标机放数据后 `rebuild_all.py` 重算;缺"无生成端"的那批用 `products_restore_missing.py --stash <交付包.zip>`(例外要带用 `--with-products`) | | ③ `logs/` `run/` + **任何位置**的 `*.log`、`*.jsonl` | **运行日志/审计流水**:`run/pids.json` 里的 PID 到新机器上是无效引用;`*.log` 兜底规则防止日志从别处漏进包 | | ④ `*.tmp *.temp *.bak* *.old *.orig *.rej *.swp *.swo *~ ~$*` | **临时与备份文件**(编辑器/工具留下) | | ④ `*_cache/ __pycache__/ .pytest_cache .mypy_cache .ruff_cache .ipynb_checkpoints .npm-cache .qa-profile` | **缓存目录**(解释器字节码、第三方工具缓存)。★2026-09-17:`release/viewer/` 下**实测存在的** `.npm-cache/`(64 件 15.9 MB,npm 缓存)与 `.qa-profile/`(250 件 40.1 MB,浏览器 QA 用户目录)已按用户令**真删**(`release/viewer` 636.0 → 580.0 MB,省 56.1 MB);两目录都不在 git(`release/viewer/` 整目录被 ignore)、不被任何运行时代码读取、本就被打包器排除 ⇒ 确定无用。规则留在打包器里当**兜底**(npm/QA 再跑一次就会重新生成,届时也不会进包) | | ④ `.DS_Store` `Thumbs.db` `desktop.ini` `*.crdownload` `*.partial` | **系统/下载临时索引** | | `install-info.json`、`*.lnk` | **装机产物**(本机装没装 / 本机快捷方式,含安装时间与本机绝对路径)—— 只在本机有效,换机即失效。★ 2026-09-17 实逮:本机的 `install-info.json` 被**原样打进了交付包**(`INCLUDE_FILES` 里没有它,但入口引用闭合兜底把 `install.ps1` 读它这件事当成了"入口引用的文件"自动补入)。后果是静默误报:目标机第一次装就读到它,安装前检查报"已安装 v2.5.0(装于打包机时间)→ 版本相同,是否重装"。处置:打包器按名字排除 + 自动补入环节过滤 + **打包后复查**(包里出现`install-info.json`或 `*.lnk` 就删包并 rc=1);`.gitignore` 也把这两类挡住 | | `.venv/` `.git/` `.github/` | venv 换机必失效(安装时重建);版本库不随交付件分发 | | `_products_off*/` | 旧设计的"清除产物"暂存档(2026-09-16 起清除=真删,不再产生;老机器上若有可手工删) | | `*.lnk` `__pycache__/ *.pyc *.pyo`、顶层 `*.zip` | 安装时建的快捷方式(绝对路径,由目标机 `install.ps1` 重建)、字节码、旧的交付压缩包(避免包中包) | **打包器自带两道闸**(不合格的包会被 `unlink()` 掉并返回非 0,不留"看起来能用"的坏包): 入口脚本**引用闭合**(`.bat/.ps1/.sh` 引用的文件必须都在包里)+ 入口脚本**编码/字符守则**(§11.3)。 开箱验证(一条命令给出"能不能装、能不能跑"的证据):`python scripts/pack_dist.py --verify ` —— 解压到 临时目录 → ①b **入口引用闭合 + 编码守则核对** → 离线安装 → **②b 快捷方式×2 + `console_hwnd=0` 无窗口 + 包内无 `.vbs`** → 起服务核验 `/`·`/detail/`·`/cms/`·`/ops`·`/healthz` → 收尾清理,要求 ≥4 个页面可用。 ★ 核验要求组件端口(18033/18020/18791/18792/64292)空闲,**验证前先 `guanlan.py stop`**,否则整段核验被跳过(打印 `[!]`)。 ### 9.1 入口引用闭合(`src/entry_refs.py`,2026-09-16 实炸后加) 现场反馈:目标机 `<安装目录>` 双击 `start.bat` → `无法找到脚本文件 "<安装目录>\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) ⇒ 客户交付件, 随交付版本变; 内容虽由分析产出, 但**冻结发布**, 不该随输入数据自动改 | |   └ `全场状态一览 (脱敏)` | `frozen-delivery` | 否 | ★2026-09-17 用户裁决前的问题: 1.5 MB 的场站状态快照(脱敏版), 内容是实际运行状态的函数 ⇒ **属于产物**, 却没有任何指纹/生成时间/来源登记, 全仓搜不到生成端 ⇒ 曾游离在产物管理之外(无溯源、无陈旧检测、无重算入口)。**保留不删**: 它是客户手里的交付件, 删了交付就缺件 | |   └ `如东取数单` | `frozen-delivery` | 否 | 取数单是「要哪些数据」的清单页, 明列产物路径 ⇒ **性质上随产物结构变**; 但只有日期没有指纹, 也没有生成端随包 ⇒ 无法自动刷新。**保留不删**: 另有 3 处交付侧引用它(`release/如东/如东取数单_2026-08-21/README.md`、 `如东现场一页单*.md`), 删了就成悬空引用 | | `page_product_cms` CMS 评估报告页/件 (门户「振动·CMS」页读的就是它们) | `data-derived` | **是** | 门户 #cms 的 iframe 指向组件 :18020, 组件页读的就是 outputs/<场>/windcms 下的这些页面件 (index.html / 逐台页 / workbench.html / report_*.md) —— 它们是**产物**(由 windcms 报告生成器读 TCM 窗索引写成), 不是程序。本包缺六层链的 model_run/fusion 产物, 重生成会掉内容 (report.md −97%, docs §7), 故**不在重算链里重建**, 交付时按随包件补齐并登记为 shipped。 管理动作: 登记(本条) + 溯源(_provenance.json 的 shipped) + 陈旧检测(源=TCM 窗索引的 mtime/sha)。 | | `page_product_vib_raw_manifest` 振动摄入清单 (数据层「CMS 振动评估报告」页引用) | `data-derived` | **是** | 每次振动摄入重写: 记本窗索引/谱库的件数、时间跨度与**未随包的六层链缺口**(missing_chain)。 数据层页面与 docs §7 都引用它 ⇒ 属产物, 且已在台账中自登记。 | | `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 @e3b531b8eddf8197 (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 占位面板 | | `detail_tabs` 工作台 /detail/v2 的 10 个标签 (SPA) | `live` | **是** | 工作台是单页应用: 外壳一次装载, 每个标签按需 fetch 后端 API, 而 API 现场读产物 (产物指纹 TTL 2s 自动重载) ⇒ 页面本体不存快照、永远与产物一致, 不需要产物管理。 其中 report 标签渲染产物 `outputs/<场>/windcms/report.md`(结论件), 该件属产物并已在台账里。 ★ 2026-09-17 现场: 清产物后 `tab=vibration` 空白, 不是页面坏了, 是它要的 `m5_cms_tcm/{handoff_vibration_v2,component_history,baseline_38}.json` 与 CMS 页面件不在位 (§7/§13.5)。 | | `delivery_pages_all` 交付页 (release/如东 下全部 html) | `frozen-delivery` | 否 | 客户交付件(治理清单分册/全册/正式报告/取数单/脱敏状态一览), 文件名自带版本号+日期 ⇒ 按交付版本冻结。 其中"脱敏状态一览"与"如东取数单"是**数据派生**的(内容随运行状态变) ⇒ 另见 §12 的逐页判定与 known_gap。 | | `viewer_pages_all` 三维页 (release/viewer 下全部 html) | `static` | 否 | 图纸/三维模型派生的资产页(工作台/索引/装配演示), 与 data/raw 无关 ⇒ 不是产物。 | --- ## 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 天**,写前清理) | `<动作>_.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` 各报一行。 ### 11.3 入口脚本的字符守则(.bat 必须纯 ASCII) 现场反馈:双击 `start.bat` 报 `'本身是控制台程序' 不是内部或外部命令` / `'模式' 不是内部或外部命令` —— **批处理注释里的中文被 cmd 当成命令执行了**。批处理的解析与输出都绑在控制台代码页上 (cp936 / cp65001 / 系统区域设置),多字节字符 + `cmd.exe` 的既有毛病会凑出这类事故。 另外我自己还写错过一处 `echo <>` —— `>` `<` 在批处理里是**重定向符**,`echo` 里出现就报 `<< was unexpected at this time`(本机已复现)。 处置:**包里所有 `.bat` 改写为纯 ASCII(英文)**,中文说明全部留在 `README_先读我.MD` 与 `docs/`; `start.bat` 功能一字未变(默认 `pythonw.exe` 无窗口起服务 / `console` 前台档 / `help` / `.venv` 与启动器在位检查)。 `install.ps1` 现在建两份快捷方式:中文名给人双击,**`start_guanlan.lnk`(ASCII 名)**给控制台与脚本引用 —— 这样纯 ASCII 的 `start.bat` 帮助里也能准确写出它的名字。 **机器守则**(`src/entry_refs.py::encoding_problems()`,在打包、开箱验证第①b步、装机自检三处执行): | 文件 | 守则 | 违反的后果 | |---|---|---| | `.bat` | **纯 ASCII**(无 >0x7F 字节)· CRLF · 无 BOM · 不含 `<<` · `>` 后必须是重定向目标 | 中文被曲解后当命令执行;`<<`/`<>` 直接报重定向错 | | `.ps1` | UTF-8 **带 BOM** + CRLF | PS 5.1 按 GBK 解码 → 中文乱码 + 级联 ParserError | | `.sh` | LF | 每个词尾粘 `\r`:`set -e` 变非法选项、`RT=""` 变 `RT="\r"` | **变异测试**:往 `start.bat` 注释里加一句中文 → 报"含 39 个非 ASCII 字节 … 现场出现过'注释被当命令执行'"; 写回 `<>` → 报"含 `<<`"+"第 101 行的 `>` 后面不是重定向目标"。本机 `cmd /c start.bat help` 实跑无任何报错。 ### 11.4 "无产物"时必须还能打开页面;开箱验证不许打扰运行中的实例 **现场症状(2026-09-17)**:在控制台点了「清除产物」之后,从门户点「登录」→ `/detail/v2` 显示 "模块不可用: 综合详细分析工作台 … 127.0.0.1:18033 未启动"、日志里 `[WinError 10061]` 连接被拒。 查下来是两件独立的事叠在一起: 1. **CMS 服务会因为"产物没了"而崩**:`src/windcms/data.py::load_scalars` 在无窗时执行 `pd.concat([])` → `ValueError: No objects to concatenate`(进程直接没了);就算进程活着,`src/windcms/serve.py` 也要 读产物里的 `index.html`,缺了就抛 `FileNotFoundError`,请求**连接被掐断**(页面白屏)。 处置:`load_scalars`/`load_alarm_counts` 无数据时返回**空表**(列齐、dtype 对、不写空缓存); `serve.py` 页面路由加兜底 —— 缺产物返回**结构化"无产物"页**(HTTP 200,与工作台侧 `err=no_products` 同口径, 页面上写清缺哪件、怎么补:放数据 → 重算 → 核验),其它异常返回 500 页 + 日志,不再出现"连接被直接掐断"。 **实测**:清空 `outputs/` 后起 CMS,`GET /?theme=light` → **HTTP 200 / 48 KB**,页面正常显示"无产物"文案。 ★ 2026-09-17 同日补:工作台侧同类静默空白也一起修了 —— `/static/*`(总览「全场状态」的 iframe 就是 `/static/index.html`,正本是产物 `outputs/<场>/windscada/index.html`)与 `/turbines/*` 缺件时原先回一个 **裸 `404` 文本**,浏览器里表现为"这一块没有信息",页面上找不出任何原因(用户当天报的正是这个: "总览『全场状态 一格一台 · 点格定位』没有信息")。现在两者都走 `_gap_page()`:**HTTP 200 + 结构化缺件页**, 写明缺哪一件、属于哪条产物线、**怎么补**(有生成端 → `rebuild_all.py`;包内无生成端 → `products_restore_missing.py --stash <含产物的交付包.zip>`),并给出运维控制台与 `healthz` 入口。 **实测**:`/detail/static/不存在的页.html` → HTTP 200 / 1,684 B 的缺件说明页(不再是 404)。 2. **开箱验证会先把用户的组件停掉**:`scripts/pack_dist.py --verify` 的核验步骤要求 18033/18020/18791/18792/64292 空闲(因为 `guanlan_gateway.ROUTES` 的上游端口当时是**写死**的), 于是验证窗口里用户正在用的 `/detail/`、`/cms/` 就是断的 —— 用户点「登录」正好撞上。 处置:`ROUTES` 的上游端口改为**可被 `configs/serve.json` 覆盖**(端口单一真源,顺带补上用户令 2 的口径), `run_and_probe` 于是把**副本整套**换到空闲端口再核验 —— **主实例照常跑,谁也不打扰谁**; 找不到空闲端口才降级为"跳过启动核验"。 **实测(主实例全程在跑,验证前后都是 6/7 ok)**:`--verify` 打印 `副本改用独立端口: gateway=28700, detail=28900, cms=29000, sim=29100, sim_sys=29200, viewer=28800` → 页面 **5/5 可用**(`/`·`/detail/`·`/cms/`·`/ops`·`/healthz`;`/cms/` 因为有新的"无产物"页也成了 200) → 收尾只停副本自己的进程;主实例 `/detail/v2` 验证后仍 **200 / 229,643 B**,副本端口无一残留。 --- ## 12. 门户逐页识别:内容是什么、是不是产物、输出在哪 判据三条(与 §10 同一套,登记表在 `configs/portal_pages.yaml`,检查器 `scripts/pages_audit.py`): **① 内容是不是由 `data/raw` 算出来的**(是 ⇒ 产物性质);**② 页面是现场读产物还是烘成快照**(现场读 ⇒ 永远一致,不用管); **③ 交付件按版本冻结还是随数据滚**(冻结 ⇒ 不是运行时产物,但要版本登记)。 | 页面 / 子页 | 内容是什么 | 由输入数据重算? | 是产物? | 若是产物: 输出路径 + 生成端 | 现状与管理 | |---|---|---|---|---|---| | `#index` 总览 | 产品承诺 + 入口导航(外壳静态文本) | 否 | **不是** | — | 受管外壳(`release/portal_src/shell.html`) | | `#architecture` 系统架构 | 分层架构叙述 | 否 | **不是** | — | 同上 | | `#method` 方法 | 方法论叙述 | 否 | **不是** | — | 同上 | | `#findings` 经验发现 | 公司级经验("59 项内部检查 / 60+ 风场")+ 八条教训 | 否 | **不是** | — | 静态;若将来放本场站计数,必须改 `data-derived` 并登记 `source` | | `#case_hydraulic` 案例·液压 | 案例叙述 | 否 | **不是** | — | 静态 | | `#cms` 振动·CMS | iframe → 组件 `:18020`(CMS 诊断页 + 逐台页 + 工作台) | **是**(组件现场读产物) | 页面本体不是;**它读的 CMS 页面件是产物** | `outputs/<场>/windcms/*.html`、`report_CMS振动状态评估报告_*.md`、`report.md`、`workbench.html` | **产物管理**: 这些件由 `windcms.py report` 生成;本包缺六层链产物故**不在重算链里重建**(`vib_raw_build.py --with-report` 会掉内容,见 §7),交付时按"随包件"补齐并登记为 `shipped` | | `#sim` 仿真与回放 | 机理/图纸叙述 + 6 个链接 | 否 | **不是** | — | 输入是图纸/机型参数(`configs/machine_packs`、`release/viewer`),与 `data/raw` 无关 | | └ 控制律仿真台(5 页) | 图纸派生的物理回路仿真台 | 否 | **不是** | — | 冻结资料包 `release/如东SWT40_控制律仿真台_20260906.zip`(`release/sim_sys_server.py` 从 zip 现读) | | └ 三维拆装工作台 | 三维模型/装配(three.js) | 否 | **不是** | — | 交付资产 `release/viewer/**`(由图纸 + esbuild 构建,`rev=` 是评审版本号) | | └ 判据/仪表台面板(内嵌 5 块) | 固定判据说明 + **产物脚注引用** | 否(正文) | 正文不是;**被引用的产物是** | 引用 `outputs/<场>/yaw_verify/findings.json`(**本包无此目录**,见 §7 缺口) | `data-citing`:正文不随数据变,但引用必须在位(审计查悬空) | | └ **实际运行回放面板**(内嵌) | 把实际 1 分钟记录与证据包**烘进页面** | **是** | **是(快照)** | 源 `outputs/<场>/cleaned/turbine_1min.parquet` + 内嵌 `source_sha256` | **产物管理**: 已登记 `data-derived` + **陈旧检测**(sha 与当前产物比;源不在包,故现在只能按生成时间判) | | `#documents` 交付文档 | 交付件导航(0 fetch) | 否 | **不是** | — | 静态导航 | | └ 治理清单分册(12 + 全册 + 正式报告) | 分析结论的冻结交付版 | 否(按交付版本) | **不是**(冻结交付件) | — | 文件名自带版本号+日期(`v1.2_2026-09-01` 等);`frozen-delivery` 规则校验版本/日期 | | └ **全场状态一览(脱敏)** | 场站实际状态快照 | **是** | **是** | 无生成端、无指纹(`release/如东/全场状态一览_脱敏_20260825.html`) | **待裁定**: 补生成端并加 `source_sha256`(推荐),或降级为冻结交付件并接受过期(现登记 `known_gap`) | | └ **如东取数单** | "要哪些数据"的清单,明列产物路径 | **是** | **是** | 无生成端(`release/如东/如东取数单_2026-08-21.html`) | 同上;审计另查出它引用的 `cleaned/`、`structured/` 两棵树不在本包 | | `#admin` 系统状态 | 外壳 JS 拉 `/healthz` 渲染 | **是**(实时) | 不是(无快照) | — | `live` | | `#recalc` 数据重算 | 门户内嵌 `/ops/recalc` | **是**(实时) | 不是 | — | `live` | | `#login` 登录 | 链接到工作台 `:18033/v2` | **是**(实时) | 不是 | — | `live` | | 内嵌交付件(28 个模板) | 报告/清单/面板正文 | 少数是(3 件带数据指纹/时间戳) | 1 件是(回放面板) | 见上 | 其余按 `frozen-delivery`/`static` 登记 | **"纳入产物管理"的四件事**(§10 已述,这里补页面侧产物):① 登记(`portal_pages.yaml`);② 溯源(`source` + `source_sha256` 或生成时间);③ **陈旧检测**(`pages_audit.py` rc=7);④ 进链(`rebuild_all.py` ⑧b 重装门户 + ⑧c 页面审计)。 **页面侧产物(`outputs/<场>/windcms/*`)另有一条硬要求**:它们必须出现在产物台账 `_provenance.json` 里(见 §13.4 的检查)。 ### 12.2 全部网页总清点(自动生成,勿手改) 共 **262** 项(门户锚点 / 工作台标签 / 组件页面件 / 组件结论件 / 产物里的过程留痕 / 交付页 / 三维页 / 门户内嵌件 / 仿真资料包页面),其中判定为**产物**的 178 项;未被管起来的 **0** 项。判据:产物树里的件看"在不在产物台账里",网页看"在不在页面登记表里"。 | 来源 | 页面/子页 | 是产物? | 依据 | 已登记? | |---|---|---|---|---| | 门户锚点 | `#index` 总览 | 不是 | 外壳页(静态/iframe/链接);逐条判定见 §10 与 §12 | ✔ | | 门户锚点 | `#architecture` 系统架构 | 不是 | 外壳页(静态/iframe/链接);逐条判定见 §10 与 §12 | ✔ | | 门户锚点 | `#method` 方法 | 不是 | 外壳页(静态/iframe/链接);逐条判定见 §10 与 §12 | ✔ | | 门户锚点 | `#findings` 经验发现 | 不是 | 外壳页(静态/iframe/链接);逐条判定见 §10 与 §12 | ✔ | | 门户锚点 | `#case_hydraulic` 案例·液压 | 不是 | 外壳页(静态/iframe/链接);逐条判定见 §10 与 §12 | ✔ | | 门户锚点 | `#cms` 振动·CMS | 不是 | 外壳页(静态/iframe/链接);逐条判定见 §10 与 §12 | ✔ | | 门户锚点 | `#sim` 仿真与回放 | 不是 | 外壳页(静态/iframe/链接);逐条判定见 §10 与 §12 | ✔ | | 门户锚点 | `#documents` 交付文档 | 不是 | 外壳页(静态/iframe/链接);逐条判定见 §10 与 §12 | ✔ | | 门户锚点 | `#admin` 系统状态 | 不是 | 外壳页(静态/iframe/链接);逐条判定见 §10 与 §12 | ✔ | | 门户锚点 | `#recalc` 数据重算 | 不是 | 外壳页(静态/iframe/链接);逐条判定见 §10 与 §12 | ✔ | | 门户锚点 | `#login` 登录 | 不是 | 外壳页(静态/iframe/链接);逐条判定见 §10 与 §12 | ✔ | | 工作台标签 | `tab=overview` | 不是 | SPA 一次装载、按 tab 现场 fetch 产物 ⇒ 页面本体不存快照 | ✔ | | 工作台标签 | `tab=energy` | 不是 | SPA 一次装载、按 tab 现场 fetch 产物 ⇒ 页面本体不存快照 | ✔ | | 工作台标签 | `tab=component` | 不是 | SPA 一次装载、按 tab 现场 fetch 产物 ⇒ 页面本体不存快照 | ✔ | | 工作台标签 | `tab=vibration` | 不是 | SPA 一次装载、按 tab 现场 fetch 产物 ⇒ 页面本体不存快照 | ✔ | | 工作台标签 | `tab=generation` | 不是 | SPA 一次装载、按 tab 现场 fetch 产物 ⇒ 页面本体不存快照 | ✔ | | 工作台标签 | `tab=fault` | 不是 | SPA 一次装载、按 tab 现场 fetch 产物 ⇒ 页面本体不存快照 | ✔ | | 工作台标签 | `tab=decision` | 不是 | SPA 一次装载、按 tab 现场 fetch 产物 ⇒ 页面本体不存快照 | ✔ | | 工作台标签 | `tab=assistant` | 不是 | SPA 一次装载、按 tab 现场 fetch 产物 ⇒ 页面本体不存快照 | ✔ | | 工作台标签 | `tab=report` | 不是 | SPA 一次装载、按 tab 现场 fetch 产物 ⇒ 页面本体不存快照 | ✔ | | 工作台标签 | `tab=system` | 不是 | SPA 一次装载、按 tab 现场 fetch 产物 ⇒ 页面本体不存快照 | ✔ | | 组件结论件 (.md,页面会渲染) | `guanlan/derived/report_summary.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/gear_grade_38.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/OEM特征频率复跑_2026-08-17.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/交接_振动→发电机分析.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/交接_振动→齿轮箱_行星级中速轴判级.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/交接_振动→齿轮箱分析.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/取数与送检需求单_20260827.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/回测训练_历史锚三轮深挖_20260826.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `m5_cms_tcm/对比_大生科技传动链报告_20260311.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/振动板块_windscada融合面_设计v1.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/振动算法复跑记录_2026-08-15.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `m5_cms_tcm/收敛报告_如东TCM振动诊断_v4.2.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `m5_cms_tcm/收敛报告_如东TCM振动诊断_v4.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `m5_cms_tcm/收敛报告_如东TCM振动诊断_v5.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/给windscada线_TCM16验收槽规格_20260827.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/给windscada线_工单库缺2024-11之后_20260827.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/给windscada线_油样复核回执_20260827.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/给windscada线_温度轴复核回执_20260826.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/给windscada线_温度轴核查请求_20260826.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/给windscada线_色标已改_20260828.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `m5_cms_tcm/综合_振动最新×发电机齿轮箱原分析_2026-08-13.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `m5_cms_tcm/诊断报告_TCM振动_202601窗.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `m5_cms_tcm/诊断报告_TCM振动_六窗终版.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `m5_cms_tcm/频谱分析报告_轴承特征频率_2026-08-13.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E3_candidate_closure/plan.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E3_candidate_closure/result.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E3_candidate_closure/归宿草案.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E5_coverage_table/plan.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E5_coverage_table/result.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E5_coverage_table/主表.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E8_rudong_rebuild/plan.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E8_rudong_rebuild/result.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E8_rudong_rebuild/S0_盘点.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E8_rudong_rebuild/S1_能问表对拍.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E8_rudong_rebuild/S3_C7干跑.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `paradigm_r1/experiments/E8_rudong_rebuild/S5_对表.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_dual_review_20260726_payload脱敏.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_review_pkg_20260822_model_additions.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_review_pkg_20260822_sealed_predictions.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_review_pkg_20260823_gende16_33.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_review_pkg_20260823_gende16_33_addendum.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_review_pkg_20260823_gende16_33_sealed_predictions.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_review_pkg_20260824_shared_component_fleet.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_review_pkg_20260824_shared_component_fleet_sealed_predictions.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_review_pkg_20260824_wtg12_outlier.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_review_pkg_20260824_wtg12_outlier_sealed.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_u6_review_20260810/封存预测_审前.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_u6_review_20260810/送审包_as_sent.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_u6_送审包_20260810.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_u6_送审包_r2_20260810.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_u8_sealed_predictions.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/_u8_送审包_20260812.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/local_review/disposition_14b_20260824.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/local_review/disposition_32b_20260824.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/local_review/review_pkg_20260824_grading_calibration.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260831/lens_A_口径统计.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260831/lens_B_交付表述.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260831/pkg.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260831/SEALED_我的预测_勿送审.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260831/处置台账.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260831/夜间工作总结_0901.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260901/lens_A_事实核查.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260901/lens_B_读者与风险.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260901/pkg_r2.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260901/SEALED_我的预测_勿送审.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260901/处置台账.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/review_20260901/复审处置_r2.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `sop/轴4返修_AdvCI两方向_2026-07-27.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/_争议裁决_封存预判_勿入送审包.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/_争议裁决送审包_20260823.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/Codex回复_rms200_mask_20260823.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/Codex复现闸回传_20260823.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/争议裁决记录_20260823.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/复现审计.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/建议采纳清单_20260823.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/振动模型规划.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/给Codex的同步_R5_WTG23包络滞后_20260823.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/给Codex的同步_rms200证据边界_20260823.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/给Codex的回传.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/给Codex的回传_R2_20260823.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/给Codex的回信_如东振动研究历程梳理_20260905.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `tcm_compatible_replay/claude_handoff_20260823/给Codex的问题_rms200_mask_20260823.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/index.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/index_eng.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/overview.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `windcms/report.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG01.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG02.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG03.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG04.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG05.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG06.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG07.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG08.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG09.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG10.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG11.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG12.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG13.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG14.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG15.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG16.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG17.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG18.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG19.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG20.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG21.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG22.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG23.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG24.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG25.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG26.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG27.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG28.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG29.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG30.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG31.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG32.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG33.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG34.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG35.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG36.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG37.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windcms/turbines/WTG38.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `windcms/报告_CMS振动状态评估报告_2026-08-23.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `windcms/报告_CMS振动状态评估报告_2026-08-24.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `windcms/报告_CMS振动状态评估报告_2026-08-26.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `windcms/报告_CMS振动状态评估报告_2026-08-28.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `windcms/评审_离线系统诊断准确度_20260824.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `windscada/_design_audit_20260826.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `windscada/_selfaudit_20260825.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/index.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `windscada/review/_sealed_predictions_20260826.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `windscada/review/审核处置_R1_20260826.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `windscada/review/审核处置_R2_20260826.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `windscada/review/审核处置_R3_20260826.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `windscada/review/送审包_windscada_v1.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `windscada/review/送审包_windscada_v2.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG01.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG02.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG03.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG04.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG05.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG06.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG07.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG08.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG09.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG10.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG11.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG12.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG13.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG14.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG15.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG16.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG17.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG18.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG19.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG20.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG21.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG22.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG23.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG24.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG25.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG26.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG27.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG28.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG29.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG30.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG31.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG32.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG33.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG34.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG35.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG36.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG37.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 组件页面件 (.html) | `windscada/turbines/WTG38.html` | **是** | 位于 outputs/<场>/ ⇒ 本身就是产物,且是被服务直接当页面呈现的件;台账: 在 | ✔ | | 产物里的过程留痕 (.md,非网页) | `windscada/给振动线_温度轴回复_20260826.md` | **是** | 产物,但没有任何页面呈现它(给下游线的交接/复跑记录);台账: 在 | ✔ | | 组件结论件 (.md,页面会渲染) | `windscada/送审_系统结论与判定_20260828.md` | **是** | 产物(结论文本);工作台 report tab / CMS report.html 会渲染它;台账: 在 | ✔ | | 交付页 (release/如东) | `如东/全场状态一览_脱敏_20260825.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东取数单_2026-08-21/如东取数单_2026-08-21.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东取数单_2026-08-21/如东现场一页单.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东取数单_2026-08-21/更正页_2026-08-21晚.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东取数单_2026-08-21.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/01_正式报告/如东_液压系统技术报告_v1.0_2026-09-01.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/02_治理清单/如东_液压系统治理清单_v1.2_2026-09-01.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/02_治理清单/如东风场治理清单_全册_v4.5_2026-09-06.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/00_总目录.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/01_如东_液压系统治理清单_v1.0_2026-09-05.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/02_如东_变桨执行治理清单_v1.0_2026-09-05.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/03_如东_偏航系统治理清单_v1.0_2026-09-05.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/04_如东_变流器治理清单_v1.0_2026-09-05.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/05_如东_主控与传感网治理清单_v1.0_2026-09-05.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/06_如东_电气并网治理清单_v1.0_2026-09-05.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/07_如东_塔架与基础治理清单_v1.0_2026-09-05.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/08_如东_润滑与冷却辅助治理清单_v1.1_2026-09-06.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/09_如东_CMS-TCM监测面治理清单_v1.1_2026-09-06.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/10_如东_参数与管理治理清单_v1.0_2026-09-05.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东治理清单_交付_20260901/03_分册全套/11_如东_故障负担榜_v1.0_2026-09-05.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 交付页 (release/如东) | `如东/如东现场一页单_2026-08-21.html` | 不是 | 客户交付件,按版本冻结 | ✔ | | 三维页 (release/viewer) | `viewer/bedframe-demo.html` | 不是 | 图纸/模型派生的三维资产 | ✔ | | 三维页 (release/viewer) | `viewer/guanlan-publish-findings.html` | 不是 | 图纸/模型派生的三维资产 | ✔ | | 三维页 (release/viewer) | `viewer/guanlan-publish-index.html` | 不是 | 图纸/模型派生的三维资产 | ✔ | | 三维页 (release/viewer) | `viewer/guanlan-publish-method.html` | 不是 | 图纸/模型派生的三维资产 | ✔ | | 三维页 (release/viewer) | `viewer/guanlan-publish-services.html` | 不是 | 图纸/模型派生的三维资产 | ✔ | | 三维页 (release/viewer) | `viewer/guanlan-publish-turbine-explorer.html` | 不是 | 图纸/模型派生的三维资产 | ✔ | | 三维页 (release/viewer) | `viewer/guanlan-publish-whitepaper.html` | 不是 | 图纸/模型派生的三维资产 | ✔ | | 三维页 (release/viewer) | `viewer/index.html` | 不是 | 图纸/模型派生的三维资产 | ✔ | | 三维页 (release/viewer) | `viewer/unit-workbench.html` | 不是 | 图纸/模型派生的三维资产 | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-coverage_ch0_zh.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-0.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-1.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-10.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-11.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-2.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-3.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-4.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-5.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-6.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-7.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-8.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-governance-9.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-report-主轴承.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-report-发电机.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-report-变桨.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-report-齿轮箱.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-sc1-local.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-standard_panel_zh.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-swt-subsystem-1.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-swt-subsystem-2.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-swt-subsystem-3.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-swt-subsystem-4.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-swt40_控制律仪表台_外发版.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-U6_变桨液压仿真台.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-U6_液压公共站健康报告_客户版.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-如东传动链实际运行诊断_单文件版.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 门户内嵌件 | `portal_src/templates/tpl-如东海上风电场_整机综合诊断与风险评估_主轴冲击深挖修订版V2.1.html.html` | —(逐条判定) | 内嵌交付件(§10 逐条判定;个别为数据派生) | ✔ | | 仿真资料包 (zip) | `2_传动链与功率.html` | 不是 | 图纸派生的冻结资料包(sim_sys_server 从 zip 现读) | ✔ | | 仿真资料包 (zip) | `4_变桨轮毂液压.html` | 不是 | 图纸派生的冻结资料包(sim_sys_server 从 zip 现读) | ✔ | | 仿真资料包 (zip) | `0_四系统合页.html` | 不是 | 图纸派生的冻结资料包(sim_sys_server 从 zip 现读) | ✔ | | 仿真资料包 (zip) | `3_发电机与热管理.html` | 不是 | 图纸派生的冻结资料包(sim_sys_server 从 zip 现读) | ✔ | | 仿真资料包 (zip) | `1_偏航系统.html` | 不是 | 图纸派生的冻结资料包(sim_sys_server 从 zip 现读) | ✔ | --- ## 13. 输入数据 → 功能 → 算法 → 产物(关系与实现逻辑) ### 13.1 关系总表(每条都能指到脚本与产物) | 输入数据(`data/raw/<场>/…`) | 功能 | 实现(脚本) | 算法/实现逻辑要点 | 产物(`outputs/<场>/…`) | 验收锚点 | |---|---|---|---|---|---| | `故障报警/*.xls`(实为 **SpreadsheetML XML**) | 报警台账 | `scripts/windscada_alarms_ingest.py` | 逐行解析 XML 报警事件 → 统一列(t_on/机组/文本/类型);列映射按随包件锁定 | `windscada/alarms.parquet` | **39,211 行** | | `风机故障记录/**/*.xls(x)`(78 张月度/年度同模板表) | 检修工单台账 | `scripts/windscada_workorder_ingest.py` | 前两行是说明与分组表头、**第 3 行列名**、数据从第 4 行起;`str(cell)` 原样字符串化(与随包件一致) | `windscada/workorders.parquet` | **5,876 行** | | `油样报告/<台号>/<部件>/*.pdf` | 油液化验索引 | `scripts/windscada_watch_channels_build.py` | 从**文件名**抽全部关键字段(`DDMMYYYY_sampleid_项目_台号_部件`),不读 PDF 正文 | `windscada/oil_samples_index.parquet` | **404 行**(缺 2026-07 批 102 行,源件不在包) | | `scada_10min/<机组>.csv`(14 GB) | L0 标准仓 + 10 个 SCADA 构建器 | `scripts/rebuild_from_raw.py --scada` | 逐台流式读 CSV → 统一列 → 按窗聚合/派生(温度、功率曲线、损失、控制、偏航、热链、停机事件…) | `windscada/{temp_bins,temp_monthly,powercurve_bins,powercurve_dev,loss_monthly,control_*,yaw_daily,stop_events,thermal_chain,system_aux,curve_*}.parquet` | `temp_monthly` **19,494 行** | | 同上(月度聚合) | 月度派生件(15 件无生成端的补上) | `scripts/windscada_monthly_build.py` | 从 10min 件**重新聚合**并**逐值对齐随包件**(随包件当标准答案,`--verify` 报命中率);规则是反推+全量比对确认 | `windscada/temp_monthly`、`loss_monthly`、`ctrl_monthly` 等同族 | 逐值相等才落盘 | | `windcms/**/*_decode.json`(Brande TCM 导出,25,680 件) | 振动窗索引 | `scripts/rudong_tcm_index.py` | 解析 `body.body[]=[{Record:…}]`;机组取 `Record.Location/LocationName`;一行一条记录 | `m5_cms_tcm/windows/<窗>/index.parquet` | 窗 `w0316` **2,066,686 行** | | 同上 | 振动谱库 | `scripts/rudong_tcm_spectra.py` | 每条谱写进 `npz` 分片 + `spectra_meta` 指针(`shard/shard_row/x_offset/x_delta`);取数 `x = x_offset + arange(n)*x_delta` | `m5_cms_tcm/windows/<窗>/spectra/*.npz` + `spectra_meta.parquet` | 窗 `w0316` **420,742 条谱 / 1,712 片** | | 同上 + 厂家报告 docx | 振动侧一键摄入 | `scripts/vib_raw_build.py`(可 `--with-report`) | 编排"索引→谱→清单";**默认不重生成报告**(缺六层链,重生成会掉内容 −97%,见 §7) | `m5_cms_tcm/{index,spectra_meta,vib_raw_manifest}.…` | `vib_raw_manifest.json` 自述缺口 | | 厂家报告 docx(PDF 是扫描件,无文本层) | 报告转录 | `scripts/vib_reports_build.py` | docx 有文本层 ⇒ **逐字转录**判级为结构化件;扫描件 PDF 只登记归档、不进判级 | `windcms/报告_CMS*.md`、`windcms/*.json` | 转录件与报告逐条对齐 | | `西门子4.0技术资料/**`(含 4 个台账 xlsx) | 本体知识层 | `python -m src.ontology.kb_ingest` | 四条链(预防/抢修/故障树/计划)图谱化:手册→AlarmCode+作业指导;对译表→中文名;维护指导书→预防性任务;每条声明出处 | `ontology/objects.json` 等 | 码覆盖 263/322 | | 上述全部产物 + 判级矩阵 | 本体铺开 | `python -m src.ontology.populate` | **零判级权**:只转录 L1 正本(`taxonomy.system_matrix` / `fusion_table`);每条 Verdict 带审级与 `claim_window`,Evidence 指回 L1 产物;同部件多源判级**并存不合并** | `ontology/objects.json`(9,613 对象) | `ontology.audit` **0 问题** | | 链盘(运行时 `/api/fleet`) | 决策链进度 | `python -m src.ontology.chain_ingest` | 每台一个 `Decision` 对象记六步状态/卡点/下一步(不是 Evidence——它是系统推出来的处置状态) | `ontology/objects.json` | 需服务在跑 | | `component_history.json` + 链盘 | 趋势/闭环证据 | `python -m src.ontology.trend_ingest` | 在升=末三窗/早三窗 ≥1.3(未闭环);闭环=检出→换件→复测回落;两类都建 Evidence 并挂 `about→turbine`、`affects→component` | `ontology/objects.json` | 幂等覆盖 | | `sop/findings.json`(119 条) | 事实契约 + 派生件 | `scripts/guanlan_facts_contract.py` | 每条结论 = claim_id + 脱敏来源引用(`文件@sha16` + 章节)+ 本条 sha256 + 时间窗 + 聚合层级 + 裁决(六枚举) | `guanlan/facts_contract_v0.json` → 派生 `guanlan/derived/{portal_claims,detail_cards,qa_refs,report_summary}` | 探针:改一条 claim → 四个消费者全变 | | 门户外壳 + 28 个内嵌件 + 契约派生件 | 门户装配 | `scripts/portal_build.py`(+ `guanlan_portal_inject_claims.py`) | 外壳受管、内嵌件与合成结果不入库;装配**先写临时文件再原子替换**,失败不破坏在服务的门户;行尾强制 LF | `release/portal.html`(20.2 MB) | `--verify` 逐字节一致 | | 上述全部 | 产物清点 + 呼应校验 | `scripts/inventory_products.py --check` | 对每类输入算跨度/条数,与它喂出来的产物逐项对拍(产物跨度必须落在输入跨度内) | `docs/系统设计说明.md` §2(自动块) | rc=0 | ### 13.2 端到端顺序(为什么是这个顺序) `scripts/rebuild_all.py` 把 14 步写死(各步幂等,可整条重跑): ``` ① 放数据 place_raw_data.py 原样搬 现场包 → data/raw/<场>/(同尺寸跳过,冲突拦下) ② 三门台账 rebuild_from_raw.py 报警/工单/油样 ← 台账类源件 ③ SCADA 侧 rebuild_from_raw.py --scada 10 个构建器 ← 14 GB 10min CSV(约 15 分钟) ④ 月度派生件 windscada_monthly_build.py 逐值对齐随包件才落盘(缺基线返回 5 只跳过"等价验收") ④b 振动摄入 vib_raw_build.py 索引/谱库 ← TCM 导出(约 14 分钟) ⑤ 补齐随包件 products_restore_missing.py 无生成端的那批(源 = 交付包 zip;缺源返回 6 跳过) ⑥ 重启组件服务 只重启组件、保留网关(chain_ingest 要吃重新加载的产物) ⑦ 本体六步 kb_ingest → populate → chain_ingest → trend_ingest → 检索索引 → 实机参数表 ⑧ 审计 ontology.audit(期望 0 问题) + ⑧b 重装门户(把新产物灌进结论段) + ⑧c 页面归口审计 ``` **依赖为什么不能换**:⑦ 的 `populate` 要吃 ②③④④b⑤ 的产物;⑤ 必须在 ⑥ 之前(本体层要读补齐后的 L1 件); ⑧b 必须在产物全部就位之后(门户的结论段是产物的渲染结果)。 ### 13.3 前端取数与"即时进页面" | 服务 | 读法 | 产物更新后 | |---|---|---| | 工作台 `:18033`(`scripts/windscada_serve.py`) | 首请求把产物读进内存 `_CACHE`;每次取数前比**产物指纹**(相关文件的 `(路径, mtime_ns, size)` 摘要,TTL 2 s) | 指纹一变自动重载,**不用重启** | | CMS `:18020`(`src/windcms/serve.py`) | 直接读产物页面件 + `spectra_meta` 指纹(`src/windcms/data.py::_mtime_stamp`) | 同上 | | 门户 `:28084`(`scripts/guanlan_gateway.py`) | 门户按 `(mtime,size)` 缓存;契约结论段由 §13.1 的事实契约派生 | 需 `portal_build.py` 重装(⑧b) | | 运维控制台 `/ops`、`/ops/recalc` | 后端即真实状态;`Cache-Control: no-store` | 刷新即最新 | ### 13.4 产物管理的四条硬要求(机器执行) 1. **路径单一真源**:产物只落 `outputs/<场>/…`,代码只经 `src/paths.py` 取(§3)。 2. **逐件来源台账**:`outputs/<场>/_provenance.json` 记 `raw-derived`(由 `data/raw` 重算,含验证依据) 或 `shipped`(包内无生成端,用随包件补齐);`raw-derived` 另由 `_derived_manifest.json` 自登记。 3. **呼应校验**:`inventory_products.py --check` —— 产物跨度必须落在输入跨度内,rc≠0 即不符。 4. **页面侧产物同样要登记**:`configs/portal_pages.yaml` 里 `data-derived` 的页面件,其 `source`(产物路径) 必须在 `_provenance.json` 里出现,否则 `pages_audit.py` 报"未纳入产物台账"。 ### 13.5 没有生成端的那批(`shipped`)与补齐办法 `pitch_daily`、`pc_monthly_bins`、`duty_monthly`、`thermal_monthly`、`sector_power`、`yaw_*`、`genbearing_monthly`、 `mblub_monthly`、`structure.parquet`、`watch_channels_monthly`、振动 `handoff_vibration_v2.json` / `component_history.json` / `baseline_38.json`、`sop/**`、`paradigm_r1/**`、`guanlan/**`、CMS 页面件 —— 全库只有读取方、 0 处写入方(§7)。**唯一来源是"含产物的交付包"**: ``` python scripts/products_restore_missing.py --stash <含产物的包.zip> # 只补缺件, 不覆盖 raw 重算件 ``` 2026-09-17 实测:用旧版含产物包 `guanlan-rudong-v2_0.2.0_test_win64.zip` 一次补齐 **567 件**(其中 39 件是日志, 已按 §11.2 归位到 `logs/build/`),补齐后与旧包**逐字节相同 538 件**。 **教训(写进 §9 的口径里)**:交付包默认不含产物 + 清除产物不留备份 ⇒ 这批件**一旦清掉就没有来源**; 要留退路就用 `scripts/pack_dist.py --with-products` 打一份带产物的包存档。 --- ## 14. 服务化安装(Windows 服务 / systemd)与版本检查(2026-09-17 用户令 1) ### 14.1 装成什么 | 平台 | 形态 | 服务体 | 注册器 | 前置 | |---|---|---|---|---| | Windows | **Windows 服务**(SCM 托管,`SERVICE_WIN32_OWN_PROCESS`) | `scripts/win_service.py`(**ctypes 直连 advapi32,不依赖 pywin32**) | `scripts/service_ctl.py install`(内部 `sc.exe create/description/failure/start`) | 管理员 | | Linux | **systemd 单元** `guanlan.service`(`Type=simple` + `Restart=always`) | `scripts/service_main.py`(前台守护) | `scripts/service_ctl.py install`(写 `/etc/systemd/system/guanlan.service` + `systemctl enable --now`) | root | | Windows(备选) | 当前用户**登录自启**(`HKCU\...\Run`)——不需要管理员 | `scripts/guanlan_start_hidden.py`(无窗口启动器) | `scripts/service_ctl.py install --user-login` | 无 | **为什么 Windows 服务体不用 `sc create python xxx.py`**:SCM 要求被拉起的进程跟它握手 (`StartServiceCtrlDispatcher` + 控制处理回调),否则起服务报 **1053**。 实现它有两条路:`pywin32`(要加新依赖,破坏"单包离线零新依赖")或 **ctypes 直连**(本实现,只用标准库)。 **两平台共用一个工作体**(`scripts/service_worker.py`):起 = `guanlan.py serve`(幂等,缺谁补谁)、 守护 = 每 15 s 巡检一次同样命令(组件掉了自动拉起)、停 = `guanlan.py stop`。 这样"服务起的"与"手工起的"口径完全一致,不会出现两套行为。 ### 14.2 版本检查与重装确认(安装时) 版本**单一真源** = `src/version.py::VERSION`(打包器、安装脚本、`guanlan.py check`、文档都以它为准)。 安装脚本在**动手之前**做一次检查,读 `<安装根>/install-info.json`(上次安装写下的记录): | 情形 | 提示 | 动作 | |---|---|---| | 没装过 | 未检测到已安装 → 全新安装 vX | 直接装 | | 版本相同 | 已安装 vX,与本包**版本相同**;重装会重建 `.venv`、覆盖程序文件(`data/`、`outputs/` 不动) | **问**是否重装 | | 已装更旧 | v旧 → v新(**升级**) | **问**是否重装 | | 已装更新 | 已装 v新 > 本包 v旧 ⇒ **降级**警告:"除非确知原因,不要用旧包覆盖新装" | **问**(并明确警告) | | 老安装无版本记录 | 按"重装/升级"处理 | **问** | * 交互式:`Read-Host` / `read` 问 `y` 才继续;**非交互**(stdin 被重定向,例如自动化核验)自动继续并打印说明; * 跳过询问:`install.bat -Force`(PowerShell 侧 `-Force`)/ `sh install.sh --force`; * 装完写 `install-info.json`:`version / installed_at / python / mode(manual|windows-service|systemd|startup-entry)`; * `guanlan.py check` 增一行显示"已装版本 vs 本包版本"。 ### 14.3 没有权限时怎么办(本次实机就是这种情况) 本机当前账号**不是管理员**,所以"真注册服务"这一步无法在此验证。为此该功能自带三条**无权限自证**路径: ``` python scripts/service_ctl.py status # 装没装/在跑没在跑(免权限) python scripts/service_ctl.py install --dry-run # 前置检查 + 逐条打印将执行的命令 + 打印将写入的 systemd 单元全文 python scripts/win_service.py --debug --once # 免权限跑一轮守护(起组件→巡检→报"网关在听") python scripts/win_service.py # 直跑服务体: 报 "不是由 SCM 启动(WinError 1063)" ⇒ SCM 接口调用正确 ``` 权限不足时 `service_ctl.py` **不抛 Access is denied**,而是打印三条可执行建议(管理员重试 / 先 `--dry-run` / 改用 `--user-login`)并返回 rc=3。**实机输出**(本机,非管理员): ``` $ python scripts/service_ctl.py install --dry-run 前置检查: 通过 $ sc.exe create guanlan binPath= "…\.venv\Scripts\python.exe" "…\scripts\win_service.py" start= auto DisplayName= "观澜·如东样板 v2 …" $ sc.exe description guanlan "观澜·如东样板 v2 v0.4.0 本机离线版(观澜风电资产智能)" $ sc.exe failure guanlan reset= 86400 actions= restart/60000/restart/60000/restart/60000 $ sc.exe start guanlan $ python scripts/win_service.py --debug --once 一轮巡检结果: 网关在听 ``` ### 14.4 与"手工启动"的关系 装了服务之后**不必**再双击快捷方式:开机会自动起(Windows 服务)/ `systemctl start guanlan`(Linux)。 手工路径仍然可用(快捷方式、`start.bat`、`guanlan.py serve`),两者互不冲突—— 服务只是"代表整套系统守着"的那一个进程。停止:`service_ctl.py stop`(或 `stop.bat`)。 ### 14.5 卸载(`uninstall.bat` / `uninstall.sh`;2026-09-17 用户令) 入口两个壳脚本 + **同一份**实现 `scripts/guanlan_uninstall.py`(与 install.bat/install.sh→install.ps1 同构, 免得两平台各写一遍、过两个月行为不一致)。**默认只拆"本程序装上去的东西"**,顺序与依据: | 步骤 | 做什么 | 依据/注意 | |---|---|---| | ① 停服务 | `service_ctl.py stop` → `guanlan.py stop` | 先停常驻体再停手工起的;没注册过就跳过(幂等) | | ② 注销服务 | `sc.exe stop/delete guanlan` / `systemctl disable --now` + 删单元文件 | **需管理员/root**;缺权限时照做其余步骤并打印手工命令(rc=3) | | ③ 撤开机自启 | 删 `HKCU\...\Run\guanlan` / systemd 用户单元 + autostart | 不需要权限;注册表异常不中断整场卸载 | | ④ 删快捷方式 | `启动观澜.lnk`、`start_guanlan.lnk`(安装根 + 桌面 + `~/.local/share/applications`) | 与 §14.2 里 `install.ps1` 建的那两份一一对应 | | ⑤ 清运行态 | `run/`、残留 `*.pid` | `run/pids.json` 换台机器就是无效引用(§9 ③ 同理) | | ⑥ 删安装记录 | `install-info.json` | 它是"已装的是哪一版"的唯一凭据;删掉 = 这台机器回到"没装过",重装不再提示版本差异 | | ⑦ `--purge` | 额外删 `data/` `outputs/` `logs/` `run/` `runtime/` `.venv/` | **会再问一次**(默认答否);`--keep-logs` 留日志 | | ⑧ `--all` | 额外删程序本体(含整个安装根) | 尽力而为,删不掉的逐条列出 + 给一条收尾命令 | **两条设计取舍(都不是省事,是踩过/想过才这么定)**: 1. **默认不删数据、不删程序目录**。`data/`(原始件)、`outputs/`(产物)是现场资产,"卸载程序 ≠ 销毁数据"; 程序目录不自己删,是因为 Windows 上正在跑的 `python.exe` 就在 `.venv` 里,**删自己必然只删一半**, 留个"删了一半的目录"比留着整棵目录更难查。`uninstall.bat` 特意先 `cd /d "%TEMP%"` 再调 Python (Windows 不允许删除"某个活进程的当前目录",而 `--all` 要删的正是这棵目录;所有路径都用绝对路径, 不依赖 cwd),因此带 `--all` 时**能**连安装根一起删掉——实测 `安装根还在? False`。 2. **`--dry-run` 不是摆设,是自证口径**。`--selftest` 做两类断言:内容断言(六个步骤 + 保留清单一个不缺)、 副作用断言(跑完 dry-run 后 `install-info.json`/`run`/`data`/`outputs`/`logs`/`.venv` 的存在性与 字节数**完全不变**)。开箱验证(`pack_dist.py --verify` 第 ③b 步)会在**刚装好的副本**上真跑一遍 卸载器,并断言"安装记录已删 / 运行态已清 / 快捷方式已撤 / **数据与产物一件不少** / 端口已释放"。 实测证据(本机,2026-09-17):`--selftest` 全绿;在临时副本上真卸载 rc=0 且 `install-info.json`/`run/`/两个 `.lnk` 全被清掉、`data`+`outputs` 件数不变;带 BOM 的 `install-info.json`(PowerShell 的 `Set-Content -Encoding UTF8` 会写 BOM)现在也能读出来 (`read_installed` 改按 `utf-8-sig` 读 —— 带 BOM 按 utf-8 读会抛异常,后果是"明明装了却报没装过", 版本检查直接失灵)。 --- ## 15. 版本管理与打包命名(2026-09-17 用户令) ### 15.1 规则 编号 **`v<大版本号>.<中版本号>.<小版本号>`**,例:`v2.5.0`(本包即此版本)。 | 级别 | 含义(用户令原文) | |---|---| | 大版本号 | 系统解决方案、架构或核心功能改变 | | 中版本号 | 非核心功能新增、减少、修改 | | 小版本号 | 消缺完善 | **怎么改**:改动落在"解决方案/架构/核心功能" → 大 +1(中/小归 0);落在"非核心功能的新增/减少/修改" → 中 +1(小归 0);只是"消缺完善" → 小 +1;一次发布混了几类,**取最高那一级**。 机器判据是 `src/version.py::level_of(旧, 新)` → `major|minor|patch|same`。 **打包文件名** = `app_guanlang_v<版本号>.zip`,例:`app_guanlang_v2.5.0.zip` (`src/version.py::package_name()` 是唯一出处,打包器默认用它命名;`--out` 可另指定)。 ### 15.2 单一真源与三处机器守卫 版本号**只写在** `src/version.py` 一行(`VERSION`)。改一处,下面全部自动跟着变: ``` src/version.py ← 唯一真源: VERSION / RULE / HISTORY / package_name() scripts/pack_dist.py ← 读 V.VERSION 写进 dist-manifest.json 并拼默认包名(不再自己写死) install.ps1 / install.sh ← 读它写 <安装根>/install-info.json,并与已装版本比对(§14.2) guanlan.py version / check ← 打印版本与规则;check 里校验"版本记录 md 与代码一致" scripts/version_log.py ← 由 HISTORY 生成 docs/版本记录.md(人读的那份) ``` 三条守卫(都是"对不上就报错",不是靠人记得): 1. **`version_log.py --check`**:`docs/版本记录.md` 的自动块必须与 `HISTORY` 逐字一致(rc=6 表示要重生成) —— 防"包说 v2.5.0、记录表最后一行还是 v2.4.0"。`guanlan.py check` 有一行盯它。 2. **打包器读真源**:`pack_dist.py` 不再自带 `VERSION` 常量(此前它写着 `0.4.0`,与 README 抬头、 说明书标题三处并存,改一处忘一处就会出现"包说 0.4.0、安装记录说 0.2.0")。 3. **入口引用闭合**(§11.3 的既有守卫)现在也覆盖 `uninstall.bat` / `uninstall.sh`: 它们引用的 `scripts/guanlan_uninstall.py` 少打进包,后果是"**装得上、卸不掉**"。 ### 15.3 版本表 完整表格见 `docs/版本记录.md`(由 `scripts/version_log.py --write` 生成)。当前: | 版本 | 日期 | 级别 | 说明 | |---|---|---|---| | `v2.5.0` | 2026-09-17 | 中(第 5 次功能性发布) | 卸载闭环 + 版本管理与打包命名 + 版本记录;大版本 2 = 二代架构(与 `NAME` 里的 v2 对齐),小版本 0 = 无消缺项 | | `v0.4.0` | 2026-09-17 | 旧编号 | 打包默认不含数据/产物/日志/临时文件;无窗口启动;统一配置与日志;页面归口;输入数据检查;服务化 | | `v0.2.0` | 2026-09-01 | 旧编号 | 含产物的旧交付基线("包内无生成端"那批产物的补救源,已入库备份) | ★ 0.x 是**旧编号体系**(未按本规则),只作历史对账;从 `v2.5.0` 起按本节规则递增。 ### 13.6 反向呼应审计:自输出 → 功能与算法 → 输入(自动生成,勿手改) 台账 2236 件产物逐件回溯:**呼应成立 1742 件** · **不成立(无生成端)285 件** · 无法验证 0 件 · 未归类 0 件。判据:① 生成端在位 ② 输入在位 ③ 跨度 ⊆ 输入 / 机组集 ⊆ 输入 / 行数 > 0 / 计数与清单一致。 | 输出族(产物 glob) | 件数 | 反向判定 | 功能 | 算法 / 生成端 | 输入(data/raw/<场>/) | 判据 | 说明 | |---|---:|---|---|---|---|---|---| | `m5_cms_tcm/windows/*/index.parquet` | 1 | ✓ 呼应成立 | 振动摄入 · 窗索引 | scripts/rudong_tcm_index.py(逐 decode JSON 解析 54 列:turbine/sensor_name/meas_name/trigger_time/rpm/condition_key/alarm_type) | windcms | span+turbines+rows | 跨度 2026-03-16~2026-04-21 ⊆ 输入 2026-03~2026-04;机组 38 台 ⊆ 输入 38 台;件内数据行合计 2,066,686 | | `['m5_cms_tcm/windows/*/spectra/**', 'm5_cms_tcm/windows/*/spectra_meta.parquet']` | 1703 | ✓ 呼应成立 | 振动摄入 · 谱库 | scripts/rudong_tcm_spectra.py(FFT_ 测量 → npz 幅值数组, 分片存 `spectra/p/`) | windcms | npz | 1703 件谱(抽样 5 件 npz 均可读非空)· 输入源 25693 件 | | `m5_cms_tcm/vib_raw_manifest.json` | 1 | ✓ 呼应成立 | 振动摄入 · 清单 | scripts/vib_raw_build.py(汇总窗/谱件数、时间跨度、missing_chain 缺口) | windcms | manifest | 清单自记: farm=rudong, window=w0316, window_dir=outputs\rudong\m5_cms_tcm\windows\w0316 | | `windscada/alarms.parquet` | 1 | ✓ 呼应成立 | 三门台账 · 报警 | scripts/windscada_alarms_ingest.py(SpreadsheetML *.xls → 事件表) | 故障报警 | span+rows | 跨度 2025-01-01~2026-07-15 ⊆ 输入 2025-01~2026-12;件内数据行合计 39,211 | | `windscada/workorders.parquet` | 1 | ✓ 呼应成立 | 三门台账 · 工单 | scripts/windscada_workorder_ingest.py(检修台账 → 工单表) | 风机故障记录 | span+rows | 产物起点 2020-01-03 早于输入起点 2021-01(输入为年粒度, 只作参考: 台账可能含更早的历史行);跨度 2020-01-03~2026-07-08 ⊆ 输入 2021-01~2026-12;件内数据行合计 5,876 | | `windscada/oil_samples_index.parquet` | 1 | ✓ 呼应成立 | 三门台账 · 油样 | scripts/windscada_watch_channels_build.py(油样 PDF → 索引) | 油样报告 | span+rows | 跨度 2024-11-19~2025-08-29 ⊆ 输入 2024-11-19~2025-08-29;件内数据行合计 404 | | `windscada/temp_monthly.parquet` | 1 | ✓ 呼应成立 | 月度派生件 | scripts/windscada_monthly_build.py(逐值对齐随包件 19,494/19,494) | scada_10min | span+rows | 跨度 2025-01-01~2026-07-01 ⊆ 输入 2025-01-01 00:00:00~2026-07-07 00:00:00;件内数据行合计 19,494 | | `windscada/*.parquet` | 27 | ✓ 呼应成立 | SCADA 派生分析(功率曲线/损失/曲线/控制/停机/温度/偏航/液压/热链/系统辅助) | src/windscada/perf/{powercurve,availability,curves,control,faults} + subsys/{temp_nbm,yaw,hydraulic,thermal_chain} + taxonomy.py | scada_10min | rows | 件内数据行合计 70,283 | | `ontology/{objects.json,retrieval_index.json,turbine_params.parquet}` | 3 | ✓ 呼应成立 | 本体层 · 码表/对象库/检索索引/实机参数 | python -m src.ontology.kb_ingest → populate → chain_ingest → trend_ingest;src.ontology.retrieval.build;src.ontology.maintenance.refresh_params | 西门子4.0技术资料 | rows | 件内数据行合计 1,708 | | `['ontology/_id_alias.json', 'ontology/retrieval_vec.npy']` | 2 | ✓ 呼应成立 | 本体层 · 别名表与向量索引缓存 | src.ontology.kb_ingest(别名)/ src.ontology.retrieval.build(use_vec=True)(向量) | 西门子4.0技术资料 | rows | 件内数据行合计 2 | | `['ontology/oil_2026H2_huabiao.json', 'ontology/parts_cost_public.json', 'ontology/scenario_29_repair.json']` | 2 | ✗ 无生成端 + ◆ 人工件 1 | 本体层 · 领域数据件(油样台账/备件价格/场景修复) | (无生成端, 领域正本出件) | —(无生成端) | — | 领域正本随包发来, 不由 data/raw 推导 | | `m5_cms_tcm/*.{json,parquet,csv,md,txt}` | 36 | ✗ 无生成端 + ◆ 人工件 35 | 振动线出件与专项扫描(handoff / 历史 / 基线 / 各类 freq scan / 判级台账) | (无生成端) | —(无生成端) | — | 振动六层链四步脚本未随包(oem_frequency_scan 等),现场正本才可重出;现只能从交付包补齐 | | `m5_cms_tcm/figs/*` | 15 | ✗ 无生成端 | 振动线出图(谱图/趋势图) | (无生成端) | —(无生成端) | — | 随包快照(振动线出件) | | `windscada/{index.html,turbines/*,review/*}` | 39 | ✗ 无生成端 + ◆ 人工件 8 | 总览「全场状态」静态页 + 逐台页 + 评审记录 | (无生成端) | —(无生成端) | — | 页面件由振动线/历史分析出件,本包无写入方 | | `windcms/**` | 45 | ✗ 无生成端 + ◆ 人工件 7 | CMS 振动评估报告页/逐台页/缓存 | scripts/windcms.py report(本包缺 model_run/fusion 产物, 重生成会掉内容 −97%, 故**按设计跳过**) | —(无生成端) | — | 生成端在包内但输入不足 ⇒ 现状按随包件冻结 | | `sop/**` | 92 | ✗ 无生成端 + ◆ 人工件 110 | SOP 中间件/评审/事实契约底稿 | (无生成端) | —(无生成端) | — | 评审过程件, 属人工作业留痕 | | `tcm_compatible_replay/**` | 43 | ✗ 无生成端 + ◆ 人工件 15 | TCM 兼容链回放资产(模型表/掩码阈值/裁决记录) | (无生成端) | —(无生成端) | — | 随包快照 | | `paradigm_r1/**` | 8 | ✗ 无生成端 + ◆ 人工件 21 | 范式实验件(E3/E5/E8 底稿) | (无生成端) | —(无生成端) | — | 实验底稿, 事实契约的输入 | | `pitch/**` | 2 | ✗ 无生成端 | 变桨侧派生件(零位/日粒度) | (无生成端;rebuild_from_raw --scada 只覆盖其中一部分) | —(无生成端) | — | 随包快照 | | `ontology/release_*/*` | 2 | ✗ 无生成端 + ◆ 人工件 3 | 本体发布层 r1/r2(只读暴露给网关 /release/) | (无生成端) | —(无生成端) | — | 发布层清单, 由研发出件 | | `guanlan/**` | 1 | ✗ 无生成端 + ◆ 人工件 4 | 事实契约与对外派生(门户结论段/取数) | scripts/guanlan_facts_contract.py(读者在包内, 输入是 ontology+m5+sop 三处**产物**) | —(无生成端) | — | 派生链的中间层:它的"输入"本身是产物而非 data/raw ⇒ 与 raw 之间隔着不止一跳 | | `windscada/release-manifest.json` | 1 | ✓ 呼应成立 | 发布清单快照(`/healthz` 与版本信息读它) | scripts/guanlan_baseline_manifest.py(从 src/windscada/ui 源重建 zh 页 → 摘要 {css_tokens, nav, dom, sha256, source blobs}) | —(无生成端) | — | | | `['windscada/mblub_alarm_cross.json', 'windscada/review_presented.json', 'windscada/给振动线_温度轴回复_20260826.json', 'windscada/给振动线_温度轴回复_20260826.md', 'windscada/送审_系统结论与判定_20260828.md']` | 5 | ◆ 人工件 | **人工正本/交证件**:与振动线的跨线通知、送审结论、发布评审快照 | (人写的, 不是算出来的) | —(无生成端) | — | 这几件是"人对人的交证件/回执", 内容由分析结论抄写与人判断坐实而成; mblub_alarm_cross.json 还被 src/ontology/populate.py 当作**证据来源**引用 ⇒ 必须随包留在原位, 但**不参与呼应校验**(它本来就没有生成端, 也不该有) | #### 反向「可逆性」:没有呼应关系的那批,能否从原始件推出来(用户令 2026-09-17 #2) 判据两条机器证据:① 生成端在不在包内 ② 产物正文里有没有**人工判断**字段(`人工/裁决/审核/校准/评审/经验/judge/review/calibrat/verdict`,抽样读件统计命中率)。含人工判断的件不是任何输入的纯函数 ⇒ 逆向工程推不出来,只能把那一步人工工作重做。 | 输出族 | 件数 | 可逆性 | 依据 | |---|---:|---|---| | `ontology_domain` | 2 | 半可逆(需人工裁定) | 抽样里 33% 的件含人工判断字段 ⇒ 机器部分可推、判断部分要人定;人工判断字段抽样命中 33%(如 ontology/parts_cost_public.json) | | `vib_handoff_and_scans` | 36 | 混合(测量件可反推 / 文本件需人定) | (23 件测量形态 / 13 件文本·判断件)测量形态的件可由 data/raw 反推口径 + 逐值对拍; 文本/判断件(评审、裁决、回复)推不出来;人工判断字段抽样命中 33%(如 m5_cms_tcm/OEM特征频率复跑_2026-08-17.md) | | `vib_figs` | 15 | 需人工裁定 | 既无生成端、形态也不是测量函数 ⇒ 需研发给口径或领域正本 | | `windscada_pages` | 39 | 不可逆(人工判断) | 抽样 6 件里都写着人工判断字段(如 windscada/review/_sealed_predictions_20260826.md) ⇒ 不是输入的纯函数, 逆向工程推不出来;人工判断字段抽样命中 85%(如 windscada/review/_sealed_predictions_20260826.md) | | `windcms_pages` | 45 | 可逆(页面可再生) | 这一族主要是**页面**(.html): 页面是产物的渲染, 不是独立数据 ⇒ 接上在包的页面生成端重出即可(未必逐字节同, 能力与数据一致);人工判断字段抽样命中 50%(如 windcms/kb.json) | | `sop_workspace` | 92 | 半可逆(需人工裁定) | 抽样里 37% 的件含人工判断字段 ⇒ 机器部分可推、判断部分要人定;人工判断字段抽样命中 37%(如 sop/_dual_review_20260726_payload脱敏.md) | | `tcm_replay` | 43 | 不可逆(人工判断) | 抽样 3 件里都写着人工判断字段(如 tcm_compatible_replay/claude_handoff_20260823/Codex回复_rms200_mask_20260823.md) ⇒ 不是输入的纯函数, 逆向工程推不出来;人工判断字段抽样命中 75%(如 tcm_compatible_replay/claude_handoff_20260823/Codex回复_rms200_mask_20260823.md) | | `paradigm_r1` | 8 | 不可逆(人工判断) | 抽样 5 件里都写着人工判断字段(如 paradigm_r1/experiments/E3_candidate_closure/plan.md) ⇒ 不是输入的纯函数, 逆向工程推不出来;人工判断字段抽样命中 100%(如 paradigm_r1/experiments/E3_candidate_closure/plan.md) | | `pitch_shipped` | 2 | 可逆(需反推口径) | 测量形态的件是测量数据的函数 ⇒ 可从 data/raw 反推口径 + 逐值对拍(做法同 temp_monthly: 反推 → 逐值一致才敢用) | | `ontology_releases` | 2 | 不可逆(人工判断) | 抽样 3 件里都写着人工判断字段(如 ontology/release_r1/manifest.json) ⇒ 不是输入的纯函数, 逆向工程推不出来;人工判断字段抽样命中 60%(如 ontology/release_r1/manifest.json) | | `guanlan_contract` | 1 | 不可逆(人工判断) | 抽样 4 件里都写着人工判断字段(如 guanlan/derived/detail_cards.json) ⇒ 不是输入的纯函数, 逆向工程推不出来;人工判断字段抽样命中 80%(如 guanlan/derived/detail_cards.json) | | `human_deliverables` | 5 | 人工件(正本/交证) | 人写的, 不参与呼应校验; 随包留在原位 | **按件数汇总**:已在重算链上 = 1742 件 · 半可逆(需人工裁定) = 94 件 · 不可逆(人工判断) = 93 件 · 可逆(页面可再生) = 45 件 · 混合(测量件可反推 / 文本件需人定) = 36 件 · 需人工裁定 = 15 件 · 人工件(正本/交证) = 5 件 · 可逆(需反推口径) = 2 件 **人工件**:含人工判断字段的件已逐件登记在 outputs/<场>/_human_artifacts.json(用户令 2026-09-17 #3),它们**不参与**呼应校验(人写成/坐实的东西不是任何输入的纯函数);生成方式:python scripts/products_reverse_audit.py --write-human-manifest。 > 结论口径:`可逆(直接)` = 包内已有生成端,接上即可;`可逆(需反推口径)` = 内容是测量数据的函数,照 `temp_monthly` 的办法反推 + 逐值对拍;`不可逆(人工判断)` = 逆向工程推不出来,要么由研发补生成端、要么承认它是人工件(不该按产物管)。