Prechádzať zdrojové kódy

完成精益场景相关功能

wukai 3 mesiacov pred
rodič
commit
3d839e30e7

+ 13 - 13
jjt-admin/src/test/java/com/jjt/dye/DyeTest.java

@@ -28,7 +28,7 @@ public class DyeTest {
     private IDyeCalcHourService calcHourService;
     @Resource
     private IDyeHourLineService hourLineService;
-    String st = "2025-11-04";
+    String st = "2025-11-07";
     String ed = "2025-11-07";
     LocalDate localDate = LocalDate.parse(st);
     LocalDate endDate = LocalDate.parse(ed);
@@ -37,18 +37,18 @@ public class DyeTest {
     @Test
     public void calc() {
         iotService.setToken();
-//        calcHourService.calc(start.toLocalDate(), 18);
-        LocalDateTime end = LocalDateTime.of(endDate.plusDays(1), LocalTime.MIN).plusHours(23);
-        LocalDateTime curr = LocalDateTime.now();
-        if (end.isAfter(curr)) {
-            end = curr.minusHours(1);
-        }
-        do {
-            int i = start.getHour();
-            System.err.println(start.toLocalDate().toString() + "\t" + i);
-            calcHourService.calc(start.toLocalDate(), i);
-            start = start.plusHours(1);
-        } while (!start.isAfter(end));
+        calcHourService.calc(start.toLocalDate(), 15);
+//        LocalDateTime end = LocalDateTime.of(endDate.plusDays(1), LocalTime.MIN).plusHours(23);
+//        LocalDateTime curr = LocalDateTime.now();
+//        if (end.isAfter(curr)) {
+//            end = curr.minusHours(1);
+//        }
+//        do {
+//            int i = start.getHour();
+//            System.err.println(start.toLocalDate().toString() + "\t" + i);
+//            calcHourService.calc(start.toLocalDate(), i);
+//            start = start.plusHours(1);
+//        } while (!start.isAfter(end));
     }
 
     @Test

+ 125 - 8
jjt-biz/src/main/java/com/jjt/calc/controller/TwinCalcStopController.java

@@ -3,6 +3,8 @@ package com.jjt.calc.controller;
 import com.jjt.calc.domain.TwinCalcStop;
 import com.jjt.calc.service.ITwinCalcStopService;
 import com.jjt.calc.vo.StopCalcVO;
+import com.jjt.calc.vo.StopDetail;
+import com.jjt.calc.vo.StopVO;
 import com.jjt.calc.vo.TpsVO;
 import com.jjt.common.annotation.Log;
 import com.jjt.common.core.controller.BaseController;
@@ -12,9 +14,9 @@ import com.jjt.common.core.page.TableDataInfo;
 import com.jjt.common.enums.BusinessType;
 import com.jjt.common.utils.poi.ExcelUtil;
 import com.jjt.utils.Tools;
+import com.jjt.utils.TrendAnalysis;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
@@ -42,14 +44,130 @@ public class TwinCalcStopController extends BaseController {
      * 查询停机数据统计列表
      */
     @ApiOperation("查询停机数据统计列表")
-    @PreAuthorize("@ss.hasPermi('calc:calcStop:list')")
     @GetMapping("/list")
     public TableDataInfo list(TwinCalcStop twinCalcStop) {
-        startPage();
         List<TwinCalcStop> list = twinCalcStopService.selectTwinCalcStopList(twinCalcStop);
         return getDataTable(list);
     }
 
+    @ApiOperation("故障停机统计")
+    @GetMapping("/gz-stop")
+    public AjaxResult gzStop(TwinCalcStop twinCalcStop) {
+        Date d;
+        if (twinCalcStop.getRemark() != null && !twinCalcStop.getRemark().isEmpty()) {
+            try {
+                int days = Integer.parseInt(twinCalcStop.getRemark());
+                d = new Date(System.currentTimeMillis() - (long) days * 24 * 60 * 60 * 1000);
+            } catch (NumberFormatException e) {
+                // 如果转换失败,则使用默认的90天
+                d = new Date(System.currentTimeMillis() - 90L * 24 * 60 * 60 * 1000);
+            }
+        } else {
+            d = new Date(System.currentTimeMillis() - 90L * 24 * 60 * 60 * 1000);
+        }
+        twinCalcStop.setWorkDay(d);
+        List<StopDetail> list = twinCalcStopService.selectTwinCalcStopDetailList(twinCalcStop);
+        List<StopVO> resultList = new ArrayList<>();
+
+        //按照deviceId分组
+        Map<Long, List<StopDetail>> deviceGroup = list.stream().collect(Collectors.groupingBy(StopDetail::getDeviceId, LinkedHashMap::new, Collectors.toList()));
+
+        // 统计各种趋势结果的数量
+        Map<String, Long> trendCounts = new HashMap<>();
+        trendCounts.put("明显上升", 0L);
+        trendCounts.put("上升", 0L);
+        trendCounts.put("平稳", 0L);
+        trendCounts.put("下降", 0L);
+        trendCounts.put("明显下降", 0L);
+
+        deviceGroup.forEach((deviceId, dataList) -> {
+            if (dataList.size() < 3) {
+                return;
+            }
+            List<Integer> times = dataList.stream()
+                    .map(StopDetail::getTimes)
+                    .collect(Collectors.toList());
+            StopVO vo = new StopVO();
+            vo.setDeviceId(deviceId);
+            vo.setDeviceName(dataList.get(0).getDeviceName());
+            vo.setList(dataList);
+            vo.setDataNum(times.size());
+            TrendAnalysis.analysisDetail(vo, times);
+            // 更新趋势计数
+            trendCounts.put(vo.getResult(), trendCounts.get(vo.getResult()) + 1);
+
+            vo.setResult(vo.getResult());
+            resultList.add(vo);
+        });
+
+        Map<String, Object> statistics = new HashMap<>();
+        statistics.put("totalDevices", resultList.size());
+        statistics.put("trendCounts", trendCounts);
+        resultList.get(0).setAdditionalInfo(statistics);
+
+        resultList.sort(Comparator.comparing(StopVO::getDeviceId));
+        return success(resultList).put("statistics", statistics);
+    }
+
+    @ApiOperation("断纱停机统计")
+    @GetMapping("/ds-stop")
+    public AjaxResult dsStop(TwinCalcStop twinCalcStop) {
+        Date d;
+        if (twinCalcStop.getRemark() != null && !twinCalcStop.getRemark().isEmpty()) {
+            try {
+                int days = Integer.parseInt(twinCalcStop.getRemark());
+                d = new Date(System.currentTimeMillis() - (long) days * 24 * 60 * 60 * 1000);
+            } catch (NumberFormatException e) {
+                // 如果转换失败,则使用默认的90天
+                d = new Date(System.currentTimeMillis() - 90L * 24 * 60 * 60 * 1000);
+            }
+        } else {
+            d = new Date(System.currentTimeMillis() - 90L * 24 * 60 * 60 * 1000);
+        }
+        twinCalcStop.setWorkDay(d);
+        List<StopDetail> list = twinCalcStopService.selectTwinCalcStopDetailList(twinCalcStop);
+        List<StopVO> resultList = new ArrayList<>();
+
+        //按照deviceId分组
+        Map<Long, List<StopDetail>> deviceGroup = list.stream().collect(Collectors.groupingBy(StopDetail::getDeviceId, LinkedHashMap::new, Collectors.toList()));
+
+        // 统计各种趋势结果的数量
+        Map<String, Long> trendCounts = new HashMap<>();
+        trendCounts.put("明显上升", 0L);
+        trendCounts.put("上升", 0L);
+        trendCounts.put("平稳", 0L);
+        trendCounts.put("下降", 0L);
+        trendCounts.put("明显下降", 0L);
+
+        deviceGroup.forEach((deviceId, dataList) -> {
+            if (dataList.size() < 3) {
+                return;
+            }
+            List<Integer> times = dataList.stream()
+                    .map(StopDetail::getTimes)
+                    .collect(Collectors.toList());
+            StopVO vo = new StopVO();
+            vo.setDeviceId(deviceId);
+            vo.setDeviceName(dataList.get(0).getDeviceName());
+            vo.setList(dataList);
+            vo.setDataNum(times.size());
+            TrendAnalysis.analysisDetail(vo, times);
+            // 更新趋势计数
+            trendCounts.put(vo.getResult(), trendCounts.get(vo.getResult()) + 1);
+
+            vo.setResult(vo.getResult());
+            resultList.add(vo);
+        });
+
+        Map<String, Object> statistics = new HashMap<>();
+        statistics.put("totalDevices", resultList.size());
+        statistics.put("trendCounts", trendCounts);
+        resultList.get(0).setAdditionalInfo(statistics);
+
+        resultList.sort(Comparator.comparing(StopVO::getDeviceId));
+        return success(resultList).put("statistics", statistics);
+    }
+
     @ApiOperation("停机统计")
     @GetMapping("/calc")
     public R calc(TwinCalcStop twinCalcStop) {
@@ -153,7 +271,6 @@ public class TwinCalcStopController extends BaseController {
      * 导出停机数据统计列表
      */
     @ApiOperation("导出停机数据统计列表")
-    @PreAuthorize("@ss.hasPermi('calc:calcStop:export')")
     @Log(title = "停机数据统计", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
     public void export(HttpServletResponse response, TwinCalcStop twinCalcStop) {
@@ -166,7 +283,7 @@ public class TwinCalcStopController extends BaseController {
      * 获取停机数据统计详细信息
      */
     @ApiOperation("获取停机数据统计详细信息")
-    @PreAuthorize("@ss.hasPermi('calc:calcStop:query')")
+    //@PreAuthorize("@ss.hasPermi('calc:calcStop:query')")
     @GetMapping(value = "/{id}")
     public AjaxResult getInfo(@PathVariable("id") Long id) {
         return success(twinCalcStopService.selectTwinCalcStopById(id));
@@ -176,7 +293,7 @@ public class TwinCalcStopController extends BaseController {
      * 新增停机数据统计
      */
     @ApiOperation("新增停机数据统计")
-    @PreAuthorize("@ss.hasPermi('calc:calcStop:add')")
+    //@PreAuthorize("@ss.hasPermi('calc:calcStop:add')")
     @Log(title = "停机数据统计", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody TwinCalcStop twinCalcStop) {
@@ -187,7 +304,7 @@ public class TwinCalcStopController extends BaseController {
      * 修改停机数据统计
      */
     @ApiOperation("修改停机数据统计")
-    @PreAuthorize("@ss.hasPermi('calc:calcStop:edit')")
+    //@PreAuthorize("@ss.hasPermi('calc:calcStop:edit')")
     @Log(title = "停机数据统计", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody TwinCalcStop twinCalcStop) {
@@ -198,7 +315,7 @@ public class TwinCalcStopController extends BaseController {
      * 删除停机数据统计
      */
     @ApiOperation("删除停机数据统计")
-    @PreAuthorize("@ss.hasPermi('calc:calcStop:remove')")
+    //@PreAuthorize("@ss.hasPermi('calc:calcStop:remove')")
     @Log(title = "停机数据统计", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids) {

+ 4 - 0
jjt-biz/src/main/java/com/jjt/calc/domain/TwinCalcStop.java

@@ -110,4 +110,8 @@ public class TwinCalcStop extends BaseEntity {
     @Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date updatedTime;
 
+    @ApiModelProperty("工作天")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date workDay;
+
 }

+ 15 - 7
jjt-biz/src/main/java/com/jjt/calc/mapper/TwinCalcStopMapper.java

@@ -3,10 +3,11 @@ package com.jjt.calc.mapper;
 import java.util.List;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.jjt.calc.domain.TwinCalcStop;
+import com.jjt.calc.vo.StopDetail;
 
 /**
  * 停机数据统计Mapper接口
- * 
+ *
  * @author wukai
  * @date 2025-01-18
  */
@@ -14,7 +15,7 @@ public interface TwinCalcStopMapper extends BaseMapper<TwinCalcStop>
 {
     /**
      * 查询停机数据统计
-     * 
+     *
      * @param id 停机数据统计主键
      * @return 停机数据统计
      */
@@ -22,7 +23,7 @@ public interface TwinCalcStopMapper extends BaseMapper<TwinCalcStop>
 
     /**
      * 查询停机数据统计列表
-     * 
+     *
      * @param twinCalcStop 停机数据统计
      * @return 停机数据统计集合
      */
@@ -30,7 +31,7 @@ public interface TwinCalcStopMapper extends BaseMapper<TwinCalcStop>
 
     /**
      * 新增停机数据统计
-     * 
+     *
      * @param twinCalcStop 停机数据统计
      * @return 结果
      */
@@ -38,7 +39,7 @@ public interface TwinCalcStopMapper extends BaseMapper<TwinCalcStop>
 
     /**
      * 修改停机数据统计
-     * 
+     *
      * @param twinCalcStop 停机数据统计
      * @return 结果
      */
@@ -46,7 +47,7 @@ public interface TwinCalcStopMapper extends BaseMapper<TwinCalcStop>
 
     /**
      * 删除停机数据统计
-     * 
+     *
      * @param id 停机数据统计主键
      * @return 结果
      */
@@ -54,9 +55,16 @@ public interface TwinCalcStopMapper extends BaseMapper<TwinCalcStop>
 
     /**
      * 批量删除停机数据统计
-     * 
+     *
      * @param ids 需要删除的数据主键集合
      * @return 结果
      */
     public int deleteTwinCalcStopByIds(Long[] ids);
+    /**
+     * 查询统计次数详情
+     *
+     * @param twinCalcStop 查询条件
+     * @return 停机详情列表
+     */
+    List<StopDetail> selectTwinCalcStopDetailList(TwinCalcStop twinCalcStop);
 }

+ 7 - 0
jjt-biz/src/main/java/com/jjt/calc/service/ITwinCalcStopService.java

@@ -6,6 +6,7 @@ import java.util.List;
 
 import com.jjt.biz.domain.TwinDevice;
 import com.jjt.calc.domain.TwinCalcStop;
+import com.jjt.calc.vo.StopDetail;
 
 /**
  * 停机数据统计Service接口
@@ -87,4 +88,10 @@ public interface ITwinCalcStopService {
      * @return 查询列表
      */
     List<TwinCalcStop> selectTwinCalcStopListByDate(Date sTime, Date eTime, int type);
+    /**
+     * 按开始和结束时间查询数据--停机类型)
+     *
+     * @return 停机详情列表
+     */
+    List<StopDetail> selectTwinCalcStopDetailList(TwinCalcStop twinCalcStop);
 }

+ 12 - 0
jjt-biz/src/main/java/com/jjt/calc/service/impl/TwinCalcStopServiceImpl.java

@@ -9,6 +9,7 @@ import com.jjt.calc.mapper.TwinCalcStopMapper;
 import com.jjt.calc.service.ITwinCalcHourService;
 import com.jjt.calc.service.ITwinCalcStopService;
 import com.jjt.calc.service.ITwinRecordStopService;
+import com.jjt.calc.vo.StopDetail;
 import com.jjt.common.utils.DateUtils;
 import com.jjt.common.utils.StringUtils;
 import com.jjt.common.utils.bean.BeanUtils;
@@ -297,4 +298,15 @@ public class TwinCalcStopServiceImpl implements ITwinCalcStopService {
         stop.setParams(params);
         return selectTwinCalcStopList(stop);
     }
+
+    /**
+     * 查询统计次数详情
+     *
+     * @param twinCalcStop 查询条件
+     * @return 停机详情列表
+     */
+    @Override
+    public List<StopDetail> selectTwinCalcStopDetailList(TwinCalcStop twinCalcStop) {
+        return twinCalcStopMapper.selectTwinCalcStopDetailList(twinCalcStop);
+    }
 }

+ 28 - 0
jjt-biz/src/main/java/com/jjt/calc/vo/StopDetail.java

@@ -0,0 +1,28 @@
+package com.jjt.calc.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 并发停机视图
+ *
+ * @author wukai
+ * @date 2025/3/15 02:51
+ */
+@ApiModel(value = "StopCalcVO", description = "停机统计视图")
+@Data
+public class StopDetail {
+    @ApiModelProperty("设备ID")
+    private Long deviceId;
+    @ApiModelProperty("设备名称")
+    private String deviceName;
+    @ApiModelProperty("时间")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date date;
+    @ApiModelProperty("停机次数")
+    private Integer times;
+}

+ 35 - 0
jjt-biz/src/main/java/com/jjt/calc/vo/StopVO.java

@@ -0,0 +1,35 @@
+package com.jjt.calc.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 并发停机视图
+ *
+ * @author wukai
+ * @date 2025/3/15 02:51
+ */
+@ApiModel(value = "StopCalcVO", description = "停机统计视图")
+@Data
+public class StopVO {
+    @ApiModelProperty("设备ID")
+    private Long deviceId;
+    @ApiModelProperty("设备名称")
+    private String deviceName;
+    @ApiModelProperty("数据量")
+    private Integer dataNum;
+    @ApiModelProperty("斜率")
+    private Double slope;
+    @ApiModelProperty("标准差")
+    private Double stdDev;
+    @ApiModelProperty("分析结果")
+    private String result;
+    @ApiModelProperty("停机详情")
+    List<StopDetail> list;
+    @ApiModelProperty("附加信息")
+    private Map<String, Object> additionalInfo;
+}

+ 184 - 0
jjt-biz/src/main/java/com/jjt/utils/TrendAnalysis.java

@@ -0,0 +1,184 @@
+package com.jjt.utils;
+
+import com.jjt.calc.vo.StopVO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * TrendAnalysis$
+ *
+ * @author wukai
+ * @date 2025/11/8 03:47
+ */
+public class TrendAnalysis {
+    /**
+     * 使用times进行趋势分析
+     *
+     * @param times 时间序列数据
+     * @return 趋势分析结果
+     */
+    public static String analysis(List<Integer> times) {
+        // 计算线性回归斜率
+        double slope = calculateSlope(times);
+
+        // 计算标准差
+        double stdDev = calculateStandardDeviation(times);
+
+        // 判断趋势类型
+        String trend = determineTrend(slope, stdDev, times);
+
+        return trend;
+    }
+
+    /**
+     * 使用times进行趋势分析并返回详细结果
+     *
+     * @param vo    停机信息
+     * @param times 时间序列数据
+     * @return 趋势分析结果
+     */
+    public static void analysisDetail(StopVO vo, List<Integer> times) {
+        // 计算线性回归斜率
+        double slope = calculateSlope(times);
+
+        // 计算标准差
+        double stdDev = calculateStandardDeviation(times);
+
+        // 判断趋势类型
+        String trend = determineTrend(slope, stdDev, times);
+        vo.setSlope(slope);
+        vo.setStdDev(stdDev);
+        vo.setResult(trend);
+        vo.setDataNum(times.size());
+    }
+
+    /**
+     * 批量趋势分析并返回统计结果
+     *
+     * @param dataList 多组时间序列数据
+     * @return 各类趋势的统计结果
+     */
+    public static java.util.Map<String, Object> batchAnalysisDetail(java.util.List<List<Integer>> dataList) {
+        java.util.Map<String, Object> result = new java.util.HashMap<>();
+
+        if (dataList == null || dataList.isEmpty()) {
+            result.put("error", "数据为空,无法进行趋势分析");
+            return result;
+        }
+
+        // 统计各类趋势的数量
+        java.util.Map<String, Integer> trendCounts = new java.util.HashMap<>();
+        trendCounts.put("明显上升", 0);
+        trendCounts.put("上升", 0);
+        trendCounts.put("平稳", 0);
+        trendCounts.put("下降", 0);
+        trendCounts.put("明显下降", 0);
+
+        int total = dataList.size();
+        int validDataCount = 0;
+
+        // 分析每组数据
+        for (java.util.List<Integer> times : dataList) {
+            if (times != null && times.size() >= 2) {
+                double slope = calculateSlope(times);
+                double stdDev = calculateStandardDeviation(times);
+                String trend = determineTrend(slope, stdDev, times);
+
+                trendCounts.put(trend, trendCounts.get(trend) + 1);
+                validDataCount++;
+            }
+        }
+
+        result.put("total", total);
+        result.put("validDataCount", validDataCount);
+        result.put("invalidDataCount", total - validDataCount);
+        result.put("trendCounts", trendCounts);
+
+        return result;
+    }
+
+    /**
+     * 计算线性回归斜率
+     *
+     * @param times 时间序列数据
+     * @return 斜率
+     */
+    private static double calculateSlope(List<Integer> times) {
+        int n = times.size();
+        double sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
+
+        for (int i = 0; i < n; i++) {
+            double x = i;
+            double y = times.get(i).doubleValue();
+            sumX += x;
+            sumY += y;
+            sumXY += x * y;
+            sumXX += x * x;
+        }
+
+        // 线性回归公式: slope = (n*sumXY - sumX*sumY) / (n*sumXX - sumX*sumX)
+        return (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
+    }
+
+    /**
+     * 计算标准差
+     *
+     * @param times 时间序列数据
+     * @return 标准差
+     */
+    private static double calculateStandardDeviation(List<Integer> times) {
+        if (times.size() <= 1) {
+            return 0;
+        }
+
+        // 计算平均值
+        double mean = times.stream().mapToInt(Integer::intValue).average().orElse(0);
+
+        // 计算方差
+        double variance = times.stream()
+                .mapToDouble(time -> Math.pow(time - mean, 2))
+                .sum() / (times.size() - 1);
+
+        // 标准差是方差的平方根
+        return Math.sqrt(variance);
+    }
+
+    /**
+     * 根据斜率和标准差判断趋势
+     *
+     * @param slope  斜率
+     * @param stdDev 标准差
+     * @param times  时间序列数据
+     * @return 趋势描述
+     */
+    private static String determineTrend(double slope, double stdDev, List<Integer> times) {
+        // 计算平均值
+        double mean = times.stream().mapToInt(Integer::intValue).average().orElse(0);
+
+        // 计算变异系数(标准差/平均值)
+        double coefficientOfVariation = mean != 0 ? stdDev / mean : 0;
+
+        // 定义阈值
+        // 5%的平均值作为斜率阈值
+        double slopeThreshold = Math.abs(mean * 0.05);
+        // 变异系数阈值
+        double cvThreshold = 0.1;
+
+        if (Math.abs(slope) < slopeThreshold) {
+            return "平稳";
+        } else if (slope > 0) {
+            if (coefficientOfVariation > cvThreshold) {
+                return "明显上升";
+            } else {
+                return "上升";
+            }
+        } else {
+            if (coefficientOfVariation > cvThreshold) {
+                return "明显下降";
+            } else {
+                return "下降";
+            }
+        }
+    }
+}

+ 15 - 0
jjt-biz/src/main/resources/mapper/calc/TwinCalcStopMapper.xml

@@ -18,11 +18,13 @@
         <result property="updatedBy" column="UPDATED_BY"/>
         <result property="updatedTime" column="UPDATED_TIME"/>
         <result property="remark" column="REMARK"/>
+        <result property="workDay" column="WORK_DAY"/>
     </resultMap>
 
     <sql id="selectTwinCalcStopVo">
         select ID,
                DEVICE_ID,
+               WORK_DAY,
                DATA_DATE,
                HOUR,
                START_TIME,
@@ -59,12 +61,25 @@
                 and (data_date &lt;= #{params.eTime} and hour>=7) and (end_time>=#{params.sTime} and hour &lt; 7)
             </if>
         </where>
+        order by DATA_DATE desc,HOUR desc
     </select>
 
     <select id="selectTwinCalcStopById" parameterType="Long" resultMap="TwinCalcStopResult">
         <include refid="selectTwinCalcStopVo"/>
         where ID = #{id}
     </select>
+    <select id="selectTwinCalcStopDetailList" resultType="com.jjt.calc.vo.StopDetail">
+        SELECT A.*, B.DEVICE_NAME
+        FROM (SELECT WORK_DAY as date, DEVICE_ID, STOP_TYPE, COUNT(*) TIMES
+              FROM TWIN_CALC_STOP
+              WHERE STOP_TYPE = #{stopType}
+                AND WORK_DAY >= #{workDay}
+                <if test="deviceId != null ">and DEVICE_ID = #{deviceId}</if>
+              GROUP BY WORK_DAY, DEVICE_ID, STOP_TYPE) A,
+             TWIN_DEVICE B
+        WHERE A.DEVICE_ID = B.DEVICE_ID
+        ORDER BY date, DEVICE_ID, STOP_TYPE
+    </select>
 
     <insert id="insertTwinCalcStop" parameterType="TwinCalcStop">
         insert into TWIN_CALC_STOP