liujie
2026-05-14 38346fde0ab9a9ce98ed83bea612cd6825378812
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
package com.estate.expert.admin.service.impl;
 
import com.estate.expert.admin.domain.business.device.Device;
import com.estate.expert.admin.domain.business.device.DeviceType;
import com.estate.expert.admin.domain.common.request.PageReq;
import com.estate.expert.admin.domain.common.response.PageResp;
import com.estate.expert.admin.domain.exam.eval.score.*;
import com.estate.expert.admin.domain.exam.record.error.ErrorRecord;
import com.estate.expert.admin.domain.exam.task.check.CheckTask;
import com.estate.expert.admin.domain.plan.PlanType;
import com.estate.expert.admin.domain.standard.expert.Expert;
import com.estate.expert.admin.domain.standard.hierachy.HierarchyData;
import com.estate.expert.admin.service.eval.EvalScoreService;
import com.estate.expert.admin.service.eval.EvalServiceImpl;
import com.estate.expert.admin.service.expert.DeviceExpertService;
import com.estate.expert.admin.service.expert.SysExpertService;
import com.estate.expert.admin.utils.tree.helper.TreeHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Stream;
 
/**
 * 评分算法服务实现
 */
@Service
public class EvalScoreServiceImpl extends EvalServiceImpl<EvalScoreResult, EvalScoreResultDto, EvalScoreResultInfo, ScoreResult> implements EvalScoreService {
 
    private static final Logger log = LoggerFactory.getLogger(EvalScoreServiceImpl.class);
    @Resource
    private EvalScoreResultRepository repository;
 
    @Override
    public PageResp<EvalScoreResultInfo> list(Integer clientId, List<Integer> planIds, List<Integer> taskIds,
                                              LocalDateTime startDate, LocalDateTime endDate, PageReq pageReq) {
        var page = repository.list(clientId, planIds, taskIds, startDate, endDate,pageReq.toPageRequest());
        return PageResp.of(page, page.getContent());
    }
 
    @Override
    public List<EvalScoreResultMiniInfo> listMini(List<Integer> projectIds, PlanType planType) {
        return repository.list(projectIds, planType);
    }
 
    @Override
    public EvalScoreResult getByCheckTaskId(Integer checkTaskId) {
        return repository.getFirstByTaskIdAndDeleted(checkTaskId, false);
    }
 
    @Override
    public boolean enableByPass() {
        return true;
    }
 
    /**
     * 打分算法
     *
     * 扣除分数:所选记录,对应的,问题的问题数量集合中,
     * 应得分数:体系层级中
     * 权重:在专家管理,打分算法,权重值,需要归一化;如果找不到,就从层级中取
     * (应得分数 - 扣除分数) * 权重,每层累加上去
     * 上一级的得分= 下级得分之和/下级应得分之和 * 上一级的总分
     * 叶子节点小问题只能出现一次
     *
     * 叶子节点的得分计算逻辑
     *
     * 假定一个节点为10分,每发现其下属的一个问题,就扣除相应的分数
     * 最终得到该节点的分数
     */
    @Override
    public ScoreResult evaluate(Expert scoreExpert, SysExpertService expertService, List<ErrorRecord> records, List<String> byPassPaths){
        // 初始化问题映射表
        var problemMap = initProblemMap(scoreExpert, expertService);
 
        // 根据专家的模型,构建评分树
        var scoreResult = initSysResult(scoreExpert, expertService);
        return commonEvaluate(problemMap, scoreResult, records, byPassPaths, 6);
    }
 
    @Override
    protected ScoreResult evaluate(Expert expert, DeviceType deviceType,
                                   DeviceExpertService expertService, List<ErrorRecord> errorRecords, List<String> byPassPaths) {
        // 初始化问题映射表
        var deviceScoreResult = initDeviceResult(deviceType, expert, expertService);
        var problemMap = initDeviceProblemMap(deviceType, expert, expertService);
        return commonEvaluate(problemMap, deviceScoreResult, errorRecords, byPassPaths, 4);
    }
 
    @Override
    protected EvalScoreResult saveResult(CheckTask checkTask, ScoreResult sysRes, Map<Integer, ScoreResult> devResMap, List<Device> devices) {
        if(sysRes != null && sysRes.root() != null)
            checkTask.setSystemScore(sysRes.root().getData().getScore());
        else
            checkTask.setSystemScore(null);
 
        var deviceScoreMap = new TreeMap<Integer, Float>();
        devResMap.forEach((k, v) -> {
            if(v != null && v.root() != null)
                deviceScoreMap.put(k, v.root().getData().getScore());
            else
                deviceScoreMap.put(k, null);
        });
        checkTask.setDeviceScoreMap(deviceScoreMap);
        return add(null , new EvalScoreResult(checkTask, sysRes, devResMap, devices));
    }
 
    private ScoreResult commonEvaluate(TreeMap<Integer, Boolean> problemMap, ScoreResult scoreResult,
                                       List<ErrorRecord> records, List<String> byPassPaths, int leafDepth){
        // 计数错误记录,将记录的扣分,累加到对应的节点上
        count(problemMap, scoreResult, records, byPassPaths);
 
        // 标记要参与评分的节点
        mark(scoreResult);
 
        // 优化掉没有参与评分的节点
        scoreResult = prune(scoreResult, leafDepth);
 
        // 按照树结构,逐层计算得分
        compute(scoreResult, leafDepth);
 
        prettyDisplay(scoreResult);
        return scoreResult;
    }
 
    private ScoreResult initSysResult(Expert expert, SysExpertService expertService) {
        var tree = expertService.getHierarchyTree(expert.getId());
        var root = TreeHelper.<HierarchyData, ScoreValue>convertTree(tree.root(), (node, newNode) ->
                ScoreValue.builder()
                        .total(node.getData().getScore())
                        .weight(node.getData().getWeight())
                        .build());
        return new ScoreResult(root);
    }
 
    private ScoreResult initDeviceResult(DeviceType deviceType, Expert expert, DeviceExpertService expertService){
        var tree = expertService.getHierarchyTree(expert.getId());
        var root = TreeHelper.<HierarchyData, ScoreValue>convertTree(tree.root(), (node, newNode) ->
                ScoreValue.builder()
                        .total(node.getData().getScore())
                        .weight(node.getData().getWeight())
                        .build(), (n) -> {
                            //删除掉非相关掉设备类型的节点
                            return n.getDepth() != 1 || deviceType.hierarchyPath().equals(n.getPath());
                        });
 
        return new ScoreResult(root);
    }
 
    private void count(TreeMap<Integer, Boolean> problemMap, ScoreResult scoreResult, List<ErrorRecord> records, List<String> byPassPaths) {
        // 打分算法
        // 将每个问题记录的扣分,累加到对应的节点上
        if(records != null) {
            records.forEach(errorRecord -> {
                var problem = errorRecord.getProblem();
                var ps = problemMap.get(problem.getId());
 
                // 如果问题不在模型中,不计算
                // 如果问题已经计算过,也不计算(同一问题的记录只计算一次)
                if (ps == null || ps)
                    return;
 
                problemMap.put(problem.getId(), true);
 
                // 数据出错,不计算
                if (problem.getNumberFactors().isEmpty() || errorRecord.getNumberFactorIndex() >= problem.getNumberFactors().size())
                    return;
 
                // 计算应扣除的分数
                var node = scoreResult.get(problem.getHierarchyPath());
                if(node == null)
                    return;
                var numberFactor = problem.getNumberFactors().get(errorRecord.getNumberFactorIndex());
                var scoreValue = node.getData();
                scoreValue.setDeduct(scoreValue.getDeduct() + numberFactor.getDeductPoint());
                scoreValue.setInclude(true);
                scoreValue.addError(errorRecord.getLevel());
                node.setData(scoreValue);
            });
        }
 
        // 轮空项,如果设置了,则需要参与计算
        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);
            });
        }
 
        // 累加到上一级
        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];
                }
            }
        });
    }
 
    /**
     * 标记节点,用于裁剪不需要参与计算的节点
     */
    private void mark(ScoreResult scoreResult)
    {
        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);
        });
    }
 
    // 对个别需要显示的数值进行更新,便于前端展示
    private void prettyDisplay(ScoreResult result) {
        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());
        });
    }
 
    /**
     * 根据层级和权重,计算得分
     */
    private void compute(ScoreResult scoreResult, int leafDepth) {
        TreeHelper.travel(scoreResult.root(), (node) -> {
            log.debug("计算节点: {}", node);
            float totalWeight = 0;
            float totalDeduct = 0;
 
            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);
 
                log.info("计算节点: {},得分: {}", node, value.getScore());
            }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);
 
                log.info("计算节点: {},得分: {}", node, value.getScore());
            }
        });
    }
 
    /**
     * 优化掉没有参与评分的节点
     */
    private ScoreResult prune(ScoreResult scoreResult, int depth){
        var root = TreeHelper.pruneTree(scoreResult.root(), (n) -> {
            return n.getData().isInclude() || n.getDepth() == depth;
        });
 
        return new ScoreResult(root);
    }
 
}