@liuhange/dsh-construction-data-inventory 0.0.0-stage → 3.1.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 +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/cordis.patch.yml +9 -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 +74 -5
package/README.md
CHANGED
|
@@ -1,3 +1,72 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @liuhange/dsh-construction-data-inventory
|
|
2
|
+
|
|
3
|
+
> 建筑工程项目数据资产盘点插件
|
|
4
|
+
|
|
5
|
+
## 插件职责
|
|
6
|
+
|
|
7
|
+
对建筑工程项目产生的多源异构数据进行资产化盘点,实现符合 GB/T 51269 标准的分类编码与可追溯资产台账输出。
|
|
8
|
+
|
|
9
|
+
## 法规依据
|
|
10
|
+
|
|
11
|
+
《建筑信息模型分类和编码标准》(GB/T 51269-2017)
|
|
12
|
+
|
|
13
|
+
## 法律免责声明
|
|
14
|
+
|
|
15
|
+
本报告由辅助盘点工具生成,不替代GB/T 51269标准的正式合规认证
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add @liuhange/dsh-construction-data-inventory
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 工具
|
|
24
|
+
|
|
25
|
+
### inspect_ai_dataset
|
|
26
|
+
|
|
27
|
+
对建筑工程项目多源异构数据进行资产化盘点,按 GB/T 51269 标准生成分类编码与资产台账。
|
|
28
|
+
|
|
29
|
+
**参数:**
|
|
30
|
+
|
|
31
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
32
|
+
|------|------|------|------|
|
|
33
|
+
| projectId | string | 是 | 建筑工程项目标识 |
|
|
34
|
+
| sourceConfigPath | string | 是 | 数据源配置文件路径 |
|
|
35
|
+
| changeDataPath | string | 否 | 变更单数据文件路径 |
|
|
36
|
+
| outputPathDir | string | 否 | 报告输出目录 |
|
|
37
|
+
|
|
38
|
+
### submit_review_override
|
|
39
|
+
|
|
40
|
+
提交人工复核覆盖指令,对自动分类与编码结果进行人工覆盖并记录覆盖理由。
|
|
41
|
+
|
|
42
|
+
**参数:**
|
|
43
|
+
|
|
44
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
45
|
+
|------|------|------|------|
|
|
46
|
+
| assetId | string | 是 | 被覆盖的资产标识 |
|
|
47
|
+
| reviewItem | string | 是 | 复核项(classification/encoding/changeTraceability) |
|
|
48
|
+
| overrideConclusion | string | 是 | 覆盖后结论 |
|
|
49
|
+
| overrideReason | string | 是 | 覆盖理由 |
|
|
50
|
+
| operator | string | 是 | 操作人标识 |
|
|
51
|
+
| inventoryReportPath | string | 是 | 盘点报告路径 |
|
|
52
|
+
|
|
53
|
+
## 配置文件
|
|
54
|
+
|
|
55
|
+
### config/classification-config.json
|
|
56
|
+
|
|
57
|
+
GB/T 51269 分类体系配置,包含建设成果(10-15)、建设进程(20-22)、建设资源(30-32, 40)、建设属性(41)四大类。
|
|
58
|
+
|
|
59
|
+
### config/encoding-rule-config.json
|
|
60
|
+
|
|
61
|
+
资产编码规则配置,包含分类层级码长度、顺序码长度、顺序码起始值、校验位算法。
|
|
62
|
+
|
|
63
|
+
### config/source-adapter-config.json
|
|
64
|
+
|
|
65
|
+
数据源适配器配置,定义 IFC/合同/进度/验收四类适配器。
|
|
66
|
+
|
|
67
|
+
## 技术栈
|
|
68
|
+
|
|
69
|
+
- TypeScript 5.7
|
|
70
|
+
- Node.js 22+
|
|
71
|
+
- DeepSeek Harness 插件框架
|
|
72
|
+
- Vitest
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"lastUpdated": "2024-01-01T00:00:00.000Z",
|
|
4
|
+
"nodes": [
|
|
5
|
+
{
|
|
6
|
+
"classificationCode": "10",
|
|
7
|
+
"classificationName": "按功能分建筑物",
|
|
8
|
+
"parentCode": "",
|
|
9
|
+
"level": 1,
|
|
10
|
+
"mappedDataTypes": ["BIM构件-建筑物", "BIM模型-建筑物", "设计图纸-建筑物"]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"classificationCode": "11",
|
|
14
|
+
"classificationName": "按形态分建筑物",
|
|
15
|
+
"parentCode": "",
|
|
16
|
+
"level": 1,
|
|
17
|
+
"mappedDataTypes": ["BIM构件-建筑物形态", "设计图纸-建筑物形态"]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"classificationCode": "12",
|
|
21
|
+
"classificationName": "按功能分建筑空间",
|
|
22
|
+
"parentCode": "",
|
|
23
|
+
"level": 1,
|
|
24
|
+
"mappedDataTypes": ["BIM构件-建筑空间", "BIM模型-建筑空间"]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"classificationCode": "13",
|
|
28
|
+
"classificationName": "按形态分建筑空间",
|
|
29
|
+
"parentCode": "",
|
|
30
|
+
"level": 1,
|
|
31
|
+
"mappedDataTypes": ["BIM构件-建筑空间形态"]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"classificationCode": "14",
|
|
35
|
+
"classificationName": "元素",
|
|
36
|
+
"parentCode": "",
|
|
37
|
+
"level": 1,
|
|
38
|
+
"mappedDataTypes": ["BIM构件-元素", "BIM模型-元素", "设计图纸-元素"]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"classificationCode": "15",
|
|
42
|
+
"classificationName": "工作成果",
|
|
43
|
+
"parentCode": "",
|
|
44
|
+
"level": 1,
|
|
45
|
+
"mappedDataTypes": ["BIM构件-工作成果", "设计图纸-工作成果"]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"classificationCode": "20",
|
|
49
|
+
"classificationName": "工程建设项目阶段",
|
|
50
|
+
"parentCode": "",
|
|
51
|
+
"level": 1,
|
|
52
|
+
"mappedDataTypes": ["进度记录-项目阶段", "工序记录-项目阶段"]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"classificationCode": "21",
|
|
56
|
+
"classificationName": "行为",
|
|
57
|
+
"parentCode": "",
|
|
58
|
+
"level": 1,
|
|
59
|
+
"mappedDataTypes": ["进度记录-行为", "工序记录-行为", "施工数据-行为"]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"classificationCode": "22",
|
|
63
|
+
"classificationName": "专业领域",
|
|
64
|
+
"parentCode": "",
|
|
65
|
+
"level": 1,
|
|
66
|
+
"mappedDataTypes": ["进度记录-专业领域", "工序记录-专业领域", "施工数据-专业领域"]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"classificationCode": "30",
|
|
70
|
+
"classificationName": "建筑产品",
|
|
71
|
+
"parentCode": "",
|
|
72
|
+
"level": 1,
|
|
73
|
+
"mappedDataTypes": ["材料清单-建筑产品", "材质信息-建筑产品"]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"classificationCode": "31",
|
|
77
|
+
"classificationName": "组织角色",
|
|
78
|
+
"parentCode": "",
|
|
79
|
+
"level": 1,
|
|
80
|
+
"mappedDataTypes": ["材料清单-组织角色", "参建方信息-组织角色"]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"classificationCode": "32",
|
|
84
|
+
"classificationName": "工具",
|
|
85
|
+
"parentCode": "",
|
|
86
|
+
"level": 1,
|
|
87
|
+
"mappedDataTypes": ["材料清单-工具"]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"classificationCode": "33",
|
|
91
|
+
"classificationName": "信息",
|
|
92
|
+
"parentCode": "",
|
|
93
|
+
"level": 1,
|
|
94
|
+
"mappedDataTypes": ["材料清单-信息", "材质信息-信息"]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"classificationCode": "40",
|
|
98
|
+
"classificationName": "材质",
|
|
99
|
+
"parentCode": "",
|
|
100
|
+
"level": 1,
|
|
101
|
+
"mappedDataTypes": ["材料清单-材质", "材质信息-材质", "材料数据-材质"]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"classificationCode": "41",
|
|
105
|
+
"classificationName": "属性",
|
|
106
|
+
"parentCode": "",
|
|
107
|
+
"level": 1,
|
|
108
|
+
"mappedDataTypes": ["验收属性", "质量属性", "验收文档-属性", "属性数据"]
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"lastUpdated": "2024-01-01T00:00:00.000Z",
|
|
4
|
+
"adapters": [
|
|
5
|
+
{
|
|
6
|
+
"type": "IFC",
|
|
7
|
+
"adapterClassName": "IfcModelAdapter",
|
|
8
|
+
"supportedVersions": ["IFC2X3", "IFC4"]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"type": "CONTRACT",
|
|
12
|
+
"adapterClassName": "ContractAdapter",
|
|
13
|
+
"supportedVersions": ["1.0"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "PROGRESS",
|
|
17
|
+
"adapterClassName": "ProgressRecordAdapter",
|
|
18
|
+
"supportedVersions": ["1.0"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"type": "ACCEPTANCE",
|
|
22
|
+
"adapterClassName": "AcceptanceDocAdapter",
|
|
23
|
+
"supportedVersions": ["1.0"]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Cordis Framework Patch Declaration for @liuhange/dsh-construction-data-inventory
|
|
2
|
+
# This file declares the plugin's runtime patch for the Cordis framework.
|
|
3
|
+
# Used by DSH plugin manager to apply framework-level patches during installation.
|
|
4
|
+
plugin: @liuhange/dsh-construction-data-inventory
|
|
5
|
+
version: 3.1.1
|
|
6
|
+
patch:
|
|
7
|
+
- target: tools
|
|
8
|
+
action: register
|
|
9
|
+
description: Register plugin tools into Cordis runtime
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
export class AcceptanceDocAdapter {
|
|
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.acceptanceId !== 'string' || record.acceptanceId.trim() === '') {
|
|
31
|
+
warnings.push('验收记录缺少acceptanceId,已跳过');
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const dataType = this.mapAcceptanceTypeToDataType(record.acceptanceType ?? '');
|
|
35
|
+
assets.push({
|
|
36
|
+
assetId: `ACCEPTANCE-${record.acceptanceId}`,
|
|
37
|
+
sourceSystemId: systemId,
|
|
38
|
+
dataType,
|
|
39
|
+
projectId,
|
|
40
|
+
collectionTime: record.acceptanceDate || collectionTime,
|
|
41
|
+
classificationCode: '',
|
|
42
|
+
assetCode: '',
|
|
43
|
+
judgmentStatus: '自动判定',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return { assets, warnings, errors };
|
|
47
|
+
}
|
|
48
|
+
mapAcceptanceTypeToDataType(acceptanceType) {
|
|
49
|
+
if (!acceptanceType)
|
|
50
|
+
return '验收属性';
|
|
51
|
+
if (acceptanceType.includes('质量'))
|
|
52
|
+
return '质量属性';
|
|
53
|
+
return '验收属性';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=acceptance-doc-adapter.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { POLICY_STANDARD_NAME } from './invariant.js';
|
|
2
|
+
import { SequentialCodeAllocator } from './sequential-code-allocator.js';
|
|
3
|
+
import { ChecksumCalculator } from './checksum-calculator.js';
|
|
4
|
+
import { CodeUniquenessValidator } from './code-uniqueness-validator.js';
|
|
5
|
+
export class AssetCodeGenerator {
|
|
6
|
+
checksumCalculator;
|
|
7
|
+
uniquenessValidator;
|
|
8
|
+
constructor() {
|
|
9
|
+
this.checksumCalculator = new ChecksumCalculator();
|
|
10
|
+
this.uniquenessValidator = new CodeUniquenessValidator();
|
|
11
|
+
}
|
|
12
|
+
generate(classifiedAssets, encodingRuleConfig) {
|
|
13
|
+
const errors = [];
|
|
14
|
+
const warnings = [];
|
|
15
|
+
const overflowAssets = [];
|
|
16
|
+
const policyBasis = `依据:${POLICY_STANDARD_NAME}编码规则`;
|
|
17
|
+
const allocator = new SequentialCodeAllocator(encodingRuleConfig);
|
|
18
|
+
this.uniquenessValidator.clear();
|
|
19
|
+
if (!encodingRuleConfig.checksumAlgorithm || encodingRuleConfig.checksumAlgorithm.trim() === '') {
|
|
20
|
+
errors.push('校验位算法配置缺失,终止编码生成');
|
|
21
|
+
for (const asset of classifiedAssets) {
|
|
22
|
+
asset.assetCode = '';
|
|
23
|
+
}
|
|
24
|
+
return { assets: classifiedAssets, errors, warnings, overflowAssets };
|
|
25
|
+
}
|
|
26
|
+
for (const asset of classifiedAssets) {
|
|
27
|
+
if (!asset.classificationCode || asset.classificationCode.trim() === '') {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const classificationLevelCode = this.padToLength(asset.classificationCode, encodingRuleConfig.classificationCodeLength);
|
|
31
|
+
const sequentialCode = allocator.allocate();
|
|
32
|
+
if (sequentialCode === null) {
|
|
33
|
+
errors.push(`顺序码溢出,资产 ${asset.assetId} 编码生成失败`);
|
|
34
|
+
overflowAssets.push(asset.assetId);
|
|
35
|
+
asset.assetCode = '';
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const sequentialCodeStr = this.padToLength(String(sequentialCode), encodingRuleConfig.sequentialCodeLength);
|
|
39
|
+
let checksum;
|
|
40
|
+
try {
|
|
41
|
+
checksum = this.checksumCalculator.calculate(classificationLevelCode + sequentialCodeStr, encodingRuleConfig.checksumAlgorithm);
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
errors.push(`校验位计算失败: ${e.message}`);
|
|
45
|
+
asset.assetCode = '';
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const assetCode = classificationLevelCode + sequentialCodeStr + checksum;
|
|
49
|
+
if (!this.uniquenessValidator.validate(assetCode)) {
|
|
50
|
+
errors.push(`编码重复: ${assetCode}`);
|
|
51
|
+
asset.assetCode = '';
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
asset.assetCode = assetCode;
|
|
55
|
+
asset.policyBasis = policyBasis;
|
|
56
|
+
}
|
|
57
|
+
return { assets: classifiedAssets, errors, warnings, overflowAssets };
|
|
58
|
+
}
|
|
59
|
+
regenerateForAsset(asset, encodingRuleConfig) {
|
|
60
|
+
const originalAssetCode = asset.assetCode;
|
|
61
|
+
const result = this.generate([asset], encodingRuleConfig);
|
|
62
|
+
if (result.assets[0] && result.assets[0].assetCode) {
|
|
63
|
+
result.assets[0].originalAssetCode = originalAssetCode;
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
generateSingleCode(classificationCode, sequentialCode, encodingRuleConfig) {
|
|
68
|
+
const policyBasis = `依据:${POLICY_STANDARD_NAME}编码规则`;
|
|
69
|
+
const classificationLevelCode = this.padToLength(classificationCode, encodingRuleConfig.classificationCodeLength);
|
|
70
|
+
const sequentialCodeStr = this.padToLength(String(sequentialCode), encodingRuleConfig.sequentialCodeLength);
|
|
71
|
+
const checksum = this.checksumCalculator.calculate(classificationLevelCode + sequentialCodeStr, encodingRuleConfig.checksumAlgorithm);
|
|
72
|
+
const assetCode = classificationLevelCode + sequentialCodeStr + checksum;
|
|
73
|
+
return { assetCode, policyBasis };
|
|
74
|
+
}
|
|
75
|
+
padToLength(value, length) {
|
|
76
|
+
if (value.length >= length) {
|
|
77
|
+
return value.slice(0, length);
|
|
78
|
+
}
|
|
79
|
+
return value.padStart(length, '0');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=asset-code-generator.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { resolve, sep } from 'node:path';
|
|
3
|
+
export class AssetLedgerExporter {
|
|
4
|
+
export(assetList, outputPath) {
|
|
5
|
+
const ledger = this.buildLedger(assetList);
|
|
6
|
+
const fullPath = resolve(outputPath);
|
|
7
|
+
const lastSep = fullPath.lastIndexOf(sep);
|
|
8
|
+
const dir = lastSep > 0 ? fullPath.substring(0, lastSep) : '';
|
|
9
|
+
if (dir && !existsSync(dir)) {
|
|
10
|
+
mkdirSync(dir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
writeFileSync(fullPath, JSON.stringify(ledger, null, 2), 'utf-8');
|
|
13
|
+
return fullPath;
|
|
14
|
+
}
|
|
15
|
+
buildLedger(assetList) {
|
|
16
|
+
const inventoryTime = new Date().toISOString();
|
|
17
|
+
return assetList.map((asset) => ({
|
|
18
|
+
assetCode: asset.assetCode,
|
|
19
|
+
classificationCode: asset.classificationCode,
|
|
20
|
+
sourceSystemId: asset.sourceSystemId,
|
|
21
|
+
inventoryTime,
|
|
22
|
+
judgmentStatus: asset.judgmentStatus,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
exportToPath(assetList, outputPathDir) {
|
|
26
|
+
const fullPath = resolve(outputPathDir, 'asset-ledger.json');
|
|
27
|
+
if (!existsSync(outputPathDir)) {
|
|
28
|
+
mkdirSync(outputPathDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
const ledger = this.buildLedger(assetList);
|
|
31
|
+
writeFileSync(fullPath, JSON.stringify(ledger, null, 2), 'utf-8');
|
|
32
|
+
return fullPath;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=asset-ledger-exporter.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { POLICY_STANDARD_NAME } from './invariant.js';
|
|
2
|
+
export class ChangeTraceabilityChecker {
|
|
3
|
+
check(changeRecords, assetIds) {
|
|
4
|
+
const results = [];
|
|
5
|
+
const warnings = [];
|
|
6
|
+
const policyBasis = `依据:${POLICY_STANDARD_NAME}变更管理要求`;
|
|
7
|
+
const assetIdSet = new Set(assetIds);
|
|
8
|
+
for (const change of changeRecords) {
|
|
9
|
+
if (!change || typeof change !== 'object') {
|
|
10
|
+
warnings.push('变更记录格式不兼容,已跳过');
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const missingItems = [];
|
|
14
|
+
const assetLinked = typeof change.relatedAssetId === 'string' &&
|
|
15
|
+
change.relatedAssetId.trim() !== '' &&
|
|
16
|
+
assetIdSet.has(change.relatedAssetId);
|
|
17
|
+
if (!assetLinked) {
|
|
18
|
+
missingItems.push('变更未关联资产');
|
|
19
|
+
}
|
|
20
|
+
const recordComplete = typeof change.changeReason === 'string' && change.changeReason.trim() !== '';
|
|
21
|
+
if (!recordComplete) {
|
|
22
|
+
missingItems.push('缺少变更原因');
|
|
23
|
+
}
|
|
24
|
+
const approvalChain = change.approvalChain;
|
|
25
|
+
const approvalChainComplete = approvalChain !== null &&
|
|
26
|
+
approvalChain !== undefined &&
|
|
27
|
+
typeof approvalChain === 'object' &&
|
|
28
|
+
typeof approvalChain.initiator === 'string' &&
|
|
29
|
+
approvalChain.initiator.trim() !== '' &&
|
|
30
|
+
typeof approvalChain.approver === 'string' &&
|
|
31
|
+
approvalChain.approver.trim() !== '' &&
|
|
32
|
+
typeof approvalChain.approvalTime === 'string' &&
|
|
33
|
+
approvalChain.approvalTime.trim() !== '' &&
|
|
34
|
+
typeof approvalChain.approvalConclusion === 'string' &&
|
|
35
|
+
approvalChain.approvalConclusion.trim() !== '';
|
|
36
|
+
if (!approvalChainComplete) {
|
|
37
|
+
const missingFields = [];
|
|
38
|
+
if (!approvalChain || typeof approvalChain.initiator !== 'string' || approvalChain.initiator.trim() === '') {
|
|
39
|
+
missingFields.push('发起人');
|
|
40
|
+
}
|
|
41
|
+
if (!approvalChain || typeof approvalChain.approver !== 'string' || approvalChain.approver.trim() === '') {
|
|
42
|
+
missingFields.push('审批人');
|
|
43
|
+
}
|
|
44
|
+
if (!approvalChain || typeof approvalChain.approvalTime !== 'string' || approvalChain.approvalTime.trim() === '') {
|
|
45
|
+
missingFields.push('审批时间');
|
|
46
|
+
}
|
|
47
|
+
if (!approvalChain || typeof approvalChain.approvalConclusion !== 'string' || approvalChain.approvalConclusion.trim() === '') {
|
|
48
|
+
missingFields.push('审批结论');
|
|
49
|
+
}
|
|
50
|
+
missingItems.push(`审批链不完整:缺少${missingFields.join('、')}`);
|
|
51
|
+
}
|
|
52
|
+
const impactScopeLinked = Array.isArray(change.impactScope) && change.impactScope.length > 0;
|
|
53
|
+
if (!impactScopeLinked) {
|
|
54
|
+
missingItems.push('影响范围未明确');
|
|
55
|
+
}
|
|
56
|
+
results.push({
|
|
57
|
+
changeId: change.changeId,
|
|
58
|
+
assetLinked,
|
|
59
|
+
recordComplete,
|
|
60
|
+
approvalChainComplete,
|
|
61
|
+
impactScopeLinked,
|
|
62
|
+
missingItems,
|
|
63
|
+
policyBasis,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return { results, warnings, skipped: false };
|
|
67
|
+
}
|
|
68
|
+
checkWithSkip(changeRecords, assetIds) {
|
|
69
|
+
if (changeRecords === null) {
|
|
70
|
+
return {
|
|
71
|
+
results: [],
|
|
72
|
+
warnings: ['变更追溯因系统不可用而跳过'],
|
|
73
|
+
skipped: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return this.check(changeRecords, assetIds);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=change-traceability-checker.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export class ChecksumCalculator {
|
|
2
|
+
calculate(code, algorithm) {
|
|
3
|
+
if (!algorithm || algorithm.trim() === '') {
|
|
4
|
+
throw new Error('CHECKSUM_ALGORITHM_MISSING');
|
|
5
|
+
}
|
|
6
|
+
switch (algorithm.toUpperCase()) {
|
|
7
|
+
case 'LUHN':
|
|
8
|
+
return this.luhnChecksum(code);
|
|
9
|
+
case 'MOD11':
|
|
10
|
+
return this.mod11Checksum(code);
|
|
11
|
+
default:
|
|
12
|
+
throw new Error('CHECKSUM_ALGORITHM_MISSING');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
luhnChecksum(code) {
|
|
16
|
+
const digits = code.replace(/\D/g, '').split('').map(Number);
|
|
17
|
+
let sum = 0;
|
|
18
|
+
let shouldDouble = false;
|
|
19
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
20
|
+
let digit = digits[i];
|
|
21
|
+
if (shouldDouble) {
|
|
22
|
+
digit *= 2;
|
|
23
|
+
if (digit > 9)
|
|
24
|
+
digit -= 9;
|
|
25
|
+
}
|
|
26
|
+
sum += digit;
|
|
27
|
+
shouldDouble = !shouldDouble;
|
|
28
|
+
}
|
|
29
|
+
const checksum = (10 - (sum % 10)) % 10;
|
|
30
|
+
return String(checksum);
|
|
31
|
+
}
|
|
32
|
+
mod11Checksum(code) {
|
|
33
|
+
const digits = code.replace(/\D/g, '').split('').map(Number);
|
|
34
|
+
let sum = 0;
|
|
35
|
+
const weights = [2, 3, 4, 5, 6, 7, 8, 9, 10, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
36
|
+
for (let i = 0; i < digits.length; i++) {
|
|
37
|
+
const weight = weights[i % weights.length];
|
|
38
|
+
sum += digits[i] * weight;
|
|
39
|
+
}
|
|
40
|
+
const remainder = sum % 11;
|
|
41
|
+
const checksum = remainder === 0 ? '0' : remainder === 1 ? 'X' : String(11 - remainder);
|
|
42
|
+
return checksum;
|
|
43
|
+
}
|
|
44
|
+
validate(codeWithChecksum, algorithm) {
|
|
45
|
+
if (codeWithChecksum.length < 2)
|
|
46
|
+
return false;
|
|
47
|
+
const code = codeWithChecksum.slice(0, -1);
|
|
48
|
+
const providedChecksum = codeWithChecksum.slice(-1);
|
|
49
|
+
const expectedChecksum = this.calculate(code, algorithm);
|
|
50
|
+
return providedChecksum === expectedChecksum;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=checksum-calculator.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { POLICY_STANDARD_NAME } from './invariant.js';
|
|
2
|
+
export class ClassificationMapper {
|
|
3
|
+
map(unifiedAssets, classificationConfig) {
|
|
4
|
+
const warnings = [];
|
|
5
|
+
const unclassified = [];
|
|
6
|
+
const policyBasis = `依据:${POLICY_STANDARD_NAME}分类体系`;
|
|
7
|
+
if (!classificationConfig || !Array.isArray(classificationConfig.nodes) || classificationConfig.nodes.length === 0) {
|
|
8
|
+
warnings.push('分类配置未提供,所有数据项标记为"未分类"');
|
|
9
|
+
for (const asset of unifiedAssets) {
|
|
10
|
+
asset.classificationCode = '';
|
|
11
|
+
asset.policyBasis = '';
|
|
12
|
+
unclassified.push(asset.assetId);
|
|
13
|
+
}
|
|
14
|
+
return { assets: unifiedAssets, warnings, unclassified };
|
|
15
|
+
}
|
|
16
|
+
for (const asset of unifiedAssets) {
|
|
17
|
+
const matchedRules = this.findMatchingRules(asset.dataType, classificationConfig.nodes);
|
|
18
|
+
if (matchedRules.length === 0) {
|
|
19
|
+
asset.classificationCode = '';
|
|
20
|
+
asset.policyBasis = '';
|
|
21
|
+
unclassified.push(asset.assetId);
|
|
22
|
+
warnings.push(`资产 ${asset.assetId} 无法分类,标记为"未分类",原始数据类型: ${asset.dataType}`);
|
|
23
|
+
}
|
|
24
|
+
else if (matchedRules.length === 1) {
|
|
25
|
+
asset.classificationCode = matchedRules[0].classificationCode;
|
|
26
|
+
asset.policyBasis = policyBasis;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const selectedRule = matchedRules[0];
|
|
30
|
+
asset.classificationCode = selectedRule.classificationCode;
|
|
31
|
+
asset.policyBasis = policyBasis;
|
|
32
|
+
warnings.push(`资产 ${asset.assetId} 多规则匹配,已按优先级选取 ${selectedRule.classificationCode}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { assets: unifiedAssets, warnings, unclassified };
|
|
36
|
+
}
|
|
37
|
+
findMatchingRules(dataType, nodes) {
|
|
38
|
+
const matched = [];
|
|
39
|
+
for (const node of nodes) {
|
|
40
|
+
if (node.mappedDataTypes && node.mappedDataTypes.includes(dataType)) {
|
|
41
|
+
matched.push(node);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return matched;
|
|
45
|
+
}
|
|
46
|
+
isValidClassificationCode(code, config) {
|
|
47
|
+
return config.nodes.some((node) => node.classificationCode === code);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=classification-mapper.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class CodeUniquenessValidator {
|
|
2
|
+
codeSet = new Set();
|
|
3
|
+
validate(assetCode) {
|
|
4
|
+
if (this.codeSet.has(assetCode)) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
this.codeSet.add(assetCode);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
has(assetCode) {
|
|
11
|
+
return this.codeSet.has(assetCode);
|
|
12
|
+
}
|
|
13
|
+
clear() {
|
|
14
|
+
this.codeSet.clear();
|
|
15
|
+
}
|
|
16
|
+
size() {
|
|
17
|
+
return this.codeSet.size;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=code-uniqueness-validator.js.map
|