dsh-data-cleaning-agent 0.8.5 → 0.8.6

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
@@ -2,6 +2,14 @@
2
2
 
3
3
  本文件记录 `dsh-data-cleaning-agent` 的版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [0.8.6] - 2026-09-06
6
+
7
+ ### Fixed
8
+ - 上传/OCR 核验页使用完整原始清单,每页 20 行可翻页,显示序号与全列;不再把前 5 行摘要当成核验视图。
9
+ - 匹配结果共用全量分页和中文字段展示,不再截断为前 8 行/列。
10
+ - 核验阶段支持全量 CSV 和原值 JSON 下载,不触发 QCC 调用;CSV 对公式及长编号作文本保护。明细未完整载入时禁用核验下载。
11
+ - OCR 完成后的引导明确真实核验路径、规则/体检/匹配步骤及自动回填,无需人工复制 dcq- 凭证,不将识别清单误报为匹配结果。
12
+
5
13
  ## [0.8.5] - 2026-09-06
6
14
 
7
15
  ### Fixed
package/README.en.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 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
4
  >
5
- > Current source version / 当前源码版本: **0.8.5** (stable release)
5
+ > Current source version / 当前源码版本: **0.8.6** (stable release)
6
6
 
7
7
  [![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)
8
8
  [![npm](https://img.shields.io/npm/v/dsh-data-cleaning-agent)](https://www.npmjs.com/package/dsh-data-cleaning-agent)
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 在 DeepSeek Harness 中清洗、补全、画像企业名单数据的智能体插件:本地 CSV/XLSX/JSON 引擎 + 可选企查查(Qichacha/QCC)MCP 企业数据补全,由企查查(Qichacha/QCC)团队发起并维护。
4
4
  >
5
- > 当前源码版本 / Current source version: **0.8.5**(正式版本)
5
+ > 当前源码版本 / Current source version: **0.8.6**(正式版本)
6
6
 
7
7
  [![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)
8
8
  [![npm](https://img.shields.io/npm/v/dsh-data-cleaning-agent)](https://www.npmjs.com/package/dsh-data-cleaning-agent)
package/lib/client.js CHANGED
@@ -887,6 +887,7 @@ window.__ModuleLoader__.load({
887
887
  if (!from || !toId || String(fromId) === String(toId)) return runtimeFor(toId);
888
888
  const target = runtimeFor(toId);
889
889
  target.rows = from.rows;
890
+ target.sourceRows = from.sourceRows;
890
891
  target.headers = from.headers;
891
892
  target.source = from.source;
892
893
  target.lastCsv = from.lastCsv;
@@ -2681,6 +2682,7 @@ window.__ModuleLoader__.load({
2681
2682
  function applyParsed(result, actions, taskId = 'unassigned', source = {}) {
2682
2683
  const runtime = runtimeFor(taskId);
2683
2684
  runtime.rows = Array.isArray(result.rows) ? result.rows : [];
2685
+ runtime.sourceRows = runtime.rows;
2684
2686
  runtime.headers = Array.isArray(result.headers) ? result.headers : [];
2685
2687
  runtime.source = {
2686
2688
  type: source.type || result.fmt || 'csv',
@@ -2712,6 +2714,7 @@ window.__ModuleLoader__.load({
2712
2714
  // 都以本次解析结果覆盖目标 runtime,避免复用已完成任务的一行旧数据。
2713
2715
  const target = runtimeFor(current.id);
2714
2716
  target.rows = Array.isArray(result.rows) ? result.rows : [];
2717
+ target.sourceRows = target.rows;
2715
2718
  target.headers = Array.isArray(result.headers) ? result.headers : [];
2716
2719
  target.source = {
2717
2720
  type: source.type || result.fmt || 'csv',
@@ -2733,6 +2736,67 @@ window.__ModuleLoader__.load({
2733
2736
  return current;
2734
2737
  }
2735
2738
 
2739
+ function reviewColumns(rows, headers = []) {
2740
+ return [...new Set([...headers, ...rows.flatMap((row) => Object.keys(row || {}))])];
2741
+ }
2742
+
2743
+ function reviewCsv(rows, headers) {
2744
+ const cell = (value) => {
2745
+ let text = value == null ? '' : typeof value === 'object' ? JSON.stringify(value) : String(value);
2746
+ // 保留长编号的文本形式,阻止外部 OCR/企业字段成为 Excel 公式。
2747
+ if (/^[\s]*[=+@-]/.test(text) || /^\d{15,}$/.test(text) || /^0\d+/.test(text)) text = "'" + text;
2748
+ return '"' + text.replace(/"/g, '""') + '"';
2749
+ };
2750
+ return '\uFEFF' + [headers.map((key) => cell(resultFieldLabel(key))).join(','),
2751
+ ...rows.map((row) => headers.map((key) => cell(row?.[key])).join(','))].join('\r\n');
2752
+ }
2753
+
2754
+ /** 页内查看和核验下载均使用完整 rows,不使用 store 的五行摘要。 */
2755
+ function DatasetReview({ rows, headers, title, expectedCount, onError }) {
2756
+ const [page, setPage] = react.useState(0);
2757
+ const pageSize = 20;
2758
+ const total = rows.length;
2759
+ const columns = reviewColumns(rows, headers);
2760
+ const currentPage = Math.min(page, Math.max(0, Math.ceil(total / pageSize) - 1));
2761
+ const start = currentPage * pageSize;
2762
+ const download = (format) => {
2763
+ try {
2764
+ const content = format === 'csv' ? reviewCsv(rows, columns) : JSON.stringify(rows, null, 2);
2765
+ const url = URL.createObjectURL(new Blob([content], { type: format === 'csv' ? 'text/csv;charset=utf-8' : 'application/json;charset=utf-8' }));
2766
+ const anchor = document.createElement('a');
2767
+ anchor.href = url;
2768
+ anchor.download = title + '.' + format;
2769
+ try { anchor.click(); } finally { setTimeout(() => URL.revokeObjectURL(url), 1000); }
2770
+ } catch (error) { onError?.(error instanceof Error ? error.message : String(error)); }
2771
+ };
2772
+ const complete = expectedCount == null || expectedCount === total;
2773
+ return h('section', { className: 'dcAgentSection', 'aria-label': title },
2774
+ h('h3', null, title),
2775
+ h('p', { className: 'dcAgentHint', role: 'status' }, complete
2776
+ ? `共 ${total} 条,当前显示 ${total ? start + 1 : 0}–${Math.min(start + pageSize, total)} 条;共 ${columns.length} 列,可横向滚动。`
2777
+ : `完整清单尚未就绪:预期 ${expectedCount} 条,当前载入 ${total} 条。请等待同步;刷新后若仍缺失,请重新上传源文件。`),
2778
+ h('div', { className: 'dcAgentPreviewTable', style: { overflow: 'auto', maxHeight: '420px' } },
2779
+ h('table', { className: 'dcAgentTable' },
2780
+ h('thead', null, h('tr', null, h('th', null, '序号'), columns.map((key) => h('th', { key }, resultFieldLabel(key))))),
2781
+ h('tbody', null, rows.slice(start, start + pageSize).map((row, index) => h('tr', { key: start + index },
2782
+ h('td', null, String(start + index + 1)),
2783
+ columns.map((key) => h('td', { key }, row?.[key] == null ? '' : typeof row[key] === 'object' ? JSON.stringify(row[key]) : String(row[key]))),
2784
+ ))),
2785
+ ),
2786
+ ),
2787
+ h('div', { className: 'dcAgentRow' },
2788
+ h('button', { type: 'button', className: 'dcAgentButton', disabled: currentPage === 0, onClick: () => setPage(currentPage - 1) }, '上一页'),
2789
+ h('span', null, `第 ${currentPage + 1} / ${Math.max(1, Math.ceil(total / pageSize))} 页`),
2790
+ h('button', { type: 'button', className: 'dcAgentButton', disabled: start + pageSize >= total, onClick: () => setPage(currentPage + 1) }, '下一页'),
2791
+ ),
2792
+ h('div', { className: 'dcAgentRow' },
2793
+ h('button', { type: 'button', className: 'dcAgentButton', disabled: !complete || !total, onClick: () => download('csv') }, `下载全部 ${total} 条 CSV`),
2794
+ h('button', { type: 'button', className: 'dcAgentButton', disabled: !complete || !total, onClick: () => download('json') }, '下载原值 JSON'),
2795
+ ),
2796
+ h('p', { className: 'dcAgentHint' }, '下载包含全部行,不受分页影响。CSV 对公式及长编号作文本保护;JSON 保留原值。仅导出当前清单,不触发企查查调用。'),
2797
+ );
2798
+ }
2799
+
2736
2800
  /** 右侧非模态工作台:中央区域始终保留 DSH 原生会话。 */
2737
2801
  function WorkbenchDrawer(props) {
2738
2802
  const { useStore, actions } = props;
@@ -2888,10 +2952,9 @@ window.__ModuleLoader__.load({
2888
2952
  const cachedTask = workflowTaskBySession.get(String(activeSessionId || 'unassigned')) ?? workflowTask;
2889
2953
  const runtimeKey = cachedTask?.id ?? `session:${activeSessionId || 'unassigned'}`;
2890
2954
  const runtime = runtimeFor(runtimeKey);
2955
+ const stagedSource = runtimeFor(`session:${activeSessionId || 'unassigned'}`, false);
2956
+ const sourceRows = stagedSource?.sourceRows ?? runtime.sourceRows ?? runtime.rows;
2891
2957
  const lastCsv = runtime.lastCsv;
2892
- const qccPreviewColumns = Array.isArray(qccRun?.rows) && qccRun.rows.length
2893
- ? Object.keys(qccRun.rows[0] || {}).slice(0, 8)
2894
- : [];
2895
2958
  const fieldByPattern = (pattern) => runtime.headers.find((field) => pattern.test(field)) ?? null;
2896
2959
  const phoneField = fieldByPattern(/^(phone|mobile|tel|telephone|联系电话|手机号码|手机号)$/i);
2897
2960
  const amountField = fieldByPattern(/^(amount|price|金额|注册资本)$/i);
@@ -3604,17 +3667,11 @@ window.__ModuleLoader__.load({
3604
3667
  stat('未匹配', qccRun.summary?.unresolved ?? 0, (qccRun.summary?.unresolved ?? 0) > 0 ? 'warn' : null),
3605
3668
  stat('失败', qccRun.summary?.failed ?? 0, (qccRun.summary?.failed ?? 0) > 0 ? 'bad' : null),
3606
3669
  ),
3607
- Array.isArray(qccRun.rows) && qccRun.rows.length ? h('div', { className: 'dcAgentTableWrap' },
3608
- h('table', { className: 'dcAgentTable', 'aria-label': '匹配补全结果预览' },
3609
- h('thead', null, h('tr', null,
3610
- qccPreviewColumns.map((key) => h('th', { key }, resultFieldLabel(key))),
3611
- )),
3612
- h('tbody', null, qccRun.rows.slice(0, 8).map((row, index) => h('tr', { key: `${index}-${row?.qcc_credit_no || row?.credit_no || ''}` },
3613
- qccPreviewColumns.map((key) => h('td', { key }, String(row?.[key] ?? ''))),
3614
- ))),
3615
- ),
3616
- h('p', { className: 'dcAgentHint' }, `显示前 ${Math.min(8, qccRun.rows.length)} 行;完整结果在下载阶段生成耐久制品。`),
3617
- ) : null,
3670
+ Array.isArray(qccRun.rows) && qccRun.rows.length ? h(DatasetReview, {
3671
+ key: qccRun.runId, rows: qccRun.rows, headers: runtime.headers,
3672
+ title: '企查查匹配补全结果', expectedCount: qccRun.summary?.totalRows,
3673
+ onError: actions.setError,
3674
+ }) : null,
3618
3675
  (qccRun.reviewQueue || []).map((item) => h('div', { key: item.companyName, className: 'dcAgentSection' },
3619
3676
  h('h3', null, `待核验:${item.companyName}`),
3620
3677
  item.candidates.map((candidate) => h('div', { key: candidate.creditNo, className: 'dcAgentCandidate' },
@@ -3716,15 +3773,16 @@ window.__ModuleLoader__.load({
3716
3773
  (dataset.headers || []).slice(0, 12).map((name) => h('span', { key: name, className: 'dcAgentChip' }, name)),
3717
3774
  (dataset.headers || []).length > 12 ? h('span', { className: 'dcAgentChip' }, `+${dataset.headers.length - 12} 列`) : null,
3718
3775
  ) : null,
3719
- dataset ? h('div', { className: 'dcAgentPreviewTable', 'aria-label': '数据预览' },
3720
- h('table', { className: 'dcAgentTable' },
3721
- h('thead', null, h('tr', null, (dataset.headers || []).slice(0, 8).map((name) => h('th', { key: name }, name)))),
3722
- h('tbody', null, (dataset.preview || []).slice(0, 5).map((row, index) => h('tr', { key: index }, (dataset.headers || []).slice(0, 8).map((name) => h('td', { key: name }, String(row?.[name] ?? '')))))),
3723
- ),
3724
- ) : null,
3776
+ dataset ? h(DatasetReview, {
3777
+ key: runtimeKey, rows: sourceRows, headers: dataset.headers || [],
3778
+ expectedCount: dataset.rowCount,
3779
+ title: runtime.source?.type === 'image' ? '图片识别原始名单(尚未匹配)' : '导入原始清单',
3780
+ onError: actions.setError,
3781
+ }) : null,
3782
+ hasData ? h('p', { className: 'dcAgentHint' }, '请核对完整名单。识别清单不代表已完成企查查匹配。核对后进入「字段映射与规则」,确认规则生成质量体检;再进入「数据匹配」估算调用量并确认账号额度,点击「生成可编辑任务说明」自动回填中央对话框,无需手工复制任务凭证。') : null,
3725
3783
  h('div', { className: 'dcAgentRow' },
3726
3784
  h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy, 'aria-label': '解析数据', onClick: handleParse }, busy ? '解析中…' : '解析数据'),
3727
- hasData ? h('button', { type: 'button', className: 'dcAgentButton', onClick: () => actions.setStep('rules') }, '下一步:字段映射与规则') : null,
3785
+ hasData ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || sourceRows.length !== dataset.rowCount, onClick: () => actions.setStep('rules') }, '已核对清单,下一步:字段映射与规则') : null,
3728
3786
  ),
3729
3787
  );
3730
3788
  }
@@ -3897,6 +3955,9 @@ window.__ModuleLoader__.load({
3897
3955
  // 测试用纯函数,不构成 Host / DSH 稳定 API。
3898
3956
  exports.__testing = {
3899
3957
  buildTaskPrompt,
3958
+ DatasetReview,
3959
+ reviewColumns,
3960
+ reviewCsv,
3900
3961
  clearCleaningDraft,
3901
3962
  entriesToDataset,
3902
3963
  extractPromptEntries,
@@ -580,7 +580,7 @@ export function registerImageIntakeTool(tools, store) {
580
580
  },
581
581
  render: (_args, value) => [{
582
582
  type: 'text',
583
- text: `企查查智能文档解析已识别图片企业名单:${value.entryCount} 条,已同步回数据清洗补全工作台等待核验。`,
583
+ text: `企查查智能文档解析已识别图片企业名单:${value.entryCount} 条,已同步回数据清洗补全工作台等待核验。请在「上传数据」查看原始清单全部行或下载全量 CSV/JSON,核对后进入「字段映射与规则」确认规则并生成质量体检,再到「数据匹配」估算调用量并确认账号额度,点击「生成可编辑任务说明」自动回填中央对话框。无需手工复制 dcq- 凭证或重新粘贴名单;识别名单不代表已匹配,尚未查询时不要编造匹配摘要。`,
584
584
  }],
585
585
  },
586
586
  async execute(args, exec) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-data-cleaning-agent",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
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.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",