Bläddra i källkod

用户令 #2 的实测答复: 六层链接口规格与缺口文档; 人工件判据扩到 .md 与随包快照(台账 183→209)

不凭猜测写实现(本项目硬规矩: 允许响亮降级, 不许造数), 先把"能不能推"查清楚:
· 接口在壳上: src/windcms/pipeline.py::analyze 写死了四步怎么调(rudong_tcm_oem_scan /
  rudong_line_energy_share / rudong_model_run / rudong_fusion_run, 无参数, 靠 M5_WINDOWS/SCAN_BANDS
  环境变量); 实测 windcms.py analyze --steps model_run → rc=2
  "can't open file scripts/rudong_model_run.py" ⇒ 缺的是实现, 不是接口。
· 对拍缺标准答案: 契约(src/windcms/config.py)要 model_run_l6.parquet 与 fusion_38.csv, 而这两件
  **从未随过包** ⇒ model_run / fusion 两步无法逐值对拍; oem_scan 侧的 ~23 件测量件有样件,
  可照 temp_monthly 的老办法(反推口径 → 逐值对拍 → 一致才敢用)。
· handoff_vibration_v2.json 实为**人工交证件**(meta 版本/交接说明 + per_turbine 人工裁决 +
  calibration_updates_v1 + open_items + tcm16_schedule) ⇒ 逆向工程对它无意义(推出来的只是同义改写),
  已按用户令 #3 登记为人工件。
· 新增 docs/振动六层链_接口规格与缺口_v0.1.md: 四步调用表 + 产物契约 + 逐步骤"能否对拍"判定 +
  给研发的最小清单(四个脚本源码 + model_run_l6/fusion_38 任一份历史产出)。

人工件判据两条扩充(用户令 #3): 产物仓里的 .md 一律算人工交付文档; docs §7 已写明含人工裁决/校准的
随包快照(component_history / baseline_38 / handoff_vibration_v1 / findings)显式登记
⇒ 台账 183 → **209 件**; 判定变为 1742 成立 / **285 不成立** / 0 无法验证 / 0 未归类。

顺带: analyze 的运行摘要 JSON 按日志口径落 logs/audit/windcms_analyze.json(原落 logs/build 下会被
日志审计判 rc=6), plugins.py 读侧同步; 复跑确认, log_audit 转绿。
zhouyang.xie 3 veckor sedan
förälder
incheckning
4e7e4a03e0

+ 76 - 0
docs/振动六层链_接口规格与缺口_v0.1.md

@@ -0,0 +1,76 @@
+# 振动六层链 —— 接口规格与缺口(v0.1,2026-09-17)
+
+> 用途:用户令 2026-09-17 #2「无生成端的那批,能否根据旧包产物通过逆向工程推导实现?」的**实测答复**。
+> 结论一句话:**接口已经写在包里的"壳"上,实现缺失;而"能否对拍验证"这件事,缺的正是标准答案** ——
+> 六层链最关键的两件产物(`model_run_l6.parquet`、`fusion_38.csv`)**从未随过包**,无从逐值对拍。
+> 因此本轮**没有**凭猜测写实现(本项目的硬规矩:允许响亮降级,**不许造数**)。
+
+---
+
+## 1. 壳在包内:四步怎么调、传什么,代码里已经写死了
+
+`src/windcms/pipeline.py::analyze()` 就是那条链的编排(`scripts/windcms.py analyze` 是它的命令行入口):
+
+| 步骤名 | 实际执行的脚本 | 参数 | 环境变量 |
+|---|---|---|---|
+| `ingest` | `scripts/rudong_tcm_index.py` + `scripts/rudong_tcm_spectra.py` | `--root <导出> --out <窗>/index.parquet` / `--out <窗>/spectra` | — |
+| `oem_scan` | **`scripts/rudong_tcm_oem_scan.py`**(缺) | 无 | `M5_WINDOWS`(逗号分隔窗清单)、`SCAN_BANDS` |
+| `energy_share` | **`scripts/rudong_line_energy_share.py`**(缺) | 无 | 同上 |
+| `model_run` | **`scripts/rudong_model_run.py`**(缺) | 无 | 同上 |
+| `fusion` | **`scripts/rudong_fusion_run.py`**(缺) | 无 | 同上 |
+| `report` | `scripts/windcms.py report --farm <场>` | — | — |
+| `kb` | `scripts/windcms.py kb --farm <场>` | — | — |
+
+**实测证据**(2026-09-17):
+
+```
+$ python scripts/windcms.py analyze --window w0316 --steps model_run
+[model_run] rc=2 0.1s
+python.exe: can't open file '…\scripts\rudong_model_run.py': [Errno 2] No such file or directory
+```
+
+⇒ 缺的是**实现**,不是接口。每步该调谁、该吃哪个环境变量,壳里一条不落。
+
+## 2. 产物契约(`src/windcms/config.py` 里写明的路径)
+
+```
+'fusion'        : outputs/<场>/m5_cms_tcm/fusion_38.csv          ← 缺(从未随包)
+'model_l6'      : outputs/<场>/m5_cms_tcm/model_run_l6.parquet  ← 缺(从未随包)
+'model_summary' : outputs/<场>/m5_cms_tcm/model_run_summary.json ← 在(随包件,已按人工件归类)
+'fleet_z'       : outputs/<场>/m5_cms_tcm/fleet_scalar_z.parquet ← 在(随包件)
+'baseline'      : outputs/<场>/m5_cms_tcm/baseline_38.json       ← 在(随包件,含人工坐实的两层基线)
+```
+
+## 3. 为什么"逆向工程 + 逐值对拍"这条路在这四步上走不通
+
+逐值对拍的前提是**有标准答案**。四步里:
+
+| 步骤 | 产物 | 盘上有没有样件 | 能否对拍 |
+|---|---|---|---|
+| `oem_scan` | `oem_frequency_scan.parquet`、`bearing_freq_scan.parquet`、`gear_freq_scan.parquet`、`cage_slip_scan.parquet`、`blade_1p_*.{parquet,csv}` 等 | **在**(约 23 件测量形态件) | 有样件 ⇒ **可以对拍**,但口径(扫哪些频带、窗如何合并、阈值)得先反推 |
+| `energy_share` | 未见同名产物 | 未识别 | 无样件 ⇒ 不可对拍 |
+| `model_run` | `model_run_l6.parquet`(+摘要 json) | **缺** | **无标准答案** ⇒ 只能凭口径实现,无法证明"复现" |
+| `fusion` | `fusion_38.csv`(+ `handoff_vibration_v2.json`) | `fusion_38.csv` **缺**;`handoff_vibration_v2.json` 在,但**它是人工交证件**(见 §4) | 同上 ⇒ 不可对拍 |
+
+## 4. `handoff_vibration_v2.json` 不是"算出来的产物",而是**人工交证件**
+
+实测其结构:`meta`(version/date/from/to/supersedes/principle/**v2.1_changes**/**v2.2_changes**/
+★消费端接线约定/★派生件同步纪律)、`detectability_prior`、`cross_source_requests`、`baseline_三层`、
+`per_turbine`(v1_generator / v1_gearbox 的**人工裁决** + claim_window)、`field_actions_v1`、
+`calibration_updates_v1`、`open_items`、`tcm16_schedule`。
+
+⇒ 它由振动线**人写**交给 windscada 线,正文里就是判断、承诺与待办。**逆向工程对它无意义**
+("推"出来的只是同义改写)。它已按用户令 2026-09-17 #3 登记进人工件台账
+`outputs/<场>/_human_artifacts.json`(`kind=human`),**不参与呼应校验**。
+
+## 5. 结论与待办(给研发/振动线的最小清单)
+
+1. **四个脚本的源码**(`rudong_tcm_oem_scan.py` / `rudong_line_energy_share.py` / `rudong_model_run.py` /
+   `rudong_fusion_run.py`)。接口已知(§1),放进 `scripts/` 即可被壳调用。
+2. **两件标准答案**:`model_run_l6.parquet` 与 `fusion_38.csv` 的**任一份历史产出**(哪一窗都行)。
+   有了它,`oem_scan` 侧那 23 件测量件就能照 `temp_monthly` 的老办法(反推口径 → 逐值对拍 → 一致才敢用)
+   转成"✓ 已在重算链上";没有它,实现出来也**无法证明**复现,只能当成"另一个算法"。
+3. 在那之前,系统按现口径如实呈现:这 36 件(`vib_handoff_and_scans` 里非人工的那部分)标 **✗ 无生成端**,
+   页面缺件时给结构化说明(不静默、不造数、不从交付包补)。
+
+> 台账现状(2026-09-17):✓ 呼应成立 1742 件 · ✗ 无生成端 285 件 · ◆ 人工件 209 件 · 无法验证 0 · 未归类 0。

+ 14 - 14
docs/系统设计说明.md

@@ -1101,7 +1101,7 @@ scripts/version_log.py             ← 由 HISTORY 生成 docs/版本记录.md
 <!-- REVERSE-AUDIT:BEGIN -->
 ### 13.6 反向呼应审计:自输出 → 功能与算法 → 输入(自动生成,勿手改)
 
-台账 2236 件产物逐件回溯:**呼应成立 1742 件** · **不成立(无生成端)311 件** · 无法验证 0 件 · 未归类 0 件。判据:① 生成端在位 ② 输入在位 ③ 跨度 ⊆ 输入 / 机组集 ⊆ 输入 / 行数 > 0 / 计数与清单一致。
+台账 2236 件产物逐件回溯:**呼应成立 1742 件** · **不成立(无生成端)285 件** · 无法验证 0 件 · 未归类 0 件。判据:① 生成端在位 ② 输入在位 ③ 跨度 ⊆ 输入 / 机组集 ⊆ 输入 / 行数 > 0 / 计数与清单一致。
 
 | 输出族(产物 glob) | 件数 | 反向判定 | 功能 | 算法 / 生成端 | 输入(data/raw/<场>/) | 判据 | 说明 |
 |---|---:|---|---|---|---|---|---|
@@ -1116,13 +1116,13 @@ scripts/version_log.py             ← 由 HISTORY 生成 docs/版本记录.md
 | `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}` | 45 | ✗ 无生成端 + ◆ 人工件 26 | 振动线出件与专项扫描(handoff / 历史 / 基线 / 各类 freq scan / 判级台账) | (无生成端) | —(无生成端) | — | 振动六层链四步脚本未随包(oem_frequency_scan 等),现场正本才可重出;现只能从交付包补齐 |
+| `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/*}` | 41 | ✗ 无生成端 + ◆ 人工件 6 | 总览「全场状态」静态页 + 逐台页 + 评审记录 | (无生成端) | —(无生成端) | — | 页面件由振动线/历史分析出件,本包无写入方 |
-| `windcms/**` | 46 | ✗ 无生成端 + ◆ 人工件 6 | CMS 振动评估报告页/逐台页/缓存 | scripts/windcms.py report(本包缺 model_run/fusion 产物, 重生成会掉内容 −97%, 故**按设计跳过**) | —(无生成端) | — | 生成端在包内但输入不足 ⇒ 现状按随包件冻结 |
-| `sop/**` | 102 | ✗ 无生成端 + ◆ 人工件 100 | SOP 中间件/评审/事实契约底稿 | (无生成端) | —(无生成端) | — | 评审过程件, 属人工作业留痕 |
-| `tcm_compatible_replay/**` | 45 | ✗ 无生成端 + ◆ 人工件 13 | TCM 兼容链回放资产(模型表/掩码阈值/裁决记录) | (无生成端) | —(无生成端) | — | 随包快照 |
-| `paradigm_r1/**` | 10 | ✗ 无生成端 + ◆ 人工件 19 | 范式实验件(E3/E5/E8 底稿) | (无生成端) | —(无生成端) | — | 实验底稿, 事实契约的输入 |
+| `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 之间隔着不止一跳 |
@@ -1136,19 +1136,19 @@ scripts/version_log.py             ← 由 HISTORY 生成 docs/版本记录.md
 | 输出族 | 件数 | 可逆性 | 依据 |
 |---|---:|---|---|
 | `ontology_domain` | 2 | 半可逆(需人工裁定) | 抽样里 33% 的件含人工判断字段 ⇒ 机器部分可推、判断部分要人定;人工判断字段抽样命中 33%(如 ontology/parts_cost_public.json) |
-| `vib_handoff_and_scans` | 45 | 混合(测量件可反推 / 文本件需人定) | (23 件测量形态 / 22 件文本·判断件)测量形态的件可由 data/raw 反推口径 + 逐值对拍; 文本/判断件(评审、裁决、回复)推不出来;人工判断字段抽样命中 33%(如 m5_cms_tcm/OEM特征频率复跑_2026-08-17.md) |
+| `vib_handoff_and_scans` | 36 | 混合(测量件可反推 / 文本件需人定) | (23 件测量形态 / 13 件文本·判断件)测量形态的件可由 data/raw 反推口径 + 逐值对拍; 文本/判断件(评审、裁决、回复)推不出来;人工判断字段抽样命中 33%(如 m5_cms_tcm/OEM特征频率复跑_2026-08-17.md) |
 | `vib_figs` | 15 | 需人工裁定 | 既无生成端、形态也不是测量函数 ⇒ 需研发给口径或领域正本 |
-| `windscada_pages` | 41 | 不可逆(人工判断) | 抽样 6 件里都写着人工判断字段(如 windscada/review/_sealed_predictions_20260826.md) ⇒ 不是输入的纯函数, 逆向工程推不出来;人工判断字段抽样命中 85%(如 windscada/review/_sealed_predictions_20260826.md) |
-| `windcms_pages` | 46 | 可逆(页面可再生) | 这一族主要是**页面**(.html): 页面是产物的渲染, 不是独立数据 ⇒ 接上在包的页面生成端重出即可(未必逐字节同, 能力与数据一致);人工判断字段抽样命中 33%(如 windcms/kb.json) |
-| `sop_workspace` | 102 | 半可逆(需人工裁定) | 抽样里 37% 的件含人工判断字段 ⇒ 机器部分可推、判断部分要人定;人工判断字段抽样命中 37%(如 sop/_dual_review_20260726_payload脱敏.md) |
-| `tcm_replay` | 45 | 不可逆(人工判断) | 抽样 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` | 10 | 不可逆(人工判断) | 抽样 5 件里都写着人工判断字段(如 paradigm_r1/experiments/E3_candidate_closure/plan.md) ⇒ 不是输入的纯函数, 逆向工程推不出来;人工判断字段抽样命中 100%(如 paradigm_r1/experiments/E3_candidate_closure/plan.md) |
+| `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 件 · 半可逆(需人工裁定) = 104 件 · 不可逆(人工判断) = 99 件 · 可逆(页面可再生) = 46 件 · 混合(测量件可反推 / 文本件需人定) = 45 件 · 需人工裁定 = 15 件 · 人工件(正本/交证) = 5 件 · 可逆(需反推口径) = 2 件
+**按件数汇总**:已在重算链上 = 1742 件 · 半可逆(需人工裁定) = 94 件 · 不可逆(人工判断) = 93 件 · 可逆(页面可再生) = 45 件 · 混合(测量件可反推 / 文本件需人定) = 36 件 · 需人工裁定 = 15 件 · 人工件(正本/交证) = 5 件 · 可逆(需反推口径) = 2 件
 
 **人工件**:含人工判断字段的件已逐件登记在 outputs/&lt;场&gt;/_human_artifacts.json(用户令 2026-09-17 #3),它们**不参与**呼应校验(人写成/坐实的东西不是任何输入的纯函数);生成方式:python scripts/products_reverse_audit.py --write-human-manifest。
 

+ 18 - 2
scripts/products_reverse_audit.py

@@ -254,6 +254,16 @@ def _npz_ok(p: pathlib.Path) -> bool:
         return False
 
 
+# 已知"含人工裁决/校准"的随包快照(按用户令 2026-09-17 #3 显式登记为人工件)。依据是文档里已经写明的性质:
+# docs §7「handoff_vibration_v2.json / component_history.json 是随包快照, 该件含人工裁决/校准更新,
+# 不是测量数据的函数」; baseline_38.json 的三层基线 self/absolute 两层由人工坐实; findings.json 是判级发现台账。
+HUMAN_SNAPSHOTS = {
+    'm5_cms_tcm/component_history.json',
+    'm5_cms_tcm/baseline_38.json',
+    'm5_cms_tcm/handoff_vibration_v1.json',
+    'm5_cms_tcm/findings.json',
+}
+
 # ── 逆向工程可行性 (用户令 2026-09-17 #2) ────────────────────────────────────────────
 # 问的是: "这批没有生成端的产物, 能不能从原始件/在包生成端**推**出来?"
 # 判定不靠印象, 靠两条机器证据:
@@ -368,8 +378,14 @@ def classify_human(farm: str, rels: list[str], cap: int = 600) -> set[str]:
     """
     human: set[str] = set()
     for rel in rels[:cap]:
+        if rel in HUMAN_SNAPSHOTS:            # ① 文档里已写明含人工裁决/校准的随包快照
+            human.add(rel)
+            continue
         suf = pathlib.Path(rel).suffix.lower()
-        if suf in ('.md', '.txt'):
+        if suf == '.md':                      # ② 产物仓里的 .md 一律是报告/交证/评审记录(人写的)
+            human.add(rel)
+            continue
+        if suf == '.txt':
             pat = JUDGE_PAT
         elif suf in ('.json', '.csv'):
             pat = JUDGE_KEY_PAT
@@ -380,7 +396,7 @@ def classify_human(farm: str, rels: list[str], cap: int = 600) -> set[str]:
             txt = p.read_text(encoding='utf-8', errors='replace')[:120000]
         except Exception:
             continue
-        if pat.search(txt):
+        if pat.search(txt):                   # ③ 正文里有"人工判断字段"
             human.add(rel)
     return human
 

+ 1 - 1
src/windcms/pipeline.py

@@ -148,7 +148,7 @@ def analyze(cfg, input_path=None, window=None, steps=None, bands='ALL'):
     #   `logs/build/<场>/windcms/analyze_log.json`; 产物仓里的旧件已被清理。
     #   日志里有中文命令回显: 缺 encoding 会按系统 locale(cp936) 写 → 中文 Windows 崩
     from src import logfile as _lf
-    logp = _lf.build_log('windcms/analyze_log.json', cfg.get('key', 'rudong'))
+    logp = _lf.audit_log('windcms_analyze.json')   # 审计/报告类 JSON 按口径落 logs/audit/
     logp.parent.mkdir(parents=True, exist_ok=True)
     with open(logp, 'w', encoding='utf-8', newline='\n') as f:
         json.dump(summary, f, ensure_ascii=False, indent=1)

+ 1 - 1
src/windcms/plugins.py

@@ -396,7 +396,7 @@ def job_status(ctx):
     # ★ 2026-09-17 用户令 2: analyze 的运行日志与结果都改落 logs/build/ (原先落在产物仓 out/ 下);
     #   读侧同步改, 并保留旧路径兜底 —— 老机器上可能还留着那时的件, 不该一改就显示"无运行日志"。
     from src import logfile as _lf
-    _b = _lf.build_log('windcms/analyze_log.json', ctx['cfg'].get('key', 'rudong')).parent
+    _b = _lf.audit_log('windcms_analyze.json').parent          # logs/audit/
     logp = (_b / 'analyze_stdout.log') if (_b / 'analyze_stdout.log').exists() else (out / 'analyze_stdout.log')
     lp = (_b / 'analyze_log.json') if (_b / 'analyze_log.json').exists() else (out / 'analyze_log.json')
     # 日志与结果都是中文文本: 不写 encoding 会按系统 locale(cp936) 读 → 乱码/解码失败