dsh-data-cleaning-agent 0.3.0 → 0.5.0
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 +71 -0
- package/README.en.md +48 -7
- package/README.md +44 -6
- package/docs/COMPATIBILITY.md +54 -2
- package/docs/G5-E2E-RUNBOOK.md +110 -0
- package/docs/G5-HOST-BRIDGE.md +136 -0
- package/docs/PHASE2-ACCEPTANCE.md +133 -0
- package/docs/PHASE3-ACCEPTANCE.md +129 -0
- package/docs/QCC-ENRICHMENT-DESIGN.md +5 -2
- package/docs/QCC-PHASES-ROADMAP.md +33 -6
- package/docs/RELEASE-0.4.0.md +66 -0
- package/docs/RELEASE-0.5.0.md +84 -0
- package/docs/USER-GUIDE.md +85 -6
- package/lib/client.js +1132 -16
- package/lib/index.js +2 -0
- package/lib/qcc-phase2-acceptance.js +191 -0
- package/lib/qcc-phase2.js +99 -0
- package/lib/qcc-phase3-batch.js +437 -0
- package/lib/qcc-phase3.js +224 -0
- package/lib/qcc-runs.js +322 -0
- package/lib/qcc-safety.js +77 -0
- package/lib/qcc.js +735 -0
- package/lib/skill-enrich.js +54 -9
- package/lib/web.js +351 -2
- package/package.json +23 -4
package/lib/index.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* 2. ctx.skills —— 注册内嵌 Skill `data-cleaning`(正文指引模型调上述工具)
|
|
7
7
|
* 3. webServer/webRuntime —— 挂载上传/解析/同步清洗补全/异步任务/UI 路由
|
|
8
8
|
* 4. ctx.jobs + ctx.storageDomain —— 异步任务状态机(web 组合内可用)
|
|
9
|
+
* 5. ctx.tools.execute —— G5 QCC Host Bridge(程序化批量补全,web 组合内可用)
|
|
9
10
|
*
|
|
10
11
|
* headless 组合无 webServer/webRuntime:用 ctx.get() 存在性守卫跳过 web 半区,
|
|
11
12
|
* 工具与 Skill 照常注册(端到端真实模型路径依赖它们)。
|
|
@@ -26,6 +27,7 @@ export function apply(ctx, config) {
|
|
|
26
27
|
skillRegistered: false,
|
|
27
28
|
webMounted: false,
|
|
28
29
|
webSkipped: false,
|
|
30
|
+
qccBridgeMounted: false,
|
|
29
31
|
};
|
|
30
32
|
const disposers = [];
|
|
31
33
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import {
|
|
3
|
+
QCC_PHASE2_COMPANY_TOOLS,
|
|
4
|
+
QCC_PHASE2_HISTORY_TOOLS,
|
|
5
|
+
qccToolRuntimeCandidates,
|
|
6
|
+
} from './qcc-phase2.js';
|
|
7
|
+
|
|
8
|
+
export const QCC_PHASE2_ACCEPTANCE_SCHEMA_VERSION = 1;
|
|
9
|
+
export const QCC_PHASE2_EVIDENCE_KIND = 'qcc-phase2-real-tool-transcript';
|
|
10
|
+
export const QCC_PHASE2_ACCEPTANCE_FLOORS = Object.freeze({
|
|
11
|
+
minimumRecords: 20,
|
|
12
|
+
minimumCurrentDimensions: 15,
|
|
13
|
+
requiredHistoryDimensions: Object.keys(QCC_PHASE2_HISTORY_TOOLS).length,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const CURRENT_TOOLS = new Map(Object.entries(QCC_PHASE2_COMPANY_TOOLS));
|
|
17
|
+
const HISTORY_TOOLS = new Map(Object.entries(QCC_PHASE2_HISTORY_TOOLS));
|
|
18
|
+
const DELIVERED_STATUSES = new Set(['resolved', 'no_data']);
|
|
19
|
+
const ALLOWED_STATUSES = new Set([
|
|
20
|
+
...DELIVERED_STATUSES,
|
|
21
|
+
'permission_required',
|
|
22
|
+
'not_available',
|
|
23
|
+
'error',
|
|
24
|
+
]);
|
|
25
|
+
const OPAQUE_REFERENCE = /^row-[0-9]{3,6}$/;
|
|
26
|
+
|
|
27
|
+
function hasOwn(value, key) {
|
|
28
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hasMeaningfulValue(value) {
|
|
32
|
+
if (value === null || value === undefined) return false;
|
|
33
|
+
if (typeof value === 'string') return value.trim().length > 0;
|
|
34
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
35
|
+
if (typeof value === 'object') return Object.keys(value).length > 0;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function evaluateField(field) {
|
|
40
|
+
if (!field || typeof field !== 'object' || Array.isArray(field)) return false;
|
|
41
|
+
if (typeof field.key !== 'string' || field.key.length === 0) return false;
|
|
42
|
+
if (!hasOwn(field, 'value') || !hasOwn(field, 'sourceValue')) return false;
|
|
43
|
+
if (!hasMeaningfulValue(field.value) || !hasMeaningfulValue(field.sourceValue)) return false;
|
|
44
|
+
return isDeepStrictEqual(field.value, field.sourceValue);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function evaluateDimension(dimension, knownTools) {
|
|
48
|
+
const failures = [];
|
|
49
|
+
if (!dimension || typeof dimension !== 'object' || Array.isArray(dimension)) {
|
|
50
|
+
return { id: null, delivered: false, failures: ['DIMENSION_INVALID'] };
|
|
51
|
+
}
|
|
52
|
+
const id = typeof dimension.id === 'string' ? dimension.id : null;
|
|
53
|
+
const expectedTool = id ? knownTools.get(id) : null;
|
|
54
|
+
if (!expectedTool) failures.push('DIMENSION_UNKNOWN');
|
|
55
|
+
if (!qccToolRuntimeCandidates(expectedTool ?? '').includes(dimension.sourceTool)) {
|
|
56
|
+
failures.push('SOURCE_TOOL_MISMATCH');
|
|
57
|
+
}
|
|
58
|
+
if (!ALLOWED_STATUSES.has(dimension.status)) failures.push('DIMENSION_STATUS_INVALID');
|
|
59
|
+
|
|
60
|
+
const fieldsAreArray = Array.isArray(dimension.fields);
|
|
61
|
+
const fields = fieldsAreArray ? dimension.fields : [];
|
|
62
|
+
if (!fieldsAreArray) failures.push('FIELDS_ARRAY_REQUIRED');
|
|
63
|
+
if (dimension.status === 'resolved') {
|
|
64
|
+
if (fields.length === 0) failures.push('RESOLVED_FIELDS_REQUIRED');
|
|
65
|
+
if (!fields.every(evaluateField)) failures.push('VALUE_NOT_VERBATIM');
|
|
66
|
+
}
|
|
67
|
+
if (dimension.status === 'no_data' && fields.length > 0) {
|
|
68
|
+
failures.push('NO_DATA_MUST_NOT_CARRY_VALUES');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
id,
|
|
73
|
+
delivered: failures.length === 0 && DELIVERED_STATUSES.has(dimension.status),
|
|
74
|
+
failures,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function evaluateRecord(record, index, { requireHistory }) {
|
|
79
|
+
const safeReference = OPAQUE_REFERENCE.test(record?.reference ?? '')
|
|
80
|
+
? record.reference
|
|
81
|
+
: `row-${String(index + 1).padStart(3, '0')}`;
|
|
82
|
+
const failures = [];
|
|
83
|
+
if (!OPAQUE_REFERENCE.test(record?.reference ?? '')) failures.push('REFERENCE_NOT_OPAQUE');
|
|
84
|
+
if (record?.entityStatus !== 'resolved') failures.push('ENTITY_NOT_RESOLVED');
|
|
85
|
+
|
|
86
|
+
const dimensions = Array.isArray(record?.dimensions) ? record.dimensions : [];
|
|
87
|
+
if (!Array.isArray(record?.dimensions)) failures.push('DIMENSIONS_REQUIRED');
|
|
88
|
+
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
const deliveredStatuses = new Map();
|
|
91
|
+
let currentDelivered = 0;
|
|
92
|
+
let historyDelivered = 0;
|
|
93
|
+
for (const dimension of dimensions) {
|
|
94
|
+
const domain = dimension?.domain;
|
|
95
|
+
const tools = domain === 'history' ? HISTORY_TOOLS : domain === 'company' ? CURRENT_TOOLS : null;
|
|
96
|
+
if (!tools) {
|
|
97
|
+
failures.push('DIMENSION_DOMAIN_INVALID');
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const uniqueKey = `${domain}:${dimension?.id ?? ''}`;
|
|
101
|
+
if (seen.has(uniqueKey)) {
|
|
102
|
+
failures.push('DIMENSION_DUPLICATE');
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
seen.add(uniqueKey);
|
|
106
|
+
const result = evaluateDimension(dimension, tools);
|
|
107
|
+
failures.push(...result.failures);
|
|
108
|
+
if (result.delivered) deliveredStatuses.set(uniqueKey, dimension.status);
|
|
109
|
+
if (result.delivered && domain === 'company') currentDelivered += 1;
|
|
110
|
+
if (result.delivered && domain === 'history') historyDelivered += 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
deliveredStatuses.get('company:resolveEntity') !== 'resolved'
|
|
115
|
+
|| deliveredStatuses.get('company:registration') !== 'resolved'
|
|
116
|
+
) {
|
|
117
|
+
failures.push('IDENTITY_EVIDENCE_REQUIRED');
|
|
118
|
+
}
|
|
119
|
+
if (currentDelivered < QCC_PHASE2_ACCEPTANCE_FLOORS.minimumCurrentDimensions) {
|
|
120
|
+
failures.push('CURRENT_DIMENSION_FLOOR_NOT_MET');
|
|
121
|
+
}
|
|
122
|
+
if (requireHistory && historyDelivered < QCC_PHASE2_ACCEPTANCE_FLOORS.requiredHistoryDimensions) {
|
|
123
|
+
failures.push('HISTORY_DIMENSION_FLOOR_NOT_MET');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
reference: safeReference,
|
|
128
|
+
passed: failures.length === 0,
|
|
129
|
+
currentDelivered,
|
|
130
|
+
historyDelivered,
|
|
131
|
+
failures: [...new Set(failures)].sort(),
|
|
132
|
+
entityStatus: ['resolved', 'ambiguous', 'unresolved'].includes(record?.entityStatus)
|
|
133
|
+
? record.entityStatus
|
|
134
|
+
: 'invalid',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 评估 0.4.0 真实 E2E 证据。返回值严格不携带企业名、信用代码或字段值。
|
|
140
|
+
*/
|
|
141
|
+
export function evaluateQccPhase2Evidence(evidence, { requireHistory = false } = {}) {
|
|
142
|
+
const globalFailures = [];
|
|
143
|
+
if (!evidence || typeof evidence !== 'object' || Array.isArray(evidence)) {
|
|
144
|
+
globalFailures.push('EVIDENCE_INVALID');
|
|
145
|
+
}
|
|
146
|
+
if (evidence?.schemaVersion !== QCC_PHASE2_ACCEPTANCE_SCHEMA_VERSION) {
|
|
147
|
+
globalFailures.push('SCHEMA_VERSION_UNSUPPORTED');
|
|
148
|
+
}
|
|
149
|
+
if (evidence?.evidenceKind !== QCC_PHASE2_EVIDENCE_KIND) {
|
|
150
|
+
globalFailures.push('EVIDENCE_KIND_UNVERIFIED');
|
|
151
|
+
}
|
|
152
|
+
// 真实 E2E 证据必须显式声明 synthetic:false;缺省值也按未验证处理,保持 fail-closed。
|
|
153
|
+
if (evidence?.synthetic !== false) globalFailures.push('SYNTHETIC_EVIDENCE_REJECTED');
|
|
154
|
+
if (!Array.isArray(evidence?.records)) globalFailures.push('RECORDS_REQUIRED');
|
|
155
|
+
const records = Array.isArray(evidence?.records) ? evidence.records : [];
|
|
156
|
+
if (records.length < QCC_PHASE2_ACCEPTANCE_FLOORS.minimumRecords) {
|
|
157
|
+
globalFailures.push('RECORD_FLOOR_NOT_MET');
|
|
158
|
+
}
|
|
159
|
+
if (requireHistory && evidence?.historyAccess !== 'enterprise-certified') {
|
|
160
|
+
globalFailures.push('ENTERPRISE_HISTORY_ACCESS_NOT_VERIFIED');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const recordReports = records.map((record, index) => evaluateRecord(record, index, { requireHistory }));
|
|
164
|
+
const references = recordReports.map((record) => record.reference);
|
|
165
|
+
if (new Set(references).size !== references.length) globalFailures.push('REFERENCE_DUPLICATE');
|
|
166
|
+
|
|
167
|
+
const failedRecords = recordReports.filter((record) => !record.passed);
|
|
168
|
+
const summary = {
|
|
169
|
+
recordCount: recordReports.length,
|
|
170
|
+
passedRecords: recordReports.length - failedRecords.length,
|
|
171
|
+
failedRecords: failedRecords.length,
|
|
172
|
+
ambiguousRecords: recordReports.filter((record) => record.entityStatus === 'ambiguous').length,
|
|
173
|
+
unresolvedRecords: recordReports.filter((record) => record.entityStatus === 'unresolved').length,
|
|
174
|
+
minimumCurrentDimensions: recordReports.length === 0
|
|
175
|
+
? 0
|
|
176
|
+
: Math.min(...recordReports.map((record) => record.currentDelivered)),
|
|
177
|
+
minimumHistoryDimensions: recordReports.length === 0
|
|
178
|
+
? 0
|
|
179
|
+
: Math.min(...recordReports.map((record) => record.historyDelivered)),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
schemaVersion: QCC_PHASE2_ACCEPTANCE_SCHEMA_VERSION,
|
|
184
|
+
passed: globalFailures.length === 0 && failedRecords.length === 0,
|
|
185
|
+
requireHistory,
|
|
186
|
+
floors: QCC_PHASE2_ACCEPTANCE_FLOORS,
|
|
187
|
+
summary,
|
|
188
|
+
globalFailures: [...new Set(globalFailures)].sort(),
|
|
189
|
+
failures: failedRecords.map(({ reference, failures }) => ({ reference, codes: failures })),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QCC 0.4.0 二期工具契约。
|
|
3
|
+
*
|
|
4
|
+
* 这里只固化已经在本地 QCC MCP 一手源码注册表中核对过的工具名。
|
|
5
|
+
* 不固化上游响应字段:方案 A 由模型中介解读工具返回,并必须保留原值。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const companyTool = (name) => `mcp__qcc-company__${name}`;
|
|
9
|
+
const historyTool = (name) => `mcp__qcc-history__${name}`;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* qcc-dsh-mcp-oauth 0.1.7 把 serverKey 直接作为 serverName,因而注册为
|
|
13
|
+
* `mcp__company__*` / `mcp__history__*`;修复版与手工配置使用文档约定的
|
|
14
|
+
* `mcp__qcc-company__*` / `mcp__qcc-history__*`。Bridge 同时兼容两者,
|
|
15
|
+
* 但始终把带 qcc- 前缀的名称作为规范契约。
|
|
16
|
+
*/
|
|
17
|
+
export function qccToolRuntimeCandidates(canonicalName) {
|
|
18
|
+
const name = String(canonicalName ?? '');
|
|
19
|
+
const legacy = name.replace(/^mcp__qcc-(company|risk|ipr|operation|history|executive)__/, 'mcp__$1__');
|
|
20
|
+
return legacy === name ? Object.freeze([name]) : Object.freeze([name, legacy]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const QCC_PHASE2_COMPANY_TOOLS = Object.freeze({
|
|
24
|
+
resolveEntity: companyTool('get_company_by_query'),
|
|
25
|
+
registration: companyTool('get_company_registration_info'),
|
|
26
|
+
profile: companyTool('get_company_profile'),
|
|
27
|
+
verifyIdentity: companyTool('verify_company_accuracy'),
|
|
28
|
+
actualController: companyTool('get_actual_controller'),
|
|
29
|
+
beneficialOwners: companyTool('get_beneficial_owners'),
|
|
30
|
+
shareholders: companyTool('get_shareholder_info'),
|
|
31
|
+
externalInvestments: companyTool('get_external_investments'),
|
|
32
|
+
branches: companyTool('get_branches'),
|
|
33
|
+
keyPersonnel: companyTool('get_key_personnel'),
|
|
34
|
+
changes: companyTool('get_change_records'),
|
|
35
|
+
annualReports: companyTool('get_annual_reports'),
|
|
36
|
+
contact: companyTool('get_contact_info'),
|
|
37
|
+
taxInvoice: companyTool('get_tax_invoice_info'),
|
|
38
|
+
listing: companyTool('get_listing_info'),
|
|
39
|
+
financial: companyTool('get_financial_data'),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const QCC_PHASE2_HISTORY_TOOLS = Object.freeze({
|
|
43
|
+
shareholders: historyTool('get_historical_shareholders'),
|
|
44
|
+
legalRepresentative: historyTool('get_historical_legal_rep'),
|
|
45
|
+
executives: historyTool('get_historical_executives'),
|
|
46
|
+
registration: historyTool('get_historical_registration'),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 用户可选的维度组。identity 是任何任务的必需步骤;其余组按用户意图调用。
|
|
51
|
+
* 字段缺失、无权或上游不可用时,应保留状态而不是补造值。
|
|
52
|
+
*/
|
|
53
|
+
export const QCC_PHASE2_DIMENSION_GROUPS = Object.freeze({
|
|
54
|
+
identity: Object.freeze({
|
|
55
|
+
label: '主体锚定与核验',
|
|
56
|
+
access: 'basic',
|
|
57
|
+
tools: Object.freeze([
|
|
58
|
+
QCC_PHASE2_COMPANY_TOOLS.resolveEntity,
|
|
59
|
+
QCC_PHASE2_COMPANY_TOOLS.registration,
|
|
60
|
+
QCC_PHASE2_COMPANY_TOOLS.verifyIdentity,
|
|
61
|
+
]),
|
|
62
|
+
}),
|
|
63
|
+
panorama: Object.freeze({
|
|
64
|
+
label: '企业全景',
|
|
65
|
+
access: 'basic',
|
|
66
|
+
tools: Object.freeze([
|
|
67
|
+
QCC_PHASE2_COMPANY_TOOLS.profile,
|
|
68
|
+
QCC_PHASE2_COMPANY_TOOLS.contact,
|
|
69
|
+
QCC_PHASE2_COMPANY_TOOLS.taxInvoice,
|
|
70
|
+
QCC_PHASE2_COMPANY_TOOLS.listing,
|
|
71
|
+
QCC_PHASE2_COMPANY_TOOLS.financial,
|
|
72
|
+
]),
|
|
73
|
+
}),
|
|
74
|
+
ownership: Object.freeze({
|
|
75
|
+
label: '股权穿透',
|
|
76
|
+
access: 'basic',
|
|
77
|
+
tools: Object.freeze([
|
|
78
|
+
QCC_PHASE2_COMPANY_TOOLS.actualController,
|
|
79
|
+
QCC_PHASE2_COMPANY_TOOLS.beneficialOwners,
|
|
80
|
+
QCC_PHASE2_COMPANY_TOOLS.shareholders,
|
|
81
|
+
QCC_PHASE2_COMPANY_TOOLS.externalInvestments,
|
|
82
|
+
]),
|
|
83
|
+
}),
|
|
84
|
+
governance: Object.freeze({
|
|
85
|
+
label: '组织与沿革',
|
|
86
|
+
access: 'basic',
|
|
87
|
+
tools: Object.freeze([
|
|
88
|
+
QCC_PHASE2_COMPANY_TOOLS.branches,
|
|
89
|
+
QCC_PHASE2_COMPANY_TOOLS.keyPersonnel,
|
|
90
|
+
QCC_PHASE2_COMPANY_TOOLS.changes,
|
|
91
|
+
QCC_PHASE2_COMPANY_TOOLS.annualReports,
|
|
92
|
+
]),
|
|
93
|
+
}),
|
|
94
|
+
history: Object.freeze({
|
|
95
|
+
label: '历史工商',
|
|
96
|
+
access: 'enterprise-certified',
|
|
97
|
+
tools: Object.freeze(Object.values(QCC_PHASE2_HISTORY_TOOLS)),
|
|
98
|
+
}),
|
|
99
|
+
});
|