Просмотр исходного кода

feat:燃气管网-可燃气体监测 管线压力监测实现接口

null 1 неделя назад
Родитель
Сommit
7e76fbadeb

+ 35 - 0
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GasMonitorController.java

@@ -0,0 +1,35 @@
+package com.zksy.web.controller.gasbasic;
+
+import com.zksy.base.service.GasMonitorService;
+import com.zksy.common.annotation.Anonymous;
+import com.zksy.common.core.domain.AjaxResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 场站可燃气体浓度监测
+ */
+@Api(tags = "燃气监测-场站可燃气体")
+@RestController
+@RequestMapping("/api/gas-monitor")
+@Anonymous
+public class GasMonitorController {
+
+    @Autowired
+    private GasMonitorService gasMonitorService;
+
+    @GetMapping("/points")
+    @ApiOperation("获取场站监测点列表(含最新气体浓度+温度)")
+    public AjaxResult getPoints() {
+        return AjaxResult.success(gasMonitorService.getPoints());
+    }
+
+    @GetMapping("/point/{pointId}/trend")
+    @ApiOperation("获取24小时气体浓度趋势")
+    public AjaxResult getTrend(@PathVariable String pointId,
+                               @RequestParam(defaultValue = "24") int hours) {
+        return AjaxResult.success(gasMonitorService.getTrend(pointId, hours));
+    }
+}

+ 43 - 0
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/PressureMonitorController.java

@@ -0,0 +1,43 @@
+package com.zksy.web.controller.gasbasic;
+
+import com.zksy.base.service.PressureMonitorService;
+import com.zksy.common.annotation.Anonymous;
+import com.zksy.common.core.domain.AjaxResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 管线压力监测
+ */
+@Api(tags = "燃气监测-管线压力")
+@RestController
+@RequestMapping("/api/pressure-monitor")
+@Anonymous
+public class PressureMonitorController {
+
+    @Autowired
+    private PressureMonitorService pressureMonitorService;
+
+    @GetMapping("/points")
+    @ApiOperation("获取压力监测点列表(含最新压力数据)")
+    public AjaxResult getPoints() {
+        return AjaxResult.success(pressureMonitorService.getPoints());
+    }
+
+    @GetMapping("/point/{pointId}/trend")
+    @ApiOperation("获取压力趋势(近N天, 每天6时段)")
+    public AjaxResult getTrend(@PathVariable String pointId,
+                               @RequestParam(defaultValue = "10") int days) {
+        return AjaxResult.success(pressureMonitorService.getTrend(pointId, days));
+    }
+
+    @GetMapping("/point/{pointId}/history")
+    @ApiOperation("分页获取压力历史记录")
+    public AjaxResult getHistory(@PathVariable String pointId,
+                                 @RequestParam(defaultValue = "1") int pageNum,
+                                 @RequestParam(defaultValue = "10") int pageSize) {
+        return AjaxResult.success(pressureMonitorService.getHistory(pointId, pageNum, pageSize));
+    }
+}

+ 16 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/GasMonitorService.java

@@ -0,0 +1,16 @@
+package com.zksy.base.service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 燃气监测页面 — 场站可燃气体浓度监测
+ */
+public interface GasMonitorService {
+
+    /** 监测点列表(含最新气体浓度+温度) */
+    List<Map<String, Object>> getPoints();
+
+    /** 24小时气体浓度趋势 */
+    Map<String, Object> getTrend(String pointId, int hours);
+}

+ 19 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/PressureMonitorService.java

@@ -0,0 +1,19 @@
+package com.zksy.base.service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 燃气监测页面 — 管线压力监测
+ */
+public interface PressureMonitorService {
+
+    /** 压力监测点列表(含最新压力数据) */
+    List<Map<String, Object>> getPoints();
+
+    /** 压力趋势(近N天, 每天6时段) */
+    Map<String, Object> getTrend(String pointId, int days);
+
+    /** 分页压力历史记录 */
+    Map<String, Object> getHistory(String pointId, int pageNum, int pageSize);
+}

+ 214 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/GasMonitorServiceImpl.java

@@ -0,0 +1,214 @@
+package com.zksy.base.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zksy.base.domain.EquipmentBase;
+import com.zksy.base.domain.EquipmentPointRel;
+import com.zksy.base.domain.MonitorPoint;
+import com.zksy.base.environment.domain.ERealTimeData;
+import com.zksy.base.environment.mapper.ERealTimeDataMapper;
+import com.zksy.base.gas.domain.GasMonitorData;
+import com.zksy.base.gas.mapper.GasMonitorDataMapper;
+import com.zksy.base.mapper.EquipmentBaseMapper;
+import com.zksy.base.mapper.EquipmentPointRelMapper;
+import com.zksy.base.mapper.MonitorPointMapper;
+import com.zksy.base.service.GasMonitorService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class GasMonitorServiceImpl implements GasMonitorService {
+
+    @Autowired
+    private MonitorPointMapper monitorPointMapper;
+    @Autowired
+    private EquipmentPointRelMapper equipmentPointRelMapper;
+    @Autowired
+    private EquipmentBaseMapper equipmentBaseMapper;
+    @Autowired
+    private GasMonitorDataMapper gasMonitorDataMapper;
+    @Autowired
+    private ERealTimeDataMapper eRealTimeDataMapper;
+
+    private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    // 设备类型常量
+    private static final String TYPE_GAS = "GAS-COMBUSTIBLE";
+    private static final String TYPE_ENV = "GAS-ENV";
+
+    @Override
+    public List<Map<String, Object>> getPoints() {
+        List<MonitorPoint> points = monitorPointMapper.selectList(
+                new LambdaQueryWrapper<MonitorPoint>()
+                        .eq(MonitorPoint::getPointType, "燃气监测")
+                        .eq(MonitorPoint::getDelFlag, "0")
+        );
+
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (MonitorPoint p : points) {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("pointId", p.getPointId());
+            item.put("pointCode", p.getPointCode());
+            item.put("pointName", p.getPointName());
+            item.put("location", p.getLocation());
+            item.put("status", "0");
+            item.put("stationCode", p.getPointCode());
+
+            // 查关联设备
+            List<EquipmentPointRel> rels = equipmentPointRelMapper.selectList(
+                    new LambdaQueryWrapper<EquipmentPointRel>()
+                            .eq(EquipmentPointRel::getPointId, p.getPointId())
+                            .eq(EquipmentPointRel::getDelFlag, "0")
+            );
+
+            if (!rels.isEmpty()) {
+                List<String> equipmentIds = rels.stream().map(EquipmentPointRel::getEquipmentId).collect(Collectors.toList());
+                List<EquipmentBase> equipments = equipmentBaseMapper.selectBatchIds(equipmentIds);
+                item.put("deviceCount", equipments.size());
+
+                for (EquipmentBase eq : equipments) {
+                    if (TYPE_GAS.equals(eq.getEquipmentTypeId())) {
+                        // 气体设备: mac_address = equipment_code
+                        GasMonitorData latestGas = getLatestGasData(eq.getEquipmentCode());
+                        if (latestGas != null) {
+                            double methane = latestGas.getGasConcentration() != null ? latestGas.getGasConcentration().doubleValue() : 0;
+                            item.put("methaneValue", Math.round(methane * 10.0) / 10.0);
+                            item.put("lastUpdateTime", latestGas.getReportTime() != null ? latestGas.getReportTime().format(DT_FMT) : "");
+                            item.put("alertStatus", methane > 20.0);
+                        }
+                    }
+                    if (TYPE_ENV.equals(eq.getEquipmentTypeId())) {
+                        // 环境设备: device_id = Integer(equipment_code)
+                        ERealTimeData latestEnv = getLatestEnvData(parseIntSafe(eq.getEquipmentCode()));
+                        if (latestEnv != null) {
+                            double temp = latestEnv.getTem() != null ? latestEnv.getTem().doubleValue() : 0;
+                            item.put("temperature", Math.round(temp * 10.0) / 10.0);
+                        }
+                    }
+                }
+            }
+
+            // 默认值
+            item.putIfAbsent("methaneValue", 0.0);
+            item.putIfAbsent("methaneThreshold", 20.0);
+            item.putIfAbsent("temperature", 0.0);
+            item.putIfAbsent("lastUpdateTime", "");
+            item.putIfAbsent("deviceCount", 0);
+            item.putIfAbsent("alertStatus", false);
+
+            result.add(item);
+        }
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> getTrend(String pointId, int hours) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        String gasEquipmentCode = findGasEquipmentCode(pointId);
+        if (gasEquipmentCode == null) {
+            result.put("times", new ArrayList<>());
+            result.put("methaneValues", new ArrayList<>());
+            result.put("threshold", 20.0);
+            return result;
+        }
+
+        // 查询该设备的所有数据,按时间排序
+        List<GasMonitorData> allData = gasMonitorDataMapper.selectList(
+                new LambdaQueryWrapper<GasMonitorData>()
+                        .eq(GasMonitorData::getMacAddress, gasEquipmentCode)
+                        .orderByAsc(GasMonitorData::getReportTime)
+        );
+
+        // 找出数据的时间范围
+        LocalDateTime dataStart = allData.isEmpty() ? LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0)
+                : allData.get(0).getReportTime();
+        LocalDateTime dataEnd = allData.isEmpty() ? dataStart.plusHours(23)
+                : allData.get(allData.size() - 1).getReportTime();
+
+        // 按小时分组聚合
+        Map<String, Double> hourMap = new LinkedHashMap<>();
+        for (GasMonitorData d : allData) {
+            if (d.getReportTime() == null || d.getGasConcentration() == null) continue;
+            String hourKey = d.getReportTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH"));
+            hourMap.merge(hourKey, d.getGasConcentration().doubleValue(), (a, b) -> (a + b) / 2);
+        }
+
+        // 构建24小时时间线,从dataStart的0点到dataEnd的23点
+        LocalDateTime rangeStart = dataStart.withHour(0).withMinute(0).withSecond(0).withNano(0);
+        LocalDateTime rangeEnd = dataEnd.withHour(23).withMinute(0).withSecond(0).withNano(0);
+        if (rangeEnd.isBefore(rangeStart.plusHours(23))) {
+            rangeEnd = rangeStart.plusHours(23);
+        }
+
+        List<String> times = new ArrayList<>();
+        List<Double> values = new ArrayList<>();
+        double lastVal = 0;
+        LocalDateTime cursor = rangeStart;
+        while (!cursor.isAfter(rangeEnd)) {
+            times.add(cursor.format(DateTimeFormatter.ofPattern("HH:mm")));
+            String key = cursor.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH"));
+            Double val = hourMap.getOrDefault(key, null);
+            if (val != null) {
+                lastVal = val;
+            }
+            values.add(Math.round(lastVal * 10.0) / 10.0);
+            cursor = cursor.plusHours(1);
+        }
+
+        result.put("times", times);
+        result.put("methaneValues", values);
+        result.put("threshold", 20.0);
+        return result;
+    }
+
+    // ========== 私有辅助方法 ==========
+
+    /** 查监测点关联的气体设备equipment_code */
+    private String findGasEquipmentCode(String pointId) {
+        List<EquipmentPointRel> rels = equipmentPointRelMapper.selectList(
+                new LambdaQueryWrapper<EquipmentPointRel>()
+                        .eq(EquipmentPointRel::getPointId, pointId)
+                        .eq(EquipmentPointRel::getDelFlag, "0")
+        );
+        if (rels.isEmpty()) return null;
+        List<String> eqIds = rels.stream().map(EquipmentPointRel::getEquipmentId).collect(Collectors.toList());
+        List<EquipmentBase> eqs = equipmentBaseMapper.selectBatchIds(eqIds);
+        for (EquipmentBase eq : eqs) {
+            if (TYPE_GAS.equals(eq.getEquipmentTypeId())) {
+                return eq.getEquipmentCode();
+            }
+        }
+        return null;
+    }
+
+    private GasMonitorData getLatestGasData(String macAddress) {
+        List<GasMonitorData> list = gasMonitorDataMapper.selectList(
+                new LambdaQueryWrapper<GasMonitorData>()
+                        .eq(GasMonitorData::getMacAddress, macAddress)
+                        .orderByDesc(GasMonitorData::getReportTime)
+                        .last("limit 1")
+        );
+        return list.isEmpty() ? null : list.get(0);
+    }
+
+    private ERealTimeData getLatestEnvData(int deviceId) {
+        List<ERealTimeData> list = eRealTimeDataMapper.selectList(
+                new LambdaQueryWrapper<ERealTimeData>()
+                        .eq(ERealTimeData::getDeviceId, deviceId)
+                        .orderByDesc(ERealTimeData::getCreateTime)
+                        .last("limit 1")
+        );
+        return list.isEmpty() ? null : list.get(0);
+    }
+
+    private int parseIntSafe(String s) {
+        try { return Integer.parseInt(s); } catch (Exception e) { return 0; }
+    }
+}

+ 259 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/PressureMonitorServiceImpl.java

@@ -0,0 +1,259 @@
+package com.zksy.base.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zksy.base.domain.EquipmentBase;
+import com.zksy.base.domain.EquipmentPointRel;
+import com.zksy.base.domain.MonitorPoint;
+import com.zksy.base.mapper.EquipmentBaseMapper;
+import com.zksy.base.mapper.EquipmentPointRelMapper;
+import com.zksy.base.mapper.MonitorPointMapper;
+import com.zksy.base.pressure.domain.FirefightingPressure;
+import com.zksy.base.pressure.mapper.FirefightingPressureMapper;
+import com.zksy.base.service.PressureMonitorService;
+import com.zksy.common.utils.StringUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class PressureMonitorServiceImpl implements PressureMonitorService {
+
+    @Autowired
+    private MonitorPointMapper monitorPointMapper;
+    @Autowired
+    private EquipmentPointRelMapper equipmentPointRelMapper;
+    @Autowired
+    private EquipmentBaseMapper equipmentBaseMapper;
+    @Autowired
+    private FirefightingPressureMapper firefightingPressureMapper;
+
+    private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    /** 压力等级 → 阈值映射 */
+    private static final Map<String, double[]> PRESSURE_THRESHOLDS = new LinkedHashMap<>();
+    static {
+        PRESSURE_THRESHOLDS.put("high",   new double[]{240, 420});
+        PRESSURE_THRESHOLDS.put("medium", new double[]{150, 250});
+        PRESSURE_THRESHOLDS.put("low",    new double[]{80,  150});
+    }
+
+    @Override
+    public List<Map<String, Object>> getPoints() {
+        List<MonitorPoint> points = monitorPointMapper.selectList(
+                new LambdaQueryWrapper<MonitorPoint>()
+                        .eq(MonitorPoint::getPointType, "燃气监测")
+                        .eq(MonitorPoint::getDelFlag, "0")
+        );
+
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (MonitorPoint p : points) {
+            // 查关联的压力设备
+            FirefightingPressure latestPressure = getLatestPressureByPoint(p.getPointId());
+            if (latestPressure == null) continue; // 只返回有压力数据的监测点
+
+            // 从设备信息推断压力等级
+            String pressureLevel = inferPressureLevel(latestPressure);
+
+            double[] thresholds = PRESSURE_THRESHOLDS.getOrDefault(pressureLevel, new double[]{150, 250});
+            double minP = thresholds[0];
+            double maxP = thresholds[1];
+            double latestVal = latestPressure.getPressureValue() != null ? latestPressure.getPressureValue() : 0;
+            String status = computeStatus(latestVal, minP, maxP);
+
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("pointId", p.getPointId());
+            item.put("name", p.getPointName());
+            item.put("pipelineName", derivePipelineName(p.getPointName(), pressureLevel));
+            item.put("pressureLevel", pressureLevel);
+            item.put("address", p.getLocation());
+            item.put("lng", p.getLongitude());
+            item.put("lat", p.getLatitude());
+            item.put("latestPressure", Math.round(latestVal * 10.0) / 10.0);
+            item.put("minPressure", minP);
+            item.put("maxPressure", maxP);
+            item.put("status", status);
+            item.put("latestTime", latestPressure.getObservedTime() != null
+                    ? latestPressure.getObservedTime().format(DT_FMT) : "");
+            result.add(item);
+        }
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> getTrend(String pointId, int days) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        String telemCode = getTelemCodeByPoint(pointId);
+
+        // 先查所有数据确定时间范围
+        List<FirefightingPressure> allData = firefightingPressureMapper.selectList(
+                new LambdaQueryWrapper<FirefightingPressure>()
+                        .eq(StringUtils.isNotEmpty(telemCode), FirefightingPressure::getTelemeteringStation, telemCode)
+                        .orderByAsc(FirefightingPressure::getObservedTime)
+        );
+
+        // 从数据中提取时间范围
+        LocalDateTime dataStart = allData.isEmpty() ? LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0)
+                : allData.get(0).getObservedTime();
+        LocalDateTime dataEnd = allData.isEmpty() ? dataStart.plusDays(9).withHour(23)
+                : allData.get(allData.size() - 1).getObservedTime();
+        if (dataEnd == null) dataEnd = dataStart.plusDays(9).withHour(23);
+
+        int actualDays = Math.max(1, (int) java.time.Duration.between(dataStart, dataEnd).toDays() + 1);
+        actualDays = Math.min(actualDays, days);
+
+        // 聚合为每天6时段 (0/4/8/12/16/20)
+        int[] hourSlots = {0, 4, 8, 12, 16, 20};
+        Map<String, List<Double>> grouped = new LinkedHashMap<>();
+        for (FirefightingPressure fp : allData) {
+            if (fp.getObservedTime() == null || fp.getPressureValue() == null) continue;
+            int slotHour = findNearestSlot(fp.getObservedTime().getHour(), hourSlots);
+            String key = fp.getObservedTime().toLocalDate().toString() + " " + String.format("%02d:00", slotHour);
+            grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(fp.getPressureValue());
+        }
+
+        LocalDateTime rangeStart = dataStart.withHour(0).withMinute(0).withSecond(0).withNano(0);
+        LocalDateTime rangeEnd = dataEnd.withHour(23).withMinute(0).withSecond(0).withNano(0);
+
+        List<String> times = new ArrayList<>();
+        List<Double> pressures = new ArrayList<>();
+        double lastVal = 0;
+
+        for (int d = actualDays - 1; d >= 0; d--) {
+            LocalDateTime day = rangeEnd.toLocalDate().atStartOfDay().minusDays(d);
+            for (int h : hourSlots) {
+                String key = day.toLocalDate().toString() + " " + String.format("%02d:00", h);
+                List<Double> vals = grouped.getOrDefault(key, Collections.emptyList());
+                double avg = vals.isEmpty() ? lastVal : vals.stream().mapToDouble(v -> v).average().orElse(lastVal);
+                if (!vals.isEmpty()) lastVal = avg;
+                times.add(day.toLocalDate().toString().substring(5) + "\n" + String.format("%02d:00", h));
+                pressures.add(Math.round(avg * 10.0) / 10.0);
+            }
+        }
+
+        // 从数据推断阈值
+        double minP = 240, maxP = 420;
+        FirefightingPressure latest = allData.isEmpty() ? null : allData.get(allData.size() - 1);
+        if (latest != null) {
+            double[] thresholds = inferThresholds(latest);
+            minP = thresholds[0];
+            maxP = thresholds[1];
+        }
+
+        result.put("times", times);
+        result.put("pressures", pressures);
+        result.put("minPressure", minP);
+        result.put("maxPressure", maxP);
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> getHistory(String pointId, int pageNum, int pageSize) {
+        String telemCode = getTelemCodeByPoint(pointId);
+
+        // 总数
+        long total = firefightingPressureMapper.selectCount(
+                new LambdaQueryWrapper<FirefightingPressure>()
+                        .eq(StringUtils.isNotEmpty(telemCode), FirefightingPressure::getTelemeteringStation, telemCode)
+        );
+
+        int offset = (pageNum - 1) * pageSize;
+        List<FirefightingPressure> list = firefightingPressureMapper.selectList(
+                new LambdaQueryWrapper<FirefightingPressure>()
+                        .eq(StringUtils.isNotEmpty(telemCode), FirefightingPressure::getTelemeteringStation, telemCode)
+                        .orderByDesc(FirefightingPressure::getObservedTime)
+                        .last("limit " + offset + ", " + pageSize)
+        );
+
+        List<Map<String, Object>> rows = new ArrayList<>();
+        for (FirefightingPressure fp : list) {
+            Map<String, Object> row = new LinkedHashMap<>();
+            row.put("time", fp.getObservedTime() != null ? fp.getObservedTime().format(DT_FMT) : "");
+            row.put("pressure", fp.getPressureValue());
+            row.put("collector", "YLCG-" + (telemCode != null ? telemCode.substring(telemCode.length() - 3) : "001"));
+            row.put("remark", "");
+            rows.add(row);
+        }
+
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("total", total);
+        result.put("rows", rows);
+        return result;
+    }
+
+    // ========== 辅助方法 ==========
+
+    private String getTelemCodeByPoint(String pointId) {
+        List<EquipmentPointRel> rels = equipmentPointRelMapper.selectList(
+                new LambdaQueryWrapper<EquipmentPointRel>()
+                        .eq(EquipmentPointRel::getPointId, pointId)
+                        .eq(EquipmentPointRel::getDelFlag, "0")
+        );
+        if (rels.isEmpty()) return null;
+        List<EquipmentBase> eqs = equipmentBaseMapper.selectBatchIds(
+                rels.stream().map(EquipmentPointRel::getEquipmentId).collect(Collectors.toList()));
+        for (EquipmentBase eq : eqs) {
+            // 压力设备的 equipment_code 即 telemetering_station
+            FirefightingPressure check = getLatestByTelemCode(eq.getEquipmentCode());
+            if (check != null) return eq.getEquipmentCode();
+        }
+        return null;
+    }
+
+    private FirefightingPressure getLatestPressureByPoint(String pointId) {
+        String telemCode = getTelemCodeByPoint(pointId);
+        if (telemCode == null) return null;
+        return getLatestByTelemCode(telemCode);
+    }
+
+    private FirefightingPressure getLatestByTelemCode(String telemCode) {
+        List<FirefightingPressure> list = firefightingPressureMapper.selectList(
+                new LambdaQueryWrapper<FirefightingPressure>()
+                        .eq(FirefightingPressure::getTelemeteringStation, telemCode)
+                        .orderByDesc(FirefightingPressure::getObservedTime)
+                        .last("limit 1")
+        );
+        return list.isEmpty() ? null : list.get(0);
+    }
+
+    private String inferPressureLevel(FirefightingPressure fp) {
+        if (fp == null || fp.getPressureValue() == null) return "medium";
+        double v = fp.getPressureValue();
+        if (v >= 240) return "high";
+        if (v >= 150) return "medium";
+        return "low";
+    }
+
+    private double[] inferThresholds(FirefightingPressure fp) {
+        return PRESSURE_THRESHOLDS.getOrDefault(inferPressureLevel(fp), new double[]{150, 250});
+    }
+
+    private String computeStatus(double val, double min, double max) {
+        if (val <= 0) return "offline";
+        if (val > max) return "danger";
+        if (val < min) return "warning";
+        return "normal";
+    }
+
+    private String derivePipelineName(String pointName, String level) {
+        String levelStr = "高压".equals(level) ? "高压段" : "中压".equals(level) ? "中压段" : "低压段";
+        return pointName.replace("监测点", "").replace("LNG储配站", "LNG段").replace("计量站", "计量段") + levelStr;
+    }
+
+    private int findNearestSlot(int hour, int[] slots) {
+        int nearest = slots[0];
+        int minDiff = Math.abs(hour - slots[0]);
+        for (int s : slots) {
+            int diff = Math.abs(hour - s);
+            if (diff < minDiff) { minDiff = diff; nearest = s; }
+        }
+        return nearest;
+    }
+}