Pārlūkot izejas kodu

feat(export): Chinese headers for all fields + field-mapping sheet

- new afmcore/export_field_zh.py: 3-layer translation (exact dict ~120
  entries / pattern rules T-Rt-Cap-h-Area-dT-Tc + HousingWJ + arrows /
  component lexicon + node-temp tokens); passthrough when no rule matches
- export headers now fully Chinese with units (461/461 english fields on
  real data, 0 residue)
- xlsx gains a '字段对照' sheet: col#/category/Chinese header/native
  Motor-CAD field/unit for all 885 columns incl. metric keys
- tests: 66 checks all pass; live re-export verified
carlin 10 stundas atpakaļ
vecāks
revīzija
6c2408a7a3

+ 7 - 0
docs/CONVERSATION_LOG.md

@@ -1208,3 +1208,10 @@ ambient_temperature=25.0)` 单点端到端 PASS——7 项热指标落盘,温
 **修复**: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 列节点表 / 矩阵表),全量解析必须分段处理。
+
+## 2026-09-04 — 导出表头中文化 + 字段对照(TEST-068)
+
+**用户要求**:导出表头英文字段翻译成中文;且要能与 Motor-CAD 原始导出对应(防后续人员弄错)。
+**本次完成**:① 新建 afmcore/export_field_zh.py 三层翻译(精确字典/模式规则/部件词典+节点token),461/461 英文字段全覆盖,无命中原样保留不杜撰;② 导出接入翻译;③ xlsx 新增"字段对照"sheet(列号/类目/中文表头/原始字段/单位,885 行,含 metric key 对照)。
+**验证**:66 项测试全过;真实导出 885 列 0 英文残留;对照关系抽查正确。
+**坑**:原始导出有双空格字段名(Cap [Endcap  Outer (F)]),翻译前需空白归一化;Rt 后缀有 Con/Conv/Radn/Rad 及 "(Total Losses)" 限定语两种形态。

+ 24 - 0
docs/TEST_RECORDS.md

@@ -2424,6 +2424,30 @@ solver.run_single_point(thermal_mode)(coupled 分支调 do_magnetic_thermal_ca
 - **修复**:parse_export_full 对该段重映射为 (name=部件名, value=温度, unit=单位);导出类目"Node Temperatures"中文化为"节点温度";并用磁盘原始导出文件重解析回填了 plan 33 的 raw_json(免重跑)。
 - **验证**:回归测试 +2(重映射正确、无 ID 泄漏);重导出确认 `Ambient[°C]=25`、`Housing[°C]=40.02`,无 `0[25]` 样式残留。共 52 项全过。
 
+---
+
+## TEST-068:导出表头中文化 + 中英字段对照表
+
+| 项目 | 内容 |
+|---|---|
+| 测试日期 | 2026-09-04 |
+| 测试目的 | 用户要求:导出表头里的英文字段翻译成中文,且必须保留与 Motor-CAD 原始导出文件的对应关系,避免后续人员对照时弄错 |
+
+### 实现
+
+1. **新建 `src/afmcore/export_field_zh.py`**(ASCII 源码):三层翻译策略——① 精确字典(电磁/损耗/绕组等 ~120 条);② 模式规则(T[]→温度、Rt[]→热阻、Cap[]→热容、h[]→换热系数、Area[]→面积、dT[]→温差、Tc[]→时间常数、热流箭头、HousingWJ 水道字段);③ 部件词典 + 节点温度 token 翻译。无规则命中时原样保留(绝不杜撰翻译)。
+2. **导出接入**:`_collect_export_rows` 对 raw 列表头调用 translate_field;xlsx 新增第二个 sheet **"字段对照"**(列号/类目/中文表头/Motor-CAD原始字段/单位,885 行全覆盖,含归一化指标的 metric key 对照)。
+
+### 验证
+
+- 翻译覆盖率实测:461/461 英文字段全部中文化(先用真实导出全量统计,未翻译 0 残留)。
+- 测试 66 项全过(新增翻译规则 8 项 + 导出验证 5 项)。
+- 真实导出:885 列 0 英文表头;抽查 `温度[端部绕组(外)最大][°C]`、`热阻[机壳(前)-环境]-对流[°C/W]`、`环境[°C]` 全部命中;对照表验证 `温度[端盖外(前)][°C]` ↔ `T [Endcap Outer (F)][°C]` 对应正确。CSV 同步中文化。
+
+### 结论
+
+表头全中文带单位;"字段对照" sheet 提供与 Motor-CAD 原生导出的一一对应,后续软件开发以对照表为准不会错位。
+
 ### 结论
 
 工程师最关心指标(转矩/脉动/效率/损耗分解/电气/温度)在 Web 表格默认可视,完整结果一键导出 xlsx,格式与 torqrippswap 一致。

+ 45 - 0
scripts/test_xlsx_export.py

@@ -259,6 +259,8 @@ def api_tests() -> None:
              "value": 52.591, "unit": "\u00b0C"},
             {"section": "Thermal-\u70ed\u963b", "name": "Rt[\u6c14\u9699]",
              "value": 5.076, "unit": "\u00b0C/W"},
+            {"section": "Thermal-\u6e29\u5ea6", "name": "T [EWdg (Outer) Maximum]",
+             "value": 53.9, "unit": "\u00b0C"},
         ])
         db.commit()
 
@@ -358,6 +360,49 @@ def api_tests() -> None:
     check("full: node temps no id leak",
           all(r["name"] != "0" for r in nt_rows))
 
+    # Chinese translation of English field names (2026-09-04 user request):
+    # headers become Chinese, mapping sheet preserves the native name.
+    from afmcore.export_field_zh import translate_field
+    check("zh: T bracket", translate_field("T [EWdg (Outer) Maximum]")
+          == "\u6e29\u5ea6[\u7aef\u90e8\u7ed5\u7ec4(\u5916)\u6700\u5927]")
+    check("zh: Rt with suffix", translate_field("Rt [Housing (F) - Amb] - Conv")
+          == "\u70ed\u963b[\u673a\u58f3(\u524d)-\u73af\u5883]-\u5bf9\u6d41")
+    check("zh: Cap double space", translate_field("Cap [Endcap  Outer (F)]")
+          == "\u70ed\u5bb9[\u7aef\u76d6\u5916(\u524d)]")
+    check("zh: exact dict", translate_field("Motor Constant (Km)")
+          == "\u7535\u673a\u5e38\u6570(Km)")
+    check("zh: node token", translate_field("Ambient", "Node Temperatures")
+          == "\u73af\u5883")
+    check("zh: node composite",
+          translate_field("EWdg_Outer (Average)(C1)", "Node Temperatures")
+          == "\u7aef\u90e8\u7ed5\u7ec4_\u5916 (\u5e73\u5747)(C1)")
+    check("zh: qualifier suffix",
+          translate_field("Rt [Winding (Average) - Ambient] (Total Losses)")
+          == "\u70ed\u963b[\u7ed5\u7ec4(\u5e73\u5747) - \u73af\u5883](\u603b\u635f\u8017)")
+    check("zh: unknown passthrough",
+          translate_field("Some Unknown Field X") == "Some Unknown Field X")
+
+    # Export now carries Chinese raw headers + a mapping sheet.
+    resp5 = client.get(f"/api/plans/{plan_pk}/export-xlsx")
+    check("zh: export 200", resp5.status_code == 200)
+    out5 = Path(tempfile.mkdtemp()) / "zh_export.xlsx"
+    out5.write_bytes(resp5.content)
+    wb5 = _oxl.load_workbook(out5)
+    check("zh: mapping sheet exists",
+          "\u5b57\u6bb5\u5bf9\u7167" in wb5.sheetnames)
+    ws5 = wb5["scan_results"]
+    h5 = [ws5.cell(2, c).value for c in range(1, ws5.max_column + 1)]
+    check("zh: raw header translated",
+          "\u6e29\u5ea6[\u7aef\u90e8\u7ed5\u7ec4(\u5916)\u6700\u5927][\u00b0C]" in h5,
+          "h5 sample: " + str([x for x in h5 if x and "\u6e29\u5ea6" in x][:3]))
+    wsm = wb5["\u5b57\u6bb5\u5bf9\u7167"]
+    map_rows = [[wsm.cell(r, c).value for c in range(1, 6)]
+                for r in range(2, wsm.max_row + 1)]
+    check("zh: mapping keeps original",
+          any(mr[3] and "T[\u7ed5\u7ec4\u5e73\u5747]" in str(mr[3]) for mr in map_rows))
+    check("zh: mapping has unit",
+          any(mr[4] == "\u00b0C" for mr in map_rows))
+
 
 if __name__ == "__main__":
     unit_tests()

+ 505 - 0
src/afmcore/export_field_zh.py

@@ -0,0 +1,505 @@
+"""Chinese translation for Motor-CAD export field names.
+
+Three-layer strategy:
+1. EXACT_ZH: exact full-name dictionary (EM/drive/loss/winding fields).
+2. Pattern rules: T[...] -> \u6e29\u5ea6[...], Rt[...] -> \u70ed\u963b[...], Cap -> \u70ed\u5bb9,
+   h -> \u6362\u70ed\u7cfb\u6570, Area -> \u9762\u79ef, node-temperature token translation.
+3. PART_ZH: component dictionary used by the pattern rules
+   (longest-match-first replacement inside brackets).
+
+translate_field(name, section) returns the Chinese name, or the original
+name when no rule matches (never invents a translation).
+
+The original Motor-CAD name is always preserved by the caller (export adds
+a mapping sheet), so downstream software can correlate with native exports.
+
+All source is ASCII only.
+"""
+from __future__ import annotations
+
+import re
+from typing import Dict, Optional
+
+# ---------------------------------------------------------------------------
+# Exact dictionary: full English field name -> Chinese
+# ---------------------------------------------------------------------------
+
+EXACT_ZH: Dict[str, str] = {
+    # Drive
+    "D Axis Phase Current (average rms)": "D\u8f74\u76f8\u7535\u6d41(\u5e73\u5747\u6709\u6548\u503c)",
+    "Q Axis Phase Current (average rms)": "Q\u8f74\u76f8\u7535\u6d41(\u5e73\u5747\u6709\u6548\u503c)",
+    "Motor Constant (Km)": "\u7535\u673a\u5e38\u6570(Km)",
+    "Short Circuit Current Density (rms)": "\u77ed\u8def\u7535\u6d41\u5bc6\u5ea6(\u6709\u6548\u503c)",
+    # EM / phasor
+    "Torque Ripple (VW)": "\u8f6c\u77e9\u8109\u52a8(VW)",
+    "Torque Ripple (VW) [%]": "\u8f6c\u77e9\u8109\u52a8(VW)[%]",
+    "Speed limit for zero q axis current": "\u96f6q\u8f74\u7535\u6d41\u8f6c\u901f\u9650\u503c",
+    "Power Factor [THD]": "\u529f\u7387\u56e0\u6570[THD]",
+    "Magnetic Length (S1)": "\u78c1\u8def\u957f\u5ea6(S1)",
+    "Magnetic Length (S2)": "\u78c1\u8def\u957f\u5ea6(S2)",
+    "Lamination Stacking Factor (Stator)": "\u53e0\u7247\u7cfb\u6570(\u5b9a\u5b50)",
+    "Lamination Stacking Factor (Rotor)": "\u53e0\u7247\u7cfb\u6570(\u8f6c\u5b50)",
+    # Flux density
+    "Airgap Flux Density (average)": "\u6c14\u9699\u78c1\u5bc6(\u5e73\u5747)",
+    "Airgap Flux Density (maximum)": "\u6c14\u9699\u78c1\u5bc6(\u6700\u5927)",
+    "Stator Tooth Flux Density (maximum)": "\u5b9a\u5b50\u9f7f\u78c1\u5bc6(\u6700\u5927)",
+    "Stator Tooth Tip Flux Density (maximum)": "\u5b9a\u5b50\u9f7f\u5c16\u78c1\u5bc6(\u6700\u5927)",
+    "Stator Back Iron Flux Density (maximum)": "\u5b9a\u5b50\u8f6d\u78c1\u5bc6(\u6700\u5927)",
+    "Rotor Back Iron Flux Density (maximum)": "\u8f6c\u5b50\u8f6d\u78c1\u5bc6(\u6700\u5927)",
+    # Losses
+    "AC Loss (Total) (Hybrid)": "\u4ea4\u6d41\u635f\u8017(\u603b)(\u6df7\u5408)",
+    "AC Loss (Total Proximity) (Hybrid)": "\u4ea4\u6d41\u635f\u8017(\u90bb\u8fd1\u6548\u5e94\u603b)(\u6df7\u5408)",
+    "AC Loss (Total Skin-Effect) (Hybrid)": "\u4ea4\u6d41\u635f\u8017(\u96c6\u80a4\u6548\u5e94\u603b)(\u6df7\u5408)",
+    "AC Loss (Proximity) (Hybrid) (Left)": "\u4ea4\u6d41\u635f\u8017(\u90bb\u8fd1\u6548\u5e94)(\u6df7\u5408)(\u5de6)",
+    "AC Loss (Proximity) (Hybrid) (Right)": "\u4ea4\u6d41\u635f\u8017(\u90bb\u8fd1\u6548\u5e94)(\u6df7\u5408)(\u53f3)",
+    "AC Loss (Proximity) (Hybrid) (C1)": "\u4ea4\u6d41\u635f\u8017(\u90bb\u8fd1\u6548\u5e94)(\u6df7\u5408)(C1)",
+    "AC Loss (Proximity) (Hybrid) (C2)": "\u4ea4\u6d41\u635f\u8017(\u90bb\u8fd1\u6548\u5e94)(\u6df7\u5408)(C2)",
+    "AC Loss (Skin Effect) (Hybrid) (C1)": "\u4ea4\u6d41\u635f\u8017(\u96c6\u80a4\u6548\u5e94)(\u6df7\u5408)(C1)",
+    "AC Loss (Skin Effect) (Hybrid) (C2)": "\u4ea4\u6d41\u635f\u8017(\u96c6\u80a4\u6548\u5e94)(\u6df7\u5408)(C2)",
+    "AC Loss (Flux Density) (Hybrid) (C1)": "\u4ea4\u6d41\u635f\u8017\u78c1\u5bc6(\u6df7\u5408)(C1)",
+    "AC Loss (Flux Density) (Hybrid) (C2)": "\u4ea4\u6d41\u635f\u8017\u78c1\u5bc6(\u6df7\u5408)(C2)",
+    "AC Loss Proportion (C1)": "\u4ea4\u6d41\u635f\u8017\u6bd4\u4f8b(C1)",
+    "AC Loss Proportion (C2)": "\u4ea4\u6d41\u635f\u8017\u6bd4\u4f8b(C2)",
+    "Magnet Block Width": "\u78c1\u5757\u5bbd\u5ea6",
+    "Magnet 2D3D Loss Factor": "\u78c1\u94a22D/3D\u635f\u8017\u7cfb\u6570",
+    "Loss [Armature Copper (dc)]": "\u635f\u8017[\u7535\u67a2\u94dc(\u76f4\u6d41)]",
+    "Loss [Armature Copper] (EWdg Outer)": "\u635f\u8017[\u7535\u67a2\u94dc](\u7aef\u90e8\u7ed5\u7ec4\u5916)",
+    "Loss [Armature Copper] (EWdg Inner)": "\u635f\u8017[\u7535\u67a2\u94dc](\u7aef\u90e8\u7ed5\u7ec4\u5185)",
+    "Loss [Armature Copper] (Total)": "\u635f\u8017[\u7535\u67a2\u94dc](\u603b)",
+    "Stall Operation (Copper Loss Multiplier)": "\u5835\u8f6c\u8fd0\u884c(\u94dc\u8017\u7cfb\u6570)",
+    "Fault Operation (Copper Loss Multiplier)": "\u6545\u969c\u8fd0\u884c(\u94dc\u8017\u7cfb\u6570)",
+    "Loss [Stator Back Iron] Schematic Addition": "\u635f\u8017[\u5b9a\u5b50\u8f6d]\u539f\u7406\u56fe\u9644\u52a0",
+    "Dissipation - Front Endcap (Active) [Con]": "\u8017\u6563-\u524d\u7aef\u76d6(\u6709\u6548\u6bb5)[\u5bf9\u6d41]",
+    "Dissipation - Front Endcap (Active) [Rad]": "\u8017\u6563-\u524d\u7aef\u76d6(\u6709\u6548\u6bb5)[\u8f90\u5c04]",
+    "Dissipation - Front Endcap (Inner) [Con]": "\u8017\u6563-\u524d\u7aef\u76d6(\u5185)[\u5bf9\u6d41]",
+    "Dissipation - Front Endcap (Inner) [Rad]": "\u8017\u6563-\u524d\u7aef\u76d6(\u5185)[\u8f90\u5c04]",
+    "Dissipation Open Endcap Outer": "\u8017\u6563\u5f00\u653e\u7aef\u76d6\u5916",
+    # Winding
+    "Armature Turn CSA": "\u7535\u67a2\u531d\u622a\u9762\u79ef",
+    "Mean Coil Pitch (Inner) (Calculated)": "\u5e73\u5747\u7ebf\u5708\u8282\u8ddd(\u5185)(\u8ba1\u7b97\u503c)",
+    "Mean Coil Pitch (Inner) (Used)": "\u5e73\u5747\u7ebf\u5708\u8282\u8ddd(\u5185)(\u91c7\u7528\u503c)",
+    "Mean Coil Pitch (Outer) (Calculated)": "\u5e73\u5747\u7ebf\u5708\u8282\u8ddd(\u5916)(\u8ba1\u7b97\u503c)",
+    "Mean Coil Pitch (Outer) (Used)": "\u5e73\u5747\u7ebf\u5708\u8282\u8ddd(\u5916)(\u91c7\u7528\u503c)",
+    "Armature EWdg MLT Inner (Used)": "\u7535\u67a2\u7aef\u90e8\u7ed5\u7ec4\u5e73\u5747\u531d\u957f\u5185(\u91c7\u7528\u503c)",
+    "Armature EWdg MLT Outer (Used)": "\u7535\u67a2\u7aef\u90e8\u7ed5\u7ec4\u5e73\u5747\u531d\u957f\u5916(\u91c7\u7528\u503c)",
+    "Armature EWdg MLT (Used)": "\u7535\u67a2\u7aef\u90e8\u7ed5\u7ec4\u5e73\u5747\u531d\u957f(\u91c7\u7528\u503c)",
+    "Slot Area (Slot 1)": "\u69fd\u9762\u79ef(\u69fd1)",
+    "Winding Area (+ Liner) (Slot 1)": "\u7ed5\u7ec4\u9762\u79ef(\u542b\u69fd\u886c)(\u69fd1)",
+    "Slot Opening Area": "\u69fd\u53e3\u9762\u79ef",
+    "Impreg Area (Slot 1)": "\u6d78\u6e0d\u9762\u79ef(\u69fd1)",
+    "Liner Area (Slot 1)": "\u69fd\u886c\u9762\u79ef(\u69fd1)",
+    "Volume Copper EWdg Inner": "\u7aef\u90e8\u7ed5\u7ec4\u5185\u94dc\u4f53\u79ef",
+    "Volume Copper EWdg Outer": "\u7aef\u90e8\u7ed5\u7ec4\u5916\u94dc\u4f53\u79ef",
+    "Slot Depth (Windable)": "\u69fd\u6df1(\u53ef\u7ed5)",
+    "EWdg OHang [Inner]": "\u7aef\u90e8\u7ed5\u7ec4\u60ac\u4f38[\u5185]",
+    "EWdg OHang [Outer]": "\u7aef\u90e8\u7ed5\u7ec4\u60ac\u4f38[\u5916]",
+    "Tang Depth": "\u6321\u8fb9\u6df1\u5ea6",
+    "Armature Copper Loss Ratio [Outer EWdg]": "\u7535\u67a2\u94dc\u8017\u6bd4\u4f8b[\u7aef\u90e8\u7ed5\u7ec4\u5916]",
+    "Armature Copper Loss Ratio [Inner EWdg]": "\u7535\u67a2\u94dc\u8017\u6bd4\u4f8b[\u7aef\u90e8\u7ed5\u7ec4\u5185]",
+    "EWdg Inner Diameter": "\u7aef\u90e8\u7ed5\u7ec4\u5185\u5f84",
+    "EWdg Outer Diameter": "\u7aef\u90e8\u7ed5\u7ec4\u5916\u5f84",
+    "Winding Loss Proportion (Active) (Cuboid 1)": "\u7ed5\u7ec4\u635f\u8017\u6bd4\u4f8b(\u6709\u6548\u6bb5)(\u57571)",
+    "Winding Loss Proportion (Active) (Cuboid 2)": "\u7ed5\u7ec4\u635f\u8017\u6bd4\u4f8b(\u6709\u6548\u6bb5)(\u57572)",
+    # Materials / weight
+    "Armature Conductor Resistivity at Ref Temp (20\u00b0C)": "\u7535\u67a2\u5bfc\u4f53\u7535\u963b\u7387(20\u00b0C\u53c2\u8003)",
+    "Stator Lamination Resistivity": "\u5b9a\u5b50\u53e0\u7247\u7535\u963b\u7387",
+    "Rotor Lamination Resistivity at Ref Temp (20\u00b0C)": "\u8f6c\u5b50\u53e0\u7247\u7535\u963b\u7387(20\u00b0C\u53c2\u8003)",
+    "Shaft Resistivity at Ref Temp (20\u00b0C)": "\u8f6c\u8f74\u7535\u963b\u7387(20\u00b0C\u53c2\u8003)",
+    "Magnet Resistivity at Ref Temp (20\u00b0C)": "\u78c1\u94a2\u7535\u963b\u7387(20\u00b0C\u53c2\u8003)",
+    "Total Motor Weight": "\u7535\u673a\u603b\u91cd\u91cf",
+    "Weight Stator Lam": "\u5b9a\u5b50\u53e0\u7247\u91cd\u91cf",
+    "Weight Rotor Lam": "\u8f6c\u5b50\u53e0\u7247\u91cd\u91cf",
+    "Weight Magnet": "\u78c1\u94a2\u91cd\u91cf",
+    "Weight Shaft Total": "\u8f6c\u8f74\u603b\u91cd\u91cf",
+    # Thermal misc
+    "Housing Corner Cutout Spacing": "\u673a\u58f3\u89d2\u5207\u53e3\u95f4\u8ddd",
+    "Housing Corner Cutout Depth": "\u673a\u58f3\u89d2\u5207\u53e3\u6df1\u5ea6",
+    "Airgap Width": "\u6c14\u9699\u5bbd\u5ea6",
+    "Airgap ratio": "\u6c14\u9699\u6bd4",
+    "Nu Free Rotor (Laminar)": "\u52aa\u585e\u5c14\u6570\u81ea\u7531\u8f6c\u5b50(\u5c42\u6d41)",
+    "Nu Free Rotor (Turbulent)": "\u52aa\u585e\u5c14\u6570\u81ea\u7531\u8f6c\u5b50(\u6e4d\u6d41)",
+    "Nu Small Airgap Ratio (Laminar)": "\u52aa\u585e\u5c14\u6570\u5c0f\u6c14\u9699\u6bd4(\u5c42\u6d41)",
+    "Nu Small Airgap Ratio (Turbulent)": "\u52aa\u585e\u5c14\u6570\u5c0f\u6c14\u9699\u6bd4(\u6e4d\u6d41)",
+    "Nu Medium Airgap Ratio (Laminar)": "\u52aa\u585e\u5c14\u6570\u4e2d\u6c14\u9699\u6bd4(\u5c42\u6d41)",
+    "Nu Medium Airgap Ratio (Turbulent)": "\u52aa\u585e\u5c14\u6570\u4e2d\u6c14\u9699\u6bd4(\u6e4d\u6d41)",
+    "Airgap Ratio correlation": "\u6c14\u9699\u6bd4\u5173\u8054",
+    "Steady State Iterations": "\u7a33\u6001\u8fed\u4ee3\u6b21\u6570",
+    "Steady State Convergence Error Max": "\u7a33\u6001\u6700\u5927\u6536\u655b\u8bef\u5dee",
+    "Shaft Speed Thermal": "\u70ed\u5206\u6790\u8f6c\u901f",
+    "Tw [Lower]": "\u51b7\u5374\u6c34\u6e29[\u4e0b\u9650]",
+    "Tw [Upper]": "\u51b7\u5374\u6c34\u6e29[\u4e0a\u9650]",
+    "Endcap Vent Flow Calculated [Outer]": "\u7aef\u76d6\u901a\u98ce\u6d41\u91cf\u8ba1\u7b97\u503c[\u5916]",
+}
+
+# ---------------------------------------------------------------------------
+# Component dictionary (longest match first at runtime)
+# ---------------------------------------------------------------------------
+
+PART_ZH: Dict[str, str] = {
+    # Multi-word first
+    "Stator Back Iron": "\u5b9a\u5b50\u8f6d",
+    "Rotor Back Iron": "\u8f6c\u5b50\u8f6d",
+    "End Space (Inner)": "\u7aef\u90e8\u7a7a\u95f4(\u5185)",
+    "End Space (Outer)": "\u7aef\u90e8\u7a7a\u95f4(\u5916)",
+    "End Space": "\u7aef\u90e8\u7a7a\u95f4",
+    "Shaft Centre (F)": "\u8f74\u4e2d\u5fc3(\u524d)",
+    "Shaft Centre (R)": "\u8f74\u4e2d\u5fc3(\u540e)",
+    "Shaft Centre": "\u8f74\u4e2d\u5fc3",
+    "Shaft Extension (Outer)": "\u8f74\u4f38(\u5916)",
+    "Shaft Extension (End)": "\u8f74\u4f38(\u7aef)",
+    "Shaft Extension": "\u8f74\u4f38",
+    "Housing OHang/2 (F)": "\u673a\u58f3\u60ac\u4f38\u534a(\u524d)",
+    "Housing OHang/2 (R)": "\u673a\u58f3\u60ac\u4f38\u534a(\u540e)",
+    "Housing OHang (F)": "\u673a\u58f3\u60ac\u4f38(\u524d)",
+    "Housing OHang (R)": "\u673a\u58f3\u60ac\u4f38(\u540e)",
+    "Housing OHang": "\u673a\u58f3\u60ac\u4f38",
+    "HousingWJ Endcap Fluid": "\u673a\u58f3\u6c34\u9053\u7aef\u76d6\u6d41\u4f53",
+    "HousingWJ Channel Fluid": "\u673a\u58f3\u6c34\u9053\u6d41\u4f53",
+    "HousingWJ Channel": "\u673a\u58f3\u6c34\u9053",
+    "HousingWJ Endcap": "\u673a\u58f3\u6c34\u9053\u7aef\u76d6",
+    "HousingWJ Fluid": "\u673a\u58f3\u6c34\u9053\u6d41\u4f53",
+    "HousingWJ": "\u673a\u58f3\u6c34\u9053",
+    "Front Endcap": "\u524d\u7aef\u76d6",
+    "Front Magnet": "\u524d\u78c1\u94a2",
+    "Front Rotor": "\u524d\u8f6c\u5b50",
+    "Front Winding": "\u524d\u7ed5\u7ec4",
+    "Endcap Outer (F)": "\u7aef\u76d6\u5916(\u524d)",
+    "Endcap Active (F)": "\u7aef\u76d6\u6709\u6548\u6bb5(\u524d)",
+    "Endcap Inner (F)": "\u7aef\u76d6\u5185(\u524d)",
+    "Endcap (Axial Outer Front)": "\u7aef\u76d6(\u8f74\u5411\u5916\u524d)",
+    "Endcap (Axial Outer Rear)": "\u7aef\u76d6(\u8f74\u5411\u5916\u540e)",
+    "Endcap (Axial Active Front)": "\u7aef\u76d6(\u8f74\u5411\u6709\u6548\u524d)",
+    "Endcap (Radial Inner Front)": "\u7aef\u76d6(\u5f84\u5411\u5185\u524d)",
+    "Endcap (Radial Active Inner Front)": "\u7aef\u76d6(\u5f84\u5411\u6709\u6548\u5185\u524d)",
+    "Endcap (Radial Active Outer Front)": "\u7aef\u76d6(\u5f84\u5411\u6709\u6548\u5916\u524d)",
+    "Endcap (Radial Outer Front)": "\u7aef\u76d6(\u5f84\u5411\u5916\u524d)",
+    "Endcap (Radial Rear)": "\u7aef\u76d6(\u5f84\u5411\u540e)",
+    "Endcap (F)": "\u7aef\u76d6(\u524d)",
+    "Endcap (R)": "\u7aef\u76d6(\u540e)",
+    "Endcap Inner": "\u7aef\u76d6\u5185",
+    "Endcap Outer": "\u7aef\u76d6\u5916",
+    "Endcap Active": "\u7aef\u76d6\u6709\u6548\u6bb5",
+    "Endcap": "\u7aef\u76d6",
+    "EWdg (Outer) Maximum": "\u7aef\u90e8\u7ed5\u7ec4(\u5916)\u6700\u5927",
+    "EWdg (Outer) Average": "\u7aef\u90e8\u7ed5\u7ec4(\u5916)\u5e73\u5747",
+    "EWdg (Outer) Minimum": "\u7aef\u90e8\u7ed5\u7ec4(\u5916)\u6700\u5c0f",
+    "EWdg (Inner) Maximum": "\u7aef\u90e8\u7ed5\u7ec4(\u5185)\u6700\u5927",
+    "EWdg (Inner) Average": "\u7aef\u90e8\u7ed5\u7ec4(\u5185)\u5e73\u5747",
+    "EWdg (Inner) Minimum": "\u7aef\u90e8\u7ed5\u7ec4(\u5185)\u6700\u5c0f",
+    "EWdg Active Outer": "\u7aef\u90e8\u7ed5\u7ec4\u6709\u6548\u5916",
+    "EWdg Active Inner": "\u7aef\u90e8\u7ed5\u7ec4\u6709\u6548\u5185",
+    "EWdg Outer Front": "\u7aef\u90e8\u7ed5\u7ec4\u5916\u524d",
+    "EWdg Inner Front": "\u7aef\u90e8\u7ed5\u7ec4\u5185\u524d",
+    "EWdg Outer": "\u7aef\u90e8\u7ed5\u7ec4\u5916",
+    "EWdg Inner": "\u7aef\u90e8\u7ed5\u7ec4\u5185",
+    "EWdg Ext": "\u7aef\u90e8\u7ed5\u7ec4\u5ef6\u4f38",
+    "Ewdg Outer Front": "\u7aef\u90e8\u7ed5\u7ec4\u5916\u524d",
+    "EWdg": "\u7aef\u90e8\u7ed5\u7ec4",
+    "Winding (A) Maximum": "\u7ed5\u7ec4(A)\u6700\u5927",
+    "Winding (A) Average": "\u7ed5\u7ec4(A)\u5e73\u5747",
+    "Winding (A) Minimum": "\u7ed5\u7ec4(A)\u6700\u5c0f",
+    "Winding (Average)": "\u7ed5\u7ec4(\u5e73\u5747)",
+    "Winding (Maximum)": "\u7ed5\u7ec4(\u6700\u5927)",
+    "Winding": "\u7ed5\u7ec4",
+    "Magnet - Rotor Lam": "\u78c1\u94a2-\u8f6c\u5b50\u53e0\u7247",
+    "Magnet (Radial Outer)": "\u78c1\u94a2(\u5f84\u5411\u5916)",
+    "Magnet (Radial Inner)": "\u78c1\u94a2(\u5f84\u5411\u5185)",
+    "Magnet Maximum": "\u78c1\u94a2\u6700\u5927",
+    "Magnet Average": "\u78c1\u94a2\u5e73\u5747",
+    "Magnet Active": "\u78c1\u94a2\u6709\u6548\u6bb5",
+    "Magnet Outer": "\u78c1\u94a2\u5916",
+    "Magnet Inner": "\u78c1\u94a2\u5185",
+    "Magnet": "\u78c1\u94a2",
+    "Rotor Lam (Active Outer)": "\u8f6c\u5b50\u53e0\u7247(\u6709\u6548\u5916)",
+    "Rotor Lam (Active Inner)": "\u8f6c\u5b50\u53e0\u7247(\u6709\u6548\u5185)",
+    "Rotor Lam (Outer)": "\u8f6c\u5b50\u53e0\u7247(\u5916)",
+    "Rotor Lam (Inner)": "\u8f6c\u5b50\u53e0\u7247(\u5185)",
+    "Rotor Lam - Shaft": "\u8f6c\u5b50\u53e0\u7247-\u8f6c\u8f74",
+    "Rotor Lam": "\u8f6c\u5b50\u53e0\u7247",
+    "Rotor Active": "\u8f6c\u5b50\u6709\u6548\u6bb5",
+    "Rotor Outer": "\u8f6c\u5b50\u5916",
+    "Rotor Inner": "\u8f6c\u5b50\u5185",
+    "Rotor": "\u8f6c\u5b50",
+    "Stator Lam - Endcap (F)": "\u5b9a\u5b50\u53e0\u7247-\u7aef\u76d6(\u524d)",
+    "Stator Lam": "\u5b9a\u5b50\u53e0\u7247",
+    "Stator": "\u5b9a\u5b50",
+    "Tooth (Slot Bottom Side)": "\u9f7f(\u69fd\u5e95\u4fa7)",
+    "Tooth (Slot Opening Side)": "\u9f7f(\u69fd\u53e3\u4fa7)",
+    "Tooth": "\u9f7f",
+    "Yoke End": "\u8f6d\u7aef",
+    "Yoke (Slot)": "\u8f6d(\u69fd)",
+    "Yoke (Tooth)": "\u8f6d(\u9f7f)",
+    "Yoke": "\u8f6d",
+    "Liner - Lam (Slot Bottom)": "\u69fd\u886c-\u53e0\u7247(\u69fd\u5e95)",
+    "Liner - Lam (Tooth Side)": "\u69fd\u886c-\u53e0\u7247(\u9f7f\u4fa7)",
+    "Liner (Tooth Side)": "\u69fd\u886c(\u9f7f\u4fa7)",
+    "Liner (Slot Bottom)": "\u69fd\u886c(\u69fd\u5e95)",
+    "Liner_Yoke": "\u69fd\u886c\u8f6d",
+    "Liner": "\u69fd\u886c",
+    "Ins (Tooth Side)": "\u7edd\u7f18(\u9f7f\u4fa7)",
+    "Ins (Slot Base)": "\u7edd\u7f18(\u69fd\u5e95)",
+    "Ins": "\u7edd\u7f18",
+    "Divider Wedge": "\u5206\u9694\u6954",
+    "Wedge": "\u69fd\u6954",
+    "Bearing - Endcap (F)": "\u8f74\u627f-\u7aef\u76d6(\u524d)",
+    "Bearing - Endcap (R)": "\u8f74\u627f-\u7aef\u76d6(\u540e)",
+    "Bearing - Shaft (F)": "\u8f74\u627f-\u8f6c\u8f74(\u524d)",
+    "Bearing - Shaft (R)": "\u8f74\u627f-\u8f6c\u8f74(\u540e)",
+    "Bearing_Front": "\u524d\u8f74\u627f",
+    "Bearing_Rear": "\u540e\u8f74\u627f",
+    "Bearing": "\u8f74\u627f",
+    "Shaft Active Radial": "\u8f74\u6709\u6548\u6bb5\u5f84\u5411",
+    "Shaft (OH Rear)": "\u8f74(\u60ac\u4f38\u540e)",
+    "Shaft (Front)": "\u8f74(\u524d)",
+    "Shaft (Rear)": "\u8f74(\u540e)",
+    "Shaft_F": "\u524d\u8f74",
+    "Shaft_R": "\u540e\u8f74",
+    "Shaft_OH_R": "\u540e\u8f74\u60ac\u4f38",
+    "Shaft OHang [R]": "\u8f74\u60ac\u4f38[\u540e]",
+    "Shaft": "\u8f6c\u8f74",
+    "Housing - Active (Act) - Amb": "\u673a\u58f3-\u6709\u6548\u6bb5(Act)-\u73af\u5883",
+    "Housing - Active - Housing OHang (F)": "\u673a\u58f3\u6709\u6548\u6bb5-\u673a\u58f3\u60ac\u4f38(\u524d)",
+    "Housing - Active - Housing OHang (R)": "\u673a\u58f3\u6709\u6548\u6bb5-\u673a\u58f3\u60ac\u4f38(\u540e)",
+    "Housing - Active - Front Total": "\u673a\u58f3\u6709\u6548\u6bb5\u524d\u603b",
+    "Housing - Active - Rear Total": "\u673a\u58f3\u6709\u6548\u6bb5\u540e\u603b",
+    "Housing - Active - Front": "\u673a\u58f3\u6709\u6548\u6bb5\u524d",
+    "Housing - Active - Rear": "\u673a\u58f3\u6709\u6548\u6bb5\u540e",
+    "Housing - Active": "\u673a\u58f3\u6709\u6548\u6bb5",
+    "Housing (F) - Amb": "\u673a\u58f3(\u524d)-\u73af\u5883",
+    "Housing (R) - Amb": "\u673a\u58f3(\u540e)-\u73af\u5883",
+    "Housing - Endcap (F)": "\u673a\u58f3-\u7aef\u76d6(\u524d)",
+    "Housing - Endcap (R)": "\u673a\u58f3-\u7aef\u76d6(\u540e)",
+    "Housing - Ambient": "\u673a\u58f3-\u73af\u5883",
+    "Housing Corner Cutout": "\u673a\u58f3\u89d2\u5207\u53e3",
+    "Housing": "\u673a\u58f3",
+    "Endcap (F) - Amb": "\u7aef\u76d6(\u524d)-\u73af\u5883",
+    "Endcap (R) - Amb": "\u7aef\u76d6(\u540e)-\u73af\u5883",
+    "Ambient": "\u73af\u5883",
+    "Amb": "\u73af\u5883",
+    "ESpace": "\u7aef\u90e8\u7a7a\u95f4",
+    "Mounting": "\u5b89\u88c5\u4ef6",
+    "Motor": "\u7535\u673a",
+    # HousingWJ / water jacket tokens
+    "Fluid Inlet Temp": "\u6d41\u4f53\u8fdb\u53e3\u6e29\u5ea6",
+    "Fluid Average Temp": "\u6d41\u4f53\u5e73\u5747\u6e29\u5ea6",
+    "Fluid Outlet Temp": "\u6d41\u4f53\u51fa\u53e3\u6e29\u5ea6",
+    "Fluid Thermal Conductivity": "\u6d41\u4f53\u5bfc\u70ed\u7cfb\u6570",
+    "Fluid Density": "\u6d41\u4f53\u5bc6\u5ea6",
+    "Fluid Specific Heat": "\u6d41\u4f53\u6bd4\u70ed\u5bb9",
+    "Fluid Kinematic Viscosity": "\u6d41\u4f53\u8fd0\u52a8\u7c98\u5ea6",
+    "Fluid Prandtl Number": "\u6d41\u4f53\u666e\u6717\u7279\u6570",
+    "Fluid Velocity": "\u6d41\u4f53\u6d41\u901f",
+    "Fluid": "\u6d41\u4f53",
+    "Channel Flow Resistance": "\u6c34\u9053\u6d41\u963b",
+    "Channel Dissipation": "\u6c34\u9053\u8017\u6563",
+    "Channel Surface Area": "\u6c34\u9053\u8868\u9762\u79ef",
+    "Channel Pressure": "\u6c34\u9053\u538b\u529b",
+    "Channel": "\u6c34\u9053",
+    "Flow": "\u6d41\u91cf",
+    "Pressure": "\u538b\u529b",
+    "Thermal Conductivity": "\u5bfc\u70ed\u7cfb\u6570",
+    "Density": "\u5bc6\u5ea6",
+    "Specific Heat": "\u6bd4\u70ed\u5bb9",
+    "Kinematic Viscosity": "\u8fd0\u52a8\u7c98\u5ea6",
+    "Prandtl Number": "\u666e\u6717\u7279\u6570",
+    "Surface Area": "\u8868\u9762\u79ef",
+    "Velocity": "\u6d41\u901f",
+    "Inlet": "\u8fdb\u53e3",
+    "Outlet": "\u51fa\u53e3",
+    "Length": "\u957f\u5ea6",
+    "Dissipation": "\u8017\u6563",
+    "Vent Flow Calculated": "\u901a\u98ce\u6d41\u91cf\u8ba1\u7b97\u503c",
+    # Node-temperature tokens (underscore joined)
+    "Stator_Yoke": "\u5b9a\u5b50\u8f6d",
+    "Stator_Surface": "\u5b9a\u5b50\u8868\u9762",
+    "Endcap_OH_F": "\u524d\u7aef\u76d6\u60ac\u4f38",
+    "Endcap_OH_R": "\u540e\u7aef\u76d6\u60ac\u4f38",
+    "Endcap_Outer_F": "\u524d\u7aef\u76d6\u5916",
+    "Endcap_Inner_F": "\u524d\u7aef\u76d6\u5185",
+    "Endcap_Active_F": "\u524d\u7aef\u76d6\u6709\u6548\u6bb5",
+    "Endcap_R": "\u540e\u7aef\u76d6",
+    "Shaft_Centre_F": "\u524d\u8f74\u4e2d\u5fc3",
+    "Shaft_Centre_R": "\u540e\u8f74\u4e2d\u5fc3",
+    "Magnet_Outer": "\u78c1\u94a2\u5916",
+    "Magnet_Inner": "\u78c1\u94a2\u5185",
+    "Rotor_Outer": "\u8f6c\u5b50\u5916",
+    "Rotor_Inner": "\u8f6c\u5b50\u5185",
+    "ESpace_Inner": "\u7aef\u90e8\u7a7a\u95f4\u5185",
+    "ESpace_Outer": "\u7aef\u90e8\u7a7a\u95f4\u5916",
+    "HousingWJ_Endcap_Inlet_F": "\u673a\u58f3\u6c34\u9053\u7aef\u76d6\u8fdb\u53e3\u524d",
+    "HousingWJ_Endcap_Inlet_R": "\u673a\u58f3\u6c34\u9053\u7aef\u76d6\u8fdb\u53e3\u540e",
+    "HousingWJ_Endcap_F": "\u673a\u58f3\u6c34\u9053\u7aef\u76d6\u524d",
+    "HousingWJ_Endcap_R": "\u673a\u58f3\u6c34\u9053\u7aef\u76d6\u540e",
+    "SlotCentre": "\u69fd\u4e2d\u5fc3",
+    "Wdg_Outer_C": "\u7ed5\u7ec4\u5916",
+    "Wdg_Inner_C": "\u7ed5\u7ec4\u5185",
+    "Wdg_F": "\u7ed5\u7ec4\u524d",
+    "Total Losses": "\u603b\u635f\u8017",
+    "Losses": "\u635f\u8017",
+    "(F)": "(\u524d)",
+    "(R)": "(\u540e)",
+    # Single tokens
+    "Maximum": "\u6700\u5927",
+    "Average": "\u5e73\u5747",
+    "Minimum": "\u6700\u5c0f",
+    "Outer": "\u5916",
+    "Inner": "\u5185",
+    "Front": "\u524d",
+    "Rear": "\u540e",
+    "Active": "\u6709\u6548\u6bb5",
+    "Total": "\u603b",
+    "Lower": "\u4e0b\u9650",
+    "Upper": "\u4e0a\u9650",
+}
+
+# Longest-first key list for greedy replacement
+_PART_KEYS = sorted(PART_ZH.keys(), key=len, reverse=True)
+
+# Node-temperature single tokens (split on '_' and spaces)
+_NODE_TOKEN_ZH: Dict[str, str] = {
+    "Ambient": "\u73af\u5883",
+    "Housing": "\u673a\u58f3",
+    "Endcap": "\u7aef\u76d6",
+    "OH": "\u60ac\u4f38",
+    "F": "\u524d",
+    "R": "\u540e",
+    "Stator": "\u5b9a\u5b50",
+    "Yoke": "\u8f6d",
+    "Surface": "\u8868\u9762",
+    "Shaft": "\u8f74",
+    "Centre": "\u4e2d\u5fc3",
+    "Bearing": "\u8f74\u627f",
+    "Front": "\u524d",
+    "Rear": "\u540e",
+    "Rotor": "\u8f6c\u5b50",
+    "Magnet": "\u78c1\u94a2",
+    "HousingWJ": "\u673a\u58f3\u6c34\u9053",
+    "Inlet": "\u8fdb\u53e3",
+    "ESpace": "\u7aef\u90e8\u7a7a\u95f4",
+    "Inner": "\u5185",
+    "Outer": "\u5916",
+    "Active": "\u6709\u6548\u6bb5",
+    "EWdg": "\u7aef\u90e8\u7ed5\u7ec4",
+    "Out": "\u5916",
+    "In": "\u5185",
+    "End": "\u7aef",
+    "Avg": "\u5e73\u5747",
+    "Average": "\u5e73\u5747",
+    "SlotCentre": "\u69fd\u4e2d\u5fc3",
+    "Liner": "\u69fd\u886c",
+    "Wdg": "\u7ed5\u7ec4",
+    "Tooth": "\u9f7f",
+    "Wedge": "\u69fd\u6954",
+    "Winding": "\u7ed5\u7ec4",
+}
+
+
+def _translate_part(text: str) -> str:
+    """Greedy longest-match replacement using PART_ZH."""
+    out = re.sub(r"\s+", " ", text)
+    for key in _PART_KEYS:
+        if key in out:
+            out = out.replace(key, PART_ZH[key])
+    return out.strip()
+
+
+def _translate_node_name(name: str) -> str:
+    """Translate a Node Temperatures component name token by token.
+
+    Keeps (C1)/(C2) winding markers and parentheses intact.
+    Example: "EWdg_Inner_Out(C1)" -> "\u7aef\u90e8\u7ed5\u7ec4_\u5185_\u5916(C1)".
+    """
+    m = re.match(r"^(.*?)(\([^)]*\))?$", name)
+    base, suffix = m.group(1), (m.group(2) or "")
+
+    def _tok(t: str) -> str:
+        return _NODE_TOKEN_ZH.get(t, t)
+
+    # Split on underscores and spaces, translate each token.
+    parts = re.split(r"([_ ])", base)
+    translated = "".join(_tok(p) if p not in ("_", " ") else p for p in parts)
+    # Parenthesized qualifiers inside base (e.g. "EWdg_Outer (Average)")
+    translated = re.sub(
+        r"\(([^)]*)\)",
+        lambda mm: "(" + _NODE_TOKEN_ZH.get(mm.group(1), mm.group(1)) + ")",
+        translated,
+    )
+    return translated + suffix
+
+
+def translate_field(name: str, section: str = "") -> str:
+    """Translate a Motor-CAD export field name to Chinese.
+
+    Returns the original name when no rule matches (never invents).
+    """
+    # Layer 1: exact dictionary
+    if name in EXACT_ZH:
+        return EXACT_ZH[name]
+
+    # Electrical loading with variable radius
+    m = re.match(r"^Electrical loading \(Section (\d+)\) \(Radius ([\d.]+) mm\)$", name)
+    if m:
+        return f"\u7535\u8d1f\u8377(\u622a\u9762{m.group(1)})(\u534a\u5f84{m.group(2)}mm)"
+
+    # Layer 2: bracketed thermal patterns
+    m = re.match(r"^T \[(.+)\]$", name)
+    if m:
+        return f"\u6e29\u5ea6[{_translate_part(m.group(1))}]"
+
+    m = re.match(r"^Rt \[(.+)\](?:\s*-\s*(.+))?$", name)
+    if m:
+        suffix = ""
+        if m.group(2):
+            raw_suf = m.group(2).strip()
+            if raw_suf in ("Con", "Conv"):
+                suffix = "-\u5bf9\u6d41"
+            elif raw_suf in ("Radn", "Rad"):
+                suffix = "-\u8f90\u5c04"
+            else:
+                suffix = "-" + _translate_part(raw_suf)
+        return f"\u70ed\u963b[{_translate_part(m.group(1))}]{suffix}"
+
+    # Rt [...] (qualifier) e.g. "Rt [Winding (Average) - Ambient] (Total Losses)"
+    m = re.match(r"^Rt \[(.+)\] \((.+)\)$", name)
+    if m:
+        return f"\u70ed\u963b[{_translate_part(m.group(1))}]({_translate_part(m.group(2))})"
+
+    m = re.match(r"^Cap \[(.+)\]$", name)
+    if m:
+        return f"\u70ed\u5bb9[{_translate_part(m.group(1))}]"
+
+    m = re.match(r"^dT \[(.+)\]$", name)
+    if m:
+        return f"\u6e29\u5dee[{_translate_part(m.group(1))}]"
+
+    m = re.match(r"^Tc \[(.+)\]$", name)
+    if m:
+        return f"\u65f6\u95f4\u5e38\u6570[{_translate_part(m.group(1))}]"
+
+    m = re.match(r"^h \[(.+)\]\s*-?\s*(.+)$", name)
+    if m:
+        return f"\u6362\u70ed\u7cfb\u6570[{_translate_part(m.group(1))}]-{_translate_part(m.group(2))}"
+
+    m = re.match(r"^h-(.+)$", name)
+    if m:
+        return f"\u6362\u70ed\u7cfb\u6570-{_translate_part(m.group(1))}"
+
+    m = re.match(r"^Area \[(.+)\](?:\s*-\s*(.+))?$", name)
+    if m:
+        suffix = ("-" + _translate_part(m.group(2))) if m.group(2) else ""
+        return f"\u9762\u79ef[{_translate_part(m.group(1))}]{suffix}"
+
+    # Heat flow arrows: "A -> B"
+    if " -> " in name:
+        left, right = name.split(" -> ", 1)
+        return f"{_translate_part(left)} -> {_translate_part(right)}"
+
+    # HousingWJ-prefixed water jacket fields
+    if name.startswith(("HousingWJ", "WJ ", "Rt HousingWJ")):
+        return _translate_part(name)
+
+    # Layer 3: Node Temperatures token translation
+    if section.endswith("Node Temperatures") or section == "Node Temperatures":
+        return _translate_node_name(name)
+
+    return name

+ 31 - 0
src/afmcore/xlsx_report.py

@@ -168,6 +168,7 @@ def write_summary_xlsx(
     col_headers: List[str],
     data_rows: List[Dict[str, Any]],
     sheet_title: str = "scan_results",
+    field_map: List[Dict[str, Any]] | None = None,
 ) -> None:
     """Write scan results as .xlsx with a two-row header.
 
@@ -181,6 +182,10 @@ def write_summary_xlsx(
         col_headers: header text (row 2) per column.
         data_rows: list of dicts keyed by col_keys.
         sheet_title: worksheet name.
+        field_map: optional per-column mapping entries
+            {"header", "category", "original", "unit"} written to a second
+            sheet so readers can correlate Chinese headers with native
+            Motor-CAD field names.
     """
     from openpyxl import Workbook
     from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
@@ -246,4 +251,30 @@ def write_summary_xlsx(
     last_col = get_column_letter(n_cols)
     ws.auto_filter.ref = f"A2:{last_col}{len(data_rows) + 2}"
 
+    # Sheet 2: field mapping (Chinese header <-> native Motor-CAD name).
+    if field_map:
+        map_title = "\u5b57\u6bb5\u5bf9\u7167"
+        ws2 = wb.create_sheet(map_title)
+        map_headers = ["\u5217\u53f7", "\u7c7b\u76ee", "\u4e2d\u6587\u8868\u5934", "Motor-CAD\u539f\u59cb\u5b57\u6bb5", "\u5355\u4f4d"]
+        for ci, h in enumerate(map_headers):
+            cell = ws2.cell(row=1, column=ci + 1, value=h)
+            cell.font = header_font
+            cell.fill = header_fill
+            cell.alignment = center
+            cell.border = thin_border
+        for ri, entry in enumerate(field_map):
+            values = [
+                ri + 1,
+                entry.get("category", ""),
+                entry.get("header", ""),
+                entry.get("original", ""),
+                entry.get("unit", ""),
+            ]
+            for ci, v in enumerate(values):
+                cell = ws2.cell(row=ri + 2, column=ci + 1, value=v)
+                cell.border = thin_border
+                cell.alignment = Alignment(vertical="center")
+        ws2.freeze_panes = "A2"
+        ws2.auto_filter.ref = f"A1:E{len(field_map) + 1}"
+
     wb.save(str(xlsx_path))

+ 32 - 6
web/backend/app/routers/plans.py

@@ -392,8 +392,8 @@ def export_plan_results_xlsx(plan_id: int, db: Session = Depends(get_db)):
     if not results:
         raise HTTPException(status_code=400, detail="No simulation results to export")
 
-    col_keys, col_categories, col_headers, data_rows = _collect_export_rows(
-        plan, results
+    col_keys, col_categories, col_headers, data_rows, field_map = (
+        _collect_export_rows(plan, results)
     )
 
     ts = datetime.now().strftime("%Y%m%d_%H%M%S")
@@ -401,7 +401,10 @@ def export_plan_results_xlsx(plan_id: int, db: Session = Depends(get_db)):
         prefix=f"scan_results_{ts}_", suffix=".xlsx", delete=False
     )
     tmp.close()
-    write_summary_xlsx(tmp.name, col_keys, col_categories, col_headers, data_rows)
+    write_summary_xlsx(
+        tmp.name, col_keys, col_categories, col_headers, data_rows,
+        field_map=field_map,
+    )
 
     plan_name = (plan.name or f"plan_{plan_id}").strip() or f"plan_{plan_id}"
     # RFC 5987: ASCII fallback + UTF-8 filename* for Chinese plan names.
@@ -489,9 +492,22 @@ def _collect_export_rows(plan, results):
         param_names, sorted(present_metrics), param_labels
     )
 
+    # Field mapping for the "字段对照" sheet: lets readers correlate every
+    # exported column with the native Motor-CAD export field name.
+    # Entries: (header, category, original_name, unit).
+    field_map: list = []
+    for key, cat, header in zip(col_keys, col_categories, col_headers):
+        field_map.append({
+            "header": header, "category": cat,
+            "original": key if key not in ("run_index", "status", "solve_time_s", "error") else "",
+            "unit": "",
+        })
+
     # Raw columns: union over all rows, first-seen order, grouped by the
     # Motor-CAD export section (Chinese category names). Thermal sections
     # were prefixed "Thermal-" by the solver; display as "热仿真-".
+    from afmcore.export_field_zh import translate_field
+
     seen_raw: set = set()
     for row in data_rows:
         for key in row:
@@ -502,7 +518,10 @@ def _collect_export_rows(plan, results):
             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
+            # Chinese display name (falls back to the original when no
+            # translation rule matches); original stays in the mapping sheet.
+            zh_name = translate_field(nm, sec)
+            header = f"{zh_name}[{entry_unit}]" if entry_unit else zh_name
             # Disambiguate duplicate headers (same field name exported in
             # two sections, e.g. 系统效率 in 驱动 and 电磁).
             if header in col_headers:
@@ -510,8 +529,13 @@ def _collect_export_rows(plan, results):
             col_keys.append(key)
             col_categories.append(category)
             col_headers.append(header)
+            field_map.append({
+                "header": header, "category": category,
+                "original": f"{nm}[{entry_unit}]" if entry_unit else nm,
+                "unit": entry_unit,
+            })
 
-    return col_keys, col_categories, col_headers, data_rows
+    return col_keys, col_categories, col_headers, data_rows, field_map
 
 
 @router.get("/{plan_id}/export-csv")
@@ -540,7 +564,9 @@ def export_plan_results_csv(plan_id: int, db: Session = Depends(get_db)):
     if not results:
         raise HTTPException(status_code=400, detail="No simulation results to export")
 
-    col_keys, _cats, col_headers, data_rows = _collect_export_rows(plan, results)
+    col_keys, _cats, col_headers, data_rows, _fmap = _collect_export_rows(
+        plan, results
+    )
 
     buf = io.StringIO()
     writer = csv.writer(buf)