@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/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
- let rulesConfig;
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 = govConfig?.inputLengthThreshold ?? 50000;
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 maxFieldCount = rulesConfig.govDataInspection?.maxFieldCount;
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 = govConfigTyped.dataSourceCredentials;
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, govConfigTyped.dataSourcePriority);
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 = govConfigTyped.logicErrorRules;
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, govConfigTyped, kb, standardRules, {
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
- localTermsPath: govConfig?.localTermsPath,
202
- degradedSimilarityThreshold: govConfig?.degradedSimilarityThreshold,
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
- result.warnings = orchestrateResult.warnings;
175
+ allWarnings.push(...orchestrateResult.warnings);
207
176
  }
208
177
  }
209
178
  if (mode === 'classification' || mode === 'full') {
210
- result.classification = classifyGovData(data, govConfigTyped.gbt47949Mapping);
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, config.guideRequiredElements, config.severityMapping, undefined, config.missingFieldStandardClause);
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 * config.guideRequiredElements.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: config.guideRequiredElements.length,
58
+ requiredFieldCount: requiredFields.length,
57
59
  totalFieldCount: totalFieldCount > 0 ? totalFieldCount : 1,
58
60
  errorDetails: allErrorDetails,
59
- scoreWeights: config.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: [] };
@@ -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.8",
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": {