Add in-vehicle trip Esper projection

This commit is contained in:
trifonovt 2026-05-21 13:03:24 +02:00
parent 9b86d13001
commit 33e9cb62c3
11 changed files with 532 additions and 55 deletions

View File

@ -5,6 +5,7 @@ import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeekly
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleTripIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import java.time.OffsetDateTime;
@ -28,6 +29,7 @@ public record TachographEsperDriverProcessingResultDto(
int unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount,
int potentialHomeOvernightStayIntervalCount,
int potentialInVehicleOvernightStayIntervalCount,
int potentialInVehicleTripIntervalCount,
int vehicleUsageIntervalCount,
int vuCardAbsentIntervalCount,
List<TachographEsperActivityIntervalEvent> activityIntervals,
@ -39,6 +41,7 @@ public record TachographEsperDriverProcessingResultDto(
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> unclassifiedDailyWeeklyRestCandidateCoverageIntervals,
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals,
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals,
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals,
List<TachographEsperVehicleUsageIntervalEvent> vehicleUsageIntervals,
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals,
List<String> notes

View File

@ -10,6 +10,7 @@ public record TachographEsperDrivingDerivedProjectionBundle(
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals,
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals,
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals,
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals,
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals
) {
}

View File

@ -9,6 +9,8 @@ public record TachographEsperPotentialHomeOvernightStayIntervalEvent(
OffsetDateTime startedAt,
OffsetDateTime endedAt,
long durationSeconds,
long cardPresentDurationSeconds,
double cardPresentCoveragePercent,
long unknownDurationSeconds,
double unknownCoveragePercent,
String previousDrivingSourceIntervalId,

View File

@ -11,6 +11,8 @@ public record TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
long durationSeconds,
long cardPresentDurationSeconds,
double cardPresentCoveragePercent,
long unknownDurationSeconds,
double unknownCoveragePercent,
String previousDrivingSourceIntervalId,
String nextDrivingSourceIntervalId,
String previousRegistrationKey,

View File

@ -0,0 +1,25 @@
package at.procon.eventhub.tachographfilesession.model;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
public record TachographEsperPotentialInVehicleTripIntervalEvent(
UUID sessionId,
String driverKey,
OffsetDateTime startedAt,
OffsetDateTime endedAt,
long durationSeconds,
String registrationKey,
String vehicleKey,
int containedPotentialInVehicleOvernightStayIntervalCount,
long containedPotentialInVehicleOvernightStayDurationSeconds,
long containedCardPresentDurationSeconds,
long containedUnknownDurationSeconds,
OffsetDateTime firstPotentialInVehicleOvernightStayStartedAt,
OffsetDateTime lastPotentialInVehicleOvernightStayEndedAt,
String firstPreviousDrivingSourceIntervalId,
String lastNextDrivingSourceIntervalId,
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals
) {
}

View File

@ -997,6 +997,8 @@ public class DriverTimelineBuilder {
(OffsetDateTime) event.get("startedAt"),
(OffsetDateTime) event.get("endedAt"),
(Long) event.get("durationSeconds"),
0L,
0.0d,
(Long) event.get("unknownDurationSeconds"),
(Double) event.get("unknownCoveragePercent"),
(String) event.get("previousDrivingSourceIntervalId"),

View File

@ -19,6 +19,7 @@ import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingDeri
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleTripIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
import java.io.IOException;
@ -108,6 +109,7 @@ public class DriverTimelineReusableProjectionBuilder {
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals = new ArrayList<>();
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals = new ArrayList<>();
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals = new ArrayList<>();
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals = new ArrayList<>();
executeWithRuntime(
configuration -> {
@ -129,7 +131,8 @@ public class DriverTimelineReusableProjectionBuilder {
"drivingInterruptionVehicleChangeIntervals", newData -> collectDrivingInterruptionIntervalEvents(newData, drivingInterruptionVehicleChangeIntervals),
"vuCardAbsentIntervals", newData -> collectVuCardAbsentIntervalEvents(newData, vuCardAbsentIntervals),
"potentialHomeOvernightStayIntervals", newData -> collectPotentialHomeOvernightStayIntervalEvents(newData, potentialHomeOvernightStayIntervals),
"potentialInVehicleOvernightStayIntervals", newData -> collectPotentialInVehicleOvernightStayIntervalEvents(newData, potentialInVehicleOvernightStayIntervals)
"potentialInVehicleOvernightStayIntervals", newData -> collectPotentialInVehicleOvernightStayIntervalEvents(newData, potentialInVehicleOvernightStayIntervals),
"potentialInVehicleTripIntervals", newData -> collectPotentialInVehicleTripIntervalEvents(newData, potentialInVehicleTripIntervals)
),
runtime -> {
if (vehicleUsageIntervals != null) {
@ -159,7 +162,8 @@ public class DriverTimelineReusableProjectionBuilder {
sortDrivingInterruptionIntervals(drivingInterruptionVehicleChangeIntervals),
sortVuCardAbsentIntervals(vuCardAbsentIntervals),
sortPotentialHomeOvernightStayIntervals(potentialHomeOvernightStayIntervals),
sortPotentialInVehicleOvernightStayIntervals(potentialInVehicleOvernightStayIntervals)
sortPotentialInVehicleOvernightStayIntervals(potentialInVehicleOvernightStayIntervals),
sortPotentialInVehicleTripIntervals(potentialInVehicleTripIntervals)
);
}
@ -172,6 +176,7 @@ public class DriverTimelineReusableProjectionBuilder {
List.of(),
List.of(),
List.of(),
List.of(),
List.of()
);
}
@ -424,6 +429,8 @@ public class DriverTimelineReusableProjectionBuilder {
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
(Long) event.get("durationSeconds"),
(Long) event.get("cardPresentDurationSeconds"),
(Double) event.get("cardPresentCoveragePercent"),
(Long) event.get("unknownDurationSeconds"),
(Double) event.get("unknownCoveragePercent"),
(String) event.get("previousDrivingSourceIntervalId"),
@ -454,6 +461,8 @@ public class DriverTimelineReusableProjectionBuilder {
(Long) event.get("durationSeconds"),
(Long) event.get("cardPresentDurationSeconds"),
(Double) event.get("cardPresentCoveragePercent"),
(Long) event.get("unknownDurationSeconds"),
(Double) event.get("unknownCoveragePercent"),
(String) event.get("previousDrivingSourceIntervalId"),
(String) event.get("nextDrivingSourceIntervalId"),
(String) event.get("previousRegistrationKey"),
@ -464,6 +473,37 @@ public class DriverTimelineReusableProjectionBuilder {
}
}
private void collectPotentialInVehicleTripIntervalEvents(
EventBean[] newData,
List<TachographEsperPotentialInVehicleTripIntervalEvent> target
) {
if (newData == null) {
return;
}
for (EventBean event : newData) {
long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond");
long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond");
target.add(new TachographEsperPotentialInVehicleTripIntervalEvent(
(UUID) event.get("sessionId"),
(String) event.get("driverKey"),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
(Long) event.get("durationSeconds"),
(String) event.get("registrationKey"),
(String) event.get("vehicleKey"),
(Integer) event.get("containedPotentialInVehicleOvernightStayIntervalCount"),
(Long) event.get("containedPotentialInVehicleOvernightStayDurationSeconds"),
(Long) event.get("containedCardPresentDurationSeconds"),
(Long) event.get("containedUnknownDurationSeconds"),
OffsetDateTime.ofInstant(Instant.ofEpochSecond((Long) event.get("firstPotentialInVehicleOvernightStayStartedAtEpochSecond")), ZoneOffset.UTC),
OffsetDateTime.ofInstant(Instant.ofEpochSecond((Long) event.get("lastPotentialInVehicleOvernightStayEndedAtEpochSecond")), ZoneOffset.UTC),
(String) event.get("firstPreviousDrivingSourceIntervalId"),
(String) event.get("lastNextDrivingSourceIntervalId"),
List.of()
));
}
}
private List<TachographEsperDrivingInterruptionIntervalEvent> sortDrivingInterruptionIntervals(
List<TachographEsperDrivingInterruptionIntervalEvent> intervals
) {
@ -509,6 +549,15 @@ public class DriverTimelineReusableProjectionBuilder {
.toList();
}
private List<TachographEsperPotentialInVehicleTripIntervalEvent> sortPotentialInVehicleTripIntervals(
List<TachographEsperPotentialInVehicleTripIntervalEvent> intervals
) {
return intervals.stream()
.sorted(Comparator.comparing(TachographEsperPotentialInVehicleTripIntervalEvent::startedAt)
.thenComparing(TachographEsperPotentialInVehicleTripIntervalEvent::endedAt))
.toList();
}
private String renderDrivingDerivedProjectionBundleEpl(int significantDrivingMinutes, int minimumRestPeriodMinutes) {
return DRIVING_DERIVED_PROJECTION_BUNDLE_EPL_TEMPLATE
.replace(

View File

@ -19,6 +19,7 @@ import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInte
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleTripIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import java.time.Duration;
@ -202,17 +203,18 @@ public class TachographFileSessionProcessingService {
requestedFrom,
requestedTo
);
List<TachographEsperVehicleUsageIntervalEvent> rawVehicleUsageIntervals =
driverTimelineBuilder.buildEsperVehicleUsageIntervalEvents(timeline);
List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals =
derivedProjectionBundle.vuCardAbsentIntervals();
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals =
clipEsperPotentialHomeOvernightStayIntervalEvents(
derivedProjectionBundle.potentialHomeOvernightStayIntervals(),
rawVuCardAbsentIntervals,
rawVehicleUsageIntervals,
requestedFrom,
requestedTo
);
List<TachographEsperVehicleUsageIntervalEvent> rawVehicleUsageIntervals =
driverTimelineBuilder.buildEsperVehicleUsageIntervalEvents(timeline);
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> dailyWeeklyRestCandidateCoverageIntervals =
clipEsperDailyWeeklyRestCandidateCoverageIntervalEvents(
derivedProjectionBundle.dailyWeeklyRestCandidateCoverageIntervals(),
@ -232,10 +234,18 @@ public class TachographFileSessionProcessingService {
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals =
clipEsperPotentialInVehicleOvernightStayIntervalEvents(
derivedProjectionBundle.potentialInVehicleOvernightStayIntervals(),
rawVuCardAbsentIntervals,
rawVehicleUsageIntervals,
requestedFrom,
requestedTo
);
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals =
clipEsperPotentialInVehicleTripIntervalEvents(
derivedProjectionBundle.potentialInVehicleTripIntervals(),
potentialInVehicleOvernightStayIntervals,
requestedFrom,
requestedTo
);
List<TachographEsperVehicleUsageIntervalEvent> vehicleUsageIntervals = clipEsperVehicleUsageIntervalEvents(
rawVehicleUsageIntervals,
requestedFrom,
@ -264,10 +274,11 @@ public class TachographFileSessionProcessingService {
unclassifiedDailyWeeklyRestCandidateCoverageIntervals.size(),
potentialHomeOvernightStayIntervals.size(),
potentialInVehicleOvernightStayIntervals.size(),
potentialInVehicleTripIntervals.size(),
vehicleUsageIntervals.size(),
vuCardAbsentIntervals.size(),
activityIntervals,
drivingIntervals,
List.of()/*activityIntervals*/,
List.of()/*drivingIntervals*/,
drivingInterruptionIntervals,
drivingInterruptionVehicleChangeIntervals,
dailyWeeklyRestCandidateIntervals,
@ -275,6 +286,7 @@ public class TachographFileSessionProcessingService {
unclassifiedDailyWeeklyRestCandidateCoverageIntervals,
potentialHomeOvernightStayIntervals,
potentialInVehicleOvernightStayIntervals,
potentialInVehicleTripIntervals,
vehicleUsageIntervals,
vuCardAbsentIntervals,
esperProjectionNotes()
@ -510,53 +522,6 @@ public class TachographFileSessionProcessingService {
private List<TachographEsperPotentialHomeOvernightStayIntervalEvent> clipEsperPotentialHomeOvernightStayIntervalEvents(
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> intervals,
List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals,
OffsetDateTime requestedFrom,
OffsetDateTime requestedTo
) {
if (requestedFrom == null || requestedTo == null) {
return List.of();
}
return intervals.stream()
.map(interval -> {
OffsetDateTime start = max(interval.startedAt(), requestedFrom);
OffsetDateTime end = min(interval.endedAt(), requestedTo);
if (!end.isAfter(start)) {
return null;
}
long durationSeconds = Duration.between(start, end).getSeconds();
long unknownDurationSeconds = overlapSeconds(
start,
end,
rawVuCardAbsentIntervals,
interval.driverKey()
);
double unknownCoveragePercent = durationSeconds == 0L
? 0.0d
: (unknownDurationSeconds * 100.0d) / durationSeconds;
return new TachographEsperPotentialHomeOvernightStayIntervalEvent(
interval.sessionId(),
interval.driverKey(),
start,
end,
durationSeconds,
unknownDurationSeconds,
unknownCoveragePercent,
interval.previousDrivingSourceIntervalId(),
interval.nextDrivingSourceIntervalId(),
interval.previousRegistrationKey(),
interval.nextRegistrationKey(),
interval.previousVehicleKey(),
interval.nextVehicleKey()
);
})
.filter(Objects::nonNull)
.sorted(Comparator.comparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::startedAt)
.thenComparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::endedAt))
.toList();
}
private List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> clipEsperPotentialInVehicleOvernightStayIntervalEvents(
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> intervals,
List<TachographEsperVehicleUsageIntervalEvent> rawVehicleUsageIntervals,
OffsetDateTime requestedFrom,
OffsetDateTime requestedTo
@ -577,11 +542,81 @@ public class TachographFileSessionProcessingService {
end,
rawVehicleUsageIntervals,
interval.driverKey(),
interval.previousRegistrationKey()
null
);
double cardPresentCoveragePercent = durationSeconds == 0L
? 0.0d
: (cardPresentDurationSeconds * 100.0d) / durationSeconds;
long unknownDurationSeconds = overlapSeconds(
start,
end,
rawVuCardAbsentIntervals,
interval.driverKey()
);
double unknownCoveragePercent = durationSeconds == 0L
? 0.0d
: (unknownDurationSeconds * 100.0d) / durationSeconds;
return new TachographEsperPotentialHomeOvernightStayIntervalEvent(
interval.sessionId(),
interval.driverKey(),
start,
end,
durationSeconds,
cardPresentDurationSeconds,
cardPresentCoveragePercent,
unknownDurationSeconds,
unknownCoveragePercent,
interval.previousDrivingSourceIntervalId(),
interval.nextDrivingSourceIntervalId(),
interval.previousRegistrationKey(),
interval.nextRegistrationKey(),
interval.previousVehicleKey(),
interval.nextVehicleKey()
);
})
.filter(Objects::nonNull)
.sorted(Comparator.comparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::startedAt)
.thenComparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::endedAt))
.toList();
}
private List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> clipEsperPotentialInVehicleOvernightStayIntervalEvents(
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> intervals,
List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals,
List<TachographEsperVehicleUsageIntervalEvent> rawVehicleUsageIntervals,
OffsetDateTime requestedFrom,
OffsetDateTime requestedTo
) {
if (requestedFrom == null || requestedTo == null) {
return List.of();
}
return intervals.stream()
.map(interval -> {
OffsetDateTime start = max(interval.startedAt(), requestedFrom);
OffsetDateTime end = min(interval.endedAt(), requestedTo);
if (!end.isAfter(start)) {
return null;
}
long durationSeconds = Duration.between(start, end).getSeconds();
long cardPresentDurationSeconds = overlapVehicleUsageSeconds(
start,
end,
rawVehicleUsageIntervals,
interval.driverKey(),
null
);
double cardPresentCoveragePercent = durationSeconds == 0L
? 0.0d
: (cardPresentDurationSeconds * 100.0d) / durationSeconds;
long unknownDurationSeconds = overlapSeconds(
start,
end,
rawVuCardAbsentIntervals,
interval.driverKey()
);
double unknownCoveragePercent = durationSeconds == 0L
? 0.0d
: (unknownDurationSeconds * 100.0d) / durationSeconds;
return new TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
interval.sessionId(),
interval.driverKey(),
@ -590,6 +625,8 @@ public class TachographFileSessionProcessingService {
durationSeconds,
cardPresentDurationSeconds,
cardPresentCoveragePercent,
unknownDurationSeconds,
unknownCoveragePercent,
interval.previousDrivingSourceIntervalId(),
interval.nextDrivingSourceIntervalId(),
interval.previousRegistrationKey(),
@ -604,6 +641,98 @@ public class TachographFileSessionProcessingService {
.toList();
}
private List<TachographEsperPotentialInVehicleTripIntervalEvent> clipEsperPotentialInVehicleTripIntervalEvents(
List<TachographEsperPotentialInVehicleTripIntervalEvent> intervals,
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals,
OffsetDateTime requestedFrom,
OffsetDateTime requestedTo
) {
if (requestedFrom == null || requestedTo == null) {
return List.of();
}
if (intervals == null || intervals.isEmpty()
|| potentialInVehicleOvernightStayIntervals == null || potentialInVehicleOvernightStayIntervals.isEmpty()) {
return List.of();
}
return intervals.stream()
.map(interval -> {
OffsetDateTime start = max(interval.startedAt(), requestedFrom);
OffsetDateTime end = min(interval.endedAt(), requestedTo);
if (!end.isAfter(start)) {
return null;
}
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> containedIntervals =
potentialInVehicleOvernightStayIntervals.stream()
.filter(candidate -> tripContainsPotentialInterval(
interval.driverKey(),
interval.registrationKey(),
interval.vehicleKey(),
start,
end,
candidate
))
.sorted(Comparator.comparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::startedAt)
.thenComparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::endedAt))
.toList();
if (containedIntervals.isEmpty()) {
return null;
}
TachographEsperPotentialInVehicleOvernightStayIntervalEvent first = containedIntervals.get(0);
TachographEsperPotentialInVehicleOvernightStayIntervalEvent last =
containedIntervals.get(containedIntervals.size() - 1);
return new TachographEsperPotentialInVehicleTripIntervalEvent(
interval.sessionId(),
interval.driverKey(),
start,
end,
Duration.between(start, end).getSeconds(),
interval.registrationKey(),
interval.vehicleKey(),
containedIntervals.size(),
containedIntervals.stream()
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::durationSeconds)
.sum(),
containedIntervals.stream()
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::cardPresentDurationSeconds)
.sum(),
containedIntervals.stream()
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::unknownDurationSeconds)
.sum(),
first.startedAt(),
last.endedAt(),
first.previousDrivingSourceIntervalId(),
last.nextDrivingSourceIntervalId(),
containedIntervals
);
})
.filter(Objects::nonNull)
.sorted(Comparator.comparing(TachographEsperPotentialInVehicleTripIntervalEvent::startedAt)
.thenComparing(TachographEsperPotentialInVehicleTripIntervalEvent::endedAt))
.toList();
}
private boolean tripContainsPotentialInterval(
String driverKey,
String registrationKey,
String vehicleKey,
OffsetDateTime tripStartedAt,
OffsetDateTime tripEndedAt,
TachographEsperPotentialInVehicleOvernightStayIntervalEvent candidate
) {
if (!Objects.equals(driverKey, candidate.driverKey())) {
return false;
}
if (!Objects.equals(registrationKey, candidate.previousRegistrationKey())) {
return false;
}
if (vehicleKey != null && candidate.previousVehicleKey() != null
&& !Objects.equals(vehicleKey, candidate.previousVehicleKey())) {
return false;
}
return !candidate.startedAt().isBefore(tripStartedAt)
&& !candidate.endedAt().isAfter(tripEndedAt);
}
private List<ResolvedActivityInterval> synthesizeUnknownGaps(
List<ResolvedActivityInterval> knownIntervals,
Duration gapDetectionTolerance
@ -1174,6 +1303,7 @@ public class TachographFileSessionProcessingService {
"Unclassified daily/weekly rest candidate coverage intervals are the rest candidates that are neither potential home overnight stays nor potential in-vehicle overnight stays.",
"Potential home overnight stay intervals are vehicle-change daily/weekly rest candidate coverage intervals where VU card-absent overlap covers at least 95% of the candidate interval.",
"Potential in-vehicle overnight stay intervals are no-change daily/weekly rest candidate coverage intervals where card-present overlap covers the candidate rest period.",
"Potential in-vehicle trip intervals span from the end of the coverage interval before a same-vehicle in-vehicle-overnight sequence to the start of the first coverage interval after that sequence.",
"VU card-absent intervals are gaps between consecutive normalized vehicle-usage intervals for the same driver.",
"occurredFrom and occurredTo clip the returned interval projections to the requested UTC time window.",
"Vehicle-usage intervals clear clipped odometer endpoints because boundary odometer values cannot be recomputed safely from the source interval."

View File

@ -137,6 +137,8 @@ create schema PotentialHomeOvernightStayInterval(
startedAtEpochSecond long,
endedAtEpochSecond long,
durationSeconds long,
cardPresentDurationSeconds long,
cardPresentCoveragePercent double,
unknownDurationSeconds long,
unknownCoveragePercent double,
previousDrivingSourceIntervalId string,
@ -155,6 +157,8 @@ create schema PotentialInVehicleOvernightStayInterval(
durationSeconds long,
cardPresentDurationSeconds long,
cardPresentCoveragePercent double,
unknownDurationSeconds long,
unknownCoveragePercent double,
previousDrivingSourceIntervalId string,
nextDrivingSourceIntervalId string,
previousRegistrationKey string,
@ -181,6 +185,44 @@ create schema UnclassifiedDailyWeeklyRestCandidateCoverageInterval(
nextVehicleKey string
);
create schema PotentialInVehicleTripState(
sessionId java.util.UUID,
driverKey string,
tripStartedAtEpochSecond long,
registrationKey string,
vehicleKey string,
containedPotentialInVehicleOvernightStayIntervalCount int,
containedPotentialInVehicleOvernightStayDurationSeconds long,
containedCardPresentDurationSeconds long,
containedUnknownDurationSeconds long,
firstPotentialInVehicleOvernightStayStartedAtEpochSecond long,
lastPotentialInVehicleOvernightStayEndedAtEpochSecond long,
firstPreviousDrivingSourceIntervalId string,
lastNextDrivingSourceIntervalId string
);
create schema PotentialInVehicleTripInterval(
sessionId java.util.UUID,
driverKey string,
startedAtEpochSecond long,
endedAtEpochSecond long,
durationSeconds long,
registrationKey string,
vehicleKey string,
containedPotentialInVehicleOvernightStayIntervalCount int,
containedPotentialInVehicleOvernightStayDurationSeconds long,
containedCardPresentDurationSeconds long,
containedUnknownDurationSeconds long,
firstPotentialInVehicleOvernightStayStartedAtEpochSecond long,
lastPotentialInVehicleOvernightStayEndedAtEpochSecond long,
firstPreviousDrivingSourceIntervalId string,
lastNextDrivingSourceIntervalId string
);
create window PreviousRestCandidateCoverageInterval#unique(driverKey) as DailyWeeklyRestCandidateCoverageInterval;
create window OpenPotentialInVehicleTripState#unique(driverKey) as PotentialInVehicleTripState;
insert into SignificantDrivingInterval
select
sessionId,
@ -435,6 +477,8 @@ select
c.startedAtEpochSecond as startedAtEpochSecond,
c.endedAtEpochSecond as endedAtEpochSecond,
c.durationSeconds as durationSeconds,
c.cardPresentDurationSeconds as cardPresentDurationSeconds,
c.cardPresentCoveragePercent as cardPresentCoveragePercent,
c.unknownDurationSeconds as unknownDurationSeconds,
c.unknownCoveragePercent as unknownCoveragePercent,
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
@ -458,6 +502,8 @@ select
c.durationSeconds as durationSeconds,
c.cardPresentDurationSeconds as cardPresentDurationSeconds,
c.cardPresentCoveragePercent as cardPresentCoveragePercent,
c.unknownDurationSeconds as unknownDurationSeconds,
c.unknownCoveragePercent as unknownCoveragePercent,
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
c.previousRegistrationKey as previousRegistrationKey,
@ -501,6 +547,130 @@ where not (
and c.cardPresentDurationSeconds >= c.durationSeconds
);
@Priority(40)
on DailyWeeklyRestCandidateCoverageInterval as c
insert into PotentialInVehicleTripInterval
select
s.sessionId as sessionId,
s.driverKey as driverKey,
s.tripStartedAtEpochSecond as startedAtEpochSecond,
c.startedAtEpochSecond as endedAtEpochSecond,
c.startedAtEpochSecond - s.tripStartedAtEpochSecond as durationSeconds,
s.registrationKey as registrationKey,
s.vehicleKey as vehicleKey,
s.containedPotentialInVehicleOvernightStayIntervalCount as containedPotentialInVehicleOvernightStayIntervalCount,
s.containedPotentialInVehicleOvernightStayDurationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
s.containedCardPresentDurationSeconds as containedCardPresentDurationSeconds,
s.containedUnknownDurationSeconds as containedUnknownDurationSeconds,
s.firstPotentialInVehicleOvernightStayStartedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
s.lastPotentialInVehicleOvernightStayEndedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
s.firstPreviousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
s.lastNextDrivingSourceIntervalId as lastNextDrivingSourceIntervalId
from OpenPotentialInVehicleTripState as s
where s.driverKey = c.driverKey
and c.startedAtEpochSecond > s.tripStartedAtEpochSecond
and (
not (
c.previousRegistrationKey is not null
and c.nextRegistrationKey is not null
and c.previousRegistrationKey = c.nextRegistrationKey
and c.cardPresentDurationSeconds >= c.durationSeconds
)
or s.registrationKey != c.previousRegistrationKey
or (
s.vehicleKey is not null
and c.previousVehicleKey is not null
and s.vehicleKey != c.previousVehicleKey
)
);
@Priority(35)
on DailyWeeklyRestCandidateCoverageInterval as c
delete from OpenPotentialInVehicleTripState as s
where s.driverKey = c.driverKey
and not (
c.previousRegistrationKey is not null
and c.nextRegistrationKey is not null
and c.previousRegistrationKey = c.nextRegistrationKey
and c.cardPresentDurationSeconds >= c.durationSeconds
);
@Priority(30)
on DailyWeeklyRestCandidateCoverageInterval as c
insert into OpenPotentialInVehicleTripState
select
s.sessionId as sessionId,
s.driverKey as driverKey,
s.tripStartedAtEpochSecond as tripStartedAtEpochSecond,
s.registrationKey as registrationKey,
s.vehicleKey as vehicleKey,
s.containedPotentialInVehicleOvernightStayIntervalCount + 1 as containedPotentialInVehicleOvernightStayIntervalCount,
s.containedPotentialInVehicleOvernightStayDurationSeconds + c.durationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
s.containedCardPresentDurationSeconds + c.cardPresentDurationSeconds as containedCardPresentDurationSeconds,
s.containedUnknownDurationSeconds + c.unknownDurationSeconds as containedUnknownDurationSeconds,
s.firstPotentialInVehicleOvernightStayStartedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
c.endedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
s.firstPreviousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
c.nextDrivingSourceIntervalId as lastNextDrivingSourceIntervalId
from OpenPotentialInVehicleTripState as s
where s.driverKey = c.driverKey
and c.previousRegistrationKey is not null
and c.nextRegistrationKey is not null
and c.previousRegistrationKey = c.nextRegistrationKey
and c.cardPresentDurationSeconds >= c.durationSeconds
and s.registrationKey = c.previousRegistrationKey
and (
s.vehicleKey is null
or c.previousVehicleKey is null
or s.vehicleKey = c.previousVehicleKey
);
@Priority(20)
on DailyWeeklyRestCandidateCoverageInterval as c
insert into OpenPotentialInVehicleTripState
select
c.sessionId as sessionId,
c.driverKey as driverKey,
priorCoverage.endedAtEpochSecond as tripStartedAtEpochSecond,
c.previousRegistrationKey as registrationKey,
case
when c.previousVehicleKey is not null then c.previousVehicleKey
else c.nextVehicleKey
end as vehicleKey,
1 as containedPotentialInVehicleOvernightStayIntervalCount,
c.durationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
c.cardPresentDurationSeconds as containedCardPresentDurationSeconds,
c.unknownDurationSeconds as containedUnknownDurationSeconds,
c.startedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
c.endedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
c.previousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
c.nextDrivingSourceIntervalId as lastNextDrivingSourceIntervalId
from PreviousRestCandidateCoverageInterval as priorCoverage
where priorCoverage.driverKey = c.driverKey
and c.previousRegistrationKey is not null
and c.nextRegistrationKey is not null
and c.previousRegistrationKey = c.nextRegistrationKey
and c.cardPresentDurationSeconds >= c.durationSeconds
and not exists (
select * from OpenPotentialInVehicleTripState as s
where s.driverKey = c.driverKey
and s.registrationKey = c.previousRegistrationKey
and (
s.vehicleKey is null
or c.previousVehicleKey is null
or s.vehicleKey = c.previousVehicleKey
)
);
@Priority(10)
on DailyWeeklyRestCandidateCoverageInterval
delete from PreviousRestCandidateCoverageInterval;
@Priority(5)
on DailyWeeklyRestCandidateCoverageInterval as current
insert into PreviousRestCandidateCoverageInterval
select *;
@name('drivingInterruptionIntervals')
select * from DrivingInterruptionInterval;
@ -522,5 +692,8 @@ select * from PotentialHomeOvernightStayInterval;
@name('potentialInVehicleOvernightStayIntervals')
select * from PotentialInVehicleOvernightStayInterval;
@name('potentialInVehicleTripIntervals')
select * from PotentialInVehicleTripInterval;
@name('unclassifiedDailyWeeklyRestCandidateCoverageIntervals')
select * from UnclassifiedDailyWeeklyRestCandidateCoverageInterval;

View File

@ -23,6 +23,7 @@ import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityInt
import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleTripIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionProcessingService;
@ -89,6 +90,7 @@ class TachographFileSessionControllerTest {
0,
1,
0,
0,
2,
1,
List.of(new TachographEsperActivityIntervalEvent(
@ -192,6 +194,8 @@ class TachographFileSessionControllerTest {
OffsetDateTime.parse("2026-05-12T10:00:00Z"),
OffsetDateTime.parse("2026-05-12T22:00:00Z"),
43_200L,
0L,
0.0d,
43_200L,
100.0d,
"ACT-2",
@ -202,6 +206,7 @@ class TachographFileSessionControllerTest {
"VIN-2"
)),
List.of(),
List.<TachographEsperPotentialInVehicleTripIntervalEvent>of(),
List.of(new TachographEsperVehicleUsageIntervalEvent(
sessionId,
"12:123",
@ -300,6 +305,7 @@ class TachographFileSessionControllerTest {
.andExpect(jsonPath("$.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount").value(0))
.andExpect(jsonPath("$.potentialHomeOvernightStayIntervalCount").value(1))
.andExpect(jsonPath("$.potentialInVehicleOvernightStayIntervalCount").value(0))
.andExpect(jsonPath("$.potentialInVehicleTripIntervalCount").value(0))
.andExpect(jsonPath("$.vuCardAbsentIntervalCount").value(1))
.andExpect(jsonPath("$.drivingInterruptionIntervals[0].previousRegistrationKey").value("12:REG-1"))
.andExpect(jsonPath("$.drivingInterruptionIntervals[0].nextRegistrationKey").value("12:REG-2"))

View File

@ -104,6 +104,7 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2);
assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
assertThat(result.vuCardAbsentIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:01Z"));
@ -201,6 +202,7 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
assertThat(result.drivingInterruptionIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T09:00:00Z"));
assertThat(result.drivingInterruptionIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
assertThat(result.drivingInterruptionIntervals().get(0).previousRegistrationKey()).isEqualTo("12:REG-1");
@ -300,6 +302,7 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2);
assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
}
@ -397,8 +400,11 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).nextRegistrationKey()).isEqualTo("12:REG-2");
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(1);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
assertThat(result.potentialHomeOvernightStayIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
assertThat(result.potentialHomeOvernightStayIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T23:00:00Z"));
assertThat(result.potentialHomeOvernightStayIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(0L);
assertThat(result.potentialHomeOvernightStayIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(0.0d);
assertThat(result.potentialHomeOvernightStayIntervals().get(0).unknownDurationSeconds()).isEqualTo(43_200L);
assertThat(result.potentialHomeOvernightStayIntervals().get(0).unknownCoveragePercent()).isEqualTo(100.0d);
}
@ -476,6 +482,7 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.drivingInterruptionVehicleChangeIntervalCount()).isEqualTo(0);
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(1);
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(50_400L);
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(100.0d);
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).unknownDurationSeconds()).isEqualTo(0L);
@ -483,6 +490,8 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(50_400L);
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(100.0d);
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).unknownDurationSeconds()).isEqualTo(0L);
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).unknownCoveragePercent()).isEqualTo(0.0d);
}
@Test
@ -565,6 +574,7 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(1);
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(1);
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).startedAt())
.isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
@ -576,6 +586,80 @@ class TachographFileSessionProcessingServiceTest {
.isLessThan(95.0d);
}
@Test
void returnsPotentialInVehicleTripIntervalsForSameVehicleOvernightSequenceBoundedByCoverageIntervals() {
EventHubProperties properties = new EventHubProperties();
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
repository,
driverTimelineBuilder,
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder),
new EventBackedDriverTimelineBuilder(
new IntervalBackedDriverTimelineEventBuilder(
driverTimelineBuilder,
new DriverKeyFactory(),
new VehicleKeyFactory(),
new EventDetailsFactory(new ObjectMapper())
)
),
properties
);
DriverExtractionSession driver = new DriverExtractionSession(
"12:123",
null,
null,
List.of(),
List.of(),
List.of(
new ExtractedCardVehicleUsageInterval("CVU-0", OffsetDateTime.parse("2026-05-01T08:00:00Z"), OffsetDateTime.parse("2026-05-01T09:00:00Z"), 100L, 150L, "12:REG-0", "VIN-0", "vu-0"),
new ExtractedCardVehicleUsageInterval("CVU-1", OffsetDateTime.parse("2026-05-01T22:00:00Z"), OffsetDateTime.parse("2026-05-04T02:00:00Z"), 200L, 400L, "12:REG-1", "VIN-1", "vu-1"),
new ExtractedCardVehicleUsageInterval("CVU-2", OffsetDateTime.parse("2026-05-05T00:00:00Z"), OffsetDateTime.parse("2026-05-05T02:00:00Z"), 401L, 430L, "12:REG-2", "VIN-2", "vu-2")
),
List.of(
new ExtractedCardActivityInterval("ACT-0", OffsetDateTime.parse("2026-05-01T08:00:00Z"), OffsetDateTime.parse("2026-05-01T09:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-0", "VIN-0", "a"),
new ExtractedCardActivityInterval("ACT-1", OffsetDateTime.parse("2026-05-01T22:00:00Z"), OffsetDateTime.parse("2026-05-02T00:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "b"),
new ExtractedCardActivityInterval("ACT-2", OffsetDateTime.parse("2026-05-02T20:00:00Z"), OffsetDateTime.parse("2026-05-02T22:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "c"),
new ExtractedCardActivityInterval("ACT-3", OffsetDateTime.parse("2026-05-03T20:00:00Z"), OffsetDateTime.parse("2026-05-03T22:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "d"),
new ExtractedCardActivityInterval("ACT-4", OffsetDateTime.parse("2026-05-05T00:00:00Z"), OffsetDateTime.parse("2026-05-05T02:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-2", "VIN-2", "e")
),
List.of(),
List.of()
);
TachographFileSession session = new TachographFileSession(
UUID.randomUUID(),
new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 5, "42", "b", true, null),
Map.of(driver.driverKey(), driver),
new ExtractionStats(1, 5, 3, 1, 1, 0),
List.of(),
Instant.now(),
Instant.now().plus(4, ChronoUnit.HOURS)
);
repository.save(session);
TachographEsperDriverProcessingResultDto result = service.getEsperDriverProcessingResults(
session.sessionId(),
driver.driverKey(),
new TachographEsperEventsProcessingRequest(
OffsetDateTime.parse("2026-05-01T10:00:00Z"),
OffsetDateTime.parse("2026-05-05T00:00:00Z"),
3,
720
)
);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(2);
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(1);
assertThat(result.potentialInVehicleTripIntervals().get(0).startedAt())
.isEqualTo(OffsetDateTime.parse("2026-05-01T22:00:00Z"));
assertThat(result.potentialInVehicleTripIntervals().get(0).endedAt())
.isEqualTo(OffsetDateTime.parse("2026-05-03T22:00:00Z"));
assertThat(result.potentialInVehicleTripIntervals().get(0).containedPotentialInVehicleOvernightStayIntervalCount()).isEqualTo(2);
assertThat(result.potentialInVehicleTripIntervals().get(0).potentialInVehicleOvernightStayIntervals()).hasSize(2);
assertThat(result.potentialInVehicleTripIntervals().get(0).registrationKey()).isEqualTo("12:REG-1");
}
@Test
void evaluatesOperatingPeriodsFromSessionTimeline() {
EventHubProperties properties = new EventHubProperties();