From f1f36e22041571e515b0190d72198c74ae0c3a9a Mon Sep 17 00:00:00 2001 From: trifonovt <87468028+TihomirTrifonov@users.noreply.github.com> Date: Fri, 22 May 2026 10:12:32 +0200 Subject: [PATCH] Add rest geo evidence and boundary odometers --- .../eventhub/config/EventHubProperties.java | 37 + ...hographEsperDriverProcessingResultDto.java | 3 + ...klyRestCandidateCoverageIntervalEvent.java | 22 +- .../TachographEsperGeoEvidenceEvent.java | 14 + ...tentialHomeOvernightStayIntervalEvent.java | 22 +- ...alInVehicleOvernightStayIntervalEvent.java | 22 +- .../model/TachographEsperSupportGeoEvent.java | 25 + .../service/DriverTimelineBuilder.java | 22 +- ...iverTimelineReusableProjectionBuilder.java | 267 +++- ...achographFileSessionProcessingService.java | 152 ++- ...raph-driving-derived-projection-bundle.epl | 1180 ++++++++++++++++- .../TachographFileSessionControllerTest.java | 67 +- ...TimelineReusableProjectionBuilderTest.java | 92 +- ...graphFileSessionProcessingServiceTest.java | 16 +- 14 files changed, 1894 insertions(+), 47 deletions(-) create mode 100644 src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperGeoEvidenceEvent.java create mode 100644 src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperSupportGeoEvent.java diff --git a/src/main/java/at/procon/eventhub/config/EventHubProperties.java b/src/main/java/at/procon/eventhub/config/EventHubProperties.java index acafed7..5dffa74 100644 --- a/src/main/java/at/procon/eventhub/config/EventHubProperties.java +++ b/src/main/java/at/procon/eventhub/config/EventHubProperties.java @@ -360,6 +360,10 @@ public class EventHubProperties { private int operatingSplitIdleHours = 7; private int significantDrivingMinutes = 3; private int minimumRestPeriodMinutes = 720; + private int restCandidateGeoLookbackMinutes = 180; + private int restCandidateGeoLookaheadMinutes = 180; + private int restCandidateGeoStationaryMaxMeters = 500; + private int restCandidateGeoMinorMovementMaxMeters = 2000; private int mergeGapSeconds = 0; private int gapDetectionToleranceSeconds = 0; @@ -397,6 +401,39 @@ public class EventHubProperties { this.minimumRestPeriodMinutes = Math.max(1, minimumRestPeriodMinutes); } + public int getRestCandidateGeoLookbackMinutes() { + return restCandidateGeoLookbackMinutes; + } + + public void setRestCandidateGeoLookbackMinutes(int restCandidateGeoLookbackMinutes) { + this.restCandidateGeoLookbackMinutes = Math.max(1, restCandidateGeoLookbackMinutes); + } + + public int getRestCandidateGeoLookaheadMinutes() { + return restCandidateGeoLookaheadMinutes; + } + + public void setRestCandidateGeoLookaheadMinutes(int restCandidateGeoLookaheadMinutes) { + this.restCandidateGeoLookaheadMinutes = Math.max(1, restCandidateGeoLookaheadMinutes); + } + + public int getRestCandidateGeoStationaryMaxMeters() { + return restCandidateGeoStationaryMaxMeters; + } + + public void setRestCandidateGeoStationaryMaxMeters(int restCandidateGeoStationaryMaxMeters) { + this.restCandidateGeoStationaryMaxMeters = Math.max(0, restCandidateGeoStationaryMaxMeters); + } + + public int getRestCandidateGeoMinorMovementMaxMeters() { + return restCandidateGeoMinorMovementMaxMeters; + } + + public void setRestCandidateGeoMinorMovementMaxMeters(int restCandidateGeoMinorMovementMaxMeters) { + this.restCandidateGeoMinorMovementMaxMeters = + Math.max(this.restCandidateGeoStationaryMaxMeters, restCandidateGeoMinorMovementMaxMeters); + } + public int getMergeGapSeconds() { return mergeGapSeconds; } diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/dto/TachographEsperDriverProcessingResultDto.java b/src/main/java/at/procon/eventhub/tachographfilesession/dto/TachographEsperDriverProcessingResultDto.java index a9ecc28..d499a37 100644 --- a/src/main/java/at/procon/eventhub/tachographfilesession/dto/TachographEsperDriverProcessingResultDto.java +++ b/src/main/java/at/procon/eventhub/tachographfilesession/dto/TachographEsperDriverProcessingResultDto.java @@ -6,6 +6,7 @@ import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInte 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.TachographEsperSupportGeoEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent; import java.time.OffsetDateTime; @@ -32,6 +33,7 @@ public record TachographEsperDriverProcessingResultDto( int potentialInVehicleTripIntervalCount, int vehicleUsageIntervalCount, int vuCardAbsentIntervalCount, + int supportGeoEventCount, List activityIntervals, List drivingIntervals, List drivingInterruptionIntervals, @@ -44,6 +46,7 @@ public record TachographEsperDriverProcessingResultDto( List potentialInVehicleTripIntervals, List vehicleUsageIntervals, List vuCardAbsentIntervals, + List supportGeoEvents, List notes ) { } diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent.java b/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent.java index 5a4f461..0ee019e 100644 --- a/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent.java +++ b/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent.java @@ -18,6 +18,26 @@ public record TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent( String previousRegistrationKey, String nextRegistrationKey, String previousVehicleKey, - String nextVehicleKey + String nextVehicleKey, + Long beginBoundaryOdometerKm, + Long endBoundaryOdometerKm, + String beginGeoEventId, + String beginGeoEventDomain, + OffsetDateTime beginGeoOccurredAt, + Double beginLatitude, + Double beginLongitude, + Long beginGeoDistanceSeconds, + Long beginGeoOdometerKm, + String endGeoEventId, + String endGeoEventDomain, + OffsetDateTime endGeoOccurredAt, + Double endLatitude, + Double endLongitude, + Long endGeoDistanceSeconds, + Long endGeoOdometerKm, + Long geoEvidenceMovementMeters, + String geoEvidenceMovementCategory, + TachographEsperGeoEvidenceEvent beginGeoEvent, + TachographEsperGeoEvidenceEvent endGeoEvent ) { } diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperGeoEvidenceEvent.java b/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperGeoEvidenceEvent.java new file mode 100644 index 0000000..3acd50f --- /dev/null +++ b/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperGeoEvidenceEvent.java @@ -0,0 +1,14 @@ +package at.procon.eventhub.tachographfilesession.model; + +import java.time.OffsetDateTime; + +public record TachographEsperGeoEvidenceEvent( + String eventId, + String eventDomain, + OffsetDateTime occurredAt, + Double latitude, + Double longitude, + Long distanceSeconds, + Long odometerKm +) { +} diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperPotentialHomeOvernightStayIntervalEvent.java b/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperPotentialHomeOvernightStayIntervalEvent.java index e17266f..6ae3794 100644 --- a/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperPotentialHomeOvernightStayIntervalEvent.java +++ b/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperPotentialHomeOvernightStayIntervalEvent.java @@ -18,6 +18,26 @@ public record TachographEsperPotentialHomeOvernightStayIntervalEvent( String previousRegistrationKey, String nextRegistrationKey, String previousVehicleKey, - String nextVehicleKey + String nextVehicleKey, + Long beginBoundaryOdometerKm, + Long endBoundaryOdometerKm, + String beginGeoEventId, + String beginGeoEventDomain, + OffsetDateTime beginGeoOccurredAt, + Double beginLatitude, + Double beginLongitude, + Long beginGeoDistanceSeconds, + Long beginGeoOdometerKm, + String endGeoEventId, + String endGeoEventDomain, + OffsetDateTime endGeoOccurredAt, + Double endLatitude, + Double endLongitude, + Long endGeoDistanceSeconds, + Long endGeoOdometerKm, + Long geoEvidenceMovementMeters, + String geoEvidenceMovementCategory, + TachographEsperGeoEvidenceEvent beginGeoEvent, + TachographEsperGeoEvidenceEvent endGeoEvent ) { } diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperPotentialInVehicleOvernightStayIntervalEvent.java b/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperPotentialInVehicleOvernightStayIntervalEvent.java index 5d41f5c..25cff2a 100644 --- a/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperPotentialInVehicleOvernightStayIntervalEvent.java +++ b/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperPotentialInVehicleOvernightStayIntervalEvent.java @@ -18,6 +18,26 @@ public record TachographEsperPotentialInVehicleOvernightStayIntervalEvent( String previousRegistrationKey, String nextRegistrationKey, String previousVehicleKey, - String nextVehicleKey + String nextVehicleKey, + Long beginBoundaryOdometerKm, + Long endBoundaryOdometerKm, + String beginGeoEventId, + String beginGeoEventDomain, + OffsetDateTime beginGeoOccurredAt, + Double beginLatitude, + Double beginLongitude, + Long beginGeoDistanceSeconds, + Long beginGeoOdometerKm, + String endGeoEventId, + String endGeoEventDomain, + OffsetDateTime endGeoOccurredAt, + Double endLatitude, + Double endLongitude, + Long endGeoDistanceSeconds, + Long endGeoOdometerKm, + Long geoEvidenceMovementMeters, + String geoEvidenceMovementCategory, + TachographEsperGeoEvidenceEvent beginGeoEvent, + TachographEsperGeoEvidenceEvent endGeoEvent ) { } diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperSupportGeoEvent.java b/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperSupportGeoEvent.java new file mode 100644 index 0000000..0cbae86 --- /dev/null +++ b/src/main/java/at/procon/eventhub/tachographfilesession/model/TachographEsperSupportGeoEvent.java @@ -0,0 +1,25 @@ +package at.procon.eventhub.tachographfilesession.model; + +import java.math.BigDecimal; +import java.time.OffsetDateTime; + +public record TachographEsperSupportGeoEvent( + String eventId, + String driverKey, + OffsetDateTime occurredAt, + String eventDomain, + String eventType, + String eventLifecycle, + String registrationKey, + String vehicleKey, + String country, + String region, + String countryFrom, + String countryTo, + String operation, + BigDecimal latitude, + BigDecimal longitude, + Long odometerKm, + String rawRecordPath +) { +} diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineBuilder.java b/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineBuilder.java index 329e18b..5ea6e12 100644 --- a/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineBuilder.java +++ b/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineBuilder.java @@ -1006,7 +1006,27 @@ public class DriverTimelineBuilder { (String) event.get("previousRegistrationKey"), (String) event.get("nextRegistrationKey"), (String) event.get("previousVehicleKey"), - (String) event.get("nextVehicleKey") + (String) event.get("nextVehicleKey"), + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "UNKNOWN", + null, + null )); } } diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineReusableProjectionBuilder.java b/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineReusableProjectionBuilder.java index 59de3fe..27847d3 100644 --- a/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineReusableProjectionBuilder.java +++ b/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineReusableProjectionBuilder.java @@ -10,13 +10,16 @@ import com.espertech.esper.runtime.client.EPDeployException; import com.espertech.esper.runtime.client.EPDeployment; import com.espertech.esper.runtime.client.EPRuntime; import com.espertech.esper.runtime.client.EPRuntimeProvider; +import at.procon.eventhub.config.EventHubProperties; import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession; +import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent; import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval; import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline; import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval; import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingDerivedProjectionBundle; import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent; +import at.procon.eventhub.tachographfilesession.model.TachographEsperGeoEvidenceEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleTripIntervalEvent; @@ -47,9 +50,14 @@ public class DriverTimelineReusableProjectionBuilder { loadResource("esper/tachograph-driving-derived-projection-bundle.epl"); private final DriverTimelineBuilder driverTimelineBuilder; + private final EventHubProperties properties; - public DriverTimelineReusableProjectionBuilder(DriverTimelineBuilder driverTimelineBuilder) { + public DriverTimelineReusableProjectionBuilder( + DriverTimelineBuilder driverTimelineBuilder, + EventHubProperties properties + ) { this.driverTimelineBuilder = driverTimelineBuilder; + this.properties = properties; } public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle( @@ -83,6 +91,7 @@ public class DriverTimelineReusableProjectionBuilder { driverKey, timeline.activityIntervals(), timeline.vehicleUsageIntervals(), + timeline.supportEvents(), significantDrivingMinutes, minimumRestPeriodMinutes ); @@ -93,6 +102,7 @@ public class DriverTimelineReusableProjectionBuilder { String driverKey, List activityIntervals, List vehicleUsageIntervals, + List supportEvents, int significantDrivingMinutes, int minimumRestPeriodMinutes ) { @@ -121,6 +131,10 @@ public class DriverTimelineReusableProjectionBuilder { "TachographVehicleUsageIntervalInputEvent", vehicleUsageIntervalInputDefinition() ); + configuration.getCommon().addEventType( + "TachographSupportGeoEvidenceInputEvent", + supportGeoEvidenceInputDefinition() + ); }, renderDrivingDerivedProjectionBundleEpl(significantDrivingMinutes, minimumRestPeriodMinutes), Map.of( @@ -135,6 +149,17 @@ public class DriverTimelineReusableProjectionBuilder { "potentialInVehicleTripIntervals", newData -> collectPotentialInVehicleTripIntervalEvents(newData, potentialInVehicleTripIntervals) ), runtime -> { + if (supportEvents != null) { + for (ExtractedSupportEvent supportEvent : supportEvents) { + Map supportGeoEvidence = toSupportGeoEvidenceInputMap(sessionId, supportEvent); + if (supportGeoEvidence != null) { + runtime.getEventService().sendEventMap( + supportGeoEvidence, + "TachographSupportGeoEvidenceInputEvent" + ); + } + } + } if (vehicleUsageIntervals != null) { for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) { runtime.getEventService().sendEventMap( @@ -260,6 +285,23 @@ public class DriverTimelineReusableProjectionBuilder { return definition; } + private Map supportGeoEvidenceInputDefinition() { + Map definition = new LinkedHashMap<>(); + definition.put("sessionId", UUID.class); + definition.put("driverKey", String.class); + definition.put("eventId", String.class); + definition.put("eventDomain", String.class); + definition.put("occurredAt", OffsetDateTime.class); + definition.put("occurredAtEpochSecond", long.class); + definition.put("registrationKey", String.class); + definition.put("vehicleKey", String.class); + definition.put("latitude", Double.class); + definition.put("longitude", Double.class); + definition.put("odometerKm", Long.class); + definition.put("priority", int.class); + return definition; + } + private Map toActivityIntervalInputMap( UUID sessionId, String driverKey, @@ -311,6 +353,50 @@ public class DriverTimelineReusableProjectionBuilder { return event; } + private Map toSupportGeoEvidenceInputMap( + UUID sessionId, + ExtractedSupportEvent supportEvent + ) { + if (supportEvent == null + || supportEvent.driverKey() == null + || supportEvent.occurredAt() == null + || supportEvent.latitude() == null + || supportEvent.longitude() == null) { + return null; + } + int priority = supportGeoPriority(supportEvent.eventDomain()); + if (priority <= 0) { + return null; + } + Map event = new LinkedHashMap<>(); + event.put("sessionId", sessionId); + event.put("driverKey", supportEvent.driverKey()); + event.put("eventId", supportEvent.eventId()); + event.put("eventDomain", supportEvent.eventDomain()); + event.put("occurredAt", supportEvent.occurredAt()); + event.put("occurredAtEpochSecond", supportEvent.occurredAt().toEpochSecond()); + event.put("registrationKey", supportEvent.registrationKey()); + event.put("vehicleKey", supportEvent.vehicleKey()); + event.put("latitude", supportEvent.latitude().doubleValue()); + event.put("longitude", supportEvent.longitude().doubleValue()); + event.put("odometerKm", supportEvent.odometerKm()); + event.put("priority", priority); + return event; + } + + private int supportGeoPriority(String eventDomain) { + if (eventDomain == null || eventDomain.isBlank()) { + return 0; + } + return switch (eventDomain.trim().toUpperCase()) { + case "POSITION" -> 500; + case "PLACE" -> 400; + case "BORDER_CROSSING" -> 300; + case "LOAD_UNLOAD" -> 250; + default -> 0; + }; + } + private String firstSourceIntervalId(ResolvedActivityInterval interval) { return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().get(0); } @@ -393,6 +479,24 @@ public class DriverTimelineReusableProjectionBuilder { for (EventBean event : newData) { long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond"); long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond"); + TachographEsperGeoEvidenceEvent beginGeoEvent = geoEvidenceEvent( + (String) event.get("beginGeoEventId"), + (String) event.get("beginGeoEventDomain"), + offsetDateTime(event.get("beginGeoOccurredAtEpochSecond")), + (Double) event.get("beginLatitude"), + (Double) event.get("beginLongitude"), + longOrNull(event.get("beginGeoDistanceSeconds")), + longOrNull(event.get("beginGeoOdometerKm")) + ); + TachographEsperGeoEvidenceEvent endGeoEvent = geoEvidenceEvent( + (String) event.get("endGeoEventId"), + (String) event.get("endGeoEventDomain"), + offsetDateTime(event.get("endGeoOccurredAtEpochSecond")), + (Double) event.get("endLatitude"), + (Double) event.get("endLongitude"), + longOrNull(event.get("endGeoDistanceSeconds")), + longOrNull(event.get("endGeoOdometerKm")) + ); target.add(new TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent( (UUID) event.get("sessionId"), (String) event.get("driverKey"), @@ -408,7 +512,27 @@ public class DriverTimelineReusableProjectionBuilder { (String) event.get("previousRegistrationKey"), (String) event.get("nextRegistrationKey"), (String) event.get("previousVehicleKey"), - (String) event.get("nextVehicleKey") + (String) event.get("nextVehicleKey"), + longOrNull(event.get("beginBoundaryOdometerKm")), + longOrNull(event.get("endBoundaryOdometerKm")), + (String) event.get("beginGeoEventId"), + (String) event.get("beginGeoEventDomain"), + offsetDateTime(event.get("beginGeoOccurredAtEpochSecond")), + (Double) event.get("beginLatitude"), + (Double) event.get("beginLongitude"), + longOrNull(event.get("beginGeoDistanceSeconds")), + longOrNull(event.get("beginGeoOdometerKm")), + (String) event.get("endGeoEventId"), + (String) event.get("endGeoEventDomain"), + offsetDateTime(event.get("endGeoOccurredAtEpochSecond")), + (Double) event.get("endLatitude"), + (Double) event.get("endLongitude"), + longOrNull(event.get("endGeoDistanceSeconds")), + longOrNull(event.get("endGeoOdometerKm")), + longOrNull(event.get("geoEvidenceMovementMeters")), + (String) event.get("geoEvidenceMovementCategory"), + beginGeoEvent, + endGeoEvent )); } } @@ -423,6 +547,24 @@ public class DriverTimelineReusableProjectionBuilder { for (EventBean event : newData) { long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond"); long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond"); + TachographEsperGeoEvidenceEvent beginGeoEvent = geoEvidenceEvent( + (String) event.get("beginGeoEventId"), + (String) event.get("beginGeoEventDomain"), + offsetDateTime(event.get("beginGeoOccurredAtEpochSecond")), + (Double) event.get("beginLatitude"), + (Double) event.get("beginLongitude"), + longOrNull(event.get("beginGeoDistanceSeconds")), + longOrNull(event.get("beginGeoOdometerKm")) + ); + TachographEsperGeoEvidenceEvent endGeoEvent = geoEvidenceEvent( + (String) event.get("endGeoEventId"), + (String) event.get("endGeoEventDomain"), + offsetDateTime(event.get("endGeoOccurredAtEpochSecond")), + (Double) event.get("endLatitude"), + (Double) event.get("endLongitude"), + longOrNull(event.get("endGeoDistanceSeconds")), + longOrNull(event.get("endGeoOdometerKm")) + ); target.add(new TachographEsperPotentialHomeOvernightStayIntervalEvent( (UUID) event.get("sessionId"), (String) event.get("driverKey"), @@ -438,7 +580,27 @@ public class DriverTimelineReusableProjectionBuilder { (String) event.get("previousRegistrationKey"), (String) event.get("nextRegistrationKey"), (String) event.get("previousVehicleKey"), - (String) event.get("nextVehicleKey") + (String) event.get("nextVehicleKey"), + longOrNull(event.get("beginBoundaryOdometerKm")), + longOrNull(event.get("endBoundaryOdometerKm")), + (String) event.get("beginGeoEventId"), + (String) event.get("beginGeoEventDomain"), + offsetDateTime(event.get("beginGeoOccurredAtEpochSecond")), + (Double) event.get("beginLatitude"), + (Double) event.get("beginLongitude"), + longOrNull(event.get("beginGeoDistanceSeconds")), + longOrNull(event.get("beginGeoOdometerKm")), + (String) event.get("endGeoEventId"), + (String) event.get("endGeoEventDomain"), + offsetDateTime(event.get("endGeoOccurredAtEpochSecond")), + (Double) event.get("endLatitude"), + (Double) event.get("endLongitude"), + longOrNull(event.get("endGeoDistanceSeconds")), + longOrNull(event.get("endGeoOdometerKm")), + longOrNull(event.get("geoEvidenceMovementMeters")), + (String) event.get("geoEvidenceMovementCategory"), + beginGeoEvent, + endGeoEvent )); } } @@ -453,6 +615,24 @@ public class DriverTimelineReusableProjectionBuilder { for (EventBean event : newData) { long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond"); long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond"); + TachographEsperGeoEvidenceEvent beginGeoEvent = geoEvidenceEvent( + (String) event.get("beginGeoEventId"), + (String) event.get("beginGeoEventDomain"), + offsetDateTime(event.get("beginGeoOccurredAtEpochSecond")), + (Double) event.get("beginLatitude"), + (Double) event.get("beginLongitude"), + longOrNull(event.get("beginGeoDistanceSeconds")), + longOrNull(event.get("beginGeoOdometerKm")) + ); + TachographEsperGeoEvidenceEvent endGeoEvent = geoEvidenceEvent( + (String) event.get("endGeoEventId"), + (String) event.get("endGeoEventDomain"), + offsetDateTime(event.get("endGeoOccurredAtEpochSecond")), + (Double) event.get("endLatitude"), + (Double) event.get("endLongitude"), + longOrNull(event.get("endGeoDistanceSeconds")), + longOrNull(event.get("endGeoOdometerKm")) + ); target.add(new TachographEsperPotentialInVehicleOvernightStayIntervalEvent( (UUID) event.get("sessionId"), (String) event.get("driverKey"), @@ -468,7 +648,27 @@ public class DriverTimelineReusableProjectionBuilder { (String) event.get("previousRegistrationKey"), (String) event.get("nextRegistrationKey"), (String) event.get("previousVehicleKey"), - (String) event.get("nextVehicleKey") + (String) event.get("nextVehicleKey"), + longOrNull(event.get("beginBoundaryOdometerKm")), + longOrNull(event.get("endBoundaryOdometerKm")), + (String) event.get("beginGeoEventId"), + (String) event.get("beginGeoEventDomain"), + offsetDateTime(event.get("beginGeoOccurredAtEpochSecond")), + (Double) event.get("beginLatitude"), + (Double) event.get("beginLongitude"), + longOrNull(event.get("beginGeoDistanceSeconds")), + longOrNull(event.get("beginGeoOdometerKm")), + (String) event.get("endGeoEventId"), + (String) event.get("endGeoEventDomain"), + offsetDateTime(event.get("endGeoOccurredAtEpochSecond")), + (Double) event.get("endLatitude"), + (Double) event.get("endLongitude"), + longOrNull(event.get("endGeoDistanceSeconds")), + longOrNull(event.get("endGeoOdometerKm")), + longOrNull(event.get("geoEvidenceMovementMeters")), + (String) event.get("geoEvidenceMovementCategory"), + beginGeoEvent, + endGeoEvent )); } } @@ -567,9 +767,68 @@ public class DriverTimelineReusableProjectionBuilder { .replace( "${MINIMUM_REST_PERIOD_THRESHOLD_SECONDS}", Long.toString(Math.max(1, minimumRestPeriodMinutes) * 60L) + ) + .replace( + "${REST_GEO_LOOKBACK_SECONDS}", + Long.toString(Math.max(1, properties.getTachographFileSession().getProcessing().getRestCandidateGeoLookbackMinutes()) * 60L) + ) + .replace( + "${REST_GEO_LOOKAHEAD_SECONDS}", + Long.toString(Math.max(1, properties.getTachographFileSession().getProcessing().getRestCandidateGeoLookaheadMinutes()) * 60L) + ) + .replace( + "${REST_GEO_STATIONARY_MAX_METERS}", + Integer.toString(Math.max(0, properties.getTachographFileSession().getProcessing().getRestCandidateGeoStationaryMaxMeters())) + ) + .replace( + "${REST_GEO_MINOR_MOVEMENT_MAX_METERS}", + Integer.toString(Math.max( + properties.getTachographFileSession().getProcessing().getRestCandidateGeoStationaryMaxMeters(), + properties.getTachographFileSession().getProcessing().getRestCandidateGeoMinorMovementMaxMeters() + )) ); } + private OffsetDateTime offsetDateTime(Object epochSecond) { + if (!(epochSecond instanceof Long value)) { + return null; + } + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(value), ZoneOffset.UTC); + } + + private Long longOrNull(Object value) { + return value instanceof Long number ? number : null; + } + + private TachographEsperGeoEvidenceEvent geoEvidenceEvent( + String eventId, + String eventDomain, + OffsetDateTime occurredAt, + Double latitude, + Double longitude, + Long distanceSeconds, + Long odometerKm + ) { + if (eventId == null + && eventDomain == null + && occurredAt == null + && latitude == null + && longitude == null + && distanceSeconds == null + && odometerKm == null) { + return null; + } + return new TachographEsperGeoEvidenceEvent( + eventId, + eventDomain, + occurredAt, + latitude, + longitude, + distanceSeconds, + odometerKm + ); + } + private static String loadResource(String path) { try { ClassPathResource resource = new ClassPathResource(path); diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/service/TachographFileSessionProcessingService.java b/src/main/java/at/procon/eventhub/tachographfilesession/service/TachographFileSessionProcessingService.java index 456788d..0ed097a 100644 --- a/src/main/java/at/procon/eventhub/tachographfilesession/service/TachographFileSessionProcessingService.java +++ b/src/main/java/at/procon/eventhub/tachographfilesession/service/TachographFileSessionProcessingService.java @@ -6,6 +6,7 @@ import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcess import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingRequest; import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingResultDto; import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession; +import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent; import at.procon.eventhub.tachographfilesession.model.PeriodizedDriverActivityInterval; import at.procon.eventhub.tachographfilesession.model.ProcessedDrivingInterruption; import at.procon.eventhub.tachographfilesession.model.ProcessedOperatingPeriod; @@ -17,9 +18,11 @@ import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeekly import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingDerivedProjectionBundle; import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographFileSession; +import at.procon.eventhub.tachographfilesession.model.TachographEsperGeoEvidenceEvent; 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.TachographEsperSupportGeoEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent; import java.time.Duration; @@ -256,6 +259,12 @@ public class TachographFileSessionProcessingService { requestedFrom, requestedTo ); + List supportGeoEvents = clipEsperSupportGeoEvents( + timeline.supportEvents(), + driverKey, + requestedFrom, + requestedTo + ); return new TachographEsperDriverProcessingResultDto( sessionId, @@ -277,8 +286,9 @@ public class TachographFileSessionProcessingService { potentialInVehicleTripIntervals.size(), vehicleUsageIntervals.size(), vuCardAbsentIntervals.size(), - List.of()/*activityIntervals*/, - List.of()/*drivingIntervals*/, + supportGeoEvents.size(), + activityIntervals, + drivingIntervals, drivingInterruptionIntervals, drivingInterruptionVehicleChangeIntervals, dailyWeeklyRestCandidateIntervals, @@ -289,6 +299,7 @@ public class TachographFileSessionProcessingService { potentialInVehicleTripIntervals, vehicleUsageIntervals, vuCardAbsentIntervals, + supportGeoEvents, esperProjectionNotes() ); } @@ -388,6 +399,45 @@ public class TachographFileSessionProcessingService { .toList(); } + private List clipEsperSupportGeoEvents( + List supportEvents, + String driverKey, + OffsetDateTime requestedFrom, + OffsetDateTime requestedTo + ) { + if (supportEvents == null || supportEvents.isEmpty() || requestedFrom == null || requestedTo == null) { + return List.of(); + } + return supportEvents.stream() + .filter(event -> Objects.equals(driverKey, event.driverKey())) + .filter(event -> event.occurredAt() != null) + .filter(event -> event.latitude() != null && event.longitude() != null) + .filter(event -> !event.occurredAt().isBefore(requestedFrom) && !event.occurredAt().isAfter(requestedTo)) + .map(event -> new TachographEsperSupportGeoEvent( + event.eventId(), + event.driverKey(), + event.occurredAt(), + event.eventDomain(), + event.eventType(), + event.eventLifecycle(), + event.registrationKey(), + event.vehicleKey(), + event.country(), + event.region(), + event.countryFrom(), + event.countryTo(), + event.operation(), + event.latitude(), + event.longitude(), + event.odometerKm(), + event.rawRecordPath() + )) + .sorted(Comparator.comparing(TachographEsperSupportGeoEvent::occurredAt) + .thenComparing(TachographEsperSupportGeoEvent::eventDomain, Comparator.nullsLast(String::compareTo)) + .thenComparing(TachographEsperSupportGeoEvent::eventId, Comparator.nullsLast(String::compareTo))) + .toList(); + } + private List clipEsperDrivingInterruptionIntervalEvents( List intervals, OffsetDateTime requestedFrom, @@ -495,6 +545,8 @@ public class TachographFileSessionProcessingService { double unknownCoveragePercent = durationSeconds == 0L ? 0.0d : (unknownDurationSeconds * 100.0d) / durationSeconds; + boolean beginBoundaryChanged = !start.equals(interval.startedAt()); + boolean endBoundaryChanged = !end.equals(interval.endedAt()); return new TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent( interval.sessionId(), interval.driverKey(), @@ -510,7 +562,25 @@ public class TachographFileSessionProcessingService { interval.previousRegistrationKey(), interval.nextRegistrationKey(), interval.previousVehicleKey(), - interval.nextVehicleKey() + interval.nextVehicleKey(), + beginBoundaryChanged ? null : interval.beginGeoEventId(), + beginBoundaryChanged ? null : interval.beginGeoEventDomain(), + beginBoundaryChanged ? null : interval.beginGeoOccurredAt(), + beginBoundaryChanged ? null : interval.beginLatitude(), + beginBoundaryChanged ? null : interval.beginLongitude(), + beginBoundaryChanged ? null : interval.beginGeoDistanceSeconds(), + beginBoundaryChanged ? null : interval.beginGeoOdometerKm(), + endBoundaryChanged ? null : interval.endGeoEventId(), + endBoundaryChanged ? null : interval.endGeoEventDomain(), + endBoundaryChanged ? null : interval.endGeoOccurredAt(), + endBoundaryChanged ? null : interval.endLatitude(), + endBoundaryChanged ? null : interval.endLongitude(), + endBoundaryChanged ? null : interval.endGeoDistanceSeconds(), + endBoundaryChanged ? null : interval.endGeoOdometerKm(), + beginBoundaryChanged || endBoundaryChanged ? null : interval.geoEvidenceMovementMeters(), + beginBoundaryChanged || endBoundaryChanged ? "UNKNOWN" : interval.geoEvidenceMovementCategory(), + beginBoundaryChanged ? null : geoEvidenceEvent(interval.beginGeoEventId(), interval.beginGeoEventDomain(), interval.beginGeoOccurredAt(), interval.beginLatitude(), interval.beginLongitude(), interval.beginGeoDistanceSeconds(), interval.beginGeoOdometerKm()), + endBoundaryChanged ? null : geoEvidenceEvent(interval.endGeoEventId(), interval.endGeoEventDomain(), interval.endGeoOccurredAt(), interval.endLatitude(), interval.endLongitude(), interval.endGeoDistanceSeconds(), interval.endGeoOdometerKm()) ); }) .filter(Objects::nonNull) @@ -556,6 +626,8 @@ public class TachographFileSessionProcessingService { double unknownCoveragePercent = durationSeconds == 0L ? 0.0d : (unknownDurationSeconds * 100.0d) / durationSeconds; + boolean beginBoundaryChanged = !start.equals(interval.startedAt()); + boolean endBoundaryChanged = !end.equals(interval.endedAt()); return new TachographEsperPotentialHomeOvernightStayIntervalEvent( interval.sessionId(), interval.driverKey(), @@ -571,7 +643,25 @@ public class TachographFileSessionProcessingService { interval.previousRegistrationKey(), interval.nextRegistrationKey(), interval.previousVehicleKey(), - interval.nextVehicleKey() + interval.nextVehicleKey(), + beginBoundaryChanged ? null : interval.beginGeoEventId(), + beginBoundaryChanged ? null : interval.beginGeoEventDomain(), + beginBoundaryChanged ? null : interval.beginGeoOccurredAt(), + beginBoundaryChanged ? null : interval.beginLatitude(), + beginBoundaryChanged ? null : interval.beginLongitude(), + beginBoundaryChanged ? null : interval.beginGeoDistanceSeconds(), + beginBoundaryChanged ? null : interval.beginGeoOdometerKm(), + endBoundaryChanged ? null : interval.endGeoEventId(), + endBoundaryChanged ? null : interval.endGeoEventDomain(), + endBoundaryChanged ? null : interval.endGeoOccurredAt(), + endBoundaryChanged ? null : interval.endLatitude(), + endBoundaryChanged ? null : interval.endLongitude(), + endBoundaryChanged ? null : interval.endGeoDistanceSeconds(), + endBoundaryChanged ? null : interval.endGeoOdometerKm(), + beginBoundaryChanged || endBoundaryChanged ? null : interval.geoEvidenceMovementMeters(), + beginBoundaryChanged || endBoundaryChanged ? "UNKNOWN" : interval.geoEvidenceMovementCategory(), + beginBoundaryChanged ? null : geoEvidenceEvent(interval.beginGeoEventId(), interval.beginGeoEventDomain(), interval.beginGeoOccurredAt(), interval.beginLatitude(), interval.beginLongitude(), interval.beginGeoDistanceSeconds(), interval.beginGeoOdometerKm()), + endBoundaryChanged ? null : geoEvidenceEvent(interval.endGeoEventId(), interval.endGeoEventDomain(), interval.endGeoOccurredAt(), interval.endLatitude(), interval.endLongitude(), interval.endGeoDistanceSeconds(), interval.endGeoOdometerKm()) ); }) .filter(Objects::nonNull) @@ -617,6 +707,8 @@ public class TachographFileSessionProcessingService { double unknownCoveragePercent = durationSeconds == 0L ? 0.0d : (unknownDurationSeconds * 100.0d) / durationSeconds; + boolean beginBoundaryChanged = !start.equals(interval.startedAt()); + boolean endBoundaryChanged = !end.equals(interval.endedAt()); return new TachographEsperPotentialInVehicleOvernightStayIntervalEvent( interval.sessionId(), interval.driverKey(), @@ -632,7 +724,25 @@ public class TachographFileSessionProcessingService { interval.previousRegistrationKey(), interval.nextRegistrationKey(), interval.previousVehicleKey(), - interval.nextVehicleKey() + interval.nextVehicleKey(), + beginBoundaryChanged ? null : interval.beginGeoEventId(), + beginBoundaryChanged ? null : interval.beginGeoEventDomain(), + beginBoundaryChanged ? null : interval.beginGeoOccurredAt(), + beginBoundaryChanged ? null : interval.beginLatitude(), + beginBoundaryChanged ? null : interval.beginLongitude(), + beginBoundaryChanged ? null : interval.beginGeoDistanceSeconds(), + beginBoundaryChanged ? null : interval.beginGeoOdometerKm(), + endBoundaryChanged ? null : interval.endGeoEventId(), + endBoundaryChanged ? null : interval.endGeoEventDomain(), + endBoundaryChanged ? null : interval.endGeoOccurredAt(), + endBoundaryChanged ? null : interval.endLatitude(), + endBoundaryChanged ? null : interval.endLongitude(), + endBoundaryChanged ? null : interval.endGeoDistanceSeconds(), + endBoundaryChanged ? null : interval.endGeoOdometerKm(), + beginBoundaryChanged || endBoundaryChanged ? null : interval.geoEvidenceMovementMeters(), + beginBoundaryChanged || endBoundaryChanged ? "UNKNOWN" : interval.geoEvidenceMovementCategory(), + beginBoundaryChanged ? null : geoEvidenceEvent(interval.beginGeoEventId(), interval.beginGeoEventDomain(), interval.beginGeoOccurredAt(), interval.beginLatitude(), interval.beginLongitude(), interval.beginGeoDistanceSeconds(), interval.beginGeoOdometerKm()), + endBoundaryChanged ? null : geoEvidenceEvent(interval.endGeoEventId(), interval.endGeoEventDomain(), interval.endGeoOccurredAt(), interval.endLatitude(), interval.endLongitude(), interval.endGeoDistanceSeconds(), interval.endGeoOdometerKm()) ); }) .filter(Objects::nonNull) @@ -1300,6 +1410,9 @@ public class TachographFileSessionProcessingService { "Driving interruption vehicle-change intervals are daily/weekly rest candidates where previousRegistrationKey differs from nextRegistrationKey.", "Daily/weekly rest candidate intervals are driving interruption intervals longer than the configured minimum rest-period threshold.", "Daily/weekly rest candidate coverage intervals enrich each rest candidate with card-present and unknown-coverage metrics computed from vehicle-usage and VU card-absent overlap.", + "Daily/weekly rest candidate coverage intervals also attach begin/end geo evidence from nearby support events for the same driver and boundary-side vehicle identity.", + "Boundary geo evidence prefers the nearest matching POSITION event, then PLACE, BORDER_CROSSING, and LOAD_UNLOAD within the configured lookback/lookahead windows.", + "If both begin and end geo evidence carry odometer values, geoEvidenceMovementCategory classifies the interval as STATIONARY, MINOR, MOVED, or UNKNOWN.", "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.", @@ -1367,6 +1480,35 @@ public class TachographFileSessionProcessingService { return value == null ? null : value.withOffsetSameInstant(java.time.ZoneOffset.UTC); } + private TachographEsperGeoEvidenceEvent geoEvidenceEvent( + String eventId, + String eventDomain, + OffsetDateTime occurredAt, + Double latitude, + Double longitude, + Long distanceSeconds, + Long odometerKm + ) { + if (eventId == null + && eventDomain == null + && occurredAt == null + && latitude == null + && longitude == null + && distanceSeconds == null + && odometerKm == null) { + return null; + } + return new TachographEsperGeoEvidenceEvent( + eventId, + eventDomain, + occurredAt, + latitude, + longitude, + distanceSeconds, + odometerKm + ); + } + private OffsetDateTime max(OffsetDateTime left, OffsetDateTime right) { if (left == null) { return right; diff --git a/src/main/resources/esper/tachograph-driving-derived-projection-bundle.epl b/src/main/resources/esper/tachograph-driving-derived-projection-bundle.epl index 6886f35..0f1b786 100644 --- a/src/main/resources/esper/tachograph-driving-derived-projection-bundle.epl +++ b/src/main/resources/esper/tachograph-driving-derived-projection-bundle.epl @@ -112,7 +112,169 @@ create schema DailyWeeklyRestCandidateCoverageInterval( previousRegistrationKey string, nextRegistrationKey string, previousVehicleKey string, - nextVehicleKey string + nextVehicleKey string, + beginBoundaryOdometerKm java.lang.Long, + endBoundaryOdometerKm java.lang.Long, + beginGeoEventId string, + beginGeoEventDomain string, + beginGeoOccurredAtEpochSecond java.lang.Long, + beginLatitude java.lang.Double, + beginLongitude java.lang.Double, + beginGeoDistanceSeconds java.lang.Long, + beginGeoOdometerKm java.lang.Long, + endGeoEventId string, + endGeoEventDomain string, + endGeoOccurredAtEpochSecond java.lang.Long, + endLatitude java.lang.Double, + endLongitude java.lang.Double, + endGeoDistanceSeconds java.lang.Long, + endGeoOdometerKm java.lang.Long, + geoEvidenceMovementMeters java.lang.Long, + geoEvidenceMovementCategory string +); + +create schema SupportGeoEvidence( + sessionId java.util.UUID, + driverKey string, + eventId string, + eventDomain string, + occurredAtEpochSecond long, + registrationKey string, + vehicleKey string, + latitude java.lang.Double, + longitude java.lang.Double, + odometerKm java.lang.Long, + priority int +); + +create schema DailyWeeklyRestCandidateBeginGeoEvidenceCandidate( + sessionId java.util.UUID, + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + boundaryOdometerKm java.lang.Long, + eventId string, + eventDomain string, + occurredAtEpochSecond long, + latitude java.lang.Double, + longitude java.lang.Double, + odometerKm java.lang.Long, + odometerDeltaKm java.lang.Long, + distanceSeconds long, + rankScore long +); + +create schema DailyWeeklyRestCandidateBeginGeoEvidenceBestScore( + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + bestRankScore long +); + +create schema DailyWeeklyRestCandidateBeginGeoEvidenceResolved( + sessionId java.util.UUID, + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + eventId string, + eventDomain string, + occurredAtEpochSecond long, + latitude java.lang.Double, + longitude java.lang.Double, + odometerKm java.lang.Long, + distanceSeconds long +); + +create schema DailyWeeklyRestCandidateEndGeoEvidenceCandidate( + sessionId java.util.UUID, + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + boundaryOdometerKm java.lang.Long, + eventId string, + eventDomain string, + occurredAtEpochSecond long, + latitude java.lang.Double, + longitude java.lang.Double, + odometerKm java.lang.Long, + odometerDeltaKm java.lang.Long, + distanceSeconds long, + rankScore long +); + +create schema DailyWeeklyRestCandidateBeginBoundaryOdometerCandidate( + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + odometerKm java.lang.Long, + distanceSeconds long, + rankScore long +); + +create schema DailyWeeklyRestCandidateBeginBoundaryOdometerBestScore( + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + bestRankScore long +); + +create schema DailyWeeklyRestCandidateBeginBoundaryOdometerResolved( + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + odometerKm java.lang.Long, + distanceSeconds long +); + +create schema DailyWeeklyRestCandidateEndBoundaryOdometerCandidate( + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + odometerKm java.lang.Long, + distanceSeconds long, + rankScore long +); + +create schema DailyWeeklyRestCandidateEndBoundaryOdometerBestScore( + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + bestRankScore long +); + +create schema DailyWeeklyRestCandidateEndBoundaryOdometerResolved( + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + odometerKm java.lang.Long, + distanceSeconds long +); + +create schema DailyWeeklyRestCandidateEndGeoEvidenceBestScore( + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + bestRankScore long +); + +create schema DailyWeeklyRestCandidateEndGeoEvidenceResolved( + sessionId java.util.UUID, + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long, + eventId string, + eventDomain string, + occurredAtEpochSecond long, + latitude java.lang.Double, + longitude java.lang.Double, + odometerKm java.lang.Long, + distanceSeconds long +); + +create schema DailyWeeklyRestCandidateCoverageFinalizationRequest( + driverKey string, + startedAtEpochSecond long, + endedAtEpochSecond long ); create context PerDriver partition by driverKey from TachographVehicleUsageIntervalInputEvent; @@ -146,7 +308,25 @@ create schema PotentialHomeOvernightStayInterval( previousRegistrationKey string, nextRegistrationKey string, previousVehicleKey string, - nextVehicleKey string + nextVehicleKey string, + beginBoundaryOdometerKm java.lang.Long, + endBoundaryOdometerKm java.lang.Long, + beginGeoEventId string, + beginGeoEventDomain string, + beginGeoOccurredAtEpochSecond java.lang.Long, + beginLatitude java.lang.Double, + beginLongitude java.lang.Double, + beginGeoDistanceSeconds java.lang.Long, + beginGeoOdometerKm java.lang.Long, + endGeoEventId string, + endGeoEventDomain string, + endGeoOccurredAtEpochSecond java.lang.Long, + endLatitude java.lang.Double, + endLongitude java.lang.Double, + endGeoDistanceSeconds java.lang.Long, + endGeoOdometerKm java.lang.Long, + geoEvidenceMovementMeters java.lang.Long, + geoEvidenceMovementCategory string ); create schema PotentialInVehicleOvernightStayInterval( @@ -164,7 +344,25 @@ create schema PotentialInVehicleOvernightStayInterval( previousRegistrationKey string, nextRegistrationKey string, previousVehicleKey string, - nextVehicleKey string + nextVehicleKey string, + beginBoundaryOdometerKm java.lang.Long, + endBoundaryOdometerKm java.lang.Long, + beginGeoEventId string, + beginGeoEventDomain string, + beginGeoOccurredAtEpochSecond java.lang.Long, + beginLatitude java.lang.Double, + beginLongitude java.lang.Double, + beginGeoDistanceSeconds java.lang.Long, + beginGeoOdometerKm java.lang.Long, + endGeoEventId string, + endGeoEventDomain string, + endGeoOccurredAtEpochSecond java.lang.Long, + endLatitude java.lang.Double, + endLongitude java.lang.Double, + endGeoDistanceSeconds java.lang.Long, + endGeoOdometerKm java.lang.Long, + geoEvidenceMovementMeters java.lang.Long, + geoEvidenceMovementCategory string ); create schema UnclassifiedDailyWeeklyRestCandidateCoverageInterval( @@ -182,7 +380,25 @@ create schema UnclassifiedDailyWeeklyRestCandidateCoverageInterval( previousRegistrationKey string, nextRegistrationKey string, previousVehicleKey string, - nextVehicleKey string + nextVehicleKey string, + beginBoundaryOdometerKm java.lang.Long, + endBoundaryOdometerKm java.lang.Long, + beginGeoEventId string, + beginGeoEventDomain string, + beginGeoOccurredAtEpochSecond java.lang.Long, + beginLatitude java.lang.Double, + beginLongitude java.lang.Double, + beginGeoDistanceSeconds java.lang.Long, + beginGeoOdometerKm java.lang.Long, + endGeoEventId string, + endGeoEventDomain string, + endGeoOccurredAtEpochSecond java.lang.Long, + endLatitude java.lang.Double, + endLongitude java.lang.Double, + endGeoDistanceSeconds java.lang.Long, + endGeoOdometerKm java.lang.Long, + geoEvidenceMovementMeters java.lang.Long, + geoEvidenceMovementCategory string ); create schema PotentialInVehicleTripState( @@ -223,6 +439,38 @@ create window PreviousRestCandidateCoverageInterval#unique(driverKey) as DailyWe create window OpenPotentialInVehicleTripState#unique(driverKey) as PotentialInVehicleTripState; +create window SupportGeoEvidenceWindow#keepall as SupportGeoEvidence; + +create window DailyWeeklyRestCandidateBeginGeoEvidenceCandidateWindow#keepall as DailyWeeklyRestCandidateBeginGeoEvidenceCandidate; +create window DailyWeeklyRestCandidateBeginGeoEvidenceBestScoreWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateBeginGeoEvidenceBestScore; +create window DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateBeginGeoEvidenceResolved; + +create window DailyWeeklyRestCandidateEndGeoEvidenceCandidateWindow#keepall as DailyWeeklyRestCandidateEndGeoEvidenceCandidate; +create window DailyWeeklyRestCandidateEndGeoEvidenceBestScoreWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateEndGeoEvidenceBestScore; +create window DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateEndGeoEvidenceResolved; +create window DailyWeeklyRestCandidateBeginBoundaryOdometerCandidateWindow#keepall as DailyWeeklyRestCandidateBeginBoundaryOdometerCandidate; +create window DailyWeeklyRestCandidateBeginBoundaryOdometerBestScoreWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateBeginBoundaryOdometerBestScore; +create window DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateBeginBoundaryOdometerResolved; +create window DailyWeeklyRestCandidateEndBoundaryOdometerCandidateWindow#keepall as DailyWeeklyRestCandidateEndBoundaryOdometerCandidate; +create window DailyWeeklyRestCandidateEndBoundaryOdometerBestScoreWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateEndBoundaryOdometerBestScore; +create window DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateEndBoundaryOdometerResolved; +create window DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateCoverageCardResolvedInterval; + +insert into SupportGeoEvidenceWindow +select + sessionId, + driverKey, + eventId, + eventDomain, + occurredAtEpochSecond, + registrationKey, + vehicleKey, + latitude, + longitude, + odometerKm, + priority +from TachographSupportGeoEvidenceInputEvent; + insert into SignificantDrivingInterval select sessionId, @@ -416,24 +664,860 @@ where not exists ( and (v.endedAtEpochSecond is null or v.endedAtEpochSecond > c.startedAtEpochSecond) ); -insert into DailyWeeklyRestCandidateCoverageInterval +insert into DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow +select * +from DailyWeeklyRestCandidateCoverageCardResolvedInterval; + +@Priority(50) +insert into DailyWeeklyRestCandidateBeginBoundaryOdometerCandidateWindow +select + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond, + v.odometerBeginKm as odometerKm, + case + when v.startedAtEpochSecond >= c.startedAtEpochSecond + then v.startedAtEpochSecond - c.startedAtEpochSecond + else c.startedAtEpochSecond - v.startedAtEpochSecond + end as distanceSeconds, + ( + ( + case + when v.startedAtEpochSecond >= c.startedAtEpochSecond + then v.startedAtEpochSecond - c.startedAtEpochSecond + else c.startedAtEpochSecond - v.startedAtEpochSecond + end + ) * 10000L + ) + ( + case + when v.startedAtEpochSecond >= c.startedAtEpochSecond + then 0L + else 1000L + end + ) as rankScore +from DailyWeeklyRestCandidateCoverageCardResolvedInterval as c unidirectional, + TachographVehicleUsageIntervalInputEvent#keepall as v +where v.driverKey = c.driverKey + and v.odometerBeginKm is not null + and v.startedAtEpochSecond >= c.startedAtEpochSecond - ${REST_GEO_LOOKBACK_SECONDS} + and v.startedAtEpochSecond <= c.startedAtEpochSecond + ${REST_GEO_LOOKAHEAD_SECONDS} + and ( + ( + c.previousVehicleKey is not null + and v.vehicleKey is not null + and c.previousVehicleKey = v.vehicleKey + ) + or ( + (c.previousVehicleKey is null or v.vehicleKey is null) + and c.previousRegistrationKey is not null + and v.registrationKey is not null + and c.previousRegistrationKey = v.registrationKey + ) + ); + +@Priority(50) +insert into DailyWeeklyRestCandidateBeginBoundaryOdometerCandidateWindow +select + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond, + v.odometerEndKm as odometerKm, + case + when v.endedAtEpochSecond >= c.startedAtEpochSecond + then v.endedAtEpochSecond - c.startedAtEpochSecond + else c.startedAtEpochSecond - v.endedAtEpochSecond + end as distanceSeconds, + ( + ( + case + when v.endedAtEpochSecond >= c.startedAtEpochSecond + then v.endedAtEpochSecond - c.startedAtEpochSecond + else c.startedAtEpochSecond - v.endedAtEpochSecond + end + ) * 10000L + ) + ( + case + when v.endedAtEpochSecond >= c.startedAtEpochSecond + then 0L + else 1000L + end + ) as rankScore +from DailyWeeklyRestCandidateCoverageCardResolvedInterval as c unidirectional, + TachographVehicleUsageIntervalInputEvent#keepall as v +where v.driverKey = c.driverKey + and v.endedAtEpochSecond is not null + and v.odometerEndKm is not null + and v.endedAtEpochSecond >= c.startedAtEpochSecond - ${REST_GEO_LOOKBACK_SECONDS} + and v.endedAtEpochSecond <= c.startedAtEpochSecond + ${REST_GEO_LOOKAHEAD_SECONDS} + and ( + ( + c.previousVehicleKey is not null + and v.vehicleKey is not null + and c.previousVehicleKey = v.vehicleKey + ) + or ( + (c.previousVehicleKey is null or v.vehicleKey is null) + and c.previousRegistrationKey is not null + and v.registrationKey is not null + and c.previousRegistrationKey = v.registrationKey + ) + ); + +@Priority(45) +insert into DailyWeeklyRestCandidateBeginBoundaryOdometerBestScoreWindow select - sessionId, driverKey, startedAtEpochSecond, endedAtEpochSecond, - durationSeconds, - cardPresentDurationSeconds, - (cardPresentDurationSeconds * 100.0d) / durationSeconds as cardPresentCoveragePercent, - unknownDurationSeconds, - (unknownDurationSeconds * 100.0d) / durationSeconds as unknownCoveragePercent, - previousDrivingSourceIntervalId, - nextDrivingSourceIntervalId, - previousRegistrationKey, - nextRegistrationKey, - previousVehicleKey, - nextVehicleKey -from DailyWeeklyRestCandidateCoverageCardResolvedInterval; + min(rankScore) as bestRankScore +from DailyWeeklyRestCandidateBeginBoundaryOdometerCandidateWindow +group by + driverKey, + startedAtEpochSecond, + endedAtEpochSecond; + +@Priority(40) +insert into DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow +select + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond, + c.odometerKm as odometerKm, + c.distanceSeconds as distanceSeconds +from DailyWeeklyRestCandidateBeginBoundaryOdometerCandidateWindow as c, + DailyWeeklyRestCandidateBeginBoundaryOdometerBestScoreWindow as best +where c.driverKey = best.driverKey + and c.startedAtEpochSecond = best.startedAtEpochSecond + and c.endedAtEpochSecond = best.endedAtEpochSecond + and c.rankScore = best.bestRankScore; + +@Priority(50) +insert into DailyWeeklyRestCandidateEndBoundaryOdometerCandidateWindow +select + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond, + v.odometerBeginKm as odometerKm, + case + when v.startedAtEpochSecond >= c.endedAtEpochSecond + then v.startedAtEpochSecond - c.endedAtEpochSecond + else c.endedAtEpochSecond - v.startedAtEpochSecond + end as distanceSeconds, + ( + ( + case + when v.startedAtEpochSecond >= c.endedAtEpochSecond + then v.startedAtEpochSecond - c.endedAtEpochSecond + else c.endedAtEpochSecond - v.startedAtEpochSecond + end + ) * 10000L + ) + ( + case + when v.startedAtEpochSecond <= c.endedAtEpochSecond + then 0L + else 1000L + end + ) as rankScore +from DailyWeeklyRestCandidateCoverageCardResolvedInterval as c unidirectional, + TachographVehicleUsageIntervalInputEvent#keepall as v +where v.driverKey = c.driverKey + and v.odometerBeginKm is not null + and v.startedAtEpochSecond >= c.endedAtEpochSecond - ${REST_GEO_LOOKBACK_SECONDS} + and v.startedAtEpochSecond <= c.endedAtEpochSecond + ${REST_GEO_LOOKAHEAD_SECONDS} + and ( + ( + c.nextVehicleKey is not null + and v.vehicleKey is not null + and c.nextVehicleKey = v.vehicleKey + ) + or ( + (c.nextVehicleKey is null or v.vehicleKey is null) + and c.nextRegistrationKey is not null + and v.registrationKey is not null + and c.nextRegistrationKey = v.registrationKey + ) + ); + +@Priority(50) +insert into DailyWeeklyRestCandidateEndBoundaryOdometerCandidateWindow +select + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond, + v.odometerEndKm as odometerKm, + case + when v.endedAtEpochSecond >= c.endedAtEpochSecond + then v.endedAtEpochSecond - c.endedAtEpochSecond + else c.endedAtEpochSecond - v.endedAtEpochSecond + end as distanceSeconds, + ( + ( + case + when v.endedAtEpochSecond >= c.endedAtEpochSecond + then v.endedAtEpochSecond - c.endedAtEpochSecond + else c.endedAtEpochSecond - v.endedAtEpochSecond + end + ) * 10000L + ) + ( + case + when v.endedAtEpochSecond <= c.endedAtEpochSecond + then 0L + else 1000L + end + ) as rankScore +from DailyWeeklyRestCandidateCoverageCardResolvedInterval as c unidirectional, + TachographVehicleUsageIntervalInputEvent#keepall as v +where v.driverKey = c.driverKey + and v.endedAtEpochSecond is not null + and v.odometerEndKm is not null + and v.endedAtEpochSecond >= c.endedAtEpochSecond - ${REST_GEO_LOOKBACK_SECONDS} + and v.endedAtEpochSecond <= c.endedAtEpochSecond + ${REST_GEO_LOOKAHEAD_SECONDS} + and ( + ( + c.nextVehicleKey is not null + and v.vehicleKey is not null + and c.nextVehicleKey = v.vehicleKey + ) + or ( + (c.nextVehicleKey is null or v.vehicleKey is null) + and c.nextRegistrationKey is not null + and v.registrationKey is not null + and c.nextRegistrationKey = v.registrationKey + ) + ); + +@Priority(45) +insert into DailyWeeklyRestCandidateEndBoundaryOdometerBestScoreWindow +select + driverKey, + startedAtEpochSecond, + endedAtEpochSecond, + min(rankScore) as bestRankScore +from DailyWeeklyRestCandidateEndBoundaryOdometerCandidateWindow +group by + driverKey, + startedAtEpochSecond, + endedAtEpochSecond; + +@Priority(40) +insert into DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow +select + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond, + c.odometerKm as odometerKm, + c.distanceSeconds as distanceSeconds +from DailyWeeklyRestCandidateEndBoundaryOdometerCandidateWindow as c, + DailyWeeklyRestCandidateEndBoundaryOdometerBestScoreWindow as best +where c.driverKey = best.driverKey + and c.startedAtEpochSecond = best.startedAtEpochSecond + and c.endedAtEpochSecond = best.endedAtEpochSecond + and c.rankScore = best.bestRankScore; + +@Priority(40) +insert into DailyWeeklyRestCandidateBeginGeoEvidenceCandidateWindow +select + c.sessionId as sessionId, + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond, + (select odometerKm from DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond) as boundaryOdometerKm, + g.eventId as eventId, + g.eventDomain as eventDomain, + g.occurredAtEpochSecond as occurredAtEpochSecond, + g.latitude as latitude, + g.longitude as longitude, + g.odometerKm as odometerKm, + case + when ( + select odometerKm from DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) is null + or g.odometerKm is null + then null + when g.odometerKm >= ( + select odometerKm from DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) + then g.odometerKm - ( + select odometerKm from DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) + else ( + select odometerKm from DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) - g.odometerKm + end as odometerDeltaKm, + case + when g.occurredAtEpochSecond >= c.startedAtEpochSecond + then g.occurredAtEpochSecond - c.startedAtEpochSecond + else c.startedAtEpochSecond - g.occurredAtEpochSecond + end as distanceSeconds, + ( + ( + case + when g.occurredAtEpochSecond >= c.startedAtEpochSecond + then g.occurredAtEpochSecond - c.startedAtEpochSecond + else c.startedAtEpochSecond - g.occurredAtEpochSecond + end + ) * 10000L + ) + ( + case + when ( + select odometerKm from DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) is not null + and g.odometerKm is not null + then ( + case + when g.odometerKm >= ( + select odometerKm from DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) + then g.odometerKm - ( + select odometerKm from DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) + else ( + select odometerKm from DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) - g.odometerKm + end + ) * 10L + else 9990L + end + ) + ( + case + when g.occurredAtEpochSecond >= c.startedAtEpochSecond + then 0L + else 1000L + end + ) + (1000L - g.priority) as rankScore +from DailyWeeklyRestCandidateCoverageCardResolvedInterval as c unidirectional, + SupportGeoEvidenceWindow as g +where g.driverKey = c.driverKey + and g.occurredAtEpochSecond >= c.startedAtEpochSecond - ${REST_GEO_LOOKBACK_SECONDS} + and g.occurredAtEpochSecond <= c.startedAtEpochSecond + ${REST_GEO_LOOKAHEAD_SECONDS} + and ( + ( + c.previousVehicleKey is not null + and g.vehicleKey is not null + and c.previousVehicleKey = g.vehicleKey + ) + or ( + (c.previousVehicleKey is null or g.vehicleKey is null) + and c.previousRegistrationKey is not null + and g.registrationKey is not null + and c.previousRegistrationKey = g.registrationKey + ) + ); + +@Priority(35) +insert into DailyWeeklyRestCandidateBeginGeoEvidenceBestScoreWindow +select + driverKey, + startedAtEpochSecond, + endedAtEpochSecond, + min(rankScore) as bestRankScore +from DailyWeeklyRestCandidateBeginGeoEvidenceCandidateWindow +group by + driverKey, + startedAtEpochSecond, + endedAtEpochSecond; + +@Priority(30) +insert into DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow +select + c.sessionId as sessionId, + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond, + c.eventId as eventId, + c.eventDomain as eventDomain, + c.occurredAtEpochSecond as occurredAtEpochSecond, + c.latitude as latitude, + c.longitude as longitude, + c.odometerKm as odometerKm, + c.distanceSeconds as distanceSeconds +from DailyWeeklyRestCandidateBeginGeoEvidenceCandidateWindow as c, + DailyWeeklyRestCandidateBeginGeoEvidenceBestScoreWindow as best +where c.driverKey = best.driverKey + and c.startedAtEpochSecond = best.startedAtEpochSecond + and c.endedAtEpochSecond = best.endedAtEpochSecond + and c.rankScore = best.bestRankScore; + +@Priority(40) +insert into DailyWeeklyRestCandidateEndGeoEvidenceCandidateWindow +select + c.sessionId as sessionId, + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond, + (select odometerKm from DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond) as boundaryOdometerKm, + g.eventId as eventId, + g.eventDomain as eventDomain, + g.occurredAtEpochSecond as occurredAtEpochSecond, + g.latitude as latitude, + g.longitude as longitude, + g.odometerKm as odometerKm, + case + when ( + select odometerKm from DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) is null + or g.odometerKm is null + then null + when g.odometerKm >= ( + select odometerKm from DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) + then g.odometerKm - ( + select odometerKm from DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) + else ( + select odometerKm from DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) - g.odometerKm + end as odometerDeltaKm, + case + when g.occurredAtEpochSecond >= c.endedAtEpochSecond + then g.occurredAtEpochSecond - c.endedAtEpochSecond + else c.endedAtEpochSecond - g.occurredAtEpochSecond + end as distanceSeconds, + ( + ( + case + when g.occurredAtEpochSecond >= c.endedAtEpochSecond + then g.occurredAtEpochSecond - c.endedAtEpochSecond + else c.endedAtEpochSecond - g.occurredAtEpochSecond + end + ) * 10000L + ) + ( + case + when ( + select odometerKm from DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) is not null + and g.odometerKm is not null + then ( + case + when g.odometerKm >= ( + select odometerKm from DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) + then g.odometerKm - ( + select odometerKm from DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) + else ( + select odometerKm from DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow as boundary + where boundary.driverKey = c.driverKey + and boundary.startedAtEpochSecond = c.startedAtEpochSecond + and boundary.endedAtEpochSecond = c.endedAtEpochSecond + ) - g.odometerKm + end + ) * 10L + else 9990L + end + ) + ( + case + when g.occurredAtEpochSecond <= c.endedAtEpochSecond + then 0L + else 1000L + end + ) + (1000L - g.priority) as rankScore +from DailyWeeklyRestCandidateCoverageCardResolvedInterval as c unidirectional, + SupportGeoEvidenceWindow as g +where g.driverKey = c.driverKey + and g.occurredAtEpochSecond >= c.endedAtEpochSecond - ${REST_GEO_LOOKBACK_SECONDS} + and g.occurredAtEpochSecond <= c.endedAtEpochSecond + ${REST_GEO_LOOKAHEAD_SECONDS} + and ( + ( + c.nextVehicleKey is not null + and g.vehicleKey is not null + and c.nextVehicleKey = g.vehicleKey + ) + or ( + (c.nextVehicleKey is null or g.vehicleKey is null) + and c.nextRegistrationKey is not null + and g.registrationKey is not null + and c.nextRegistrationKey = g.registrationKey + ) + ); + +@Priority(35) +insert into DailyWeeklyRestCandidateEndGeoEvidenceBestScoreWindow +select + driverKey, + startedAtEpochSecond, + endedAtEpochSecond, + min(rankScore) as bestRankScore +from DailyWeeklyRestCandidateEndGeoEvidenceCandidateWindow +group by + driverKey, + startedAtEpochSecond, + endedAtEpochSecond; + +@Priority(30) +insert into DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow +select + c.sessionId as sessionId, + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond, + c.eventId as eventId, + c.eventDomain as eventDomain, + c.occurredAtEpochSecond as occurredAtEpochSecond, + c.latitude as latitude, + c.longitude as longitude, + c.odometerKm as odometerKm, + c.distanceSeconds as distanceSeconds +from DailyWeeklyRestCandidateEndGeoEvidenceCandidateWindow as c, + DailyWeeklyRestCandidateEndGeoEvidenceBestScoreWindow as best +where c.driverKey = best.driverKey + and c.startedAtEpochSecond = best.startedAtEpochSecond + and c.endedAtEpochSecond = best.endedAtEpochSecond + and c.rankScore = best.bestRankScore; + +@Priority(20) +insert into DailyWeeklyRestCandidateCoverageFinalizationRequest +select + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond +from DailyWeeklyRestCandidateCoverageCardResolvedInterval as c +where not exists ( + select * from SupportGeoEvidenceWindow as g + where g.driverKey = c.driverKey + and g.occurredAtEpochSecond >= c.startedAtEpochSecond - ${REST_GEO_LOOKBACK_SECONDS} + and g.occurredAtEpochSecond <= c.startedAtEpochSecond + ${REST_GEO_LOOKAHEAD_SECONDS} + and ( + ( + c.previousVehicleKey is not null + and g.vehicleKey is not null + and c.previousVehicleKey = g.vehicleKey + ) + or ( + (c.previousVehicleKey is null or g.vehicleKey is null) + and c.previousRegistrationKey is not null + and g.registrationKey is not null + and c.previousRegistrationKey = g.registrationKey + ) + ) +) +and not exists ( + select * from SupportGeoEvidenceWindow as g + where g.driverKey = c.driverKey + and g.occurredAtEpochSecond >= c.endedAtEpochSecond - ${REST_GEO_LOOKBACK_SECONDS} + and g.occurredAtEpochSecond <= c.endedAtEpochSecond + ${REST_GEO_LOOKAHEAD_SECONDS} + and ( + ( + c.nextVehicleKey is not null + and g.vehicleKey is not null + and c.nextVehicleKey = g.vehicleKey + ) + or ( + (c.nextVehicleKey is null or g.vehicleKey is null) + and c.nextRegistrationKey is not null + and g.registrationKey is not null + and c.nextRegistrationKey = g.registrationKey + ) + ) +); + +@Priority(20) +on DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo +insert into DailyWeeklyRestCandidateCoverageFinalizationRequest +select + beginGeo.driverKey as driverKey, + beginGeo.startedAtEpochSecond as startedAtEpochSecond, + beginGeo.endedAtEpochSecond as endedAtEpochSecond +from DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow as c +where c.driverKey = beginGeo.driverKey + and c.startedAtEpochSecond = beginGeo.startedAtEpochSecond + and c.endedAtEpochSecond = beginGeo.endedAtEpochSecond + and not exists ( + select * from SupportGeoEvidenceWindow as g + where g.driverKey = c.driverKey + and g.occurredAtEpochSecond >= c.endedAtEpochSecond - ${REST_GEO_LOOKBACK_SECONDS} + and g.occurredAtEpochSecond <= c.endedAtEpochSecond + ${REST_GEO_LOOKAHEAD_SECONDS} + and ( + ( + c.nextVehicleKey is not null + and g.vehicleKey is not null + and c.nextVehicleKey = g.vehicleKey + ) + or ( + (c.nextVehicleKey is null or g.vehicleKey is null) + and c.nextRegistrationKey is not null + and g.registrationKey is not null + and c.nextRegistrationKey = g.registrationKey + ) + ) + ); + +@Priority(20) +on DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo +insert into DailyWeeklyRestCandidateCoverageFinalizationRequest +select + endGeo.driverKey as driverKey, + endGeo.startedAtEpochSecond as startedAtEpochSecond, + endGeo.endedAtEpochSecond as endedAtEpochSecond +from DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow as c +where c.driverKey = endGeo.driverKey + and c.startedAtEpochSecond = endGeo.startedAtEpochSecond + and c.endedAtEpochSecond = endGeo.endedAtEpochSecond; + +@Priority(10) +insert into DailyWeeklyRestCandidateCoverageInterval +select + c.sessionId as sessionId, + c.driverKey as driverKey, + c.startedAtEpochSecond as startedAtEpochSecond, + c.endedAtEpochSecond as endedAtEpochSecond, + c.durationSeconds as durationSeconds, + c.cardPresentDurationSeconds as cardPresentDurationSeconds, + (c.cardPresentDurationSeconds * 100.0d) / c.durationSeconds as cardPresentCoveragePercent, + c.unknownDurationSeconds as unknownDurationSeconds, + (c.unknownDurationSeconds * 100.0d) / c.durationSeconds as unknownCoveragePercent, + c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId, + c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId, + c.previousRegistrationKey as previousRegistrationKey, + c.nextRegistrationKey as nextRegistrationKey, + c.previousVehicleKey as previousVehicleKey, + c.nextVehicleKey as nextVehicleKey, + (select odometerKm from DailyWeeklyRestCandidateBeginBoundaryOdometerResolvedWindow as beginBoundary + where beginBoundary.driverKey = c.driverKey + and beginBoundary.startedAtEpochSecond = c.startedAtEpochSecond + and beginBoundary.endedAtEpochSecond = c.endedAtEpochSecond) as beginBoundaryOdometerKm, + (select odometerKm from DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow as endBoundary + where endBoundary.driverKey = c.driverKey + and endBoundary.startedAtEpochSecond = c.startedAtEpochSecond + and endBoundary.endedAtEpochSecond = c.endedAtEpochSecond) as endBoundaryOdometerKm, + (select eventId from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) as beginGeoEventId, + (select eventDomain from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) as beginGeoEventDomain, + (select occurredAtEpochSecond from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) as beginGeoOccurredAtEpochSecond, + (select latitude from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) as beginLatitude, + (select longitude from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) as beginLongitude, + (select distanceSeconds from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) as beginGeoDistanceSeconds, + (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) as beginGeoOdometerKm, + (select eventId from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) as endGeoEventId, + (select eventDomain from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) as endGeoEventDomain, + (select occurredAtEpochSecond from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) as endGeoOccurredAtEpochSecond, + (select latitude from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) as endLatitude, + (select longitude from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) as endLongitude, + (select distanceSeconds from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) as endGeoDistanceSeconds, + (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) as endGeoOdometerKm, + case + when (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) is null + or (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) is null + then null + when (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) + >= + (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) + then ( + (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) + - + (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) + ) * 1000L + else ( + (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) + - + (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) + ) * 1000L + end as geoEvidenceMovementMeters, + case + when (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) is null + or (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) is null + then 'UNKNOWN' + when ( + case + when (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) + >= + (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) + then ( + (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) + - + (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) + ) * 1000L + else ( + (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) + - + (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) + ) * 1000L + end + ) <= ${REST_GEO_STATIONARY_MAX_METERS} + then 'STATIONARY' + when ( + case + when (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) + >= + (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) + then ( + (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) + - + (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) + ) * 1000L + else ( + (select odometerKm from DailyWeeklyRestCandidateBeginGeoEvidenceResolvedWindow as beginGeo + where beginGeo.driverKey = c.driverKey + and beginGeo.startedAtEpochSecond = c.startedAtEpochSecond + and beginGeo.endedAtEpochSecond = c.endedAtEpochSecond) + - + (select odometerKm from DailyWeeklyRestCandidateEndGeoEvidenceResolvedWindow as endGeo + where endGeo.driverKey = c.driverKey + and endGeo.startedAtEpochSecond = c.startedAtEpochSecond + and endGeo.endedAtEpochSecond = c.endedAtEpochSecond) + ) * 1000L + end + ) <= ${REST_GEO_MINOR_MOVEMENT_MAX_METERS} + then 'MINOR' + else 'MOVED' + end as geoEvidenceMovementCategory +from DailyWeeklyRestCandidateCoverageFinalizationRequest as request unidirectional, + DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow as c +where c.driverKey = request.driverKey + and c.startedAtEpochSecond = request.startedAtEpochSecond + and c.endedAtEpochSecond = request.endedAtEpochSecond; context PerDriver create window PreviousVehicleUsageInterval#lastevent as TachographVehicleUsageIntervalInputEvent; @@ -486,7 +1570,25 @@ select c.previousRegistrationKey as previousRegistrationKey, c.nextRegistrationKey as nextRegistrationKey, c.previousVehicleKey as previousVehicleKey, - c.nextVehicleKey as nextVehicleKey + c.nextVehicleKey as nextVehicleKey, + c.beginBoundaryOdometerKm as beginBoundaryOdometerKm, + c.endBoundaryOdometerKm as endBoundaryOdometerKm, + c.beginGeoEventId as beginGeoEventId, + c.beginGeoEventDomain as beginGeoEventDomain, + c.beginGeoOccurredAtEpochSecond as beginGeoOccurredAtEpochSecond, + c.beginLatitude as beginLatitude, + c.beginLongitude as beginLongitude, + c.beginGeoDistanceSeconds as beginGeoDistanceSeconds, + c.beginGeoOdometerKm as beginGeoOdometerKm, + c.endGeoEventId as endGeoEventId, + c.endGeoEventDomain as endGeoEventDomain, + c.endGeoOccurredAtEpochSecond as endGeoOccurredAtEpochSecond, + c.endLatitude as endLatitude, + c.endLongitude as endLongitude, + c.endGeoDistanceSeconds as endGeoDistanceSeconds, + c.endGeoOdometerKm as endGeoOdometerKm, + c.geoEvidenceMovementMeters as geoEvidenceMovementMeters, + c.geoEvidenceMovementCategory as geoEvidenceMovementCategory from DailyWeeklyRestCandidateCoverageInterval as c where c.previousRegistrationKey is not null and c.nextRegistrationKey is not null @@ -509,7 +1611,25 @@ select c.previousRegistrationKey as previousRegistrationKey, c.nextRegistrationKey as nextRegistrationKey, c.previousVehicleKey as previousVehicleKey, - c.nextVehicleKey as nextVehicleKey + c.nextVehicleKey as nextVehicleKey, + c.beginBoundaryOdometerKm as beginBoundaryOdometerKm, + c.endBoundaryOdometerKm as endBoundaryOdometerKm, + c.beginGeoEventId as beginGeoEventId, + c.beginGeoEventDomain as beginGeoEventDomain, + c.beginGeoOccurredAtEpochSecond as beginGeoOccurredAtEpochSecond, + c.beginLatitude as beginLatitude, + c.beginLongitude as beginLongitude, + c.beginGeoDistanceSeconds as beginGeoDistanceSeconds, + c.beginGeoOdometerKm as beginGeoOdometerKm, + c.endGeoEventId as endGeoEventId, + c.endGeoEventDomain as endGeoEventDomain, + c.endGeoOccurredAtEpochSecond as endGeoOccurredAtEpochSecond, + c.endLatitude as endLatitude, + c.endLongitude as endLongitude, + c.endGeoDistanceSeconds as endGeoDistanceSeconds, + c.endGeoOdometerKm as endGeoOdometerKm, + c.geoEvidenceMovementMeters as geoEvidenceMovementMeters, + c.geoEvidenceMovementCategory as geoEvidenceMovementCategory from DailyWeeklyRestCandidateCoverageInterval as c where c.previousRegistrationKey is not null and c.nextRegistrationKey is not null @@ -532,7 +1652,25 @@ select c.previousRegistrationKey as previousRegistrationKey, c.nextRegistrationKey as nextRegistrationKey, c.previousVehicleKey as previousVehicleKey, - c.nextVehicleKey as nextVehicleKey + c.nextVehicleKey as nextVehicleKey, + c.beginBoundaryOdometerKm as beginBoundaryOdometerKm, + c.endBoundaryOdometerKm as endBoundaryOdometerKm, + c.beginGeoEventId as beginGeoEventId, + c.beginGeoEventDomain as beginGeoEventDomain, + c.beginGeoOccurredAtEpochSecond as beginGeoOccurredAtEpochSecond, + c.beginLatitude as beginLatitude, + c.beginLongitude as beginLongitude, + c.beginGeoDistanceSeconds as beginGeoDistanceSeconds, + c.beginGeoOdometerKm as beginGeoOdometerKm, + c.endGeoEventId as endGeoEventId, + c.endGeoEventDomain as endGeoEventDomain, + c.endGeoOccurredAtEpochSecond as endGeoOccurredAtEpochSecond, + c.endLatitude as endLatitude, + c.endLongitude as endLongitude, + c.endGeoDistanceSeconds as endGeoDistanceSeconds, + c.endGeoOdometerKm as endGeoOdometerKm, + c.geoEvidenceMovementMeters as geoEvidenceMovementMeters, + c.geoEvidenceMovementCategory as geoEvidenceMovementCategory from DailyWeeklyRestCandidateCoverageInterval as c where not ( c.previousRegistrationKey is not null diff --git a/src/test/java/at/procon/eventhub/tachographfilesession/api/TachographFileSessionControllerTest.java b/src/test/java/at/procon/eventhub/tachographfilesession/api/TachographFileSessionControllerTest.java index bd086b5..6ea091e 100644 --- a/src/test/java/at/procon/eventhub/tachographfilesession/api/TachographFileSessionControllerTest.java +++ b/src/test/java/at/procon/eventhub/tachographfilesession/api/TachographFileSessionControllerTest.java @@ -24,6 +24,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.TachographEsperPotentialInVehicleTripIntervalEvent; +import at.procon.eventhub.tachographfilesession.model.TachographEsperSupportGeoEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent; import at.procon.eventhub.tachographfilesession.service.TachographFileSessionProcessingService; @@ -93,6 +94,7 @@ class TachographFileSessionControllerTest { 0, 2, 1, + 1, List.of(new TachographEsperActivityIntervalEvent( sessionId, "12:123", @@ -185,7 +187,27 @@ class TachographFileSessionControllerTest { "12:REG-1", "12:REG-2", "VIN-1", - "VIN-2" + "VIN-2", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "UNKNOWN", + null, + null )), List.of(), List.of(new TachographEsperPotentialHomeOvernightStayIntervalEvent( @@ -203,7 +225,27 @@ class TachographFileSessionControllerTest { "12:REG-1", "12:REG-2", "VIN-1", - "VIN-2" + "VIN-2", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "UNKNOWN", + null, + null )), List.of(), List.of(), @@ -234,6 +276,25 @@ class TachographFileSessionControllerTest { "VIN-1", "VIN-2" )), + List.of(new TachographEsperSupportGeoEvent( + "SUP-1", + "12:123", + OffsetDateTime.parse("2026-05-12T10:15:00Z"), + "POSITION", + "POSITION_RECORDED", + "SNAPSHOT", + "12:REG-1", + "VIN-1", + null, + null, + null, + null, + null, + java.math.BigDecimal.valueOf(48.2082), + java.math.BigDecimal.valueOf(16.3738), + 123L, + "path" + )), List.of("note") )); when(processingService.evaluateOperatingPeriods(eq(sessionId), eq("12:123"), org.mockito.ArgumentMatchers.any(TachographOperatingPeriodsProcessingRequest.class))) @@ -307,6 +368,8 @@ class TachographFileSessionControllerTest { .andExpect(jsonPath("$.potentialInVehicleOvernightStayIntervalCount").value(0)) .andExpect(jsonPath("$.potentialInVehicleTripIntervalCount").value(0)) .andExpect(jsonPath("$.vuCardAbsentIntervalCount").value(1)) + .andExpect(jsonPath("$.supportGeoEventCount").value(1)) + .andExpect(jsonPath("$.supportGeoEvents[0].eventId").value("SUP-1")) .andExpect(jsonPath("$.drivingInterruptionIntervals[0].previousRegistrationKey").value("12:REG-1")) .andExpect(jsonPath("$.drivingInterruptionIntervals[0].nextRegistrationKey").value("12:REG-2")) .andExpect(jsonPath("$.drivingInterruptionIntervals[0].previousVehicleKey").value("VIN-1")) diff --git a/src/test/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineReusableProjectionBuilderTest.java b/src/test/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineReusableProjectionBuilderTest.java index fe16a59..fd30572 100644 --- a/src/test/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineReusableProjectionBuilderTest.java +++ b/src/test/java/at/procon/eventhub/tachographfilesession/service/DriverTimelineReusableProjectionBuilderTest.java @@ -1,10 +1,12 @@ package at.procon.eventhub.tachographfilesession.service; +import at.procon.eventhub.config.EventHubProperties; import static org.assertj.core.api.Assertions.assertThat; import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession; import at.procon.eventhub.tachographfilesession.model.ExtractedCardActivityInterval; import at.procon.eventhub.tachographfilesession.model.ExtractedCardVehicleUsageInterval; +import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent; import at.procon.eventhub.tachographfilesession.model.ExtractionStats; import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline; import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent; @@ -26,7 +28,7 @@ class DriverTimelineReusableProjectionBuilderTest { private final DriverTimelineBuilder legacyBuilder = new DriverTimelineBuilder(); private final DriverTimelineReusableProjectionBuilder reusableBuilder = - new DriverTimelineReusableProjectionBuilder(legacyBuilder); + new DriverTimelineReusableProjectionBuilder(legacyBuilder, new EventHubProperties()); @Test void matchesLegacyDrivingDerivedProjectionChain() { @@ -130,7 +132,17 @@ class DriverTimelineReusableProjectionBuilderTest { assertThat(coverageInterval.cardPresentCoveragePercent()).isEqualTo(0.0d); assertThat(reusableBundle.drivingInterruptionVehicleChangeIntervals()).containsExactlyElementsOf(legacyDrivingInterruptionVehicleChanges); assertThat(reusableBundle.vuCardAbsentIntervals()).containsExactlyElementsOf(legacyVuCardAbsentIntervals); - assertThat(reusableBundle.potentialHomeOvernightStayIntervals()).containsExactlyElementsOf(legacyPotentialHomeOvernightStays); + assertThat(reusableBundle.potentialHomeOvernightStayIntervals()).hasSize(1); + assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).startedAt()) + .isEqualTo(legacyPotentialHomeOvernightStays.get(0).startedAt()); + assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).endedAt()) + .isEqualTo(legacyPotentialHomeOvernightStays.get(0).endedAt()); + assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).unknownCoveragePercent()) + .isEqualTo(legacyPotentialHomeOvernightStays.get(0).unknownCoveragePercent()); + assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).beginBoundaryOdometerKm()) + .isEqualTo(200L); + assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).endBoundaryOdometerKm()) + .isEqualTo(201L); assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals()).isEmpty(); } @@ -180,7 +192,56 @@ class DriverTimelineReusableProjectionBuilderTest { "b" ) ), - List.of(), + List.of( + new ExtractedSupportEvent( + "SUP-1", + "12:123", + OffsetDateTime.parse("2026-05-01T09:45:00Z"), + "POSITION", + "POSITION_RECORDED", + "SNAPSHOT", + null, + "12:REG-1", + "VIN-1", + null, + null, + null, + null, + null, + new java.math.BigDecimal("48.2082"), + new java.math.BigDecimal("16.3738"), + null, + 150L, + null, + null, + null, + "geo-1" + ), + new ExtractedSupportEvent( + "SUP-2", + "12:123", + OffsetDateTime.parse("2026-05-02T00:15:00Z"), + "POSITION", + "POSITION_RECORDED", + "SNAPSHOT", + null, + "12:REG-1", + "VIN-1", + null, + null, + null, + null, + null, + new java.math.BigDecimal("48.2090"), + new java.math.BigDecimal("16.3740"), + null, + 150L, + null, + null, + null, + "geo-2" + ) + ), List.of() ); TachographFileSession session = new TachographFileSession( @@ -209,6 +270,26 @@ class DriverTimelineReusableProjectionBuilderTest { .isEqualTo(100.0d); assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).unknownCoveragePercent()) .isEqualTo(0.0d); + assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).beginGeoEventId()) + .isEqualTo("SUP-1"); + assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).beginGeoEvent()) + .extracting("eventId", "eventDomain") + .containsExactly("SUP-1", "POSITION"); + assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).endGeoEventId()) + .isEqualTo("SUP-2"); + assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).endGeoEvent()) + .extracting("eventId", "eventDomain") + .containsExactly("SUP-2", "POSITION"); + assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).beginGeoDistanceSeconds()) + .isEqualTo(900L); + assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).endGeoDistanceSeconds()) + .isEqualTo(900L); + assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).beginBoundaryOdometerKm()) + .isEqualTo(100L); + assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).endBoundaryOdometerKm()) + .isEqualTo(260L); + assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).geoEvidenceMovementCategory()) + .isEqualTo("STATIONARY"); assertThat(reusableBundle.potentialHomeOvernightStayIntervals()).isEmpty(); assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals()).hasSize(1); assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).startedAt()) @@ -217,5 +298,10 @@ class DriverTimelineReusableProjectionBuilderTest { .isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z")); assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).cardPresentCoveragePercent()) .isEqualTo(100.0d); + assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).beginGeoEventId()) + .isEqualTo("SUP-1"); + assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).beginGeoEvent()) + .extracting("eventId", "eventDomain") + .containsExactly("SUP-1", "POSITION"); } } diff --git a/src/test/java/at/procon/eventhub/tachographfilesession/service/TachographFileSessionProcessingServiceTest.java b/src/test/java/at/procon/eventhub/tachographfilesession/service/TachographFileSessionProcessingServiceTest.java index 3258d79..c5e57cf 100644 --- a/src/test/java/at/procon/eventhub/tachographfilesession/service/TachographFileSessionProcessingServiceTest.java +++ b/src/test/java/at/procon/eventhub/tachographfilesession/service/TachographFileSessionProcessingServiceTest.java @@ -33,7 +33,7 @@ class TachographFileSessionProcessingServiceTest { TachographFileSessionProcessingService service = new TachographFileSessionProcessingService( repository, driverTimelineBuilder, - new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder), + new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()), new EventBackedDriverTimelineBuilder( new IntervalBackedDriverTimelineEventBuilder( driverTimelineBuilder, @@ -119,7 +119,7 @@ class TachographFileSessionProcessingServiceTest { TachographFileSessionProcessingService service = new TachographFileSessionProcessingService( repository, driverTimelineBuilder, - new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder), + new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()), new EventBackedDriverTimelineBuilder( new IntervalBackedDriverTimelineEventBuilder( driverTimelineBuilder, @@ -226,7 +226,7 @@ class TachographFileSessionProcessingServiceTest { TachographFileSessionProcessingService service = new TachographFileSessionProcessingService( repository, driverTimelineBuilder, - new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder), + new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()), new EventBackedDriverTimelineBuilder( new IntervalBackedDriverTimelineEventBuilder( driverTimelineBuilder, @@ -315,7 +315,7 @@ class TachographFileSessionProcessingServiceTest { TachographFileSessionProcessingService service = new TachographFileSessionProcessingService( repository, driverTimelineBuilder, - new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder), + new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()), new EventBackedDriverTimelineBuilder( new IntervalBackedDriverTimelineEventBuilder( driverTimelineBuilder, @@ -417,7 +417,7 @@ class TachographFileSessionProcessingServiceTest { TachographFileSessionProcessingService service = new TachographFileSessionProcessingService( repository, driverTimelineBuilder, - new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder), + new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()), new EventBackedDriverTimelineBuilder( new IntervalBackedDriverTimelineEventBuilder( driverTimelineBuilder, @@ -502,7 +502,7 @@ class TachographFileSessionProcessingServiceTest { TachographFileSessionProcessingService service = new TachographFileSessionProcessingService( repository, driverTimelineBuilder, - new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder), + new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()), new EventBackedDriverTimelineBuilder( new IntervalBackedDriverTimelineEventBuilder( driverTimelineBuilder, @@ -594,7 +594,7 @@ class TachographFileSessionProcessingServiceTest { TachographFileSessionProcessingService service = new TachographFileSessionProcessingService( repository, driverTimelineBuilder, - new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder), + new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()), new EventBackedDriverTimelineBuilder( new IntervalBackedDriverTimelineEventBuilder( driverTimelineBuilder, @@ -668,7 +668,7 @@ class TachographFileSessionProcessingServiceTest { TachographFileSessionProcessingService service = new TachographFileSessionProcessingService( repository, driverTimelineBuilder, - new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder), + new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()), new EventBackedDriverTimelineBuilder( new IntervalBackedDriverTimelineEventBuilder( driverTimelineBuilder,