Pārlūkot izejas kodu

水质报警 手动触发逻辑调整:完全独立,并且报警相关和决策均修改

王苗苗 1 dienu atpakaļ
vecāks
revīzija
8f5d046f77

+ 617 - 2
slibra-admin/src/main/java/com/slibra/web/controller/business/HandleDataController.java

@@ -8,23 +8,40 @@ import cn.hutool.poi.excel.ExcelUtil;
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
+import com.alibaba.fastjson2.JSONWriter;
+import com.google.protobuf.ByteString;
 import com.slibra.business.domain.*;
 import com.slibra.business.mapper.*;
+import com.slibra.business.req.ChatReq;
+import com.slibra.business.req.GenerateArgs;
+import com.slibra.business.req.PolicyReq;
 import com.slibra.business.res.ChartBasic;
+import com.slibra.business.res.ShowValueCSBasic;
+import com.slibra.business.res.ShowValueJSBasic;
 import com.slibra.business.service.ITXinyiForecastComparisonService;
 import com.slibra.common.DecimalUtils;
+import com.slibra.common.config.BigModelConfig;
+import com.slibra.common.core.domain.DecisionReq;
 import com.slibra.common.core.domain.TXinyiDaily;
 import com.slibra.common.core.controller.BaseController;
 import com.slibra.common.enums.BusinessEnum;
+import com.slibra.common.exception.ServiceException;
 import com.slibra.common.utils.DateUtils;
 import com.slibra.common.utils.StringUtils;
+import com.slibra.common.utils.format.WaterFormat;
+import com.slibra.common.utils.uuid.IdUtils;
+import com.slibra.quartz.business.JsCsFormatData;
 import com.slibra.quartz.task.AsyncTask;
 import com.slibra.quartz.task.RyTask;
+import inference.InferenceAPIsServiceGrpc;
+import inference.PredictionResponse;
+import inference.PredictionsRequest;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.session.ExecutorType;
 import org.apache.ibatis.session.SqlSession;
 import org.apache.ibatis.session.SqlSessionFactory;
-import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
@@ -38,6 +55,8 @@ import java.util.*;
 
 import static com.slibra.common.constant.MyConstants.*;
 import static com.slibra.common.constant.MyConstants.BigDecimal_100;
+import static com.slibra.common.enums.BusinessEnum.BigModelWarningTypeRemarkEnum.ONE_SH_WARNING;
+import static com.slibra.common.enums.BusinessEnum.BigModelWarningTypeRemarkEnum.ZERO_SZ_WARNING;
 import static com.slibra.common.enums.BusinessEnum.WarningCategoryEnum.*;
 
 /**
@@ -89,6 +108,18 @@ public class HandleDataController extends BaseController
     @Autowired
     private ITXinyiForecastComparisonService xinyiForecastComparisonService;
 
+    @Autowired
+    private TXinyiWarningRecordMapper xinyiWarningRecordMapper;
+
+    @Autowired
+    private BigModelConfig bigModelConfig;
+
+    @Autowired
+    private JsCsFormatData jsCsFormatData;
+
+    @Autowired
+    private TXinyiChatRecordMapper xinyiChatRecordMapper;
+
 
     public static final String[] queryTags = {"信义污水厂JS_COD_Value","信义污水厂JS_PH_Value","信义污水厂JS_SS_Value","信义污水厂JS_ZL_Value","信义污水厂JS_ZA_Value","信义污水厂JS_AD_Value","信义污水厂JS_T_Value","信义污水厂进水泵房液位","信义污水厂出水瞬时流量","信义污水厂升级出水COD","信义污水厂升级出水PH","信义污水厂升级出水SS","信义污水厂升级出水TN","信义污水厂升级出水TP","信义污水厂升级出水氨氮","信义污水厂AIT202_Value","信义污水厂AIT203_Value","信义污水厂AIT207_Value","信义污水厂AIT206_Value","信义污水厂AIT209_Value","信义污水厂AIT210_Value","信义污水厂进水TDS","信义污水厂FT101_Value","信义污水厂SWCHHYHLB1_R_Value","信义污水厂SWCHHYHLB2_R_Value","信义污水厂SWCHHYHLB3_R_Value","信义污水厂SWCHHYHLB4_R_Value","信义污水厂SWCHHYHLB5_R_Value","信义污水厂SWCHHYHLB6_R_Value","信义污水厂SWCWNHLB1_R_Value","信义污水厂SWCWNHLB2_R_Value","信义污水厂SWCWNHLB3_R_Value","信义污水厂SWCWNHLB4_R_Value","信义污水厂SWCWNHLB5_R_Value","信义污水厂GFJ1_R_Value","信义污水厂GFJ2_R_Value","信义污水厂GFJ3_R_Value","信义污水厂GFJ4_R_Value","信义污水厂GFJ5_R_Value","信义污水厂GFJ6_R_Value","信义污水厂GFJ1_KQLL_Value","信义污水厂GFJ2_KQLL_Value","信义污水厂GFJ3_KQLL_Value","信义污水厂GFJ4_KQLL_Value","信义污水厂GFJ5_KQLL_Value","信义污水厂GFJ6_KQLL_Value"};
 
@@ -2229,12 +2260,596 @@ public class HandleDataController extends BaseController
         List<TXinyiIndustry> tXinyiIndustries = this.xinyiIndustryMapper.selectTXinyiIndustryList(TXinyiIndustry.builder().timeBegin(dateBegin).timeEnd(dateEnd).build());
         if(!CollectionUtils.isEmpty(tXinyiIndustries)){
             for (TXinyiIndustry tXinyiIndustry : tXinyiIndustries) {
-                asyncTask.handleSZWarningNew(tXinyiIndustry, normConfig);
+//                asyncTask.handleSZWarningNew(tXinyiIndustry, normConfig);
+                //2025年05月08日15:47:39 只处理调用决策相关的报警
+                this.handleSZWarningNew(tXinyiIndustry, normConfig);
             }
         }
         return "success";
     }
 
+    private void handleSZWarningNew(TXinyiIndustry tXinyiIndustry, TXinyiNormConfig normConfig) {
+        BigDecimal jsSlq = tXinyiIndustry.getJsSlq();
+        BigDecimal jsslSjz = normConfig.getJsslSjz();
+        BigDecimal jszdSjz = normConfig.getJszdSjz();
+        BigDecimal jsadSjz = normConfig.getJsadSjz();
+        //进水总氮超标报警
+        BigDecimal jsTn = tXinyiIndustry.getJsTn();
+        //进水氨氮超标报警
+        BigDecimal jsNh3 = tXinyiIndustry.getJsNh3();
+        if(!Objects.isNull(jszdSjz) && jszdSjz.compareTo(BigDecimal.ZERO) > 0)
+            handleXinYiWarningRecordJSOriginZD(jszdSjz, jsTn, BusinessEnum.WarningCategoryEnum.JS_ZD.getCode(), tXinyiIndustry, normConfig, getCwrwfhzByDetail(jsSlq, tXinyiIndustry.getJsTn(), jsslSjz, normConfig.getJszdSjz()));
+        if(!Objects.isNull(jsadSjz) && jsadSjz.compareTo(BigDecimal.ZERO) > 0)
+            handleXinYiWarningRecordJSOriginNh3(jsadSjz, jsNh3, BusinessEnum.WarningCategoryEnum.JS_AD.getCode(), tXinyiIndustry, normConfig, getCwrwfhzByDetail(jsSlq, tXinyiIndustry.getJsNh3(), jsslSjz, normConfig.getJsadSjz()));
+    }
+
+
+    /**
+     * 不同指标报警规则不一样,全部区分开来处理,此方法为:氨氮
+     * 通过输入的值 生成对应类型的报警对象(进水)- 氨氮
+     *
+     * @param jsBzz
+     * @param currentVal
+     * @param category
+     * @param tXinyiIndustry
+     * @param normConfig
+     * @param cwrwfhz
+     * @return
+     */
+    private void handleXinYiWarningRecordJSOriginNh3(BigDecimal jsBzz, BigDecimal currentVal, String category, TXinyiIndustry tXinyiIndustry, TXinyiNormConfig normConfig, BigDecimal cwrwfhz) {
+
+        String testHour = tXinyiIndustry.getTestHour();
+        //这里也不能用当前日期,要用对应检测时间
+//        Date nowDate = DateUtils.getNowDate();
+        Date nowDate = DateUtils.parseDate(testHour);
+
+        List<TXinyiIndustry> tXinyiIndustries = this.xinyiIndustryMapper.selectNIndustryBeforeHour(INT_8, testHour);
+        if(CollectionUtils.isEmpty(tXinyiIndustries) || tXinyiIndustries.size() < INT_8){
+            log.error("处理生化报警时,获取最近的8条工业库数据失败~~~~~~~");
+            return ;
+        }
+
+        //最开始的工艺报警  (超标准工艺报警) 2025年04月16日15:40:23 改成只有超标准才会报警,并且具体逻辑做拆分
+        normalIndustryWarningJsNh3(jsBzz, currentVal, category, tXinyiIndustry, normConfig, cwrwfhz, nowDate, tXinyiIndustries);
+
+    }
+
+
+    /**
+     * 最开始的工艺报警  (超标准工艺报警) -- 进水氨氮
+     * @param jsBzz
+     * @param currentVal
+     * @param category
+     * @param tXinyiIndustry
+     * @param normConfig
+     * @param cwrwfhz
+     * @param nowDate
+     * @param tXinyiIndustries
+     */
+    private void normalIndustryWarningJsNh3(BigDecimal jsBzz, BigDecimal currentVal, String category, TXinyiIndustry tXinyiIndustry, TXinyiNormConfig normConfig, BigDecimal cwrwfhz, Date nowDate, List<TXinyiIndustry> tXinyiIndustries) {
+        //新的判断进水氨氮是否超标
+        TXinyiWarningRecord tXinyiWarningRecord = existsNormalJsNh3(jsBzz, currentVal, category, cwrwfhz, tXinyiIndustries, normConfig, nowDate);
+
+        //最近3条输出 连续升高趋势 和 新增的报警都需要
+        //当前状态正常 需要查询历史有无正在报警的数据,如果有,将报警状态改完2(系统自动关闭)
+        List<TXinyiWarningRecord> tXinyiWarningRecords = this.xinyiWarningRecordMapper.selectTXinyiWarningRecordList(TXinyiWarningRecord.builder().type(0).category(category).status(0).build());
+        if(Objects.isNull(tXinyiWarningRecord)){//数据正常,无告警信息
+            if(!CollectionUtils.isEmpty(tXinyiWarningRecords)){
+                log.info( "{}:现在恢复正常,历史报警数据为{}", category,JSON.toJSONString(tXinyiWarningRecords));
+                for (TXinyiWarningRecord xinyiWarningRecord : tXinyiWarningRecords) {
+                    closeWarning(xinyiWarningRecord);
+                }
+            }
+        }else{//有告警信息
+            if(CollectionUtils.isEmpty(tXinyiWarningRecords)){//之前没有告警记录
+                //保存到数据库中
+                //2024年7月15日11:06:16 因为只有一个告警记录,但是如果一直报警,现在报警时间取的是更新时间,兼容第一次报警处理
+                tXinyiWarningRecord.setUpdateTime(nowDate);
+                this.xinyiWarningRecordMapper.insertTXinyiWarningRecord(tXinyiWarningRecord);
+                //继续调用决策
+                this.handleDecision(tXinyiWarningRecord, tXinyiIndustry, normConfig, false, nowDate);
+            }else{
+                log.info("{}:之前已经有过告警记录了,且还是继续报警,无需重复添加报警,但是决策仍然要调用", category);
+                for (TXinyiWarningRecord xinyiWarningRecord : tXinyiWarningRecords) {//理论上只有一个的
+                    //2024年7月5日10:45:24 逻辑调整:更新一直报警的那条记录的显示值
+                    xinyiWarningRecord.setWarningVal(tXinyiWarningRecord.getWarningVal());
+//                        xinyiWarningRecord.setDesignVal(tXinyiWarningRecord.getDesignVal());
+//                        tXinyiWarningRecord.setControlVal(tXinyiWarningRecord.getControlVal());
+                    xinyiWarningRecord.setTime(tXinyiWarningRecord.getTime());
+                    xinyiWarningRecord.setUpdateTime(nowDate);
+                    xinyiWarningRecord.setUpdateBy(WARNING_DEFAULT_CREATE);
+                    //2024年7月15日11:07:33 报警的级别也要重新计算并更新
+                    xinyiWarningRecord.setLevel(tXinyiWarningRecord.getLevel());
+                    //2024年7月31日09:20:47 报警的原因(超标还是超管控也需要更新)
+                    xinyiWarningRecord.setReason(tXinyiWarningRecord.getReason());
+                    //更新数据库
+                    this.xinyiWarningRecordMapper.updateTXinyiWarningRecord(xinyiWarningRecord);
+                    //继续调用决策
+                    this.handleDecision(xinyiWarningRecord, tXinyiIndustry, normConfig, false, nowDate);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 根据新的规则,判断进水氨氮是否超标,超标了则返回报警对象;没有超标,返回空对象 -- 进水氨氮
+     *
+     * @param jsBzz
+     * @param currentVal
+     * @param category
+     * @param cwrwfhz
+     * @param tXinyiIndustries
+     * @param normConfig
+     * @param nowDate
+     * @return
+     */
+    private TXinyiWarningRecord existsNormalJsNh3(BigDecimal jsBzz, BigDecimal currentVal, String category, BigDecimal cwrwfhz, List<TXinyiIndustry> tXinyiIndustries, TXinyiNormConfig normConfig, Date nowDate) {
+        boolean flag = false;
+        TXinyiIndustry tXinyiIndustry = tXinyiIndustries.get(INDEX_0);
+        BigDecimal jsNh30 = tXinyiIndustry.getJsNh3();
+        BigDecimal jsNh31 = tXinyiIndustries.get(INDEX_1).getJsNh3();
+        BigDecimal jsNh32 = tXinyiIndustries.get(INDEX_2).getJsNh3();
+        BigDecimal jsNh33 = tXinyiIndustries.get(INDEX_3).getJsNh3();
+        if(!Objects.isNull(jsNh30) && jsNh30.compareTo(jsBzz) > 0 && !Objects.isNull(jsNh31) && jsNh31.compareTo(jsBzz) > 0
+                && !Objects.isNull(jsNh32) && jsNh32.compareTo(jsBzz) > 0 && !Objects.isNull(jsNh33) && jsNh33.compareTo(jsBzz) > 0)
+            flag = true;//最近4个都超标
+        if(!Objects.isNull(jsNh30) && jsNh30.compareTo(jsBzz) > 0 && !Objects.isNull(jsNh31) && jsNh30.compareTo(jsNh31) >= 0
+                && !Objects.isNull(jsNh32) && jsNh31.compareTo(jsNh32) >= 0)
+            flag = true;//最新的超标了并且连续3个值都有升高趋势
+        BigDecimal jsCod = tXinyiIndustry.getJsCod();
+        BigDecimal jsTn = tXinyiIndustry.getJsTn();
+        BigDecimal jsTp = tXinyiIndustry.getJsTp();
+        if(!Objects.isNull(jsNh30) && jsNh30.compareTo(jsBzz) > 0 && (!Objects.isNull(jsCod) && jsCod.compareTo(BigDecimal.valueOf(630)) > 0
+                || !Objects.isNull(jsTn) && jsTn.compareTo(BigDecimal.valueOf(65)) > 0 || !Objects.isNull(jsTp) && jsTp.compareTo(BigDecimal.valueOf(7.1)) > 0))
+            flag = true;
+        if(!flag)
+            return null;
+        //存在符合的报警情况,生成报警对象
+        TXinyiWarningRecord tXinyiWarningRecord = new TXinyiWarningRecord();
+        /*String category = BusinessEnum.WarningCategoryEnum.CS_AD.getCode();*/
+        tXinyiWarningRecord.setStatus(0);
+        tXinyiWarningRecord.setType(0);
+        tXinyiWarningRecord.setCategory(category);
+        tXinyiWarningRecord.setTime(nowDate);
+        tXinyiWarningRecord.setWarningVal(currentVal);
+        tXinyiWarningRecord.setDesignVal(jsBzz);
+//        tXinyiWarningRecord.setControlVal(csGkz);
+        tXinyiWarningRecord.setCreateBy(WARNING_DEFAULT_CREATE);
+        tXinyiWarningRecord.setCreateTime(nowDate);
+        tXinyiWarningRecord.setRemark(ZERO_SZ_WARNING.getCode());
+        //2025年02月10日14:45:08 下面是新增加的字段
+        tXinyiWarningRecord.setWaterType(BusinessEnum.WaterTypeEnum.JS.getCode());
+        //超污染物限制:计算公式为:当前氨氮/氨氮设计值 或者 当前氨氮/氨氮管控值(智能工单使用)  这里是出水的 【需要根据超标还是超管控,具体计算】
+        //超污染物负荷值:计算公式为:当前水量与氨氮乘积  /  设计水量与设计进水氨氮的乘积(智能工单使用)
+        tXinyiWarningRecord.setCwrwfhz(cwrwfhz);//2025年02月14日10:48:32 具体的值由上游传过来
+        tXinyiWarningRecord.setReason(category + CHAOBIAO_WARNING);
+        tXinyiWarningRecord.setLevel(WARNING_LEVEL_ONE);
+        tXinyiWarningRecord.setSymbol(BusinessEnum.WarningSymbolEnum.CBZZ.getCode());
+        tXinyiWarningRecord.setCwrwxz(getCwrwxzByDetail(currentVal, normConfig, category, true));//根据当前值和类型以及是否超标处理
+
+        return tXinyiWarningRecord;
+    }
+
+
+    /**
+     * 不同指标报警规则不一样,全部区分开来处理,此方法为:总氮
+     * 通过输入的值 生成对应类型的报警对象(进水)- 总氮
+     *
+     * @param jsBzz
+     * @param currentVal
+     * @param category
+     * @param tXinyiIndustry
+     * @param normConfig
+     * @param cwrwfhz
+     * @return
+     */
+    private void handleXinYiWarningRecordJSOriginZD(BigDecimal jsBzz, BigDecimal currentVal, String category, TXinyiIndustry tXinyiIndustry, TXinyiNormConfig normConfig, BigDecimal cwrwfhz) {
+
+        String testHour = tXinyiIndustry.getTestHour();
+        //这里也不能用当前日期,要用对应检测时间
+//        Date nowDate = DateUtils.getNowDate();
+        Date nowDate = DateUtils.parseDate(testHour);
+
+        //2025年05月08日15:49:42 这里获取某个时间段之前的数据,新增方法,不影响原来的逻辑
+        List<TXinyiIndustry> tXinyiIndustries = this.xinyiIndustryMapper.selectNIndustryBeforeHour(INT_8, testHour);
+        if(CollectionUtils.isEmpty(tXinyiIndustries) || tXinyiIndustries.size() < INT_8){
+            log.error("处理生化报警时,获取最近的8条工业库数据失败~~~~~~~");
+            return ;
+        }
+
+        //最开始的工艺报警  (超标准工艺报警) 2025年04月16日15:40:23 改成只有超标准才会报警,并且具体逻辑做拆分
+        normalIndustryWarningJsTD(jsBzz, currentVal, category, tXinyiIndustry, normConfig, cwrwfhz, nowDate, tXinyiIndustries);
+    }
+
+
+
+    /**
+     * 最开始的工艺报警  (超标准工艺报警) -- 进水总氮
+     * @param jsBzz
+     * @param currentVal
+     * @param category
+     * @param tXinyiIndustry
+     * @param normConfig
+     * @param cwrwfhz
+     * @param nowDate
+     * @param tXinyiIndustries
+     */
+    private void normalIndustryWarningJsTD(BigDecimal jsBzz, BigDecimal currentVal, String category, TXinyiIndustry tXinyiIndustry, TXinyiNormConfig normConfig, BigDecimal cwrwfhz, Date nowDate, List<TXinyiIndustry> tXinyiIndustries) {
+        //新的判断进水氨氮是否超标
+        TXinyiWarningRecord tXinyiWarningRecord = existsNormalJsZD(jsBzz, currentVal, category, cwrwfhz, tXinyiIndustries, normConfig, nowDate);
+
+        //最近3条输出 连续升高趋势 和 新增的报警都需要
+        //当前状态正常 需要查询历史有无正在报警的数据,如果有,将报警状态改完2(系统自动关闭)
+        List<TXinyiWarningRecord> tXinyiWarningRecords = this.xinyiWarningRecordMapper.selectTXinyiWarningRecordList(TXinyiWarningRecord.builder().type(0).category(category).status(0).build());
+        if(Objects.isNull(tXinyiWarningRecord)){//数据正常,无告警信息
+            if(!CollectionUtils.isEmpty(tXinyiWarningRecords)){
+                log.info( "{}:现在恢复正常,历史报警数据为{}", category,JSON.toJSONString(tXinyiWarningRecords));
+                for (TXinyiWarningRecord xinyiWarningRecord : tXinyiWarningRecords) {
+                    closeWarning(xinyiWarningRecord);
+                }
+            }
+        }else{//有告警信息
+            if(CollectionUtils.isEmpty(tXinyiWarningRecords)){//之前没有告警记录
+                //保存到数据库中
+                //2024年7月15日11:06:16 因为只有一个告警记录,但是如果一直报警,现在报警时间取的是更新时间,兼容第一次报警处理
+                tXinyiWarningRecord.setUpdateTime(nowDate);
+                this.xinyiWarningRecordMapper.insertTXinyiWarningRecord(tXinyiWarningRecord);
+                //继续调用决策
+                this.handleDecision(tXinyiWarningRecord, tXinyiIndustry, normConfig, false, nowDate);
+            }else{
+                log.info("{}:之前已经有过告警记录了,且还是继续报警,无需重复添加报警,但是决策仍然要调用", category);
+                for (TXinyiWarningRecord xinyiWarningRecord : tXinyiWarningRecords) {//理论上只有一个的
+                    //2024年7月5日10:45:24 逻辑调整:更新一直报警的那条记录的显示值
+                    xinyiWarningRecord.setWarningVal(tXinyiWarningRecord.getWarningVal());
+//                        xinyiWarningRecord.setDesignVal(tXinyiWarningRecord.getDesignVal());
+//                        tXinyiWarningRecord.setControlVal(tXinyiWarningRecord.getControlVal());
+                    xinyiWarningRecord.setTime(tXinyiWarningRecord.getTime());
+                    xinyiWarningRecord.setUpdateTime(nowDate);
+                    xinyiWarningRecord.setUpdateBy(WARNING_DEFAULT_CREATE);
+                    //2024年7月15日11:07:33 报警的级别也要重新计算并更新
+                    xinyiWarningRecord.setLevel(tXinyiWarningRecord.getLevel());
+                    //2024年7月31日09:20:47 报警的原因(超标还是超管控也需要更新)
+                    xinyiWarningRecord.setReason(tXinyiWarningRecord.getReason());
+                    //更新数据库
+                    this.xinyiWarningRecordMapper.updateTXinyiWarningRecord(xinyiWarningRecord);
+                    //继续调用决策
+                    this.handleDecision(xinyiWarningRecord, tXinyiIndustry, normConfig, false, nowDate);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 根据新的规则,判断进水氨氮是否超标,超标了则返回报警对象;没有超标,返回空对象 -- 进水总氮
+     *
+     * @param jsBzz
+     * @param currentVal
+     * @param category
+     * @param cwrwfhz
+     * @param tXinyiIndustries
+     * @param normConfig
+     * @param nowDate
+     * @return
+     */
+    private TXinyiWarningRecord existsNormalJsZD(BigDecimal jsBzz, BigDecimal currentVal, String category, BigDecimal cwrwfhz, List<TXinyiIndustry> tXinyiIndustries, TXinyiNormConfig normConfig, Date nowDate) {
+        boolean flag = false;
+        TXinyiIndustry tXinyiIndustry = tXinyiIndustries.get(INDEX_0);
+        BigDecimal jsTn0 = tXinyiIndustry.getJsTn();
+        BigDecimal jsTn1 = tXinyiIndustries.get(INDEX_1).getJsTn();
+        BigDecimal jsTn2 = tXinyiIndustries.get(INDEX_2).getJsTn();
+        BigDecimal jsTn3 = tXinyiIndustries.get(INDEX_3).getJsTn();
+        if(!Objects.isNull(jsTn0) && jsTn0.compareTo(jsBzz) > 0 && !Objects.isNull(jsTn1) && jsTn1.compareTo(jsBzz) > 0
+                && !Objects.isNull(jsTn2) && jsTn2.compareTo(jsBzz) > 0 && !Objects.isNull(jsTn3) && jsTn3.compareTo(jsBzz) > 0)
+            flag = true;//最近4个都超标
+        if(!Objects.isNull(jsTn0) && jsTn0.compareTo(jsBzz) > 0 && !Objects.isNull(jsTn1) && jsTn0.compareTo(jsTn1) >= 0
+                && !Objects.isNull(jsTn2) && jsTn1.compareTo(jsTn2) >= 0)
+            flag = true;//最新的超标了并且连续3个值都有升高趋势
+        BigDecimal jsCod = tXinyiIndustry.getJsCod();
+        BigDecimal jsNh3 = tXinyiIndustry.getJsNh3();
+        BigDecimal jsTp = tXinyiIndustry.getJsTp();
+        if(!Objects.isNull(jsTn0) && jsTn0.compareTo(jsBzz) > 0 && (!Objects.isNull(jsCod) && jsCod.compareTo(BigDecimal.valueOf(630)) > 0
+                || !Objects.isNull(jsNh3) && jsNh3.compareTo(BigDecimal.valueOf(58)) > 0 || !Objects.isNull(jsTp) && jsTp.compareTo(BigDecimal.valueOf(7.1)) > 0))
+            flag = true;
+        if(!flag)
+            return null;
+        //存在符合的报警情况,生成报警对象
+        TXinyiWarningRecord tXinyiWarningRecord = new TXinyiWarningRecord();
+        /*String category = BusinessEnum.WarningCategoryEnum.CS_AD.getCode();*/
+        tXinyiWarningRecord.setStatus(0);
+        tXinyiWarningRecord.setType(0);
+        tXinyiWarningRecord.setCategory(category);
+        tXinyiWarningRecord.setTime(nowDate);
+        tXinyiWarningRecord.setWarningVal(currentVal);
+        tXinyiWarningRecord.setDesignVal(jsBzz);
+//        tXinyiWarningRecord.setControlVal(csGkz);
+        tXinyiWarningRecord.setCreateBy(WARNING_DEFAULT_CREATE);
+        tXinyiWarningRecord.setCreateTime(nowDate);
+        tXinyiWarningRecord.setRemark(ZERO_SZ_WARNING.getCode());
+        //2025年02月10日14:45:08 下面是新增加的字段
+        tXinyiWarningRecord.setWaterType(BusinessEnum.WaterTypeEnum.JS.getCode());
+        //超污染物限制:计算公式为:当前氨氮/氨氮设计值 或者 当前氨氮/氨氮管控值(智能工单使用)  这里是出水的 【需要根据超标还是超管控,具体计算】
+        //超污染物负荷值:计算公式为:当前水量与氨氮乘积  /  设计水量与设计进水氨氮的乘积(智能工单使用)
+        tXinyiWarningRecord.setCwrwfhz(cwrwfhz);//2025年02月14日10:48:32 具体的值由上游传过来
+        tXinyiWarningRecord.setReason(category + CHAOBIAO_WARNING);
+        tXinyiWarningRecord.setLevel(WARNING_LEVEL_ONE);
+        tXinyiWarningRecord.setSymbol(BusinessEnum.WarningSymbolEnum.CBZZ.getCode());
+        tXinyiWarningRecord.setCwrwxz(getCwrwxzByDetail(currentVal, normConfig, category, true));//根据当前值和类型以及是否超标处理
+
+        return tXinyiWarningRecord;
+    }
+
+
+    /**
+     * 调用大模型获取决策结果 并同时记录对应信息到聊天记录表中
+     *
+     * @param tXinyiWarningRecord
+     * @param tXinyiIndustry
+     * @param normConfig
+     * @param isSpecial           机器人化验库,需要把总磷和总氮的值特殊处理一下
+     * @param nowDate
+     */
+    private void handleDecision(TXinyiWarningRecord tXinyiWarningRecord, TXinyiIndustry tXinyiIndustry, TXinyiNormConfig normConfig, boolean isSpecial, Date nowDate) {
+        log.info("进入了调⽤大模型决策接口");
+        ChatReq chatReq = new ChatReq();
+//        StringBuilder sb = new StringBuilder();
+        //大模型结果 放入一个结合中
+        List<String> resultData = new ArrayList<>();
+        //决策和问答不一样 没有历史的概念 所以sessionId都是新的  次数都是1
+        String sessionId = IdUtils.simpleUUID();
+//        String feedback = chatReq.getFeedback();
+        //决策请求的业务参数
+//        List<HashMap<String, Object>> list = this.xinyiIndustryMapper.selectLast10RecordsForDecision();
+        //2024年5月21日15:23:07 这里不能用关联查询处理,日报要获取最新的一条而不是今日的数据。
+        List<DecisionReq> decisionReqs = this.xinyiIndustryMapper.selectLast10RecordsForDecisionOnlyIndustryByHour(tXinyiIndustry.getTestHour());
+        if(!CollectionUtils.isEmpty(decisionReqs)){
+            //处理日报数据
+//            TXinyiDaily daily = this.xinyiDailyMapper.selectNewestData();
+            TXinyiDaily daily = this.xinyiDailyMapper.getDetailByDay(tXinyiIndustry.getTestDate());
+
+            for (DecisionReq decisionReq : decisionReqs) {
+                if(!Objects.isNull(daily)){
+                    WaterFormat.getWaterDecimationData(decisionReq, daily);
+                }
+            }
+        }
+//        String rows = JSON.toJSONString(decisionReqs, JSONWriter.Feature.WriteNulls);
+        // 获取输出流
+        ManagedChannel channel = null;
+        String dataJson = "";
+        try {
+            channel = ManagedChannelBuilder.forAddress(bigModelConfig.getIp(), bigModelConfig.getPort())
+                    .usePlaintext()
+                    .build();
+            InferenceAPIsServiceGrpc.InferenceAPIsServiceBlockingStub stub = InferenceAPIsServiceGrpc.newBlockingStub(channel);
+//            dataJson = "{\"bot_id\":\"b00001\",\"exp_id\":\"721\",\"norm\":\"" + tXinyiWarningRecord.getCategory() + "\",\"feedback\":" + feedback + ",\"session_id\":" + "\"" + sessionId + "\"" + ",\"generate_args\":{\"max_new_tokens\":1024,\"max_length\":4096,\"num_beams\":1,\"do_sample\":true,\"top_p\":0.7,\"temperature\":0.95},\"extra\":{\"rows\":" +  rows + "}}";
+            //2024年6月24日17:59:17  优化,不再拼接JSON字符串
+            PolicyReq policyReq = new PolicyReq();
+            //2024年7月11日17:57:53 因为化验室的改了  但是 调用决策还得用原来的几个值
+            String category = tXinyiWarningRecord.getCategory();
+            /*if(ROBOT_XSY_1.getCode().equalsIgnoreCase(category) || ROBOT_XSY_2.getCode().equalsIgnoreCase(category)){
+                category = CS_ZD.getCode();
+            } else if (ROBOT_ANDAN_1.getCode().equalsIgnoreCase(category) || ROBOT_ANDAN_2.getCode().equalsIgnoreCase(category)) {
+                category = CS_AD.getCode();
+            }else if (ROBOT_ECCZLSY.getCode().equalsIgnoreCase(category)) {
+                category = CS_ZL.getCode();
+            }*/
+            //2024年08月25日15:40:31 处理数据映射,改成字母,但是显示的还是中文
+            category = this.parseCategory(category);
+            policyReq.setNorm(category);
+            HashMap<String, Object> hashMap = new HashMap<>();
+            policyReq.setFeedback(hashMap);//不能传null
+            policyReq.setSimulate(hashMap);//不能传null
+            policyReq.setSessionId(sessionId);
+            //2024年7月5日13:24:10 temperature做区分
+            GenerateArgs generateArgs = new GenerateArgs();
+            generateArgs.setTemperature(bigModelConfig.getTemperature());
+            policyReq.setGenerateArgs(generateArgs);
+            HashMap<String, Object> map = new HashMap<>();
+            map.put("rows", decisionReqs);
+            //2024年6月25日14:16:05 增加报警是管控值报警还是标准值报警
+            if(WARNING_LEVEL_ONE.equals(tXinyiWarningRecord.getLevel()) || WARNING_LEVEL_TWO.equals(tXinyiWarningRecord.getLevel()))
+                map.put("source", "bzz");
+            else
+                map.put("source", "gkz");
+            //2025年05月08日16:04:18 调用历史决策要传该参数
+            map.put("current_time", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS , tXinyiWarningRecord.getCreateTime()));
+            policyReq.setExtra(map);
+            dataJson = JSON.toJSONString(policyReq, JSONWriter.Feature.WriteNulls);
+//            log.info("~~~~~~请求大模型的决策的参数为{}", dataJson);
+            PredictionsRequest request = PredictionsRequest.newBuilder()
+                    .setModelName("slibra_bot")
+                    .putInput("method", ByteString.copyFrom("decision_stream", "utf-8"))//推理
+                    .putInput("data", ByteString.copyFrom(dataJson, "utf-8"))
+                    .buildPartial();
+            Iterator<PredictionResponse> predictions = stub.streamPredictions(request);
+            while (predictions.hasNext()) {
+                String responseStr = predictions.next().getPrediction().toStringUtf8();
+//                log.info("决策流式返回的结果是{}", responseStr);
+
+                //2024年5月25日16:37:16  按照大模型返回的类型解析数据
+                String biz = JSON.parseObject(responseStr).getString("biz");
+                if(BusinessEnum.BigModelBizEnum.OK.getCode().equals(biz)){
+                    log.info("结尾语句并且是非JSON,无需处理");
+                    //结束语句也流式输出,但是并不记录下来  2024年5月24日11:15:23 也不返回前端
+                    /*outputStream.write(responseStr.getBytes());
+                    outputStream.flush();*/
+                }else if(BusinessEnum.BigModelBizEnum.DECISION_DEBUGGER.getCode().equals(biz)){
+                    log.info("中间过程,目前只打印日志,不记录数据,也不返回给前端,返回数据为{}", responseStr);
+                    //结束语句也流式输出,但是并不记录下来  2024年5月24日11:15:23 也不返回前端
+                    /*outputStream.write(responseStr.getBytes());
+                    outputStream.flush();*/
+                }else{//其他 要么错误 要么alert 要么出的报告
+//                    sb.append(responseStr);
+                    resultData.add(responseStr);
+                }
+            }
+        } catch (Exception e) {
+//            throw new RuntimeException(e);
+            log.error("定时任务处理告警调用决策异常,异常信息为{}", JSON.toJSONString(e));
+            resultData.add("{\"biz\":\"ERROR\",\"message\":\"大模型分析数据异常,请稍后再试\"}");
+        } finally {
+//            log.info("决策最终要保存的数据是{}", JSON.toJSONString(resultData));
+            //保存聊天记录
+            //将问答更新到数据库中
+            chatReq.setSessionId(sessionId);
+            chatReq.setType(1);//0问答 1决策
+            chatReq.setModule(3);
+            /*String userId = SecurityUtils.getUserId().toString();
+            String username = SecurityUtils.getUsername();*/
+            chatReq.setUserId(WARNING_DEFAULT_CREATE);
+            String showVal = this.buildShowValue(tXinyiWarningRecord, tXinyiIndustry, normConfig, nowDate);
+            chatReq.setShowVal(showVal);//前端展示的数据和提问的数据不一致
+            chatReq.setQuestion(dataJson);
+            chatReq.setAnswer(JSON.toJSONString(resultData));
+            chatReq.setWarningId(String.valueOf(tXinyiWarningRecord.getId()));
+            chatReq.setCounts(1);//问答次数
+
+            chatReq.setCreateBy(WARNING_DEFAULT_CREATE);
+            chatReq.setCreateTime(DateUtils.getNowDate());
+            this.xinyiChatRecordMapper.insertTXinyiChatRecord(chatReq);
+            // 关闭输出流
+            if(!Objects.isNull(channel))
+                channel.shutdown();
+        }
+    }
+
+
+    private String buildShowValue(TXinyiWarningRecord tXinyiWarningRecord, TXinyiIndustry tXinyiIndustry, TXinyiNormConfig normConfig, Date nowDate) {
+        JSONObject result = new JSONObject();
+//        JSONObject basic = new JSONObject();
+        Integer status = tXinyiWarningRecord.getStatus();
+//        Date warningTime = tXinyiWarningRecord.getTime();
+        //2025年02月21日17:09:29 因为报警时间改过一次逻辑,以前是最开始的那个时候,后来每次报警都更新它,所以这里需要的是创建时间
+        Date warningTime = tXinyiWarningRecord.getCreateTime();
+        String remark = tXinyiWarningRecord.getRemark();
+        //2025年02月26日16:17:10 这里计算的截止时间可能是当前时间,也可能是报警结束时间,需要判断
+        Date endDate = null;
+        //判断类型 如果结束了 截止时间就是告警截止时间;  如果正在报警 截止时间就是当前时间
+        if(0 == tXinyiWarningRecord.getStatus() || 3 == tXinyiWarningRecord.getStatus())
+            endDate = DateUtils.getNowDate();
+        else
+            endDate = tXinyiWarningRecord.getOffTime();
+        long count = DateUtils.differentHoursByLocalDateTime(warningTime, endDate) + 1;
+        String category = tXinyiWarningRecord.getCategory();
+        /*basic.put("title", tXinyiWarningRecord.getReason());
+        basic.put("报警时间", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM ,warningTime));
+        basic.put("报警值", tXinyiWarningRecord.getWarningVal());
+        basic.put("标准值", tXinyiWarningRecord.getDesignVal());
+        basic.put("管控值", tXinyiWarningRecord.getControlVal());
+        basic.put("报警次数", count > MAX_COUNT ? MAX_COUNT_STR : count);
+        if(tXinyiWarningRecord.getType() != 2)
+            basic.put("状态", status == 0 ? "报警中" : status == 1 ? "用户关闭" : status == 2 ? "系统关闭" : "应急处理中");
+        else
+            basic.put("状态", status == 0 ? "预警中" : "已完成");
+        //2024年5月27日14:04:22  额外返回2个字段 [管控值 和 告警级别]  返回的json没有数据是因为value没有值
+//        basic.put("管控值", tXinyiWarningRecord.getControlVal());
+        basic.put("告警级别", tXinyiWarningRecord.getLevel());*/
+        //2024年6月25日16:00:18 进出水展示的不一样
+        //2024年7月15日10:57:31 因为新增了化验室的3个指标,也是出水数据
+        if(category.contains(CHUSHUI)
+                || ROBOT_ECCZLSY.getCode().equalsIgnoreCase(category)
+                || ROBOT_XSY_1.getCode().equalsIgnoreCase(category)
+                || ROBOT_XSY_2.getCode().equalsIgnoreCase(category)
+                || ROBOT_ANDAN_1.getCode().equalsIgnoreCase(category)
+                || ROBOT_ANDAN_2.getCode().equalsIgnoreCase(category)
+                || ROBOT_CS_AD.getCode().equalsIgnoreCase(category)){//出水的展示
+            ShowValueCSBasic showValueCSBasic = new ShowValueCSBasic();
+            if(ZERO_SZ_WARNING.getCode().equals(remark)){//水质报警
+                showValueCSBasic.setH(ZAIXIANYIBIAO);
+//                showValueCSBasic.setF(tXinyiWarningRecord.getLevel());
+                showValueCSBasic.setF(tXinyiWarningRecord.getSymbolDesc());
+            }else if(ONE_SH_WARNING.getCode().equals(remark)){//生化报警
+                showValueCSBasic.setH(ZAIXIANYIBIAO);
+            }else if(BusinessEnum.BigModelWarningTypeRemarkEnum.TWO_YC_WARNING.getCode().equals(remark)){//预测报警
+                showValueCSBasic.setH(YVCE);
+//                showValueCSBasic.setF(tXinyiWarningRecord.getLevel());
+            }else {//机器人化验室报警(特殊的水质报警)
+                showValueCSBasic.setH(LIANXUJIANCE);
+//                showValueCSBasic.setF(tXinyiWarningRecord.getLevel());
+                showValueCSBasic.setF(tXinyiWarningRecord.getSymbolDesc());
+            }
+            //通用的
+            //        showValueBasic.setA(tXinyiWarningRecord.getReason());
+//            showValueCSBasic.setB(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM ,warningTime));
+            showValueCSBasic.setB(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM ,nowDate));
+            showValueCSBasic.setC(DecimalUtils.getAbsAndScale(tXinyiWarningRecord.getWarningVal(), INT_2));
+            showValueCSBasic.setD(DecimalUtils.getAbsAndScale(tXinyiWarningRecord.getControlVal(), INT_2));
+            showValueCSBasic.setE(DecimalUtils.getAbsAndScale(tXinyiWarningRecord.getDesignVal(), INT_2));
+            showValueCSBasic.setG(count > MAX_COUNT ? MAX_COUNT_STR : String.valueOf(count));
+            if(tXinyiWarningRecord.getType() != 2)
+                showValueCSBasic.setI(status == 0 ? "报警中" : status == 1 ? "用户关闭" : status == 2 ? "系统关闭" : "应急处理中");
+            else
+                showValueCSBasic.setI(status == 0 ? "预警中" : "已完成");
+            result.put("basic", showValueCSBasic);
+        }else{//进水的展示
+            ShowValueJSBasic showValueJSBasic = new ShowValueJSBasic();
+            if(ZERO_SZ_WARNING.getCode().equals(remark)){//水质报警
+                showValueJSBasic.setH(ZAIXIANYIBIAO);
+//                showValueJSBasic.setF(tXinyiWarningRecord.getLevel());
+                showValueJSBasic.setF(tXinyiWarningRecord.getSymbolDesc());
+            }else if(ONE_SH_WARNING.getCode().equals(remark)){//生化报警
+                showValueJSBasic.setH(ZAIXIANYIBIAO);
+            }else if(BusinessEnum.BigModelWarningTypeRemarkEnum.TWO_YC_WARNING.getCode().equals(remark)){//预测报警
+                showValueJSBasic.setH(YVCE);
+//                showValueCSBasic.setF(tXinyiWarningRecord.getLevel());
+            }else {//机器人化验室报警(特殊的 水质报警)
+                showValueJSBasic.setH(LIANXUJIANCE);
+//                showValueJSBasic.setF(tXinyiWarningRecord.getLevel());
+                showValueJSBasic.setF(tXinyiWarningRecord.getSymbolDesc());
+            }
+            //通用的
+            //        showValueBasic.setA(tXinyiWarningRecord.getReason());
+            showValueJSBasic.setB(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM ,tXinyiWarningRecord.getTime()));
+            showValueJSBasic.setC(DecimalUtils.getAbsAndScale(tXinyiWarningRecord.getWarningVal(), INT_2));
+            showValueJSBasic.setE(DecimalUtils.getAbsAndScale(tXinyiWarningRecord.getDesignVal(), INT_2));
+            showValueJSBasic.setG(count > MAX_COUNT ? MAX_COUNT_STR : String.valueOf(count));
+            if(tXinyiWarningRecord.getType() != 2)
+                showValueJSBasic.setI(status == 0 ? "报警中" : status == 1 ? "用户关闭" : status == 2 ? "系统关闭" : "应急处理中");
+            else
+                showValueJSBasic.setI(status == 0 ? "预警中" : "已完成");
+            result.put("basic", showValueJSBasic);
+        }
+        JSONObject jsData = jsCsFormatData.getJsonObject(tXinyiIndustry, normConfig);//进水数据
+        result.put("jsData", jsData);
+        JSONObject csData = jsCsFormatData.getCsonObject(tXinyiIndustry, normConfig);//出水数据
+        result.put("csData", csData);
+//        return JSON.toJSONString(result, JSONWriter.Feature.WriteNulls);
+        return JSON.toJSONString(result);
+    }
+
+
+    private String parseCategory(String category) {
+        switch (category) {
+            case "进水COD" : return "js_cod";
+            case "进水总磷" : return "js_tp";
+            case "进水总氮" : return "js_tn";
+            case "进水氨氮" : return "js_andan";
+            case "进水SS" : return "js_ss";
+            case "出水COD" : return "cs_cod";
+            case "出水总磷" : return "cs_tp";
+            case "出水总氮" : return "cs_tn";
+            case "出水氨氮" : return "cs_andan";
+            case "出水SS" : return "cs_ss";
+            case "#1好氧硝酸盐" : return "cs_tn_lxjc1";
+            case "#2好氧硝酸盐" : return "cs_tn_lxjc2";
+            case "出水氨氮连续检测" : return "cs_andan_lxjc1";
+            case "二沉池正磷酸盐" : return "cs_tp_lxjc";
+            default: throw new ServiceException("暂时不支持的类型");
+        }
+    }
+
+
+    private void closeWarning(TXinyiWarningRecord tXinyiWarningRecord) {
+        Date nowDate = DateUtils.getNowDate();
+        tXinyiWarningRecord.setStatus(2);
+        tXinyiWarningRecord.setOffTime(nowDate);
+        tXinyiWarningRecord.setUpdateTime(nowDate);
+        tXinyiWarningRecord.setUpdateBy(WARNING_DEFAULT_CREATE);
+        this.xinyiWarningRecordMapper.updateTXinyiWarningRecord(tXinyiWarningRecord);
+    }
+
 
 
     /**

+ 2 - 1
slibra-common/src/main/java/com/slibra/common/utils/DateUtils.java

@@ -41,7 +41,8 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
     private static String[] parsePatterns = {
             "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", 
             "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
-            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM",
+            "yyyy/MM/dd HH"};
 
     /**
      * 获取当前Date型日期

+ 4 - 0
slibra-system/src/main/java/com/slibra/business/mapper/TXinyiIndustryMapper.java

@@ -82,10 +82,14 @@ public interface TXinyiIndustryMapper
 
     List<DecisionReq> selectLast10RecordsForDecisionOnlyIndustry();
 
+    List<DecisionReq> selectLast10RecordsForDecisionOnlyIndustryByHour(String hour);
+
     TXinyiIndustry selectTXinyiIndustryNewest();
 
     List<TXinyiIndustry> selectNIndustry(int size);
 
+    List<TXinyiIndustry> selectNIndustryBeforeHour(@Param("size") int int8, @Param("testHour") String testHour);
+
     TXinyiIndustry selectIndustryAvgByDate(String date);
 
 

+ 77 - 0
slibra-system/src/main/resources/mapper/business/TXinyiIndustryMapper.xml

@@ -542,6 +542,77 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 
 
+    <select id="selectLast10RecordsForDecisionOnlyIndustryByHour" resultType="com.slibra.common.core.domain.DecisionReq">
+        SELECT
+            a.ID ID,
+            a.TEST_DATE testDate,
+            a.TEST_HOUR testHour,
+            a.JS_COD codIn,
+            b.JSCOD_SJZ codInName,
+            a.CS_COD codOff,
+            b.CSCOD_BZZ codOffName,
+            b.CSCOD_GKZ codOffCon,
+            a.JS_TP tpIn,
+            b.JSZL_SJZ tpInName,
+            a.CS_TP tpOff,
+            b.CSZL_BZZ tpOffName,
+            b.CSZL_GKZ tpOffCon,
+            a.JS_TN tnIn,
+            b.JSZD_SJZ tnInName,
+            a.CS_TN tnOff,
+            b.CSZZ_BZZ tnOffName,
+            b.CSZZ_GKZ tnOffCon,
+            a.JS_NH3 addanIn,
+            b.JSAD_SJZ addanInName,
+            a.CS_NH3 andanOff,
+            b.CSAD_BZZ andanOffName,
+            b.CSAD_GKZ andanOffCon,
+            a.JS_SS ssIn,
+            b.JSSS_SJZ ssInName,
+            a.CS_SS ssOff,
+            b.CSSS_BZZ ssOffName,
+            b.CSSS_GKZ ssOffCon,
+            a.JS_TDS tdsIn,
+            b.JDTDS_GKZ tdsInName,
+            a.JS_PH pH,
+            a.JS_SLQ qIn,
+            b.JSSL_SJZ qInName,
+            b.WNTJZS_GKZ sviCon,
+            b.WNCJB_GKZ svCon,
+            a.CL_P04_YCZ CLP04YCZ,
+            ( a.JS_TP - a.CL_P04_YCZ )/ a.JS_TP tpLv,
+            a.SJTYJLY tyjyl,
+            a.CLJYSSLL cltjl,
+            c.HYCXSY_ALL hycxsyAll,
+            c.QYAN_ALL qyanAll,
+            c.QYCKXSY_ALL qyckxsyAll,
+            c.TP_RCC_JQR eccZl,
+            c.HYZLSY_ALL hyzlsyAll
+        FROM
+            t_xinyi_industry a
+                LEFT JOIN t_xinyi_norm_config b ON 1 = 1
+                LEFT JOIN (
+                SELECT
+                    TEST_HOUR,
+                    any_value ( HYCXSY_ALL ) HYCXSY_ALL,
+                    any_value ( QYAN_ALL ) QYAN_ALL,
+                    any_value ( QYCKXSY_ALL ) QYCKXSY_ALL,
+                    any_value ( TP_RCC_JQR ) TP_RCC_JQR,
+                    any_value ( HYZLSY_ALL ) HYZLSY_ALL
+                FROM
+                    t_xinyi_robot
+                GROUP BY
+                    TEST_HOUR
+            ) c ON a.TEST_HOUR = c.TEST_HOUR
+        WHERE
+            1 = 1
+        and a.TEST_HOUR &lt;= #{hour}
+        ORDER BY
+            a.ID DESC
+            LIMIT 0,25
+    </select>
+
+
     <select id="selectTXinyiIndustryNewest" resultMap="TXinyiIndustryResult">
         <include refid="selectTXinyiIndustryVo"/>
         order by id desc limit 1
@@ -552,6 +623,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         order by id desc limit #{size}
     </select>
 
+    <select id="selectNIndustryBeforeHour" resultMap="TXinyiIndustryResult">
+        <include refid="selectTXinyiIndustryVo"/>
+        where TEST_HOUR &lt;= #{testHour} order by id desc limit #{size}
+    </select>
+
+
     <select id="selectIndustryAvgByDate" resultMap="TXinyiIndustryResult">
         SELECT
             AVG(JS_SLQ) JS_SLQ,