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 endedAt,
|
||||
long durationSeconds,
|
||||
long cardPresentDurationSeconds,
|
||||
double cardPresentCoveragePercent,
|
||||
long unknownDurationSeconds,
|
||||
double unknownCoveragePercent,
|
||||
long cardAbsentDurationSeconds,
|
||||
double cardAbsentCoveragePercent,
|
||||
String previousDrivingSourceIntervalId,
|
||||
String nextDrivingSourceIntervalId,
|
||||
String previousRegistrationKey,
|
||||
|
|
|
|||
|
|
@ -9,10 +9,8 @@ public record TachographEsperPotentialHomeOvernightStayIntervalEvent(
|
|||
OffsetDateTime startedAt,
|
||||
OffsetDateTime endedAt,
|
||||
long durationSeconds,
|
||||
long cardPresentDurationSeconds,
|
||||
double cardPresentCoveragePercent,
|
||||
long unknownDurationSeconds,
|
||||
double unknownCoveragePercent,
|
||||
long cardAbsentDurationSeconds,
|
||||
double cardAbsentCoveragePercent,
|
||||
String previousDrivingSourceIntervalId,
|
||||
String nextDrivingSourceIntervalId,
|
||||
String previousRegistrationKey,
|
||||
|
|
|
|||
|
|
@ -9,10 +9,8 @@ public record TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
|
|||
OffsetDateTime startedAt,
|
||||
OffsetDateTime endedAt,
|
||||
long durationSeconds,
|
||||
long cardPresentDurationSeconds,
|
||||
double cardPresentCoveragePercent,
|
||||
long unknownDurationSeconds,
|
||||
double unknownCoveragePercent,
|
||||
long cardAbsentDurationSeconds,
|
||||
double cardAbsentCoveragePercent,
|
||||
String previousDrivingSourceIntervalId,
|
||||
String nextDrivingSourceIntervalId,
|
||||
String previousRegistrationKey,
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ public record TachographEsperPotentialInVehicleTripIntervalEvent(
|
|||
String vehicleKey,
|
||||
int containedPotentialInVehicleOvernightStayIntervalCount,
|
||||
long containedPotentialInVehicleOvernightStayDurationSeconds,
|
||||
long containedCardPresentDurationSeconds,
|
||||
long containedUnknownDurationSeconds,
|
||||
long containedCardAbsentDurationSeconds,
|
||||
OffsetDateTime firstPotentialInVehicleOvernightStayStartedAt,
|
||||
OffsetDateTime lastPotentialInVehicleOvernightStayEndedAt,
|
||||
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("endedAt"),
|
||||
(Long) event.get("durationSeconds"),
|
||||
0L,
|
||||
0.0d,
|
||||
(Long) event.get("unknownDurationSeconds"),
|
||||
(Double) event.get("unknownCoveragePercent"),
|
||||
(Long) event.get("cardAbsentDurationSeconds"),
|
||||
(Double) event.get("cardAbsentCoveragePercent"),
|
||||
(String) event.get("previousDrivingSourceIntervalId"),
|
||||
(String) event.get("nextDrivingSourceIntervalId"),
|
||||
(String) event.get("previousRegistrationKey"),
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import java.util.Comparator;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
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(
|
||||
UUID sessionId,
|
||||
String driverKey,
|
||||
|
|
@ -87,27 +136,23 @@ public class DriverTimelineReusableProjectionBuilder {
|
|||
return emptyBundle();
|
||||
}
|
||||
return buildEsperDrivingDerivedProjectionBundle(
|
||||
sessionId,
|
||||
driverKey,
|
||||
timeline.activityIntervals(),
|
||||
timeline.vehicleUsageIntervals(),
|
||||
timeline.supportEvents(),
|
||||
buildActivityIntervalInputEvents(sessionId, driverKey, timeline.activityIntervals()),
|
||||
buildVehicleUsageIntervalInputEvents(timeline.vehicleUsageIntervals()),
|
||||
buildSupportGeoInputEvents(sessionId, timeline.supportEvents()),
|
||||
significantDrivingMinutes,
|
||||
minimumRestPeriodMinutes
|
||||
);
|
||||
}
|
||||
|
||||
private TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
|
||||
UUID sessionId,
|
||||
String driverKey,
|
||||
List<ResolvedActivityInterval> activityIntervals,
|
||||
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
|
||||
List<ExtractedSupportEvent> supportEvents,
|
||||
List<Map<String, Object>> activityInputEvents,
|
||||
List<Map<String, Object>> vehicleUsageInputEvents,
|
||||
List<Map<String, Object>> supportGeoInputEvents,
|
||||
int significantDrivingMinutes,
|
||||
int minimumRestPeriodMinutes
|
||||
) {
|
||||
if ((activityIntervals == null || activityIntervals.isEmpty())
|
||||
&& (vehicleUsageIntervals == null || vehicleUsageIntervals.isEmpty())) {
|
||||
if ((activityInputEvents == null || activityInputEvents.isEmpty())
|
||||
&& (vehicleUsageInputEvents == null || vehicleUsageInputEvents.isEmpty())) {
|
||||
return emptyBundle();
|
||||
}
|
||||
|
||||
|
|
@ -149,29 +194,26 @@ public class DriverTimelineReusableProjectionBuilder {
|
|||
"potentialInVehicleTripIntervals", newData -> collectPotentialInVehicleTripIntervalEvents(newData, potentialInVehicleTripIntervals)
|
||||
),
|
||||
runtime -> {
|
||||
if (supportEvents != null) {
|
||||
for (ExtractedSupportEvent supportEvent : supportEvents) {
|
||||
Map<String, Object> supportGeoEvidence = toSupportGeoEvidenceInputMap(sessionId, supportEvent);
|
||||
if (supportGeoEvidence != null) {
|
||||
if (supportGeoInputEvents != null) {
|
||||
for (Map<String, Object> supportGeoEvidence : supportGeoInputEvents) {
|
||||
runtime.getEventService().sendEventMap(
|
||||
supportGeoEvidence,
|
||||
"TachographSupportGeoEvidenceInputEvent"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vehicleUsageIntervals != null) {
|
||||
for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) {
|
||||
if (vehicleUsageInputEvents != null) {
|
||||
for (Map<String, Object> interval : vehicleUsageInputEvents) {
|
||||
runtime.getEventService().sendEventMap(
|
||||
toVehicleUsageIntervalInputMap(interval),
|
||||
interval,
|
||||
"TachographVehicleUsageIntervalInputEvent"
|
||||
);
|
||||
}
|
||||
}
|
||||
if (activityIntervals != null) {
|
||||
for (ResolvedActivityInterval interval : activityIntervals) {
|
||||
if (activityInputEvents != null) {
|
||||
for (Map<String, Object> interval : activityInputEvents) {
|
||||
runtime.getEventService().sendEventMap(
|
||||
toActivityIntervalInputMap(sessionId, driverKey, interval),
|
||||
interval,
|
||||
"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() {
|
||||
return new TachographEsperDrivingDerivedProjectionBundle(
|
||||
List.of(),
|
||||
|
|
@ -503,10 +589,8 @@ public class DriverTimelineReusableProjectionBuilder {
|
|||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
||||
(Long) event.get("durationSeconds"),
|
||||
(Long) event.get("cardPresentDurationSeconds"),
|
||||
(Double) event.get("cardPresentCoveragePercent"),
|
||||
(Long) event.get("unknownDurationSeconds"),
|
||||
(Double) event.get("unknownCoveragePercent"),
|
||||
(Long) event.get("cardAbsentDurationSeconds"),
|
||||
(Double) event.get("cardAbsentCoveragePercent"),
|
||||
(String) event.get("previousDrivingSourceIntervalId"),
|
||||
(String) event.get("nextDrivingSourceIntervalId"),
|
||||
(String) event.get("previousRegistrationKey"),
|
||||
|
|
@ -571,10 +655,8 @@ public class DriverTimelineReusableProjectionBuilder {
|
|||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
||||
(Long) event.get("durationSeconds"),
|
||||
(Long) event.get("cardPresentDurationSeconds"),
|
||||
(Double) event.get("cardPresentCoveragePercent"),
|
||||
(Long) event.get("unknownDurationSeconds"),
|
||||
(Double) event.get("unknownCoveragePercent"),
|
||||
(Long) event.get("cardAbsentDurationSeconds"),
|
||||
(Double) event.get("cardAbsentCoveragePercent"),
|
||||
(String) event.get("previousDrivingSourceIntervalId"),
|
||||
(String) event.get("nextDrivingSourceIntervalId"),
|
||||
(String) event.get("previousRegistrationKey"),
|
||||
|
|
@ -639,10 +721,8 @@ public class DriverTimelineReusableProjectionBuilder {
|
|||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
||||
(Long) event.get("durationSeconds"),
|
||||
(Long) event.get("cardPresentDurationSeconds"),
|
||||
(Double) event.get("cardPresentCoveragePercent"),
|
||||
(Long) event.get("unknownDurationSeconds"),
|
||||
(Double) event.get("unknownCoveragePercent"),
|
||||
(Long) event.get("cardAbsentDurationSeconds"),
|
||||
(Double) event.get("cardAbsentCoveragePercent"),
|
||||
(String) event.get("previousDrivingSourceIntervalId"),
|
||||
(String) event.get("nextDrivingSourceIntervalId"),
|
||||
(String) event.get("previousRegistrationKey"),
|
||||
|
|
@ -693,8 +773,7 @@ public class DriverTimelineReusableProjectionBuilder {
|
|||
(String) event.get("vehicleKey"),
|
||||
(Integer) event.get("containedPotentialInVehicleOvernightStayIntervalCount"),
|
||||
(Long) event.get("containedPotentialInVehicleOvernightStayDurationSeconds"),
|
||||
(Long) event.get("containedCardPresentDurationSeconds"),
|
||||
(Long) event.get("containedUnknownDurationSeconds"),
|
||||
(Long) event.get("containedCardAbsentDurationSeconds"),
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond((Long) event.get("firstPotentialInVehicleOvernightStayStartedAtEpochSecond")), ZoneOffset.UTC),
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochSecond((Long) event.get("lastPotentialInVehicleOvernightStayEndedAtEpochSecond")), ZoneOffset.UTC),
|
||||
(String) event.get("firstPreviousDrivingSourceIntervalId"),
|
||||
|
|
@ -725,7 +804,7 @@ public class DriverTimelineReusableProjectionBuilder {
|
|||
private List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> sortDailyWeeklyRestCandidateCoverageIntervals(
|
||||
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> intervals
|
||||
) {
|
||||
return intervals.stream()
|
||||
return deduplicateRestCoverageIntervals(intervals).stream()
|
||||
.sorted(Comparator.comparing(TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent::startedAt)
|
||||
.thenComparing(TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent::endedAt))
|
||||
.toList();
|
||||
|
|
@ -734,7 +813,7 @@ public class DriverTimelineReusableProjectionBuilder {
|
|||
private List<TachographEsperPotentialHomeOvernightStayIntervalEvent> sortPotentialHomeOvernightStayIntervals(
|
||||
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> intervals
|
||||
) {
|
||||
return intervals.stream()
|
||||
return deduplicatePotentialHomeOvernightStayIntervals(intervals).stream()
|
||||
.sorted(Comparator.comparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::startedAt)
|
||||
.thenComparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::endedAt))
|
||||
.toList();
|
||||
|
|
@ -743,12 +822,76 @@ public class DriverTimelineReusableProjectionBuilder {
|
|||
private List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> sortPotentialInVehicleOvernightStayIntervals(
|
||||
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> intervals
|
||||
) {
|
||||
return intervals.stream()
|
||||
return deduplicatePotentialInVehicleOvernightStayIntervals(intervals).stream()
|
||||
.sorted(Comparator.comparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::startedAt)
|
||||
.thenComparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::endedAt))
|
||||
.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(
|
||||
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;
|
||||
}
|
||||
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 endBoundaryChanged = !end.equals(interval.endedAt());
|
||||
return new TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent(
|
||||
|
|
@ -553,16 +534,16 @@ public class TachographFileSessionProcessingService {
|
|||
start,
|
||||
end,
|
||||
durationSeconds,
|
||||
cardPresentDurationSeconds,
|
||||
cardPresentCoveragePercent,
|
||||
unknownDurationSeconds,
|
||||
unknownCoveragePercent,
|
||||
interval.cardAbsentDurationSeconds(),
|
||||
interval.cardAbsentCoveragePercent(),
|
||||
interval.previousDrivingSourceIntervalId(),
|
||||
interval.nextDrivingSourceIntervalId(),
|
||||
interval.previousRegistrationKey(),
|
||||
interval.nextRegistrationKey(),
|
||||
interval.previousVehicleKey(),
|
||||
interval.nextVehicleKey(),
|
||||
beginBoundaryChanged ? null : interval.beginBoundaryOdometerKm(),
|
||||
endBoundaryChanged ? null : interval.endBoundaryOdometerKm(),
|
||||
beginBoundaryChanged ? null : interval.beginGeoEventId(),
|
||||
beginBoundaryChanged ? null : interval.beginGeoEventDomain(),
|
||||
beginBoundaryChanged ? null : interval.beginGeoOccurredAt(),
|
||||
|
|
@ -607,25 +588,6 @@ public class TachographFileSessionProcessingService {
|
|||
return null;
|
||||
}
|
||||
long durationSeconds = Duration.between(start, end).getSeconds();
|
||||
long cardPresentDurationSeconds = overlapVehicleUsageSeconds(
|
||||
start,
|
||||
end,
|
||||
rawVehicleUsageIntervals,
|
||||
interval.driverKey(),
|
||||
null
|
||||
);
|
||||
double cardPresentCoveragePercent = durationSeconds == 0L
|
||||
? 0.0d
|
||||
: (cardPresentDurationSeconds * 100.0d) / durationSeconds;
|
||||
long unknownDurationSeconds = overlapSeconds(
|
||||
start,
|
||||
end,
|
||||
rawVuCardAbsentIntervals,
|
||||
interval.driverKey()
|
||||
);
|
||||
double unknownCoveragePercent = durationSeconds == 0L
|
||||
? 0.0d
|
||||
: (unknownDurationSeconds * 100.0d) / durationSeconds;
|
||||
boolean beginBoundaryChanged = !start.equals(interval.startedAt());
|
||||
boolean endBoundaryChanged = !end.equals(interval.endedAt());
|
||||
return new TachographEsperPotentialHomeOvernightStayIntervalEvent(
|
||||
|
|
@ -634,16 +596,16 @@ public class TachographFileSessionProcessingService {
|
|||
start,
|
||||
end,
|
||||
durationSeconds,
|
||||
cardPresentDurationSeconds,
|
||||
cardPresentCoveragePercent,
|
||||
unknownDurationSeconds,
|
||||
unknownCoveragePercent,
|
||||
interval.cardAbsentDurationSeconds(),
|
||||
interval.cardAbsentCoveragePercent(),
|
||||
interval.previousDrivingSourceIntervalId(),
|
||||
interval.nextDrivingSourceIntervalId(),
|
||||
interval.previousRegistrationKey(),
|
||||
interval.nextRegistrationKey(),
|
||||
interval.previousVehicleKey(),
|
||||
interval.nextVehicleKey(),
|
||||
beginBoundaryChanged ? null : interval.beginBoundaryOdometerKm(),
|
||||
endBoundaryChanged ? null : interval.endBoundaryOdometerKm(),
|
||||
beginBoundaryChanged ? null : interval.beginGeoEventId(),
|
||||
beginBoundaryChanged ? null : interval.beginGeoEventDomain(),
|
||||
beginBoundaryChanged ? null : interval.beginGeoOccurredAt(),
|
||||
|
|
@ -688,25 +650,6 @@ public class TachographFileSessionProcessingService {
|
|||
return null;
|
||||
}
|
||||
long durationSeconds = Duration.between(start, end).getSeconds();
|
||||
long cardPresentDurationSeconds = overlapVehicleUsageSeconds(
|
||||
start,
|
||||
end,
|
||||
rawVehicleUsageIntervals,
|
||||
interval.driverKey(),
|
||||
null
|
||||
);
|
||||
double cardPresentCoveragePercent = durationSeconds == 0L
|
||||
? 0.0d
|
||||
: (cardPresentDurationSeconds * 100.0d) / durationSeconds;
|
||||
long unknownDurationSeconds = overlapSeconds(
|
||||
start,
|
||||
end,
|
||||
rawVuCardAbsentIntervals,
|
||||
interval.driverKey()
|
||||
);
|
||||
double unknownCoveragePercent = durationSeconds == 0L
|
||||
? 0.0d
|
||||
: (unknownDurationSeconds * 100.0d) / durationSeconds;
|
||||
boolean beginBoundaryChanged = !start.equals(interval.startedAt());
|
||||
boolean endBoundaryChanged = !end.equals(interval.endedAt());
|
||||
return new TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
|
||||
|
|
@ -715,16 +658,16 @@ public class TachographFileSessionProcessingService {
|
|||
start,
|
||||
end,
|
||||
durationSeconds,
|
||||
cardPresentDurationSeconds,
|
||||
cardPresentCoveragePercent,
|
||||
unknownDurationSeconds,
|
||||
unknownCoveragePercent,
|
||||
interval.cardAbsentDurationSeconds(),
|
||||
interval.cardAbsentCoveragePercent(),
|
||||
interval.previousDrivingSourceIntervalId(),
|
||||
interval.nextDrivingSourceIntervalId(),
|
||||
interval.previousRegistrationKey(),
|
||||
interval.nextRegistrationKey(),
|
||||
interval.previousVehicleKey(),
|
||||
interval.nextVehicleKey(),
|
||||
beginBoundaryChanged ? null : interval.beginBoundaryOdometerKm(),
|
||||
endBoundaryChanged ? null : interval.endBoundaryOdometerKm(),
|
||||
beginBoundaryChanged ? null : interval.beginGeoEventId(),
|
||||
beginBoundaryChanged ? null : interval.beginGeoEventDomain(),
|
||||
beginBoundaryChanged ? null : interval.beginGeoOccurredAt(),
|
||||
|
|
@ -803,10 +746,7 @@ public class TachographFileSessionProcessingService {
|
|||
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::durationSeconds)
|
||||
.sum(),
|
||||
containedIntervals.stream()
|
||||
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::cardPresentDurationSeconds)
|
||||
.sum(),
|
||||
containedIntervals.stream()
|
||||
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::unknownDurationSeconds)
|
||||
.mapToLong(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::cardAbsentDurationSeconds)
|
||||
.sum(),
|
||||
first.startedAt(),
|
||||
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 vehicle-change intervals are daily/weekly rest candidates where previousRegistrationKey differs from nextRegistrationKey.",
|
||||
"Daily/weekly rest candidate intervals are driving interruption intervals longer than the configured minimum rest-period threshold.",
|
||||
"Daily/weekly rest candidate coverage intervals enrich each rest candidate with card-present and unknown-coverage metrics computed from vehicle-usage and VU card-absent overlap.",
|
||||
"Daily/weekly rest candidate coverage intervals 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.",
|
||||
"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.",
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ create schema DailyWeeklyRestCandidateCoverageUnknownResolvedInterval(
|
|||
startedAtEpochSecond long,
|
||||
endedAtEpochSecond long,
|
||||
durationSeconds long,
|
||||
unknownDurationSeconds long,
|
||||
cardAbsentDurationSeconds long,
|
||||
previousDrivingSourceIntervalId string,
|
||||
nextDrivingSourceIntervalId string,
|
||||
previousRegistrationKey string,
|
||||
|
|
@ -87,8 +87,7 @@ create schema DailyWeeklyRestCandidateCoverageCardResolvedInterval(
|
|||
startedAtEpochSecond long,
|
||||
endedAtEpochSecond long,
|
||||
durationSeconds long,
|
||||
cardPresentDurationSeconds long,
|
||||
unknownDurationSeconds long,
|
||||
cardAbsentDurationSeconds long,
|
||||
previousDrivingSourceIntervalId string,
|
||||
nextDrivingSourceIntervalId string,
|
||||
previousRegistrationKey string,
|
||||
|
|
@ -103,10 +102,8 @@ create schema DailyWeeklyRestCandidateCoverageInterval(
|
|||
startedAtEpochSecond long,
|
||||
endedAtEpochSecond long,
|
||||
durationSeconds long,
|
||||
cardPresentDurationSeconds long,
|
||||
cardPresentCoveragePercent double,
|
||||
unknownDurationSeconds long,
|
||||
unknownCoveragePercent double,
|
||||
cardAbsentDurationSeconds long,
|
||||
cardAbsentCoveragePercent double,
|
||||
previousDrivingSourceIntervalId string,
|
||||
nextDrivingSourceIntervalId string,
|
||||
previousRegistrationKey string,
|
||||
|
|
@ -277,6 +274,12 @@ create schema DailyWeeklyRestCandidateCoverageFinalizationRequest(
|
|||
endedAtEpochSecond long
|
||||
);
|
||||
|
||||
create schema DailyWeeklyRestCandidateCoverageEmittedKey(
|
||||
driverKey string,
|
||||
startedAtEpochSecond long,
|
||||
endedAtEpochSecond long
|
||||
);
|
||||
|
||||
create context PerDriver partition by driverKey from TachographVehicleUsageIntervalInputEvent;
|
||||
|
||||
create schema VuCardAbsentInterval(
|
||||
|
|
@ -299,10 +302,8 @@ create schema PotentialHomeOvernightStayInterval(
|
|||
startedAtEpochSecond long,
|
||||
endedAtEpochSecond long,
|
||||
durationSeconds long,
|
||||
cardPresentDurationSeconds long,
|
||||
cardPresentCoveragePercent double,
|
||||
unknownDurationSeconds long,
|
||||
unknownCoveragePercent double,
|
||||
cardAbsentDurationSeconds long,
|
||||
cardAbsentCoveragePercent double,
|
||||
previousDrivingSourceIntervalId string,
|
||||
nextDrivingSourceIntervalId string,
|
||||
previousRegistrationKey string,
|
||||
|
|
@ -335,10 +336,8 @@ create schema PotentialInVehicleOvernightStayInterval(
|
|||
startedAtEpochSecond long,
|
||||
endedAtEpochSecond long,
|
||||
durationSeconds long,
|
||||
cardPresentDurationSeconds long,
|
||||
cardPresentCoveragePercent double,
|
||||
unknownDurationSeconds long,
|
||||
unknownCoveragePercent double,
|
||||
cardAbsentDurationSeconds long,
|
||||
cardAbsentCoveragePercent double,
|
||||
previousDrivingSourceIntervalId string,
|
||||
nextDrivingSourceIntervalId string,
|
||||
previousRegistrationKey string,
|
||||
|
|
@ -371,10 +370,8 @@ create schema UnclassifiedDailyWeeklyRestCandidateCoverageInterval(
|
|||
startedAtEpochSecond long,
|
||||
endedAtEpochSecond long,
|
||||
durationSeconds long,
|
||||
cardPresentDurationSeconds long,
|
||||
cardPresentCoveragePercent double,
|
||||
unknownDurationSeconds long,
|
||||
unknownCoveragePercent double,
|
||||
cardAbsentDurationSeconds long,
|
||||
cardAbsentCoveragePercent double,
|
||||
previousDrivingSourceIntervalId string,
|
||||
nextDrivingSourceIntervalId string,
|
||||
previousRegistrationKey string,
|
||||
|
|
@ -409,8 +406,7 @@ create schema PotentialInVehicleTripState(
|
|||
vehicleKey string,
|
||||
containedPotentialInVehicleOvernightStayIntervalCount int,
|
||||
containedPotentialInVehicleOvernightStayDurationSeconds long,
|
||||
containedCardPresentDurationSeconds long,
|
||||
containedUnknownDurationSeconds long,
|
||||
containedCardAbsentDurationSeconds long,
|
||||
firstPotentialInVehicleOvernightStayStartedAtEpochSecond long,
|
||||
lastPotentialInVehicleOvernightStayEndedAtEpochSecond long,
|
||||
firstPreviousDrivingSourceIntervalId string,
|
||||
|
|
@ -427,8 +423,7 @@ create schema PotentialInVehicleTripInterval(
|
|||
vehicleKey string,
|
||||
containedPotentialInVehicleOvernightStayIntervalCount int,
|
||||
containedPotentialInVehicleOvernightStayDurationSeconds long,
|
||||
containedCardPresentDurationSeconds long,
|
||||
containedUnknownDurationSeconds long,
|
||||
containedCardAbsentDurationSeconds long,
|
||||
firstPotentialInVehicleOvernightStayStartedAtEpochSecond long,
|
||||
lastPotentialInVehicleOvernightStayEndedAtEpochSecond long,
|
||||
firstPreviousDrivingSourceIntervalId string,
|
||||
|
|
@ -455,6 +450,7 @@ create window DailyWeeklyRestCandidateEndBoundaryOdometerCandidateWindow#keepall
|
|||
create window DailyWeeklyRestCandidateEndBoundaryOdometerBestScoreWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateEndBoundaryOdometerBestScore;
|
||||
create window DailyWeeklyRestCandidateEndBoundaryOdometerResolvedWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateEndBoundaryOdometerResolved;
|
||||
create window DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateCoverageCardResolvedInterval;
|
||||
create window DailyWeeklyRestCandidateCoverageEmittedKeyWindow#unique(driverKey, startedAtEpochSecond, endedAtEpochSecond) as DailyWeeklyRestCandidateCoverageEmittedKey;
|
||||
|
||||
insert into SupportGeoEvidenceWindow
|
||||
select
|
||||
|
|
@ -550,7 +546,7 @@ select
|
|||
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
||||
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
||||
end
|
||||
) as unknownDurationSeconds,
|
||||
) as cardAbsentDurationSeconds,
|
||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||
c.previousRegistrationKey as previousRegistrationKey,
|
||||
|
|
@ -582,7 +578,7 @@ select
|
|||
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||
c.durationSeconds as durationSeconds,
|
||||
0L as unknownDurationSeconds,
|
||||
0L as cardAbsentDurationSeconds,
|
||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||
c.previousRegistrationKey as previousRegistrationKey,
|
||||
|
|
@ -604,65 +600,14 @@ select
|
|||
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||
c.durationSeconds as durationSeconds,
|
||||
sum(
|
||||
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.cardAbsentDurationSeconds as cardAbsentDurationSeconds,
|
||||
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 unidirectional,
|
||||
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)
|
||||
);
|
||||
from DailyWeeklyRestCandidateCoverageUnknownResolvedInterval as c;
|
||||
|
||||
insert into DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow
|
||||
select *
|
||||
|
|
@ -1313,10 +1258,8 @@ select
|
|||
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||
c.durationSeconds as durationSeconds,
|
||||
c.cardPresentDurationSeconds as cardPresentDurationSeconds,
|
||||
(c.cardPresentDurationSeconds * 100.0d) / c.durationSeconds as cardPresentCoveragePercent,
|
||||
c.unknownDurationSeconds as unknownDurationSeconds,
|
||||
(c.unknownDurationSeconds * 100.0d) / c.durationSeconds as unknownCoveragePercent,
|
||||
c.cardAbsentDurationSeconds as cardAbsentDurationSeconds,
|
||||
(c.cardAbsentDurationSeconds * 100.0d) / c.durationSeconds as cardAbsentCoveragePercent,
|
||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||
c.previousRegistrationKey as previousRegistrationKey,
|
||||
|
|
@ -1517,7 +1460,20 @@ from DailyWeeklyRestCandidateCoverageFinalizationRequest as request unidirection
|
|||
DailyWeeklyRestCandidateCoverageCardResolvedIntervalWindow as c
|
||||
where c.driverKey = request.driverKey
|
||||
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
|
||||
create window PreviousVehicleUsageInterval#lastevent as TachographVehicleUsageIntervalInputEvent;
|
||||
|
|
@ -1561,10 +1517,8 @@ select
|
|||
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||
c.durationSeconds as durationSeconds,
|
||||
c.cardPresentDurationSeconds as cardPresentDurationSeconds,
|
||||
c.cardPresentCoveragePercent as cardPresentCoveragePercent,
|
||||
c.unknownDurationSeconds as unknownDurationSeconds,
|
||||
c.unknownCoveragePercent as unknownCoveragePercent,
|
||||
c.cardAbsentDurationSeconds as cardAbsentDurationSeconds,
|
||||
c.cardAbsentCoveragePercent as cardAbsentCoveragePercent,
|
||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||
c.previousRegistrationKey as previousRegistrationKey,
|
||||
|
|
@ -1593,7 +1547,7 @@ from DailyWeeklyRestCandidateCoverageInterval as c
|
|||
where c.previousRegistrationKey is not null
|
||||
and c.nextRegistrationKey is not null
|
||||
and c.previousRegistrationKey != c.nextRegistrationKey
|
||||
and c.unknownDurationSeconds * 100L >= c.durationSeconds * 95L;
|
||||
and c.cardAbsentDurationSeconds * 100L >= c.durationSeconds * 95L;
|
||||
|
||||
insert into PotentialInVehicleOvernightStayInterval
|
||||
select
|
||||
|
|
@ -1602,10 +1556,8 @@ select
|
|||
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||
c.durationSeconds as durationSeconds,
|
||||
c.cardPresentDurationSeconds as cardPresentDurationSeconds,
|
||||
c.cardPresentCoveragePercent as cardPresentCoveragePercent,
|
||||
c.unknownDurationSeconds as unknownDurationSeconds,
|
||||
c.unknownCoveragePercent as unknownCoveragePercent,
|
||||
c.cardAbsentDurationSeconds as cardAbsentDurationSeconds,
|
||||
c.cardAbsentCoveragePercent as cardAbsentCoveragePercent,
|
||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||
c.previousRegistrationKey as previousRegistrationKey,
|
||||
|
|
@ -1634,7 +1586,7 @@ from DailyWeeklyRestCandidateCoverageInterval as c
|
|||
where c.previousRegistrationKey is not null
|
||||
and c.nextRegistrationKey is not null
|
||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||
and c.cardPresentDurationSeconds >= c.durationSeconds;
|
||||
and c.cardAbsentDurationSeconds = 0L;
|
||||
|
||||
insert into UnclassifiedDailyWeeklyRestCandidateCoverageInterval
|
||||
select
|
||||
|
|
@ -1643,10 +1595,8 @@ select
|
|||
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||
c.durationSeconds as durationSeconds,
|
||||
c.cardPresentDurationSeconds as cardPresentDurationSeconds,
|
||||
c.cardPresentCoveragePercent as cardPresentCoveragePercent,
|
||||
c.unknownDurationSeconds as unknownDurationSeconds,
|
||||
c.unknownCoveragePercent as unknownCoveragePercent,
|
||||
c.cardAbsentDurationSeconds as cardAbsentDurationSeconds,
|
||||
c.cardAbsentCoveragePercent as cardAbsentCoveragePercent,
|
||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||
c.previousRegistrationKey as previousRegistrationKey,
|
||||
|
|
@ -1676,13 +1626,13 @@ where not (
|
|||
c.previousRegistrationKey is not null
|
||||
and c.nextRegistrationKey is not null
|
||||
and c.previousRegistrationKey != c.nextRegistrationKey
|
||||
and c.unknownDurationSeconds * 100L >= c.durationSeconds * 95L
|
||||
and c.cardAbsentDurationSeconds * 100L >= c.durationSeconds * 95L
|
||||
)
|
||||
and not (
|
||||
c.previousRegistrationKey is not null
|
||||
and c.nextRegistrationKey is not null
|
||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||
and c.cardPresentDurationSeconds >= c.durationSeconds
|
||||
and c.cardAbsentDurationSeconds = 0L
|
||||
);
|
||||
|
||||
@Priority(40)
|
||||
|
|
@ -1698,8 +1648,7 @@ select
|
|||
s.vehicleKey as vehicleKey,
|
||||
s.containedPotentialInVehicleOvernightStayIntervalCount as containedPotentialInVehicleOvernightStayIntervalCount,
|
||||
s.containedPotentialInVehicleOvernightStayDurationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
|
||||
s.containedCardPresentDurationSeconds as containedCardPresentDurationSeconds,
|
||||
s.containedUnknownDurationSeconds as containedUnknownDurationSeconds,
|
||||
s.containedCardAbsentDurationSeconds as containedCardAbsentDurationSeconds,
|
||||
s.firstPotentialInVehicleOvernightStayStartedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
|
||||
s.lastPotentialInVehicleOvernightStayEndedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
|
||||
s.firstPreviousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
|
||||
|
|
@ -1712,7 +1661,7 @@ where s.driverKey = c.driverKey
|
|||
c.previousRegistrationKey is not null
|
||||
and c.nextRegistrationKey is not null
|
||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||
and c.cardPresentDurationSeconds >= c.durationSeconds
|
||||
and c.cardAbsentDurationSeconds = 0L
|
||||
)
|
||||
or s.registrationKey != c.previousRegistrationKey
|
||||
or (
|
||||
|
|
@ -1730,7 +1679,7 @@ where s.driverKey = c.driverKey
|
|||
c.previousRegistrationKey is not null
|
||||
and c.nextRegistrationKey is not null
|
||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||
and c.cardPresentDurationSeconds >= c.durationSeconds
|
||||
and c.cardAbsentDurationSeconds = 0L
|
||||
);
|
||||
|
||||
@Priority(30)
|
||||
|
|
@ -1744,8 +1693,7 @@ select
|
|||
s.vehicleKey as vehicleKey,
|
||||
s.containedPotentialInVehicleOvernightStayIntervalCount + 1 as containedPotentialInVehicleOvernightStayIntervalCount,
|
||||
s.containedPotentialInVehicleOvernightStayDurationSeconds + c.durationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
|
||||
s.containedCardPresentDurationSeconds + c.cardPresentDurationSeconds as containedCardPresentDurationSeconds,
|
||||
s.containedUnknownDurationSeconds + c.unknownDurationSeconds as containedUnknownDurationSeconds,
|
||||
s.containedCardAbsentDurationSeconds + c.cardAbsentDurationSeconds as containedCardAbsentDurationSeconds,
|
||||
s.firstPotentialInVehicleOvernightStayStartedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
|
||||
c.endedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
|
||||
s.firstPreviousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
|
||||
|
|
@ -1755,7 +1703,7 @@ where s.driverKey = c.driverKey
|
|||
and c.previousRegistrationKey is not null
|
||||
and c.nextRegistrationKey is not null
|
||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||
and c.cardPresentDurationSeconds >= c.durationSeconds
|
||||
and c.cardAbsentDurationSeconds = 0L
|
||||
and s.registrationKey = c.previousRegistrationKey
|
||||
and (
|
||||
s.vehicleKey is null
|
||||
|
|
@ -1777,8 +1725,7 @@ select
|
|||
end as vehicleKey,
|
||||
1 as containedPotentialInVehicleOvernightStayIntervalCount,
|
||||
c.durationSeconds as containedPotentialInVehicleOvernightStayDurationSeconds,
|
||||
c.cardPresentDurationSeconds as containedCardPresentDurationSeconds,
|
||||
c.unknownDurationSeconds as containedUnknownDurationSeconds,
|
||||
c.cardAbsentDurationSeconds as containedCardAbsentDurationSeconds,
|
||||
c.startedAtEpochSecond as firstPotentialInVehicleOvernightStayStartedAtEpochSecond,
|
||||
c.endedAtEpochSecond as lastPotentialInVehicleOvernightStayEndedAtEpochSecond,
|
||||
c.previousDrivingSourceIntervalId as firstPreviousDrivingSourceIntervalId,
|
||||
|
|
@ -1788,7 +1735,7 @@ where priorCoverage.driverKey = c.driverKey
|
|||
and c.previousRegistrationKey is not null
|
||||
and c.nextRegistrationKey is not null
|
||||
and c.previousRegistrationKey = c.nextRegistrationKey
|
||||
and c.cardPresentDurationSeconds >= c.durationSeconds
|
||||
and c.cardAbsentDurationSeconds = 0L
|
||||
and not exists (
|
||||
select * from OpenPotentialInVehicleTripState as s
|
||||
where s.driverKey = c.driverKey
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ select
|
|||
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
||||
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
||||
end
|
||||
) as unknownDurationSeconds,
|
||||
) as cardAbsentDurationSeconds,
|
||||
(sum(
|
||||
case
|
||||
when u.startedAtEpochSecond <= c.startedAtEpochSecond and u.endedAtEpochSecond >= c.endedAtEpochSecond
|
||||
|
|
@ -26,7 +26,7 @@ select
|
|||
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
||||
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
||||
end
|
||||
) * 100.0d) / c.durationSeconds as unknownCoveragePercent,
|
||||
) * 100.0d) / c.durationSeconds as cardAbsentCoveragePercent,
|
||||
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||
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-12T22:00:00Z"),
|
||||
43_200L,
|
||||
0L,
|
||||
0.0d,
|
||||
43_200L,
|
||||
100.0d,
|
||||
"ACT-2",
|
||||
|
|
@ -216,8 +214,6 @@ class TachographFileSessionControllerTest {
|
|||
OffsetDateTime.parse("2026-05-12T10:00:00Z"),
|
||||
OffsetDateTime.parse("2026-05-12T22:00:00Z"),
|
||||
43_200L,
|
||||
0L,
|
||||
0.0d,
|
||||
43_200L,
|
||||
100.0d,
|
||||
"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).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
|
||||
assertThat(intervals.get(0).durationSeconds()).isEqualTo(50_400L);
|
||||
assertThat(intervals.get(0).unknownDurationSeconds()).isEqualTo(50_399L);
|
||||
assertThat(intervals.get(0).unknownCoveragePercent()).isGreaterThan(99.9d);
|
||||
assertThat(intervals.get(0).cardAbsentDurationSeconds()).isEqualTo(50_399L);
|
||||
assertThat(intervals.get(0).cardAbsentCoveragePercent()).isGreaterThan(99.9d);
|
||||
assertThat(intervals.get(0).previousDrivingSourceIntervalId()).isEqualTo("ACT-1");
|
||||
assertThat(intervals.get(0).nextDrivingSourceIntervalId()).isEqualTo("ACT-2");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,8 +128,7 @@ class DriverTimelineReusableProjectionBuilderTest {
|
|||
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals()).hasSize(1);
|
||||
TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent coverageInterval =
|
||||
reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0);
|
||||
assertThat(coverageInterval.unknownCoveragePercent()).isCloseTo(99.998d, org.assertj.core.data.Offset.offset(0.001d));
|
||||
assertThat(coverageInterval.cardPresentCoveragePercent()).isEqualTo(0.0d);
|
||||
assertThat(coverageInterval.cardAbsentCoveragePercent()).isCloseTo(99.998d, org.assertj.core.data.Offset.offset(0.001d));
|
||||
assertThat(reusableBundle.drivingInterruptionVehicleChangeIntervals()).containsExactlyElementsOf(legacyDrivingInterruptionVehicleChanges);
|
||||
assertThat(reusableBundle.vuCardAbsentIntervals()).containsExactlyElementsOf(legacyVuCardAbsentIntervals);
|
||||
assertThat(reusableBundle.potentialHomeOvernightStayIntervals()).hasSize(1);
|
||||
|
|
@ -137,8 +136,8 @@ class DriverTimelineReusableProjectionBuilderTest {
|
|||
.isEqualTo(legacyPotentialHomeOvernightStays.get(0).startedAt());
|
||||
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).endedAt())
|
||||
.isEqualTo(legacyPotentialHomeOvernightStays.get(0).endedAt());
|
||||
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).unknownCoveragePercent())
|
||||
.isEqualTo(legacyPotentialHomeOvernightStays.get(0).unknownCoveragePercent());
|
||||
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).cardAbsentCoveragePercent())
|
||||
.isEqualTo(legacyPotentialHomeOvernightStays.get(0).cardAbsentCoveragePercent());
|
||||
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).beginBoundaryOdometerKm())
|
||||
.isEqualTo(200L);
|
||||
assertThat(reusableBundle.potentialHomeOvernightStayIntervals().get(0).endBoundaryOdometerKm())
|
||||
|
|
@ -266,9 +265,7 @@ class DriverTimelineReusableProjectionBuilderTest {
|
|||
|
||||
assertThat(reusableBundle.drivingInterruptionVehicleChangeIntervals()).isEmpty();
|
||||
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals()).hasSize(1);
|
||||
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentCoveragePercent())
|
||||
.isEqualTo(100.0d);
|
||||
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).unknownCoveragePercent())
|
||||
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentCoveragePercent())
|
||||
.isEqualTo(0.0d);
|
||||
assertThat(reusableBundle.dailyWeeklyRestCandidateCoverageIntervals().get(0).beginGeoEventId())
|
||||
.isEqualTo("SUP-1");
|
||||
|
|
@ -296,8 +293,6 @@ class DriverTimelineReusableProjectionBuilderTest {
|
|||
.isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
||||
assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).endedAt())
|
||||
.isEqualTo(OffsetDateTime.parse("2026-05-02T00:00:00Z"));
|
||||
assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).cardPresentCoveragePercent())
|
||||
.isEqualTo(100.0d);
|
||||
assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).beginGeoEventId())
|
||||
.isEqualTo("SUP-1");
|
||||
assertThat(reusableBundle.potentialInVehicleOvernightStayIntervals().get(0).beginGeoEvent())
|
||||
|
|
|
|||
|
|
@ -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).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).unknownDurationSeconds()).isEqualTo(43_200L);
|
||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).unknownCoveragePercent()).isEqualTo(100.0d);
|
||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(0L);
|
||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(0.0d);
|
||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(50_399L);
|
||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentCoveragePercent())
|
||||
.isCloseTo(99.998d, org.assertj.core.data.Offset.offset(0.001d));
|
||||
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).nextRegistrationKey()).isEqualTo("12:REG-2");
|
||||
|
|
@ -403,10 +402,9 @@ class TachographFileSessionProcessingServiceTest {
|
|||
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
|
||||
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).cardPresentDurationSeconds()).isEqualTo(0L);
|
||||
assertThat(result.potentialHomeOvernightStayIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(0.0d);
|
||||
assertThat(result.potentialHomeOvernightStayIntervals().get(0).unknownDurationSeconds()).isEqualTo(43_200L);
|
||||
assertThat(result.potentialHomeOvernightStayIntervals().get(0).unknownCoveragePercent()).isEqualTo(100.0d);
|
||||
assertThat(result.potentialHomeOvernightStayIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(50_399L);
|
||||
assertThat(result.potentialHomeOvernightStayIntervals().get(0).cardAbsentCoveragePercent())
|
||||
.isCloseTo(99.998d, org.assertj.core.data.Offset.offset(0.001d));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -483,15 +481,11 @@ class TachographFileSessionProcessingServiceTest {
|
|||
assertThat(result.potentialHomeOvernightStayIntervalCount()).isEqualTo(0);
|
||||
assertThat(result.potentialInVehicleOvernightStayIntervalCount()).isEqualTo(1);
|
||||
assertThat(result.potentialInVehicleTripIntervalCount()).isEqualTo(0);
|
||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentDurationSeconds()).isEqualTo(50_400L);
|
||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(100.0d);
|
||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).unknownDurationSeconds()).isEqualTo(0L);
|
||||
assertThat(result.dailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(0L);
|
||||
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).cardPresentDurationSeconds()).isEqualTo(50_400L);
|
||||
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardPresentCoveragePercent()).isEqualTo(100.0d);
|
||||
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).unknownDurationSeconds()).isEqualTo(0L);
|
||||
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).unknownCoveragePercent()).isEqualTo(0.0d);
|
||||
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardAbsentDurationSeconds()).isEqualTo(0L);
|
||||
assertThat(result.potentialInVehicleOvernightStayIntervals().get(0).cardAbsentCoveragePercent()).isEqualTo(0.0d);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -580,9 +574,7 @@ class TachographFileSessionProcessingServiceTest {
|
|||
.isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
||||
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).endedAt())
|
||||
.isEqualTo(OffsetDateTime.parse("2026-05-02T00:30:00Z"));
|
||||
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).cardPresentCoveragePercent())
|
||||
.isLessThan(95.0d);
|
||||
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).unknownCoveragePercent())
|
||||
assertThat(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().get(0).cardAbsentCoveragePercent())
|
||||
.isLessThan(95.0d);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue