Series refactor

This commit is contained in:
Patrick Haßel 2024-10-15 16:24:36 +02:00
parent 5ba7f3d4d6
commit c85fbb12c3
10 changed files with 69 additions and 112 deletions

View File

@ -1,38 +0,0 @@
package de.ph87.data.demo;
import de.ph87.data.series.Series;
import de.ph87.data.series.SeriesMode;
import de.ph87.data.series.SeriesRepository;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import static de.ph87.data.electricity.grid.GridReceiver.GRID_DELIVERY_SERIES_NAME;
import static de.ph87.data.electricity.grid.GridReceiver.GRID_PURCHASE_SERIES_NAME;
import static de.ph87.data.electricity.photovoltaic.PhotovoltaicReceiver.PHOTOVOLTAIC_ENERGY_SERIES_NAME;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
@SuppressWarnings("SameParameterValue")
public class DemoService {
private final SeriesRepository seriesRepository;
@EventListener(ApplicationStartedEvent.class)
public void startup() {
series(PHOTOVOLTAIC_ENERGY_SERIES_NAME, SeriesMode.INCREASING);
series(GRID_PURCHASE_SERIES_NAME, SeriesMode.INCREASING);
series(GRID_DELIVERY_SERIES_NAME, SeriesMode.INCREASING);
}
private void series(@NonNull final String name, @NonNull final SeriesMode mode) {
seriesRepository.findByNameOrAliasesContains(name, name).orElseGet(() -> seriesRepository.save(new Series(name, mode)));
}
}

View File

@ -41,10 +41,10 @@ public class GridReceiver {
}
final double purchaseKWh = energyToKWh(inbound.purchaseWh, "Wh");
applicationEventPublisher.publishEvent(new ConsumptionEvent(GRID_PURCHASE_SERIES_NAME, inbound.meter, inbound.date, purchaseKWh));
applicationEventPublisher.publishEvent(new ConsumptionEvent(GRID_PURCHASE_SERIES_NAME, true, inbound.meter, inbound.date, purchaseKWh));
final double deliveryKWh = energyToKWh(inbound.deliveryWh, "Wh");
applicationEventPublisher.publishEvent(new ConsumptionEvent(GRID_DELIVERY_SERIES_NAME, inbound.meter, inbound.date, deliveryKWh));
applicationEventPublisher.publishEvent(new ConsumptionEvent(GRID_DELIVERY_SERIES_NAME, true, inbound.meter, inbound.date, deliveryKWh));
}
}

View File

@ -23,7 +23,7 @@ public class PhotovoltaicReceiver {
public static final String PHOTOVOLTAIC_ENERGY_SERIES_NAME = "photovoltaic.energyKWh";
private static final Pattern REGEX = Pattern.compile("^(?<serial>\\S+) (?<epochSeconds>\\d+) (?<power>\\d+(:?\\.\\d+)?)(?<powerUnit>\\S+) (?<energy>\\d+(:?\\.\\d+)?)(?<energyUnit>\\S+)$");
private static final Pattern REGEX = Pattern.compile("^(?<serial>\\S+) (?<epochSeconds>\\d+) (?<powerValue>\\d+(:?\\.\\d+)?)(?<powerUnit>\\S+) (?<producedValue>\\d+(:?\\.\\d+)?)(?<producedUnit>\\S+)$");
private final ApplicationEventPublisher applicationEventPublisher;
@ -40,10 +40,10 @@ public class PhotovoltaicReceiver {
final String serial = matcher.group("serial");
final ZonedDateTime date = ZDT(Long.parseLong(matcher.group("epochSeconds")));
final double energy = Double.parseDouble(matcher.group("energy"));
final String energyUnit = matcher.group("energyUnit");
final double energyKWh = energyToKWh(energy, energyUnit);
applicationEventPublisher.publishEvent(new ConsumptionEvent(PHOTOVOLTAIC_ENERGY_SERIES_NAME, serial, date, energyKWh));
final double producedValue = Double.parseDouble(matcher.group("producedValue"));
final String producedUnit = matcher.group("producedUnit");
final double producedKWh = energyToKWh(producedValue, producedUnit);
applicationEventPublisher.publishEvent(new ConsumptionEvent(PHOTOVOLTAIC_ENERGY_SERIES_NAME, true, serial, date, producedKWh));
}
}

View File

@ -0,0 +1,37 @@
package de.ph87.data.series;
import de.ph87.data.series.consumption.unit.Unit;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.ManyToOne;
import lombok.*;
import java.io.Serializable;
import java.time.ZonedDateTime;
@Getter
@ToString
@Embeddable
@EqualsAndHashCode
@NoArgsConstructor
public class SeriesIntervalKey implements Serializable {
@NonNull
@ManyToOne(optional = false)
private Series series;
@NonNull
@Column(nullable = false, updatable = false, columnDefinition = "CHAR(1)")
private Unit unit;
@NonNull
@Column(nullable = false, updatable = false)
private ZonedDateTime aligned;
public SeriesIntervalKey(@NonNull final Series series, @NonNull final Unit unit, @NonNull final ZonedDateTime unaligned) {
this.series = series;
this.unit = unit;
this.aligned = unit.align(unaligned);
}
}

View File

@ -1,17 +1,11 @@
package de.ph87.data.series;
import de.ph87.data.series.consumption.ConsumptionEvent;
import de.ph87.data.series.consumption.ConsumptionEventTooOld;
import de.ph87.data.series.consumption.period.PeriodService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Slf4j
@Service
@Transactional
@ -20,24 +14,13 @@ public class SeriesService {
private final SeriesRepository seriesRepository;
private final PeriodService periodService;
@EventListener(ConsumptionEvent.class)
public void onConsumptionEvent(@NonNull final ConsumptionEvent event) {
log.debug("Handling ConsumptionEvent: {}", event);
final Optional<Series> seriesOptional = seriesRepository.findByNameOrAliasesContains(event.getName(), event.getName());
if (seriesOptional.isEmpty()) {
log.warn("No series found with name or alias: \"{}\"", event.getName());
return;
}
final Series series = seriesOptional.get();
log.debug("Series found: {}", series);
try {
periodService.onConsumptionEvent(series, event);
} catch (ConsumptionEventTooOld e) {
log.warn(e.toString());
@NonNull
public Series getOrCreateByName(@NonNull final String name, @NonNull final SeriesMode mode) {
final Series series = seriesRepository.findByNameOrAliasesContains(name, name).orElseGet(() -> seriesRepository.save(new Series(name, mode)));
if (series.getMode() != mode) {
throw new RuntimeException("'mode' argument for getOrCreateByName does not match 'mode' of existing Series: mode=%s, series=%s".formatted(mode, series));
}
return series;
}
}

View File

@ -15,6 +15,8 @@ public class ConsumptionEvent {
@NonNull
private final String name;
private final boolean increasing;
@NonNull
private final String periodName;

View File

@ -1,12 +0,0 @@
package de.ph87.data.series.consumption;
import de.ph87.data.series.consumption.period.Period;
import lombok.NonNull;
public class ConsumptionEventTooOld extends Exception {
public ConsumptionEventTooOld(@NonNull final Period period, @NonNull final ConsumptionEvent event) {
super("Date of received Event older than last stored one: event=%s, period=%s".formatted(event, period));
}
}

View File

@ -1,10 +1,15 @@
package de.ph87.data.series.consumption;
import de.ph87.data.series.Series;
import de.ph87.data.series.SeriesMode;
import de.ph87.data.series.SeriesService;
import de.ph87.data.series.consumption.period.Period;
import de.ph87.data.series.consumption.period.PeriodService;
import de.ph87.data.series.consumption.unit.Unit;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -19,7 +24,17 @@ public class ConsumptionService {
private final ConsumptionRepository consumptionRepository;
public void onConsumptionEvent(@NonNull final Period period, @NonNull final ConsumptionEvent event) {
private final SeriesService seriesService;
private final PeriodService periodService;
@EventListener(ConsumptionEvent.class)
public void onConsumptionEvent(@NonNull final ConsumptionEvent event) {
log.debug("Handling ConsumptionEvent: {}", event);
final Series series = seriesService.getOrCreateByName(event.getName(), event.isIncreasing() ? SeriesMode.INCREASING : SeriesMode.DECREASING);
final Period period = periodService.getOrCreatePeriod(series, event);
period.setLastDate(event.getDate());
period.setLastValue(event.getValue());
for (final Unit unit : Unit.values()) {
final ZonedDateTime aligned = unit.align(event.getDate());
final Optional<Consumption> existingOptional = consumptionRepository.findByIdPeriodAndIdUnitAndIdAligned(period, unit, aligned);

View File

@ -3,8 +3,6 @@ package de.ph87.data.series.consumption.period;
import de.ph87.data.series.Series;
import de.ph87.data.series.SeriesMode;
import de.ph87.data.series.consumption.ConsumptionEvent;
import de.ph87.data.series.consumption.ConsumptionEventTooOld;
import de.ph87.data.series.consumption.ConsumptionService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -19,22 +17,10 @@ public class PeriodService {
private final PeriodRepository periodRepository;
private final ConsumptionService consumptionService;
public void onConsumptionEvent(@NonNull final Series series, @NonNull final ConsumptionEvent event) throws ConsumptionEventTooOld {
final Period period = getOrCreatePeriod(series, event);
period.setLastDate(event.getDate());
period.setLastValue(event.getValue());
consumptionService.onConsumptionEvent(period, event);
}
@NonNull
private Period getOrCreatePeriod(@NonNull final Series series, @NonNull final ConsumptionEvent event) throws ConsumptionEventTooOld {
public Period getOrCreatePeriod(@NonNull final Series series, @NonNull final ConsumptionEvent event) {
if (series.getPeriod() != null) {
log.debug("Last Period exists: {}", series.getPeriod());
if (isEventTooOld(series.getPeriod(), event)) {
throw new ConsumptionEventTooOld(series.getPeriod(), event);
}
if (isPeriodValid(series.getPeriod(), event)) {
log.debug("Last Period still VALID.");
return series.getPeriod();
@ -49,10 +35,6 @@ public class PeriodService {
return newPeriod;
}
private static boolean isEventTooOld(@NonNull final Period period, @NonNull final ConsumptionEvent event) {
return !period.getLastDate().isBefore(event.getDate());
}
private boolean isPeriodValid(@NonNull final Period period, @NonNull final ConsumptionEvent event) {
if (!period.getName().equals(event.getPeriodName())) {
log.debug("Period name changed: old={}, new={}", period.getName(), event.getPeriodName());

View File

@ -1,12 +0,0 @@
package de.ph87.data.series.measure;
import de.ph87.data.series.period.Period;
import lombok.NonNull;
public class MeasureEventTooOld extends Exception {
public MeasureEventTooOld(@NonNull final Period period, @NonNull final MeasureEvent event) {
super("Date of received MeasureEvent older than last stored one: event=%s, period=%s".formatted(event, period));
}
}