@liuhange/dsh-underground-pipeline-inspector 1.0.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/lib/index.js ADDED
@@ -0,0 +1,248 @@
1
+ export const name = '@liuhange/dsh-underground-pipeline-inspector';
2
+ export const inject = ['tools'];
3
+ import { readFileSync, existsSync } from 'node:fs';
4
+ import { resolve, dirname } from 'node:path';
5
+ import { PipelineConfigLoader } from './pipeline-config-loader.js';
6
+ import { InputValidator } from './input-validator.js';
7
+ import { CoverageChecker } from './coverage-checker.js';
8
+ import { EdgeMatchingChecker } from './edge-matching-checker.js';
9
+ import { TopologyChecker } from './topology-checker.js';
10
+ import { CoordinateChecker } from './coordinate-checker.js';
11
+ import { FlowDirectionChecker } from './flow-direction-checker.js';
12
+ import { AttributePrecisionChecker } from './attribute-precision-checker.js';
13
+ import { SpatialReferenceChecker } from './spatial-reference-checker.js';
14
+ import { QualityElementScorer } from './quality-element-scorer.js';
15
+ import { ErrorClassifier } from './error-classifier.js';
16
+ import { GrossErrorRateCalculator } from './gross-error-rate-calculator.js';
17
+ import { InspectionReportGenerator } from './inspection-report-generator.js';
18
+ import { ReviewOverrideRecorder } from './review-override-recorder.js';
19
+ import { PolicyBasisBuilder } from './policy-basis-builder.js';
20
+ import { ERROR_CODES, LEGAL_DISCLAIMER } from './invariant.js';
21
+ export function apply(ctx) {
22
+ ctx.tools.register({
23
+ name: 'inspect_underground_pipeline',
24
+ description: '对地下管线数据进行质量巡检,输出检测结果、质量元素评分、错误分类统计、粗差率与政策依据',
25
+ parameters: {
26
+ type: 'object',
27
+ properties: {
28
+ datasetFilePath: { type: 'string', description: '地下管线数据集文件路径(JSON格式,含points/segments/structures)' },
29
+ outputPathDir: { type: 'string', description: '报告输出目录(默认数据文件所在目录)' },
30
+ },
31
+ required: ['datasetFilePath'],
32
+ },
33
+ async execute(args) {
34
+ try {
35
+ if (!args || typeof args !== 'object') {
36
+ return JSON.stringify({ error: ERROR_CODES.INPUT_INVALID, message: '参数不能为空' });
37
+ }
38
+ const datasetFilePath = args.datasetFilePath;
39
+ if (!datasetFilePath || typeof datasetFilePath !== 'string') {
40
+ return JSON.stringify({ error: ERROR_CODES.INPUT_INVALID, message: 'datasetFilePath 为必填字段' });
41
+ }
42
+ if (!existsSync(datasetFilePath)) {
43
+ return JSON.stringify({ error: ERROR_CODES.FILE_NOT_FOUND, message: `文件不存在 - ${datasetFilePath}` });
44
+ }
45
+ const datasetFileRaw = readFileSync(datasetFilePath, 'utf-8');
46
+ let datasetFileData;
47
+ try {
48
+ datasetFileData = JSON.parse(datasetFileRaw);
49
+ }
50
+ catch {
51
+ return JSON.stringify({ error: ERROR_CODES.CONFIG_INVALID, message: '数据集文件 JSON 解析失败' });
52
+ }
53
+ const configLoader = new PipelineConfigLoader();
54
+ const configLoadResult = configLoader.load();
55
+ const { inspectionConfig, attributeConfig, qualityElementConfig } = configLoadResult;
56
+ const inputValidator = new InputValidator();
57
+ const { dataset, errors: validationErrors } = inputValidator.validateDataset(datasetFileData);
58
+ if (dataset === null) {
59
+ return JSON.stringify({
60
+ error: ERROR_CODES.DATASET_EMPTY,
61
+ message: '数据集无效,无法执行巡检',
62
+ validationErrors,
63
+ });
64
+ }
65
+ const allCheckResults = [];
66
+ const allWarnings = [...configLoadResult.warnings];
67
+ const coverageChecker = new CoverageChecker();
68
+ const { checkResults: coverageResults } = coverageChecker.check(dataset, inspectionConfig);
69
+ allCheckResults.push(...coverageResults);
70
+ const edgeMatchingChecker = new EdgeMatchingChecker();
71
+ const { checkResults: edgeResults } = edgeMatchingChecker.check(dataset.points, inspectionConfig);
72
+ allCheckResults.push(...edgeResults);
73
+ const topologyChecker = new TopologyChecker();
74
+ const { checkResults: topologyResults } = topologyChecker.check(dataset, inspectionConfig);
75
+ allCheckResults.push(...topologyResults);
76
+ const coordinateChecker = new CoordinateChecker();
77
+ const { checkResults: coordinateResults } = coordinateChecker.check(dataset, inspectionConfig);
78
+ allCheckResults.push(...coordinateResults);
79
+ const flowDirectionChecker = new FlowDirectionChecker();
80
+ const { checkResults: flowResults } = flowDirectionChecker.check(dataset, attributeConfig);
81
+ allCheckResults.push(...flowResults);
82
+ const attributePrecisionChecker = new AttributePrecisionChecker();
83
+ const { checkResults: attributeResults } = attributePrecisionChecker.check(dataset, attributeConfig);
84
+ allCheckResults.push(...attributeResults);
85
+ const spatialReferenceChecker = new SpatialReferenceChecker();
86
+ const { checkResults: spatialResults } = spatialReferenceChecker.check(dataset, inspectionConfig);
87
+ allCheckResults.push(...spatialResults);
88
+ const qualityElementScorer = new QualityElementScorer();
89
+ const { scores: qualityElementScores, totalScore } = qualityElementScorer.score(allCheckResults, qualityElementConfig);
90
+ const policyBasisBuilder = new PolicyBasisBuilder();
91
+ const errorClassPolicyBasis = policyBasisBuilder.buildByKey('GBT_24356', '第7.2条');
92
+ const errorClassifier = new ErrorClassifier();
93
+ const errorClassStatistics = errorClassifier.classify(allCheckResults, errorClassPolicyBasis);
94
+ const grossErrorRateCalculator = new GrossErrorRateCalculator();
95
+ const grossErrorRate = grossErrorRateCalculator.calculate(allCheckResults, dataset.points.length, inspectionConfig);
96
+ const overrideRecorder = new ReviewOverrideRecorder();
97
+ const overrideList = overrideRecorder.readAll();
98
+ const configVersions = {
99
+ inspectionConfigVersion: inspectionConfig.version,
100
+ attributeConfigVersion: attributeConfig.version,
101
+ qualityElementConfigVersion: qualityElementConfig.version,
102
+ };
103
+ if (dataset.points.length === 0 && dataset.segments.length === 0) {
104
+ allWarnings.push('巡检范围内无管线数据');
105
+ }
106
+ const reportGenerator = new InspectionReportGenerator();
107
+ const report = reportGenerator.generate({
108
+ checkResults: allCheckResults,
109
+ qualityElementScores,
110
+ totalQualityScore: totalScore,
111
+ errorClassStatistics,
112
+ grossErrorRate,
113
+ overrideList,
114
+ errorList: validationErrors,
115
+ configVersions,
116
+ inspectionScope: datasetFilePath,
117
+ warnings: allWarnings,
118
+ });
119
+ const outputPathDir = args.outputPathDir ?? dirname(datasetFilePath);
120
+ const jsonPath = resolve(outputPathDir, 'inspection-report.json');
121
+ const markdownPath = resolve(outputPathDir, 'inspection-report.md');
122
+ reportGenerator.writeJson(report, jsonPath);
123
+ reportGenerator.writeMarkdown(report, markdownPath);
124
+ const result = {
125
+ reportId: report.reportId,
126
+ inspectionTime: report.inspectionTime,
127
+ summary: {
128
+ totalPoints: dataset.points.length,
129
+ totalSegments: dataset.segments.length,
130
+ totalStructures: dataset.structures.length,
131
+ totalCheckResults: allCheckResults.length,
132
+ classAErrors: errorClassStatistics.classACount,
133
+ classBErrors: errorClassStatistics.classBCount,
134
+ classCErrors: errorClassStatistics.classCCount,
135
+ classDErrors: errorClassStatistics.classDCount,
136
+ totalQualityScore: totalScore,
137
+ grossErrorRate: grossErrorRate.rate,
138
+ grossErrorRatePassed: grossErrorRate.passed,
139
+ overrideCount: overrideList.length,
140
+ validationErrorCount: validationErrors.length,
141
+ },
142
+ configVersions,
143
+ legalDisclaimer: LEGAL_DISCLAIMER,
144
+ reportPaths: { json: jsonPath, markdown: markdownPath },
145
+ };
146
+ return JSON.stringify(result, null, 2);
147
+ }
148
+ catch (e) {
149
+ return JSON.stringify({ error: ERROR_CODES.UNKNOWN_ERROR, message: e.message });
150
+ }
151
+ },
152
+ });
153
+ ctx.tools.register({
154
+ name: 'submit_review_override',
155
+ description: '提交人工复核覆盖指令,对自动判定结果进行人工覆盖并记录覆盖理由',
156
+ parameters: {
157
+ type: 'object',
158
+ properties: {
159
+ objectId: { type: 'string', description: '被覆盖的对象标识' },
160
+ overrideConclusion: { type: 'string', description: '覆盖后结论(合格/不合格/误判/需复检)' },
161
+ overrideReason: { type: 'string', description: '覆盖理由(不可为空)' },
162
+ operator: { type: 'string', description: '操作人标识' },
163
+ inspectionReportPath: { type: 'string', description: '巡检报告路径(用于定位判定结果)' },
164
+ },
165
+ required: ['objectId', 'overrideConclusion', 'overrideReason', 'operator', 'inspectionReportPath'],
166
+ },
167
+ async execute(args) {
168
+ try {
169
+ if (!args || typeof args !== 'object') {
170
+ return JSON.stringify({ error: ERROR_CODES.INPUT_INVALID, message: '参数不能为空' });
171
+ }
172
+ const objectId = args.objectId;
173
+ const overrideConclusion = args.overrideConclusion;
174
+ const overrideReason = args.overrideReason;
175
+ const operator = args.operator;
176
+ const inspectionReportPath = args.inspectionReportPath;
177
+ if (!objectId || typeof objectId !== 'string') {
178
+ return JSON.stringify({ error: ERROR_CODES.INPUT_INVALID, message: 'objectId 为必填字段' });
179
+ }
180
+ if (!overrideConclusion || typeof overrideConclusion !== 'string') {
181
+ return JSON.stringify({ error: ERROR_CODES.INPUT_INVALID, message: 'overrideConclusion 为必填字段' });
182
+ }
183
+ if (!overrideReason || typeof overrideReason !== 'string') {
184
+ return JSON.stringify({ error: ERROR_CODES.REASON_REQUIRED, message: '覆盖理由不可为空' });
185
+ }
186
+ if (!operator || typeof operator !== 'string') {
187
+ return JSON.stringify({ error: ERROR_CODES.INPUT_INVALID, message: 'operator 为必填字段' });
188
+ }
189
+ if (!inspectionReportPath || typeof inspectionReportPath !== 'string') {
190
+ return JSON.stringify({ error: ERROR_CODES.INPUT_INVALID, message: 'inspectionReportPath 为必填字段' });
191
+ }
192
+ if (!existsSync(inspectionReportPath)) {
193
+ return JSON.stringify({ error: ERROR_CODES.FILE_NOT_FOUND, message: `巡检报告文件不存在 - ${inspectionReportPath}` });
194
+ }
195
+ const reportRaw = readFileSync(inspectionReportPath, 'utf-8');
196
+ let reportData;
197
+ try {
198
+ reportData = JSON.parse(reportRaw);
199
+ }
200
+ catch {
201
+ return JSON.stringify({ error: ERROR_CODES.CONFIG_INVALID, message: '巡检报告文件 JSON 解析失败' });
202
+ }
203
+ const existingObjectIds = new Set();
204
+ for (const item of reportData.checkResults) {
205
+ existingObjectIds.add(item.objectId);
206
+ }
207
+ const overrideRecorder = new ReviewOverrideRecorder();
208
+ const validation = overrideRecorder.validateOverrideInstruction(objectId, overrideConclusion, overrideReason, existingObjectIds);
209
+ if (!validation.valid) {
210
+ return JSON.stringify({ error: validation.error, message: validation.message });
211
+ }
212
+ const originalConclusion = findOriginalConclusion(reportData, objectId);
213
+ const overrideRecord = {
214
+ objectId,
215
+ originalConclusion,
216
+ newConclusion: overrideConclusion,
217
+ overrideReason,
218
+ operator,
219
+ overrideTime: new Date().toISOString(),
220
+ };
221
+ overrideRecorder.record(overrideRecord);
222
+ const result = {
223
+ success: true,
224
+ objectId,
225
+ originalConclusion,
226
+ newConclusion: overrideConclusion,
227
+ overrideReason,
228
+ operator,
229
+ overrideTime: overrideRecord.overrideTime,
230
+ message: '人工复核覆盖已记录',
231
+ };
232
+ return JSON.stringify(result, null, 2);
233
+ }
234
+ catch (e) {
235
+ return JSON.stringify({ error: ERROR_CODES.UNKNOWN_ERROR, message: e.message });
236
+ }
237
+ },
238
+ });
239
+ }
240
+ function findOriginalConclusion(report, objectId) {
241
+ for (const item of report.checkResults) {
242
+ if (item.objectId === objectId) {
243
+ return `${item.errorClass}类错误`;
244
+ }
245
+ }
246
+ return '未知';
247
+ }
248
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,245 @@
1
+ const POINT_REQUIRED_FIELDS = ['pointId', 'pointType', 'x', 'y', 'elevation'];
2
+ const SEGMENT_REQUIRED_FIELDS = [
3
+ 'segmentId',
4
+ 'startPointId',
5
+ 'endPointId',
6
+ 'diameter',
7
+ 'material',
8
+ 'buryMethod',
9
+ ];
10
+ const STRUCTURE_REQUIRED_FIELDS = ['structureId', 'structureType'];
11
+ export class InputValidator {
12
+ validatePoint(raw) {
13
+ if (raw === null || raw === undefined || typeof raw !== 'object' || Array.isArray(raw)) {
14
+ return {
15
+ point: null,
16
+ error: {
17
+ objectId: 'unknown',
18
+ missingFields: [...POINT_REQUIRED_FIELDS],
19
+ errorMessage: '管线点数据不是有效对象',
20
+ },
21
+ };
22
+ }
23
+ const obj = raw;
24
+ const missingFields = [];
25
+ const errors = [];
26
+ for (const field of POINT_REQUIRED_FIELDS) {
27
+ const value = obj[field];
28
+ if (value === null || value === undefined || value === '') {
29
+ missingFields.push(field);
30
+ }
31
+ }
32
+ if (typeof obj.pointId === 'string' && obj.pointId !== '') {
33
+ if (typeof obj.x !== 'number' || Number.isNaN(obj.x)) {
34
+ errors.push('x 必须为数值');
35
+ if (!missingFields.includes('x'))
36
+ missingFields.push('x');
37
+ }
38
+ if (typeof obj.y !== 'number' || Number.isNaN(obj.y)) {
39
+ errors.push('y 必须为数值');
40
+ if (!missingFields.includes('y'))
41
+ missingFields.push('y');
42
+ }
43
+ if (typeof obj.elevation !== 'number' || Number.isNaN(obj.elevation)) {
44
+ errors.push('elevation 必须为数值');
45
+ if (!missingFields.includes('elevation'))
46
+ missingFields.push('elevation');
47
+ }
48
+ }
49
+ if (missingFields.length > 0 || errors.length > 0) {
50
+ return {
51
+ point: null,
52
+ error: {
53
+ objectId: typeof obj.pointId === 'string' && obj.pointId !== '' ? obj.pointId : 'unknown',
54
+ missingFields,
55
+ errorMessage: errors.length > 0 ? errors.join('; ') : '必填字段缺失',
56
+ },
57
+ };
58
+ }
59
+ const point = {
60
+ pointId: obj.pointId,
61
+ pointType: obj.pointType,
62
+ x: obj.x,
63
+ y: obj.y,
64
+ elevation: obj.elevation,
65
+ };
66
+ if (typeof obj.srid === 'string' && obj.srid !== '') {
67
+ point.srid = obj.srid;
68
+ }
69
+ if (typeof obj.verticalDatum === 'string' && obj.verticalDatum !== '') {
70
+ point.verticalDatum = obj.verticalDatum;
71
+ }
72
+ return { point, error: null };
73
+ }
74
+ validateSegment(raw) {
75
+ if (raw === null || raw === undefined || typeof raw !== 'object' || Array.isArray(raw)) {
76
+ return {
77
+ segment: null,
78
+ error: {
79
+ objectId: 'unknown',
80
+ missingFields: [...SEGMENT_REQUIRED_FIELDS],
81
+ errorMessage: '管段数据不是有效对象',
82
+ },
83
+ };
84
+ }
85
+ const obj = raw;
86
+ const missingFields = [];
87
+ const errors = [];
88
+ for (const field of SEGMENT_REQUIRED_FIELDS) {
89
+ const value = obj[field];
90
+ if (value === null || value === undefined || value === '') {
91
+ missingFields.push(field);
92
+ }
93
+ }
94
+ if (typeof obj.segmentId === 'string' && obj.segmentId !== '') {
95
+ if (typeof obj.diameter !== 'number' || Number.isNaN(obj.diameter)) {
96
+ errors.push('diameter 必须为数值');
97
+ if (!missingFields.includes('diameter'))
98
+ missingFields.push('diameter');
99
+ }
100
+ }
101
+ if (missingFields.length > 0 || errors.length > 0) {
102
+ return {
103
+ segment: null,
104
+ error: {
105
+ objectId: typeof obj.segmentId === 'string' && obj.segmentId !== '' ? obj.segmentId : 'unknown',
106
+ missingFields,
107
+ errorMessage: errors.length > 0 ? errors.join('; ') : '必填字段缺失',
108
+ },
109
+ };
110
+ }
111
+ const segment = {
112
+ segmentId: obj.segmentId,
113
+ startPointId: obj.startPointId,
114
+ endPointId: obj.endPointId,
115
+ diameter: obj.diameter,
116
+ material: obj.material,
117
+ buryMethod: obj.buryMethod,
118
+ };
119
+ if (typeof obj.flowDirection === 'string' && obj.flowDirection !== '') {
120
+ segment.flowDirection = obj.flowDirection;
121
+ }
122
+ return { segment, error: null };
123
+ }
124
+ validateStructure(raw) {
125
+ if (raw === null || raw === undefined || typeof raw !== 'object' || Array.isArray(raw)) {
126
+ return {
127
+ structure: null,
128
+ error: {
129
+ objectId: 'unknown',
130
+ missingFields: [...STRUCTURE_REQUIRED_FIELDS],
131
+ errorMessage: '构筑物数据不是有效对象',
132
+ },
133
+ };
134
+ }
135
+ const obj = raw;
136
+ const missingFields = [];
137
+ for (const field of STRUCTURE_REQUIRED_FIELDS) {
138
+ const value = obj[field];
139
+ if (value === null || value === undefined || value === '') {
140
+ missingFields.push(field);
141
+ }
142
+ }
143
+ if (missingFields.length > 0) {
144
+ return {
145
+ structure: null,
146
+ error: {
147
+ objectId: typeof obj.structureId === 'string' && obj.structureId !== ''
148
+ ? obj.structureId
149
+ : 'unknown',
150
+ missingFields,
151
+ errorMessage: '必填字段缺失',
152
+ },
153
+ };
154
+ }
155
+ const structure = {
156
+ structureId: obj.structureId,
157
+ structureType: obj.structureType,
158
+ };
159
+ if (typeof obj.x === 'number' && !Number.isNaN(obj.x)) {
160
+ structure.x = obj.x;
161
+ }
162
+ if (typeof obj.y === 'number' && !Number.isNaN(obj.y)) {
163
+ structure.y = obj.y;
164
+ }
165
+ if (typeof obj.elevation === 'number' && !Number.isNaN(obj.elevation)) {
166
+ structure.elevation = obj.elevation;
167
+ }
168
+ return { structure, error: null };
169
+ }
170
+ validateDataset(raw) {
171
+ const errors = [];
172
+ if (raw === null || raw === undefined || typeof raw !== 'object' || Array.isArray(raw)) {
173
+ errors.push({
174
+ objectId: 'dataset',
175
+ missingFields: ['points', 'segments', 'structures'],
176
+ errorMessage: '数据集不是有效对象',
177
+ });
178
+ return { dataset: null, errors };
179
+ }
180
+ const obj = raw;
181
+ const points = [];
182
+ const segments = [];
183
+ const structures = [];
184
+ if (Array.isArray(obj.points)) {
185
+ for (const rawPoint of obj.points) {
186
+ const { point, error } = this.validatePoint(rawPoint);
187
+ if (point !== null) {
188
+ points.push(point);
189
+ }
190
+ else if (error !== null) {
191
+ errors.push(error);
192
+ }
193
+ }
194
+ }
195
+ else {
196
+ errors.push({
197
+ objectId: 'dataset',
198
+ missingFields: ['points'],
199
+ errorMessage: '数据集缺少 points 数组',
200
+ });
201
+ }
202
+ if (Array.isArray(obj.segments)) {
203
+ for (const rawSegment of obj.segments) {
204
+ const { segment, error } = this.validateSegment(rawSegment);
205
+ if (segment !== null) {
206
+ segments.push(segment);
207
+ }
208
+ else if (error !== null) {
209
+ errors.push(error);
210
+ }
211
+ }
212
+ }
213
+ else {
214
+ errors.push({
215
+ objectId: 'dataset',
216
+ missingFields: ['segments'],
217
+ errorMessage: '数据集缺少 segments 数组',
218
+ });
219
+ }
220
+ if (Array.isArray(obj.structures)) {
221
+ for (const rawStructure of obj.structures) {
222
+ const { structure, error } = this.validateStructure(rawStructure);
223
+ if (structure !== null) {
224
+ structures.push(structure);
225
+ }
226
+ else if (error !== null) {
227
+ errors.push(error);
228
+ }
229
+ }
230
+ }
231
+ else {
232
+ errors.push({
233
+ objectId: 'dataset',
234
+ missingFields: ['structures'],
235
+ errorMessage: '数据集缺少 structures 数组',
236
+ });
237
+ }
238
+ const dataset = { points, segments, structures };
239
+ if (Array.isArray(obj.workspaceLayers)) {
240
+ dataset.workspaceLayers = obj.workspaceLayers;
241
+ }
242
+ return { dataset, errors };
243
+ }
244
+ }
245
+ //# sourceMappingURL=input-validator.js.map
@@ -0,0 +1,170 @@
1
+ import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { PolicyBasisBuilder } from './policy-basis-builder.js';
4
+ import { LEGAL_DISCLAIMER, INSPECTION_RATIO_STATEMENT } from './invariant.js';
5
+ export class InspectionReportGenerator {
6
+ policyBasisBuilder;
7
+ constructor() {
8
+ this.policyBasisBuilder = new PolicyBasisBuilder();
9
+ }
10
+ generate(params) {
11
+ const { checkResults, qualityElementScores, errorClassStatistics, grossErrorRate, overrideList, errorList, configVersions, inspectionScope, warnings, } = params;
12
+ const allPolicyBasis = [];
13
+ for (const result of checkResults) {
14
+ allPolicyBasis.push(result.policyBasis);
15
+ }
16
+ for (const score of qualityElementScores) {
17
+ allPolicyBasis.push(score.policyBasis);
18
+ }
19
+ allPolicyBasis.push(errorClassStatistics.policyBasis);
20
+ allPolicyBasis.push(grossErrorRate.policyBasis);
21
+ const policyBasisSummary = this.policyBasisBuilder.buildSummary(allPolicyBasis);
22
+ const report = {
23
+ reportId: this.generateReportId(),
24
+ inspectionTime: new Date().toISOString(),
25
+ inspectionScope,
26
+ inspectionRatioStatement: INSPECTION_RATIO_STATEMENT,
27
+ checkResults,
28
+ qualityElementScores,
29
+ errorClassStatistics,
30
+ grossErrorRate,
31
+ overrideList,
32
+ errorList,
33
+ policyBasisSummary,
34
+ configVersions,
35
+ legalDisclaimer: LEGAL_DISCLAIMER,
36
+ warnings,
37
+ };
38
+ return report;
39
+ }
40
+ writeJson(report, outputPath) {
41
+ const dir = dirname(outputPath);
42
+ if (!existsSync(dir)) {
43
+ mkdirSync(dir, { recursive: true });
44
+ }
45
+ writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8');
46
+ }
47
+ writeMarkdown(report, outputPath) {
48
+ const dir = dirname(outputPath);
49
+ if (!existsSync(dir)) {
50
+ mkdirSync(dir, { recursive: true });
51
+ }
52
+ const markdown = this.renderMarkdown(report);
53
+ writeFileSync(outputPath, markdown, 'utf-8');
54
+ }
55
+ generateReportId() {
56
+ const timestamp = Date.now();
57
+ const random = Math.floor(Math.random() * 10000);
58
+ return `RPT-${timestamp}-${random}`;
59
+ }
60
+ renderMarkdown(report) {
61
+ const lines = [];
62
+ lines.push('# 地下管线数据质量巡检报告');
63
+ lines.push('');
64
+ lines.push(`> ${report.legalDisclaimer}`);
65
+ lines.push('');
66
+ lines.push('## 巡检元数据');
67
+ lines.push('');
68
+ lines.push(`- 报告标识: ${report.reportId}`);
69
+ lines.push(`- 巡检时间: ${report.inspectionTime}`);
70
+ lines.push(`- 巡检范围: ${report.inspectionScope}`);
71
+ lines.push(`- 检测比例: ${report.inspectionRatioStatement}`);
72
+ lines.push(`- 巡检配置版本: ${report.configVersions.inspectionConfigVersion}`);
73
+ lines.push(`- 属性配置版本: ${report.configVersions.attributeConfigVersion}`);
74
+ lines.push(`- 质量元素配置版本: ${report.configVersions.qualityElementConfigVersion}`);
75
+ lines.push('');
76
+ lines.push('## 检测结果清单');
77
+ lines.push('');
78
+ if (report.checkResults.length === 0) {
79
+ lines.push('(无检测结果)');
80
+ }
81
+ else {
82
+ lines.push('| 检查项 | 错误类别 | 对象标识 | 描述 | 政策依据 |');
83
+ lines.push('|---|---|---|---|---|');
84
+ for (const result of report.checkResults) {
85
+ lines.push(`| ${result.checkName} | ${result.errorClass} | ${result.objectId} | ${result.description} | ${result.policyBasis} |`);
86
+ }
87
+ }
88
+ lines.push('');
89
+ lines.push('## 质量元素评分');
90
+ lines.push('');
91
+ if (report.qualityElementScores.length === 0) {
92
+ lines.push('(无质量元素评分)');
93
+ }
94
+ else {
95
+ lines.push('| 质量元素 | 权重 | 得分 | 政策依据 |');
96
+ lines.push('|---|---|---|---|');
97
+ for (const score of report.qualityElementScores) {
98
+ lines.push(`| ${score.elementName} | ${score.weight} | ${score.score} | ${score.policyBasis} |`);
99
+ }
100
+ }
101
+ lines.push('');
102
+ lines.push('## 错误分类统计');
103
+ lines.push('');
104
+ const stats = report.errorClassStatistics;
105
+ lines.push(`- A类错误: ${stats.classACount}`);
106
+ lines.push(`- B类错误: ${stats.classBCount}`);
107
+ lines.push(`- C类错误: ${stats.classCCount}`);
108
+ lines.push(`- D类错误: ${stats.classDCount}`);
109
+ lines.push(`- 总计: ${stats.totalCount}`);
110
+ lines.push(`- 政策依据: ${stats.policyBasis}`);
111
+ lines.push('');
112
+ lines.push('## 管线点粗差率');
113
+ lines.push('');
114
+ const ger = report.grossErrorRate;
115
+ lines.push(`- 管线点总数: ${ger.totalPoints}`);
116
+ lines.push(`- 粗差点数: ${ger.grossErrorPoints}`);
117
+ lines.push(`- 粗差率: ${(ger.rate * 100).toFixed(2)}%`);
118
+ lines.push(`- 阈值: ${(ger.threshold * 100).toFixed(2)}%`);
119
+ lines.push(`- 结论: ${ger.passed ? '合格' : '不合格'}`);
120
+ lines.push(`- 政策依据: ${ger.policyBasis}`);
121
+ lines.push('');
122
+ lines.push('## 人工覆盖记录清单');
123
+ lines.push('');
124
+ if (report.overrideList.length === 0) {
125
+ lines.push('(无人工覆盖记录)');
126
+ }
127
+ else {
128
+ lines.push('| 对象标识 | 原结论 | 新结论 | 覆盖理由 | 操作人 | 操作时间 |');
129
+ lines.push('|---|---|---|---|---|---|');
130
+ for (const item of report.overrideList) {
131
+ lines.push(`| ${item.objectId} | ${item.originalConclusion} | ${item.newConclusion} | ${item.overrideReason} | ${item.operator} | ${item.overrideTime} |`);
132
+ }
133
+ }
134
+ lines.push('');
135
+ lines.push('## 数据校验错误清单');
136
+ lines.push('');
137
+ if (report.errorList.length === 0) {
138
+ lines.push('(无校验错误)');
139
+ }
140
+ else {
141
+ lines.push('| 对象标识 | 缺失字段 | 错误信息 |');
142
+ lines.push('|---|---|---|');
143
+ for (const item of report.errorList) {
144
+ lines.push(`| ${item.objectId} | ${item.missingFields.join(', ')} | ${item.errorMessage} |`);
145
+ }
146
+ }
147
+ lines.push('');
148
+ lines.push('## 政策依据汇总');
149
+ lines.push('');
150
+ if (report.policyBasisSummary.length === 0) {
151
+ lines.push('(无政策依据)');
152
+ }
153
+ else {
154
+ for (const basis of report.policyBasisSummary) {
155
+ lines.push(`- ${basis}`);
156
+ }
157
+ }
158
+ lines.push('');
159
+ if (report.warnings.length > 0) {
160
+ lines.push('## 警告信息');
161
+ lines.push('');
162
+ for (const warning of report.warnings) {
163
+ lines.push(`- ${warning}`);
164
+ }
165
+ lines.push('');
166
+ }
167
+ return lines.join('\n');
168
+ }
169
+ }
170
+ //# sourceMappingURL=inspection-report-generator.js.map