Entry -> Varying + Meter cleanup

This commit is contained in:
Patrick Haßel 2025-02-25 09:17:21 +01:00
parent 963648765f
commit 9169060cae
63 changed files with 763 additions and 555 deletions

View File

@ -4,11 +4,12 @@ import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.*;
import de.ph87.data.message.*;
import de.ph87.data.series.entry.*;
import de.ph87.data.series.varying.*;
import de.ph87.data.value.Value;
import de.ph87.data.value.*;
import lombok.*;
import lombok.extern.slf4j.*;
import org.springframework.context.*;
import org.springframework.stereotype.*;
import java.time.*;
@ -25,7 +26,7 @@ public class EspHomeHandler implements IMessageHandler {
private final ObjectMapper objectMapper;
private final EntryService entryService;
private final ApplicationEventPublisher applicationEventPublisher;
@Override
public void handle(@NonNull final Message message) throws Exception {
@ -58,7 +59,7 @@ public class EspHomeHandler implements IMessageHandler {
return;
}
final Value value = new Value(inbound.value, unitFromPayload);
entryService.receive(name, inbound.date, value.as(targetUnit));
applicationEventPublisher.publishEvent(new VaryingInbound(name, inbound.date, value.as(targetUnit)));
}
private String propertyReplace(final String property) {

View File

@ -2,11 +2,12 @@ package de.ph87.data.message.handler;
import com.fasterxml.jackson.databind.*;
import de.ph87.data.message.*;
import de.ph87.data.series.entry.*;
import de.ph87.data.series.varying.*;
import de.ph87.data.value.Value;
import de.ph87.data.value.*;
import lombok.*;
import lombok.extern.slf4j.*;
import org.springframework.context.*;
import org.springframework.stereotype.*;
import java.time.*;
@ -21,7 +22,7 @@ public class HeizungHandler implements IMessageHandler {
private final ObjectMapper objectMapper;
private final EntryService entryService;
private final ApplicationEventPublisher applicationEventPublisher;
@Override
public void handle(@NonNull final Message message) throws Exception {
@ -31,7 +32,7 @@ public class HeizungHandler implements IMessageHandler {
}
final String property = matcher.group("property");
final Inbound inbound = objectMapper.readValue(message.payload, Inbound.class);
entryService.receive(property, inbound.date, inbound.value);
applicationEventPublisher.publishEvent(new VaryingInbound(property, inbound.date, inbound.value));
}
@Getter

View File

@ -2,12 +2,13 @@ package de.ph87.data.message.handler;
import com.fasterxml.jackson.databind.*;
import de.ph87.data.message.*;
import de.ph87.data.meter.*;
import de.ph87.data.series.entry.*;
import de.ph87.data.series.meter.*;
import de.ph87.data.series.varying.*;
import de.ph87.data.value.Value;
import de.ph87.data.value.*;
import lombok.*;
import lombok.extern.slf4j.*;
import org.springframework.context.*;
import org.springframework.stereotype.*;
import java.time.*;
@ -21,18 +22,16 @@ public class OpenDTUHandler implements IMessageHandler {
private final ObjectMapper objectMapper;
private final EntryService entryService;
private final MeterService meterService;
private final ApplicationEventPublisher applicationEventPublisher;
@Override
public void handle(final @NonNull Message message) throws Exception {
public void handle(@NonNull final Message message) throws Exception {
if (!"openDTU/pv/patrix/json".equals(message.topic)) {
return;
}
final Inbound inbound = objectMapper.readValue(message.payload, Inbound.class);
entryService.receive("power/produced", inbound.date, inbound.power.as(Unit.POWER_W));
meterService.receive("energy/produced", METER_NUMBER, inbound.date, inbound.energy);
applicationEventPublisher.publishEvent(new VaryingInbound("power/produced", inbound.date, inbound.power.as(Unit.POWER_W)));
applicationEventPublisher.publishEvent(new MeterInbound("energy/produced", METER_NUMBER, inbound.date, inbound.energy));
}
@Getter

View File

@ -2,11 +2,12 @@ package de.ph87.data.message.handler;
import com.fasterxml.jackson.databind.*;
import de.ph87.data.message.*;
import de.ph87.data.series.entry.*;
import de.ph87.data.series.varying.*;
import de.ph87.data.value.Value;
import de.ph87.data.value.*;
import lombok.*;
import lombok.extern.slf4j.*;
import org.springframework.context.*;
import org.springframework.stereotype.*;
import java.time.*;
@ -18,7 +19,7 @@ public class SimpleJsonHandler implements IMessageHandler {
private final ObjectMapper objectMapper;
private final EntryService entryService;
private final ApplicationEventPublisher applicationEventPublisher;
@Override
public void handle(@NonNull final Message message) throws Exception {
@ -26,7 +27,7 @@ public class SimpleJsonHandler implements IMessageHandler {
return;
}
final Inbound inbound = objectMapper.readValue(message.payload, Inbound.class);
entryService.receive(inbound.name, inbound.date, inbound.value);
applicationEventPublisher.publishEvent(new VaryingInbound(inbound.name, inbound.date, inbound.value));
}
@Getter

View File

@ -2,12 +2,13 @@ package de.ph87.data.message.handler;
import com.fasterxml.jackson.databind.*;
import de.ph87.data.message.*;
import de.ph87.data.meter.*;
import de.ph87.data.series.entry.*;
import de.ph87.data.series.meter.*;
import de.ph87.data.series.varying.*;
import de.ph87.data.value.Value;
import de.ph87.data.value.*;
import lombok.*;
import lombok.extern.slf4j.*;
import org.springframework.context.*;
import org.springframework.stereotype.*;
import java.time.*;
@ -21,9 +22,7 @@ public class SmartMeterHandler implements IMessageHandler {
private final ObjectMapper objectMapper;
private final EntryService entryService;
private final MeterService meterService;
private final ApplicationEventPublisher applicationEventPublisher;
@Override
public void handle(@NonNull final Message message) throws Exception {
@ -31,9 +30,9 @@ public class SmartMeterHandler implements IMessageHandler {
return;
}
final Inbound inbound = objectMapper.readValue(message.payload, Inbound.class);
entryService.receive("power/balance", inbound.date, inbound.powerBalance.as(Unit.POWER_W));
meterService.receive("energy/purchased", METER_NUMBER, inbound.date, inbound.energyPurchased);
meterService.receive("energy/delivered", METER_NUMBER, inbound.date, inbound.energyDelivered);
applicationEventPublisher.publishEvent(new VaryingInbound("power/balance", inbound.date, inbound.powerBalance.as(Unit.POWER_W)));
applicationEventPublisher.publishEvent(new MeterInbound("energy/purchased", METER_NUMBER, inbound.date, inbound.energyPurchased));
applicationEventPublisher.publishEvent(new MeterInbound("energy/delivered", METER_NUMBER, inbound.date, inbound.energyDelivered));
}
@Getter

View File

@ -1,93 +0,0 @@
package de.ph87.data.meter;
import de.ph87.data.meter.day.*;
import de.ph87.data.meter.five.*;
import de.ph87.data.meter.month.*;
import de.ph87.data.meter.week.*;
import de.ph87.data.meter.year.*;
import de.ph87.data.series.*;
import de.ph87.data.value.Value;
import lombok.*;
import lombok.extern.slf4j.*;
import org.springframework.stereotype.*;
import org.springframework.transaction.annotation.*;
import java.time.*;
import java.time.temporal.*;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class MeterService {
private final SeriesService seriesService;
private final MeterRepository meterRepository;
private final MeterDayRepository meterDayRepository;
private final MeterWeekRepository meterWeekRepository;
private final MeterMonthRepository meterMonthRepository;
private final MeterYearRepository meterYearRepository;
private final MeterFiveRepository meterFiveRepository;
public void receive(@NonNull final String seriesName, @NonNull final String meterNumber, @NonNull final ZonedDateTime date, @NonNull final Value value) {
final Series series = seriesService.getOrCreate(seriesName, value.unit);
final Meter meter = meterRepository.findFirstBySeriesOrderByDateDesc(series)
.filter(m -> m.getNumber().equals(meterNumber))
.orElseGet(() -> meterRepository.save(new Meter(series, date, meterNumber)));
final MeterFive.Id five = new MeterFive.Id(meter, date.truncatedTo(ChronoUnit.MINUTES).minusMinutes(date.getMinute() % 5));
meterFiveRepository.findById(five).ifPresentOrElse(
existing -> existing.update(value),
() -> meterFiveRepository.save(new MeterFive(five, value))
);
final MeterDay.Id day = new MeterDay.Id(meter, date.toLocalDate());
meterDayRepository.findById(day).ifPresentOrElse(
existing -> existing.update(value),
() -> meterDayRepository.save(new MeterDay(day, value))
);
final MeterWeek.Id week = new MeterWeek.Id(meter, toWeek(date));
meterWeekRepository.findById(week).ifPresentOrElse(
existing -> existing.update(value),
() -> meterWeekRepository.save(new MeterWeek(week, value))
);
final MeterMonth.Id month = new MeterMonth.Id(meter, toMonth(date));
meterMonthRepository.findById(month).ifPresentOrElse(
existing -> existing.update(value),
() -> meterMonthRepository.save(new MeterMonth(month, value))
);
final MeterYear.Id year = new MeterYear.Id(meter, toYear(date));
meterYearRepository.findById(year).ifPresentOrElse(
existing -> existing.update(value),
() -> meterYearRepository.save(new MeterYear(year, value))
);
}
@NonNull
private static LocalDate toWeek(@NonNull final ZonedDateTime date) {
final LocalDate t = date.toLocalDate();
return t.minusDays(t.getDayOfWeek().getValue() - 1);
}
@NonNull
private static LocalDate toMonth(@NonNull final ZonedDateTime date) {
final LocalDate t = date.toLocalDate();
return t.minusDays(t.getDayOfMonth() - 1);
}
@NonNull
private static LocalDate toYear(@NonNull final ZonedDateTime date) {
final LocalDate t = date.toLocalDate();
return t.minusDays(t.getDayOfYear() - 1);
}
}

View File

@ -1,61 +0,0 @@
package de.ph87.data.meter.day;
import de.ph87.data.meter.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
import java.time.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class MeterDay {
@EmbeddedId
private Id id;
@Column(nullable = false)
private double min;
@Column(nullable = false)
private double max;
public MeterDay(@NonNull final Id id, @NonNull final Value value) {
this.id = id;
this.min = value.as(id.getMeter().getSeries().getUnit()).value;
this.max = this.min;
}
public void update(@NonNull final Value value) {
final double v = value.as(id.getMeter().getSeries().getUnit()).value;
if (v < this.max) {
throw new RuntimeException();
}
this.max = v;
}
@Getter
@ToString
@Embeddable
@EqualsAndHashCode
@NoArgsConstructor
public static class Id {
@NonNull
@ManyToOne
private Meter meter;
@NonNull
@Column(nullable = false)
private LocalDate date;
public Id(@NonNull final Meter meter, @NonNull final LocalDate date) {
this.meter = meter;
this.date = date;
}
}
}

View File

@ -1,7 +0,0 @@
package de.ph87.data.meter.day;
import org.springframework.data.repository.*;
public interface MeterDayRepository extends ListCrudRepository<MeterDay, MeterDay.Id> {
}

View File

@ -1,7 +0,0 @@
package de.ph87.data.meter.five;
import org.springframework.data.repository.*;
public interface MeterFiveRepository extends ListCrudRepository<MeterFive, MeterFive.Id> {
}

View File

@ -1,61 +0,0 @@
package de.ph87.data.meter.month;
import de.ph87.data.meter.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
import java.time.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class MeterMonth {
@EmbeddedId
private Id id;
@Column(nullable = false)
private double min;
@Column(nullable = false)
private double max;
public MeterMonth(@NonNull final Id id, @NonNull final Value value) {
this.id = id;
this.min = value.as(id.getMeter().getSeries().getUnit()).value;
this.max = this.min;
}
public void update(@NonNull final Value value) {
final double v = value.as(id.getMeter().getSeries().getUnit()).value;
if (v < this.max) {
throw new RuntimeException();
}
this.max = v;
}
@Getter
@ToString
@Embeddable
@EqualsAndHashCode
@NoArgsConstructor
public static class Id {
@NonNull
@ManyToOne
private Meter meter;
@NonNull
@Column(nullable = false)
private LocalDate date;
public Id(@NonNull final Meter meter, @NonNull final LocalDate date) {
this.meter = meter;
this.date = date;
}
}
}

View File

@ -1,7 +0,0 @@
package de.ph87.data.meter.month;
import org.springframework.data.repository.*;
public interface MeterMonthRepository extends ListCrudRepository<MeterMonth, MeterMonth.Id> {
}

View File

@ -1,61 +0,0 @@
package de.ph87.data.meter.week;
import de.ph87.data.meter.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
import java.time.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class MeterWeek {
@EmbeddedId
private Id id;
@Column(nullable = false)
private double min;
@Column(nullable = false)
private double max;
public MeterWeek(@NonNull final Id id, @NonNull final Value value) {
this.id = id;
this.min = value.as(id.getMeter().getSeries().getUnit()).value;
this.max = this.min;
}
public void update(@NonNull final Value value) {
final double v = value.as(id.getMeter().getSeries().getUnit()).value;
if (v < this.max) {
throw new RuntimeException();
}
this.max = v;
}
@Getter
@ToString
@Embeddable
@EqualsAndHashCode
@NoArgsConstructor
public static class Id {
@NonNull
@ManyToOne
private Meter meter;
@NonNull
@Column(nullable = false)
private LocalDate date;
public Id(@NonNull final Meter meter, @NonNull final LocalDate date) {
this.meter = meter;
this.date = date;
}
}
}

View File

@ -1,7 +0,0 @@
package de.ph87.data.meter.week;
import org.springframework.data.repository.*;
public interface MeterWeekRepository extends ListCrudRepository<MeterWeek, MeterWeek.Id> {
}

View File

@ -1,61 +0,0 @@
package de.ph87.data.meter.year;
import de.ph87.data.meter.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
import java.time.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class MeterYear {
@EmbeddedId
private Id id;
@Column(nullable = false)
private double min;
@Column(nullable = false)
private double max;
public MeterYear(@NonNull final Id id, @NonNull final Value value) {
this.id = id;
this.min = value.as(id.getMeter().getSeries().getUnit()).value;
this.max = this.min;
}
public void update(@NonNull final Value value) {
final double v = value.as(id.getMeter().getSeries().getUnit()).value;
if (v < this.max) {
throw new RuntimeException();
}
this.max = v;
}
@Getter
@ToString
@Embeddable
@EqualsAndHashCode
@NoArgsConstructor
public static class Id {
@NonNull
@ManyToOne
private Meter meter;
@NonNull
@Column(nullable = false)
private LocalDate date;
public Id(@NonNull final Meter meter, @NonNull final LocalDate date) {
this.meter = meter;
this.date = date;
}
}
}

View File

@ -1,7 +0,0 @@
package de.ph87.data.meter.year;
import org.springframework.data.repository.*;
public interface MeterYearRepository extends ListCrudRepository<MeterYear, MeterYear.Id> {
}

View File

@ -0,0 +1,26 @@
package de.ph87.data.series;
import lombok.*;
import java.time.*;
@Data
public class Aligned {
@NonNull
public final Alignment interval;
@NonNull
public final ZonedDateTime date;
public Aligned(@NonNull final Alignment interval, @NonNull final ZonedDateTime date) {
this.interval = interval;
this.date = interval.align.apply(date);
}
@NonNull
public Aligned minus(final long offset) {
return new Aligned(interval, interval.plus.apply(date, -offset));
}
}

View File

@ -0,0 +1,34 @@
package de.ph87.data.series;
import lombok.*;
import java.time.*;
import java.time.temporal.*;
import java.util.function.*;
public enum Alignment {
FIVE(t -> t.truncatedTo(ChronoUnit.MINUTES).minusMinutes(t.getMinute() % 5), (t, a) -> t.plusMinutes(5 * a)),
HOUR(t -> t.truncatedTo(ChronoUnit.HOURS).minusMinutes(t.getMinute() % 5), ZonedDateTime::plusHours),
DAY(t -> t.truncatedTo(ChronoUnit.DAYS), ZonedDateTime::plusDays),
WEEK(t -> t.truncatedTo(ChronoUnit.DAYS).minusWeeks(t.getDayOfWeek().getValue() - 1), ZonedDateTime::plusWeeks),
MONTH(t -> t.truncatedTo(ChronoUnit.DAYS).minusMonths(t.getDayOfMonth() - 1), ZonedDateTime::plusMonths),
YEAR(t -> t.truncatedTo(ChronoUnit.DAYS).minusYears(t.getDayOfYear() - 1), ZonedDateTime::plusYears),
;
@NonNull
public final Function<ZonedDateTime, ZonedDateTime> align;
@NonNull
public final BiFunction<ZonedDateTime, Long, ZonedDateTime> plus;
Alignment(@NonNull final Function<ZonedDateTime, ZonedDateTime> align, @NonNull final BiFunction<ZonedDateTime, Long, ZonedDateTime> plus) {
this.align = align;
this.plus = plus;
}
@NonNull
public Aligned align(@NonNull final ZonedDateTime date) {
return new Aligned(this, date);
}
}

View File

@ -33,10 +33,15 @@ public class Series {
@Column(nullable = false)
private int decimals = 1;
public Series(@NonNull final String name, @NonNull final Unit unit) {
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private SeriesType type;
public Series(@NonNull final String name, @NonNull final Unit unit, @NonNull final SeriesType type) {
this.name = name;
this.title = name;
this.unit = unit;
this.type = type;
}
}

View File

@ -18,12 +18,15 @@ public class SeriesDto {
public final int decimals;
public final SeriesType type;
public SeriesDto(@NonNull final Series series) {
this.id = series.getId();
this.name = series.getName();
this.title = series.getTitle();
this.unit = series.getUnit();
this.decimals = series.getDecimals();
this.type = series.getType();
}
public String format(@Nullable final Double value) {

View File

@ -49,14 +49,18 @@ public class SeriesService {
return new SeriesDto(series);
}
public Series getOrCreate(@NonNull final String name, @NonNull final Unit unit) {
return seriesRepository
public Series getOrCreate(@NonNull final String name, @NonNull final Unit unit, @NonNull final SeriesType type) {
final Series series = seriesRepository
.findByName(name)
.orElseGet(() -> {
final Series series = seriesRepository.save(new Series(name, unit));
publish(series, Action.CREATED);
return series;
final Series fresh = seriesRepository.save(new Series(name, unit, type));
publish(fresh, Action.CREATED);
return fresh;
});
if (series.getType() != type) {
log.warn("Existing Series type does not match requested type: requested={}, series={}", type, series);
}
return series;
}
}

View File

@ -0,0 +1,5 @@
package de.ph87.data.series;
public enum SeriesType {
VARYING, METER,
}

View File

@ -1,50 +0,0 @@
package de.ph87.data.series.entry;
import de.ph87.data.series.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
import java.time.*;
import java.time.temporal.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class Entry {
@EmbeddedId
private EntryId id;
@Column(nullable = false, name = "`value`")
private double value;
public Entry(@NonNull final EntryId id, @NonNull final Value value) {
this.id = id;
this.value = value.as(id.getSeries().getUnit()).value;
}
@Getter
@ToString
@Embeddable
@EqualsAndHashCode
@NoArgsConstructor
public static class EntryId {
@NonNull
@ManyToOne
private Series series;
@NonNull
@Column(nullable = false)
private ZonedDateTime date;
public EntryId(@NonNull final Series series, final @NonNull ZonedDateTime date) {
this.series = series;
this.date = date.truncatedTo(ChronoUnit.MINUTES).minusMinutes(date.getMinute() % 5);
}
}
}

View File

@ -1,15 +0,0 @@
package de.ph87.data.series.entry;
import lombok.*;
import org.springframework.data.repository.*;
import java.time.*;
import java.util.*;
public interface EntryRepository extends ListCrudRepository<Entry, Entry.EntryId> {
Optional<Entry> findById(@NonNull Entry.EntryId id);
List<Entry> findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(long id, @NonNull ZonedDateTime begin, @NonNull ZonedDateTime end);
}

View File

@ -1,35 +0,0 @@
package de.ph87.data.series.entry;
import de.ph87.data.series.*;
import de.ph87.data.value.Value;
import lombok.*;
import lombok.extern.slf4j.*;
import org.springframework.stereotype.*;
import org.springframework.transaction.annotation.*;
import java.time.*;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class EntryService {
private final EntryRepository entryRepository;
private final SeriesService seriesService;
public void receive(final String name, final @NonNull ZonedDateTime date, final Value value) {
final Series series = seriesService.getOrCreate(name, value.unit);
write(series, date, value);
}
public void write(@NonNull final Series series, final @NonNull ZonedDateTime date, final Value value) {
final Entry.EntryId id = new Entry.EntryId(series, date);
if (entryRepository.findById(id).isEmpty()) {
final Entry created = entryRepository.save(new Entry(id, value));
log.debug("Created: {}", created);
}
}
}

View File

@ -1,14 +1,12 @@
package de.ph87.data.series.graph;
import de.ph87.data.series.*;
import de.ph87.data.series.entry.*;
import jakarta.annotation.*;
import lombok.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.time.*;
import java.util.List;
import java.util.function.*;
@ -24,10 +22,10 @@ public class Graph {
public final SeriesDto series;
@NonNull
public final ZonedDateTime begin;
public final Aligned begin;
@NonNull
public final ZonedDateTime end;
public final Aligned end;
public final int width;
@ -61,7 +59,7 @@ public class Graph {
public final int maxLabelWidth;
public Graph(@NonNull final SeriesDto series, @NonNull final List<Entry> entries, final @NonNull ZonedDateTime begin, final @NonNull ZonedDateTime end, final int width, final int height, final int border) {
public Graph(@NonNull final SeriesDto series, @NonNull final List<GraphPoint> points, final @NonNull Aligned begin, final @NonNull Aligned end, final int width, final int height, final int border) {
this.series = series;
this.begin = begin;
this.end = end;
@ -74,20 +72,20 @@ public class Graph {
double vMax = Double.MIN_VALUE;
int maxLabelWidth = 80;
final FontMetrics fontMetrics = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).getGraphics().getFontMetrics();
for (final Entry entry : entries) {
vMin = Math.min(vMin, entry.getValue());
vMax = max(vMax, entry.getValue());
vSum += entry.getValue();
maxLabelWidth = max(maxLabelWidth, fontMetrics.stringWidth(series.format(entry.getValue())));
for (final GraphPoint point : points) {
vMin = Math.min(vMin, point.getValue());
vMax = max(vMax, point.getValue());
vSum += point.getValue();
maxLabelWidth = max(maxLabelWidth, fontMetrics.stringWidth(series.format(point.getValue())));
}
this.valueAvg = vSum / entries.size();
this.valueAvg = vSum / points.size();
this.maxLabelWidth = maxLabelWidth;
widthInner = width - 3 * border - maxLabelWidth;
heightInner = height - 2 * border;
minuteMin = begin.toEpochSecond() / 60;
minuteMax = end.toEpochSecond() / 60;
minuteMin = begin.date.toEpochSecond() / 60;
minuteMax = end.date.toEpochSecond() / 60;
minuteRange = minuteMax - minuteMin;
minuteScale = (double) widthInner / minuteRange;
@ -96,7 +94,7 @@ public class Graph {
valueRange = vMax - vMin;
valueScale = heightInner / valueRange;
points = entries.stream().map(toPoint(minuteMin, minuteScale, vMin, valueScale)).toList();
this.points = points.stream().map(toPoint(minuteMin, minuteScale, vMin, valueScale)).toList();
}
public BufferedImage draw() {
@ -131,7 +129,7 @@ public class Graph {
return image;
}
private void yLabel(final Graphics2D g, final double value, @Nullable final Stroke stroke, final @Nullable Color color) {
private void yLabel(final Graphics2D g, final double value, @Nullable final Stroke stroke, @Nullable final Color color) {
final String string = series.format(value);
final int offset = maxLabelWidth - g.getFontMetrics().stringWidth(string);
final int y = height - ((int) Math.round((value - valueMin) * valueScale) + border);
@ -143,14 +141,14 @@ public class Graph {
}
}
private Function<Entry, Point> toPoint(final long minuteMin, final double minuteScale, final double valueMin, final double valueScale) {
return entry -> {
final long minuteEpoch = entry.getId().getDate().toEpochSecond() / 60;
private Function<GraphPoint, Point> toPoint(final long minuteMin, final double minuteScale, final double valueMin, final double valueScale) {
return point -> {
final long minuteEpoch = point.getDate().toEpochSecond() / 60;
final long minuteRelative = minuteEpoch - minuteMin;
final double minuteScaled = minuteRelative * minuteScale;
final int x = (int) Math.round(minuteScaled);
final double valueRelative = entry.getValue() - valueMin;
final double valueRelative = point.getValue() - valueMin;
final double valueScaled = valueRelative * valueScale;
final int y = (int) Math.round(valueScaled);

View File

@ -1,5 +1,6 @@
package de.ph87.data.series.graph;
import de.ph87.data.series.*;
import jakarta.servlet.http.*;
import lombok.*;
import lombok.extern.slf4j.*;
@ -18,10 +19,11 @@ public class GraphController {
private final GraphService graphService;
@GetMapping(path = "{seriesId}/{width}/{height}/{offset}/{duration}", produces = "image/png")
public void graph(@PathVariable final long seriesId, final HttpServletResponse response, @PathVariable final int width, @PathVariable final int height, @PathVariable final String offset, @PathVariable final String duration) throws IOException {
final ZonedDateTime end = ZonedDateTime.now().minus(Duration.parse(offset));
final ZonedDateTime begin = end.minus(Duration.parse(duration));
@GetMapping(path = "{seriesId}/{width}/{height}/{intervalName}/{offset}/{duration}", produces = "image/png")
public void graph(@PathVariable final long seriesId, final HttpServletResponse response, @PathVariable final int width, @PathVariable final int height, @PathVariable final String intervalName, @PathVariable final long offset, @PathVariable final long duration) throws IOException {
final Alignment interval = Alignment.valueOf(intervalName);
final Aligned end = interval.align(ZonedDateTime.now()).minus(offset);
final Aligned begin = end.minus(duration);
final Graph graph = graphService.getGraph(seriesId, begin, end, width, height, 10);
final BufferedImage image = graph.draw();
response.setContentType("image/png");

View File

@ -0,0 +1,14 @@
package de.ph87.data.series.graph;
import lombok.*;
import java.time.*;
@Data
public class GraphPoint {
public final ZonedDateTime date;
public final double value;
}

View File

@ -1,28 +1,32 @@
package de.ph87.data.series.graph;
import de.ph87.data.series.*;
import de.ph87.data.series.entry.*;
import de.ph87.data.series.meter.*;
import de.ph87.data.series.varying.*;
import lombok.*;
import lombok.extern.slf4j.*;
import org.springframework.stereotype.*;
import org.springframework.transaction.annotation.*;
import java.time.*;
import java.util.*;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class GraphService {
private final EntryRepository entryRepository;
private final SeriesService seriesService;
public Graph getGraph(final long seriesId, @NonNull final ZonedDateTime begin, @NonNull final ZonedDateTime end, final int width, final int height, final int border) {
private final VaryingService varyingService;
private final MeterService meterService;
@NonNull
public Graph getGraph(final long seriesId, @NonNull final Aligned begin, @NonNull final Aligned end, final int width, final int height, final int border) {
final SeriesDto series = seriesService.getDtoById(seriesId);
final List<Entry> entries = entryRepository.findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(seriesId, begin, end);
final List<GraphPoint> entries = switch (series.getType()) {
case METER -> meterService.getPoints(series, begin, end);
case VARYING -> varyingService.getPoints(series, begin, end);
};
return new Graph(series, entries, begin, end, width, height, border);
}

View File

@ -1,4 +1,4 @@
package de.ph87.data.meter;
package de.ph87.data.series.meter;
import de.ph87.data.series.*;
import jakarta.persistence.*;

View File

@ -0,0 +1,30 @@
package de.ph87.data.series.meter;
import de.ph87.data.value.Value;
import lombok.*;
import java.time.*;
@Getter
@ToString
public class MeterInbound {
@NonNull
public final String seriesName;
@NonNull
public final String meterNumber;
@NonNull
public final ZonedDateTime date;
public final Value value;
public MeterInbound(@NonNull final String seriesName, @NonNull final String meterNumber, @NonNull final ZonedDateTime date, final Value value) {
this.seriesName = seriesName;
this.meterNumber = meterNumber;
this.date = date;
this.value = value;
}
}

View File

@ -1,4 +1,4 @@
package de.ph87.data.meter;
package de.ph87.data.series.meter;
import de.ph87.data.series.*;
import org.springframework.data.repository.*;

View File

@ -0,0 +1,85 @@
package de.ph87.data.series.meter;
import de.ph87.data.series.*;
import de.ph87.data.series.graph.*;
import de.ph87.data.series.meter.day.*;
import de.ph87.data.series.meter.five.*;
import de.ph87.data.series.meter.hour.*;
import de.ph87.data.series.meter.month.*;
import de.ph87.data.series.meter.week.*;
import de.ph87.data.series.meter.year.*;
import lombok.*;
import lombok.extern.slf4j.*;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.*;
import org.springframework.transaction.annotation.*;
import java.util.*;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class MeterService {
private final SeriesService seriesService;
private final MeterRepository meterRepository;
private final MeterFiveRepository meterFiveRepository;
private final MeterHourRepository meterHourRepository;
private final MeterDayRepository meterDayRepository;
private final MeterWeekRepository meterWeekRepository;
private final MeterMonthRepository meterMonthRepository;
private final MeterYearRepository meterYearRepository;
@EventListener(MeterInbound.class)
public void onEvent(@NonNull final MeterInbound event) {
final Meter meter = getOrCreate(event);
final MeterValue.Id five = new MeterValue.Id(meter, event.date, Alignment.FIVE);
meterFiveRepository.findById(five).ifPresentOrElse(existing -> existing.update(event.value), () -> meterFiveRepository.save(new MeterFive(five, event.value)));
final MeterValue.Id hour = new MeterValue.Id(meter, event.date, Alignment.HOUR);
meterHourRepository.findById(hour).ifPresentOrElse(existing -> existing.update(event.value), () -> meterHourRepository.save(new MeterHour(hour, event.value)));
final MeterValue.Id day = new MeterValue.Id(meter, event.date, Alignment.DAY);
meterDayRepository.findById(day).ifPresentOrElse(existing -> existing.update(event.value), () -> meterDayRepository.save(new MeterDay(day, event.value)));
final MeterValue.Id week = new MeterValue.Id(meter, event.date, Alignment.WEEK);
meterWeekRepository.findById(week).ifPresentOrElse(existing -> existing.update(event.value), () -> meterWeekRepository.save(new MeterWeek(week, event.value)));
final MeterValue.Id month = new MeterValue.Id(meter, event.date, Alignment.MONTH);
meterMonthRepository.findById(month).ifPresentOrElse(existing -> existing.update(event.value), () -> meterMonthRepository.save(new MeterMonth(month, event.value)));
final MeterValue.Id year = new MeterValue.Id(meter, event.date, Alignment.YEAR);
meterYearRepository.findById(year).ifPresentOrElse(existing -> existing.update(event.value), () -> meterYearRepository.save(new MeterYear(year, event.value)));
}
@NonNull
private Meter getOrCreate(@NonNull final MeterInbound event) {
final Series series = seriesService.getOrCreate(event.seriesName, event.value.unit, SeriesType.METER);
return meterRepository.findFirstBySeriesOrderByDateDesc(series)
.filter(m -> m.getNumber().equals(event.meterNumber))
.orElseGet(() -> meterRepository.save(new Meter(series, event.date, event.meterNumber)));
}
@NonNull
public List<GraphPoint> getPoints(@NonNull final SeriesDto series, @NonNull final Aligned begin, @NonNull final Aligned end) {
final List<? extends MeterValue> graphPoints = switch (begin.interval) {
case FIVE -> meterFiveRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
case HOUR -> meterHourRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
case DAY -> meterDayRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
case WEEK -> meterWeekRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
case MONTH -> meterMonthRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
case YEAR -> meterYearRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
};
return graphPoints.stream().map(v -> new GraphPoint(v.getId().getDate(), v.getMax() - v.getMin())).toList();
}
}

View File

@ -1,17 +1,17 @@
package de.ph87.data.meter.five;
package de.ph87.data.series.meter;
import de.ph87.data.meter.*;
import de.ph87.data.series.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
import java.time.*;
@Entity
@Getter
@ToString
@MappedSuperclass
@NoArgsConstructor
public class MeterFive {
public abstract class MeterValue {
@EmbeddedId
private Id id;
@ -22,24 +22,23 @@ public class MeterFive {
@Column(nullable = false)
private double max;
public MeterFive(@NonNull final Id id, @NonNull final Value value) {
public MeterValue(@NonNull final Id id, @NonNull final Value value) {
this.id = id;
this.min = value.as(id.getMeter().getSeries().getUnit()).value;
this.max = this.min;
final double converted = value.as(id.getMeter().getSeries().getUnit()).value;
this.min = converted;
this.max = converted;
}
public void update(@NonNull final Value value) {
final double v = value.as(id.getMeter().getSeries().getUnit()).value;
if (v < this.max) {
final double converted = value.as(id.getMeter().getSeries().getUnit()).value;
if (converted < this.max) {
throw new RuntimeException();
}
this.max = v;
this.max = converted;
}
@Getter
@ToString
@Data
@Embeddable
@EqualsAndHashCode
@NoArgsConstructor
public static class Id {
@ -51,9 +50,9 @@ public class MeterFive {
@Column(nullable = false)
private ZonedDateTime date;
public Id(@NonNull final Meter meter, @NonNull final ZonedDateTime date) {
public Id(@NonNull final Meter meter, @NonNull final ZonedDateTime date, @NonNull final Alignment interval) {
this.meter = meter;
this.date = date;
this.date = interval.align(date).date;
}
}

View File

@ -0,0 +1,14 @@
package de.ph87.data.series.meter;
import lombok.*;
import org.springframework.data.repository.*;
import java.time.*;
import java.util.*;
@NoRepositoryBean
public interface MeterValueRepository<T extends MeterValue> extends ListCrudRepository<T, MeterValue.Id> {
List<T> findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(long id, @NonNull ZonedDateTime begin, @NonNull ZonedDateTime end);
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.meter.day;
import de.ph87.data.series.meter.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class MeterDay extends MeterValue {
public MeterDay(@NonNull final Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.meter.day;
import de.ph87.data.series.meter.*;
public interface MeterDayRepository extends MeterValueRepository<MeterDay> {
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.meter.five;
import de.ph87.data.series.meter.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class MeterFive extends MeterValue {
public MeterFive(@NonNull final Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.meter.five;
import de.ph87.data.series.meter.*;
public interface MeterFiveRepository extends MeterValueRepository<MeterFive> {
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.meter.hour;
import de.ph87.data.series.meter.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class MeterHour extends MeterValue {
public MeterHour(@NonNull final Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.meter.hour;
import de.ph87.data.series.meter.*;
public interface MeterHourRepository extends MeterValueRepository<MeterHour> {
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.meter.month;
import de.ph87.data.series.meter.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class MeterMonth extends MeterValue {
public MeterMonth(@NonNull final Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.meter.month;
import de.ph87.data.series.meter.*;
public interface MeterMonthRepository extends MeterValueRepository<MeterMonth> {
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.meter.week;
import de.ph87.data.series.meter.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class MeterWeek extends MeterValue {
public MeterWeek(@NonNull final Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.meter.week;
import de.ph87.data.series.meter.*;
public interface MeterWeekRepository extends MeterValueRepository<MeterWeek> {
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.meter.year;
import de.ph87.data.series.meter.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class MeterYear extends MeterValue {
public MeterYear(@NonNull final Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.meter.year;
import de.ph87.data.series.meter.*;
public interface MeterYearRepository extends MeterValueRepository<MeterYear> {
}

View File

@ -0,0 +1,67 @@
package de.ph87.data.series.varying;
import de.ph87.data.series.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
import java.time.*;
@Getter
@ToString
@MappedSuperclass
@NoArgsConstructor
public abstract class Varying {
@EmbeddedId
private Id id;
@Column(nullable = false)
private double min;
@Column(nullable = false)
private double max;
@Column(nullable = false)
private double avg;
@Column(nullable = false)
private int count;
protected Varying(@NonNull final Id id, @NonNull final Value value) {
this.id = id;
final double converted = value.as(id.getSeries().getUnit()).value;
this.min = converted;
this.max = converted;
this.avg = converted;
this.count = 1;
}
public void update(@NonNull final Value value) {
final double converted = value.as(id.getSeries().getUnit()).value;
this.min = Math.min(this.min, converted);
this.max = Math.max(this.max, converted);
this.avg = (this.avg * this.count + converted) / ++this.count;
}
@Data
@Embeddable
@NoArgsConstructor
public static class Id {
@NonNull
@ManyToOne
private Series series;
@NonNull
@Column(nullable = false)
private ZonedDateTime date;
public Id(@NonNull final Series series, @NonNull final ZonedDateTime date, @NonNull final Alignment interval) {
this.series = series;
this.date = interval.align(date).date;
}
}
}

View File

@ -1,4 +1,4 @@
package de.ph87.data.series;
package de.ph87.data.series.varying;
import de.ph87.data.value.Value;
import lombok.*;
@ -7,7 +7,7 @@ import java.time.*;
@Getter
@ToString
public class SeriesInbound {
public class VaryingInbound {
@NonNull
public final String name;
@ -17,7 +17,7 @@ public class SeriesInbound {
public final Value value;
public SeriesInbound(@NonNull final String name, @NonNull final ZonedDateTime date, final Value value) {
public VaryingInbound(@NonNull final String name, @NonNull final ZonedDateTime date, final Value value) {
this.name = name;
this.date = date;
this.value = value;

View File

@ -0,0 +1,14 @@
package de.ph87.data.series.varying;
import lombok.*;
import org.springframework.data.repository.*;
import java.time.*;
import java.util.*;
@NoRepositoryBean
public interface VaryingRepository<T extends Varying> extends ListCrudRepository<T, Varying.Id> {
List<T> findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(long id, @NonNull ZonedDateTime begin, @NonNull ZonedDateTime end);
}

View File

@ -0,0 +1,75 @@
package de.ph87.data.series.varying;
import de.ph87.data.series.*;
import de.ph87.data.series.graph.*;
import de.ph87.data.series.varying.day.*;
import de.ph87.data.series.varying.five.*;
import de.ph87.data.series.varying.hour.*;
import de.ph87.data.series.varying.month.*;
import de.ph87.data.series.varying.week.*;
import de.ph87.data.series.varying.year.*;
import lombok.*;
import lombok.extern.slf4j.*;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.*;
import org.springframework.transaction.annotation.*;
import java.util.*;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class VaryingService {
private final SeriesService seriesService;
private final VaryingFiveRepository varyingFiveRepository;
private final VaryingDayRepository varyingDayRepository;
private final VaryingWeekRepository varyingWeekRepository;
private final VaryingMonthRepository varyingMonthRepository;
private final VaryingYearRepository varyingYearRepository;
private final VaryingHourRepository varyingHourRepository;
@EventListener(VaryingInbound.class)
public void onEvent(@NonNull final VaryingInbound event) {
final Series series = seriesService.getOrCreate(event.name, event.value.unit, SeriesType.VARYING);
final Varying.Id five = new Varying.Id(series, event.date, Alignment.FIVE);
varyingFiveRepository.findById(five).ifPresentOrElse(existing -> existing.update(event.value), () -> varyingFiveRepository.save(new VaryingFive(five, event.value)));
final Varying.Id hour = new Varying.Id(series, event.date, Alignment.HOUR);
varyingHourRepository.findById(hour).ifPresentOrElse(existing -> existing.update(event.value), () -> varyingHourRepository.save(new VaryingHour(hour, event.value)));
final Varying.Id day = new Varying.Id(series, event.date, Alignment.DAY);
varyingDayRepository.findById(day).ifPresentOrElse(existing -> existing.update(event.value), () -> varyingDayRepository.save(new VaryingDay(day, event.value)));
final Varying.Id week = new Varying.Id(series, event.date, Alignment.WEEK);
varyingWeekRepository.findById(week).ifPresentOrElse(existing -> existing.update(event.value), () -> varyingWeekRepository.save(new VaryingWeek(week, event.value)));
final Varying.Id month = new Varying.Id(series, event.date, Alignment.MONTH);
varyingMonthRepository.findById(month).ifPresentOrElse(existing -> existing.update(event.value), () -> varyingMonthRepository.save(new VaryingMonth(month, event.value)));
final Varying.Id year = new Varying.Id(series, event.date, Alignment.YEAR);
varyingYearRepository.findById(year).ifPresentOrElse(existing -> existing.update(event.value), () -> varyingYearRepository.save(new VaryingYear(year, event.value)));
}
@NonNull
public List<GraphPoint> getPoints(@NonNull final SeriesDto series, @NonNull final Aligned begin, @NonNull final Aligned end) {
final List<? extends Varying> graphPoints = switch (begin.interval) {
case FIVE -> varyingFiveRepository.findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
case HOUR -> varyingHourRepository.findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
case DAY -> varyingDayRepository.findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
case WEEK -> varyingWeekRepository.findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
case MONTH -> varyingMonthRepository.findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
case YEAR -> varyingYearRepository.findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date);
};
return graphPoints.stream().map(v -> new GraphPoint(v.getId().getDate(), v.getMax() - v.getMin())).toList();
}
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.varying.day;
import de.ph87.data.series.varying.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class VaryingDay extends Varying {
public VaryingDay(@NonNull final Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.varying.day;
import de.ph87.data.series.varying.*;
public interface VaryingDayRepository extends VaryingRepository<VaryingDay> {
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.varying.five;
import de.ph87.data.series.varying.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class VaryingFive extends Varying {
public VaryingFive(@NonNull final Varying.Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.varying.five;
import de.ph87.data.series.varying.*;
public interface VaryingFiveRepository extends VaryingRepository<VaryingFive> {
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.varying.hour;
import de.ph87.data.series.varying.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class VaryingHour extends Varying {
public VaryingHour(@NonNull final Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.varying.hour;
import de.ph87.data.series.varying.*;
public interface VaryingHourRepository extends VaryingRepository<VaryingHour> {
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.varying.month;
import de.ph87.data.series.varying.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class VaryingMonth extends Varying {
public VaryingMonth(@NonNull final Varying.Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.varying.month;
import de.ph87.data.series.varying.*;
public interface VaryingMonthRepository extends VaryingRepository<VaryingMonth> {
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.varying.week;
import de.ph87.data.series.varying.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class VaryingWeek extends Varying {
public VaryingWeek(@NonNull final Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.varying.week;
import de.ph87.data.series.varying.*;
public interface VaryingWeekRepository extends VaryingRepository<VaryingWeek> {
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.varying.year;
import de.ph87.data.series.varying.*;
import de.ph87.data.value.Value;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Getter
@ToString
@NoArgsConstructor
public class VaryingYear extends Varying {
public VaryingYear(@NonNull final Varying.Id id, @NonNull final Value value) {
super(id, value);
}
}

View File

@ -0,0 +1,7 @@
package de.ph87.data.series.varying.year;
import de.ph87.data.series.varying.*;
public interface VaryingYearRepository extends VaryingRepository<VaryingYear> {
}

View File

@ -47,7 +47,7 @@ public enum Unit {
public static class NotConvertible extends RuntimeException {
public NotConvertible(final @NonNull Unit source, final Unit target) {
public NotConvertible(@NonNull final Unit source, final Unit target) {
super("Cannot convert Units: source=%s, target=%s".formatted(source, target));
}