Переглянути джерело

fix(export): Node Temperatures 4-column section misaligned (header 0[25], part names in data)

- parse_export_full remaps the 4-col layout (node_id;name;value;unit) to
  (name, value, unit); category translated to Chinese
- backfilled plan 33 raw_json from on-disk raw exports (no re-run needed)
- regression tests +2 (52 total, all pass); re-export verified
  Ambient[°C]=25, Housing[°C]=40.02, no 0[25] residue
carlin 12 годин тому
батько
коміт
4df84b67fb

+ 8 - 0
docs/CONVERSATION_LOG.md

@@ -1200,3 +1200,11 @@ ambient_temperature=25.0)` 单点端到端 PASS——7 项热指标落盘,温
 **故障实录**:首跑任务因 EM 导出含 "INF"(恒转矩转速限值字段)JSON 上报被拒,任务卡 running——修复后重跑通过。旧任务卡单已取消。
 **验证**:测试 50 项全过;真实任务 fcc12b6c 端到端 897 字段入库、导出 885 列 xlsx。
 **遗留**:旧任务无 raw 存档(向后兼容,仅归一化列);Web 结果表格仍只显示归一化指标(设计如此)。
+
+## 2026-09-04 — 节点温度段错位修复(TEST-067 续)
+
+**用户报障**:导出"热仿真-Node Temperatures"表头 0[25]、1[40.02],数据行是部件名(Ambient/Housing)。
+**根因**:该段 4 列布局 `节点ID;部件名;温度;单位`,parse_export_full 按标准 3 列解析导致错位。
+**修复**:parse_export_full 对该段重映射(name=部件名, value=温度, unit=单位);类目译"节点温度";用磁盘原始 CSV 重解析回填 plan 33 raw_json(免重跑)。
+**验证**:+2 回归(共 52 全过);重导出 Ambient[°C]=25、Housing[°C]=40.02 正确,无 0[25] 残留。
+**坑**:Motor-CAD 导出不同段列布局不一致(3 列键值 / 4 列节点表 / 矩阵表),全量解析必须分段处理。

+ 7 - 0
docs/TEST_RECORDS.md

@@ -2417,6 +2417,13 @@ solver.run_single_point(thermal_mode)(coupled 分支调 do_magnetic_thermal_ca
 
 导出现在是无损全量:归一化关键指标列在前(供快速阅读),Motor-CAD 原生全字段在后(供深度分析),电磁+热仿真数据同表。旧任务无 raw_flat 存档则导出仅含归一化列(向后兼容)。
 
+### 热仿真-节点温度段修复(同日 16:20,用户报障)
+
+- **报障**:导出文件"热仿真-Node Temperatures"段表头为 `0[25]`、`1[40.02]`,数据行却是部件名(Ambient/Housing)——行列错位。
+- **根因**:该段是 4 列布局 `节点ID;部件名;温度;单位`,与标准 3 列 `字段;值;单位` 不同,parse_export_full 按 3 列解析导致 ID 入表头、温度入单位、部件名入数据。
+- **修复**:parse_export_full 对该段重映射为 (name=部件名, value=温度, unit=单位);导出类目"Node Temperatures"中文化为"节点温度";并用磁盘原始导出文件重解析回填了 plan 33 的 raw_json(免重跑)。
+- **验证**:回归测试 +2(重映射正确、无 ID 泄漏);重导出确认 `Ambient[°C]=25`、`Housing[°C]=40.02`,无 `0[25]` 样式残留。共 52 项全过。
+
 ### 结论
 
 工程师最关心指标(转矩/脉动/效率/损耗分解/电气/温度)在 Web 表格默认可视,完整结果一键导出 xlsx,格式与 torqrippswap 一致。

+ 18 - 0
scripts/test_xlsx_export.py

@@ -340,6 +340,24 @@ def api_tests() -> None:
     check("full: parse_export skips INF",
           all(_math.isfinite(v) for v in flat_vals))
 
+    # Node Temperatures 4-column layout: "node_id;name;value;unit" must be
+    # remapped to (name, value, unit) - regression for 2026-09-04 user report
+    # where headers became "0[25]" and data cells held part names.
+    nt_csv = Path(tempfile.mkdtemp()) / "node_temps.csv"
+    nt_csv.write_text(
+        "Node Temperatures\n"
+        "0;\"Ambient\";25;\u00b0C\n"
+        "1;\"Housing\";39.99;\u00b0C\n",
+        encoding="utf-8",
+    )
+    nt_rows = parse_export_full(nt_csv)
+    check("full: node temps remapped",
+          nt_rows[0]["name"] == "Ambient"
+          and nt_rows[0]["value"] == 25.0
+          and nt_rows[0]["unit"] == "\u00b0C")
+    check("full: node temps no id leak",
+          all(r["name"] != "0" for r in nt_rows))
+
 
 if __name__ == "__main__":
     unit_tests()

+ 6 - 0
src/afmcore/metrics.py

@@ -805,6 +805,12 @@ def parse_export_full(path: str | Path) -> List[Dict[str, Any]]:
         if section.startswith("Node to Node"):
             continue
         parts = [p.strip().strip('"') for p in line.split(";")]
+        # "Node Temperatures" section uses a 4-column layout:
+        #   node_id ; node_name ; value ; unit
+        # Remap to (name=node_name, value, unit) so the node id does not
+        # leak into the column header and the temperature into the unit.
+        if section == "Node Temperatures" and len(parts) >= 4:
+            parts = [parts[1], parts[2], parts[3]]
         field = parts[0]
         if not field:
             continue

+ 1 - 0
web/backend/app/routers/plans.py

@@ -501,6 +501,7 @@ def _collect_export_rows(plan, results):
             sec, _, nm = key[len("__raw__"):].partition("|")
             entry_unit = raw_units.get(f"{sec}|{nm}", "")
             category = sec.replace("Thermal-", "热仿真-")
+            category = category.replace("Node Temperatures", "节点温度")
             header = f"{nm}[{entry_unit}]" if entry_unit else nm
             # Disambiguate duplicate headers (same field name exported in
             # two sections, e.g. 系统效率 in 驱动 and 电磁).