zxl
2025-06-05 0ba453c66cd5d20b970c894358031c9c06df7071
ycl-server/src/main/java/com/ycl/task/ContractTask.java
@@ -1,36 +1,608 @@
package com.ycl.task;
import com.ycl.platform.mapper.TContractMapper;
import com.ycl.platform.mapper.TMonitorMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.ycl.platform.domain.entity.ContractRuleRecord;
import com.ycl.platform.domain.entity.ContractScore;
import com.ycl.platform.domain.entity.WorkOrder;
import com.ycl.platform.domain.entity.YwPoint;
import com.ycl.platform.domain.result.HK.PicAccessResult;
import com.ycl.platform.domain.result.SYS.TMonitorResult;
import com.ycl.platform.domain.result.UY.RecordMetaDSumResult;
import com.ycl.platform.domain.vo.CalculateRuleVO;
import com.ycl.platform.domain.vo.WorkOrderVO;
import com.ycl.platform.mapper.*;
import com.ycl.platform.service.IContractScoreService;
import com.ycl.system.mapper.SysConfigMapper;
import com.ycl.utils.DateUtils;
import com.ycl.utils.StringUtils;
import constant.ApiConstants;
import enumeration.ContractRule;
import enumeration.ErrorType;
import enumeration.general.AuditingStatus;
import enumeration.general.WorkOrderStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
 * 合同考核定时任务
 */
@Slf4j
@Component("contractTask")
//TODO:重新看下逻辑
public class ContractTask {
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private TMonitorMapper monitorMapper;
    @Autowired
    private TContractMapper contractMapper;
    @Autowired
    private YwPointMapper ywPointMapper;
    @Autowired
    private ReportMapper reportMapper;
    @Autowired
    private ContractRuleRecordMapper recordMapper;
    @Autowired
    private IContractScoreService contractScoreService;
    @Autowired
    private WorkOrderMapper workOrderMapper;
    @Autowired
    private SysConfigMapper sysConfigMapper;
    private static final Integer Online = 1;
    private static final Integer Offline = -1;
    private static final String AuditStatus_Pass = "1";
    private static final String Remark = "系统生成";
    /**
     * 合同考核 在线率每日任务检测
     * 查生效的合同关联的公司,获取unitId集合
     * 根据unitId查询对应点位获取各个公司管理的设备Ids
     * 查询三种设备在线不在线情况,封装为一个map<国标码,在线状态>
     * 计算每日每家公司的在线率存入redis
     * 计算每日每家公司的在线率存入mysql
     * 月底计算平均值,根据在线率和合同标准扣减分数
     */
    public void onlineCheck() {
//        contractMapper.selectByRuleName();
        log.info("开始计算合同点位在线率");
        List<CalculateRuleVO> ruleVos = contractMapper.getCalculateRule(new Date()).stream()
                .filter(calculateRuleVO -> ContractRule.CONTRACT_RULE_Online.getName().equals(calculateRuleVO.getRuleName()))
                .collect(Collectors.toList());
        List<Integer> unitIds = ruleVos.stream().map(CalculateRuleVO::getUnitId).collect(Collectors.toList());
        List<YwPoint> ywPoints = ywPointMapper.selectList(new QueryWrapper<YwPoint>().in("unit_id", unitIds).eq("examine_status", 1));
        //key是unitId value是设备编码集合
        Map<Long, List<YwPoint>> unitMap = ywPoints.stream().filter(point -> point.getUnitId() != null)
                .collect(Collectors.groupingBy(
                                YwPoint::getUnitId
                        )
                );
        //查询报备列表
        List<String> reportNumbers = reportMapper.selectNumberList(AuditStatus_Pass, DateUtils.getDate());
        //计算每个公司的点位在线率
        List<ContractRuleRecord> ruleRecordList = new ArrayList<>();
        unitMap.forEach((unitId, pointList) -> {
            int totalSite = 0;
            int onlineSite = 0;
            for (YwPoint point : pointList) {
                //报备过不纳入计算
                if (!CollectionUtils.isEmpty(reportNumbers) && reportNumbers.contains(point.getSerialNumber()))
                    continue;
                totalSite++;
                if (ApiConstants.UY_OnlineSite_Online.equals(point.getOnline()) && ApiConstants.UY_OnlineSite_Online.equals(point.getPingOnline())) {
                    onlineSite++;
                }
            }
            BigDecimal online = BigDecimal.ONE;
            if (totalSite != 0) {
                online = new BigDecimal(onlineSite).divide(new BigDecimal(totalSite), 2, RoundingMode.DOWN);
            }
            ContractRuleRecord contractRuleRecord = new ContractRuleRecord();
            contractRuleRecord.setSiteOnline(online);
            contractRuleRecord.setCreateTime(new Date());
            contractRuleRecord.setUnitId(unitId);
            ruleRecordList.add(contractRuleRecord);
        });
        //存储结果
        recordMapper.insertBatch(ruleRecordList);
        log.info("结束计算合同点位在线率");
    }
    //月底计算在线率分数
    public void calculateOnlineScore() {
        log.info("开始计算合同点位在线率分数");
        //月底需要统计平均在线率然后进行积分扣除
        String mouthStart = DateUtils.getMouthStart(new Date());
        String mouthEnd = DateUtils.getMouthEnd(new Date());
        //查一个月的记录
        List<ContractRuleRecord> ruleMonthRecords = recordMapper.selectMonth(mouthStart, mouthEnd);
        //通过unitId分单位,获取当月数据map
        Map<Long, List<ContractRuleRecord>> unitMap = ruleMonthRecords.stream().collect(Collectors.groupingBy(ContractRuleRecord::getUnitId));
        //查在线率规则 获取key为合同id,value为在线率规则集合
        Map<Integer, List<CalculateRuleVO>> contractMap = contractMapper.getCalculateRule(new Date()).stream()
                .filter(calculateRuleVO -> ContractRule.CONTRACT_RULE_Online.getName().equals(calculateRuleVO.getRuleName()))
                .collect(Collectors.groupingBy(CalculateRuleVO::getContractId));
        //准备批量打分的集合
        List<ContractScore> contractScoreList = new ArrayList<>();
        contractMap.forEach((contractId, ruleList) -> {
            //一个合同对应一个单位,因此unitId都相同
            CalculateRuleVO calculateRuleVO = ruleList.get(0);
            Integer unitId = calculateRuleVO.getUnitId();
            List<ContractRuleRecord> ruleRecordList = unitMap.get(Long.parseLong(unitId + ""));
            if (!CollectionUtils.isEmpty(ruleRecordList)) {
                BigDecimal siteOnlineTotal = ruleRecordList.stream().map(ContractRuleRecord::getSiteOnline).reduce(BigDecimal.ZERO, BigDecimal::add);
                BigDecimal siteOnline = siteOnlineTotal.divide(new BigDecimal(ruleRecordList.size()), 2, RoundingMode.DOWN);
                for (CalculateRuleVO ruleVO : ruleList) {
                    Double max = ruleVO.getMax();
                    Double min = ruleVO.getMin();
                    //判断范围在哪个区间
                    if (checkRange(min, max, siteOnline.multiply(new BigDecimal(100)))) {
                        //需要扣除的分数
                        Double deductScore = ruleVO.getCalcFraction();
                        ContractScore contractScore = getContractScore(ruleVO, deductScore, siteOnline + "", Remark);
                        contractScoreList.add(contractScore);
                    }
                }
            }
        });
        contractScoreService.saveBatch(contractScoreList);
        log.info("结束计算合同点位在线率分数");
    }
    /**
     * 检测工单表 进行合同积分扣除
     * 查出工单需要扣分的所有规则
     * 查出未扣分且已经审核完成了的工单组成map<unitId,List<WorkOrder>> 工单需要连工单故障表查出多个故障类型
     * 循环工单map,每个工单故障类型查对应的规则,根据规则和工单创建时间和审核通过时间进行扣分
     * 插入合同积分表,修改工单状态为已扣分
     */
    public void workOrderDeduct() {
        log.info("开始扫描工单扣分");
        //准备批量打分的集合
        List<ContractScore> contractScoreList = new ArrayList<>();
        List<String> workOrderList = new ArrayList<>();
        //查询生效合同对应所有的规则
        List<CalculateRuleVO> calculateRules = contractMapper.getCalculateRule(new Date());
        Map<String, Map<Integer, List<CalculateRuleVO>>> ruleMap = calculateRules.stream()
                .collect(Collectors.groupingBy(
                        CalculateRuleVO::getRuleName,  // 按规则名称分组
                        Collectors.groupingBy(
                                CalculateRuleVO::getContractId // 每个规则名称内部再按合同ID分组,value为规则集合(多个合同情况)
                        )
                ));
        //前端感知源治理工作(时钟同步规则、OSD规则、一机一档规则) 获取key为合同id,value为规则的map
        Map<Integer, List<CalculateRuleVO>> monitorRuleMap = ruleMap.get(ContractRule.CONTRACT_RULE_Monitor.getName());
        //存储故障(24小时以内,48小时以内) 获取key为合同id,value为规则的map   (改成手动的了)
//        Map<Integer, List<CalculateRuleVO>> storeRuleMap = ruleMap.get(ContractRule.CONTRACT_RULE_Store.getName());
        //点位异常情况处理 获取key为合同id,value为规则的map
        Map<Integer, List<CalculateRuleVO>> siteRuleMap = ruleMap.get(ContractRule.CONTRACT_RULE_Site.getName());
        //查询报备列表
        List<String> reportNumbers = reportMapper.selectNumberList(AuditStatus_Pass, DateUtils.getDate());
        //查询30天内所有未扣分、审核通过的工单
        // 获取当前日期
        LocalDateTime endTime = LocalDateTime.now();
        // 计算30天前的日期
        LocalDateTime startTime = endTime.minusDays(30);
        //自动扣分的工单故障类型
        List<String> deductErrorType = new ArrayList<>();
        deductErrorType.add(ErrorType.OSD_ERROR.getValue());
        deductErrorType.add(ErrorType.ABNORMAL_PIC.getValue());
        deductErrorType.add(ErrorType.POINT_INFO_ERROR.getValue());
        deductErrorType.add(ErrorType.CLOCK_SKEW.getValue());
        List<WorkOrderVO> workOrders = workOrderMapper.selectPassOrder(startTime, endTime, WorkOrderStatusEnum.AUDITING_SUCCESS.getValue(), deductErrorType, "审核通过");
        //存在有两种扣分的故障 只扣减第一个故障
        Map<String, WorkOrderVO> map = workOrders.stream()
                .collect(Collectors.toMap(
                        WorkOrderVO::getWorkOrderNo,
                        Function.identity(),
                        (older, newer) -> older.getDistributeTime().compareTo(newer.getDistributeTime()) <= 0 ? older : newer
                ));
        workOrders = new ArrayList<>(map.values());
        List<String> workOrderNos = workOrders.stream().map(WorkOrderVO::getWorkOrderNo).collect(Collectors.toList());
        List<WorkOrderVO> auditTimeList = workOrderMapper.getAuditTimeList(workOrderNos);
        Map<String, List<Date>> auditTimeMap = auditTimeList.stream()
                .collect(Collectors.groupingBy(
                        WorkOrderVO::getWorkOrderNo, // 分组的键
                        Collectors.mapping(WorkOrderVO::getAuditTime, Collectors.toList())));
        List<WorkOrderVO> handleTimeList = workOrderMapper.getHandleTimeList(workOrderNos);
        Map<String, List<Date>> handleTimeMap = handleTimeList.stream()
                .collect(Collectors.groupingBy(
                        WorkOrderVO::getWorkOrderNo, // 分组的键
                        Collectors.mapping(WorkOrderVO::getHandleTime, Collectors.toList())));
        for (WorkOrderVO workOrder : workOrders) {
            //检测是否报备过
            if (!CollectionUtils.isEmpty(reportNumbers)) {
                if (reportNumbers.contains(workOrder.getSerialNumber())) continue;
            }
//            if(!WorkOrderStatusEnum.AUDITING_SUCCESS.equals(workOrder.getStatus())) continue;
            String errorType = workOrder.getErrorType();
            //存储故障 录像或图片访问异常 (改成手动打分了)
//            if (ErrorType.VIDEO_NONE.getValue().equals(errorType) || ErrorType.PIC_URLABNORMAL.getValue().equals(errorType)) {
//                if (!CollectionUtils.isEmpty(storeRuleMap)) {
//                    storeRuleMap.forEach((contractId, rules) -> {
//                        Integer unitId = rules.get(0).getUnitId();
//                        //找到对应的规则
//                        if (workOrder.getUnitId().equals(unitId)) {
//                            //工单下发时间
//                            Date createTime = workOrder.getDistributeTime();
//                            Date auditTime = workOrder.getAuditTime();
//                            double diffTime = (double) (auditTime.getTime() - createTime.getTime()) / (1000 * 60 * 60);
//                            //选择时间范围内的规则
//                            for (CalculateRuleVO rule : rules) {
//                                if (checkRange(rule.getMin(), rule.getMax(), new BigDecimal(diffTime))) {
//                                    double deductScore = rule.getCalcFraction() * Math.ceil(diffTime);
//                                    ContractScore contractScore = getContractScore(rule, deductScore, Math.round(diffTime * 100) / 100 + "", Remark + "工单编号为:" + workOrder.getWorkOrderNo() + "处理超时,扣除" + deductScore + "分");
//                                    contractScoreList.add(contractScore);
//                                    workOrderList.add(workOrder.getWorkOrderNo());
//                                }
//                            }
//                        }
//                    });
//                }
//            }
            //前端感知源治理工作(时钟同步规则、OSD规则、一机一档规则)
            if (ErrorType.OSD_ERROR.getValue().equals(errorType) || ErrorType.CLOCK_SKEW.getValue().equals(errorType) || ErrorType.POINT_INFO_ERROR.getValue().equals(errorType)) {
                if (!CollectionUtils.isEmpty(monitorRuleMap)) {
                    monitorRuleMap.forEach((contractId, rules) -> {
                        Integer unitId = rules.get(0).getUnitId();
                        //找到对应的规则
                        if (workOrder.getUnitId().equals(unitId)) {
                            //工单下发时间
                            Date createTime = workOrder.getDistributeTime();
                            List<Date> auditTimes = auditTimeMap.get(workOrder.getWorkOrderNo());
                            List<Date> handleTimes = handleTimeMap.get(workOrder.getWorkOrderNo());
                            //拿到审核时间
                            long auditDuration = getAuditDuration(auditTimes, handleTimes);
                            //审核通过时间
                            Date passTime = Collections.max(auditTimes);
                            //处理花费总时长
                            double diffTime = (double) (passTime.getTime() - createTime.getTime() - auditDuration) / (1000 * 60 * 60);
                            //找到对应规则、选择时间范围内的规则
                            for (CalculateRuleVO rule : rules) {
                                if (ErrorType.OSD_ERROR.getValue().equals(errorType)) {
                                    if (!rule.getRuleCondition().equals("OSD标识")) continue;
                                } else if (ErrorType.CLOCK_SKEW.getValue().equals(errorType)) {
                                    if (!rule.getRuleCondition().equals("时钟同步")) continue;
                                } else if (ErrorType.POINT_INFO_ERROR.getValue().equals(errorType)) {
                                    if (!rule.getRuleCondition().equals("一机一档")) continue;
                                }
                                if (checkRange(rule.getMin(), rule.getMax(), new BigDecimal(diffTime))) {
                                    double deductScore = rule.getCalcFraction();
                                    ContractScore contractScore = getContractScore(rule, deductScore, Math.round(diffTime * 100) / 100 + "", Remark + "工单编号为:" + workOrder.getWorkOrderNo() + "处理超时,扣除" + deductScore + "分");
                                    contractScoreList.add(contractScore);
                                    workOrderList.add(workOrder.getWorkOrderNo());
                                }
                            }
                        }
                    });
                }
            }
            //点位异常情况处理(镜头异常、摄像头遮挡等)
            if (ErrorType.ABNORMAL_PIC.getValue().equals(errorType)) {
                if (!CollectionUtils.isEmpty(monitorRuleMap)) {
                    siteRuleMap.forEach((contractId, rules) -> {
                        Integer unitId = rules.get(0).getUnitId();
                        //设备是否是该公司运维
                        if (workOrder.getUnitId().equals(unitId)) {
                            //工单下发时间
                            Date createTime = workOrder.getDistributeTime();
                            List<Date> auditTimes = auditTimeMap.get(workOrder.getWorkOrderNo());
                            List<Date> handleTimes = handleTimeMap.get(workOrder.getWorkOrderNo());
                            //拿到审核时间
                            long auditDuration = getAuditDuration(auditTimes, handleTimes);
                            //审核通过时间
                            Date passTime = Collections.max(auditTimes);
                            double diffTime = (double) (passTime.getTime() - createTime.getTime() - auditDuration) / (1000 * 60 * 60);
                            //选择时间范围内的规则
                            for (CalculateRuleVO rule : rules) {
                                if (checkRange(rule.getMin(), rule.getMax(), new BigDecimal(diffTime))) {
                                    double deductScore = 0d;
                                    if (ContractRule.CONTRACT_RULE_Site_Error48.getName().equals(rule.getRuleCondition())) {
                                        //计算超时天数
                                        int day = (int) ((diffTime - 48) / 24 + 1);
                                        deductScore = rule.getCalcFraction() * (day);
                                    } else {
                                        deductScore = rule.getCalcFraction();
                                    }
                                    ContractScore contractScore = getContractScore(rule, deductScore, Math.round(diffTime * 100) / 100 + "", Remark + "工单编号为:" + workOrder.getWorkOrderNo() + "处理超时,扣除" + deductScore + "分");
                                    contractScoreList.add(contractScore);
                                    workOrderList.add(workOrder.getWorkOrderNo());
                                }
                            }
                        }
                    });
                }
            }
        }
        contractScoreService.saveBatch(contractScoreList);
        if (!CollectionUtils.isEmpty(workOrderList)) {
            //修改工单扣分状态为已扣分
            UpdateWrapper<WorkOrder> updateWrapper = new UpdateWrapper<>();
            updateWrapper.in("work_order_no", workOrderList);
            updateWrapper.set("deduct", 1);
            workOrderMapper.update(null, updateWrapper);
        }
        log.info("结束执行工单扣分");
    }
    private long getAuditDuration(List<Date> auditTimes, List<Date> handleTimes) {
        //计算出审核的总时间(由于故障类型会更新导致可能提交的时间点数量和审核的时间点数量不一致)
        long auditDuration = 0;
        for (Date auditTime : auditTimes) {
            //离这次审核最近的一次提交处理时间点
            Date nearestHandleTime = null;
            long minDifference = Long.MAX_VALUE;
            for (Date handleTime : handleTimes) {
                if (handleTime.before(auditTime)) {
                    long difference = Math.abs(auditTime.getTime() - handleTime.getTime());
                    if (difference < minDifference) {
                        minDifference = difference;
                        nearestHandleTime = handleTime;
                    }
                }
            }
            auditDuration += auditTime.getTime() - nearestHandleTime.getTime();
        }
        return auditDuration;
    }
    /**
     * 不定期检查数据 扣除积分
     * 海康取人脸车辆
     */
    public void randomDeductPic() {
        try {
            log.info("开始抽查图片完整状态");
            //这个月随机抽取一天
//        Date date = getRandomDate();
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.MONTH, -1); // 上个月
            calendar.set(Calendar.DAY_OF_MONTH, 1); // 设置为1号
            int lastDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
            Random random = new Random();
            int randomDay = random.nextInt(lastDay) + 1;
            calendar.set(Calendar.DAY_OF_MONTH, randomDay);
            Date date = calendar.getTime();
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            //准备批量打分的集合
            List<ContractScore> contractScoreList = new ArrayList<>();
            //查询报备列表 通过审核并且创建时间在当前日期的报备id列表
            List<String> reportNumbers = reportMapper.selectNumberList(AuditStatus_Pass, DateUtils.getDate());
            //查图片完整性规则 获取key为合同id,value为规则的map
            Map<Integer, List<CalculateRuleVO>> contractMap = contractMapper.getCalculateRule(new Date()).stream()
                    .filter(calculateRuleVO -> ContractRule.CONTRACT_RULE_PicComplete.getName().equals(calculateRuleVO.getRuleName()))
                    .collect(Collectors.groupingBy(CalculateRuleVO::getContractId));
            //判断车辆、人脸图片是否可用
            Query query = new Query(Criteria
                    .where("mongoCreateTime").gte(DateUtils.getDayStart(date)).lt(DateUtils.getDayEnd(date)));
            List<PicAccessResult> picAccessResults = mongoTemplate.find(query, PicAccessResult.class);
            List<String> serialNumbers = picAccessResults.stream().map(PicAccessResult::getExternalIndexCode).collect(Collectors.toList());
            QueryWrapper<YwPoint> queryWrapper = new QueryWrapper<>();
            queryWrapper.in("serial_number", serialNumbers);
            //获取公司所运维的设备集合,key为unitId value为设备国标码集合
            Map<Long, List<String>> unitMonitorMap = ywPointMapper.selectList(queryWrapper).stream()
                    .filter(ywPoint -> ywPoint.getUnitId() != null).collect(Collectors.groupingBy(YwPoint::getUnitId,
                            Collectors.mapping(
                                    YwPoint::getSerialNumber,
                                    Collectors.toList())));
            if (!CollectionUtils.isEmpty(contractMap)) {
                contractMap.forEach((contractId, ruleList) -> {
                    boolean deduct = false;
                    String serialNumber = null;
                    //此规则对应的unitId均相等
                    CalculateRuleVO ruleVO = ruleList.get(0);
                    Integer unitId = ruleVO.getUnitId();
                    List<String> monitorList = unitMonitorMap.get(Long.parseLong(unitId + ""));
                    for (PicAccessResult picAccessResult : picAccessResults) {
                        //判断是否报备过
                        if (!CollectionUtils.isEmpty(reportNumbers)) {
                            if (reportNumbers.contains(picAccessResult.getExternalIndexCode())) continue;
                        }
                        //判断是否是该公司运维
                        if (monitorList.contains(picAccessResult.getExternalIndexCode())) {
                            //存在图片访问异常数据量,需要扣减
                            if (picAccessResult.getExpCount() > 0) {
                                deduct = true;
                                serialNumber = picAccessResult.getExternalIndexCode();
                                break;
                            }
                        }
                    }
                    if (deduct) {
                        //需要扣除的分数,此规则只有一条不需要判断范围
                        Double deductScore = ruleVO.getCalcFraction();
                        ContractScore contractScore = getContractScore(ruleVO, deductScore, "1", Remark + "国标码为:" + serialNumber + "时间:" + format.format(date) + "存在大图不可用数据");
                        contractScoreList.add(contractScore);
                    }
                });
            }
            contractScoreService.saveBatch(contractScoreList);
            log.info("结束抽查图片完整状态");
        }catch (NullPointerException e) {
            log.error("空指针异常,抽查图片完整状态失败: {}", e.getMessage(), e);
        } catch (DataAccessException e) {
            log.error("数据库访问异常,抽查图片完整状态失败: {}", e.getMessage(), e);
        } catch (IllegalArgumentException e) {
            log.error("参数不合法异常,抽查图片完整状态失败: {}", e.getMessage(), e);
        } catch (Exception e) {
            log.error("未知异常,抽查图片完整状态失败: ", e); // 注意这里使用逗号而不是+,可以打印完整堆栈
            // 如果需要可以抛出运行时异常
            throw new RuntimeException("抽查图片完整状态失败", e);
        }
    }
    /**
     * 不定期检查数据 扣除积分
     * 优云取录像
     */
    public void randomDeductVideo() {
        try{
        log.info("开始抽查录像完整状态");
        //这个月随机抽取一天
        Date date = getRandomDate();
//        Calendar instance = Calendar.getInstance();
//        // 设定年、月、日(注意月份从0开始,即0代表1月,11代表12月)
//        instance.set(2024, 10, 1);
//        Date date = instance.getTime();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        //准备批量打分的集合
        List<ContractScore> contractScoreList = new ArrayList<>();
        //查询报备列表
        List<String> reportNumbers = reportMapper.selectNumberList(AuditStatus_Pass, DateUtils.getDate());
        //查图片完整性规则 获取key为合同id,value为规则的map
        Map<Integer, List<CalculateRuleVO>> contractMap = contractMapper.getCalculateRule(new Date()).stream()
                .filter(calculateRuleVO -> ContractRule.CONTRACT_RULE_VideoRecord.getName().equals(calculateRuleVO.getRuleName()))
                .collect(Collectors.groupingBy(CalculateRuleVO::getContractId));
        //取录像数据
        Query query = new Query(Criteria
                .where("mongoCreateTime").gte(DateUtils.getDayStart(date)).lt(DateUtils.getDayEnd(date)));
        List<RecordMetaDSumResult> recordMetaDSumResults = mongoTemplate.find(query, RecordMetaDSumResult.class);
        List<String> serialNumbers = recordMetaDSumResults.stream().map(RecordMetaDSumResult::getDeviceId).collect(Collectors.toList());
        QueryWrapper<YwPoint> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("serial_number", serialNumbers);
        //获取公司所运维的设备集合,key为unitId value为设备国标码集合
        Map<Long, List<String>> unitMonitorMap = ywPointMapper.selectList(queryWrapper).stream()
                .filter(ywPoint -> ywPoint.getUnitId() != null).collect(Collectors.groupingBy(YwPoint::getUnitId,
                        Collectors.mapping(
                                YwPoint::getSerialNumber,
                                Collectors.toList())));
        if (!CollectionUtils.isEmpty(contractMap)) {
            contractMap.forEach((contractId, ruleList) -> {
                //此规则对应的unitId均相等
                CalculateRuleVO ruleVO = ruleList.get(0);
                Integer unitId = ruleVO.getUnitId();
                List<String> monitorList = unitMonitorMap.get(Long.parseLong(unitId + ""));
                for (RecordMetaDSumResult result : recordMetaDSumResults) {
                    //判断是否报备过
                    if (!CollectionUtils.isEmpty(reportNumbers)) {
                        if (reportNumbers.contains(result.getDeviceId())) continue;
                    }
                    //判断是否是该公司运维
                    if (monitorList.contains(result.getDeviceId())) {
                        //录像状态不完整
                        if (!Objects.equals(result.getRecordStatus(), ApiConstants.UY_RecordStatus_Integrity)) {
                            for (CalculateRuleVO calculateRuleVO : ruleList) {
                                Double max = calculateRuleVO.getMax();
                                Double min = calculateRuleVO.getMin();
                                //判断范围在哪个区间 单位是小时转换为分钟
                                if (checkRange(min, max, BigDecimal.valueOf(result.getMissDuration() * 60))) {
                                    if (calculateRuleVO.getNum() == null) {
                                        calculateRuleVO.setNum(1);
                                    } else {
                                        calculateRuleVO.setNum(calculateRuleVO.getNum() + 1);
                                    }
                                }
                            }
                        }
                    }
                }
                for (CalculateRuleVO calculateRuleVO : ruleList) {
                    if (calculateRuleVO.getNum() != null && calculateRuleVO.getNum() > 0) {
                        //需要扣除的分数,此规则只有一条不需要判断范围
                        double deductScore = calculateRuleVO.getCalcFraction() * calculateRuleVO.getNum();
                        ContractScore contractScore = getContractScore(calculateRuleVO, deductScore, calculateRuleVO.getNum() + "", Remark +"抽查时间"+format.format(date)+"存在"+ calculateRuleVO.getNum() + "路设备违反规则");
                        contractScoreList.add(contractScore);
                    }
                }
            });
        }
        contractScoreService.saveBatch(contractScoreList);
    }catch (NullPointerException e) {
            log.error("空指针异常,抽查图片完整状态失败: {}", e.getMessage(), e);
        } catch (DataAccessException e) {
            log.error("数据库访问异常,抽查图片完整状态失败: {}", e.getMessage(), e);
        } catch (IllegalArgumentException e) {
            log.error("参数不合法异常,抽查图片完整状态失败: {}", e.getMessage(), e);
        } catch (Exception e) {
            log.error("未知异常,抽查图片完整状态失败: ", e); // 注意这里使用逗号而不是+,可以打印完整堆栈
            // 如果需要可以抛出运行时异常
            throw new RuntimeException("抽查图片完整状态失败", e);
        }
    }
    private Date getRandomDate() {
        // 获取当前日历实例
        Calendar calendar = Calendar.getInstance();
        // 获取当前日期
        Date now = calendar.getTime();
        // 设置为当前月份的第一天
        calendar.setTime(now);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        Date firstDayOfMonth = calendar.getTime();
        // 设置为当前月份的最后一天(下个月的第一天减一)
        calendar.add(Calendar.MONTH, 1);
        calendar.set(Calendar.DAY_OF_MONTH, 0); // Calendar.DAY_OF_MONTH设为0表示上个月最后一天,但这里是为了得到本月最后一天,所以先加一月
        Date lastDayOfMonth = calendar.getTime();
        log.info("最后一天" + lastDayOfMonth);
        // 计算天数差(加1因为包含起始和结束日期)
        long daysBetween = (lastDayOfMonth.getTime() - firstDayOfMonth.getTime()) / (1000 * 60 * 60 * 24) + 1;
        // 生成随机数
        Random random = new Random();
        int randomDayIndex = random.nextInt((int) daysBetween);
        // 获取随机日期
        calendar.setTime(firstDayOfMonth);
        calendar.add(Calendar.DAY_OF_MONTH, randomDayIndex);
        Date randomDate = calendar.getTime();
        return randomDate;
    }
    private boolean checkRange(Double min, Double max, BigDecimal index) {
        if (index == null) {
            return false;
        }
        if (max == null && min == null) {
            return false;
        }
        if (max != null && index.setScale(0, RoundingMode.DOWN).compareTo(new BigDecimal(max)) > 0) {
            return false;
        }
        if (min != null && index.setScale(0, RoundingMode.UP).compareTo(new BigDecimal(min)) < 0) {
            return false;
        }
        return true;
    }
    private ContractScore getContractScore(CalculateRuleVO rule, double deductScore, String num, String remark) {
        ContractScore contractScore = new ContractScore();
        contractScore.setContractId(Long.parseLong(rule.getContractId() + ""));
        contractScore.setAuditingStatus(AuditingStatus.PASS);
        contractScore.setAuditingTime(new Date());
        contractScore.setAuditingUser(Remark);
        contractScore.setUnitId(Long.parseLong(rule.getUnitId() + ""));
        contractScore.setRuleId(Long.parseLong(rule.getId() + ""));
        contractScore.setRuleIds("0," + rule.getId());
        contractScore.setNum(num);
        contractScore.setDeductCategory(rule.getDeductCategory().getDesc());
        contractScore.setScore(new BigDecimal(deductScore));
        contractScore.setRuleName(rule.getRuleName() + "/" + rule.getRuleCondition());
        contractScore.setCreateTime(new Date());
        contractScore.setUpdateTime(new Date());
        contractScore.setRemark(remark);
        return contractScore;
    }
}