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

View File

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

View File

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

View File

@ -11,6 +11,8 @@ public record TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
long durationSeconds, long durationSeconds,
long cardPresentDurationSeconds, long cardPresentDurationSeconds,
double cardPresentCoveragePercent, double cardPresentCoveragePercent,
long unknownDurationSeconds,
double unknownCoveragePercent,
String previousDrivingSourceIntervalId, String previousDrivingSourceIntervalId,
String nextDrivingSourceIntervalId, String nextDrivingSourceIntervalId,
String previousRegistrationKey, 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("startedAt"),
(OffsetDateTime) event.get("endedAt"), (OffsetDateTime) event.get("endedAt"),
(Long) event.get("durationSeconds"), (Long) event.get("durationSeconds"),
0L,
0.0d,
(Long) event.get("unknownDurationSeconds"), (Long) event.get("unknownDurationSeconds"),
(Double) event.get("unknownCoveragePercent"), (Double) event.get("unknownCoveragePercent"),
(String) event.get("previousDrivingSourceIntervalId"), (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.TachographEsperDrivingInterruptionIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent; 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.TachographEsperVuCardAbsentIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographFileSession; import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
import java.io.IOException; import java.io.IOException;
@ -108,6 +109,7 @@ public class DriverTimelineReusableProjectionBuilder {
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals = new ArrayList<>(); List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals = new ArrayList<>();
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals = new ArrayList<>(); List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals = new ArrayList<>();
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals = new ArrayList<>(); List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals = new ArrayList<>();
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals = new ArrayList<>();
executeWithRuntime( executeWithRuntime(
configuration -> { configuration -> {
@ -129,7 +131,8 @@ public class DriverTimelineReusableProjectionBuilder {
"drivingInterruptionVehicleChangeIntervals", newData -> collectDrivingInterruptionIntervalEvents(newData, drivingInterruptionVehicleChangeIntervals), "drivingInterruptionVehicleChangeIntervals", newData -> collectDrivingInterruptionIntervalEvents(newData, drivingInterruptionVehicleChangeIntervals),
"vuCardAbsentIntervals", newData -> collectVuCardAbsentIntervalEvents(newData, vuCardAbsentIntervals), "vuCardAbsentIntervals", newData -> collectVuCardAbsentIntervalEvents(newData, vuCardAbsentIntervals),
"potentialHomeOvernightStayIntervals", newData -> collectPotentialHomeOvernightStayIntervalEvents(newData, potentialHomeOvernightStayIntervals), "potentialHomeOvernightStayIntervals", newData -> collectPotentialHomeOvernightStayIntervalEvents(newData, potentialHomeOvernightStayIntervals),
"potentialInVehicleOvernightStayIntervals", newData -> collectPotentialInVehicleOvernightStayIntervalEvents(newData, potentialInVehicleOvernightStayIntervals) "potentialInVehicleOvernightStayIntervals", newData -> collectPotentialInVehicleOvernightStayIntervalEvents(newData, potentialInVehicleOvernightStayIntervals),
"potentialInVehicleTripIntervals", newData -> collectPotentialInVehicleTripIntervalEvents(newData, potentialInVehicleTripIntervals)
), ),
runtime -> { runtime -> {
if (vehicleUsageIntervals != null) { if (vehicleUsageIntervals != null) {
@ -159,7 +162,8 @@ public class DriverTimelineReusableProjectionBuilder {
sortDrivingInterruptionIntervals(drivingInterruptionVehicleChangeIntervals), sortDrivingInterruptionIntervals(drivingInterruptionVehicleChangeIntervals),
sortVuCardAbsentIntervals(vuCardAbsentIntervals), sortVuCardAbsentIntervals(vuCardAbsentIntervals),
sortPotentialHomeOvernightStayIntervals(potentialHomeOvernightStayIntervals), 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(), 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(startedAtEpochSecond), ZoneOffset.UTC),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC), OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
(Long) event.get("durationSeconds"), (Long) event.get("durationSeconds"),
(Long) event.get("cardPresentDurationSeconds"),
(Double) event.get("cardPresentCoveragePercent"),
(Long) event.get("unknownDurationSeconds"), (Long) event.get("unknownDurationSeconds"),
(Double) event.get("unknownCoveragePercent"), (Double) event.get("unknownCoveragePercent"),
(String) event.get("previousDrivingSourceIntervalId"), (String) event.get("previousDrivingSourceIntervalId"),
@ -454,6 +461,8 @@ public class DriverTimelineReusableProjectionBuilder {
(Long) event.get("durationSeconds"), (Long) event.get("durationSeconds"),
(Long) event.get("cardPresentDurationSeconds"), (Long) event.get("cardPresentDurationSeconds"),
(Double) event.get("cardPresentCoveragePercent"), (Double) event.get("cardPresentCoveragePercent"),
(Long) event.get("unknownDurationSeconds"),
(Double) event.get("unknownCoveragePercent"),
(String) event.get("previousDrivingSourceIntervalId"), (String) event.get("previousDrivingSourceIntervalId"),
(String) event.get("nextDrivingSourceIntervalId"), (String) event.get("nextDrivingSourceIntervalId"),
(String) event.get("previousRegistrationKey"), (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( private List<TachographEsperDrivingInterruptionIntervalEvent> sortDrivingInterruptionIntervals(
List<TachographEsperDrivingInterruptionIntervalEvent> intervals List<TachographEsperDrivingInterruptionIntervalEvent> intervals
) { ) {
@ -509,6 +549,15 @@ public class DriverTimelineReusableProjectionBuilder {
.toList(); .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) { private String renderDrivingDerivedProjectionBundleEpl(int significantDrivingMinutes, int minimumRestPeriodMinutes) {
return DRIVING_DERIVED_PROJECTION_BUNDLE_EPL_TEMPLATE return DRIVING_DERIVED_PROJECTION_BUNDLE_EPL_TEMPLATE
.replace( .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.TachographFileSession;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent; 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.TachographEsperVehicleUsageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import java.time.Duration; import java.time.Duration;
@ -202,17 +203,18 @@ public class TachographFileSessionProcessingService {
requestedFrom, requestedFrom,
requestedTo requestedTo
); );
List<TachographEsperVehicleUsageIntervalEvent> rawVehicleUsageIntervals =
driverTimelineBuilder.buildEsperVehicleUsageIntervalEvents(timeline);
List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals = List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals =
derivedProjectionBundle.vuCardAbsentIntervals(); derivedProjectionBundle.vuCardAbsentIntervals();
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals = List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals =
clipEsperPotentialHomeOvernightStayIntervalEvents( clipEsperPotentialHomeOvernightStayIntervalEvents(
derivedProjectionBundle.potentialHomeOvernightStayIntervals(), derivedProjectionBundle.potentialHomeOvernightStayIntervals(),
rawVuCardAbsentIntervals, rawVuCardAbsentIntervals,
rawVehicleUsageIntervals,
requestedFrom, requestedFrom,
requestedTo requestedTo
); );
List<TachographEsperVehicleUsageIntervalEvent> rawVehicleUsageIntervals =
driverTimelineBuilder.buildEsperVehicleUsageIntervalEvents(timeline);
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> dailyWeeklyRestCandidateCoverageIntervals = List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> dailyWeeklyRestCandidateCoverageIntervals =
clipEsperDailyWeeklyRestCandidateCoverageIntervalEvents( clipEsperDailyWeeklyRestCandidateCoverageIntervalEvents(
derivedProjectionBundle.dailyWeeklyRestCandidateCoverageIntervals(), derivedProjectionBundle.dailyWeeklyRestCandidateCoverageIntervals(),
@ -232,10 +234,18 @@ public class TachographFileSessionProcessingService {
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals = List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals =
clipEsperPotentialInVehicleOvernightStayIntervalEvents( clipEsperPotentialInVehicleOvernightStayIntervalEvents(
derivedProjectionBundle.potentialInVehicleOvernightStayIntervals(), derivedProjectionBundle.potentialInVehicleOvernightStayIntervals(),
rawVuCardAbsentIntervals,
rawVehicleUsageIntervals, rawVehicleUsageIntervals,
requestedFrom, requestedFrom,
requestedTo requestedTo
); );
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals =
clipEsperPotentialInVehicleTripIntervalEvents(
derivedProjectionBundle.potentialInVehicleTripIntervals(),
potentialInVehicleOvernightStayIntervals,
requestedFrom,
requestedTo
);
List<TachographEsperVehicleUsageIntervalEvent> vehicleUsageIntervals = clipEsperVehicleUsageIntervalEvents( List<TachographEsperVehicleUsageIntervalEvent> vehicleUsageIntervals = clipEsperVehicleUsageIntervalEvents(
rawVehicleUsageIntervals, rawVehicleUsageIntervals,
requestedFrom, requestedFrom,
@ -264,10 +274,11 @@ public class TachographFileSessionProcessingService {
unclassifiedDailyWeeklyRestCandidateCoverageIntervals.size(), unclassifiedDailyWeeklyRestCandidateCoverageIntervals.size(),
potentialHomeOvernightStayIntervals.size(), potentialHomeOvernightStayIntervals.size(),
potentialInVehicleOvernightStayIntervals.size(), potentialInVehicleOvernightStayIntervals.size(),
potentialInVehicleTripIntervals.size(),
vehicleUsageIntervals.size(), vehicleUsageIntervals.size(),
vuCardAbsentIntervals.size(), vuCardAbsentIntervals.size(),
activityIntervals, List.of()/*activityIntervals*/,
drivingIntervals, List.of()/*drivingIntervals*/,
drivingInterruptionIntervals, drivingInterruptionIntervals,
drivingInterruptionVehicleChangeIntervals, drivingInterruptionVehicleChangeIntervals,
dailyWeeklyRestCandidateIntervals, dailyWeeklyRestCandidateIntervals,
@ -275,6 +286,7 @@ public class TachographFileSessionProcessingService {
unclassifiedDailyWeeklyRestCandidateCoverageIntervals, unclassifiedDailyWeeklyRestCandidateCoverageIntervals,
potentialHomeOvernightStayIntervals, potentialHomeOvernightStayIntervals,
potentialInVehicleOvernightStayIntervals, potentialInVehicleOvernightStayIntervals,
potentialInVehicleTripIntervals,
vehicleUsageIntervals, vehicleUsageIntervals,
vuCardAbsentIntervals, vuCardAbsentIntervals,
esperProjectionNotes() esperProjectionNotes()
@ -510,53 +522,6 @@ public class TachographFileSessionProcessingService {
private List<TachographEsperPotentialHomeOvernightStayIntervalEvent> clipEsperPotentialHomeOvernightStayIntervalEvents( private List<TachographEsperPotentialHomeOvernightStayIntervalEvent> clipEsperPotentialHomeOvernightStayIntervalEvents(
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> intervals, List<TachographEsperPotentialHomeOvernightStayIntervalEvent> intervals,
List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals, 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, List<TachographEsperVehicleUsageIntervalEvent> rawVehicleUsageIntervals,
OffsetDateTime requestedFrom, OffsetDateTime requestedFrom,
OffsetDateTime requestedTo OffsetDateTime requestedTo
@ -577,11 +542,81 @@ public class TachographFileSessionProcessingService {
end, end,
rawVehicleUsageIntervals, rawVehicleUsageIntervals,
interval.driverKey(), interval.driverKey(),
interval.previousRegistrationKey() null
); );
double cardPresentCoveragePercent = durationSeconds == 0L double cardPresentCoveragePercent = durationSeconds == 0L
? 0.0d ? 0.0d
: (cardPresentDurationSeconds * 100.0d) / durationSeconds; : (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( return new TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
interval.sessionId(), interval.sessionId(),
interval.driverKey(), interval.driverKey(),
@ -590,6 +625,8 @@ public class TachographFileSessionProcessingService {
durationSeconds, durationSeconds,
cardPresentDurationSeconds, cardPresentDurationSeconds,
cardPresentCoveragePercent, cardPresentCoveragePercent,
unknownDurationSeconds,
unknownCoveragePercent,
interval.previousDrivingSourceIntervalId(), interval.previousDrivingSourceIntervalId(),
interval.nextDrivingSourceIntervalId(), interval.nextDrivingSourceIntervalId(),
interval.previousRegistrationKey(), interval.previousRegistrationKey(),
@ -604,6 +641,98 @@ public class TachographFileSessionProcessingService {
.toList(); .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( private List<ResolvedActivityInterval> synthesizeUnknownGaps(
List<ResolvedActivityInterval> knownIntervals, List<ResolvedActivityInterval> knownIntervals,
Duration gapDetectionTolerance 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.", "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 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 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.", "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.", "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." "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, startedAtEpochSecond long,
endedAtEpochSecond long, endedAtEpochSecond long,
durationSeconds long, durationSeconds long,
cardPresentDurationSeconds long,
cardPresentCoveragePercent double,
unknownDurationSeconds long, unknownDurationSeconds long,
unknownCoveragePercent double, unknownCoveragePercent double,
previousDrivingSourceIntervalId string, previousDrivingSourceIntervalId string,
@ -155,6 +157,8 @@ create schema PotentialInVehicleOvernightStayInterval(
durationSeconds long, durationSeconds long,
cardPresentDurationSeconds long, cardPresentDurationSeconds long,
cardPresentCoveragePercent double, cardPresentCoveragePercent double,
unknownDurationSeconds long,
unknownCoveragePercent double,
previousDrivingSourceIntervalId string, previousDrivingSourceIntervalId string,
nextDrivingSourceIntervalId string, nextDrivingSourceIntervalId string,
previousRegistrationKey string, previousRegistrationKey string,
@ -181,6 +185,44 @@ create schema UnclassifiedDailyWeeklyRestCandidateCoverageInterval(
nextVehicleKey string 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 insert into SignificantDrivingInterval
select select
sessionId, sessionId,
@ -435,6 +477,8 @@ select
c.startedAtEpochSecond as startedAtEpochSecond, c.startedAtEpochSecond as startedAtEpochSecond,
c.endedAtEpochSecond as endedAtEpochSecond, c.endedAtEpochSecond as endedAtEpochSecond,
c.durationSeconds as durationSeconds, c.durationSeconds as durationSeconds,
c.cardPresentDurationSeconds as cardPresentDurationSeconds,
c.cardPresentCoveragePercent as cardPresentCoveragePercent,
c.unknownDurationSeconds as unknownDurationSeconds, c.unknownDurationSeconds as unknownDurationSeconds,
c.unknownCoveragePercent as unknownCoveragePercent, c.unknownCoveragePercent as unknownCoveragePercent,
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId, c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
@ -458,6 +502,8 @@ select
c.durationSeconds as durationSeconds, c.durationSeconds as durationSeconds,
c.cardPresentDurationSeconds as cardPresentDurationSeconds, c.cardPresentDurationSeconds as cardPresentDurationSeconds,
c.cardPresentCoveragePercent as cardPresentCoveragePercent, c.cardPresentCoveragePercent as cardPresentCoveragePercent,
c.unknownDurationSeconds as unknownDurationSeconds,
c.unknownCoveragePercent as unknownCoveragePercent,
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId, c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId, c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
c.previousRegistrationKey as previousRegistrationKey, c.previousRegistrationKey as previousRegistrationKey,
@ -501,6 +547,130 @@ where not (
and c.cardPresentDurationSeconds >= c.durationSeconds 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') @name('drivingInterruptionIntervals')
select * from DrivingInterruptionInterval; select * from DrivingInterruptionInterval;
@ -522,5 +692,8 @@ select * from PotentialHomeOvernightStayInterval;
@name('potentialInVehicleOvernightStayIntervals') @name('potentialInVehicleOvernightStayIntervals')
select * from PotentialInVehicleOvernightStayInterval; select * from PotentialInVehicleOvernightStayInterval;
@name('potentialInVehicleTripIntervals')
select * from PotentialInVehicleTripInterval;
@name('unclassifiedDailyWeeklyRestCandidateCoverageIntervals') @name('unclassifiedDailyWeeklyRestCandidateCoverageIntervals')
select * from UnclassifiedDailyWeeklyRestCandidateCoverageInterval; 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.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent; 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.TachographEsperVehicleUsageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionProcessingService; import at.procon.eventhub.tachographfilesession.service.TachographFileSessionProcessingService;
@ -89,6 +90,7 @@ class TachographFileSessionControllerTest {
0, 0,
1, 1,
0, 0,
0,
2, 2,
1, 1,
List.of(new TachographEsperActivityIntervalEvent( List.of(new TachographEsperActivityIntervalEvent(
@ -192,6 +194,8 @@ class TachographFileSessionControllerTest {
OffsetDateTime.parse("2026-05-12T10:00:00Z"), OffsetDateTime.parse("2026-05-12T10:00:00Z"),
OffsetDateTime.parse("2026-05-12T22:00:00Z"), OffsetDateTime.parse("2026-05-12T22:00:00Z"),
43_200L, 43_200L,
0L,
0.0d,
43_200L, 43_200L,
100.0d, 100.0d,
"ACT-2", "ACT-2",
@ -202,6 +206,7 @@ class TachographFileSessionControllerTest {
"VIN-2" "VIN-2"
)), )),
List.of(), List.of(),
List.<TachographEsperPotentialInVehicleTripIntervalEvent>of(),
List.of(new TachographEsperVehicleUsageIntervalEvent( List.of(new TachographEsperVehicleUsageIntervalEvent(
sessionId, sessionId,
"12:123", "12:123",
@ -300,6 +305,7 @@ class TachographFileSessionControllerTest {
.andExpect(jsonPath("$.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount").value(0)) .andExpect(jsonPath("$.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount").value(0))
.andExpect(jsonPath("$.potentialHomeOvernightStayIntervalCount").value(1)) .andExpect(jsonPath("$.potentialHomeOvernightStayIntervalCount").value(1))
.andExpect(jsonPath("$.potentialInVehicleOvernightStayIntervalCount").value(0)) .andExpect(jsonPath("$.potentialInVehicleOvernightStayIntervalCount").value(0))
.andExpect(jsonPath("$.potentialInVehicleTripIntervalCount").value(0))
.andExpect(jsonPath("$.vuCardAbsentIntervalCount").value(1)) .andExpect(jsonPath("$.vuCardAbsentIntervalCount").value(1))
.andExpect(jsonPath("$.drivingInterruptionIntervals[0].previousRegistrationKey").value("12:REG-1")) .andExpect(jsonPath("$.drivingInterruptionIntervals[0].previousRegistrationKey").value("12:REG-1"))
.andExpect(jsonPath("$.drivingInterruptionIntervals[0].nextRegistrationKey").value("12:REG-2")) .andExpect(jsonPath("$.drivingInterruptionIntervals[0].nextRegistrationKey").value("12:REG-2"))

View File

@ -104,6 +104,7 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0); assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0); assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0); assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2); assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2);
assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1); assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
assertThat(result.vuCardAbsentIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:01Z")); 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.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0); assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).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).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).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
assertThat(result.drivingInterruptionIntervals().get(0).previousRegistrationKey()).isEqualTo("12:REG-1"); assertThat(result.drivingInterruptionIntervals().get(0).previousRegistrationKey()).isEqualTo("12:REG-1");
@ -300,6 +302,7 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0); assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0); assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0); assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2); assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2);
assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1); assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
} }
@ -397,8 +400,11 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).nextRegistrationKey()).isEqualTo("12:REG-2"); assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).nextRegistrationKey()).isEqualTo("12:REG-2");
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(1); assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(1);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0); 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).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).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).unknownDurationSeconds()).isEqualTo(43_200L);
assertThat(result.potentialHomeOvernightStayIntervals().get(0).unknownCoveragePercent()).isEqualTo(100.0d); assertThat(result.potentialHomeOvernightStayIntervals().get(0).unknownCoveragePercent()).isEqualTo(100.0d);
} }
@ -476,6 +482,7 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.drivingInterruptionVehicleChangeIntervalCount()).isEqualTo(0); assertThat(result.drivingInterruptionVehicleChangeIntervalCount()).isEqualTo(0);
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0); assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(1); assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(1);
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(50_400L); assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(50_400L);
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(100.0d); assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(100.0d);
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).unknownDurationSeconds()).isEqualTo(0L); 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).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(50_400L); assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(50_400L);
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(100.0d); 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 @Test
@ -565,6 +574,7 @@ class TachographFileSessionProcessingServiceTest {
assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(1); assertThat(result.dailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(1);
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0); assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0); assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(0);
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(1); assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()).isEqualTo(1);
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).startedAt()) assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).startedAt())
.isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z")); .isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
@ -576,6 +586,80 @@ class TachographFileSessionProcessingServiceTest {
.isLessThan(95.0d); .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 @Test
void evaluatesOperatingPeriodsFromSessionTimeline() { void evaluatesOperatingPeriodsFromSessionTimeline() {
EventHubProperties properties = new EventHubProperties(); EventHubProperties properties = new EventHubProperties();