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;
/**
* 演示数据实现:气表按天生成模拟表码与用量。
*
*
模拟值是“设备ID + 日期”的确定性函数,重复生成同一天结果一致,便于核对。
*/
@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 wrapper = new LambdaQueryWrapper<>();
wrapper.eq(EnergyMeterDevice::getMeterType, EnergyMeterConstants.TYPE_GAS)
.eq(EnergyMeterDevice::getSimulated, 1)
.eq(EnergyMeterDevice::getEnabled, 1);
List 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 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();
}
/**
* 确定性模拟用量:按食品厂蒸煮/灭菌的实际用气量估算。
*
* 估算口径:蒸煮锅按 4~6 m³/h、每天 6~8 小时,单台约 25~35 m³/天;
* 灭菌锅、小煮锅约 14~20 m³/天。基础值按设备区分,再叠加季节波动与噪点。
*/
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);
}
}