|
@@ -1,18 +1,26 @@
|
|
|
package com.ufo.project.forecast.fileupload.task;
|
|
package com.ufo.project.forecast.fileupload.task;
|
|
|
|
|
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.nio.file.Files;
|
|
|
|
|
+import java.time.Instant;
|
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDate;
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.LocalTime;
|
|
import java.time.LocalTime;
|
|
|
import java.time.ZoneId;
|
|
import java.time.ZoneId;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
import java.util.Collections;
|
|
|
import java.util.Comparator;
|
|
import java.util.Comparator;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
|
|
+import java.util.Locale;
|
|
|
|
|
|
|
|
import com.alibaba.fastjson2.JSON;
|
|
import com.alibaba.fastjson2.JSON;
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.scheduling.annotation.Scheduled;
|
|
import org.springframework.scheduling.annotation.Scheduled;
|
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
|
@@ -24,6 +32,10 @@ import com.ufo.project.config.service.IConfigStationService;
|
|
|
import com.ufo.project.forecast.fileupload.domain.DataPowerForecastReportBatch;
|
|
import com.ufo.project.forecast.fileupload.domain.DataPowerForecastReportBatch;
|
|
|
import com.ufo.project.forecast.fileupload.service.IDataPowerForecastReportBatchService;
|
|
import com.ufo.project.forecast.fileupload.service.IDataPowerForecastReportBatchService;
|
|
|
import com.ufo.project.forecast.processor.ForecastProcessorFactory;
|
|
import com.ufo.project.forecast.processor.ForecastProcessorFactory;
|
|
|
|
|
+import com.ufo.project.forecast.shortterm.power.domain.DataShortTermPowerForecastReport;
|
|
|
|
|
+import com.ufo.project.forecast.shortterm.power.mapper.DataShortTermPowerForecastReportMapper;
|
|
|
|
|
+import com.ufo.project.forecast.supershort.domain.DataSuperShortTermForecastReport;
|
|
|
|
|
+import com.ufo.project.forecast.supershort.mapper.DataSuperShortTermForecastReportMapper;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 功率预测上报 - 批次生成定时任务
|
|
* 功率预测上报 - 批次生成定时任务
|
|
@@ -60,6 +72,12 @@ public class DataPowerForecastReportBatchTask
|
|
|
|
|
|
|
|
private static final DateTimeFormatter FMT_BATCH_NO = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
|
private static final DateTimeFormatter FMT_BATCH_NO = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
|
|
|
|
|
|
|
|
|
+ /** 数据行时间格式(北京时间) */
|
|
|
|
|
+ private static final DateTimeFormatter FMT_POINT_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZONE_SH);
|
|
|
|
|
+
|
|
|
|
|
+ /** 日期格式(yyyy-MM-dd) */
|
|
|
|
|
+ private static final DateTimeFormatter FMT_DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
|
|
+
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private ISysDictTypeService dictTypeService;
|
|
private ISysDictTypeService dictTypeService;
|
|
|
|
|
|
|
@@ -72,6 +90,15 @@ public class DataPowerForecastReportBatchTask
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private ForecastProcessorFactory factory;
|
|
private ForecastProcessorFactory factory;
|
|
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private DataShortTermPowerForecastReportMapper shortTermReportMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private DataSuperShortTermForecastReportMapper superShortTermReportMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Value("${project.forecast.reportfile.rootfolder}")
|
|
|
|
|
+ private String reportFileRootFolder;
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 扫描字典配置, 为窗口内的预计上报时点生成批次行并触发数据初始化
|
|
* 扫描字典配置, 为窗口内的预计上报时点生成批次行并触发数据初始化
|
|
|
*/
|
|
*/
|
|
@@ -97,6 +124,187 @@ public class DataPowerForecastReportBatchTask
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 状态推进: 2已确认 -> 4文件上传中 (每分钟第23秒)
|
|
|
|
|
+ */
|
|
|
|
|
+ @Scheduled(cron = "23 * * * * *")
|
|
|
|
|
+ public void startingFileGenerating()
|
|
|
|
|
+ {
|
|
|
|
|
+ DataPowerForecastReportBatch q = new DataPowerForecastReportBatch();
|
|
|
|
|
+ q.setStatus(DataPowerForecastReportBatch.STATUS_CONFIRMED);
|
|
|
|
|
+ List<DataPowerForecastReportBatch> batches = reportBatchService.selectDataPowerForecastReportBatchList(q);
|
|
|
|
|
+ if (batches == null || batches.isEmpty())
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (DataPowerForecastReportBatch b : batches)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ DataPowerForecastReportBatch u = new DataPowerForecastReportBatch();
|
|
|
|
|
+ u.setId(b.getId());
|
|
|
|
|
+ u.setStatus(DataPowerForecastReportBatch.STATUS_FILE_UPLOADING);
|
|
|
|
|
+ reportBatchService.updateDataPowerForecastReportBatch(u);
|
|
|
|
|
+ log.info("[PowerForecastReportBatch] 状态 2->4, batchNo={}", b.getBatchNo());
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception e)
|
|
|
|
|
+ {
|
|
|
|
|
+ log.error("[PowerForecastReportBatch] 状态 2->4 失败, batchNo={}", b.getBatchNo(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 生成上报文件: 4文件上传中 -> 5文件上传成功 / 9文件上传失败 (每分钟第45秒)
|
|
|
|
|
+ */
|
|
|
|
|
+ @Scheduled(cron = "45 * * * * *")
|
|
|
|
|
+ public void generateReportFile()
|
|
|
|
|
+ {
|
|
|
|
|
+ DataPowerForecastReportBatch q = new DataPowerForecastReportBatch();
|
|
|
|
|
+ q.setStatus(DataPowerForecastReportBatch.STATUS_FILE_UPLOADING);
|
|
|
|
|
+ List<DataPowerForecastReportBatch> batches = reportBatchService.selectDataPowerForecastReportBatchList(q);
|
|
|
|
|
+ if (batches == null || batches.isEmpty())
|
|
|
|
|
+ {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (DataPowerForecastReportBatch b : batches)
|
|
|
|
|
+ {
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ File file = buildReportFile(b);
|
|
|
|
|
+ DataPowerForecastReportBatch u = new DataPowerForecastReportBatch();
|
|
|
|
|
+ u.setId(b.getId());
|
|
|
|
|
+ u.setFile(file.getAbsolutePath());
|
|
|
|
|
+ u.setStatus(DataPowerForecastReportBatch.STATUS_FILE_UPLOAD_SUCCESS);
|
|
|
|
|
+ reportBatchService.updateDataPowerForecastReportBatch(u);
|
|
|
|
|
+ log.info("[PowerForecastReportBatch] 上报文件生成成功, batchNo={}, file={}",
|
|
|
|
|
+ b.getBatchNo(), file.getAbsolutePath());
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception e)
|
|
|
|
|
+ {
|
|
|
|
|
+ log.error("[PowerForecastReportBatch] 上报文件生成失败, batchNo={}", b.getBatchNo(), e);
|
|
|
|
|
+ DataPowerForecastReportBatch u = new DataPowerForecastReportBatch();
|
|
|
|
|
+ u.setId(b.getId());
|
|
|
|
|
+ u.setStatus(DataPowerForecastReportBatch.STATUS_FILE_UPLOAD_FAILED);
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ reportBatchService.updateDataPowerForecastReportBatch(u);
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (Exception ex)
|
|
|
|
|
+ {
|
|
|
|
|
+ log.error("[PowerForecastReportBatch] 状态置9失败, batchNo={}", b.getBatchNo(), ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 生成 .dat 上报文件并返回文件对象(异常向上抛, 由调用方置状态9)
|
|
|
|
|
+ */
|
|
|
|
|
+ private File buildReportFile(DataPowerForecastReportBatch batch) throws IOException
|
|
|
|
|
+ {
|
|
|
|
|
+ List<ReportPoint> points = loadReportPoints(batch);
|
|
|
|
|
+ if (points.isEmpty())
|
|
|
|
|
+ {
|
|
|
|
|
+ throw new IllegalStateException("批次无上报数据, batchNo=" + batch.getBatchNo());
|
|
|
|
|
+ }
|
|
|
|
|
+ ConfigStation sq = new ConfigStation();
|
|
|
|
|
+ sq.setStationCode(batch.getStationCode());
|
|
|
|
|
+ List<ConfigStation> stations = configStationService.selectConfigStationList(sq);
|
|
|
|
|
+ if (stations == null || stations.isEmpty())
|
|
|
|
|
+ {
|
|
|
|
|
+ throw new IllegalStateException("未找到场站, stationCode=" + batch.getStationCode());
|
|
|
|
|
+ }
|
|
|
|
|
+ Long capacity = stations.get(0).getInstalledCapacity();
|
|
|
|
|
+ if (capacity == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ throw new IllegalStateException("场站装机容量为空, stationCode=" + batch.getStationCode());
|
|
|
|
|
+ }
|
|
|
|
|
+ String marker = DataPowerForecastReportBatch.TYPE_ULTRA.equals(batch.getType()) ? "CDQYC" : "DQYC";
|
|
|
|
|
+ String fileName = "GF_" + batch.getStationName() + "_" + marker + "_" + batch.getReportDay()
|
|
|
|
|
+ + "_" + batch.getEstReportTime().replace(":", "") + ".dat";
|
|
|
|
|
+ int reportDay = batch.getReportDay();
|
|
|
|
|
+ String year = String.valueOf(reportDay / 10000);
|
|
|
|
|
+ String month = String.format(Locale.ROOT, "%02d", (reportDay / 100) % 100);
|
|
|
|
|
+ File dir = new File(reportFileRootFolder,
|
|
|
|
|
+ batch.getType() + File.separator + batch.getStationCode()
|
|
|
|
|
+ + File.separator + year + File.separator + month);
|
|
|
|
|
+ if (!dir.exists() && !dir.mkdirs())
|
|
|
|
|
+ {
|
|
|
|
|
+ throw new IOException("创建目录失败: " + dir.getAbsolutePath());
|
|
|
|
|
+ }
|
|
|
|
|
+ File target = new File(dir, fileName);
|
|
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
|
|
+ sb.append("<! System=PowerForecast, Version=1.0, Code=UTF-8, Data=").append(batch.getReportDay()).append(" !>\n");
|
|
|
|
|
+ sb.append('<').append(marker).append("::").append(batch.getStationName()).append(">\n");
|
|
|
|
|
+ sb.append("Date=").append(LocalDate.parse(String.valueOf(batch.getReportDay()), FMT_REPORT_DAY).format(FMT_DATE))
|
|
|
|
|
+ .append(", Time=").append(batch.getEstReportTime().replace(":", "-")).append('\n');
|
|
|
|
|
+ sb.append("# 点号\t时间\t预测功率(MW)\t预计开机容量(MW)\t备注\n");
|
|
|
|
|
+ String capacityMw = String.format(Locale.ROOT, "%.2f", capacity / 1000.0);
|
|
|
|
|
+ for (int i = 0; i < points.size(); i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ ReportPoint p = points.get(i);
|
|
|
|
|
+ sb.append(i + 1).append('\t')
|
|
|
|
|
+ .append(FMT_POINT_TIME.format(Instant.ofEpochSecond(p.forecastTime))).append('\t')
|
|
|
|
|
+ .append(String.format(Locale.ROOT, "%.2f", p.power / 1000.0)).append('\t')
|
|
|
|
|
+ .append(capacityMw).append('\t')
|
|
|
|
|
+ .append("正常").append('\n');
|
|
|
|
|
+ }
|
|
|
|
|
+ sb.append("</").append(marker).append("::").append(batch.getStationName()).append(">\n");
|
|
|
|
|
+ Files.write(target.toPath(), sb.toString().getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ return target;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按批次号加载上报数据点, 按预测时刻升序(power 为 kW 原始值)
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<ReportPoint> loadReportPoints(DataPowerForecastReportBatch batch)
|
|
|
|
|
+ {
|
|
|
|
|
+ List<ReportPoint> points = new ArrayList<>();
|
|
|
|
|
+ if (DataPowerForecastReportBatch.TYPE_ULTRA.equals(batch.getType()))
|
|
|
|
|
+ {
|
|
|
|
|
+ DataSuperShortTermForecastReport q = new DataSuperShortTermForecastReport();
|
|
|
|
|
+ q.setBatchNo(batch.getBatchNo());
|
|
|
|
|
+ for (DataSuperShortTermForecastReport r : superShortTermReportMapper.selectDataSuperShortTermForecastReportList(q))
|
|
|
|
|
+ {
|
|
|
|
|
+ if (r.getForecastTime() != null && StringUtils.isNotBlank(r.getPower()))
|
|
|
|
|
+ {
|
|
|
|
|
+ points.add(new ReportPoint(r.getForecastTime(), Double.parseDouble(r.getPower())));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ DataShortTermPowerForecastReport q = new DataShortTermPowerForecastReport();
|
|
|
|
|
+ q.setBatchNo(batch.getBatchNo());
|
|
|
|
|
+ for (DataShortTermPowerForecastReport r : shortTermReportMapper.selectDataShortTermPowerForecastReportList(q))
|
|
|
|
|
+ {
|
|
|
|
|
+ if (r.getForecastTime() != null && StringUtils.isNotBlank(r.getPower()))
|
|
|
|
|
+ {
|
|
|
|
|
+ points.add(new ReportPoint(r.getForecastTime(), Double.parseDouble(r.getPower())));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ points.sort(Comparator.comparingLong(p -> p.forecastTime));
|
|
|
|
|
+ return points;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 上报数据点: 预测时刻(UTC秒) + 功率(kW)
|
|
|
|
|
+ */
|
|
|
|
|
+ private static class ReportPoint
|
|
|
|
|
+ {
|
|
|
|
|
+ private final long forecastTime;
|
|
|
|
|
+
|
|
|
|
|
+ private final double power;
|
|
|
|
|
+
|
|
|
|
|
+ ReportPoint(long forecastTime, double power)
|
|
|
|
|
+ {
|
|
|
|
|
+ this.forecastTime = forecastTime;
|
|
|
|
|
+ this.power = power;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 读取字典配置, 解析为场站配置列表
|
|
* 读取字典配置, 解析为场站配置列表
|
|
|
*/
|
|
*/
|