Add esper driving interruption projections
This commit is contained in:
parent
0e2b83270c
commit
eb4e04f144
|
|
@ -374,6 +374,38 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Process tachograph file session Esper events",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"significantDrivingMinutes\": 3\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/eventhub/tachograph-file-sessions/{{sessionId}}/drivers/{{driverKey}}/processing/esper-events",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"eventhub",
|
||||
"tachograph-file-sessions",
|
||||
"{{sessionId}}",
|
||||
"drivers",
|
||||
"{{driverKey}}",
|
||||
"processing",
|
||||
"esper-events"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Process tachograph file session operating periods",
|
||||
"request": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package at.procon.eventhub.tachographfilesession.api;
|
||||
|
||||
import at.procon.eventhub.tachographfilesession.dto.CreateTachographFileSessionResponse;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographEsperEventsProcessingRequest;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographFileDriverDetailDto;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingRequest;
|
||||
|
|
@ -75,6 +76,15 @@ public class TachographFileSessionController {
|
|||
return ResponseEntity.ok(processingService.getEsperDriverProcessingResults(sessionId, driverKey));
|
||||
}
|
||||
|
||||
@PostMapping("/{sessionId}/drivers/{driverKey}/processing/esper-events")
|
||||
public ResponseEntity<TachographEsperDriverProcessingResultDto> evaluateEsperDriverProcessingResults(
|
||||
@PathVariable UUID sessionId,
|
||||
@PathVariable String driverKey,
|
||||
@RequestBody(required = false) TachographEsperEventsProcessingRequest request
|
||||
) {
|
||||
return ResponseEntity.ok(processingService.getEsperDriverProcessingResults(sessionId, driverKey, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{sessionId}/drivers/{driverKey}/processing/operating-periods")
|
||||
public ResponseEntity<TachographOperatingPeriodsProcessingResultDto> evaluateOperatingPeriods(
|
||||
@PathVariable UUID sessionId,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package at.procon.eventhub.tachographfilesession.dto;
|
||||
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
|
||||
import java.time.OffsetDateTime;
|
||||
|
|
@ -13,12 +14,16 @@ public record TachographEsperDriverProcessingResultDto(
|
|||
String sourceKind,
|
||||
OffsetDateTime loadedFrom,
|
||||
OffsetDateTime loadedTo,
|
||||
OffsetDateTime requestedFrom,
|
||||
OffsetDateTime requestedTo,
|
||||
int activityIntervalCount,
|
||||
int drivingIntervalCount,
|
||||
int drivingInterruptionIntervalCount,
|
||||
int vehicleUsageIntervalCount,
|
||||
int vuCardAbsentIntervalCount,
|
||||
List<TachographEsperActivityIntervalEvent> activityIntervals,
|
||||
List<TachographEsperActivityIntervalEvent> drivingIntervals,
|
||||
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals,
|
||||
List<TachographEsperVehicleUsageIntervalEvent> vehicleUsageIntervals,
|
||||
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals,
|
||||
List<String> notes
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package at.procon.eventhub.tachographfilesession.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public record TachographEsperEventsProcessingRequest(
|
||||
OffsetDateTime occurredFrom,
|
||||
OffsetDateTime occurredTo,
|
||||
Integer significantDrivingMinutes
|
||||
) {
|
||||
public TachographEsperEventsProcessingRequest {
|
||||
significantDrivingMinutes = significantDrivingMinutes == null ? null : Math.max(1, significantDrivingMinutes);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package at.procon.eventhub.tachographfilesession.model;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public record TachographEsperDrivingInterruptionIntervalEvent(
|
||||
UUID sessionId,
|
||||
String driverKey,
|
||||
OffsetDateTime startedAt,
|
||||
OffsetDateTime endedAt,
|
||||
long durationSeconds,
|
||||
String previousDrivingSourceIntervalId,
|
||||
String nextDrivingSourceIntervalId,
|
||||
String previousVehicleKey,
|
||||
String nextVehicleKey
|
||||
) {
|
||||
}
|
||||
|
|
@ -19,12 +19,15 @@ 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.TachographEsperActivityIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
|
|
@ -47,6 +50,8 @@ public class DriverTimelineBuilder {
|
|||
loadResource("esper/tachograph-activity-interval-events.epl");
|
||||
private static final String DRIVING_INTERVAL_EVENTS_EPL =
|
||||
loadResource("esper/tachograph-driving-interval-events.epl");
|
||||
private static final String DRIVING_INTERRUPTION_INTERVAL_EVENTS_EPL_TEMPLATE =
|
||||
loadResource("esper/tachograph-driving-interruption-interval-events.epl");
|
||||
private static final String VEHICLE_USAGE_INTERVAL_EVENTS_EPL =
|
||||
loadResource("esper/tachograph-vehicle-usage-interval-events.epl");
|
||||
private static final String VU_CARD_ABSENT_INTERVAL_EVENTS_EPL =
|
||||
|
|
@ -139,6 +144,38 @@ public class DriverTimelineBuilder {
|
|||
return timeline == null ? List.of() : buildEsperVehicleUsageIntervalEvents(timeline.vehicleUsageIntervals());
|
||||
}
|
||||
|
||||
public List<TachographEsperDrivingInterruptionIntervalEvent> buildEsperDrivingInterruptionIntervalEvents(
|
||||
TachographFileSession session,
|
||||
DriverExtractionSession driverSession,
|
||||
int significantDrivingMinutes
|
||||
) {
|
||||
String sourceKind = session.metadata().driverCardFile() ? "DRIVER_CARD" : "VEHICLE_UNIT";
|
||||
List<ResolvedActivityInterval> activityIntervals = resolveActivities(driverSession.cardActivityIntervals(), sourceKind);
|
||||
return buildEsperDrivingInterruptionIntervalEvents(
|
||||
session.sessionId(),
|
||||
driverSession.driverKey(),
|
||||
activityIntervals,
|
||||
significantDrivingMinutes
|
||||
);
|
||||
}
|
||||
|
||||
public List<TachographEsperDrivingInterruptionIntervalEvent> buildEsperDrivingInterruptionIntervalEvents(
|
||||
UUID sessionId,
|
||||
String driverKey,
|
||||
ResolvedDriverTimeline timeline,
|
||||
int significantDrivingMinutes
|
||||
) {
|
||||
if (timeline == null) {
|
||||
return List.of();
|
||||
}
|
||||
return buildEsperDrivingInterruptionIntervalEvents(
|
||||
sessionId,
|
||||
driverKey,
|
||||
timeline.activityIntervals(),
|
||||
significantDrivingMinutes
|
||||
);
|
||||
}
|
||||
|
||||
public List<TachographEsperVuCardAbsentIntervalEvent> buildEsperVuCardAbsentIntervalEvents(
|
||||
TachographFileSession session,
|
||||
DriverExtractionSession driverSession
|
||||
|
|
@ -244,6 +281,36 @@ public class DriverTimelineBuilder {
|
|||
return result;
|
||||
}
|
||||
|
||||
private List<TachographEsperDrivingInterruptionIntervalEvent> buildEsperDrivingInterruptionIntervalEvents(
|
||||
UUID sessionId,
|
||||
String driverKey,
|
||||
List<ResolvedActivityInterval> activityIntervals,
|
||||
int significantDrivingMinutes
|
||||
) {
|
||||
if (activityIntervals == null || activityIntervals.size() < 2) {
|
||||
return List.of();
|
||||
}
|
||||
List<TachographEsperDrivingInterruptionIntervalEvent> result = new ArrayList<>();
|
||||
executeWithRuntime(
|
||||
configuration -> configuration.getCommon().addEventType(
|
||||
"TachographActivityIntervalInputEvent",
|
||||
activityIntervalInputDefinition()
|
||||
),
|
||||
renderDrivingInterruptionIntervalEventsEpl(significantDrivingMinutes),
|
||||
"drivingInterruptionIntervals",
|
||||
newData -> collectDrivingInterruptionIntervalEvents(newData, result),
|
||||
runtime -> {
|
||||
for (ResolvedActivityInterval interval : activityIntervals) {
|
||||
runtime.getEventService().sendEventMap(
|
||||
toActivityIntervalInputMap(sessionId, driverKey, interval),
|
||||
"TachographActivityIntervalInputEvent"
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<TachographEsperVuCardAbsentIntervalEvent> buildEsperVuCardAbsentIntervalEvents(
|
||||
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals
|
||||
) {
|
||||
|
|
@ -477,7 +544,10 @@ public class DriverTimelineBuilder {
|
|||
|
||||
sender.accept(runtime);
|
||||
} catch (EPCompileException | EPDeployException e) {
|
||||
throw new IllegalStateException("Cannot compile/deploy tachograph projection EPL", e);
|
||||
throw new IllegalStateException(
|
||||
"Cannot compile/deploy tachograph projection EPL for statement '" + statementName + "'",
|
||||
e
|
||||
);
|
||||
} finally {
|
||||
if (runtime != null) {
|
||||
runtime.destroy();
|
||||
|
|
@ -497,8 +567,12 @@ public class DriverTimelineBuilder {
|
|||
definition.put("registrationKey", String.class);
|
||||
definition.put("vehicleKey", String.class);
|
||||
definition.put("sourceKind", String.class);
|
||||
definition.put("firstSourceIntervalId", String.class);
|
||||
definition.put("lastSourceIntervalId", String.class);
|
||||
definition.put("startedAt", OffsetDateTime.class);
|
||||
definition.put("endedAt", OffsetDateTime.class);
|
||||
definition.put("startedAtEpochSecond", long.class);
|
||||
definition.put("endedAtEpochSecond", long.class);
|
||||
definition.put("durationSeconds", long.class);
|
||||
definition.put("sourceIntervalIds", java.util.List.class);
|
||||
definition.put("synthetic", boolean.class);
|
||||
|
|
@ -512,8 +586,12 @@ public class DriverTimelineBuilder {
|
|||
definition.put("sessionId", UUID.class);
|
||||
definition.put("driverKey", String.class);
|
||||
definition.put("intervalId", String.class);
|
||||
definition.put("firstSourceIntervalId", String.class);
|
||||
definition.put("lastSourceIntervalId", String.class);
|
||||
definition.put("startedAt", OffsetDateTime.class);
|
||||
definition.put("endedAt", OffsetDateTime.class);
|
||||
definition.put("startedAtEpochSecond", long.class);
|
||||
definition.put("endedAtEpochSecond", Long.class);
|
||||
definition.put("durationSeconds", long.class);
|
||||
definition.put("odometerBeginKm", Long.class);
|
||||
definition.put("odometerEndKm", Long.class);
|
||||
|
|
@ -540,8 +618,12 @@ public class DriverTimelineBuilder {
|
|||
event.put("registrationKey", interval.registrationKey());
|
||||
event.put("vehicleKey", interval.vehicleKey());
|
||||
event.put("sourceKind", interval.sourceKind());
|
||||
event.put("firstSourceIntervalId", firstSourceIntervalId(interval));
|
||||
event.put("lastSourceIntervalId", lastSourceIntervalId(interval));
|
||||
event.put("startedAt", interval.from());
|
||||
event.put("endedAt", interval.to());
|
||||
event.put("startedAtEpochSecond", interval.from().toEpochSecond());
|
||||
event.put("endedAtEpochSecond", interval.to().toEpochSecond());
|
||||
event.put("durationSeconds", interval.durationSeconds());
|
||||
event.put("sourceIntervalIds", interval.sourceIntervalIds());
|
||||
event.put("synthetic", interval.synthetic());
|
||||
|
|
@ -550,13 +632,27 @@ public class DriverTimelineBuilder {
|
|||
return event;
|
||||
}
|
||||
|
||||
private String firstSourceIntervalId(ResolvedActivityInterval interval) {
|
||||
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().get(0);
|
||||
}
|
||||
|
||||
private String lastSourceIntervalId(ResolvedActivityInterval interval) {
|
||||
return interval.sourceIntervalIds().isEmpty()
|
||||
? interval.intervalId()
|
||||
: interval.sourceIntervalIds().get(interval.sourceIntervalIds().size() - 1);
|
||||
}
|
||||
|
||||
private Map<String, Object> toVehicleUsageIntervalInputMap(ResolvedVehicleUsageInterval interval) {
|
||||
Map<String, Object> event = new LinkedHashMap<>();
|
||||
event.put("sessionId", interval.sessionId());
|
||||
event.put("driverKey", interval.driverKey());
|
||||
event.put("intervalId", interval.intervalId());
|
||||
event.put("firstSourceIntervalId", firstSourceIntervalId(interval));
|
||||
event.put("lastSourceIntervalId", lastSourceIntervalId(interval));
|
||||
event.put("startedAt", interval.from());
|
||||
event.put("endedAt", interval.to());
|
||||
event.put("startedAtEpochSecond", interval.from().toEpochSecond());
|
||||
event.put("endedAtEpochSecond", interval.to() == null ? null : interval.to().toEpochSecond());
|
||||
event.put("durationSeconds", interval.durationSeconds());
|
||||
event.put("odometerBeginKm", interval.odometerBeginKm());
|
||||
event.put("odometerEndKm", interval.odometerEndKm());
|
||||
|
|
@ -567,6 +663,16 @@ public class DriverTimelineBuilder {
|
|||
return event;
|
||||
}
|
||||
|
||||
private String firstSourceIntervalId(ResolvedVehicleUsageInterval interval) {
|
||||
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().get(0);
|
||||
}
|
||||
|
||||
private String lastSourceIntervalId(ResolvedVehicleUsageInterval interval) {
|
||||
return interval.sourceIntervalIds().isEmpty()
|
||||
? interval.intervalId()
|
||||
: interval.sourceIntervalIds().get(interval.sourceIntervalIds().size() - 1);
|
||||
}
|
||||
|
||||
private void collectActivityIntervalEvents(
|
||||
EventBean[] newData,
|
||||
List<TachographEsperActivityIntervalEvent> target
|
||||
|
|
@ -622,6 +728,30 @@ public class DriverTimelineBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
private void collectDrivingInterruptionIntervalEvents(
|
||||
EventBean[] newData,
|
||||
List<TachographEsperDrivingInterruptionIntervalEvent> target
|
||||
) {
|
||||
if (newData == null) {
|
||||
return;
|
||||
}
|
||||
for (EventBean event : newData) {
|
||||
long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond");
|
||||
long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond");
|
||||
target.add(new TachographEsperDrivingInterruptionIntervalEvent(
|
||||
(UUID) event.get("sessionId"),
|
||||
(String) event.get("driverKey"),
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
||||
(Long) event.get("durationSeconds"),
|
||||
(String) event.get("previousDrivingSourceIntervalId"),
|
||||
(String) event.get("nextDrivingSourceIntervalId"),
|
||||
(String) event.get("previousVehicleKey"),
|
||||
(String) event.get("nextVehicleKey")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void collectVuCardAbsentIntervalEvents(
|
||||
EventBean[] newData,
|
||||
List<TachographEsperVuCardAbsentIntervalEvent> target
|
||||
|
|
@ -630,11 +760,13 @@ public class DriverTimelineBuilder {
|
|||
return;
|
||||
}
|
||||
for (EventBean event : newData) {
|
||||
long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond");
|
||||
long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond");
|
||||
target.add(new TachographEsperVuCardAbsentIntervalEvent(
|
||||
(UUID) event.get("sessionId"),
|
||||
(String) event.get("driverKey"),
|
||||
(OffsetDateTime) event.get("startedAt"),
|
||||
(OffsetDateTime) event.get("endedAt"),
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
||||
(Long) event.get("durationSeconds"),
|
||||
(String) event.get("previousUsageIntervalId"),
|
||||
(String) event.get("nextUsageIntervalId"),
|
||||
|
|
@ -659,4 +791,12 @@ public class DriverTimelineBuilder {
|
|||
throw new IllegalStateException("Cannot load EPL resource: " + path, e);
|
||||
}
|
||||
}
|
||||
|
||||
private String renderDrivingInterruptionIntervalEventsEpl(int significantDrivingMinutes) {
|
||||
long thresholdSeconds = Math.max(1, significantDrivingMinutes) * 60L;
|
||||
return DRIVING_INTERRUPTION_INTERVAL_EVENTS_EPL_TEMPLATE.replace(
|
||||
"${SIGNIFICANT_DRIVING_THRESHOLD_SECONDS}",
|
||||
Long.toString(thresholdSeconds)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package at.procon.eventhub.tachographfilesession.service;
|
||||
|
||||
import at.procon.eventhub.config.EventHubProperties;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographEsperEventsProcessingRequest;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingRequest;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingResultDto;
|
||||
|
|
@ -12,6 +13,7 @@ import at.procon.eventhub.tachographfilesession.model.ProcessedShiftDrivingEvalu
|
|||
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
|
||||
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
|
||||
|
|
@ -121,6 +123,17 @@ public class TachographFileSessionProcessingService {
|
|||
UUID sessionId,
|
||||
String driverKey
|
||||
) {
|
||||
return getEsperDriverProcessingResults(sessionId, driverKey, null);
|
||||
}
|
||||
|
||||
public TachographEsperDriverProcessingResultDto getEsperDriverProcessingResults(
|
||||
UUID sessionId,
|
||||
String driverKey,
|
||||
TachographEsperEventsProcessingRequest request
|
||||
) {
|
||||
TachographEsperEventsProcessingRequest effectiveRequest = request == null
|
||||
? new TachographEsperEventsProcessingRequest(null, null, null)
|
||||
: request;
|
||||
TachographFileSession session = repository.find(sessionId)
|
||||
.orElseThrow(() -> new TachographFileSessionNotFoundException(sessionId));
|
||||
DriverExtractionSession driver = session.driversByKey().get(driverKey);
|
||||
|
|
@ -129,14 +142,44 @@ public class TachographFileSessionProcessingService {
|
|||
}
|
||||
|
||||
ResolvedDriverTimeline timeline = driverTimelineBuilder.build(session, driver);
|
||||
List<TachographEsperActivityIntervalEvent> activityIntervals =
|
||||
driverTimelineBuilder.buildEsperActivityIntervalEvents(sessionId, driverKey, timeline);
|
||||
List<TachographEsperActivityIntervalEvent> drivingIntervals =
|
||||
driverTimelineBuilder.buildEsperDrivingIntervalEvents(sessionId, driverKey, timeline);
|
||||
List<TachographEsperVehicleUsageIntervalEvent> vehicleUsageIntervals =
|
||||
driverTimelineBuilder.buildEsperVehicleUsageIntervalEvents(timeline);
|
||||
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals =
|
||||
driverTimelineBuilder.buildEsperVuCardAbsentIntervalEvents(timeline);
|
||||
OffsetDateTime requestedFrom = effectiveRequest.occurredFrom() == null ? timeline.loadedFrom() : utc(effectiveRequest.occurredFrom());
|
||||
OffsetDateTime requestedTo = effectiveRequest.occurredTo() == null ? timeline.loadedTo() : utc(effectiveRequest.occurredTo());
|
||||
if (requestedFrom != null && requestedTo != null && requestedTo.isBefore(requestedFrom)) {
|
||||
throw new IllegalArgumentException("occurredTo must not be before occurredFrom.");
|
||||
}
|
||||
int significantDrivingMinutes = resolveEsperSignificantDrivingMinutes(effectiveRequest);
|
||||
|
||||
List<TachographEsperActivityIntervalEvent> activityIntervals = clipEsperActivityIntervalEvents(
|
||||
driverTimelineBuilder.buildEsperActivityIntervalEvents(sessionId, driverKey, timeline),
|
||||
requestedFrom,
|
||||
requestedTo
|
||||
);
|
||||
List<TachographEsperActivityIntervalEvent> drivingIntervals = clipEsperActivityIntervalEvents(
|
||||
driverTimelineBuilder.buildEsperDrivingIntervalEvents(sessionId, driverKey, timeline),
|
||||
requestedFrom,
|
||||
requestedTo
|
||||
);
|
||||
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals =
|
||||
clipEsperDrivingInterruptionIntervalEvents(
|
||||
driverTimelineBuilder.buildEsperDrivingInterruptionIntervalEvents(
|
||||
sessionId,
|
||||
driverKey,
|
||||
timeline,
|
||||
significantDrivingMinutes
|
||||
),
|
||||
requestedFrom,
|
||||
requestedTo
|
||||
);
|
||||
List<TachographEsperVehicleUsageIntervalEvent> vehicleUsageIntervals = clipEsperVehicleUsageIntervalEvents(
|
||||
driverTimelineBuilder.buildEsperVehicleUsageIntervalEvents(timeline),
|
||||
requestedFrom,
|
||||
requestedTo
|
||||
);
|
||||
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals = clipEsperVuCardAbsentIntervalEvents(
|
||||
driverTimelineBuilder.buildEsperVuCardAbsentIntervalEvents(timeline),
|
||||
requestedFrom,
|
||||
requestedTo
|
||||
);
|
||||
|
||||
return new TachographEsperDriverProcessingResultDto(
|
||||
sessionId,
|
||||
|
|
@ -144,18 +187,174 @@ public class TachographFileSessionProcessingService {
|
|||
timeline.sourceKind(),
|
||||
timeline.loadedFrom(),
|
||||
timeline.loadedTo(),
|
||||
requestedFrom,
|
||||
requestedTo,
|
||||
activityIntervals.size(),
|
||||
drivingIntervals.size(),
|
||||
drivingInterruptionIntervals.size(),
|
||||
vehicleUsageIntervals.size(),
|
||||
vuCardAbsentIntervals.size(),
|
||||
activityIntervals,
|
||||
drivingIntervals,
|
||||
drivingInterruptionIntervals,
|
||||
vehicleUsageIntervals,
|
||||
vuCardAbsentIntervals,
|
||||
esperProjectionNotes()
|
||||
);
|
||||
}
|
||||
|
||||
private List<TachographEsperActivityIntervalEvent> clipEsperActivityIntervalEvents(
|
||||
List<TachographEsperActivityIntervalEvent> intervals,
|
||||
OffsetDateTime requestedFrom,
|
||||
OffsetDateTime requestedTo
|
||||
) {
|
||||
if (requestedFrom == null || requestedTo == null) {
|
||||
return List.of();
|
||||
}
|
||||
return intervals.stream()
|
||||
.map(interval -> {
|
||||
OffsetDateTime start = max(interval.startedAt(), requestedFrom);
|
||||
OffsetDateTime end = min(interval.endedAt(), requestedTo);
|
||||
if (!end.isAfter(start)) {
|
||||
return null;
|
||||
}
|
||||
boolean clipped = interval.clippedToRequestedPeriod()
|
||||
|| !start.equals(interval.startedAt())
|
||||
|| !end.equals(interval.endedAt());
|
||||
return new TachographEsperActivityIntervalEvent(
|
||||
interval.sessionId(),
|
||||
interval.driverKey(),
|
||||
interval.intervalId(),
|
||||
interval.activityType(),
|
||||
interval.cardSlot(),
|
||||
interval.cardStatus(),
|
||||
interval.drivingStatus(),
|
||||
interval.registrationKey(),
|
||||
interval.vehicleKey(),
|
||||
interval.sourceKind(),
|
||||
start,
|
||||
end,
|
||||
Duration.between(start, end).getSeconds(),
|
||||
interval.sourceIntervalIds(),
|
||||
interval.synthetic(),
|
||||
clipped,
|
||||
interval.level()
|
||||
);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(TachographEsperActivityIntervalEvent::startedAt)
|
||||
.thenComparing(TachographEsperActivityIntervalEvent::endedAt)
|
||||
.thenComparing(TachographEsperActivityIntervalEvent::activityType, Comparator.nullsLast(String::compareTo)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<TachographEsperVehicleUsageIntervalEvent> clipEsperVehicleUsageIntervalEvents(
|
||||
List<TachographEsperVehicleUsageIntervalEvent> intervals,
|
||||
OffsetDateTime requestedFrom,
|
||||
OffsetDateTime requestedTo
|
||||
) {
|
||||
if (requestedFrom == null || requestedTo == null) {
|
||||
return List.of();
|
||||
}
|
||||
return intervals.stream()
|
||||
.map(interval -> {
|
||||
OffsetDateTime start = max(interval.startedAt(), requestedFrom);
|
||||
OffsetDateTime end = min(interval.endedAt(), requestedTo);
|
||||
if (!end.isAfter(start)) {
|
||||
return null;
|
||||
}
|
||||
boolean startClipped = !start.equals(interval.startedAt());
|
||||
boolean endClipped = !end.equals(interval.endedAt());
|
||||
return new TachographEsperVehicleUsageIntervalEvent(
|
||||
interval.sessionId(),
|
||||
interval.driverKey(),
|
||||
interval.intervalId(),
|
||||
start,
|
||||
end,
|
||||
Duration.between(start, end).getSeconds(),
|
||||
startClipped ? null : interval.odometerBeginKm(),
|
||||
endClipped ? null : interval.odometerEndKm(),
|
||||
interval.registrationKey(),
|
||||
interval.vehicleKey(),
|
||||
interval.sourceKind(),
|
||||
interval.sourceIntervalIds()
|
||||
);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(TachographEsperVehicleUsageIntervalEvent::startedAt)
|
||||
.thenComparing(TachographEsperVehicleUsageIntervalEvent::endedAt)
|
||||
.thenComparing(TachographEsperVehicleUsageIntervalEvent::intervalId, Comparator.nullsLast(String::compareTo)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<TachographEsperDrivingInterruptionIntervalEvent> clipEsperDrivingInterruptionIntervalEvents(
|
||||
List<TachographEsperDrivingInterruptionIntervalEvent> intervals,
|
||||
OffsetDateTime requestedFrom,
|
||||
OffsetDateTime requestedTo
|
||||
) {
|
||||
if (requestedFrom == null || requestedTo == null) {
|
||||
return List.of();
|
||||
}
|
||||
return intervals.stream()
|
||||
.map(interval -> {
|
||||
OffsetDateTime start = max(interval.startedAt(), requestedFrom);
|
||||
OffsetDateTime end = min(interval.endedAt(), requestedTo);
|
||||
if (!end.isAfter(start)) {
|
||||
return null;
|
||||
}
|
||||
return new TachographEsperDrivingInterruptionIntervalEvent(
|
||||
interval.sessionId(),
|
||||
interval.driverKey(),
|
||||
start,
|
||||
end,
|
||||
Duration.between(start, end).getSeconds(),
|
||||
interval.previousDrivingSourceIntervalId(),
|
||||
interval.nextDrivingSourceIntervalId(),
|
||||
interval.previousVehicleKey(),
|
||||
interval.nextVehicleKey()
|
||||
);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(TachographEsperDrivingInterruptionIntervalEvent::startedAt)
|
||||
.thenComparing(TachographEsperDrivingInterruptionIntervalEvent::endedAt))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<TachographEsperVuCardAbsentIntervalEvent> clipEsperVuCardAbsentIntervalEvents(
|
||||
List<TachographEsperVuCardAbsentIntervalEvent> intervals,
|
||||
OffsetDateTime requestedFrom,
|
||||
OffsetDateTime requestedTo
|
||||
) {
|
||||
if (requestedFrom == null || requestedTo == null) {
|
||||
return List.of();
|
||||
}
|
||||
return intervals.stream()
|
||||
.map(interval -> {
|
||||
OffsetDateTime start = max(interval.startedAt(), requestedFrom);
|
||||
OffsetDateTime end = min(interval.endedAt(), requestedTo);
|
||||
if (!end.isAfter(start)) {
|
||||
return null;
|
||||
}
|
||||
return new TachographEsperVuCardAbsentIntervalEvent(
|
||||
interval.sessionId(),
|
||||
interval.driverKey(),
|
||||
start,
|
||||
end,
|
||||
Duration.between(start, end).getSeconds(),
|
||||
interval.previousUsageIntervalId(),
|
||||
interval.nextUsageIntervalId(),
|
||||
interval.previousRegistrationKey(),
|
||||
interval.nextRegistrationKey(),
|
||||
interval.previousVehicleKey(),
|
||||
interval.nextVehicleKey()
|
||||
);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(TachographEsperVuCardAbsentIntervalEvent::startedAt)
|
||||
.thenComparing(TachographEsperVuCardAbsentIntervalEvent::endedAt))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<ResolvedActivityInterval> synthesizeUnknownGaps(
|
||||
List<ResolvedActivityInterval> knownIntervals,
|
||||
Duration gapDetectionTolerance
|
||||
|
|
@ -694,6 +893,12 @@ public class TachographFileSessionProcessingService {
|
|||
: request.gapDetectionToleranceSeconds();
|
||||
}
|
||||
|
||||
private int resolveEsperSignificantDrivingMinutes(TachographEsperEventsProcessingRequest request) {
|
||||
return request.significantDrivingMinutes() == null
|
||||
? properties.getTachographFileSession().getProcessing().getSignificantDrivingMinutes()
|
||||
: request.significantDrivingMinutes();
|
||||
}
|
||||
|
||||
private List<String> notes() {
|
||||
return List.of(
|
||||
"This endpoint evaluates operating periods from the in-memory tachograph file-session model.",
|
||||
|
|
@ -707,7 +912,10 @@ public class TachographFileSessionProcessingService {
|
|||
return List.of(
|
||||
"This endpoint returns Esper-backed per-driver interval projections from the in-memory tachograph file-session model.",
|
||||
"Driving intervals are a filtered projection of activity intervals where activityType = DRIVE.",
|
||||
"VU card-absent intervals are gaps between consecutive normalized vehicle-usage intervals for the same driver."
|
||||
"Driving interruption intervals are gaps between consecutive driving intervals longer than the configured significant-driving threshold.",
|
||||
"VU card-absent intervals are gaps between consecutive normalized vehicle-usage intervals for the same driver.",
|
||||
"occurredFrom and occurredTo clip the returned interval projections to the requested UTC time window.",
|
||||
"Vehicle-usage intervals clear clipped odometer endpoints because boundary odometer values cannot be recomputed safely from the source interval."
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
create schema SignificantDrivingInterval(
|
||||
sessionId java.util.UUID,
|
||||
driverKey string,
|
||||
firstSourceIntervalId string,
|
||||
lastSourceIntervalId string,
|
||||
startedAtEpochSecond long,
|
||||
endedAtEpochSecond long,
|
||||
durationSeconds long,
|
||||
vehicleKey string
|
||||
);
|
||||
|
||||
create schema DrivingInterruptionInterval(
|
||||
sessionId java.util.UUID,
|
||||
driverKey string,
|
||||
startedAtEpochSecond long,
|
||||
endedAtEpochSecond long,
|
||||
durationSeconds long,
|
||||
previousDrivingSourceIntervalId string,
|
||||
nextDrivingSourceIntervalId string,
|
||||
previousVehicleKey string,
|
||||
nextVehicleKey string
|
||||
);
|
||||
|
||||
insert into SignificantDrivingInterval
|
||||
select
|
||||
sessionId,
|
||||
driverKey,
|
||||
firstSourceIntervalId,
|
||||
lastSourceIntervalId,
|
||||
startedAtEpochSecond,
|
||||
endedAtEpochSecond,
|
||||
durationSeconds,
|
||||
vehicleKey
|
||||
from TachographActivityIntervalInputEvent(activityType = 'DRIVE', durationSeconds > ${SIGNIFICANT_DRIVING_THRESHOLD_SECONDS});
|
||||
|
||||
create window PreviousSignificantDrivingInterval#unique(driverKey) as SignificantDrivingInterval;
|
||||
|
||||
on SignificantDrivingInterval as next
|
||||
insert into DrivingInterruptionInterval
|
||||
select
|
||||
priorInterval.sessionId as sessionId,
|
||||
priorInterval.driverKey as driverKey,
|
||||
priorInterval.endedAtEpochSecond as startedAtEpochSecond,
|
||||
next.startedAtEpochSecond as endedAtEpochSecond,
|
||||
next.startedAtEpochSecond - priorInterval.endedAtEpochSecond as durationSeconds,
|
||||
priorInterval.lastSourceIntervalId as previousDrivingSourceIntervalId,
|
||||
next.firstSourceIntervalId as nextDrivingSourceIntervalId,
|
||||
priorInterval.vehicleKey as previousVehicleKey,
|
||||
next.vehicleKey as nextVehicleKey
|
||||
from PreviousSignificantDrivingInterval as priorInterval
|
||||
where priorInterval.driverKey = next.driverKey
|
||||
and next.startedAtEpochSecond > priorInterval.endedAtEpochSecond;
|
||||
|
||||
@Priority(20)
|
||||
on SignificantDrivingInterval
|
||||
delete from PreviousSignificantDrivingInterval;
|
||||
|
||||
@Priority(10)
|
||||
on SignificantDrivingInterval as current
|
||||
insert into PreviousSignificantDrivingInterval
|
||||
select *;
|
||||
|
||||
@name('drivingInterruptionIntervals')
|
||||
select * from DrivingInterruptionInterval;
|
||||
|
|
@ -3,8 +3,8 @@ create context PerDriver partition by driverKey from TachographVehicleUsageInter
|
|||
create schema VuCardAbsentInterval(
|
||||
sessionId java.util.UUID,
|
||||
driverKey string,
|
||||
startedAt java.time.OffsetDateTime,
|
||||
endedAt java.time.OffsetDateTime,
|
||||
startedAtEpochSecond long,
|
||||
endedAtEpochSecond long,
|
||||
durationSeconds long,
|
||||
previousUsageIntervalId string,
|
||||
nextUsageIntervalId string,
|
||||
|
|
@ -17,38 +17,37 @@ create schema VuCardAbsentInterval(
|
|||
context PerDriver
|
||||
create window PreviousVehicleUsageInterval#lastevent as TachographVehicleUsageIntervalInputEvent;
|
||||
|
||||
context PerDriver
|
||||
@Priority(30)
|
||||
context PerDriver
|
||||
on TachographVehicleUsageIntervalInputEvent as next
|
||||
insert into VuCardAbsentInterval
|
||||
select
|
||||
prev.sessionId as sessionId,
|
||||
prev.driverKey as driverKey,
|
||||
prev.endedAt.plusSeconds(1) as startedAt,
|
||||
next.startedAt as endedAt,
|
||||
java.time.Duration.between(prev.endedAt.plusSeconds(1), next.startedAt).getSeconds() as durationSeconds,
|
||||
prev.intervalId as previousUsageIntervalId,
|
||||
next.intervalId as nextUsageIntervalId,
|
||||
prev.registrationKey as previousRegistrationKey,
|
||||
priorInterval.sessionId as sessionId,
|
||||
priorInterval.driverKey as driverKey,
|
||||
priorInterval.endedAtEpochSecond + 1L as startedAtEpochSecond,
|
||||
next.startedAtEpochSecond as endedAtEpochSecond,
|
||||
next.startedAtEpochSecond - (priorInterval.endedAtEpochSecond + 1L) as durationSeconds,
|
||||
priorInterval.lastSourceIntervalId as previousUsageIntervalId,
|
||||
next.firstSourceIntervalId as nextUsageIntervalId,
|
||||
priorInterval.registrationKey as previousRegistrationKey,
|
||||
next.registrationKey as nextRegistrationKey,
|
||||
prev.vehicleKey as previousVehicleKey,
|
||||
priorInterval.vehicleKey as previousVehicleKey,
|
||||
next.vehicleKey as nextVehicleKey
|
||||
from PreviousVehicleUsageInterval as prev
|
||||
where prev.endedAt is not null
|
||||
from PreviousVehicleUsageInterval as priorInterval
|
||||
where priorInterval.endedAt is not null
|
||||
and next.startedAt is not null
|
||||
and next.startedAt.isAfter(prev.endedAt.plusSeconds(1));
|
||||
and next.startedAtEpochSecond > priorInterval.endedAtEpochSecond + 1L;
|
||||
|
||||
context PerDriver
|
||||
@Priority(20)
|
||||
context PerDriver
|
||||
on TachographVehicleUsageIntervalInputEvent
|
||||
delete from PreviousVehicleUsageInterval;
|
||||
|
||||
context PerDriver
|
||||
@Priority(10)
|
||||
context PerDriver
|
||||
on TachographVehicleUsageIntervalInputEvent as current
|
||||
insert into PreviousVehicleUsageInterval
|
||||
select *;
|
||||
|
||||
@name('vuCardAbsentIntervals')
|
||||
context PerDriver
|
||||
select * from VuCardAbsentInterval;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import at.procon.eventhub.tachographfilesession.dto.CreateTachographFileSessionResponse;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographEsperEventsProcessingRequest;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographFileDriverDetailDto;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingResultDto;
|
||||
|
|
@ -19,6 +20,7 @@ import at.procon.eventhub.tachographfilesession.dto.TachographFileSessionListDri
|
|||
import at.procon.eventhub.tachographfilesession.dto.TachographFileSessionSummaryDto;
|
||||
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionProcessingService;
|
||||
|
|
@ -63,15 +65,22 @@ class TachographFileSessionControllerTest {
|
|||
when(service.getSession(sessionId)).thenReturn(summary);
|
||||
when(service.listDrivers(sessionId)).thenReturn(new TachographFileSessionListDriversResponse(sessionId, List.of(driver)));
|
||||
when(service.getDriver(sessionId, "12:123")).thenReturn(new TachographFileDriverDetailDto(sessionId, "12:123", null, null, List.of(), List.of(), List.of(), List.of(), List.of(), List.of()));
|
||||
when(processingService.getEsperDriverProcessingResults(sessionId, "12:123"))
|
||||
when(processingService.getEsperDriverProcessingResults(
|
||||
eq(sessionId),
|
||||
eq("12:123"),
|
||||
org.mockito.ArgumentMatchers.any(TachographEsperEventsProcessingRequest.class)
|
||||
))
|
||||
.thenReturn(new TachographEsperDriverProcessingResultDto(
|
||||
sessionId,
|
||||
"12:123",
|
||||
"DRIVER_CARD",
|
||||
OffsetDateTime.parse("2026-05-12T08:00:00Z"),
|
||||
OffsetDateTime.parse("2026-05-12T12:00:00Z"),
|
||||
OffsetDateTime.parse("2026-05-12T08:30:00Z"),
|
||||
OffsetDateTime.parse("2026-05-12T11:30:00Z"),
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
List.of(new TachographEsperActivityIntervalEvent(
|
||||
|
|
@ -112,6 +121,17 @@ class TachographFileSessionControllerTest {
|
|||
false,
|
||||
"RAW_INTERVAL"
|
||||
)),
|
||||
List.of(new TachographEsperDrivingInterruptionIntervalEvent(
|
||||
sessionId,
|
||||
"12:123",
|
||||
OffsetDateTime.parse("2026-05-12T10:00:00Z"),
|
||||
OffsetDateTime.parse("2026-05-12T10:30:00Z"),
|
||||
1800L,
|
||||
"ACT-2",
|
||||
"ACT-3",
|
||||
"VIN-1",
|
||||
"VIN-2"
|
||||
)),
|
||||
List.of(new TachographEsperVehicleUsageIntervalEvent(
|
||||
sessionId,
|
||||
"12:123",
|
||||
|
|
@ -187,12 +207,25 @@ class TachographFileSessionControllerTest {
|
|||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.driverKey").value("12:123"));
|
||||
|
||||
mockMvc.perform(get("/api/eventhub/tachograph-file-sessions/{sessionId}/drivers/{driverKey}/processing/esper-events", sessionId, "12:123"))
|
||||
mockMvc.perform(post("/api/eventhub/tachograph-file-sessions/{sessionId}/drivers/{driverKey}/processing/esper-events", sessionId, "12:123")
|
||||
.contentType("application/json")
|
||||
.content("""
|
||||
{
|
||||
"occurredFrom": "2026-05-12T08:30:00Z",
|
||||
"occurredTo": "2026-05-12T11:30:00Z",
|
||||
"significantDrivingMinutes": 3
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.driverKey").value("12:123"))
|
||||
.andExpect(jsonPath("$.sourceKind").value("DRIVER_CARD"))
|
||||
.andExpect(jsonPath("$.requestedFrom").value("2026-05-12T08:30:00Z"))
|
||||
.andExpect(jsonPath("$.requestedTo").value("2026-05-12T11:30:00Z"))
|
||||
.andExpect(jsonPath("$.activityIntervalCount").value(2))
|
||||
.andExpect(jsonPath("$.drivingInterruptionIntervalCount").value(1))
|
||||
.andExpect(jsonPath("$.vuCardAbsentIntervalCount").value(1))
|
||||
.andExpect(jsonPath("$.drivingInterruptionIntervals[0].previousVehicleKey").value("VIN-1"))
|
||||
.andExpect(jsonPath("$.drivingInterruptionIntervals[0].nextVehicleKey").value("VIN-2"))
|
||||
.andExpect(jsonPath("$.drivingIntervals[0].activityType").value("DRIVE"));
|
||||
|
||||
mockMvc.perform(post("/api/eventhub/tachograph-file-sessions/{sessionId}/drivers/{driverKey}/processing/operating-periods", sessionId, "12:123")
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ 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.TachographEsperActivityIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
|
||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||
|
|
@ -375,4 +376,79 @@ class DriverTimelineBuilderTest {
|
|||
assertThat(absentIntervals.get(0).previousVehicleKey()).isEqualTo("VIN-1");
|
||||
assertThat(absentIntervals.get(0).nextVehicleKey()).isEqualTo("VIN-2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildsEsperDrivingInterruptionIntervalEventsFromSignificantDrivingGaps() {
|
||||
DriverExtractionSession driver = new DriverExtractionSession(
|
||||
"12:123",
|
||||
null,
|
||||
null,
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(
|
||||
new ExtractedCardActivityInterval(
|
||||
"ACT-1",
|
||||
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||
OffsetDateTime.parse("2026-05-01T08:02:00Z"),
|
||||
"DRIVE",
|
||||
"DRIVER",
|
||||
"INSERTED",
|
||||
"SINGLE",
|
||||
"12:REG-1",
|
||||
"VIN-1",
|
||||
"a"
|
||||
),
|
||||
new ExtractedCardActivityInterval(
|
||||
"ACT-2",
|
||||
OffsetDateTime.parse("2026-05-01T08:02:00Z"),
|
||||
OffsetDateTime.parse("2026-05-01T08:10:00Z"),
|
||||
"WORK",
|
||||
"DRIVER",
|
||||
"INSERTED",
|
||||
"SINGLE",
|
||||
"12:REG-1",
|
||||
"VIN-1",
|
||||
"b"
|
||||
),
|
||||
new ExtractedCardActivityInterval(
|
||||
"ACT-3",
|
||||
OffsetDateTime.parse("2026-05-01T08:10:00Z"),
|
||||
OffsetDateTime.parse("2026-05-01T08:15:00Z"),
|
||||
"DRIVE",
|
||||
"DRIVER",
|
||||
"INSERTED",
|
||||
"SINGLE",
|
||||
"12:REG-2",
|
||||
"VIN-2",
|
||||
"c"
|
||||
)
|
||||
),
|
||||
List.of(),
|
||||
List.of()
|
||||
);
|
||||
TachographFileSession session = new TachographFileSession(
|
||||
UUID.randomUUID(),
|
||||
new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 3, "42", "b", true, null),
|
||||
Map.of(driver.driverKey(), driver),
|
||||
new ExtractionStats(1, 3, 0, 0, 0, 0),
|
||||
List.of(),
|
||||
Instant.now(),
|
||||
Instant.now().plus(4, ChronoUnit.HOURS)
|
||||
);
|
||||
|
||||
List<TachographEsperDrivingInterruptionIntervalEvent> interruptions =
|
||||
builder.buildEsperDrivingInterruptionIntervalEvents(session, driver, 1);
|
||||
|
||||
assertThat(interruptions).hasSize(1);
|
||||
assertThat(interruptions.get(0).sessionId()).isEqualTo(session.sessionId());
|
||||
assertThat(interruptions.get(0).driverKey()).isEqualTo("12:123");
|
||||
assertThat(interruptions.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:02:00Z"));
|
||||
assertThat(interruptions.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:10:00Z"));
|
||||
assertThat(interruptions.get(0).durationSeconds()).isEqualTo(480L);
|
||||
assertThat(interruptions.get(0).previousDrivingSourceIntervalId()).isEqualTo("ACT-1");
|
||||
assertThat(interruptions.get(0).nextDrivingSourceIntervalId()).isEqualTo("ACT-3");
|
||||
assertThat(interruptions.get(0).previousVehicleKey()).isEqualTo("VIN-1");
|
||||
assertThat(interruptions.get(0).nextVehicleKey()).isEqualTo("VIN-2");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package at.procon.eventhub.tachographfilesession.service;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import at.procon.eventhub.config.EventHubProperties;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographEsperEventsProcessingRequest;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingRequest;
|
||||
import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingResultDto;
|
||||
|
|
@ -84,12 +85,99 @@ class TachographFileSessionProcessingServiceTest {
|
|||
assertThat(result.sourceKind()).isEqualTo("DRIVER_CARD");
|
||||
assertThat(result.activityIntervalCount()).isEqualTo(2);
|
||||
assertThat(result.drivingIntervalCount()).isEqualTo(1);
|
||||
assertThat(result.drivingInterruptionIntervalCount()).isEqualTo(0);
|
||||
assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2);
|
||||
assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
|
||||
assertThat(result.vuCardAbsentIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:01Z"));
|
||||
assertThat(result.vuCardAbsentIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T12:00:00Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void appliesOccurredWindowToEsperDriverProcessingResults() {
|
||||
EventHubProperties properties = new EventHubProperties();
|
||||
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
||||
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
|
||||
repository,
|
||||
new DriverTimelineBuilder(),
|
||||
properties
|
||||
);
|
||||
|
||||
DriverExtractionSession driver = new DriverExtractionSession(
|
||||
"12:123",
|
||||
null,
|
||||
null,
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(
|
||||
new ExtractedCardVehicleUsageInterval(
|
||||
"CVU-1",
|
||||
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||
OffsetDateTime.parse("2026-05-01T11:00:00Z"),
|
||||
100L,
|
||||
200L,
|
||||
"12:REG-1",
|
||||
"VIN-1",
|
||||
"vu-1"
|
||||
),
|
||||
new ExtractedCardVehicleUsageInterval(
|
||||
"CVU-2",
|
||||
OffsetDateTime.parse("2026-05-01T12:00:00Z"),
|
||||
OffsetDateTime.parse("2026-05-01T13:00:00Z"),
|
||||
201L,
|
||||
260L,
|
||||
"12:REG-2",
|
||||
"VIN-2",
|
||||
"vu-2"
|
||||
)
|
||||
),
|
||||
List.of(
|
||||
new ExtractedCardActivityInterval("ACT-1", OffsetDateTime.parse("2026-05-01T08:30:00Z"), OffsetDateTime.parse("2026-05-01T09:00:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "a"),
|
||||
new ExtractedCardActivityInterval("ACT-2", OffsetDateTime.parse("2026-05-01T09:00:00Z"), OffsetDateTime.parse("2026-05-01T10:00:00Z"), "WORK", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "b"),
|
||||
new ExtractedCardActivityInterval("ACT-3", OffsetDateTime.parse("2026-05-01T10:00:00Z"), OffsetDateTime.parse("2026-05-01T10:05:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-2", "VIN-2", "c")
|
||||
),
|
||||
List.of(),
|
||||
List.of()
|
||||
);
|
||||
TachographFileSession session = new TachographFileSession(
|
||||
UUID.randomUUID(),
|
||||
new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 3, "42", "b", true, null),
|
||||
Map.of(driver.driverKey(), driver),
|
||||
new ExtractionStats(1, 2, 2, 1, 1, 0),
|
||||
List.of(),
|
||||
Instant.now(),
|
||||
Instant.now().plus(4, ChronoUnit.HOURS)
|
||||
);
|
||||
repository.save(session);
|
||||
|
||||
TachographEsperDriverProcessingResultDto result = service.getEsperDriverProcessingResults(
|
||||
session.sessionId(),
|
||||
driver.driverKey(),
|
||||
new TachographEsperEventsProcessingRequest(
|
||||
OffsetDateTime.parse("2026-05-01T08:45:00Z"),
|
||||
OffsetDateTime.parse("2026-05-01T12:30:00Z"),
|
||||
3
|
||||
)
|
||||
);
|
||||
|
||||
assertThat(result.requestedFrom()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:45:00Z"));
|
||||
assertThat(result.requestedTo()).isEqualTo(OffsetDateTime.parse("2026-05-01T12:30:00Z"));
|
||||
assertThat(result.activityIntervalCount()).isEqualTo(3);
|
||||
assertThat(result.activityIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:45:00Z"));
|
||||
assertThat(result.activityIntervals().get(0).clippedToRequestedPeriod()).isTrue();
|
||||
assertThat(result.drivingIntervalCount()).isEqualTo(2);
|
||||
assertThat(result.drivingInterruptionIntervalCount()).isEqualTo(1);
|
||||
assertThat(result.drivingInterruptionIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T09:00:00Z"));
|
||||
assertThat(result.drivingInterruptionIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
||||
assertThat(result.drivingInterruptionIntervals().get(0).previousVehicleKey()).isEqualTo("VIN-1");
|
||||
assertThat(result.drivingInterruptionIntervals().get(0).nextVehicleKey()).isEqualTo("VIN-2");
|
||||
assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2);
|
||||
assertThat(result.vehicleUsageIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:45:00Z"));
|
||||
assertThat(result.vehicleUsageIntervals().get(0).odometerBeginKm()).isNull();
|
||||
assertThat(result.vehicleUsageIntervals().get(1).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T12:30:00Z"));
|
||||
assertThat(result.vehicleUsageIntervals().get(1).odometerEndKm()).isNull();
|
||||
assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void evaluatesOperatingPeriodsFromSessionTimeline() {
|
||||
EventHubProperties properties = new EventHubProperties();
|
||||
|
|
|
|||
Loading…
Reference in New Issue