dsh-data-cleaning-agent 0.8.17 → 0.8.19
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 +15 -0
- package/README.en.md +1 -1
- package/README.md +3 -3
- package/docs/RELEASE-0.8.19.md +13 -0
- package/lib/artifacts.js +1 -0
- package/lib/client.js +56 -13
- package/lib/qcc-command.js +8 -2
- package/lib/qcc-field-catalog.js +3 -2
- package/lib/qcc-runs.js +1 -0
- package/lib/qcc.js +10 -1
- package/lib/web.js +10 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.8.19] - 2026-09-09
|
|
8
|
+
|
|
9
|
+
- 受益人与主营业务收入增加待确认候选,明确首条限制及财务口径差异,不自动替换。
|
|
10
|
+
- 映射依次展示搜索、大类、可见字段列表;组合筛选保留已有映射。
|
|
11
|
+
- 区分去重主体处理数与输入行补全数,结果卡片和工作台提供原生预览入口。
|
|
12
|
+
- 零异常时不展示空异常清单;有待核验记录时保留下载。
|
|
13
|
+
- 245 项测试及 10 组浏览器布局回归通过;实际 DSH 宿主点击链路仍需升级后验收。
|
|
14
|
+
|
|
15
|
+
## [0.8.18] - 2026-09-09
|
|
16
|
+
|
|
17
|
+
- 新增受益所有人首条名称及首报告期营业总收入、利润总额、总资产,共 136 个字段;共享契约 0.2.0。
|
|
18
|
+
- 映射支持大类与关键词联合筛选,统一十一类排序,保留既有选择。
|
|
19
|
+
- 执行期间同步 Host 任务状态和已处理主体数,展示来源、条目数与补全字段数;状态徽标仅反映当前任务。
|
|
20
|
+
- 工具结果返回真实 XLSX 预览/下载入口,绑定 taskId;安全预览前 50 行,不修改源文件、不重复收费查询。
|
|
21
|
+
|
|
7
22
|
## [0.8.17] - 2026-09-08
|
|
8
23
|
|
|
9
24
|
### Fixed
|
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.19** (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.19.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.19
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
请先满足下文的 DSH、连接器及侧边栏依赖要求;安装后完整停止并重启对应 Profile。
|
|
@@ -25,7 +25,7 @@ dsh plugin --profile web add dsh-data-cleaning-agent@0.8.17
|
|
|
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.19**(正式版本)
|
|
29
29
|
|
|
30
30
|
本版上传表格后展示完整清单及空白数量,用绿色、琥珀色、红色及文字区分已通过、待确认和未匹配映射。已映射字段自动纳入补全范围,额外字段默认折叠;多个原列可经确认使用同一非主体字段,分别补空并保留非空原值。修复 Host 草稿重置为默认五字段的问题,增加前后端版本错配提示。生成说明不查询,发送才执行;升级后需完整重启 DSH,历史下载文件不自动改写。
|
|
31
31
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 0.8.19 发布说明
|
|
2
|
+
|
|
3
|
+
本版修复字段候选、搜索展示和结果交付体验。
|
|
4
|
+
|
|
5
|
+
- “受益人”推荐首条受益所有人;“主营业务收入”推荐营业总收入并提示口径不同,均需人工确认。
|
|
6
|
+
- 映射选择器按当前映射、搜索、大类、字段清单展示;筛选不修改已选映射。
|
|
7
|
+
- 已处理主体数包含未匹配或失败,不代表已补全;同时展示按输入行统计的补全与待核验数。
|
|
8
|
+
- QCC 工具结果卡片与工作台增加原生文件链接,不只依赖模型生成 Markdown。
|
|
9
|
+
- 空异常清单不展示;实际存在异常时保留。
|
|
10
|
+
|
|
11
|
+
验证:245 项自动测试、10 组明暗色与响应式浏览器回归通过。未发起付费 QCC 查询,未修改上传源文件。实际 DSH 宿主中的文件点击仍需升级后验收。
|
|
12
|
+
|
|
13
|
+
升级至 0.8.19 后完整重启 DSH。历史对话文本不会自动重写;历史制品可从工作台获取。
|
package/lib/artifacts.js
CHANGED
package/lib/client.js
CHANGED
|
@@ -1141,7 +1141,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1141
1141
|
['tax_violation', '税收违法'], ['judgment_debtor', '被执行人'], ['terminated_case', '终本案件'],
|
|
1142
1142
|
['equity_freeze', '股权冻结'],
|
|
1143
1143
|
];
|
|
1144
|
+
const FIELD_GROUP_ORDER = ['company_registration', 'contact_info', 'actual_controller', 'beneficial_owners', 'company_profile', 'financial_data', 'tax_invoice_info', 'listing_info', 'import_export_credit', 'company_risk_scan', 'company_related_risk_scan'];
|
|
1144
1145
|
const FIELD_GROUPS = [
|
|
1146
|
+
['beneficial_owners', '受益所有人', [['beneficial_owner_first_name', '受益所有人名称(首条)']], 'get_beneficial_owners'],
|
|
1147
|
+
['financial_data', '财务数据', [['financial_total_revenue', '营业总收入'], ['financial_total_profit', '利润总额'], ['financial_total_assets', '总资产']], 'get_financial_data'],
|
|
1145
1148
|
// Shared contract mirror: checked against qcc-field-contracts in tests.
|
|
1146
1149
|
['actual_controller', '实际控制人', [
|
|
1147
1150
|
['actual_controller_name', '实际控制人名称'],
|
|
@@ -1204,6 +1207,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1204
1207
|
...RELATED_RISK_KEY_FIELD_OPTIONS.map(([id, label]) => [`related_risk_${id}_party_count`, `关联风险-${label}命中关联方数`]),
|
|
1205
1208
|
], 'get_company_related_risk_scan'],
|
|
1206
1209
|
];
|
|
1210
|
+
FIELD_GROUPS.sort((a, b) => FIELD_GROUP_ORDER.indexOf(a[0]) - FIELD_GROUP_ORDER.indexOf(b[0]));
|
|
1207
1211
|
const ENRICHMENT_OPTIONS = FIELD_GROUPS.flatMap(([, , fields]) => fields);
|
|
1208
1212
|
const INPUT_ONLY_MAPPING_OPTIONS = [['phone', '联系电话']];
|
|
1209
1213
|
const RESULT_FIELD_LABELS = new Map([
|
|
@@ -1476,6 +1480,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1476
1480
|
}
|
|
1477
1481
|
|
|
1478
1482
|
const MAPPING_ALIASES = {
|
|
1483
|
+
beneficial_owner_first_name: ['首条受益所有人名称'],
|
|
1479
1484
|
actual_controller_name: ['实控人', '实际控制人', '企业实控人名称', '企业实际控制人', '企业实控人名称(自然人请填写姓名)'],
|
|
1480
1485
|
actual_controller_direct_ratio: ['实际控制人直接持股比例'],
|
|
1481
1486
|
actual_controller_total_ratio: ['实际控制人总持股比例'],
|
|
@@ -1492,6 +1497,8 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1492
1497
|
};
|
|
1493
1498
|
// 语义不完全等同:只推荐,绝不冒充精确匹配或发起调用。
|
|
1494
1499
|
const MAPPING_HINTS = {
|
|
1500
|
+
受益人: ['beneficial_owner_first_name'], 受益所有人: ['beneficial_owner_first_name'], 受益所有人名称: ['beneficial_owner_first_name'], UBO: ['beneficial_owner_first_name'],
|
|
1501
|
+
主营业务收入: ['financial_total_revenue'],
|
|
1495
1502
|
地址: ['registered_address', 'mailing_address', 'invoice_address'],
|
|
1496
1503
|
address: ['registered_address', 'mailing_address'],
|
|
1497
1504
|
企业地址: ['registered_address', 'mailing_address'], 公司地址: ['registered_address', 'mailing_address'],
|
|
@@ -1518,6 +1525,8 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1518
1525
|
const hints = Object.entries(MAPPING_HINTS).find(([key]) => normalizeMappingLabel(key) === label)?.[1] || [];
|
|
1519
1526
|
candidates = hints.flatMap((key) => fields.filter(([id]) => id === key));
|
|
1520
1527
|
reason = candidates.length ? '含义需确认,点击推荐后采用' : '未识别表头,请手动选择';
|
|
1528
|
+
if (candidates.length && label === normalizeMappingLabel('主营业务收入')) reason = '主营业务收入与营业总收入口径不同;确认接受该口径后采用';
|
|
1529
|
+
if (candidates.length && label === normalizeMappingLabel('受益人')) reason = '受益人含义需确认;仅取首条受益所有人,不代表唯一或最大受益人';
|
|
1521
1530
|
}
|
|
1522
1531
|
return { sourceField: String(sourceField), candidates, automatic: exact, reason };
|
|
1523
1532
|
});
|
|
@@ -1556,11 +1565,17 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1556
1565
|
|
|
1557
1566
|
function MappingPicker({ sourceField, mappings, groups, recommendation, onChange }) {
|
|
1558
1567
|
const [query, setQuery] = react.useState('');
|
|
1568
|
+
const [category, setCategory] = react.useState('');
|
|
1559
1569
|
const value = mappings.find((item) => item.sourceField === sourceField)?.targetField || '';
|
|
1560
1570
|
const occupied = (id) => isAnchorField(id) && mappings.some((item) => item.sourceField !== sourceField && item.targetField === id);
|
|
1561
1571
|
const fields = groups.flatMap(([, , items]) => items);
|
|
1562
1572
|
const label = fields.find(([id]) => id === value)?.[1] || '不参与匹配 / 补全';
|
|
1563
|
-
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);
|
|
1564
1579
|
return h('div', { className: 'dcAgentMappingRow' },
|
|
1565
1580
|
h('span', { title: sourceField }, sourceField), h('b', { 'aria-hidden': 'true' }, '→'),
|
|
1566
1581
|
h('div', { style: { minWidth: 0 } },
|
|
@@ -1571,18 +1586,23 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1571
1586
|
}
|
|
1572
1587
|
} },
|
|
1573
1588
|
h('summary', { className: 'dcAgentField', 'aria-label': `${sourceField} 当前映射` }, label),
|
|
1574
|
-
h('input', { className: 'dcAgentField', value: query, placeholder: '
|
|
1575
|
-
h('select', { className: 'dcAgentField', value, 'aria-label': `${sourceField}
|
|
1589
|
+
h('input', { className: 'dcAgentField', value: query, placeholder: '模糊搜索字段名称 / 英文键 / 别名', 'aria-label': `${sourceField} 搜索映射字段`, onChange: (event) => setQuery(event.target.value) }),
|
|
1590
|
+
h('select', { className: 'dcAgentField', value: category, 'aria-label': `${sourceField} 映射大类`, onChange: (event) => setCategory(event.target.value) },
|
|
1591
|
+
h('option', { value: '' }, '全部维度'),
|
|
1592
|
+
groups.map(([id, title]) => h('option', { key: id, value: id }, title))),
|
|
1593
|
+
h('select', { className: 'dcAgentField', size: 8, style: { height: 'auto', minHeight: '180px' }, value, 'aria-label': `${sourceField} 字段映射`, onChange: (event) => {
|
|
1576
1594
|
onChange(sourceField, event.target.value);
|
|
1577
1595
|
const details = event.currentTarget.closest('details');
|
|
1578
1596
|
if (details) { details.open = false; details.querySelector('summary')?.focus(); }
|
|
1579
1597
|
} },
|
|
1580
1598
|
h('option', { value: '' }, '不参与匹配 / 补全'),
|
|
1581
|
-
|
|
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]) =>
|
|
1582
1601
|
h('option', { key, value: key, disabled: occupied(key) }, `${name}${occupied(key) ? '(已被其他列使用)' : ''}`)))),
|
|
1583
1602
|
),
|
|
1584
|
-
|
|
1603
|
+
h('p', { className: 'dcAgentHint', role: 'status' }, filteredFields.length ? `匹配 ${filteredFields.length} 个字段;选择后采用。` : '无匹配字段;当前选择保持不变。'),
|
|
1585
1604
|
),
|
|
1605
|
+
value === 'beneficial_owner_first_name' || value.startsWith('financial_') ? h('p', { className: 'dcAgentHint' }, value === 'beneficial_owner_first_name' ? '仅取返回首条受益所有人,不代表唯一或最大受益人。' : '取返回首个报告期,不跨期补值;报告期保留在结果取值说明中。') : null,
|
|
1586
1606
|
h('small', { className: 'dcAgentMappingStatus', 'data-status': value ? 'confirmed' : recommendation.candidates.length ? 'review' : 'unmatched',
|
|
1587
1607
|
style: { display: 'block', padding: '6px 0' } },
|
|
1588
1608
|
value ? `${recommendation.automatic && recommendation.candidates[0]?.[0] === value ? '自动通过' : '已确认'}:${label};可手动调整` : recommendation.candidates.length ? `待人工确认:${recommendation.conflict ? '重复列可确认使用同一补全字段;主体标识只选一列' : recommendation.reason}` : '未匹配:保留原列,可手动选择字段'),
|
|
@@ -1670,10 +1690,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1670
1690
|
return requestJson(path, body === undefined ? 'GET' : 'POST', body);
|
|
1671
1691
|
}
|
|
1672
1692
|
|
|
1673
|
-
async function waitForQccCommand(commandId, attempts = 180) {
|
|
1693
|
+
async function waitForQccCommand(commandId, attempts = 180, onStatus) {
|
|
1674
1694
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1675
1695
|
const response = await api(`/data-cleaning/api/g5/commands/${encodeURIComponent(commandId)}`);
|
|
1676
1696
|
const command = response?.command;
|
|
1697
|
+
await onStatus?.(command);
|
|
1677
1698
|
if (command?.state === 'completed' || command?.state === 'failed') return command;
|
|
1678
1699
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1679
1700
|
}
|
|
@@ -1834,7 +1855,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1834
1855
|
return response.task;
|
|
1835
1856
|
}
|
|
1836
1857
|
function watchHostedCommand(actions, sessionId, command) {
|
|
1837
|
-
void waitForQccCommand(command.commandId, 1800
|
|
1858
|
+
void waitForQccCommand(command.commandId, 1800, async (status) => {
|
|
1859
|
+
if (!status || status.state === 'prepared') return;
|
|
1860
|
+
await refreshHostedTask(actions, sessionId, command.taskId, null, true).catch(() => {});
|
|
1861
|
+
if (isCleaningSession(sessionId) && workflowTaskBySession.get(String(sessionId))?.id === command.taskId) actions.setCommandProgress?.({ taskId: command.taskId, ...status.progress });
|
|
1862
|
+
}).then(async (completed) => {
|
|
1838
1863
|
await refreshHostedTask(actions, sessionId, command.taskId, completed.run);
|
|
1839
1864
|
if (completed.state === 'failed') throw new Error(completed.error?.message || 'Host 执行失败,请在工作台查看状态。');
|
|
1840
1865
|
}).catch((error) => {
|
|
@@ -1893,6 +1918,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1893
1918
|
setError: (draft, error) => { draft.error = error; },
|
|
1894
1919
|
setInput: (draft, input) => { draft.input = input; },
|
|
1895
1920
|
setWorkflowContract: (draft, value) => { draft.workflowContract = value; },
|
|
1921
|
+
setCommandProgress: (draft, value) => { draft.commandProgress = value; },
|
|
1896
1922
|
setWorkflowTask: (draft, value) => { draft.workflowTask = value ?? null; },
|
|
1897
1923
|
setWorkflowTasks: (draft, value) => { draft.workflowTasks = Array.isArray(value) ? value : []; },
|
|
1898
1924
|
setTaskTitle: (draft, value) => { draft.taskTitle = String(value ?? ''); },
|
|
@@ -3148,6 +3174,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3148
3174
|
|
|
3149
3175
|
/** 模型工具 tool.call.toolview 卡片元数据(wire 名 → 展示文案)。 */
|
|
3150
3176
|
const TOOL_VIEW_META = {
|
|
3177
|
+
data_cleaning_qcc_run: { icon: '🧩', label: '企查查清洗补全结果', hint: '已处理包含未匹配或失败;不等于已补全。' },
|
|
3151
3178
|
data_clean_rows: { icon: '🧹', label: '数据清洗', hint: '去重 · 剔除非法金额 · 缺失补 0' },
|
|
3152
3179
|
data_complete_rows: { icon: '🧩', label: '数据补全', hint: '名称 / 金额 / 手机号归一' },
|
|
3153
3180
|
data_profile: { icon: '🩺', label: '数据体检', hint: '缺失率 · 去重值 · 金额分布' },
|
|
@@ -3186,6 +3213,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3186
3213
|
),
|
|
3187
3214
|
meta.hint ? h('div', { className: 'dcAgentToolCardHint' }, meta.hint) : null,
|
|
3188
3215
|
summary ? h('div', { className: 'dcAgentToolCardBody' }, summary) : null,
|
|
3216
|
+
toolName === 'data_cleaning_qcc_run' ? h('div', { className: 'dcAgentArtifactList' },
|
|
3217
|
+
[...summary.matchAll(/\[([^\]]+)\]\((\/data-cleaning\/api\/workflow\/tasks\/[a-zA-Z0-9%_-]+\/artifacts\/[a-zA-Z0-9%_-]+(?:\?preview=1)?)\)/g)].map((match, index) =>
|
|
3218
|
+
h('a', { key: index, className: 'dcAgentButton', href: match[2], target: '_blank', rel: 'noopener noreferrer' }, match[1]))) : null,
|
|
3189
3219
|
);
|
|
3190
3220
|
}
|
|
3191
3221
|
|
|
@@ -3429,6 +3459,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3429
3459
|
const selectedDomains = useStore((state) => state.selectedDomains);
|
|
3430
3460
|
const qccCapabilities = useStore((state) => state.qccCapabilities);
|
|
3431
3461
|
const qccRun = useStore((state) => state.qccRun);
|
|
3462
|
+
const commandProgress = useStore((state) => state.commandProgress);
|
|
3432
3463
|
const paidConfirmed = useStore((state) => state.paidConfirmed);
|
|
3433
3464
|
const jobs = useStore((state) => state.jobs);
|
|
3434
3465
|
const activeSessionId = useStore((state) => state.activeSessionId);
|
|
@@ -4325,10 +4356,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4325
4356
|
h('div', { className: 'dcAgentRow' }, h('button', { type: 'button', className: 'dcAgentButton is-primary', onClick: () => actions.setStep('download') }, '进入下载数据')),
|
|
4326
4357
|
);
|
|
4327
4358
|
} else if (step === 'download') {
|
|
4328
|
-
const availableArtifacts = cachedTask?.artifacts || [];
|
|
4359
|
+
const availableArtifacts = (cachedTask?.artifacts || []).filter(artifact => artifact.kind !== 'review' || artifact.rowCount > 0);
|
|
4329
4360
|
const canCreateArtifacts = Boolean(exportRows()?.length) && ['rules_confirmed', 'diagnosed', 'export_ready', 'partial'].includes(cachedTask?.state);
|
|
4330
4361
|
pane = h('div', { className: 'dcAgentPane' },
|
|
4331
|
-
h('p', { className: 'dcAgentHint' }, '
|
|
4362
|
+
h('p', { className: 'dcAgentHint' }, '结果文件由 Host 保存;仅在有待核验记录时展示异常清单。任务或插件重启后,可从任务历史继续下载;无需再次调用企查查。'),
|
|
4332
4363
|
h('div', { className: 'dcAgentGrid' },
|
|
4333
4364
|
stat('输入行数', dataset ? dataset.rowCount : workflowTask?.source?.rowCount ?? '—'),
|
|
4334
4365
|
stat('匹配补全', qccEnrichedCount, qccEnrichedCount > 0 ? 'good' : null),
|
|
@@ -4336,6 +4367,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4336
4367
|
stat('任务状态', WORKFLOW_STATE_LABELS[cachedTask?.state] || cachedTask?.state || '进行中'),
|
|
4337
4368
|
),
|
|
4338
4369
|
availableArtifacts.length ? h('div', { className: 'dcAgentArtifactList' },
|
|
4370
|
+
availableArtifacts.filter(artifact => artifact.format === 'xlsx').map(artifact => h('a', {
|
|
4371
|
+
key: 'preview-' + artifact.id, className: 'dcAgentButton',
|
|
4372
|
+
href: `/data-cleaning/api/workflow/tasks/${encodeURIComponent(cachedTask.id)}/artifacts/${encodeURIComponent(artifact.id)}?preview=1`,
|
|
4373
|
+
target: '_blank', rel: 'noopener noreferrer',
|
|
4374
|
+
}, `预览 / 打开:${artifact.fileName}`)),
|
|
4339
4375
|
availableArtifacts.map((artifact) => h('button', {
|
|
4340
4376
|
key: artifact.id,
|
|
4341
4377
|
type: 'button',
|
|
@@ -4348,8 +4384,8 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4348
4384
|
h('div', { className: 'dcAgentRow' },
|
|
4349
4385
|
h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('complete', 'xlsx') }, '生成并下载结果 XLSX'),
|
|
4350
4386
|
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('complete', 'csv') }, '生成并下载结果 CSV'),
|
|
4351
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('review', 'xlsx') }, '生成并下载异常清单 XLSX'),
|
|
4352
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('review', 'csv') }, '生成并下载异常清单 CSV'),
|
|
4387
|
+
qccReviewCount > 0 ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('review', 'xlsx') }, '生成并下载异常清单 XLSX') : null,
|
|
4388
|
+
qccReviewCount > 0 ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !canCreateArtifacts, onClick: () => createAndDownload('review', 'csv') }, '生成并下载异常清单 CSV') : null,
|
|
4353
4389
|
),
|
|
4354
4390
|
!canCreateArtifacts ? h('p', { className: 'dcAgentHint' }, '请先完成规则确认与清洗补全;历史任务若尚未生成制品,需要重新上传源文件。') : null,
|
|
4355
4391
|
),
|
|
@@ -4456,7 +4492,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4456
4492
|
className: 'dcAgentQccBadge',
|
|
4457
4493
|
title: qccRun ? `任务 ${qccRun.runId}` : '仅在当前用户确认使用自己的企查查账号后调用',
|
|
4458
4494
|
}, qccRun ? `QCC · ${qccRun.state}` : qccCapabilities ? (qccCapabilities.capabilities?.ready ? 'QCC · 工具已就绪' : 'QCC · 能力不完整') : 'QCC · 自动校验'),
|
|
4459
|
-
h('span', { className: 'dcAgentJobsPill', 'data-state':
|
|
4495
|
+
h('span', { className: 'dcAgentJobsPill', 'data-state': cachedTask?.state || 'idle', title: '当前清洗任务状态' }, cachedTask ? WORKFLOW_STATE_LABELS[cachedTask.state] || cachedTask.state : '尚未开始'),
|
|
4460
4496
|
h('button', {
|
|
4461
4497
|
className: 'dcAgentWbClose',
|
|
4462
4498
|
type: 'button',
|
|
@@ -4495,6 +4531,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4495
4531
|
h('span', { className: 'dcAgentStepLabel' }, st.label))),
|
|
4496
4532
|
) : null,
|
|
4497
4533
|
h('div', { className: 'dcAgentWbBody' },
|
|
4534
|
+
cachedTask && step !== 'history' ? h('section', { className: 'dcAgentSection', 'aria-label': '当前任务进展', 'aria-live': 'polite' },
|
|
4535
|
+
h('b', null, WORKFLOW_STATE_LABELS[cachedTask.state] || cachedTask.state),
|
|
4536
|
+
h('p', { className: 'dcAgentHint' }, `任务:${cachedTask.id}`),
|
|
4537
|
+
h('p', null, `${cachedTask.source?.type === 'image' ? '图片识别' : cachedTask.source?.type === 'text' ? '粘贴名单' : '上传表格'} · ${cachedTask.source?.fileName || ''} · ${cachedTask.source?.rowCount || 0} 条 · ${cachedTask.fieldSelection?.length || 0} 个补全字段`),
|
|
4538
|
+
commandProgress?.taskId === cachedTask.id && Number.isFinite(commandProgress.totalUnique) ? h('p', null, `已处理主体 ${commandProgress.completedUnique}/${commandProgress.totalUnique}(去重后,含未匹配或失败,不代表补全成功)`) : null,
|
|
4539
|
+
h('p', null, `已补全 ${qccEnrichedCount} 条 · 待核验 ${qccReviewCount} 条(按输入行统计)`),
|
|
4540
|
+
) : null,
|
|
4498
4541
|
error ? h('div', { className: 'dcAgentError', role: 'alert' }, error) : null,
|
|
4499
4542
|
step !== 'history' ? h('details', { className: 'dcAgentSection' },
|
|
4500
4543
|
h('summary', null, '使用说明与任务流程'),
|
|
@@ -4588,7 +4631,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4588
4631
|
|
|
4589
4632
|
// 模型工具的 tool.call.toolview 富化卡片(keyed by wire name,替代裸 JSON 摘要)。
|
|
4590
4633
|
// 说明:分三条独立 inject(而非 generator)——测试 shim 对 inject 回调仅执行一次并 push 其返回值。
|
|
4591
|
-
const toolviewKeys = ['data_clean_rows', 'data_complete_rows', 'data_profile', 'data_cleaning_extract_image_companies'];
|
|
4634
|
+
const toolviewKeys = ['data_clean_rows', 'data_complete_rows', 'data_profile', 'data_cleaning_extract_image_companies', 'data_cleaning_qcc_run'];
|
|
4592
4635
|
for (const key of toolviewKeys) {
|
|
4593
4636
|
ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({
|
|
4594
4637
|
name: 'tool.call.toolview',
|
package/lib/qcc-command.js
CHANGED
|
@@ -104,6 +104,7 @@ export function serializeQccCommandPrompt(command) {
|
|
|
104
104
|
`安全任务凭证:${command.commandId}`,
|
|
105
105
|
'',
|
|
106
106
|
`发送本说明后,请仅调用一次数据清洗补全执行工具(${TOOL_QCC_COMMAND}),参数只传递上述安全任务凭证。`,
|
|
107
|
+
'完成后请在对话中列出工具返回的 XLSX 文件名、预览和下载链接;只使用实际返回的链接,不编造文件或要求源文件路径。',
|
|
107
108
|
'不要要求重复粘贴企业名单,不要直接调用 mcp__qcc-* 工具,不要重试、扩大名单或追加字段。',
|
|
108
109
|
input.workflowOwned
|
|
109
110
|
? '发送即执行已确认方案,不再询问额度或写回确认。Host 会自动生成新的清洗补全结果 XLSX,原始上传文件不修改。不要寻找源文件路径、运行 Bash、编写导出脚本或另行覆盖源文件。'
|
|
@@ -182,6 +183,7 @@ export class QccCommandStore {
|
|
|
182
183
|
runId: record.runId,
|
|
183
184
|
error: record.error,
|
|
184
185
|
estimate: record.estimate,
|
|
186
|
+
progress: record.progress ?? null,
|
|
185
187
|
workflowOwned: record.input.workflowOwned === true,
|
|
186
188
|
delivery: record.delivery ?? null,
|
|
187
189
|
expiresInMs: this.ttlMs,
|
|
@@ -233,6 +235,7 @@ export class QccCommandStore {
|
|
|
233
235
|
return this.runs.retryCompanies(record.input.runId, record.input.companyNames, this.bridge, options);
|
|
234
236
|
}
|
|
235
237
|
const audit = [];
|
|
238
|
+
record.progress = { completedUnique: 0, totalUnique: record.estimate?.uniqueCompanies || 0 };
|
|
236
239
|
const result = await this.bridge.enrichRows(record.input.rows, {
|
|
237
240
|
nameField: record.input.nameField,
|
|
238
241
|
includeRisk: record.input.includeRisk,
|
|
@@ -241,6 +244,7 @@ export class QccCommandStore {
|
|
|
241
244
|
maxRows: 100,
|
|
242
245
|
execution,
|
|
243
246
|
onAudit: (event) => audit.push(event),
|
|
247
|
+
onProgress: (progress) => { record.progress = progress; },
|
|
244
248
|
});
|
|
245
249
|
return this.runs.createRun({
|
|
246
250
|
headers: record.input.headers,
|
|
@@ -260,7 +264,8 @@ export class QccCommandStore {
|
|
|
260
264
|
runId: run.runId,
|
|
261
265
|
state: run.state,
|
|
262
266
|
summary: run.summary,
|
|
263
|
-
...(record.delivery ? { deliveryState: record.delivery.state, artifactCount: record.delivery.artifacts.length
|
|
267
|
+
...(record.delivery ? { deliveryState: record.delivery.state, artifactCount: record.delivery.artifacts.length,
|
|
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)}` })) } : {}),
|
|
264
269
|
});
|
|
265
270
|
}
|
|
266
271
|
}
|
|
@@ -286,6 +291,7 @@ export function registerQccCommandTool(tools, commands) {
|
|
|
286
291
|
state: { type: 'string' },
|
|
287
292
|
deliveryState: { type: 'string' },
|
|
288
293
|
artifactCount: { type: 'integer' },
|
|
294
|
+
artifacts: { type: 'array', items: { type: 'object', additionalProperties: false, properties: { fileName: { type: 'string' }, url: { type: 'string' } }, required: ['fileName', 'url'] } },
|
|
289
295
|
summary: {
|
|
290
296
|
type: 'object',
|
|
291
297
|
additionalProperties: false,
|
|
@@ -307,7 +313,7 @@ export function registerQccCommandTool(tools, commands) {
|
|
|
307
313
|
},
|
|
308
314
|
render: (_args, value) => [{
|
|
309
315
|
type: 'text',
|
|
310
|
-
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')}`,
|
|
311
317
|
}],
|
|
312
318
|
},
|
|
313
319
|
async execute(args, exec) {
|
package/lib/qcc-field-catalog.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 任何明细列表(电话全集、海关资质、风险关联方等)都不得进入本目录。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { ACTUAL_CONTROLLER_GROUP } from 'qcc-field-contracts';
|
|
8
|
+
import { ACTUAL_CONTROLLER_GROUP, SNAPSHOT_GROUPS, SNAPSHOT_GROUP_ORDER } from 'qcc-field-contracts';
|
|
9
9
|
const field = (id, label, options = {}) => Object.freeze({ id, label, ...options });
|
|
10
10
|
const group = (id, label, sourceTool, fields, options = {}) => Object.freeze({
|
|
11
11
|
id,
|
|
@@ -175,7 +175,8 @@ export const QCC_FIELD_CATALOG = Object.freeze([
|
|
|
175
175
|
...RELATED_RISK_KEY_FACTORS.map(([id, label]) => field(`related_risk_${id}_party_count`, `关联风险-${label}命中关联方数`)),
|
|
176
176
|
], { releaseBatch: 'batch-2', catalogVersion: RISK_FACTOR_CATALOG_VERSION }),
|
|
177
177
|
ACTUAL_CONTROLLER_GROUP,
|
|
178
|
-
|
|
178
|
+
...SNAPSHOT_GROUPS,
|
|
179
|
+
].sort((a, b) => SNAPSHOT_GROUP_ORDER.indexOf(a.id) - SNAPSHOT_GROUP_ORDER.indexOf(b.id)));
|
|
179
180
|
|
|
180
181
|
export const QCC_FIELD_SOURCE_TOOL = Object.freeze(Object.fromEntries(
|
|
181
182
|
QCC_FIELD_CATALOG.flatMap((entry) => entry.fields.map((item) => [item.id, entry.sourceTool])),
|
package/lib/qcc-runs.js
CHANGED
|
@@ -235,6 +235,7 @@ export class G5RunStore {
|
|
|
235
235
|
qcc_match_status: 'enriched',
|
|
236
236
|
...(row.qcc_field_issues || result.fieldIssues ? { qcc_field_issues: Object.entries(result.fieldIssues || {}).map(([field, issue]) => field + ': ' + issue.message).join(';') } : {}),
|
|
237
237
|
qcc_source: 'qcc-mcp',
|
|
238
|
+
...(result.snapshotNote ? { qcc_snapshot_note: result.snapshotNote } : {}),
|
|
238
239
|
};
|
|
239
240
|
} else {
|
|
240
241
|
record.rows[index] = { ...row, qcc_match_status: result.status };
|
package/lib/qcc.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - 批量补全只在 Host/Web 内处理明细;模型仍只接收摘要。
|
|
9
9
|
*/
|
|
10
10
|
import { safeAuditEvent } from './qcc-safety.js';
|
|
11
|
-
import { projectActualController } from 'qcc-field-contracts';
|
|
11
|
+
import { projectActualController, projectFirstSnapshot } from 'qcc-field-contracts';
|
|
12
12
|
import {
|
|
13
13
|
QCC_PHASE2_COMPANY_TOOLS,
|
|
14
14
|
QCC_PHASE2_HISTORY_TOOLS,
|
|
@@ -558,6 +558,7 @@ export function mapActualControllerFields(value, match) {
|
|
|
558
558
|
return result.values;
|
|
559
559
|
}
|
|
560
560
|
const SOURCE_TOOL_CONFIG = Object.freeze({
|
|
561
|
+
...Object.fromEntries(['get_beneficial_owners', 'get_financial_data'].map(tool => [tool, { name: `mcp__qcc-company__${tool}`, map: data => projectFirstSnapshot(data, tool).values }])),
|
|
561
562
|
get_actual_controller: Object.freeze({ name: QCC_TOOL_NAMES.actualController, map: mapActualControllerFields }),
|
|
562
563
|
get_company_registration_info: Object.freeze({ name: QCC_TOOL_NAMES.registration, map: mapRegistrationFields }),
|
|
563
564
|
get_company_profile: Object.freeze({ name: QCC_TOOL_NAMES.profile, map: mapProfileFields }),
|
|
@@ -928,6 +929,7 @@ export class QccHostBridge {
|
|
|
928
929
|
const sourceTools = sourceToolsForFieldSelection(options.fieldSelection, options.includeRisk);
|
|
929
930
|
let mapped = {};
|
|
930
931
|
const fieldIssues = {};
|
|
932
|
+
const snapshotNotes = [];
|
|
931
933
|
let legacyRiskTags = '';
|
|
932
934
|
for (const sourceTool of sourceTools) {
|
|
933
935
|
const config = SOURCE_TOOL_CONFIG[sourceTool];
|
|
@@ -935,6 +937,11 @@ export class QccHostBridge {
|
|
|
935
937
|
const response = await this.call(config.name, { searchKey: lockedKey, ...(config.args ?? {}) }, options);
|
|
936
938
|
const fields = config.map(response.data, match);
|
|
937
939
|
mapped = mergeMappedFields(mapped, fields);
|
|
940
|
+
if (['get_beneficial_owners', 'get_financial_data'].includes(sourceTool)) {
|
|
941
|
+
const projection = projectFirstSnapshot(response.data, sourceTool);
|
|
942
|
+
Object.assign(fieldIssues, Object.fromEntries(Object.entries(projection.issues).filter(([id]) => options.fieldSelection?.includes(id))));
|
|
943
|
+
snapshotNotes.push(sourceTool === 'get_financial_data' ? `get_financial_data:返回首期 ${projection.provenance.reportPeriod || '未返回报告期'};${projection.provenance.disclosure || '未返回披露等级'};金额原值未换算` : `get_beneficial_owners:仅取首条;返回 ${projection.provenance.recordCount} 条,不代表唯一受益人`);
|
|
944
|
+
}
|
|
938
945
|
if (sourceTool === 'get_actual_controller') {
|
|
939
946
|
Object.assign(fieldIssues, Object.fromEntries(Object.entries(projectActualController(response.data).issues)
|
|
940
947
|
.filter(([field]) => options.fieldSelection?.includes(field))));
|
|
@@ -958,6 +965,7 @@ export class QccHostBridge {
|
|
|
958
965
|
const fields = projectSelectedFields(mapped, options.fieldSelection);
|
|
959
966
|
if (options.includeRisk) fields.risk_tags = legacyRiskTags;
|
|
960
967
|
return { status: 'enriched', companyName: String(match?.companyName ?? ''), fields,
|
|
968
|
+
...(snapshotNotes.length ? { snapshotNote: snapshotNotes.join(';') } : {}),
|
|
961
969
|
...(Object.keys(fieldIssues).length ? { fieldIssues } : {}) };
|
|
962
970
|
}
|
|
963
971
|
|
|
@@ -1061,6 +1069,7 @@ export class QccHostBridge {
|
|
|
1061
1069
|
...Object.fromEntries(Object.entries(result.fields || {}).filter(([key]) => row[key] == null || (typeof row[key] === 'string' && row[key].trim() === ''))),
|
|
1062
1070
|
qcc_match_status: 'enriched',
|
|
1063
1071
|
qcc_source: 'qcc-mcp',
|
|
1072
|
+
...(result.snapshotNote ? { qcc_snapshot_note: result.snapshotNote } : {}),
|
|
1064
1073
|
...(row.qcc_field_issues || result.fieldIssues ? { qcc_field_issues: Object.entries(result.fieldIssues || {}).map(([field, issue]) => field + ': ' + issue.message).join(';') } : {}),
|
|
1065
1074
|
};
|
|
1066
1075
|
}
|
package/lib/web.js
CHANGED
|
@@ -559,6 +559,16 @@ export function mountWebRoutes(wctx, { logger, report, TOOL_NAME, SKILL_NAME })
|
|
|
559
559
|
const artifact = current.artifacts.find((item) => item.id === artifactId);
|
|
560
560
|
if (!artifact) throw new ArtifactError('DC_ARTIFACT_NOT_FOUND', 'Artifact not found for this task.', 404);
|
|
561
561
|
const bytes = await artifactStore.read(taskId, artifact);
|
|
562
|
+
if (artifact.format === 'xlsx' && new URL(req.url, 'http://127.0.0.1').searchParams.get('preview') === '1') {
|
|
563
|
+
const parsed = await parseXlsx(bytes);
|
|
564
|
+
const escape = value => String(value ?? '').replace(/[&<>\"]/g, c => ({ '&': '&', '<': '<', '>': '>', '\"': '"' }[c]));
|
|
565
|
+
const columns = parsed.headers || [];
|
|
566
|
+
const rows = (parsed.rows || []).slice(0, 50);
|
|
567
|
+
const download = `/data-cleaning/api/workflow/tasks/${encodeURIComponent(taskId)}/artifacts/${encodeURIComponent(artifactId)}`;
|
|
568
|
+
const html = `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>${escape(artifact.fileName)}</title><style>body{font:14px system-ui;padding:24px;color:#243044}table{border-collapse:collapse}td,th{border:1px solid #dde5ef;padding:8px;min-width:100px;white-space:pre-wrap}th{background:#e5f3ff}a{color:#0875ce}</style><h1>${escape(artifact.fileName)}</h1><p>任务 ${escape(taskId)} · 共 ${artifact.rowCount} 行,预览前 50 行;源文件未修改。</p><a href="${download}">下载完整 Excel</a><table><thead><tr>${columns.map(c => `<th>${escape(c)}</th>`).join('')}</tr></thead><tbody>${rows.map(row => `<tr>${columns.map(c => `<td>${escape(row[c])}</td>`).join('')}</tr>`).join('')}</tbody></table>`;
|
|
569
|
+
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' });
|
|
570
|
+
return res.end(html);
|
|
571
|
+
}
|
|
562
572
|
const fallback = artifact.format === 'xlsx' ? 'export.xlsx' : 'export.csv';
|
|
563
573
|
const asciiName = String(artifact.fileName || fallback).replace(/[^\x20-\x7e]/g, '_').replace(/["\\]/g, '_');
|
|
564
574
|
res.writeHead(200, {
|
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.19",
|
|
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,6 +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.19.md",
|
|
43
44
|
"docs/TEMPLATE-MAPPING-ACCEPTANCE.md",
|
|
44
45
|
"docs/UI-WORKFLOW-V2.md",
|
|
45
46
|
"docs/UI-WORKFLOW-V2-MIGRATION.md",
|
|
@@ -117,7 +118,7 @@
|
|
|
117
118
|
"node": ">=20"
|
|
118
119
|
},
|
|
119
120
|
"dependencies": {
|
|
120
|
-
"qcc-field-contracts": "0.
|
|
121
|
+
"qcc-field-contracts": "0.2.0",
|
|
121
122
|
"xlsx": "^0.18.5"
|
|
122
123
|
},
|
|
123
124
|
"peerDependencies": {
|