dsh-data-cleaning-agent 0.8.18 → 0.8.20
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 +16 -0
- package/README.en.md +1 -1
- package/README.md +3 -3
- package/docs/RELEASE-0.8.20.md +11 -0
- package/lib/artifacts.js +1 -0
- package/lib/client.js +56 -17
- package/lib/qcc-command.js +2 -2
- package/lib/qcc.js +2 -1
- package/package.json +2 -2
- package/docs/RELEASE-0.8.18.md +0 -15
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.8.20] - 2026-09-09
|
|
8
|
+
|
|
9
|
+
- 普通无记录/未披露字段保留为无数据说明,尊重共享契约 reviewRequired=false,不再误计为待核验。
|
|
10
|
+
- 当前任务刷新同步任务历史,拒绝旧版本历史摘要覆盖新状态。
|
|
11
|
+
- 增加彩色主体处理进度条;结果计数未产生时显示统计中,避免 undefined。
|
|
12
|
+
- 当前任务和历史复用文件名、行数、创建时间生成的展示名;任务编号收纳至详情。
|
|
13
|
+
- 246 项测试与 10 组浏览器回归通过。旧任务结果不自动改写,不重复调用 QCC。
|
|
14
|
+
|
|
15
|
+
## [0.8.19] - 2026-09-09
|
|
16
|
+
|
|
17
|
+
- 受益人与主营业务收入增加待确认候选,明确首条限制及财务口径差异,不自动替换。
|
|
18
|
+
- 映射依次展示搜索、大类、可见字段列表;组合筛选保留已有映射。
|
|
19
|
+
- 区分去重主体处理数与输入行补全数,结果卡片和工作台提供原生预览入口。
|
|
20
|
+
- 零异常时不展示空异常清单;有待核验记录时保留下载。
|
|
21
|
+
- 245 项测试及 10 组浏览器布局回归通过;实际 DSH 宿主点击链路仍需升级后验收。
|
|
22
|
+
|
|
7
23
|
## [0.8.18] - 2026-09-09
|
|
8
24
|
|
|
9
25
|
- 新增受益所有人首条名称及首报告期营业总收入、利润总额、总资产,共 136 个字段;共享契约 0.2.0。
|
package/README.en.md
CHANGED
|
@@ -8,7 +8,7 @@ Related agents: [数据清洗补全](https://github.com/duhu2000/dsh-data-cleani
|
|
|
8
8
|
|
|
9
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.
|
|
10
10
|
>
|
|
11
|
-
> Current source version / 当前源码版本: **0.8.
|
|
11
|
+
> Current source version / 当前源码版本: **0.8.20** (stable release)
|
|
12
12
|
|
|
13
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.
|
|
14
14
|
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# dsh-data-cleaning-agent
|
|
2
2
|
|
|
3
|
-
本版统一向导与工作台的 Host 执行闭环:发送后自动生成新 Excel,原文件不修改,工作台入口可恢复;发布记录见 [发布说明](docs/RELEASE-0.8.
|
|
3
|
+
本版统一向导与工作台的 Host 执行闭环:发送后自动生成新 Excel,原文件不修改,工作台入口可恢复;发布记录见 [发布说明](docs/RELEASE-0.8.20.md)。
|
|
4
4
|
|
|
5
5
|
## 安装与三分钟上手
|
|
6
6
|
|
|
7
7
|
数据清洗补全智能体:面向 Excel/CSV/JSON 企业名单,提供数据清洗、表格清洗、清洗补全、去重、企业数据补全与字段补全,支持企查查 MCP 和结果导出。
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
dsh plugin --profile web add dsh-data-cleaning-agent@0.8.
|
|
10
|
+
dsh plugin --profile web add dsh-data-cleaning-agent@0.8.20
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
请先满足下文的 DSH、连接器及侧边栏依赖要求;安装后完整停止并重启对应 Profile。
|
|
@@ -25,7 +25,7 @@ dsh plugin --profile web add dsh-data-cleaning-agent@0.8.18
|
|
|
25
25
|
|
|
26
26
|
> 在 DeepSeek Harness 中清洗、补全、画像企业名单数据的智能体插件:本地 CSV/XLSX/JSON 引擎 + 可选企查查(Qichacha/QCC)MCP 企业数据补全,由企查查(Qichacha/QCC)团队发起并维护。
|
|
27
27
|
>
|
|
28
|
-
> 当前源码版本 / Current source version: **0.8.
|
|
28
|
+
> 当前源码版本 / Current source version: **0.8.20**(正式版本)
|
|
29
29
|
|
|
30
30
|
本版上传表格后展示完整清单及空白数量,用绿色、琥珀色、红色及文字区分已通过、待确认和未匹配映射。已映射字段自动纳入补全范围,额外字段默认折叠;多个原列可经确认使用同一非主体字段,分别补空并保留非空原值。修复 Host 草稿重置为默认五字段的问题,增加前后端版本错配提示。生成说明不查询,发送才执行;升级后需完整重启 DSH,历史下载文件不自动改写。
|
|
31
31
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# 0.8.20 发布说明
|
|
2
|
+
|
|
3
|
+
修复未披露数据被误判为人工核验;保留未披露说明和空白值,不伪造数据。真正的多候选或无法判定的问题继续进入待核验。
|
|
4
|
+
|
|
5
|
+
当前页和历史列表同步最新任务状态。处理进度条区分处理中、完成、需核验和失败;100% 仅表示主体处理结束。摘要尚未生成时显示“结果统计中”。
|
|
6
|
+
|
|
7
|
+
任务展示名优先尊重自定义名称,否则由源文件名、行数和创建时间生成;当前页与历史一致。
|
|
8
|
+
|
|
9
|
+
验证:246 项自动测试、10 组浏览器回归。无付费查询,源文件未修改。
|
|
10
|
+
|
|
11
|
+
升级并完整重启 DSH 后生效;历史任务的旧异常文件和统计不会自动重算,不应仅为更新展示而重复查询。
|
package/lib/artifacts.js
CHANGED
package/lib/client.js
CHANGED
|
@@ -1497,7 +1497,8 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1497
1497
|
};
|
|
1498
1498
|
// 语义不完全等同:只推荐,绝不冒充精确匹配或发起调用。
|
|
1499
1499
|
const MAPPING_HINTS = {
|
|
1500
|
-
受益所有人: ['beneficial_owner_first_name'], 受益所有人名称: ['beneficial_owner_first_name'], UBO: ['beneficial_owner_first_name'],
|
|
1500
|
+
受益人: ['beneficial_owner_first_name'], 受益所有人: ['beneficial_owner_first_name'], 受益所有人名称: ['beneficial_owner_first_name'], UBO: ['beneficial_owner_first_name'],
|
|
1501
|
+
主营业务收入: ['financial_total_revenue'],
|
|
1501
1502
|
地址: ['registered_address', 'mailing_address', 'invoice_address'],
|
|
1502
1503
|
address: ['registered_address', 'mailing_address'],
|
|
1503
1504
|
企业地址: ['registered_address', 'mailing_address'], 公司地址: ['registered_address', 'mailing_address'],
|
|
@@ -1524,6 +1525,8 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1524
1525
|
const hints = Object.entries(MAPPING_HINTS).find(([key]) => normalizeMappingLabel(key) === label)?.[1] || [];
|
|
1525
1526
|
candidates = hints.flatMap((key) => fields.filter(([id]) => id === key));
|
|
1526
1527
|
reason = candidates.length ? '含义需确认,点击推荐后采用' : '未识别表头,请手动选择';
|
|
1528
|
+
if (candidates.length && label === normalizeMappingLabel('主营业务收入')) reason = '主营业务收入与营业总收入口径不同;确认接受该口径后采用';
|
|
1529
|
+
if (candidates.length && label === normalizeMappingLabel('受益人')) reason = '受益人含义需确认;仅取首条受益所有人,不代表唯一或最大受益人';
|
|
1527
1530
|
}
|
|
1528
1531
|
return { sourceField: String(sourceField), candidates, automatic: exact, reason };
|
|
1529
1532
|
});
|
|
@@ -1567,7 +1570,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1567
1570
|
const occupied = (id) => isAnchorField(id) && mappings.some((item) => item.sourceField !== sourceField && item.targetField === id);
|
|
1568
1571
|
const fields = groups.flatMap(([, , items]) => items);
|
|
1569
1572
|
const label = fields.find(([id]) => id === value)?.[1] || '不参与匹配 / 补全';
|
|
1570
|
-
const matches = (id, name) => normalizeMappingLabel([id, name, ...(MAPPING_ALIASES[id] || [])].join(' ')).includes(normalizeMappingLabel(query));
|
|
1573
|
+
const matches = (id, name) => normalizeMappingLabel([id, name, ...(MAPPING_ALIASES[id] || []), ...Object.entries(MAPPING_HINTS).filter(([, ids]) => ids.includes(id)).map(([hint]) => hint)].join(' ')).includes(normalizeMappingLabel(query));
|
|
1574
|
+
const filteredGroups = groups.filter(([id]) => !category || id === category)
|
|
1575
|
+
.map(([id, title, items]) => [id, title, items.filter(([key, name]) => matches(key, name))])
|
|
1576
|
+
.filter(([, , items]) => items.length);
|
|
1577
|
+
const filteredFields = filteredGroups.flatMap(([, , items]) => items);
|
|
1578
|
+
const hiddenSelection = value && !filteredFields.some(([id]) => id === value);
|
|
1571
1579
|
return h('div', { className: 'dcAgentMappingRow' },
|
|
1572
1580
|
h('span', { title: sourceField }, sourceField), h('b', { 'aria-hidden': 'true' }, '→'),
|
|
1573
1581
|
h('div', { style: { minWidth: 0 } },
|
|
@@ -1578,20 +1586,21 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1578
1586
|
}
|
|
1579
1587
|
} },
|
|
1580
1588
|
h('summary', { className: 'dcAgentField', 'aria-label': `${sourceField} 当前映射` }, label),
|
|
1589
|
+
h('input', { className: 'dcAgentField', value: query, placeholder: '模糊搜索字段名称 / 英文键 / 别名', 'aria-label': `${sourceField} 搜索映射字段`, onChange: (event) => setQuery(event.target.value) }),
|
|
1581
1590
|
h('select', { className: 'dcAgentField', value: category, 'aria-label': `${sourceField} 映射大类`, onChange: (event) => setCategory(event.target.value) },
|
|
1582
1591
|
h('option', { value: '' }, '全部维度'),
|
|
1583
1592
|
groups.map(([id, title]) => h('option', { key: id, value: id }, title))),
|
|
1584
|
-
h('
|
|
1585
|
-
h('select', { className: 'dcAgentField', value, 'aria-label': `${sourceField} 字段映射`, onChange: (event) => {
|
|
1593
|
+
h('select', { className: 'dcAgentField', size: 8, style: { height: 'auto', minHeight: '180px' }, value, 'aria-label': `${sourceField} 字段映射`, onChange: (event) => {
|
|
1586
1594
|
onChange(sourceField, event.target.value);
|
|
1587
1595
|
const details = event.currentTarget.closest('details');
|
|
1588
1596
|
if (details) { details.open = false; details.querySelector('summary')?.focus(); }
|
|
1589
1597
|
} },
|
|
1590
1598
|
h('option', { value: '' }, '不参与匹配 / 补全'),
|
|
1591
|
-
|
|
1599
|
+
hiddenSelection ? h('option', { value, disabled: true }, `当前选择:${label}(不符合筛选,保持不变)`) : null,
|
|
1600
|
+
filteredGroups.map(([id, title, items]) => h('optgroup', { key: id, label: title }, items.map(([key, name]) =>
|
|
1592
1601
|
h('option', { key, value: key, disabled: occupied(key) }, `${name}${occupied(key) ? '(已被其他列使用)' : ''}`)))),
|
|
1593
1602
|
),
|
|
1594
|
-
|
|
1603
|
+
h('p', { className: 'dcAgentHint', role: 'status' }, filteredFields.length ? `匹配 ${filteredFields.length} 个字段;选择后采用。` : '无匹配字段;当前选择保持不变。'),
|
|
1595
1604
|
),
|
|
1596
1605
|
value === 'beneficial_owner_first_name' || value.startsWith('financial_') ? h('p', { className: 'dcAgentHint' }, value === 'beneficial_owner_first_name' ? '仅取返回首条受益所有人,不代表唯一或最大受益人。' : '取返回首个报告期,不跨期补值;报告期保留在结果取值说明中。') : null,
|
|
1597
1606
|
h('small', { className: 'dcAgentMappingStatus', 'data-status': value ? 'confirmed' : recommendation.candidates.length ? 'review' : 'unmatched',
|
|
@@ -1694,6 +1703,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1694
1703
|
throw error;
|
|
1695
1704
|
}
|
|
1696
1705
|
|
|
1706
|
+
function workflowDisplayName(task) {
|
|
1707
|
+
if (task.title && !task.title.startsWith('企业数据清洗补全任务')) return task.title;
|
|
1708
|
+
const base = task.source?.fileName?.replace(/\.[^.]+$/, '') || (task.source?.type === 'image' ? '图片名单' : '粘贴名单');
|
|
1709
|
+
const date = task.createdAt ? new Date(task.createdAt) : null;
|
|
1710
|
+
const time = date && Number.isFinite(date.getTime()) ? date.toLocaleString() : '';
|
|
1711
|
+
return `${base}|${task.source?.rowCount ?? 0} 条${time ? '|' + time : ''}`;
|
|
1712
|
+
}
|
|
1697
1713
|
function cacheWorkflowTask(actions, sessionId, task) {
|
|
1698
1714
|
if (!task) return null;
|
|
1699
1715
|
if (sessionId) workflowTaskBySession.set(String(sessionId), task);
|
|
@@ -1910,8 +1926,15 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1910
1926
|
setInput: (draft, input) => { draft.input = input; },
|
|
1911
1927
|
setWorkflowContract: (draft, value) => { draft.workflowContract = value; },
|
|
1912
1928
|
setCommandProgress: (draft, value) => { draft.commandProgress = value; },
|
|
1913
|
-
setWorkflowTask: (draft, value) => {
|
|
1914
|
-
|
|
1929
|
+
setWorkflowTask: (draft, value) => {
|
|
1930
|
+
draft.workflowTask = value ?? null;
|
|
1931
|
+
if (value) draft.workflowTasks = [value, ...(draft.workflowTasks || []).filter(task => task.id !== value.id)];
|
|
1932
|
+
},
|
|
1933
|
+
setWorkflowTasks: (draft, value) => {
|
|
1934
|
+
const cached = new Map((draft.workflowTasks || []).map(task => [task.id, task]));
|
|
1935
|
+
draft.workflowTasks = (Array.isArray(value) ? value : []).map(task =>
|
|
1936
|
+
(cached.get(task.id)?.revision ?? -1) > (task.revision ?? -1) ? cached.get(task.id) : task);
|
|
1937
|
+
},
|
|
1915
1938
|
setTaskTitle: (draft, value) => { draft.taskTitle = String(value ?? ''); },
|
|
1916
1939
|
setMappings: (draft, value) => {
|
|
1917
1940
|
const next = Array.isArray(value) ? value : [];
|
|
@@ -2288,7 +2311,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2288
2311
|
const WORKFLOW_STATE_LABELS = {
|
|
2289
2312
|
draft: '待上传', uploaded: '待确认规则', rules_confirmed: '规则已确认', diagnosed: '体检完成',
|
|
2290
2313
|
matching: '匹配中', review_required: '待人工核验', matched: '匹配完成', enriching: '补全中',
|
|
2291
|
-
export_ready: '可下载', completed: '已完成', partial: '
|
|
2314
|
+
export_ready: '可下载', completed: '已完成', partial: '处理结束(需核验)', failed: '执行失败',
|
|
2292
2315
|
authorization_required: '待连接企查查', cancelled: '已取消', parse_failed: '解析失败',
|
|
2293
2316
|
};
|
|
2294
2317
|
|
|
@@ -3165,6 +3188,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3165
3188
|
|
|
3166
3189
|
/** 模型工具 tool.call.toolview 卡片元数据(wire 名 → 展示文案)。 */
|
|
3167
3190
|
const TOOL_VIEW_META = {
|
|
3191
|
+
data_cleaning_qcc_run: { icon: '🧩', label: '企查查清洗补全结果', hint: '已处理包含未匹配或失败;不等于已补全。' },
|
|
3168
3192
|
data_clean_rows: { icon: '🧹', label: '数据清洗', hint: '去重 · 剔除非法金额 · 缺失补 0' },
|
|
3169
3193
|
data_complete_rows: { icon: '🧩', label: '数据补全', hint: '名称 / 金额 / 手机号归一' },
|
|
3170
3194
|
data_profile: { icon: '🩺', label: '数据体检', hint: '缺失率 · 去重值 · 金额分布' },
|
|
@@ -3203,6 +3227,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3203
3227
|
),
|
|
3204
3228
|
meta.hint ? h('div', { className: 'dcAgentToolCardHint' }, meta.hint) : null,
|
|
3205
3229
|
summary ? h('div', { className: 'dcAgentToolCardBody' }, summary) : null,
|
|
3230
|
+
toolName === 'data_cleaning_qcc_run' ? h('div', { className: 'dcAgentArtifactList' },
|
|
3231
|
+
[...summary.matchAll(/\[([^\]]+)\]\((\/data-cleaning\/api\/workflow\/tasks\/[a-zA-Z0-9%_-]+\/artifacts\/[a-zA-Z0-9%_-]+(?:\?preview=1)?)\)/g)].map((match, index) =>
|
|
3232
|
+
h('a', { key: index, className: 'dcAgentButton', href: match[2], target: '_blank', rel: 'noopener noreferrer' }, match[1]))) : null,
|
|
3206
3233
|
);
|
|
3207
3234
|
}
|
|
3208
3235
|
|
|
@@ -4152,7 +4179,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4152
4179
|
),
|
|
4153
4180
|
workflowTasks.length ? h('div', { className: 'dcAgentPane', 'aria-label': '任务历史列表' },
|
|
4154
4181
|
workflowTasks.map((task) => h('button', { key: task.id, type: 'button', className: 'dcAgentHistoryTask', onClick: () => resumeWorkflowTask(task) },
|
|
4155
|
-
h('h3', null, task
|
|
4182
|
+
h('h3', null, workflowDisplayName(task)),
|
|
4156
4183
|
h('div', { className: 'dcAgentRow' },
|
|
4157
4184
|
h('span', { className: 'dcAgentJobsPill', 'data-state': task.state ?? 'idle' }, WORKFLOW_STATE_LABELS[task.state] ?? task.state ?? '未知'),
|
|
4158
4185
|
h('span', { className: 'dcAgentHint' }, `${task.source?.rowCount ?? 0} 行 · ${task.updatedAt ? new Date(task.updatedAt).toLocaleString() : ''}`),
|
|
@@ -4343,10 +4370,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4343
4370
|
h('div', { className: 'dcAgentRow' }, h('button', { type: 'button', className: 'dcAgentButton is-primary', onClick: () => actions.setStep('download') }, '进入下载数据')),
|
|
4344
4371
|
);
|
|
4345
4372
|
} else if (step === 'download') {
|
|
4346
|
-
const availableArtifacts = cachedTask?.artifacts || [];
|
|
4373
|
+
const availableArtifacts = (cachedTask?.artifacts || []).filter(artifact => artifact.kind !== 'review' || artifact.rowCount > 0);
|
|
4347
4374
|
const canCreateArtifacts = Boolean(exportRows()?.length) && ['rules_confirmed', 'diagnosed', 'export_ready', 'partial'].includes(cachedTask?.state);
|
|
4348
4375
|
pane = h('div', { className: 'dcAgentPane' },
|
|
4349
|
-
h('p', { className: 'dcAgentHint' }, '
|
|
4376
|
+
h('p', { className: 'dcAgentHint' }, '结果文件由 Host 保存;仅在有待核验记录时展示异常清单。任务或插件重启后,可从任务历史继续下载;无需再次调用企查查。'),
|
|
4350
4377
|
h('div', { className: 'dcAgentGrid' },
|
|
4351
4378
|
stat('输入行数', dataset ? dataset.rowCount : workflowTask?.source?.rowCount ?? '—'),
|
|
4352
4379
|
stat('匹配补全', qccEnrichedCount, qccEnrichedCount > 0 ? 'good' : null),
|
|
@@ -4354,6 +4381,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4354
4381
|
stat('任务状态', WORKFLOW_STATE_LABELS[cachedTask?.state] || cachedTask?.state || '进行中'),
|
|
4355
4382
|
),
|
|
4356
4383
|
availableArtifacts.length ? h('div', { className: 'dcAgentArtifactList' },
|
|
4384
|
+
availableArtifacts.filter(artifact => artifact.format === 'xlsx').map(artifact => h('a', {
|
|
4385
|
+
key: 'preview-' + artifact.id, className: 'dcAgentButton',
|
|
4386
|
+
href: `/data-cleaning/api/workflow/tasks/${encodeURIComponent(cachedTask.id)}/artifacts/${encodeURIComponent(artifact.id)}?preview=1`,
|
|
4387
|
+
target: '_blank', rel: 'noopener noreferrer',
|
|
4388
|
+
}, `预览 / 打开:${artifact.fileName}`)),
|
|
4357
4389
|
availableArtifacts.map((artifact) => h('button', {
|
|
4358
4390
|
key: artifact.id,
|
|
4359
4391
|
type: 'button',
|
|
@@ -4366,8 +4398,8 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4366
4398
|
h('div', { className: 'dcAgentRow' },
|
|
4367
4399
|
h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('complete', 'xlsx') }, '生成并下载结果 XLSX'),
|
|
4368
4400
|
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('complete', 'csv') }, '生成并下载结果 CSV'),
|
|
4369
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('review', 'xlsx') }, '生成并下载异常清单 XLSX'),
|
|
4370
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('review', 'csv') }, '生成并下载异常清单 CSV'),
|
|
4401
|
+
qccReviewCount > 0 ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('review', 'xlsx') }, '生成并下载异常清单 XLSX') : null,
|
|
4402
|
+
qccReviewCount > 0 ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('review', 'csv') }, '生成并下载异常清单 CSV') : null,
|
|
4371
4403
|
),
|
|
4372
4404
|
!canCreateArtifacts ? h('p', { className: 'dcAgentHint' }, '请先完成规则确认与清洗补全;历史任务若尚未生成制品,需要重新上传源文件。') : null,
|
|
4373
4405
|
),
|
|
@@ -4515,9 +4547,16 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4515
4547
|
h('div', { className: 'dcAgentWbBody' },
|
|
4516
4548
|
cachedTask && step !== 'history' ? h('section', { className: 'dcAgentSection', 'aria-label': '当前任务进展', 'aria-live': 'polite' },
|
|
4517
4549
|
h('b', null, WORKFLOW_STATE_LABELS[cachedTask.state] || cachedTask.state),
|
|
4518
|
-
h('
|
|
4550
|
+
h('h3', null, workflowDisplayName(cachedTask)),
|
|
4551
|
+
h('details', null, h('summary', null, '任务详情'), h('p', { className: 'dcAgentHint' }, `任务编号:${cachedTask.id}`)),
|
|
4519
4552
|
h('p', null, `${cachedTask.source?.type === 'image' ? '图片识别' : cachedTask.source?.type === 'text' ? '粘贴名单' : '上传表格'} · ${cachedTask.source?.fileName || ''} · ${cachedTask.source?.rowCount || 0} 条 · ${cachedTask.fieldSelection?.length || 0} 个补全字段`),
|
|
4520
|
-
commandProgress?.taskId === cachedTask.id && Number.isFinite(commandProgress.totalUnique) ? h('p', null, `已处理主体 ${commandProgress.completedUnique}/${commandProgress.totalUnique}
|
|
4553
|
+
commandProgress?.taskId === cachedTask.id && Number.isFinite(commandProgress.totalUnique) ? h('p', null, `已处理主体 ${commandProgress.completedUnique}/${commandProgress.totalUnique}(去重后,含未匹配或失败,不代表补全成功)`) : null,
|
|
4554
|
+
commandProgress?.taskId === cachedTask.id && Number.isFinite(commandProgress.totalUnique) && commandProgress.totalUnique > 0 ? h('progress', {
|
|
4555
|
+
max: commandProgress.totalUnique, value: Math.min(commandProgress.totalUnique, Math.max(0, commandProgress.completedUnique || 0)),
|
|
4556
|
+
'aria-label': '主体处理进度', style: { width: '100%', accentColor: cachedTask.state === 'completed' ? '#16845b' : cachedTask.state === 'failed' ? '#c62828' : cachedTask.state === 'partial' ? '#a66a00' : '#087dcc' },
|
|
4557
|
+
}) : null,
|
|
4558
|
+
h('p', { style: { color: Number(qccReviewCount) > 0 ? '#a66a00' : Number.isFinite(qccEnrichedCount) ? '#16845b' : 'inherit' } },
|
|
4559
|
+
Number.isFinite(qccEnrichedCount) ? `已处理并补全 ${qccEnrichedCount} 条 · 待核验 ${Number.isFinite(qccReviewCount) ? qccReviewCount : '统计中'} 条(按输入行统计;未披露字段保留空白)` : '结果统计中,处理结束后展示补全与待核验数量'),
|
|
4521
4560
|
) : null,
|
|
4522
4561
|
error ? h('div', { className: 'dcAgentError', role: 'alert' }, error) : null,
|
|
4523
4562
|
step !== 'history' ? h('details', { className: 'dcAgentSection' },
|
|
@@ -4612,7 +4651,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4612
4651
|
|
|
4613
4652
|
// 模型工具的 tool.call.toolview 富化卡片(keyed by wire name,替代裸 JSON 摘要)。
|
|
4614
4653
|
// 说明:分三条独立 inject(而非 generator)——测试 shim 对 inject 回调仅执行一次并 push 其返回值。
|
|
4615
|
-
const toolviewKeys = ['data_clean_rows', 'data_complete_rows', 'data_profile', 'data_cleaning_extract_image_companies'];
|
|
4654
|
+
const toolviewKeys = ['data_clean_rows', 'data_complete_rows', 'data_profile', 'data_cleaning_extract_image_companies', 'data_cleaning_qcc_run'];
|
|
4616
4655
|
for (const key of toolviewKeys) {
|
|
4617
4656
|
ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({
|
|
4618
4657
|
name: 'tool.call.toolview',
|
package/lib/qcc-command.js
CHANGED
|
@@ -265,7 +265,7 @@ export class QccCommandStore {
|
|
|
265
265
|
state: run.state,
|
|
266
266
|
summary: run.summary,
|
|
267
267
|
...(record.delivery ? { deliveryState: record.delivery.state, artifactCount: record.delivery.artifacts.length,
|
|
268
|
-
artifacts: record.delivery.artifacts.filter(a => a.format === 'xlsx').map(a => ({ fileName: a.fileName, url: `/data-cleaning/api/workflow/tasks/${encodeURIComponent(record.taskId)}/artifacts/${encodeURIComponent(a.id)}` })) } : {}),
|
|
268
|
+
artifacts: record.delivery.artifacts.filter(a => a.format === 'xlsx' && (a.kind !== 'review' || a.rowCount > 0)).map(a => ({ fileName: a.fileName, url: `/data-cleaning/api/workflow/tasks/${encodeURIComponent(record.taskId)}/artifacts/${encodeURIComponent(a.id)}` })) } : {}),
|
|
269
269
|
});
|
|
270
270
|
}
|
|
271
271
|
}
|
|
@@ -313,7 +313,7 @@ export function registerQccCommandTool(tools, commands) {
|
|
|
313
313
|
},
|
|
314
314
|
render: (_args, value) => [{
|
|
315
315
|
type: 'text',
|
|
316
|
-
text:
|
|
316
|
+
text: `处理结束:已处理 ${value.summary?.uniqueCompanies ?? 0} 个去重主体(含未匹配或失败);按输入行统计:${value.summary?.enriched ?? 0}/${value.summary?.totalRows ?? 0} 条已补全。未匹配 ${value.summary?.unresolved ?? 0} 条、失败 ${value.summary?.failed ?? 0} 条、多候选 ${value.summary?.ambiguous ?? 0} 条、字段待核验 ${value.summary?.fieldReview ?? 0} 条。不得仅凭补全为 0 推断授权异常;请依据实际错误记录。${value.artifactCount ? 'Host 已生成新的结果 XLSX,请返回工作台「结果下载」。源文件未修改,无需再次确认或提供路径。' : value.deliveryState === 'review_required' ? '存在多候选,请返回工作台确认主体。' : '请返回工作台查看任务状态。'}\n${(value.artifacts || []).map(a => `${String(a.fileName).replace(/[^a-zA-Z0-9\u4e00-\u9fff._ -]/g, '_')}:[预览 / 打开](${a.url}?preview=1) · [下载 Excel](${a.url})`).join('\n')}`,
|
|
317
317
|
}],
|
|
318
318
|
},
|
|
319
319
|
async execute(args, exec) {
|
package/lib/qcc.js
CHANGED
|
@@ -1070,7 +1070,8 @@ export class QccHostBridge {
|
|
|
1070
1070
|
qcc_match_status: 'enriched',
|
|
1071
1071
|
qcc_source: 'qcc-mcp',
|
|
1072
1072
|
...(result.snapshotNote ? { qcc_snapshot_note: result.snapshotNote } : {}),
|
|
1073
|
-
...(row.qcc_field_issues || result.fieldIssues ? { qcc_field_issues: Object.entries(result.fieldIssues || {}).map(([field, issue]) => field + ': ' + issue.message).join(';') } : {}),
|
|
1073
|
+
...(row.qcc_field_issues || result.fieldIssues ? { qcc_field_issues: Object.entries(result.fieldIssues || {}).filter(([, issue]) => issue.reviewRequired !== false).map(([field, issue]) => field + ': ' + issue.message).join(';') } : {}),
|
|
1074
|
+
...(result.fieldIssues ? { qcc_field_notes: Object.entries(result.fieldIssues).filter(([, issue]) => issue.reviewRequired === false).map(([field, issue]) => field + ': ' + issue.message).join(';') } : {}),
|
|
1074
1075
|
};
|
|
1075
1076
|
}
|
|
1076
1077
|
return { ...row, qcc_match_status: result?.status ?? 'failed' };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-data-cleaning-agent",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.20",
|
|
4
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",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"docs/RELEASE-0.8.1.md",
|
|
41
41
|
"docs/RELEASE-0.8.16.md",
|
|
42
42
|
"docs/RELEASE-0.8.17.md",
|
|
43
|
-
"docs/RELEASE-0.8.
|
|
43
|
+
"docs/RELEASE-0.8.20.md",
|
|
44
44
|
"docs/TEMPLATE-MAPPING-ACCEPTANCE.md",
|
|
45
45
|
"docs/UI-WORKFLOW-V2.md",
|
|
46
46
|
"docs/UI-WORKFLOW-V2-MIGRATION.md",
|
package/docs/RELEASE-0.8.18.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# 0.8.18
|
|
2
|
-
|
|
3
|
-
新增共享首条快照契约(qcc-field-contracts 0.2.0),目录共 136 个字段。
|
|
4
|
-
|
|
5
|
-
- 受益所有人仅取工具返回首条名称,不代表唯一或最大受益人;不以经营管理人员替代。
|
|
6
|
-
- 财务三项取同一个首返回报告期的主要财务指标,缺项留空,不跨期拼接;保留 0、原始精度、报告期与披露等级。
|
|
7
|
-
- 映射大类与关键词联合筛选,当前选择不因过滤丢失。十一类按工商、联系方式、实控人、受益所有人、简介、财务、税务、上市、进出口、自身风险、关联风险排序。
|
|
8
|
-
- 执行中的任务每秒读取 Host 命令状态与任务元数据。显示来源方式、文件名、输入条数、补全字段数、去重后已处理主体数;已处理不等于成功补全。
|
|
9
|
-
- 对话工具结果包含实际任务的 XLSX 链接:浏览器安全预览前 50 行、下载完整新文件。原上传文件不修改。
|
|
10
|
-
|
|
11
|
-
## 验证
|
|
12
|
-
|
|
13
|
-
使用合成返回测试首条规则、缺失和精度;验证原列补空、多列映射及 Host 跨重挂载下载。浏览器回归覆盖明暗主题及五种视口。无真实企查查收费查询。
|
|
14
|
-
|
|
15
|
-
升级后完整重启 DSH 并重新生成任务说明;旧凭证不改写。
|