Enrich operating periods with raw activity details

This commit is contained in:
trifonovt 2026-05-06 10:25:42 +02:00
parent 94767bc161
commit 14a6f8d42e
2 changed files with 119 additions and 2 deletions

View File

@ -1,6 +1,7 @@
package at.procon.eventhub.esperpoc.dto; package at.procon.eventhub.esperpoc.dto;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.List;
public record OperatingPeriodDto( public record OperatingPeriodDto(
long operatingPeriodNo, long operatingPeriodNo,
@ -8,11 +9,14 @@ public record OperatingPeriodDto(
OffsetDateTime endedAt, OffsetDateTime endedAt,
long durationSeconds, long durationSeconds,
String closedBy, String closedBy,
List<ActivityIntervalDto> rawActivities,
long breakRestSeconds,
long drivingSeconds, long drivingSeconds,
long workSeconds, long workSeconds,
long availabilitySeconds, long availabilitySeconds,
long unknownSeconds, long unknownSeconds,
int intervalCount, int intervalCount,
ShiftDrivingEvaluationDto drivingTimeInterruptionEvaluation,
boolean clippedToRequestedPeriod boolean clippedToRequestedPeriod
) { ) {
} }

View File

@ -2,6 +2,7 @@ package at.procon.eventhub.esperpoc.service;
import at.procon.eventhub.config.EventHubProperties; import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.esperpoc.dto.ActivityIntervalDto; 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.EsperOperatingPeriodRequest;
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodResultDto; import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodResultDto;
import at.procon.eventhub.esperpoc.dto.EsperUnknownTreatmentMode; 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.OperatingPeriodActivityIntervalDto;
import at.procon.eventhub.esperpoc.dto.OperatingPeriodDto; import at.procon.eventhub.esperpoc.dto.OperatingPeriodDto;
import at.procon.eventhub.esperpoc.dto.RawActivityEventDto; import at.procon.eventhub.esperpoc.dto.RawActivityEventDto;
import at.procon.eventhub.esperpoc.dto.ShiftDrivingEvaluationDto;
import at.procon.eventhub.esperpoc.persistence.EsperPocActivityRepository; import at.procon.eventhub.esperpoc.persistence.EsperPocActivityRepository;
import java.time.Duration; import java.time.Duration;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
@ -140,8 +142,11 @@ public class EsperOperatingPeriodEvaluationService {
List<OperatingPeriodDto> operatingPeriods = buildOperatingPeriods( List<OperatingPeriodDto> operatingPeriods = buildOperatingPeriods(
evaluation.closedPeriods(), evaluation.closedPeriods(),
periodizedIntervals, periodizedIntervals,
clipKnownActivities(resolvedKnownLoadedIntervals, requestedFrom, requestedTo),
requestedFrom, requestedFrom,
requestedTo requestedTo,
mergeGapTolerance,
significantDrivingThreshold
); );
long totalElapsedMs = elapsedMillis(startedNanos); long totalElapsedMs = elapsedMillis(startedNanos);
@ -366,8 +371,11 @@ public class EsperOperatingPeriodEvaluationService {
private List<OperatingPeriodDto> buildOperatingPeriods( private List<OperatingPeriodDto> buildOperatingPeriods(
List<EsperClosedOperatingPeriod> closedPeriods, List<EsperClosedOperatingPeriod> closedPeriods,
List<OperatingPeriodActivityIntervalDto> clippedPeriodizedIntervals, List<OperatingPeriodActivityIntervalDto> clippedPeriodizedIntervals,
List<ActivityIntervalDto> clippedKnownActivities,
OffsetDateTime requestedFrom, OffsetDateTime requestedFrom,
OffsetDateTime requestedTo OffsetDateTime requestedTo,
Duration mergeGapTolerance,
Duration significantDrivingThreshold
) { ) {
Map<Long, List<OperatingPeriodActivityIntervalDto>> intervalsByPeriod = new LinkedHashMap<>(); Map<Long, List<OperatingPeriodActivityIntervalDto>> intervalsByPeriod = new LinkedHashMap<>();
for (OperatingPeriodActivityIntervalDto interval : clippedPeriodizedIntervals) { for (OperatingPeriodActivityIntervalDto interval : clippedPeriodizedIntervals) {
@ -384,6 +392,16 @@ public class EsperOperatingPeriodEvaluationService {
continue; continue;
} }
List<OperatingPeriodActivityIntervalDto> intervals = intervalsByPeriod.getOrDefault(closedPeriod.operatingPeriodNo(), List.of()); List<OperatingPeriodActivityIntervalDto> intervals = intervalsByPeriod.getOrDefault(closedPeriod.operatingPeriodNo(), List.of());
List<ActivityIntervalDto> 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 drivingSeconds = sumActivitySeconds(intervals, "DRIVE");
long workSeconds = sumActivitySeconds(intervals, "WORK"); long workSeconds = sumActivitySeconds(intervals, "WORK");
long availabilitySeconds = sumActivitySeconds(intervals, "AVAILABILITY"); long availabilitySeconds = sumActivitySeconds(intervals, "AVAILABILITY");
@ -394,11 +412,14 @@ public class EsperOperatingPeriodEvaluationService {
end, end,
Duration.between(start, end).getSeconds(), Duration.between(start, end).getSeconds(),
closedPeriod.closedBy(), closedPeriod.closedBy(),
rawActivities,
breakRestSeconds,
drivingSeconds, drivingSeconds,
workSeconds, workSeconds,
availabilitySeconds, availabilitySeconds,
unknownSeconds, unknownSeconds,
intervals.size(), intervals.size(),
drivingEvaluation,
!start.equals(closedPeriod.startedAt()) || !end.equals(closedPeriod.endedAt()) !start.equals(closedPeriod.startedAt()) || !end.equals(closedPeriod.endedAt())
)); ));
} }
@ -433,6 +454,38 @@ public class EsperOperatingPeriodEvaluationService {
.toList(); .toList();
} }
private List<ActivityIntervalDto> clipKnownActivities(
List<ActivityIntervalDto> activities,
OffsetDateTime requestedFrom,
OffsetDateTime requestedTo
) {
return clipKnownActivitiesToPeriod(activities, requestedFrom, requestedTo);
}
private List<ActivityIntervalDto> clipKnownActivitiesToPeriod(
List<ActivityIntervalDto> 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<NonDrivingIntervalDto> clipNonDrivingIntervals( private List<NonDrivingIntervalDto> clipNonDrivingIntervals(
List<NonDrivingIntervalDto> intervals, List<NonDrivingIntervalDto> intervals,
OffsetDateTime requestedFrom, OffsetDateTime requestedFrom,
@ -558,6 +611,66 @@ public class EsperOperatingPeriodEvaluationService {
.toList(); .toList();
} }
private ShiftDrivingEvaluationDto evaluateSignificantDrivingWithEsper(
List<ActivityIntervalDto> rawActivities,
Duration mergeGapTolerance,
Duration significantDrivingThreshold
) {
if (rawActivities.isEmpty()) {
return new ShiftDrivingEvaluationDto(
(int) significantDrivingThreshold.toMinutes(),
null,
null,
null,
null,
List.of()
);
}
List<ActivityIntervalDto> mergedActivities = activityEngine.mergeConsecutiveIdenticalActivities(
rawActivities,
mergeGapTolerance
);
List<ActivityIntervalDto> 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<DrivingInterruptionDto> 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) { private int resolveOperatingSplitIdleHours(EsperOperatingPeriodRequest request) {
if (request.operatingSplitIdleHours() != null) { if (request.operatingSplitIdleHours() != null) {
return request.operatingSplitIdleHours(); return request.operatingSplitIdleHours();