|
|
@@ -2,12 +2,14 @@ package com.ufo.project.data.service.impl;
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
|
+import java.time.Instant;
|
|
|
import java.time.LocalDate;
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.YearMonth;
|
|
|
import java.time.ZoneId;
|
|
|
-import java.time.ZoneOffset;
|
|
|
+import java.time.ZonedDateTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
+import java.time.format.DateTimeParseException;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
|
import java.util.LinkedHashMap;
|
|
|
@@ -17,14 +19,11 @@ import java.util.Map.Entry;
|
|
|
import java.util.stream.Collectors;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
-import com.ufo.base.common.utils.StringUtils;
|
|
|
import com.ufo.project.data.domain.vo.UltraShortTermPowerForecastReportQuery;
|
|
|
import com.ufo.project.data.domain.vo.UltraShortTermPowerForecastReportVO;
|
|
|
import com.ufo.project.data.service.IUltraShortTermPowerForecastReportService;
|
|
|
-import com.ufo.project.forecast.supershort.domain.DataSuperShortTermForecastIssued;
|
|
|
-import com.ufo.project.forecast.supershort.domain.DataUltraShortTermPowerForecasting;
|
|
|
-import com.ufo.project.forecast.supershort.service.IDataSuperShortTermForecastIssuedService;
|
|
|
-import com.ufo.project.forecast.supershort.service.IDataUltraShortTermPowerForecastingService;
|
|
|
+import com.ufo.project.forecast.shortterm.power.domain.ForecastPowerAverage;
|
|
|
+import com.ufo.project.forecast.supershort.service.IDataSuperShortTermForecastReportService;
|
|
|
import com.ufo.project.model.domain.DataElectricityLocal;
|
|
|
import com.ufo.project.model.service.IDataElectricityLocalService;
|
|
|
|
|
|
@@ -39,22 +38,17 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
{
|
|
|
private static final int SCALE = 3;
|
|
|
private static final long SECONDS_PER_DAY = 86400L;
|
|
|
- private static final long SECONDS_PER_HOUR = 3600L;
|
|
|
- private static final long SECONDS_PER_4H = SECONDS_PER_HOUR * 4L;
|
|
|
|
|
|
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
private static final DateTimeFormatter MONTH_FMT = DateTimeFormatter.ofPattern("yyyy-MM");
|
|
|
private static final ZoneId ZONE_SHANGHAI = ZoneId.of("Asia/Shanghai");
|
|
|
- private static final ZoneOffset ZONE_8 = ZoneOffset.ofHours(8);
|
|
|
+ private static final DateTimeFormatter DATE_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
|
@Autowired
|
|
|
private IDataElectricityLocalService electricityLocalService;
|
|
|
|
|
|
@Autowired
|
|
|
- private IDataSuperShortTermForecastIssuedService superShortService;
|
|
|
-
|
|
|
- @Autowired
|
|
|
- private IDataUltraShortTermPowerForecastingService ultraForecastService;
|
|
|
+ private IDataSuperShortTermForecastReportService superShortService;
|
|
|
|
|
|
/**
|
|
|
* 超短期预测报表查询
|
|
|
@@ -78,52 +72,56 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
}
|
|
|
|
|
|
// 2. 获取超短期预测功率数据
|
|
|
- Long startSec = toUnixSeconds(startStr);
|
|
|
- Long endSec = toUnixSeconds(endStr);
|
|
|
- List<DataSuperShortTermForecastIssued> forecastList = selectSuperShortByTimeRange(entityId, startSec, endSec);
|
|
|
-
|
|
|
- // 3. 获取超短期预报数据(提前4h)
|
|
|
- Long startSec4h = startSec - SECONDS_PER_4H;
|
|
|
- List<DataUltraShortTermPowerForecasting> ultraForecastList = ultraForecastService
|
|
|
- .selectByTimeRange(entityId, startSec4h, endSec);
|
|
|
+ long utcStartTime = parseLong(startStr);
|
|
|
+ long utcEndTime = parseLong(endStr);
|
|
|
+ List<ForecastPowerAverage> forecastList = superShortService.selectAveragePowerByTimeRange(entityId, utcStartTime, utcEndTime);
|
|
|
+ this.dataForecastList2Gmt8(forecastList);
|
|
|
|
|
|
// 4. 按基准类型分组
|
|
|
switch (type)
|
|
|
{
|
|
|
case "daily":
|
|
|
- return buildDailyReport(actualList, forecastList, ultraForecastList);
|
|
|
+ return buildDailyReport(actualList, forecastList);
|
|
|
case "monthly":
|
|
|
- return buildMonthlyReport(actualList, forecastList, ultraForecastList);
|
|
|
+ return buildMonthlyReport(actualList, forecastList);
|
|
|
case "yearly":
|
|
|
- return buildYearlyReport(actualList, forecastList, ultraForecastList);
|
|
|
+ return buildYearlyReport(actualList, forecastList);
|
|
|
default:
|
|
|
return Collections.emptyList();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private void dataForecastList2Gmt8(List<ForecastPowerAverage> forecastList)
|
|
|
+ {
|
|
|
+ for (ForecastPowerAverage record : forecastList)
|
|
|
+ {
|
|
|
+ ZonedDateTime forecastZdt = ZonedDateTime.ofInstant(Instant.ofEpochSecond(record.getForecastTime()),
|
|
|
+ ZoneId.systemDefault());
|
|
|
+ ZonedDateTime forecastGmt8 = forecastZdt.withZoneSameInstant(ZONE_SHANGHAI);
|
|
|
+ record.setForecastTime(forecastGmt8.toEpochSecond());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 构建日报
|
|
|
*/
|
|
|
private List<UltraShortTermPowerForecastReportVO> buildDailyReport(
|
|
|
List<DataElectricityLocal> actualList,
|
|
|
- List<DataSuperShortTermForecastIssued> forecastList,
|
|
|
- List<DataUltraShortTermPowerForecasting> ultraForecastList)
|
|
|
+ List<ForecastPowerAverage> forecastList)
|
|
|
{
|
|
|
Map<String, List<DataElectricityLocal>> dailyMap = groupByDay(actualList);
|
|
|
- Map<String, List<DataSuperShortTermForecastIssued>> forecastDailyMap = groupForecastByDay(forecastList);
|
|
|
- Map<Long, List<DataUltraShortTermPowerForecasting>> ultraForecastMap = groupUltraForecastByStartSec(ultraForecastList);
|
|
|
+ Map<String, List<ForecastPowerAverage>> forecastDailyMap = groupForecastByDay(forecastList);
|
|
|
|
|
|
List<UltraShortTermPowerForecastReportVO> result = new ArrayList<>();
|
|
|
for (Entry<String, List<DataElectricityLocal>> entry : dailyMap.entrySet())
|
|
|
{
|
|
|
String day = entry.getKey();
|
|
|
List<DataElectricityLocal> records = entry.getValue();
|
|
|
- List<DataSuperShortTermForecastIssued> forecastRecords = forecastDailyMap.getOrDefault(day, Collections.emptyList());
|
|
|
- List<DataUltraShortTermPowerForecasting> ultraRecords = collectUltraForDay(day, ultraForecastMap);
|
|
|
+ List<ForecastPowerAverage> forecastRecords = forecastDailyMap.getOrDefault(day, Collections.emptyList());
|
|
|
|
|
|
UltraShortTermPowerForecastReportVO vo = new UltraShortTermPowerForecastReportVO();
|
|
|
vo.setTime(day);
|
|
|
- computeMetrics(vo, records, forecastRecords, ultraRecords);
|
|
|
+ computeMetrics(vo, records, forecastRecords);
|
|
|
result.add(vo);
|
|
|
}
|
|
|
return result;
|
|
|
@@ -134,25 +132,22 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
*/
|
|
|
private List<UltraShortTermPowerForecastReportVO> buildMonthlyReport(
|
|
|
List<DataElectricityLocal> actualList,
|
|
|
- List<DataSuperShortTermForecastIssued> forecastList,
|
|
|
- List<DataUltraShortTermPowerForecasting> ultraForecastList)
|
|
|
+ List<ForecastPowerAverage> forecastList)
|
|
|
{
|
|
|
Map<String, List<DataElectricityLocal>> monthlyMap = groupByMonth(actualList);
|
|
|
- Map<String, List<DataSuperShortTermForecastIssued>> forecastMonthlyMap = groupForecastByMonth(forecastList);
|
|
|
- Map<Long, List<DataUltraShortTermPowerForecasting>> ultraForecastMap = groupUltraForecastByStartSec(ultraForecastList);
|
|
|
+ Map<String, List<ForecastPowerAverage>> forecastMonthlyMap = groupForecastByMonth(forecastList);
|
|
|
|
|
|
List<UltraShortTermPowerForecastReportVO> result = new ArrayList<>();
|
|
|
for (Entry<String, List<DataElectricityLocal>> entry : monthlyMap.entrySet())
|
|
|
{
|
|
|
String month = entry.getKey();
|
|
|
List<DataElectricityLocal> records = entry.getValue();
|
|
|
- List<DataSuperShortTermForecastIssued> forecastRecords =
|
|
|
+ List<ForecastPowerAverage> forecastRecords =
|
|
|
forecastMonthlyMap.getOrDefault(month, Collections.emptyList());
|
|
|
- List<DataUltraShortTermPowerForecasting> ultraRecords = collectUltraForMonth(month, ultraForecastMap);
|
|
|
|
|
|
UltraShortTermPowerForecastReportVO vo = new UltraShortTermPowerForecastReportVO();
|
|
|
vo.setTime(month);
|
|
|
- computeMetrics(vo, records, forecastRecords, ultraRecords);
|
|
|
+ computeMetrics(vo, records, forecastRecords);
|
|
|
result.add(vo);
|
|
|
}
|
|
|
return result;
|
|
|
@@ -163,25 +158,22 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
*/
|
|
|
private List<UltraShortTermPowerForecastReportVO> buildYearlyReport(
|
|
|
List<DataElectricityLocal> actualList,
|
|
|
- List<DataSuperShortTermForecastIssued> forecastList,
|
|
|
- List<DataUltraShortTermPowerForecasting> ultraForecastList)
|
|
|
+ List<ForecastPowerAverage> forecastList)
|
|
|
{
|
|
|
Map<String, List<DataElectricityLocal>> yearlyMap = groupByYear(actualList);
|
|
|
- Map<String, List<DataSuperShortTermForecastIssued>> forecastYearlyMap = groupForecastByYear(forecastList);
|
|
|
- Map<Long, List<DataUltraShortTermPowerForecasting>> ultraForecastMap = groupUltraForecastByStartSec(ultraForecastList);
|
|
|
+ Map<String, List<ForecastPowerAverage>> forecastYearlyMap = groupForecastByYear(forecastList);
|
|
|
|
|
|
List<UltraShortTermPowerForecastReportVO> result = new ArrayList<>();
|
|
|
for (Entry<String, List<DataElectricityLocal>> entry : yearlyMap.entrySet())
|
|
|
{
|
|
|
String year = entry.getKey();
|
|
|
List<DataElectricityLocal> records = entry.getValue();
|
|
|
- List<DataSuperShortTermForecastIssued> forecastRecords =
|
|
|
+ List<ForecastPowerAverage> forecastRecords =
|
|
|
forecastYearlyMap.getOrDefault(year, Collections.emptyList());
|
|
|
- List<DataUltraShortTermPowerForecasting> ultraRecords = collectUltraForYear(year, ultraForecastMap);
|
|
|
|
|
|
UltraShortTermPowerForecastReportVO vo = new UltraShortTermPowerForecastReportVO();
|
|
|
vo.setTime(year);
|
|
|
- computeMetrics(vo, records, forecastRecords, ultraRecords);
|
|
|
+ computeMetrics(vo, records, forecastRecords);
|
|
|
result.add(vo);
|
|
|
}
|
|
|
return result;
|
|
|
@@ -192,8 +184,7 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
*/
|
|
|
private void computeMetrics(UltraShortTermPowerForecastReportVO vo,
|
|
|
List<DataElectricityLocal> actualRecords,
|
|
|
- List<DataSuperShortTermForecastIssued> forecastRecords,
|
|
|
- List<DataUltraShortTermPowerForecasting> ultraRecords)
|
|
|
+ List<ForecastPowerAverage> forecastRecords)
|
|
|
{
|
|
|
// 1. 开机容量
|
|
|
BigDecimal operatingCapacity = computeOperatingCapacity(actualRecords);
|
|
|
@@ -207,21 +198,22 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
BigDecimal forecastPower = computeForecastPower(forecastRecords);
|
|
|
vo.setForecastPower(forecastPower);
|
|
|
|
|
|
+ Map<Long, BigDecimal> forecastMap = buildForecastMap(forecastRecords);
|
|
|
// 4. RMSE
|
|
|
- BigDecimal rmse = computeRmse(actualRecords, forecastRecords);
|
|
|
+ BigDecimal rmse = computeRmse(actualRecords, forecastRecords, forecastMap);
|
|
|
vo.setRootMeanSquareError(rmse);
|
|
|
|
|
|
// 5. MAE
|
|
|
- BigDecimal mae = computeMae(actualRecords, forecastRecords);
|
|
|
+ BigDecimal mae = computeMae(actualRecords, forecastRecords, forecastMap);
|
|
|
vo.setMeanAbsoluteError(mae);
|
|
|
|
|
|
// 6. 相关性系数
|
|
|
- BigDecimal corr = computeCorrelation(actualRecords, forecastRecords);
|
|
|
+ BigDecimal corr = computeCorrelation(actualRecords, forecastRecords, forecastMap);
|
|
|
vo.setCorrelationCoefficientR(corr);
|
|
|
|
|
|
- // 7. 4小时前预测准确率
|
|
|
- BigDecimal accuracy = computeAheadForecastAccuracy4h(actualRecords, ultraRecords, operatingCapacity);
|
|
|
- vo.setAheadForecastAccuracy4h(accuracy);
|
|
|
+ // 7. 准确率 (acc)
|
|
|
+ BigDecimal acc = computeAcc(actualRecords, forecastRecords, operatingCapacity);
|
|
|
+ vo.setAcc(acc);
|
|
|
}
|
|
|
|
|
|
// ==================== 基础计算 ====================
|
|
|
@@ -264,18 +256,18 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
return BigDecimal.valueOf(sum / count / 1000.0).setScale(SCALE, RoundingMode.HALF_UP);
|
|
|
}
|
|
|
|
|
|
- private BigDecimal computeForecastPower(List<DataSuperShortTermForecastIssued> records)
|
|
|
+ private BigDecimal computeForecastPower(List<ForecastPowerAverage> records)
|
|
|
{
|
|
|
- double sum = 0.0;
|
|
|
+ BigDecimal sum = BigDecimal.ZERO;
|
|
|
int count = 0;
|
|
|
- for (DataSuperShortTermForecastIssued r : records)
|
|
|
+ for (ForecastPowerAverage r : records)
|
|
|
{
|
|
|
- String power = r.getPower();
|
|
|
- if (StringUtils.isNotEmpty(power))
|
|
|
+ BigDecimal power = r.getAvgPower();
|
|
|
+ if (power != null)
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
- sum += Double.parseDouble(power.trim());
|
|
|
+ sum = sum.add(power);
|
|
|
count++;
|
|
|
}
|
|
|
catch (NumberFormatException e)
|
|
|
@@ -285,229 +277,178 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
}
|
|
|
}
|
|
|
if (count == 0) return BigDecimal.ZERO;
|
|
|
- return BigDecimal.valueOf(sum / count / 1000.0).setScale(SCALE, RoundingMode.HALF_UP);
|
|
|
+ BigDecimal avg = sum.divide(BigDecimal.valueOf(count), 10, RoundingMode.HALF_UP);
|
|
|
+ return avg.divide(new BigDecimal(1000), 3, RoundingMode.HALF_UP);
|
|
|
}
|
|
|
|
|
|
private BigDecimal computeRmse(List<DataElectricityLocal> actualRecords,
|
|
|
- List<DataSuperShortTermForecastIssued> forecastRecords)
|
|
|
+ List<ForecastPowerAverage> forecastRecords,
|
|
|
+ Map<Long, BigDecimal> forecastMap)
|
|
|
{
|
|
|
- Map<Long, Double> forecastMap = buildForecastMap(forecastRecords);
|
|
|
if (forecastMap.isEmpty()) return BigDecimal.ZERO;
|
|
|
|
|
|
- double sumSquaredError = 0.0;
|
|
|
+ BigDecimal sumSquaredError = BigDecimal.ZERO;
|
|
|
int count = 0;
|
|
|
|
|
|
for (DataElectricityLocal r : actualRecords)
|
|
|
{
|
|
|
if (r.getTm() == null) continue;
|
|
|
long ts = r.getTm().toInstant().atZone(ZONE_SHANGHAI).toEpochSecond();
|
|
|
- Double fp = forecastMap.get(ts);
|
|
|
+ BigDecimal fp = forecastMap.get(ts);
|
|
|
if (fp == null) continue;
|
|
|
|
|
|
- Double ap = r.getActualPower();
|
|
|
- if (ap == null) continue;
|
|
|
+ BigDecimal ap = new BigDecimal(r.getActualPower());
|
|
|
|
|
|
- double diffMW = (ap - fp) / 1000.0;
|
|
|
- sumSquaredError += diffMW * diffMW;
|
|
|
+ BigDecimal diffMW = ap.subtract(fp).divide(BigDecimal.valueOf(1000), 10, RoundingMode.HALF_UP);
|
|
|
+ sumSquaredError = sumSquaredError.add(diffMW.multiply(diffMW));
|
|
|
count++;
|
|
|
}
|
|
|
|
|
|
if (count == 0) return BigDecimal.ZERO;
|
|
|
- return BigDecimal.valueOf(Math.sqrt(sumSquaredError / count)).setScale(SCALE, RoundingMode.HALF_UP);
|
|
|
+ return BigDecimal.valueOf(Math.sqrt(sumSquaredError.divide(BigDecimal.valueOf(count), 10, RoundingMode.HALF_UP).doubleValue()));
|
|
|
}
|
|
|
|
|
|
private BigDecimal computeMae(List<DataElectricityLocal> actualRecords,
|
|
|
- List<DataSuperShortTermForecastIssued> forecastRecords)
|
|
|
+ List<ForecastPowerAverage> forecastRecords,
|
|
|
+ Map<Long, BigDecimal> forecastMap)
|
|
|
{
|
|
|
- Map<Long, Double> forecastMap = buildForecastMap(forecastRecords);
|
|
|
if (forecastMap.isEmpty()) return BigDecimal.ZERO;
|
|
|
|
|
|
- double sumAbsError = 0.0;
|
|
|
+ BigDecimal sumAbsError = BigDecimal.ZERO;
|
|
|
int count = 0;
|
|
|
|
|
|
for (DataElectricityLocal r : actualRecords)
|
|
|
{
|
|
|
if (r.getTm() == null) continue;
|
|
|
long ts = r.getTm().toInstant().atZone(ZONE_SHANGHAI).toEpochSecond();
|
|
|
- Double fp = forecastMap.get(ts);
|
|
|
+ BigDecimal fp = forecastMap.get(ts);
|
|
|
if (fp == null) continue;
|
|
|
|
|
|
- Double ap = r.getActualPower();
|
|
|
+ BigDecimal ap = BigDecimal.valueOf(r.getActualPower());
|
|
|
if (ap == null) continue;
|
|
|
|
|
|
- double absErrorMW = Math.abs(ap - fp) / 1000.0;
|
|
|
- sumAbsError += absErrorMW;
|
|
|
+ BigDecimal absErrorMW = ap.subtract(fp).divide(BigDecimal.valueOf(1000), 10, RoundingMode.HALF_UP);
|
|
|
+ sumAbsError = sumAbsError.add(absErrorMW);
|
|
|
count++;
|
|
|
}
|
|
|
|
|
|
if (count == 0) return BigDecimal.ZERO;
|
|
|
- return BigDecimal.valueOf(sumAbsError / count).setScale(SCALE, RoundingMode.HALF_UP);
|
|
|
+ return sumAbsError.divide(BigDecimal.valueOf(count), 10, RoundingMode.HALF_UP);
|
|
|
}
|
|
|
|
|
|
private BigDecimal computeCorrelation(List<DataElectricityLocal> actualRecords,
|
|
|
- List<DataSuperShortTermForecastIssued> forecastRecords)
|
|
|
+ List<ForecastPowerAverage> forecastRecords,
|
|
|
+ Map<Long, BigDecimal> forecastMap)
|
|
|
{
|
|
|
- Map<Long, Double> forecastMap = buildForecastMap(forecastRecords);
|
|
|
if (forecastMap.isEmpty()) return BigDecimal.ZERO;
|
|
|
|
|
|
- List<Double> xList = new ArrayList<>();
|
|
|
- List<Double> yList = new ArrayList<>();
|
|
|
+ List<BigDecimal> xList = new ArrayList<>();
|
|
|
+ List<BigDecimal> yList = new ArrayList<>();
|
|
|
|
|
|
for (DataElectricityLocal r : actualRecords)
|
|
|
{
|
|
|
if (r.getTm() == null) continue;
|
|
|
long ts = r.getTm().toInstant().atZone(ZONE_SHANGHAI).toEpochSecond();
|
|
|
- Double fp = forecastMap.get(ts);
|
|
|
+ BigDecimal fp = forecastMap.get(ts);
|
|
|
if (fp == null) continue;
|
|
|
|
|
|
- Double ap = r.getActualPower();
|
|
|
+ BigDecimal ap = BigDecimal.valueOf(r.getActualPower());
|
|
|
if (ap == null) continue;
|
|
|
|
|
|
- xList.add(ap / 1000.0);
|
|
|
- yList.add(fp / 1000.0);
|
|
|
+ xList.add(ap.divide(BigDecimal.valueOf(1000), 10, RoundingMode.HALF_UP));
|
|
|
+ yList.add(fp.divide(BigDecimal.valueOf(1000), 10, RoundingMode.HALF_UP));
|
|
|
}
|
|
|
|
|
|
int n = xList.size();
|
|
|
if (n < 2) return BigDecimal.ZERO;
|
|
|
|
|
|
- double meanX = xList.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
|
|
|
- double meanY = yList.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
|
|
|
+ BigDecimal meanX = xList.stream()
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add)
|
|
|
+ .divide(BigDecimal.valueOf(xList.size()), 10, RoundingMode.HALF_UP);
|
|
|
+ BigDecimal meanY = yList.stream()
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add)
|
|
|
+ .divide(BigDecimal.valueOf(yList.size()), 10, RoundingMode.HALF_UP);
|
|
|
|
|
|
- double numerator = 0.0;
|
|
|
- double sumSqX = 0.0;
|
|
|
- double sumSqY = 0.0;
|
|
|
+ BigDecimal numerator = BigDecimal.ZERO;
|
|
|
+ BigDecimal sumSqX = BigDecimal.ZERO;
|
|
|
+ BigDecimal sumSqY = BigDecimal.ZERO;
|
|
|
|
|
|
for (int i = 0; i < n; i++)
|
|
|
{
|
|
|
- double dx = xList.get(i) - meanX;
|
|
|
- double dy = yList.get(i) - meanY;
|
|
|
- numerator += dx * dy;
|
|
|
- sumSqX += dx * dx;
|
|
|
- sumSqY += dy * dy;
|
|
|
+ BigDecimal dx = xList.get(i).subtract(meanX);
|
|
|
+ BigDecimal dy = yList.get(i).subtract(meanY);
|
|
|
+ numerator = numerator.add(dx.multiply(dy));
|
|
|
+ sumSqX = sumSqX.add(dx.multiply(dx));
|
|
|
+ sumSqY = sumSqY.add(dy.multiply(dy));
|
|
|
}
|
|
|
|
|
|
- double denominator = Math.sqrt(sumSqX * sumSqY);
|
|
|
- if (denominator == 0.0) return BigDecimal.ZERO;
|
|
|
+ BigDecimal denominator = BigDecimal.valueOf(Math.sqrt(sumSqX.multiply(sumSqY).doubleValue()));
|
|
|
+ if (denominator.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO;
|
|
|
|
|
|
- double r = numerator / denominator;
|
|
|
- return BigDecimal.valueOf(r).setScale(SCALE, RoundingMode.HALF_UP);
|
|
|
+ BigDecimal r = numerator.divide(denominator, 10, RoundingMode.HALF_UP);
|
|
|
+ return r;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 计算4小时前预测准确率
|
|
|
- *
|
|
|
- * 对每个实际数据点,找到start_time匹配的超短期预报记录
|
|
|
- * 从该记录的power15~power240字段提取16个预测值,取算术平均得P_Pi
|
|
|
- * error_i = (P_Mi - P_Pi) / max(P_Mi, 0.2 * Cap)
|
|
|
- * ACC = 1 - sqrt((1/N) * sum(error_i^2))
|
|
|
+ * 计算准确率(acc)
|
|
|
+ * acc = (1 - 偏差值) × 100%
|
|
|
+ * 偏差值 = sqrt( sum(Si) / N )
|
|
|
+ * Si = ((Pmi - Ppi) / max(阈值, Pmi))^2
|
|
|
+ * 阈值 = 开机容量(MW) * 0.2
|
|
|
*/
|
|
|
- private BigDecimal computeAheadForecastAccuracy4h(List<DataElectricityLocal> actualRecords,
|
|
|
- List<DataUltraShortTermPowerForecasting> ultraRecords,
|
|
|
+ private BigDecimal computeAcc(List<DataElectricityLocal> actualRecords,
|
|
|
+ List<ForecastPowerAverage> forecastRecords,
|
|
|
BigDecimal operatingCapacity)
|
|
|
{
|
|
|
- if (ultraRecords == null || ultraRecords.isEmpty()) return BigDecimal.ZERO;
|
|
|
- if (operatingCapacity == null || operatingCapacity.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO;
|
|
|
+ if (forecastRecords == null || forecastRecords.isEmpty()) return BigDecimal.ZERO;
|
|
|
|
|
|
- double capacityMW = operatingCapacity.doubleValue();
|
|
|
- double thresholdMW = Math.max(0.2 * capacityMW, 0.001); // 防止分母过小
|
|
|
+ // 1. 阈值 = 开机容量(MW) * 0.2 (四舍五入保留3位小数)
|
|
|
+ BigDecimal threshold = operatingCapacity.multiply(new BigDecimal("0.2")).setScale(SCALE, RoundingMode.HALF_UP);
|
|
|
|
|
|
- // 构建超短期预报数据映射: start_time(秒) -> 记录
|
|
|
- Map<Long, DataUltraShortTermPowerForecasting> ultraMap = new LinkedHashMap<>();
|
|
|
- for (DataUltraShortTermPowerForecasting r : ultraRecords)
|
|
|
- {
|
|
|
- if (r.getStartTime() != null)
|
|
|
- {
|
|
|
- ultraMap.put(r.getStartTime(), r);
|
|
|
- }
|
|
|
- }
|
|
|
+ // 2. 构建预测数据映射: unix秒(GMT+8) -> power(kW)
|
|
|
+ Map<Long, BigDecimal> forecastMap = buildForecastMap(forecastRecords);
|
|
|
+ if (forecastMap.isEmpty()) return BigDecimal.ZERO;
|
|
|
|
|
|
- double sumSquaredError = 0.0;
|
|
|
- int count = 0;
|
|
|
+ // 3. 遍历actualRecords计算Si
|
|
|
+ BigDecimal stotal = BigDecimal.ZERO;
|
|
|
+ int n = 0;
|
|
|
|
|
|
- for (DataElectricityLocal actual : actualRecords)
|
|
|
+ for (DataElectricityLocal r : actualRecords)
|
|
|
{
|
|
|
- if (actual.getTm() == null || actual.getActualPower() == null) continue;
|
|
|
-
|
|
|
- // 实际数据的unix秒时间戳
|
|
|
- long actualTs = actual.getTm().toInstant().atZone(ZONE_SHANGHAI).toEpochSecond();
|
|
|
-
|
|
|
- // 查找start_time匹配的超短期预报记录
|
|
|
- DataUltraShortTermPowerForecasting ultra = ultraMap.get(actualTs);
|
|
|
- if (ultra == null) continue;
|
|
|
-
|
|
|
- // 计算P_Pi = (power15 + power30 + ... + power240) / 16 的算术平均
|
|
|
- double ppSum = 0.0;
|
|
|
- int ppCount = 0;
|
|
|
+ if (r.getTm() == null) continue;
|
|
|
|
|
|
- String[] powerFields = {
|
|
|
- "power15", "power30", "power45", "power60",
|
|
|
- "power75", "power90", "power105", "power120",
|
|
|
- "power135", "power150", "power165", "power180",
|
|
|
- "power195", "power210", "power225", "power240"
|
|
|
- };
|
|
|
+ // 将actual的tm转换为unix秒(GMT+8)以匹配forecastMap的key
|
|
|
+ long ts = r.getTm().toInstant().atZone(ZONE_SHANGHAI).toEpochSecond();
|
|
|
+ BigDecimal fp = forecastMap.get(ts);
|
|
|
+ if (fp == null) continue;
|
|
|
|
|
|
- for (String field : powerFields)
|
|
|
- {
|
|
|
- Double value = getPowerField(ultra, field);
|
|
|
- if (value != null)
|
|
|
- {
|
|
|
- ppSum += value;
|
|
|
- ppCount++;
|
|
|
- }
|
|
|
- }
|
|
|
+ BigDecimal pm = BigDecimal.valueOf(r.getActualPower()); // kW
|
|
|
+ BigDecimal pp = fp; // kW
|
|
|
|
|
|
- if (ppCount == 0) continue;
|
|
|
- double ppMean = ppSum / ppCount; // P_Pi (kW)
|
|
|
+ // Si = ((Pmi - Ppi) / max(阈值, Pmi))^2
|
|
|
+ BigDecimal denom = threshold.max(pm);
|
|
|
+ if (denom.compareTo(BigDecimal.ZERO) == 0) continue;
|
|
|
|
|
|
- // P_Mi (kW)
|
|
|
- double pm = actual.getActualPower();
|
|
|
+ BigDecimal diff = pm.subtract(pp).divide(denom, 10, RoundingMode.HALF_UP);
|
|
|
+ stotal = stotal.add(diff.multiply(diff));
|
|
|
+ n++;
|
|
|
+ }
|
|
|
|
|
|
- // error_i = (P_Mi - P_Pi) / max(P_Mi, 0.2 * Cap)
|
|
|
- double denominator = Math.max(Math.abs(pm), thresholdMW);
|
|
|
- double error = (pm - ppMean) / denominator;
|
|
|
+ if (n == 0) return BigDecimal.ZERO;
|
|
|
|
|
|
- sumSquaredError += error * error;
|
|
|
- count++;
|
|
|
- }
|
|
|
+ // 4. 偏差值 = sqrt(sum(Si) / N)
|
|
|
+ BigDecimal meanSq = stotal.divide(BigDecimal.valueOf(n), 10, RoundingMode.HALF_UP);
|
|
|
+ BigDecimal deviation = BigDecimal.valueOf(Math.sqrt(meanSq.doubleValue()));
|
|
|
|
|
|
- if (count == 0) return BigDecimal.ZERO;
|
|
|
+ // 5. acc = (1 - 偏差值) × 100%
|
|
|
+ BigDecimal acc = new BigDecimal("1").subtract(deviation).multiply(new BigDecimal("100"))
|
|
|
+ .setScale(SCALE, RoundingMode.HALF_UP);
|
|
|
|
|
|
- double acc = 1.0 - Math.sqrt(sumSquaredError / count);
|
|
|
- return BigDecimal.valueOf(acc).setScale(SCALE, RoundingMode.HALF_UP);
|
|
|
- }
|
|
|
+ // 6. 边界处理
|
|
|
+ if (acc.compareTo(BigDecimal.ZERO) < 0) acc = BigDecimal.ZERO;
|
|
|
+ if (acc.compareTo(new BigDecimal("100")) > 0) acc = new BigDecimal("100");
|
|
|
|
|
|
- /**
|
|
|
- * 安全获取Power字段值
|
|
|
- */
|
|
|
- private Double getPowerField(DataUltraShortTermPowerForecasting record, String fieldName)
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- switch (fieldName)
|
|
|
- {
|
|
|
- case "power15": return parseDouble(record.getPower15());
|
|
|
- case "power30": return parseDouble(record.getPower30());
|
|
|
- case "power45": return parseDouble(record.getPower45());
|
|
|
- case "power60": return parseDouble(record.getPower60());
|
|
|
- case "power75": return parseDouble(record.getPower75());
|
|
|
- case "power90": return parseDouble(record.getPower90());
|
|
|
- case "power105": return parseDouble(record.getPower105());
|
|
|
- case "power120": return parseDouble(record.getPower120());
|
|
|
- case "power135": return parseDouble(record.getPower135());
|
|
|
- case "power150": return parseDouble(record.getPower150());
|
|
|
- case "power165": return parseDouble(record.getPower165());
|
|
|
- case "power180": return parseDouble(record.getPower180());
|
|
|
- case "power195": return parseDouble(record.getPower195());
|
|
|
- case "power210": return parseDouble(record.getPower210());
|
|
|
- case "power225": return parseDouble(record.getPower225());
|
|
|
- case "power240": return parseDouble(record.getPower240());
|
|
|
- default: return null;
|
|
|
- }
|
|
|
- }
|
|
|
- catch (Exception e)
|
|
|
- {
|
|
|
- return null;
|
|
|
- }
|
|
|
+ return acc;
|
|
|
}
|
|
|
|
|
|
// ==================== 辅助方法 ====================
|
|
|
@@ -515,25 +456,20 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
/**
|
|
|
* 构建超短期预测数据映射: unix秒 -> power(kW)
|
|
|
*/
|
|
|
- private Map<Long, Double> buildForecastMap(List<DataSuperShortTermForecastIssued> records)
|
|
|
+ private Map<Long, BigDecimal> buildForecastMap(List<ForecastPowerAverage> records)
|
|
|
{
|
|
|
- Map<Long, Double> map = new LinkedHashMap<>();
|
|
|
+ Map<Long, BigDecimal> map = new LinkedHashMap<>();
|
|
|
if (records == null) return map;
|
|
|
|
|
|
- for (DataSuperShortTermForecastIssued r : records)
|
|
|
+ for (ForecastPowerAverage r : records)
|
|
|
{
|
|
|
- String power = r.getPower();
|
|
|
- if (StringUtils.isEmpty(power)) continue;
|
|
|
+ BigDecimal power = r.getAvgPower();
|
|
|
+ if (power == null) continue;
|
|
|
try
|
|
|
{
|
|
|
- Double p = Double.parseDouble(power.trim());
|
|
|
if (r.getForecastTime() != null)
|
|
|
{
|
|
|
- map.put(r.getForecastTime(), p);
|
|
|
- }
|
|
|
- if (r.getStartTime() != null)
|
|
|
- {
|
|
|
- map.putIfAbsent(r.getStartTime(), p);
|
|
|
+ map.put(r.getForecastTime(), power);
|
|
|
}
|
|
|
}
|
|
|
catch (NumberFormatException e)
|
|
|
@@ -560,33 +496,6 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 安全解析String为Long
|
|
|
- */
|
|
|
- private Long parseLong(String value)
|
|
|
- {
|
|
|
- if (value == null || value.isEmpty()) return 0L;
|
|
|
- try
|
|
|
- {
|
|
|
- return Long.parseLong(value.trim());
|
|
|
- }
|
|
|
- catch (NumberFormatException e)
|
|
|
- {
|
|
|
- return 0L;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 将yyyy-MM-dd HH:mm:ss字符串转换为unix秒级时间戳
|
|
|
- */
|
|
|
- private Long toUnixSeconds(String dateTime)
|
|
|
- {
|
|
|
- return java.time.LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
|
|
- .atZone(ZONE_8)
|
|
|
- .toInstant()
|
|
|
- .getEpochSecond();
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 按日分组
|
|
|
*/
|
|
|
@@ -638,8 +547,8 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
/**
|
|
|
* 按日分组超短期预测数据
|
|
|
*/
|
|
|
- private Map<String, List<DataSuperShortTermForecastIssued>> groupForecastByDay(
|
|
|
- List<DataSuperShortTermForecastIssued> records)
|
|
|
+ private Map<String, List<ForecastPowerAverage>> groupForecastByDay(
|
|
|
+ List<ForecastPowerAverage> records)
|
|
|
{
|
|
|
return records.stream()
|
|
|
.filter(r -> r.getForecastTime() != null)
|
|
|
@@ -652,8 +561,8 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
/**
|
|
|
* 按月分组超短期预测数据
|
|
|
*/
|
|
|
- private Map<String, List<DataSuperShortTermForecastIssued>> groupForecastByMonth(
|
|
|
- List<DataSuperShortTermForecastIssued> records)
|
|
|
+ private Map<String, List<ForecastPowerAverage>> groupForecastByMonth(
|
|
|
+ List<ForecastPowerAverage> records)
|
|
|
{
|
|
|
return records.stream()
|
|
|
.filter(r -> r.getForecastTime() != null)
|
|
|
@@ -670,8 +579,8 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
/**
|
|
|
* 按年分组超短期预测数据
|
|
|
*/
|
|
|
- private Map<String, List<DataSuperShortTermForecastIssued>> groupForecastByYear(
|
|
|
- List<DataSuperShortTermForecastIssued> records)
|
|
|
+ private Map<String, List<ForecastPowerAverage>> groupForecastByYear(
|
|
|
+ List<ForecastPowerAverage> records)
|
|
|
{
|
|
|
return records.stream()
|
|
|
.filter(r -> r.getForecastTime() != null)
|
|
|
@@ -681,101 +590,53 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
|
|
|
Collectors.toList()));
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 按start_time秒分组超短期预报数据
|
|
|
+ /**
|
|
|
+ * 安全解析Long,支持纯数字时间戳或日期字符串(yyyy-MM-dd、yyyy-MM-dd HH:mm:ss、yyyy-MM)。
|
|
|
+ * 输入的日期字符串均为 GMT+8 时间,解析为 UTC epoch seconds 供查询使用。
|
|
|
*/
|
|
|
- private Map<Long, List<DataUltraShortTermPowerForecasting>> groupUltraForecastByStartSec(
|
|
|
- List<DataUltraShortTermPowerForecasting> records)
|
|
|
+ private Long parseLong(String value)
|
|
|
{
|
|
|
- Map<Long, List<DataUltraShortTermPowerForecasting>> map = new LinkedHashMap<>();
|
|
|
- if (records == null) return map;
|
|
|
- for (DataUltraShortTermPowerForecasting r : records)
|
|
|
+ if (value == null || value.isEmpty()) return 0L;
|
|
|
+ String trimmed = value.trim();
|
|
|
+ // 先尝试直接解析为数字时间戳
|
|
|
+ try
|
|
|
{
|
|
|
- if (r.getStartTime() != null)
|
|
|
- {
|
|
|
- map.computeIfAbsent(r.getStartTime(), k -> new ArrayList<>()).add(r);
|
|
|
- }
|
|
|
+ return Long.parseLong(trimmed);
|
|
|
}
|
|
|
- return map;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 收集某日对应的超短期预报记录
|
|
|
- */
|
|
|
- private List<DataUltraShortTermPowerForecasting> collectUltraForDay(
|
|
|
- String day, Map<Long, List<DataUltraShortTermPowerForecasting>> ultraMap)
|
|
|
- {
|
|
|
- LocalDate ld = LocalDate.parse(day, DATE_FMT);
|
|
|
- long dayStartSec = ld.atStartOfDay(ZONE_SHANGHAI).toInstant().getEpochSecond();
|
|
|
- return collectByStartSec(dayStartSec, SECONDS_PER_DAY, ultraMap);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 收集某月对应的超短期预报记录
|
|
|
- */
|
|
|
- private List<DataUltraShortTermPowerForecasting> collectUltraForMonth(
|
|
|
- String month, Map<Long, List<DataUltraShortTermPowerForecasting>> ultraMap)
|
|
|
- {
|
|
|
- YearMonth ym = YearMonth.parse(month, MONTH_FMT);
|
|
|
- LocalDate startDate = ym.atDay(1);
|
|
|
- LocalDate endDate = ym.atEndOfMonth();
|
|
|
- long startSec = startDate.atStartOfDay(ZONE_SHANGHAI).toInstant().getEpochSecond();
|
|
|
- long endSec = endDate.atTime(23, 59, 59).atZone(ZONE_SHANGHAI).toInstant().getEpochSecond();
|
|
|
- long duration = endSec - startSec;
|
|
|
- return collectByStartSecRange(startSec, duration, ultraMap);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 收集某年对应的超短期预报记录
|
|
|
- */
|
|
|
- private List<DataUltraShortTermPowerForecasting> collectUltraForYear(
|
|
|
- String year, Map<Long, List<DataUltraShortTermPowerForecasting>> ultraMap)
|
|
|
- {
|
|
|
- int y = Integer.parseInt(year);
|
|
|
- LocalDate startDate = LocalDate.of(y, 1, 1);
|
|
|
- LocalDate endDate = LocalDate.of(y, 12, 31);
|
|
|
- long startSec = startDate.atStartOfDay(ZONE_SHANGHAI).toInstant().getEpochSecond();
|
|
|
- long endSec = endDate.atTime(23, 59, 59).atZone(ZONE_SHANGHAI).toInstant().getEpochSecond();
|
|
|
- long duration = endSec - startSec;
|
|
|
- return collectByStartSecRange(startSec, duration, ultraMap);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 根据start_time范围收集超短期预报记录
|
|
|
- */
|
|
|
- private List<DataUltraShortTermPowerForecasting> collectByStartSec(
|
|
|
- long startSec, long duration, Map<Long, List<DataUltraShortTermPowerForecasting>> ultraMap)
|
|
|
- {
|
|
|
- long endSec = startSec + duration;
|
|
|
- return ultraMap.keySet().stream()
|
|
|
- .filter(k -> k >= startSec && k < endSec)
|
|
|
- .flatMap(k -> ultraMap.get(k).stream())
|
|
|
- .collect(Collectors.toList());
|
|
|
- }
|
|
|
-
|
|
|
- private List<DataUltraShortTermPowerForecasting> collectByStartSecRange(
|
|
|
- long startSec, long duration, Map<Long, List<DataUltraShortTermPowerForecasting>> ultraMap)
|
|
|
- {
|
|
|
- long endSec = startSec + duration;
|
|
|
- List<DataUltraShortTermPowerForecasting> result = new ArrayList<>();
|
|
|
- for (long k = startSec; k < endSec; k += SECONDS_PER_DAY)
|
|
|
+ catch (NumberFormatException e)
|
|
|
{
|
|
|
- List<DataUltraShortTermPowerForecasting> dayRecords = collectByStartSec(k, SECONDS_PER_DAY, ultraMap);
|
|
|
- result.addAll(dayRecords);
|
|
|
+ // 尝试作为日期字符串解析为 UTC epoch seconds
|
|
|
+ try
|
|
|
+ {
|
|
|
+ // yyyy-MM-dd HH:mm:ss
|
|
|
+ ZonedDateTime zdt = LocalDateTime.parse(trimmed, DATE_TIME_FMT)
|
|
|
+ .atZone(ZONE_SHANGHAI);
|
|
|
+ return zdt.toInstant().getEpochSecond();
|
|
|
+ }
|
|
|
+ catch (DateTimeParseException e2)
|
|
|
+ {
|
|
|
+ // yyyy-MM-dd
|
|
|
+ try
|
|
|
+ {
|
|
|
+ ZonedDateTime zdt = LocalDate.parse(trimmed, DATE_FMT)
|
|
|
+ .atStartOfDay(ZONE_SHANGHAI);
|
|
|
+ return zdt.toInstant().getEpochSecond();
|
|
|
+ }
|
|
|
+ catch (DateTimeParseException e3)
|
|
|
+ {
|
|
|
+ // yyyy-MM(月末)
|
|
|
+ try
|
|
|
+ {
|
|
|
+ YearMonth ym = YearMonth.parse(trimmed, MONTH_FMT);
|
|
|
+ ZonedDateTime zdt = ym.atDay(ym.lengthOfMonth()).atTime(23, 59, 59).atZone(ZONE_SHANGHAI);
|
|
|
+ return zdt.toInstant().getEpochSecond();
|
|
|
+ }
|
|
|
+ catch (DateTimeParseException e4)
|
|
|
+ {
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 查询超短期预测功率数据列表
|
|
|
- */
|
|
|
- private List<DataSuperShortTermForecastIssued> selectSuperShortByTimeRange(
|
|
|
- String entityId, Long startTime, Long endTime)
|
|
|
- {
|
|
|
- DataSuperShortTermForecastIssued query = new DataSuperShortTermForecastIssued();
|
|
|
- query.setEntityId(entityId);
|
|
|
- query.setForecastStartTime(startTime);
|
|
|
- query.setForecastEndTime(endTime);
|
|
|
- return superShortService.selectDataSuperShortTermForecastIssuedList(query);
|
|
|
}
|
|
|
}
|