Browse Source

feat: 工具结果 CSV 表头/产物文件名/工具名中文化

1) CSV 表头用字段中文名(key 仍留英文供取值)
   - 新增 FieldLabels 三层解析:按行类型精确 → table_field 表字段元数据 → 兜底并集
   - 兜底字典只收实体/DTO 的票且多数派 ≥0.6,拿不准的留英文,不猜错中文
   - 治理表查询优先用服务端 head 的中文列名;表头重名自动加序号防预览并列
   - 历史老产物磁盘不动,预览接口现场把英文表头翻成中文(行键同步换)
2) 产物文件名中文化
   - 磁盘 id 仍是 ASCII(路径白名单只放行安全字符),展示/下载名 fileName 用中文前缀:
     通话分析_20261009153012_ab12cd34.csv
3) 工具名中文化
   - 前端 TOOL_LABELS 由 8 条补齐到 69 条(覆盖后端全部 @Tool + reset_equipped_tools),
     时间线/状态栏/产物栏统一走 toolLabel

字典由 ai-server/scripts/gen_field_labels.py 从源码注释(javadoc 首行 / @ExcelProperty)
生成到 resources/agent/field-labels.json,改字段注释后重跑脚本即可。
cc 5 days ago
parent
commit
df03094137

+ 4 - 4
ai-electron/frontend/src/ai/api/types.ts

@@ -128,9 +128,9 @@ export interface ToolStep {
  * 数据本体在文件里,下载走 `/agent/artifact/download`。
  */
 export interface ArtifactMeta {
-  /** 产物 id(即服务端文件名) */
+  /** 产物 id(服务端磁盘文件名,ASCII 安全字符,预览/下载接口按它定位) */
   id: string;
-  /** 展示用文件名(当前与 id 同值) */
+  /** 展示与下载用的文件名(中文前缀,如 `通话分析_20261009153012_ab12cd34.csv`;历史消息里可能仍是英文 id) */
   fileName: string;
   /** 固定 csv */
   format?: string;
@@ -138,11 +138,11 @@ export interface ArtifactMeta {
   totalRows?: number;
   /** 文件字节数 */
   sizeBytes?: number;
-  /** 表头(列名,与聊天表格一致的键序) */
+  /** 表头(字段中文名,与 CSV 第一行同值;翻译不出来的列保留英文原名) */
   columns?: string[];
 }
 
-/** 产物预览响应:表头 + 前 N 行(总行数由调用方从 ArtifactMeta 取,接口不重复数行) */
+/** 产物预览响应:表头(字段中文名) + 前 N 行(总行数由调用方从 ArtifactMeta 取,接口不重复数行) */
 export interface ArtifactPreview {
   columns: string[];
   rows: Array<Record<string, string>>;

+ 84 - 0
ai-electron/frontend/src/ai/utils/constants.ts

@@ -31,17 +31,101 @@ export const SESSION_PAGE_SIZE = 20;
 
 /* ------------------------------------------------------------------ *
  * 工具名称 → 中文展示
+ *
+ * 覆盖聊天时间线/状态栏/产物栏会露出来的全部工具(后端 @Tool 名,见
+ * ai-server .../module/agent/tools 与 mcp/AgentScopeMcpToolProvider)。
+ * ★ 加了新工具要同步补这里,否则时间线会退回显示英文工具名;
+ *   产物文件名的中文前缀另有一份(后端 ArtifactService.ARTIFACT_LABELS)。
  * ------------------------------------------------------------------ */
 
 export const TOOL_LABELS: Record<string, string> = {
+  // ---- 基础设施 ----
   execute_sql: '执行 SQL 查询',
   list_tables: '列出数据表',
   search_table_schema: '检索表结构',
+  query_table_data: '查询表格数据',
   execute_python: '执行 Python 分析',
   render_chart: '校验图表配置',
   render_graph: '校验关系图谱',
   get_current_workspace: '读取当前工作空间',
   get_user_selected_workspace: '读取用户所选空间',
+  get_case_graph: '取关系图谱数据',
+
+  // ---- 人员画像与亲密度(person) ----
+  list_persons: '列出案件人员',
+  get_person_call_profile: '通话数据画像',
+  stat_person_call_station: '通话基站分布',
+  stat_person_call_top10: '最常联系人 TOP10',
+  stat_call_record_summary: '通话记录汇总',
+  get_person_trans_profile: '交易数据画像',
+  stat_person_trans_top10: '最常交易对手 TOP10',
+  stat_trans_record_summary: '交易记录汇总',
+  get_trans_bank_card_info: '银行卡交易汇总',
+  stat_trans_amount_trend_month: '交易金额月度趋势',
+  stat_trans_amount_trend_year: '交易金额年度趋势',
+  stat_intimacy_summary: '亲密度总览',
+
+  // ---- 通话明细(call) ----
+  get_call_records: '查询通话明细',
+  stat_call_night_summary: '夜间通话汇总',
+  stat_call_night_detail: '夜间通话明细',
+  stat_call_continuous_summary: '持续通话汇总',
+  stat_call_continuous_detail: '持续通话明细',
+  stat_call_sensitive_summary: '敏感日期通话汇总',
+  stat_call_sensitive_detail: '敏感日期通话明细',
+
+  // ---- 交易与资金(trans) ----
+  get_trans_records: '查询交易明细',
+  stat_trans_big_summary: '大额交易汇总',
+  stat_trans_big_by_date: '大额交易按日统计',
+  stat_trans_big_detail: '大额交易明细',
+  stat_trans_together: '共同交易分析',
+  stat_trans_card_hold: '代持卡分析',
+  stat_trans_cash_flow: '交易现金流汇总',
+  stat_trans_continuous_summary: '持续交易汇总',
+  stat_trans_continuous_detail: '持续交易明细',
+  stat_trans_fast_fund_flow: '资金快转分析',
+  stat_trans_financial: '金融交易筛选',
+  stat_trans_fixed_deposit: '定期存款汇总',
+  stat_trans_fixed_deposit_detail: '定期存款明细',
+  stat_trans_frequency: '交易频次统计',
+  stat_trans_sensitive_summary: '敏感日期交易汇总',
+  stat_trans_sensitive_detail: '敏感日期交易明细',
+
+  // ---- 轨迹 / 出行 / 快递(track) ----
+  stat_track_cell_tower_summary: '基站轨迹汇总',
+  stat_track_cell_tower_detail: '基站轨迹明细',
+  stat_track_en_local_summary: '境外昼夜活动汇总',
+  stat_track_en_local_detail: '境外昼夜活动明细',
+  stat_express_summary: '快递收寄汇总',
+  stat_express_by_phone: '按号码查快递',
+  get_express_records: '查询快递明细',
+  stat_track_meet_summary: '人员碰面汇总',
+  stat_track_meet_detail: '人员碰面明细',
+  stat_together_live_summary: '同住关系汇总',
+  stat_together_live_detail: '同住关系明细',
+  get_hotel_stay_detail: '旅馆住宿明细',
+  get_together_live_info_detail: '同住服务记录明细',
+  stat_together_travel_summary: '同行出行汇总',
+  stat_together_travel_detail: '同行出行明细',
+  get_travel_record_detail: '出行票务明细',
+
+  // ---- 关系图谱(graph) ----
+  // get_case_graph 见基础段
+
+  // ---- 非表格材料档案(file) ----
+  list_file_profiles: '检索材料清单',
+  stat_file_profiles_by_category: '材料分类统计',
+  get_file_sample: '读取材料正文片段',
+
+  // ---- 案件管理(MCP 接入用) ----
+  list_cases: '列出案件清单',
+  open_case: '切换案件',
+  current_case: '查看当前案件',
+  close_case: '解除案件绑定',
+
+  // ---- 框架内置(AgentScope 提供,源码里没有 @Tool 注解) ----
+  reset_equipped_tools: '切换工具组',
 };
 
 export function toolLabel(name?: string): string {

+ 176 - 0
ai-server/scripts/gen_field_labels.py

@@ -0,0 +1,176 @@
+# -*- coding: utf-8 -*-
+"""
+生成字段中文名字典 ai-server/src/main/resources/agent/field-labels.json。
+
+数据源(按优先级):
+  1. 字段上的 @ExcelProperty("中文名")(导出表头用,最权威)
+  2. 紧邻字段的 javadoc 首行(/** 本方姓名 */)
+
+产物两段:
+  classes —— 类全限定名 → {字段名: 中文名},用于「按行的实际类型精确取名」(如 CallNightDTO.time = 时间段标识)
+  "*"     —— 字段名 → 中文名 的兜底并集,仅收实体/DTO 的票且多数派占比 ≥ 0.6,
+             用于 SQL 列别名 / 手拼 Map 行这类拿不到类型的场景;拿不准的字段留英文不猜
+
+重新生成:python ai-server/scripts/gen_field_labels.py
+"""
+import json
+import pathlib
+import re
+import sys
+
+ROOT = pathlib.Path(__file__).resolve().parents[1] / "src/main/java"
+OUT = pathlib.Path(__file__).resolve().parents[1] / "src/main/resources/agent/field-labels.json"
+
+# 只扫业务数据类所在包:实体 / DTO / VO / 查询对象(服务类字段没有业务含义)
+SCAN_PREFIXES = ("com.zsjz.ai.common.model.", "com.zsjz.ai.module.")
+
+PACKAGE_RE = re.compile(r"^\s*package\s+([\w.]+)\s*;")
+CLASS_RE = re.compile(r"^\s*(?:@\w+(?:\([^)]*\))?\s*)*(?:public\s+|final\s+|abstract\s+)*"
+                      r"(?:class|record|enum)\s+(\w+)")
+# 字段声明:私有/受保护/公有 + 非 static/transient + 类型 + 名字 + 分号(方法带括号,天然排除)
+FIELD_RE = re.compile(r"^\s*(?:private|protected|public)\s+(?!static\b)(?!transient\b)"
+                      r"([\w.$?<>,\[\]\s]+?)\s+(\w+)\s*(?:=\s*[^;]+)?;\s*$")
+EXCEL_RE = re.compile(r'@ExcelProperty\s*\(([^;]*?)\)\s*$')
+QUOTED_RE = re.compile(r'"([^"]+)"')
+# 历史代码里有一批「//对方名称\")」形态的残缺行注释(Javadoc 被工具改坏了),同样当列名用
+BROKEN_COMMENT_RE = re.compile(r'^//\s*"?(.+?)"?\)\s*$')
+# javadoc 行内标记 {@link Xxx} / {@code xxx}:取标记里的文字,别让花括号把整条注释判废
+INLINE_TAG_RE = re.compile(r"\{@\w+\s+([^}]*)\}")
+
+
+def clean(text: str) -> str:
+    """注释行 → 可用的中文列名:去 * 前缀、摊平行内 javadoc 标记、去 HTML 标签与引号,截到第一个句读"""
+    text = re.sub(r"^[\s*]+|[\s*]+$", "", text).strip()
+    text = INLINE_TAG_RE.sub(lambda m: m.group(1), text)
+    text = re.sub(r"</?[a-zA-Z][^>]*>", "", text)  # <p> <b> 之类
+    # {@link #sampleText} 展开后是自引用「#sampleText 的字符数」——去掉引用,别让表头带上字段名
+    text = re.sub(r"#\w+\s*", "", text).lstrip("的").strip()
+    text = text.strip('"').strip()
+    if not text or text.startswith("@") or text.startswith("{@") or text.startswith("<"):
+        return ""
+    # 「借贷类型全部='',收入=收入」这类取值域说明不是列名
+    if any(ch in text for ch in "=;{}"):
+        return ""
+    # 「失败原因(FAIL / SKIP 时才有值),供排查」→ 取**位置最早**的句读截断
+    # (按分隔符优先级顺序找会漏掉更靠前的括号,必须比位置)
+    cuts = [i for i in (text.find(sep) for sep in ("。", ":", ",", ",", "(", "("))
+            if 0 < i <= 30]
+    if cuts:
+        text = text[:min(cuts)]
+    return text[:30].strip()
+
+
+def scan(path: pathlib.Path):
+    """返回 (类全限定名, {字段: 中文名});无可用中文名的类返回 None"""
+    lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
+    package = ""
+    cls = ""
+    fields = {}
+    i = 0
+    while i < len(lines):
+        line = lines[i]
+        if not cls:
+            m = PACKAGE_RE.match(line)
+            if m:
+                package = m.group(1)
+            m = CLASS_RE.match(line)
+            if m and package:
+                cls = m.group(1)
+            i += 1
+            continue
+
+        m = FIELD_RE.match(line)
+        if not m:
+            i += 1
+            continue
+        name = m.group(2)
+        if name == "serialVersionUID" or name in fields:
+            i += 1
+            continue
+
+        # 回溯:跳过注解行(可能多行),再找紧邻的 javadoc
+        excel = ""
+        j = i - 1
+        while j >= 0 and lines[j].strip().startswith("@"):
+            em = EXCEL_RE.match(lines[j].strip())
+            if em:
+                quoted = QUOTED_RE.findall(em.group(1))
+                if quoted:
+                    excel = quoted[-1]  # 多级表头取最后一级 = 该列标题
+            j -= 1
+        label = clean(excel)
+        if not label and j >= 0:
+            broken = BROKEN_COMMENT_RE.match(lines[j].strip())
+            if broken:
+                label = clean(broken.group(1))
+        if not label and j >= 0 and lines[j].strip().endswith("*/"):
+            k = j
+            while k >= 0 and "/**" not in lines[k]:
+                k -= 1
+            if k >= 0:
+                block = []
+                for raw in lines[k:j + 1]:
+                    body = raw.strip()
+                    body = body[3:] if body.startswith("/**") else body
+                    body = body[:-2] if body.endswith("*/") else body
+                    block.append(body.lstrip("*").strip())
+                for row in block:
+                    if not row or row.startswith("@"):
+                        continue
+                    label = clean(row)
+                    if label:
+                        break
+        if label:
+            fields[name] = label
+        i += 1
+
+    if not fields:
+        return None
+    return f"{package}.{cls}", fields
+
+
+def main() -> int:
+    classes = {}
+    for path in sorted(ROOT.rglob("*.java")):
+        rel = str(path.relative_to(ROOT)).replace("\\", "/")
+        if not rel.replace("/", ".").startswith(SCAN_PREFIXES):
+            continue
+        found = scan(path)
+        if found:
+            classes[found[0]] = found[1]
+
+    # 兜底并集(JSON 的 "*" 段):只收「实体 / DTO」这类数据类的票,
+    # 且要求多数派占比 >= 0.6 —— 拿不准的字段宁可留英文也不猜。
+    # 同名字段跨域含义不同(personName 在话单是「本方姓名」、在机票是「旅客姓名」;
+    # count 在统计行是「份数」、在轨迹是「同行次数」),猜错比不翻更误导。
+    # 行本身带类型(classes 段)或 table_field 里有的字段,不依赖这段兜底。
+    votes: dict[str, dict[str, int]] = {}
+    for fqn in sorted(classes):
+        if ".entity." not in fqn and ".dto." not in fqn:
+            continue
+        weight = 2 if ".entity." in fqn else 1
+        for key, cn in classes[fqn].items():
+            bucket = votes.setdefault(key, {})
+            bucket[cn] = bucket.get(cn, 0) + weight
+    fallback = {}
+    for key, bucket in votes.items():
+        ranked = sorted(bucket.items(), key=lambda kv: (-kv[1], kv[0]))
+        if ranked[0][1] / sum(bucket.values()) >= 0.6:
+            fallback[key] = ranked[0][0]
+
+    payload = {
+        "_comment": "由 ai-server/scripts/gen_field_labels.py 生成,勿手改;"
+                    "重新生成后按需提交。classes=按类精确取名,*=SQL 别名/Map 行的兜底。",
+        "classes": classes,
+        "*": fallback,
+    }
+    OUT.parent.mkdir(parents=True, exist_ok=True)
+    OUT.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
+                   encoding="utf-8")
+    print(f"classes={len(classes)} fields={sum(len(v) for v in classes.values())} "
+          f"fallback={len(fallback)} -> {OUT}")
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())

+ 48 - 0
ai-server/src/main/java/com/zsjz/ai/common/cache/GlobalCache.java

@@ -141,6 +141,24 @@ public class GlobalCache {
         return tableHead;
     }
 
+    /**
+     * 字段英文名 → 中文名(跨表拉平,键为 {@code table_field.field_name_en})。
+     *
+     * <p>用途:工具结果落 CSV 产物/预览表时把英文列名换成中文表头
+     * (见 {@link com.zsjz.ai.common.utils.FieldLabels})。同名字段跨表含义可能不同,
+     * 这里按 {@code table_field} 主键序先到先得——拿得到行类型的场景应优先按类型精确取名。
+     */
+    private static volatile Map<String, String> fieldCnMap = Map.of();
+
+    public static Map<String, String> fieldCnMap() {
+        return fieldCnMap;
+    }
+
+    /** 字段英文名 → 中文名;未知返回 {@code null} */
+    public static String fieldCn(String fieldNameEn) {
+        return fieldCnMap.get(fieldNameEn);
+    }
+
     /**
      * 方向配置映射,存储与帐话单方向相关的配置信息。
      */
@@ -156,6 +174,7 @@ public class GlobalCache {
     public static void loadSnapshotForTesting(Map<Integer, TableInfo> templates, Map<Integer, List<TableField>> fields) {
         templateTableMap = Collections.unmodifiableMap(new HashMap<>(templates));
         tableFieldCache = Collections.unmodifiableMap(new HashMap<>(fields));
+        fieldCnMap = Collections.unmodifiableMap(buildFieldCnMap(tableFieldCache, Map.of()));
     }
 
     /**
@@ -164,6 +183,7 @@ public class GlobalCache {
     public static void clearForTesting() {
         templateTableMap = Map.of();
         tableFieldCache = Map.of();
+        fieldCnMap = Map.of();
     }
 
     /**
@@ -192,6 +212,7 @@ public class GlobalCache {
         Map<String, List<BasicColumn>> newTableHead = new HashMap<>(100);
         buildFieldCaches(newTableInfoList, newDirectionConfMap, newTableFieldCache, newAllFieldsMap,
                 newAllMatchedFieldsMap, newTableHead);
+        Map<String, String> newFieldCnMap = buildFieldCnMap(newTableFieldCache, newTableHead);
 
         // 全部构建完成后一次性整体替换引用(volatile 写,读方立即可见且只见完整快照)
         tableInfoList = List.copyOf(newTableInfoList);
@@ -202,9 +223,36 @@ public class GlobalCache {
         allFieldsMap = Collections.unmodifiableMap(newAllFieldsMap);
         allMatchedFieldsMap = Collections.unmodifiableMap(newAllMatchedFieldsMap);
         tableHead = Collections.unmodifiableMap(newTableHead);
+        fieldCnMap = Collections.unmodifiableMap(newFieldCnMap);
         log.info("initCache success");
     }
 
+    /**
+     * 字段英文名 → 中文名 的拉平映射:{@code table_field} 全量字段 + 派生表头
+     * (表头里有 {@code table_field} 没有的统计列,如「交易总金额 totalMoney」)。
+     *
+     * <p>同名先到先得({@code table_field} 按主键序读入),空中文名不入表。
+     */
+    private static Map<String, String> buildFieldCnMap(Map<Integer, List<TableField>> fieldsByTable,
+                                                       Map<String, List<BasicColumn>> heads) {
+        Map<String, String> map = new HashMap<>();
+        fieldsByTable.values().stream().flatMap(List::stream).forEach(f -> {
+            String en = f.getFieldNameEn();
+            String cn = f.getFieldNameCn();
+            if (StrUtil.isNotBlank(en) && StrUtil.isNotBlank(cn)) {
+                map.putIfAbsent(en, cn);
+            }
+        });
+        heads.values().stream().flatMap(List::stream).forEach(col -> {
+            String en = col.getDataIndex();
+            String cn = col.getTitle();
+            if (StrUtil.isNotBlank(en) && StrUtil.isNotBlank(cn)) {
+                map.putIfAbsent(en, cn);
+            }
+        });
+        return map;
+    }
+
     /**
      * 构建字段侧缓存(方向配置、字段缓存、全字段、匹配字段、表头)。
      * 纯构建方法:只写传入的局部容器,不触碰 volatile 字段。

+ 216 - 0
ai-server/src/main/java/com/zsjz/ai/common/utils/FieldLabels.java

@@ -0,0 +1,216 @@
+package com.zsjz.ai.common.utils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.zsjz.ai.common.cache.GlobalCache;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 字段英文名 → 中文名解析器(工具结果表格的表头/列标签统一走这里)。
+ *
+ * <p>工具吐出的行是 {@code LinkedHashMap}(键 = 英文字段名),落到 CSV 产物与预览表里时
+ * 用户要看的是中文列名,因此解析链是三层,从准到宽:
+ * <ol>
+ *   <li><b>按类型精确</b>:{@link #label(Class, String)} 先查该行真实类型的字段注释
+ *       ({@code field-labels.json} 的 {@code classes} 段)——同一个 {@code callCount}
+ *       在 {@code CallNightDTO} 是「通话总次数」、在 {@code TrackCellTowerDTO} 是「通信次数」,
+ *       只有带上类型才不会张冠李戴;</li>
+ *   <li><b>表字段元数据</b>:{@link GlobalCache#fieldCn(String)},来自 {@code table_field}
+ *       (与 {@code list_tables} 给模型的「字段中文注释」同源),是物理表列的权威中文名;</li>
+ *   <li><b>兜底并集</b>:{@code field-labels.json} 的 {@code *} 段——SQL 列别名、
+ *       工具内手工拼的 {@code Map} 行这类拿不到类型时用。只收实体/DTO 的票且多数派
+ *       占比 ≥ 0.6,拿不准的一律不猜({@code personName} 在话单是「本方姓名」、
+ *       在机票是「旅客姓名」,猜错比不翻更误导);</li>
+ * </ol>
+ * 三层都没命中就返回 {@code null},调用方保留英文原名(宁可露英文,也不能给错中文)。
+ *
+ * <p>字典文件由 {@code ai-server/scripts/gen_field_labels.py} 从源码注释
+ * (字段 javadoc 首行 / {@code @ExcelProperty})生成,改动了 DTO 字段注释后重新跑脚本即可。
+ * 文件缺失或解析失败只降级为空字典,不影响工具主流程。
+ */
+@Slf4j
+public final class FieldLabels {
+
+    /** 生成的字典(classpath 资源) */
+    private static final String RESOURCE = "/agent/field-labels.json";
+
+    /** 类全限定名 → {字段名: 中文名} */
+    private static final Map<String, Map<String, String>> CLASS_LABELS;
+
+    /** 字段名 → 中文名 的兜底并集 */
+    private static final Map<String, String> GLOBAL_LABELS;
+
+    static {
+        Map<String, Map<String, String>> classes = new HashMap<>();
+        Map<String, String> global = new HashMap<>();
+        try (InputStream in = FieldLabels.class.getResourceAsStream(RESOURCE)) {
+            if (in == null) {
+                log.warn("字段名字典缺失: {}", RESOURCE);
+            } else {
+                JsonNode root = Json.objectMapper().readTree(in);
+                Map<String, Map<String, String>> raw =
+                        Json.objectMapper().convertValue(root.path("classes"),
+                                new TypeReference<Map<String, Map<String, String>>>() {
+                                });
+                Map<String, String> flat =
+                        Json.objectMapper().convertValue(root.path("*"),
+                                new TypeReference<Map<String, String>>() {
+                                });
+                copyClassSection(raw, classes);
+                copyFlatSection(flat, global);
+            }
+        } catch (Exception e) {
+            log.warn("字段名字典加载失败,列名将回退英文原名: {}", e.getMessage());
+            classes.clear();
+            global.clear();
+        }
+        CLASS_LABELS = Collections.unmodifiableMap(classes);
+        GLOBAL_LABELS = Collections.unmodifiableMap(global);
+    }
+
+    private FieldLabels() {
+    }
+
+    /** 过滤掉空中文名(生成脚本只在有注释时产出条目,这里再兜一次底) */
+    private static void copyClassSection(Map<String, Map<String, String>> src,
+                                         Map<String, Map<String, String>> dest) {
+        if (src == null) {
+            return;
+        }
+        src.forEach((k, v) -> {
+            if (v == null) {
+                return;
+            }
+            Map<String, String> labels = new HashMap<>();
+            v.forEach((field, cn) -> {
+                if (cn != null && !cn.isBlank()) {
+                    labels.put(field, cn);
+                }
+            });
+            if (!labels.isEmpty()) {
+                dest.put(k, labels);
+            }
+        });
+    }
+
+    /** 单层 {@code {字段: 中文名}} 的同款过滤 */
+    private static void copyFlatSection(Map<String, String> src, Map<String, String> dest) {
+        if (src == null) {
+            return;
+        }
+        src.forEach((k, v) -> {
+            if (v != null && !v.isBlank()) {
+                dest.put(k, v);
+            }
+        });
+    }
+
+
+    /**
+     * 兜底解析:表字段元数据 → 生成的并集字典。
+     *
+     * @param key 英文字段名
+     * @return 中文名;未知返回 {@code null}(调用方保留原名)
+     */
+    public static String label(String key) {
+        if (key == null || key.isBlank()) {
+            return null;
+        }
+        String cn = GlobalCache.fieldCn(key);
+        if (isPresent(cn)) {
+            return cn;
+        }
+        cn = GLOBAL_LABELS.get(key);
+        return isPresent(cn) ? cn : null;
+    }
+
+    /**
+     * 按行的真实类型精确解析(未命中再走 {@link #label(String)} 兜底)。
+     *
+     * @param type 行的源类型(POJO),传 {@code null} 等价于兜底解析
+     */
+    public static String label(Class<?> type, String key) {
+        if (type != null && key != null) {
+            Map<String, String> labels = CLASS_LABELS.get(type.getName());
+            if (labels != null) {
+                String cn = labels.get(key);
+                if (isPresent(cn)) {
+                    return cn;
+                }
+            }
+        }
+        return label(key);
+    }
+
+    /**
+     * 一批英文列名 → 展示用表头:能翻译的换成中文,翻译不出来保留原名(见 {@link #uniqueHeaders})。
+     *
+     * @param keys 英文列名(保持入参顺序)
+     * @param type 行的源类型,可为 {@code null}
+     */
+    public static List<String> headers(List<String> keys, Class<?> type) {
+        if (keys == null || keys.isEmpty()) {
+            return List.of();
+        }
+        List<String> display = new ArrayList<>(keys.size());
+        for (String key : keys) {
+            String cn = label(type, key);
+            display.add(cn != null ? cn : key);
+        }
+        return uniqueHeaders(display);
+    }
+
+    /**
+     * 表头去重:CSV 表头若重名,预览回读会把同名列并成一列丢数据,
+     * 因此重名的后到者追加序号({@code 卡号_2}),空名占位为「列N」。
+     *
+     * @param candidates 已选好的展示名(可含 {@code null}/空串)
+     */
+    public static List<String> uniqueHeaders(List<String> candidates) {
+        if (candidates == null || candidates.isEmpty()) {
+            return List.of();
+        }
+        List<String> out = new ArrayList<>(candidates.size());
+        Set<String> used = new HashSet<>();
+        for (int i = 0; i < candidates.size(); i++) {
+            String raw = candidates.get(i);
+            String name = (raw == null || raw.isBlank()) ? "列" + (i + 1) : raw;
+            if (used.add(name)) {
+                out.add(name);
+                continue;
+            }
+            int n = 2;
+            String candidate = name + "_" + n;
+            while (!used.add(candidate)) {
+                candidate = name + "_" + (++n);
+            }
+            out.add(candidate);
+        }
+        return out;
+    }
+
+    private static boolean isPresent(String value) {
+        return value != null && !value.isBlank();
+    }
+
+    /** 仅测试与诊断用:字典是否加载成功 */
+    public static Map<String, String> globalLabelsSnapshot() {
+        return GLOBAL_LABELS;
+    }
+
+    /** 仅测试与诊断用:某类的字段字典 */
+    public static Map<String, String> classLabelsSnapshot(Class<?> type) {
+        Map<String, String> labels = CLASS_LABELS.get(type.getName());
+        return labels == null ? Map.of() : new LinkedHashMap<>(labels);
+    }
+}

+ 26 - 12
ai-server/src/main/java/com/zsjz/ai/module/agent/artifact/ArtifactCsvWriter.java

@@ -18,7 +18,9 @@ import java.util.Map;
  *
  * <p><b>三条硬性口径</b>(与聊天内预览是两条独立路径):
  * <ul>
- *   <li><b>带表头</b>:列名取自首行数据的键序(与聊天表格列一致);</li>
+ *   <li><b>带表头</b>:表头写<b>字段中文名</b>(翻译不出来的列保留英文原名,见
+ *       {@link com.zsjz.ai.common.utils.FieldLabels}),列序与聊天表格列一致,
+ *       单元格仍按英文字段名取值;</li>
  *   <li><b>UTF-8 带 BOM</b>:Excel 直接双击打开中文表头不乱码;</li>
  *   <li><b>单元格不截断</b>:聊天预览有 500 字符/单元格截断({@code ToolResultTable.CELL_LIMIT}),
  *       产物文件写原始完整值,截断只发生在「给模型看」的路径上。</li>
@@ -37,7 +39,10 @@ public final class ArtifactCsvWriter implements AutoCloseable {
     private static final int FLUSH_EVERY = 1000;
 
     private final Path file;
-    private final List<String> columns;
+    /** 表头(展示名,优先中文) */
+    private final List<String> header;
+    /** 取值键(行数据的英文字段名,与 header 一一对应同序同长) */
+    private final List<String> rowKeys;
     private final BufferedWriter writer;
     private final CSVPrinter printer;
 
@@ -45,9 +50,11 @@ public final class ArtifactCsvWriter implements AutoCloseable {
     private int sinceFlush;
     private boolean closed;
 
-    private ArtifactCsvWriter(Path file, List<String> columns, BufferedWriter writer, CSVPrinter printer) {
+    private ArtifactCsvWriter(Path file, List<String> header, List<String> rowKeys,
+                              BufferedWriter writer, CSVPrinter printer) {
         this.file = file;
-        this.columns = columns;
+        this.header = header;
+        this.rowKeys = rowKeys;
         this.writer = writer;
         this.printer = printer;
     }
@@ -55,9 +62,15 @@ public final class ArtifactCsvWriter implements AutoCloseable {
     /**
      * 建文件并写表头(父目录不存在则创建)
      *
-     * @param columns 列名,决定 CSV 表头与每行取值的键序
+     * @param header  表头(写进 CSV 第一行的展示名,用户看到的是字段中文名)
+     * @param rowKeys 取值键(行数据里的英文字段名,与 {@code header} 等长同序)
      */
-    public static ArtifactCsvWriter open(Path file, List<String> columns) throws IOException {
+    public static ArtifactCsvWriter open(Path file, List<String> header, List<String> rowKeys)
+            throws IOException {
+        if (header.size() != rowKeys.size()) {
+            throw new IllegalArgumentException("表头与取值键必须等长: header="
+                    + header.size() + ", rowKeys=" + rowKeys.size());
+        }
         Path parent = file.getParent();
         if (parent != null) {
             Files.createDirectories(parent);
@@ -65,18 +78,19 @@ public final class ArtifactCsvWriter implements AutoCloseable {
         BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8);
         writer.write(BOM);
         CSVPrinter printer = new CSVPrinter(writer, CSVFormat.DEFAULT);
-        printer.printRecord(columns);
+        printer.printRecord(header);
         printer.flush();
-        return new ArtifactCsvWriter(file, List.copyOf(columns), writer, printer);
+        return new ArtifactCsvWriter(file, List.copyOf(header), List.copyOf(rowKeys), writer, printer);
     }
 
     /**
-     * 写一行:按建文件时的列序取值,缺失的键写空,多余的键忽略
+     * 写一行:按建文件时的列序取值(表头是中文、行键是英文,按 {@code rowKeys} 对位取),
+     * 缺失的键写空,多余的键忽略
      */
     public void writeRow(Map<String, Object> row) throws IOException {
-        List<String> values = new ArrayList<>(columns.size());
-        for (String col : columns) {
-            values.add(stringify(row == null ? null : row.get(col)));
+        List<String> values = new ArrayList<>(rowKeys.size());
+        for (String key : rowKeys) {
+            values.add(stringify(row == null ? null : row.get(key)));
         }
         printer.printRecord(values);
         rows++;

+ 90 - 17
ai-server/src/main/java/com/zsjz/ai/module/agent/artifact/ArtifactService.java

@@ -3,6 +3,7 @@ package com.zsjz.ai.module.agent.artifact;
 import com.zsjz.ai.common.constants.PathConst;
 import com.zsjz.ai.common.context.CaseContextHolder;
 import com.zsjz.ai.common.exception.ServerException;
+import com.zsjz.ai.common.utils.FieldLabels;
 import com.zsjz.ai.module.agent.config.ArtifactProperties;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -62,6 +63,21 @@ public class ArtifactService {
     /** id 只允许:字母、数字、下划线、连字符、点(点不得构成 ".."),与 Python 产物同一口径 */
     private static final String ID_PATTERN = "[A-Za-z0-9_\\-]+(\\.[A-Za-z0-9_\\-]+)*";
 
+    /**
+     * 工具标识 → 产物文件名的中文前缀。
+     *
+     * <p>键 = 各工具类的 {@code ARTIFACT_TOOL}(工具域标识)与两个基础设施工具名,
+     * 与前端 {@code TOOL_LABELS} 的措辞保持一致;未登记的兜底「数据查询」。
+     */
+    private static final Map<String, String> ARTIFACT_LABELS = Map.of(
+            "call_analysis", "通话分析",
+            "trans_analysis", "交易分析",
+            "track_analysis", "轨迹出行与快递",
+            "person_analysis", "人员画像与亲密度",
+            "file_analysis", "非表格材料档案",
+            "execute_sql", "SQL查询",
+            "query_table_data", "表格查询");
+
     private final ArtifactProperties props;
 
     /** 随工具结果回传的预览行数(模型与前端都不看全量,全量在 CSV 里) */
@@ -79,6 +95,9 @@ public class ArtifactService {
     /**
      * 全量行 → CSV 产物(业务分析工具用:行已在内存里)。
      *
+     * <p><b>表头写字段中文名</b>:取列定义里的 {@code label}(工具侧已按字段类型/表元数据
+     * 翻译),翻译不出来的列保留英文原名——宁可露英文也不给错中文。
+     *
      * @param toolName 用于文件名的工具标识(如 {@code call_analysis})
      * @param columns  列定义({@code ToolResultTable#buildColumns} 的产物:每项含 key/label,
      *                 键序即表头顺序)
@@ -90,11 +109,15 @@ public class ArtifactService {
         if (rows == null || rows.isEmpty() || columns == null || columns.isEmpty()) {
             return null;
         }
-        List<String> header = new ArrayList<>(columns.size());
+        List<String> rowKeys = new ArrayList<>(columns.size());
+        List<String> display = new ArrayList<>(columns.size());
         for (Map<String, Object> col : columns) {
             Object key = col.get("key");
-            header.add(key == null ? "" : String.valueOf(key));
+            String name = key == null ? "" : String.valueOf(key);
+            rowKeys.add(name);
+            display.add(resolveHeader(col.get("label"), name));
         }
+        List<String> header = FieldLabels.uniqueHeaders(display);
         Path dir = artifactsRoot();
         if (dir == null) {
             log.warn("无案件上下文,跳过产物导出: tool={}", toolName);
@@ -103,15 +126,16 @@ public class ArtifactService {
         Path file = null;
         try {
             cleanupExpired(dir);
-            file = dir.resolve(buildId(toolName));
+            Names names = buildNames(toolName);
+            file = dir.resolve(names.id());
             long total;
-            try (ArtifactCsvWriter writer = ArtifactCsvWriter.open(file, header)) {
+            try (ArtifactCsvWriter writer = ArtifactCsvWriter.open(file, header, rowKeys)) {
                 for (Map<String, Object> row : rows) {
                     writer.writeRow(row);
                 }
                 total = writer.closeAndGetRows();
             }
-            return meta(file.getFileName().toString(), header, total, file);
+            return meta(names.id(), names.fileName(), header, total, file);
         } catch (Exception e) {
             log.warn("产物导出失败: tool={}, error={}", toolName, e.getMessage(), e);
             deleteQuietly(file);
@@ -119,6 +143,19 @@ public class ArtifactService {
         }
     }
 
+    /**
+     * 列的展示名:列定义的 {@code label} 优先(工具侧带的中文名),为空或与 key 相同时
+     * 再按字典翻译一次,仍翻译不出来就用英文 key。
+     */
+    private static String resolveHeader(Object label, String key) {
+        String cn = label instanceof String s ? s.trim() : null;
+        if (cn != null && !cn.isEmpty() && !cn.equals(key)) {
+            return cn;
+        }
+        String resolved = FieldLabels.label(key);
+        return resolved == null || resolved.equals(key) ? key : resolved;
+    }
+
     /**
      * 流式导出({@code execute_sql} 用):查询结果一边返回一边写文件,
      * 内存里只保留需要做聊天预览的前 N 行,因此行数无上限也不会撑爆堆。
@@ -130,7 +167,7 @@ public class ArtifactService {
      *     artifact = sink.finish();
      * }
      * </pre>
-     * 首行到达时才建文件(列名取首行键序);一行都没有则不产文件。
+     * 首行到达时才建文件(列序取首行键序,表头写字段中文名);一行都没有则不产文件。
      */
     public RowSink exportStream(String toolName) {
         return new RowSink(toolName);
@@ -144,8 +181,13 @@ public class ArtifactService {
 
         private final String toolName;
         private ArtifactCsvWriter writer;
-        private List<String> columns;
+        /** 行数据的英文字段名(取值键,首行键序) */
+        private List<String> rowKeys;
+        /** CSV 表头(字段中文名,翻译不出来的列保留英文原名) */
+        private List<String> header;
         private String id;
+        /** 中文展示名(卡片与下载用);磁盘文件名仍是 ASCII 的 {@link #id} */
+        private String fileName;
         /** 首行到达时确定的文件路径(避免解析期反复取案件上下文) */
         private Path file;
         private long total;
@@ -168,10 +210,13 @@ public class ArtifactService {
                         return;
                     }
                     cleanupExpired(dir);
-                    columns = new ArrayList<>(row.keySet());
-                    id = buildId(toolName);
+                    rowKeys = new ArrayList<>(row.keySet());
+                    header = FieldLabels.headers(rowKeys, null);
+                    Names names = buildNames(toolName);
+                    id = names.id();
+                    fileName = names.fileName();
                     file = dir.resolve(id);
-                    writer = ArtifactCsvWriter.open(file, columns);
+                    writer = ArtifactCsvWriter.open(file, header, rowKeys);
                 }
                 writer.writeRow(row);
                 total++;
@@ -202,7 +247,7 @@ public class ArtifactService {
                 deleteQuietly(file);
                 return null;
             }
-            return meta(id, columns, total, file);
+            return meta(id, fileName, header, total, file);
         }
 
         @Override
@@ -235,6 +280,10 @@ public class ArtifactService {
      * <p>总行数不在这里数(大文件会白读一遍)——前端从工具结果里的
      * {@code artifact.totalRows} 已经拿到了。
      *
+     * <p><b>表头中文化</b>:中文化改造之前落盘的老产物表头还是英文字段名,
+     * 这里按同一套字典现场翻成中文(行键同步替换,前端 columns/rows 才对得上);
+     * 文件本身不改写,下载拿到的仍是原文件。
+     *
      * @return {@code {columns, rows}};rows 为对象数组,前端 a-table 直接当 dataSource
      */
     public Map<String, Object> preview(String id, int limit) {
@@ -246,7 +295,7 @@ public class ArtifactService {
                      .setSkipHeaderRecord(true)
                      .get()
                      .parse(reader)) {
-            List<String> columns = parser.getHeaderNames();
+            List<String> columns = FieldLabels.headers(parser.getHeaderNames(), null);
             List<Map<String, Object>> rows = new ArrayList<>();
             Iterator<CSVRecord> it = parser.iterator();
             while (it.hasNext() && rows.size() < safeLimit) {
@@ -307,8 +356,17 @@ public class ArtifactService {
                 .normalize();
     }
 
-    /** 文件名 = {工具}_{时间戳}_{8位随机}.csv,全部安全字符 */
-    private String buildId(String toolName) {
+    /**
+     * 产物文件名对:磁盘名({@code id})与展示名({@code fileName})共用同一时间戳与随机串。
+     *
+     * <p>磁盘名保持纯 ASCII——路径解析白名单 {@link #ID_PATTERN} 只放行字母/数字/下划线/连字符/点,
+     * 中文进 id 会让预览与下载直接 400;用户看到与下载的文件名取 {@code fileName}(中文前缀)。
+     */
+    private record Names(String id, String fileName) {
+    }
+
+    /** 文件名 = {工具}_{时间戳}_{8位随机}.csv */
+    private Names buildNames(String toolName) {
         StringBuilder safe = new StringBuilder();
         String name = toolName == null ? "query" : toolName.trim().toLowerCase(Locale.ROOT);
         for (int i = 0; i < name.length() && safe.length() < 40; i++) {
@@ -323,14 +381,29 @@ public class ArtifactService {
             safe.append("query");
         }
         String uuid = UUID.randomUUID().toString().replace("-", "");
-        return safe + "_" + LocalDateTime.now().format(ID_TS) + "_"
+        String suffix = "_" + LocalDateTime.now().format(ID_TS) + "_"
                 + uuid.substring(0, 8) + ".csv";
+        return new Names(safe + suffix, artifactLabel(toolName) + suffix);
+    }
+
+    /**
+     * 工具标识 → 产物文件名的中文前缀。
+     *
+     * <p>键取的是各工具类的 {@code ARTIFACT_TOOL}(工具域标识,不是具体 @Tool 名),
+     * 未登记的兜底「数据查询」——文件名永远不露英文。
+     */
+    private static String artifactLabel(String toolName) {
+        if (toolName == null) {
+            return "数据查询";
+        }
+        return ARTIFACT_LABELS.getOrDefault(toolName.trim().toLowerCase(Locale.ROOT), "数据查询");
     }
 
-    private Map<String, Object> meta(String id, List<String> columns, long totalRows, Path file) {
+    private Map<String, Object> meta(String id, String fileName, List<String> columns,
+                                     long totalRows, Path file) {
         Map<String, Object> meta = new LinkedHashMap<>();
         meta.put("id", id);
-        meta.put("fileName", id);
+        meta.put("fileName", fileName);
         meta.put("format", "csv");
         meta.put("totalRows", totalRows);
         try {

+ 26 - 2
ai-server/src/main/java/com/zsjz/ai/module/agent/tools/GovernTableTool.java

@@ -182,9 +182,13 @@ public class GovernTableTool {
         @SuppressWarnings("unchecked")
         Page<Object> page = (Page<Object>) (out == null ? null : out.get("pages"));
         List<Object> records = page == null || page.getRecords() == null ? List.of() : page.getRecords();
+        @SuppressWarnings("unchecked")
+        List<BasicColumn> head = (List<BasicColumn>) (out == null ? null : out.get("head"));
 
         List<LinkedHashMap<String, Object>> rows = ToolResultTable.toRows(records);
         List<Map<String, Object>> columns = ToolResultTable.buildColumns(rows);
+        // 治理服务返回的 head 带列中文名,比字典更准,直接覆盖 label(CSV 表头按它写)
+        applyHeadTitles(columns, head);
         Map<String, Object> artifact = artifacts == null ? null : artifacts.export(TOOL_NAME, columns, rows);
 
         int headMax = artifacts == null ? Integer.MAX_VALUE : Math.max(1, artifacts.headRows());
@@ -197,8 +201,6 @@ public class GovernTableTool {
 
         Map<String, Object> result = new LinkedHashMap<>();
         result.put("table", tableName);
-        @SuppressWarnings("unchecked")
-        List<BasicColumn> head = (List<BasicColumn>) (out == null ? null : out.get("head"));
         if (head != null && !head.isEmpty()) {
             List<Map<String, Object>> headCols = new ArrayList<>(head.size());
             for (BasicColumn col : head) {
@@ -216,6 +218,28 @@ public class GovernTableTool {
         return Json.toStr(result);
     }
 
+    /** 服务端 {@code head} 的中文列名 → {@code columns.label}(行数据仍按英文 key 取值) */
+    private static void applyHeadTitles(List<Map<String, Object>> columns, List<BasicColumn> head) {
+        if (head == null || head.isEmpty()) {
+            return;
+        }
+        Map<String, String> titles = new LinkedHashMap<>();
+        for (BasicColumn col : head) {
+            if (col != null && StrUtil.isNotBlank(col.getDataIndex()) && StrUtil.isNotBlank(col.getTitle())) {
+                titles.putIfAbsent(col.getDataIndex(), col.getTitle());
+            }
+        }
+        if (titles.isEmpty()) {
+            return;
+        }
+        for (Map<String, Object> col : columns) {
+            String title = titles.get(String.valueOf(col.get("key")));
+            if (title != null) {
+                col.put("label", title);
+            }
+        }
+    }
+
     /**
      * 表名是否已注册为 MyBatis-Plus 实体表({@code getTreeTablePage} 的硬前提)。
      * 包级可见:单测用 spy 覆盖,免得依赖 MP 静态注册表。

+ 42 - 3
ai-server/src/main/java/com/zsjz/ai/module/agent/tools/ToolResultTable.java

@@ -1,6 +1,7 @@
 package com.zsjz.ai.module.agent.tools;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.zsjz.ai.common.utils.FieldLabels;
 import com.zsjz.ai.common.utils.Json;
 import com.zsjz.ai.module.agent.artifact.ArtifactService;
 import io.agentscope.core.message.ToolResultBlock;
@@ -39,12 +40,26 @@ final class ToolResultTable {
      * 从首行推导列定义(LinkedHashMap 保序)
      */
     static List<Map<String, Object>> buildColumns(List<LinkedHashMap<String, Object>> rows) {
+        return buildColumns(rows, null);
+    }
+
+    /**
+     * 从首行推导列定义:{@code key} 保留英文原名(行数据按它取值),{@code label} 换成
+     * 字段中文名(拿不到中文名时保留原名)——CSV 表头与前端列头显示的都是 {@code label}。
+     *
+     * @param sourceType 行的源类型(POJO)。同一英文字段在不同 DTO 里含义可能不同
+     *                   ({@code callCount} 在通话 DTO 是「通话总次数」、在轨迹 DTO 是「通信次数」),
+     *                   带上类型才能精确取名;{@code Map} 行 / 拿不到类型时传 {@code null}
+     */
+    static List<Map<String, Object>> buildColumns(List<LinkedHashMap<String, Object>> rows,
+                                                  Class<?> sourceType) {
         List<Map<String, Object>> columns = new ArrayList<>();
         if (rows != null && !rows.isEmpty()) {
             for (String key : rows.get(0).keySet()) {
                 Map<String, Object> col = new LinkedHashMap<>();
                 col.put("key", key);
-                col.put("label", key);
+                String label = FieldLabels.label(sourceType, key);
+                col.put("label", label == null ? key : label);
                 columns.add(col);
             }
         }
@@ -135,6 +150,28 @@ final class ToolResultTable {
         return converted == null ? new LinkedHashMap<>() : converted;
     }
 
+    /**
+     * 取这批记录的「源类型」用于精确翻译列名:首个非空、非标量、非 {@code Map} 的元素类型。
+     *
+     * <p>空列表 / 纯 {@code Map} 行 / 标量行返回 {@code null},列名只能走兜底字典。
+     */
+    private static Class<?> sourceTypeOf(List<?> records) {
+        if (records == null) {
+            return null;
+        }
+        for (Object item : records) {
+            if (item == null || item instanceof Map) {
+                continue;
+            }
+            if (item instanceof CharSequence || item instanceof Number
+                    || item instanceof Boolean || item instanceof Character) {
+                return null;
+            }
+            return item.getClass();
+        }
+        return null;
+    }
+
     /**
      * 业务工具的「一步到位」出口:业务对象列表 → <b>全量落 CSV 产物</b> → 返回头部预览 JSON。
      *
@@ -142,11 +179,13 @@ final class ToolResultTable {
      * (单元格截断 500 字符、行数取 {@code artifact.head-rows})。「预览限量、文件全量」。
      *
      * @param artifacts 产物服务;传 null 时跳过导出(如无案件上下文的测试场景)
-     * @param toolName  工具标识,进产物文件名(如 {@code call_analysis})
+     * @param toolName  工具域标识:磁盘文件名前缀取它,展示/下载名由它翻成中文
+     *                  ({@code call_analysis} → 「通话分析」,见 {@code ArtifactService})
      */
     static ToolResultBlock store(ArtifactService artifacts, String toolName, List<?> records) {
+        Class<?> sourceType = sourceTypeOf(records);
         List<LinkedHashMap<String, Object>> rows = toRows(records);
-        List<Map<String, Object>> columns = buildColumns(rows);
+        List<Map<String, Object>> columns = buildColumns(rows, sourceType);
 
         // ① 全量行 → CSV(原始值;null = 无案件上下文/写失败,跳过但不打断工具)
         Map<String, Object> artifact = artifacts == null ? null

+ 3061 - 0
ai-server/src/main/resources/agent/field-labels.json

@@ -0,0 +1,3061 @@
+{
+  "*": {
+    "accent": "口音",
+    "accountCkqx": "存款期限",
+    "accountLeftMoney": "账户余额",
+    "accountStatus": "账户状态",
+    "accountType": "账户类型",
+    "activationDateTime": "入网时间",
+    "activeDateTime": "激活时间",
+    "addr": "住所地址",
+    "address": "住址详址",
+    "addressCity": "地址 - 城市",
+    "addressDetail": "地址 - 详细地址",
+    "age": "年龄",
+    "agent": "出票单位",
+    "agentRowId": "使用的 agent 配置行",
+    "aiLoadCount": "走出口 B 成功入库的 sheet 数",
+    "aka": "曾用名",
+    "alias": "别名绰号",
+    "approveTime": "登记审批时间",
+    "arrAirport": "到达机场代码",
+    "arrCode": "到达航站三字码",
+    "attachmentIds": "附件 ID 列表",
+    "attribution": "归属地",
+    "authdatetime": "授权时间",
+    "availableBalance": "可用余额",
+    "balance": "账户余额",
+    "bankBin": "银行卡 BIN 号",
+    "bankCardPersonList": "银行卡代持人员列表",
+    "bankCountMap": "银行分布映射",
+    "banks": "交易信息 - 银行统计",
+    "belongArea": "所属省市县",
+    "birth": "出生日期",
+    "birthCountry": "出身地国家/地区",
+    "birthDate": "出生日期",
+    "birthDist": "出生地区划",
+    "bizType": "研判场景维度",
+    "blockNo": "单 sheet 多表头时的块序号",
+    "bodyMark": "体表标记",
+    "borrowMoney": "借款金额",
+    "bounty": "抓逃奖金",
+    "bplace": "出生地国家/地区",
+    "branchCode": "交易网点代码",
+    "branchName": "交易网点名称",
+    "busCarType": "班车车型",
+    "busClassType": "班次类型",
+    "busNumber": "班次号",
+    "buyDateTime": "购票成功时间",
+    "cabinClass": "舱位等级",
+    "callDate": "通话日期",
+    "callDateEnd": "通话结束日期",
+    "callDateStart": "通话开始日期",
+    "callDateTime": "通话日期时间",
+    "callDirection": "主被叫标识",
+    "callOrderNbr": "CALLORDER_NBR调单号码",
+    "callRatioMap": "通话类型占比映射",
+    "callTime": "通话时间",
+    "callType": "通话类型",
+    "carNo": "车厢号",
+    "cardCount": "银行卡数量",
+    "cardList": "银行卡",
+    "cardNo": "银行卡号",
+    "cardType": "卡片类型",
+    "caseBrief": "简要案情",
+    "caseDate": "立案日期",
+    "caseIdSrc": "首次识别出该模板的案件 ID",
+    "caseNo": "案件编号",
+    "caseType": "案件类别",
+    "caseUnit": "立案单位",
+    "caseUnitType": "立案单位分类",
+    "cashFlag": "现金标志",
+    "cashKey": "现金关键字内容",
+    "catchDate": "抓获日期",
+    "catchDist": "抓获地点区划",
+    "catchDistCode": "抓获地点行政区划代码",
+    "catchPlace": "抓获地点",
+    "catchUnit": "抓获单位",
+    "catchWay": "抓获方式",
+    "category": "业务分类",
+    "categoryL2": "二级分类",
+    "certNo": "证件号码",
+    "changeDate": "改签日期",
+    "charSet": "字符集",
+    "childName": "下一个节点的名称",
+    "children": "子节点列表",
+    "cid": "身份证号",
+    "classify": "类别",
+    "clientNo": "客户编号",
+    "closeAccountDate": "销户日期",
+    "closedBranch": "销户网点",
+    "closingDate": "销户日期",
+    "closingDateTime": "销户时间",
+    "cmnCount": "联系次数",
+    "coCount": "同次数",
+    "coType": "同住类型",
+    "code": "代码",
+    "colIndex": "原文件里的列序号",
+    "columnName": "物理列名",
+    "columnRule": "新增 列规则",
+    "communicationCount": "通信次数阈值",
+    "communicationMode": "通信口径标签",
+    "company": "从业单位",
+    "concatInfo": "附属信息",
+    "confStr": "配置内容 y:开启 n:关闭",
+    "confType": "配置类型",
+    "confidence": "登记时的置信度",
+    "contGroupId": "连续交易组 ID",
+    "contactAddr": "联系住所详址",
+    "content": "文件对应行的数据内容",
+    "contentType": "text | insight",
+    "context": "★ 查询条件 + 结果快照 JSON",
+    "contextHash": "context 的 sha256",
+    "costMillis": "整条识别链路耗时",
+    "country": "前往/归来国家",
+    "createDateTime": "建档时间",
+    "createTime": "创建时间",
+    "curAddr": "现住址",
+    "curDist": "现住址区划",
+    "currency": "币种",
+    "data": "出行数据列表",
+    "dataCount": "数据数量",
+    "dataLevel": "数据层级",
+    "dataNum": "文件包含的数据数量",
+    "dayNightTotal": "昼夜活动总次数",
+    "db": "数据库名称",
+    "dbPath": "数据库路径",
+    "dealCount": "交易次数",
+    "dealJe": "交易净额",
+    "dealOrg": "处理机关",
+    "dealTime": "处理时间",
+    "deathDate": "死亡日期",
+    "debitCreditFlag": "借贷标志",
+    "defaultActive": "是否新会话默认装备",
+    "degree": "学位",
+    "delimiter": "分隔符",
+    "depAirport": "起飞机场代码",
+    "depCode": "起飞港三字码",
+    "departurePlace": "始发地名称",
+    "depositTerm": "存款期限",
+    "destinationPlace": "目的地名称",
+    "detail": "VerifyReport 的 JSON",
+    "dimension": "统计维度",
+    "directionConf": "方向配置",
+    "displayName": "显示名",
+    "dnaNo": "在逃人员DNA编号",
+    "docNo": "法律文书号",
+    "dplace": "前往地国家/地区",
+    "draft": "模型返回的原始草稿 JSON",
+    "duration": "通话时长",
+    "edges": "关系",
+    "edu": "文化程度",
+    "education": "学历",
+    "endCallDate": "通话结束日期",
+    "endHour": "结束小时",
+    "endTime": "结束使用时间",
+    "endTransDate": "末次交易日期",
+    "engCc": "机动车发动机排量",
+    "engModel": "机动车发动机型号",
+    "engPwr": "机动车发动机功率",
+    "ethnicity": "民族",
+    "existingHitCount": "AI 判定为命中既有模板、并入那一次 doClean 的 s",
+    "exitCode": "出口",
+    "exitReason": "出境事由",
+    "expDate": "截止日期",
+    "expenseRatio": "支出占比",
+    "expenseTotalAmount": "支出总金额",
+    "expressCompanyCode": "快递公司编号",
+    "ext1": "扩展信息1 交易类型|交易摘要|备注  三个合并",
+    "failNum": "失败导入数据行数",
+    "failReason": "失败原因",
+    "fare": "票价",
+    "festivalName": "节假日",
+    "field1": "查询号码",
+    "field2": "通信时间",
+    "field3": "基站地址",
+    "field4": "经度",
+    "field5": "纬度",
+    "field6": "转换结果",
+    "fieldLen": "字段长度",
+    "fieldNameCn": "字段中文名",
+    "fieldSort": "排序",
+    "fieldType": "字段类型",
+    "fileColIndex": "文件列索引",
+    "fileColName": "文件列名",
+    "fileName": "文件名",
+    "fileNo": "驾驶证档案编号",
+    "filePath": "文件路径",
+    "fileSize": "文件大小",
+    "fileStatus": "文件状态",
+    "filters": "分析条件快照",
+    "financialKey": "关键字",
+    "firstCallDateTime": "首次通话时间",
+    "firstDate": "初次领证日期",
+    "fixedDepositKey": "存取关键字内容",
+    "flag": "标志位",
+    "fleeDir": "逃跑方向简要情况",
+    "fleeTime": "逃跑时间",
+    "flightClass": "航段仓位",
+    "flightDateTime": "航班日期",
+    "flightNo": "航班号",
+    "flightSuffix": "航班后缀",
+    "footLen": "足长",
+    "formerName": "曾用名",
+    "fprintNo": "十指指纹编号",
+    "fuelType": "机动车能源种类",
+    "fugiNo": "在逃人员编号",
+    "funList": "清洗函数列表",
+    "funcRegex": "表头之上正文里外提姓名/卡号的正则",
+    "funcRegx": "模板函数正则",
+    "fx": "方向",
+    "gender": "性别",
+    "givenName": "名",
+    "goodsName": "物品名称",
+    "graph": "当前画布的图谱快照",
+    "groupCode": "所属工具组编码",
+    "groupDescription": "工具组描述",
+    "groupId": "分组 ID",
+    "groupLabel": "工具组中文名",
+    "groupSort": "工具组在页面上的排序位",
+    "gx": "关系描述",
+    "hasAuto": "是否自动 1-是",
+    "hasInner": "是否有内嵌 1是0否",
+    "hasNew": "表名英文",
+    "hasTogether": "疑似同住标识",
+    "header": "表头",
+    "headerLineNo": "表头所在行",
+    "headerMd5": "归一化表头指纹",
+    "headers": "表头",
+    "height": "身高",
+    "heightMax": "身高上限",
+    "heightMin": "身高下限",
+    "hints": "扁平提示 JSON 数组",
+    "holderCertNo": "开户人证件号码",
+    "hotelAddress": "酒店地址",
+    "hotelName": "酒店名称",
+    "householdRegistered": "户口所在地",
+    "huntLevel": "部督捕级别",
+    "idStr": "id逗号分隔",
+    "identitys": "个人身份",
+    "inCallRatio": "被叫占比",
+    "inDateTime": "入住时间",
+    "inReason": "入所原因",
+    "inTotalCount": "呼入总时长",
+    "inTotalMoney": "收入总金额",
+    "incomeRatio": "收入占比",
+    "incomeTotalAmount": "收入总金额",
+    "indicator": "指标",
+    "init": "1是0否",
+    "intimacy": "亲密度数值",
+    "intimacyBehavior": "亲密行为",
+    "ioDate": "出入日期",
+    "ioTime": "出入时间",
+    "isp": "运营商",
+    "issDate": "签发日期",
+    "issueDate": "出票日期",
+    "job": "职业",
+    "jobType": "职业类别",
+    "keywords": "关键词",
+    "kind": "实体类型",
+    "lac": "LAC",
+    "lastSeq": "★ 会话内消息序号水位",
+    "lastSuspensionDateTime": "最后停机时间",
+    "lastTransDateTime": "最后交易时间",
+    "level": "层级",
+    "libType": "1-证件号",
+    "licNo": "驾驶证号",
+    "licTerm": "驾驶证期限",
+    "lineData": "线上数据 交易信息",
+    "lineNo": "表头行号",
+    "lineNum": "文件中的行号",
+    "linkedFundAccount": "关联资金账户",
+    "liveCount": "同住次数",
+    "liveWithCertNo": "同住人身份证号",
+    "liveWithName": "同住人姓名",
+    "llmCalls": "本 job 调了几次模型",
+    "loadCap": "机动车核定载质量",
+    "loanMoney": "贷款金额",
+    "lx": "类型 1卡 2人 3 其他",
+    "machinecode": "机器码",
+    "madeDate": "出厂日期",
+    "mailingAddress": "通信地址",
+    "mainDesc": "主体/成员标识",
+    "mainId": "主模版 ID",
+    "mainOrg": "主办单位公安机关名称",
+    "mainPersonName": "主体对象名称",
+    "mainRefTemplateId": "判定时参考过的既有模板 id",
+    "mainUnit": "主办单位",
+    "mainUnitType": "主办单位分类",
+    "map": "同住对象映射",
+    "marital": "婚姻状况",
+    "marry": "婚姻状态",
+    "matchTier": "定档结果",
+    "matched": "是否匹配",
+    "matchedBy": "命中途径",
+    "matchedCount": "原本就匹配上模板、直接交既有链路的 sheet 数",
+    "maxCallDuration": "最长通话时长",
+    "maxDate": "最大日期",
+    "maxSingleExpense": "最大单笔支出",
+    "maxSingleIncome": "最大单笔收入",
+    "merchantName": "商户名称",
+    "merchantNo": "商户号",
+    "message": "追问内容;为空表示首轮自动分析",
+    "messageCount": "消息条数",
+    "messageType": "chat | first_round",
+    "metadata": "思考过程 / 中断标记 / 耗时等轻量字段",
+    "middleName": "中间名",
+    "milStatus": "兵役情况",
+    "mimeType": "Tika 按文件内容检测出的 MIME 类型",
+    "minDate": "最小日期",
+    "minTime": "最小时间",
+    "modTime": "在逃人员修改处理时间",
+    "modelName": "实际调用的模型名",
+    "name": "姓名",
+    "nationality": "民族",
+    "nativeCountry": "籍贯国家",
+    "nativePlace": "籍贯",
+    "needsStruct": "这类模板永远不参与既有匹配",
+    "newPassword": "新密码",
+    "newPwd": "新密码",
+    "node1cn": "节点1中文名",
+    "node2cn": "节点2中文名",
+    "nodes": "节点",
+    "num": "数量",
+    "numericalOrder": "序号",
+    "objName": "调查对象姓名",
+    "officer": "主办人姓名",
+    "oldPwd": "原密码",
+    "openAccountDate": "开户日期",
+    "openingDate": "账号开户时间",
+    "openingDateTime": "开户时间",
+    "operCountMap": "运营商标码统计映射",
+    "operName": "操作员姓名",
+    "operatorId": "运营商",
+    "oprt": "运营商",
+    "orderSort": "排序",
+    "origType": "原准驾车型",
+    "originCountry": "籍贯国家",
+    "otherAccount": "交易对方账号",
+    "otherBalance": "交易对方余额",
+    "otherCallRatio": "其他通话占比",
+    "otherCount": "其他通话次数",
+    "otherPhone": "对方号码",
+    "otherPhoneArea": "对方号码归属地",
+    "otherSummaryList": "交易对方汇总列表",
+    "outCallRatio": "主叫占比",
+    "outDate": "出所日期",
+    "outDateTime": "退房时间",
+    "outReason": "出所原因",
+    "outTo": "出所去向",
+    "outTotalCount": "呼出总时长",
+    "outTotalMoney": "支出总金额",
+    "outputJson": "★ ```insight 结构化产物",
+    "overScoreDate": "驾驶证超分日期",
+    "overdatetime": "过期时间",
+    "ownerId": "案件归属用户 ID",
+    "pairTotalCalls": "通话总次数",
+    "pairTotalTrans": "交易总次数",
+    "paragraph": "手机号段",
+    "parameters": "入参 JSON Schema",
+    "parentName": "上一个节点的名称",
+    "parseStatus": "识别状态",
+    "pcs": "所属派出所",
+    "person": "当事人",
+    "personAccount": "查询账号",
+    "personAccountNo": "交易账号",
+    "personAddress": "地址",
+    "personAge": "年龄",
+    "personBirth": "出生日期",
+    "personCardNos": "本方卡号",
+    "personCellId": "本方基站小区号",
+    "personCertAddress": "使用人证件地址",
+    "personCertName": "证件类型名称",
+    "personCertType": "证件类型",
+    "personGender": "性别",
+    "personIpAddress": "本方IP地址",
+    "personLibId": "关联人卡库",
+    "personList": "人员列表",
+    "personMacAddress": "本方MAC地址",
+    "personNameCn": "旅客中文姓名",
+    "personNameEn": "旅客英文名",
+    "personPhoneArea": "本方号码归属地",
+    "personPhonePrefix": "前缀6位",
+    "personRegionId": "本方基站大区号",
+    "personStationLatitude": "本方基站纬度",
+    "personStationLongitude": "本方基站经度",
+    "personStationName": "本方基站名称",
+    "personTag": "标签",
+    "phoneCount": "手机号数量",
+    "phoneList": "手机号",
+    "phoneStatus": "号码状态",
+    "phoneType": "号码类型",
+    "photo": "照片",
+    "photo1": "照片1",
+    "photo2": "照片2",
+    "photograph": "照片",
+    "pickUpTime": "取票时间",
+    "plateNumber": "车牌号",
+    "plateType": "号牌种类",
+    "pnr": "记录编号",
+    "policeStation": "所属派出所名称",
+    "port": "出入口岸",
+    "pwd": "案件密码",
+    "pwdHash": "BCrypt 密码哈希 —— 绝不返回给前端",
+    "queryAge": "查询人年龄",
+    "queryCertNo": "查询人身份证号",
+    "queryCertType": "查询人证件类型",
+    "queryEthnicity": "查询人民族",
+    "queryGender": "查询人性别",
+    "queryName": "查询人姓名",
+    "queryOrigin": "查询人户籍地",
+    "rcpId": "收件ID集合",
+    "readCount": "文件sheet读取次数",
+    "receiveSmsPhone": "接收短信手机号码",
+    "recipientCompany": "收件单位名称",
+    "recipientCount": "收快递次数",
+    "recipientCurrentAddress": "收件人现住址地址名称",
+    "recipientName": "收件人姓名",
+    "recipientPhone": "收件人联系电话",
+    "refundAmount": "退票金额",
+    "refundDate": "退票日期",
+    "refundDateTime": "退票时间",
+    "regAddr": "户籍地",
+    "regDetail": "户口所在地详址",
+    "regDist": "户籍地区划",
+    "regName": "登记审批姓名",
+    "regPerson": "登记人员姓名",
+    "rejectReason": "审批未通过原因",
+    "relation": "与户主关系",
+    "remainingDate": "剩余时间",
+    "remark": "备注",
+    "remarks": "备注信息",
+    "replyToMessageId": "追问指向的首轮消息",
+    "required": "是否必填",
+    "respondents": "分析对象",
+    "result": "警情处理结果",
+    "revokeApprover": "撤销审批人姓名",
+    "revokeName": "撤销人姓名",
+    "revokeProcTime": "撤销处理时间",
+    "revokeTime": "撤销时间",
+    "role": "user | assistant | system",
+    "roomNo": "房间号",
+    "rowId": "主键 ID",
+    "rows": "数据行",
+    "salesChannelCode": "销售渠道编码",
+    "sampleLength": "字符数",
+    "sampleText": "Tika 抽取的文本样本",
+    "sandboxMode": "沙箱执行模式",
+    "sandboxScope": "沙箱隔离范围",
+    "scope": "分析范围",
+    "score": "累积记分",
+    "seatCnt": "机动车核定载客人数",
+    "seatNo": "座位号",
+    "sendDate": "寄件日期",
+    "sendIds": "发件ID集合",
+    "senderCount": "发快递次数",
+    "senderCurrentAddress": "寄件人现住址地址名称",
+    "senderName": "寄件人姓名",
+    "senderPhone": "寄件人电话",
+    "sentence": "刑期",
+    "seq": "★ 会话内单调递增序号",
+    "serviceLocation": "服务所处",
+    "sessionId": "会话ID",
+    "sheetId": "工作表的唯一标识符",
+    "sj": "时间",
+    "skill": "专长",
+    "skillNames": "允许使用的技能名",
+    "skillRepositoriesJson": "技能仓库配置列表",
+    "skillsAllowJson": "允许使用的技能列表",
+    "skillsDenyJson": "禁止使用的技能列表",
+    "srye": "上日余额",
+    "startCallDate": "通话开始日期",
+    "startHour": "开始小时",
+    "startTime": "开始使用时间",
+    "startTransDate": "首次交易日期",
+    "stayCount": "同住次数",
+    "storeTime": "入库时间",
+    "structOps": "结构指令快照 JSON",
+    "successNum": "成功导入数据行数",
+    "summary": "一句话摘要",
+    "suspectedTogether": "疑似同住标识",
+    "sysPrompt": "系统提示词",
+    "tableFields": "表字段",
+    "tableId": "表id",
+    "tableRule": "表规则",
+    "tag": "关键字标签",
+    "target": "终点节点 ID",
+    "tbId": "表 ID",
+    "templateId": "匹配到的模板 ID",
+    "terminalNo": "终端号",
+    "texts": "待嵌入文本",
+    "ticketNo": "票号",
+    "ticketPrice": "车票价格",
+    "ticketStatus": "车票状态",
+    "ticketType": "车票类型",
+    "time": "时间段标识",
+    "title": "会话标题",
+    "tmpAddr": "暂住地址",
+    "tmpResNo": "暂住证编号",
+    "togetherCertNo": "同住人身份证号",
+    "toolClass": "声明该工具方法的类",
+    "toolEvents": "★ 工具调用明细",
+    "toolGroups": "启用的业务工具组",
+    "toolMethod": "方法名",
+    "toolNames": "工具名清单",
+    "toolPackIds": "勾选的工具包 id 列表",
+    "toolPacksJson": "方案勾选的工具包 id 列表",
+    "toolsAllowJson": "允许使用的工具列表",
+    "toolsDenyJson": "禁止使用的工具列表",
+    "top5List": "对方信息top5",
+    "topN": "期望的研判规模",
+    "totalBalance": "总余额",
+    "totalCount": "总通话次数",
+    "totalDealMoney": "交易金额",
+    "totalEdges": "画布 / 接口返回的关系总数",
+    "totalMonth": "总月数",
+    "totalMonthCount": "总月份数",
+    "totalNodes": "画布 / 接口返回的节点总数",
+    "totalTime": "总通话时长",
+    "traceData": "追溯返回数据集合",
+    "tradeAmountMax": "交易金额上限",
+    "tradeAmountMin": "交易金额下限",
+    "tradeMode": "交易口径标签",
+    "transAgg": "交易聚合信息",
+    "transAmount": "交易金额",
+    "transBalance": "交易余额",
+    "transBigThirdList": "交易明细列表",
+    "transChannel": "交易渠道",
+    "transClerkNo": "交易柜员号",
+    "transCount": "交易次数",
+    "transDate": "交易日期",
+    "transExpend": "交易信息-总支出",
+    "transIn": "收入",
+    "transIncome": "交易信息-总收入",
+    "transInfoStat": "交易统计信息",
+    "transLocation": "交易发生地",
+    "transOut": "支出",
+    "transPersonDTOList": "参与交易的被调查对象列表",
+    "transSerialNo": "交易流水号",
+    "transSummary": "交易摘要",
+    "transTotalAmount": "交易总金额",
+    "transTotalMoney": "交易总金额",
+    "transType": "交易类型",
+    "transport": "交通方式",
+    "tripDateTime": "具体日期时间",
+    "tripNo": "旅程号",
+    "userId": "创建者",
+    "username": "登录名",
+    "vcCount": "通话次数",
+    "vehType": "准驾车型",
+    "vehicle": "交通工具",
+    "violAct": "违法行为",
+    "violAddr": "违法地址",
+    "violTime": "违法时间",
+    "visaNo": "签证号码",
+    "visaType": "签证种类",
+    "warrantNo": "通缉令编号",
+    "waybillNumber": "运单号",
+    "zf": "开启正负转换 1是、 0否"
+  },
+  "_comment": "由 ai-server/scripts/gen_field_labels.py 生成,勿手改;重新生成后按需提交。classes=按类精确取名,*=SQL 别名/Map 行的兜底。",
+  "classes": {
+    "com.zsjz.ai.common.model.call.dto.CallContinuousDTO": {
+      "continuousEndMonth": "连续通话结束月份",
+      "continuousStartMonth": "连续通话开始月份",
+      "otherName": "对方姓名",
+      "otherPhone": "合并对方手机号",
+      "pairContMonthCount": "持续月份",
+      "pairTotalCalls": "通话总次数",
+      "personPhone": "本方手机号"
+    },
+    "com.zsjz.ai.common.model.call.dto.CallNightDTO": {
+      "callCount": "通话总次数",
+      "endHour": "结束小时",
+      "otherName": "对方姓名",
+      "otherPhone": "对方号码集合",
+      "startHour": "开始小时",
+      "time": "时间段标识",
+      "totalMonthCount": "总月份数"
+    },
+    "com.zsjz.ai.common.model.call.dto.CallSensitiveDTO": {
+      "callCount": "通话次数",
+      "otherName": "对方姓名",
+      "otherPhone": "对方号码"
+    },
+    "com.zsjz.ai.common.model.call.entity.CallOpenInfo": {
+      "activationDateTime": "入网时间",
+      "city": "号码归属市",
+      "closingDateTime": "销户时间",
+      "firstCallDateTime": "首次通话时间",
+      "lastSuspensionDateTime": "最后停机时间",
+      "mailingAddress": "通信地址",
+      "operatorId": "归属运营商标识",
+      "personCertAddress": "使用人证件地址",
+      "personCertNo": "使用人证件号码",
+      "personName": "使用人姓名",
+      "personPhone": "号码",
+      "personType": "用户类型",
+      "phoneStatus": "号码状态",
+      "phoneType": "号码类型",
+      "province": "号码归属省"
+    },
+    "com.zsjz.ai.common.model.call.entity.CallRecord": {
+      "callDate": "通话日期",
+      "callDateTime": "通话日期时间",
+      "callDirection": "主被叫标识",
+      "callTime": "通话时间",
+      "callType": "通话类型",
+      "duration": "通话时长",
+      "festivalName": "节假日",
+      "operatorId": "运营商",
+      "otherCertNo": "对方证件号码",
+      "otherName": "对方户名",
+      "otherPhone": "对方号码",
+      "otherPhoneArea": "对方号码归属地",
+      "personCellId": "本方基站小区号",
+      "personCertNo": "本方证件号码",
+      "personLibId": "关联人卡库",
+      "personName": "本方姓名",
+      "personPhone": "本方号码",
+      "personPhoneArea": "本方号码归属地",
+      "personRegionId": "本方基站大区号",
+      "personStationLatitude": "本方基站纬度",
+      "personStationLongitude": "本方基站经度",
+      "personStationName": "本方基站名称",
+      "remark": "备注"
+    },
+    "com.zsjz.ai.common.model.call.entity.CallRecordAll": {
+      "callDate": "通话日期",
+      "callDateTime": "通话日期时间",
+      "callDirection": "主被叫标识",
+      "callTime": "通话时间",
+      "callType": "通话类型",
+      "duration": "通话时长",
+      "festivalName": "节假日",
+      "operatorId": "运营商",
+      "otherCertNo": "对方证件号码",
+      "otherName": "对方户名",
+      "otherPhone": "对方号码",
+      "otherPhoneArea": "对方号码归属地",
+      "personCellId": "本方基站小区号",
+      "personCertNo": "本方证件号码",
+      "personLibId": "关联人卡库",
+      "personName": "本方姓名",
+      "personPhone": "本方号码",
+      "personPhoneArea": "本方号码归属地",
+      "personRegionId": "本方基站大区号",
+      "personStationLatitude": "本方基站纬度",
+      "personStationLongitude": "本方基站经度",
+      "personStationName": "本方基站名称",
+      "remark": "备注"
+    },
+    "com.zsjz.ai.common.model.call.entity.UseOpenInfoCallRecord": {
+      "callDate": "通话日期",
+      "callDateTime": "通话日期时间",
+      "callDirection": "主被叫标识",
+      "callTime": "通话时间",
+      "callType": "通话类型",
+      "duration": "通话时长",
+      "operatorId": "运营商",
+      "otherCertNo": "对方证件号码",
+      "otherName": "对方户名",
+      "otherPhone": "对方号码",
+      "otherPhoneArea": "对方号码归属地",
+      "personCellId": "本方基站小区号",
+      "personCertNo": "本方证件号码",
+      "personLibId": "关联人卡库",
+      "personName": "本方姓名",
+      "personPhone": "本方号码",
+      "personPhoneArea": "本方号码归属地",
+      "personRegionId": "本方基站大区号",
+      "personStationLatitude": "本方基站纬度",
+      "personStationLongitude": "本方基站经度",
+      "personStationName": "本方基站名称",
+      "remark": "备注"
+    },
+    "com.zsjz.ai.common.model.call.query.CallContinuousQuery": {
+      "monthVal": "连续月份阈值"
+    },
+    "com.zsjz.ai.common.model.call.query.CallRecordQuery": {
+      "callDirection": "主被叫标识",
+      "callType": "通话类型",
+      "duration": "通话时长",
+      "operatorId": "运营商",
+      "otherName": "对方户名",
+      "otherPhones": "对方号码",
+      "personCellId": "本方基站小区号",
+      "personCertNo": "本方证件号码",
+      "personPhoneArea": "本方号码归属地",
+      "personPhones": "本方号码",
+      "personRegionId": "本方基站大区号",
+      "symbol": "ge le"
+    },
+    "com.zsjz.ai.common.model.call.query.CallSensitiveQuery": {
+      "otherPhone": "对方号码"
+    },
+    "com.zsjz.ai.common.model.dm.dto.DeletedFilesDTO": {
+      "ids": "待删除的文件/工作表 ID 列表"
+    },
+    "com.zsjz.ai.common.model.dm.dto.RawPreviewDTO": {
+      "headers": "表头",
+      "rows": "数据行"
+    },
+    "com.zsjz.ai.common.model.dm.dto.TableInfoDTO": {
+      "classify": "类别",
+      "hasInner": "是否有内嵌 1是0否",
+      "headers": "表头",
+      "id": "id",
+      "tableNameCn": "表名中文",
+      "tableNameEn": "表名英文",
+      "tableRule": "表规则"
+    },
+    "com.zsjz.ai.common.model.dm.dto.TableRuleDTO": {
+      "columnRule": "新增 列规则",
+      "directionConf": "方向配置",
+      "fieldLen": "字段长度",
+      "fieldNameCn": "字段中文名",
+      "fieldNameEn": "字段英文名",
+      "fieldSort": "排序",
+      "fieldType": "字段类型",
+      "fileColIndex": "文件列索引",
+      "fileColName": "文件列名",
+      "funList": "清洗函数列表",
+      "id": "主键",
+      "matched": "是否匹配",
+      "required": "是否必填",
+      "tableId": "表id",
+      "zf": "开启正负转换 1是、 0否"
+    },
+    "com.zsjz.ai.common.model.dm.entity.EdgeConf": {
+      "clazz": "关系类型",
+      "mc": "关系描述",
+      "node1": "对象",
+      "node2": "对象",
+      "tableName": "需要识别关系的表名"
+    },
+    "com.zsjz.ai.common.model.dm.entity.FileAiProfile": {
+      "batchId": "上传批次 ID",
+      "caseId": "所属案件 ID",
+      "category": "业务分类",
+      "costMillis": "整条识别链路耗时",
+      "createDateTime": "建档时间",
+      "failReason": "失败原因",
+      "fileId": "关联 file_info.id",
+      "fileName": "原始文件名",
+      "fileType": "原始后缀",
+      "id": "主键",
+      "keywords": "关键词",
+      "mimeType": "Tika 按文件内容检测出的 MIME 类型",
+      "modelName": "实际调用的模型名",
+      "parseStatus": "识别状态",
+      "sampleLength": "字符数",
+      "sampleText": "Tika 抽取的文本样本",
+      "summary": "一句话摘要"
+    },
+    "com.zsjz.ai.common.model.dm.entity.FileData": {
+      "batchId": "批次号",
+      "content": "文件对应行的数据内容",
+      "fileId": "文件的唯一标识符",
+      "fileName": "文件名",
+      "id": "记录的唯一标识符",
+      "lineNum": "文件中的行号",
+      "readCount": "文件sheet读取次数",
+      "sheetId": "工作表的唯一标识符",
+      "sheetName": "工作表名"
+    },
+    "com.zsjz.ai.common.model.dm.entity.FileInfo": {
+      "batchId": "批次ID",
+      "charSet": "字符集",
+      "children": "子节点列表",
+      "classify": "类别",
+      "dataNum": "文件包含的数据数量",
+      "delimiter": "分隔符",
+      "failNum": "失败导入数据行数",
+      "fileName": "文件名",
+      "filePath": "文件路径",
+      "fileSize": "文件大小",
+      "fileStatus": "文件状态",
+      "fileType": "文件类型",
+      "funcRegx": "模板函数正则",
+      "hasNew": "表名英文",
+      "header": "表头",
+      "lineNo": "表头行号",
+      "password": "密码",
+      "sheetName": "文件名",
+      "successNum": "成功导入数据行数",
+      "tableName": "数据库映射表名称",
+      "tableNameEn": "表名英文",
+      "templateId": "匹配到的模板 ID"
+    },
+    "com.zsjz.ai.common.model.dm.entity.PersonRelEdge": {
+      "clazz": "类别",
+      "fx": "方向",
+      "id": "主键ID",
+      "lx": "类型 1卡 2人 3 其他",
+      "mc": "名称",
+      "node1": "节点1",
+      "node1cn": "节点1中文名",
+      "node2": "节点2",
+      "node2cn": "节点2中文名",
+      "num": "数量",
+      "sj": "时间"
+    },
+    "com.zsjz.ai.common.model.dm.query.FileAiProfileQuery": {
+      "batchId": "按上传批次过滤",
+      "category": "业务分类",
+      "fileId": "精确匹配某个文件",
+      "parseStatus": "识别状态"
+    },
+    "com.zsjz.ai.common.model.dm.query.RawPreviewQuery": {
+      "headerRowIndex": "表头行索引",
+      "headers": "兜底表头",
+      "sheetId": "工作表 ID"
+    },
+    "com.zsjz.ai.common.model.external.entity.CertInfo": {
+      "birth": "出生日期",
+      "bplace": "出生地国家/地区",
+      "certNo": "证件号码",
+      "certType": "证件类型",
+      "cid": "身份证号",
+      "dplace": "前往地国家/地区",
+      "exitReason": "出境事由",
+      "expDate": "截止日期",
+      "issDate": "签发日期",
+      "issOrg": "签发机关",
+      "pcs": "所属派出所",
+      "photo": "照片",
+      "regAddr": "户籍所在地",
+      "storeTime": "入库时间"
+    },
+    "com.zsjz.ai.common.model.external.entity.CoResident": {
+      "age": "年龄",
+      "aka": "曾用名",
+      "birth": "出生日期",
+      "birthCountry": "出身地国家/地区",
+      "birthDist": "出生地区划",
+      "cid": "身份证号",
+      "company": "从业单位",
+      "deathDate": "死亡日期",
+      "edu": "文化程度",
+      "ethnicity": "民族",
+      "gender": "性别",
+      "height": "身高",
+      "job": "职业",
+      "marital": "婚姻状况",
+      "milStatus": "兵役情况",
+      "name": "姓名",
+      "origin": "籍贯",
+      "originCountry": "籍贯国家",
+      "photo": "照片",
+      "regAddr": "户籍地",
+      "regDist": "户籍地区划",
+      "relation": "与户主关系",
+      "status": "人员状态"
+    },
+    "com.zsjz.ai.common.model.external.entity.CoVehViol": {
+      "cid": "身份证号",
+      "coCount": "同次数",
+      "name": "姓名",
+      "photo": "照片",
+      "plateNo": "车号牌"
+    },
+    "com.zsjz.ai.common.model.external.entity.Criminal": {
+      "alias": "别名绰号",
+      "birth": "出生日期",
+      "bodyMark": "体表标记",
+      "caseBrief": "简要案情",
+      "caseType": "案件类别",
+      "catchDate": "抓获/入所日期",
+      "cid": "身份证号",
+      "curAddr": "现居住地",
+      "docNo": "法律文书号",
+      "edu": "文化程度",
+      "ethnicity": "民族",
+      "footLen": "足长",
+      "gender": "性别",
+      "height": "身高",
+      "identitys": "个人身份",
+      "inReason": "入所原因",
+      "name": "姓名",
+      "outDate": "出所日期",
+      "outReason": "出所原因",
+      "outTo": "出所去向",
+      "photo": "照片",
+      "regDetail": "户口所在地详址",
+      "regDist": "户口所在地区划",
+      "remark": "备注",
+      "result": "警情处理结果",
+      "sentence": "刑期",
+      "skill": "专长",
+      "unit": "办案单位"
+    },
+    "com.zsjz.ai.common.model.external.entity.EntryRec": {
+      "birth": "出生日期",
+      "certNo": "证件号码",
+      "certType": "证件类别",
+      "cid": "身份证号",
+      "country": "前往/归来国家",
+      "gender": "性别",
+      "ioDate": "出入日期",
+      "ioTime": "出入时间",
+      "nation": "国籍",
+      "photo": "照片",
+      "port": "出入口岸",
+      "transport": "交通方式",
+      "vehicle": "交通工具",
+      "visaNo": "签证号码",
+      "visaType": "签证种类"
+    },
+    "com.zsjz.ai.common.model.external.entity.FugiCo": {
+      "accent": "口音",
+      "approveTime": "登记审批时间",
+      "birth": "出生日期",
+      "bounty": "抓逃奖金",
+      "caseBrief": "简要案情",
+      "caseDate": "立案日期",
+      "caseNo": "案件编号",
+      "caseType": "案件类别",
+      "caseUnit": "立案单位",
+      "caseUnitType": "立案单位分类",
+      "certNo": "证件号码",
+      "curAddr": "现住址",
+      "curDist": "现住址区划",
+      "dnaNo": "在逃人员DNA编号",
+      "ethnicity": "民族",
+      "fleeDir": "逃跑方向简要情况",
+      "fleeTime": "逃跑时间",
+      "fprintNo": "十指指纹编号",
+      "fugiNo": "在逃人员编号",
+      "gender": "性别",
+      "heightMax": "身高上限",
+      "heightMin": "身高下限",
+      "huntLevel": "部督捕级别",
+      "jobType": "职业类别",
+      "mainOrg": "主办单位公安机关名称",
+      "mainUnitType": "主办单位分类",
+      "modTime": "在逃人员修改处理时间",
+      "name": "姓名",
+      "officer": "主办人姓名",
+      "origin": "籍贯",
+      "personType": "在逃人员类型",
+      "phone": "主办单位联系电话",
+      "photo": "照片",
+      "regAddr": "户籍地",
+      "regDist": "户籍地区划",
+      "regName": "登记审批姓名",
+      "regOrg": "登记单位公安机关名称",
+      "regPerson": "登记人员姓名",
+      "regTime": "登记时间",
+      "rejectReason": "审批未通过原因",
+      "warrantNo": "通缉令编号"
+    },
+    "com.zsjz.ai.common.model.external.entity.FugiCoRevoke": {
+      "accent": "口音",
+      "approveTime": "登记审批时间",
+      "birth": "出生日期",
+      "bounty": "抓逃奖金",
+      "caseBrief": "简要案情",
+      "caseDate": "立案日期",
+      "caseNo": "案件编号",
+      "caseType": "案件类别",
+      "caseUnit": "立案单位",
+      "caseUnitType": "立案单位分类",
+      "catchDate": "抓获日期",
+      "catchPlace": "抓获地点",
+      "catchUnit": "抓获单位",
+      "catchWay": "抓获方式",
+      "certNo": "证件号码",
+      "curAddr": "现住址",
+      "curDist": "现住址区划",
+      "dnaNo": "在逃人员DNA编号",
+      "ethnicity": "民族",
+      "fleeDir": "逃跑方向简要情况",
+      "fleeTime": "逃跑时间",
+      "fprintNo": "十指指纹编号",
+      "fugiNo": "在逃人员编号",
+      "gender": "性别",
+      "heightMax": "身高上限",
+      "heightMin": "身高下限",
+      "huntLevel": "部督捕级别",
+      "jobType": "职业类别",
+      "mainUnit": "主办单位",
+      "mainUnitType": "主办单位分类",
+      "modTime": "在逃人员修改处理时间",
+      "name": "姓名",
+      "officer": "主办人姓名",
+      "origin": "籍贯",
+      "personType": "在逃人员类型",
+      "phone": "主办单位联系电话",
+      "photo": "照片",
+      "regAddr": "户籍地",
+      "regDist": "户籍地区划",
+      "regName": "登记审批姓名",
+      "regOrg": "登记单位",
+      "regPerson": "登记人员姓名",
+      "regTime": "登记时间",
+      "rejectReason": "审批未通过原因",
+      "revokeApprover": "撤销审批人姓名",
+      "revokeName": "撤销人姓名",
+      "revokeProcTime": "撤销处理时间",
+      "revokeTime": "撤销时间",
+      "warrantNo": "通缉令编号"
+    },
+    "com.zsjz.ai.common.model.external.entity.FugiRevoke": {
+      "accent": "口音",
+      "birth": "出生日期",
+      "bounty": "抓逃奖金",
+      "caseBrief": "简要案情",
+      "caseDate": "立案日期",
+      "caseNo": "案件编号",
+      "caseType": "案件类别",
+      "caseUnit": "立案单位",
+      "caseUnitType": "立案单位分类",
+      "catchDate": "抓获日期",
+      "catchDist": "抓获地点区划",
+      "catchDistCode": "抓获地点行政区划代码",
+      "catchUnit": "抓获单位",
+      "catchWay": "抓获方式",
+      "certNo": "证件号码",
+      "code": "代码",
+      "curAddr": "现住地址",
+      "curDist": "现住地区划",
+      "dnaNo": "在逃人员DNA编号",
+      "ethnicity": "民族",
+      "fleeDir": "逃跑方向简要情况",
+      "fleeTime": "逃跑时间",
+      "fprintNo": "十指指纹编号",
+      "fugiNo": "在逃人员编号",
+      "gender": "性别",
+      "heightMax": "身高上限",
+      "heightMin": "身高下限",
+      "jobType": "职业类别",
+      "mainUnit": "主办单位",
+      "mainUnitType": "主办单位分类",
+      "name": "姓名",
+      "officer": "主办人姓名",
+      "origin": "籍贯",
+      "personType": "人员类型",
+      "phone": "主办人联系电话",
+      "photo": "照片",
+      "regAddr": "户籍地",
+      "regDist": "户籍地区划",
+      "regName": "登记审批姓名",
+      "regOrg": "登记单位",
+      "regTime": "登记审批时间",
+      "revokeApprover": "撤销审批人姓名",
+      "revokeName": "撤销人姓名",
+      "revokeTime": "撤销时间",
+      "warrantNo": "通缉令编号"
+    },
+    "com.zsjz.ai.common.model.external.entity.Fugitive": {
+      "accent": "口音",
+      "birth": "出生日期",
+      "bounty": "抓逃奖金",
+      "caseBrief": "简要案情",
+      "caseDate": "立案日期",
+      "caseNo": "案件编号",
+      "caseType": "案件类别",
+      "caseUnit": "立案单位",
+      "caseUnitType": "立案单位分类",
+      "certNo": "证件号码",
+      "curAddr": "现住址",
+      "curDist": "现住址区划",
+      "dnaNo": "在逃人员DNA编号",
+      "ethnicity": "民族",
+      "fleeDir": "逃跑方向简要情况",
+      "fleeTime": "逃跑时间",
+      "fprintNo": "十指指纹编号",
+      "fugiNo": "在逃人员编号",
+      "gender": "性别",
+      "heightMax": "身高上限",
+      "heightMin": "身高下限",
+      "huntLevel": "督捕级别",
+      "jobType": "职业类别",
+      "mainOrg": "主办单位公安机关名称",
+      "mainUnitType": "主办单位分类",
+      "modTime": "在逃人员修改处理时间",
+      "name": "姓名",
+      "officer": "主办人姓名",
+      "origin": "籍贯",
+      "personType": "人员类型",
+      "phone": "主办单位联系电话",
+      "photo": "照片",
+      "regAddr": "户籍地",
+      "regDist": "户籍地区划",
+      "regName": "登记审批姓名",
+      "regOrg": "登记单位公安机关名称",
+      "regTime": "登记审批时间",
+      "warrantNo": "通缉令编号"
+    },
+    "com.zsjz.ai.common.model.external.entity.Household": {
+      "age": "年龄",
+      "birthCountry": "出身地国家/地区",
+      "birthDist": "出生地区划",
+      "company": "从业单位",
+      "deathDate": "死亡日期",
+      "edu": "文化程度",
+      "height": "身高",
+      "job": "职业",
+      "marital": "婚姻状况",
+      "milStatus": "兵役情况",
+      "originCountry": "籍贯国家",
+      "photo": "照片",
+      "regDist": "户籍地区划",
+      "relation": "与户主关系",
+      "status": "人员状态"
+    },
+    "com.zsjz.ai.common.model.external.entity.LicInfo": {
+      "contactAddr": "联系住所详址",
+      "fileNo": "驾驶证档案编号",
+      "firstDate": "初次领证日期",
+      "issOrg": "发证机关",
+      "licTerm": "驾驶证期限",
+      "nation": "国籍/地区",
+      "origType": "原准驾车型",
+      "overScoreDate": "驾驶证超分日期",
+      "photo": "照片",
+      "regAddr": "登记住所详址",
+      "score": "累积记分",
+      "status": "驾驶证状态",
+      "tmpResNo": "暂住证编号",
+      "vehType": "准驾车型"
+    },
+    "com.zsjz.ai.common.model.external.entity.VehViolation": {
+      "dealOrg": "处理机关",
+      "dealTime": "处理时间",
+      "licNo": "驾驶证号",
+      "person": "当事人",
+      "photo": "照片",
+      "plateNo": "号牌号码",
+      "plateType": "号牌种类",
+      "violAct": "违法行为",
+      "violAddr": "违法地址",
+      "violTime": "违法时间"
+    },
+    "com.zsjz.ai.common.model.external.entity.VehicleInfo": {
+      "addr": "住所地址",
+      "engCc": "机动车发动机排量",
+      "engModel": "机动车发动机型号",
+      "engPwr": "机动车发动机功率",
+      "fuelType": "机动车能源种类",
+      "loadCap": "机动车核定载质量",
+      "madeDate": "出厂日期",
+      "photo": "照片",
+      "seatCnt": "机动车核定载客人数",
+      "tmpAddr": "暂住地址"
+    },
+    "com.zsjz.ai.common.model.govern.dto.BankStatDTO": {
+      "accountCkqx": "存款期限",
+      "accountLeftMoney": "账户余额",
+      "accountStatus": "账户状态",
+      "closeAccountDate": "销户日期",
+      "dealJe": "交易净额",
+      "openAccountDate": "开户日期",
+      "personCardNo": "交易信息-所有卡号",
+      "personName": "归属人",
+      "transExpend": "交易信息-总支出",
+      "transIncome": "交易信息-总收入"
+    },
+    "com.zsjz.ai.common.model.govern.dto.OperStatDTO": {
+      "inCount": "呼入次数",
+      "inTotalCount": "呼入总时长",
+      "operName": "操作员姓名",
+      "outCount": "呼出次数",
+      "outTotalCount": "呼出总时长",
+      "personName": "人员姓名",
+      "personPhone": "人员手机号",
+      "totalCount": "总通话次数",
+      "totalTime": "总通话时长"
+    },
+    "com.zsjz.ai.common.model.govern.entity.GovernConf": {
+      "confStr": "配置内容 y:开启 n:关闭",
+      "confType": "配置类型",
+      "id": "主键 ID"
+    },
+    "com.zsjz.ai.common.model.govern.entity.GovernTree": {
+      "dataCount": "数据数量",
+      "tableNameCn": "表名",
+      "tableNameEn": "表名",
+      "type": "类型"
+    },
+    "com.zsjz.ai.common.model.graph.query.GraphCallDetailQuery": {
+      "otherPhone": "对方号码",
+      "personPhone": "本方号码"
+    },
+    "com.zsjz.ai.common.model.graph.query.GraphTransDetailQuery": {
+      "ext1": "交易摘要",
+      "otherCardNo": "对方卡号",
+      "otherName": "交易对方名称",
+      "personCardNo": "本方卡号"
+    },
+    "com.zsjz.ai.common.model.otg.query.TimeSeriesQuery": {
+      "hasLocal": "是否包含本地数据",
+      "id": "源数据id",
+      "rowId": "时间轴的id",
+      "rowIdSymbol": "滚动方向",
+      "tableNameEn": "中文表名"
+    },
+    "com.zsjz.ai.common.model.person.dto.AddPersonGroupDTO": {
+      "groupId": "分组 ID",
+      "personList": "人员列表"
+    },
+    "com.zsjz.ai.common.model.person.dto.CallDataProfileStatDTO": {
+      "callCount": "通话总次数",
+      "callDateEnd": "通话结束日期",
+      "callDateStart": "通话开始日期",
+      "callRatioMap": "通话类型占比映射",
+      "inCount": "被叫次数",
+      "operCountMap": "运营商标码统计映射",
+      "otherCount": "其他通话次数",
+      "outCount": "主叫次数",
+      "personName": "人员姓名",
+      "phoneCount": "手机号数量"
+    },
+    "com.zsjz.ai.common.model.person.dto.CallDirectionRatioDTO": {
+      "inCallRatio": "被叫占比",
+      "otherCallRatio": "其他通话占比",
+      "outCallRatio": "主叫占比"
+    },
+    "com.zsjz.ai.common.model.person.dto.CallListDTO": {
+      "attribution": "归属地",
+      "count": "联系次数",
+      "otherName": "对方姓名",
+      "otherPhone": "对方号码",
+      "personName": "本方号码"
+    },
+    "com.zsjz.ai.common.model.person.dto.CallOftenTop10DTO": {
+      "callCount": "通话次数",
+      "otherName": "对方姓名",
+      "otherPhone": "对方电话",
+      "personName": "人员姓名"
+    },
+    "com.zsjz.ai.common.model.person.dto.CallRecordSummaryDTO": {
+      "callCount": "通话次数",
+      "otherName": "对方姓名",
+      "otherPhone": "对方电话",
+      "personName": "人员姓名"
+    },
+    "com.zsjz.ai.common.model.person.dto.CallStatStationDTO": {
+      "personCellId": "本方基站小区号",
+      "personRegionId": "本方基站大区号",
+      "personStationLatitude": "本方基站纬度",
+      "personStationLongitude": "本方基站经度",
+      "personStationName": "本方基站名称"
+    },
+    "com.zsjz.ai.common.model.person.dto.CallVoiceDTO": {
+      "otherName": "对方姓名",
+      "personName": "被调查对象姓名",
+      "vcCount": "通话次数"
+    },
+    "com.zsjz.ai.common.model.person.dto.DealSummaryVO": {
+      "dealCount": "交易次数",
+      "id": "该条记录主键id",
+      "maxTime": "最大时间",
+      "minTime": "最小时间",
+      "otherCardNo": "交易对方卡号",
+      "otherName": "交易对方姓名",
+      "personCardNo": "本方卡号",
+      "totalDealMoney": "交易金额"
+    },
+    "com.zsjz.ai.common.model.person.dto.DeletePersonGroupDTO": {
+      "id": "分组 ID"
+    },
+    "com.zsjz.ai.common.model.person.dto.IntimacyDTO": {
+      "detail": "亲密度详情",
+      "intimacy": "亲密度数值",
+      "intimacyBehavior": "亲密行为",
+      "otherName": "对方名称"
+    },
+    "com.zsjz.ai.common.model.person.dto.NightVoiceDTO": {
+      "top5List": "对方信息top5"
+    },
+    "com.zsjz.ai.common.model.person.dto.NightVoiceTempDTO": {
+      "callDate": "开始通话时间",
+      "otherName": "对方号码",
+      "personName": "本方号码"
+    },
+    "com.zsjz.ai.common.model.person.dto.NightVoiceTop5DTO": {
+      "cmnCount": "联系次数",
+      "otherName": "对方姓名",
+      "otherPhone": "对方号码",
+      "totalMonth": "总月数"
+    },
+    "com.zsjz.ai.common.model.person.dto.PersonBasicInfoDTO": {
+      "cardList": "银行卡",
+      "householdRegistered": "户口所在地",
+      "marry": "婚姻状态",
+      "nation": "民族",
+      "nativePlace": "籍贯",
+      "personAddress": "地址",
+      "personAge": "年龄",
+      "personBirth": "出生日期",
+      "personCertNo": "证件号",
+      "personGender": "性别",
+      "personName": "姓名",
+      "personTag": "标签",
+      "phoneList": "手机号",
+      "photograph": "照片"
+    },
+    "com.zsjz.ai.common.model.person.dto.SpecialDmDTO": {
+      "count": "次数",
+      "idStr": "id逗号分隔",
+      "otherCardNo": "交易对方卡号",
+      "otherName": "交易对方名称",
+      "personName": "名称"
+    },
+    "com.zsjz.ai.common.model.person.dto.TransDataProfileStatDTO": {
+      "bankCountMap": "银行分布映射",
+      "cardCount": "银行卡数量",
+      "personName": "人员姓名",
+      "totalBalance": "总余额",
+      "transInfoStat": "交易统计信息"
+    },
+    "com.zsjz.ai.common.model.person.dto.TransInfoStatDTO": {
+      "expenseRatio": "支出占比",
+      "expenseTotalAmount": "支出总金额",
+      "incomeRatio": "收入占比",
+      "incomeTotalAmount": "收入总金额",
+      "maxSingleExpense": "最大单笔支出",
+      "maxSingleIncome": "最大单笔收入",
+      "totalBalance": "总余额",
+      "transTotalMoney": "交易总金额"
+    },
+    "com.zsjz.ai.common.model.person.dto.TransOftenTop10DTO": {
+      "dimension": "统计维度",
+      "expenseTotalAmount": "支出总金额",
+      "incomeTotalAmount": "收入总金额",
+      "indicator": "指标",
+      "transCount": "交易次数",
+      "transTotalAmount": "交易总金额"
+    },
+    "com.zsjz.ai.common.model.person.dto.TransTrendDTO": {
+      "debitCreditFlag": "借贷标志",
+      "transDate": "交易日期",
+      "transTotalAmount": "交易总金额"
+    },
+    "com.zsjz.ai.common.model.person.entity.PersonBasicInfo": {
+      "householdRegistered": "户口所在地",
+      "marry": "婚姻状态",
+      "nation": "民族",
+      "nativePlace": "籍贯",
+      "orderSort": "排序",
+      "personAddress": "地址",
+      "personAge": "年龄",
+      "personBirth": "出生日期",
+      "personCertNo": "证件号",
+      "personGender": "性别",
+      "personName": "姓名",
+      "personTag": "标签",
+      "photograph": "照片"
+    },
+    "com.zsjz.ai.common.model.person.entity.PersonLibNo": {
+      "endTime": "结束使用时间",
+      "hasAuto": "是否自动 1-是",
+      "libType": "1-证件号",
+      "startTime": "开始使用时间"
+    },
+    "com.zsjz.ai.common.model.person.query.CallOperPhoneInfoQuery": {
+      "operatorId": "运营商ID",
+      "phoneList": "电话号码列表"
+    },
+    "com.zsjz.ai.common.model.person.query.CallRecordSummaryQuery": {
+      "otherName": "对方姓名",
+      "otherPhone": "对方电话号码"
+    },
+    "com.zsjz.ai.common.model.person.query.TransOftenTop10Query": {
+      "indicator": "常交易对象TOP10 - 统计指标",
+      "otherName": "对方姓名",
+      "transAmount": "交易金额",
+      "transAmountSymbol": "交易金额比较符号",
+      "transCount": "交易次数",
+      "transCountSymbol": "交易次数比较符号"
+    },
+    "com.zsjz.ai.common.model.person.query.TransRecordSummaryQuery": {
+      "cashKey": "现金关键字列表",
+      "otherName": "对方姓名",
+      "personCardNo": "人员银行卡号",
+      "transAmount": "交易金额",
+      "transAmountSymbol": "交易金额比较符号",
+      "transCount": "交易次数",
+      "transCountSymbol": "交易次数比较符号"
+    },
+    "com.zsjz.ai.common.model.plat.dto.ChangePwdDTO": {
+      "newPwd": "新密码",
+      "oldPwd": "原密码"
+    },
+    "com.zsjz.ai.common.model.plat.dto.LoginDTO": {
+      "password": "明文密码",
+      "username": "登录名"
+    },
+    "com.zsjz.ai.common.model.plat.dto.OpenCaseDTO": {
+      "id": "案件 ID",
+      "pwd": "案件密码"
+    },
+    "com.zsjz.ai.common.model.plat.dto.ResetPwdDTO": {
+      "id": "目标用户 ID",
+      "newPassword": "新密码"
+    },
+    "com.zsjz.ai.common.model.plat.dto.TowerConvertDTO": {
+      "cid": "CID",
+      "field1": "查询号码",
+      "field2": "通信时间",
+      "field3": "基站地址",
+      "field4": "经度",
+      "field5": "纬度",
+      "field6": "转换结果",
+      "lac": "LAC",
+      "oprt": "运营商"
+    },
+    "com.zsjz.ai.common.model.plat.dto.UserSaveDTO": {
+      "displayName": "显示名",
+      "password": "明文密码;新建时必填",
+      "username": "登录名;新建时必填"
+    },
+    "com.zsjz.ai.common.model.plat.dto.VerifyPwdDTO": {
+      "password": "待校验的明文密码"
+    },
+    "com.zsjz.ai.common.model.plat.entity.CardIssuerBank": {
+      "bankBin": "银行卡 BIN 号",
+      "bankName": "发卡行名称",
+      "cardType": "卡片类型",
+      "flag": "标志位"
+    },
+    "com.zsjz.ai.common.model.plat.entity.CaseInfo": {
+      "createTime": "创建时间",
+      "db": "数据库名称",
+      "dbPath": "数据库路径",
+      "id": "案件ID",
+      "init": "1是0否",
+      "name": "案件名称",
+      "ownerId": "案件归属用户 ID",
+      "pwd": "案件密码",
+      "remark": "案件备注",
+      "type": "案件类型",
+      "workspacePath": "案件工作空间目录"
+    },
+    "com.zsjz.ai.common.model.plat.entity.PhoneIsp": {
+      "city": "城市",
+      "isp": "运营商",
+      "paragraph": "手机号段",
+      "province": "省份"
+    },
+    "com.zsjz.ai.common.model.plat.entity.SysUser": {
+      "displayName": "显示名",
+      "id": "用户 ID",
+      "pwdHash": "BCrypt 密码哈希 —— 绝不返回给前端",
+      "username": "登录名"
+    },
+    "com.zsjz.ai.common.model.plat.entity.SystemInfo": {
+      "activeDateTime": "激活时间",
+      "authdatetime": "授权时间",
+      "clientNo": "客户编号",
+      "machinecode": "机器码",
+      "overdatetime": "过期时间",
+      "phone": "手机号",
+      "remainingDate": "剩余时间",
+      "unit": "单位"
+    },
+    "com.zsjz.ai.common.model.plat.entity.TableField": {
+      "directionConf": "方向配置",
+      "fieldLen": "字段长度",
+      "fieldNameCn": "字段中文名",
+      "fieldNameEn": "字段英文名",
+      "fieldSort": "排序",
+      "fieldType": "字段类型",
+      "id": "主键",
+      "matched": "是否匹配",
+      "required": "是否必填",
+      "tableId": "表id",
+      "tableNameEn": "表英文名",
+      "zf": "开启正负转换 1是、 0否"
+    },
+    "com.zsjz.ai.common.model.plat.entity.TableInfo": {
+      "classify": "类别",
+      "createTime": "创建时间",
+      "funcRegx": "模板函数正则",
+      "hasInner": "是否有内嵌 1是0否",
+      "headers": "表头",
+      "id": "id",
+      "lineNo": "表头行号",
+      "mainId": "主模版 ID",
+      "tableFields": "表字段",
+      "tableNameCn": "表名中文",
+      "tableNameEn": "表名英文"
+    },
+    "com.zsjz.ai.common.model.plat.vo.LoginVO": {
+      "token": "sa-token 登录凭据"
+    },
+    "com.zsjz.ai.common.model.track.dto.ExpressDTO": {
+      "ids": "ID集合",
+      "location": "地点",
+      "numericalOrder": "序号",
+      "personName": "被调查对象",
+      "rcpId": "收件ID集合",
+      "recipientCount": "收快递次数",
+      "sendIds": "发件ID集合",
+      "senderCount": "发快递次数"
+    },
+    "com.zsjz.ai.common.model.track.dto.ExpressInfoDTO": {
+      "location": "地理位置",
+      "personName": "人员姓名",
+      "type": "关联的快递 ID 集合"
+    },
+    "com.zsjz.ai.common.model.track.dto.FindPressPlaceDTO": {
+      "id": "地点唯一标识符",
+      "location": "收发地点地址",
+      "objName": "调查对象姓名",
+      "type": "快递类型标识"
+    },
+    "com.zsjz.ai.common.model.track.dto.TogetherLiveDTO": {
+      "hasTogether": "疑似同住标识",
+      "hotelName": "同住酒店",
+      "id": "旅馆住宿记录主键ID",
+      "inDateTime": "入住时间",
+      "liveCount": "同住次数",
+      "liveWithCertNo": "同住人身份证号",
+      "liveWithName": "同住人姓名",
+      "personCertNo": "被调查对象身份证号",
+      "personName": "被调查对象姓名",
+      "photograph": "调查对象照片"
+    },
+    "com.zsjz.ai.common.model.track.dto.TrackCellTowerDTO": {
+      "callCount": "通信次数",
+      "endDate": "最晚日期",
+      "personCellId": "CID",
+      "personName": "通信人",
+      "personRegionId": "LAC",
+      "personStationLatitude": "基站纬度",
+      "personStationLongitude": "基站经度",
+      "personStationName": "基站名称",
+      "startDate": "最早日期"
+    },
+    "com.zsjz.ai.common.model.track.dto.TrackCellTowerSecondDTO": {
+      "callCount": "通信次数",
+      "personName": "通信人姓名",
+      "personStationName": "基站名称"
+    },
+    "com.zsjz.ai.common.model.track.dto.TrackEnLocalDTO": {
+      "dayNightTotal": "昼夜活动总次数",
+      "personCellId": "人员小区标识",
+      "personName": "人员姓名",
+      "personRegionId": "人员区域标识",
+      "personStationName": "基站名称"
+    },
+    "com.zsjz.ai.common.model.track.dto.TrackPersonTogetherLiveDTO": {
+      "list": "同住对象集合",
+      "map": "同住对象映射",
+      "personCertNo": "被调查对象身份证号",
+      "personName": "被调查对象姓名",
+      "photograph": "被调查对象照片"
+    },
+    "com.zsjz.ai.common.model.track.dto.TrackTogetherLiveDTO": {
+      "hotelName": "同住酒店",
+      "id": "旅馆住宿记录主键ID",
+      "inDateTime": "入住时间",
+      "liveCount": "同住次数",
+      "liveWithName": "同住人姓名",
+      "numericalOrder": "序号",
+      "personName": "被调查对象",
+      "suspectedTogether": "疑似同住标识"
+    },
+    "com.zsjz.ai.common.model.track.dto.TravelDataDTO": {
+      "companionName": "同行人名称",
+      "count": "同行次数",
+      "destination": "目的地",
+      "id": "标准表主键 ID",
+      "origin": "出发地",
+      "personCertNo": "被调查人证件号码",
+      "personName": "被调查人名称",
+      "tripDate": "日期",
+      "tripDateTime": "具体日期时间",
+      "tripNo": "旅程号",
+      "type": "表类型"
+    },
+    "com.zsjz.ai.common.model.track.dto.TravelPairDTO": {
+      "companionName": "同行人员姓名",
+      "personName": "主要人员姓名"
+    },
+    "com.zsjz.ai.common.model.track.dto.TravelRecordDTO": {
+      "data": "出行数据列表",
+      "tripDate": "出行日期"
+    },
+    "com.zsjz.ai.common.model.track.dto.TravelTimelineDTO": {
+      "companionName": "(同行人)",
+      "count": "同行次数",
+      "idList": "数据ID列表",
+      "personName": "(被调查人)"
+    },
+    "com.zsjz.ai.common.model.track.entity.ExpressInfo": {
+      "departurePlace": "始发地名称",
+      "destinationPlace": "目的地名称",
+      "expressCompanyCode": "快递公司编号",
+      "goodsName": "物品名称",
+      "recipientCompany": "收件单位名称",
+      "recipientCurrentAddress": "收件人现住址地址名称",
+      "recipientName": "收件人姓名",
+      "recipientPhone": "收件人联系电话",
+      "sendDate": "寄件日期",
+      "senderCurrentAddress": "寄件人现住址地址名称",
+      "senderName": "寄件人姓名",
+      "senderPhone": "寄件人电话",
+      "waybillNumber": "运单号"
+    },
+    "com.zsjz.ai.common.model.track.entity.FlightDepartureInfo": {
+      "arrCode": "到达航站三字码",
+      "arrDateTime": "进港日期",
+      "birthDate": "出生日期",
+      "birthPlace": "出生地",
+      "carrier": "航空公司代码",
+      "depCode": "起飞港三字码",
+      "depDateTime": "离港日期",
+      "flightDateTime": "航班日期",
+      "flightNo": "航班号",
+      "flightSuffix": "航班后缀",
+      "gender": "性别",
+      "givenName": "名",
+      "middleName": "中间名",
+      "personCertNo": "证件号码",
+      "personName": "旅客姓名",
+      "personNameCn": "旅客中文姓名",
+      "seatNo": "旅客座位信息",
+      "surname": "姓"
+    },
+    "com.zsjz.ai.common.model.track.entity.FlightTicketInfo": {
+      "agent": "出票单位",
+      "arrAirport": "到达机场代码",
+      "arrDateTime": "当地到达时间",
+      "carrier": "承运航空公司代码",
+      "depAirport": "起飞机场代码",
+      "depDateTime": "当地出发时间",
+      "fare": "票价",
+      "flightClass": "航段仓位",
+      "flightNo": "航班号",
+      "issueDate": "出票日期",
+      "personCertNo": "证件号码",
+      "personName": "旅客中文名",
+      "personNameEn": "旅客英文名",
+      "pnr": "记录编号",
+      "status": "航段状态",
+      "surname": "旅客英文姓",
+      "ticketNo": "票号"
+    },
+    "com.zsjz.ai.common.model.track.entity.HotelStayInfo": {
+      "addressCity": "地址 - 城市",
+      "addressDetail": "地址 - 详细地址",
+      "gender": "性别",
+      "hotelAddress": "酒店地址",
+      "hotelName": "酒店名称",
+      "inDateTime": "入住时间",
+      "outDateTime": "退房时间",
+      "personCertName": "证件类型名称",
+      "personCertNo": "证件号码",
+      "personName": "旅客姓名",
+      "roomNo": "房间号"
+    },
+    "com.zsjz.ai.common.model.track.entity.ResidentPopulationInfo": {
+      "address": "住址详址",
+      "belongArea": "所属省市县",
+      "birthCountry": "出生地国家",
+      "birthDate": "出生日期",
+      "birthPlace": "出生地省市县",
+      "degree": "学位",
+      "education": "学历",
+      "formerName": "曾用名",
+      "gender": "性别",
+      "height": "身高",
+      "name": "姓名",
+      "nationality": "民族",
+      "nativeCountry": "籍贯国家",
+      "nativePlace": "籍贯省市县",
+      "personCertNo": "公民身份号码",
+      "photo1": "照片1",
+      "photo2": "照片2",
+      "policeStation": "所属派出所名称",
+      "serviceLocation": "服务所处"
+    },
+    "com.zsjz.ai.common.model.track.entity.TogetherFlightInfo": {
+      "cabinClass": "舱位等级",
+      "companionName": "同行人姓名",
+      "depDateTime": "出发时间",
+      "destination": "目的地",
+      "flightDateTime": "航班日期",
+      "flightNo": "航班号",
+      "origin": "出发地",
+      "personCertNo": "证件号码",
+      "personCertType": "证件类型",
+      "seatNo": "座位号"
+    },
+    "com.zsjz.ai.common.model.track.entity.TogetherLiveInfo": {
+      "coType": "同住类型",
+      "hotelName": "酒店名称",
+      "inDateTime": "入住时间",
+      "outDateTime": "退房时间",
+      "queryAge": "查询人年龄",
+      "queryCertNo": "查询人身份证号",
+      "queryCertType": "查询人证件类型",
+      "queryEthnicity": "查询人民族",
+      "queryGender": "查询人性别",
+      "queryName": "查询人姓名",
+      "queryOrigin": "查询人户籍地",
+      "remark": "备注",
+      "roomNo": "房间号",
+      "stayCount": "同住次数",
+      "togetherCertNo": "同住人身份证号"
+    },
+    "com.zsjz.ai.common.model.track.entity.TogetherTrainInfo": {
+      "carNo": "车厢号",
+      "companionName": "同行人姓名",
+      "depDateTime": "乘车时间",
+      "destination": "终点站名",
+      "origin": "起始站名",
+      "personCertNo": "身份证号",
+      "personCertType": "证件类型",
+      "personName": "姓名",
+      "seatNo": "座位号",
+      "trainNo": "车次"
+    },
+    "com.zsjz.ai.common.model.track.entity.TrackCarTicketInfo": {
+      "busCarType": "班车车型",
+      "busClassType": "班次类型",
+      "busNumber": "班次号",
+      "buyDateTime": "购票成功时间",
+      "depDateTime": "发车时间",
+      "destination": "目的地名称",
+      "origin": "始发车站名称",
+      "personCertNo": "证件号码",
+      "personCertType": "乘车人证件类型",
+      "personName": "乘车人姓名",
+      "pickUpTime": "取票时间",
+      "plateNumber": "车牌号",
+      "receiveSmsPhone": "接收短信手机号码",
+      "refundAmount": "退票金额",
+      "refundDateTime": "退票时间",
+      "salesChannelCode": "销售渠道编码",
+      "seatNo": "座位号",
+      "ticketPrice": "车票价格",
+      "ticketStatus": "车票状态",
+      "ticketType": "车票类型"
+    },
+    "com.zsjz.ai.common.model.track.entity.TrainTicketInfo": {
+      "carNo": "车厢号",
+      "changeDate": "改签日期",
+      "depDateTime": "发车日期",
+      "destination": "到站",
+      "origin": "发站",
+      "personCertNo": "证件号码",
+      "personName": "旅客姓名",
+      "refundDate": "退票日期",
+      "seatNo": "座位号",
+      "ticketStatus": "车票状态",
+      "trainNo": "车号"
+    },
+    "com.zsjz.ai.common.model.track.query.ExpressInfoQuery": {
+      "departurePlace": "出发地",
+      "destinationPlace": "目的地",
+      "ids": "快递 ID 列表",
+      "location": "地理位置",
+      "recipientName": "收件人姓名",
+      "senderName": "寄件人姓名"
+    },
+    "com.zsjz.ai.common.model.track.query.TogetherLiveDetailQuery": {
+      "hasTogether": "疑似同住标识",
+      "hotelName": "酒店名称",
+      "inEndTime": "入住结束时间",
+      "inStartTime": "入住开始时间",
+      "liveWithCertNo": "同住人身份证号",
+      "liveWithName": "同住人名称",
+      "outEndTime": "退房结束时间",
+      "outStartTime": "退房开始时间",
+      "personCertNo": "被调查对象身份证号",
+      "personName": "被调查对象名称",
+      "search": "旅店名称",
+      "timeGap": "时间间隔"
+    },
+    "com.zsjz.ai.common.model.track.query.TrackCellTowerQuery": {
+      "otherPhone": "对方号码",
+      "personCellId": "小区 ID",
+      "personRegionId": "区域 ID",
+      "personStationName": "基站名称"
+    },
+    "com.zsjz.ai.common.model.track.query.TrackEnLocalQuery": {
+      "dayNightTotal": "昼夜活动总次数",
+      "morningTime": "白天时段阈值",
+      "nightTime": "夜晚时段阈值"
+    },
+    "com.zsjz.ai.common.model.track.query.TrackMeetQuery": {
+      "cid": "基站小区 ID",
+      "lac": "基站区域 ID",
+      "timeInterval": "时间间隔"
+    },
+    "com.zsjz.ai.common.model.track.query.TrackTogetherLiveQuery": {
+      "hotelName": "旅店名称",
+      "ids": "数据 ID 列表",
+      "liveWithName": "同住人名称",
+      "outCount": "同住次数阈值",
+      "outCountSymbol": "同住次数比较符号",
+      "personName": "被调查对象名称",
+      "search": "调查对象姓名或身份证号列表",
+      "suspectedTogether": "疑似同住标记",
+      "timeGap": "时间间隔",
+      "togHotel": "同住酒店名称"
+    },
+    "com.zsjz.ai.common.model.trans.dto.StatTogetherDoTransDTO": {
+      "endDate": "时间窗口最晚日期",
+      "otherSummaryList": "交易对方汇总列表",
+      "startDate": "时间窗口最早日期"
+    },
+    "com.zsjz.ai.common.model.trans.dto.StatTogetherDoTransOtherSummaryDTO": {
+      "otherCardNo": "交易对方卡号",
+      "otherName": "交易对方名称",
+      "transPersonDTOList": "参与交易的被调查对象列表"
+    },
+    "com.zsjz.ai.common.model.trans.dto.StatTogetherDoTransPersonDTO": {
+      "bankName": "所属银行名称",
+      "endDate": "最晚交易日期",
+      "ids": "交易记录 ID 集合",
+      "mainDesc": "主体/成员标识",
+      "personCardNo": "被调查对象银行卡号",
+      "personName": "被调查对象名称",
+      "startDate": "最早交易日期",
+      "tag": "对象标签",
+      "transAmount": "交易金额"
+    },
+    "com.zsjz.ai.common.model.trans.dto.StatTransCardHoldDTO": {
+      "bankCardPersonList": "银行卡代持人员列表",
+      "mainPersonName": "主体对象名称"
+    },
+    "com.zsjz.ai.common.model.trans.dto.StatTransCardHoldPersonDTO": {
+      "gx": "关系描述",
+      "ids": "交易记录 ID 列表",
+      "otherCardNo": "对方卡号",
+      "otherName": "其他人员姓名",
+      "type": "关联类型"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransBankCardInfoDTO": {
+      "accountStatus": "账户状态",
+      "bankName": "所属银行",
+      "closingDate": "销户日期",
+      "depositTerm": "存款期限",
+      "expenseTotalAmount": "支出总金额",
+      "incomeTotalAmount": "收入总金额",
+      "openingDate": "账号开户时间",
+      "personCardNo": "本方银行卡号",
+      "personName": "本方姓名",
+      "totalBalance": "交易余额",
+      "transCount": "交易次数",
+      "transTotalAmount": "交易总金额"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransBigDTO": {
+      "debitCreditFlag": "借贷标志",
+      "id": "交易记录 ID",
+      "otherCardNo": "对方证件号码",
+      "otherName": "对方姓名",
+      "personCardNo": "交易人证件号码",
+      "personName": "交易人姓名",
+      "totalTransAmount": "总交易金额",
+      "transAgg": "交易聚合信息",
+      "transAmount": "单笔交易金额",
+      "transBigThirdList": "交易明细列表",
+      "transCount": "交易次数",
+      "transDate": "交易日期"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransBigThirdDTO": {
+      "debitCreditFlag": "借贷标志",
+      "id": "交易记录唯一标识",
+      "transAmount": "交易金额",
+      "transDate": "交易日期"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransCashCallBeforeAfterDTO": {
+      "callCount": "通话次数",
+      "endCallDate": "通话结束日期",
+      "maxCallDuration": "最长通话时长",
+      "otherName": "对方姓名",
+      "otherPhone": "对方电话号码",
+      "startCallDate": "通话开始日期"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransCashFlowDTO": {
+      "bankName": "所属银行",
+      "expenseTotalAmount": "支出总金额",
+      "incomeTotalAmount": "收入总金额",
+      "otherCardNo": "交易对方卡号",
+      "otherName": "交易对方姓名",
+      "personName": "交易人姓名",
+      "transCount": "交易次数",
+      "transTotalAmount": "交易总金额"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransContinuousDTO": {
+      "contGroupId": "连续交易组 ID",
+      "continuousEndMonth": "连续交易结束月份",
+      "continuousStartMonth": "连续交易开始月份",
+      "otherCardNos": "合并的对方卡号列表",
+      "otherName": "对方姓名",
+      "pairContMonthCount": "连续月份数",
+      "pairTotalTrans": "交易总次数",
+      "personCardNo": "本方卡号",
+      "personName": "本方姓名"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransFastFundFloDayDTO": {
+      "cardNo": "银行卡号",
+      "debitCreditFlag": "借贷标志",
+      "idList": "交易 ID 集合",
+      "totalTransAmount": "交易总金额",
+      "transCount": "交易次数",
+      "transDate": "交易日期"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransFastFundFlowBankDTO": {
+      "bankName": "银行名称",
+      "cardNo": "银行卡号",
+      "inTotalMoney": "收入总金额",
+      "outTotalMoney": "支出总金额"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransFastFundFlowDTO": {
+      "banks": "交易信息 - 银行统计",
+      "endDate": "查询结束日期",
+      "list": "交易统计列表",
+      "startDate": "查询开始日期"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransFinancialDTO": {
+      "id": "交易记录 ID",
+      "otherCardNo": "对方卡号",
+      "otherName": "对方姓名",
+      "personCardNo": "交易人证件号码",
+      "personName": "交易人姓名",
+      "remark": "备注信息",
+      "transDateTime": "交易时间",
+      "transSummary": "交易摘要"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransFlowDTO": {
+      "children": "子节点",
+      "dataLevel": "数据层级",
+      "debitCreditFlag": "借贷标志",
+      "ids": "数据 ID 集合",
+      "level": "层级",
+      "lineData": "线上数据 交易信息",
+      "maxDate": "最大日期",
+      "maxTime": "最大的交易时间",
+      "minDate": "最小日期",
+      "name": "姓名",
+      "otherCardNos": "对方卡号",
+      "otherName": "交易对方名称",
+      "personCardNos": "本方卡号",
+      "totalTransAmount": "交易金额",
+      "traceData": "追溯返回数据集合",
+      "transCount": "交易次数"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransFlowPageDTO": {
+      "childName": "下一个节点的名称",
+      "dataLevel": "数据层级",
+      "debitCreditFlag": "交易类型",
+      "id": "ID",
+      "otherCardNo": "对方卡号",
+      "otherName": "对方姓名",
+      "parentName": "上一个节点的名称",
+      "personCardNos": "本方卡号",
+      "transAmount": "交易金额",
+      "transDateTime": "交易时间"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransFrequencyDTO": {
+      "endTransDate": "末次交易日期",
+      "expenseTotalAmount": "支出总金额",
+      "incomeTotalAmount": "收入总金额",
+      "otherCardNo": "交易对方卡号",
+      "otherName": "交易对方姓名",
+      "personName": "交易人姓名",
+      "startTransDate": "首次交易日期",
+      "transCount": "交易次数",
+      "transTotalAmount": "交易总金额"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransFundFlowDTO": {
+      "debitCreditFlag": "借贷标志",
+      "otherName": "交易对方名称",
+      "personCardNo": "查询卡号",
+      "personName": "客户名称",
+      "transAmount": "交易金额",
+      "transDateTime": "交易日期时间"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransPersonAcctFixedDepositDTO": {
+      "accountStatus": "账户状态",
+      "balance": "账户余额",
+      "closingDateTime": "销户时间",
+      "id": "账户 ID",
+      "openingDateTime": "开户时间",
+      "personCardNo": "人员证件号码",
+      "personName": "人员姓名",
+      "remarks": "备注信息"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransPersonFixedDepositDTO": {
+      "expenseTotalAmount": "支出总金额",
+      "incomeTotalAmount": "收入总金额",
+      "personName": "人员姓名",
+      "totalTransAmount": "总交易金额"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransPersonFixedDepositDetailDTO": {
+      "accountStatus": "卡状态",
+      "concatInfo": "附属信息",
+      "debitCreditFlag": "借贷标志",
+      "id": "记录 ID",
+      "personCardNo": "被调查对象卡",
+      "personName": "被调查对象姓名",
+      "source": "数据来源标识",
+      "tbId": "表 ID",
+      "transAmount": "交易金额",
+      "transBalance": "余额",
+      "transDateTime": "发生日期"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransRecordSummaryDTO": {
+      "bankName": "所属银行",
+      "expenseTotalAmount": "支出总金额",
+      "incomeTotalAmount": "收入总金额",
+      "otherCardNo": "交易对方卡号",
+      "otherName": "交易对方名称",
+      "personCardNo": "本方银行卡号",
+      "personName": "本方姓名",
+      "transCount": "交易次数",
+      "transTotalAmount": "交易总金额"
+    },
+    "com.zsjz.ai.common.model.trans.dto.TransSensitiveDTO": {
+      "otherCardNo": "对方卡号",
+      "otherName": "对方姓名",
+      "personName": "本方姓名",
+      "transCount": "交易次数"
+    },
+    "com.zsjz.ai.common.model.trans.entity.AcctOpenInfo": {
+      "accountStatus": "账户状态",
+      "accountType": "账户类型",
+      "availableBalance": "可用余额",
+      "balance": "账户余额",
+      "bankName": "账号开户银行",
+      "branchCode": "开户网点代码",
+      "branchName": "开户网点",
+      "closedBranch": "销户网点",
+      "closingDate": "销户日期",
+      "currency": "币种",
+      "depositTerm": "存款期限",
+      "holderCertNo": "开户人证件号码",
+      "lastTransDateTime": "最后交易时间",
+      "linkedFundAccount": "关联资金账户",
+      "mailingAddress": "通信地址",
+      "openingDate": "账号开户时间",
+      "personAccountNo": "交易账号",
+      "personCardNo": "交易卡号",
+      "personName": "账户开户名称",
+      "phone": "联系电话",
+      "remark": "备注"
+    },
+    "com.zsjz.ai.common.model.trans.entity.TransCashKey": {
+      "cashKey": "现金关键字内容",
+      "id": "主键 ID"
+    },
+    "com.zsjz.ai.common.model.trans.entity.TransFinancialKey": {
+      "financialKey": "关键字",
+      "id": "主键 ID",
+      "tag": "关键字标签"
+    },
+    "com.zsjz.ai.common.model.trans.entity.TransFixedDepositKey": {
+      "fixedDepositKey": "存取关键字内容",
+      "id": "主键 ID"
+    },
+    "com.zsjz.ai.common.model.trans.entity.TransRecord": {
+      "bankName": "银行名称",
+      "branchCode": "交易网点代码",
+      "branchName": "交易网点名称",
+      "cashFlag": "现金标志",
+      "currency": "币种",
+      "debitCreditFlag": "借贷标志",
+      "ext1": "扩展信息1 交易类型|交易摘要|备注  三个合并",
+      "festivalName": "节假日",
+      "merchantName": "商户名称",
+      "merchantNo": "商户号",
+      "otherAccount": "交易对方账号",
+      "otherAccountBank": "交易对方账号开户行",
+      "otherBalance": "交易对方余额",
+      "otherCardNo": "交易对方卡号",
+      "otherCertNo": "交易对方证件号码",
+      "otherName": "交易对方名称",
+      "personAccount": "查询账号",
+      "personCardNo": "查询卡号",
+      "personCertNo": "客户证件号码",
+      "personIpAddress": "本方IP地址",
+      "personLibId": "关联人卡库",
+      "personMacAddress": "本方MAC地址",
+      "personName": "客户名称",
+      "remark": "备注",
+      "terminalNo": "终端号",
+      "transAmount": "交易金额",
+      "transBalance": "交易余额",
+      "transChannel": "交易渠道",
+      "transClerkNo": "交易柜员号",
+      "transDate": "交易日期",
+      "transDateTime": "交易日期时间",
+      "transLocation": "交易发生地",
+      "transSerialNo": "交易流水号",
+      "transSummary": "交易摘要",
+      "transType": "交易类型"
+    },
+    "com.zsjz.ai.common.model.trans.entity.TransRecordAll": {
+      "bankName": "银行名称",
+      "branchCode": "交易网点代码",
+      "branchName": "交易网点名称",
+      "cashFlag": "现金标志",
+      "currency": "币种",
+      "debitCreditFlag": "借贷标志",
+      "ext1": "扩展信息1 交易类型|交易摘要|备注  三个合并",
+      "festivalName": "节假日",
+      "merchantName": "商户名称",
+      "merchantNo": "商户号",
+      "otherAccount": "交易对方账号",
+      "otherAccountBank": "交易对方账号开户行",
+      "otherBalance": "交易对方余额",
+      "otherCardNo": "交易对方卡号",
+      "otherCertNo": "交易对方证件号码",
+      "otherName": "交易对方名称",
+      "personAccount": "查询账号",
+      "personCardNo": "查询卡号",
+      "personCertNo": "客户证件号码",
+      "personIpAddress": "本方IP地址",
+      "personLibId": "关联人卡库",
+      "personMacAddress": "本方MAC地址",
+      "personName": "客户名称",
+      "remark": "备注",
+      "terminalNo": "终端号",
+      "transAmount": "交易金额",
+      "transBalance": "交易余额",
+      "transChannel": "交易渠道",
+      "transClerkNo": "交易柜员号",
+      "transDate": "交易日期",
+      "transDateTime": "交易日期时间",
+      "transLocation": "交易发生地",
+      "transSerialNo": "交易流水号",
+      "transSummary": "交易摘要",
+      "transType": "交易类型"
+    },
+    "com.zsjz.ai.common.model.trans.query.StatTogetherDoTransQuery": {
+      "cardNum": "共同交易卡数量阈值",
+      "cardNumSymbol": "共同交易卡数量比较符号",
+      "caseId": "案件 ID",
+      "dayGap": "时间间隔天数",
+      "daySymbol": "时间范围符号",
+      "groupId": "分组 ID",
+      "personNames": "被调查对象名称列表",
+      "transAmount": "交易金额阈值",
+      "transAmountSymbol": "交易金额比较符号"
+    },
+    "com.zsjz.ai.common.model.trans.query.StatTransCardHoldQuery": {
+      "groupId": "被调查对象分组 ID",
+      "transAmount": "交易金额阈值",
+      "transAmountSymbol": "交易金额比较符号"
+    },
+    "com.zsjz.ai.common.model.trans.query.TransBigQuery": {
+      "otherName": "对手方姓名",
+      "transAmount": "交易金额阈值",
+      "transAmountSymbol": "交易金额比较符号",
+      "transCount": "交易次数阈值",
+      "transCountSymbol": "交易次数比较符号"
+    },
+    "com.zsjz.ai.common.model.trans.query.TransCashCallBeforeAfterQuery": {
+      "gapDay": "扩展天数",
+      "id": "交易记录 ID",
+      "otherName": "对方姓名",
+      "otherPhone": "对方电话号码"
+    },
+    "com.zsjz.ai.common.model.trans.query.TransCashFlowQuery": {
+      "cashKey": "现金关键字列表",
+      "transCount": "最小交易次数阈值",
+      "transCountSymbol": "交易次数阈值符号"
+    },
+    "com.zsjz.ai.common.model.trans.query.TransContinuousQuery": {
+      "maxTransMoney": "最大交易金额",
+      "minTransMoney": "最小交易金额",
+      "monthVal": "连续月份值",
+      "otherCardNo": "交易对方电话",
+      "otherName": "交易对方姓名"
+    },
+    "com.zsjz.ai.common.model.trans.query.TransFastFundFlowQuery": {
+      "hour": "时间范围",
+      "personCardNo": "人员银行卡号",
+      "transAmount": "交易金额阈值",
+      "transAmountSymbol": "交易金额比较符号"
+    },
+    "com.zsjz.ai.common.model.trans.query.TransFinancialQuery": {
+      "financialKey": "金融关键词列表",
+      "financialKeyTag": "金融关键词标签"
+    },
+    "com.zsjz.ai.common.model.trans.query.TransFlowGraphQuery": {
+      "cashOnly": "只看现金",
+      "direction": "追溯方向",
+      "flowPageDTO": "选择的对象",
+      "level": "层级最大 4 层",
+      "money": "金额",
+      "moneySymbol": "金额符号",
+      "personName": "调查对象姓名",
+      "timeGap": "时间间隔",
+      "timeGapSymbol": "时间间隔符号",
+      "traceDirection": "追溯方向",
+      "traceParamList": "追溯数据列表"
+    },
+    "com.zsjz.ai.common.model.trans.query.TransFrequencyQuery": {
+      "otherName": "交易对方名称",
+      "transCount": "最小交易次数阈值",
+      "transCountSymbol": "交易次数阈值符号"
+    },
+    "com.zsjz.ai.common.model.trans.query.TransRecordQuery": {
+      "branchName": "交易网点名称",
+      "debitCreditFlag": "借贷标志",
+      "id": "id",
+      "maxTransAmount": "交易金额范围最大值",
+      "minTransAmount": "交易金额范围最小值",
+      "personCardNo": "查询卡号",
+      "remark": "备注",
+      "transAmount": "交易金额",
+      "transDateTime": "交易日期时间",
+      "transSummary": "交易摘要"
+    },
+    "com.zsjz.ai.module.agent.artifact.ArtifactCsvWriter": {
+      "header": "表头",
+      "rowKeys": "取值键"
+    },
+    "com.zsjz.ai.module.agent.artifact.ArtifactService": {
+      "file": "首行到达时确定的文件路径",
+      "fileName": "中文展示名",
+      "header": "CSV 表头",
+      "rowKeys": "行数据的英文字段名"
+    },
+    "com.zsjz.ai.module.agent.attachment.AttachmentDocIndexer": {
+      "globalEmbedGate": "全局嵌入并发闸门"
+    },
+    "com.zsjz.ai.module.agent.attachment.ChatAttachment": {
+      "indexStatus": "NONE / READY / FAILED",
+      "inlineText": "路径 A 的内联全文",
+      "kind": "DOCUMENT / TABLE / UNSUPPORTED",
+      "linkMessageId": "发出后回填关联消息 id",
+      "sidecarPath": "衍生文件",
+      "status": "READY / FAILED"
+    },
+    "com.zsjz.ai.module.agent.attachment.ChatAttachmentProperties": {
+      "chunkOverlap": "RAG 分块重叠",
+      "chunkSize": "RAG 分块大小",
+      "enabled": "是否启用附件功能",
+      "indexBudgetSeconds": "单附件索引总预算",
+      "indexConcurrency": "RAG 索引并发度",
+      "inlineMaxChars": "文档内联/RAG 的单次提取字符上限",
+      "inlineMaxRows": "表格内联行数上限",
+      "maxChunks": "RAG 分块数量上限",
+      "maxDocMb": "文档类大小上限",
+      "maxSheetMb": "表格类大小上限",
+      "orphanHours": "孤儿附件保留小时数",
+      "sampleRows": "表格样本行数",
+      "topK": "RAG 检索片段数"
+    },
+    "com.zsjz.ai.module.agent.config.ArtifactProperties": {
+      "headRows": "随工具结果回传给模型/SSE 的预览行数",
+      "maxRows": "工具查询行数保险丝",
+      "previewLimitMax": "预览接口单次返回的最大行数",
+      "retainDays": "文件不许过期"
+    },
+    "com.zsjz.ai.module.agent.config.EtlProperties": {
+      "followup": "对话结束后的下一步引导建议配置",
+      "intent": "意图识别与语义增强配置",
+      "python": "Python 数据分析执行配置",
+      "rag": "RAG 表结构向量检索配置"
+    },
+    "com.zsjz.ai.module.agent.config.SkillRepositoryConfigEntry": {
+      "localPath": "git",
+      "path": "Directory containing skill fol",
+      "skillsRoot": "rejected by the underlying rep",
+      "type": "filesystem — load from a direc"
+    },
+    "com.zsjz.ai.module.agent.dto.ChatRequestDTO": {
+      "attachmentIds": "附件 ID 列表",
+      "modelId": "模型配置ID"
+    },
+    "com.zsjz.ai.module.agent.dto.CreateChatSessionDTO": {
+      "modelId": "绑定的模型配置ID"
+    },
+    "com.zsjz.ai.module.agent.dto.SaveExpertDTO": {
+      "skillNames": "允许使用的技能名",
+      "sysPrompt": "系统提示词",
+      "toolGroups": "启用的业务工具组",
+      "toolPackIds": "勾选的工具包 id 列表"
+    },
+    "com.zsjz.ai.module.agent.dto.SaveSkillDTO": {
+      "content": "技能正文",
+      "description": "一句话描述",
+      "name": "技能名"
+    },
+    "com.zsjz.ai.module.agent.dto.SaveToolPackDTO": {
+      "toolNames": "包内工具名清单"
+    },
+    "com.zsjz.ai.module.agent.entity.AgentChatSession": {
+      "modelId": "绑定的模型配置ID"
+    },
+    "com.zsjz.ai.module.agent.entity.AgentEntity": {
+      "description": "Agent 描述信息",
+      "rowId": "主键 ID",
+      "sandboxMode": "沙箱执行模式",
+      "sandboxScope": "沙箱隔离范围",
+      "skillRepositoriesJson": "技能仓库配置列表",
+      "skillsAllowJson": "允许使用的技能列表",
+      "skillsDenyJson": "禁止使用的技能列表",
+      "sysPrompt": "系统提示词",
+      "toolPacksJson": "方案勾选的工具包 id 列表",
+      "toolsAllowJson": "允许使用的工具列表",
+      "toolsDenyJson": "禁止使用的工具列表",
+      "userId": "创建者",
+      "workspacePath": "用户提供的 Agent 工作区路径"
+    },
+    "com.zsjz.ai.module.agent.entity.AgentTool": {
+      "defaultActive": "是否新会话默认装备",
+      "groupCode": "所属工具组编码",
+      "groupDescription": "工具组描述",
+      "groupLabel": "工具组中文名",
+      "groupSort": "工具组在页面上的排序位",
+      "parameters": "入参 JSON Schema",
+      "status": "状态",
+      "toolClass": "声明该工具方法的类",
+      "toolMethod": "方法名"
+    },
+    "com.zsjz.ai.module.agent.entity.AgentToolPackEntity": {
+      "description": "一句话说明",
+      "id": "雪花 ID",
+      "name": "工具包名",
+      "toolNames": "工具名清单",
+      "userId": "创建者"
+    },
+    "com.zsjz.ai.module.agent.followup.FollowupProperties": {
+      "enabled": "是否启用对话结束后的下一步引导建议",
+      "maxTokens": "LLM 输出 token 上限",
+      "questionCount": "生成建议条数",
+      "timeoutSeconds": "LLM 调用超时秒数"
+    },
+    "com.zsjz.ai.module.agent.insight.agent.InsightAgentFactory": {
+      "lastAccessAt": "最近访问时间"
+    },
+    "com.zsjz.ai.module.agent.insight.config.InsightProperties": {
+      "defaultTopN": "图谱快照默认取前 N 个节点 / N 条关系",
+      "maxSessionsPerCase": "单案会话数上限",
+      "maxTopN": "图谱快照硬上限",
+      "messagePageSize": "消息分页 limit 默认值",
+      "messagePageSizeMax": "消息分页 limit 上限;超出直接截断",
+      "sessionLockTimeoutMs": "会话锁等待超时"
+    },
+    "com.zsjz.ai.module.agent.insight.context.InsightContext": {
+      "bizType": "研判场景",
+      "caseId": "案件ID",
+      "caseName": "案件名称",
+      "filters": "用户分析条件",
+      "graph": "图谱快照",
+      "providedEdges": "实际提供给模型的关系数",
+      "providedNodes": "实际提供给模型的节点数",
+      "scope": "BATCH | PAIR",
+      "topN": "期望的研判规模"
+    },
+    "com.zsjz.ai.module.agent.insight.dto.InsightCreateDTO": {
+      "bizType": "研判场景;缺省 GRAPH_RELATION",
+      "caseId": "案件ID",
+      "filters": "分析条件快照",
+      "graph": "当前画布的图谱快照",
+      "scope": "分析范围",
+      "topN": "期望的研判规模"
+    },
+    "com.zsjz.ai.module.agent.insight.dto.InsightFilterDTO": {
+      "communicationCount": "通信次数阈值",
+      "communicationMode": "通信口径标签",
+      "endDate": "分析时间范围终点",
+      "respondents": "分析对象",
+      "startDate": "分析时间范围起点",
+      "totalEdges": "画布 / 接口返回的关系总数",
+      "totalNodes": "画布 / 接口返回的节点总数",
+      "tradeAmountMax": "交易金额上限",
+      "tradeAmountMin": "交易金额下限",
+      "tradeMode": "交易口径标签"
+    },
+    "com.zsjz.ai.module.agent.insight.dto.InsightGraphDTO": {
+      "edges": "关系",
+      "nodes": "节点"
+    },
+    "com.zsjz.ai.module.agent.insight.dto.InsightGraphEdgeDTO": {
+      "label": "关系文字",
+      "source": "起点节点 ID",
+      "target": "终点节点 ID"
+    },
+    "com.zsjz.ai.module.agent.insight.dto.InsightGraphNodeDTO": {
+      "id": "节点 ID",
+      "kind": "实体类型",
+      "label": "展示名"
+    },
+    "com.zsjz.ai.module.agent.insight.dto.InsightMessageQuery": {
+      "caseId": "案件ID",
+      "sessionId": "会话ID"
+    },
+    "com.zsjz.ai.module.agent.insight.dto.InsightStreamDTO": {
+      "caseId": "案件ID",
+      "message": "追问内容;为空表示首轮自动分析",
+      "sessionId": "会话ID"
+    },
+    "com.zsjz.ai.module.agent.insight.entity.AgentInsightMessage": {
+      "caseId": "★ 冗余自会话表",
+      "contentType": "text | insight",
+      "messageType": "chat | first_round",
+      "metadata": "思考过程 / 中断标记 / 耗时等轻量字段",
+      "outputJson": "★ ```insight 结构化产物",
+      "replyToMessageId": "追问指向的首轮消息",
+      "role": "user | assistant | system",
+      "seq": "★ 会话内单调递增序号",
+      "status": "DONE | INTERRUPTED",
+      "toolEvents": "★ 工具调用明细"
+    },
+    "com.zsjz.ai.module.agent.insight.entity.AgentInsightSession": {
+      "agentRowId": "使用的 agent 配置行",
+      "bizType": "研判场景维度",
+      "caseId": "★ 案件级硬隔离边界",
+      "context": "★ 查询条件 + 结果快照 JSON",
+      "contextHash": "context 的 sha256",
+      "id": "主键",
+      "lastSeq": "★ 会话内消息序号水位",
+      "messageCount": "消息条数",
+      "modelId": "创建时确定的默认模型",
+      "status": "ACTIVE | STREAMING",
+      "sysPrompt": "★ 会话内实际使用的完整 sysPrompt",
+      "title": "会话标题"
+    },
+    "com.zsjz.ai.module.agent.insight.prompt.InsightPromptBuilder": {
+      "staticPromptCache": "静态人设缓存"
+    },
+    "com.zsjz.ai.module.agent.insight.service.impl.InsightServiceImpl": {
+      "createCaseLocks": "createSession 的按案件互斥",
+      "sessionLocks": "会话级互斥闸门"
+    },
+    "com.zsjz.ai.module.agent.insight.vo.InsightClueVO": {
+      "createAt": "来源消息创建时间",
+      "evidence": "数字证据清单",
+      "id": "线索编号",
+      "level": "价值分级",
+      "links": "跳转动作",
+      "messageId": "来源消息ID",
+      "nextActions": "建议核验动作",
+      "reason": "可疑理由",
+      "sessionId": "来源会话ID",
+      "type": "线索类型"
+    },
+    "com.zsjz.ai.module.agent.insight.vo.InsightContextVO": {
+      "caseId": "案件ID",
+      "caseName": "案件名称",
+      "communicationCount": "通信次数阈值",
+      "communicationMode": "通信口径标签",
+      "endDate": "分析时间范围终点",
+      "providedEdges": "本次实际提供给模型的关系数",
+      "providedNodes": "本次实际提供给模型的节点数",
+      "respondents": "分析对象;空表示未限定",
+      "scope": "BATCH | PAIR",
+      "startDate": "分析时间范围起点",
+      "topN": "研判规模配置值",
+      "totalEdges": "画布 / 接口返回的关系总数",
+      "totalNodes": "画布 / 接口返回的节点总数",
+      "tradeAmountMax": "交易金额上限",
+      "tradeAmountMin": "交易金额下限",
+      "tradeMode": "交易口径标签"
+    },
+    "com.zsjz.ai.module.agent.insight.vo.InsightMessageTraceVO": {
+      "metadata": "轻量 metadata JSON 文本",
+      "toolEvents": "工具调用事件 JSON 文本"
+    },
+    "com.zsjz.ai.module.agent.insight.vo.InsightMessageVO": {
+      "contentType": "text | insight",
+      "messageType": "chat | first_round | followup",
+      "metadata": "思考过程 / 中断标记等轻量 metadata JSON 文",
+      "role": "user | assistant | system",
+      "seq": "会话内单调序号",
+      "status": "DONE | INTERRUPTED"
+    },
+    "com.zsjz.ai.module.agent.insight.vo.InsightSessionVO": {
+      "bizType": "研判场景",
+      "context": "上下文摘要",
+      "scope": "BATCH | PAIR",
+      "status": "ACTIVE | STREAMING | ARCHIVED"
+    },
+    "com.zsjz.ai.module.agent.intent.IntentProperties": {
+      "enabled": "是否启用前置意图识别管道",
+      "historyTurns": "参与上下文增强的最近消息条数",
+      "maxTokens": "LLM 输出 token 上限",
+      "timeoutSeconds": "LLM 调用超时秒数"
+    },
+    "com.zsjz.ai.module.agent.llm.LlmRequest": {
+      "maxTokens": "最大输出 token;为空时用厂商默认",
+      "messages": "完整消息列表",
+      "modelId": "模型 ID",
+      "system": "系统提示词",
+      "temperature": "采样温度;为空时用厂商默认",
+      "timeout": "单次调用超时;为空时取",
+      "user": "用户提示词;与 二选一"
+    },
+    "com.zsjz.ai.module.agent.mcp.AgentScopeMcpToolProvider": {
+      "toolkit": "MCP 专用工具容器"
+    },
+    "com.zsjz.ai.module.agent.mcp.AgentScopeToolCallback": {
+      "invoker": "(toolInputJson"
+    },
+    "com.zsjz.ai.module.agent.mcp.McpCaseSession": {
+      "sessionCases": "MCP 会话 ID → 案件 ID"
+    },
+    "com.zsjz.ai.module.agent.middleware.CaseScopeMiddleware": {
+      "caseId": "agent 所属工作空间"
+    },
+    "com.zsjz.ai.module.agent.python.PythonProperties": {
+      "command": "Python 解释器命令",
+      "enabled": "是否启用 Python 工具",
+      "maxHistoryExecs": "Python 分析产出图要能在历史会话里长期回看",
+      "maxImages": "单次执行最多捕获图片数",
+      "maxOutputChars": "stdout/stderr 截断字符上限",
+      "snapshotEnabled": "是否为每次执行导出一份案件库快照给 Python 连接",
+      "timeoutSeconds": "执行超时秒数"
+    },
+    "com.zsjz.ai.module.agent.rag.RagProperties": {
+      "autoIndexOnSwitch": "工作空间切换后是否自动后台重建索引",
+      "enabled": "是否为 Agent 注册表结构检索工具",
+      "scoreThreshold": "相似度阈值",
+      "topK": "检索默认返回条数"
+    },
+    "com.zsjz.ai.module.agent.rag.RagSchemaService": {
+      "indexing": "索引重建防重入标记"
+    },
+    "com.zsjz.ai.module.agent.service.AgentInstancePool": {
+      "accessSeq": "访问序号",
+      "accessWallMs": "访问时刻"
+    },
+    "com.zsjz.ai.module.agent.service.impl.AgentChatServiceImpl": {
+      "streamLocks": "会话级流互斥闸门"
+    },
+    "com.zsjz.ai.module.agent.tools.AgentToolRegistry": {
+      "artifactService": "聊天产物服务"
+    },
+    "com.zsjz.ai.module.agent.tools.CallToolSpecs": {
+      "callDirection": "主被叫",
+      "endDate": "结束日期",
+      "monthVal": "连续月份阈值",
+      "nightStart": "夜间起始小时",
+      "operatorId": "运营商",
+      "otherPhone": "对方号码",
+      "pageSize": "每页行数",
+      "personCellId": "本方基站小区号",
+      "personCertNo": "本方证件号码",
+      "personName": "本方姓名",
+      "personPhoneArea": "本方号码归属地",
+      "personRegionId": "本方基站大区号",
+      "startDate": "开始日期"
+    },
+    "com.zsjz.ai.module.agent.tools.GraphSpec": {
+      "category": "分类名",
+      "desc": "节点说明",
+      "edges": "边列表",
+      "id": "节点唯一标识",
+      "label": "关系名称",
+      "name": "分类名",
+      "nodes": "节点列表",
+      "source": "起点节点 id",
+      "target": "终点节点 id",
+      "title": "图谱标题",
+      "value": "度数或权重"
+    },
+    "com.zsjz.ai.module.agent.tools.GraphToolSpecs": {
+      "endDate": "交易日期上限",
+      "pageSize": "每页行数",
+      "startDate": "交易日期下限"
+    },
+    "com.zsjz.ai.module.agent.tools.PersonToolSpecs": {
+      "bankName": "银行名称",
+      "endDate": "结束日期",
+      "otherCardNo": "对方卡号",
+      "otherName": "对方姓名",
+      "otherPhone": "对方号码",
+      "pageSize": "每页行数",
+      "personCardNo": "本方卡号",
+      "personName": "本方姓名",
+      "startDate": "开始日期",
+      "transAmount": "交易金额",
+      "transCount": "交易次数"
+    },
+    "com.zsjz.ai.module.agent.tools.TrackToolSpecs": {
+      "departurePlace": "出发地",
+      "destinationPlace": "目的地",
+      "endDate": "结束日期",
+      "otherPhone": "对方号码",
+      "pageSize": "每页行数",
+      "recipientName": "收件人姓名",
+      "senderName": "寄件人姓名",
+      "startDate": "开始日期"
+    },
+    "com.zsjz.ai.module.agent.tools.TransToolSpecs": {
+      "branchName": "网点名称关键词",
+      "debitCreditFlag": "收付方向",
+      "endDate": "结束日期",
+      "groupId": "人员分组ID",
+      "mainIpList": "IP 列表",
+      "mainMacList": "MAC 列表",
+      "maxTransAmount": "金额上限",
+      "maxTransMoney": "最大金额",
+      "minTransAmount": "金额下限",
+      "minTransMoney": "最小金额",
+      "monthVal": "连续月份阈值",
+      "otherCardNo": "对方卡号",
+      "otherName": "对方姓名",
+      "pageSize": "每页行数",
+      "personCardNo": "本方卡号",
+      "personName": "本方姓名",
+      "personNames": "本方姓名列表",
+      "remark": "备注关键词",
+      "startDate": "开始日期",
+      "transAmount": "金额阈值",
+      "transCount": "交易次数阈值",
+      "transSummary": "交易摘要关键词"
+    },
+    "com.zsjz.ai.module.agent.vo.AgentToolVO": {
+      "defaultActive": "是否新会话默认装备",
+      "description": "工具描述",
+      "groupCode": "所属工具组编码",
+      "groupDescription": "工具组描述",
+      "groupLabel": "工具组中文名",
+      "groupSort": "分组排序位",
+      "readOnly": "是否只读工具",
+      "toolClass": "声明该工具的类",
+      "toolMethod": "方法名",
+      "toolName": "工具名",
+      "updatedAt": "最近一次同步时间"
+    },
+    "com.zsjz.ai.module.agent.vo.ExpertVO": {
+      "builtin": "是否内置",
+      "id": "主键",
+      "mine": "是否当前用户的自建专家",
+      "sysPrompt": "系统提示词",
+      "toolPackIds": "勾选的工具包 id 列表"
+    },
+    "com.zsjz.ai.module.agent.vo.McpServerInfoVO": {
+      "caseTools": "MCP 专用的案件管理工具名",
+      "contextPath": "服务上下文路径",
+      "enabled": "是否启用 MCP Server",
+      "endpoint": "当前协议实际生效的端点路径",
+      "name": "服务名",
+      "note": "接入说明",
+      "port": "服务端口",
+      "protocol": "传输协议",
+      "sseEndpoint": "SSE 端点路径",
+      "toolCount": "已暴露的工具总数"
+    },
+    "com.zsjz.ai.module.agent.vo.ModelTestResultVO": {
+      "latencyMs": "本次真实调用耗时",
+      "message": "结果说明",
+      "success": "是否畅通"
+    },
+    "com.zsjz.ai.module.agent.vo.RagSearchResultVO": {
+      "columns": "字段列表",
+      "rowCount": "行数",
+      "score": "相似度得分",
+      "tableComment": "表注释",
+      "tableName": "表名"
+    },
+    "com.zsjz.ai.module.agent.vo.SkillDetailVO": {
+      "content": "SKILL.md 原文",
+      "enabled": "是否启用",
+      "files": "附属文件",
+      "managed": "是否可管理",
+      "path": "技能所在目录的绝对路径",
+      "size": "字节数",
+      "source": "来源"
+    },
+    "com.zsjz.ai.module.agent.vo.SkillVO": {
+      "description": "技能描述",
+      "enabled": "是否启用",
+      "managed": "是否可管理",
+      "name": "技能名",
+      "path": "技能所在目录的绝对路径"
+    },
+    "com.zsjz.ai.module.agent.vo.ToolPackVO": {
+      "builtin": "是否内置",
+      "id": "主键",
+      "mine": "是否当前用户的自建工具包",
+      "toolCount": "工具数量",
+      "toolNames": "包内工具名清单"
+    },
+    "com.zsjz.ai.module.aiclean.entity.AiCleanBatch": {
+      "aiLoadCount": "走出口 B 成功入库的 sheet 数",
+      "batchId": "上传批次 ID",
+      "existingHitCount": "AI 判定为命中既有模板、并入那一次 doClean 的 s",
+      "matchedCount": "原本就匹配上模板、直接交既有链路的 sheet 数"
+    },
+    "com.zsjz.ai.module.aiclean.entity.AiCleanField": {
+      "colIndex": "原文件里的列序号",
+      "columnName": "物理列名",
+      "directionConf": "借贷方向字典 JSON",
+      "fieldNameCn": "展示名",
+      "fieldNameEn": "语义英文名",
+      "fieldSort": "列序",
+      "fieldType": "取值 FieldTypeEnum"
+    },
+    "com.zsjz.ai.module.aiclean.entity.AiCleanJob": {
+      "aiTemplateId": "出口 B",
+      "blockNo": "单 sheet 多表头时的块序号",
+      "detail": "VerifyReport 的 JSON",
+      "draft": "模型返回的原始草稿 JSON",
+      "exitCode": "出口",
+      "llmCalls": "本 job 调了几次模型",
+      "matchedBy": "命中途径",
+      "refTemplateId": "出口 A",
+      "ver": "处理版本"
+    },
+    "com.zsjz.ai.module.aiclean.entity.AiCleanRule": {
+      "aiTemplateId": "归属 AI 模板",
+      "fieldNameEn": "目标字段英文名",
+      "hints": "扁平提示 JSON 数组",
+      "refTemplateId": "出口 A 时记下的既有 table_info.id"
+    },
+    "com.zsjz.ai.module.aiclean.entity.AiCleanTemplate": {
+      "blockNo": "单 sheet 多表头时的块序号",
+      "caseIdSrc": "首次识别出该模板的案件 ID",
+      "category": "业务分类",
+      "categoryL2": "二级分类",
+      "confidence": "登记时的置信度",
+      "funcRegex": "表头之上正文里外提姓名/卡号的正则",
+      "headerLineNo": "表头所在行",
+      "headerMd5": "归一化表头指纹",
+      "headers": "结构归一之后的列头",
+      "mainRefTemplateId": "判定时参考过的既有模板 id",
+      "matchTier": "定档结果",
+      "needsStruct": "这类模板永远不参与既有匹配",
+      "status": "DRAFT / ONLINE / OFFLINE",
+      "structOps": "结构指令快照 JSON",
+      "tableNameCn": "表中文名",
+      "tableNameEn": "物理表名",
+      "ver": "规则版本"
+    },
+    "com.zsjz.ai.module.aiclean.enums.RowPatternEnum": {
+      "prefixes": "行首前缀",
+      "suffixes": "行尾后缀"
+    },
+    "com.zsjz.ai.module.aiclean.enums.RuleHintEnum": {
+      "argsRequired": "该提示是否需要参数"
+    },
+    "com.zsjz.ai.module.aiclean.model.AiRuleDraft": {
+      "binds": "逐列绑定",
+      "category": "业务分类",
+      "confidence": "模型自报置信度",
+      "headerRow": "表头所在行",
+      "reason": "判定理由",
+      "structHints": "结构指令名",
+      "templateNo": "选中候选的序号"
+    },
+    "com.zsjz.ai.module.aiclean.model.RuleBind": {
+      "confidence": "模型自报的本列置信度",
+      "fieldNameEn": "目标字段英文名",
+      "fileColIndex": "文件列序号",
+      "hints": "清洗提示",
+      "required": "该列是否必填"
+    },
+    "com.zsjz.ai.module.aiclean.service.AiCleanWatchdog": {
+      "staleMinutes": "心跳超时分钟数"
+    },
+    "com.zsjz.ai.module.aiclean.service.AiSmartCleanService": {
+      "batchTokenBudget": "判定每完成一个 sheet 就把真实用量累加进 Stats",
+      "reviewItems": "待人工明细",
+      "tokens": "本轮已消耗的模型 token"
+    },
+    "com.zsjz.ai.module.dm.ai.FileRecognitionResult": {
+      "category": "业务分类",
+      "keywords": "关键词",
+      "summary": "这个文件是干什么的"
+    },
+    "com.zsjz.ai.module.dm.ai.FileRecognitionService": {
+      "concurrency": "并发闸门",
+      "inflight": "已提交但尚未结束的任务数",
+      "maxConcurrent": "同时调模型的任务数上限",
+      "maxPending": "排队积压上限",
+      "timeout": "单次模型调用超时"
+    },
+    "com.zsjz.ai.module.dm.ai.FileSampleExtractor": {
+      "tika": "Tika 实例是线程安全的"
+    },
+    "com.zsjz.ai.module.dm.clean.AbstractDataCleaner": {
+      "defaultDateFunMap": "函数映射",
+      "funDebitCredit": "借贷标志",
+      "funcRegx": "函数正则",
+      "personCardNo": "卡号",
+      "personCertNo": "证件号",
+      "personName": "姓名",
+      "tableRules": "提取数据的规则"
+    },
+    "com.zsjz.ai.module.dm.clean.AbstractDataLoader": {
+      "searchDocIndexService": "全文检索写入服务"
+    },
+    "com.zsjz.ai.module.dm.clean.CleanResult": {
+      "data": "清洗后的数据对象",
+      "error": "清洗过程中的错误日志信息",
+      "valid": "数据是否有效标识"
+    },
+    "com.zsjz.ai.module.dm.clean.StreamingEtlListener": {
+      "currentSheet": "当前处理的工作表信息",
+      "dataCleaner": "数据清洗器实例",
+      "dataLoader": "数据加载器实例",
+      "dirtyHandler": "脏数据处理器",
+      "extractSheetList": "用于记录当前已处理处理的工作表信息",
+      "headerList": "表头列表",
+      "sheetFailRows": "当前工作表失败行数计数器",
+      "sheetList": "待处理的所有工作表信息列表",
+      "sheetRows": "当前工作表行数计数器",
+      "totalRows": "总行数计数器"
+    },
+    "com.zsjz.ai.module.dm.clean.func.FunAbs": {
+      "opened": "是否打开"
+    },
+    "com.zsjz.ai.module.dm.clean.func.FunExtra": {
+      "opened": "提取前后几个字符串 true 前  false 后"
+    },
+    "com.zsjz.ai.module.dm.clean.func.FunMultiplier": {
+      "factor": "乘法系数"
+    },
+    "com.zsjz.ai.module.dm.clean.func.FunProcess": {
+      "exceptionHandler": "异常处理程序",
+      "funs": "存储一系列字符串处理规则的列表"
+    },
+    "com.zsjz.ai.module.dm.clean.func.FunRadix": {
+      "opened": "是否打开",
+      "radixType": "进制互转"
+    },
+    "com.zsjz.ai.module.dm.clean.func.FunReplace": {
+      "replacement": "用于替换目标字符串的新字符串",
+      "target": "要被替换的目标字符串"
+    },
+    "com.zsjz.ai.module.dm.clean.func.FunSpExtra": {
+      "index": "分割提取"
+    },
+    "com.zsjz.ai.module.dm.clean.func.RuleData": {
+      "columnRule": "规则列的拼接数据"
+    },
+    "com.zsjz.ai.module.dm.clean.func.RuleMapping": {
+      "content": "规则映射的内容",
+      "mappingEnum": "映射枚举",
+      "refIndex": "引用索引"
+    },
+    "com.zsjz.ai.module.dm.clean.func.RuleSplit": {
+      "len": "-",
+      "refIndex": "-",
+      "splitChar": "-",
+      "splitNameIndex": "拆分后所在列索引1",
+      "splitNameIndex2": "拆分后所在列索引2"
+    },
+    "com.zsjz.ai.module.dm.clean.impl.dto.CallInOutDataDTO": {
+      "callDate": "通话日期",
+      "callDateTime": "通话日期时间-START_DATE开始时间",
+      "callDirection": "主被叫标识",
+      "callOrderNbr": "CALLORDER_NBR调单号码",
+      "callTime": "通话时间",
+      "duration": "通话时长-DURATION时长",
+      "operatorId": "运营商",
+      "otherPhone": "对方号码-CALLED_NBR被叫号码",
+      "personCertNo": "本方证件号码",
+      "personName": "本方姓名",
+      "personPhone": "本方号码-CALLING_NBR主叫号码",
+      "personPhoneArea": "本方号码归属地",
+      "personPhonePrefix": "前缀6位",
+      "remark": "备注"
+    },
+    "com.zsjz.ai.module.dm.clean.impl.dto.TransAliPayDataDTO2": {
+      "bankName": "银行名称",
+      "debitCreditFlag": "借贷标志",
+      "otherCardNo": "交易对方卡号",
+      "otherCertNo": "交易对方证件号码",
+      "otherName": "交易对方名称",
+      "personCardNo": "查询卡号",
+      "personCertNo": "证件号",
+      "personName": "客户名称",
+      "transAmount": "交易金额",
+      "transDate": "交易日期",
+      "transDateTime": "交易日期时间",
+      "transSummary": "交易摘要",
+      "transType": "交易类型"
+    },
+    "com.zsjz.ai.module.dm.clean.impl.dto.TransAmountBalanceCalcDTO": {
+      "branchCode": "交易网点代码",
+      "branchName": "交易网点名称",
+      "debitCreditFlag": "借贷标志",
+      "otherAccountBank": "对方行名",
+      "otherCardNo": "对方卡",
+      "otherName": "对方名称",
+      "personCardNo": "本方卡",
+      "personCertNo": "证件号",
+      "personName": "客户名称",
+      "remark": "交易备注",
+      "transAmount": "交易金额",
+      "transBalance": "交易余额",
+      "transClerkNo": "柜员号",
+      "transDate": "交易日期",
+      "transDateTime": "交易日期时间",
+      "transSerialNo": "交易流水号",
+      "transSummary": "交易摘要"
+    },
+    "com.zsjz.ai.module.dm.clean.impl.dto.TransCcbDataDTO": {
+      "borrowMoney": "借款金额",
+      "branchCode": "交易网点代码",
+      "branchName": "交易网点名称",
+      "debitCreditFlag": "借贷标志",
+      "loanMoney": "贷款金额",
+      "otherAccountBank": "对方行名",
+      "otherCardNo": "对方卡",
+      "otherName": "对方名称",
+      "personCardNo": "卡号",
+      "personCertNo": "证件号",
+      "personName": "客户名称",
+      "remark": "交易备注",
+      "transAmount": "交易金额",
+      "transBalance": "交易余额",
+      "transClerkNo": "柜员号",
+      "transDate": "交易日期",
+      "transDateTime": "交易日期",
+      "transSerialNo": "交易流水号",
+      "transSummary": "交易摘要"
+    },
+    "com.zsjz.ai.module.dm.clean.impl.dto.TransCoreHisBalanceDTO": {
+      "bankName": "银行名称",
+      "cashFlag": "现金标志-钞汇标志",
+      "currency": "币种",
+      "debitCreditFlag": "借贷标志",
+      "personCardNo": "查询卡号-账号",
+      "personCertNo": "证件号",
+      "personName": "客户名称",
+      "srye": "上日余额",
+      "transAmount": "交易金额",
+      "transBalance": "交易余额-账号余额",
+      "transDate": "交易日期",
+      "transDateTime": "交易日期时间-最后更新日期",
+      "transSerialNo": "交易流水号-上日结单号"
+    },
+    "com.zsjz.ai.module.dm.clean.impl.dto.TransInOutDTO": {
+      "bankName": "银行名称",
+      "branchName": "交易银行",
+      "currency": "币种",
+      "debitCreditFlag": "借贷标志",
+      "otherAccountBank": "对方行名-对方开户银行",
+      "otherCardNo": "对方卡",
+      "otherName": "对方名称",
+      "personCardNo": "本方卡",
+      "personCertNo": "客户证件号码-证件号",
+      "personName": "客户名称-调查户名",
+      "remark": "交易备注",
+      "transAmount": "交易金额",
+      "transBalance": "交易余额",
+      "transClerkNo": "交易柜员号",
+      "transDate": "交易日期",
+      "transDateTime": "交易日期-交易时间",
+      "transIn": "收入",
+      "transOut": "支出",
+      "transSummary": "交易摘要"
+    },
+    "com.zsjz.ai.module.dm.clean.impl.dto.TransInOutDTO2": {
+      "bankName": "银行名称",
+      "debitCreditFlag": "借贷标志",
+      "otherCardNo": "对方卡",
+      "otherName": "对方名称-转入户名",
+      "personCardNo": "本方卡",
+      "personCertNo": "证件号",
+      "personName": "客户名称转出户名",
+      "transAmount": "交易金额",
+      "transDate": "交易日期-日期",
+      "transDateTime": "交易日期-交易时间",
+      "transSummary": "交易摘要"
+    },
+    "com.zsjz.ai.module.dm.clean.impl.dto.TransInOutDTO3": {
+      "bankName": "银行名称",
+      "branchCode": "交易网点名称-交易机构",
+      "branchName": "交易网点代码-交易机构名",
+      "currency": "币种",
+      "debitCreditFlag": "借贷标志",
+      "otherCardNo": "对方卡",
+      "otherName": "对方名称-收款人",
+      "personAccount": "查询账号-账号",
+      "personCardNo": "本方卡",
+      "personCertNo": "证件号",
+      "personName": "客户名称-付款人",
+      "remark": "备注",
+      "transAmount": "交易金额",
+      "transBalance": "交易余额-账户余额",
+      "transClerkNo": "交易柜员",
+      "transDate": "交易日期-交易日期",
+      "transDateTime": "交易日期时间",
+      "transSummary": "交易摘要"
+    },
+    "com.zsjz.ai.module.dm.clean.impl.dto.TransWeChatDataDTO": {
+      "personCertNo": "证件号"
+    },
+    "com.zsjz.ai.module.dm.pre.PreDataListener": {
+      "batchId": "批次 ID",
+      "fileId": "文件 ID",
+      "fileInfoList": "文件信息列表",
+      "fileName": "文件名",
+      "fileSheet": "当前工作表的文件信息对象",
+      "matched": "是否已匹配到模板的标识",
+      "rawAppender": "当前 sheet 的 Appender",
+      "rawConn": "当前 sheet 的 DuckDB 连接",
+      "rawTableInited": "当前 sheet 是否已尝试初始化动态表",
+      "rowCount": "当前 sheet 已读取行数"
+    },
+    "com.zsjz.ai.module.dm.pre.PreTask": {
+      "batchId": "批次 ID",
+      "delimiter": "CSV 分隔符",
+      "fileName": "文件名",
+      "filePath": "文件路径",
+      "md5": "文件内容 MD5",
+      "password": "文件密码"
+    },
+    "com.zsjz.ai.module.dm.service.DmService": {
+      "fileAiProfileService": "非表格文件的 AI 识别档案"
+    },
+    "com.zsjz.ai.module.embedding.config.EmbeddingProperties": {
+      "batchSize": "批量推理时每批文本条数",
+      "eagerLoad": "是否在启动时后台预热加载模型;默认懒加载",
+      "enabled": "是否启用本地嵌入模块",
+      "intraOpThreads": "ORT intra-op 并行线程数;0 表示交给 ONNX",
+      "maxSeqLen": "单文本最大 token 数",
+      "modelPath": "模型目录",
+      "optLevel": "ONNX 图优化级别"
+    },
+    "com.zsjz.ai.module.embedding.core.OnnxEmbeddingEngine": {
+      "dimension": "输出向量维度",
+      "embeddingOutputName": "池化输出名",
+      "inputNames": "会话实际声明的输入名集合"
+    },
+    "com.zsjz.ai.module.embedding.dto.EmbeddingDebugDTO": {
+      "texts": "待嵌入文本"
+    },
+    "com.zsjz.ai.module.embedding.vo.EmbeddingDebugVO": {
+      "dimension": "输出向量维度",
+      "embedMillis": "本次嵌入耗时",
+      "similarity": "两两余弦相似度矩阵",
+      "vectors": "与输入顺序一致的向量"
+    },
+    "com.zsjz.ai.module.govern.rs.CallResultHandler": {
+      "gxList": "关系边数据缓存列表",
+      "personRelEdgeMapper": "人员关系边 Mapper 接口",
+      "sts": "有效方向标识列表"
+    },
+    "com.zsjz.ai.module.govern.rs.TransResultHandler": {
+      "gxList": "关系边数据缓存列表",
+      "personRelEdgeMapper": "人员关系边 Mapper 接口"
+    },
+    "com.zsjz.ai.module.govern.serivce.GovernService": {
+      "aiGovernService": "AI 清洗模板的治理节点"
+    },
+    "com.zsjz.ai.module.plat.service.SseService": {
+      "emitters": "当前连接的用户 → emitter"
+    },
+    "com.zsjz.ai.module.search.domain.SearchDoc": {
+      "content": "全字段拼接文本",
+      "dateTime": "业务时间",
+      "docId": "ES 文档 _id",
+      "rowId": "业务行主键"
+    },
+    "com.zsjz.ai.module.search.service.SearchDocIndexService": {
+      "buffers": "caseId → 攒批缓冲",
+      "indexReady": "索引是否已确认存在"
+    },
+    "com.zsjz.ai.module.trans.controller.TransCardHoldController": {
+      "transCardHoldService": "银行卡代持服务类"
+    }
+  }
+}

+ 70 - 0
ai-server/src/test/java/com/zsjz/ai/common/utils/FieldLabelsTest.java

@@ -0,0 +1,70 @@
+package com.zsjz.ai.common.utils;
+
+import com.zsjz.ai.common.cache.GlobalCache;
+import com.zsjz.ai.common.model.call.dto.CallNightDTO;
+import com.zsjz.ai.common.model.call.entity.CallRecord;
+import com.zsjz.ai.common.model.plat.entity.TableField;
+import com.zsjz.ai.common.model.track.dto.TrackCellTowerDTO;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * 字段英文名 → 中文名解析链契约(工具结果 CSV 表头/列 label 的来源)。
+ *
+ * <p>三层优先级:按类型精确(生成字典 classes 段)→ 表字段元数据(table_field 快照)
+ * → 生成字典兜底(* 段);全落空返回 {@code null},调用方保留英文原名。
+ */
+class FieldLabelsTest {
+
+    @BeforeEach
+    @AfterEach
+    void resetGlobalCache() {
+        // 其它测试类会往快照里塞数据,字典兜底断言必须从空快照起
+        GlobalCache.clearForTesting();
+    }
+
+    @Test
+    @DisplayName("按类型精确取名:同名字段在不同 DTO 里各是各的中文名")
+    void classScopedLabelsWin() {
+        assertEquals("通话总次数", FieldLabels.label(CallNightDTO.class, "callCount"));
+        assertEquals("通信次数", FieldLabels.label(TrackCellTowerDTO.class, "callCount"),
+                "同一个 callCount 换个类型就是另一个含义,必须按类型取");
+        assertEquals("本方姓名", FieldLabels.label(CallRecord.class, "personName"));
+        // 字典里没有的字段 → null,调用方保留英文原名
+        assertNull(FieldLabels.label(CallNightDTO.class, "noSuchFieldFoo"));
+    }
+
+    @Test
+    @DisplayName("表字段元数据优先于生成字典(table_field 是物理表列的权威中文名)")
+    void tableFieldMetadataBeatsGeneratedDictionary() {
+        assertEquals("通话时长", FieldLabels.label("duration"), "快照为空时落到生成字典");
+
+        TableField field = new TableField();
+        field.setFieldNameEn("personName");
+        field.setFieldNameCn("机主姓名");
+        GlobalCache.loadSnapshotForTesting(Map.of(), Map.of(1, List.of(field)));
+
+        assertEquals("机主姓名", FieldLabels.label("personName"), "table_field 快照优先");
+        // 按类型取名时类型仍然最优先:类型字典说的是实体自己的字段注释
+        assertEquals("本方姓名", FieldLabels.label(CallRecord.class, "personName"));
+    }
+
+    @Test
+    @DisplayName("headers:能翻译的换中文、翻译不出来留英文,重名追加序号")
+    void headersTranslateAndDeduplicate() {
+        assertEquals(List.of("通话时长", "通话时长_2", "unknownFoo"),
+                FieldLabels.headers(List.of("duration", "duration", "unknownFoo"), null));
+        assertEquals(List.of("通话总次数"),
+                FieldLabels.headers(List.of("callCount"), CallNightDTO.class));
+        assertTrue(FieldLabels.headers(List.of(), null).isEmpty());
+    }
+}

+ 61 - 0
ai-server/src/test/java/com/zsjz/ai/module/agent/artifact/ArtifactServiceTest.java

@@ -1,5 +1,6 @@
 package com.zsjz.ai.module.agent.artifact;
 
+import com.zsjz.ai.common.cache.GlobalCache;
 import com.zsjz.ai.common.constants.PathConst;
 import com.zsjz.ai.common.context.CaseContextHolder;
 import com.zsjz.ai.common.exception.ServerException;
@@ -100,6 +101,66 @@ class ArtifactServiceTest {
         assertEquals("", previewRows.get(2).get("姓名"), "null 单元格写成空单元格");
     }
 
+    @Test
+    @DisplayName("表头写字段中文名、行值仍按英文键取;文件名中文化而磁盘 id 保持 ASCII")
+    void exportUsesChineseHeaderAndFileName() throws IOException {
+        // 清掉表字段元数据快照,保证字典兜底口径与生成文件一致
+        GlobalCache.clearForTesting();
+
+        Map<String, Object> labeled = new LinkedHashMap<>();
+        labeled.put("key", "personName");
+        labeled.put("label", "本方姓名");
+        Map<String, Object> dict = new LinkedHashMap<>();
+        dict.put("key", "duration");
+        dict.put("label", "duration");
+        Map<String, Object> unknown = new LinkedHashMap<>();
+        unknown.put("key", "unknownFoo");
+        unknown.put("label", "unknownFoo");
+        Map<String, Object> row = new LinkedHashMap<>();
+        row.put("personName", "张三");
+        row.put("duration", 125);
+        row.put("unknownFoo", "x");
+
+        Map<String, Object> meta = inCase(() -> service.export("call_analysis",
+                List.of(labeled, dict, unknown), List.of(row)));
+        assertNotNull(meta);
+        String id = (String) meta.get("id");
+        Path file = track(id);
+        assertTrue(id.matches("call_analysis_\\d{14}_[0-9a-f]{8}\\.csv"), "磁盘名仍是 ASCII id: " + id);
+
+        String fileName = (String) meta.get("fileName");
+        assertTrue(fileName.startsWith("通话分析_"), "展示/下载名要有中文前缀: " + fileName);
+        assertEquals(List.of("本方姓名", "通话时长", "unknownFoo"), meta.get("columns"),
+                "元数据列名 = CSV 表头(中文优先,翻译不出来保留英文)");
+
+        String text = Files.readString(file, StandardCharsets.UTF_8);
+        assertTrue(text.contains("本方姓名,通话时长,unknownFoo"), "CSV 首行是中文表头: " + text);
+        assertTrue(text.contains("张三"), "单元格仍按英文键取值");
+
+        // 产物预览的列名同样来自表头(产物栏表格直接拿它当列头)
+        Map<String, Object> preview = inCase(() -> service.preview(id, 5));
+        assertEquals(List.of("本方姓名", "通话时长", "unknownFoo"), preview.get("columns"));
+    }
+
+    @Test
+    @DisplayName("老产物(英文表头)预览时现场翻成中文,行键同步换、文件本身不动")
+    @SuppressWarnings("unchecked")
+    void previewTranslatesLegacyEnglishHeader() throws IOException {
+        GlobalCache.clearForTesting();
+        Path dir = root(CASE_ID);
+        Files.createDirectories(dir);
+        Path file = dir.resolve("legacy_call_20260101000000_deadbeef.csv");
+        Files.writeString(file, "\uFEFFduration,fileName\n125,笔录.txt\n", StandardCharsets.UTF_8);
+        created.add(file);
+
+        Map<String, Object> preview = inCase(() -> service.preview(file.getFileName().toString(), 5));
+        assertEquals(List.of("通话时长", "文件名"), preview.get("columns"), "老文件的英文表头也翻成中文");
+        List<Map<String, Object>> rows = (List<Map<String, Object>>) preview.get("rows");
+        assertEquals("笔录.txt", rows.get(0).get("文件名"), "行键要跟着换,否则前端列头与数据对不上");
+        assertTrue(Files.readString(file, StandardCharsets.UTF_8).contains("duration,fileName"),
+                "预览只影响展示,磁盘文件不能被改写");
+    }
+
     @Test
     @DisplayName("无案件上下文:导出静默跳过并返回 null,不打断工具主流程")
     void exportWithoutCaseContextReturnsNull() {

+ 2 - 1
ai-server/src/test/java/com/zsjz/ai/module/agent/tools/CallAnalysisToolTest.java

@@ -116,7 +116,8 @@ class CallAnalysisToolTest {
         assertTrue(json.startsWith("{\"columns\""), json);
         assertFalse(json.startsWith("\""), json);
         assertFalse(json.contains("\\\""), json);
-        assertTrue(json.contains("{\"key\":\"personName\",\"label\":\"personName\"}"), json);
+        // key 保留英文(行数据按它取值),label 按 CallRecord 的字段注释翻成中文(CSV 表头同口径)
+        assertTrue(json.contains("{\"key\":\"personName\",\"label\":\"本方姓名\"}"), json);
         assertTrue(json.contains("\"personName\":\"张三\""), json);
         // LocalDateTime 走全局格式化,与 execute_sql 的单元格形态一致
         assertTrue(json.contains("\"callDateTime\":\"2024-01-31 22:30:00\""), json);

+ 14 - 1
ai-server/src/test/java/com/zsjz/ai/module/agent/tools/SqlAnalysisToolTest.java

@@ -21,6 +21,7 @@ import io.agentscope.core.tool.Toolkit;
 import org.apache.ibatis.builder.MapperBuilderAssistant;
 import org.apache.ibatis.executor.result.DefaultResultContext;
 import org.apache.ibatis.session.ResultHandler;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
@@ -68,6 +69,12 @@ class SqlAnalysisToolTest {
     private final TableInfoMapper tableInfoMapper = mock(TableInfoMapper.class);
     private final TableFieldMapper tableFieldMapper = mock(TableFieldMapper.class);
 
+    /** 用例会往 GlobalCache 快照里塞表字段(验证列 label 链路),跑完清掉不污染别的测试 */
+    @org.junit.jupiter.api.AfterEach
+    void resetGlobalCache() {
+        com.zsjz.ai.common.cache.GlobalCache.clearForTesting();
+    }
+
     /** 产物配置:预览上限等由用例按需调整(行数保险丝默认 0 = 不限制) */
     private final ArtifactProperties artifactProps = new ArtifactProperties();
 
@@ -157,6 +164,11 @@ class SqlAnalysisToolTest {
     @Test
     @DisplayName("execute_sql 成功时返回干净 JSON,含 artifact / columns / rows / totalRows")
     void executeSqlReturnsCleanJson() {
+        // 列 label 走「表字段元数据」这条链:生产上 execute_sql 的列中文名正来自 table_field
+        TableField label = new TableField();
+        label.setFieldNameEn("personName");
+        label.setFieldNameCn("本方姓名");
+        com.zsjz.ai.common.cache.GlobalCache.loadSnapshotForTesting(Map.of(), Map.of(1, List.of(label)));
         LinkedHashMap<String, Object> row = new LinkedHashMap<>();
         row.put("personName", "张三");
         row.put("callCount", 5);
@@ -169,7 +181,8 @@ class SqlAnalysisToolTest {
         assertTrue(json.startsWith("{\"columns\":"), json);
         assertFalse(json.startsWith("\""), json);
         assertFalse(json.contains("\\\""), json);
-        assertTrue(json.contains("\"columns\":[{\"key\":\"personName\",\"label\":\"personName\"}"), json);
+        // key 保留英文(行数据按它取值),label 是字段中文名(CSV 表头同一套口径)
+        assertTrue(json.contains("\"columns\":[{\"key\":\"personName\",\"label\":\"本方姓名\"}"), json);
         assertTrue(json.contains("\"rows\":[{\"personName\":\"张三\",\"callCount\":\"5\"}]"), json);
         assertTrue(json.contains("\"totalRows\":1"), json);
     }