Jelajahi Sumber

Merge branch 'master' of http://124.70.43.205:3000/wangb/runeconomy-nx

wangb 1 Minggu lalu
induk
melakukan
6f67c16347

+ 3 - 1
common/src/main/java/com/gyee/common/contant/ContantXk.java

@@ -180,7 +180,7 @@ public class ContantXk {
     public static final String CJ_FX = "AI067";  //风向
     public static final String CJ_WGGL = "AI107";  //风向
     public static final String CJ_DFJD = "AI073";  //对风角度
-    public static final String CJ_PHJD = "AI028";  //对风角度
+    public static final String CJ_PHJD = "AI028";  //偏航角度
 
     public static final String TPOINT_WP_AGC = "AGC002";// agc
     public static final String TPOINT_WP_CXGL = "AGC001";// 出线
@@ -215,6 +215,8 @@ public class ContantXk {
     public static final String FCCFTFX50 = "FCCFTFX50";//测风塔50米风向
     public static final String FCCFTFS70 = "FCCFTFS70";//测风塔70米风速
     public static final String FCCFTFX70 = "FCCFTFX70";//测风塔70米风向
+    public static final String FCCFTFS90 = "FCCFTFS90";//测风塔90米风速
+    public static final String FCCFTFX90 = "FCCFTFX90";//测风塔90米风向
     public static final String FCCFTWD = "FCCFTWD";//测风塔温度
     public static final String FCCFTSD = "FCCFTSD";//测风塔湿度
     public static final String KQMD = "KQMD";//测风塔空气密度

+ 2 - 1
data-adapter/src/main/java/com/gyee/dataadapter/controller/AdapterController.java

@@ -81,7 +81,8 @@ public class AdapterController {
 
     @GetMapping("/history/stat3")
     public List<PointData> getHistoryStat3(@RequestParam("tagName") String tagName,
-                                          @RequestParam("startTs") Long startTs, @RequestParam("endTs") Long endTs,
+                                          @RequestParam("startTs") Long startTs,
+                                           @RequestParam("endTs") Long endTs,
                                           @RequestParam(value = "interval", required = false) Integer interval,
                                           @RequestParam(value = "type", required = false) Integer type) {
         return tsDataService.getHistoryStat(tagName, new Date(startTs), new Date(endTs), interval, type);

+ 27 - 0
runeconomy-xk/src/main/java/com/gyee/runeconomy/controller/WindDirection/WindDirectionController.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.gyee.runeconomy.dto.result.JsonResult;
 import com.gyee.runeconomy.dto.result.ResultCode;
 import com.gyee.runeconomy.service.WindDirection.WindDirectionService;
+import com.gyee.runeconomy.service.WindDirection.WindMachineService;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
@@ -15,6 +16,8 @@ import java.text.ParseException;
 public class WindDirectionController {
     @Resource
     private WindDirectionService windDirectionService;
+    @Resource
+    private WindMachineService windMachineService;
     /***
      * 风速风向频次玫瑰图
      * @return
@@ -24,4 +27,28 @@ public class WindDirectionController {
         Object o = windDirectionService.fxRoses(wtid, kssj, jssj);
         return JsonResult.successData(ResultCode.SUCCESS, o);
     }
+
+    /**
+     * 对风偏差分析
+
+     * @return
+     */
+    @GetMapping("/deviation/ratio")
+    public JSONObject fsDeviationRatio(String wtid,String kssj,String jssj) throws ParseException {
+
+        Object o = windDirectionService.windDeviationRatio(wtid, kssj, jssj);
+        return JsonResult.successData(ResultCode.SUCCESS, o);
+    }
+
+    /**
+     * 单机偏差分析
+
+     * @return
+     */
+    @GetMapping("/machine")
+    public JSONObject Deviationmachine(String wpid) throws Exception {
+
+        Object o = windMachineService.machine(wpid);
+        return JsonResult.successData(ResultCode.SUCCESS, o);
+    }
 }

+ 77 - 0
runeconomy-xk/src/main/java/com/gyee/runeconomy/model/vo/WindData.java

@@ -0,0 +1,77 @@
+package com.gyee.runeconomy.model.vo;
+
+public class WindData {
+    private String wtid;
+    private String time;
+    private double fanSpeed;
+    private double towerSpeed;
+    private double pcSpeed;
+    private double fanDirection;
+    private double towerDirection;
+    private double pcDirection;
+
+
+    public String getWtid() {
+        return wtid;
+    }
+
+    public void setWtid(String wtid) {
+        this.wtid = wtid;
+    }
+
+    public String getTime() {
+        return time;
+    }
+
+    public void setTime(String time) {
+        this.time = time;
+    }
+
+    public double getFanSpeed() {
+        return fanSpeed;
+    }
+
+    public void setFanSpeed(double fanSpeed) {
+        this.fanSpeed = fanSpeed;
+    }
+
+    public double getFanDirection() {
+        return fanDirection;
+    }
+
+    public void setFanDirection(double fanDirection) {
+        this.fanDirection = fanDirection;
+    }
+
+    public double getPcSpeed() {
+        return pcSpeed;
+    }
+
+    public void setPcSpeed(double pcSpeed) {
+        this.pcSpeed = pcSpeed;
+    }
+
+    public double getPcDirection() {
+        return pcDirection;
+    }
+
+    public void setPcDirection(double pcDirection) {
+        this.pcDirection = pcDirection;
+    }
+
+    public double getTowerSpeed() {
+        return towerSpeed;
+    }
+
+    public void setTowerSpeed(double towerSpeed) {
+        this.towerSpeed = towerSpeed;
+    }
+
+    public double getTowerDirection() {
+        return towerDirection;
+    }
+
+    public void setTowerDirection(double towerDirection) {
+        this.towerDirection = towerDirection;
+    }
+}

+ 23 - 68
runeconomy-xk/src/main/java/com/gyee/runeconomy/service/EarlyWarning/ReportService.java

@@ -7,6 +7,7 @@ import com.gyee.runeconomy.model.auto.ProEconEquipmentmodel;
 import com.gyee.runeconomy.model.auto.TurbineInfoDay;
 import com.gyee.runeconomy.model.vo.IndicatorData;
 import com.gyee.runeconomy.service.auto.ITurbineInfoDayService;
+import com.gyee.runeconomy.util.DateUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -29,15 +30,15 @@ public class ReportService {
     public Map Electricity(String wtid, String time) throws Exception {
 
         //查询时间-开始结束
-        String[] timeRange = generateTimeRange(time);
+        String[] timeRange = DateUtils.generateTimeRange(time);
 
         //去年同期时间
-        String qntq = subtractOneYear(time);
-        String[] qntime = generateTimeRange(qntq);
+        String qntq = DateUtils.subtractOneYear(time);
+        String[] qntime = DateUtils.generateTimeRange(qntq);
 
         //月天数
-        int daysInMonth = getDaysInMonth(timeRange);
-        int qnInMonth = getDaysInMonth(qntime);
+        int daysInMonth = DateUtils.getDaysInMonth(timeRange);
+        int qnInMonth = DateUtils.getDaysInMonth(qntime);
 
         System.out.println("开始时间: " + timeRange[0]);
         System.out.println("结束时间: " + timeRange[1]);
@@ -165,11 +166,24 @@ public class ReportService {
                 .sum(); // 去年同期计划检修损失电量
 
         double currentPerformanceLossPower = turbineList.stream()
-                .mapToDouble(t -> t.getXnss() == null ? 0 : Math.round(t.getXnss().doubleValue() * 100.0) / 100.0)
+                .mapToDouble(t -> t.getXnss() == null ? 0 : new BigDecimal(t.getXnss().doubleValue())
+                        .setScale(2, BigDecimal.ROUND_HALF_UP) // 保留两位小数并四舍五入
+                        .doubleValue())
                 .sum(); // 本月性能未达标损失电量
+        currentPerformanceLossPower = new BigDecimal(currentPerformanceLossPower)
+                .setScale(2, BigDecimal.ROUND_HALF_UP) // 确保最后的结果保留两位小数
+                .doubleValue();
+
+
         double lastYearPerformanceLossPower = tqturbineList.stream()
-                .mapToDouble(t -> t.getXnss() == null ? 0 : Math.round(t.getXnss().doubleValue() * 100.0) / 100.0)
+                .mapToDouble(t -> t.getXnss() == null ? 0 : new BigDecimal(t.getXnss().doubleValue())
+                        .setScale(2, BigDecimal.ROUND_HALF_UP) // 保留两位小数并四舍五入
+                        .doubleValue())
                 .sum(); // 去年同期性能未达标损失电量
+        lastYearPerformanceLossPower = new BigDecimal(lastYearPerformanceLossPower)
+                .setScale(2, BigDecimal.ROUND_HALF_UP)
+                .doubleValue();
+
 
         double currentFaultDowntime = turbineList.stream()
                 .mapToDouble(t -> t.getGzMin() == null ? 0 : Math.round(t.getGzMin().doubleValue() * 100.0) / 100.0)
@@ -236,9 +250,9 @@ public class ReportService {
                 "设备可利用率"+ currentEquipmentUtilization +"%、同比"+ lastYearEquipmentUtilization +"%,等效可用系数"+ currentEfficiencyCoefficient +"%、同比"+ lastYearEfficiencyCoefficient +"%," +
                 "静风频率达到"+ currentCalmWindFrequency +"%、同比"+ lastYearCalmWindFrequency +"%。";
         String wz2 = collect.get(0).getName() + time +"月平均风速"+ currentWindSpeed +"m/s、同比"+ lastYearWindSpeed +"m/s,小风平均切入风速"+ currentSmallWindSpeed +"m/s、同比"+ lastYearSmallWindSpeed +"m/," +
-                "有效风时数"+ currentRunningWindHours +"小时、同比:"+ lastYearRunningWindHours +"小时,实际发电电"+ currentPower +"万kmh、同比"+ lastYearPower +"万kwh,同比增长率"+ fdlzzl +"%," +
+                "有效风时数"+ currentRunningWindHours +"小时、同比"+ lastYearRunningWindHours +"小时,实际发电电"+ currentPower +"万kmh、同比"+ lastYearPower +"万kwh,同比增长率"+ fdlzzl +"%," +
                 "各项损失电量累计"+ ssdllj +"万kmh、同比"+ tqssdllj +"万kmh,同比增长率"+ ssdlzzl +"%," +
-                "理论发电量"+ currentTheoryPower +"万kwh,实际发电量与理论发电相差"+ dlxc +"万kwh。";
+                "理论发电量"+ currentTheoryPower +"万kwh,实际发电量与理论发电相差"+ dlxc +"万kwh。";
         map.put("wz1",wz1);
         map.put("wz2",wz2);
         turbineList.sort(Comparator.comparing(TurbineInfoDay::getRecordDate));
@@ -264,63 +278,4 @@ public class ReportService {
                 df.format(calculateGrowthRate(currentValue, lastYearValue))
         );
     }
-    /**
-     * 获取时间月开始时间和月结束时间
-     *
-     * @param time 格式为 "yyyy-MM",例如 "2024-11"
-     * @return 包含开始时间和结束时间的字符串数组
-     */
-    public static String[] generateTimeRange(String time) {
-        // 时间格式化器,用于生成输出格式
-        DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("yyyy-MM");
-        DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-
-        // 解析输入的年月,并补充为该月的第一天
-        String timeWithDay = time + "-01";  // 补充为 "yyyy-MM-01"
-        LocalDate firstDayOfMonth = LocalDate.parse(timeWithDay, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
-
-        // 开始时间:time月的第一天,00:00:00
-        String startTime = firstDayOfMonth.atStartOfDay().format(outputFormatter);
-
-        // 结束时间:time月的最后一天,23:59:59
-        LocalDate lastDayOfMonth = firstDayOfMonth.withDayOfMonth(firstDayOfMonth.lengthOfMonth());
-        String endTime = lastDayOfMonth.atTime(23, 59, 59).format(outputFormatter);
-
-        // 返回字符串数组,包含开始时间和结束时间
-        return new String[]{startTime, endTime};
-    }
-
-    /**
-     * 获取时间去年同期
-     *
-     * @param time
-     * @return
-     */
-    public static String subtractOneYear(String time) {
-        // 定义时间格式化器
-        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
-
-        // 使用 YearMonth 解析和操作年月
-        YearMonth yearMonth = YearMonth.parse(time, formatter);
-        YearMonth result = yearMonth.minusYears(1);
-
-        // 格式化为字符串
-        return result.format(formatter);
-    }
-
-    /**
-     * 获取指定时间段内的天数
-     *
-     * @param timeRange 包含开始时间和结束时间的字符串数组
-     * @return 天数
-     */
-    public static int getDaysInMonth(String[] timeRange) {
-        // 解析开始时间和结束时间
-        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-        LocalDate startDate = LocalDate.parse(timeRange[0], formatter);
-        LocalDate endDate = LocalDate.parse(timeRange[1], formatter);
-
-        // 计算开始时间到结束时间的天数差
-        return (int) (ChronoUnit.DAYS.between(startDate, endDate) + 1);
-    }
 }

+ 20 - 0
runeconomy-xk/src/main/java/com/gyee/runeconomy/service/WindDirection/Deviation.java

@@ -0,0 +1,20 @@
+package com.gyee.runeconomy.service.WindDirection;
+
+public class Deviation {
+    // 计算风速偏差率
+    public static double calculateSpeedDeviation(double fanSpeed, double towerSpeed) {
+        if (towerSpeed == 0) {
+            throw new IllegalArgumentException("Tower speed cannot be zero.");
+        }
+        return Math.abs(towerSpeed - fanSpeed) / towerSpeed * 100;
+    }
+
+    // 计算风向差值
+    public static double calculateDirectionDeviation(double fanDirection, double towerDirection) {
+        double difference = Math.abs(towerDirection - fanDirection);
+        if (difference > 180) {
+            difference = 360 - difference;
+        }
+        return difference;
+    }
+}

+ 54 - 41
runeconomy-xk/src/main/java/com/gyee/runeconomy/service/WindDirection/WindDirectionService.java

@@ -1,24 +1,18 @@
 package com.gyee.runeconomy.service.WindDirection;
 
 import com.gyee.common.contant.ContantXk;
-import com.gyee.common.model.PointData;
 import com.gyee.runeconomy.dto.result.PowerPointData;
 import com.gyee.runeconomy.init.CacheContext;
 import com.gyee.runeconomy.model.auto.ProBasicEquipment;
 import com.gyee.runeconomy.model.auto.ProBasicEquipmentPoint;
 import com.gyee.runeconomy.service.auto.IProBasicEquipmentPointService;
-import com.gyee.runeconomy.util.realtimesource.IEdosUtil;
+import com.gyee.runeconomy.util.DateUtils;
 import com.gyee.runeconomy.util.realtimesource.feign.RemoteServiceBuilder;
 import com.gyee.runeconomy.util.realtimesource.feign.TsDoubleData;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -27,8 +21,6 @@ import java.util.stream.Collectors;
 public class WindDirectionService {
 
     @Resource
-    private IEdosUtil edosUtil;
-    @Resource
     private IProBasicEquipmentPointService proBasicEquipmentPointService;
     @Resource
     private RemoteServiceBuilder remoteService;
@@ -39,7 +31,7 @@ public class WindDirectionService {
      */
     public Object fxRoses(String wtid, String kssj, String jssj) throws Exception {
 
-        Date[] sjcl = sjcl(kssj, jssj);
+        Date[] sjcl = DateUtils.sjcl(kssj, jssj);
 
         // 获取 startDate 和 endDate
         Date startDate = sjcl[0];
@@ -81,7 +73,7 @@ public class WindDirectionService {
             TsDoubleData glData = glMap.get(ts);
 
             PowerPointData data = new PowerPointData();
-            data.setTime(formatTimestamp(ts));
+            data.setTime(DateUtils.formatTimestamp(ts));
             data.setFx(fxData.getDoubleValue());
             data.setSpeed(fsData != null ? fsData.getDoubleValue() : 0);
             data.setMxzt(ztData != null ? (int) ztData.getDoubleValue() : 0);
@@ -100,10 +92,6 @@ public class WindDirectionService {
             map.put("speeedtime", WindALG.speedtimeNewRoses(lslist));//风速
             map.put("roses", WindALG.fxysNewRoses(lslist));//风速、风向
             map.put("count", WindALG.fxNewCountRoses(lslist));//风速、风向
-            int[] count = WindALG.windDeviationRatio(lslist);//风向、对风误差
-            List<Point> scatter = WindALG.windDeviationScatter(lslist);//风速、风向、对风误差
-            map.put("countpcl", count);
-            map.put("scatter", scatter);
             map.put("radar", WindALG.fxRadarRoses(lslist));//对风误差
             map.put("frequency", WindALG.windDeviationPoint(lslist)); //风速、风向、明细状态、功率、偏航角度
             result.add(map);
@@ -111,35 +99,64 @@ public class WindDirectionService {
 
         return result;
     }
+    /**
+     * 对风偏差率
 
-    private Date[] sjcl (String kssj,String jssj) throws ParseException {
+     */
+    public List<Object> windDeviationRatio(String wtid, String kssj, String jssj) throws ParseException {
 
-        // 时间格式,格式是 "yyyy-MM-dd HH:mm"
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        Date[] sjcl = DateUtils.sjcl(kssj, jssj);
 
-        // 将字符串转换为 Date 对象
-        Date kssjDate = sdf.parse(kssj);
-        Date jssjDate = sdf.parse(jssj);
+        // 获取 startDate 和 endDate
+        Date startDate = sjcl[0];
+        Date endDate = sjcl[1];
 
-        // 创建 Calendar 对象
-        Calendar kssjCalendar = Calendar.getInstance();
-        Calendar jssjCalendar = Calendar.getInstance();
+        List<Object> list = new ArrayList<>();
+        // 初始化设备点
+        Map<EquipmentPointType, ProBasicEquipmentPoint> points = Arrays.stream(EquipmentPointType.values())
+                .collect(Collectors.toMap(type -> type, type -> getEquipmentPoint(wtid, type)));
 
-        // 将 Date 设置到 Calendar 对象中
-        kssjCalendar.setTime(kssjDate);
-        jssjCalendar.setTime(jssjDate);
+        // 获取历史数据
+        Map<EquipmentPointType, List<TsDoubleData>> historyData = points.entrySet().stream()
+                .collect(Collectors.toMap(Map.Entry::getKey,
+                        entry -> getSortedHistory(entry.getValue(), startDate.getTime(), endDate.getTime(), 900)));
 
-        // 设置 endDate 为当天的 23:59:59
-        jssjCalendar.set(Calendar.HOUR_OF_DAY, 23);
-        jssjCalendar.set(Calendar.MINUTE, 59);
-        jssjCalendar.set(Calendar.SECOND, 59);
-        jssjCalendar.set(Calendar.MILLISECOND, 999);
+        // 创建时间戳到数据的映射表
+        Map<Long, TsDoubleData> pjfsMap = historyData.get(EquipmentPointType.PJFS).stream()
+                .collect(Collectors.toMap(TsDoubleData::getTs, Function.identity(), (a, b) -> a));
+        Map<Long, TsDoubleData> dfwcMap = historyData.get(EquipmentPointType.DFWC).stream()
+                .collect(Collectors.toMap(TsDoubleData::getTs, Function.identity(), (a, b) -> a));
 
-        // 获取 startDate 和 endDate
-        Date startDate = kssjCalendar.getTime();
-        Date endDate = jssjCalendar.getTime();
-        return new Date[] {startDate, endDate};
+        // 构造结果数据
+        List<PowerPointData> lslist = new ArrayList<>();
+        for (TsDoubleData fxData : historyData.get(EquipmentPointType.PJFX)) {
+            long ts = fxData.getTs();
+            TsDoubleData fsData = pjfsMap.get(ts);
+            TsDoubleData dfwcData = dfwcMap.get(ts);
+
+            PowerPointData data = new PowerPointData();
+            data.setTime(DateUtils.formatTimestamp(ts));
+            data.setFx(fxData.getDoubleValue());
+            data.setSpeed(fsData != null ? fsData.getDoubleValue() : 0);
+            data.setDfwc(dfwcData != null ? dfwcData.getDoubleValue() : 0);
+            lslist.add(data);
+        }
+
+        List<ProBasicEquipment> listObj = CacheContext.wtls.stream().filter(wt -> wtid.equals(wt.getId())).collect(Collectors.toList());
+
+            listObj.forEach(obj -> {
+                int[] count = WindALG.windDeviationRatio(lslist);//风向、对风误差
+                List<Point> scatter = WindALG.windDeviationScatter(lslist);//风速、风向、对风误差
+                Map<String, Object> map = new HashMap<>();
+                map.put("wtId", obj.getAname());
+                map.put("count", count);
+                map.put("scatter", scatter);
+                list.add(map);
+            });
+
+        return list;
     }
+
     // 工具方法:获取设备点
     private ProBasicEquipmentPoint getEquipmentPoint(String wtid, EquipmentPointType type) {
         return proBasicEquipmentPointService.getEquipmentPoint(wtid, type.getCode());
@@ -153,11 +170,7 @@ public class WindDirectionService {
         return data;
     }
 
-    // 工具方法:格式化时间戳
-    private String formatTimestamp(long timestamp) {
-        LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
-        return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
-    }
+
     // 枚举定义
     public enum EquipmentPointType {
         PJFS(ContantXk.CJ_SSFS),

+ 165 - 0
runeconomy-xk/src/main/java/com/gyee/runeconomy/service/WindDirection/WindMachineJsService.java

@@ -0,0 +1,165 @@
+package com.gyee.runeconomy.service.WindDirection;
+
+
+import com.gyee.common.contant.ContantXk;
+import com.gyee.runeconomy.init.CacheContext;
+import com.gyee.runeconomy.model.auto.*;
+import com.gyee.runeconomy.model.vo.WindData;
+import com.gyee.runeconomy.service.auto.IProBasicEquipmentPointService;
+import com.gyee.runeconomy.service.auto.IProBasicPowerstationPointService;
+import com.gyee.runeconomy.service.auto.ITurbineInfoDayService;
+import com.gyee.runeconomy.util.DateUtils;
+import com.gyee.runeconomy.util.realtimesource.feign.RemoteServiceBuilder;
+import com.gyee.runeconomy.util.realtimesource.feign.TsDoubleData;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+public class WindMachineJsService {
+
+    @Resource
+    private IProBasicEquipmentPointService proBasicEquipmentPointService;
+    @Resource
+    private IProBasicPowerstationPointService proBasicPowerstationPointService;
+    @Resource
+    private RemoteServiceBuilder remoteService;
+    @Resource
+    private ITurbineInfoDayService turbineInfoDayService;
+
+
+    public Object machine(String wpid,String time) throws Exception {
+
+        //查询时间-开始结束
+        String[] timeRange = DateUtils.generateTimeRange(time);
+        String kssj = timeRange[0];
+        String jssj = timeRange[1];
+
+        Date[] sjcl = DateUtils.sjcl(kssj, jssj);
+
+        // 获取 startDate 和 endDate
+        Date startDate = sjcl[0];
+        Date endDate = sjcl[1];
+
+        Map<String, Object> map = new HashMap<>();
+
+        List<WindData> list = new ArrayList<>();
+        List<ProBasicEquipment> collect = CacheContext.wtls.stream()
+                .filter(wt -> wpid.equals(wt.getWindpowerstationId()))
+                .collect(Collectors.toList());
+
+        List<ProBasicWeatherStation> subStations = CacheContext.weawpls.stream()
+                .filter(ws -> wpid.equals(ws.getWindpowerstationId()))
+                .collect(Collectors.toList());
+        String weaStation = subStations.isEmpty() ? "NX_FGS_HA01_WS" : subStations.get(0).getId();
+
+        // 初始化场站点
+        String finalWeaStation = weaStation;
+        Map<stationtype, ProBasicPowerstationPoint> czpoints = Arrays.stream(stationtype.values())
+                .collect(Collectors.toMap(type -> type, type -> getpowerstationPoint(finalWeaStation, type)));
+
+        // 获取场站点的历史数据
+        Map<stationtype, TsDoubleData> czhistoryData = czpoints.entrySet().stream()
+                .collect(Collectors.toMap(Map.Entry::getKey, entry -> getczSortedHistory(entry.getValue(), startDate.getTime(), endDate.getTime())));
+
+        //风机循环取数
+        for (ProBasicEquipment eq : collect) {
+            // 初始化设备点
+            Map<EquipmentPointType, ProBasicEquipmentPoint> points = Arrays.stream(EquipmentPointType.values())
+                    .collect(Collectors.toMap(type -> type, type -> getEquipmentPoint(eq.getId(), type)));
+
+            // 获取设备点的历史数据
+            Map<EquipmentPointType, TsDoubleData> historyData = points.entrySet().stream()
+                    .collect(Collectors.toMap(Map.Entry::getKey, entry -> getSortedHistory(entry.getValue(), startDate.getTime(), endDate.getTime())));
+
+            WindData wd = new WindData();
+            wd.setWtid(eq.getId());
+
+            // 获取历史数据
+            TsDoubleData fs = historyData.get(EquipmentPointType.PJFS);
+            TsDoubleData fx = historyData.get(EquipmentPointType.PJFX);
+            TsDoubleData cftfs = czhistoryData.get(stationtype.FCCFTFS90);
+            TsDoubleData cftfx = czhistoryData.get(stationtype.FCCFTFX90);
+
+            // 计算偏差,若某个数据为空则默认为 1.0
+            double fspc = Deviation.calculateSpeedDeviation(
+                    Optional.ofNullable(fs).map(TsDoubleData::getDoubleValue).orElse(1.0),
+                    Optional.ofNullable(cftfs).map(TsDoubleData::getDoubleValue).orElse(1.0)
+            );
+            double fxpc = Deviation.calculateDirectionDeviation(
+                    Optional.ofNullable(fx).map(TsDoubleData::getDoubleValue).orElse(1.0),
+                    Optional.ofNullable(cftfx).map(TsDoubleData::getDoubleValue).orElse(1.0)
+            );
+
+            // 设置 WindData 对象的属性
+            wd.setFanSpeed(Optional.ofNullable(fs).map(TsDoubleData::getDoubleValue).orElse(1.0));
+            wd.setTowerSpeed(Optional.ofNullable(cftfs).map(TsDoubleData::getDoubleValue).orElse(1.0));
+            wd.setPcSpeed(fspc);
+
+            wd.setFanDirection(Optional.ofNullable(fx).map(TsDoubleData::getDoubleValue).orElse(1.0));
+            wd.setTowerDirection(Optional.ofNullable(cftfx).map(TsDoubleData::getDoubleValue).orElse(1.0));
+            wd.setPcDirection(fxpc);
+
+            list.add(wd);
+        }
+
+        return list;
+    }
+
+
+    // 工具方法:获取设备点
+    private ProBasicEquipmentPoint getEquipmentPoint(String wtid, EquipmentPointType type) {
+        return proBasicEquipmentPointService.getEquipmentPoint(wtid, type.getCode());
+    }
+    // 工具方法:获取设备点
+    private ProBasicPowerstationPoint getpowerstationPoint(String wpid, stationtype type) {
+        return proBasicPowerstationPointService.getPowerstationPoint(wpid, type.getCode());
+    }
+
+    // 工具方法:获取风机历史数据
+    private TsDoubleData getSortedHistory(ProBasicEquipmentPoint point, long startTime, long endTime) {
+        if (point == null) return null;
+        Map<String, TsDoubleData> historyStat = remoteService.adapterfd().getHistoryStat(point.getNemCode(), startTime, endTime);
+        TsDoubleData avg = historyStat.get("avg");
+        return avg;
+    }
+    // 工具方法:获取风机历史数据
+    private TsDoubleData getczSortedHistory(ProBasicPowerstationPoint point, long startTime, long endTime) {
+        if (point == null) return null;
+        Map<String, TsDoubleData> data = remoteService.adapterfd().getHistoryStat(point.getNemCode(), startTime, endTime);
+        TsDoubleData avg = data.get("avg");
+        return avg;
+    }
+    // 枚举定义
+    public enum EquipmentPointType {
+        PJFS(ContantXk.CJ_SSFS),
+        PJFX(ContantXk.CJ_FX);
+
+        private final String code;
+
+        EquipmentPointType(String code) {
+            this.code = code;
+        }
+
+        public String getCode() {
+            return code;
+        }
+    }
+    // 枚举定义
+    public enum stationtype {
+        FCCFTFS90(ContantXk.FCCFTFS90),
+        FCCFTFX90(ContantXk.FCCFTFX90);
+
+        private final String code;
+
+        stationtype(String code) {
+            this.code = code;
+        }
+
+        public String getCode() {
+            return code;
+        }
+    }
+}

+ 139 - 0
runeconomy-xk/src/main/java/com/gyee/runeconomy/service/WindDirection/WindMachineService.java

@@ -0,0 +1,139 @@
+package com.gyee.runeconomy.service.WindDirection;
+
+
+import com.gyee.runeconomy.init.CacheContext;
+import com.gyee.runeconomy.model.auto.*;
+import com.gyee.runeconomy.service.auto.ITurbineInfoDayService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+public class WindMachineService {
+
+
+    @Resource
+    private ITurbineInfoDayService turbineInfoDayService;
+
+
+    public Object machine(String wpid) throws Exception {
+        // 获取当前日期
+        int currentYear = LocalDate.now().getYear();
+
+        List<ProBasicEquipment> collect = CacheContext.wtls.stream()
+                .filter(wt -> wpid.equals(wt.getWindpowerstationId()))
+                .collect(Collectors.toList());
+
+        Map<String, List> monthlyData = new LinkedHashMap<>();
+
+        // 风机循环:每台风机每个月一个 WindData 对象
+        for (ProBasicEquipment eq : collect) {
+
+            // 获取当前年份的开始日期(1月1日)
+            LocalDate startOfYear = LocalDate.of(currentYear, 1, 1);
+
+            // 获取当前年份的结束日期(12月31日)
+            LocalDate endOfYear = LocalDate.of(currentYear, 12, 31);
+
+            // 使用 DateTimeFormatter 格式化日期为字符串
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+            // 转换为字符串
+            String startOfYearStr = startOfYear.format(formatter);
+            String endOfYearStr = endOfYear.format(formatter);
+
+            List<TurbineInfoDay> turbineList = turbineInfoDayService.getTurbineList(eq.getId(), startOfYearStr, endOfYearStr);
+            List<String> zb = new ArrayList<>();
+            zb.add("fsavg");
+            zb.add("cftfsavg");
+            zb.add("fspc");
+            zb.add("fxavg");
+            zb.add("cftfxavg");
+            zb.add("fxpc");
+
+            for (String z : zb) {
+                // 循环从1月到12月
+                List<Double> result = new ArrayList<>();
+                String zbwz = "";
+                for (int month = 1; month <= 12; month++) {
+                    // 过滤 turbineList 中的记录,筛选出对应月份的数据
+                    int finalMonth = month;
+                    List<TurbineInfoDay> filteredList = turbineList.stream()
+                            .filter(turbine -> {
+                                // 获取 turbineInfoDay 的 recordDate(类型为 LocalDate)
+                                LocalDate recordDate = turbine.getRecordDate();
+
+                                // 获取记录的月份(注意,月份从 1 开始,1 表示1月)
+                                int recordMonth = recordDate.getMonthValue();
+
+                                // 判断记录的月份是否等于给定的 time
+                                return recordMonth == finalMonth;  // 比较 int 类型的月份和 time 变量
+                            })
+                            .collect(Collectors.toList());
+
+                    //风速
+                    double fsaverage = filteredList.stream()
+                            .mapToDouble(turbineInfoDay -> turbineInfoDay.getPjfs().doubleValue())
+                            .average()
+                            .orElse(0.0);  // 默认值0.0
+
+                    //风向
+                    double fxaverage = filteredList.stream()
+                            .mapToDouble(turbineInfoDay -> turbineInfoDay.getPjfs().doubleValue())
+                            .average()
+                            .orElse(0.0);  // 默认值0.0
+
+                    //测风塔风速
+                    double cftfsaverage = filteredList.stream()
+                            .mapToDouble(turbineInfoDay -> turbineInfoDay.getPjfs().doubleValue())
+                            .average()
+                            .orElse(0.0);  // 默认值0.0
+
+                    //测风塔风向
+                    double cftfxaverage = filteredList.stream()
+                            .mapToDouble(turbineInfoDay -> turbineInfoDay.getPjfs().doubleValue())
+                            .average()
+                            .orElse(0.0);  // 默认值0.0
+
+                    double zbsj = 0.0;
+
+                    if (z.toString().equals("fsavg")) {
+                        // 保留两位小数并转换回 double
+                        zbsj = Double.parseDouble(String.format("%.2f", fsaverage));
+                        zbwz = "风速";
+                    } else if (z.toString().equals("fxavg")) {
+                        zbsj = Double.parseDouble(String.format("%.2f", fxaverage));
+                        zbwz = "风向";
+                    } else if (z.toString().equals("cftfsavg")) {
+                        zbsj = Double.parseDouble(String.format("%.2f", cftfsaverage));
+                        zbwz = "测风塔风速";
+                    } else if (z.toString().equals("cftfxavg")) {
+                        zbsj = Double.parseDouble(String.format("%.2f", cftfxaverage));
+                        zbwz = "测风塔风向";
+                    } else if (z.toString().equals("fspc")) {
+                        zbsj = Deviation.calculateSpeedDeviation(
+                                fsaverage,
+                                cftfsaverage
+                        );
+                        zbwz = "风速偏差";
+                    } else if (z.toString().equals("fxpc")) {
+                        zbsj = Deviation.calculateDirectionDeviation(
+                                fxaverage,
+                                cftfxaverage
+                        );
+                        zbwz = "风向偏差";
+                    }
+                    result.add(zbsj);
+                }
+                monthlyData.put(eq.getAname() + zbwz, result);
+            }
+
+        }
+
+        return monthlyData;
+    }
+}

+ 93 - 0
runeconomy-xk/src/main/java/com/gyee/runeconomy/util/DateUtils.java

@@ -2,6 +2,9 @@ package com.gyee.runeconomy.util;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
 import java.util.Calendar;
 import java.util.Date;
 
@@ -627,7 +630,97 @@ public class DateUtils {
         return diff;
     }
 
+    /**
+     * 获取时间月开始时间和月结束时间
+     *
+     * @param time 格式为 "yyyy-MM",例如 "2024-11"
+     * @return 包含开始时间和结束时间的字符串数组
+     */
+    public static String[] generateTimeRange(String time) {
+        // 时间格式化器,用于生成输出格式
+        DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+        // 解析输入的年月,并补充为该月的第一天
+        String timeWithDay = time + "-01";  // 补充为 "yyyy-MM-01"
+        LocalDate firstDayOfMonth = LocalDate.parse(timeWithDay, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+
+        // 开始时间:time月的第一天,00:00:00
+        String startTime = firstDayOfMonth.atStartOfDay().format(outputFormatter);
+
+        // 结束时间:time月的最后一天,23:59:59
+        LocalDate lastDayOfMonth = firstDayOfMonth.withDayOfMonth(firstDayOfMonth.lengthOfMonth());
+        String endTime = lastDayOfMonth.atTime(23, 59, 59).format(outputFormatter);
+
+        // 返回字符串数组,包含开始时间和结束时间
+        return new String[]{startTime, endTime};
+    }
 
+    /**
+     * 获取时间去年同期
+     *
+     * @param time
+     * @return
+     */
+    public static String subtractOneYear(String time) {
+        // 定义时间格式化器
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
+
+        // 使用 YearMonth 解析和操作年月
+        YearMonth yearMonth = YearMonth.parse(time, formatter);
+        YearMonth result = yearMonth.minusYears(1);
+
+        // 格式化为字符串
+        return result.format(formatter);
+    }
+
+    /**
+     * 获取指定时间段内的天数
+     *
+     * @param timeRange 包含开始时间和结束时间的字符串数组
+     * @return 天数
+     */
+    public static int getDaysInMonth(String[] timeRange) {
+        // 解析开始时间和结束时间
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        LocalDate startDate = LocalDate.parse(timeRange[0], formatter);
+        LocalDate endDate = LocalDate.parse(timeRange[1], formatter);
+
+        // 计算开始时间到结束时间的天数差
+        return (int) (ChronoUnit.DAYS.between(startDate, endDate) + 1);
+    }
+    // 工具方法:格式化时间戳
+    public static String formatTimestamp(long timestamp) {
+        LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
+        return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+    }
+    public static Date[] sjcl (String kssj,String jssj) throws ParseException {
+
+        // 时间格式,格式是 "yyyy-MM-dd HH:mm"
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+
+        // 将字符串转换为 Date 对象
+        Date kssjDate = sdf.parse(kssj);
+        Date jssjDate = sdf.parse(jssj);
+
+        // 创建 Calendar 对象
+        Calendar kssjCalendar = Calendar.getInstance();
+        Calendar jssjCalendar = Calendar.getInstance();
+
+        // 将 Date 设置到 Calendar 对象中
+        kssjCalendar.setTime(kssjDate);
+        jssjCalendar.setTime(jssjDate);
+
+        // 设置 endDate 为当天的 23:59:59
+        jssjCalendar.set(Calendar.HOUR_OF_DAY, 23);
+        jssjCalendar.set(Calendar.MINUTE, 59);
+        jssjCalendar.set(Calendar.SECOND, 59);
+        jssjCalendar.set(Calendar.MILLISECOND, 999);
+
+        // 获取 startDate 和 endDate
+        Date startDate = kssjCalendar.getTime();
+        Date endDate = jssjCalendar.getTime();
+        return new Date[] {startDate, endDate};
+    }
     public static String convertEdnaTime(String pointTime) {
         return convertEdnaTime2(pointTime, false);
     }

+ 6 - 0
runeconomy-xk/src/main/java/com/gyee/runeconomy/util/realtimesource/feign/IAdapterService.java

@@ -2,6 +2,7 @@ package com.gyee.runeconomy.util.realtimesource.feign;
 
 
 import com.gyee.common.model.PointData;
+import com.gyee.runeconomy.util.realtimesource.timeseries.DoubleStatData;
 import feign.Headers;
 import feign.Param;
 import feign.RequestLine;
@@ -36,6 +37,11 @@ public interface IAdapterService {
     @RequestLine("GET /history/section?tagNames={tagNames}&ts={ts}")
     Map<String, TsDoubleData> getHistorySection(@Param(value = "tagNames") String tagNames, @Param(value = "ts") long ts);
 
+    @Headers({"Content-Type: application/json", "Accept: application/json"})
+    @RequestLine("GET /history/stat2?tagName={tagName}&startTs={startTs}&endTs={endTs}")
+    Map<String, TsDoubleData> getHistoryStat(@Param(value = "tagName") String tagName, @Param(value = "startTs") long startTs,
+                                               @Param(value = "endTs") long endTs);
+
 
     /**
      * 获取等间隔历史数据