dsh-data-cleaning-agent 0.8.14 → 0.8.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.8.16] - 2026-09-08
8
+
9
+ ### Changed
10
+ - 表格提示词向导展示完整解析清单与空白单元格数,并与右侧工作台统一原列映射、人工候选确认及补全范围。
11
+ - 映射用绿色(自动通过/已确认)、琥珀色(待人工确认)、红色(未匹配)和文字共同标识;已映射字段自动进入补全范围,额外字段选择默认折叠。
12
+ - 允许多个原列经确认映射同一非主体字段,只补空白、保留非空原值;企业名称、信用代码、注册号各保留唯一匹配主列。
13
+
14
+ ### Fixed
15
+ - 修复 Host 草稿回传将新映射的补全范围重置为默认五字段的问题。
16
+ - 生成任务说明前检查前后端协议能力,旧 Host 配新页面时显示中文升级与安全重启指引;不伪造额度确认,不提前执行查询。
17
+
18
+ ## [0.8.15] - 2026-09-08
19
+
20
+ - 改善搜索元数据、安装说明、能力边界与四产品互链;无运行时或依赖变更。
21
+
7
22
  ## [0.8.14] - 2026-09-07
8
23
 
9
24
  ### Changed
package/README.en.md CHANGED
@@ -1,10 +1,16 @@
1
1
  # dsh-data-cleaning-agent
2
2
 
3
+ Data cleaning and data enrichment for CSV/XLSX/JSON enterprise lists in DeepSeek Harness, including spreadsheet cleaning, deduplication, profiling, optional Qichacha MCP and exports.
4
+
5
+ Install a pinned release using the command in the [Chinese quick start](README.md), review the preview or evidence before confirming, and keep the previous version and task-directory backup for rollback. Qichacha calls use the customer's authorized account.
6
+
7
+ Related agents: [数据清洗补全](https://github.com/duhu2000/dsh-data-cleaning-agent) · [AI填表](https://github.com/duhu2000/dsh-form-fill-agent) · [访前尽调](https://github.com/duhu2000/dsh-pre-duediligence) · [招投标](https://github.com/duhu2000/dsh-tender-workbench)
8
+
3
9
  > A data cleaning & completion agent plugin for DeepSeek Harness: local CSV/XLSX/JSON engine plus optional Qichacha (QCC) MCP enterprise-data enrichment. Initiated and maintained by the Qichacha (QCC) team.
4
10
  >
5
- > Current source version / 当前源码版本: **0.8.14** (stable release)
11
+ > Current source version / 当前源码版本: **0.8.16** (stable release)
6
12
 
7
- This release fixes misleading import errors after a successful upload. Files are parsed automatically, with persistent source, filename, and row-count status; text parsing handles only newly pasted content. The same file can be selected again, failed parsing preserves existing data, and pending pasted content must be parsed or cleared before continuing. Automatic mapping for 128 fields, blank-cell enrichment in original columns, QCC-blue menus, the resizable workbench, and native DSH sending remain unchanged. Existing download artifacts are not rewritten.
13
+ This release unifies spreadsheet previews, column mapping, and enrichment scope across the prompt wizard and workbench. Mapping statuses use color and text; mapped fields automatically enter the scope, with optional extra fields collapsed. Multiple confirmed output columns may share a non-identity field while preserving non-empty values. It fixes saved drafts resetting the scope to five defaults and detects stale Host versions. Preparing a draft does not query QCC; sending starts execution. Fully restart DSH after upgrading. Existing download artifacts are not rewritten.
8
14
 
9
15
  [![CI](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml/badge.svg)](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml)
10
16
  [![npm](https://img.shields.io/npm/v/dsh-data-cleaning-agent)](https://www.npmjs.com/package/dsh-data-cleaning-agent)
package/README.md CHANGED
@@ -1,10 +1,33 @@
1
1
  # dsh-data-cleaning-agent
2
2
 
3
+ 本版统一提示词向导与右侧工作台的原表解析、字段映射和补全范围;发布记录见 [发布说明](docs/RELEASE-0.8.16.md)。
4
+
5
+ ## 安装与三分钟上手
6
+
7
+ 数据清洗补全智能体:面向 Excel/CSV/JSON 企业名单,提供数据清洗、表格清洗、清洗补全、去重、企业数据补全与字段补全,支持企查查 MCP 和结果导出。
8
+
9
+ ```sh
10
+ dsh plugin --profile web add dsh-data-cleaning-agent@0.8.16
11
+ ```
12
+
13
+ 请先满足下文的 DSH、连接器及侧边栏依赖要求;安装后完整停止并重启对应 Profile。
14
+
15
+ 上传一份合成 CSV(如表头“姓名,手机号,金额”,两行重复的“演示用户,13800000000,100”),选择去重并检查结果,再导出新文件。企业字段补全另需已授权的企查查 MCP;先用本地清洗体验流程。
16
+
17
+ **流程样例(示意,非真实调用结果):** 输入两条相同合成记录 → 选择去重规则 → 预览一条保留记录 → 导出结果。
18
+
19
+ **能力边界:** 本地清洗与企业数据补全是不同步骤;外部事实来自用户授权的数据源,无法确定的主体或字段交给人工复核,不编造企业事实。
20
+
21
+ **升级与回滚:** 升级前停止 Profile 并备份任务目录,记录当前精确版本;使用上面的固定版本命令升级,再完整重启。回滚时将版本号替换为升级前记录的版本,并使用升级前任务目录副本;不以旧版直接读取已迁移任务目录。
22
+
23
+ 相关智能体:[数据清洗补全](https://github.com/duhu2000/dsh-data-cleaning-agent) · [AI填表](https://github.com/duhu2000/dsh-form-fill-agent) · [访前尽调](https://github.com/duhu2000/dsh-pre-duediligence) · [招投标](https://github.com/duhu2000/dsh-tender-workbench)
24
+
25
+
3
26
  > 在 DeepSeek Harness 中清洗、补全、画像企业名单数据的智能体插件:本地 CSV/XLSX/JSON 引擎 + 可选企查查(Qichacha/QCC)MCP 企业数据补全,由企查查(Qichacha/QCC)团队发起并维护。
4
27
  >
5
- > 当前源码版本 / Current source version: **0.8.14**(正式版本)
28
+ > 当前源码版本 / Current source version: **0.8.16**(正式版本)
6
29
 
7
- 本版修复文件上传成功后的导入状态误报:文件选择后自动解析,当前来源、文件名和行数持续显示;文本解析仅处理新粘贴内容。支持重新选择同一文件,解析失败保留现有数据,新粘贴内容解析或清空后再进入下一步。保留 128 字段智能映射、原列补空、企查查蓝菜单、可调宽工作台及 DSH 原生发送机制。历史下载文件不自动改写。
30
+ 本版上传表格后展示完整清单及空白数量,用绿色、琥珀色、红色及文字区分已通过、待确认和未匹配映射。已映射字段自动纳入补全范围,额外字段默认折叠;多个原列可经确认使用同一非主体字段,分别补空并保留非空原值。修复 Host 草稿重置为默认五字段的问题,增加前后端版本错配提示。生成说明不查询,发送才执行;升级后需完整重启 DSH,历史下载文件不自动改写。
8
31
 
9
32
  [![CI](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml/badge.svg)](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml)
10
33
  [![npm](https://img.shields.io/npm/v/dsh-data-cleaning-agent)](https://www.npmjs.com/package/dsh-data-cleaning-agent)
@@ -0,0 +1,24 @@
1
+ # dsh-data-cleaning-agent v0.8.16
2
+
3
+ ## 本版改进
4
+
5
+ - 提示词向导与右侧工作台统一:上传显示完整分页清单及空白数量,确认原列映射后自动形成补全范围。
6
+ - 映射状态同时提供颜色和文字:绿色已通过、琥珀色待人工确认、红色未匹配。模糊候选不自动当作已确认。
7
+ - 多个原列可确认使用同一非主体字段;查询字段去重,分别补空,保留非空值、0 与 false。企业名称、信用代码、注册号各保留唯一匹配主列。
8
+ - 已映射字段无需重复选择;额外字段目录默认折叠。修复 Host 草稿把新范围覆盖成默认五字段的问题。
9
+ - 草稿生成前检查 Host 协议能力,发现新页面配旧进程时提供中文升级提示;生成不查询,发送才执行。
10
+
11
+ ## 验收与边界
12
+
13
+ 233 项自动化测试通过,深浅色 × 5 种视口的 10 组隔离 React/Chromium 回归通过。使用合成数据,不以此声称已完成真实 QCC 查询或本机安装验收。详见 [映射验收](TEMPLATE-MAPPING-ACCEPTANCE.md)。
14
+
15
+ 本版只改数据清洗补全仓库,不改 AI 填表及共享字段契约包版本。未知或不支持的字段保留原列,不猜测填充。
16
+
17
+ ## 升级与回滚
18
+
19
+ 1. 保存或导出现有任务,备份任务目录,并记录现有精确版本。
20
+ 2. 停止对应 DSH Profile,安装:`dsh plugin --profile web add dsh-data-cleaning-agent@0.8.16`。
21
+ 3. 完整重启 DSH 并刷新页面;仅刷新浏览器不足以更新旧内存 Host。
22
+ 4. 回滚可安装 `dsh-data-cleaning-agent@0.8.15` 并恢复升级前任务副本。旧版不支持新增多列共享字段映射,不能直接用旧版编辑新版多列映射任务。
23
+
24
+ 历史导出文件不会被自动改写。
@@ -0,0 +1,29 @@
1
+ # 原表补全与统一映射验收(v0.8.16)
2
+
3
+ 本次范围:数据清洗补全的提示词向导与右侧工作台;未修改 AI 填表或共享内核仓库。
4
+
5
+ ## 交互与数据规则
6
+
7
+ - 表格上传后展示完整分页清单、列数、空白单元格数,并提供原始清单下载。
8
+ - 标准表头及明确别名自动通过(绿色);歧义与重复列给出候选(琥珀色),人工选择后转绿色;未知字段为未匹配(红色),原列保留。颜色同时配有文字。
9
+ - 已确认映射自动构成补全范围,不再要求重复勾选默认五字段。额外字段目录默认折叠,只用于新增原表之外的字段。
10
+ - 多个原列可人工确认映射同一非主体字段。查询字段去重,结果分别补空,保留非空原值及数值 0、布尔值 false。
11
+ - 企业名称、统一社会信用代码、注册号分别只允许一个匹配主列;一个原列不能同时映射多个字段。
12
+ - 映射阶段只匹配字段语义,不代表企业主体已匹配或字段数据已查询。没有当前目录支持的字段不猜测、不替换为近似口径。
13
+ - 最终任务说明包含原列映射、补空策略、未映射列及额外新增字段。用户仍可在中央对话框编辑,发送才执行查询。
14
+
15
+ ## 回归证据
16
+
17
+ 使用与反馈表头同结构的合成测试数据,不复制客户工作簿到仓库、不调用真实 QCC:
18
+
19
+ - 23 列、1 行、22 个空白单元格:9 列自动通过;确认重复列、企业状态、行业候选后,15 列映射至 13 个不同输出字段,8 列未匹配保留。
20
+ - 提示词回填后,Host 保存 15 个映射及 13 个字段,不被旧草稿默认五字段覆盖。
21
+ - CSV/XLSX 结果及异常清单覆盖多个原列共享同一补全字段、原值保护与异常行保护。
22
+ - 自动化检查覆盖契约、Host 路由、草稿不需要费用勾选、实际执行边界,以及深浅色 × 5 种视口的真实 React/Chromium 交互。
23
+ - 浏览器验证旧 Host 能力缺失时显示中文版本错配提示,且不创建或执行 QCC 命令。
24
+
25
+ ## 本机升级注意
26
+
27
+ 本次排查发现正在运行的 DSH 进程启动时间早于插件磁盘更新,可能出现新页面配旧内存 Host。新增能力标记 `draftPreparationVersion: 2`、`multipleOutputColumns: true` 用于提前识别这一情况。
28
+
29
+ 发布安装后需先保存或导出现有任务,再重启 DSH 并刷新页面。仅刷新浏览器不能替换内存中的旧 Host。开发验收未安装或重启本机 DSH,也未改动用户源文件。
package/lib/client.js CHANGED
@@ -949,6 +949,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
949
949
  .dcAgentMappingPicker input, .dcAgentMappingPicker select { width: 100%; margin-top: 8px; max-width: 100%; min-width: 0; }
950
950
  .dcAgentMappingRow .dcAgentButton { max-width: 100%; white-space: normal; overflow-wrap: anywhere; }
951
951
  .dcAgentMappingRow small { display: block; margin: 4px 0; overflow-wrap: anywhere; }
952
+ .dcAgentMappingStatus[data-status="confirmed"] { color: light-dark(#15803d, #86efac); }
953
+ .dcAgentMappingStatus[data-status="review"] { color: light-dark(#a16207, #fcd34d); }
954
+ .dcAgentMappingStatus[data-status="unmatched"] { color: light-dark(#b91c1c, #fca5a5); }
952
955
  .dcAgentRulesGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
953
956
  .dcAgentFieldGroup { display: grid; gap: 7px; margin-top: 12px; }
954
957
  .dcAgentFieldGroupHead { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
@@ -1403,6 +1406,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1403
1406
  }
1404
1407
  lines.push(`清洗要求:${cleanLabels.length ? cleanLabels.join('、') : '仅解析,不自动修改'}。`);
1405
1408
  lines.push(`需要补全的字段 / 维度:${enrichLabels.length ? enrichLabels.join('、') : '不调用外部补全,仅输出本地清洗结果'}。`);
1409
+ if (mode === 'excel' && config.headers?.length) {
1410
+ const pairs = config.mappings || [];
1411
+ lines.push(`原表共 ${config.headers.length} 列。确认写回:${pairs.map((item) => `「${item.sourceField}」←${resultFieldLabel(item.targetField)}`).join(';') || '暂无'}。`);
1412
+ const skipped = config.headers.filter((header) => !pairs.some((item) => item.sourceField === header));
1413
+ if (skipped.length) lines.push(`暂不补全的原列:${skipped.join('、')};保留原值,不猜测或用相近指标替代。`);
1414
+ const added = (config.enrichmentKeys || []).filter((id) => !pairs.some((item) => item.targetField === id));
1415
+ lines.push(`仅补已确认原列的空白单元格,保留非空值(含 0)。多个原列可共用一个补全字段,查询按主体与工具去重。额外新增字段:${added.map(resultFieldLabel).join('、') || '无'}。`);
1416
+ }
1406
1417
  lines.push(`处理规则:优先使用${anchorLabels.length ? anchorLabels.join('、') : '企业名称、统一社会信用代码或注册号'}精确匹配;${config.matchRules?.manualReviewAmbiguous === false ? '精确匹配失败时直接输出未匹配记录' : '精确匹配失败时再进入模糊候选,存在多个候选必须暂停并让我确认,不得默认选择第一项'}。`);
1407
1418
  lines.push('请先生成数据质量体检与匹配摘要,经我确认后再执行需要消耗额度的企查查 MCP 调用;企查查连接、套餐额度和费用均由当前用户自己的账号承担。缺失或无权限字段请留空并标记原因,不得编造。');
1408
1419
  lines.push('完成后保留来源原值、标准主体、匹配状态与字段来源,并提供结果和待复核清单的导出。');
@@ -1463,6 +1474,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1463
1474
  企业地址: ['registered_address', 'mailing_address'], 公司地址: ['registered_address', 'mailing_address'],
1464
1475
  网址: ['contact_official_website'], website: ['contact_official_website'],
1465
1476
  开业时间: ['establish_date'], 开业日期: ['establish_date'],
1477
+ 企业状态: ['reg_status'], 所属行业: ['industry_category', 'qcc_industry'],
1466
1478
  行业: ['industry_category', 'qcc_industry'],
1467
1479
  法人: ['legal_rep'], 邮箱: ['contact_preferred_email'], email: ['contact_preferred_email'],
1468
1480
  };
@@ -1471,7 +1483,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1471
1483
  }
1472
1484
  function mappingRecommendations(headers, fields = [...ENRICHMENT_OPTIONS, ...INPUT_ONLY_MAPPING_OPTIONS]) {
1473
1485
  const rows = (Array.isArray(headers) ? headers : []).map((sourceField) => {
1474
- const label = normalizeMappingLabel(String(sourceField).replace(/(重复列 \d+)$/, ''));
1486
+ const label = normalizeMappingLabel(String(sourceField).replace(/(重复列 \d+)$/, '').replace(/\s*YYYY[-/]MM[-/]DD\s*$/i, ''));
1475
1487
  let candidates = fields.filter(([id, name]) => [id, name].some((value) => normalizeMappingLabel(value) === label));
1476
1488
  let reason = '标准字段名匹配';
1477
1489
  if (!candidates.length) {
@@ -1506,10 +1518,23 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1506
1518
  return output;
1507
1519
  }
1508
1520
 
1521
+ const isAnchorField = (id) => ['company_name', 'credit_no', 'reg_no'].includes(id);
1522
+ function mappedOutputFields(mappings) {
1523
+ return [...new Set(mappings.map((item) => item.targetField))].filter((id) => ENRICHMENT_OPTIONS.some(([key]) => key === id));
1524
+ }
1525
+ function syncMappedSelection(previous, next, selected) {
1526
+ const old = new Set(mappedOutputFields(previous));
1527
+ return [...new Set([...selected.filter((id) => !old.has(id)), ...mappedOutputFields(next)])];
1528
+ }
1529
+ function updateColumnMapping(mappings, sourceField, targetField) {
1530
+ if (isAnchorField(targetField) && mappings.some((item) => item.sourceField !== sourceField && item.targetField === targetField)) return mappings;
1531
+ return [...mappings.filter((item) => item.sourceField !== sourceField), ...(targetField ? [{ sourceField, targetField }] : [])];
1532
+ }
1533
+
1509
1534
  function MappingPicker({ sourceField, mappings, groups, recommendation, onChange }) {
1510
1535
  const [query, setQuery] = react.useState('');
1511
1536
  const value = mappings.find((item) => item.sourceField === sourceField)?.targetField || '';
1512
- const occupied = (id) => mappings.some((item) => item.sourceField !== sourceField && item.targetField === id);
1537
+ const occupied = (id) => isAnchorField(id) && mappings.some((item) => item.sourceField !== sourceField && item.targetField === id);
1513
1538
  const fields = groups.flatMap(([, , items]) => items);
1514
1539
  const label = fields.find(([id]) => id === value)?.[1] || '不参与匹配 / 补全';
1515
1540
  const matches = (id, name) => normalizeMappingLabel([id, name, ...(MAPPING_ALIASES[id] || [])].join(' ')).includes(normalizeMappingLabel(query));
@@ -1535,7 +1560,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1535
1560
  ),
1536
1561
  query && !fields.some(([id, name]) => matches(id, name)) ? h('p', { className: 'dcAgentHint', role: 'status' }, '无匹配字段;当前选择保持不变。') : null,
1537
1562
  ),
1538
- h('small', { className: 'dcAgentHint' }, value ? `已映射:${label};可手动调整` : recommendation.reason),
1563
+ h('small', { className: 'dcAgentMappingStatus', 'data-status': value ? 'confirmed' : recommendation.candidates.length ? 'review' : 'unmatched',
1564
+ style: { display: 'block', padding: '6px 0' } },
1565
+ value ? `${recommendation.automatic && recommendation.candidates[0]?.[0] === value ? '自动通过' : '已确认'}:${label};可手动调整` : recommendation.candidates.length ? `待人工确认:${recommendation.conflict ? '重复列可确认使用同一补全字段;主体标识只选一列' : recommendation.reason}` : '未匹配:保留原列,可手动选择字段'),
1539
1566
  !value && recommendation.candidates.length ? h('div', { className: 'dcAgentRow', 'aria-label': `${sourceField} 推荐映射` }, recommendation.candidates.map(([id, name]) =>
1540
1567
  h('button', { key: id, type: 'button', className: 'dcAgentButton', disabled: occupied(id), onClick: () => onChange(sourceField, id) }, `推荐:${name}${occupied(id) ? '(已占用)' : ''}`))) : null,
1541
1568
  ),
@@ -1546,8 +1573,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1546
1573
  const sourceHeaders = [...new Set(headers)];
1547
1574
  const selected = [...new Set(fieldSelection)];
1548
1575
  const pairs = mappings.filter((item) => sourceHeaders.includes(item.sourceField) && selected.includes(item.targetField)
1549
- && mappings.filter((other) => other.sourceField === item.sourceField).length === 1
1550
- && mappings.filter((other) => other.targetField === item.targetField).length === 1);
1576
+ && mappings.filter((other) => other.sourceField === item.sourceField).length === 1);
1551
1577
  const hidden = new Set(pairs.filter((item) => item.sourceField !== item.targetField && !sourceHeaders.includes(item.targetField)).map((item) => item.targetField));
1552
1578
  const blank = (value) => value == null || (typeof value === 'string' && value.trim() === '');
1553
1579
  const output = rows.map((row) => {
@@ -1639,8 +1665,8 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1639
1665
  actions.setWorkflowTask(task);
1640
1666
  if (task.title) actions.setTaskTitle(task.title);
1641
1667
  if (Array.isArray(task.objectives)) actions.setObjectives(task.objectives);
1668
+ if (Array.isArray(task.mappings)) actions.setMappings(task.mappings);
1642
1669
  if (Array.isArray(task.fieldSelection)) actions.setFieldSelection(task.fieldSelection);
1643
- if (Array.isArray(task.mappings) && task.mappings.length) actions.setMappings(task.mappings);
1644
1670
  return task;
1645
1671
  }
1646
1672
 
@@ -1776,11 +1802,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
1776
1802
  setWorkflowTask: (draft, value) => { draft.workflowTask = value ?? null; },
1777
1803
  setWorkflowTasks: (draft, value) => { draft.workflowTasks = Array.isArray(value) ? value : []; },
1778
1804
  setTaskTitle: (draft, value) => { draft.taskTitle = String(value ?? ''); },
1779
- setMappings: (draft, value) => { draft.mappings = Array.isArray(value) ? value : []; },
1805
+ setMappings: (draft, value) => {
1806
+ const next = Array.isArray(value) ? value : [];
1807
+ draft.fieldSelection = syncMappedSelection(draft.mappings, next, draft.fieldSelection);
1808
+ draft.mappings = next;
1809
+ },
1780
1810
  setMapping: (draft, sourceField, targetField) => {
1781
- if (targetField && draft.mappings.some((item) => item.sourceField !== sourceField && item.targetField === targetField)) return;
1782
- const mappings = draft.mappings.filter((item) => item.sourceField !== sourceField);
1783
- if (targetField) mappings.push({ sourceField, targetField });
1811
+ const mappings = updateColumnMapping(draft.mappings, sourceField, targetField);
1812
+ draft.fieldSelection = syncMappedSelection(draft.mappings, mappings, draft.fieldSelection);
1784
1813
  draft.mappings = mappings;
1785
1814
  },
1786
1815
  setObjectives: (draft, value) => { draft.objectives = Array.isArray(value) ? value : []; },
@@ -2055,6 +2084,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2055
2084
  if (rootWorkbenchActions) {
2056
2085
  rootWorkbenchActions.setTaskTitle(draft.title);
2057
2086
  rootWorkbenchActions.setObjectives(draft.objectives);
2087
+ if (draft.mappings) rootWorkbenchActions.setMappings(draft.mappings);
2058
2088
  rootWorkbenchActions.setFieldSelection(draft.fieldSelection);
2059
2089
  }
2060
2090
  }
@@ -2456,6 +2486,8 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2456
2486
  const [entries, setEntries] = react.useState([]);
2457
2487
  const [entryCount, setEntryCount] = react.useState(0);
2458
2488
  const [spreadsheetFileName, setSpreadsheetFileName] = react.useState('');
2489
+ const [spreadsheetData, setSpreadsheetData] = react.useState(null);
2490
+ const [spreadsheetMappings, setSpreadsheetMappings] = react.useState([]);
2459
2491
  const [imageFileName, setImageFileName] = react.useState('');
2460
2492
  const [imageSizeBytes, setImageSizeBytes] = react.useState(0);
2461
2493
  const [imageAttachment, setImageAttachment] = react.useState(null);
@@ -2593,14 +2625,19 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2593
2625
  setEntries(extracted);
2594
2626
  setEntryCount(Number.isFinite(result.rowCount) ? result.rowCount : (Array.isArray(result.rows) ? result.rows.length : extracted.length));
2595
2627
  setSpreadsheetFileName(file.name);
2628
+ setSpreadsheetData(result);
2629
+ const suggested = guessMappings(result.headers || []);
2630
+ setSpreadsheetMappings(suggested);
2631
+ setEnrichmentKeys(mappedOutputFields(suggested));
2632
+ setAnchorKeys(suggested.filter((item) => isAnchorField(item.targetField)).map((item) => item.targetField));
2596
2633
  requestWorkbenchDataset(result, sessionId, {
2597
2634
  type: result.fmt || 'xlsx', fileName: file.name, sizeBytes: file.size || 0,
2598
2635
  });
2599
2636
  } catch (fileError) {
2637
+ setSpreadsheetData(null); setSpreadsheetMappings([]); setSpreadsheetFileName(''); setEntries([]); setEntryCount(0);
2600
2638
  setError(fileError instanceof Error ? fileError.message : String(fileError));
2601
2639
  } finally {
2602
2640
  setBusy(false);
2603
- if (event.target) event.target.value = '';
2604
2641
  }
2605
2642
  };
2606
2643
  const selectedEntries = () => mode === 'text'
@@ -2626,6 +2663,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2626
2663
  enrichmentKeys,
2627
2664
  anchorKeys,
2628
2665
  matchRules,
2666
+ ...(mode === 'excel' ? { headers: spreadsheetData?.headers || [], mappings: spreadsheetMappings } : {}),
2629
2667
  imageCommandId: imageCommand?.commandId ?? '',
2630
2668
  imageState: imageCommand?.state ?? null,
2631
2669
  });
@@ -2666,6 +2704,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2666
2704
  objectives: cleaningKeys,
2667
2705
  fieldSelection: enrichmentKeys,
2668
2706
  matchRules,
2707
+ ...(mode === 'excel' ? { mappings: spreadsheetMappings } : {}),
2669
2708
  source: { type: mode === 'excel' ? 'xlsx' : mode, fileName: config.fileName || '', rowCount: config.entryCount || 0 },
2670
2709
  }, sessionId);
2671
2710
  setError(null);
@@ -2737,6 +2776,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2737
2776
  event.preventDefault();
2738
2777
  discardCurrentImage();
2739
2778
  setMode('text'); setRawText(''); setEntries([]); setEntryCount(0); setSpreadsheetFileName('');
2779
+ setSpreadsheetData(null); setSpreadsheetMappings([]);
2740
2780
  setCleaningKeys(DEFAULT_CLEANING_KEYS); setEnrichmentKeys(DEFAULT_ENRICHMENT_KEYS);
2741
2781
  setAnchorKeys(['company_name', 'credit_no']);
2742
2782
  setPromptMatchRules({ normalizeNames: true, preferCreditNo: true, deduplicate: true, manualReviewAmbiguous: true });
@@ -2776,7 +2816,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2776
2816
  const groupIds = new Set(fields.map(([id]) => id));
2777
2817
  const selected = new Set(enrichmentKeys);
2778
2818
  for (const id of groupIds) {
2779
- if (enabledGroup) selected.add(id); else selected.delete(id);
2819
+ if (enabledGroup || (mode === 'excel' && mappedOutputFields(spreadsheetMappings).includes(id))) selected.add(id); else selected.delete(id);
2780
2820
  }
2781
2821
  const catalogOrder = FIELD_GROUPS.flatMap(([, , groupFields]) => groupFields.map(([id]) => id));
2782
2822
  setEnrichmentKeys(catalogOrder.filter((id) => selected.has(id)));
@@ -2884,11 +2924,30 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2884
2924
  ? h('b', null, `${mode === 'excel' ? spreadsheetFileName : imageFileName}${mode === 'excel' ? ` · ${entryCount} 条` : ' · 已附加'}`)
2885
2925
  : null,
2886
2926
  ),
2927
+ mode === 'excel' && spreadsheetData ? h(DatasetReview, {
2928
+ rows: spreadsheetData.rows || [], headers: spreadsheetData.headers || [],
2929
+ title: `已载入:${spreadsheetFileName} · ${entryCount} 条 / ${spreadsheetData.headers?.length || 0} 列`,
2930
+ expectedCount: entryCount, onError: setError,
2931
+ }) : null,
2932
+ mode === 'excel' && spreadsheetData ? h('p', { className: 'dcAgentHint' },
2933
+ `原表空白单元格:${(spreadsheetData.rows || []).reduce((n, row) => n + (spreadsheetData.headers || []).filter((key) => row[key] == null || String(row[key]).trim() === '').length, 0)} 个。下一步确认原列映射;文件已保存在本次任务中。`) : null,
2887
2934
  ) : null,
2888
2935
  wizardStep === 2 ? h('div', { className: 'dcAgentWizardPane' },
2889
2936
  h('h4', null, '确认匹配规则'),
2890
2937
  h('p', null, '强标识优先;名称定位不明确时生成候选,不自动写回。'),
2891
- h('div', { className: 'dcAgentPromptChoices' }, choice(MATCH_ANCHOR_OPTIONS, anchorKeys, setAnchorKeys)),
2938
+ mode === 'excel' ? h('div', null,
2939
+ h('p', { className: 'dcAgentHint' }, '绿色=自动通过 / 已确认;黄色=待人工确认;红色=未匹配。这里是原列与字段的映射,不是企业查询结果。确认映射即选入补全范围,无需再重复勾选。'),
2940
+ mappingRecommendations(spreadsheetData?.headers || []).map((recommendation) => h(MappingPicker, {
2941
+ key: recommendation.sourceField, sourceField: recommendation.sourceField, mappings: spreadsheetMappings,
2942
+ groups: [...FIELD_GROUPS, ['input-only', '输入校验字段', INPUT_ONLY_MAPPING_OPTIONS]], recommendation,
2943
+ onChange: (sourceField, targetField) => {
2944
+ const next = updateColumnMapping(spreadsheetMappings, sourceField, targetField);
2945
+ setEnrichmentKeys(syncMappedSelection(spreadsheetMappings, next, enrichmentKeys));
2946
+ setSpreadsheetMappings(next);
2947
+ setAnchorKeys(next.filter((item) => isAnchorField(item.targetField)).map((item) => item.targetField));
2948
+ },
2949
+ })),
2950
+ ) : h('div', { className: 'dcAgentPromptChoices' }, choice(MATCH_ANCHOR_OPTIONS, anchorKeys, setAnchorKeys)),
2892
2951
  h('div', { className: 'dcAgentPromptRules' },
2893
2952
  [['normalizeNames', '匹配前统一企业名称格式'], ['preferCreditNo', '有信用代码时优先精确匹配'], ['deduplicate', '匹配前合并重复主体'], ['manualReviewAmbiguous', '多候选必须人工确认']].map(([key, label]) => h('label', { key },
2894
2953
  h('input', { type: 'checkbox', checked: matchRules[key], onChange: (event) => setPromptMatchRules({ ...matchRules, [key]: event.target.checked }) }), label,
@@ -2897,11 +2956,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2897
2956
  ) : null,
2898
2957
  wizardStep === 3 ? h('div', { className: 'dcAgentWizardPane' },
2899
2958
  h('h4', null, '选择清洗目标和补全字段'),
2900
- h('p', null, '共 128 个一企一行字段:基础 30、第一批 40、第二批 58。最终可用字段以当前用户连接的 QCC MCP 能力为准。'),
2959
+ h('p', null, `共 ${ENRICHMENT_OPTIONS.length} 个一企一行字段。最终可用字段以当前用户连接的 QCC MCP 能力为准。`),
2960
+ mode === 'excel' ? h('p', { className: 'dcAgentHint' }, `已按确认映射选择 ${mappedOutputFields(spreadsheetMappings).length} 个原列补全字段,无需重复选择。以下仅用于追加原表之外的字段;默认只补空白、保留原值。`) : null,
2901
2961
  h('div', { className: 'dcAgentPromptGroup' },
2902
2962
  h('b', null, '清洗目标'),
2903
2963
  h('div', { className: 'dcAgentPromptChoices' }, choice(CLEANING_OPTIONS, cleaningKeys, setCleaningKeys)),
2904
2964
  ),
2965
+ h('details', { className: 'dcAgentExtraFields', open: mode !== 'excel' },
2966
+ h('summary', null, `可选:追加补全字段(共 ${ENRICHMENT_OPTIONS.length} 项)`),
2905
2967
  h('label', { className: 'dcAgentPromptField' },
2906
2968
  h('span', null, `查找补全字段 · 已选 ${enrichmentKeys.length}`),
2907
2969
  h('input', { type: 'search', className: 'dcAgentField', value: fieldQuery, 'aria-label': '查找补全字段',
@@ -2920,10 +2982,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
2920
2982
  ),
2921
2983
  h('div', { className: 'dcAgentPromptFieldGrid' }, visibleCatalogFields([groupId, label, fields, sourceTool], fieldQuery).map(([id, fieldLabel]) => h('label', {
2922
2984
  key: id, className: `dcAgentPromptChoice${enrichmentKeys.includes(id) ? ' is-selected' : ''}`,
2923
- }, h('input', { type: 'checkbox', checked: enrichmentKeys.includes(id),
2985
+ }, h('input', { type: 'checkbox', checked: enrichmentKeys.includes(id), disabled: mode === 'excel' && mappedOutputFields(spreadsheetMappings).includes(id),
2924
2986
  onChange: (event) => setEnrichmentKeys((current) => event.target.checked ? [...new Set([...current, id])] : current.filter((key) => key !== id)),
2925
2987
  }), fieldLabel))),
2926
2988
  )),
2989
+ ),
2927
2990
  ) : null,
2928
2991
  wizardStep === 4 ? h('div', { className: 'dcAgentWizardPane' },
2929
2992
  h('h4', null, '确认任务描述'),
@@ -3118,6 +3181,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3118
3181
  ?? 'name';
3119
3182
  actions.setNameField(guessedNameField);
3120
3183
  actions.setMappings(mappings);
3184
+ if (result.fmt !== 'text') actions.setFieldSelection(mappedOutputFields(mappings));
3121
3185
  actions.setDataset({
3122
3186
  fmt: result.fmt ?? 'csv',
3123
3187
  headers: runtime.headers,
@@ -3128,7 +3192,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3128
3192
  }
3129
3193
 
3130
3194
  async function persistParsedWorkflow(actions, sessionId, result, source = {}, task) {
3131
- let current = await ensureEditableWorkflowTask(actions, sessionId, {}, task);
3195
+ const importedMappings = guessMappings(result.headers || []);
3196
+ const importedDraft = { mappings: importedMappings,
3197
+ fieldSelection: result.fmt === 'text' ? DEFAULT_ENRICHMENT_KEYS : mappedOutputFields(importedMappings) };
3198
+ let current = await ensureEditableWorkflowTask(actions, sessionId, importedDraft, task);
3132
3199
  const stagingKey = `session:${sessionId || 'unassigned'}`;
3133
3200
  moveRuntime(stagingKey, current.id);
3134
3201
  // handleParse 可能先把新数据写入旧 task runtime。无论是否新建 taskId,
@@ -3154,6 +3221,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3154
3221
  },
3155
3222
  });
3156
3223
  }
3224
+ if (current.state === 'uploaded') current = await updateWorkflowTask(actions, sessionId, current, importedDraft);
3157
3225
  return current;
3158
3226
  }
3159
3227
 
@@ -3294,6 +3362,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3294
3362
  if (detail.sessionId) actions.setActiveSession(detail.sessionId);
3295
3363
  if (draft.title) actions.setTaskTitle(draft.title);
3296
3364
  if (draft.objectives) actions.setObjectives(draft.objectives);
3365
+ if (draft.mappings) actions.setMappings(draft.mappings);
3297
3366
  if (draft.fieldSelection) actions.setFieldSelection(draft.fieldSelection);
3298
3367
  for (const [key, value] of Object.entries(draft.matchRules || {})) actions.setMatchRule(key, value);
3299
3368
  void queueWorkflowOperation(detail.sessionId, async () => {
@@ -3616,6 +3685,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3616
3685
  if (!activeSessionId || typeof setSessionDraft !== 'function') {
3617
3686
  throw new Error('当前会话无法回填可编辑的数据清洗补全任务说明');
3618
3687
  }
3688
+ const host = await api('/data-cleaning/api/g5/capabilities');
3689
+ if (host?.draftPreparationVersion !== 2 || host?.multipleOutputColumns !== true) {
3690
+ throw new Error('页面与 Host 版本不一致。请先保存或导出现有任务,再升级并重启 DSH、刷新页面。生成任务说明不需要勾选费用确认。');
3691
+ }
3619
3692
  const prepared = await api('/data-cleaning/api/g5/commands', {
3620
3693
  ...payload,
3621
3694
  });
@@ -3900,7 +3973,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3900
3973
  const setWorkbenchFieldGroup = (fields, enabledGroup) => {
3901
3974
  const selected = new Set(fieldSelection);
3902
3975
  for (const [id] of fields) {
3903
- if (enabledGroup) selected.add(id); else selected.delete(id);
3976
+ if (enabledGroup || mappedOutputFields(mappings).includes(id)) selected.add(id); else selected.delete(id);
3904
3977
  }
3905
3978
  const catalogOrder = uiFieldGroups.flatMap(([, , groupFields]) => groupFields.map(([id]) => id));
3906
3979
  actions.setFieldSelection(catalogOrder.filter((id) => selected.has(id)));
@@ -3947,7 +4020,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3947
4020
  h('section', { className: 'dcAgentSection' },
3948
4021
  h('h3', null, '字段映射'),
3949
4022
  h('p', { className: 'dcAgentHint' }, '至少映射企业名称、统一社会信用代码或注册号其中一项。未映射列会原样保留,但不参与匹配。'),
3950
- h('p', { className: 'dcAgentHint' }, `已映射 ${mappings.length} / ${dataset?.headers?.length || 0} 列。标准字段名和明确别名自动匹配;多义表头、重复列请确认推荐。仅在本机匹配表头,不调用模型或企查查,不改变下方输出字段。`),
4023
+ h('p', { className: 'dcAgentHint' }, `已映射 ${mappings.length} / ${dataset?.headers?.length || 0} 列。绿色=通过;黄色=待确认;红色=未匹配。确认映射自动选入补全范围,不调用模型或企查查。多个原列可确认使用同一补全字段,主体匹配标识只选一列。`),
3951
4024
  h('button', { type: 'button', className: 'dcAgentButton', onClick: () => actions.setMappings(mergeRecommendedMappings(dataset?.headers || [], mappings, targetFields)) }, '补充自动映射(保留已有选择)'),
3952
4025
  mappingHints.map((recommendation, index) => h(MappingPicker, {
3953
4026
  key: `${index}:${recommendation.sourceField}`, sourceField: recommendation.sourceField,
@@ -3970,8 +4043,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3970
4043
  ),
3971
4044
  ),
3972
4045
  h('section', { className: 'dcAgentSection' },
3973
- h('h3', null, `输出字段 · 已选 ${fieldSelection.length}`),
4046
+ h('h3', null, `补全范围 · 已选 ${fieldSelection.length}(映射自动带入 ${mappedOutputFields(mappings).length})`),
4047
+ h('p', { className: 'dcAgentHint' }, '已映射字段无需重复勾选;如需取消,请调整上方映射。以下可选目录用于追加字段。'),
3974
4048
  h('p', { className: 'dcAgentHint' }, '补全结果优先填入已映射原列的空白单元格,保留非空原值;没有对应映射列的已选字段新增列。重复或多义列请先确认映射。'),
4049
+ h('details', { className: 'dcAgentExtraFields' },
4050
+ h('summary', null, `可选:追加补全字段(共 ${targetFields.filter(([id]) => id !== 'phone').length} 项)`),
3975
4051
  h('div', { className: 'dcAgentRow', 'aria-label': '补全字段批次概览' },
3976
4052
  h('span', { className: 'dcAgentChip' }, '基础字段 30'),
3977
4053
  h('span', { className: 'dcAgentChip' }, '第一批 40'),
@@ -3993,9 +4069,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
3993
4069
  ),
3994
4070
  ),
3995
4071
  h('div', { className: 'dcAgentChips' }, visibleCatalogFields([groupId, label, fields, sourceTool], fieldQuery).map(([key, fieldLabel]) => h('label', { key, className: `dcAgentChip${fieldSelection.includes(key) ? ' is-selected' : ''}` },
3996
- h('input', { type: 'checkbox', checked: fieldSelection.includes(key), onChange: () => actions.toggleField(key) }), ` ${fieldLabel}`,
4072
+ h('input', { type: 'checkbox', checked: fieldSelection.includes(key), disabled: mappedOutputFields(mappings).includes(key), onChange: () => actions.toggleField(key) }), ` ${fieldLabel}`,
3997
4073
  ))),
3998
4074
  )),
4075
+ ),
3999
4076
  ),
4000
4077
  h('div', { className: 'dcAgentRow' },
4001
4078
  h('button', { type: 'button', className: 'dcAgentButton', disabled: busy, onClick: saveTaskSettings }, '保存草稿'),
@@ -4418,6 +4495,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
4418
4495
  intakeState,
4419
4496
  projectCompletionResult,
4420
4497
  mappingRecommendations,
4498
+ mappedOutputFields,
4499
+ syncMappedSelection,
4500
+ updateColumnMapping,
4421
4501
  mergeRecommendedMappings,
4422
4502
  MappingPicker,
4423
4503
  deactivateCleaningSession,
package/lib/engine.js CHANGED
@@ -29,8 +29,7 @@ export function projectCompletionResult({ rows = [], headers = [], mappings = []
29
29
  const sourceHeaders = [...new Set(headers)];
30
30
  const selected = [...new Set(fieldSelection)];
31
31
  const pairs = mappings.filter((item) => sourceHeaders.includes(item.sourceField) && selected.includes(item.targetField)
32
- && mappings.filter((other) => other.sourceField === item.sourceField).length === 1
33
- && mappings.filter((other) => other.targetField === item.targetField).length === 1);
32
+ && mappings.filter((other) => other.sourceField === item.sourceField).length === 1);
34
33
  const hidden = new Set(pairs.filter((item) => item.sourceField !== item.targetField && !sourceHeaders.includes(item.targetField)).map((item) => item.targetField));
35
34
  const blank = (value) => value == null || (typeof value === 'string' && value.trim() === '');
36
35
  const output = rows.map((row) => {
package/lib/web.js CHANGED
@@ -617,6 +617,9 @@ export function mountWebRoutes(wctx, { logger, report, TOOL_NAME, SKILL_NAME })
617
617
  capabilities: qccBridge.capabilities(),
618
618
  limits: { maxRows: 100, maxConcurrency: 4 },
619
619
  paidCallConfirmationRequired: true,
620
+ draftPreparationVersion: 2,
621
+ draftPaidCallConfirmationRequired: false,
622
+ multipleOutputColumns: true,
620
623
  idempotencyRequired: true,
621
624
  candidateResume: true,
622
625
  manualRetry: true,
@@ -139,7 +139,7 @@ export function validateMappings(value) {
139
139
  const targets = new Set();
140
140
  const sources = new Set();
141
141
  for (const mapping of mappings) {
142
- if (targets.has(mapping.targetField)) {
142
+ if (MATCH_ANCHORS.includes(mapping.targetField) && targets.has(mapping.targetField)) {
143
143
  throw new WorkflowContractError('DC_WORKFLOW_DUPLICATE_MAPPING', `Duplicate target mapping: ${mapping.targetField}`);
144
144
  }
145
145
  if (sources.has(mapping.sourceField)) {
@@ -3,7 +3,7 @@
3
3
  "repository": "duhu2000/dsh-data-cleaning-agent",
4
4
  "packageName": "dsh-data-cleaning-agent",
5
5
  "npm": {
6
- "description": "Clean, complete, and profile enterprise name lists in DeepSeek Harness — a data cleaning & completion agent plugin with a local CSV/XLSX/JSON engine and optional Qichacha (QCC) MCP enrichment. Maintained by Qichacha/QCC.",
6
+ "description": "Data cleaning and data enrichment for CSV/XLSX/JSON enterprise lists in DeepSeek Harness, including spreadsheet cleaning, deduplication, profiling, optional Qichacha MCP and exports.",
7
7
  "requiredKeywords": [
8
8
  "deepseek-harness",
9
9
  "deepseek",
@@ -30,11 +30,26 @@
30
30
  "ai-tools",
31
31
  "llm-tools",
32
32
  "ai-agents",
33
- "agent-skills"
33
+ "agent-skills",
34
+ "data-enrichment",
35
+ "spreadsheet-cleaning",
36
+ "数据清洗补全",
37
+ "数据清洗",
38
+ "清洗补全",
39
+ "表格清洗",
40
+ "企业数据补全",
41
+ "字段补全",
42
+ "data cleaning",
43
+ "data enrichment",
44
+ "spreadsheet cleaning",
45
+ "数据 清洗 补全",
46
+ "数据清洗、补全",
47
+ "企查查MCP",
48
+ "企查查 MCP"
34
49
  ]
35
50
  },
36
51
  "github": {
37
- "description": "DeepSeek Harness 数据清洗补全智能体插件:清洗、补全、画像企业名单数据,本地 CSV/XLSX/JSON 引擎 + 可选企查查 MCP 企业数据补全。由企查查(Qichacha/QCC)团队发起维护。A data cleaning & completion agent plugin for DeepSeek Harness.",
52
+ "description": "Data cleaning and data enrichment for CSV/XLSX/JSON enterprise lists in DeepSeek Harness, including spreadsheet cleaning, deduplication, profiling, optional Qichacha MCP and exports.",
38
53
  "topics": [
39
54
  "deepseek-harness",
40
55
  "deepseek",
@@ -61,7 +76,9 @@
61
76
  "ai-tools",
62
77
  "llm-tools",
63
78
  "ai-agents",
64
- "agent-skills"
79
+ "agent-skills",
80
+ "data-enrichment",
81
+ "spreadsheet-cleaning"
65
82
  ]
66
83
  },
67
84
  "readme": {
@@ -71,8 +88,8 @@
71
88
  "ctaEn": "If the plugin helps you clean company lists faster, consider [starring the repository](https://github.com/duhu2000/dsh-data-cleaning-agent/stargazers), [filing an issue](https://github.com/duhu2000/dsh-data-cleaning-agent/issues), or [contributing a fix](CONTRIBUTING.md)."
72
89
  },
73
90
  "externalListing": {
74
- "en": "Data cleaning & completion agent plugin for DeepSeek Harness with a local CSV/XLSX/JSON engine (clean / complete / profile / deduplicate), built-in Skill, async job state machine, and optional Qichacha (QCC) MCP enterprise-data enrichment.",
75
- "zh": "DeepSeek Harness 数据清洗补全智能体插件:本地 CSV/XLSX/JSON 引擎(清洗/补全/画像/去重)、内嵌 Skill、异步任务状态机,可选企查查 MCP 企业数据补全。"
91
+ "en": "Data cleaning and data enrichment for CSV/XLSX/JSON enterprise lists in DeepSeek Harness, including spreadsheet cleaning, deduplication, profiling, optional Qichacha MCP and exports.",
92
+ "zh": "数据清洗补全智能体:面向 Excel/CSV/JSON 企业名单,提供数据清洗、表格清洗、清洗补全、去重、企业数据补全与字段补全,支持企查查 MCP 和结果导出。"
76
93
  },
77
94
  "links": {
78
95
  "repository": "https://github.com/duhu2000/dsh-data-cleaning-agent",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-data-cleaning-agent",
3
- "version": "0.8.14",
4
- "description": "Clean, complete, and profile enterprise name lists in DeepSeek Harness — a data cleaning & completion agent plugin with local CSV/XLSX/JSON engine and optional Qichacha (QCC) MCP enrichment. Maintained by Qichacha/QCC.",
3
+ "version": "0.8.16",
4
+ "description": "Data cleaning and data enrichment for CSV/XLSX/JSON enterprise lists in DeepSeek Harness, including spreadsheet cleaning, deduplication, profiling, optional Qichacha MCP and exports.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -38,6 +38,8 @@
38
38
  "docs/RELEASE-0.7.0.md",
39
39
  "docs/RELEASE-0.8.0.md",
40
40
  "docs/RELEASE-0.8.1.md",
41
+ "docs/RELEASE-0.8.16.md",
42
+ "docs/TEMPLATE-MAPPING-ACCEPTANCE.md",
41
43
  "docs/UI-WORKFLOW-V2.md",
42
44
  "docs/UI-WORKFLOW-V2-MIGRATION.md",
43
45
  "docs/UI-WORKFLOW-V2-ACCEPTANCE.md",
@@ -84,7 +86,22 @@
84
86
  "ai-tools",
85
87
  "llm-tools",
86
88
  "ai-agents",
87
- "agent-skills"
89
+ "agent-skills",
90
+ "数据清洗补全",
91
+ "数据清洗",
92
+ "清洗补全",
93
+ "表格清洗",
94
+ "企业数据补全",
95
+ "字段补全",
96
+ "data cleaning",
97
+ "data enrichment",
98
+ "spreadsheet cleaning",
99
+ "数据 清洗 补全",
100
+ "数据清洗、补全",
101
+ "企查查MCP",
102
+ "企查查 MCP",
103
+ "data-enrichment",
104
+ "spreadsheet-cleaning"
88
105
  ],
89
106
  "license": "MIT",
90
107
  "repository": {