From 14a6f8d42ea865fdefa7cb1d24cfa9edc38e18f9 Mon Sep 17 00:00:00 2001 From: trifonovt <87468028+TihomirTrifonov@users.noreply.github.com> Date: Wed, 6 May 2026 10:25:42 +0200 Subject: [PATCH] Enrich operating periods with raw activity details --- .../esperpoc/dto/OperatingPeriodDto.java | 4 + ...EsperOperatingPeriodEvaluationService.java | 117 +++++++++++++++++- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/main/java/at/procon/eventhub/esperpoc/dto/OperatingPeriodDto.java b/src/main/java/at/procon/eventhub/esperpoc/dto/OperatingPeriodDto.java index e3a176d..100f343 100644 --- a/src/main/java/at/procon/eventhub/esperpoc/dto/OperatingPeriodDto.java +++ b/src/main/java/at/procon/eventhub/esperpoc/dto/OperatingPeriodDto.java @@ -1,6 +1,7 @@ package at.procon.eventhub.esperpoc.dto; import java.time.OffsetDateTime; +import java.util.List; public record OperatingPeriodDto( long operatingPeriodNo, @@ -8,11 +9,14 @@ public record OperatingPeriodDto( OffsetDateTime endedAt, long durationSeconds, String closedBy, + List rawActivities, + long breakRestSeconds, long drivingSeconds, long workSeconds, long availabilitySeconds, long unknownSeconds, int intervalCount, + ShiftDrivingEvaluationDto drivingTimeInterruptionEvaluation, boolean clippedToRequestedPeriod ) { } diff --git a/src/main/java/at/procon/eventhub/esperpoc/service/EsperOperatingPeriodEvaluationService.java b/src/main/java/at/procon/eventhub/esperpoc/service/EsperOperatingPeriodEvaluationService.java index 5977ffb..e6a2bb2 100644 --- a/src/main/java/at/procon/eventhub/esperpoc/service/EsperOperatingPeriodEvaluationService.java +++ b/src/main/java/at/procon/eventhub/esperpoc/service/EsperOperatingPeriodEvaluationService.java @@ -2,6 +2,7 @@ package at.procon.eventhub.esperpoc.service; import at.procon.eventhub.config.EventHubProperties; import at.procon.eventhub.esperpoc.dto.ActivityIntervalDto; +import at.procon.eventhub.esperpoc.dto.DrivingInterruptionDto; import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodRequest; import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodResultDto; import at.procon.eventhub.esperpoc.dto.EsperUnknownTreatmentMode; @@ -9,6 +10,7 @@ import at.procon.eventhub.esperpoc.dto.NonDrivingIntervalDto; import at.procon.eventhub.esperpoc.dto.OperatingPeriodActivityIntervalDto; import at.procon.eventhub.esperpoc.dto.OperatingPeriodDto; import at.procon.eventhub.esperpoc.dto.RawActivityEventDto; +import at.procon.eventhub.esperpoc.dto.ShiftDrivingEvaluationDto; import at.procon.eventhub.esperpoc.persistence.EsperPocActivityRepository; import java.time.Duration; import java.time.OffsetDateTime; @@ -140,8 +142,11 @@ public class EsperOperatingPeriodEvaluationService { List operatingPeriods = buildOperatingPeriods( evaluation.closedPeriods(), periodizedIntervals, + clipKnownActivities(resolvedKnownLoadedIntervals, requestedFrom, requestedTo), requestedFrom, - requestedTo + requestedTo, + mergeGapTolerance, + significantDrivingThreshold ); long totalElapsedMs = elapsedMillis(startedNanos); @@ -366,8 +371,11 @@ public class EsperOperatingPeriodEvaluationService { private List buildOperatingPeriods( List closedPeriods, List clippedPeriodizedIntervals, + List clippedKnownActivities, OffsetDateTime requestedFrom, - OffsetDateTime requestedTo + OffsetDateTime requestedTo, + Duration mergeGapTolerance, + Duration significantDrivingThreshold ) { Map> intervalsByPeriod = new LinkedHashMap<>(); for (OperatingPeriodActivityIntervalDto interval : clippedPeriodizedIntervals) { @@ -384,6 +392,16 @@ public class EsperOperatingPeriodEvaluationService { continue; } List intervals = intervalsByPeriod.getOrDefault(closedPeriod.operatingPeriodNo(), List.of()); + List rawActivities = clipKnownActivitiesToPeriod(clippedKnownActivities, start, end); + long breakRestSeconds = rawActivities.stream() + .filter(activity -> "BREAK_REST".equals(activity.activityType())) + .mapToLong(ActivityIntervalDto::durationSeconds) + .sum(); + ShiftDrivingEvaluationDto drivingEvaluation = evaluateSignificantDrivingWithEsper( + rawActivities, + mergeGapTolerance, + significantDrivingThreshold + ); long drivingSeconds = sumActivitySeconds(intervals, "DRIVE"); long workSeconds = sumActivitySeconds(intervals, "WORK"); long availabilitySeconds = sumActivitySeconds(intervals, "AVAILABILITY"); @@ -394,11 +412,14 @@ public class EsperOperatingPeriodEvaluationService { end, Duration.between(start, end).getSeconds(), closedPeriod.closedBy(), + rawActivities, + breakRestSeconds, drivingSeconds, workSeconds, availabilitySeconds, unknownSeconds, intervals.size(), + drivingEvaluation, !start.equals(closedPeriod.startedAt()) || !end.equals(closedPeriod.endedAt()) )); } @@ -433,6 +454,38 @@ public class EsperOperatingPeriodEvaluationService { .toList(); } + private List clipKnownActivities( + List activities, + OffsetDateTime requestedFrom, + OffsetDateTime requestedTo + ) { + return clipKnownActivitiesToPeriod(activities, requestedFrom, requestedTo); + } + + private List clipKnownActivitiesToPeriod( + List activities, + OffsetDateTime periodFrom, + OffsetDateTime periodTo + ) { + return activities.stream() + .map(activity -> { + OffsetDateTime start = max(activity.startedAt(), periodFrom); + OffsetDateTime end = min(activity.endedAt(), periodTo); + if (!end.isAfter(start)) { + return null; + } + boolean clipped = activity.clippedToRequestedPeriod() + || !start.equals(activity.startedAt()) + || !end.equals(activity.endedAt()); + return activity.withTime(start, end, clipped); + }) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(ActivityIntervalDto::startedAt) + .thenComparing(ActivityIntervalDto::endedAt) + .thenComparing(ActivityIntervalDto::activityType, Comparator.nullsLast(String::compareTo))) + .toList(); + } + private List clipNonDrivingIntervals( List intervals, OffsetDateTime requestedFrom, @@ -558,6 +611,66 @@ public class EsperOperatingPeriodEvaluationService { .toList(); } + private ShiftDrivingEvaluationDto evaluateSignificantDrivingWithEsper( + List rawActivities, + Duration mergeGapTolerance, + Duration significantDrivingThreshold + ) { + if (rawActivities.isEmpty()) { + return new ShiftDrivingEvaluationDto( + (int) significantDrivingThreshold.toMinutes(), + null, + null, + null, + null, + List.of() + ); + } + List mergedActivities = activityEngine.mergeConsecutiveIdenticalActivities( + rawActivities, + mergeGapTolerance + ); + List significantDrivingPeriods = mergedActivities.stream() + .filter(activity -> "DRIVE".equals(activity.activityType())) + .filter(activity -> activity.durationSeconds() > significantDrivingThreshold.getSeconds()) + .sorted(Comparator.comparing(ActivityIntervalDto::startedAt)) + .toList(); + if (significantDrivingPeriods.isEmpty()) { + return new ShiftDrivingEvaluationDto( + (int) significantDrivingThreshold.toMinutes(), + null, + null, + null, + null, + List.of() + ); + } + List interruptions = new ArrayList<>(); + for (int index = 1; index < significantDrivingPeriods.size(); index++) { + ActivityIntervalDto previous = significantDrivingPeriods.get(index - 1); + ActivityIntervalDto next = significantDrivingPeriods.get(index); + if (next.startedAt().isAfter(previous.endedAt())) { + interruptions.add(new DrivingInterruptionDto( + previous.endedAt(), + next.startedAt(), + Duration.between(previous.endedAt(), next.startedAt()).getSeconds(), + previous.sourceRowId(), + next.sourceRowId() + )); + } + } + ActivityIntervalDto first = significantDrivingPeriods.get(0); + ActivityIntervalDto last = significantDrivingPeriods.get(significantDrivingPeriods.size() - 1); + return new ShiftDrivingEvaluationDto( + (int) significantDrivingThreshold.toMinutes(), + first.startedAt(), + last.endedAt(), + first, + last, + interruptions + ); + } + private int resolveOperatingSplitIdleHours(EsperOperatingPeriodRequest request) { if (request.operatingSplitIdleHours() != null) { return request.operatingSplitIdleHours();