gongchunyi
昨天 e9b4ceeea014f9d81263672f3d9c46a567c39591
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package com.ruoyi.energymeter.service.impl;
 
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.energymeter.constant.EnergyMeterConstants;
import com.ruoyi.energymeter.mapper.EnergyMeterDeviceMapper;
import com.ruoyi.energymeter.mapper.EnergyMeterMeasurementMapper;
import com.ruoyi.energymeter.pojo.EnergyMeterDevice;
import com.ruoyi.energymeter.pojo.EnergyMeterMeasurement;
import com.ruoyi.energymeter.service.EnergyMeterDemoService;
import com.ruoyi.energymeter.util.EnergyMeterSupport;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
 
/**
 * 演示数据实现:气表按天生成模拟表码与用量。
 *
 * <p>模拟值是“设备ID + 日期”的确定性函数,重复生成同一天结果一致,便于核对。</p>
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class EnergyMeterDemoServiceImpl implements EnergyMeterDemoService {
 
    private final EnergyMeterDeviceMapper deviceMapper;
    private final EnergyMeterMeasurementMapper measurementMapper;
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int generateGasDemo(LocalDate date) {
        if (date == null) {
            return 0;
        }
        LambdaQueryWrapper<EnergyMeterDevice> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(EnergyMeterDevice::getMeterType, EnergyMeterConstants.TYPE_GAS)
                .eq(EnergyMeterDevice::getSimulated, 1)
                .eq(EnergyMeterDevice::getEnabled, 1);
        List<EnergyMeterDevice> devices = deviceMapper.selectList(wrapper);
        if (devices.isEmpty()) {
            return 0;
        }
 
        int generated = 0;
        LocalDateTime measureTime = date.atStartOfDay();
        for (EnergyMeterDevice device : devices) {
            if (hasUsage(device.getId(), measureTime)) {
                // 数据已存在时只刷新采集时间,保证“最近采集/在线”反映最近一次自动生成
                deviceMapper.markCollectSuccess(device.getId(), EnergyMeterSupport.now());
                continue;
            }
            BigDecimal usage = simulatedUsage(device.getId(), date);
            BigDecimal previous = previousReading(device, measureTime);
            BigDecimal reading = previous.add(usage);
 
            EnergyMeterMeasurement usagePoint = new EnergyMeterMeasurement();
            usagePoint.setTenantId(device.getTenantId());
            usagePoint.setMeterId(device.getId());
            usagePoint.setMetricCode(EnergyMeterConstants.METRIC_GAS_USAGE);
            usagePoint.setGranularity(EnergyMeterConstants.GRANULARITY_DAY);
            usagePoint.setMeasureTime(measureTime);
            usagePoint.setMetricValue(usage);
            usagePoint.setUnit(EnergyMeterConstants.UNIT_GAS);
            usagePoint.setSourceApi(EnergyMeterConstants.SOURCE_MANUAL);
            usagePoint.setSourceRecordTime(measureTime);
            usagePoint.setCollectedAt(EnergyMeterSupport.now());
            usagePoint.setRemark("模拟数据:每日用量");
 
            EnergyMeterMeasurement readingPoint = new EnergyMeterMeasurement();
            readingPoint.setTenantId(device.getTenantId());
            readingPoint.setMeterId(device.getId());
            readingPoint.setMetricCode(EnergyMeterConstants.METRIC_GAS_READING);
            readingPoint.setGranularity(EnergyMeterConstants.GRANULARITY_POINT);
            readingPoint.setMeasureTime(measureTime);
            readingPoint.setMetricValue(reading);
            readingPoint.setUnit(EnergyMeterConstants.UNIT_GAS);
            readingPoint.setSourceApi(EnergyMeterConstants.SOURCE_MANUAL);
            readingPoint.setSourceRecordTime(measureTime);
            readingPoint.setCollectedAt(EnergyMeterSupport.now());
            readingPoint.setRemark("模拟数据:每日表码");
 
            measurementMapper.upsertBatch(List.of(usagePoint, readingPoint));
 
            // 采集时间按实际执行时间记录,而不是数据所属日期的 00:00
            deviceMapper.markCollectSuccess(device.getId(), EnergyMeterSupport.now());
            generated++;
        }
        log.info("气表演示数据生成完成 date={} 设备数={}", date, generated);
        return generated;
    }
 
    private boolean hasUsage(Long meterId, LocalDateTime measureTime) {
        LambdaQueryWrapper<EnergyMeterMeasurement> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(EnergyMeterMeasurement::getMeterId, meterId)
                .eq(EnergyMeterMeasurement::getMetricCode, EnergyMeterConstants.METRIC_GAS_USAGE)
                .eq(EnergyMeterMeasurement::getMeasureTime, measureTime);
        return measurementMapper.selectCount(wrapper) > 0;
    }
 
    /**
     * 上一期表码:取该日期之前的最近一条示值,没有就用设备起始表码。
     */
    private BigDecimal previousReading(EnergyMeterDevice device, LocalDateTime measureTime) {
        EnergyMeterMeasurement previous = measurementMapper.selectPreviousPoint(device.getId(),
                EnergyMeterConstants.METRIC_GAS_READING, measureTime);
        if (previous != null && previous.getMetricValue() != null) {
            return previous.getMetricValue();
        }
        return device.getInitialReading() == null ? BigDecimal.ZERO : device.getInitialReading();
    }
 
    /**
     * 确定性模拟用量:按食品厂蒸煮/灭菌的实际用气量估算。
     *
     * <p>估算口径:蒸煮锅按 4~6 m³/h、每天 6~8 小时,单台约 25~35 m³/天;
     * 灭菌锅、小煮锅约 14~20 m³/天。基础值按设备区分,再叠加季节波动与噪点。</p>
     */
    private BigDecimal simulatedUsage(Long meterId, LocalDate date) {
        long seed = meterId * 131L + date.toEpochDay();
        double base = 14 + (meterId % 5) * 5;
        double season = base * 0.1 * Math.sin(2 * Math.PI * date.getDayOfYear() / 365);
        double noise = ((seed * 37 % 200) / 200.0 - 0.5) * 3;
        double usage = Math.max(2, base + season + noise);
        return BigDecimal.valueOf(usage).setScale(2, RoundingMode.HALF_UP);
    }
}