@liuhange/dsh-gov-data-inspector 3.1.8 → 3.2.1
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/README.md +120 -1
- package/lib/__tests__/configPack.test.js +76 -0
- package/lib/__tests__/configPackLoader.test.js +90 -0
- package/lib/__tests__/configPackSchemaValidator.test.js +75 -0
- package/lib/__tests__/defaultConfigPack.test.js +27 -0
- package/lib/__tests__/govConfigPacks.test.js +59 -0
- package/lib/config-pack-schema.json +186 -0
- package/lib/configPack.js +38 -0
- package/lib/configPackLoader.js +126 -0
- package/lib/configPackSchemaValidator.js +196 -0
- package/lib/default-config-pack.json +854 -0
- package/lib/index.js +33 -61
- package/lib/inspectionOrchestrator.js +7 -5
- package/lib/local-terms-loader.js +7 -1
- package/lib/policyBasis.js +4 -1
- package/lib/semanticRuleEngine.js +1 -1
- package/package.json +5 -2
package/lib/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export const name = '@liuhange/dsh-gov-data-inspector';
|
|
2
2
|
export const inject = ['tools'];
|
|
3
|
-
import { loadJsonConfig } from '@liuhange/dsh-data-asset-shared';
|
|
4
3
|
import { resolvePolicyBasis } from './policyBasis.js';
|
|
5
4
|
import { KnowledgeBaseLoader } from './knowledgeBaseLoader.js';
|
|
6
5
|
import { InspectionOrchestrator } from './inspectionOrchestrator.js';
|
|
@@ -9,6 +8,7 @@ import { EncodingDetector } from './encodingDetector.js';
|
|
|
9
8
|
import { DataScaleGuard } from './dataScaleGuard.js';
|
|
10
9
|
import { UrlReachabilityChecker } from './urlReachabilityChecker.js';
|
|
11
10
|
import { InputBoundaryChecker } from './input-boundary-checker.js';
|
|
11
|
+
import { ConfigPackLoader } from './configPackLoader.js';
|
|
12
12
|
export function apply(ctx) {
|
|
13
13
|
ctx.tools.register({
|
|
14
14
|
name: 'inspect_gov_data',
|
|
@@ -30,6 +30,9 @@ export function apply(ctx) {
|
|
|
30
30
|
inspectionMode: { type: 'string', enum: ['guide', 'classification', 'full'], description: '巡检模式' },
|
|
31
31
|
timestamp: { type: 'string', description: '巡检时间戳(ISO8601)' },
|
|
32
32
|
policyBasis: { type: 'array', items: { type: 'string' }, description: '政策依据列表' },
|
|
33
|
+
configPackId: { type: 'string', description: '配置包标识' },
|
|
34
|
+
region: { type: 'string', description: '配置包适用地区' },
|
|
35
|
+
configPackVersion: { type: 'string', description: '配置包版本' },
|
|
33
36
|
dataSourceStatus: { type: 'object', description: '数据源获取状态' },
|
|
34
37
|
degradedMode: { type: 'boolean', description: '是否降级模式' },
|
|
35
38
|
degradedReason: { type: 'string', description: '降级原因' },
|
|
@@ -46,54 +49,7 @@ export function apply(ctx) {
|
|
|
46
49
|
if (!args?.dataSource || typeof args.dataSource !== 'string') {
|
|
47
50
|
return JSON.stringify({ error: 'GOV_DATA_INPUT_INVALID', message: 'dataSource is required' });
|
|
48
51
|
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
rulesConfig = loadJsonConfig('BUSINESS_RULES_PATH', 'config/business-rules.json', '@liuhange/dsh-data-asset-shared/config/business-rules.json');
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return JSON.stringify({ error: 'GOV_DATA_RULES_MISSING', message: '无法加载business-rules.json' });
|
|
55
|
-
}
|
|
56
|
-
const govConfigRaw = rulesConfig.govDataInspection;
|
|
57
|
-
if (!govConfigRaw || typeof govConfigRaw !== 'object') {
|
|
58
|
-
return JSON.stringify({ error: 'GOV_DATA_RULES_MISSING', message: 'govDataInspection配置段缺失' });
|
|
59
|
-
}
|
|
60
|
-
const govConfig = govConfigRaw;
|
|
61
|
-
const requiredFields = {
|
|
62
|
-
guideRequiredElements: 'string[]',
|
|
63
|
-
logicErrorRules: 'array',
|
|
64
|
-
formatRules: 'array',
|
|
65
|
-
severityMapping: 'object',
|
|
66
|
-
scoreWeights: 'object',
|
|
67
|
-
convenienceWeights: 'object',
|
|
68
|
-
gbt47949Mapping: 'object',
|
|
69
|
-
itemTypeMatching: 'object',
|
|
70
|
-
materialConciseThreshold: 'number',
|
|
71
|
-
missingFieldStandardClause: 'string',
|
|
72
|
-
dataSourcePriority: 'array',
|
|
73
|
-
dataSourceCredentials: 'object',
|
|
74
|
-
};
|
|
75
|
-
for (const [field, expectedType] of Object.entries(requiredFields)) {
|
|
76
|
-
const val = govConfig[field];
|
|
77
|
-
if (val === undefined || val === null) {
|
|
78
|
-
return JSON.stringify({ error: 'GOV_DATA_RULES_MISSING', message: `govDataInspection.${field}配置缺失` });
|
|
79
|
-
}
|
|
80
|
-
if (expectedType === 'array' && !Array.isArray(val)) {
|
|
81
|
-
return JSON.stringify({ error: 'GOV_DATA_RULES_MISSING', message: `govDataInspection.${field}应为数组` });
|
|
82
|
-
}
|
|
83
|
-
if (expectedType === 'object' && (typeof val !== 'object' || Array.isArray(val))) {
|
|
84
|
-
return JSON.stringify({ error: 'GOV_DATA_RULES_MISSING', message: `govDataInspection.${field}应为对象` });
|
|
85
|
-
}
|
|
86
|
-
if (expectedType === 'string' && typeof val !== 'string') {
|
|
87
|
-
return JSON.stringify({ error: 'GOV_DATA_RULES_MISSING', message: `govDataInspection.${field}应为字符串` });
|
|
88
|
-
}
|
|
89
|
-
if (expectedType === 'number' && typeof val !== 'number') {
|
|
90
|
-
return JSON.stringify({ error: 'GOV_DATA_RULES_MISSING', message: `govDataInspection.${field}应为数字` });
|
|
91
|
-
}
|
|
92
|
-
if (expectedType === 'string[]' && (!Array.isArray(val) || val.some((v) => typeof v !== 'string'))) {
|
|
93
|
-
return JSON.stringify({ error: 'GOV_DATA_RULES_MISSING', message: `govDataInspection.${field}应为字符串数组` });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const govConfigTyped = govConfig;
|
|
52
|
+
const { configPack, warnings: packWarnings } = ConfigPackLoader.load();
|
|
97
53
|
const encodingResult = EncodingDetector.detect(args.dataSource);
|
|
98
54
|
if (encodingResult.content === null) {
|
|
99
55
|
if (encodingResult.encodingError === '文件为空') {
|
|
@@ -113,7 +69,7 @@ export function apply(ctx) {
|
|
|
113
69
|
if (raw.trim() === '') {
|
|
114
70
|
return JSON.stringify({ error: 'GOV_DATA_INPUT_INVALID', message: '无数据,请确认输入文件内容' });
|
|
115
71
|
}
|
|
116
|
-
const inputLengthThreshold =
|
|
72
|
+
const inputLengthThreshold = configPack.inputLengthThreshold ?? 50000;
|
|
117
73
|
const tooLargeResult = InputBoundaryChecker.checkTooLarge(raw, inputLengthThreshold);
|
|
118
74
|
if (tooLargeResult.isTooLarge) {
|
|
119
75
|
return JSON.stringify({ error: 'GOV_DATA_INPUT_TOO_LARGE', message: '输入数据超过处理上限,请分批提交' });
|
|
@@ -132,12 +88,11 @@ export function apply(ctx) {
|
|
|
132
88
|
if (emptyCheck.isEmpty) {
|
|
133
89
|
return JSON.stringify({ error: 'GOV_DATA_NO_DATA', message: '未检测到有效数据' });
|
|
134
90
|
}
|
|
135
|
-
const
|
|
136
|
-
const scaleResult = DataScaleGuard.check(data, maxFieldCount);
|
|
91
|
+
const scaleResult = DataScaleGuard.check(data, undefined);
|
|
137
92
|
if (scaleResult.exceeded) {
|
|
138
93
|
return JSON.stringify({ error: 'GOV_DATA_SCALE_EXCEEDED', message: `数据量超出处理上限(字段数${scaleResult.fieldCount}),请分批处理或增加maxFieldCount配置` });
|
|
139
94
|
}
|
|
140
|
-
const credentials =
|
|
95
|
+
const credentials = configPack.dataSourceCredentials;
|
|
141
96
|
const credConfig = {
|
|
142
97
|
national: {
|
|
143
98
|
apiKey: process.env[credentials.national.apiKey],
|
|
@@ -148,7 +103,7 @@ export function apply(ctx) {
|
|
|
148
103
|
endpoint: process.env[credentials.provincial.endpoint],
|
|
149
104
|
},
|
|
150
105
|
};
|
|
151
|
-
const kbLoadResult = await KnowledgeBaseLoader.load(credConfig,
|
|
106
|
+
const kbLoadResult = await KnowledgeBaseLoader.load(credConfig, configPack.dataSourcePriority);
|
|
152
107
|
const kb = kbLoadResult.kb;
|
|
153
108
|
const degradedMode = kbLoadResult.degraded;
|
|
154
109
|
const degradedReason = kbLoadResult.degradedReason;
|
|
@@ -163,7 +118,7 @@ export function apply(ctx) {
|
|
|
163
118
|
dataSourceStatus.standard = stdRuleResult.status;
|
|
164
119
|
}
|
|
165
120
|
else {
|
|
166
|
-
standardRules =
|
|
121
|
+
standardRules = configPack.logicErrorRules;
|
|
167
122
|
}
|
|
168
123
|
if (!standardRules || standardRules.length === 0) {
|
|
169
124
|
if (!degradedMode) {
|
|
@@ -173,7 +128,7 @@ export function apply(ctx) {
|
|
|
173
128
|
}
|
|
174
129
|
let policyBasis;
|
|
175
130
|
try {
|
|
176
|
-
policyBasis = resolvePolicyBasis('GOV_DATA_INSPECTION');
|
|
131
|
+
policyBasis = resolvePolicyBasis('GOV_DATA_INSPECTION', configPack.policyBasis);
|
|
177
132
|
if (!policyBasis || policyBasis.length === 0) {
|
|
178
133
|
return JSON.stringify({ error: 'GOV_DATA_POLICY_MISSING', message: '政策依据配置缺失' });
|
|
179
134
|
}
|
|
@@ -188,26 +143,43 @@ export function apply(ctx) {
|
|
|
188
143
|
inspectionMode: mode,
|
|
189
144
|
timestamp: new Date().toISOString(),
|
|
190
145
|
policyBasis,
|
|
146
|
+
configPackId: configPack.configPackId,
|
|
147
|
+
region: configPack.region,
|
|
148
|
+
configPackVersion: configPack.configPackVersion,
|
|
191
149
|
dataSourceStatus,
|
|
192
150
|
};
|
|
193
151
|
if (degradedMode) {
|
|
194
152
|
result.degradedMode = true;
|
|
195
153
|
result.degradedReason = degradedReason;
|
|
196
154
|
}
|
|
155
|
+
const allWarnings = [...packWarnings];
|
|
197
156
|
if (mode === 'guide' || mode === 'full') {
|
|
198
|
-
const orchestrateResult = InspectionOrchestrator.orchestrate(data,
|
|
157
|
+
const orchestrateResult = InspectionOrchestrator.orchestrate(data, {
|
|
158
|
+
requiredFields: configPack.requiredFields,
|
|
159
|
+
formatRules: configPack.formatRules,
|
|
160
|
+
severityMapping: configPack.severityMapping,
|
|
161
|
+
scoringWeights: configPack.scoringWeights,
|
|
162
|
+
convenienceWeights: configPack.convenienceWeights ?? { timeLimit: 0.3, onlineCapable: 0.4, materialConcise: 0.3 },
|
|
163
|
+
itemTypeMatching: configPack.itemTypeMatching,
|
|
164
|
+
materialConciseThreshold: configPack.materialConciseThreshold ?? 5,
|
|
165
|
+
missingFieldStandardClause: configPack.missingFieldStandardClause ?? '标准规范条款',
|
|
166
|
+
gbtMapping: configPack.gbtMapping,
|
|
167
|
+
}, kb, standardRules, {
|
|
199
168
|
degradedMode,
|
|
200
169
|
itemTypeOverride,
|
|
201
|
-
|
|
202
|
-
|
|
170
|
+
degradedSimilarityThreshold: configPack.degradedSimilarityThreshold,
|
|
171
|
+
inlineLocalTerms: configPack.localStandardTerms,
|
|
203
172
|
});
|
|
204
173
|
result.guideInspection = orchestrateResult;
|
|
205
174
|
if (orchestrateResult.warnings && orchestrateResult.warnings.length > 0) {
|
|
206
|
-
|
|
175
|
+
allWarnings.push(...orchestrateResult.warnings);
|
|
207
176
|
}
|
|
208
177
|
}
|
|
209
178
|
if (mode === 'classification' || mode === 'full') {
|
|
210
|
-
result.classification = classifyGovData(data,
|
|
179
|
+
result.classification = classifyGovData(data, configPack.gbtMapping);
|
|
180
|
+
}
|
|
181
|
+
if (allWarnings.length > 0) {
|
|
182
|
+
result.warnings = allWarnings;
|
|
211
183
|
}
|
|
212
184
|
return JSON.stringify(result, null, 2);
|
|
213
185
|
}
|
|
@@ -11,18 +11,20 @@ export const InspectionOrchestrator = {
|
|
|
11
11
|
const allWarnings = [];
|
|
12
12
|
let completeCount = 0;
|
|
13
13
|
let serviceConvenience = 0;
|
|
14
|
+
const requiredFields = config.requiredFields ?? config.guideRequiredElements ?? [];
|
|
15
|
+
const scoringWeights = config.scoringWeights ?? config.scoreWeights;
|
|
14
16
|
for (let i = 0; i < data.length; i++) {
|
|
15
17
|
const row = data[i];
|
|
16
18
|
if (typeof row !== 'object' || row === null)
|
|
17
19
|
continue;
|
|
18
20
|
const guide = row;
|
|
19
21
|
const guideId = String(guide['事项名称'] ?? `指南${i + 1}`);
|
|
20
|
-
const missingDetails = MissingFieldDetector.detect(guide, guideId,
|
|
22
|
+
const missingDetails = MissingFieldDetector.detect(guide, guideId, requiredFields, config.severityMapping, undefined, config.missingFieldStandardClause);
|
|
21
23
|
allErrorDetails.push(...missingDetails);
|
|
22
24
|
const isComplete = missingDetails.length === 0;
|
|
23
25
|
if (isComplete)
|
|
24
26
|
completeCount++;
|
|
25
|
-
const semanticResult = SemanticRuleEngine.detect(guide, guideId, kb, config.itemTypeMatching, config.severityMapping, { degradedMode: options?.degradedMode, itemTypeOverride: options?.itemTypeOverride, localTermsPath: options?.localTermsPath, degradedSimilarityThreshold: options?.degradedSimilarityThreshold });
|
|
27
|
+
const semanticResult = SemanticRuleEngine.detect(guide, guideId, kb, config.itemTypeMatching, config.severityMapping, { degradedMode: options?.degradedMode, itemTypeOverride: options?.itemTypeOverride, localTermsPath: options?.localTermsPath, degradedSimilarityThreshold: options?.degradedSimilarityThreshold, inlineLocalTerms: options?.inlineLocalTerms });
|
|
26
28
|
allErrorDetails.push(...semanticResult.details);
|
|
27
29
|
if (semanticResult.warning) {
|
|
28
30
|
allWarnings.push(semanticResult.warning);
|
|
@@ -46,17 +48,17 @@ export const InspectionOrchestrator = {
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
const counts = ErrorDetailBuilder.countByType(allErrorDetails);
|
|
49
|
-
const totalFieldCount = data.length *
|
|
51
|
+
const totalFieldCount = data.length * requiredFields.length;
|
|
50
52
|
const detectionRates = QualityMetricsCalculator.calculate({
|
|
51
53
|
semanticErrorCount: counts.semantic,
|
|
52
54
|
logicalErrorCount: counts.logical,
|
|
53
55
|
missingFieldCount: counts.missing,
|
|
54
56
|
formatIssueCount: allFormatIssues.length,
|
|
55
57
|
totalGuides: data.length,
|
|
56
|
-
requiredFieldCount:
|
|
58
|
+
requiredFieldCount: requiredFields.length,
|
|
57
59
|
totalFieldCount: totalFieldCount > 0 ? totalFieldCount : 1,
|
|
58
60
|
errorDetails: allErrorDetails,
|
|
59
|
-
scoreWeights:
|
|
61
|
+
scoreWeights: scoringWeights,
|
|
60
62
|
});
|
|
61
63
|
return {
|
|
62
64
|
completeness: data.length > 0 ? Math.round((completeCount / data.length) * 100) : 0,
|
|
@@ -3,7 +3,13 @@ import { loadJsonConfig } from '@liuhange/dsh-data-asset-shared';
|
|
|
3
3
|
const DEFAULT_LOCAL_TERMS_PATH = 'config/local-standard-terms.json';
|
|
4
4
|
const SHARED_LOCAL_TERMS_PATH = '@liuhange/dsh-data-asset-shared/config/local-standard-terms.json';
|
|
5
5
|
export const LocalTermsLoader = {
|
|
6
|
-
load(configPath) {
|
|
6
|
+
load(configPath, inlineTerms) {
|
|
7
|
+
if (inlineTerms && (inlineTerms.materials.length > 0 || inlineTerms.conditions.length > 0)) {
|
|
8
|
+
return {
|
|
9
|
+
materials: inlineTerms.materials.filter((m) => typeof m === 'string'),
|
|
10
|
+
conditions: inlineTerms.conditions.filter((c) => typeof c === 'string'),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
7
13
|
try {
|
|
8
14
|
if (configPath && !existsSync(configPath)) {
|
|
9
15
|
return { materials: [], conditions: [] };
|
package/lib/policyBasis.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { loadJsonConfig } from '@liuhange/dsh-data-asset-shared';
|
|
2
|
-
export function resolvePolicyBasis(stage) {
|
|
2
|
+
export function resolvePolicyBasis(stage, packPolicyBasis) {
|
|
3
|
+
if (packPolicyBasis && packPolicyBasis.length > 0) {
|
|
4
|
+
return packPolicyBasis.map((d) => `依据:《${d.name}》(${d.docNumber})—${d.coreRequirement}`);
|
|
5
|
+
}
|
|
3
6
|
try {
|
|
4
7
|
const config = loadJsonConfig('POLICY_REFS_PATH', 'config/policy-references.json', '@liuhange/dsh-data-asset-shared/config/policy-references.json');
|
|
5
8
|
const refs = config.policyReferences;
|
|
@@ -176,7 +176,7 @@ export const SemanticRuleEngine = {
|
|
|
176
176
|
}
|
|
177
177
|
const hasKbData = kb.timeLimits.length > 0 || kb.materials.length > 0 || kb.conditions.length > 0;
|
|
178
178
|
if (!hasKbData && options?.degradedMode) {
|
|
179
|
-
const localTerms = LocalTermsLoader.load(options.localTermsPath);
|
|
179
|
+
const localTerms = LocalTermsLoader.load(options.localTermsPath, options.inlineLocalTerms);
|
|
180
180
|
if (localTerms.materials.length === 0 && localTerms.conditions.length === 0) {
|
|
181
181
|
return { details: [] };
|
|
182
182
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liuhange/dsh-gov-data-inspector",
|
|
3
3
|
"description": "Government data inspection plugin per GB/T 47949-2026 & GB/T 47950-2026: service guide quality check, public data asset classification, semantic & logic detection, compliance audit, data governance for digital government 一网通办",
|
|
4
|
-
"version": "3.1
|
|
4
|
+
"version": "3.2.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"lib/**/*.js",
|
|
26
26
|
"lib/types/**/*.d.ts",
|
|
27
|
+
"lib/**/*.json",
|
|
27
28
|
"cordis.patch.yml"
|
|
28
29
|
],
|
|
29
30
|
"license": "MIT",
|
|
@@ -37,7 +38,9 @@
|
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
|
39
40
|
"@deepseek-ai/cordis": "^4.0.0",
|
|
40
|
-
"@deepseek-ai/dsh-tools": "0.2.0-rc.2"
|
|
41
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.2",
|
|
42
|
+
"@liuhange/gov-config-gd": "^2026.1.0",
|
|
43
|
+
"@liuhange/gov-config-zj": "^2026.1.0"
|
|
41
44
|
},
|
|
42
45
|
"dsh": {
|
|
43
46
|
"bundle": {
|