package com.xzzn.ems.service.impl;

import com.xzzn.common.constant.RedisKeyConstants;
import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.enums.AmmeterCategory;
import com.xzzn.common.enums.DeviceCategory;
import com.xzzn.common.utils.DateUtils;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsAmmeterData;
import com.xzzn.ems.domain.EmsBatteryCluster;
import com.xzzn.ems.domain.EmsBatteryData;
import com.xzzn.ems.domain.EmsBatteryStack;
import com.xzzn.ems.domain.EmsCoolingData;
import com.xzzn.ems.domain.EmsDhData;
import com.xzzn.ems.domain.EmsDailyChargeData;
import com.xzzn.ems.domain.EmsDevicesSetting;
import com.xzzn.ems.domain.EmsEmsData;
import com.xzzn.ems.domain.EmsPcsBranchData;
import com.xzzn.ems.domain.EmsSiteMonitorPointMatch;
import com.xzzn.ems.domain.EmsStrategyTemp;
import com.xzzn.ems.domain.EmsXfData;
import com.xzzn.ems.domain.vo.*;
import com.xzzn.ems.mapper.EmsAlarmRecordsMapper;
import com.xzzn.ems.mapper.EmsAmmeterDataMapper;
import com.xzzn.ems.mapper.EmsBatteryClusterMapper;
import com.xzzn.ems.mapper.EmsBatteryStackMapper;
import com.xzzn.ems.mapper.EmsDailyChargeDataMapper;
import com.xzzn.ems.mapper.EmsDailyEnergyDataMapper;
import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.mapper.EmsEnergyPriceConfigMapper;
import com.xzzn.ems.mapper.EmsPcsDataMapper;
import com.xzzn.ems.mapper.EmsPointMatchMapper;
import com.xzzn.ems.mapper.EmsSiteMonitorPointMatchMapper;
import com.xzzn.ems.mapper.EmsStrategyRunningMapper;
import com.xzzn.ems.mapper.EmsStrategyTempMapper;
import com.xzzn.ems.service.IEmsEnergyPriceConfigService;
import com.xzzn.ems.service.IEmsDeviceSettingService;
import com.xzzn.ems.service.ISingleSiteService;
import com.xzzn.ems.service.InfluxPointDataWriter;
import com.xzzn.ems.utils.DevicePointMatchDataProcessor;

import java.beans.PropertyDescriptor;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

/**
 * 单点监控 服务层实现
 */
@Service
public class SingleSiteServiceImpl implements ISingleSiteService {

    private static final Logger log = LoggerFactory.getLogger(SingleSiteServiceImpl.class);
    private static final String RUNNING_GRAPH_DEBUG = "RunningGraphDebug";
    private static final String FIELD_CURVE_PCS_ACTIVE_POWER = "SBJK_SSYX__curvePcsActivePower";
    private static final String FIELD_CURVE_PCS_REACTIVE_POWER = "SBJK_SSYX__curvePcsReactivePower";
    private static final String FIELD_CURVE_PCS_MAX_TEMP = "SBJK_SSYX__curvePcsMaxTemp";
    private static final String FIELD_CURVE_BATTERY_AVE_SOC = "SBJK_SSYX__curveBatteryAveSoc";
    private static final String FIELD_CURVE_BATTERY_AVE_TEMP = "SBJK_SSYX__curveBatteryAveTemp";
    private static final String FIELD_TOTAL_ACTIVE_POWER = "SBJK_SSYX__totalActivePower";
    private static final String FIELD_TOTAL_REACTIVE_POWER = "SBJK_SSYX__totalReactivePower";
    private static final String FIELD_SOC = "SBJK_SSYX__soc";
    private static final String FIELD_HOME_AVG_TEMP = "HOME__avgTemp";
    private static final String RUNNING_GRAPH_DEFAULT_DEVICE = "SITE";
    private static final int USE_FIXED_DISPLAY_YES = 1;
    private static final String DEVICE_INFO_ID = "id";
    private static final String DEVICE_INFO_NAME = "deviceName";
    private static final String DEVICE_INFO_COMM_STATUS = "communicationStatus";
    private static final String DEVICE_INFO_DEVICE_STATUS = "deviceStatus";
    private static final Set<String> PCS_DETAIL_META_FIELDS = new HashSet<>(Arrays.asList(
            "siteId", "deviceId", "deviceName", "alarmNum", "pcsBranchInfoList", "dataUpdateTime"
    ));
    private static final Set<String> BATTERY_DETAIL_META_FIELDS = new HashSet<>(Arrays.asList(
            "deviceId", "clusterDeviceId", "dataTimestamp"
    ));
    private static final String MENU_SBJK_DTDC = "SBJK_DTDC";
    private static final String MENU_SBJK_BMSDCC = "SBJK_BMSDCC";

    private static final String CLUSTER_DATA_TEP = "温度";

    private static final String CLUSTER_DATA_VOLTAGE = "电压";

    private static final String CLUSTER_DATA_SOC = "SOC";

    // 初始化List，存储指定的电表deviceId
    List<String> ammeterDeviceIds = Arrays.asList(
            "LOAD", "METE", "METEGF"
    );
    @Autowired
    private EmsPcsDataMapper emsPcsDataMapper;
    @Autowired
    private EmsAlarmRecordsMapper emsAlarmRecordsMapper;
    @Autowired
    private EmsBatteryStackMapper emsBatteryStackMapper;
    @Autowired
    private EmsBatteryClusterMapper emsBatteryClusterMapper;
    @Autowired
    private EmsAmmeterDataMapper emsAmmeterDataMapper;
    @Autowired
    private EmsDevicesSettingMapper emsDevicesSettingMapper;
    @Autowired
    private EmsPointMatchMapper emsPointMatchMapper;
    @Autowired
    private EmsSiteMonitorPointMatchMapper emsSiteMonitorPointMatchMapper;
    @Autowired
    private InfluxPointDataWriter influxPointDataWriter;

    @Autowired
    private RedisCache redisCache;
    @Autowired
    private EmsStrategyRunningMapper emsStrategyRunningMapper;
    @Autowired
    private EmsStrategyTempMapper emsStrategyTempMapper;
    @Autowired
    private EmsDailyChargeDataMapper emsDailyChargeDataMapper;
    @Autowired
    private EmsDailyEnergyDataMapper emsDailyEnergyDataMapper;
    @Autowired
    private EmsEnergyPriceConfigMapper emsEnergyPriceConfigMapper;
    @Autowired
    private IEmsEnergyPriceConfigService iEmsEnergyPriceConfigService;
    @Autowired
    private IEmsDeviceSettingService iEmsDeviceSettingService;
    @Autowired
    private DevicePointMatchDataProcessor devicePointMatchDataProcessor;

    @Override
    public SiteMonitorHomeVo getSiteMonitorDataVo(String siteId) {
        SiteMonitorHomeVo siteMonitorHomeVo = new SiteMonitorHomeVo();

        if (!StringUtils.isEmpty(siteId)) {
            // 获取单站点的总充+总放+电网实时功率
            String nowData = DateUtils.getDate();
            Map<String,BigDecimal> dataMap = emsDailyChargeDataMapper.getAllSiteChargeData(nowData,siteId);
            if (dataMap != null) {
                siteMonitorHomeVo.setTotalChargedCap(dataMap.get("totalChargedCap"));
                siteMonitorHomeVo.setTotalDischargedCap(dataMap.get("totalDischargedCap"));
                //siteMonitorHomeVo.setGridNrtPower(dataMap.get("gridNrtPower"));
            }

            // 总收入+当日实时收入
            Map<String, BigDecimal> dayRevenueMap = iEmsEnergyPriceConfigService.getDayRevenueMap(siteId);
            siteMonitorHomeVo.setTotalRevenue(
                    dayRevenueMap.get("totalRevenue") ==  null ? BigDecimal.ZERO : dayRevenueMap.get("totalRevenue"));
            siteMonitorHomeVo.setDayRevenue(
                    dayRevenueMap.get("dayRevenue") == null ? BigDecimal.ZERO :dayRevenueMap.get("dayRevenue"));
            siteMonitorHomeVo.setYesterdayRevenue(
                    dayRevenueMap.get("yesterdayRevenue") == null ? BigDecimal.ZERO :dayRevenueMap.get("yesterdayRevenue"));

            // 实时告警数据 名称+状态+告警内容
            List<SiteMonitorHomeAlarmVo> siteMonitorHomeAlarmVo = emsAlarmRecordsMapper.getAlarmRecordsBySiteId(siteId);
            siteMonitorHomeVo.setSiteMonitorHomeAlarmVo(siteMonitorHomeAlarmVo);

            // 策略运行-主策略模板数据
            List<StrategyRunningVo> runningVo = emsStrategyRunningMapper.getRunningList(siteId);
            if (runningVo != null && runningVo.size() > 0) {
                StrategyTempInfo siteTempInfo = new StrategyTempInfo();
                siteTempInfo.setMainStrategyName(runningVo.get(0).getMainStrategyName());
                Long mainStrategyId = runningVo.get(0).getMainStrategyId();
                List<EmsStrategyTemp> tempList = emsStrategyTempMapper.getTempListBySiteIdAndStrategyId(siteId, mainStrategyId);
                siteTempInfo.setSiteMonitorDataVo(tempList);
                siteMonitorHomeVo.setStrategyTempInfo(siteTempInfo);
            }

            // 能量数据
            LocalDate sevenDaysAgo = LocalDate.now().minusDays(6);
            Date startDate = DateUtils.toDate(sevenDaysAgo);
            Date endDate = new Date();
            List<SiteMonitorDataVo> siteMonitorDataVoList = emsDailyChargeDataMapper.getSingleSiteChargeData(siteId,startDate,endDate);
            if (!CollectionUtils.isEmpty(siteMonitorDataVoList)) {
                for (SiteMonitorDataVo sitePcsData : siteMonitorDataVoList) {
                    // 获取当天的充电量+放电量
                    String pcsDate = sitePcsData.getAmmeterDate();
                    boolean isToday= DateUtils.checkIsToday(pcsDate);
                    if(isToday){
                        siteMonitorHomeVo.setDayChargedCap(sitePcsData.getChargedCap());
                        siteMonitorHomeVo.setDayDisChargedCap(sitePcsData.getDisChargedCap());
                    }
                    // 获取昨日充电量+放电量
                    if (DateUtils.getYesterdayDayString().equals(pcsDate)) {
                        siteMonitorHomeVo.setYesterdayChargedCap(sitePcsData.getChargedCap());
                        siteMonitorHomeVo.setYesterdayDisChargedCap(sitePcsData.getDisChargedCap());
                    }

                }
                // 储能可用电量
                BigDecimal energyStorageAvailElec = siteMonitorHomeVo.getTotalDischargedCap().subtract(siteMonitorHomeVo.getTotalChargedCap());
                siteMonitorHomeVo.setEnergyStorageAvailElec(energyStorageAvailElec);
            }

            siteMonitorHomeVo.setSiteMonitorDataVo(siteMonitorDataVoList);
        }

        return siteMonitorHomeVo;
    }

    @Override
    public SiteMonitorHomeVo getSiteMonitorTotalDataVo(String siteId) {
        SiteMonitorHomeVo siteMonitorHomeVo = new SiteMonitorHomeVo();
        if (StringUtils.isEmpty(siteId)) {
            return siteMonitorHomeVo;
        }

        String nowDate = DateUtils.getDate();
        Map<String, BigDecimal> chargeDataMap = emsDailyChargeDataMapper.getAllSiteChargeData(nowDate, siteId);
        BigDecimal totalChargedCap = BigDecimal.ZERO;
        BigDecimal totalDischargedCap = BigDecimal.ZERO;
        if (chargeDataMap != null) {
            totalChargedCap = chargeDataMap.get("totalChargedCap") == null ? BigDecimal.ZERO : chargeDataMap.get("totalChargedCap");
            totalDischargedCap = chargeDataMap.get("totalDischargedCap") == null ? BigDecimal.ZERO : chargeDataMap.get("totalDischargedCap");
        }
        siteMonitorHomeVo.setTotalChargedCap(totalChargedCap);
        siteMonitorHomeVo.setTotalDischargedCap(totalDischargedCap);

        Map<String, BigDecimal> revenueMap = emsDailyEnergyDataMapper.getRealTimeRevenue(siteId);
        siteMonitorHomeVo.setTotalRevenue(revenueMap == null || revenueMap.get("totalRevenue") == null ? BigDecimal.ZERO : revenueMap.get("totalRevenue"));
        siteMonitorHomeVo.setDayRevenue(revenueMap == null || revenueMap.get("dayRevenue") == null ? BigDecimal.ZERO : revenueMap.get("dayRevenue"));

        Date yesterdayDate = DateUtils.toDate(LocalDate.now().minusDays(1));
        EmsDailyChargeData yesterdayChargeData = emsDailyChargeDataMapper.selectBySiteIdAndDateTime(siteId, yesterdayDate);
        siteMonitorHomeVo.setYesterdayRevenue(yesterdayChargeData == null || yesterdayChargeData.getDayRevenue() == null
                ? BigDecimal.ZERO
                : yesterdayChargeData.getDayRevenue());

        List<SiteMonitorHomeAlarmVo> alarmList = emsAlarmRecordsMapper.getAlarmRecordsBySiteId(siteId);
        siteMonitorHomeVo.setSiteMonitorHomeAlarmVo(alarmList);

        LocalDate sevenDaysAgo = LocalDate.now().minusDays(6);
        Date startDate = DateUtils.toDate(sevenDaysAgo);
        Date endDate = new Date();
        List<SiteMonitorDataVo> siteMonitorDataVoList = emsDailyChargeDataMapper.getSingleSiteChargeData(siteId, startDate, endDate);
        if (!CollectionUtils.isEmpty(siteMonitorDataVoList)) {
            for (SiteMonitorDataVo sitePcsData : siteMonitorDataVoList) {
                String pcsDate = sitePcsData.getAmmeterDate();
                if (DateUtils.checkIsToday(pcsDate)) {
                    siteMonitorHomeVo.setDayChargedCap(sitePcsData.getChargedCap());
                    siteMonitorHomeVo.setDayDisChargedCap(sitePcsData.getDisChargedCap());
                }
                if (DateUtils.getYesterdayDayString().equals(pcsDate)) {
                    siteMonitorHomeVo.setYesterdayChargedCap(sitePcsData.getChargedCap());
                    siteMonitorHomeVo.setYesterdayDisChargedCap(sitePcsData.getDisChargedCap());
                }
            }
        }

        siteMonitorHomeVo.setEnergyStorageAvailElec(totalDischargedCap.subtract(totalChargedCap));
        siteMonitorHomeVo.setSiteMonitorDataVo(siteMonitorDataVoList);
        return siteMonitorHomeVo;
    }

    // 获取单站监控实时运行头部数据
    @Override
    public SiteMonitorRunningHeadInfoVo getSiteRunningHeadInfo(String siteId) {
        SiteMonitorRunningHeadInfoVo siteMonitorRunningHeadInfoVo = new SiteMonitorRunningHeadInfoVo();

        if (!StringUtils.isEmpty(siteId)) {
            // 实时有功功率/实时无功功率
            SiteMonitorRunningHeadInfoVo tempVo = emsPcsDataMapper.getSiteRunningHeadInfo(siteId);
            if (tempVo != null) {
                siteMonitorRunningHeadInfoVo.setTotalActivePower(tempVo.getTotalActivePower());
                siteMonitorRunningHeadInfoVo.setTotalReactivePower(tempVo.getTotalReactivePower());
            }

            // 今日充放电
            LocalDateTime now = LocalDateTime.now();
            LocalDateTime startOfDay = now.with(LocalTime.MIN);
            Date startDate = DateUtils.toDate(startOfDay);
            Date endDate = new Date();
            List<SiteMonitorDataVo> siteMonitorDataVoList = emsDailyChargeDataMapper.getSingleSiteChargeData(siteId,startDate,endDate);
            if (siteMonitorDataVoList != null && siteMonitorDataVoList.size() > 0) {
                siteMonitorRunningHeadInfoVo.setDayChargedCap(siteMonitorDataVoList.get(0).getChargedCap());
                siteMonitorRunningHeadInfoVo.setDayDisChargedCap(siteMonitorDataVoList.get(0).getDisChargedCap());
            }
            // 电池堆SOC + 电池堆SOH
            EmsBatteryStack emsBatteryStack = emsBatteryStackMapper.getSiteSumStackInfo(siteId);
            if (emsBatteryStack != null) {
                siteMonitorRunningHeadInfoVo.setSoc(emsBatteryStack.getStackSoc());
                siteMonitorRunningHeadInfoVo.setSoh(emsBatteryStack.getStackSoh());
            }
        }

        return siteMonitorRunningHeadInfoVo;
    }

    //  获取单站监控实时运行-PCS有功无功功率
    @Override
    public SiteMonitorRuningInfoVo getRunningGraphStorage(RunningGraphRequest request) {
        SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
        if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
            log.info("{} storage skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
            return siteMonitorRuningInfoVo;
        }
        String siteId = request.getSiteId();
        Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
        Date startDate = dateRange[0];
        Date endDate = dateRange[1];
        List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId);
        Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList);
        List<DeviceMeta> pcsDeviceList = getDeviceMetaListByCategory(siteId, DeviceCategory.PCS.getCode());
        if (CollectionUtils.isEmpty(pcsDeviceList)) {
            pcsDeviceList = Collections.singletonList(new DeviceMeta(RUNNING_GRAPH_DEFAULT_DEVICE, RUNNING_GRAPH_DEFAULT_DEVICE));
        }

        List<PcsPowerList> pcsPowerList = new ArrayList<>();
        for (DeviceMeta pcsDevice : pcsDeviceList) {
            String deviceId = StringUtils.defaultString(pcsDevice.getDeviceId());
            String activePointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_ACTIVE_POWER);
            String reactivePointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_REACTIVE_POWER);

            List<InfluxPointDataWriter.PointValue> activeValues = queryInfluxPointValues(siteId, activePointId, startDate, endDate);
            List<InfluxPointDataWriter.PointValue> reactiveValues = queryInfluxPointValues(siteId, reactivePointId, startDate, endDate);
            Map<Long, BigDecimal> reactiveByTs = reactiveValues.stream()
                    .filter(v -> v != null && v.getDataTime() != null && v.getPointValue() != null)
                    .collect(Collectors.toMap(v -> v.getDataTime().getTime(), InfluxPointDataWriter.PointValue::getPointValue, (a, b) -> b));

            List<EnergyStoragePowVo> energyStoragePowList = new ArrayList<>();
            for (InfluxPointDataWriter.PointValue activeValue : activeValues) {
                if (activeValue == null || activeValue.getDataTime() == null || activeValue.getPointValue() == null) {
                    continue;
                }
                Date dataTime = activeValue.getDataTime();
                EnergyStoragePowVo vo = new EnergyStoragePowVo();
                vo.setDeviceId(deviceId);
                vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
                vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
                vo.setGroupTime(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, dataTime));
                vo.setPcsTotalActPower(activeValue.getPointValue());
                vo.setPcsTotalReactivePower(reactiveByTs.get(dataTime.getTime()));
                energyStoragePowList.add(vo);
            }

            PcsPowerList pcdData = new PcsPowerList();
            pcdData.setDeviceId(deviceId);
            pcdData.setEnergyStoragePowList(energyStoragePowList);
            pcsPowerList.add(pcdData);
        }

        siteMonitorRuningInfoVo.setPcsPowerList(pcsPowerList);
        int pointCount = pcsPowerList.stream()
                .filter(item -> item != null && item.getEnergyStoragePowList() != null)
                .mapToInt(item -> item.getEnergyStoragePowList().size())
                .sum();
        log.info("{} storage, siteId={}, startDate={}, endDate={}, deviceCount={}, pointCount={}",
                RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pcsPowerList.size(), pointCount);

        return siteMonitorRuningInfoVo;
    }

    private List<PcsPowerList> fullFillData(List<PcsPowerList> pcsPowerList, List<LocalDateTime> targetMinutes) {
        List<PcsPowerList> resultList = new ArrayList<>();

        for (PcsPowerList pcsPower : pcsPowerList) {
            Map<String, EnergyStoragePowVo> dataMap = new HashMap<>();
            PcsPowerList pcsResultPower = new PcsPowerList();
            String deviceId = pcsPower.getDeviceId();
            List<EnergyStoragePowVo> powerList = pcsPower.getEnergyStoragePowList();

            if (CollectionUtils.isEmpty(powerList)) {
                continue;
            }
            // 将数据转Map便于后续根据时间获取
            dataMap = powerList.stream().collect(
                    Collectors.toMap(
                            data -> data.getGroupTime(),
                            data -> data,
                            (existing, replacement) -> replacement
                    ));

            // 按时间列表遍历
            List<EnergyStoragePowVo> resultPowerList = new ArrayList<>();
            EnergyStoragePowVo lastData = null;
            for(LocalDateTime targetMinute : targetMinutes) {
                String currentDate = DateUtils.convertToString(targetMinute);
                EnergyStoragePowVo currentData = dataMap.get(currentDate);
                if (currentData == null && lastData != null) {
                    BigDecimal pcsTotalActPower = lastData.getPcsTotalActPower();
                    BigDecimal pcsTotalReactivePower = lastData.getPcsTotalReactivePower();
                    currentData = new EnergyStoragePowVo();
                    currentData.setPcsTotalActPower(pcsTotalActPower);
                    currentData.setPcsTotalReactivePower(pcsTotalReactivePower);
                    currentData.setDeviceId(deviceId);
                } else if (currentData == null && lastData == null){
                    // 开头无数据，所有字段设为null
                    currentData = new EnergyStoragePowVo();
                    currentData.setDeviceId(deviceId);
                }
                lastData = currentData;
//                currentData.setCreateDate(currentDate.substring(11));
                currentData.setCreateDate(currentData.getCreateDate());
                resultPowerList.add(currentData);
            }
            pcsResultPower.setDeviceId(deviceId);
            pcsResultPower.setEnergyStoragePowList(resultPowerList);
            resultList.add(pcsResultPower);
        }

        return  resultList;
    }

    //  获取单站监控实时运行-pcs最高温度
    @Override
    public SiteMonitorRuningInfoVo getRunningGraphPcsMaxTemp(RunningGraphRequest request) {
        SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
        if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
            log.info("{} pcsMaxTemp skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
            return siteMonitorRuningInfoVo;
        }
        String siteId = request.getSiteId();
        Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
        Date startDate = dateRange[0];
        Date endDate = dateRange[1];
        List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId);
        Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList);
        List<DeviceMeta> pcsDeviceList = getDeviceMetaListByCategory(siteId, DeviceCategory.PCS.getCode());
        if (CollectionUtils.isEmpty(pcsDeviceList)) {
            pcsDeviceList = Collections.singletonList(new DeviceMeta(RUNNING_GRAPH_DEFAULT_DEVICE, RUNNING_GRAPH_DEFAULT_DEVICE));
        }

        List<PcsMaxTempList> pcsMaxTempList = new ArrayList<>();
        for (DeviceMeta pcsDevice : pcsDeviceList) {
            String deviceId = StringUtils.defaultString(pcsDevice.getDeviceId());
            String pointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_MAX_TEMP);
            List<InfluxPointDataWriter.PointValue> values = queryInfluxPointValues(siteId, pointId, startDate, endDate);
            List<PcsMaxTempVo> pcsMaxTempVos = new ArrayList<>();
            for (InfluxPointDataWriter.PointValue value : values) {
                if (value == null || value.getDataTime() == null || value.getPointValue() == null) {
                    continue;
                }
                Date dataTime = value.getDataTime();
                PcsMaxTempVo vo = new PcsMaxTempVo();
                vo.setDeviceId(deviceId);
                vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
                vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
                vo.setTemp(value.getPointValue());
                pcsMaxTempVos.add(vo);
            }
            PcsMaxTempList pcdData = new PcsMaxTempList();
            pcdData.setDeviceId(deviceId);
            pcdData.setMaxTempVoList(pcsMaxTempVos);
            pcsMaxTempList.add(pcdData);
        }

        siteMonitorRuningInfoVo.setPcsMaxTempList(pcsMaxTempList);
        int pointCount = pcsMaxTempList.stream()
                .filter(item -> item != null && item.getMaxTempVoList() != null)
                .mapToInt(item -> item.getMaxTempVoList().size())
                .sum();
        log.info("{} pcsMaxTemp, siteId={}, startDate={}, endDate={}, deviceCount={}, pointCount={}",
                RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pcsMaxTempList.size(), pointCount);
        return siteMonitorRuningInfoVo;
    }
    private List<PcsMaxTempList> fullFillMaxTempData(List<PcsMaxTempList> pcsMaxTempList, List<LocalDateTime> targetHours) {
        List<PcsMaxTempList> resultList = new ArrayList<>();

        for (PcsMaxTempList pcsMaxTemp : pcsMaxTempList) {
            PcsMaxTempList pcsTemp = new PcsMaxTempList();
            List<PcsMaxTempVo> resultVos = new ArrayList<>();
            Map<String, PcsMaxTempVo> dataMap = new HashMap<>();

            String deviceId = pcsMaxTemp.getDeviceId();
            List<PcsMaxTempVo> pcsMaxTempVos = pcsMaxTemp.getMaxTempVoList();
            if (CollectionUtils.isEmpty(pcsMaxTempVos)) {
                continue;
            }

            // 将数据转Map便于后续根据时间获取
            dataMap = pcsMaxTempVos.stream().collect(
                    Collectors.toMap(
                            data -> data.getCreateDate(),
                            data -> data,
                            (existing, replacement) -> replacement
                    ));

            PcsMaxTempVo lastData = null;
            List<PcsMaxTempVo> resultTempList = new ArrayList<>();
            // 遍历时间填充数据
            for (LocalDateTime targetHour : targetHours) {
                String currentDate = DateUtils.convertToString(targetHour);
                PcsMaxTempVo currentData = dataMap.get(currentDate);
                if (currentData == null && lastData != null) {
                    BigDecimal temp = lastData.getTemp();
                    currentData = new PcsMaxTempVo();
                    currentData.setTemp(temp);
                    currentData.setDeviceId(deviceId);
                } else if (currentData == null && lastData == null){
                    // 开头无数据，所有字段设为null
                    currentData = new PcsMaxTempVo();
                    currentData.setDeviceId(deviceId);
                }
                lastData = currentData;
                currentData.setCreateDate(currentDate.substring(11));
                resultTempList.add(currentData);

            }
            pcsTemp.setDeviceId(deviceId);
            pcsTemp.setMaxTempVoList(resultTempList);
            resultList.add(pcsTemp);
        }
        return resultList;
    }

    //  获取单站监控实时运行-平均soc
    @Override
    public SiteMonitorRuningInfoVo getRunningGraphBatterySoc(RunningGraphRequest request) {
        SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
        if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
            log.info("{} batteryAveSoc skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
            return siteMonitorRuningInfoVo;
        }
        Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
        Date startDate = dateRange[0];
        Date endDate = dateRange[1];
        Map<String, EmsSiteMonitorPointMatch> mappingByField = getMonitorPointMappingByField(request.getSiteId());
        String pointId = firstNonBlankPoint(mappingByField, FIELD_CURVE_BATTERY_AVE_SOC, FIELD_SOC);
        List<InfluxPointDataWriter.PointValue> values = queryInfluxPointValues(request.getSiteId(), pointId, startDate, endDate);
        List<BatteryAveSOCVo> batteryAveSOCList = new ArrayList<>();
        for (InfluxPointDataWriter.PointValue value : values) {
            if (value == null || value.getDataTime() == null || value.getPointValue() == null) {
                continue;
            }
            Date dataTime = value.getDataTime();
            BatteryAveSOCVo vo = new BatteryAveSOCVo();
            vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
            vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
            vo.setBatterySOC(value.getPointValue());
            batteryAveSOCList.add(vo);
        }
        siteMonitorRuningInfoVo.setBatteryAveSOCList(batteryAveSOCList);
        int pointCount = CollectionUtils.isEmpty(batteryAveSOCList) ? 0 : batteryAveSOCList.size();
        log.info("{} batteryAveSoc, siteId={}, startDate={}, endDate={}, pointId={}, pointCount={}",
                RUNNING_GRAPH_DEBUG, request.getSiteId(), startDate, endDate, pointId, pointCount);
        return siteMonitorRuningInfoVo;
    }
    //  获取单站监控实时运行-电池平均温度
    @Override
    public SiteMonitorRuningInfoVo getRunningGraphBatteryTemp(RunningGraphRequest request) {
        SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
        if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
            log.info("{} batteryAveTemp skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
            return siteMonitorRuningInfoVo;
        }
        String siteId = request.getSiteId();
        Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
        Date startDate = dateRange[0];
        Date endDate = dateRange[1];
        Map<String, EmsSiteMonitorPointMatch> mappingByField = getMonitorPointMappingByField(siteId);
        String pointId = firstNonBlankPoint(mappingByField, FIELD_CURVE_BATTERY_AVE_TEMP, FIELD_HOME_AVG_TEMP);
        List<InfluxPointDataWriter.PointValue> values = queryInfluxPointValues(siteId, pointId, startDate, endDate);
        List<BatteryAveTempVo> batteryAveTempList = new ArrayList<>();
        for (InfluxPointDataWriter.PointValue value : values) {
            if (value == null || value.getDataTime() == null || value.getPointValue() == null) {
                continue;
            }
            Date dataTime = value.getDataTime();
            BatteryAveTempVo vo = new BatteryAveTempVo();
            vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
            vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
            vo.setBatteryTemp(value.getPointValue());
            batteryAveTempList.add(vo);
        }
        siteMonitorRuningInfoVo.setBatteryAveTempList(batteryAveTempList);
        int pointCount = CollectionUtils.isEmpty(batteryAveTempList) ? 0 : batteryAveTempList.size();
        log.info("{} batteryAveTemp, siteId={}, startDate={}, endDate={}, pointId={}, pointCount={}",
                RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pointId, pointCount);
        return siteMonitorRuningInfoVo;
    }

    private Date[] normalizeRunningGraphDateRange(Date startDate, Date endDate) {
        Date normalizedStart = startDate;
        Date normalizedEnd = endDate;
        if (normalizedStart == null || normalizedEnd == null) {
            Date today = DateUtils.getNowDate();
            Date yesterday = DateUtils.addDays(today, -1);
            normalizedStart = normalizedStart == null ? yesterday : normalizedStart;
            normalizedEnd = normalizedEnd == null ? today : normalizedEnd;
        }
        LocalDate startDay = DateUtils.toLocalDateTime(normalizedStart).toLocalDate();
        LocalDate endDay = DateUtils.toLocalDateTime(normalizedEnd).toLocalDate();
        Date dayStart = DateUtils.toDate(startDay.atStartOfDay());
        Date dayEnd = DateUtils.toDate(endDay.plusDays(1).atStartOfDay().minusNanos(1_000_000));
        return new Date[]{dayStart, dayEnd};
    }

    private Map<String, EmsSiteMonitorPointMatch> getMonitorPointMappingByField(String siteId) {
        if (StringUtils.isBlank(siteId)) {
            return Collections.emptyMap();
        }
        List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId.trim());
        if (CollectionUtils.isEmpty(mappingList)) {
            return Collections.emptyMap();
        }
        return mappingList.stream()
                .filter(item -> item != null && StringUtils.isNotBlank(item.getFieldCode()))
                .collect(Collectors.toMap(
                        item -> item.getFieldCode().trim(),
                        item -> item,
                        (a, b) -> b
                ));
    }

    private Map<String, EmsSiteMonitorPointMatch> buildMonitorPointMappingByFieldAndDevice(List<EmsSiteMonitorPointMatch> mappingList) {
        if (CollectionUtils.isEmpty(mappingList)) {
            return Collections.emptyMap();
        }
        return mappingList.stream()
                .filter(item -> item != null && StringUtils.isNotBlank(item.getFieldCode()))
                .collect(Collectors.toMap(
                        item -> buildFieldDeviceKey(item.getFieldCode(), item.getDeviceId()),
                        item -> item,
                        (a, b) -> b
                ));
    }

    private String buildFieldDeviceKey(String fieldCode, String deviceId) {
        return StringUtils.defaultString(fieldCode).trim() + "|" + StringUtils.defaultString(deviceId).trim();
    }

    private String firstNonBlankPointByDevice(Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice,
                                              String deviceId, String... fieldCodes) {
        if (mappingByFieldAndDevice == null || fieldCodes == null) {
            return null;
        }
        String normalizedDeviceId = StringUtils.defaultString(deviceId).trim();
        for (String fieldCode : fieldCodes) {
            if (StringUtils.isBlank(fieldCode)) {
                continue;
            }
            EmsSiteMonitorPointMatch exact = mappingByFieldAndDevice.get(buildFieldDeviceKey(fieldCode, normalizedDeviceId));
            if (exact != null && StringUtils.isNotBlank(exact.getDataPoint())) {
                return exact.getDataPoint().trim();
            }
            EmsSiteMonitorPointMatch fallback = mappingByFieldAndDevice.get(buildFieldDeviceKey(fieldCode, ""));
            if (fallback != null && StringUtils.isNotBlank(fallback.getDataPoint())) {
                return fallback.getDataPoint().trim();
            }
        }
        return null;
    }

    private List<DeviceMeta> getDeviceMetaListByCategory(String siteId, String deviceCategory) {
        if (StringUtils.isBlank(siteId) || StringUtils.isBlank(deviceCategory)) {
            return Collections.emptyList();
        }
        List<Map<String, Object>> deviceList = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, deviceCategory);
        if (CollectionUtils.isEmpty(deviceList)) {
            return Collections.emptyList();
        }
        List<DeviceMeta> result = new ArrayList<>();
        for (Map<String, Object> item : deviceList) {
            if (item == null || item.get("id") == null) {
                continue;
            }
            String deviceId = String.valueOf(item.get("id")).trim();
            if (StringUtils.isBlank(deviceId)) {
                continue;
            }
            String deviceName = item.get("deviceName") == null ? deviceId : String.valueOf(item.get("deviceName")).trim();
            result.add(new DeviceMeta(deviceId, deviceName));
        }
        result.sort(Comparator.comparing(DeviceMeta::getDeviceName, Comparator.nullsLast(String::compareTo))
                .thenComparing(DeviceMeta::getDeviceId, Comparator.nullsLast(String::compareTo)));
        return result;
    }

    private static class DeviceMeta {
        private final String deviceId;
        private final String deviceName;

        private DeviceMeta(String deviceId, String deviceName) {
            this.deviceId = deviceId;
            this.deviceName = deviceName;
        }

        public String getDeviceId() {
            return deviceId;
        }

        public String getDeviceName() {
            return deviceName;
        }
    }

    private String firstNonBlankPoint(Map<String, EmsSiteMonitorPointMatch> mappingByField, String... fieldCodes) {
        if (mappingByField == null || fieldCodes == null) {
            return null;
        }
        for (String fieldCode : fieldCodes) {
            if (StringUtils.isBlank(fieldCode)) {
                continue;
            }
            EmsSiteMonitorPointMatch match = mappingByField.get(fieldCode);
            if (match != null && StringUtils.isNotBlank(match.getDataPoint())) {
                return match.getDataPoint().trim();
            }
        }
        return null;
    }

    private List<InfluxPointDataWriter.PointValue> queryInfluxPointValues(String siteId, String pointId, Date startDate, Date endDate) {
        if (StringUtils.isBlank(siteId) || StringUtils.isBlank(pointId) || startDate == null || endDate == null) {
            return Collections.emptyList();
        }
        List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveDataByPointKey(siteId, pointId, startDate, endDate);
        if (CollectionUtils.isEmpty(values)) {
            return Collections.emptyList();
        }
        values.sort(Comparator.comparing(InfluxPointDataWriter.PointValue::getDataTime, Comparator.nullsLast(Date::compareTo)));
        return values;
    }

    // 根据site_id获取pcs详细数据+支路数据
    @Override
    public List<PcsDetailInfoVo> getPcsDetailInfo(String siteId) {
        List<PcsDetailInfoVo> pcsDetailInfoVoList = new ArrayList<>();

        if (!StringUtils.isEmpty(siteId)) {
            // 获取该设备下所有pcs的id
            List<Map<String, Object>> pcsIds = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.PCS.getCode());
            List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId);
            Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList);
            Map<String, InfluxPointDataWriter.PointValue> latestPointCache = new HashMap<>();

            for (Map<String, Object> pcsDevice : pcsIds) {
                PcsDetailInfoVo pcsDetailInfoVo = new PcsDetailInfoVo();
                String pcsId = String.valueOf(pcsDevice.get(DEVICE_INFO_ID));
                pcsDetailInfoVo.setSiteId(siteId);
                pcsDetailInfoVo.setDeviceId(pcsId);
                pcsDetailInfoVo.setDeviceName(String.valueOf(pcsDevice.get(DEVICE_INFO_NAME)));

                fillPcsDetailByLatestPointMapping(siteId, pcsId, pcsDetailInfoVo, mappingByFieldAndDevice, latestPointCache);
                if (StringUtils.isBlank(pcsDetailInfoVo.getCommunicationStatus())) {
                    pcsDetailInfoVo.setCommunicationStatus(pcsDevice.get(DEVICE_INFO_COMM_STATUS) == null
                            ? ""
                            : pcsDevice.get(DEVICE_INFO_COMM_STATUS).toString());
                }
                if (StringUtils.isBlank(pcsDetailInfoVo.getDeviceStatus())) {
                    pcsDetailInfoVo.setDeviceStatus(pcsDevice.get(DEVICE_INFO_DEVICE_STATUS) == null
                            ? ""
                            : pcsDevice.get(DEVICE_INFO_DEVICE_STATUS).toString());
                }
                // 支路信息数据
                List<PcsBranchInfo> pcsBranchInfoList  = new  ArrayList<>();
                processBranchDataInfo(siteId,pcsId,pcsBranchInfoList);
                pcsDetailInfoVo.setPcsBranchInfoList(pcsBranchInfoList);
                // 报警个数
                int alarmNum = emsAlarmRecordsMapper.getDeviceAlarmNum(siteId,pcsId);
//                // 告警设备点位个数
//                int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, pcsId, DeviceCategory.PCS.getCode());
                pcsDetailInfoVo.setAlarmNum(alarmNum);

                // 处理枚举匹配字段
                devicePointMatchDataProcessor.convertFieldValueToEnumMatch(siteId, DeviceCategory.PCS.getCode(), pcsDetailInfoVo);

                pcsDetailInfoVoList.add(pcsDetailInfoVo);
            }
        }
        return pcsDetailInfoVoList;
    }

    private void fillPcsDetailByLatestPointMapping(String siteId,
                                                   String deviceId,
                                                   PcsDetailInfoVo target,
                                                   Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice,
                                                   Map<String, InfluxPointDataWriter.PointValue> latestPointCache) {
        if (StringUtils.isAnyBlank(siteId, deviceId) || target == null || mappingByFieldAndDevice == null) {
            return;
        }
        BeanWrapper beanWrapper = new BeanWrapperImpl(target);
        Date latestDataTime = null;
        for (PropertyDescriptor pd : beanWrapper.getPropertyDescriptors()) {
            if (pd == null || StringUtils.isBlank(pd.getName()) || pd.getWriteMethod() == null) {
                continue;
            }
            String fieldCode = pd.getName();
            if (PCS_DETAIL_META_FIELDS.contains(fieldCode)) {
                continue;
            }
            EmsSiteMonitorPointMatch pointMatch = resolvePointMatchByFieldAndDevice(mappingByFieldAndDevice, fieldCode, deviceId);
            if (pointMatch == null) {
                continue;
            }
            Object fieldValue;
            Date valueTime = null;
            if (USE_FIXED_DISPLAY_YES == (pointMatch.getUseFixedDisplay() == null ? 0 : pointMatch.getUseFixedDisplay())
                    && StringUtils.isNotBlank(pointMatch.getFixedDataPoint())) {
                fieldValue = pointMatch.getFixedDataPoint().trim();
            } else {
                InfluxPointDataWriter.PointValue latestValue = getLatestPointValueByPointId(siteId, pointMatch.getDataPoint(), latestPointCache);
                if (latestValue == null || latestValue.getPointValue() == null) {
                    continue;
                }
                fieldValue = latestValue.getPointValue();
                valueTime = latestValue.getDataTime();
            }
            Object convertedValue = convertFieldValueByType(fieldValue, pd.getPropertyType());
            if (convertedValue == null) {
                continue;
            }
            beanWrapper.setPropertyValue(fieldCode, convertedValue);
            if (valueTime != null && (latestDataTime == null || valueTime.after(latestDataTime))) {
                latestDataTime = valueTime;
            }
        }
        if (latestDataTime != null) {
            target.setDataUpdateTime(latestDataTime);
        }
    }

    private EmsSiteMonitorPointMatch resolvePointMatchByFieldAndDevice(Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice,
                                                                        String fieldCode,
                                                                        String deviceId) {
        if (mappingByFieldAndDevice == null || StringUtils.isBlank(fieldCode)) {
            return null;
        }
        String normalizedField = fieldCode.trim();
        String normalizedDeviceId = StringUtils.defaultString(deviceId).trim();
        EmsSiteMonitorPointMatch exact = mappingByFieldAndDevice.get(buildFieldDeviceKey(normalizedField, normalizedDeviceId));
        if (exact != null) {
            return exact;
        }
        return mappingByFieldAndDevice.get(buildFieldDeviceKey(normalizedField, ""));
    }

    private InfluxPointDataWriter.PointValue getLatestPointValueByPointId(String siteId,
                                                                           String pointId,
                                                                           Map<String, InfluxPointDataWriter.PointValue> latestPointCache) {
        if (StringUtils.isAnyBlank(siteId, pointId)) {
            return null;
        }
        String pointCacheKey = pointId.trim().toUpperCase();
        if (latestPointCache.containsKey(pointCacheKey)) {
            return latestPointCache.get(pointCacheKey);
        }
        Date endTime = DateUtils.getNowDate();
        Date fastStartTime = DateUtils.addDays(endTime, -1);
        InfluxPointDataWriter.PointValue latest = influxPointDataWriter.queryLatestPointValueByPointKey(
                siteId.trim(), pointId.trim(), fastStartTime, endTime
        );
        if (latest == null) {
            Date fallbackStartTime = DateUtils.addDays(endTime, -7);
            latest = influxPointDataWriter.queryLatestPointValueByPointKey(
                    siteId.trim(), pointId.trim(), fallbackStartTime, endTime
            );
        }
        latestPointCache.put(pointCacheKey, latest);
        return latest;
    }

    private Object convertFieldValueByType(Object rawValue, Class<?> targetType) {
        if (rawValue == null || targetType == null) {
            return null;
        }
        if (BigDecimal.class.equals(targetType)) {
            return StringUtils.getBigDecimal(rawValue);
        }
        if (String.class.equals(targetType)) {
            if (rawValue instanceof BigDecimal) {
                return ((BigDecimal) rawValue).stripTrailingZeros().toPlainString();
            }
            return String.valueOf(rawValue);
        }
        if (Date.class.equals(targetType) && rawValue instanceof Date) {
            return rawValue;
        }
        return null;
    }

    private void processBranchDataInfo(String siteId, String pcsId, List<PcsBranchInfo> pcsBranchInfoList) {
        if (!StringUtils.isEmpty(pcsId)) {
            List<EmsPcsBranchData> pcsBranchData = redisCache.getCacheObject(RedisKeyConstants.BRANCH +siteId+"_"+pcsId);
            if (pcsBranchData != null) {
                for(EmsPcsBranchData emsPcsBranchData : pcsBranchData) {
                    PcsBranchInfo pcsBranchInfo = new PcsBranchInfo();
                    BeanUtils.copyProperties(emsPcsBranchData, pcsBranchInfo);
                    pcsBranchInfoList.add(pcsBranchInfo);
                }
            }
        }
    }

    // 获取BMS总览数据
    @Override
    public List<BMSOverViewVo> getBMSOverView(String siteId) {
        List<BMSOverViewVo> bmsOverViewVoList = new ArrayList<>();

        // 获取所有电池堆
        if (!StringUtils.isEmpty(siteId)) {
            List<Map<String, Object>> stackIds = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.STACK.getCode());
            for (Map<String, Object> stackDevice : stackIds) {
                BMSOverViewVo bmsOverViewVo = new BMSOverViewVo();
                bmsOverViewVo.setDeviceName(stackDevice.get("deviceName").toString());
                // 从redis取堆单个详细数据
                String stackId = stackDevice.get("id").toString();
                EmsBatteryStack stackData = redisCache.getCacheObject(RedisKeyConstants.STACK +siteId+"_"+stackId);
                if  (stackData != null) {
                    BeanUtils.copyProperties(stackData, bmsOverViewVo);
                }
                // 下面簇列表数据
                List<BMSBatteryDataList> batteryDataLists = new  ArrayList<>();
                if (!StringUtils.isEmpty(stackId)) {
                    batteryDataLists = getBMSClusterListInfo(siteId,stackId,null);
                    bmsOverViewVo.setBatteryDataList(batteryDataLists);
                }

                // 报警个数
                int alarmNum = emsAlarmRecordsMapper.getDeviceAlarmNum(siteId, stackId);
//                // 告警设备点位个数
//                int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, stackId, DeviceCategory.STACK.getCode());
                bmsOverViewVo.setAlarmNum(alarmNum);

                // 处理枚举匹配字段
                devicePointMatchDataProcessor.convertFieldValueToEnumMatch(siteId, DeviceCategory.STACK.getCode(), bmsOverViewVo);

                bmsOverViewVoList.add(bmsOverViewVo);
            }
        }
        return bmsOverViewVoList;
    }

    private List<BMSBatteryDataList> getBMSClusterListInfo(String siteId, String stackId, String clusterDeviceId) {
        List<BMSBatteryDataList> batteryDataList = new ArrayList();

        List<Map<String, Object>> clusterIds = new ArrayList<>();
        if (StringUtils.isEmpty(clusterDeviceId)) {
            clusterIds = emsDevicesSettingMapper.getClusterIdsByFuzzyQuery(siteId, DeviceCategory.CLUSTER.getCode(),stackId);
        } else {
            Map<String, Object> map = new HashMap<>();
            map.put("id", clusterDeviceId);
            clusterIds.add(map);
        }

        for (Map<String, Object> clusterDevice : clusterIds) {
            BMSBatteryDataList bmsBatteryDataList= new BMSBatteryDataList();
            // 从redis取单个簇的详细数据
            String clusterId = clusterDevice.get("id").toString();
            bmsBatteryDataList.setClusterId(clusterId);
            EmsBatteryCluster clusterData = redisCache.getCacheObject(RedisKeyConstants.CLUSTER + siteId + "_" + clusterId);
            if (clusterData != null) {
                BeanUtils.copyProperties(clusterData, bmsBatteryDataList);
            }
            batteryDataList.add(bmsBatteryDataList);
        }

        return batteryDataList;
    }

    // 获取BMS电池簇数据
    @Override
    public List<BMSBatteryClusterVo> getBMSBatteryCluster(String siteId) {
        List<BMSBatteryClusterVo> bmsBatteryClusterVoList = new ArrayList<>();

        if (!StringUtils.isEmpty(siteId)) {
            // 获取所有设备下的电池簇id
            List<Map<String, Object>> clusterIds = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.CLUSTER.getCode());
            for (Map<String, Object> clusterDevice : clusterIds) {
                BMSBatteryClusterVo bmsBatteryClusterVo = new BMSBatteryClusterVo();
                bmsBatteryClusterVo.setDeviceName(clusterDevice.get("deviceName").toString());
                bmsBatteryClusterVo.setParentDeviceName(clusterDevice.get("parentDeviceName") == null ? null : clusterDevice.get("parentDeviceName").toString());
                // 从redis取单个簇详细数据
                String clusterId = clusterDevice.get("id").toString();
                EmsBatteryCluster clusterData = redisCache.getCacheObject(RedisKeyConstants.CLUSTER +siteId+"_"+clusterId);
                String parentDeviceId = "";
                if  (clusterData != null) {
                    BeanUtils.copyProperties(clusterData, bmsBatteryClusterVo);
                    // 处理单体电池数据-平均/最大/最小
                    List<BMSBatteryClusterDataList> clusterDataList = new ArrayList<>();
                    dealWithBatteryClusterData(clusterData,clusterDataList);
                    bmsBatteryClusterVo.setBatteryDataList(clusterDataList);
                }
                // 报警个数
                int alarmNum = emsAlarmRecordsMapper.getDeviceAlarmNum(siteId, clusterId);
//                // 告警设备点位个数
//                int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, clusterId, DeviceCategory.CLUSTER.getCode());
                bmsBatteryClusterVo.setAlarmNum(alarmNum);

                // 处理枚举匹配字段
                devicePointMatchDataProcessor.convertFieldValueToEnumMatch(siteId, DeviceCategory.CLUSTER.getCode(), bmsBatteryClusterVo);

                bmsBatteryClusterVoList.add(bmsBatteryClusterVo);
            }
        }
        return bmsBatteryClusterVoList;
    }

    private void dealWithBatteryClusterData(EmsBatteryCluster clusterData, List<BMSBatteryClusterDataList> clusterDataList) {
        BMSBatteryClusterDataList voltageData = new BMSBatteryClusterDataList();
        BMSBatteryClusterDataList tempData = new BMSBatteryClusterDataList();
        BMSBatteryClusterDataList socData = new BMSBatteryClusterDataList();

        // 设值
        voltageData.setDataName(CLUSTER_DATA_VOLTAGE);
        voltageData.setAvgData(clusterData.getAvgCellVoltage());
        voltageData.setMaxData(clusterData.getMaxCellVoltage());
        voltageData.setMaxDataID(clusterData.getMaxCellVoltageId() == null ? null : clusterData.getMaxCellVoltageId().toString());
        voltageData.setMinData(clusterData.getMinCellVoltage());
        voltageData.setMinDataID(clusterData.getMinCellVoltageId() == null ? null : clusterData.getMinCellVoltageId().toString());
        tempData.setDataName(CLUSTER_DATA_TEP);
        tempData.setAvgData(clusterData.getAvgCellTemp());
        tempData.setMaxData(clusterData.getMaxCellTemp());
        tempData.setMaxDataID(clusterData.getMaxCellTempId() == null ? null : clusterData.getMaxCellTempId().toString());
        tempData.setMinData(clusterData.getMinCellTemp());
        tempData.setMinDataID(clusterData.getMinCellTempId() == null ? null : clusterData.getMinCellTempId().toString());
        socData.setDataName(CLUSTER_DATA_SOC);
        socData.setAvgData(clusterData.getCurrentSoc());
        socData.setMaxData(clusterData.getMaxCellSoc());
        socData.setMaxDataID(clusterData.getMaxCellSocId() == null ? null : clusterData.getMaxCellSocId().toString());
        socData.setMinData(clusterData.getMinCellSoc());
        socData.setMinDataID(clusterData.getMinCellSocId() == null ? null : clusterData.getMinCellSocId().toString());

        clusterDataList.add(voltageData);
        clusterDataList.add(tempData);
        clusterDataList.add(socData);
    }

    // 获取液冷设备参数
    @Override
    public  List<CoolingDataViewVo> getCoolingDataList(String siteId) {
        List<CoolingDataViewVo> emsCoolingDataList = new ArrayList<>();

        List<Map<String, Object>> deviceIds = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.COOLING.getCode());
        for (Map<String, Object> deviceId : deviceIds) {
            CoolingDataViewVo dataViewVo = new CoolingDataViewVo();
            dataViewVo.setDeviceName(deviceId.get("deviceName").toString());
            // 从redis取堆单个详细数据
            String coolingId = deviceId.get("id").toString();
            EmsCoolingData coolingData = redisCache.getCacheObject(RedisKeyConstants.COOLING + siteId + "_" + coolingId);
            if (coolingData != null) {
                BeanUtils.copyProperties(coolingData, dataViewVo);
                // 报警个数
                int alarmNum = emsAlarmRecordsMapper.getDeviceAlarmNum(siteId, coolingId);
//                // 告警设备点位个数
//                int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, coolingId, DeviceCategory.COOLING.getCode());
                dataViewVo.setAlarmNum(alarmNum);
            }

            emsCoolingDataList.add(dataViewVo);
        }
        return emsCoolingDataList;
    }


    /**
     * 根据电池堆获取单体电池数据
     * @param clusterDeviceId
     * @return
     */
    @Override
    public  List<BatteryDataStatsListVo> getClusterDataInfoList(String clusterDeviceId,String  siteId,
                                                                String stackDeviceId, String batteryId) {
        List<BatteryDataStatsListVo> batteryDataStatsListVo = new ArrayList<>();
        log.info("getClusterDataInfoList start, siteId={}, stackDeviceId={}, clusterDeviceId={}, batteryId={}",
                siteId, stackDeviceId, clusterDeviceId, batteryId);
        String targetStackDeviceId = StringUtils.trimToEmpty(stackDeviceId);
        String targetClusterDeviceId = StringUtils.trimToEmpty(clusterDeviceId);
        String targetBatteryId = StringUtils.trimToEmpty(batteryId);

        if ("undefined".equalsIgnoreCase(targetStackDeviceId) || "null".equalsIgnoreCase(targetStackDeviceId)) {
            targetStackDeviceId = "";
        }
        if ("undefined".equalsIgnoreCase(targetClusterDeviceId) || "null".equalsIgnoreCase(targetClusterDeviceId)) {
            targetClusterDeviceId = "";
        }
        if ("undefined".equalsIgnoreCase(targetBatteryId) || "null".equalsIgnoreCase(targetBatteryId)) {
            targetBatteryId = "";
        }
        log.info("getClusterDataInfoList normalized params, siteId={}, stackDeviceId={}, clusterDeviceId={}, batteryId={}",
                siteId, targetStackDeviceId, targetClusterDeviceId, targetBatteryId);

        if (StringUtils.isEmpty(siteId)) {
            log.warn("getClusterDataInfoList early return, siteId is empty");
            return batteryDataStatsListVo;
        }
        List<SiteMonitorProjectDisplayVo> projectDisplayList = iEmsDeviceSettingService.getSiteMonitorProjectDisplay(siteId);
        Map<String, Map<String, SiteMonitorProjectDisplayVo>> batteryDisplayMap =
                buildBatteryDisplayMap(projectDisplayList);
        Map<String, Map<String, SiteMonitorProjectDisplayVo>> clusterDisplayMap =
                buildClusterDisplayMap(projectDisplayList);
        if (batteryDisplayMap.isEmpty()) {
            log.warn("getClusterDataInfoList no battery display data from projectDisplay, siteId={}", siteId);
        }
        if (clusterDisplayMap.isEmpty()) {
            log.warn("getClusterDataInfoList no cluster display data from projectDisplay, siteId={}", siteId);
        }
        log.info("getClusterDataInfoList battery list source projectDisplay, siteId={}, batteryCount={}",
                siteId, batteryDisplayMap.size());

        Map<String, EmsDevicesSetting> batteryDeviceCache = new HashMap<>();
        Map<String, EmsDevicesSetting> clusterDeviceCache = new HashMap<>();
        List<String> sortedBatteryIds = new ArrayList<>(batteryDisplayMap.keySet());
        Collections.sort(sortedBatteryIds);

        for (String batteryDeviceId : sortedBatteryIds) {
            if (StringUtils.isEmpty(batteryDeviceId)) {
                continue;
            }
            if (StringUtils.isNotEmpty(targetBatteryId) && !batteryDeviceId.equals(targetBatteryId)) {
                continue;
            }

            EmsDevicesSetting batterySetting = batteryDeviceCache.computeIfAbsent(
                    batteryDeviceId, key -> emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(key, siteId));
            String currentClusterDeviceId = batterySetting == null ? "" : StringUtils.trimToEmpty(batterySetting.getParentId());

            if (StringUtils.isNotEmpty(targetClusterDeviceId) && !targetClusterDeviceId.equals(currentClusterDeviceId)) {
                continue;
            }
            if (StringUtils.isNotEmpty(targetStackDeviceId)) {
                if (StringUtils.isEmpty(currentClusterDeviceId)) {
                    continue;
                }
                EmsDevicesSetting clusterSetting = clusterDeviceCache.computeIfAbsent(
                        currentClusterDeviceId, key -> emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(key, siteId));
                String parentStackDeviceId = clusterSetting == null ? "" : StringUtils.trimToEmpty(clusterSetting.getParentId());
                if (!targetStackDeviceId.equals(parentStackDeviceId)) {
                    continue;
                }
            }

            BatteryDataStatsListVo batteryDataStatsVo = new BatteryDataStatsListVo();
            fillBatteryValueFromProjectDisplay(batteryDataStatsVo, batteryDisplayMap.get(batteryDeviceId));
            batteryDataStatsVo.setDeviceId(batteryDeviceId);
            batteryDataStatsVo.setClusterDeviceId(currentClusterDeviceId);
            // 单体电池未配置 SOC/SOH 点位时，回退使用所属电池簇的 SOC/SOH 点位和值。
            applyClusterSocSohFallback(batteryDataStatsVo,
                    clusterDisplayMap.get(currentClusterDeviceId), currentClusterDeviceId);
            batteryDataStatsListVo.add(batteryDataStatsVo);
            log.info("getClusterDataInfoList battery from projectDisplay list, siteId={}, batteryDeviceId={}, clusterDeviceId={}, voltage={}, temperature={}, soc={}, soh={}, voltagePointId={}, temperaturePointId={}, socPointId={}, sohPointId={}, dataTimestamp={}",
                    siteId, batteryDeviceId, currentClusterDeviceId, batteryDataStatsVo.getVoltage(),
                    batteryDataStatsVo.getTemperature(), batteryDataStatsVo.getSoc(),
                    batteryDataStatsVo.getSoh(), batteryDataStatsVo.getVoltagePointId(),
                    batteryDataStatsVo.getTemperaturePointId(), batteryDataStatsVo.getSocPointId(),
                    batteryDataStatsVo.getSohPointId(), batteryDataStatsVo.getDataTimestamp());
        }

        if (batteryDataStatsListVo.isEmpty()) {
            log.info("getClusterDataInfoList fallback to realtime cache, siteId={}, stackDeviceId={}, clusterDeviceId={}, batteryId={}",
                    siteId, targetStackDeviceId, targetClusterDeviceId, targetBatteryId);
            appendBatteryFromRealtimeCache(siteId, targetStackDeviceId, targetClusterDeviceId, targetBatteryId,
                    batteryDisplayMap, clusterDisplayMap, batteryDataStatsListVo);
        }

        log.info("getClusterDataInfoList end, siteId={}, stackDeviceId={}, clusterDeviceId={}, batteryId={}, total={}",
                siteId, targetStackDeviceId, targetClusterDeviceId, targetBatteryId, batteryDataStatsListVo.size());
        return batteryDataStatsListVo;
    }

    private void appendBatteryFromRealtimeCache(String siteId,
                                                String targetStackDeviceId,
                                                String targetClusterDeviceId,
                                                String targetBatteryId,
                                                Map<String, Map<String, SiteMonitorProjectDisplayVo>> batteryDisplayMap,
                                                Map<String, Map<String, SiteMonitorProjectDisplayVo>> clusterDisplayMap,
                                                List<BatteryDataStatsListVo> targetList) {
        if (StringUtils.isBlank(siteId) || targetList == null) {
            return;
        }
        List<Map<String, Object>> clusterIds = new ArrayList<>();
        if (StringUtils.isNotBlank(targetClusterDeviceId)) {
            Map<String, Object> map = new HashMap<>();
            map.put("id", targetClusterDeviceId);
            clusterIds.add(map);
        } else {
            clusterIds = emsDevicesSettingMapper.getClusterIdsByFuzzyQuery(siteId,
                    DeviceCategory.CLUSTER.getCode(), StringUtils.trimToEmpty(targetStackDeviceId));
        }
        if (CollectionUtils.isEmpty(clusterIds)) {
            return;
        }
        for (Map<String, Object> clusterDevice : clusterIds) {
            if (clusterDevice == null || clusterDevice.get("id") == null) {
                continue;
            }
            String clusterId = StringUtils.trimToEmpty(String.valueOf(clusterDevice.get("id")));
            if (StringUtils.isBlank(clusterId)) {
                continue;
            }
            List<EmsBatteryData> batteryDataList = redisCache.getCacheList(RedisKeyConstants.BATTERY + siteId + "_" + clusterId);
            if (CollectionUtils.isEmpty(batteryDataList)) {
                continue;
            }
            for (EmsBatteryData batteryData : batteryDataList) {
                if (batteryData == null) {
                    continue;
                }
                String currentBatteryId = StringUtils.trimToEmpty(batteryData.getDeviceId());
                if (StringUtils.isNotEmpty(targetBatteryId) && !targetBatteryId.equals(currentBatteryId)) {
                    continue;
                }
                BatteryDataStatsListVo batteryDataStatsVo = new BatteryDataStatsListVo();
                BeanUtils.copyProperties(batteryData, batteryDataStatsVo);
                if (StringUtils.isBlank(batteryDataStatsVo.getClusterDeviceId())) {
                    batteryDataStatsVo.setClusterDeviceId(clusterId);
                }
                fillBatteryPointIdFromProjectDisplay(batteryDataStatsVo, batteryDisplayMap == null ? null : batteryDisplayMap.get(currentBatteryId));
                // Redis 实时缓存中的单体数据如果没有单独配置 SOC/SOH 点位，同样回退到电池簇配置。
                applyClusterSocSohFallback(batteryDataStatsVo,
                        clusterDisplayMap == null ? null : clusterDisplayMap.get(clusterId), clusterId);
                targetList.add(batteryDataStatsVo);
            }
        }
    }

    private void fillBatteryDetailByLatestPointMapping(String siteId,
                                                       String deviceId,
                                                       BatteryDataStatsListVo target,
                                                       Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice,
                                                       Map<String, InfluxPointDataWriter.PointValue> latestPointCache) {
        if (StringUtils.isAnyBlank(siteId, deviceId) || target == null || mappingByFieldAndDevice == null) {
            return;
        }
        BeanWrapper beanWrapper = new BeanWrapperImpl(target);
        Date latestDataTime = null;
        for (PropertyDescriptor pd : beanWrapper.getPropertyDescriptors()) {
            if (pd == null || StringUtils.isBlank(pd.getName()) || pd.getWriteMethod() == null) {
                continue;
            }
            String fieldCode = pd.getName();
            if (BATTERY_DETAIL_META_FIELDS.contains(fieldCode)) {
                continue;
            }
            EmsSiteMonitorPointMatch pointMatch = resolvePointMatchByFieldAndDevice(mappingByFieldAndDevice, fieldCode, deviceId);
            if (pointMatch == null) {
                continue;
            }
            Object fieldValue;
            Date valueTime = null;
            if (USE_FIXED_DISPLAY_YES == (pointMatch.getUseFixedDisplay() == null ? 0 : pointMatch.getUseFixedDisplay())
                    && StringUtils.isNotBlank(pointMatch.getFixedDataPoint())) {
                fieldValue = pointMatch.getFixedDataPoint().trim();
            } else {
                InfluxPointDataWriter.PointValue latestValue = getLatestPointValueByPointId(siteId, pointMatch.getDataPoint(), latestPointCache);
                if (latestValue == null || latestValue.getPointValue() == null) {
                    continue;
                }
                fieldValue = latestValue.getPointValue();
                valueTime = latestValue.getDataTime();
            }
            Object convertedValue = convertFieldValueByType(fieldValue, pd.getPropertyType());
            if (convertedValue == null) {
                continue;
            }
            beanWrapper.setPropertyValue(fieldCode, convertedValue);
            if (valueTime != null && (latestDataTime == null || valueTime.after(latestDataTime))) {
                latestDataTime = valueTime;
            }
        }
        if (latestDataTime != null) {
            target.setDataTimestamp(latestDataTime);
        }
    }

    private Map<String, Map<String, SiteMonitorProjectDisplayVo>> buildBatteryDisplayMap(List<SiteMonitorProjectDisplayVo> displayList) {
        Map<String, Map<String, SiteMonitorProjectDisplayVo>> result = new HashMap<>();
        if (CollectionUtils.isEmpty(displayList)) {
            return result;
        }
        for (SiteMonitorProjectDisplayVo item : displayList) {
            if (item == null) {
                continue;
            }
            String menuCode = StringUtils.trimToEmpty(item.getMenuCode());
            String deviceId = StringUtils.trimToEmpty(item.getDeviceId());
            String fieldCode = StringUtils.trimToEmpty(item.getFieldCode());
            if (!MENU_SBJK_DTDC.equals(menuCode) || StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(fieldCode)) {
                continue;
            }
            Map<String, SiteMonitorProjectDisplayVo> fieldMap = result.computeIfAbsent(deviceId, key -> new HashMap<>());
            fieldMap.put(fieldCode, item);
            String normalizedFieldCode = normalizeBatteryFieldCode(fieldCode);
            if (StringUtils.isNotBlank(normalizedFieldCode)) {
                fieldMap.put(normalizedFieldCode, item);
            }
        }
        return result;
    }

    private Map<String, Map<String, SiteMonitorProjectDisplayVo>> buildClusterDisplayMap(List<SiteMonitorProjectDisplayVo> displayList) {
        Map<String, Map<String, SiteMonitorProjectDisplayVo>> result = new HashMap<>();
        if (CollectionUtils.isEmpty(displayList)) {
            return result;
        }
        for (SiteMonitorProjectDisplayVo item : displayList) {
            if (item == null) {
                continue;
            }
            String menuCode = StringUtils.trimToEmpty(item.getMenuCode());
            String deviceId = StringUtils.trimToEmpty(item.getDeviceId());
            String fieldCode = StringUtils.trimToEmpty(item.getFieldCode());
            if (!MENU_SBJK_BMSDCC.equals(menuCode) || StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(fieldCode)) {
                continue;
            }
            Map<String, SiteMonitorProjectDisplayVo> fieldMap = result.computeIfAbsent(deviceId, key -> new HashMap<>());
            fieldMap.put(fieldCode, item);
            String normalizedFieldCode = normalizeBatteryFieldCode(fieldCode);
            if (StringUtils.isNotBlank(normalizedFieldCode)) {
                fieldMap.put(normalizedFieldCode, item);
            }
        }
        return result;
    }

    private String normalizeBatteryFieldCode(String fieldCode) {
        String normalized = StringUtils.trimToEmpty(fieldCode);
        if (StringUtils.isBlank(normalized)) {
            return normalized;
        }
        int splitIndex = normalized.lastIndexOf("__");
        if (splitIndex >= 0 && splitIndex + 2 < normalized.length()) {
            normalized = normalized.substring(splitIndex + 2);
        }
        return normalized.toLowerCase();
    }

    private void fillBatteryValueFromProjectDisplay(BatteryDataStatsListVo target,
                                                    Map<String, SiteMonitorProjectDisplayVo> fieldMap) {
        if (target == null || fieldMap == null || fieldMap.isEmpty()) {
            return;
        }
        SiteMonitorProjectDisplayVo voltageVo = fieldMap.get("voltage");
        SiteMonitorProjectDisplayVo temperatureVo = fieldMap.get("temperature");
        SiteMonitorProjectDisplayVo socVo = fieldMap.get("soc");
        SiteMonitorProjectDisplayVo sohVo = fieldMap.get("soh");

        target.setVoltagePointId(voltageVo == null ? null : StringUtils.trimToNull(voltageVo.getDataPoint()));
        target.setTemperaturePointId(temperatureVo == null ? null : StringUtils.trimToNull(temperatureVo.getDataPoint()));
        target.setSocPointId(socVo == null ? null : StringUtils.trimToNull(socVo.getDataPoint()));
        target.setSohPointId(sohVo == null ? null : StringUtils.trimToNull(sohVo.getDataPoint()));

        if (voltageVo != null && StringUtils.isNotBlank(voltageVo.getFieldValue())) {
            target.setVoltage(StringUtils.getBigDecimal(voltageVo.getFieldValue()));
        }
        if (temperatureVo != null && StringUtils.isNotBlank(temperatureVo.getFieldValue())) {
            target.setTemperature(StringUtils.getBigDecimal(temperatureVo.getFieldValue()));
        }
        if (socVo != null && StringUtils.isNotBlank(socVo.getFieldValue())) {
            target.setSoc(StringUtils.getBigDecimal(socVo.getFieldValue()));
        }
        if (sohVo != null && StringUtils.isNotBlank(sohVo.getFieldValue())) {
            target.setSoh(StringUtils.getBigDecimal(sohVo.getFieldValue()));
        }

        Date latest = null;
        latest = pickLaterTime(latest, voltageVo == null ? null : voltageVo.getValueTime());
        latest = pickLaterTime(latest, temperatureVo == null ? null : temperatureVo.getValueTime());
        latest = pickLaterTime(latest, socVo == null ? null : socVo.getValueTime());
        latest = pickLaterTime(latest, sohVo == null ? null : sohVo.getValueTime());
        if (latest != null) {
            target.setDataTimestamp(latest);
        }
    }

    private void fillBatteryPointIdFromProjectDisplay(BatteryDataStatsListVo target,
                                                      Map<String, SiteMonitorProjectDisplayVo> fieldMap) {
        if (target == null || fieldMap == null || fieldMap.isEmpty()) {
            return;
        }
        SiteMonitorProjectDisplayVo voltageVo = fieldMap.get("voltage");
        SiteMonitorProjectDisplayVo temperatureVo = fieldMap.get("temperature");
        SiteMonitorProjectDisplayVo socVo = fieldMap.get("soc");
        SiteMonitorProjectDisplayVo sohVo = fieldMap.get("soh");
        target.setVoltagePointId(voltageVo == null ? null : StringUtils.trimToNull(voltageVo.getDataPoint()));
        target.setTemperaturePointId(temperatureVo == null ? null : StringUtils.trimToNull(temperatureVo.getDataPoint()));
        target.setSocPointId(socVo == null ? null : StringUtils.trimToNull(socVo.getDataPoint()));
        target.setSohPointId(sohVo == null ? null : StringUtils.trimToNull(sohVo.getDataPoint()));
    }

    private void applyClusterSocSohFallback(BatteryDataStatsListVo target,
                                            Map<String, SiteMonitorProjectDisplayVo> clusterFieldMap,
                                            String clusterDeviceId) {
        if (target == null || clusterFieldMap == null || clusterFieldMap.isEmpty()) {
            return;
        }
        boolean useClusterSoc = StringUtils.isBlank(target.getSocPointId());
        boolean useClusterSoh = StringUtils.isBlank(target.getSohPointId());
        if (!useClusterSoc && !useClusterSoh) {
            return;
        }

        SiteMonitorProjectDisplayVo clusterSocVo = clusterFieldMap.get("currentsoc");
        SiteMonitorProjectDisplayVo clusterSohVo = clusterFieldMap.get("soh");
        Date latest = target.getDataTimestamp();

        // 单体没有配置 SOC 点位时，使用所属电池簇的 currentSoc 作为兜底。
        if (useClusterSoc && clusterSocVo != null) {
            target.setSocPointId(StringUtils.trimToNull(clusterSocVo.getDataPoint()));
            if (StringUtils.isNotBlank(clusterSocVo.getFieldValue())) {
                target.setSoc(StringUtils.getBigDecimal(clusterSocVo.getFieldValue()));
            }
            latest = pickLaterTime(latest, clusterSocVo.getValueTime());
        }

        // 单体没有配置 SOH 点位时，使用所属电池簇的 soh 作为兜底。
        if (useClusterSoh && clusterSohVo != null) {
            target.setSohPointId(StringUtils.trimToNull(clusterSohVo.getDataPoint()));
            if (StringUtils.isNotBlank(clusterSohVo.getFieldValue())) {
                target.setSoh(StringUtils.getBigDecimal(clusterSohVo.getFieldValue()));
            }
            latest = pickLaterTime(latest, clusterSohVo.getValueTime());
        }

        if (latest != null) {
            target.setDataTimestamp(latest);
        }
        log.info("applyClusterSocSohFallback, clusterDeviceId={}, batteryDeviceId={}, useClusterSoc={}, useClusterSoh={}, socPointId={}, sohPointId={}, soc={}, soh={}",
                clusterDeviceId, target.getDeviceId(), useClusterSoc, useClusterSoh,
                target.getSocPointId(), target.getSohPointId(), target.getSoc(), target.getSoh());
    }

    private Date pickLaterTime(Date left, Date right) {
        if (left == null) {
            return right;
        }
        if (right == null) {
            return left;
        }
        return right.after(left) ? right : left;
    }

    // 获取单站的最大最小温度和电压单体数据id
    @Override
    public List<BMSBatteryDataList> getClusterBatteryList(String siteId, String stackDeviceId, String clusterDeviceId) {
        return getBMSClusterListInfo(siteId, stackDeviceId,clusterDeviceId);
    }

    /**
     * 电表数据信息
     * @param siteId
     * @return
     */
    @Override
    public List<AmmeterDataVo> getAmmeterDataList(String siteId) {
        List<AmmeterDataVo> ammeterResponse = new ArrayList<>();

        if (StringUtils.isEmpty(siteId)) {
            return ammeterResponse;
        }

        // 先获取电表设备
        List<Map<String, Object>> ammeterIdList = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.AMMETER.getCode());
        if (ammeterIdList == null || ammeterIdList.isEmpty()) {
            return ammeterResponse;
        }
        for (Map<String, Object> ammeterDevice : ammeterIdList) {
            String ammeterId = ammeterDevice.get("id").toString();
            // 从redis取总表详细数据
            EmsAmmeterData ammeterData = redisCache.getCacheObject(RedisKeyConstants.AMMETER + siteId + "_" +ammeterId);
            // 报警个数
            int alarmNum = emsAlarmRecordsMapper.getDeviceAlarmNum(siteId, ammeterId);
//            // 告警设备点位个数
//            int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, ammeterId, DeviceCategory.AMMETER.getCode());

            AmmeterDataVo ammeterDataVo = new AmmeterDataVo();
            ammeterDataVo.setDeviceName(ammeterDevice.get("deviceName").toString());
            ammeterDataVo.setDeviceId(ammeterDevice.get("id").toString());
            ammeterDataVo.setEmsCommunicationStatus(ammeterDevice.get("communicationStatus") == null? "" :
                    ammeterDevice.get("communicationStatus").toString());
            ammeterDataVo.setAlarmNum(alarmNum);
            // 设置表数据
            dealAmmeterData(ammeterData,ammeterDataVo);

            // 处理枚举匹配字段
            devicePointMatchDataProcessor.convertFieldValueToEnumMatch(siteId, DeviceCategory.AMMETER.getCode(), ammeterDataVo);

            ammeterResponse.add(ammeterDataVo);
        }
        return ammeterResponse;
    }

    private void dealAmmeterData(EmsAmmeterData ammeterData, AmmeterDataVo ammeterDataVo) {
        if  (ammeterData != null) {
            ammeterDataVo.setActivePower(ammeterData.getTotalActivePower() == null ? ammeterData.getPower() : ammeterData.getTotalActivePower());
            ammeterDataVo.setReactivePower(ammeterData.getTotalReactivePower());
            ammeterDataVo.setForwardActive(ammeterData.getCurrentForwardActiveTotal());
            ammeterDataVo.setForwardReactive(ammeterData.getCurrentForwardReactiveTotal());
            ammeterDataVo.setReverseActive(ammeterData.getCurrentReverseActiveTotal());
            ammeterDataVo.setReverseReactive(ammeterData.getCurrentReverseReactiveTotal());
            ammeterDataVo.setActiveEnergy(ammeterData.getPositiveActiveEnergyEpPlus());
            ammeterDataVo.setReactiveEnergy(ammeterData.getPositiveReactiveEnergyEqPlus());
            ammeterDataVo.setDataUpdateTime(ammeterData.getDataUpdateTime());
        }
    }

    private void dealAmmeterLoadData(EmsAmmeterData ammeterData, AmmeterDataVo ammeterDataVo) {
        if  (ammeterData != null) {
            // 数据更新时间
            ammeterDataVo.setDataUpdateTime(ammeterData.getDataUpdateTime());

            List<LoadDataDetailInfo> loadDataDetailInfos = new ArrayList<>();
            // 拼接数据
            // 当前正向有功
            LoadDataDetailInfo ammeterDataDetailInfo1 = new LoadDataDetailInfo();
            ammeterDataDetailInfo1.setCategory(AmmeterCategory.CURRENT_FORWARD_ACTIVE.getInfo());
            ammeterDataDetailInfo1.setTotalKwh(ammeterData.getCurrentForwardActiveTotal());
            ammeterDataDetailInfo1.setPeakKwh(ammeterData.getCurrentForwardActivePeak());
            ammeterDataDetailInfo1.setHighKwh(ammeterData.getCurrentForwardActiveHigh());
            ammeterDataDetailInfo1.setFlatKwh(ammeterData.getCurrentForwardActiveFlat());
            ammeterDataDetailInfo1.setValleyKwh(ammeterData.getCurrentForwardActiveValley());
            loadDataDetailInfos.add(ammeterDataDetailInfo1);
            // 当前正向无功
            LoadDataDetailInfo ammeterDataDetailInfo2 = new LoadDataDetailInfo();
            ammeterDataDetailInfo2.setCategory(AmmeterCategory.CURRENT_FORWARD_REACTIVE.getInfo());
            ammeterDataDetailInfo2.setTotalKwh(ammeterData.getCurrentForwardReactiveTotal());
            ammeterDataDetailInfo2.setPeakKwh(ammeterData.getCurrentForwardReactivePeak());
            ammeterDataDetailInfo2.setHighKwh(ammeterData.getCurrentForwardReactiveHigh());
            ammeterDataDetailInfo2.setFlatKwh(ammeterData.getCurrentForwardReactiveFlat());
            ammeterDataDetailInfo2.setValleyKwh(ammeterData.getCurrentForwardReactiveValley());
            loadDataDetailInfos.add(ammeterDataDetailInfo2);
            // 当前反向有功
            LoadDataDetailInfo ammeterDataDetailInfo3 = new LoadDataDetailInfo();
            ammeterDataDetailInfo3.setCategory(AmmeterCategory.CURRENT_REVERSE_ACTIVE.getInfo());
            ammeterDataDetailInfo3.setTotalKwh(ammeterData.getCurrentReverseActiveTotal());
            ammeterDataDetailInfo3.setPeakKwh(ammeterData.getCurrentReverseActivePeak());
            ammeterDataDetailInfo3.setHighKwh(ammeterData.getCurrentReverseActiveHigh());
            ammeterDataDetailInfo3.setFlatKwh(ammeterData.getCurrentReverseActiveFlat());
            ammeterDataDetailInfo3.setValleyKwh(ammeterData.getCurrentReverseActiveValley());
            loadDataDetailInfos.add(ammeterDataDetailInfo3);
            // 当前反向无功
            LoadDataDetailInfo ammeterDataDetailInfo4 = new LoadDataDetailInfo();
            ammeterDataDetailInfo4.setCategory(AmmeterCategory.CURRENT_REVERSE_REACTIVE.getInfo());
            ammeterDataDetailInfo4.setTotalKwh(ammeterData.getCurrentReverseReactiveTotal());
            ammeterDataDetailInfo4.setPeakKwh(ammeterData.getCurrentReverseReactivePeak());
            ammeterDataDetailInfo4.setHighKwh(ammeterData.getCurrentReverseReactiveHigh());
            ammeterDataDetailInfo4.setFlatKwh(ammeterData.getCurrentReverseReactiveFlat());
            ammeterDataDetailInfo4.setValleyKwh(ammeterData.getCurrentReverseReactiveValley());
            loadDataDetailInfos.add(ammeterDataDetailInfo4);

            ammeterDataVo.setLoadDataDetailInfo(loadDataDetailInfos);
        }

    }

    /**
     * 获取动环设备参数
     * @param siteId
     * @return
     */
    @Override
    public  List<DhDataVo> getDhDataList(String siteId) {
        List<DhDataVo> emsDhDataList = new ArrayList<>();

        List<Map<String, Object>> dhDeviceList = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.DH.getCode());
        for (Map<String, Object> dhDevice : dhDeviceList) {
            DhDataVo dhDataVo = new DhDataVo();
            dhDataVo.setDeviceName(dhDevice.get("deviceName").toString());
            // 从redis取堆单个详细数据
            String dhDeviceId = dhDevice.get("id").toString();
            EmsDhData dhData = redisCache.getCacheObject(RedisKeyConstants.DH + siteId + "_" + dhDeviceId);
            if (dhData != null) {
                BeanUtils.copyProperties(dhData, dhDataVo);
            }
            // 报警个数
            int alarmNum = emsAlarmRecordsMapper.getDeviceAlarmNum(siteId, dhDeviceId);
//            // 告警设备点位个数
//            int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, dhDeviceId, DeviceCategory.DH.getCode());
            dhDataVo.setAlarmNum(alarmNum);

            // 处理枚举匹配字段
            devicePointMatchDataProcessor.convertFieldValueToEnumMatch(siteId, DeviceCategory.DH.getCode(), dhDataVo);

            emsDhDataList.add(dhDataVo);
        }
        return emsDhDataList;
    }

    /**
     * 获取消防设备参数
     * @param siteId
     * @return
     */
    @Override
    public  List<XfDataVo> getXfDataList(String siteId) {
        List<XfDataVo> emsXfDataList = new ArrayList<>();

        List<Map<String, Object>> xfDeviceList = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.XF.getCode());
        for (Map<String, Object> xfDevice : xfDeviceList) {
            XfDataVo xfDataVo = new XfDataVo();
            xfDataVo.setDeviceName(xfDevice.get("deviceName").toString());
            // 从redis取堆单个详细数据
            String xfDeviceId = xfDevice.get("id").toString();
            EmsXfData xfData = redisCache.getCacheObject(RedisKeyConstants.XF + siteId + "_" + xfDeviceId);
            log.info("查询消防数据xfDataVo:{}", xfDataVo);
            if (xfData != null) {
                BeanUtils.copyProperties(xfData, xfDataVo);
                xfDataVo.setDataUpdateTime(xfData.getDataTimestamp());
                // 报警个数
                int alarmNum = emsAlarmRecordsMapper.getDeviceAlarmNum(siteId, xfDeviceId);
//                // 告警设备点位个数
//                int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, xfDeviceId, DeviceCategory.XF.getCode());
                xfDataVo.setAlarmNum(alarmNum);
            }
            xfDataVo.setEmsCommunicationStatus(xfDevice.get("communicationStatus") == null ? "" : xfDevice.get("communicationStatus").toString());

            // 处理枚举匹配字段
            devicePointMatchDataProcessor.convertFieldValueToEnumMatch(siteId, DeviceCategory.XF.getCode(), xfDataVo);

            emsXfDataList.add(xfDataVo);
        }
        return emsXfDataList;
    }

    @Override
    public List<EmsDataVo> getEmsDataList(String siteId) {
        List<EmsDataVo> emsDataList = new ArrayList<>();

        List<Map<String, Object>> xfDeviceList = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.EMS.getCode());

        for (Map<String, Object> emsDevice : xfDeviceList) {
            EmsDataVo emsDataVo = new EmsDataVo();
            emsDataVo.setDeviceName(emsDevice.get("deviceName").toString());
            // 从redis取堆单个详细数据
            String emsDeviceId = emsDevice.get("id").toString();
            EmsEmsData emsData = redisCache.getCacheObject(RedisKeyConstants.EMS + siteId + "_" + emsDeviceId);
            if (emsData != null) {
                BeanUtils.copyProperties(emsData, emsDataVo);
                // 报警个数
                int alarmNum = emsAlarmRecordsMapper.getDeviceAlarmNum(siteId, emsDeviceId);
//                // 告警设备点位个数
//                int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, emsDeviceId, DeviceCategory.EMS.getCode());
                emsDataVo.setAlarmNum(alarmNum);
            }

            // 处理枚举匹配字段
            devicePointMatchDataProcessor.convertFieldValueToEnumMatch(siteId, DeviceCategory.EMS.getCode(), emsDataVo);

            emsDataList.add(emsDataVo);
        }
        return emsDataList;
    }

    /**
     * 单站监控-首页-获取重点数据
     * @param requestVo
     * @return
     */
    @Override
    public List<PointDataResponse> getPointData(DateSearchRequest requestVo) {
        List<PointDataResponse> responseList = new ArrayList<>();
        List<PowerStatisListVo> powerList = new ArrayList<>();//功率数据
        List<StackPointVo> stackList = new ArrayList<>();//电池堆数据
//        List<Map<String,BigDecimal>> fxAvgTempList = new ArrayList<>();
        List<Map<String,BigDecimal>> avgTempList = new ArrayList<>();

        // 默认时间-7天
        dealRequestTime(requestVo);
        Date startDate = requestVo.getStartDate();
        Date endDate = requestVo.getEndDate();
        List<String> timeList = new ArrayList<>();

//        //  开始日期和结束日期同一天，展示 0-24 小时数据
//        if (DateUtils.isSameDay(startDate, endDate)){
//            endDate = DateUtils.addDays(endDate, 1);
//            requestVo.setEndDate(endDate);
//            powerList = emsAmmeterDataMapper.getPowerDataByHour(requestVo);//功率数据
//            stackList = emsBatteryStackMapper.getStackPointByHour(requestVo);//电池堆数据
//            if (SiteEnum.FX.getCode().equals(requestVo.getSiteId())){
//                fxAvgTempList = emsBatteryClusterMapper.getAvgTempByHour(requestVo);
//            }
//            avgTempList = emsBatteryClusterMapper.getAvgTempByHour(requestVo);//电池簇数据
//        }
        //  开始日期和结束日期同一天，展示每5分钟一个点数据
        if (DateUtils.isSameDay(startDate, endDate)){
            endDate = DateUtils.addDays(endDate, 1);
            requestVo.setEndDate(endDate);
            powerList = emsAmmeterDataMapper.getPowerDataByMinute(requestVo);//功率数据
            stackList = emsBatteryStackMapper.getStackPointByMinute(requestVo);//电池堆数据
            avgTempList = emsBatteryClusterMapper.getAvgTempByMinute(requestVo);//电池簇数据
        }
        else if (DateUtils.differentDaysByMillisecond(endDate, startDate) >= 1
                && DateUtils.differentDaysByMillisecond(endDate, startDate) < 30){
            endDate = DateUtils.addDays(endDate, 1);
            requestVo.setEndDate(endDate);
            //  开始日期-结束日期大于 1 天，小于30 天，按天展示数据
            powerList = emsAmmeterDataMapper.getPowerDataByDay(requestVo);//功率数据
            stackList = emsBatteryStackMapper.getStackPointByDay(requestVo);//电池堆数据
//            if (SiteEnum.FX.getCode().equals(requestVo.getSiteId())){
//                fxAvgTempList = emsBatteryClusterMapper.getAvgTempByDay(requestVo);
//            }
            avgTempList = emsBatteryClusterMapper.getAvgTempByDay(requestVo);//电池簇数据
        } else if (DateUtils.differentDaysByMillisecond(endDate, startDate) >= 30){
            endDate = DateUtils.addDays(endDate, 1);
            requestVo.setEndDate(endDate);
            //  开始日期-结束日期大于 1 个月，按月展示数据
            powerList = emsAmmeterDataMapper.getPowerDataByMonth(requestVo);//功率数据
            stackList = emsBatteryStackMapper.getStackPointByMonth(requestVo);//电池堆数据
//            if (SiteEnum.FX.getCode().equals(requestVo.getSiteId())){
//                fxAvgTempList = emsBatteryClusterMapper.getAvgTempByMonth(requestVo);
//            }
            avgTempList = emsBatteryClusterMapper.getAvgTempByMonth(requestVo);//电池簇数据
        }

//        // 如果奉贤数据：电池平均温度单独取值。将其塞入stackList里面
//        if (SiteEnum.FX.getCode().equals(requestVo.getSiteId())){
//            processFXXTempData(stackList,fxAvgTempList);
//        }
        // 优先从电池堆取，电池堆没有的话再从电池簇取
        processTempData(stackList, avgTempList);

        // 数据转换为Map
        Map<String, PowerStatisListVo> powerMap = powerList.stream()
                .collect(Collectors.toMap(PowerStatisListVo::getStatisDate, stat -> stat));
        Map<String, StackPointVo> stackMap = stackList.stream()
                .collect(Collectors.toMap(StackPointVo::getStatisDate, stat -> stat));
        // 收集所有日期
        Set<String> allDates = new HashSet<>();
        allDates.addAll(powerMap.keySet());
        allDates.addAll(stackMap.keySet());

        // 按日期合并数据
        responseList = allDates.stream()
                .map(date -> {
                    PowerStatisListVo powerStat = powerMap.get(date);
                    StackPointVo stackStat = stackMap.get(date);

                    // null时取0
                    BigDecimal pvPower = powerStat != null ? powerStat.getPvPower() : BigDecimal.ZERO;
                    BigDecimal storagePower = powerStat != null ? powerStat.getStoragePower() : BigDecimal.ZERO;
                    BigDecimal gridPower = powerStat != null ? powerStat.getGridPower() : BigDecimal.ZERO;
                    BigDecimal avgSoh = stackStat != null ? (stackStat.getAvgSoh() == null ? BigDecimal.ZERO : stackStat.getAvgSoh().setScale(2,RoundingMode.HALF_UP)) : BigDecimal.ZERO;
                    BigDecimal avgSoc = stackStat != null ? (stackStat.getAvgSoc() == null ? BigDecimal.ZERO : stackStat.getAvgSoc().setScale(2,RoundingMode.HALF_UP)) : BigDecimal.ZERO;
                    BigDecimal avgTemp = stackStat != null ? (stackStat.getAvgTemp() == null ? BigDecimal.ZERO : stackStat.getAvgTemp().setScale(2,RoundingMode.HALF_UP)) : BigDecimal.ZERO;

                    // 假设完整构造参数（补充原代码中缺失的部分）
                    return new PointDataResponse(date, pvPower, storagePower, BigDecimal.ZERO, gridPower, avgSoh, avgSoc, avgTemp);
                })
                // 按日期排序
                .sorted(Comparator.comparing(PointDataResponse::getStatisDate))
                .collect(Collectors.toList());

        // 负荷功率
        dealDataPower(requestVo.getSiteId(),responseList);

        return responseList;
    }

    private void dealDataPower(String siteId, List<PointDataResponse> dataList) {
        if (dataList == null || dataList.size() == 0){
            return;
        }

        for (PointDataResponse pointDataResponse : dataList) {
            BigDecimal gridPower = pointDataResponse.getGridPower() == null ? BigDecimal.ZERO : pointDataResponse.getGridPower();
            BigDecimal storagePower = pointDataResponse.getStoragePower() == null ? BigDecimal.ZERO : pointDataResponse.getStoragePower();
            BigDecimal pvPower = pointDataResponse.getPvPower() == null ? BigDecimal.ZERO : pointDataResponse.getPvPower();
            BigDecimal loadPower = new BigDecimal(0);

//            // 电动所：负荷功率=电网功率+光伏功率-储能功率
//            // 奉贤：负荷功率=电网功率-储能功率；
//            if (SiteEnum.DDS.getCode().equals(siteId)){
//                loadPower = gridPower.add(pvPower).subtract(storagePower);
//                pointDataResponse.setLoadPower(loadPower);
//            } else if (SiteEnum.FX.getCode().equals(siteId)){
//                // 奉贤：负荷功率=电网功率-储能功率；
//                loadPower = gridPower.subtract(storagePower);
//                pointDataResponse.setLoadPower(loadPower);
//            }
            // 负荷功率=电网功率+光伏功率-储能功率
            loadPower = gridPower.add(pvPower).subtract(storagePower);
            pointDataResponse.setLoadPower(loadPower);
        }
    }

    private void processTempData(List<StackPointVo> stackList, List<Map<String, BigDecimal>> avgTempList) {
        if (stackList == null || avgTempList == null) {
            return;
        }


        Map<String,BigDecimal>  avgTempMap = avgTempList.stream()
                .filter(subMap -> subMap != null && !subMap.isEmpty())
                .filter(subMap -> subMap.containsKey("statisDate") && subMap.containsKey("avgTemp"))
                .collect(Collectors.toMap(
                        subMap -> String.valueOf(subMap.get("statisDate")),
                        subMap -> (BigDecimal)subMap.get("avgTemp"),
                        (existingValue, newValue) -> newValue,
                        HashMap::new
                ));

        for (StackPointVo stackPointVo : stackList) {
            if (stackPointVo.getAvgTemp() != null) {
                continue;
            }
            // 从map中获取temp
            BigDecimal mapTemp = (avgTempMap != null) ? avgTempMap.get(stackPointVo.getStatisDate()) : null;
            if (mapTemp != null) {
                stackPointVo.setAvgTemp(mapTemp);
            }
        }

    }

    private void processFXXTempData(List<StackPointVo> stackList, List<Map<String, BigDecimal>> fxAvgTempList) {
        if (stackList == null || fxAvgTempList == null) {
            return;
        }

        Map<String,BigDecimal>  fxAvgTempMap = fxAvgTempList.stream()
                 .filter(subMap -> subMap != null && !subMap.isEmpty())
                 .filter(subMap -> subMap.containsKey("statisDate") && subMap.containsKey("avgTemp"))
                 .collect(Collectors.toMap(
                         subMap -> String.valueOf(subMap.get("statisDate")),
                         subMap -> (BigDecimal)subMap.get("avgTemp"),
                         (existingValue, newValue) -> newValue,
                         HashMap::new
                ));

        for (StackPointVo stackPointVo : stackList) {
            // 从map中获取temp
            BigDecimal mapTemp = (fxAvgTempMap != null) ? fxAvgTempMap.get(stackPointVo.getStatisDate()) : null;
            if (mapTemp != null) {
                stackPointVo.setAvgTemp(mapTemp);
            }
        }

    }

    // 统计入参时间处理
    public void dealRequestTime(DateSearchRequest requestVo){
        Date startDate = requestVo.getStartDate();
        Date endDate = requestVo.getEndDate();
        if (startDate == null && endDate == null) {
            // 如果没有传时间，默认从今天往前7天
            LocalDate sevenDaysAgo = LocalDate.now().minusDays(6);
            startDate = DateUtils.toDate(sevenDaysAgo);
            endDate = new Date();
            requestVo.setStartDate(startDate);
            requestVo.setEndDate(endDate);
        }
    }

}
