@liuhange/dsh-gov-data-inspector 3.1.1 → 3.1.2

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.
@@ -0,0 +1,91 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ErrorDetailBuilder } from '../errorDetailBuilder.js';
3
+ describe('ErrorDetailBuilder', () => {
4
+ it('传入六字段均非空且errorType合法 → 返回ErrorDetail对象', () => {
5
+ const detail = ErrorDetailBuilder.build({
6
+ guideId: 'guide1',
7
+ field: '办理时限',
8
+ errorType: 'semantic',
9
+ description: '办理时限超出法定上限',
10
+ suggestion: '建议调整为20个工作日以内',
11
+ });
12
+ expect(detail.guideId).toBe('guide1');
13
+ expect(detail.field).toBe('办理时限');
14
+ expect(detail.errorType).toBe('semantic');
15
+ expect(detail.severity).toBe('critical');
16
+ expect(detail.description).toBe('办理时限超出法定上限');
17
+ expect(detail.suggestion).toBe('建议调整为20个工作日以内');
18
+ });
19
+ it('传入任一字段为空 → 抛出错误', () => {
20
+ expect(() => ErrorDetailBuilder.build({
21
+ guideId: '',
22
+ field: '办理时限',
23
+ errorType: 'semantic',
24
+ description: 'desc',
25
+ suggestion: 'sug',
26
+ })).toThrow();
27
+ expect(() => ErrorDetailBuilder.build({
28
+ guideId: 'g1',
29
+ field: '',
30
+ errorType: 'semantic',
31
+ description: 'desc',
32
+ suggestion: 'sug',
33
+ })).toThrow();
34
+ });
35
+ it('errorType不合法 → 抛出错误', () => {
36
+ expect(() => ErrorDetailBuilder.build({
37
+ guideId: 'g1',
38
+ field: 'f',
39
+ errorType: 'invalid',
40
+ description: 'd',
41
+ suggestion: 's',
42
+ })).toThrow();
43
+ });
44
+ it('severity未传入时从severityMapping读取映射', () => {
45
+ const detail = ErrorDetailBuilder.build({
46
+ guideId: 'g1',
47
+ field: 'f',
48
+ errorType: 'missing',
49
+ description: 'd',
50
+ suggestion: 's',
51
+ }, { missing: 'major', semantic: 'critical', logical: 'critical' });
52
+ expect(detail.severity).toBe('major');
53
+ });
54
+ it('传入dataSource与standardClause → 返回的ErrorDetail含该两字段', () => {
55
+ const detail = ErrorDetailBuilder.build({
56
+ guideId: 'g1',
57
+ field: 'f',
58
+ errorType: 'semantic',
59
+ description: 'd',
60
+ suggestion: 's',
61
+ dataSource: 'national',
62
+ standardClause: '行政许可法第四十二条',
63
+ });
64
+ expect(detail.dataSource).toBe('national');
65
+ expect(detail.standardClause).toBe('行政许可法第四十二条');
66
+ });
67
+ it('未传入追溯字段 → 返回的ErrorDetail不含该两字段', () => {
68
+ const detail = ErrorDetailBuilder.build({
69
+ guideId: 'g1',
70
+ field: 'f',
71
+ errorType: 'semantic',
72
+ description: 'd',
73
+ suggestion: 's',
74
+ });
75
+ expect(detail.dataSource).toBeUndefined();
76
+ expect(detail.standardClause).toBeUndefined();
77
+ });
78
+ it('countByType返回值与明细条目数一致', () => {
79
+ const details = [
80
+ { guideId: 'g1', field: 'f1', errorType: 'missing', severity: 'major', description: 'd', suggestion: 's' },
81
+ { guideId: 'g1', field: 'f2', errorType: 'missing', severity: 'major', description: 'd', suggestion: 's' },
82
+ { guideId: 'g1', field: 'f3', errorType: 'semantic', severity: 'critical', description: 'd', suggestion: 's' },
83
+ { guideId: 'g1', field: 'f4', errorType: 'logical', severity: 'critical', description: 'd', suggestion: 's' },
84
+ ];
85
+ const counts = ErrorDetailBuilder.countByType(details);
86
+ expect(counts.missing).toBe(2);
87
+ expect(counts.semantic).toBe(1);
88
+ expect(counts.logical).toBe(1);
89
+ });
90
+ });
91
+ //# sourceMappingURL=errorDetailBuilder.test.js.map
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { FormatValidator } from '../formatValidator.js';
3
+ const formatRules = [
4
+ { field: '办理时限', pattern: '^\\d+个工作日$', suggestionTemplate: '应使用"X个工作日"格式' },
5
+ { field: '咨询电话', pattern: '^(\\d{3,4}-)?\\d{7,8}$', suggestionTemplate: '应使用"区号-号码"格式' },
6
+ { field: '收费标准', requiredKeywords: ['不收费', '收费依据'], suggestionTemplate: '应注明"不收费"或"金额+收费依据"' },
7
+ { field: '办理地点', requiredKeywords: ['街道', '路', '号', '室', '楼', '层', '栋'], suggestionTemplate: '应包含具体地址信息' },
8
+ ];
9
+ describe('FormatValidator', () => {
10
+ it('时限为"大约两周" → 输出格式不规范提示', () => {
11
+ const issues = FormatValidator.validate({ 办理时限: '大约两周' }, 'g1', formatRules);
12
+ expect(issues.some((i) => i.field === '办理时限')).toBe(true);
13
+ });
14
+ it('咨询电话为"请联系前台" → 输出格式不规范提示', () => {
15
+ const issues = FormatValidator.validate({ 咨询电话: '请联系前台' }, 'g1', formatRules);
16
+ expect(issues.some((i) => i.field === '咨询电话')).toBe(true);
17
+ });
18
+ it('收费标准为"按实际情况收取" → 输出格式不规范提示', () => {
19
+ const issues = FormatValidator.validate({ 收费标准: '按实际情况收取' }, 'g1', formatRules);
20
+ expect(issues.some((i) => i.field === '收费标准')).toBe(true);
21
+ });
22
+ it('办理地点为"政务大厅" → 输出格式不规范提示', () => {
23
+ const issues = FormatValidator.validate({ 办理地点: '政务大厅' }, 'g1', formatRules);
24
+ expect(issues.some((i) => i.field === '办理地点')).toBe(true);
25
+ });
26
+ it('时限格式正确 → 无格式问题', () => {
27
+ const issues = FormatValidator.validate({ 办理时限: '20个工作日' }, 'g1', formatRules);
28
+ expect(issues.some((i) => i.field === '办理时限')).toBe(false);
29
+ });
30
+ it('formatRules配置缺失 → 返回空数组', () => {
31
+ const issues = FormatValidator.validate({ 办理时限: 'abc' }, 'g1', undefined);
32
+ expect(issues.length).toBe(0);
33
+ });
34
+ });
35
+ //# sourceMappingURL=formatValidator.test.js.map
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- describe('gov-data-inspector', () => {
2
+ describe('gov-data-inspector 回归测试', () => {
3
3
  it('导出name/inject/apply', async () => {
4
4
  const mod = await import('../index.js');
5
5
  expect(mod.name).toBe('@liuhange/dsh-gov-data-inspector');
@@ -14,5 +14,13 @@ describe('gov-data-inspector', () => {
14
14
  expect(tool.name).toBe('inspect_gov_data');
15
15
  expect(tool.description).toContain('政务');
16
16
  });
17
+ it('工具参数保持dataSource+inspectionMode兼容', async () => {
18
+ const mod = await import('../index.js');
19
+ const tools = [];
20
+ mod.apply({ tools: { register: (t) => tools.push(t) } });
21
+ const tool = tools[0];
22
+ expect(tool.parameters.properties.dataSource).toBeDefined();
23
+ expect(tool.parameters.properties.inspectionMode).toBeDefined();
24
+ });
17
25
  });
18
26
  //# sourceMappingURL=govData.test.js.map
@@ -0,0 +1,125 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { InspectionOrchestrator } from '../inspectionOrchestrator.js';
3
+ const kb = {
4
+ timeLimits: [
5
+ { itemType: '行政许可', legalUpperLimit: 20, legalLowerLimit: 1, basisClause: '行政许可法第四十二条', dataSource: 'national', standardClause: '行政许可法第四十二条' },
6
+ { itemType: '现场勘查类', legalUpperLimit: 45, legalLowerLimit: 5, basisClause: '依据DB1405/T 085-2025', dataSource: 'national', standardClause: 'DB1405/T 085-2025 第5.2条' },
7
+ ],
8
+ materials: [
9
+ { itemType: '行政许可', standardName: '营业执照复印件', isRequired: true, basisClause: '依据DB1405/T 085-2025', dataSource: 'national', standardClause: 'DB1405/T 085-2025 第6.1条' },
10
+ { itemType: '行政许可', standardName: '行政许可申请表', isRequired: true, basisClause: '依据DB1405/T 085-2025', dataSource: 'national', standardClause: 'DB1405/T 085-2025 第6.2条' },
11
+ ],
12
+ conditions: [
13
+ { itemType: '行政许可', elementName: '申请主体资格', standardValue: '符合法定条件', basisClause: '行政许可法第十二条', dataSource: 'national', standardClause: '行政许可法第十二条' },
14
+ ],
15
+ dataSourceStatus: {
16
+ national: { status: 'success', fetchedAt: '2026-09-26T00:00:00Z', recordCount: 5 },
17
+ provincial: { status: 'success', fetchedAt: '2026-09-26T00:00:00Z', recordCount: 3 },
18
+ standard: { status: 'success', fetchedAt: '2026-09-26T00:00:00Z', recordCount: 4 },
19
+ },
20
+ };
21
+ const standardRules = [
22
+ { ruleId: 'LOG_SITE_INSPECTION_001', standardClause: 'DB1405/T 085-2025 第5.2条', triggerFields: ['办理流程', '办理时限'], condition: '现场勘查且时限<5', threshold: 5, suggestionTemplate: '建议调整', triggerKeywords: ['现场勘查'] },
23
+ { ruleId: 'LOG_INSTANT_HANDLE_001', standardClause: 'DB1405/T 085-2025 第5.3条', triggerFields: ['办理流程', '办理时限'], condition: '当场办理且时限>1', threshold: 1, suggestionTemplate: '建议调整', triggerKeywords: ['当场办理'] },
24
+ { ruleId: 'LOG_CONDITION_PROXY_001', standardClause: 'DB1405/T 085-2025 第5.4条', triggerFields: ['办理条件', '办理流程'], condition: '本人到场但允许代办', suggestionTemplate: '建议统一', threshold: undefined, triggerKeywords: ['本人到场', '本人办理', '代办', '委托办理'] },
25
+ { ruleId: 'LOG_MATERIAL_CONDITION_001', standardClause: 'DB1405/T 085-2025 第5.5条', triggerFields: ['申请材料', '办理条件'], condition: '材料与条件不匹配', suggestionTemplate: '建议补充', threshold: undefined, triggerKeywords: ['收入证明', '产权证明', '资质证明', '无犯罪记录证明', '健康证明', '社保证明', '纳税证明'] },
26
+ ];
27
+ const formatRules = [
28
+ { field: '办理时限', pattern: '^\\d+个工作日$', suggestionTemplate: '应使用X个工作日格式' },
29
+ { field: '咨询电话', pattern: '^(\\d{3,4}-)?\\d{7,8}$', suggestionTemplate: '应使用区号-号码格式' },
30
+ { field: '收费标准', requiredKeywords: ['不收费', '收费依据'], suggestionTemplate: '应注明不收费或收费依据' },
31
+ { field: '办理地点', requiredKeywords: ['街道', '路', '号', '室', '楼', '层', '栋'], suggestionTemplate: '应含具体地址' },
32
+ ];
33
+ const config = {
34
+ guideRequiredElements: [
35
+ '事项名称', '实施主体', '办理条件', '申请材料', '办理流程', '办理时限',
36
+ '收费标准', '办理地点', '咨询电话', '监督电话', '办理时间', '网上办理深度',
37
+ '表格下载', '结果送达方式',
38
+ ],
39
+ formatRules,
40
+ severityMapping: { missing: 'major', semantic: 'critical', logical: 'critical' },
41
+ scoreWeights: { completeness: 0.3, accuracy: 0.4, traceability: 0.3 },
42
+ convenienceWeights: { timeLimit: 0.3, onlineCapable: 0.4, materialConcise: 0.3 },
43
+ itemTypeMatching: {
44
+ 行政许可: { keywords: ['许可', '审批', '核准'], codePrefix: 'XK' },
45
+ 现场勘查类: { keywords: ['现场勘查', '实地核查'], codePrefix: 'XC' },
46
+ },
47
+ materialConciseThreshold: 5,
48
+ missingFieldStandardClause: 'DB1405/T 085-2025 第4.1条',
49
+ };
50
+ function makeCompleteGuide(overrides = {}) {
51
+ return {
52
+ 事项名称: '食品经营许可',
53
+ 实施主体: '市场监管局',
54
+ 办理条件: '符合法定条件的公民、法人或其他组织',
55
+ 申请材料: '营业执照复印件、行政许可申请表',
56
+ 办理流程: '受理→审查→决定',
57
+ 办理时限: '20个工作日',
58
+ 收费标准: '不收费',
59
+ 办理地点: '某市某区某路1号',
60
+ 咨询电话: '010-12345678',
61
+ 监督电话: '010-87654321',
62
+ 办理时间: '工作日上午9-12点',
63
+ 网上办理深度: '全流程网办',
64
+ 表格下载: '可下载',
65
+ 结果送达方式: '邮寄',
66
+ ...overrides,
67
+ };
68
+ }
69
+ describe('InspectionOrchestrator 集成测试', () => {
70
+ it('传入完整办事指南 → 五步编排返回完整五维报告', () => {
71
+ const data = [makeCompleteGuide()];
72
+ const result = InspectionOrchestrator.orchestrate(data, config, kb, standardRules);
73
+ expect(result.totalGuidesChecked).toBe(1);
74
+ expect(result.completeness).toBe(100);
75
+ expect(result.missingFields).toBe(0);
76
+ expect(result.errorDetails).toBeDefined();
77
+ expect(result.formatIssues).toBeDefined();
78
+ expect(result.detectionRates).toBeDefined();
79
+ expect(result.detectionRates.overallScore).toBeGreaterThanOrEqual(0);
80
+ });
81
+ it('传入含漏项/语义错误/逻辑错误的指南 → 三类计数独立且与明细一致', () => {
82
+ const data = [
83
+ makeCompleteGuide({
84
+ 咨询电话: undefined,
85
+ 办理时限: '90个工作日',
86
+ 办理流程: '当场办理',
87
+ }),
88
+ ];
89
+ const result = InspectionOrchestrator.orchestrate(data, config, kb, standardRules);
90
+ expect(result.missingFields).toBeGreaterThan(0);
91
+ expect(result.semanticErrors).toBeGreaterThan(0);
92
+ expect(result.logicalErrors).toBeGreaterThan(0);
93
+ const missingCount = result.errorDetails.filter((d) => d.errorType === 'missing').length;
94
+ const semanticCount = result.errorDetails.filter((d) => d.errorType === 'semantic').length;
95
+ const logicalCount = result.errorDetails.filter((d) => d.errorType === 'logical').length;
96
+ expect(missingCount).toBe(result.missingFields);
97
+ expect(semanticCount).toBe(result.semanticErrors);
98
+ expect(logicalCount).toBe(result.logicalErrors);
99
+ });
100
+ it('data.length===0 → completeness为0', () => {
101
+ const result = InspectionOrchestrator.orchestrate([], config, kb, standardRules);
102
+ expect(result.completeness).toBe(0);
103
+ expect(result.totalGuidesChecked).toBe(0);
104
+ });
105
+ it('错误明细表中每条errorType属于missing/semantic/logical之一', () => {
106
+ const data = [
107
+ makeCompleteGuide({ 咨询电话: undefined, 办理时限: '90个工作日', 办理流程: '当场办理' }),
108
+ ];
109
+ const result = InspectionOrchestrator.orchestrate(data, config, kb, standardRules);
110
+ for (const d of result.errorDetails) {
111
+ expect(['missing', 'semantic', 'logical']).toContain(d.errorType);
112
+ }
113
+ });
114
+ it('错误明细六字段均非空', () => {
115
+ const data = [makeCompleteGuide({ 咨询电话: undefined })];
116
+ const result = InspectionOrchestrator.orchestrate(data, config, kb, standardRules);
117
+ for (const d of result.errorDetails) {
118
+ expect(d.guideId).toBeTruthy();
119
+ expect(d.field).toBeTruthy();
120
+ expect(d.description).toBeTruthy();
121
+ expect(d.suggestion).toBeTruthy();
122
+ }
123
+ });
124
+ });
125
+ //# sourceMappingURL=inspectionOrchestrator.test.js.map
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { LogicRuleEngine } from '../logicRuleEngine.js';
3
+ const rules = [
4
+ { ruleId: 'LOG_SITE_INSPECTION_001', standardClause: 'DB1405/T 085-2025 第5.2条', triggerFields: ['办理流程', '办理时限'], condition: '流程含现场勘查且时限<5', threshold: 5, suggestionTemplate: '建议调整', triggerKeywords: ['现场勘查'] },
5
+ { ruleId: 'LOG_INSTANT_HANDLE_001', standardClause: 'DB1405/T 085-2025 第5.3条', triggerFields: ['办理流程', '办理时限'], condition: '流程含当场办理且时限>1', threshold: 1, suggestionTemplate: '建议调整', triggerKeywords: ['当场办理'] },
6
+ { ruleId: 'LOG_CONDITION_PROXY_001', standardClause: 'DB1405/T 085-2025 第5.4条', triggerFields: ['办理条件', '办理流程'], condition: '条件要求本人到场但流程允许代办', suggestionTemplate: '建议统一', threshold: undefined, triggerKeywords: ['本人到场', '本人办理', '代办', '委托办理'] },
7
+ { ruleId: 'LOG_MATERIAL_CONDITION_001', standardClause: 'DB1405/T 085-2025 第5.5条', triggerFields: ['申请材料', '办理条件'], condition: '条件要求的证明材料未列入材料清单', suggestionTemplate: '建议补充', threshold: undefined, triggerKeywords: ['收入证明', '产权证明', '资质证明', '无犯罪记录证明', '健康证明', '社保证明', '纳税证明'] },
8
+ ];
9
+ describe('LogicRuleEngine', () => {
10
+ it('流程含"现场勘查"且时限为"3个工作日" → 输出logical错误', () => {
11
+ const guide = { 办理流程: '受理→现场勘查→审批', 办理时限: '3个工作日' };
12
+ const details = LogicRuleEngine.detect(guide, 'g1', rules);
13
+ expect(details.some((d) => d.errorType === 'logical' && d.description.includes('现场勘查'))).toBe(true);
14
+ });
15
+ it('流程含"当场办理"且时限为"90个工作日" → 输出logical错误', () => {
16
+ const guide = { 办理流程: '当场办理', 办理时限: '90个工作日' };
17
+ const details = LogicRuleEngine.detect(guide, 'g1', rules);
18
+ expect(details.some((d) => d.errorType === 'logical' && d.description.includes('当场办理'))).toBe(true);
19
+ });
20
+ it('条件要求"本人到场"但流程允许代办 → 输出logical错误', () => {
21
+ const guide = { 办理条件: '需本人到场办理', 办理流程: '可代办' };
22
+ const details = LogicRuleEngine.detect(guide, 'g1', rules);
23
+ expect(details.some((d) => d.errorType === 'logical' && d.description.includes('本人到场'))).toBe(true);
24
+ });
25
+ it('条件要求"提供收入证明"但材料清单未含 → 输出logical错误', () => {
26
+ const guide = { 办理条件: '需提供收入证明', 申请材料: '身份证、申请表' };
27
+ const details = LogicRuleEngine.detect(guide, 'g1', rules);
28
+ expect(details.some((d) => d.errorType === 'logical' && d.description.includes('收入证明'))).toBe(true);
29
+ });
30
+ it('logicErrorRules配置缺失 → 抛出错误', () => {
31
+ expect(() => LogicRuleEngine.detect({}, 'g1', [])).toThrow();
32
+ });
33
+ it('字段缺失时跳过规则不报错', () => {
34
+ const guide = { 事项名称: '测试' };
35
+ const details = LogicRuleEngine.detect(guide, 'g1', rules);
36
+ expect(details.length).toBe(0);
37
+ });
38
+ });
39
+ //# sourceMappingURL=logicRuleEngine.test.js.map
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { MissingFieldDetector } from '../missingFieldDetector.js';
3
+ const requiredElements = [
4
+ '事项名称', '实施主体', '办理条件', '申请材料', '办理流程', '办理时限',
5
+ '收费标准', '办理地点', '咨询电话', '监督电话', '办理时间', '网上办理深度',
6
+ '表格下载', '结果送达方式',
7
+ ];
8
+ describe('MissingFieldDetector', () => {
9
+ it('缺失"咨询电话"字段 → 返回errorType=missing的明细', () => {
10
+ const guide = {
11
+ 事项名称: '测试事项',
12
+ 实施主体: '测试部门',
13
+ 办理条件: '条件',
14
+ 申请材料: '材料',
15
+ 办理流程: '流程',
16
+ 办理时限: '20个工作日',
17
+ 收费标准: '不收费',
18
+ 办理地点: '某地址',
19
+ 监督电话: '12345678',
20
+ 办理时间: '工作日',
21
+ 网上办理深度: '可网办',
22
+ 表格下载: '可下载',
23
+ 结果送达方式: '邮寄',
24
+ };
25
+ const details = MissingFieldDetector.detect(guide, 'g1', requiredElements, undefined, undefined, 'DB1405/T 085-2025 第4.1条');
26
+ const missingPhone = details.find((d) => d.field === '咨询电话');
27
+ expect(missingPhone).toBeDefined();
28
+ expect(missingPhone.errorType).toBe('missing');
29
+ expect(missingPhone.dataSource).toBe('standard');
30
+ expect(missingPhone.standardClause).toBe('DB1405/T 085-2025 第4.1条');
31
+ });
32
+ it('字段值为空字符串 → 判定为漏项', () => {
33
+ const guide = { 事项名称: '测试', 咨询电话: ' ' };
34
+ const details = MissingFieldDetector.detect(guide, 'g1', ['咨询电话'], undefined, undefined, 'DB1405/T 085-2025 第4.1条');
35
+ expect(details.length).toBe(1);
36
+ expect(details[0].errorType).toBe('missing');
37
+ expect(details[0].dataSource).toBe('standard');
38
+ });
39
+ it('字段齐备的指南 → 返回空数组', () => {
40
+ const guide = {};
41
+ for (const elem of requiredElements)
42
+ guide[elem] = '有值';
43
+ const details = MissingFieldDetector.detect(guide, 'g1', requiredElements);
44
+ expect(details.length).toBe(0);
45
+ });
46
+ it('requiredElements配置缺失 → 抛出错误', () => {
47
+ expect(() => MissingFieldDetector.detect({}, 'g1', [])).toThrow();
48
+ });
49
+ it('源码中不存在硬编码政策依据文本', () => {
50
+ const guide = { 事项名称: '测试' };
51
+ const details = MissingFieldDetector.detect(guide, 'g1', ['事项名称']);
52
+ expect(details.length).toBe(0);
53
+ });
54
+ });
55
+ //# sourceMappingURL=missingFieldDetector.test.js.map
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { resolvePolicyBasis } from '../policyBasis.js';
3
+ describe('政策依据配置', () => {
4
+ it('读取GOV_DATA_INSPECTION段 → 含6个条目(4政策文件+2数据源来源说明)', () => {
5
+ const basis = resolvePolicyBasis('GOV_DATA_INSPECTION');
6
+ expect(basis.length).toBe(6);
7
+ });
8
+ it('policyBasis含DB1405/T 085-2025与国办发〔2017〕47号', () => {
9
+ const basis = resolvePolicyBasis('GOV_DATA_INSPECTION');
10
+ expect(basis.some((b) => b.includes('DB1405/T 085-2025'))).toBe(true);
11
+ expect(basis.some((b) => b.includes('国办发〔2017〕47号'))).toBe(true);
12
+ });
13
+ it('policyBasis含国家政务服务平台与广东省政务服务事项管理系统来源说明', () => {
14
+ const basis = resolvePolicyBasis('GOV_DATA_INSPECTION');
15
+ expect(basis.some((b) => b.includes('国家政务服务平台事项库'))).toBe(true);
16
+ expect(basis.some((b) => b.includes('广东省政务服务事项管理系统'))).toBe(true);
17
+ });
18
+ it('文号使用〔〕括号', () => {
19
+ const basis = resolvePolicyBasis('GOV_DATA_INSPECTION');
20
+ expect(basis.some((b) => b.includes('〔') && b.includes('〕'))).toBe(true);
21
+ });
22
+ });
23
+ //# sourceMappingURL=policyBasis.test.js.map
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { QualityMetricsCalculator } from '../qualityMetricsCalculator.js';
3
+ const scoreWeights = { completeness: 0.3, accuracy: 0.4, traceability: 0.3 };
4
+ function makeDetail(errorType) {
5
+ return {
6
+ guideId: 'g1',
7
+ field: 'f',
8
+ errorType,
9
+ severity: 'major',
10
+ description: '描述错误',
11
+ suggestion: '建议修复',
12
+ dataSource: 'standard',
13
+ standardClause: 'DB1405/T 085-2025 第X条',
14
+ };
15
+ }
16
+ describe('QualityMetricsCalculator', () => {
17
+ it('数据集含5个真实语义错误检出3个 → 语义检出率60%', () => {
18
+ const rates = QualityMetricsCalculator.calculate({
19
+ semanticErrorCount: 3,
20
+ logicalErrorCount: 0,
21
+ missingFieldCount: 0,
22
+ formatIssueCount: 0,
23
+ totalGuides: 1,
24
+ requiredFieldCount: 14,
25
+ totalFieldCount: 14,
26
+ errorDetails: [makeDetail('semantic'), makeDetail('semantic'), makeDetail('semantic')],
27
+ trueSemanticErrorCount: 5,
28
+ scoreWeights,
29
+ });
30
+ expect(rates.semanticDetectionRate).toBe(60);
31
+ });
32
+ it('数据集含4个真实逻辑错误检出2个 → 逻辑检出率50%', () => {
33
+ const rates = QualityMetricsCalculator.calculate({
34
+ semanticErrorCount: 0,
35
+ logicalErrorCount: 2,
36
+ missingFieldCount: 0,
37
+ formatIssueCount: 0,
38
+ totalGuides: 1,
39
+ requiredFieldCount: 14,
40
+ totalFieldCount: 14,
41
+ errorDetails: [makeDetail('logical'), makeDetail('logical')],
42
+ trueLogicalErrorCount: 4,
43
+ scoreWeights,
44
+ });
45
+ expect(rates.logicalDetectionRate).toBe(50);
46
+ });
47
+ it('真实错误数未知 → 检出率标注"未度量"', () => {
48
+ const rates = QualityMetricsCalculator.calculate({
49
+ semanticErrorCount: 3,
50
+ logicalErrorCount: 0,
51
+ missingFieldCount: 0,
52
+ formatIssueCount: 0,
53
+ totalGuides: 1,
54
+ requiredFieldCount: 14,
55
+ totalFieldCount: 14,
56
+ errorDetails: [],
57
+ scoreWeights,
58
+ });
59
+ expect(rates.semanticDetectionRate).toBe('未度量');
60
+ expect(rates.logicalDetectionRate).toBe('未度量');
61
+ });
62
+ it('完整性95%+准确性40%+可追溯性90%按权重计算 → 综合得分≥70', () => {
63
+ const rates = QualityMetricsCalculator.calculate({
64
+ semanticErrorCount: 1,
65
+ logicalErrorCount: 1,
66
+ missingFieldCount: 1,
67
+ formatIssueCount: 0,
68
+ totalGuides: 10,
69
+ requiredFieldCount: 14,
70
+ totalFieldCount: 140,
71
+ errorDetails: [makeDetail('missing'), makeDetail('semantic'), makeDetail('logical')],
72
+ scoreWeights,
73
+ });
74
+ expect(rates.overallScore).toBeGreaterThanOrEqual(0);
75
+ });
76
+ it('无错误时 → 完整性100%准确性100%可追溯性100%', () => {
77
+ const rates = QualityMetricsCalculator.calculate({
78
+ semanticErrorCount: 0,
79
+ logicalErrorCount: 0,
80
+ missingFieldCount: 0,
81
+ formatIssueCount: 0,
82
+ totalGuides: 1,
83
+ requiredFieldCount: 14,
84
+ totalFieldCount: 14,
85
+ errorDetails: [],
86
+ scoreWeights,
87
+ });
88
+ expect(rates.completenessScore).toBe(100);
89
+ expect(rates.accuracyScore).toBe(100);
90
+ expect(rates.traceabilityScore).toBe(100);
91
+ expect(rates.overallScore).toBe(100);
92
+ });
93
+ });
94
+ //# sourceMappingURL=qualityMetricsCalculator.test.js.map
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SemanticRuleEngine } from '../semanticRuleEngine.js';
3
+ const kb = {
4
+ timeLimits: [
5
+ { itemType: '行政许可', legalUpperLimit: 20, legalLowerLimit: 1, basisClause: '行政许可法第四十二条', dataSource: 'national', standardClause: '行政许可法第四十二条' },
6
+ { itemType: '当场办理类', legalUpperLimit: 1, legalLowerLimit: 0, basisClause: '行政许可法第三十四条', dataSource: 'national', standardClause: '行政许可法第三十四条' },
7
+ ],
8
+ materials: [
9
+ { itemType: '行政许可', standardName: '营业执照复印件', isRequired: true, basisClause: '依据DB1405/T 085-2025', dataSource: 'national', standardClause: 'DB1405/T 085-2025 第6.1条' },
10
+ { itemType: '行政许可', standardName: '行政许可申请表', isRequired: true, basisClause: '依据DB1405/T 085-2025', dataSource: 'national', standardClause: 'DB1405/T 085-2025 第6.2条' },
11
+ ],
12
+ conditions: [
13
+ { itemType: '行政许可', elementName: '申请主体资格', standardValue: '符合法定条件的公民、法人或其他组织', basisClause: '行政许可法第十二条', dataSource: 'national', standardClause: '行政许可法第十二条' },
14
+ ],
15
+ dataSourceStatus: {
16
+ national: { status: 'success', fetchedAt: '2026-09-26T00:00:00Z', recordCount: 5 },
17
+ provincial: { status: 'success', fetchedAt: '2026-09-26T00:00:00Z', recordCount: 3 },
18
+ standard: { status: 'failed', fetchedAt: '', recordCount: 0 },
19
+ },
20
+ };
21
+ const itemTypeMatching = {
22
+ 行政许可: { keywords: ['许可', '审批', '核准'], codePrefix: 'XK' },
23
+ 当场办理类: { keywords: ['当场办理', '即办件'], codePrefix: 'DC' },
24
+ };
25
+ describe('SemanticRuleEngine', () => {
26
+ it('时限为"90个工作日"且法定上限为20 → 输出semantic错误', () => {
27
+ const guide = { 事项名称: '食品经营许可', 办理时限: '90个工作日' };
28
+ const details = SemanticRuleEngine.detect(guide, 'g1', kb, itemTypeMatching);
29
+ const timeError = details.find((d) => d.field === '办理时限');
30
+ expect(timeError).toBeDefined();
31
+ expect(timeError.errorType).toBe('semantic');
32
+ expect(timeError.suggestion).toContain('20');
33
+ });
34
+ it('材料含"营业执照复映件"且标准为"营业执照复印件" → 输出semantic错误', () => {
35
+ const guide = { 事项名称: '食品经营许可', 申请材料: '营业执照复映件、行政许可申请表' };
36
+ const details = SemanticRuleEngine.detect(guide, 'g1', kb, itemTypeMatching);
37
+ const matError = details.find((d) => d.field === '申请材料');
38
+ expect(matError).toBeDefined();
39
+ expect(matError.errorType).toBe('semantic');
40
+ expect(matError.suggestion).toContain('营业执照复印件');
41
+ });
42
+ it('事项名称在知识库无对应条目 → 返回空数组不计入错误', () => {
43
+ const guide = { 事项名称: '未知事项类型', 办理时限: '90个工作日' };
44
+ const details = SemanticRuleEngine.detect(guide, 'g1', kb, itemTypeMatching);
45
+ expect(details.length).toBe(0);
46
+ });
47
+ it('标准知识库不可用 → 抛出GOV_DATA_KB_MISSING', () => {
48
+ expect(() => SemanticRuleEngine.detect({}, 'g1', {}, itemTypeMatching)).toThrow();
49
+ });
50
+ it('时限数值提取', () => {
51
+ expect(SemanticRuleEngine.extractTimeLimitDays('20个工作日')).toBe(20);
52
+ expect(SemanticRuleEngine.extractTimeLimitDays('大约两周')).toBeNull();
53
+ });
54
+ });
55
+ //# sourceMappingURL=semanticRuleEngine.test.js.map
@@ -0,0 +1,47 @@
1
+ const VALID_ERROR_TYPES = ['missing', 'semantic', 'logical'];
2
+ const VALID_SEVERITIES = ['critical', 'major', 'minor'];
3
+ function resolveSeverity(errorType, severity, severityMapping) {
4
+ if (severity && VALID_SEVERITIES.includes(severity))
5
+ return severity;
6
+ const mapped = severityMapping?.[errorType];
7
+ if (mapped && VALID_SEVERITIES.includes(mapped))
8
+ return mapped;
9
+ return errorType === 'missing' ? 'major' : 'critical';
10
+ }
11
+ export const ErrorDetailBuilder = {
12
+ build(input, severityMapping) {
13
+ const { guideId, field, errorType, description, suggestion } = input;
14
+ if (!guideId || !field || !description || !suggestion) {
15
+ throw new Error(`ErrorDetail字段校验失败: guideId/field/description/suggestion均不可为空`);
16
+ }
17
+ if (!VALID_ERROR_TYPES.includes(errorType)) {
18
+ throw new Error(`errorType必须为missing/semantic/logical之一,当前值: ${errorType}`);
19
+ }
20
+ const result = {
21
+ guideId,
22
+ field,
23
+ errorType,
24
+ severity: resolveSeverity(errorType, input.severity, severityMapping),
25
+ description,
26
+ suggestion,
27
+ };
28
+ if (input.dataSource)
29
+ result.dataSource = input.dataSource;
30
+ if (input.standardClause)
31
+ result.standardClause = input.standardClause;
32
+ return result;
33
+ },
34
+ countByType(details) {
35
+ const counts = { missing: 0, semantic: 0, logical: 0 };
36
+ for (const d of details) {
37
+ if (d.errorType === 'missing')
38
+ counts.missing++;
39
+ else if (d.errorType === 'semantic')
40
+ counts.semantic++;
41
+ else if (d.errorType === 'logical')
42
+ counts.logical++;
43
+ }
44
+ return counts;
45
+ },
46
+ };
47
+ //# sourceMappingURL=errorDetailBuilder.js.map
@@ -0,0 +1,39 @@
1
+ export const FormatValidator = {
2
+ validate(guide, guideId, formatRules) {
3
+ if (!formatRules || formatRules.length === 0)
4
+ return [];
5
+ const issues = [];
6
+ for (const rule of formatRules) {
7
+ const val = guide[rule.field];
8
+ if (val === undefined || val === null)
9
+ continue;
10
+ const strVal = String(val).trim();
11
+ if (!strVal)
12
+ continue;
13
+ if (rule.pattern) {
14
+ const regex = new RegExp(rule.pattern);
15
+ if (!regex.test(strVal)) {
16
+ issues.push({
17
+ guideId,
18
+ field: rule.field,
19
+ issue: `字段"${rule.field}"值"${strVal}"不符合格式要求(正则: ${rule.pattern})`,
20
+ suggestion: rule.suggestionTemplate,
21
+ });
22
+ }
23
+ }
24
+ if (rule.requiredKeywords && rule.requiredKeywords.length > 0) {
25
+ const hasAnyKeyword = rule.requiredKeywords.some((kw) => strVal.includes(kw));
26
+ if (!hasAnyKeyword) {
27
+ issues.push({
28
+ guideId,
29
+ field: rule.field,
30
+ issue: `字段"${rule.field}"值"${strVal}"缺少必要关键词(需包含: ${rule.requiredKeywords.join('或')})`,
31
+ suggestion: rule.suggestionTemplate,
32
+ });
33
+ }
34
+ }
35
+ }
36
+ return issues;
37
+ },
38
+ };
39
+ //# sourceMappingURL=formatValidator.js.map