@liuhange/dsh-construction-data-inventory 0.0.0-stage → 3.1.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/README.md +72 -3
- package/config/classification-config.json +111 -0
- package/config/encoding-rule-config.json +9 -0
- package/config/source-adapter-config.json +26 -0
- package/lib/acceptance-doc-adapter.js +56 -0
- package/lib/asset-code-generator.js +82 -0
- package/lib/asset-ledger-exporter.js +35 -0
- package/lib/change-traceability-checker.js +79 -0
- package/lib/checksum-calculator.js +53 -0
- package/lib/classification-mapper.js +50 -0
- package/lib/code-uniqueness-validator.js +20 -0
- package/lib/construction-config-loader.js +105 -0
- package/lib/contract-adapter.js +57 -0
- package/lib/default-classification-config.js +112 -0
- package/lib/default-encoding-rule-config.js +10 -0
- package/lib/ifc-model-adapter.js +156 -0
- package/lib/index.js +304 -0
- package/lib/input-validator.js +113 -0
- package/lib/invariant.js +32 -0
- package/lib/inventory-report-generator.js +140 -0
- package/lib/policy-basis-builder.js +25 -0
- package/lib/progress-record-adapter.js +58 -0
- package/lib/report-desensitizer.js +72 -0
- package/lib/review-override-recorder.js +54 -0
- package/lib/sequential-code-allocator.js +22 -0
- package/lib/source-adapter.js +2 -0
- package/lib/types/acceptance-doc-adapter.d.ts +11 -0
- package/lib/types/asset-code-generator.d.ts +17 -0
- package/lib/types/asset-ledger-exporter.d.ts +7 -0
- package/lib/types/change-traceability-checker.d.ts +11 -0
- package/lib/types/checksum-calculator.d.ts +7 -0
- package/lib/types/classification-mapper.d.ts +12 -0
- package/lib/types/code-uniqueness-validator.d.ts +8 -0
- package/lib/types/construction-config-loader.d.ts +11 -0
- package/lib/types/contract-adapter.d.ts +11 -0
- package/lib/types/default-classification-config.d.ts +3 -0
- package/lib/types/default-encoding-rule-config.d.ts +3 -0
- package/lib/types/ifc-model-adapter.d.ts +14 -0
- package/lib/types/index.d.ts +8 -0
- package/lib/types/input-validator.d.ts +12 -0
- package/lib/types/invariant.d.ts +12 -0
- package/lib/types/inventory-report-generator.d.ts +22 -0
- package/lib/types/policy-basis-builder.d.ts +8 -0
- package/lib/types/progress-record-adapter.d.ts +11 -0
- package/lib/types/report-desensitizer.d.ts +12 -0
- package/lib/types/review-override-recorder.d.ts +21 -0
- package/lib/types/sequential-code-allocator.d.ts +10 -0
- package/lib/types/source-adapter.d.ts +9 -0
- package/lib/types/types.d.ts +157 -0
- package/lib/types/unified-asset-merger.d.ts +13 -0
- package/lib/types.js +2 -0
- package/lib/unified-asset-merger.js +18 -0
- package/package.json +68 -5
package/lib/index.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
export const name = '@liuhange/dsh-construction-data-inventory';
|
|
2
|
+
export const inject = ['tools'];
|
|
3
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { PLUGIN_NAME } from './invariant.js';
|
|
6
|
+
import { ConstructionConfigLoader } from './construction-config-loader.js';
|
|
7
|
+
import { InputValidator } from './input-validator.js';
|
|
8
|
+
import { IfcModelAdapter } from './ifc-model-adapter.js';
|
|
9
|
+
import { ContractAdapter } from './contract-adapter.js';
|
|
10
|
+
import { ProgressRecordAdapter } from './progress-record-adapter.js';
|
|
11
|
+
import { AcceptanceDocAdapter } from './acceptance-doc-adapter.js';
|
|
12
|
+
import { UnifiedAssetMerger } from './unified-asset-merger.js';
|
|
13
|
+
import { ClassificationMapper } from './classification-mapper.js';
|
|
14
|
+
import { AssetCodeGenerator } from './asset-code-generator.js';
|
|
15
|
+
import { ChangeTraceabilityChecker } from './change-traceability-checker.js';
|
|
16
|
+
import { ReviewOverrideRecorder } from './review-override-recorder.js';
|
|
17
|
+
import { InventoryReportGenerator } from './inventory-report-generator.js';
|
|
18
|
+
function errorResult(error, message) {
|
|
19
|
+
return JSON.stringify({ error, message });
|
|
20
|
+
}
|
|
21
|
+
function getAdapter(type) {
|
|
22
|
+
switch (type.toUpperCase()) {
|
|
23
|
+
case 'IFC':
|
|
24
|
+
return new IfcModelAdapter();
|
|
25
|
+
case 'CONTRACT':
|
|
26
|
+
return new ContractAdapter();
|
|
27
|
+
case 'PROGRESS':
|
|
28
|
+
return new ProgressRecordAdapter();
|
|
29
|
+
case 'ACCEPTANCE':
|
|
30
|
+
return new AcceptanceDocAdapter();
|
|
31
|
+
default:
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function loadSourceConfig(sourceConfigPath) {
|
|
36
|
+
const fullPath = resolve(sourceConfigPath);
|
|
37
|
+
const raw = readFileSync(fullPath, 'utf-8');
|
|
38
|
+
return JSON.parse(raw);
|
|
39
|
+
}
|
|
40
|
+
function loadChangeRecords(changeDataPath) {
|
|
41
|
+
const fullPath = resolve(changeDataPath);
|
|
42
|
+
const raw = readFileSync(fullPath, 'utf-8');
|
|
43
|
+
return JSON.parse(raw);
|
|
44
|
+
}
|
|
45
|
+
function buildClassificationEncodingList(assets) {
|
|
46
|
+
const list = [];
|
|
47
|
+
for (const asset of assets) {
|
|
48
|
+
if (asset.classificationCode && asset.assetCode) {
|
|
49
|
+
list.push({
|
|
50
|
+
assetId: asset.assetId,
|
|
51
|
+
classificationCode: asset.classificationCode,
|
|
52
|
+
assetCode: asset.assetCode,
|
|
53
|
+
policyBasis: asset.policyBasis ?? '',
|
|
54
|
+
judgmentStatus: asset.judgmentStatus,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return list;
|
|
59
|
+
}
|
|
60
|
+
export function apply(ctx) {
|
|
61
|
+
const configLoader = new ConstructionConfigLoader();
|
|
62
|
+
const inputValidator = new InputValidator();
|
|
63
|
+
const merger = new UnifiedAssetMerger();
|
|
64
|
+
const classificationMapper = new ClassificationMapper();
|
|
65
|
+
const assetCodeGenerator = new AssetCodeGenerator();
|
|
66
|
+
const changeTraceabilityChecker = new ChangeTraceabilityChecker();
|
|
67
|
+
const reportGenerator = new InventoryReportGenerator();
|
|
68
|
+
const overrideRecorder = new ReviewOverrideRecorder();
|
|
69
|
+
ctx.tools.register({
|
|
70
|
+
name: 'inspect_ai_dataset',
|
|
71
|
+
description: '对建筑工程项目多源异构数据进行资产化盘点,按GB/T 51269标准生成分类编码与资产台账',
|
|
72
|
+
parameters: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
projectId: { type: 'string', description: '建筑工程项目标识' },
|
|
76
|
+
sourceConfigPath: { type: 'string', description: '数据源配置文件路径(含各数据源类型与路径)' },
|
|
77
|
+
changeDataPath: { type: 'string', description: '变更单数据文件路径(可选,缺省则跳过变更追溯检测)' },
|
|
78
|
+
outputPathDir: { type: 'string', description: '报告输出目录(默认当前工作目录)' },
|
|
79
|
+
},
|
|
80
|
+
required: ['projectId', 'sourceConfigPath'],
|
|
81
|
+
},
|
|
82
|
+
async execute(args) {
|
|
83
|
+
try {
|
|
84
|
+
const validation = inputValidator.validateInspectInput(args);
|
|
85
|
+
if (!validation.isValid) {
|
|
86
|
+
return errorResult('INPUT_INVALID', validation.errors.map((e) => e.errorMessage).join('; '));
|
|
87
|
+
}
|
|
88
|
+
const projectId = args.projectId;
|
|
89
|
+
const sourceConfigPath = args.sourceConfigPath;
|
|
90
|
+
const changeDataPath = args.changeDataPath;
|
|
91
|
+
const outputPathDir = args.outputPathDir ?? process.cwd();
|
|
92
|
+
const configLoadResult = configLoader.load();
|
|
93
|
+
const { classificationConfig, encodingRuleConfig, sourceAdapterConfig } = configLoadResult;
|
|
94
|
+
let sourceConfig;
|
|
95
|
+
try {
|
|
96
|
+
sourceConfig = loadSourceConfig(sourceConfigPath);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
return errorResult('INPUT_INVALID', `数据源配置加载失败: ${e.message}`);
|
|
100
|
+
}
|
|
101
|
+
const sourceValidation = inputValidator.validateSources(sourceConfig);
|
|
102
|
+
if (!sourceValidation.isValid) {
|
|
103
|
+
return errorResult('INPUT_INVALID', sourceValidation.errors.map((e) => e.errorMessage).join('; '));
|
|
104
|
+
}
|
|
105
|
+
const allWarnings = [];
|
|
106
|
+
const allErrors = [];
|
|
107
|
+
const errorList = [];
|
|
108
|
+
const adaptedLists = [];
|
|
109
|
+
for (const source of sourceConfig.sources) {
|
|
110
|
+
if (!inputValidator.isSourceAvailable(source.path)) {
|
|
111
|
+
allWarnings.push(`数据源 ${source.type} 不可用,已跳过`);
|
|
112
|
+
errorList.push({ assetId: '', errorMessage: `数据源 ${source.type} 不可用,已跳过` });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const adapter = getAdapter(source.type);
|
|
116
|
+
if (adapter === null) {
|
|
117
|
+
allWarnings.push(`未知数据源类型 ${source.type},已跳过`);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const result = adapter.adapt(source.path, source.systemId, projectId);
|
|
121
|
+
adaptedLists.push(result);
|
|
122
|
+
allWarnings.push(...result.warnings);
|
|
123
|
+
allErrors.push(...result.errors);
|
|
124
|
+
}
|
|
125
|
+
const mergeResult = merger.merge(adaptedLists);
|
|
126
|
+
allWarnings.push(...mergeResult.warnings);
|
|
127
|
+
allErrors.push(...mergeResult.errors);
|
|
128
|
+
let unifiedAssets = mergeResult.unifiedAssets;
|
|
129
|
+
const classificationResult = classificationMapper.map(unifiedAssets, classificationConfig);
|
|
130
|
+
allWarnings.push(...classificationResult.warnings);
|
|
131
|
+
unifiedAssets = classificationResult.assets;
|
|
132
|
+
const codeGenResult = assetCodeGenerator.generate(unifiedAssets, encodingRuleConfig);
|
|
133
|
+
allErrors.push(...codeGenResult.errors);
|
|
134
|
+
allWarnings.push(...codeGenResult.warnings);
|
|
135
|
+
unifiedAssets = codeGenResult.assets;
|
|
136
|
+
for (const overflowAssetId of codeGenResult.overflowAssets) {
|
|
137
|
+
errorList.push({ assetId: overflowAssetId, errorMessage: '编码生成失败:顺序码溢出' });
|
|
138
|
+
}
|
|
139
|
+
for (const errMsg of allErrors) {
|
|
140
|
+
errorList.push({ assetId: '', errorMessage: errMsg });
|
|
141
|
+
}
|
|
142
|
+
let changeTraceabilityReport = [];
|
|
143
|
+
let skippedChangeTraceability = false;
|
|
144
|
+
if (changeDataPath) {
|
|
145
|
+
if (!existsSync(changeDataPath)) {
|
|
146
|
+
allWarnings.push('变更追溯因系统不可用而跳过');
|
|
147
|
+
skippedChangeTraceability = true;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
try {
|
|
151
|
+
const changeRecords = loadChangeRecords(changeDataPath);
|
|
152
|
+
const changeValidation = inputValidator.validateChangeRecords(changeRecords);
|
|
153
|
+
for (const invalidMsg of changeValidation.invalid) {
|
|
154
|
+
allWarnings.push(invalidMsg);
|
|
155
|
+
}
|
|
156
|
+
const assetIds = unifiedAssets.map((a) => a.assetId);
|
|
157
|
+
const traceabilityResult = changeTraceabilityChecker.check(changeValidation.valid, assetIds);
|
|
158
|
+
changeTraceabilityReport = traceabilityResult.results;
|
|
159
|
+
allWarnings.push(...traceabilityResult.warnings);
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
allWarnings.push(`变更追溯检测失败: ${e.message}`);
|
|
163
|
+
skippedChangeTraceability = true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const classificationEncodingList = buildClassificationEncodingList(unifiedAssets);
|
|
168
|
+
const overrideList = overrideRecorder.getAll();
|
|
169
|
+
const configVersions = {
|
|
170
|
+
classificationConfigVersion: classificationConfig.version,
|
|
171
|
+
encodingRuleConfigVersion: encodingRuleConfig.version,
|
|
172
|
+
sourceAdapterConfigVersion: sourceAdapterConfig.version,
|
|
173
|
+
};
|
|
174
|
+
const report = reportGenerator.generate({
|
|
175
|
+
assetList: unifiedAssets,
|
|
176
|
+
classificationEncodingList,
|
|
177
|
+
changeTraceabilityReport,
|
|
178
|
+
overrideList,
|
|
179
|
+
errorList,
|
|
180
|
+
configVersions,
|
|
181
|
+
projectId,
|
|
182
|
+
skippedChangeTraceability,
|
|
183
|
+
});
|
|
184
|
+
const reportJsonPath = reportGenerator.writeJson(report, outputPathDir);
|
|
185
|
+
const reportMdPath = reportGenerator.writeMarkdown(report, outputPathDir);
|
|
186
|
+
const ledgerPath = reportGenerator.exportLedger(unifiedAssets, outputPathDir);
|
|
187
|
+
const result = {
|
|
188
|
+
reportId: report.reportId,
|
|
189
|
+
projectId,
|
|
190
|
+
assetCount: unifiedAssets.length,
|
|
191
|
+
classifiedCount: unifiedAssets.filter((a) => a.classificationCode).length,
|
|
192
|
+
codedCount: unifiedAssets.filter((a) => a.assetCode).length,
|
|
193
|
+
unclassifiedCount: classificationResult.unclassified.length,
|
|
194
|
+
changeTraceabilityCount: changeTraceabilityReport.length,
|
|
195
|
+
overrideCount: overrideList.length,
|
|
196
|
+
errorCount: errorList.length,
|
|
197
|
+
configLoadStatus: configLoadResult.loadStatus,
|
|
198
|
+
reportJsonPath,
|
|
199
|
+
reportMdPath,
|
|
200
|
+
ledgerPath,
|
|
201
|
+
warnings: allWarnings,
|
|
202
|
+
policyBasisSummary: report.policyBasisSummary,
|
|
203
|
+
legalDisclaimer: report.legalDisclaimer,
|
|
204
|
+
timestamp: new Date().toISOString(),
|
|
205
|
+
};
|
|
206
|
+
return JSON.stringify(result, null, 2);
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
return errorResult('UNKNOWN_ERROR', e.message);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
ctx.tools.register({
|
|
214
|
+
name: 'submit_review_override',
|
|
215
|
+
description: '提交人工复核覆盖指令,对自动分类与编码结果进行人工覆盖并记录覆盖理由',
|
|
216
|
+
parameters: {
|
|
217
|
+
type: 'object',
|
|
218
|
+
properties: {
|
|
219
|
+
assetId: { type: 'string', description: '被覆盖的资产标识' },
|
|
220
|
+
reviewItem: { type: 'string', enum: ['classification', 'encoding', 'changeTraceability'], description: '复核项' },
|
|
221
|
+
overrideConclusion: { type: 'string', description: '覆盖后结论(如新分类代码)' },
|
|
222
|
+
overrideReason: { type: 'string', description: '覆盖理由(不可为空)' },
|
|
223
|
+
operator: { type: 'string', description: '操作人标识' },
|
|
224
|
+
inventoryReportPath: { type: 'string', description: '盘点报告路径(用于定位判定结果)' },
|
|
225
|
+
},
|
|
226
|
+
required: ['assetId', 'reviewItem', 'overrideConclusion', 'overrideReason', 'operator', 'inventoryReportPath'],
|
|
227
|
+
},
|
|
228
|
+
async execute(args) {
|
|
229
|
+
try {
|
|
230
|
+
const validation = inputValidator.validateOverrideInput(args);
|
|
231
|
+
if (!validation.isValid) {
|
|
232
|
+
return errorResult('INPUT_INVALID', validation.errors.map((e) => e.errorMessage).join('; '));
|
|
233
|
+
}
|
|
234
|
+
const assetId = args.assetId;
|
|
235
|
+
const reviewItem = args.reviewItem;
|
|
236
|
+
const overrideConclusion = args.overrideConclusion;
|
|
237
|
+
const overrideReason = args.overrideReason;
|
|
238
|
+
const operator = args.operator;
|
|
239
|
+
const inventoryReportPath = args.inventoryReportPath;
|
|
240
|
+
if (!existsSync(inventoryReportPath)) {
|
|
241
|
+
return errorResult('ASSET_NOT_FOUND', `盘点报告不存在: ${inventoryReportPath}`);
|
|
242
|
+
}
|
|
243
|
+
const reportRaw = readFileSync(inventoryReportPath, 'utf-8');
|
|
244
|
+
const report = JSON.parse(reportRaw);
|
|
245
|
+
const asset = report.assetList.find((a) => a.assetId === assetId);
|
|
246
|
+
if (!asset) {
|
|
247
|
+
return errorResult('ASSET_NOT_FOUND', `资产标识不存在: ${assetId}`);
|
|
248
|
+
}
|
|
249
|
+
if (reviewItem === 'classification') {
|
|
250
|
+
const configLoadResult = configLoader.load();
|
|
251
|
+
const isValidCode = classificationMapper.isValidClassificationCode(overrideConclusion, configLoadResult.classificationConfig);
|
|
252
|
+
if (!isValidCode) {
|
|
253
|
+
return errorResult('INVALID_CLASSIFICATION_CODE', `分类代码不在标准体系: ${overrideConclusion}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const originalConclusion = asset.classificationCode;
|
|
257
|
+
const originalAssetCode = asset.assetCode;
|
|
258
|
+
const overrideRecord = overrideRecorder.buildRecord({
|
|
259
|
+
assetId,
|
|
260
|
+
reviewItem,
|
|
261
|
+
originalConclusion,
|
|
262
|
+
newConclusion: overrideConclusion,
|
|
263
|
+
overrideReason,
|
|
264
|
+
operator,
|
|
265
|
+
originalAssetCode,
|
|
266
|
+
});
|
|
267
|
+
overrideRecorder.record(overrideRecord);
|
|
268
|
+
let newAssetCode = originalAssetCode;
|
|
269
|
+
let regenerated = false;
|
|
270
|
+
if (reviewItem === 'classification') {
|
|
271
|
+
asset.classificationCode = overrideConclusion;
|
|
272
|
+
asset.judgmentStatus = '人工确认';
|
|
273
|
+
const configLoadResult = configLoader.load();
|
|
274
|
+
const regenResult = assetCodeGenerator.regenerateForAsset(asset, configLoadResult.encodingRuleConfig);
|
|
275
|
+
if (regenResult.assets[0] && regenResult.assets[0].assetCode) {
|
|
276
|
+
newAssetCode = regenResult.assets[0].assetCode;
|
|
277
|
+
regenerated = true;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
asset.judgmentStatus = '人工确认';
|
|
282
|
+
}
|
|
283
|
+
const result = {
|
|
284
|
+
assetId,
|
|
285
|
+
reviewItem,
|
|
286
|
+
originalConclusion,
|
|
287
|
+
newConclusion: overrideConclusion,
|
|
288
|
+
originalAssetCode,
|
|
289
|
+
newAssetCode,
|
|
290
|
+
regenerated,
|
|
291
|
+
operator,
|
|
292
|
+
overrideTime: overrideRecord.overrideTime,
|
|
293
|
+
status: '覆盖成功',
|
|
294
|
+
};
|
|
295
|
+
return JSON.stringify(result, null, 2);
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
return errorResult('UNKNOWN_ERROR', e.message);
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
console.log(`[${PLUGIN_NAME}] 建筑工程数据资产盘点插件已加载`);
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
export class InputValidator {
|
|
3
|
+
validateInspectInput(args) {
|
|
4
|
+
const errors = [];
|
|
5
|
+
if (args === null || args === undefined) {
|
|
6
|
+
errors.push({ assetId: '', errorMessage: '输入参数为空' });
|
|
7
|
+
return { isValid: false, errors };
|
|
8
|
+
}
|
|
9
|
+
const obj = args;
|
|
10
|
+
if (typeof obj.projectId !== 'string' || obj.projectId.trim() === '') {
|
|
11
|
+
errors.push({ assetId: '', errorMessage: 'projectId必填且为非空字符串' });
|
|
12
|
+
}
|
|
13
|
+
if (typeof obj.sourceConfigPath !== 'string' || obj.sourceConfigPath.trim() === '') {
|
|
14
|
+
errors.push({ assetId: '', errorMessage: 'sourceConfigPath必填且为非空字符串' });
|
|
15
|
+
}
|
|
16
|
+
return { isValid: errors.length === 0, errors };
|
|
17
|
+
}
|
|
18
|
+
validateOverrideInput(args) {
|
|
19
|
+
const errors = [];
|
|
20
|
+
if (args === null || args === undefined) {
|
|
21
|
+
errors.push({ assetId: '', errorMessage: '输入参数为空' });
|
|
22
|
+
return { isValid: false, errors };
|
|
23
|
+
}
|
|
24
|
+
const obj = args;
|
|
25
|
+
const assetId = obj.assetId;
|
|
26
|
+
if (typeof assetId !== 'string' || assetId.trim() === '') {
|
|
27
|
+
errors.push({ assetId: '', errorMessage: 'assetId必填且为非空字符串' });
|
|
28
|
+
}
|
|
29
|
+
if (typeof obj.reviewItem !== 'string' || obj.reviewItem.trim() === '') {
|
|
30
|
+
errors.push({ assetId: String(assetId ?? ''), errorMessage: 'reviewItem必填且为非空字符串' });
|
|
31
|
+
}
|
|
32
|
+
if (typeof obj.overrideConclusion !== 'string' || obj.overrideConclusion.trim() === '') {
|
|
33
|
+
errors.push({ assetId: String(assetId ?? ''), errorMessage: 'overrideConclusion必填且为非空字符串' });
|
|
34
|
+
}
|
|
35
|
+
if (typeof obj.overrideReason !== 'string' || obj.overrideReason.trim() === '') {
|
|
36
|
+
errors.push({ assetId: String(assetId ?? ''), errorMessage: 'overrideReason必填且为非空字符串' });
|
|
37
|
+
}
|
|
38
|
+
if (typeof obj.operator !== 'string' || obj.operator.trim() === '') {
|
|
39
|
+
errors.push({ assetId: String(assetId ?? ''), errorMessage: 'operator必填且为非空字符串' });
|
|
40
|
+
}
|
|
41
|
+
if (typeof obj.inventoryReportPath !== 'string' || obj.inventoryReportPath.trim() === '') {
|
|
42
|
+
errors.push({ assetId: String(assetId ?? ''), errorMessage: 'inventoryReportPath必填且为非空字符串' });
|
|
43
|
+
}
|
|
44
|
+
return { isValid: errors.length === 0, errors };
|
|
45
|
+
}
|
|
46
|
+
validateSources(sourceConfig) {
|
|
47
|
+
const errors = [];
|
|
48
|
+
if (!sourceConfig || typeof sourceConfig !== 'object') {
|
|
49
|
+
errors.push({ assetId: '', errorMessage: '数据源配置为空' });
|
|
50
|
+
return { isValid: false, errors };
|
|
51
|
+
}
|
|
52
|
+
if (typeof sourceConfig.projectId !== 'string' || sourceConfig.projectId.trim() === '') {
|
|
53
|
+
errors.push({ assetId: '', errorMessage: '数据源配置projectId必填' });
|
|
54
|
+
}
|
|
55
|
+
if (!Array.isArray(sourceConfig.sources)) {
|
|
56
|
+
errors.push({ assetId: '', errorMessage: '数据源配置sources须为数组' });
|
|
57
|
+
return { isValid: false, errors };
|
|
58
|
+
}
|
|
59
|
+
for (const source of sourceConfig.sources) {
|
|
60
|
+
if (!source || typeof source !== 'object') {
|
|
61
|
+
errors.push({ assetId: '', errorMessage: '数据源项格式不合法' });
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (typeof source.type !== 'string' || source.type.trim() === '') {
|
|
65
|
+
errors.push({ assetId: '', errorMessage: '数据源type必填' });
|
|
66
|
+
}
|
|
67
|
+
if (typeof source.systemId !== 'string' || source.systemId.trim() === '') {
|
|
68
|
+
errors.push({ assetId: '', errorMessage: '数据源systemId必填' });
|
|
69
|
+
}
|
|
70
|
+
if (typeof source.path !== 'string' || source.path.trim() === '') {
|
|
71
|
+
errors.push({ assetId: '', errorMessage: '数据源path必填' });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { isValid: errors.length === 0, errors };
|
|
75
|
+
}
|
|
76
|
+
isSourceAvailable(sourcePath) {
|
|
77
|
+
return existsSync(sourcePath);
|
|
78
|
+
}
|
|
79
|
+
validateChangeRecords(changeRecords) {
|
|
80
|
+
const valid = [];
|
|
81
|
+
const invalid = [];
|
|
82
|
+
if (!Array.isArray(changeRecords)) {
|
|
83
|
+
invalid.push('变更记录非数组');
|
|
84
|
+
return { valid, invalid };
|
|
85
|
+
}
|
|
86
|
+
for (const record of changeRecords) {
|
|
87
|
+
if (!record || typeof record !== 'object') {
|
|
88
|
+
invalid.push('变更记录格式不兼容');
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const r = record;
|
|
92
|
+
if (typeof r.changeId !== 'string' || r.changeId.trim() === '') {
|
|
93
|
+
invalid.push('变更记录缺少changeId');
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
valid.push({
|
|
97
|
+
changeId: r.changeId,
|
|
98
|
+
relatedAssetId: typeof r.relatedAssetId === 'string' ? r.relatedAssetId : '',
|
|
99
|
+
changeReason: typeof r.changeReason === 'string' ? r.changeReason : '',
|
|
100
|
+
approvalChain: r.approvalChain ?? {
|
|
101
|
+
initiator: '',
|
|
102
|
+
approver: '',
|
|
103
|
+
approvalTime: '',
|
|
104
|
+
approvalConclusion: '',
|
|
105
|
+
},
|
|
106
|
+
impactScope: Array.isArray(r.impactScope) ? r.impactScope : [],
|
|
107
|
+
changeDate: typeof r.changeDate === 'string' ? r.changeDate : '',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return { valid, invalid };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=input-validator.js.map
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const PLUGIN_NAME = '@liuhange/dsh-construction-data-inventory';
|
|
2
|
+
export const DEFAULT_CLASSIFICATION_CONFIG_PATH = 'config/classification-config.json';
|
|
3
|
+
export const DEFAULT_ENCODING_RULE_CONFIG_PATH = 'config/encoding-rule-config.json';
|
|
4
|
+
export const DEFAULT_SOURCE_ADAPTER_CONFIG_PATH = 'config/source-adapter-config.json';
|
|
5
|
+
export const DEFAULT_OVERRIDE_LOG_PATH = 'logs/override-records.log';
|
|
6
|
+
export const LEGAL_DISCLAIMER = '本报告由辅助盘点工具生成,不替代GB/T 51269标准的正式合规认证';
|
|
7
|
+
export const POLICY_STANDARD_NAME = '《建筑信息模型分类和编码标准》(GB/T 51269-2017)';
|
|
8
|
+
export const SUPPORTED_IFC_VERSIONS = ['IFC2X3', 'IFC4'];
|
|
9
|
+
export const IFC_ENTITY_TYPES = [
|
|
10
|
+
'IFCWALL',
|
|
11
|
+
'IFCWALLSTANDARDCASE',
|
|
12
|
+
'IFCCOLUMN',
|
|
13
|
+
'IFCCOLUMNSTANDARDCASE',
|
|
14
|
+
'IFCSLAB',
|
|
15
|
+
'IFCSLABSTANDARDCASE',
|
|
16
|
+
'IFCDOOR',
|
|
17
|
+
'IFCWINDOW',
|
|
18
|
+
'IFCSPACE',
|
|
19
|
+
'IFCBUILDINGSTOREY',
|
|
20
|
+
'IFCBUILDING',
|
|
21
|
+
'IFCSITE',
|
|
22
|
+
'IFCROOF',
|
|
23
|
+
'IFCBEAM',
|
|
24
|
+
'IFCBEAMSTANDARDCASE',
|
|
25
|
+
'IFCFOOTING',
|
|
26
|
+
'IFCSTAIR',
|
|
27
|
+
'IFCSTAIRFLIGHT',
|
|
28
|
+
'IFCRAILING',
|
|
29
|
+
'IFCCURTAINWALL',
|
|
30
|
+
'IFCPROPERTYSET',
|
|
31
|
+
];
|
|
32
|
+
//# sourceMappingURL=invariant.js.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { LEGAL_DISCLAIMER } from './invariant.js';
|
|
5
|
+
import { PolicyBasisBuilder } from './policy-basis-builder.js';
|
|
6
|
+
import { ReportDesensitizer } from './report-desensitizer.js';
|
|
7
|
+
import { AssetLedgerExporter } from './asset-ledger-exporter.js';
|
|
8
|
+
export class InventoryReportGenerator {
|
|
9
|
+
policyBasisBuilder;
|
|
10
|
+
desensitizer;
|
|
11
|
+
ledgerExporter;
|
|
12
|
+
constructor() {
|
|
13
|
+
this.policyBasisBuilder = new PolicyBasisBuilder();
|
|
14
|
+
this.desensitizer = new ReportDesensitizer();
|
|
15
|
+
this.ledgerExporter = new AssetLedgerExporter();
|
|
16
|
+
}
|
|
17
|
+
generate(params) {
|
|
18
|
+
const { assetList, classificationEncodingList, changeTraceabilityReport, overrideList, errorList, configVersions, projectId, } = params;
|
|
19
|
+
const policyBasisList = [];
|
|
20
|
+
for (const asset of assetList) {
|
|
21
|
+
if (asset.policyBasis)
|
|
22
|
+
policyBasisList.push(asset.policyBasis);
|
|
23
|
+
}
|
|
24
|
+
for (const entry of classificationEncodingList) {
|
|
25
|
+
if (entry.policyBasis)
|
|
26
|
+
policyBasisList.push(entry.policyBasis);
|
|
27
|
+
}
|
|
28
|
+
for (const result of changeTraceabilityReport) {
|
|
29
|
+
if (result.policyBasis)
|
|
30
|
+
policyBasisList.push(result.policyBasis);
|
|
31
|
+
}
|
|
32
|
+
const policyBasisSummary = this.policyBasisBuilder.buildSummary(policyBasisList);
|
|
33
|
+
let report = {
|
|
34
|
+
reportId: randomUUID(),
|
|
35
|
+
inventoryTime: new Date().toISOString(),
|
|
36
|
+
projectId,
|
|
37
|
+
assetList,
|
|
38
|
+
classificationEncodingList,
|
|
39
|
+
changeTraceabilityReport,
|
|
40
|
+
overrideList,
|
|
41
|
+
errorList,
|
|
42
|
+
policyBasisSummary,
|
|
43
|
+
configVersions,
|
|
44
|
+
legalDisclaimer: LEGAL_DISCLAIMER,
|
|
45
|
+
};
|
|
46
|
+
const desensitizeResult = this.desensitizer.desensitize(report);
|
|
47
|
+
report = desensitizeResult.report;
|
|
48
|
+
return report;
|
|
49
|
+
}
|
|
50
|
+
writeJson(report, outputPathDir) {
|
|
51
|
+
const fullPath = resolve(outputPathDir, 'inventory-report.json');
|
|
52
|
+
if (!existsSync(outputPathDir)) {
|
|
53
|
+
mkdirSync(outputPathDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
writeFileSync(fullPath, JSON.stringify(report, null, 2), 'utf-8');
|
|
56
|
+
return fullPath;
|
|
57
|
+
}
|
|
58
|
+
writeMarkdown(report, outputPathDir) {
|
|
59
|
+
const fullPath = resolve(outputPathDir, 'inventory-report.md');
|
|
60
|
+
if (!existsSync(outputPathDir)) {
|
|
61
|
+
mkdirSync(outputPathDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
const markdown = this.renderMarkdown(report);
|
|
64
|
+
writeFileSync(fullPath, markdown, 'utf-8');
|
|
65
|
+
return fullPath;
|
|
66
|
+
}
|
|
67
|
+
exportLedger(assetList, outputPathDir) {
|
|
68
|
+
return this.ledgerExporter.exportToPath(assetList, outputPathDir);
|
|
69
|
+
}
|
|
70
|
+
renderMarkdown(report) {
|
|
71
|
+
const lines = [];
|
|
72
|
+
lines.push(`# 建筑工程项目数据资产盘点报告`);
|
|
73
|
+
lines.push('');
|
|
74
|
+
lines.push(`> ${report.legalDisclaimer}`);
|
|
75
|
+
lines.push('');
|
|
76
|
+
lines.push(`## 报告元信息`);
|
|
77
|
+
lines.push(`- 报告标识: ${report.reportId}`);
|
|
78
|
+
lines.push(`- 盘点时间: ${report.inventoryTime}`);
|
|
79
|
+
lines.push(`- 项目标识: ${report.projectId}`);
|
|
80
|
+
lines.push(`- 分类配置版本: ${report.configVersions.classificationConfigVersion}`);
|
|
81
|
+
lines.push(`- 编码规则配置版本: ${report.configVersions.encodingRuleConfigVersion}`);
|
|
82
|
+
lines.push(`- 适配器配置版本: ${report.configVersions.sourceAdapterConfigVersion}`);
|
|
83
|
+
lines.push('');
|
|
84
|
+
lines.push(`## 资产清单 (${report.assetList.length} 项)`);
|
|
85
|
+
lines.push('| 资产标识 | 数据类型 | 分类代码 | 资产编码 | 判定状态 | 政策依据 |');
|
|
86
|
+
lines.push('|---------|---------|---------|---------|---------|---------|');
|
|
87
|
+
for (const asset of report.assetList) {
|
|
88
|
+
lines.push(`| ${asset.assetId} | ${asset.dataType} | ${asset.classificationCode || '未分类'} | ${asset.assetCode || '未生成'} | ${asset.judgmentStatus} | ${asset.policyBasis || '-'} |`);
|
|
89
|
+
}
|
|
90
|
+
lines.push('');
|
|
91
|
+
lines.push(`## 分类编码清单 (${report.classificationEncodingList.length} 项)`);
|
|
92
|
+
lines.push('| 资产标识 | 分类代码 | 资产编码 | 判定状态 | 政策依据 |');
|
|
93
|
+
lines.push('|---------|---------|---------|---------|---------|');
|
|
94
|
+
for (const entry of report.classificationEncodingList) {
|
|
95
|
+
lines.push(`| ${entry.assetId} | ${entry.classificationCode} | ${entry.assetCode} | ${entry.judgmentStatus} | ${entry.policyBasis} |`);
|
|
96
|
+
}
|
|
97
|
+
lines.push('');
|
|
98
|
+
lines.push(`## 变更追溯报告 (${report.changeTraceabilityReport.length} 项)`);
|
|
99
|
+
if (report.changeTraceabilityReport.length === 0) {
|
|
100
|
+
lines.push('无变更追溯结果');
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
lines.push('| 变更标识 | 关联资产 | 记录完整 | 审批链完整 | 影响范围 | 缺失项 | 政策依据 |');
|
|
104
|
+
lines.push('|---------|---------|---------|---------|---------|---------|---------|');
|
|
105
|
+
for (const result of report.changeTraceabilityReport) {
|
|
106
|
+
lines.push(`| ${result.changeId} | ${result.assetLinked ? '是' : '否'} | ${result.recordComplete ? '是' : '否'} | ${result.approvalChainComplete ? '是' : '否'} | ${result.impactScopeLinked ? '是' : '否'} | ${result.missingItems.join('、') || '-'} | ${result.policyBasis} |`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
lines.push('');
|
|
110
|
+
lines.push(`## 人工覆盖记录 (${report.overrideList.length} 项)`);
|
|
111
|
+
if (report.overrideList.length === 0) {
|
|
112
|
+
lines.push('无人工覆盖记录');
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
lines.push('| 资产标识 | 复核项 | 原结论 | 新结论 | 覆盖理由 | 操作人 | 操作时间 | 原资产编码 |');
|
|
116
|
+
lines.push('|---------|---------|---------|---------|---------|---------|---------|---------|');
|
|
117
|
+
for (const override of report.overrideList) {
|
|
118
|
+
lines.push(`| ${override.assetId} | ${override.reviewItem} | ${override.originalConclusion} | ${override.newConclusion} | ${override.overrideReason} | ${override.operator} | ${override.overrideTime} | ${override.originalAssetCode} |`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
lines.push('');
|
|
122
|
+
lines.push(`## 错误清单 (${report.errorList.length} 项)`);
|
|
123
|
+
if (report.errorList.length === 0) {
|
|
124
|
+
lines.push('无错误');
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
for (const error of report.errorList) {
|
|
128
|
+
lines.push(`- ${error.assetId}: ${error.errorMessage}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
lines.push('');
|
|
132
|
+
lines.push(`## 政策依据汇总`);
|
|
133
|
+
for (const basis of report.policyBasisSummary) {
|
|
134
|
+
lines.push(`- ${basis}`);
|
|
135
|
+
}
|
|
136
|
+
lines.push('');
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=inventory-report-generator.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { POLICY_STANDARD_NAME } from './invariant.js';
|
|
2
|
+
export class PolicyBasisBuilder {
|
|
3
|
+
build(clause) {
|
|
4
|
+
return `依据:${POLICY_STANDARD_NAME}第${clause}章`;
|
|
5
|
+
}
|
|
6
|
+
buildClassification() {
|
|
7
|
+
return `依据:${POLICY_STANDARD_NAME}分类体系`;
|
|
8
|
+
}
|
|
9
|
+
buildEncoding() {
|
|
10
|
+
return `依据:${POLICY_STANDARD_NAME}编码规则`;
|
|
11
|
+
}
|
|
12
|
+
buildChangeTraceability() {
|
|
13
|
+
return `依据:${POLICY_STANDARD_NAME}变更管理要求`;
|
|
14
|
+
}
|
|
15
|
+
buildSummary(policyBasisList) {
|
|
16
|
+
const unique = new Set();
|
|
17
|
+
for (const basis of policyBasisList) {
|
|
18
|
+
if (basis && basis.trim() !== '') {
|
|
19
|
+
unique.add(basis);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return Array.from(unique).sort();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=policy-basis-builder.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
export class ProgressRecordAdapter {
|
|
3
|
+
adapt(sourcePath, systemId, projectId) {
|
|
4
|
+
const assets = [];
|
|
5
|
+
const warnings = [];
|
|
6
|
+
const errors = [];
|
|
7
|
+
let content;
|
|
8
|
+
try {
|
|
9
|
+
content = readFileSync(sourcePath, 'utf-8');
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
errors.push(`进度文件读取失败: ${sourcePath} - ${e.message}`);
|
|
13
|
+
return { assets, warnings, errors };
|
|
14
|
+
}
|
|
15
|
+
let records;
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(content);
|
|
18
|
+
records = Array.isArray(parsed) ? parsed : [parsed];
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
errors.push(`进度文件解析失败: ${sourcePath} - ${e.message}`);
|
|
22
|
+
return { assets, warnings, errors };
|
|
23
|
+
}
|
|
24
|
+
const collectionTime = new Date().toISOString();
|
|
25
|
+
for (const record of records) {
|
|
26
|
+
if (!record || typeof record !== 'object') {
|
|
27
|
+
warnings.push('进度记录格式不合法,已跳过');
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (typeof record.recordId !== 'string' || record.recordId.trim() === '') {
|
|
31
|
+
warnings.push('进度记录缺少recordId,已跳过');
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const dataType = this.mapProcessTypeToDataType(record.processType ?? '');
|
|
35
|
+
assets.push({
|
|
36
|
+
assetId: `PROGRESS-${record.recordId}`,
|
|
37
|
+
sourceSystemId: systemId,
|
|
38
|
+
dataType,
|
|
39
|
+
projectId,
|
|
40
|
+
collectionTime: record.completionTime || collectionTime,
|
|
41
|
+
classificationCode: '',
|
|
42
|
+
assetCode: '',
|
|
43
|
+
judgmentStatus: '自动判定',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return { assets, warnings, errors };
|
|
47
|
+
}
|
|
48
|
+
mapProcessTypeToDataType(processType) {
|
|
49
|
+
if (!processType)
|
|
50
|
+
return '进度记录-行为';
|
|
51
|
+
if (processType.includes('阶段'))
|
|
52
|
+
return '进度记录-项目阶段';
|
|
53
|
+
if (processType.includes('专业'))
|
|
54
|
+
return '进度记录-专业领域';
|
|
55
|
+
return '进度记录-行为';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=progress-record-adapter.js.map
|