Add rest geo evidence and boundary odometers

This commit is contained in:
trifonovt 2026-05-22 10:12:32 +02:00
parent 4535f620fc
commit f1f36e2204
14 changed files with 1894 additions and 47 deletions

View File

@ -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;
}

View File

@ -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<TachographEsperActivityIntervalEvent> activityIntervals,
List<TachographEsperActivityIntervalEvent> drivingIntervals,
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals,
@ -44,6 +46,7 @@ public record TachographEsperDriverProcessingResultDto(
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals,
List<TachographEsperVehicleUsageIntervalEvent> vehicleUsageIntervals,
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals,
List<TachographEsperSupportGeoEvent> supportGeoEvents,
List<String> notes
) {
}

View File

@ -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
) {
}

View File

@ -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
) {
}

View File

@ -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
) {
}

View File

@ -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
) {
}

View File

@ -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
) {
}

View File

@ -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
));
}
}

View File

@ -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<ResolvedActivityInterval> activityIntervals,
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
List<ExtractedSupportEvent> 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<String, Object> 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<String, Object> supportGeoEvidenceInputDefinition() {
Map<String, Object> 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<String, Object> toActivityIntervalInputMap(
UUID sessionId,
String driverKey,
@ -311,6 +353,50 @@ public class DriverTimelineReusableProjectionBuilder {
return event;
}
private Map<String, Object> 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<String, Object> 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);

View File

@ -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<TachographEsperSupportGeoEvent> 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<TachographEsperSupportGeoEvent> clipEsperSupportGeoEvents(
List<ExtractedSupportEvent> 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<TachographEsperDrivingInterruptionIntervalEvent> clipEsperDrivingInterruptionIntervalEvents(
List<TachographEsperDrivingInterruptionIntervalEvent> 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;

View File

@ -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.<TachographEsperPotentialInVehicleTripIntervalEvent>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"))

View File

@ -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");
}
}

View File

@ -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,