Simplify rest card absence metrics
This commit is contained in:
parent
f1f36e2204
commit
1128bd3f56
|
|
@ -0,0 +1,63 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.api;
|
||||||
|
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.CreateTachographCompositeSessionRequest;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.CreateTachographCompositeSessionResponse;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographCompositeDriverEventsResponse;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographCompositeSessionListDriversResponse;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographCompositeSessionSummaryDto;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographCompositeSessionService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/eventhub/tachograph-composite-sessions")
|
||||||
|
public class TachographCompositeSessionController {
|
||||||
|
|
||||||
|
private final TachographCompositeSessionService service;
|
||||||
|
|
||||||
|
public TachographCompositeSessionController(TachographCompositeSessionService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<CreateTachographCompositeSessionResponse> createCompositeSession(
|
||||||
|
@Valid @RequestBody CreateTachographCompositeSessionRequest request
|
||||||
|
) {
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(service.createCompositeSession(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{compositeSessionId}")
|
||||||
|
public ResponseEntity<TachographCompositeSessionSummaryDto> getCompositeSession(@PathVariable UUID compositeSessionId) {
|
||||||
|
return ResponseEntity.ok(service.getCompositeSession(compositeSessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{compositeSessionId}/drivers")
|
||||||
|
public ResponseEntity<TachographCompositeSessionListDriversResponse> listDrivers(@PathVariable UUID compositeSessionId) {
|
||||||
|
return ResponseEntity.ok(service.listDrivers(compositeSessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{compositeSessionId}/drivers/{driverKey}/events")
|
||||||
|
public ResponseEntity<TachographCompositeDriverEventsResponse> getMergedDriverEvents(
|
||||||
|
@PathVariable UUID compositeSessionId,
|
||||||
|
@PathVariable String driverKey
|
||||||
|
) {
|
||||||
|
return ResponseEntity.ok(service.getMergedDriverEvents(compositeSessionId, driverKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{compositeSessionId}/drivers/{driverKey}/timeline")
|
||||||
|
public ResponseEntity<ResolvedDriverTimeline> getMergedDriverTimeline(
|
||||||
|
@PathVariable UUID compositeSessionId,
|
||||||
|
@PathVariable String driverKey
|
||||||
|
) {
|
||||||
|
return ResponseEntity.ok(service.getMergedDriverTimeline(compositeSessionId, driverKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record CreateTachographCompositeSessionRequest(
|
||||||
|
@NotEmpty List<UUID> sessionIds,
|
||||||
|
String label
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.dto;
|
||||||
|
|
||||||
|
public record CreateTachographCompositeSessionResponse(
|
||||||
|
TachographCompositeSessionSummaryDto session
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.dto;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record TachographCompositeDriverEventsResponse(
|
||||||
|
UUID compositeSessionId,
|
||||||
|
String driverKey,
|
||||||
|
List<UUID> sourceSessionIds,
|
||||||
|
int eventCount,
|
||||||
|
List<EventHubEventDto> events
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record TachographCompositeSessionListDriversResponse(
|
||||||
|
UUID compositeSessionId,
|
||||||
|
List<TachographFileDriverSummaryDto> drivers
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.dto;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record TachographCompositeSessionSummaryDto(
|
||||||
|
UUID compositeSessionId,
|
||||||
|
String tenantKey,
|
||||||
|
String label,
|
||||||
|
List<UUID> memberSessionIds,
|
||||||
|
List<TachographFileDriverSummaryDto> drivers,
|
||||||
|
Instant createdAt
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.model;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record TachographCompositeSession(
|
||||||
|
UUID compositeSessionId,
|
||||||
|
String tenantKey,
|
||||||
|
String label,
|
||||||
|
List<UUID> memberSessionIds,
|
||||||
|
Instant createdAt
|
||||||
|
) {
|
||||||
|
public TachographCompositeSession {
|
||||||
|
memberSessionIds = memberSessionIds == null ? List.of() : List.copyOf(memberSessionIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,10 +9,8 @@ public record TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent(
|
||||||
OffsetDateTime startedAt,
|
OffsetDateTime startedAt,
|
||||||
OffsetDateTime endedAt,
|
OffsetDateTime endedAt,
|
||||||
long durationSeconds,
|
long durationSeconds,
|
||||||
long cardPresentDurationSeconds,
|
long cardAbsentDurationSeconds,
|
||||||
double cardPresentCoveragePercent,
|
double cardAbsentCoveragePercent,
|
||||||
long unknownDurationSeconds,
|
|
||||||
double unknownCoveragePercent,
|
|
||||||
String previousDrivingSourceIntervalId,
|
String previousDrivingSourceIntervalId,
|
||||||
String nextDrivingSourceIntervalId,
|
String nextDrivingSourceIntervalId,
|
||||||
String previousRegistrationKey,
|
String previousRegistrationKey,
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,8 @@ public record TachographEsperPotentialHomeOvernightStayIntervalEvent(
|
||||||
OffsetDateTime startedAt,
|
OffsetDateTime startedAt,
|
||||||
OffsetDateTime endedAt,
|
OffsetDateTime endedAt,
|
||||||
long durationSeconds,
|
long durationSeconds,
|
||||||
long cardPresentDurationSeconds,
|
long cardAbsentDurationSeconds,
|
||||||
double cardPresentCoveragePercent,
|
double cardAbsentCoveragePercent,
|
||||||
long unknownDurationSeconds,
|
|
||||||
double unknownCoveragePercent,
|
|
||||||
String previousDrivingSourceIntervalId,
|
String previousDrivingSourceIntervalId,
|
||||||
String nextDrivingSourceIntervalId,
|
String nextDrivingSourceIntervalId,
|
||||||
String previousRegistrationKey,
|
String previousRegistrationKey,
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,8 @@ public record TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
|
||||||
OffsetDateTime startedAt,
|
OffsetDateTime startedAt,
|
||||||
OffsetDateTime endedAt,
|
OffsetDateTime endedAt,
|
||||||
long durationSeconds,
|
long durationSeconds,
|
||||||
long cardPresentDurationSeconds,
|
long cardAbsentDurationSeconds,
|
||||||
double cardPresentCoveragePercent,
|
double cardAbsentCoveragePercent,
|
||||||
long unknownDurationSeconds,
|
|
||||||
double unknownCoveragePercent,
|
|
||||||
String previousDrivingSourceIntervalId,
|
String previousDrivingSourceIntervalId,
|
||||||
String nextDrivingSourceIntervalId,
|
String nextDrivingSourceIntervalId,
|
||||||
String previousRegistrationKey,
|
String previousRegistrationKey,
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,7 @@ public record TachographEsperPotentialInVehicleTripIntervalEvent(
|
||||||
String vehicleKey,
|
String vehicleKey,
|
||||||
int containedPotentialInVehicleOvernightStayIntervalCount,
|
int containedPotentialInVehicleOvernightStayIntervalCount,
|
||||||
long containedPotentialInVehicleOvernightStayDurationSeconds,
|
long containedPotentialInVehicleOvernightStayDurationSeconds,
|
||||||
long containedCardPresentDurationSeconds,
|
long containedCardAbsentDurationSeconds,
|
||||||
long containedUnknownDurationSeconds,
|
|
||||||
OffsetDateTime firstPotentialInVehicleOvernightStayStartedAt,
|
OffsetDateTime firstPotentialInVehicleOvernightStayStartedAt,
|
||||||
OffsetDateTime lastPotentialInVehicleOvernightStayEndedAt,
|
OffsetDateTime lastPotentialInVehicleOvernightStayEndedAt,
|
||||||
String firstPreviousDrivingSourceIntervalId,
|
String firstPreviousDrivingSourceIntervalId,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.service;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class DriverNotFoundInCompositeSessionException extends RuntimeException {
|
||||||
|
|
||||||
|
public DriverNotFoundInCompositeSessionException(UUID compositeSessionId, String driverKey) {
|
||||||
|
super("Driver '%s' was not found in tachograph composite session '%s'.".formatted(driverKey, compositeSessionId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -997,10 +997,8 @@ public class DriverTimelineBuilder {
|
||||||
(OffsetDateTime) event.get("startedAt"),
|
(OffsetDateTime) event.get("startedAt"),
|
||||||
(OffsetDateTime) event.get("endedAt"),
|
(OffsetDateTime) event.get("endedAt"),
|
||||||
(Long) event.get("durationSeconds"),
|
(Long) event.get("durationSeconds"),
|
||||||
0L,
|
(Long) event.get("cardAbsentDurationSeconds"),
|
||||||
0.0d,
|
(Double) event.get("cardAbsentCoveragePercent"),
|
||||||
(Long) event.get("unknownDurationSeconds"),
|
|
||||||
(Double) event.get("unknownCoveragePercent"),
|
|
||||||
(String) event.get("previousDrivingSourceIntervalId"),
|
(String) event.get("previousDrivingSourceIntervalId"),
|
||||||
(String) event.get("nextDrivingSourceIntervalId"),
|
(String) event.get("nextDrivingSourceIntervalId"),
|
||||||
(String) event.get("previousRegistrationKey"),
|
(String) event.get("previousRegistrationKey"),
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import java.util.Comparator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
@ -76,6 +77,54 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
|
||||||
|
TachographFileSession session,
|
||||||
|
int significantDrivingMinutes,
|
||||||
|
int minimumRestPeriodMinutes
|
||||||
|
) {
|
||||||
|
if (session == null || session.driversByKey() == null || session.driversByKey().isEmpty()) {
|
||||||
|
return emptyBundle();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, Object>> activityInputEvents = new ArrayList<>();
|
||||||
|
List<Map<String, Object>> vehicleUsageInputEvents = new ArrayList<>();
|
||||||
|
List<Map<String, Object>> supportGeoInputEvents = new ArrayList<>();
|
||||||
|
|
||||||
|
for (DriverExtractionSession driverSession : session.driversByKey().values()) {
|
||||||
|
if (driverSession == null || driverSession.driverKey() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ResolvedDriverTimeline timeline = driverTimelineBuilder.build(session, driverSession);
|
||||||
|
if (timeline == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (ResolvedActivityInterval interval : safeList(timeline.activityIntervals())) {
|
||||||
|
activityInputEvents.add(toActivityIntervalInputMap(
|
||||||
|
session.sessionId(),
|
||||||
|
driverSession.driverKey(),
|
||||||
|
interval
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for (ResolvedVehicleUsageInterval interval : safeList(timeline.vehicleUsageIntervals())) {
|
||||||
|
vehicleUsageInputEvents.add(toVehicleUsageIntervalInputMap(interval));
|
||||||
|
}
|
||||||
|
for (ExtractedSupportEvent supportEvent : safeList(timeline.supportEvents())) {
|
||||||
|
Map<String, Object> supportGeoEvidence = toSupportGeoEvidenceInputMap(session.sessionId(), supportEvent);
|
||||||
|
if (supportGeoEvidence != null) {
|
||||||
|
supportGeoInputEvents.add(supportGeoEvidence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildEsperDrivingDerivedProjectionBundle(
|
||||||
|
activityInputEvents,
|
||||||
|
vehicleUsageInputEvents,
|
||||||
|
supportGeoInputEvents,
|
||||||
|
significantDrivingMinutes,
|
||||||
|
minimumRestPeriodMinutes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
|
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
|
||||||
UUID sessionId,
|
UUID sessionId,
|
||||||
String driverKey,
|
String driverKey,
|
||||||
|
|
@ -87,27 +136,23 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
return emptyBundle();
|
return emptyBundle();
|
||||||
}
|
}
|
||||||
return buildEsperDrivingDerivedProjectionBundle(
|
return buildEsperDrivingDerivedProjectionBundle(
|
||||||
sessionId,
|
buildActivityIntervalInputEvents(sessionId, driverKey, timeline.activityIntervals()),
|
||||||
driverKey,
|
buildVehicleUsageIntervalInputEvents(timeline.vehicleUsageIntervals()),
|
||||||
timeline.activityIntervals(),
|
buildSupportGeoInputEvents(sessionId, timeline.supportEvents()),
|
||||||
timeline.vehicleUsageIntervals(),
|
|
||||||
timeline.supportEvents(),
|
|
||||||
significantDrivingMinutes,
|
significantDrivingMinutes,
|
||||||
minimumRestPeriodMinutes
|
minimumRestPeriodMinutes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
|
private TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
|
||||||
UUID sessionId,
|
List<Map<String, Object>> activityInputEvents,
|
||||||
String driverKey,
|
List<Map<String, Object>> vehicleUsageInputEvents,
|
||||||
List<ResolvedActivityInterval> activityIntervals,
|
List<Map<String, Object>> supportGeoInputEvents,
|
||||||
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
|
|
||||||
List<ExtractedSupportEvent> supportEvents,
|
|
||||||
int significantDrivingMinutes,
|
int significantDrivingMinutes,
|
||||||
int minimumRestPeriodMinutes
|
int minimumRestPeriodMinutes
|
||||||
) {
|
) {
|
||||||
if ((activityIntervals == null || activityIntervals.isEmpty())
|
if ((activityInputEvents == null || activityInputEvents.isEmpty())
|
||||||
&& (vehicleUsageIntervals == null || vehicleUsageIntervals.isEmpty())) {
|
&& (vehicleUsageInputEvents == null || vehicleUsageInputEvents.isEmpty())) {
|
||||||
return emptyBundle();
|
return emptyBundle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,29 +194,26 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
"potentialInVehicleTripIntervals", newData -> collectPotentialInVehicleTripIntervalEvents(newData, potentialInVehicleTripIntervals)
|
"potentialInVehicleTripIntervals", newData -> collectPotentialInVehicleTripIntervalEvents(newData, potentialInVehicleTripIntervals)
|
||||||
),
|
),
|
||||||
runtime -> {
|
runtime -> {
|
||||||
if (supportEvents != null) {
|
if (supportGeoInputEvents != null) {
|
||||||
for (ExtractedSupportEvent supportEvent : supportEvents) {
|
for (Map<String, Object> supportGeoEvidence : supportGeoInputEvents) {
|
||||||
Map<String, Object> supportGeoEvidence = toSupportGeoEvidenceInputMap(sessionId, supportEvent);
|
|
||||||
if (supportGeoEvidence != null) {
|
|
||||||
runtime.getEventService().sendEventMap(
|
runtime.getEventService().sendEventMap(
|
||||||
supportGeoEvidence,
|
supportGeoEvidence,
|
||||||
"TachographSupportGeoEvidenceInputEvent"
|
"TachographSupportGeoEvidenceInputEvent"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (vehicleUsageInputEvents != null) {
|
||||||
if (vehicleUsageIntervals != null) {
|
for (Map<String, Object> interval : vehicleUsageInputEvents) {
|
||||||
for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) {
|
|
||||||
runtime.getEventService().sendEventMap(
|
runtime.getEventService().sendEventMap(
|
||||||
toVehicleUsageIntervalInputMap(interval),
|
interval,
|
||||||
"TachographVehicleUsageIntervalInputEvent"
|
"TachographVehicleUsageIntervalInputEvent"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (activityIntervals != null) {
|
if (activityInputEvents != null) {
|
||||||
for (ResolvedActivityInterval interval : activityIntervals) {
|
for (Map<String, Object> interval : activityInputEvents) {
|
||||||
runtime.getEventService().sendEventMap(
|
runtime.getEventService().sendEventMap(
|
||||||
toActivityIntervalInputMap(sessionId, driverKey, interval),
|
interval,
|
||||||
"TachographActivityIntervalInputEvent"
|
"TachographActivityIntervalInputEvent"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -192,6 +234,50 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> buildActivityIntervalInputEvents(
|
||||||
|
UUID sessionId,
|
||||||
|
String driverKey,
|
||||||
|
List<ResolvedActivityInterval> activityIntervals
|
||||||
|
) {
|
||||||
|
return safeList(activityIntervals).stream()
|
||||||
|
.map(interval -> toActivityIntervalInputMap(sessionId, driverKey, interval))
|
||||||
|
.sorted(Comparator
|
||||||
|
.comparing((Map<String, Object> event) -> (Long) event.get("startedAtEpochSecond"))
|
||||||
|
.thenComparing(event -> Objects.toString(event.get("driverKey"), ""))
|
||||||
|
.thenComparing(event -> Objects.toString(event.get("intervalId"), "")))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> buildVehicleUsageIntervalInputEvents(
|
||||||
|
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals
|
||||||
|
) {
|
||||||
|
return safeList(vehicleUsageIntervals).stream()
|
||||||
|
.map(this::toVehicleUsageIntervalInputMap)
|
||||||
|
.sorted(Comparator
|
||||||
|
.comparing((Map<String, Object> event) -> (Long) event.get("startedAtEpochSecond"))
|
||||||
|
.thenComparing(event -> Objects.toString(event.get("driverKey"), ""))
|
||||||
|
.thenComparing(event -> Objects.toString(event.get("intervalId"), "")))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> buildSupportGeoInputEvents(
|
||||||
|
UUID sessionId,
|
||||||
|
List<ExtractedSupportEvent> supportEvents
|
||||||
|
) {
|
||||||
|
return safeList(supportEvents).stream()
|
||||||
|
.map(event -> toSupportGeoEvidenceInputMap(sessionId, event))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.sorted(Comparator
|
||||||
|
.comparing((Map<String, Object> event) -> (Long) event.get("occurredAtEpochSecond"))
|
||||||
|
.thenComparing(event -> Objects.toString(event.get("driverKey"), ""))
|
||||||
|
.thenComparing(event -> Objects.toString(event.get("eventId"), "")))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> List<T> safeList(List<T> values) {
|
||||||
|
return values == null ? List.of() : values;
|
||||||
|
}
|
||||||
|
|
||||||
private TachographEsperDrivingDerivedProjectionBundle emptyBundle() {
|
private TachographEsperDrivingDerivedProjectionBundle emptyBundle() {
|
||||||
return new TachographEsperDrivingDerivedProjectionBundle(
|
return new TachographEsperDrivingDerivedProjectionBundle(
|
||||||
List.of(),
|
List.of(),
|
||||||
|
|
@ -503,10 +589,8 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
||||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
||||||
(Long) event.get("durationSeconds"),
|
(Long) event.get("durationSeconds"),
|
||||||
(Long) event.get("cardPresentDurationSeconds"),
|
(Long) event.get("cardAbsentDurationSeconds"),
|
||||||
(Double) event.get("cardPresentCoveragePercent"),
|
(Double) event.get("cardAbsentCoveragePercent"),
|
||||||
(Long) event.get("unknownDurationSeconds"),
|
|
||||||
(Double) event.get("unknownCoveragePercent"),
|
|
||||||
(String) event.get("previousDrivingSourceIntervalId"),
|
(String) event.get("previousDrivingSourceIntervalId"),
|
||||||
(String) event.get("nextDrivingSourceIntervalId"),
|
(String) event.get("nextDrivingSourceIntervalId"),
|
||||||
(String) event.get("previousRegistrationKey"),
|
(String) event.get("previousRegistrationKey"),
|
||||||
|
|
@ -571,10 +655,8 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
||||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
||||||
(Long) event.get("durationSeconds"),
|
(Long) event.get("durationSeconds"),
|
||||||
(Long) event.get("cardPresentDurationSeconds"),
|
(Long) event.get("cardAbsentDurationSeconds"),
|
||||||
(Double) event.get("cardPresentCoveragePercent"),
|
(Double) event.get("cardAbsentCoveragePercent"),
|
||||||
(Long) event.get("unknownDurationSeconds"),
|
|
||||||
(Double) event.get("unknownCoveragePercent"),
|
|
||||||
(String) event.get("previousDrivingSourceIntervalId"),
|
(String) event.get("previousDrivingSourceIntervalId"),
|
||||||
(String) event.get("nextDrivingSourceIntervalId"),
|
(String) event.get("nextDrivingSourceIntervalId"),
|
||||||
(String) event.get("previousRegistrationKey"),
|
(String) event.get("previousRegistrationKey"),
|
||||||
|
|
@ -639,10 +721,8 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
||||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
||||||
(Long) event.get("durationSeconds"),
|
(Long) event.get("durationSeconds"),
|
||||||
(Long) event.get("cardPresentDurationSeconds"),
|
(Long) event.get("cardAbsentDurationSeconds"),
|
||||||
(Double) event.get("cardPresentCoveragePercent"),
|
(Double) event.get("cardAbsentCoveragePercent"),
|
||||||
(Long) event.get("unknownDurationSeconds"),
|
|
||||||
(Double) event.get("unknownCoveragePercent"),
|
|
||||||
(String) event.get("previousDrivingSourceIntervalId"),
|
(String) event.get("previousDrivingSourceIntervalId"),
|
||||||
(String) event.get("nextDrivingSourceIntervalId"),
|
(String) event.get("nextDrivingSourceIntervalId"),
|
||||||
(String) event.get("previousRegistrationKey"),
|
(String) event.get("previousRegistrationKey"),
|
||||||
|
|
@ -693,8 +773,7 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
(String) event.get("vehicleKey"),
|
(String) event.get("vehicleKey"),
|
||||||
(Integer) event.get("containedPotentialInVehicleOvernightStayIntervalCount"),
|
(Integer) event.get("containedPotentialInVehicleOvernightStayIntervalCount"),
|
||||||
(Long) event.get("containedPotentialInVehicleOvernightStayDurationSeconds"),
|
(Long) event.get("containedPotentialInVehicleOvernightStayDurationSeconds"),
|
||||||
(Long) event.get("containedCardPresentDurationSeconds"),
|
(Long) event.get("containedCardAbsentDurationSeconds"),
|
||||||
(Long) event.get("containedUnknownDurationSeconds"),
|
|
||||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond((Long) event.get("firstPotentialInVehicleOvernightStayStartedAtEpochSecond")), ZoneOffset.UTC),
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond((Long) event.get("firstPotentialInVehicleOvernightStayStartedAtEpochSecond")), ZoneOffset.UTC),
|
||||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond((Long) event.get("lastPotentialInVehicleOvernightStayEndedAtEpochSecond")), ZoneOffset.UTC),
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond((Long) event.get("lastPotentialInVehicleOvernightStayEndedAtEpochSecond")), ZoneOffset.UTC),
|
||||||
(String) event.get("firstPreviousDrivingSourceIntervalId"),
|
(String) event.get("firstPreviousDrivingSourceIntervalId"),
|
||||||
|
|
@ -725,7 +804,7 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
private List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> sortDailyWeeklyRestCandidateCoverageIntervals(
|
private List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> sortDailyWeeklyRestCandidateCoverageIntervals(
|
||||||
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> intervals
|
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> intervals
|
||||||
) {
|
) {
|
||||||
return intervals.stream()
|
return deduplicateRestCoverageIntervals(intervals).stream()
|
||||||
.sorted(Comparator.comparing(TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent::startedAt)
|
.sorted(Comparator.comparing(TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent::startedAt)
|
||||||
.thenComparing(TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent::endedAt))
|
.thenComparing(TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent::endedAt))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
@ -734,7 +813,7 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
private List<TachographEsperPotentialHomeOvernightStayIntervalEvent> sortPotentialHomeOvernightStayIntervals(
|
private List<TachographEsperPotentialHomeOvernightStayIntervalEvent> sortPotentialHomeOvernightStayIntervals(
|
||||||
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> intervals
|
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> intervals
|
||||||
) {
|
) {
|
||||||
return intervals.stream()
|
return deduplicatePotentialHomeOvernightStayIntervals(intervals).stream()
|
||||||
.sorted(Comparator.comparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::startedAt)
|
.sorted(Comparator.comparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::startedAt)
|
||||||
.thenComparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::endedAt))
|
.thenComparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::endedAt))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
@ -743,12 +822,76 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
private List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> sortPotentialInVehicleOvernightStayIntervals(
|
private List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> sortPotentialInVehicleOvernightStayIntervals(
|
||||||
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> intervals
|
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> intervals
|
||||||
) {
|
) {
|
||||||
return intervals.stream()
|
return deduplicatePotentialInVehicleOvernightStayIntervals(intervals).stream()
|
||||||
.sorted(Comparator.comparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::startedAt)
|
.sorted(Comparator.comparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::startedAt)
|
||||||
.thenComparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::endedAt))
|
.thenComparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::endedAt))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> deduplicateRestCoverageIntervals(
|
||||||
|
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> intervals
|
||||||
|
) {
|
||||||
|
Map<String, TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> deduplicated = new LinkedHashMap<>();
|
||||||
|
for (TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent interval : intervals) {
|
||||||
|
deduplicated.put(restCoverageIntervalKey(
|
||||||
|
interval.driverKey(),
|
||||||
|
interval.startedAt(),
|
||||||
|
interval.endedAt(),
|
||||||
|
interval.previousDrivingSourceIntervalId(),
|
||||||
|
interval.nextDrivingSourceIntervalId()
|
||||||
|
), interval);
|
||||||
|
}
|
||||||
|
return new ArrayList<>(deduplicated.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TachographEsperPotentialHomeOvernightStayIntervalEvent> deduplicatePotentialHomeOvernightStayIntervals(
|
||||||
|
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> intervals
|
||||||
|
) {
|
||||||
|
Map<String, TachographEsperPotentialHomeOvernightStayIntervalEvent> deduplicated = new LinkedHashMap<>();
|
||||||
|
for (TachographEsperPotentialHomeOvernightStayIntervalEvent interval : intervals) {
|
||||||
|
deduplicated.put(restCoverageIntervalKey(
|
||||||
|
interval.driverKey(),
|
||||||
|
interval.startedAt(),
|
||||||
|
interval.endedAt(),
|
||||||
|
interval.previousDrivingSourceIntervalId(),
|
||||||
|
interval.nextDrivingSourceIntervalId()
|
||||||
|
), interval);
|
||||||
|
}
|
||||||
|
return new ArrayList<>(deduplicated.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> deduplicatePotentialInVehicleOvernightStayIntervals(
|
||||||
|
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> intervals
|
||||||
|
) {
|
||||||
|
Map<String, TachographEsperPotentialInVehicleOvernightStayIntervalEvent> deduplicated = new LinkedHashMap<>();
|
||||||
|
for (TachographEsperPotentialInVehicleOvernightStayIntervalEvent interval : intervals) {
|
||||||
|
deduplicated.put(restCoverageIntervalKey(
|
||||||
|
interval.driverKey(),
|
||||||
|
interval.startedAt(),
|
||||||
|
interval.endedAt(),
|
||||||
|
interval.previousDrivingSourceIntervalId(),
|
||||||
|
interval.nextDrivingSourceIntervalId()
|
||||||
|
), interval);
|
||||||
|
}
|
||||||
|
return new ArrayList<>(deduplicated.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String restCoverageIntervalKey(
|
||||||
|
String driverKey,
|
||||||
|
OffsetDateTime startedAt,
|
||||||
|
OffsetDateTime endedAt,
|
||||||
|
String previousDrivingSourceIntervalId,
|
||||||
|
String nextDrivingSourceIntervalId
|
||||||
|
) {
|
||||||
|
return String.join("|",
|
||||||
|
driverKey == null ? "" : driverKey,
|
||||||
|
startedAt == null ? "" : startedAt.toString(),
|
||||||
|
endedAt == null ? "" : endedAt.toString(),
|
||||||
|
previousDrivingSourceIntervalId == null ? "" : previousDrivingSourceIntervalId,
|
||||||
|
nextDrivingSourceIntervalId == null ? "" : nextDrivingSourceIntervalId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private List<TachographEsperPotentialInVehicleTripIntervalEvent> sortPotentialInVehicleTripIntervals(
|
private List<TachographEsperPotentialInVehicleTripIntervalEvent> sortPotentialInVehicleTripIntervals(
|
||||||
List<TachographEsperPotentialInVehicleTripIntervalEvent> intervals
|
List<TachographEsperPotentialInVehicleTripIntervalEvent> intervals
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographCompositeSession;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public class InMemoryTachographCompositeSessionRepository implements TachographCompositeSessionRepository {
|
||||||
|
|
||||||
|
private final Map<UUID, TachographCompositeSession> sessionsById = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized TachographCompositeSession save(TachographCompositeSession session) {
|
||||||
|
TachographCompositeSession toSave = session.createdAt() == null
|
||||||
|
? new TachographCompositeSession(
|
||||||
|
session.compositeSessionId(),
|
||||||
|
session.tenantKey(),
|
||||||
|
session.label(),
|
||||||
|
session.memberSessionIds(),
|
||||||
|
Instant.now()
|
||||||
|
)
|
||||||
|
: session;
|
||||||
|
sessionsById.put(toSave.compositeSessionId(), toSave);
|
||||||
|
return toSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Optional<TachographCompositeSession> find(UUID compositeSessionId) {
|
||||||
|
return Optional.ofNullable(sessionsById.get(compositeSessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean delete(UUID compositeSessionId) {
|
||||||
|
return sessionsById.remove(compositeSessionId) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized List<TachographCompositeSession> list() {
|
||||||
|
List<TachographCompositeSession> result = new ArrayList<>(sessionsById.values());
|
||||||
|
result.sort(Comparator.comparing(TachographCompositeSession::createdAt, Comparator.nullsLast(Comparator.naturalOrder()))
|
||||||
|
.thenComparing(TachographCompositeSession::compositeSessionId));
|
||||||
|
return List.copyOf(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.service;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class TachographCompositeSessionNotFoundException extends RuntimeException {
|
||||||
|
|
||||||
|
public TachographCompositeSessionNotFoundException(UUID compositeSessionId) {
|
||||||
|
super("Tachograph composite session '%s' was not found.".formatted(compositeSessionId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographCompositeSession;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface TachographCompositeSessionRepository {
|
||||||
|
|
||||||
|
TachographCompositeSession save(TachographCompositeSession session);
|
||||||
|
|
||||||
|
Optional<TachographCompositeSession> find(UUID compositeSessionId);
|
||||||
|
|
||||||
|
boolean delete(UUID compositeSessionId);
|
||||||
|
|
||||||
|
List<TachographCompositeSession> list();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,261 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.processing.service.UnifiedEventTimelineReconstructor;
|
||||||
|
import at.procon.eventhub.service.EventAcquisitionRecordKeyService;
|
||||||
|
import at.procon.eventhub.service.EventHubEventSorter;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.CreateTachographCompositeSessionRequest;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.CreateTachographCompositeSessionResponse;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographCompositeDriverEventsResponse;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographCompositeSessionListDriversResponse;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographCompositeSessionSummaryDto;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographFileDriverSummaryDto;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriverCard;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographCompositeSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TachographCompositeSessionService {
|
||||||
|
|
||||||
|
private final TachographCompositeSessionRepository compositeRepository;
|
||||||
|
private final TachographFileSessionRepository fileSessionRepository;
|
||||||
|
private final DriverTimelineEventBuilder driverTimelineEventBuilder;
|
||||||
|
private final UnifiedEventTimelineReconstructor timelineReconstructor;
|
||||||
|
private final EventAcquisitionRecordKeyService eventKeyService;
|
||||||
|
private final EventHubEventSorter eventSorter;
|
||||||
|
|
||||||
|
public TachographCompositeSessionService(
|
||||||
|
TachographCompositeSessionRepository compositeRepository,
|
||||||
|
TachographFileSessionRepository fileSessionRepository,
|
||||||
|
DriverTimelineEventBuilder driverTimelineEventBuilder,
|
||||||
|
UnifiedEventTimelineReconstructor timelineReconstructor,
|
||||||
|
EventAcquisitionRecordKeyService eventKeyService,
|
||||||
|
EventHubEventSorter eventSorter
|
||||||
|
) {
|
||||||
|
this.compositeRepository = compositeRepository;
|
||||||
|
this.fileSessionRepository = fileSessionRepository;
|
||||||
|
this.driverTimelineEventBuilder = driverTimelineEventBuilder;
|
||||||
|
this.timelineReconstructor = timelineReconstructor;
|
||||||
|
this.eventKeyService = eventKeyService;
|
||||||
|
this.eventSorter = eventSorter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CreateTachographCompositeSessionResponse createCompositeSession(CreateTachographCompositeSessionRequest request) {
|
||||||
|
if (request == null || request.sessionIds() == null || request.sessionIds().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("sessionIds must not be empty.");
|
||||||
|
}
|
||||||
|
List<UUID> memberSessionIds = request.sessionIds().stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
if (memberSessionIds.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("sessionIds must not be empty.");
|
||||||
|
}
|
||||||
|
List<TachographFileSession> sessions = memberSessions(memberSessionIds);
|
||||||
|
String tenantKey = sessions.stream()
|
||||||
|
.map(session -> session.metadata() == null ? null : session.metadata().tenantKey())
|
||||||
|
.filter(value -> value != null && !value.isBlank())
|
||||||
|
.findFirst()
|
||||||
|
.orElse("default");
|
||||||
|
TachographCompositeSession compositeSession = compositeRepository.save(new TachographCompositeSession(
|
||||||
|
UUID.randomUUID(),
|
||||||
|
tenantKey,
|
||||||
|
blankToNull(request.label()),
|
||||||
|
memberSessionIds,
|
||||||
|
Instant.now()
|
||||||
|
));
|
||||||
|
return new CreateTachographCompositeSessionResponse(toSummary(compositeSession, sessions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TachographCompositeSessionSummaryDto getCompositeSession(UUID compositeSessionId) {
|
||||||
|
TachographCompositeSession compositeSession = requireCompositeSession(compositeSessionId);
|
||||||
|
return toSummary(compositeSession, memberSessions(compositeSession.memberSessionIds()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TachographCompositeSessionListDriversResponse listDrivers(UUID compositeSessionId) {
|
||||||
|
TachographCompositeSession compositeSession = requireCompositeSession(compositeSessionId);
|
||||||
|
return new TachographCompositeSessionListDriversResponse(
|
||||||
|
compositeSessionId,
|
||||||
|
aggregateDriverSummaries(memberSessions(compositeSession.memberSessionIds()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TachographCompositeDriverEventsResponse getMergedDriverEvents(UUID compositeSessionId, String driverKey) {
|
||||||
|
TachographCompositeSession compositeSession = requireCompositeSession(compositeSessionId);
|
||||||
|
List<TachographFileSession> memberSessions = memberSessions(compositeSession.memberSessionIds());
|
||||||
|
List<TachographFileSession> sourceSessions = sessionsWithDriver(memberSessions, driverKey);
|
||||||
|
if (sourceSessions.isEmpty()) {
|
||||||
|
throw new DriverNotFoundInCompositeSessionException(compositeSessionId, driverKey);
|
||||||
|
}
|
||||||
|
List<EventHubEventDto> mergedEvents = mergeDriverEvents(sourceSessions, driverKey);
|
||||||
|
return new TachographCompositeDriverEventsResponse(
|
||||||
|
compositeSessionId,
|
||||||
|
driverKey,
|
||||||
|
sourceSessions.stream().map(TachographFileSession::sessionId).toList(),
|
||||||
|
mergedEvents.size(),
|
||||||
|
mergedEvents
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResolvedDriverTimeline getMergedDriverTimeline(UUID compositeSessionId, String driverKey) {
|
||||||
|
TachographCompositeSession compositeSession = requireCompositeSession(compositeSessionId);
|
||||||
|
List<TachographFileSession> memberSessions = memberSessions(compositeSession.memberSessionIds());
|
||||||
|
List<TachographFileSession> sourceSessions = sessionsWithDriver(memberSessions, driverKey);
|
||||||
|
if (sourceSessions.isEmpty()) {
|
||||||
|
throw new DriverNotFoundInCompositeSessionException(compositeSessionId, driverKey);
|
||||||
|
}
|
||||||
|
List<EventHubEventDto> mergedEvents = mergeDriverEvents(sourceSessions, driverKey);
|
||||||
|
return timelineReconstructor.reconstruct(
|
||||||
|
compositeSessionId,
|
||||||
|
driverKey,
|
||||||
|
mergedEvents,
|
||||||
|
mergeWarnings(sourceSessions, driverKey),
|
||||||
|
"COMPOSITE_TACHOGRAPH_FILE_SESSION"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TachographCompositeSession requireCompositeSession(UUID compositeSessionId) {
|
||||||
|
return compositeRepository.find(compositeSessionId)
|
||||||
|
.orElseThrow(() -> new TachographCompositeSessionNotFoundException(compositeSessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TachographFileSession> memberSessions(List<UUID> memberSessionIds) {
|
||||||
|
List<TachographFileSession> sessions = new ArrayList<>();
|
||||||
|
for (UUID sessionId : memberSessionIds) {
|
||||||
|
sessions.add(fileSessionRepository.find(sessionId)
|
||||||
|
.orElseThrow(() -> new TachographFileSessionNotFoundException(sessionId)));
|
||||||
|
}
|
||||||
|
return List.copyOf(sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TachographFileSession> sessionsWithDriver(List<TachographFileSession> sessions, String driverKey) {
|
||||||
|
return sessions.stream()
|
||||||
|
.filter(session -> session.driversByKey() != null && session.driversByKey().containsKey(driverKey))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EventHubEventDto> mergeDriverEvents(List<TachographFileSession> sessions, String driverKey) {
|
||||||
|
LinkedHashMap<String, EventHubEventDto> bySignature = new LinkedHashMap<>();
|
||||||
|
for (TachographFileSession session : sessions) {
|
||||||
|
DriverExtractionSession driverSession = session.driversByKey().get(driverKey);
|
||||||
|
if (driverSession == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (EventHubEventDto event : driverTimelineEventBuilder.buildEvents(session, driverSession)) {
|
||||||
|
bySignature.putIfAbsent(eventKeyService.buildEventSignatureHash(event), event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eventSorter.sort(new ArrayList<>(bySignature.values()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ExtractionWarning> mergeWarnings(List<TachographFileSession> sessions, String driverKey) {
|
||||||
|
LinkedHashSet<ExtractionWarning> warnings = new LinkedHashSet<>();
|
||||||
|
for (TachographFileSession session : sessions) {
|
||||||
|
if (session.warnings() != null) {
|
||||||
|
warnings.addAll(session.warnings());
|
||||||
|
}
|
||||||
|
DriverExtractionSession driverSession = session.driversByKey().get(driverKey);
|
||||||
|
if (driverSession != null && driverSession.warnings() != null) {
|
||||||
|
warnings.addAll(driverSession.warnings());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.copyOf(warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TachographCompositeSessionSummaryDto toSummary(
|
||||||
|
TachographCompositeSession compositeSession,
|
||||||
|
List<TachographFileSession> memberSessions
|
||||||
|
) {
|
||||||
|
return new TachographCompositeSessionSummaryDto(
|
||||||
|
compositeSession.compositeSessionId(),
|
||||||
|
compositeSession.tenantKey(),
|
||||||
|
compositeSession.label(),
|
||||||
|
compositeSession.memberSessionIds(),
|
||||||
|
aggregateDriverSummaries(memberSessions),
|
||||||
|
compositeSession.createdAt()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TachographFileDriverSummaryDto> aggregateDriverSummaries(List<TachographFileSession> sessions) {
|
||||||
|
Map<String, DriverSummaryAccumulator> byDriverKey = new LinkedHashMap<>();
|
||||||
|
for (TachographFileSession session : sessions) {
|
||||||
|
if (session.driversByKey() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (DriverExtractionSession driverSession : session.driversByKey().values()) {
|
||||||
|
if (driverSession == null || driverSession.driverKey() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
byDriverKey.computeIfAbsent(driverSession.driverKey(), ignored -> new DriverSummaryAccumulator(driverSession.driverKey()))
|
||||||
|
.accept(driverSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return byDriverKey.values().stream()
|
||||||
|
.map(DriverSummaryAccumulator::finish)
|
||||||
|
.sorted(Comparator.comparing(TachographFileDriverSummaryDto::driverKey))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String blankToNull(String value) {
|
||||||
|
return value == null || value.isBlank() ? null : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DriverSummaryAccumulator {
|
||||||
|
private final String driverKey;
|
||||||
|
private String surname;
|
||||||
|
private String firstNames;
|
||||||
|
private String cardNation;
|
||||||
|
private String cardNumber;
|
||||||
|
private int activityIntervalCount;
|
||||||
|
private int cardVehicleUsageIntervalCount;
|
||||||
|
|
||||||
|
private DriverSummaryAccumulator(String driverKey) {
|
||||||
|
this.driverKey = driverKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void accept(DriverExtractionSession session) {
|
||||||
|
ExtractedDriver driver = session.driver();
|
||||||
|
ExtractedDriverCard card = session.driverCard();
|
||||||
|
if (surname == null && driver != null) {
|
||||||
|
surname = driver.surname();
|
||||||
|
}
|
||||||
|
if (firstNames == null && driver != null) {
|
||||||
|
firstNames = driver.firstNames();
|
||||||
|
}
|
||||||
|
if (cardNation == null && card != null) {
|
||||||
|
cardNation = card.cardNation();
|
||||||
|
}
|
||||||
|
if (cardNumber == null && card != null) {
|
||||||
|
cardNumber = card.cardNumber();
|
||||||
|
}
|
||||||
|
activityIntervalCount += session.cardActivityIntervals() == null ? 0 : session.cardActivityIntervals().size();
|
||||||
|
cardVehicleUsageIntervalCount += session.cardVehicleUsageIntervals() == null ? 0 : session.cardVehicleUsageIntervals().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TachographFileDriverSummaryDto finish() {
|
||||||
|
return new TachographFileDriverSummaryDto(
|
||||||
|
driverKey,
|
||||||
|
surname,
|
||||||
|
firstNames,
|
||||||
|
cardNation,
|
||||||
|
cardNumber,
|
||||||
|
activityIntervalCount,
|
||||||
|
cardVehicleUsageIntervalCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -526,25 +526,6 @@ public class TachographFileSessionProcessingService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
long durationSeconds = Duration.between(start, end).getSeconds();
|
long durationSeconds = Duration.between(start, end).getSeconds();
|
||||||
long cardPresentDurationSeconds = overlapVehicleUsageSeconds(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
rawVehicleUsageIntervals,
|
|
||||||
interval.driverKey(),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
double cardPresentCoveragePercent = durationSeconds == 0L
|
|
||||||
? 0.0d
|
|
||||||
: (cardPresentDurationSeconds * 100.0d) / durationSeconds;
|
|
||||||
long unknownDurationSeconds = overlapSeconds(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
rawVuCardAbsentIntervals,
|
|
||||||
interval.driverKey()
|
|
||||||
);
|
|
||||||
double unknownCoveragePercent = durationSeconds == 0L
|
|
||||||
? 0.0d
|
|
||||||
: (unknownDurationSeconds * 100.0d) / durationSeconds;
|
|
||||||
boolean beginBoundaryChanged = !start.equals(interval.startedAt());
|
boolean beginBoundaryChanged = !start.equals(interval.startedAt());
|
||||||
boolean endBoundaryChanged = !end.equals(interval.endedAt());
|
boolean endBoundaryChanged = !end.equals(interval.endedAt());
|
||||||
return new TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent(
|
return new TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent(
|
||||||
|
|
@ -553,16 +534,16 @@ public class TachographFileSessionProcessingService {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
durationSeconds,
|
durationSeconds,
|
||||||
cardPresentDurationSeconds,
|
interval.cardAbsentDurationSeconds(),
|
||||||
cardPresentCoveragePercent,
|
interval.cardAbsentCoveragePercent(),
|
||||||
unknownDurationSeconds,
|
|
||||||
unknownCoveragePercent,
|
|
||||||
interval.previousDrivingSourceIntervalId(),
|
interval.previousDrivingSourceIntervalId(),
|
||||||
interval.nextDrivingSourceIntervalId(),
|
interval.nextDrivingSourceIntervalId(),
|
||||||
interval.previousRegistrationKey(),
|
interval.previousRegistrationKey(),
|
||||||
interval.nextRegistrationKey(),
|
interval.nextRegistrationKey(),
|
||||||
interval.previousVehicleKey(),
|
interval.previousVehicleKey(),
|
||||||
interval.nextVehicleKey(),
|
interval.nextVehicleKey(),
|
||||||
|
beginBoundaryChanged ? null : interval.beginBoundaryOdometerKm(),
|
||||||
|
endBoundaryChanged ? null : interval.endBoundaryOdometerKm(),
|
||||||
beginBoundaryChanged ? null : interval.beginGeoEventId(),
|
beginBoundaryChanged ? null : interval.beginGeoEventId(),
|
||||||
beginBoundaryChanged ? null : interval.beginGeoEventDomain(),
|
beginBoundaryChanged ? null : interval.beginGeoEventDomain(),
|
||||||
beginBoundaryChanged ? null : interval.beginGeoOccurredAt(),
|
beginBoundaryChanged ? null : interval.beginGeoOccurredAt(),
|
||||||
|
|
@ -607,25 +588,6 @@ public class TachographFileSessionProcessingService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
long durationSeconds = Duration.between(start, end).getSeconds();
|
long durationSeconds = Duration.between(start, end).getSeconds();
|
||||||
long cardPresentDurationSeconds = overlapVehicleUsageSeconds(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
rawVehicleUsageIntervals,
|
|
||||||
interval.driverKey(),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
double cardPresentCoveragePercent = durationSeconds == 0L
|
|
||||||
? 0.0d
|
|
||||||
: (cardPresentDurationSeconds * 100.0d) / durationSeconds;
|
|
||||||
long unknownDurationSeconds = overlapSeconds(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
rawVuCardAbsentIntervals,
|
|
||||||
interval.driverKey()
|
|
||||||
);
|
|
||||||
double unknownCoveragePercent = durationSeconds == 0L
|
|
||||||
? 0.0d
|
|
||||||
: (unknownDurationSeconds * 100.0d) / durationSeconds;
|
|
||||||
boolean beginBoundaryChanged = !start.equals(interval.startedAt());
|
boolean beginBoundaryChanged = !start.equals(interval.startedAt());
|
||||||
boolean endBoundaryChanged = !end.equals(interval.endedAt());
|
boolean endBoundaryChanged = !end.equals(interval.endedAt());
|
||||||
return new TachographEsperPotentialHomeOvernightStayIntervalEvent(
|
return new TachographEsperPotentialHomeOvernightStayIntervalEvent(
|
||||||
|
|
@ -634,16 +596,16 @@ public class TachographFileSessionProcessingService {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
durationSeconds,
|
durationSeconds,
|
||||||
cardPresentDurationSeconds,
|
interval.cardAbsentDurationSeconds(),
|
||||||
cardPresentCoveragePercent,
|
interval.cardAbsentCoveragePercent(),
|
||||||
unknownDurationSeconds,
|
|
||||||
unknownCoveragePercent,
|
|
||||||
interval.previousDrivingSourceIntervalId(),
|
interval.previousDrivingSourceIntervalId(),
|
||||||
interval.nextDrivingSourceIntervalId(),
|
interval.nextDrivingSourceIntervalId(),
|
||||||
interval.previousRegistrationKey(),
|
interval.previousRegistrationKey(),
|
||||||
interval.nextRegistrationKey(),
|
interval.nextRegistrationKey(),
|
||||||
interval.previousVehicleKey(),
|
interval.previousVehicleKey(),
|
||||||
interval.nextVehicleKey(),
|
interval.nextVehicleKey(),
|
||||||
|
beginBoundaryChanged ? null : interval.beginBoundaryOdometerKm(),
|
||||||
|
endBoundaryChanged ? null : interval.endBoundaryOdometerKm(),
|
||||||
beginBoundaryChanged ? null : interval.beginGeoEventId(),
|
beginBoundaryChanged ? null : interval.beginGeoEventId(),
|
||||||
beginBoundaryChanged ? null : interval.beginGeoEventDomain(),
|
beginBoundaryChanged ? null : interval.beginGeoEventDomain(),
|
||||||
beginBoundaryChanged ? null : interval.beginGeoOccurredAt(),
|
beginBoundaryChanged ? null : interval.beginGeoOccurredAt(),
|
||||||
|
|
@ -688,25 +650,6 @@ public class TachographFileSessionProcessingService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
long durationSeconds = Duration.between(start, end).getSeconds();
|
long durationSeconds = Duration.between(start, end).getSeconds();
|
||||||
long cardPresentDurationSeconds = overlapVehicleUsageSeconds(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
rawVehicleUsageIntervals,
|
|
||||||
interval.driverKey(),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
double cardPresentCoveragePercent = durationSeconds == 0L
|
|
||||||
? 0.0d
|
|
||||||
: (cardPresentDurationSeconds * 100.0d) / durationSeconds;
|
|
||||||
long unknownDurationSeconds = overlapSeconds(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
rawVuCardAbsentIntervals,
|
|
||||||
interval.driverKey()
|
|
||||||
);
|
|
||||||
double unknownCoveragePercent = durationSeconds == 0L
|
|
||||||
? 0.0d
|
|
||||||
: (unknownDurationSeconds * 100.0d) / durationSeconds;
|
|
||||||
boolean beginBoundaryChanged = !start.equals(interval.startedAt());
|
boolean beginBoundaryChanged = !start.equals(interval.startedAt());
|
||||||
boolean endBoundaryChanged = !end.equals(interval.endedAt());
|
boolean endBoundaryChanged = !end.equals(interval.endedAt());
|
||||||
return new TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
|
return new TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
|
||||||
|
|
@ -715,16 +658,16 @@ public class TachographFileSessionProcessingService {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
durationSeconds,
|
durationSeconds,
|
||||||
cardPresentDurationSeconds,
|
interval.cardAbsentDurationSeconds(),
|
||||||
cardPresentCoveragePercent,
|
interval.cardAbsentCoveragePercent(),
|
||||||
unknownDurationSeconds,
|
|
||||||
unknownCoveragePercent,
|
|
||||||
interval.previousDrivingSourceIntervalId(),
|
interval.previousDrivingSourceIntervalId(),
|
||||||
interval.nextDrivingSourceIntervalId(),
|
interval.nextDrivingSourceIntervalId(),
|
||||||
interval.previousRegistrationKey(),
|
interval.previousRegistrationKey(),
|
||||||
interval.nextRegistrationKey(),
|
interval.nextRegistrationKey(),
|
||||||
interval.previousVehicleKey(),
|
interval.previousVehicleKey(),
|
||||||
interval.nextVehicleKey(),
|
interval.nextVehicleKey(),
|
||||||
|
beginBoundaryChanged ? null : interval.beginBoundaryOdometerKm(),
|
||||||
|
endBoundaryChanged ? null : interval.endBoundaryOdometerKm(),
|
||||||
beginBoundaryChanged ? null : interval.beginGeoEventId(),
|
beginBoundaryChanged ? null : interval.beginGeoEventId(),
|
||||||
beginBoundaryChanged ? null : interval.beginGeoEventDomain(),
|
beginBoundaryChanged ? null : interval.beginGeoEventDomain(),
|
||||||
beginBoundaryChanged ? null : interval.beginGeoOccurredAt(),
|
beginBoundaryChanged ? null : interval.beginGeoOccurredAt(),
|
||||||
|
|
@ -803,10 +746,7 @@ public class TachographFileSessionProcessingService {
|
||||||
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::durationSeconds)
|
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::durationSeconds)
|
||||||
.sum(),
|
.sum(),
|
||||||
containedIntervals.stream()
|
containedIntervals.stream()
|
||||||
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::cardPresentDurationSeconds)
|
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::cardAbsentDurationSeconds)
|
||||||
.sum(),
|
|
||||||
containedIntervals.stream()
|
|
||||||
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::unknownDurationSeconds)
|
|
||||||
.sum(),
|
.sum(),
|
||||||
first.startedAt(),
|
first.startedAt(),
|
||||||
last.endedAt(),
|
last.endedAt(),
|
||||||
|
|
@ -1409,7 +1349,7 @@ public class TachographFileSessionProcessingService {
|
||||||
"Driving interruption intervals are gaps between consecutive driving intervals longer than the configured significant-driving threshold.",
|
"Driving interruption intervals are gaps between consecutive driving intervals longer than the configured significant-driving threshold.",
|
||||||
"Driving interruption vehicle-change intervals are daily/weekly rest candidates where previousRegistrationKey differs from nextRegistrationKey.",
|
"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 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 enrich each rest candidate with card-present and card-absent 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.",
|
"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.",
|
"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.",
|
"If both begin and end geo evidence carry odometer values, geoEvidenceMovementCategory classifies the interval as STATIONARY, MINOR, MOVED, or UNKNOWN.",
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ create schema DailyWeeklyRestCandidateCoverageUnknownResolvedInterval(
|
||||||
startedAtEpochSecond long,
|
startedAtEpochSecond long,
|
||||||
endedAtEpochSecond long,
|
endedAtEpochSecond long,
|
||||||
durationSeconds long,
|
durationSeconds long,
|
||||||
unknownDurationSeconds long,
|
cardAbsentDurationSeconds long,
|
||||||
previousDrivingSourceIntervalId string,
|
previousDrivingSourceIntervalId string,
|
||||||
nextDrivingSourceIntervalId string,
|
nextDrivingSourceIntervalId string,
|
||||||
previousRegistrationKey string,
|
previousRegistrationKey string,
|
||||||
|
|
@ -87,8 +87,7 @@ create schema DailyWeeklyRestCandidateCoverageCardResolvedInterval(
|
||||||
startedAtEpochSecond long,
|
startedAtEpochSecond long,
|
||||||
endedAtEpochSecond long,
|
endedAtEpochSecond long,
|
||||||
durationSeconds long,
|
durationSeconds long,
|
||||||
cardPresentDurationSeconds long,
|
cardAbsentDurationSeconds long,
|
||||||
unknownDurationSeconds long,
|
|
||||||
previousDrivingSourceIntervalId string,
|
previousDrivingSourceIntervalId string,
|
||||||
nextDrivingSourceIntervalId string,
|
nextDrivingSourceIntervalId string,
|
||||||
previousRegistrationKey string,
|
previousRegistrationKey string,
|
||||||
|
|
@ -103,10 +102,8 @@ create schema DailyWeeklyRestCandidateCoverageInterval(
|
||||||
startedAtEpochSecond long,
|
startedAtEpochSecond long,
|
||||||
endedAtEpochSecond long,
|
endedAtEpochSecond long,
|
||||||
durationSeconds long,
|
durationSeconds long,
|
||||||
cardPresentDurationSeconds long,
|
cardAbsentDurationSeconds long,
|
||||||
cardPresentCoveragePercent double,
|
cardAbsentCoveragePercent double,
|
||||||
unknownDurationSeconds long,
|
|
||||||
unknownCoveragePercent double,
|
|
||||||
previousDrivingSourceIntervalId string,
|
previousDrivingSourceIntervalId string,
|
||||||
nextDrivingSourceIntervalId string,
|
nextDrivingSourceIntervalId string,
|
||||||
previousRegistrationKey string,
|
previousRegistrationKey string,
|
||||||
|
|
@ -277,6 +274,12 @@ create schema DailyWeeklyRestCandidateCoverageFinalizationRequest(
|
||||||
endedAtEpochSecond long
|
endedAtEpochSecond long
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create schema DailyWeeklyRestCandidateCoverageEmittedKey(
|
||||||
|
driverKey string,
|
||||||
|
startedAtEpochSecond long,
|
||||||
|
endedAtEpochSecond long
|
||||||
|
);
|
||||||
|
|
||||||
create context PerDriver partition by driverKey from TachographVehicleUsageIntervalInputEvent;
|
create context PerDriver partition by driverKey from TachographVehicleUsageIntervalInputEvent;
|
||||||
|
|
||||||
create schema VuCardAbsentInterval(
|
create schema VuCardAbsentInterval(
|
||||||
|
|
@ -299,10 +302,8 @@ create schema PotentialHomeOvernightStayInterval(
|
||||||
startedAtEpochSecond long,
|
startedAtEpochSecond long,
|
||||||
endedAtEpochSecond long,
|
endedAtEpochSecond long,
|
||||||
durationSeconds long,
|
durationSeconds long,
|
||||||
cardPresentDurationSeconds long,
|
cardAbsentDurationSeconds long,
|
||||||
cardPresentCoveragePercent double,
|
cardAbsentCoveragePercent double,
|
||||||
unknownDurationSeconds long,
|
|
||||||
unknownCoveragePercent double,
|
|
||||||
previousDrivingSourceIntervalId string,
|
previousDrivingSourceIntervalId string,
|
||||||
nextDrivingSourceIntervalId string,
|
nextDrivingSourceIntervalId string,
|
||||||
previousRegistrationKey string,
|
previousRegistrationKey string,
|
||||||
|
|
@ -335,10 +336,8 @@ create schema PotentialInVehicleOvernightStayInterval(
|
||||||
startedAtEpochSecond long,
|
startedAtEpochSecond long,
|
||||||
endedAtEpochSecond long,
|
endedAtEpochSecond long,
|
||||||
durationSeconds long,
|
durationSeconds long,
|
||||||
cardPresentDurationSeconds long,
|
cardAbsentDurationSeconds long,
|
||||||
cardPresentCoveragePercent double,
|
cardAbsentCoveragePercent double,
|
||||||
unknownDurationSeconds long,
|
|
||||||
unknownCoveragePercent double,
|
|
||||||
previousDrivingSourceIntervalId string,
|
previousDrivingSourceIntervalId string,
|
||||||
nextDrivingSourceIntervalId string,
|
nextDrivingSourceIntervalId string,
|
||||||
previousRegistrationKey string,
|
previousRegistrationKey string,
|
||||||
|
|
@ -371,10 +370,8 @@ create schema UnclassifiedDailyWeeklyRestCandidateCoverageInterval(
|
||||||
startedAtEpochSecond long,
|
startedAtEpochSecond long,
|
||||||
endedAtEpochSecond long,
|
endedAtEpochSecond long,
|
||||||
durationSeconds long,
|
durationSeconds long,
|
||||||
cardPresentDurationSeconds long,
|
cardAbsentDurationSeconds long,
|
||||||
cardPresentCoveragePercent double,
|
cardAbsentCoveragePercent double,
|
||||||
unknownDurationSeconds long,
|
|
||||||
unknownCoveragePercent double,
|
|
||||||
previousDrivingSourceIntervalId string,
|
previousDrivingSourceIntervalId string,
|
||||||
nextDrivingSourceIntervalId string,
|
nextDrivingSourceIntervalId string,
|
||||||
previousRegistrationKey string,
|
previousRegistrationKey string,
|
||||||
|
|
@ -409,8 +406,7 @@ create schema PotentialInVehicleTripState(
|
||||||
vehicleKey string,
|
vehicleKey string,
|
||||||
containedPotentialInVehicleOvernightStayIntervalCount int,
|
containedPotentialInVehicleOvernightStayIntervalCount int,
|
||||||
containedPotentialInVehicleOvernightStayDurationSeconds long,
|
containedPotentialInVehicleOvernightStayDurationSeconds long,
|
||||||
containedCardPresentDurationSeconds long,
|
containedCardAbsentDurationSeconds long,
|
||||||
containedUnknownDurationSeconds long,
|
|
||||||
firstPotentialInVehicleOvernightStayStartedAtEpochSecond long,
|
firstPotentialInVehicleOvernightStayStartedAtEpochSecond long,
|
||||||
lastPotentialInVehicleOvernightStayEndedAtEpochSecond long,
|
lastPotentialInVehicleOvernightStayEndedAtEpochSecond long,
|
||||||
firstPreviousDrivingSourceIntervalId string,
|
firstPreviousDrivingSourceIntervalId string,
|
||||||
|
|
@ -427,8 +423,7 @@ create schema PotentialInVehicleTripInterval(
|
||||||
vehicleKey string,
|
vehicleKey string,
|
||||||
containedPotentialInVehicleOvernightStayIntervalCount int,
|
containedPotentialInVehicleOvernightStayIntervalCount int,
|
||||||
containedPotentialInVehicleOvernightStayDurationSeconds long,
|
containedPotentialInVehicleOvernightStayDurationSeconds long,
|
||||||
containedCardPresentDurationSeconds long,
|
containedCardAbsentDurationSeconds long,
|
||||||
containedUnknownDurationSeconds long,
|
|
||||||
firstPotentialInVehicleOvernightStayStartedAtEpochSecond long,
|
firstPotentialInVehicleOvernightStayStartedAtEpochSecond long,
|
||||||
lastPotentialInVehicleOvernightStayEndedAtEpochSecond long,
|
lastPotentialInVehicleOvernightStayEndedAtEpochSecond long,
|
||||||
firstPreviousDrivingSourceIntervalId string,
|
firstPreviousDrivingSourceIntervalId string,
|
||||||
|
|
@ -455,6 +450,7 @@ create window DailyWeeklyRestCandidateEndBoundaryOdometerCandidateWindow#keepall
|
||||||
create window DailyWeeklyRestCandidateEndBoundaryOdometerBestScoreWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateEndBoundaryOdometerBestScore;
|
create window DailyWeeklyRestCandidateEndBoundaryOdometerBestScoreWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateEndBoundaryOdometerBestScore;
|
||||||
create window DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateEndBoundaryOdometerResolved;
|
create window DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateEndBoundaryOdometerResolved;
|
||||||
create window DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateCoverageCardResolvedInterval;
|
create window DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateCoverageCardResolvedInterval;
|
||||||
|
create window DailyWeeklyRestCandidateCoverageEmittedKeyWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateCoverageEmittedKey;
|
||||||
|
|
||||||
insert into SupportGeoEvidenceWindow
|
insert into SupportGeoEvidenceWindow
|
||||||
select
|
select
|
||||||
|
|
@ -550,7 +546,7 @@ select
|
||||||
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
end
|
end
|
||||||
) as unknownDurationSeconds,
|
) as cardAbsentDurationSeconds,
|
||||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||||
c.previousRegistrationKey as previousRegistrationKey,
|
c.previousRegistrationKey as previousRegistrationKey,
|
||||||
|
|
@ -582,7 +578,7 @@ select
|
||||||
c.startedAtEpochSecond as startedAtEpochSecond,
|
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||||
c.durationSeconds as durationSeconds,
|
c.durationSeconds as durationSeconds,
|
||||||
0L as unknownDurationSeconds,
|
0L as cardAbsentDurationSeconds,
|
||||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||||
c.previousRegistrationKey as previousRegistrationKey,
|
c.previousRegistrationKey as previousRegistrationKey,
|
||||||
|
|
@ -604,65 +600,14 @@ select
|
||||||
c.startedAtEpochSecond as startedAtEpochSecond,
|
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||||
c.durationSeconds as durationSeconds,
|
c.durationSeconds as durationSeconds,
|
||||||
sum(
|
c.cardAbsentDurationSeconds as cardAbsentDurationSeconds,
|
||||||
case
|
|
||||||
when v.startedAtEpochSecond <= c.startedAtEpochSecond and (v.endedAtEpochSecond is null or v.endedAtEpochSecond >= c.endedAtEpochSecond)
|
|
||||||
then c.durationSeconds
|
|
||||||
when v.startedAtEpochSecond <= c.startedAtEpochSecond
|
|
||||||
then v.endedAtEpochSecond - c.startedAtEpochSecond
|
|
||||||
when v.endedAtEpochSecond is null or v.endedAtEpochSecond >= c.endedAtEpochSecond
|
|
||||||
then c.endedAtEpochSecond - v.startedAtEpochSecond
|
|
||||||
else v.endedAtEpochSecond - v.startedAtEpochSecond
|
|
||||||
end
|
|
||||||
) as cardPresentDurationSeconds,
|
|
||||||
c.unknownDurationSeconds as unknownDurationSeconds,
|
|
||||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||||
c.previousRegistrationKey as previousRegistrationKey,
|
c.previousRegistrationKey as previousRegistrationKey,
|
||||||
c.nextRegistrationKey as nextRegistrationKey,
|
c.nextRegistrationKey as nextRegistrationKey,
|
||||||
c.previousVehicleKey as previousVehicleKey,
|
c.previousVehicleKey as previousVehicleKey,
|
||||||
c.nextVehicleKey as nextVehicleKey
|
c.nextVehicleKey as nextVehicleKey
|
||||||
from DailyWeeklyRestCandidateCoverageUnknownResolvedInterval as c unidirectional,
|
from DailyWeeklyRestCandidateCoverageUnknownResolvedInterval as c;
|
||||||
TachographVehicleUsageIntervalInputEvent#keepall as v
|
|
||||||
where v.driverKey = c.driverKey
|
|
||||||
and v.startedAtEpochSecond < c.endedAtEpochSecond
|
|
||||||
and (v.endedAtEpochSecond is null or v.endedAtEpochSecond > c.startedAtEpochSecond)
|
|
||||||
group by
|
|
||||||
c.sessionId,
|
|
||||||
c.driverKey,
|
|
||||||
c.startedAtEpochSecond,
|
|
||||||
c.endedAtEpochSecond,
|
|
||||||
c.durationSeconds,
|
|
||||||
c.unknownDurationSeconds,
|
|
||||||
c.previousDrivingSourceIntervalId,
|
|
||||||
c.nextDrivingSourceIntervalId,
|
|
||||||
c.previousRegistrationKey,
|
|
||||||
c.nextRegistrationKey,
|
|
||||||
c.previousVehicleKey,
|
|
||||||
c.nextVehicleKey;
|
|
||||||
|
|
||||||
insert into DailyWeeklyRestCandidateCoverageCardResolvedInterval
|
|
||||||
select
|
|
||||||
c.sessionId as sessionId,
|
|
||||||
c.driverKey as driverKey,
|
|
||||||
c.startedAtEpochSecond as startedAtEpochSecond,
|
|
||||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
|
||||||
c.durationSeconds as durationSeconds,
|
|
||||||
0L as cardPresentDurationSeconds,
|
|
||||||
c.unknownDurationSeconds as unknownDurationSeconds,
|
|
||||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
|
||||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
|
||||||
c.previousRegistrationKey as previousRegistrationKey,
|
|
||||||
c.nextRegistrationKey as nextRegistrationKey,
|
|
||||||
c.previousVehicleKey as previousVehicleKey,
|
|
||||||
c.nextVehicleKey as nextVehicleKey
|
|
||||||
from DailyWeeklyRestCandidateCoverageUnknownResolvedInterval as c
|
|
||||||
where not exists (
|
|
||||||
select * from TachographVehicleUsageIntervalInputEvent#keepall as v
|
|
||||||
where v.driverKey = c.driverKey
|
|
||||||
and v.startedAtEpochSecond < c.endedAtEpochSecond
|
|
||||||
and (v.endedAtEpochSecond is null or v.endedAtEpochSecond > c.startedAtEpochSecond)
|
|
||||||
);
|
|
||||||
|
|
||||||
insert into DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow
|
insert into DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow
|
||||||
select *
|
select *
|
||||||
|
|
@ -1313,10 +1258,8 @@ select
|
||||||
c.startedAtEpochSecond as startedAtEpochSecond,
|
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||||
c.durationSeconds as durationSeconds,
|
c.durationSeconds as durationSeconds,
|
||||||
c.cardPresentDurationSeconds as cardPresentDurationSeconds,
|
c.cardAbsentDurationSeconds as cardAbsentDurationSeconds,
|
||||||
(c.cardPresentDurationSeconds * 100.0d) / c.durationSeconds as cardPresentCoveragePercent,
|
(c.cardAbsentDurationSeconds * 100.0d) / c.durationSeconds as cardAbsentCoveragePercent,
|
||||||
c.unknownDurationSeconds as unknownDurationSeconds,
|
|
||||||
(c.unknownDurationSeconds * 100.0d) / c.durationSeconds as unknownCoveragePercent,
|
|
||||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||||
c.previousRegistrationKey as previousRegistrationKey,
|
c.previousRegistrationKey as previousRegistrationKey,
|
||||||
|
|
@ -1517,7 +1460,20 @@ from DailyWeeklyRestCandidateCoverageFinalizationRequest as request unidirection
|
||||||
DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow as c
|
DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow as c
|
||||||
where c.driverKey = request.driverKey
|
where c.driverKey = request.driverKey
|
||||||
and c.startedAtEpochSecond = request.startedAtEpochSecond
|
and c.startedAtEpochSecond = request.startedAtEpochSecond
|
||||||
and c.endedAtEpochSecond = request.endedAtEpochSecond;
|
and c.endedAtEpochSecond = request.endedAtEpochSecond
|
||||||
|
and not exists (
|
||||||
|
select * from DailyWeeklyRestCandidateCoverageEmittedKeyWindow as emitted
|
||||||
|
where emitted.driverKey = request.driverKey
|
||||||
|
and emitted.startedAtEpochSecond = request.startedAtEpochSecond
|
||||||
|
and emitted.endedAtEpochSecond = request.endedAtEpochSecond
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into DailyWeeklyRestCandidateCoverageEmittedKeyWindow
|
||||||
|
select
|
||||||
|
driverKey,
|
||||||
|
startedAtEpochSecond,
|
||||||
|
endedAtEpochSecond
|
||||||
|
from DailyWeeklyRestCandidateCoverageInterval;
|
||||||
|
|
||||||
context PerDriver
|
context PerDriver
|
||||||
create window PreviousVehicleUsageInterval#lastevent as TachographVehicleUsageIntervalInputEvent;
|
create window PreviousVehicleUsageInterval#lastevent as TachographVehicleUsageIntervalInputEvent;
|
||||||
|
|
@ -1561,10 +1517,8 @@ select
|
||||||
c.startedAtEpochSecond as startedAtEpochSecond,
|
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||||
c.durationSeconds as durationSeconds,
|
c.durationSeconds as durationSeconds,
|
||||||
c.cardPresentDurationSeconds as cardPresentDurationSeconds,
|
c.cardAbsentDurationSeconds as cardAbsentDurationSeconds,
|
||||||
c.cardPresentCoveragePercent as cardPresentCoveragePercent,
|
c.cardAbsentCoveragePercent as cardAbsentCoveragePercent,
|
||||||
c.unknownDurationSeconds as unknownDurationSeconds,
|
|
||||||
c.unknownCoveragePercent as unknownCoveragePercent,
|
|
||||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||||
c.previousRegistrationKey as previousRegistrationKey,
|
c.previousRegistrationKey as previousRegistrationKey,
|
||||||
|
|
@ -1593,7 +1547,7 @@ from DailyWeeklyRestCandidateCoverageInterval as c
|
||||||
where c.previousRegistrationKey is not null
|
where c.previousRegistrationKey is not null
|
||||||
and c.nextRegistrationKey is not null
|
and c.nextRegistrationKey is not null
|
||||||
and c.previousRegistrationKey != c.nextRegistrationKey
|
and c.previousRegistrationKey != c.nextRegistrationKey
|
||||||
and c.unknownDurationSeconds * 100L >= c.durationSeconds * 95L;
|
and c.cardAbsentDurationSeconds * 100L >= c.durationSeconds * 95L;
|
||||||
|
|
||||||
insert into PotentialInVehicleOvernightStayInterval
|
insert into PotentialInVehicleOvernightStayInterval
|
||||||
select
|
select
|
||||||
|
|
@ -1602,10 +1556,8 @@ select
|
||||||
c.startedAtEpochSecond as startedAtEpochSecond,
|
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||||
c.durationSeconds as durationSeconds,
|
c.durationSeconds as durationSeconds,
|
||||||
c.cardPresentDurationSeconds as cardPresentDurationSeconds,
|
c.cardAbsentDurationSeconds as cardAbsentDurationSeconds,
|
||||||
c.cardPresentCoveragePercent as cardPresentCoveragePercent,
|
c.cardAbsentCoveragePercent as cardAbsentCoveragePercent,
|
||||||
c.unknownDurationSeconds as unknownDurationSeconds,
|
|
||||||
c.unknownCoveragePercent as unknownCoveragePercent,
|
|
||||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||||
c.previousRegistrationKey as previousRegistrationKey,
|
c.previousRegistrationKey as previousRegistrationKey,
|
||||||
|
|
@ -1634,7 +1586,7 @@ from DailyWeeklyRestCandidateCoverageInterval as c
|
||||||
where c.previousRegistrationKey is not null
|
where c.previousRegistrationKey is not null
|
||||||
and c.nextRegistrationKey is not null
|
and c.nextRegistrationKey is not null
|
||||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||||
and c.cardPresentDurationSeconds >= c.durationSeconds;
|
and c.cardAbsentDurationSeconds = 0L;
|
||||||
|
|
||||||
insert into UnclassifiedDailyWeeklyRestCandidateCoverageInterval
|
insert into UnclassifiedDailyWeeklyRestCandidateCoverageInterval
|
||||||
select
|
select
|
||||||
|
|
@ -1643,10 +1595,8 @@ select
|
||||||
c.startedAtEpochSecond as startedAtEpochSecond,
|
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||||
c.durationSeconds as durationSeconds,
|
c.durationSeconds as durationSeconds,
|
||||||
c.cardPresentDurationSeconds as cardPresentDurationSeconds,
|
c.cardAbsentDurationSeconds as cardAbsentDurationSeconds,
|
||||||
c.cardPresentCoveragePercent as cardPresentCoveragePercent,
|
c.cardAbsentCoveragePercent as cardAbsentCoveragePercent,
|
||||||
c.unknownDurationSeconds as unknownDurationSeconds,
|
|
||||||
c.unknownCoveragePercent as unknownCoveragePercent,
|
|
||||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||||
c.previousRegistrationKey as previousRegistrationKey,
|
c.previousRegistrationKey as previousRegistrationKey,
|
||||||
|
|
@ -1676,13 +1626,13 @@ where not (
|
||||||
c.previousRegistrationKey is not null
|
c.previousRegistrationKey is not null
|
||||||
and c.nextRegistrationKey is not null
|
and c.nextRegistrationKey is not null
|
||||||
and c.previousRegistrationKey != c.nextRegistrationKey
|
and c.previousRegistrationKey != c.nextRegistrationKey
|
||||||
and c.unknownDurationSeconds * 100L >= c.durationSeconds * 95L
|
and c.cardAbsentDurationSeconds * 100L >= c.durationSeconds * 95L
|
||||||
)
|
)
|
||||||
and not (
|
and not (
|
||||||
c.previousRegistrationKey is not null
|
c.previousRegistrationKey is not null
|
||||||
and c.nextRegistrationKey is not null
|
and c.nextRegistrationKey is not null
|
||||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||||
and c.cardPresentDurationSeconds >= c.durationSeconds
|
and c.cardAbsentDurationSeconds = 0L
|
||||||
);
|
);
|
||||||
|
|
||||||
@Priority(40)
|
@Priority(40)
|
||||||
|
|
@ -1698,8 +1648,7 @@ select
|
||||||
s.vehicleKey as vehicleKey,
|
s.vehicleKey as vehicleKey,
|
||||||
s.containedPotentialInVehicleOvernightStayIntervalCount as containedPotentialInVehicleOvernightStayIntervalCount,
|
s.containedPotentialInVehicleOvernightStayIntervalCount as containedPotentialInVehicleOvernightStayIntervalCount,
|
||||||
s.containedPotentialInVehicleOvernightStayDurationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
|
s.containedPotentialInVehicleOvernightStayDurationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
|
||||||
s.containedCardPresentDurationSeconds as containedCardPresentDurationSeconds,
|
s.containedCardAbsentDurationSeconds as containedCardAbsentDurationSeconds,
|
||||||
s.containedUnknownDurationSeconds as containedUnknownDurationSeconds,
|
|
||||||
s.firstPotentialInVehicleOvernightStayStartedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
|
s.firstPotentialInVehicleOvernightStayStartedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
|
||||||
s.lastPotentialInVehicleOvernightStayEndedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
|
s.lastPotentialInVehicleOvernightStayEndedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
|
||||||
s.firstPreviousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
|
s.firstPreviousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
|
||||||
|
|
@ -1712,7 +1661,7 @@ where s.driverKey = c.driverKey
|
||||||
c.previousRegistrationKey is not null
|
c.previousRegistrationKey is not null
|
||||||
and c.nextRegistrationKey is not null
|
and c.nextRegistrationKey is not null
|
||||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||||
and c.cardPresentDurationSeconds >= c.durationSeconds
|
and c.cardAbsentDurationSeconds = 0L
|
||||||
)
|
)
|
||||||
or s.registrationKey != c.previousRegistrationKey
|
or s.registrationKey != c.previousRegistrationKey
|
||||||
or (
|
or (
|
||||||
|
|
@ -1730,7 +1679,7 @@ where s.driverKey = c.driverKey
|
||||||
c.previousRegistrationKey is not null
|
c.previousRegistrationKey is not null
|
||||||
and c.nextRegistrationKey is not null
|
and c.nextRegistrationKey is not null
|
||||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||||
and c.cardPresentDurationSeconds >= c.durationSeconds
|
and c.cardAbsentDurationSeconds = 0L
|
||||||
);
|
);
|
||||||
|
|
||||||
@Priority(30)
|
@Priority(30)
|
||||||
|
|
@ -1744,8 +1693,7 @@ select
|
||||||
s.vehicleKey as vehicleKey,
|
s.vehicleKey as vehicleKey,
|
||||||
s.containedPotentialInVehicleOvernightStayIntervalCount + 1 as containedPotentialInVehicleOvernightStayIntervalCount,
|
s.containedPotentialInVehicleOvernightStayIntervalCount + 1 as containedPotentialInVehicleOvernightStayIntervalCount,
|
||||||
s.containedPotentialInVehicleOvernightStayDurationSeconds + c.durationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
|
s.containedPotentialInVehicleOvernightStayDurationSeconds + c.durationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
|
||||||
s.containedCardPresentDurationSeconds + c.cardPresentDurationSeconds as containedCardPresentDurationSeconds,
|
s.containedCardAbsentDurationSeconds + c.cardAbsentDurationSeconds as containedCardAbsentDurationSeconds,
|
||||||
s.containedUnknownDurationSeconds + c.unknownDurationSeconds as containedUnknownDurationSeconds,
|
|
||||||
s.firstPotentialInVehicleOvernightStayStartedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
|
s.firstPotentialInVehicleOvernightStayStartedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
|
||||||
c.endedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
|
c.endedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
|
||||||
s.firstPreviousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
|
s.firstPreviousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
|
||||||
|
|
@ -1755,7 +1703,7 @@ where s.driverKey = c.driverKey
|
||||||
and c.previousRegistrationKey is not null
|
and c.previousRegistrationKey is not null
|
||||||
and c.nextRegistrationKey is not null
|
and c.nextRegistrationKey is not null
|
||||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||||
and c.cardPresentDurationSeconds >= c.durationSeconds
|
and c.cardAbsentDurationSeconds = 0L
|
||||||
and s.registrationKey = c.previousRegistrationKey
|
and s.registrationKey = c.previousRegistrationKey
|
||||||
and (
|
and (
|
||||||
s.vehicleKey is null
|
s.vehicleKey is null
|
||||||
|
|
@ -1777,8 +1725,7 @@ select
|
||||||
end as vehicleKey,
|
end as vehicleKey,
|
||||||
1 as containedPotentialInVehicleOvernightStayIntervalCount,
|
1 as containedPotentialInVehicleOvernightStayIntervalCount,
|
||||||
c.durationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
|
c.durationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
|
||||||
c.cardPresentDurationSeconds as containedCardPresentDurationSeconds,
|
c.cardAbsentDurationSeconds as containedCardAbsentDurationSeconds,
|
||||||
c.unknownDurationSeconds as containedUnknownDurationSeconds,
|
|
||||||
c.startedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
|
c.startedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
|
||||||
c.endedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
|
c.endedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
|
||||||
c.previousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
|
c.previousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
|
||||||
|
|
@ -1788,7 +1735,7 @@ where priorCoverage.driverKey = c.driverKey
|
||||||
and c.previousRegistrationKey is not null
|
and c.previousRegistrationKey is not null
|
||||||
and c.nextRegistrationKey is not null
|
and c.nextRegistrationKey is not null
|
||||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||||
and c.cardPresentDurationSeconds >= c.durationSeconds
|
and c.cardAbsentDurationSeconds = 0L
|
||||||
and not exists (
|
and not exists (
|
||||||
select * from OpenPotentialInVehicleTripState as s
|
select * from OpenPotentialInVehicleTripState as s
|
||||||
where s.driverKey = c.driverKey
|
where s.driverKey = c.driverKey
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ select
|
||||||
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
end
|
end
|
||||||
) as unknownDurationSeconds,
|
) as cardAbsentDurationSeconds,
|
||||||
(sum(
|
(sum(
|
||||||
case
|
case
|
||||||
when u.startedAtEpochSecond <= c.startedAtEpochSecond and u.endedAtEpochSecond >= c.endedAtEpochSecond
|
when u.startedAtEpochSecond <= c.startedAtEpochSecond and u.endedAtEpochSecond >= c.endedAtEpochSecond
|
||||||
|
|
@ -26,7 +26,7 @@ select
|
||||||
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
end
|
end
|
||||||
) * 100.0d) / c.durationSeconds as unknownCoveragePercent,
|
) * 100.0d) / c.durationSeconds as cardAbsentCoveragePercent,
|
||||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||||
c.previousRegistrationKey as previousRegistrationKey,
|
c.previousRegistrationKey as previousRegistrationKey,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.api;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.DriverCardRefDto;
|
||||||
|
import at.procon.eventhub.dto.DriverRefDto;
|
||||||
|
import at.procon.eventhub.dto.EventDomain;
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.dto.EventLifecycle;
|
||||||
|
import at.procon.eventhub.dto.EventType;
|
||||||
|
import at.procon.eventhub.dto.VehicleRefDto;
|
||||||
|
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.CreateTachographCompositeSessionResponse;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographCompositeDriverEventsResponse;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographCompositeSessionListDriversResponse;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographCompositeSessionSummaryDto;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographFileDriverSummaryDto;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.DriverNotFoundInCompositeSessionException;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographCompositeSessionService;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
|
|
||||||
|
class TachographCompositeSessionControllerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createsLoadsListsAndProcessesCompositeSession() throws Exception {
|
||||||
|
TachographCompositeSessionService service = org.mockito.Mockito.mock(TachographCompositeSessionService.class);
|
||||||
|
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TachographCompositeSessionController(service))
|
||||||
|
.setControllerAdvice(new TachographFileSessionExceptionHandler())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
UUID compositeSessionId = UUID.randomUUID();
|
||||||
|
UUID firstSessionId = UUID.randomUUID();
|
||||||
|
UUID secondSessionId = UUID.randomUUID();
|
||||||
|
TachographFileDriverSummaryDto driver = new TachographFileDriverSummaryDto("12:123", "Muster", "Max", "12", "CARD0000000001", 5, 3);
|
||||||
|
TachographCompositeSessionSummaryDto summary = new TachographCompositeSessionSummaryDto(
|
||||||
|
compositeSessionId,
|
||||||
|
"default",
|
||||||
|
"fleet-week-1",
|
||||||
|
List.of(firstSessionId, secondSessionId),
|
||||||
|
List.of(driver),
|
||||||
|
Instant.parse("2026-05-22T10:00:00Z")
|
||||||
|
);
|
||||||
|
when(service.createCompositeSession(org.mockito.ArgumentMatchers.any()))
|
||||||
|
.thenReturn(new CreateTachographCompositeSessionResponse(summary));
|
||||||
|
when(service.getCompositeSession(compositeSessionId)).thenReturn(summary);
|
||||||
|
when(service.listDrivers(compositeSessionId))
|
||||||
|
.thenReturn(new TachographCompositeSessionListDriversResponse(compositeSessionId, List.of(driver)));
|
||||||
|
when(service.getMergedDriverEvents(compositeSessionId, "12:123"))
|
||||||
|
.thenReturn(new TachographCompositeDriverEventsResponse(
|
||||||
|
compositeSessionId,
|
||||||
|
"12:123",
|
||||||
|
List.of(firstSessionId, secondSessionId),
|
||||||
|
1,
|
||||||
|
List.of(event("EVT-1", OffsetDateTime.parse("2026-05-22T08:00:00Z"), EventType.WORK))
|
||||||
|
));
|
||||||
|
when(service.getMergedDriverTimeline(compositeSessionId, "12:123"))
|
||||||
|
.thenReturn(new ResolvedDriverTimeline(
|
||||||
|
"COMPOSITE_TACHOGRAPH_FILE_SESSION",
|
||||||
|
OffsetDateTime.parse("2026-05-22T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-22T10:00:00Z"),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of()
|
||||||
|
));
|
||||||
|
|
||||||
|
mockMvc.perform(post("/api/eventhub/tachograph-composite-sessions")
|
||||||
|
.contentType("application/json")
|
||||||
|
.content("""
|
||||||
|
{
|
||||||
|
"sessionIds": ["%s", "%s"],
|
||||||
|
"label": "fleet-week-1"
|
||||||
|
}
|
||||||
|
""".formatted(firstSessionId, secondSessionId)))
|
||||||
|
.andExpect(status().isCreated())
|
||||||
|
.andExpect(jsonPath("$.session.compositeSessionId").value(compositeSessionId.toString()))
|
||||||
|
.andExpect(jsonPath("$.session.memberSessionIds[0]").value(firstSessionId.toString()));
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/eventhub/tachograph-composite-sessions/{compositeSessionId}", compositeSessionId))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.compositeSessionId").value(compositeSessionId.toString()))
|
||||||
|
.andExpect(jsonPath("$.drivers[0].driverKey").value("12:123"));
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/eventhub/tachograph-composite-sessions/{compositeSessionId}/drivers", compositeSessionId))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.drivers[0].cardVehicleUsageIntervalCount").value(3));
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/eventhub/tachograph-composite-sessions/{compositeSessionId}/drivers/{driverKey}/events",
|
||||||
|
compositeSessionId, "12:123"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.eventCount").value(1))
|
||||||
|
.andExpect(jsonPath("$.events[0].externalSourceEventId").value("EVT-1"))
|
||||||
|
.andExpect(jsonPath("$.events[0].occurredAt").value("2026-05-22T08:00:00Z"));
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/eventhub/tachograph-composite-sessions/{compositeSessionId}/drivers/{driverKey}/timeline",
|
||||||
|
compositeSessionId, "12:123"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.sourceKind").value("COMPOSITE_TACHOGRAPH_FILE_SESSION"))
|
||||||
|
.andExpect(jsonPath("$.loadedFrom").value("2026-05-22T08:00:00Z"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void returnsNotFoundWhenDriverIsMissingInCompositeSession() throws Exception {
|
||||||
|
TachographCompositeSessionService service = org.mockito.Mockito.mock(TachographCompositeSessionService.class);
|
||||||
|
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TachographCompositeSessionController(service))
|
||||||
|
.setControllerAdvice(new TachographFileSessionExceptionHandler())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
UUID compositeSessionId = UUID.randomUUID();
|
||||||
|
when(service.getMergedDriverEvents(eq(compositeSessionId), eq("missing")))
|
||||||
|
.thenThrow(new DriverNotFoundInCompositeSessionException(compositeSessionId, "missing"));
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/eventhub/tachograph-composite-sessions/{compositeSessionId}/drivers/{driverKey}/events",
|
||||||
|
compositeSessionId, "missing"))
|
||||||
|
.andExpect(status().isNotFound())
|
||||||
|
.andExpect(jsonPath("$.message").value(
|
||||||
|
"Driver 'missing' was not found in tachograph composite session '%s'.".formatted(compositeSessionId)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EventHubEventDto event(String externalSourceEventId, OffsetDateTime occurredAt, EventType eventType) {
|
||||||
|
return new EventHubEventDto(
|
||||||
|
null,
|
||||||
|
externalSourceEventId,
|
||||||
|
new DriverRefDto(null, new DriverCardRefDto("12", 12, "CARD0000000001")),
|
||||||
|
new VehicleRefDto(null, "VIN00000000000001", null, new VehicleRegistrationRefDto("12", 12, "W-12345A")),
|
||||||
|
occurredAt,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
EventDomain.DRIVER_ACTIVITY,
|
||||||
|
eventType,
|
||||||
|
EventLifecycle.START,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -178,8 +178,6 @@ class TachographFileSessionControllerTest {
|
||||||
OffsetDateTime.parse("2026-05-12T10:00:00Z"),
|
OffsetDateTime.parse("2026-05-12T10:00:00Z"),
|
||||||
OffsetDateTime.parse("2026-05-12T22:00:00Z"),
|
OffsetDateTime.parse("2026-05-12T22:00:00Z"),
|
||||||
43_200L,
|
43_200L,
|
||||||
0L,
|
|
||||||
0.0d,
|
|
||||||
43_200L,
|
43_200L,
|
||||||
100.0d,
|
100.0d,
|
||||||
"ACT-2",
|
"ACT-2",
|
||||||
|
|
@ -216,8 +214,6 @@ class TachographFileSessionControllerTest {
|
||||||
OffsetDateTime.parse("2026-05-12T10:00:00Z"),
|
OffsetDateTime.parse("2026-05-12T10:00:00Z"),
|
||||||
OffsetDateTime.parse("2026-05-12T22:00:00Z"),
|
OffsetDateTime.parse("2026-05-12T22:00:00Z"),
|
||||||
43_200L,
|
43_200L,
|
||||||
0L,
|
|
||||||
0.0d,
|
|
||||||
43_200L,
|
43_200L,
|
||||||
100.0d,
|
100.0d,
|
||||||
"ACT-2",
|
"ACT-2",
|
||||||
|
|
|
||||||
|
|
@ -563,8 +563,8 @@ class DriverTimelineBuilderTest {
|
||||||
assertThat(intervals.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
assertThat(intervals.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
||||||
assertThat(intervals.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
|
assertThat(intervals.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
|
||||||
assertThat(intervals.get(0).durationSeconds()).isEqualTo(50_400L);
|
assertThat(intervals.get(0).durationSeconds()).isEqualTo(50_400L);
|
||||||
assertThat(intervals.get(0).unknownDurationSeconds()).isEqualTo(50_399L);
|
assertThat(intervals.get(0).cardAbsentDurationSeconds()).isEqualTo(50_399L);
|
||||||
assertThat(intervals.get(0).unknownCoveragePercent()).isGreaterThan(99.9d);
|
assertThat(intervals.get(0).cardAbsentCoveragePercent()).isGreaterThan(99.9d);
|
||||||
assertThat(intervals.get(0).previousDrivingSourceIntervalId()).isEqualTo("ACT-1");
|
assertThat(intervals.get(0).previousDrivingSourceIntervalId()).isEqualTo("ACT-1");
|
||||||
assertThat(intervals.get(0).nextDrivingSourceIntervalId()).isEqualTo("ACT-2");
|
assertThat(intervals.get(0).nextDrivingSourceIntervalId()).isEqualTo("ACT-2");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,8 +128,7 @@ class DriverTimelineReusableProjectionBuilderTest {
|
||||||
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals()).hasSize(1);
|
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals()).hasSize(1);
|
||||||
TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent coverageInterval =
|
TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent coverageInterval =
|
||||||
reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0);
|
reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0);
|
||||||
assertThat(coverageInterval.unknownCoveragePercent()).isCloseTo(99.998d, org.assertj.core.data.Offset.offset(0.001d));
|
assertThat(coverageInterval.cardAbsentCoveragePercent()).isCloseTo(99.998d, org.assertj.core.data.Offset.offset(0.001d));
|
||||||
assertThat(coverageInterval.cardPresentCoveragePercent()).isEqualTo(0.0d);
|
|
||||||
assertThat(reusableBundle.drivingInterruptionVehicleChangeIntervals()).containsExactlyElementsOf(legacyDrivingInterruptionVehicleChanges);
|
assertThat(reusableBundle.drivingInterruptionVehicleChangeIntervals()).containsExactlyElementsOf(legacyDrivingInterruptionVehicleChanges);
|
||||||
assertThat(reusableBundle.vuCardAbsentIntervals()).containsExactlyElementsOf(legacyVuCardAbsentIntervals);
|
assertThat(reusableBundle.vuCardAbsentIntervals()).containsExactlyElementsOf(legacyVuCardAbsentIntervals);
|
||||||
assertThat(reusableBundle.potentialHomeOvernightStayIntervals()).hasSize(1);
|
assertThat(reusableBundle.potentialHomeOvernightStayIntervals()).hasSize(1);
|
||||||
|
|
@ -137,8 +136,8 @@ class DriverTimelineReusableProjectionBuilderTest {
|
||||||
.isEqualTo(legacyPotentialHomeOvernightStays.get(0).startedAt());
|
.isEqualTo(legacyPotentialHomeOvernightStays.get(0).startedAt());
|
||||||
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).endedAt())
|
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).endedAt())
|
||||||
.isEqualTo(legacyPotentialHomeOvernightStays.get(0).endedAt());
|
.isEqualTo(legacyPotentialHomeOvernightStays.get(0).endedAt());
|
||||||
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).unknownCoveragePercent())
|
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).cardAbsentCoveragePercent())
|
||||||
.isEqualTo(legacyPotentialHomeOvernightStays.get(0).unknownCoveragePercent());
|
.isEqualTo(legacyPotentialHomeOvernightStays.get(0).cardAbsentCoveragePercent());
|
||||||
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).beginBoundaryOdometerKm())
|
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).beginBoundaryOdometerKm())
|
||||||
.isEqualTo(200L);
|
.isEqualTo(200L);
|
||||||
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).endBoundaryOdometerKm())
|
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).endBoundaryOdometerKm())
|
||||||
|
|
@ -266,9 +265,7 @@ class DriverTimelineReusableProjectionBuilderTest {
|
||||||
|
|
||||||
assertThat(reusableBundle.drivingInterruptionVehicleChangeIntervals()).isEmpty();
|
assertThat(reusableBundle.drivingInterruptionVehicleChangeIntervals()).isEmpty();
|
||||||
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals()).hasSize(1);
|
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals()).hasSize(1);
|
||||||
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentCoveragePercent())
|
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentCoveragePercent())
|
||||||
.isEqualTo(100.0d);
|
|
||||||
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).unknownCoveragePercent())
|
|
||||||
.isEqualTo(0.0d);
|
.isEqualTo(0.0d);
|
||||||
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).beginGeoEventId())
|
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).beginGeoEventId())
|
||||||
.isEqualTo("SUP-1");
|
.isEqualTo("SUP-1");
|
||||||
|
|
@ -296,8 +293,6 @@ class DriverTimelineReusableProjectionBuilderTest {
|
||||||
.isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
.isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
||||||
assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).endedAt())
|
assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).endedAt())
|
||||||
.isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
|
.isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
|
||||||
assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).cardPresentCoveragePercent())
|
|
||||||
.isEqualTo(100.0d);
|
|
||||||
assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).beginGeoEventId())
|
assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).beginGeoEventId())
|
||||||
.isEqualTo("SUP-1");
|
.isEqualTo("SUP-1");
|
||||||
assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).beginGeoEvent())
|
assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).beginGeoEvent())
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.service;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.DriverCardRefDto;
|
||||||
|
import at.procon.eventhub.dto.DriverRefDto;
|
||||||
|
import at.procon.eventhub.dto.EventDomain;
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.dto.EventLifecycle;
|
||||||
|
import at.procon.eventhub.dto.EventType;
|
||||||
|
import at.procon.eventhub.dto.VehicleRefDto;
|
||||||
|
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
|
||||||
|
import at.procon.eventhub.processing.service.UnifiedEventTimelineReconstructor;
|
||||||
|
import at.procon.eventhub.service.EventAcquisitionRecordKeyService;
|
||||||
|
import at.procon.eventhub.service.EventHubEventSorter;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.CreateTachographCompositeSessionRequest;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.CreateTachographCompositeSessionResponse;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographCompositeDriverEventsResponse;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriverCard;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
class TachographCompositeSessionServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createsCompositeSessionAndAggregatesDriversAcrossMemberSessions() {
|
||||||
|
TachographCompositeSessionRepository compositeRepository = new InMemoryTachographCompositeSessionRepository();
|
||||||
|
TachographFileSessionRepository fileSessionRepository = Mockito.mock(TachographFileSessionRepository.class);
|
||||||
|
DriverTimelineEventBuilder eventBuilder = Mockito.mock(DriverTimelineEventBuilder.class);
|
||||||
|
UnifiedEventTimelineReconstructor reconstructor = Mockito.mock(UnifiedEventTimelineReconstructor.class);
|
||||||
|
TachographCompositeSessionService service = new TachographCompositeSessionService(
|
||||||
|
compositeRepository,
|
||||||
|
fileSessionRepository,
|
||||||
|
eventBuilder,
|
||||||
|
reconstructor,
|
||||||
|
new EventAcquisitionRecordKeyService(),
|
||||||
|
new EventHubEventSorter()
|
||||||
|
);
|
||||||
|
|
||||||
|
UUID firstSessionId = UUID.randomUUID();
|
||||||
|
UUID secondSessionId = UUID.randomUUID();
|
||||||
|
TachographFileSession first = session(
|
||||||
|
firstSessionId,
|
||||||
|
driver("12:123", "Muster", "Max", "12", "CARD0000000001", 2, 1, List.of(new ExtractionWarning("A", "a", "a"))),
|
||||||
|
driver("13:456", "Doe", "Jane", "13", "CARD0000000002", 1, 1, List.of())
|
||||||
|
);
|
||||||
|
TachographFileSession second = session(
|
||||||
|
secondSessionId,
|
||||||
|
driver("12:123", "Muster", "Max", "12", "CARD0000000001", 3, 2, List.of()),
|
||||||
|
driver("14:789", "Smith", "John", "14", "CARD0000000003", 4, 5, List.of())
|
||||||
|
);
|
||||||
|
when(fileSessionRepository.find(firstSessionId)).thenReturn(java.util.Optional.of(first));
|
||||||
|
when(fileSessionRepository.find(secondSessionId)).thenReturn(java.util.Optional.of(second));
|
||||||
|
|
||||||
|
CreateTachographCompositeSessionResponse response = service.createCompositeSession(
|
||||||
|
new CreateTachographCompositeSessionRequest(List.of(firstSessionId, secondSessionId, firstSessionId), "fleet-week-1")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(response.session().tenantKey()).isEqualTo("default");
|
||||||
|
assertThat(response.session().label()).isEqualTo("fleet-week-1");
|
||||||
|
assertThat(response.session().memberSessionIds()).containsExactly(firstSessionId, secondSessionId);
|
||||||
|
assertThat(response.session().drivers()).hasSize(3);
|
||||||
|
assertThat(response.session().drivers())
|
||||||
|
.filteredOn(driver -> driver.driverKey().equals("12:123"))
|
||||||
|
.singleElement()
|
||||||
|
.satisfies(driverSummary -> {
|
||||||
|
assertThat(driverSummary.activityIntervalCount()).isEqualTo(5);
|
||||||
|
assertThat(driverSummary.cardVehicleUsageIntervalCount()).isEqualTo(3);
|
||||||
|
assertThat(driverSummary.surname()).isEqualTo("Muster");
|
||||||
|
assertThat(driverSummary.firstNames()).isEqualTo("Max");
|
||||||
|
assertThat(driverSummary.cardNumber()).isEqualTo("CARD0000000001");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mergesDriverEventsAcrossSessionsAndDeduplicatesBySemanticSignature() {
|
||||||
|
TachographCompositeSessionRepository compositeRepository = new InMemoryTachographCompositeSessionRepository();
|
||||||
|
TachographFileSessionRepository fileSessionRepository = Mockito.mock(TachographFileSessionRepository.class);
|
||||||
|
DriverTimelineEventBuilder eventBuilder = Mockito.mock(DriverTimelineEventBuilder.class);
|
||||||
|
UnifiedEventTimelineReconstructor reconstructor = Mockito.mock(UnifiedEventTimelineReconstructor.class);
|
||||||
|
TachographCompositeSessionService service = new TachographCompositeSessionService(
|
||||||
|
compositeRepository,
|
||||||
|
fileSessionRepository,
|
||||||
|
eventBuilder,
|
||||||
|
reconstructor,
|
||||||
|
new EventAcquisitionRecordKeyService(),
|
||||||
|
new EventHubEventSorter()
|
||||||
|
);
|
||||||
|
|
||||||
|
UUID firstSessionId = UUID.randomUUID();
|
||||||
|
UUID secondSessionId = UUID.randomUUID();
|
||||||
|
TachographFileSession first = session(firstSessionId, driver("12:123", "Muster", "Max", "12", "CARD0000000001", 0, 0, List.of()));
|
||||||
|
TachographFileSession second = session(secondSessionId, driver("12:123", "Muster", "Max", "12", "CARD0000000001", 0, 0, List.of()));
|
||||||
|
when(fileSessionRepository.find(firstSessionId)).thenReturn(java.util.Optional.of(first));
|
||||||
|
when(fileSessionRepository.find(secondSessionId)).thenReturn(java.util.Optional.of(second));
|
||||||
|
|
||||||
|
CreateTachographCompositeSessionResponse created = service.createCompositeSession(
|
||||||
|
new CreateTachographCompositeSessionRequest(List.of(firstSessionId, secondSessionId), "driver-merge")
|
||||||
|
);
|
||||||
|
|
||||||
|
DriverExtractionSession firstDriver = first.driversByKey().get("12:123");
|
||||||
|
DriverExtractionSession secondDriver = second.driversByKey().get("12:123");
|
||||||
|
OffsetDateTime firstTimestamp = OffsetDateTime.parse("2026-05-22T08:00:00Z");
|
||||||
|
OffsetDateTime secondTimestamp = OffsetDateTime.parse("2026-05-22T09:00:00Z");
|
||||||
|
when(eventBuilder.buildEvents(first, firstDriver)).thenReturn(List.of(
|
||||||
|
event("duplicate-a", firstTimestamp, EventType.WORK),
|
||||||
|
event("unique-a", secondTimestamp, EventType.DRIVE)
|
||||||
|
));
|
||||||
|
when(eventBuilder.buildEvents(second, secondDriver)).thenReturn(List.of(
|
||||||
|
event("duplicate-b", firstTimestamp, EventType.WORK),
|
||||||
|
event("unique-b", secondTimestamp.plusMinutes(30), EventType.BREAK_REST)
|
||||||
|
));
|
||||||
|
|
||||||
|
TachographCompositeDriverEventsResponse response =
|
||||||
|
service.getMergedDriverEvents(created.session().compositeSessionId(), "12:123");
|
||||||
|
|
||||||
|
assertThat(response.sourceSessionIds()).containsExactly(firstSessionId, secondSessionId);
|
||||||
|
assertThat(response.eventCount()).isEqualTo(3);
|
||||||
|
assertThat(response.events()).extracting(EventHubEventDto::externalSourceEventId)
|
||||||
|
.containsExactly("duplicate-a", "unique-a", "unique-b");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reconstructsMergedDriverTimelineFromMergedEventsAndWarnings() {
|
||||||
|
TachographCompositeSessionRepository compositeRepository = new InMemoryTachographCompositeSessionRepository();
|
||||||
|
TachographFileSessionRepository fileSessionRepository = Mockito.mock(TachographFileSessionRepository.class);
|
||||||
|
DriverTimelineEventBuilder eventBuilder = Mockito.mock(DriverTimelineEventBuilder.class);
|
||||||
|
UnifiedEventTimelineReconstructor reconstructor = Mockito.mock(UnifiedEventTimelineReconstructor.class);
|
||||||
|
TachographCompositeSessionService service = new TachographCompositeSessionService(
|
||||||
|
compositeRepository,
|
||||||
|
fileSessionRepository,
|
||||||
|
eventBuilder,
|
||||||
|
reconstructor,
|
||||||
|
new EventAcquisitionRecordKeyService(),
|
||||||
|
new EventHubEventSorter()
|
||||||
|
);
|
||||||
|
|
||||||
|
UUID firstSessionId = UUID.randomUUID();
|
||||||
|
UUID secondSessionId = UUID.randomUUID();
|
||||||
|
DriverExtractionSession firstDriver = driver("12:123", "Muster", "Max", "12", "CARD0000000001", 0, 0, List.of(
|
||||||
|
new ExtractionWarning("CARD_GAP", "gap", "p1")
|
||||||
|
));
|
||||||
|
DriverExtractionSession secondDriver = driver("12:123", "Muster", "Max", "12", "CARD0000000001", 0, 0, List.of(
|
||||||
|
new ExtractionWarning("DUPLICATE", "duplicate", "p2")
|
||||||
|
));
|
||||||
|
TachographFileSession first = session(firstSessionId, List.of(new ExtractionWarning("SESSION_A", "warn-a", "s1")), firstDriver);
|
||||||
|
TachographFileSession second = session(secondSessionId, List.of(new ExtractionWarning("SESSION_B", "warn-b", "s2")), secondDriver);
|
||||||
|
when(fileSessionRepository.find(firstSessionId)).thenReturn(java.util.Optional.of(first));
|
||||||
|
when(fileSessionRepository.find(secondSessionId)).thenReturn(java.util.Optional.of(second));
|
||||||
|
|
||||||
|
CreateTachographCompositeSessionResponse created = service.createCompositeSession(
|
||||||
|
new CreateTachographCompositeSessionRequest(List.of(firstSessionId, secondSessionId), null)
|
||||||
|
);
|
||||||
|
|
||||||
|
OffsetDateTime occurredAt = OffsetDateTime.parse("2026-05-22T08:00:00Z");
|
||||||
|
when(eventBuilder.buildEvents(first, firstDriver)).thenReturn(List.of(event("a", occurredAt, EventType.WORK)));
|
||||||
|
when(eventBuilder.buildEvents(second, secondDriver)).thenReturn(List.of(event("b", occurredAt.plusHours(1), EventType.DRIVE)));
|
||||||
|
|
||||||
|
ResolvedDriverTimeline expected = new ResolvedDriverTimeline(
|
||||||
|
"COMPOSITE_TACHOGRAPH_FILE_SESSION",
|
||||||
|
occurredAt,
|
||||||
|
occurredAt.plusHours(1),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
when(reconstructor.reconstruct(any(), eq("12:123"), any(), any(), eq("COMPOSITE_TACHOGRAPH_FILE_SESSION")))
|
||||||
|
.thenReturn(expected);
|
||||||
|
|
||||||
|
ResolvedDriverTimeline actual =
|
||||||
|
service.getMergedDriverTimeline(created.session().compositeSessionId(), "12:123");
|
||||||
|
|
||||||
|
assertThat(actual).isSameAs(expected);
|
||||||
|
ArgumentCaptor<List<EventHubEventDto>> eventsCaptor = ArgumentCaptor.forClass(List.class);
|
||||||
|
ArgumentCaptor<List<ExtractionWarning>> warningsCaptor = ArgumentCaptor.forClass(List.class);
|
||||||
|
verify(reconstructor).reconstruct(
|
||||||
|
eq(created.session().compositeSessionId()),
|
||||||
|
eq("12:123"),
|
||||||
|
eventsCaptor.capture(),
|
||||||
|
warningsCaptor.capture(),
|
||||||
|
eq("COMPOSITE_TACHOGRAPH_FILE_SESSION")
|
||||||
|
);
|
||||||
|
assertThat(eventsCaptor.getValue()).extracting(EventHubEventDto::externalSourceEventId)
|
||||||
|
.containsExactly("a", "b");
|
||||||
|
assertThat(warningsCaptor.getValue()).containsExactly(
|
||||||
|
new ExtractionWarning("SESSION_A", "warn-a", "s1"),
|
||||||
|
new ExtractionWarning("CARD_GAP", "gap", "p1"),
|
||||||
|
new ExtractionWarning("SESSION_B", "warn-b", "s2"),
|
||||||
|
new ExtractionWarning("DUPLICATE", "duplicate", "p2")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsMissingDriverInCompositeSession() {
|
||||||
|
TachographCompositeSessionRepository compositeRepository = new InMemoryTachographCompositeSessionRepository();
|
||||||
|
TachographFileSessionRepository fileSessionRepository = Mockito.mock(TachographFileSessionRepository.class);
|
||||||
|
TachographCompositeSessionService service = new TachographCompositeSessionService(
|
||||||
|
compositeRepository,
|
||||||
|
fileSessionRepository,
|
||||||
|
Mockito.mock(DriverTimelineEventBuilder.class),
|
||||||
|
Mockito.mock(UnifiedEventTimelineReconstructor.class),
|
||||||
|
new EventAcquisitionRecordKeyService(),
|
||||||
|
new EventHubEventSorter()
|
||||||
|
);
|
||||||
|
|
||||||
|
UUID sessionId = UUID.randomUUID();
|
||||||
|
when(fileSessionRepository.find(sessionId)).thenReturn(java.util.Optional.of(
|
||||||
|
session(sessionId, driver("12:123", "Muster", "Max", "12", "CARD0000000001", 0, 0, List.of()))
|
||||||
|
));
|
||||||
|
|
||||||
|
CreateTachographCompositeSessionResponse created = service.createCompositeSession(
|
||||||
|
new CreateTachographCompositeSessionRequest(List.of(sessionId), null)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> service.getMergedDriverEvents(created.session().compositeSessionId(), "missing"))
|
||||||
|
.isInstanceOf(DriverNotFoundInCompositeSessionException.class)
|
||||||
|
.hasMessageContaining("missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TachographFileSession session(UUID sessionId, DriverExtractionSession... drivers) {
|
||||||
|
return session(sessionId, List.of(), drivers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TachographFileSession session(
|
||||||
|
UUID sessionId,
|
||||||
|
List<ExtractionWarning> warnings,
|
||||||
|
DriverExtractionSession... drivers
|
||||||
|
) {
|
||||||
|
Map<String, DriverExtractionSession> byKey = java.util.Arrays.stream(drivers)
|
||||||
|
.collect(java.util.stream.Collectors.toMap(DriverExtractionSession::driverKey, driver -> driver));
|
||||||
|
return new TachographFileSession(
|
||||||
|
sessionId,
|
||||||
|
new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "sha", 3, "42", "xml", true, null),
|
||||||
|
byKey,
|
||||||
|
new ExtractionStats(byKey.size(), 0, 0, 0, 0, warnings.size()),
|
||||||
|
warnings,
|
||||||
|
Instant.parse("2026-05-22T08:00:00Z"),
|
||||||
|
Instant.parse("2026-05-22T12:00:00Z")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DriverExtractionSession driver(
|
||||||
|
String driverKey,
|
||||||
|
String surname,
|
||||||
|
String firstNames,
|
||||||
|
String cardNation,
|
||||||
|
String cardNumber,
|
||||||
|
int activityIntervalCount,
|
||||||
|
int cardVehicleUsageIntervalCount,
|
||||||
|
List<ExtractionWarning> warnings
|
||||||
|
) {
|
||||||
|
return new DriverExtractionSession(
|
||||||
|
driverKey,
|
||||||
|
new ExtractedDriver(driverKey, "SOURCE:" + driverKey, surname, firstNames, LocalDate.parse("1980-01-01"), null, null, null, null),
|
||||||
|
new ExtractedDriverCard("CARD:" + driverKey, cardNation, cardNumber, cardNumber + "01", null, null, null, null),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
java.util.Collections.nCopies(cardVehicleUsageIntervalCount, null),
|
||||||
|
java.util.Collections.nCopies(activityIntervalCount, null),
|
||||||
|
List.of(),
|
||||||
|
warnings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EventHubEventDto event(String externalSourceEventId, OffsetDateTime occurredAt, EventType eventType) {
|
||||||
|
return new EventHubEventDto(
|
||||||
|
null,
|
||||||
|
externalSourceEventId,
|
||||||
|
new DriverRefDto(null, new DriverCardRefDto("12", 12, "CARD0000000001")),
|
||||||
|
new VehicleRefDto(null, "VIN00000000000001", null, new VehicleRegistrationRefDto("12", 12, "W-12345A")),
|
||||||
|
occurredAt,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
eventType == EventType.DRIVE ? EventDomain.DRIVER_ACTIVITY : EventDomain.DRIVER_ACTIVITY,
|
||||||
|
eventType,
|
||||||
|
eventType == EventType.BREAK_REST ? EventLifecycle.END : EventLifecycle.START,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -391,10 +391,9 @@ class TachographFileSessionProcessingServiceTest {
|
||||||
assertThat(result.dailyWeeklyRestCandidateIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
|
assertThat(result.dailyWeeklyRestCandidateIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
|
||||||
assertThat(result.dailyWeeklyRestCandidateIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T23:00:00Z"));
|
assertThat(result.dailyWeeklyRestCandidateIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T23:00:00Z"));
|
||||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
|
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
|
||||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).unknownDurationSeconds()).isEqualTo(43_200L);
|
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(50_399L);
|
||||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).unknownCoveragePercent()).isEqualTo(100.0d);
|
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentCoveragePercent())
|
||||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(0L);
|
.isCloseTo(99.998d, org.assertj.core.data.Offset.offset(0.001d));
|
||||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(0.0d);
|
|
||||||
assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
|
assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
|
||||||
assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).previousRegistrationKey()).isEqualTo("12:REG-1");
|
assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).previousRegistrationKey()).isEqualTo("12:REG-1");
|
||||||
assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).nextRegistrationKey()).isEqualTo("12:REG-2");
|
assertThat(result.drivingInterruptionVehicleChangeIntervals().get(0).nextRegistrationKey()).isEqualTo("12:REG-2");
|
||||||
|
|
@ -403,10 +402,9 @@ class TachographFileSessionProcessingServiceTest {
|
||||||
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
|
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
|
||||||
assertThat(result.potentialHomeOvernightStayIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
|
assertThat(result.potentialHomeOvernightStayIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T11:00:00Z"));
|
||||||
assertThat(result.potentialHomeOvernightStayIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T23:00:00Z"));
|
assertThat(result.potentialHomeOvernightStayIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T23:00:00Z"));
|
||||||
assertThat(result.potentialHomeOvernightStayIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(0L);
|
assertThat(result.potentialHomeOvernightStayIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(50_399L);
|
||||||
assertThat(result.potentialHomeOvernightStayIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(0.0d);
|
assertThat(result.potentialHomeOvernightStayIntervals().get(0).cardAbsentCoveragePercent())
|
||||||
assertThat(result.potentialHomeOvernightStayIntervals().get(0).unknownDurationSeconds()).isEqualTo(43_200L);
|
.isCloseTo(99.998d, org.assertj.core.data.Offset.offset(0.001d));
|
||||||
assertThat(result.potentialHomeOvernightStayIntervals().get(0).unknownCoveragePercent()).isEqualTo(100.0d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -483,15 +481,11 @@ class TachographFileSessionProcessingServiceTest {
|
||||||
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
|
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
|
||||||
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(1);
|
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(1);
|
||||||
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
|
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
|
||||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(50_400L);
|
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(0L);
|
||||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(100.0d);
|
|
||||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).unknownDurationSeconds()).isEqualTo(0L);
|
|
||||||
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
||||||
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
|
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
|
||||||
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(50_400L);
|
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(0L);
|
||||||
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(100.0d);
|
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardAbsentCoveragePercent()).isEqualTo(0.0d);
|
||||||
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).unknownDurationSeconds()).isEqualTo(0L);
|
|
||||||
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).unknownCoveragePercent()).isEqualTo(0.0d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -580,9 +574,7 @@ class TachographFileSessionProcessingServiceTest {
|
||||||
.isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
.isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
||||||
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).endedAt())
|
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).endedAt())
|
||||||
.isEqualTo(OffsetDateTime.parse("2026-05-02T00:30:00Z"));
|
.isEqualTo(OffsetDateTime.parse("2026-05-02T00:30:00Z"));
|
||||||
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentCoveragePercent())
|
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentCoveragePercent())
|
||||||
.isLessThan(95.0d);
|
|
||||||
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).unknownCoveragePercent())
|
|
||||||
.isLessThan(95.0d);
|
.isLessThan(95.0d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue