Aggregation

This commit is contained in:
Patrick Haßel 2025-02-26 09:52:59 +01:00
parent c998d79c16
commit 3782c10693
14 changed files with 240 additions and 31 deletions

View File

@ -0,0 +1,23 @@
package de.ph87.data.series;
import lombok.*;
import java.time.*;
import java.util.*;
@Data
public class AggregationWrapperDto {
public final Alignment alignment;
public final ZonedDateTime date;
public final List<IAggregationDto> aggregations;
public AggregationWrapperDto(final Alignment alignment, final ZonedDateTime date, final List<IAggregationDto> aggregations) {
this.alignment = alignment;
this.date = date;
this.aggregations = aggregations;
}
}

View File

@ -19,8 +19,13 @@ public class Aligned {
}
@NonNull
public Aligned minus(final long offset) {
return new Aligned(alignment, alignment.plus.apply(date, -offset));
public Aligned plus(final long amount) {
return new Aligned(alignment, alignment.plus(date, amount));
}
@NonNull
public Aligned minus(final long amount) {
return plus(-amount);
}
}

View File

@ -7,27 +7,27 @@ import java.time.temporal.*;
import java.util.function.*;
public enum Alignment {
FIVE(Duration.ofMinutes(5), t -> t.truncatedTo(ChronoUnit.MINUTES).minusMinutes(t.getMinute() % 5), (t, a) -> t.plusMinutes(5 * a)),
HOUR(Duration.ofHours(1), t -> t.truncatedTo(ChronoUnit.HOURS), ZonedDateTime::plusHours),
DAY(Duration.ofDays(1), t -> t.truncatedTo(ChronoUnit.DAYS), ZonedDateTime::plusDays),
WEEK(Duration.ofDays(7), t -> t.truncatedTo(ChronoUnit.DAYS).minusDays(t.getDayOfWeek().getValue() - 1), ZonedDateTime::plusWeeks),
MONTH(Duration.ofDays(31), t -> t.truncatedTo(ChronoUnit.DAYS).minusDays(t.getDayOfMonth() - 1), ZonedDateTime::plusMonths),
YEAR(Duration.ofDays(366), t -> t.truncatedTo(ChronoUnit.DAYS).minusDays(t.getDayOfYear() - 1), ZonedDateTime::plusYears),
FIVE(t -> t.truncatedTo(ChronoUnit.MINUTES).minusMinutes(t.getMinute() % 5), Duration.ofMinutes(5), Duration.ofMinutes(5)),
HOUR(t -> t.truncatedTo(ChronoUnit.HOURS), Duration.ofHours(1), Duration.ofHours(1)),
DAY(t -> t.truncatedTo(ChronoUnit.DAYS), Duration.ofDays(1), Duration.ofDays(1)),
WEEK(t -> t.truncatedTo(ChronoUnit.DAYS).minusDays(t.getDayOfWeek().getValue() - 1), Period.ofWeeks(1), Duration.ofDays(7)),
MONTH(t -> t.truncatedTo(ChronoUnit.DAYS).minusDays(t.getDayOfMonth() - 1), Period.ofMonths(1), Duration.ofDays(31)),
YEAR(t -> t.truncatedTo(ChronoUnit.DAYS).minusDays(t.getDayOfYear() - 1), Period.ofYears(1), Duration.ofDays(366)),
;
@NonNull
public final Duration maxDuration;
@NonNull
public final Function<ZonedDateTime, ZonedDateTime> align;
@NonNull
public final BiFunction<ZonedDateTime, Long, ZonedDateTime> plus;
public final TemporalAmount amount;
Alignment(@NonNull final Duration maxDuration, @NonNull final Function<@NonNull ZonedDateTime, @NonNull ZonedDateTime> align, @NonNull final BiFunction<@NonNull ZonedDateTime, @NonNull Long, @NonNull ZonedDateTime> plus) {
@NonNull
public final Duration maxDuration;
Alignment(@NonNull final Function<@NonNull ZonedDateTime, @NonNull ZonedDateTime> align, @NonNull final TemporalAmount amount, @NonNull final Duration maxDuration) {
this.maxDuration = maxDuration;
this.align = align;
this.plus = plus;
this.amount = amount;
}
@NonNull
@ -35,4 +35,25 @@ public enum Alignment {
return new Aligned(this, date);
}
@NonNull
public ZonedDateTime plus(@NonNull final ZonedDateTime date, final long amount) {
return date.plus(multiplyAmount(amount));
}
@NonNull
@SuppressWarnings("unused")
public ZonedDateTime minus(@NonNull final ZonedDateTime date, final long amount) {
return plus(date, -amount);
}
@NonNull
public TemporalAmount multiplyAmount(final long amount) {
if (this.amount instanceof final Duration duration) {
return duration.multipliedBy(amount);
} else if (this.amount instanceof final Period period) {
return period.multipliedBy((int) amount);
}
throw new RuntimeException();
}
}

View File

@ -0,0 +1,10 @@
package de.ph87.data.series;
import lombok.*;
public interface IAggregationDto {
@NonNull
SeriesDto getSeries();
}

View File

@ -0,0 +1,31 @@
package de.ph87.data.series;
import de.ph87.data.series.meter.*;
import de.ph87.data.series.varying.*;
import lombok.*;
import org.springframework.web.bind.annotation.*;
import java.time.*;
import java.util.*;
@RestController
@RequiredArgsConstructor
@RequestMapping("Series")
public class SeriesController {
private final MeterService meterService;
private final VaryingService varyingService;
@NonNull
@GetMapping("agg/all/{alignmentName}/{offsetAmount}")
public AggregationWrapperDto agg(@PathVariable final String alignmentName, @PathVariable final long offsetAmount) {
final Alignment alignment = Alignment.valueOf(alignmentName);
final ZonedDateTime date = alignment.align(ZonedDateTime.now()).minus(offsetAmount).date;
final List<IAggregationDto> aggregations = new ArrayList<>();
aggregations.addAll(meterService.findAllAggregations(alignment, date));
aggregations.addAll(varyingService.findAllAggregations(alignment, date));
return new AggregationWrapperDto(alignment, date, aggregations);
}
}

View File

@ -48,7 +48,7 @@ public class SeriesService {
}
@NonNull
private SeriesDto toDto(@NonNull final Series series) {
public SeriesDto toDto(@NonNull final Series series) {
return new SeriesDto(series);
}

View File

@ -0,0 +1,14 @@
package de.ph87.data.series.meter;
import de.ph87.data.series.*;
import lombok.*;
@Data
public class MeterAggregation {
@NonNull
public final Series series;
public final double delta;
}

View File

@ -0,0 +1,19 @@
package de.ph87.data.series.meter;
import de.ph87.data.series.*;
import lombok.*;
@Data
public class MeterAggregationDto implements IAggregationDto {
@NonNull
public final SeriesDto series;
public final double delta;
public MeterAggregationDto(@NonNull final MeterAggregation meterAggregation, @NonNull final SeriesDto series) {
this.series = series;
this.delta = meterAggregation.delta;
}
}

View File

@ -14,6 +14,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.*;
import org.springframework.transaction.annotation.*;
import java.time.*;
import java.util.*;
import java.util.stream.*;
@ -75,14 +76,7 @@ public class MeterService {
@NonNull
public List<GraphPoint> getPoints(@NonNull final SeriesDto series, @NonNull final Aligned begin, @NonNull final Aligned end) {
final List<? extends MeterValue> graphPoints = switch (begin.alignment) {
case FIVE -> meterFiveRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqualOrderByIdDate(series.id, begin.date, end.date);
case HOUR -> meterHourRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqualOrderByIdDate(series.id, begin.date, end.date);
case DAY -> meterDayRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqualOrderByIdDate(series.id, begin.date, end.date);
case WEEK -> meterWeekRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqualOrderByIdDate(series.id, begin.date, end.date);
case MONTH -> meterMonthRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqualOrderByIdDate(series.id, begin.date, end.date);
case YEAR -> meterYearRepository.findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqualOrderByIdDate(series.id, begin.date, end.date);
};
final List<? extends MeterValue> graphPoints = findRepository(begin.alignment).findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqualOrderByIdDate(series.id, begin.date, end.date);
final List<GraphPoint> points = graphPoints.stream().map(meterValue -> new GraphPoint(meterValue.getId().getDate(), meterValue.getMax() - meterValue.getMin())).collect(Collectors.toCollection(LinkedList::new));
for (int i = 0; i < points.size() - 1; i++) {
if (points.get(i).date.compareTo(points.get(i + 1).date) == 0) {
@ -94,4 +88,26 @@ public class MeterService {
return points;
}
@NonNull
public List<MeterAggregationDto> findAllAggregations(@NonNull final Alignment alignment, @NonNull final ZonedDateTime date) {
return findRepository(alignment).findAllDeltaSum(date).stream().map(this::toDto).toList();
}
@NonNull
private MeterAggregationDto toDto(@NonNull final MeterAggregation meterAggregation) {
return new MeterAggregationDto(meterAggregation, seriesService.toDto(meterAggregation.series));
}
@NonNull
private MeterValueRepository<?> findRepository(@NonNull final Alignment alignment) {
return switch (alignment) {
case FIVE -> meterFiveRepository;
case HOUR -> meterHourRepository;
case DAY -> meterDayRepository;
case WEEK -> meterWeekRepository;
case MONTH -> meterMonthRepository;
case YEAR -> meterYearRepository;
};
}
}

View File

@ -1,6 +1,7 @@
package de.ph87.data.series.meter;
import lombok.*;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.*;
import java.time.*;
@ -11,4 +12,7 @@ public interface MeterValueRepository<T extends MeterValue> extends ListCrudRepo
List<T> findAllByIdMeterSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqualOrderByIdDate(long id, @NonNull ZonedDateTime begin, @NonNull ZonedDateTime end);
@Query("select new de.ph87.data.series.meter.MeterAggregation(m.id.meter.series, sum(m.max - m.min)) from #{#entityName} m where m.id.date = :date group by m.id.meter.series")
List<MeterAggregation> findAllDeltaSum(@NonNull ZonedDateTime date);
}

View File

@ -0,0 +1,18 @@
package de.ph87.data.series.varying;
import de.ph87.data.series.*;
import lombok.*;
@Data
public class VaryingAggregation {
@NonNull
public final Series series;
public final double min;
public final double avg;
public final double max;
}

View File

@ -0,0 +1,25 @@
package de.ph87.data.series.varying;
import de.ph87.data.series.*;
import lombok.*;
@Data
public class VaryingAggregationDto implements IAggregationDto {
@NonNull
public final SeriesDto series;
public final double min;
public final double avg;
public final double max;
public VaryingAggregationDto(@NonNull final VaryingAggregation varyingAggregation, @NonNull final SeriesDto series) {
this.series = series;
this.min = varyingAggregation.min;
this.avg = varyingAggregation.avg;
this.max = varyingAggregation.max;
}
}

View File

@ -1,6 +1,7 @@
package de.ph87.data.series.varying;
import lombok.*;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.*;
import java.time.*;
@ -11,4 +12,7 @@ public interface VaryingRepository<T extends Varying> extends ListCrudRepository
List<T> findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(long id, @NonNull ZonedDateTime begin, @NonNull ZonedDateTime end);
@Query("select new de.ph87.data.series.varying.VaryingAggregation(m.id.series, m.min, m.avg, m.max) from #{#entityName} m where m.id.date = :date")
List<VaryingAggregation> findAllDeltaSum(@NonNull ZonedDateTime date);
}

View File

@ -14,6 +14,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.*;
import org.springframework.transaction.annotation.*;
import java.time.*;
import java.util.*;
@Slf4j
@ -61,15 +62,33 @@ public class VaryingService {
@NonNull
public List<GraphPoint> getPoints(@NonNull final SeriesDto series, @NonNull final Aligned begin, @NonNull final Aligned end) {
final List<? extends Varying> graphPoints = switch (begin.alignment) {
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 findRepository(begin.alignment)
.findAllByIdSeriesIdAndIdDateGreaterThanEqualAndIdDateLessThanEqual(series.id, begin.date, end.date)
.stream()
.map(v -> new GraphPoint(v.getId().getDate(), v.getAvg()))
.toList();
}
@NonNull
public List<VaryingAggregationDto> findAllAggregations(@NonNull final Alignment alignment, @NonNull final ZonedDateTime date) {
return findRepository(alignment).findAllDeltaSum(date).stream().map(this::toDto).toList();
}
@NonNull
private VaryingAggregationDto toDto(@NonNull final VaryingAggregation varyingAggregation) {
return new VaryingAggregationDto(varyingAggregation, seriesService.toDto(varyingAggregation.series));
}
@NonNull
private VaryingRepository<?> findRepository(@NonNull final Alignment alignment) {
return switch (alignment) {
case FIVE -> varyingFiveRepository;
case HOUR -> varyingHourRepository;
case DAY -> varyingDayRepository;
case WEEK -> varyingWeekRepository;
case MONTH -> varyingMonthRepository;
case YEAR -> varyingYearRepository;
};
return graphPoints.stream().map(v -> new GraphPoint(v.getId().getDate(), v.getAvg())).toList();
}
}