@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 CHANGED
@@ -6,7 +6,7 @@
6
6
  |------|-----|
7
7
  | 插件名 | dsh-gov-data-inspector |
8
8
  | 包名 | @liuhange/dsh-gov-data-inspector |
9
- | 版本 | 3.1.0 |
9
+ | 版本 | 3.2.0 |
10
10
  | 工具 | inspect_gov_data |
11
11
 
12
12
  ## 功能说明
@@ -113,3 +113,122 @@ execute({
113
113
  ```
114
114
 
115
115
  匹配逻辑支持四通道:手动指定 → 名称关键词 → 编码前缀 → 流程字段关键词。
116
+ ## 配置包机制
117
+
118
+ 本插件采用**通用检测引擎 + 可替换配置包**架构。插件本体不含任何地区特定的政策标准,所有地区差异通过配置包注入。新增地区配置包无需修改插件代码。
119
+
120
+ ### 架构原理
121
+
122
+ ```
123
+ ┌─────────────────────────┐ ┌──────────────────────────┐
124
+ │ 通用检测引擎(插件) │◄────│ 地区配置包(可替换) │
125
+ │ - 格式校验 │ │ - 政策依据 │
126
+ │ - 语义检测 │ │ - 必填字段 │
127
+ │ - 逻辑检测 │ │ - 格式规则 │
128
+ │ - 评分计算 │ │ - 标准词表 │
129
+ │ - 分类判定 │ │ - 数据源凭证 │
130
+ └─────────────────────────┘ └──────────────────────────┘
131
+ ```
132
+
133
+ ### 配置包加载优先级
134
+
135
+ 插件按以下三级优先级加载配置包:
136
+
137
+ 1. **文件路径**(`GOV_CONFIG_PACK_PATH` 环境变量)— 加载指定 JSON 文件
138
+ 2. **npm 包**(`GOV_CONFIG_PACK_PACKAGE` 环境变量)— 加载已安装的 npm 配置包
139
+ 3. **缺省配置包** — 插件内置的通用配置包,作为兜底
140
+
141
+ 每级加载成功后执行 Schema 校验,校验失败则记录告警并尝试下一级。三级全部失败时抛出 `GOV_CONFIG_PACK_UNAVAILABLE` 错误。
142
+
143
+ ### 环境变量
144
+
145
+ | 环境变量 | 说明 | 示例 |
146
+ |---------|------|------|
147
+ | `GOV_CONFIG_PACK_PATH` | 配置包 JSON 文件的绝对或相对路径 | `/path/to/config-pack.json` |
148
+ | `GOV_CONFIG_PACK_PACKAGE` | 已安装的 npm 配置包包名 | `@liuhange/gov-config-gd` |
149
+
150
+ 两个环境变量均为可选。同时设置时,`GOV_CONFIG_PACK_PATH` 优先于 `GOV_CONFIG_PACK_PACKAGE`。均未设置时使用缺省配置包。
151
+
152
+ ### 巡检报告来源标注
153
+
154
+ 加载配置包后,巡检报告包含以下来源标注字段:
155
+
156
+ ```json
157
+ {
158
+ "configPackId": "gd-gov-2026",
159
+ "region": "广东省",
160
+ "configPackVersion": "2026.1.0"
161
+ }
162
+ ```
163
+
164
+ 使用缺省配置包时,`configPackId` 为 `default`,`region` 为 `通用`。
165
+
166
+ ## 如何新增地区配置包
167
+
168
+ ### 配置包字段清单
169
+
170
+ 配置包 JSON 文件包含以下 14 个必填字段与 5 个可选字段:
171
+
172
+ | 字段 | 类型 | 必填 | 说明 |
173
+ |------|------|------|------|
174
+ | `configPackId` | string | 是 | 配置包标识,格式 `^[a-z]{2}-[a-z]+-\d{4}$` |
175
+ | `region` | string | 是 | 地区名称 |
176
+ | `configPackVersion` | string | 是 | 语义化版本号 |
177
+ | `policyBasis` | array | 是 | 政策依据文档列表 |
178
+ | `requiredFields` | string[] | 是 | 办事指南必填字段列表 |
179
+ | `formatRules` | array | 是 | 格式校验规则(含正则) |
180
+ | `localStandardTerms` | object | 是 | 本地标准词表(materials + conditions) |
181
+ | `logicErrorRules` | array | 是 | 逻辑错误检测规则 |
182
+ | `scoringWeights` | object | 是 | 评分权重(completeness + accuracy + traceability,和为 1) |
183
+ | `itemTypeMatching` | object | 是 | 事项类型匹配规则 |
184
+ | `severityMapping` | object | 是 | 严重等级映射 |
185
+ | `gbtMapping` | object | 是 | GB/T 47949 分类映射 |
186
+ | `dataSourceCredentials` | object | 是 | 数据源凭证配置 |
187
+ | `dataSourcePriority` | string[] | 是 | 数据源优先级 |
188
+ | `convenienceWeights` | object | 否 | 便利度权重 |
189
+ | `materialConciseThreshold` | number | 否 | 材料精简阈值 |
190
+ | `missingFieldStandardClause` | string | 否 | 缺失字段标准条款引用 |
191
+ | `degradedSimilarityThreshold` | number | 否 | 降级相似度阈值 |
192
+ | `inputLengthThreshold` | number | 否 | 输入长度阈值 |
193
+
194
+ ### 字段名兼容映射
195
+
196
+ 配置包支持新旧字段名兼容。当旧名字段存在而新名字段缺失时,自动映射:
197
+
198
+ | 新字段名 | 旧字段名(兼容) |
199
+ |---------|---------------|
200
+ | `requiredFields` | `guideRequiredElements` |
201
+ | `scoringWeights` | `scoreWeights` |
202
+ | `gbtMapping` | `gbt47949Mapping` |
203
+
204
+ 新名与旧名同时存在时,新名优先。
205
+
206
+ ### 创建步骤
207
+
208
+ 1. **创建配置包 JSON 文件**:按上述字段清单编写 `config-pack.json`,填入目标地区的政策依据、标准词表等
209
+
210
+ 2. **Schema 校验**:使用 `@liuhange/gov-config-schema` 包离线校验配置包内容
211
+
212
+ 3. **创建 npm 包**:`package.json` 中 `name` 使用 scoped 命名 `@liuhange/gov-config-{地区缩写}`,`exports` 指向 `config-pack.json`
213
+
214
+ 4. **发布**:`npm publish --access public`
215
+
216
+ 5. **使用**:安装配置包后设置环境变量 `GOV_CONFIG_PACK_PACKAGE=@liuhange/gov-config-{地区缩写}`
217
+
218
+ ### 已有地区配置包
219
+
220
+ | 包名 | 地区 | 版本 |
221
+ |------|------|------|
222
+ | `@liuhange/gov-config-gd` | 广东省 | 2026.1.0 |
223
+ | `@liuhange/gov-config-zj` | 浙江省 | 2026.1.0 |
224
+ | `@liuhange/gov-config-schema` | Schema 校验工具 | 2026.1.0 |
225
+
226
+ ## 配置包交付周期
227
+
228
+ 配置包独立于插件发布,遵循语义化版本管理:
229
+
230
+ - **政策更新时**:配置包 minor 或 patch 版本升级(如 `2026.1.0` → `2026.1.1`),插件版本不变
231
+ - **新增地区配置包**:新建 npm 包,初始版本 `2026.1.0`
232
+ - **配置包 Schema 变更**:`@liuhange/gov-config-schema` 版本升级,插件内嵌 Schema 同步更新
233
+
234
+ 配置包维护者可通过 `@liuhange/gov-config-schema` 包离线校验配置包内容,无需安装完整插件。
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { normalizeConfigPack } from '../configPack.js';
3
+ describe('ConfigPack 类型与字段名映射', () => {
4
+ const basePack = {
5
+ configPackId: 'gd-gov-2026',
6
+ region: '广东省',
7
+ configPackVersion: '2026.1.0',
8
+ policyBasis: [{ name: '测试标准', docNumber: '测试文号', coreRequirement: '测试要求' }],
9
+ requiredFields: ['事项名称', '办理条件'],
10
+ formatRules: [{ field: '办理时限', pattern: '^\\d+个工作日$', suggestionTemplate: '提示' }],
11
+ localStandardTerms: { materials: ['身份证'], conditions: ['年满18周岁'] },
12
+ logicErrorRules: [{ ruleId: 'LOG_001', standardClause: '测试条款', triggerFields: ['办理流程'], triggerKeywords: ['现场勘查'], condition: '测试', suggestionTemplate: '测试' }],
13
+ scoringWeights: { completeness: 0.3, accuracy: 0.4, traceability: 0.3 },
14
+ itemTypeMatching: { 行政许可: { keywords: ['许可'], codePrefix: 'XK' } },
15
+ severityMapping: { missing: 'major', semantic: 'critical', logical: 'critical' },
16
+ gbtMapping: { structured: 'A01', semiStructured: 'A02', unstructured: 'A03' },
17
+ dataSourceCredentials: {
18
+ national: { apiKey: 'NATIONAL_KEY', endpoint: 'NATIONAL_ENDPOINT' },
19
+ provincial: { apiKey: 'PROVINCIAL_KEY', endpoint: 'PROVINCIAL_ENDPOINT' },
20
+ standard: { docPath: 'DOC_PATH', endpoint: 'RULES_ENDPOINT' },
21
+ },
22
+ dataSourcePriority: ['provincial', 'national'],
23
+ };
24
+ it('仅含新名时返回原值', () => {
25
+ const result = normalizeConfigPack({ ...basePack });
26
+ expect(result.requiredFields).toEqual(['事项名称', '办理条件']);
27
+ expect(result.scoringWeights).toEqual({ completeness: 0.3, accuracy: 0.4, traceability: 0.3 });
28
+ expect(result.gbtMapping).toEqual({ structured: 'A01', semiStructured: 'A02', unstructured: 'A03' });
29
+ });
30
+ it('仅含旧名时映射为新名', () => {
31
+ const oldPack = { ...basePack };
32
+ delete oldPack.requiredFields;
33
+ delete oldPack.scoringWeights;
34
+ delete oldPack.gbtMapping;
35
+ oldPack.guideRequiredElements = ['事项名称', '办理条件'];
36
+ oldPack.scoreWeights = { completeness: 0.3, accuracy: 0.4, traceability: 0.3 };
37
+ oldPack.gbt47949Mapping = { structured: 'A01', semiStructured: 'A02', unstructured: 'A03' };
38
+ const result = normalizeConfigPack(oldPack);
39
+ expect(result.requiredFields).toEqual(['事项名称', '办理条件']);
40
+ expect(result.scoringWeights).toEqual({ completeness: 0.3, accuracy: 0.4, traceability: 0.3 });
41
+ expect(result.gbtMapping).toEqual({ structured: 'A01', semiStructured: 'A02', unstructured: 'A03' });
42
+ });
43
+ it('新名与旧名同时存在时新名优先', () => {
44
+ const mixedPack = { ...basePack };
45
+ mixedPack.guideRequiredElements = ['旧字段'];
46
+ mixedPack.scoreWeights = { completeness: 0.1, accuracy: 0.1, traceability: 0.1 };
47
+ mixedPack.gbt47949Mapping = { structured: 'OLD' };
48
+ const result = normalizeConfigPack(mixedPack);
49
+ expect(result.requiredFields).toEqual(['事项名称', '办理条件']);
50
+ expect(result.scoringWeights).toEqual({ completeness: 0.3, accuracy: 0.4, traceability: 0.3 });
51
+ expect(result.gbtMapping).toEqual({ structured: 'A01', semiStructured: 'A02', unstructured: 'A03' });
52
+ });
53
+ it('必填字段双名均缺失时抛错', () => {
54
+ const missingPack = { ...basePack };
55
+ delete missingPack.requiredFields;
56
+ delete missingPack.guideRequiredElements;
57
+ expect(() => normalizeConfigPack(missingPack)).toThrow('GOV_CONFIG_PACK_INVALID');
58
+ });
59
+ it('configPackId 缺失时抛错', () => {
60
+ const missingPack = { ...basePack };
61
+ delete missingPack.configPackId;
62
+ expect(() => normalizeConfigPack(missingPack)).toThrow('GOV_CONFIG_PACK_INVALID');
63
+ });
64
+ it('可选字段保留', () => {
65
+ const packWithOptional = { ...basePack, convenienceWeights: { timeLimit: 0.3 }, materialConciseThreshold: 5 };
66
+ const result = normalizeConfigPack(packWithOptional);
67
+ expect(result.convenienceWeights).toEqual({ timeLimit: 0.3 });
68
+ expect(result.materialConciseThreshold).toBe(5);
69
+ });
70
+ it('default configPackId 被接受', () => {
71
+ const defaultPack = { ...basePack, configPackId: 'default' };
72
+ const result = normalizeConfigPack(defaultPack);
73
+ expect(result.configPackId).toBe('default');
74
+ });
75
+ });
76
+ //# sourceMappingURL=configPack.test.js.map
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { ConfigPackLoader } from '../configPackLoader.js';
3
+ import { writeFileSync, unlinkSync } from 'node:fs';
4
+ import { resolve } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ describe('配置包加载器', () => {
7
+ const tmpFile = resolve(tmpdir(), 'test-config-pack.json');
8
+ afterEach(() => {
9
+ try {
10
+ unlinkSync(tmpFile);
11
+ }
12
+ catch { /* ignore */ }
13
+ });
14
+ it('未设置环境变量时使用缺省配置包', () => {
15
+ const result = ConfigPackLoader.load();
16
+ expect(result.loadSource).toBe('default');
17
+ expect(result.configPack.configPackId).toBe('default');
18
+ expect(result.configPack.region).toBe('通用');
19
+ });
20
+ it('设置 GOV_CONFIG_PACK_PATH 指向合法文件时 loadSource 为 file', () => {
21
+ const pack = {
22
+ configPackId: 'gd-gov-2026',
23
+ region: '广东省',
24
+ configPackVersion: '2026.1.0',
25
+ policyBasis: [{ name: '测试', docNumber: '粤府办〔2024〕1号', coreRequirement: '测试' }],
26
+ requiredFields: ['事项名称'],
27
+ formatRules: [],
28
+ localStandardTerms: { materials: [], conditions: [] },
29
+ logicErrorRules: [{ ruleId: 'LOG_001', standardClause: '测试', triggerFields: ['办理流程'], triggerKeywords: ['测试'], condition: '测试', suggestionTemplate: '测试' }],
30
+ scoringWeights: { completeness: 0.3, accuracy: 0.4, traceability: 0.3 },
31
+ itemTypeMatching: { 行政许可: { keywords: ['许可'], codePrefix: 'XK' } },
32
+ severityMapping: { missing: 'major', semantic: 'critical', logical: 'critical' },
33
+ gbtMapping: { structured: 'A01', semiStructured: 'A02', unstructured: 'A03' },
34
+ dataSourceCredentials: {
35
+ national: { apiKey: 'KEY', endpoint: 'EP' },
36
+ provincial: { apiKey: 'KEY', endpoint: 'EP' },
37
+ standard: { docPath: 'PATH', endpoint: 'EP' },
38
+ },
39
+ dataSourcePriority: ['provincial', 'national'],
40
+ };
41
+ writeFileSync(tmpFile, JSON.stringify(pack), 'utf-8');
42
+ process.env.GOV_CONFIG_PACK_PATH = tmpFile;
43
+ const result = ConfigPackLoader.load();
44
+ expect(result.loadSource).toBe('file');
45
+ expect(result.configPack.configPackId).toBe('gd-gov-2026');
46
+ expect(result.configPack.region).toBe('广东省');
47
+ delete process.env.GOV_CONFIG_PACK_PATH;
48
+ });
49
+ it('设置 GOV_CONFIG_PACK_PATH 指向不存在文件时回退到缺省', () => {
50
+ process.env.GOV_CONFIG_PACK_PATH = '/nonexistent/path/pack.json';
51
+ const result = ConfigPackLoader.load();
52
+ expect(result.loadSource).toBe('default');
53
+ expect(result.warnings.length).toBeGreaterThan(0);
54
+ delete process.env.GOV_CONFIG_PACK_PATH;
55
+ });
56
+ it('设置 GOV_CONFIG_PACK_PATH 指向非法 JSON 时回退到缺省', () => {
57
+ writeFileSync(tmpFile, 'not json', 'utf-8');
58
+ process.env.GOV_CONFIG_PACK_PATH = tmpFile;
59
+ const result = ConfigPackLoader.load();
60
+ expect(result.loadSource).toBe('default');
61
+ expect(result.warnings.length).toBeGreaterThan(0);
62
+ delete process.env.GOV_CONFIG_PACK_PATH;
63
+ });
64
+ it('设置 GOV_CONFIG_PACK_PACKAGE 指向未安装包时回退到缺省', () => {
65
+ process.env.GOV_CONFIG_PACK_PACKAGE = '@nonexistent/config-pack';
66
+ const result = ConfigPackLoader.load();
67
+ expect(result.loadSource).toBe('default');
68
+ expect(result.warnings.length).toBeGreaterThan(0);
69
+ delete process.env.GOV_CONFIG_PACK_PACKAGE;
70
+ });
71
+ it('缺省配置包含 14 个必填字段', () => {
72
+ const result = ConfigPackLoader.load();
73
+ const pack = result.configPack;
74
+ expect(pack.configPackId).toBeDefined();
75
+ expect(pack.region).toBeDefined();
76
+ expect(pack.configPackVersion).toBeDefined();
77
+ expect(pack.policyBasis).toBeDefined();
78
+ expect(pack.requiredFields).toBeDefined();
79
+ expect(pack.formatRules).toBeDefined();
80
+ expect(pack.localStandardTerms).toBeDefined();
81
+ expect(pack.logicErrorRules).toBeDefined();
82
+ expect(pack.scoringWeights).toBeDefined();
83
+ expect(pack.itemTypeMatching).toBeDefined();
84
+ expect(pack.severityMapping).toBeDefined();
85
+ expect(pack.gbtMapping).toBeDefined();
86
+ expect(pack.dataSourceCredentials).toBeDefined();
87
+ expect(pack.dataSourcePriority).toBeDefined();
88
+ });
89
+ });
90
+ //# sourceMappingURL=configPackLoader.test.js.map
@@ -0,0 +1,75 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ConfigPackSchemaValidator } from '../configPackSchemaValidator.js';
3
+ import defaultPack from '../default-config-pack.json' with { type: 'json' };
4
+ describe('Schema 校验器', () => {
5
+ it('缺省配置包校验通过', () => {
6
+ const result = ConfigPackSchemaValidator.validate(defaultPack);
7
+ expect(result.valid).toBe(true);
8
+ expect(result.errors).toEqual([]);
9
+ });
10
+ it('非对象输入校验失败', () => {
11
+ const result = ConfigPackSchemaValidator.validate('not an object');
12
+ expect(result.valid).toBe(false);
13
+ });
14
+ it('缺少必填字段校验失败', () => {
15
+ const pack = { ...defaultPack };
16
+ delete pack.configPackId;
17
+ const result = ConfigPackSchemaValidator.validate(pack);
18
+ expect(result.valid).toBe(false);
19
+ expect(result.errors.some((e) => e.path === 'configPackId')).toBe(true);
20
+ });
21
+ it('configPackVersion 格式错误校验失败', () => {
22
+ const pack = { ...defaultPack, configPackVersion: 'invalid' };
23
+ const result = ConfigPackSchemaValidator.validate(pack);
24
+ expect(result.valid).toBe(false);
25
+ expect(result.errors.some((e) => e.path === 'configPackVersion')).toBe(true);
26
+ });
27
+ it('scoringWeights 三权重之和≠1校验失败', () => {
28
+ const pack = { ...defaultPack, scoringWeights: { completeness: 0.1, accuracy: 0.1, traceability: 0.1 } };
29
+ const result = ConfigPackSchemaValidator.validate(pack);
30
+ expect(result.valid).toBe(false);
31
+ expect(result.errors.some((e) => e.path === 'scoringWeights')).toBe(true);
32
+ });
33
+ it('scoringWeights 三权重之和=1校验通过', () => {
34
+ const pack = { ...defaultPack, scoringWeights: { completeness: 0.3, accuracy: 0.4, traceability: 0.3 } };
35
+ const result = ConfigPackSchemaValidator.validate(pack);
36
+ expect(result.errors.some((e) => e.path === 'scoringWeights')).toBe(false);
37
+ });
38
+ it('policyBasis 为空数组校验失败', () => {
39
+ const pack = { ...defaultPack, policyBasis: [] };
40
+ const result = ConfigPackSchemaValidator.validate(pack);
41
+ expect(result.valid).toBe(false);
42
+ expect(result.errors.some((e) => e.path === 'policyBasis')).toBe(true);
43
+ });
44
+ it('requiredFields 为空数组校验失败', () => {
45
+ const pack = { ...defaultPack, requiredFields: [] };
46
+ const result = ConfigPackSchemaValidator.validate(pack);
47
+ expect(result.valid).toBe(false);
48
+ });
49
+ it('severityMapping 值非枚举校验失败', () => {
50
+ const pack = { ...defaultPack, severityMapping: { missing: 'invalid', semantic: 'critical', logical: 'critical' } };
51
+ const result = ConfigPackSchemaValidator.validate(pack);
52
+ expect(result.valid).toBe(false);
53
+ expect(result.errors.some((e) => e.path === 'severityMapping.missing')).toBe(true);
54
+ });
55
+ it('dataSourcePriority 含非法值校验失败', () => {
56
+ const pack = { ...defaultPack, dataSourcePriority: ['invalid'] };
57
+ const result = ConfigPackSchemaValidator.validate(pack);
58
+ expect(result.valid).toBe(false);
59
+ expect(result.errors.some((e) => e.path === 'dataSourcePriority[0]')).toBe(true);
60
+ });
61
+ });
62
+ describe('正则 ReDoS 安全预检', () => {
63
+ it('安全正则通过', () => {
64
+ expect(ConfigPackSchemaValidator.checkRegexSafety('^\\d+个工作日$')).toBe(true);
65
+ expect(ConfigPackSchemaValidator.checkRegexSafety('1[3-9]\\d{9}')).toBe(true);
66
+ });
67
+ it('嵌套量词正则被拒绝', () => {
68
+ expect(ConfigPackSchemaValidator.checkRegexSafety('(a+)+')).toBe(false);
69
+ expect(ConfigPackSchemaValidator.checkRegexSafety('(a*)*')).toBe(false);
70
+ });
71
+ it('无效正则被拒绝', () => {
72
+ expect(ConfigPackSchemaValidator.checkRegexSafety('[')).toBe(false);
73
+ });
74
+ });
75
+ //# sourceMappingURL=configPackSchemaValidator.test.js.map
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ConfigPackSchemaValidator } from '../configPackSchemaValidator.js';
3
+ import defaultPack from '../default-config-pack.json' with { type: 'json' };
4
+ describe('缺省配置包完整性', () => {
5
+ it('通过 Schema 校验', () => {
6
+ const result = ConfigPackSchemaValidator.validate(defaultPack);
7
+ expect(result.valid).toBe(true);
8
+ expect(result.errors).toEqual([]);
9
+ });
10
+ it('configPackId 为 default', () => {
11
+ expect(defaultPack.configPackId).toBe('default');
12
+ });
13
+ it('region 为 通用', () => {
14
+ expect(defaultPack.region).toBe('通用');
15
+ });
16
+ it('policyBasis 含 6 条且包含广东省政务服务事项管理系统', () => {
17
+ expect(defaultPack.policyBasis.length).toBe(6);
18
+ expect(defaultPack.policyBasis.some((d) => d.name.includes('广东省政务服务事项管理系统'))).toBe(true);
19
+ });
20
+ it('14 个必填字段完整', () => {
21
+ const required = ['configPackId', 'region', 'configPackVersion', 'policyBasis', 'requiredFields', 'formatRules', 'localStandardTerms', 'logicErrorRules', 'scoringWeights', 'itemTypeMatching', 'severityMapping', 'gbtMapping', 'dataSourceCredentials', 'dataSourcePriority'];
22
+ for (const field of required) {
23
+ expect(defaultPack[field]).toBeDefined();
24
+ }
25
+ });
26
+ });
27
+ //# sourceMappingURL=defaultConfigPack.test.js.map
@@ -0,0 +1,59 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ConfigPackSchemaValidator } from '../configPackSchemaValidator.js';
3
+ import { normalizeConfigPack } from '../configPack.js';
4
+ import { readFileSync } from 'node:fs';
5
+ import { resolve } from 'node:path';
6
+ const gdPack = JSON.parse(readFileSync(resolve(__dirname, '..', '..', '..', 'gov-config-gd', 'config-pack.json'), 'utf-8'));
7
+ const zjPack = JSON.parse(readFileSync(resolve(__dirname, '..', '..', '..', 'gov-config-zj', 'config-pack.json'), 'utf-8'));
8
+ describe('广东省配置包', () => {
9
+ it('Schema 校验通过', () => {
10
+ const normalized = normalizeConfigPack(gdPack);
11
+ const result = ConfigPackSchemaValidator.validate(normalized);
12
+ expect(result.valid).toBe(true);
13
+ expect(result.errors).toEqual([]);
14
+ });
15
+ it('configPackId 为 gd-gov-2026', () => {
16
+ expect(gdPack.configPackId).toBe('gd-gov-2026');
17
+ });
18
+ it('region 为 广东省', () => {
19
+ expect(gdPack.region).toBe('广东省');
20
+ });
21
+ it('policyBasis 含文号含粤字的政策文件', () => {
22
+ expect(gdPack.policyBasis.some((d) => d.docNumber.includes('粤'))).toBe(true);
23
+ });
24
+ it('dataSourcePriority 省级优先', () => {
25
+ expect(gdPack.dataSourcePriority[0]).toBe('provincial');
26
+ });
27
+ });
28
+ describe('浙江省配置包', () => {
29
+ it('Schema 校验通过', () => {
30
+ const normalized = normalizeConfigPack(zjPack);
31
+ const result = ConfigPackSchemaValidator.validate(normalized);
32
+ expect(result.valid).toBe(true);
33
+ expect(result.errors).toEqual([]);
34
+ });
35
+ it('configPackId 为 zj-gov-2026', () => {
36
+ expect(zjPack.configPackId).toBe('zj-gov-2026');
37
+ });
38
+ it('region 为 浙江省', () => {
39
+ expect(zjPack.region).toBe('浙江省');
40
+ });
41
+ it('policyBasis 含文号含浙字的政策文件', () => {
42
+ expect(zjPack.policyBasis.some((d) => d.docNumber.includes('浙'))).toBe(true);
43
+ });
44
+ it('dataSourcePriority 省级优先', () => {
45
+ expect(zjPack.dataSourcePriority[0]).toBe('provincial');
46
+ });
47
+ });
48
+ describe('地区配置包独立性', () => {
49
+ it('广东省与浙江省 configPackId 不同', () => {
50
+ expect(gdPack.configPackId).not.toBe(zjPack.configPackId);
51
+ });
52
+ it('广东省与浙江省 region 不同', () => {
53
+ expect(gdPack.region).not.toBe(zjPack.region);
54
+ });
55
+ it('广东省与浙江省 dataSourceCredentials 不同', () => {
56
+ expect(gdPack.dataSourceCredentials.national.apiKey).not.toBe(zjPack.dataSourceCredentials.national.apiKey);
57
+ });
58
+ });
59
+ //# sourceMappingURL=govConfigPacks.test.js.map
@@ -0,0 +1,186 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://liuhange.dev/gov-data-inspector/config-pack-schema.json",
4
+ "title": "Government Data Inspector Config Pack",
5
+ "type": "object",
6
+ "required": [
7
+ "configPackId",
8
+ "region",
9
+ "configPackVersion",
10
+ "policyBasis",
11
+ "requiredFields",
12
+ "formatRules",
13
+ "localStandardTerms",
14
+ "logicErrorRules",
15
+ "scoringWeights",
16
+ "itemTypeMatching",
17
+ "severityMapping",
18
+ "gbtMapping",
19
+ "dataSourceCredentials",
20
+ "dataSourcePriority"
21
+ ],
22
+ "properties": {
23
+ "configPackId": {
24
+ "type": "string",
25
+ "pattern": "^[a-z]{2}-[a-z]+-\\d{4}$|^default$"
26
+ },
27
+ "region": {
28
+ "type": "string",
29
+ "minLength": 2
30
+ },
31
+ "configPackVersion": {
32
+ "type": "string",
33
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
34
+ },
35
+ "policyBasis": {
36
+ "type": "array",
37
+ "minItems": 1,
38
+ "items": {
39
+ "type": "object",
40
+ "required": ["name", "docNumber", "coreRequirement"],
41
+ "properties": {
42
+ "name": { "type": "string", "minLength": 1 },
43
+ "docNumber": { "type": "string", "minLength": 1 },
44
+ "coreRequirement": { "type": "string", "minLength": 1 }
45
+ }
46
+ }
47
+ },
48
+ "requiredFields": {
49
+ "type": "array",
50
+ "minItems": 1,
51
+ "items": { "type": "string" }
52
+ },
53
+ "formatRules": {
54
+ "type": "array",
55
+ "items": {
56
+ "type": "object",
57
+ "required": ["field", "suggestionTemplate"],
58
+ "properties": {
59
+ "field": { "type": "string" },
60
+ "pattern": { "type": "string" },
61
+ "requiredKeywords": {
62
+ "type": "array",
63
+ "items": { "type": "string" }
64
+ },
65
+ "suggestionTemplate": { "type": "string" }
66
+ }
67
+ }
68
+ },
69
+ "localStandardTerms": {
70
+ "type": "object",
71
+ "required": ["materials", "conditions"],
72
+ "properties": {
73
+ "materials": {
74
+ "type": "array",
75
+ "items": { "type": "string" }
76
+ },
77
+ "conditions": {
78
+ "type": "array",
79
+ "items": { "type": "string" }
80
+ }
81
+ }
82
+ },
83
+ "logicErrorRules": {
84
+ "type": "array",
85
+ "items": {
86
+ "type": "object",
87
+ "required": ["ruleId", "standardClause", "triggerFields", "triggerKeywords"],
88
+ "properties": {
89
+ "ruleId": { "type": "string" },
90
+ "standardClause": { "type": "string" },
91
+ "triggerFields": {
92
+ "type": "array",
93
+ "items": { "type": "string" }
94
+ },
95
+ "condition": { "type": "string" },
96
+ "threshold": { "type": ["number", "null"] },
97
+ "suggestionTemplate": { "type": "string" },
98
+ "triggerKeywords": {
99
+ "type": "array",
100
+ "items": { "type": "string" }
101
+ }
102
+ }
103
+ }
104
+ },
105
+ "scoringWeights": {
106
+ "type": "object",
107
+ "required": ["completeness", "accuracy", "traceability"],
108
+ "properties": {
109
+ "completeness": { "type": "number", "minimum": 0, "maximum": 1 },
110
+ "accuracy": { "type": "number", "minimum": 0, "maximum": 1 },
111
+ "traceability": { "type": "number", "minimum": 0, "maximum": 1 }
112
+ }
113
+ },
114
+ "itemTypeMatching": {
115
+ "type": "object",
116
+ "minProperties": 1,
117
+ "additionalProperties": {
118
+ "type": "object",
119
+ "required": ["keywords", "codePrefix"],
120
+ "properties": {
121
+ "keywords": {
122
+ "type": "array",
123
+ "items": { "type": "string" }
124
+ },
125
+ "codePrefix": { "type": "string" }
126
+ }
127
+ }
128
+ },
129
+ "severityMapping": {
130
+ "type": "object",
131
+ "properties": {
132
+ "missing": { "type": "string", "enum": ["critical", "major", "minor"] },
133
+ "semantic": { "type": "string", "enum": ["critical", "major", "minor"] },
134
+ "logical": { "type": "string", "enum": ["critical", "major", "minor"] }
135
+ }
136
+ },
137
+ "gbtMapping": {
138
+ "type": "object",
139
+ "minProperties": 1,
140
+ "additionalProperties": { "type": "string" }
141
+ },
142
+ "dataSourceCredentials": {
143
+ "type": "object",
144
+ "required": ["national", "provincial", "standard"],
145
+ "properties": {
146
+ "national": {
147
+ "type": "object",
148
+ "required": ["apiKey", "endpoint"],
149
+ "properties": {
150
+ "apiKey": { "type": "string" },
151
+ "endpoint": { "type": "string" }
152
+ }
153
+ },
154
+ "provincial": {
155
+ "type": "object",
156
+ "required": ["apiKey", "endpoint"],
157
+ "properties": {
158
+ "apiKey": { "type": "string" },
159
+ "endpoint": { "type": "string" }
160
+ }
161
+ },
162
+ "standard": {
163
+ "type": "object",
164
+ "required": ["docPath", "endpoint"],
165
+ "properties": {
166
+ "docPath": { "type": "string" },
167
+ "endpoint": { "type": "string" }
168
+ }
169
+ }
170
+ }
171
+ },
172
+ "dataSourcePriority": {
173
+ "type": "array",
174
+ "minItems": 1,
175
+ "items": { "type": "string", "enum": ["national", "provincial"] }
176
+ },
177
+ "convenienceWeights": {
178
+ "type": "object",
179
+ "additionalProperties": { "type": "number" }
180
+ },
181
+ "materialConciseThreshold": { "type": "number" },
182
+ "missingFieldStandardClause": { "type": "string" },
183
+ "degradedSimilarityThreshold": { "type": "number", "minimum": 0, "maximum": 1 },
184
+ "inputLengthThreshold": { "type": "number" }
185
+ }
186
+ }