|
|
@@ -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;
|
|
|
+ }
|
|
|
}
|