Browse Source

[26110501] 光伏上传率统计Mapper聚合SQL与Service: 天xtype成功计数(上海时区分天/只统计status=1/左闭右开含截止日全天), 比率=成功数/字典numx100保留2位, 无记录天0.00, 手动分页窗口, 字典fail fast

taosj 17 giờ trước cách đây
mục cha
commit
0fa94686cc

+ 22 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/fileupload/mapper/DataReportLogMapper.java

@@ -1,7 +1,11 @@
 package com.ufo.project.forecast.fileupload.mapper;
 
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.ufo.project.forecast.fileupload.domain.DataReportLog;
+import com.ufo.project.forecast.fileupload.domain.vo.UploadRateCountRow;
 
 /**
  * 功率预测上报日志Mapper接口(MyBatis-Plus, 无需Mapper XML)
@@ -11,4 +15,22 @@ import com.ufo.project.forecast.fileupload.domain.DataReportLog;
  */
 public interface DataReportLogMapper extends BaseMapper<DataReportLog>
 {
+    /**
+     * 按"天×type"统计上报成功条数(只统计status=1, 上海时区分天, 时间范围左闭右开含截止日全天)
+     *
+     * @param stationCode 场站编码
+     * @param windowStart 窗口起始日 yyyy-MM-dd
+     * @param windowEnd 窗口截止日 yyyy-MM-dd
+     * @return statDate/type/cnt 聚合行
+     */
+    @Select("SELECT DATE(est_report_time AT TIME ZONE 'Asia/Shanghai') AS stat_date, "
+            + "type, COUNT(*) AS cnt "
+            + "FROM data_report_log "
+            + "WHERE station_code = #{stationCode} "
+            + "AND status = 1 "
+            + "AND est_report_time >= (#{windowStart}::timestamp AT TIME ZONE 'Asia/Shanghai') "
+            + "AND est_report_time < (#{windowEnd}::timestamp AT TIME ZONE 'Asia/Shanghai') + INTERVAL '1 day' "
+            + "GROUP BY 1, 2")
+    List<UploadRateCountRow> selectUploadRateCounts(@Param("stationCode") String stationCode,
+            @Param("windowStart") String windowStart, @Param("windowEnd") String windowEnd);
 }

+ 17 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/fileupload/service/IDataReportLogService.java

@@ -2,6 +2,8 @@ package com.ufo.project.forecast.fileupload.service;
 
 import java.util.List;
 import com.ufo.project.forecast.fileupload.domain.DataReportLog;
+import com.ufo.project.forecast.fileupload.domain.vo.UploadRateStatQuery;
+import com.ufo.project.forecast.fileupload.domain.vo.UploadRateStatVO;
 
 /**
  * 功率预测上报日志Service接口
@@ -26,4 +28,19 @@ public interface IDataReportLogService
      * @return 功率预测上报日志集合
      */
     public List<DataReportLog> selectDataReportLogList(DataReportLog dataReportLog);
+
+    /**
+     * 光伏上传率统计: 日期范围天数(手动分页的total)
+     */
+    public long uploadRateTotalDays(UploadRateStatQuery query);
+
+    /**
+     * 光伏上传率统计: 查询当前页(每页每天一行, 无记录天为0.00)
+     */
+    public List<UploadRateStatVO> uploadRatePageList(UploadRateStatQuery query);
+
+    /**
+     * 光伏上传率统计: 查询全量日期(导出用)
+     */
+    public List<UploadRateStatVO> uploadRateExportList(UploadRateStatQuery query);
 }

+ 187 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/fileupload/service/impl/DataReportLogServiceImpl.java

@@ -1,7 +1,12 @@
 package com.ufo.project.forecast.fileupload.service.impl;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.LocalDate;
 import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
@@ -10,11 +15,19 @@ import java.util.Map;
 import java.util.Set;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ufo.base.common.exception.ServiceException;
 import com.ufo.base.common.utils.StringUtils;
+import com.ufo.module.system.domain.SysDictData;
+import com.ufo.module.system.service.ISysDictTypeService;
 import com.ufo.project.config.domain.ConfigStation;
 import com.ufo.project.config.service.IConfigStationService;
 import com.ufo.project.forecast.fileupload.domain.DataReportLog;
+import com.ufo.project.forecast.fileupload.domain.vo.UploadRateCountRow;
+import com.ufo.project.forecast.fileupload.domain.vo.UploadRateStatQuery;
+import com.ufo.project.forecast.fileupload.domain.vo.UploadRateStatVO;
 import com.ufo.project.forecast.fileupload.mapper.DataReportLogMapper;
 import com.ufo.project.forecast.fileupload.service.IDataReportLogService;
 
@@ -33,6 +46,17 @@ public class DataReportLogServiceImpl implements IDataReportLogService
     @Autowired
     private IConfigStationService configStationService;
 
+    /** 字典类型: 每日文件上传配置 */
+    private static final String DICT_TYPE = "daily_file_upload_config";
+
+    /** 9 类上报文件 type(与字典及VO字段顺序一致) */
+    private static final List<String> ALL_TYPES = Arrays.asList("supershort", "short", "medium",
+            "envlDetector", "inverterOperation", "boosterTransformer",
+            "theoreticalPower", "numericalWeather", "powerGenerationPlan");
+
+    @Autowired
+    private ISysDictTypeService dictTypeService;
+
     /**
      * 查询功率预测上报日志
      *
@@ -72,6 +96,42 @@ public class DataReportLogServiceImpl implements IDataReportLogService
         return list;
     }
 
+    @Override
+    public long uploadRateTotalDays(UploadRateStatQuery query)
+    {
+        LocalDate[] range = parseRange(query);
+        return ChronoUnit.DAYS.between(range[0], range[1]) + 1;
+    }
+
+    @Override
+    public List<UploadRateStatVO> uploadRatePageList(UploadRateStatQuery query)
+    {
+        LocalDate[] range = parseRange(query);
+        LocalDate start = range[0];
+        LocalDate end = range[1];
+        int pageNum = query.getPageNum() == null || query.getPageNum() < 1 ? 1 : query.getPageNum();
+        int pageSize = query.getPageSize() == null || query.getPageSize() < 1 ? 10 : query.getPageSize();
+        LocalDate windowStart = start.plusDays((long) (pageNum - 1) * pageSize);
+        if (windowStart.isAfter(end))
+        {
+            // 超出末页: 返回空
+            return new ArrayList<>();
+        }
+        LocalDate windowEnd = windowStart.plusDays(pageSize - 1L);
+        if (windowEnd.isAfter(end))
+        {
+            windowEnd = end;
+        }
+        return buildDayVos(windowStart, windowEnd, query.getStationCode());
+    }
+
+    @Override
+    public List<UploadRateStatVO> uploadRateExportList(UploadRateStatQuery query)
+    {
+        LocalDate[] range = parseRange(query);
+        return buildDayVos(range[0], range[1], query.getStationCode());
+    }
+
     /**
      * 按计划上报时间过滤单日(queryDate, yyyy-MM-dd)
      */
@@ -135,4 +195,131 @@ public class DataReportLogServiceImpl implements IDataReportLogService
             }
         }
     }
+
+    /**
+     * 查窗口内聚合并按天生成VO(无记录天全0.00)
+     */
+    private List<UploadRateStatVO> buildDayVos(LocalDate windowStart, LocalDate windowEnd, String stationCode)
+    {
+        Map<String, Integer> nums = loadNums();
+        List<UploadRateCountRow> rows = dataReportLogMapper.selectUploadRateCounts(
+                stationCode, windowStart.toString(), windowEnd.toString());
+        Map<String, Map<String, Long>> byDay = new HashMap<>();
+        for (UploadRateCountRow row : rows)
+        {
+            byDay.computeIfAbsent(row.getStatDate(), k -> new HashMap<>()).put(row.getType(), row.getCnt());
+        }
+        List<UploadRateStatVO> result = new ArrayList<>();
+        for (LocalDate day = windowStart; !day.isAfter(windowEnd); day = day.plusDays(1))
+        {
+            Map<String, Long> typeCnt = byDay.get(day.toString());
+            UploadRateStatVO vo = new UploadRateStatVO();
+            vo.setDate(day.toString());
+            vo.setSupershortRate(calcRate("supershort", typeCnt, nums));
+            vo.setShortRate(calcRate("short", typeCnt, nums));
+            vo.setMediumRate(calcRate("medium", typeCnt, nums));
+            vo.setEnvlDetectorRate(calcRate("envlDetector", typeCnt, nums));
+            vo.setInverterOperationRate(calcRate("inverterOperation", typeCnt, nums));
+            vo.setBoosterTransformerRate(calcRate("boosterTransformer", typeCnt, nums));
+            vo.setTheoreticalPowerRate(calcRate("theoreticalPower", typeCnt, nums));
+            vo.setNumericalWeatherRate(calcRate("numericalWeather", typeCnt, nums));
+            vo.setPowerGenerationPlanRate(calcRate("powerGenerationPlan", typeCnt, nums));
+            result.add(vo);
+        }
+        return result;
+    }
+
+    /**
+     * 比率 = 成功条数/num×100, 保留2位小数(HALF_UP); 无记录为0.0
+     */
+    private Double calcRate(String type, Map<String, Long> typeCnt, Map<String, Integer> nums)
+    {
+        long cnt = typeCnt == null ? 0L : typeCnt.getOrDefault(type, 0L);
+        if (cnt == 0)
+        {
+            return 0.0;
+        }
+        return new BigDecimal(cnt * 100).divide(new BigDecimal(nums.get(type)), 2, RoundingMode.HALF_UP)
+                .doubleValue();
+    }
+
+    /**
+     * 参数校验: stationCode/startTime/endTime非空、格式yyyy-MM-dd、start<=end
+     */
+    private LocalDate[] parseRange(UploadRateStatQuery query)
+    {
+        if (StringUtils.isEmpty(query.getStationCode()))
+        {
+            throw new ServiceException("stationCode不能为空");
+        }
+        LocalDate start = parseDay(query.getStartTime(), "起始时间");
+        LocalDate end = parseDay(query.getEndTime(), "截止时间");
+        if (start.isAfter(end))
+        {
+            throw new ServiceException("起始时间不能晚于截止时间");
+        }
+        return new LocalDate[]{start, end};
+    }
+
+    private LocalDate parseDay(String value, String label)
+    {
+        if (StringUtils.isEmpty(value))
+        {
+            throw new ServiceException(label + "不能为空");
+        }
+        try
+        {
+            return LocalDate.parse(value);
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException(label + "格式不正确, 应为yyyy-MM-dd: " + value);
+        }
+    }
+
+    /**
+     * 读字典 daily_file_upload_config → type→num; 未配置/JSON非法/缺项/num非法时fail fast
+     */
+    private Map<String, Integer> loadNums()
+    {
+        List<SysDictData> dictDatas = dictTypeService.selectDictDataByType(DICT_TYPE);
+        if (dictDatas == null || dictDatas.isEmpty())
+        {
+            throw new ServiceException("字典" + DICT_TYPE + "未配置, 请先在字典管理中添加每日文件上传配置");
+        }
+        Map<String, Integer> nums = new HashMap<>();
+        for (SysDictData dict : dictDatas)
+        {
+            String dictValue = dict.getDictValue();
+            try
+            {
+                JSONObject obj = JSON.parseObject(dictValue);
+                String type = obj == null ? null : obj.getString("type");
+                Integer num = obj == null ? null : obj.getInteger("num");
+                if (StringUtils.isEmpty(type) || num == null || num <= 0)
+                {
+                    throw new ServiceException("字典" + DICT_TYPE + "数据[" + dict.getDictLabel()
+                            + "]缺少type或num不合法: " + dictValue);
+                }
+                nums.put(type, num);
+            }
+            catch (ServiceException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new ServiceException("字典" + DICT_TYPE + "数据[" + dict.getDictLabel()
+                        + "]不是合法JSON: " + dictValue);
+            }
+        }
+        for (String type : ALL_TYPES)
+        {
+            if (!nums.containsKey(type))
+            {
+                throw new ServiceException("字典" + DICT_TYPE + "缺少" + type + "的配置");
+            }
+        }
+        return nums;
+    }
 }