打分算法说明

1. 算法概述

打分算法是房地产专家评估系统中的核心评估算法之一,用于对建筑工程安全风险进行量化评估。该算法基于层级体系结构,通过问题扣分和权重计算,最终得出综合评分。

2. 算法流程图

flowchart TD
    A[开始评估] --> B[初始化问题映射表]
    B --> C[构建评分树]
    C --> D[统计错误记录扣分]
    D --> E[标记参与评分节点]
    E --> F[裁剪无用节点]
    F --> G[逐层计算得分]
    G --> H[美化显示数据]
    H --> I[返回评估结果]
    
    subgraph "初始化阶段"
        B1[获取专家权重配置]
        B2[构建层级树结构]
        B3[初始化ScoreValue节点]
    end
    
    subgraph "统计阶段"
        D1[遍历错误记录]
        D2[计算问题扣分]
        D3[累加到对应节点]
        D4[处理轮空项]
        D5[向上累加错误统计]
    end
    
    subgraph "计算阶段"
        G1[标记叶子节点]
        G2[计算子节点得分]
        G3[权重归一化]
        G4[计算父节点得分]
        G5[递归向上计算]
    end
    
    B --> B1
    B1 --> B2
    B2 --> B3
    
    D --> D1
    D1 --> D2
    D2 --> D3
    D3 --> D4
    D4 --> D5
    
    G --> G1
    G1 --> G2
    G2 --> G3
    G3 --> G4
    G4 --> G5

3. 详细算法步骤

3.1 初始化阶段

3.1.1 初始化问题映射表

var problemMap = initProblemMap(scoreExpert, expertService);
  • 目的:建立问题ID到布尔值的映射,用于标记问题是否已被计算
  • 实现:从专家的模型中选择的问题ID列表,初始值设为false
  • 作用:确保同一问题只被计算一次,避免重复扣分

3.1.2 构建评分树

var scoreResult = initSysResult(scoreExpert, expertService);
  • 目的:根据专家的层级配置构建评分树结构
  • 实现:从层级数据中提取分数和权重信息,创建ScoreValue节点
  • 结构:树形结构,每个节点包含总分、权重、扣分等信息

3.2 统计阶段

3.2.1 统计错误记录扣分

count(problemMap, scoreResult, records, byPassPaths);

步骤详解:
1. 遍历错误记录:对每个错误记录进行处理
2. 问题验证:检查问题是否在模型中且未被计算过
3. 扣分计算:根据问题的NumberFactor计算应扣除的分数
4. 节点更新:将扣分累加到对应层级的节点上
5. 错误统计:记录错误等级和数量

关键逻辑:
java // 计算应扣除的分数 var numberFactor = problem.getNumberFactors().get(errorRecord.getNumberFactorIndex()); var scoreValue = node.getData(); scoreValue.setDeduct(scoreValue.getDeduct() + numberFactor.getDeductPoint());

3.2.2 处理轮空项

// 轮空项,如果设置了,则需要参与计算
if(byPassPaths != null){
    byPassPaths.forEach(path -> {
        var node = scoreResult.get(path);
        if(node == null) return;
        var scoreValue = node.getData();
        scoreValue.setInclude(true);
        node.setData(scoreValue);
    });
}
  • 目的:处理评估中需要跳过但参与计算的项
  • 实现:将轮空路径对应的节点标记为参与评分
  • 作用:确保轮空项不影响整体评分结构

3.2.3 向上累加错误统计

TreeHelper.travel(scoreResult.root(), (node) -> {
    var nodeData = node.getData();
    for(var child : node.getChildren().values()) {
        var childData = child.getData();
        for(var i = 0; i < 4; i++) {
            nodeData.getChildErrorCounts()[i] += childData.getErrorCounts()[i] + childData.getChildErrorCounts()[i];
        }
    }
});
  • 目的:将子节点的错误统计累加到父节点
  • 实现:遍历树结构,累加各级错误数量
  • 作用:为后续计算提供完整的错误统计信息

3.3 标记阶段

3.3.1 标记参与评分节点

mark(scoreResult);

标记逻辑:
```java
TreeHelper.travel(scoreResult.root(), (node) -> {
// 只去更新非叶子节点的include属性
if(node.getChildren().isEmpty()) return;

// 如果有子节点参与评分,当前节点也参与评分
boolean include = false;
for (var child : node.getChildren().values()) {
    if(child.getData().isInclude())
        include = true;
}
node.getData().setInclude(include);

});
```

  • 目的:标记哪些节点需要参与最终评分计算
  • 规则:如果子节点参与评分,父节点也必须参与评分
  • 作用:为后续裁剪和计算提供依据

3.4 裁剪阶段

3.4.1 裁剪无用节点

scoreResult = prune(scoreResult, leafDepth);

裁剪逻辑:
java var root = TreeHelper.pruneTree(scoreResult.root(), (n) -> { return n.getData().isInclude() || n.getDepth() == depth; });

  • 目的:移除不参与评分的节点,优化计算结构
  • 规则:保留参与评分的节点和指定深度的节点
  • 作用:减少计算量,提高算法效率

3.5 计算阶段

3.5.1 逐层计算得分

compute(scoreResult, leafDepth);

计算逻辑分为两个层次:

3.5.1.1 叶子节点层计算(最下两层)
if(node.getDepth() >= (leafDepth - 2)) {
    // 最下的两层的计算逻辑不一样,只计算得分,不计算权重
    float total = 0;
    for(var child : node.getChildren().values()) {
        if (!child.getData().isInclude()) continue;
        
        // 计算总扣分
        var sv = child.getData();
        totalDeduct += Math.min(sv.getTotal(), sv.getDeduct() + sv.getChildrenDeduct());
        total += sv.getTotal();
    }
    
    var value = node.getData();
    if(node.getDepth() != leafDepth)
        value.setTotal(total);
    
    value.setChildrenDeduct(totalDeduct);
    value.setTotalDeduct(value.getDeduct() + value.getChildrenDeduct());
    value.setScore(Math.max(value.getTotal() - value.getTotalDeduct(), 0));
    value.setNormalizedWeight(1.0f);
}

特点:
- 直接计算得分,不进行权重归一化
- 扣分不能超过总分
- 最终得分 = 总分 - 总扣分(最小为0)

3.5.1.2 父节点层计算
else {
    var total = 0.0f;
    
    // 计算总权重
    for (var child : node.getChildren().values()) {
        if (!child.getData().isInclude()) continue;
        totalWeight += Math.max(child.getData().getWeight(), 1e-3f); // 防止权重为0
    }
    
    // 归一化权重,计算总扣分
    for (var child : node.getChildren().values()) {
        if (!child.getData().isInclude()) continue;
        
        var sv = child.getData();
        sv.setNormalizedWeight(Math.max(sv.getWeight(), 1e-3f) / totalWeight);
        
        // 最大扣分不能超过总分
        totalDeduct += Math.min(sv.getTotal(), sv.getDeduct() + sv.getChildrenDeduct()) / sv.getTotal() * sv.getNormalizedWeight();
        total += sv.getTotal();
    }
    
    var value = node.getData();
    value.setChildrenDeduct(totalDeduct * value.getTotal());
    value.setTotalDeduct(value.getDeduct() + value.getChildrenDeduct());
    value.setScore(Math.max(value.getTotal() - value.getTotalDeduct(), 0));
    value.setNormalizedWeight(1.0f);
}

特点:
- 进行权重归一化处理
- 按权重比例计算扣分
- 考虑子节点的得分情况

3.6 美化阶段

3.6.1 美化显示数据

prettyDisplay(scoreResult);

美化逻辑:
java TreeHelper.travel(result.root(), (node) -> { var fuzzyValue = node.getData(); // totalErrorCounts = errorCounts + childErrorCounts fuzzyValue.setTotalErrorCounts(Stream.of(0,1,2,3) .map(i -> fuzzyValue.getErrorCounts()[i] + fuzzyValue.getChildErrorCounts()[i]) .mapToInt(Integer::intValue) .toArray()); });

  • 目的:计算总错误数量,便于前端显示
  • 实现:将节点自身错误和子节点错误累加
  • 作用:提供完整的错误统计信息

4. 算法特点

4.1 核心特性

  1. 层级计算:基于树形结构逐层计算得分
  2. 权重归一化:确保权重分配的合理性
  3. 扣分限制:扣分不能超过总分,避免负分
  4. 问题去重:同一问题只计算一次扣分
  5. 轮空处理:支持轮空项的特殊处理

4.2 计算规则

  1. 叶子节点:直接计算得分,不进行权重处理
  2. 父节点:基于子节点得分和权重计算
  3. 权重处理:防止权重为0,进行归一化
  4. 扣分处理:最大扣分不超过总分

4.3 优化策略

  1. 节点裁剪:移除不参与评分的节点
  2. 问题映射:避免重复计算同一问题
  3. 树形遍历:高效的树结构操作
  4. 内存优化:合理的数据结构设计

5. 算法复杂度

  • 时间复杂度:O(n + m),其中n是节点数量,m是错误记录数量
  • 空间复杂度:O(n),主要是树形结构的存储
  • 优化效果:通过裁剪和映射优化,实际执行效率较高

6. 应用场景

  1. 综合体系评估:基于系统模型的综合评分
  2. 设备体系评估:基于设备模型的专项评分
  3. 混合评估:同时进行综合和设备评估
  4. 历史评估:对历史检查任务进行重新评估

7. 注意事项

  1. 数据完整性:确保层级数据和问题数据的完整性
  2. 权重配置:专家权重配置需要合理设置
  3. 问题映射:问题ID映射表需要正确初始化
  4. 轮空处理:轮空项的处理需要特别关注
  5. 边界条件:处理权重为0、扣分超限等边界情况