dsh-data-cleaning-agent 0.9.3 → 0.9.5
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 +10 -0
- package/README.en.md +1 -1
- package/README.md +3 -3
- package/docs/USER-GUIDE.md +1 -1
- package/lib/artifact-preview.js +17 -0
- package/lib/artifacts.js +19 -5
- package/lib/client.js +9 -7
- package/lib/qcc.js +8 -2
- package/lib/web.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.5] - 2026-09-11
|
|
8
|
+
|
|
9
|
+
- 主结果 XLSX 仅保留客户原列和选定补全字段;执行状态、来源及诊断信息改为独立任务结果报告,状态中文化。
|
|
10
|
+
- 来源记录实际调用的企查查 MCP server / 工具;历史缺失来源明确标注,不推断补造。
|
|
11
|
+
- 新任务只生成 XLSX,下载区按文件将预览与明确的下载按钮相邻展示,异常清单仅在有异常时提供。
|
|
12
|
+
|
|
13
|
+
## [0.9.4] - 2026-09-10
|
|
14
|
+
|
|
15
|
+
- 文件预览采用 CSV 式单行表格,长文本不再撑高整行;独立双向滚动区域固定表头与序号列,下载保持完整原值。
|
|
16
|
+
|
|
7
17
|
## [0.9.3] - 2026-09-10
|
|
8
18
|
|
|
9
19
|
- 图片向导保留独立缩略图与来源元数据,识别前显示待识别而非表格零行。
|
package/README.en.md
CHANGED
|
@@ -10,7 +10,7 @@ Related agents: [数据清洗补全](https://github.com/duhu2000/dsh-data-cleani
|
|
|
10
10
|
|
|
11
11
|
> 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.
|
|
12
12
|
>
|
|
13
|
-
> Current source version / 当前源码版本: **0.9.
|
|
13
|
+
> Current source version / 当前源码版本: **0.9.5** (stable release)
|
|
14
14
|
|
|
15
15
|
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.
|
|
16
16
|
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
数据清洗补全智能体:面向 Excel/CSV/JSON 企业名单,提供数据清洗、表格清洗、清洗补全、去重、企业数据补全与字段补全,支持企查查 MCP 和结果导出。
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
dsh plugin --profile web add dsh-data-cleaning-agent@0.9.
|
|
12
|
+
dsh plugin --profile web add dsh-data-cleaning-agent@0.9.5
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
请先满足下文的 DSH、连接器及侧边栏依赖要求;安装后完整停止并重启对应 Profile。
|
|
@@ -27,7 +27,7 @@ dsh plugin --profile web add dsh-data-cleaning-agent@0.9.3
|
|
|
27
27
|
|
|
28
28
|
> 在 DeepSeek Harness 中清洗、补全、画像企业名单数据的智能体插件:本地 CSV/XLSX/JSON 引擎 + 可选企查查(Qichacha/QCC)MCP 企业数据补全,由企查查(Qichacha/QCC)团队发起并维护。
|
|
29
29
|
>
|
|
30
|
-
> 当前源码版本 / Current source version: **0.9.
|
|
30
|
+
> 当前源码版本 / Current source version: **0.9.5**(正式版本)
|
|
31
31
|
|
|
32
32
|
本版上传表格后展示完整清单及空白数量,用绿色、琥珀色、红色及文字区分已通过、待确认和未匹配映射。已映射字段自动纳入补全范围,额外字段默认折叠;多个原列可经确认使用同一非主体字段,分别补空并保留非空原值。修复 Host 草稿重置为默认五字段的问题,增加前后端版本错配提示。生成说明不查询,发送才执行;升级后需完整重启 DSH,历史下载文件不自动改写。
|
|
33
33
|
|
|
@@ -67,7 +67,7 @@ dsh plugin --profile web add dsh-data-cleaning-agent
|
|
|
67
67
|
DSH 原生会话:首页标题、产品说明与工作流会替换通用探索页;输入框左上角提供提示词生成器,支持
|
|
68
68
|
粘贴名单、解析 Excel/CSV/JSON 或附加图片并选择清洗动作、补全字段。上传清洗、质量体检、匹配核验、
|
|
69
69
|
字段补全和任务历史五个入口位于输入框下方,右侧工作台承载“上传数据 → 规则确认 → 数据匹配 →
|
|
70
|
-
清洗补全 →
|
|
70
|
+
清洗补全 → 下载数据”五步闭环。完成后可预览、下载清洗补全结果 XLSX 和独立任务结果报告 XLSX;有异常时另提供异常清单 XLSX。执行状态和来源信息不会追加到客户主结果中。
|
|
71
71
|
|
|
72
72
|
0.8.1 起,在数据清洗补全会话的原生输入框粘贴图片,或在「提示词生成」的图片页
|
|
73
73
|
拖入/选择 PNG、JPEG、WebP,会显示可点击放大的原生缩略图。用户可继续完成匹配规则、清洗项与补全字段,
|
package/docs/USER-GUIDE.md
CHANGED
|
@@ -43,7 +43,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
|
|
|
43
43
|
2. **规则确认**:映射企业名称/统一社会信用代码/注册号,选择清洗目标与补全字段;
|
|
44
44
|
3. **数据匹配**:运行质量体检与主体匹配,多候选由人工确认;
|
|
45
45
|
4. **清洗补全**:执行本地确定性清洗;需要 QCC 时先零调用估算,再由当前用户确认使用自己的账号额度;
|
|
46
|
-
5.
|
|
46
|
+
5. **下载数据**:生成清洗补全结果 XLSX 和独立任务结果报告 XLSX;有异常时另提供异常清单 XLSX。主结果仅保留客户原列与选定补全字段,报告记录中文执行状态、实际 MCP server / 工具及诊断信息。每个文件的预览与下载按钮相邻,后续可从任务历史恢复下载。
|
|
47
47
|
|
|
48
48
|
0.8.11 起的原列回填规则:规则确认时同时检查「字段映射」和「输出字段」。已选字段优先填入已映射原列的空白单元格,非空原值保持不变,原表没有对应映射的已选字段才新增列。地址、网址、开业时间等推荐需确认含义;重复列需明确选定一列。未返回的数据不编造。结果预览、XLSX/CSV 及异常清单遵循同一规则;历史已生成文件不会自动改写。
|
|
49
49
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Read-only CSV-style preview: preserve values, but never wrap long cells.
|
|
2
|
+
export function renderArtifactPreview({ fileName, taskId, rowCount, columns, rows, download }) {
|
|
3
|
+
const escape = value => String(value ?? '').replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
|
|
4
|
+
return `<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escape(fileName)}</title><style>
|
|
5
|
+
*{box-sizing:border-box}
|
|
6
|
+
html,body{height:100%;margin:0;overflow:hidden}
|
|
7
|
+
body{font:14px system-ui;color:#243044;display:flex;flex-direction:column;padding:16px;gap:12px}
|
|
8
|
+
header{flex:none;min-width:0}h1{font-size:18px;margin:0 0 8px;overflow-wrap:anywhere}p{margin:0 0 8px;color:#66758b;font-size:12px;overflow-wrap:anywhere}a{color:#0875ce}
|
|
9
|
+
.grid{flex:1;min-height:0;min-width:0;overflow:auto;scrollbar-gutter:stable;border:1px solid #dde5ef;border-radius:6px}
|
|
10
|
+
table{border-collapse:separate;border-spacing:0;table-layout:auto;width:max-content;min-width:100%}
|
|
11
|
+
td,th{white-space:nowrap;word-break:normal;overflow-wrap:normal;vertical-align:middle;min-width:100px;height:34px;padding:7px 12px;border-right:1px solid #dde5ef;border-bottom:1px solid #dde5ef;text-align:left;line-height:20px}
|
|
12
|
+
thead th{position:sticky;top:0;z-index:2;background:#e5f3ff;box-shadow:0 1px 0 #c7d8e8}
|
|
13
|
+
tbody tr:nth-child(even){background:#f8fafc}tbody tr:hover{background:#edf6ff}
|
|
14
|
+
.row-number{min-width:48px;text-align:right;color:#66758b;background:#f3f7fb;position:sticky;left:0;z-index:1}thead .row-number{z-index:3;background:#e5f3ff}
|
|
15
|
+
.grid:focus-visible{outline:2px solid #0875ce;outline-offset:1px}
|
|
16
|
+
</style><header><h1>${escape(fileName)}</h1><p>任务 ${escape(taskId)} · 共 ${escape(rowCount)} 行 / ${columns.length} 列,当前预览 ${rows.length} 行(最多 50 行);源文件未修改。左右滚动查看全部字段,表头固定。</p><a href="${escape(download)}">下载完整 Excel</a></header><div class="grid" role="region" aria-label="结果数据表格,可横向和纵向滚动" tabindex="0"><table><thead><tr><th class="row-number" scope="col">序号</th>${columns.map(c => `<th scope="col">${escape(c)}</th>`).join('')}</tr></thead><tbody>${rows.map((row, i) => `<tr><th class="row-number" scope="row">${i + 1}</th>${columns.map(c => `<td>${escape(row[c])}</td>`).join('')}</tr>`).join('')}</tbody></table></div></html>`;
|
|
17
|
+
}
|
package/lib/artifacts.js
CHANGED
|
@@ -31,6 +31,18 @@ const SYSTEM_FIELD_LABELS = Object.freeze({
|
|
|
31
31
|
_exception_reason: '异常原因',
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
const isExecutionField = key => ['qcc_match_status', 'qcc_field_issues', 'qcc_field_notes', 'qcc_source', 'qcc_snapshot_note', 'qcc_error', 'match_status', '_exception_reason', 'error', '_incomplete'].includes(key);
|
|
35
|
+
const STATUS_LABELS = { enriched: '已匹配并补全', exact: '精确匹配', candidate: '候选主体待确认', ambiguous: '多候选待确认',
|
|
36
|
+
review_required: '待核验', unresolved: '未匹配', not_found: '未找到', failed: '失败', error: '失败', partial: '部分完成', 'missing-name': '缺少主体名称' };
|
|
37
|
+
function reportRow(row) {
|
|
38
|
+
const next = { ...row };
|
|
39
|
+
for (const key of ['qcc_match_status', 'match_status']) {
|
|
40
|
+
if (key in next) next[key] = STATUS_LABELS[next[key]] || (next[key] ? '待核验(未知状态)' : '未记录');
|
|
41
|
+
}
|
|
42
|
+
if (next.qcc_source === 'qcc-mcp') next.qcc_source = '企查查 MCP(历史记录未保存具体 server / 工具)';
|
|
43
|
+
return next;
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
export class ArtifactError extends Error {
|
|
35
47
|
constructor(code, message, status = 400) {
|
|
36
48
|
super(message);
|
|
@@ -228,20 +240,22 @@ export class WorkflowArtifactStore {
|
|
|
228
240
|
mappings: input.mappings || [], fieldSelection: normalizeFieldSelection(input.fieldSelection),
|
|
229
241
|
});
|
|
230
242
|
const rows = projected.rows;
|
|
231
|
-
const
|
|
243
|
+
const allowed = new Set([...(input.headers || []), ...normalizeFieldSelection(input.fieldSelection)]);
|
|
244
|
+
const headers = normalizeHeaders(projected.headers, rows).filter(key => !isExecutionField(key)
|
|
245
|
+
&& (!allowed.size || allowed.has(key)));
|
|
232
246
|
if (!headers.length) throw new ArtifactError('DC_ARTIFACT_HEADERS_REQUIRED', 'At least one export column is required.', 400);
|
|
233
247
|
const exceptions = input.exceptionRows === undefined
|
|
234
248
|
? deriveExceptionRows(rows)
|
|
235
249
|
: projectCompletionResult({ rows: normalizeRows(input.exceptionRows), headers: input.headers || [],
|
|
236
250
|
mappings: input.mappings || [], fieldSelection: normalizeFieldSelection(input.fieldSelection) }).rows;
|
|
237
|
-
const
|
|
251
|
+
const reportHeaders = normalizeHeaders([...headers, 'qcc_match_status', 'qcc_source'], rows).filter(key => headers.includes(key) || isExecutionField(key));
|
|
252
|
+
const exceptionHeaders = normalizeHeaders([...reportHeaders, '_exception_reason'], exceptions).filter(key => reportHeaders.includes(key) || isExecutionField(key));
|
|
238
253
|
const baseName = safeFilePart(input.baseName, '数据清洗补全结果');
|
|
239
254
|
const timestamp = this.nowFn();
|
|
240
255
|
const definitions = [
|
|
241
|
-
{ kind: 'complete', format: 'csv', fileName: `${baseName}.csv`, rows, headers, sheet: '清洗补全结果' },
|
|
242
256
|
{ kind: 'complete', format: 'xlsx', fileName: `${baseName}.xlsx`, rows, headers, sheet: '清洗补全结果' },
|
|
243
|
-
{ kind: '
|
|
244
|
-
{ kind: 'review', format: 'xlsx', fileName: `${baseName}-异常清单.xlsx`, rows: exceptions, headers: exceptionHeaders, sheet: '异常清单' },
|
|
257
|
+
{ kind: 'report', format: 'xlsx', fileName: `${baseName}-任务结果报告.xlsx`, rows: rows.map(reportRow), headers: reportHeaders, sheet: '任务结果报告' },
|
|
258
|
+
...(exceptions.length ? [{ kind: 'review', format: 'xlsx', fileName: `${baseName}-异常清单.xlsx`, rows: exceptions.map(reportRow), headers: exceptionHeaders, sheet: '异常清单' }] : []),
|
|
245
259
|
];
|
|
246
260
|
const artifacts = [];
|
|
247
261
|
for (const definition of definitions) {
|
package/lib/client.js
CHANGED
|
@@ -762,6 +762,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
762
762
|
.dcAgentArtifactList { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 16px; min-width: 0; }
|
|
763
763
|
.dcAgentArtifactList > .dcAgentButton { display: inline-flex; align-items: center; box-sizing: border-box; max-width: 100%; min-width: 0; height: auto; white-space: normal; overflow-wrap: anywhere; line-height: 1.5; text-decoration: none; }
|
|
764
764
|
.dcAgentArtifactList > a.dcAgentButton { flex-basis: 100%; }
|
|
765
|
+
.dcAgentArtifactList > .dcAgentCard { width: 100%; min-width: 0; box-sizing: border-box; }
|
|
766
|
+
.dcAgentArtifactList .dcAgentCard p { overflow-wrap: anywhere; margin: 0 0 10px; }
|
|
767
|
+
.dcAgentArtifactList .dcAgentCard .dcAgentButton { max-width: 100%; height: auto; white-space: normal; overflow-wrap: anywhere; }
|
|
765
768
|
.dcAgentPreviewTable { min-width: 0; max-width: 100%; scrollbar-gutter: stable; }
|
|
766
769
|
.dcAgentPreviewTable > .dcAgentTable {
|
|
767
770
|
width: max-content; min-width: 100%; max-width: none;
|
|
@@ -4087,7 +4090,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4087
4090
|
h('div', { className: 'dcAgentRow' }, h('button', { type: 'button', className: 'dcAgentButton is-primary', onClick: () => actions.setStep('download') }, '进入下载数据')),
|
|
4088
4091
|
);
|
|
4089
4092
|
} else if (step === 'download') {
|
|
4090
|
-
const availableArtifacts = (cachedTask?.artifacts || []).filter(artifact => artifact.kind !== 'review' || artifact.rowCount > 0);
|
|
4093
|
+
const availableArtifacts = (cachedTask?.artifacts || []).filter(artifact => artifact.format === 'xlsx' && (artifact.kind !== 'review' || artifact.rowCount > 0));
|
|
4091
4094
|
const canCreateArtifacts = Boolean(exportRows()?.length) && ['rules_confirmed', 'diagnosed', 'export_ready', 'partial'].includes(cachedTask?.state);
|
|
4092
4095
|
pane = h('div', { className: 'dcAgentPane' },
|
|
4093
4096
|
h('p', { className: 'dcAgentHint' }, '结果文件由 Host 保存;有失败、未匹配或待核验记录时展示异常清单。任务或插件重启后,可从任务历史继续下载;无需再次调用企查查。'),
|
|
@@ -4099,25 +4102,24 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4099
4102
|
stat('失败', qccFailedCount, qccFailedCount > 0 ? 'bad' : null),
|
|
4100
4103
|
),
|
|
4101
4104
|
availableArtifacts.length ? h('div', { className: 'dcAgentArtifactList' },
|
|
4102
|
-
availableArtifacts.
|
|
4105
|
+
availableArtifacts.map(artifact => h('div', { key: artifact.id, className: 'dcAgentCard' },
|
|
4106
|
+
h('p', { className: 'dcAgentHint' }, artifact.fileName),
|
|
4107
|
+
h('div', { className: 'dcAgentRow' }, h('a', {
|
|
4103
4108
|
key: 'preview-' + artifact.id, className: 'dcAgentButton',
|
|
4104
4109
|
href: `/data-cleaning/api/workflow/tasks/${encodeURIComponent(cachedTask.id)}/artifacts/${encodeURIComponent(artifact.id)}?preview=1`,
|
|
4105
4110
|
target: '_blank', rel: 'noopener noreferrer',
|
|
4106
|
-
},
|
|
4107
|
-
availableArtifacts.map((artifact) => h('button', {
|
|
4111
|
+
}, '预览 / 打开'), h('button', {
|
|
4108
4112
|
key: artifact.id,
|
|
4109
4113
|
type: 'button',
|
|
4110
4114
|
className: `dcAgentButton${artifact.kind === 'complete' && artifact.format === 'xlsx' ? ' is-primary' : ''}`,
|
|
4111
4115
|
disabled: busy,
|
|
4112
4116
|
onClick: () => downloadArtifact(artifact),
|
|
4113
4117
|
'aria-label': `下载 ${artifact.fileName}`,
|
|
4114
|
-
},
|
|
4118
|
+
}, `下载${artifact.kind === 'review' ? '异常清单' : artifact.kind === 'report' ? '任务结果报告' : '清洗补全结果'} XLSX · ${artifact.rowCount} 行`)))),
|
|
4115
4119
|
) : h('div', null,
|
|
4116
4120
|
h('div', { className: 'dcAgentRow' },
|
|
4117
4121
|
h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('complete', 'xlsx') }, '生成并下载结果 XLSX'),
|
|
4118
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('complete', 'csv') }, '生成并下载结果 CSV'),
|
|
4119
4122
|
qccReviewCount > 0 ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('review', 'xlsx') }, '生成并下载异常清单 XLSX') : null,
|
|
4120
|
-
qccReviewCount > 0 ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('review', 'csv') }, '生成并下载异常清单 CSV') : null,
|
|
4121
4123
|
),
|
|
4122
4124
|
!canCreateArtifacts ? h('p', { className: 'dcAgentHint' }, '请先完成规则确认与清洗补全;历史任务若尚未生成制品,需要重新上传源文件。') : null,
|
|
4123
4125
|
),
|
package/lib/qcc.js
CHANGED
|
@@ -921,7 +921,7 @@ export class QccHostBridge {
|
|
|
921
921
|
);
|
|
922
922
|
const match = classifyEntityMatch(lookup.data);
|
|
923
923
|
if (match.status !== 'exact') return match;
|
|
924
|
-
return this.enrichMatchedCompany(match, options);
|
|
924
|
+
return this.enrichMatchedCompany(match, { ...options, lookupTool: lookup.toolName });
|
|
925
925
|
}
|
|
926
926
|
|
|
927
927
|
async enrichMatchedCompany(match, options = {}) {
|
|
@@ -930,11 +930,13 @@ export class QccHostBridge {
|
|
|
930
930
|
let mapped = {};
|
|
931
931
|
const fieldIssues = {};
|
|
932
932
|
const snapshotNotes = [];
|
|
933
|
+
const actualTools = options.lookupTool ? [options.lookupTool] : [];
|
|
933
934
|
let legacyRiskTags = '';
|
|
934
935
|
for (const sourceTool of sourceTools) {
|
|
935
936
|
const config = SOURCE_TOOL_CONFIG[sourceTool];
|
|
936
937
|
if (!config) continue;
|
|
937
938
|
const response = await this.call(config.name, { searchKey: lockedKey, ...(config.args ?? {}) }, options);
|
|
939
|
+
actualTools.push(response.toolName);
|
|
938
940
|
const fields = config.map(response.data, match);
|
|
939
941
|
mapped = mergeMappedFields(mapped, fields);
|
|
940
942
|
if (['get_beneficial_owners', 'get_financial_data'].includes(sourceTool)) {
|
|
@@ -965,6 +967,10 @@ export class QccHostBridge {
|
|
|
965
967
|
const fields = projectSelectedFields(mapped, options.fieldSelection);
|
|
966
968
|
if (options.includeRisk) fields.risk_tags = legacyRiskTags;
|
|
967
969
|
return { status: 'enriched', companyName: String(match?.companyName ?? ''), fields,
|
|
970
|
+
source: [...new Set(actualTools)].map(name => {
|
|
971
|
+
const parts = /^mcp__(.+?)__(.+)$/.exec(name || '');
|
|
972
|
+
return parts ? `企查查 MCP · server: ${parts[1]} · 工具: ${parts[2]}` : `企查查 MCP · 工具: ${name || '未记录'}`;
|
|
973
|
+
}).join(';'),
|
|
968
974
|
...(snapshotNotes.length ? { snapshotNote: snapshotNotes.join(';') } : {}),
|
|
969
975
|
...(Object.keys(fieldIssues).length ? { fieldIssues } : {}) };
|
|
970
976
|
}
|
|
@@ -1068,7 +1074,7 @@ export class QccHostBridge {
|
|
|
1068
1074
|
...row,
|
|
1069
1075
|
...Object.fromEntries(Object.entries(result.fields || {}).filter(([key]) => row[key] == null || (typeof row[key] === 'string' && row[key].trim() === ''))),
|
|
1070
1076
|
qcc_match_status: 'enriched',
|
|
1071
|
-
qcc_source: '
|
|
1077
|
+
qcc_source: result.source || '企查查 MCP(未记录具体 server / 工具)',
|
|
1072
1078
|
...(result.snapshotNote ? { qcc_snapshot_note: result.snapshotNote } : {}),
|
|
1073
1079
|
...(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
1080
|
...(result.fieldIssues ? { qcc_field_notes: Object.entries(result.fieldIssues).filter(([, issue]) => issue.reviewRequired === false).map(([field, issue]) => field + ': ' + issue.message).join(';') } : {}),
|
package/lib/web.js
CHANGED
|
@@ -16,6 +16,7 @@ import { PHASE3_BATCH_LIMITS, Phase3BatchService, Phase3RunStore } from './qcc-p
|
|
|
16
16
|
import { publicWorkflowContract } from './workflow-contract.js';
|
|
17
17
|
import { DataCleaningWorkflowStore, WorkflowError } from './workflow.js';
|
|
18
18
|
import { ArtifactError, WorkflowArtifactStore } from './artifacts.js';
|
|
19
|
+
import { renderArtifactPreview } from './artifact-preview.js';
|
|
19
20
|
import { createWorkflowExecution } from './workflow-execution.js';
|
|
20
21
|
import { createImageWorkflow } from './image-workflow.js';
|
|
21
22
|
import {
|
|
@@ -583,11 +584,10 @@ export function mountWebRoutes(wctx, { logger, report, TOOL_NAME, SKILL_NAME })
|
|
|
583
584
|
const bytes = await artifactStore.read(taskId, artifact);
|
|
584
585
|
if (artifact.format === 'xlsx' && new URL(req.url, 'http://127.0.0.1').searchParams.get('preview') === '1') {
|
|
585
586
|
const parsed = await parseXlsx(bytes);
|
|
586
|
-
const escape = value => String(value ?? '').replace(/[&<>\"]/g, c => ({ '&': '&', '<': '<', '>': '>', '\"': '"' }[c]));
|
|
587
587
|
const columns = parsed.headers || [];
|
|
588
588
|
const rows = (parsed.rows || []).slice(0, 50);
|
|
589
589
|
const download = `/data-cleaning/api/workflow/tasks/${encodeURIComponent(taskId)}/artifacts/${encodeURIComponent(artifactId)}`;
|
|
590
|
-
const html =
|
|
590
|
+
const html = renderArtifactPreview({ fileName: artifact.fileName, taskId, rowCount: artifact.rowCount, columns, rows, download });
|
|
591
591
|
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'self'", 'x-content-type-options': 'nosniff' });
|
|
592
592
|
return res.end(html);
|
|
593
593
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-data-cleaning-agent",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
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",
|