taosj 1 неделя назад
Родитель
Сommit
56bbbfd285

+ 50 - 21
forecast-backend-server/src/main/java/com/ufo/project/data/service/impl/MediumTermPowerForecastReportServiceImpl.java

@@ -81,11 +81,11 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
         switch (type)
         {
             case "daily":
-                return buildDailyReport(actualList, pureForecastList);
+                return buildDailyReport(actualList, pureForecastList, forecastList);
             case "monthly":
-                return buildMonthlyReport(actualList, pureForecastList);
+                return buildMonthlyReport(actualList, pureForecastList, forecastList);
             case "yearly":
-                return buildYearlyReport(actualList, pureForecastList);
+                return buildYearlyReport(actualList, pureForecastList, forecastList);
             default:
                 return Collections.emptyList();
         }
@@ -157,7 +157,7 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
      * 构建日报
      */
     private List<MediumTermPowerForecastReportVO> buildDailyReport(List<DataElectricityLocal> actualList,
-            List<ForecastPowerAverage> forecastList)
+            List<ForecastPowerAverage> forecastList, List<DataMediumTermPowerForecastReport> rawForecastList)
     {
         Map<String, List<DataElectricityLocal>> dailyMap = groupByDay(actualList);
         Map<String, List<ForecastPowerAverage>> forecastDailyMap = groupForecastByDay(forecastList);
@@ -171,7 +171,7 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
 
             MediumTermPowerForecastReportVO vo = new MediumTermPowerForecastReportVO();
             vo.setTime(day);
-            computeDailyMetrics(vo, records, forecastRecords);
+            computeDailyMetrics(vo, records, forecastRecords, rawForecastList);
             result.add(vo);
         }
         return result;
@@ -181,7 +181,7 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
      * 构建月报
      */
     private List<MediumTermPowerForecastReportVO> buildMonthlyReport(List<DataElectricityLocal> actualList,
-            List<ForecastPowerAverage> forecastList)
+            List<ForecastPowerAverage> forecastList, List<DataMediumTermPowerForecastReport> rawForecastList)
     {
         Map<String, List<DataElectricityLocal>> monthlyMap = groupByMonth(actualList);
         Map<String, List<ForecastPowerAverage>> forecastMonthlyMap = groupForecastByMonth(forecastList);
@@ -196,7 +196,7 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
 
             MediumTermPowerForecastReportVO vo = new MediumTermPowerForecastReportVO();
             vo.setTime(month);
-            computeDailyMetrics(vo, records, forecastRecords);
+            computeDailyMetrics(vo, records, forecastRecords, rawForecastList);
             result.add(vo);
         }
         return result;
@@ -206,7 +206,7 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
      * 构建年报
      */
     private List<MediumTermPowerForecastReportVO> buildYearlyReport(List<DataElectricityLocal> actualList,
-            List<ForecastPowerAverage> forecastList)
+            List<ForecastPowerAverage> forecastList, List<DataMediumTermPowerForecastReport> rawForecastList)
     {
         Map<String, List<DataElectricityLocal>> yearlyMap = groupByYear(actualList);
         Map<String, List<ForecastPowerAverage>> forecastYearlyMap = groupForecastByYear(forecastList);
@@ -221,7 +221,7 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
 
             MediumTermPowerForecastReportVO vo = new MediumTermPowerForecastReportVO();
             vo.setTime(year);
-            computeDailyMetrics(vo, records, forecastRecords);
+            computeDailyMetrics(vo, records, forecastRecords, rawForecastList);
             result.add(vo);
         }
         return result;
@@ -231,7 +231,7 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
      * 计算指标(日报/月报/年报通用)
      */
     private void computeDailyMetrics(MediumTermPowerForecastReportVO vo,
-            List<DataElectricityLocal> actualRecords, List<ForecastPowerAverage> forecastRecords)
+            List<DataElectricityLocal> actualRecords, List<ForecastPowerAverage> forecastRecords, List<DataMediumTermPowerForecastReport> rawForecastList)
     {
         // 预先构建预测数据映射(避免各方法重复调用 buildForecastMap)
         Map<Long, BigDecimal> forecastMap = buildForecastMap(forecastRecords);
@@ -269,7 +269,7 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
         vo.setMaximumErrorPassRate(passRate);
 
         // 8. acc: 合格率
-        BigDecimal acc = computeAccuracy(actualRecords, forecastMap, operatingCapacity);
+        BigDecimal acc = computeAccuracy(actualRecords, forecastMap, operatingCapacity, rawForecastList);
         vo.setAcc(acc);
     }
 
@@ -486,23 +486,24 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
      *
      * <p>其中:
      * <ul>
-     *   <li>阈值 = 开机容量 * 0.2</li>
+     *   <li>阈值 = 开机容量 * 0.25</li>
      *   <li>Si = ((Pmi - Ppi) / Pmi)² 当 Pmi >= 阈值</li>
      *   <li>Si = ((Pmi - Ppi) / 阈值)² 当 Pmi &lt; 阈值</li>
      * </ul>
      */
     private BigDecimal computeAccuracy(List<DataElectricityLocal> actualRecords,
             Map<Long, BigDecimal> forecastMap,
-            BigDecimal operatingCapacity)
+            BigDecimal operatingCapacity, List<DataMediumTermPowerForecastReport> rawForecastList)
     {
-        if (forecastMap == null || forecastMap.isEmpty() || operatingCapacity == null
+        if (operatingCapacity == null
                 || operatingCapacity.compareTo(BigDecimal.ZERO) == 0)
         {
             return BigDecimal.ZERO;
         }
 
-        // 阈值 = 开机容量(MW) * 0.2
-        BigDecimal threshold = operatingCapacity.multiply(new BigDecimal("0.2")).setScale(3, RoundingMode.HALF_UP);
+        // 阈值 = 开机容量(MW) * 0.25
+        BigDecimal baseThreshold = new BigDecimal("0.25");
+        BigDecimal threshold = operatingCapacity.multiply(baseThreshold).setScale(3, RoundingMode.HALF_UP);
 
         BigDecimal stotal = BigDecimal.ZERO;
         int n = 0;
@@ -513,7 +514,7 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
             if (r.getTm() == null || r.getActualPower() == null) continue;
 
             Long ts = r.getTm().getTime() / 1000;
-            BigDecimal fp = forecastMap.get(ts);
+            BigDecimal fp = getAccForecastReportPower(ts, rawForecastList);
             if (fp == null) continue;
 
             BigDecimal pmiMW = BigDecimal.valueOf(r.getActualPower()).divide(BigDecimal.valueOf(1000), precisionLength, RoundingMode.HALF_UP);
@@ -545,11 +546,11 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
 
         // acc = (1 - sqrt(stotal / n)) * 100
         double meanSquare = stotal.divide(BigDecimal.valueOf(n), precisionLength, RoundingMode.HALF_UP).doubleValue();
-        log.info("meanSquare: {}", meanSquare);
+        log.debug("meanSquare: {}", meanSquare);
         double deviation = Math.sqrt(meanSquare);
-        log.info("deviation: {}", deviation);
-        double acc = (1.0 - deviation) * 100.0;
-        log.info("acc: {}", acc);
+        log.debug("deviation: {}", deviation);
+        double acc = deviation * 100.0;
+        log.debug("acc: {}", acc);
 
         // 边界处理
         if (acc < 0) acc = 0;
@@ -558,6 +559,34 @@ public class MediumTermPowerForecastReportServiceImpl implements IMediumTermPowe
         return BigDecimal.valueOf(acc).setScale(3, RoundingMode.HALF_UP);
     }
 
+    /**
+     * 获取日前timestamp预报数据
+     * @param timestamp
+     * @param rawForecastList
+     * @return
+     */
+    private BigDecimal getAccForecastReportPower(long timestamp, List<DataMediumTermPowerForecastReport> rawForecastList)
+    {
+        return rawForecastList.stream()
+                .filter(r -> r.getForecastTime() != null && r.getStartTime() != null && r.getForecastTime() == timestamp && is4DaysBefore(timestamp, r.getStartTime()))
+                .max(Comparator.comparing(DataMediumTermPowerForecastReport::getStartTime))
+                .map(r -> new BigDecimal(r.getPower()))
+                .orElse(null);
+    }
+
+    /**
+     * 判断datTimestamp是否是timestamp的4日前
+     * @param timestamp
+     * @param datTimestamp
+     * @return
+     */
+    private boolean is4DaysBefore(long timestamp, long datTimestamp)
+    {
+        return LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZONE_SHANGHAI).toLocalDate()
+                .minusDays(4)
+                .equals(LocalDateTime.ofInstant(Instant.ofEpochSecond(datTimestamp), ZONE_SHANGHAI).toLocalDate());
+    }
+
     /**
      * 构建预测数据映射:时间戳(秒) -> power(kW)
      */

+ 50 - 95
forecast-backend-server/src/main/java/com/ufo/project/data/service/impl/ShortTermPowerForecastReportServiceImpl.java

@@ -99,11 +99,11 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
         switch (type)
         {
             case "daily":
-                return buildDailyReport(actualList, pureForecastList);
+                return buildDailyReport(actualList, pureForecastList, forecastList);
             case "monthly":
-                return buildMonthlyReport(actualList, pureForecastList);
+                return buildMonthlyReport(actualList, pureForecastList, forecastList);
             case "yearly":
-                return buildYearlyReport(actualList, pureForecastList);
+                return buildYearlyReport(actualList, pureForecastList, forecastList);
             default:
                 return Collections.emptyList();
         }
@@ -175,7 +175,7 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
      * 构建日报
      */
     private List<ShortTermPowerForecastReportVO> buildDailyReport(List<DataElectricityLocal> actualList,
-            List<ForecastPowerAverage> forecastList)
+            List<ForecastPowerAverage> forecastList, List<DataShortTermPowerForecastReport> rawForecastList)
     {
         Map<String, List<DataElectricityLocal>> dailyMap = groupByDay(actualList);
         Map<String, List<ForecastPowerAverage>> forecastDailyMap = groupForecastByDay(forecastList);
@@ -189,7 +189,7 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
 
             ShortTermPowerForecastReportVO vo = new ShortTermPowerForecastReportVO();
             vo.setTime(day);
-            computeDailyMetrics(vo, records, forecastRecords);
+            computeDailyMetrics(vo, records, forecastRecords, rawForecastList);
             result.add(vo);
         }
         return result;
@@ -199,7 +199,7 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
      * 构建月报
      */
     private List<ShortTermPowerForecastReportVO> buildMonthlyReport(List<DataElectricityLocal> actualList,
-            List<ForecastPowerAverage> forecastList)
+            List<ForecastPowerAverage> forecastList, List<DataShortTermPowerForecastReport> rawForecastList)
     {
         Map<String, List<DataElectricityLocal>> monthlyMap = groupByMonth(actualList);
         Map<String, List<ForecastPowerAverage>> forecastMonthlyMap = groupForecastByMonth(forecastList);
@@ -214,7 +214,7 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
 
             ShortTermPowerForecastReportVO vo = new ShortTermPowerForecastReportVO();
             vo.setTime(month);
-            computeDailyMetrics(vo, records, forecastRecords);
+            computeDailyMetrics(vo, records, forecastRecords, rawForecastList);
             result.add(vo);
         }
         return result;
@@ -224,7 +224,7 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
      * 构建年报
      */
     private List<ShortTermPowerForecastReportVO> buildYearlyReport(List<DataElectricityLocal> actualList,
-            List<ForecastPowerAverage> forecastList)
+            List<ForecastPowerAverage> forecastList, List<DataShortTermPowerForecastReport> rawForecastList)
     {
         Map<String, List<DataElectricityLocal>> yearlyMap = groupByYear(actualList);
         Map<String, List<ForecastPowerAverage>> forecastYearlyMap = groupForecastByYear(forecastList);
@@ -239,7 +239,7 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
 
             ShortTermPowerForecastReportVO vo = new ShortTermPowerForecastReportVO();
             vo.setTime(year);
-            computeDailyMetrics(vo, records, forecastRecords);
+            computeDailyMetrics(vo, records, forecastRecords, rawForecastList);
             result.add(vo);
         }
         return result;
@@ -249,7 +249,7 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
      * 计算指标(日报/月报/年报通用)
      */
     private void computeDailyMetrics(ShortTermPowerForecastReportVO vo,
-            List<DataElectricityLocal> actualRecords, List<ForecastPowerAverage> forecastRecords)
+            List<DataElectricityLocal> actualRecords, List<ForecastPowerAverage> forecastRecords, List<DataShortTermPowerForecastReport> rawForecastList)
     {
         // 预先构建预测数据映射(避免各方法重复调用 buildForecastMap)
         Map<Long, BigDecimal> forecastMap = buildForecastMap(forecastRecords);
@@ -287,7 +287,7 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
         vo.setMaximumErrorPassRate(passRate);
 
         // 8. acc: 合格率
-        BigDecimal acc = computeAccuracy(actualRecords, forecastMap, operatingCapacity);
+        BigDecimal acc = computeAccuracy(actualRecords, forecastMap, operatingCapacity, rawForecastList);
         vo.setAcc(acc);
     }
 
@@ -499,81 +499,20 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
         return BigDecimal.valueOf(r).setScale(3, RoundingMode.HALF_UP);
     }
 
-    /**
-     * 计算最大误差合格率
-     */
-    private BigDecimal computeMaximumErrorPassRate(List<DataElectricityLocal> actualRecords,
-            Map<Long, BigDecimal> forecastMap,
-            BigDecimal operatingCapacity)
-    {
-        if (operatingCapacity == null || operatingCapacity.compareTo(BigDecimal.ZERO) == 0)
-        {
-            return BigDecimal.ZERO;
-        }
-
-        String threshold = getErrorThreshold();
-        if (StringUtils.isEmpty(threshold))
-        {
-            return BigDecimal.ZERO;
-        }
-
-        BigDecimal thresholdValue;
-        try
-        {
-            thresholdValue = new BigDecimal(threshold.trim());
-        }
-        catch (NumberFormatException e)
-        {
-            return BigDecimal.ZERO;
-        }
-
-        BigDecimal passCount = BigDecimal.ZERO;
-        BigDecimal totalCount = BigDecimal.ZERO;
-        BigDecimal thousand = BigDecimal.valueOf(1000);
-
-        for (DataElectricityLocal r : actualRecords)
-        {
-            if (r.getTm() == null || r.getActualPower() == null) continue;
-
-            Long ts = r.getTm().getTime() / 1000;
-            BigDecimal fp = forecastMap.get(ts);
-            if (fp == null) continue;
-
-            totalCount = totalCount.add(BigDecimal.ONE);
-            BigDecimal errorMW = BigDecimal.valueOf(r.getActualPower())
-                    .subtract(fp)
-                    .abs()
-                    .divide(thousand, 10, RoundingMode.HALF_UP);
-            BigDecimal allowed = thresholdValue.multiply(operatingCapacity);
-            if (errorMW.compareTo(allowed) <= 0)
-            {
-                passCount = passCount.add(BigDecimal.ONE);
-            }
-        }
-
-        if (totalCount.compareTo(BigDecimal.ZERO) == 0)
-        {
-            return BigDecimal.ZERO;
-        }
-
-        BigDecimal rate = passCount.divide(totalCount, 10, RoundingMode.HALF_UP)
-                .multiply(BigDecimal.valueOf(100));
-        return rate.setScale(3, RoundingMode.HALF_UP);
-    }
-
     /**
      * 计算准确率 acc = (1 - sqrt(ΣSi / N)) * 100
      *
      * <p>其中:
      * <ul>
-     *   <li>阈值 = 开机容量 * 0.2</li>
+     *   <li>阈值 = 开机容量 * 0.5</li>
      *   <li>Si = ((Pmi - Ppi) / Pmi)² 当 Pmi >= 阈值</li>
      *   <li>Si = ((Pmi - Ppi) / 阈值)² 当 Pmi &lt; 阈值</li>
      * </ul>
      */
     private BigDecimal computeAccuracy(List<DataElectricityLocal> actualRecords,
             Map<Long, BigDecimal> forecastMap,
-            BigDecimal operatingCapacity)
+            BigDecimal operatingCapacity,
+            List<DataShortTermPowerForecastReport> rawForecastList)
     {
         if (forecastMap == null || forecastMap.isEmpty() || operatingCapacity == null
                 || operatingCapacity.compareTo(BigDecimal.ZERO) == 0)
@@ -581,8 +520,9 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
             return BigDecimal.ZERO;
         }
 
-        // 阈值 = 开机容量(MW) * 0.2
-        BigDecimal threshold = operatingCapacity.multiply(new BigDecimal("0.2")).setScale(3, RoundingMode.HALF_UP);
+        // 阈值 = 开机容量(MW) * 0.25
+        BigDecimal baseThreshold = new BigDecimal("0.25");
+        BigDecimal threshold = operatingCapacity.multiply(baseThreshold).setScale(3, RoundingMode.HALF_UP);
 
         BigDecimal stotal = BigDecimal.ZERO;
         int n = 0;
@@ -593,7 +533,7 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
             if (r.getTm() == null || r.getActualPower() == null) continue;
 
             Long ts = r.getTm().getTime() / 1000;
-            BigDecimal fp = forecastMap.get(ts);
+            BigDecimal fp = getAccForecastReportPower(ts, rawForecastList);
             if (fp == null) continue;
 
             BigDecimal pmiMW = BigDecimal.valueOf(r.getActualPower()).divide(BigDecimal.valueOf(1000), precisionLength, RoundingMode.HALF_UP);
@@ -625,11 +565,11 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
 
         // acc = (1 - sqrt(stotal / n)) * 100
         double meanSquare = stotal.divide(BigDecimal.valueOf(n), precisionLength, RoundingMode.HALF_UP).doubleValue();
-        log.info("meanSquare: {}", meanSquare);
+        log.debug("meanSquare: {}", meanSquare);
         double deviation = Math.sqrt(meanSquare);
-        log.info("deviation: {}", deviation);
-        double acc = (1.0 - deviation) * 100.0;
-        log.info("acc: {}", acc);
+        log.debug("deviation: {}", deviation);
+        double acc = deviation * 100.0;
+        log.debug("acc: {}", acc);
 
         // 边界处理
         if (acc < 0) acc = 0;
@@ -638,6 +578,34 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
         return BigDecimal.valueOf(acc).setScale(3, RoundingMode.HALF_UP);
     }
 
+    /**
+     * 获取日前timestamp预报数据
+     * @param timestamp
+     * @param rawForecastList
+     * @return
+     */
+    private BigDecimal getAccForecastReportPower(long timestamp, List<DataShortTermPowerForecastReport> rawForecastList)
+    {
+        return rawForecastList.stream()
+                .filter(r -> r.getForecastTime() != null && r.getStartTime() != null && r.getForecastTime() == timestamp && isYesterday(timestamp, r.getStartTime()))
+                .max(Comparator.comparing(DataShortTermPowerForecastReport::getStartTime))
+                .map(r -> new BigDecimal(r.getPower()))
+                .orElse(null);
+    }
+
+    /**
+     * 判断datTimestamp是否是timestamp的前一天
+     * @param timestamp
+     * @param datTimestamp
+     * @return
+     */
+    private boolean isYesterday(long timestamp, long datTimestamp)
+    {
+        return LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZONE_SHANGHAI).toLocalDate()
+                .minusDays(1)
+                .equals(LocalDateTime.ofInstant(Instant.ofEpochSecond(datTimestamp), ZONE_SHANGHAI).toLocalDate());
+    }
+
     /**
      * 构建预测数据映射:时间戳(秒) -> power(kW)
      */
@@ -816,19 +784,6 @@ public class ShortTermPowerForecastReportServiceImpl implements IShortTermPowerF
         }
     }
 
-    /**
-     * 获取误差阈值
-     */
-    private String getErrorThreshold()
-    {
-        List<SysDictData> dictList = dictTypeService.selectDictDataByType(DICT_TYPE_SHORT_TERM_ERROR_THRESHOLD);
-        if (dictList != null && !dictList.isEmpty())
-        {
-            return dictList.get(0).getDictValue();
-        }
-        return null;
-    }
-
     /**
      * 刷新阈值配置
      */

+ 60 - 24
forecast-backend-server/src/main/java/com/ufo/project/data/service/impl/UltraShortTermPowerForecastReportServiceImpl.java

@@ -2,7 +2,6 @@ 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;
@@ -20,10 +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 org.springframework.util.CollectionUtils;
+
 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.shortterm.power.domain.DataShortTermPowerForecastReport;
 import com.ufo.project.forecast.shortterm.power.domain.ForecastPowerAverage;
 import com.ufo.project.forecast.supershort.domain.DataSuperShortTermForecastReport;
 import com.ufo.project.forecast.supershort.service.IDataSuperShortTermForecastReportService;
@@ -87,11 +87,11 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
         switch (type)
         {
             case "daily":
-                return buildDailyReport(actualList, pureForecastList);
+                return buildDailyReport(actualList, pureForecastList, forecastList);
             case "monthly":
-                return buildMonthlyReport(actualList, pureForecastList);
+                return buildMonthlyReport(actualList, pureForecastList, forecastList);
             case "yearly":
-                return buildYearlyReport(actualList, pureForecastList);
+                return buildYearlyReport(actualList, pureForecastList, forecastList);
             default:
                 return Collections.emptyList();
         }
@@ -164,7 +164,8 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
      */
     private List<UltraShortTermPowerForecastReportVO> buildDailyReport(
             List<DataElectricityLocal> actualList,
-            List<ForecastPowerAverage> forecastList)
+            List<ForecastPowerAverage> forecastList,
+            List<DataSuperShortTermForecastReport> rawForecastList)
     {
         Map<String, List<DataElectricityLocal>> dailyMap = groupByDay(actualList);
         Map<String, List<ForecastPowerAverage>> forecastDailyMap = groupForecastByDay(forecastList);
@@ -178,7 +179,7 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
 
             UltraShortTermPowerForecastReportVO vo = new UltraShortTermPowerForecastReportVO();
             vo.setTime(day);
-            computeMetrics(vo, records, forecastRecords);
+            computeMetrics(vo, records, forecastRecords, rawForecastList);
             result.add(vo);
         }
         return result;
@@ -189,7 +190,8 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
      */
     private List<UltraShortTermPowerForecastReportVO> buildMonthlyReport(
             List<DataElectricityLocal> actualList,
-            List<ForecastPowerAverage> forecastList)
+            List<ForecastPowerAverage> forecastList,
+            List<DataSuperShortTermForecastReport> rawForecastList)
     {
         Map<String, List<DataElectricityLocal>> monthlyMap = groupByMonth(actualList);
         Map<String, List<ForecastPowerAverage>> forecastMonthlyMap = groupForecastByMonth(forecastList);
@@ -204,7 +206,7 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
 
             UltraShortTermPowerForecastReportVO vo = new UltraShortTermPowerForecastReportVO();
             vo.setTime(month);
-            computeMetrics(vo, records, forecastRecords);
+            computeMetrics(vo, records, forecastRecords, rawForecastList);
             result.add(vo);
         }
         return result;
@@ -215,7 +217,8 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
      */
     private List<UltraShortTermPowerForecastReportVO> buildYearlyReport(
             List<DataElectricityLocal> actualList,
-            List<ForecastPowerAverage> forecastList)
+            List<ForecastPowerAverage> forecastList,
+            List<DataSuperShortTermForecastReport> rawForecastList)
     {
         Map<String, List<DataElectricityLocal>> yearlyMap = groupByYear(actualList);
         Map<String, List<ForecastPowerAverage>> forecastYearlyMap = groupForecastByYear(forecastList);
@@ -230,7 +233,7 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
 
             UltraShortTermPowerForecastReportVO vo = new UltraShortTermPowerForecastReportVO();
             vo.setTime(year);
-            computeMetrics(vo, records, forecastRecords);
+            computeMetrics(vo, records, forecastRecords, rawForecastList);
             result.add(vo);
         }
         return result;
@@ -241,7 +244,8 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
      */
     private void computeMetrics(UltraShortTermPowerForecastReportVO vo,
             List<DataElectricityLocal> actualRecords,
-            List<ForecastPowerAverage> forecastRecords)
+            List<ForecastPowerAverage> forecastRecords,
+            List<DataSuperShortTermForecastReport> rawForecastList)
     {
         // 1. 开机容量
         BigDecimal operatingCapacity = computeOperatingCapacity(actualRecords);
@@ -277,7 +281,7 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
         vo.setMaximumErrorPassRate(passRate);
 
         // 8. 合格率 (acc)
-        BigDecimal acc = computeAcc(actualRecords, forecastRecords, operatingCapacity);
+        BigDecimal acc = computeAcc(actualRecords, forecastRecords, operatingCapacity, rawForecastList);
         vo.setAcc(acc);
     }
 
@@ -460,20 +464,17 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
      * acc = (1 - 偏差值) × 100%
      * 偏差值 = sqrt( sum(Si) / N )
      * Si = ((Pmi - Ppi) / max(阈值, Pmi))^2
-     * 阈值 = 开机容量(MW) * 0.2
+     * 阈值 = 开机容量(MW) * 0.5
      */
     private BigDecimal computeAcc(List<DataElectricityLocal> actualRecords,
             List<ForecastPowerAverage> forecastRecords,
-            BigDecimal operatingCapacity)
+            BigDecimal operatingCapacity, List<DataSuperShortTermForecastReport> rawForecastList)
     {
         if (forecastRecords == null || forecastRecords.isEmpty()) return BigDecimal.ZERO;
 
-        // 1. 阈值 = 开机容量(MW) * 0.2 (四舍五入保留3位小数)
-        BigDecimal threshold = operatingCapacity.multiply(new BigDecimal("0.2")).setScale(SCALE, RoundingMode.HALF_UP);
-
-        // 2. 构建预测数据映射: unix秒(GMT+8) -> power(kW)
-        Map<Long, BigDecimal> forecastMap = buildForecastMap(forecastRecords);
-        if (forecastMap.isEmpty()) return BigDecimal.ZERO;
+        // 1. 阈值 = 开机容量(MW) * 0.25 (四舍五入保留3位小数)
+        BigDecimal baseThreshold = new BigDecimal("0.25");
+        BigDecimal threshold = operatingCapacity.multiply(baseThreshold).setScale(SCALE, RoundingMode.HALF_UP);
 
         // 3. 遍历actualRecords计算Si
         BigDecimal stotal = BigDecimal.ZERO;
@@ -485,7 +486,7 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
 
             // 将actual的tm转换为unix秒(GMT+8)以匹配forecastMap的key
             long ts = r.getTm().toInstant().atZone(ZONE_SHANGHAI).toEpochSecond();
-            BigDecimal fp = forecastMap.get(ts);
+            BigDecimal fp = getAccForecastReportPower(ts, rawForecastList);
             if (fp == null) continue;
 
             BigDecimal pm = BigDecimal.valueOf(r.getActualPower()); // kW
@@ -503,11 +504,11 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
         if (n == 0) return BigDecimal.ZERO;
 
         // 4. 偏差值 = sqrt(sum(Si) / N)
-        BigDecimal meanSq = stotal.divide(BigDecimal.valueOf(n), 10, RoundingMode.HALF_UP);
+        BigDecimal meanSq = stotal.divide(BigDecimal.valueOf(n), 20, RoundingMode.HALF_UP);
         BigDecimal deviation = BigDecimal.valueOf(Math.sqrt(meanSq.doubleValue()));
 
         // 5. acc = (1 - 偏差值) × 100%
-        BigDecimal acc = new BigDecimal("1").subtract(deviation).multiply(new BigDecimal("100"))
+        BigDecimal acc = deviation.multiply(new BigDecimal("100"))
                 .setScale(SCALE, RoundingMode.HALF_UP);
 
         // 6. 边界处理
@@ -517,6 +518,41 @@ public class UltraShortTermPowerForecastReportServiceImpl implements IUltraShort
         return acc;
     }
 
+    /**
+     * 获取日前timestamp预报数据
+     * @param timestamp
+     * @param rawForecastList
+     * @return
+     */
+    private BigDecimal getAccForecastReportPower(long timestamp, List<DataSuperShortTermForecastReport> rawForecastList)
+    {
+        List<BigDecimal> matchPowers = rawForecastList.stream()
+                .filter(r -> r.getForecastTime() != null && r.getStartTime() != null && r.getForecastTime() == timestamp && is4HourBeforeTo15MinuteBefore(timestamp, r.getStartTime()))
+                .map(r -> new BigDecimal(r.getPower()))
+                .filter(power -> power != null)
+                .collect(Collectors.toList());
+        if(CollectionUtils.isEmpty(matchPowers)){
+            return BigDecimal.ZERO;
+        }
+        BigDecimal sum = matchPowers.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal average = sum.divide(new BigDecimal(matchPowers.size()), 3, RoundingMode.HALF_UP);
+        return average;
+    }
+
+    /**
+     * 判断 startTime 是否介于 [forecastTime - 4h, forecastTime - 15h] 之间
+     * @param timestamp
+     * @param datTimestamp
+     * @return
+     */
+    private boolean is4HourBeforeTo15MinuteBefore(long timestamp, long datTimestamp)
+    {
+        long fourHoursAgo = timestamp - 4 * 3600;   // 4小时前
+        long fifteenHoursAgo = timestamp - 15 * 3600; // 15小时前
+        // 注意:4小时前 > 15小时前,所以 startTime 应该在这两者之间
+        return datTimestamp >= fifteenHoursAgo && datTimestamp <= fourHoursAgo;
+    }
+
     // ==================== 辅助方法 ====================
 
     /**