Compare commits

...

2 Commits

Author SHA1 Message Date
trifonovt 927ac3b903 Finalize neutral working-time projection architecture 2026-05-27 08:47:52 +02:00
trifonovt cd090010c6 Extract neutral working-time projection engine 2026-05-27 08:47:52 +02:00
25 changed files with 3002 additions and 2358 deletions

View File

@ -1,14 +1,12 @@
package at.procon.eventhub.processing.driverworkingtime.dto;
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleTripIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperSupportGeoEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeActivityInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDrivingInterruptionInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimePotentialInVehicleTripInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeRestCoverageInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeSupportGeoEvent;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVuCardAbsentInterval;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
@ -34,19 +32,19 @@ public record DriverWorkingTimeProcessingResultDto(
int vehicleUsageIntervalCount,
int vuCardAbsentIntervalCount,
int supportGeoEventCount,
List<TachographEsperActivityIntervalEvent> activityIntervals,
List<TachographEsperActivityIntervalEvent> drivingIntervals,
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals,
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals,
List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals,
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> dailyWeeklyRestCandidateCoverageIntervals,
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> unclassifiedDailyWeeklyRestCandidateCoverageIntervals,
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals,
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals,
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals,
List<TachographEsperVehicleUsageIntervalEvent> vehicleUsageIntervals,
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals,
List<TachographEsperSupportGeoEvent> supportGeoEvents,
List<DriverWorkingTimeActivityInterval> activityIntervals,
List<DriverWorkingTimeActivityInterval> drivingIntervals,
List<DriverWorkingTimeDrivingInterruptionInterval> drivingInterruptionIntervals,
List<DriverWorkingTimeDrivingInterruptionInterval> drivingInterruptionVehicleChangeIntervals,
List<DriverWorkingTimeDrivingInterruptionInterval> dailyWeeklyRestCandidateIntervals,
List<DriverWorkingTimeRestCoverageInterval> dailyWeeklyRestCandidateCoverageIntervals,
List<DriverWorkingTimeRestCoverageInterval> unclassifiedDailyWeeklyRestCandidateCoverageIntervals,
List<DriverWorkingTimeRestCoverageInterval> potentialHomeOvernightStayIntervals,
List<DriverWorkingTimeRestCoverageInterval> potentialInVehicleOvernightStayIntervals,
List<DriverWorkingTimePotentialInVehicleTripInterval> potentialInVehicleTripIntervals,
List<DriverWorkingTimeVehicleUsageInterval> vehicleUsageIntervals,
List<DriverWorkingTimeVuCardAbsentInterval> vuCardAbsentIntervals,
List<DriverWorkingTimeSupportGeoEvent> supportGeoEvents,
List<String> notes
) {
}

View File

@ -1,6 +1,5 @@
package at.procon.eventhub.processing.driverworkingtime.model;
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
@ -62,49 +61,6 @@ public record DriverWorkingTimeActivityInterval(
);
}
public static DriverWorkingTimeActivityInterval fromResolved(
UUID sessionId,
String driverKey,
ResolvedActivityInterval interval
) {
if (interval == null || interval.from() == null || interval.to() == null) {
return null;
}
return new DriverWorkingTimeActivityInterval(
sessionId,
driverKey,
interval.intervalId(),
interval.activityType(),
interval.slot(),
interval.cardStatus(),
interval.drivingStatus(),
interval.registrationKey(),
interval.vehicleKey(),
interval.sourceKind(),
firstSourceIntervalId(interval),
lastSourceIntervalId(interval),
interval.from(),
interval.to(),
interval.from().toEpochSecond(),
interval.to().toEpochSecond(),
interval.durationSeconds(),
interval.sourceIntervalIds(),
interval.synthetic(),
interval.clippedToRequestedPeriod(),
interval.level()
);
}
private static String firstSourceIntervalId(ResolvedActivityInterval interval) {
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().get(0);
}
private static String lastSourceIntervalId(ResolvedActivityInterval interval) {
return interval.sourceIntervalIds().isEmpty()
? interval.intervalId()
: interval.sourceIntervalIds().get(interval.sourceIntervalIds().size() - 1);
}
@SuppressWarnings("unchecked")
private static List<String> castStringList(Object value) {
return value instanceof List<?> list ? (List<String>) list : List.of();

View File

@ -0,0 +1,31 @@
package at.procon.eventhub.processing.driverworkingtime.model;
import java.util.List;
public record DriverWorkingTimeDerivedProjectionBundle(
List<DriverWorkingTimeDrivingInterruptionInterval> drivingInterruptionIntervals,
List<DriverWorkingTimeDrivingInterruptionInterval> dailyWeeklyRestCandidateIntervals,
List<DriverWorkingTimeRestCoverageInterval> dailyWeeklyRestCandidateCoverageIntervals,
List<DriverWorkingTimeRestCoverageInterval> unclassifiedDailyWeeklyRestCandidateCoverageIntervals,
List<DriverWorkingTimeDrivingInterruptionInterval> drivingInterruptionVehicleChangeIntervals,
List<DriverWorkingTimeVuCardAbsentInterval> vuCardAbsentIntervals,
List<DriverWorkingTimeRestCoverageInterval> potentialHomeOvernightStayIntervals,
List<DriverWorkingTimeRestCoverageInterval> potentialInVehicleOvernightStayIntervals,
List<DriverWorkingTimePotentialInVehicleTripInterval> potentialInVehicleTripIntervals
) {
public DriverWorkingTimeDerivedProjectionBundle {
drivingInterruptionIntervals = safe(drivingInterruptionIntervals);
dailyWeeklyRestCandidateIntervals = safe(dailyWeeklyRestCandidateIntervals);
dailyWeeklyRestCandidateCoverageIntervals = safe(dailyWeeklyRestCandidateCoverageIntervals);
unclassifiedDailyWeeklyRestCandidateCoverageIntervals = safe(unclassifiedDailyWeeklyRestCandidateCoverageIntervals);
drivingInterruptionVehicleChangeIntervals = safe(drivingInterruptionVehicleChangeIntervals);
vuCardAbsentIntervals = safe(vuCardAbsentIntervals);
potentialHomeOvernightStayIntervals = safe(potentialHomeOvernightStayIntervals);
potentialInVehicleOvernightStayIntervals = safe(potentialInVehicleOvernightStayIntervals);
potentialInVehicleTripIntervals = safe(potentialInVehicleTripIntervals);
}
private static <T> List<T> safe(List<T> values) {
return values == null ? List.of() : List.copyOf(values);
}
}

View File

@ -0,0 +1,19 @@
package at.procon.eventhub.processing.driverworkingtime.model;
import java.time.OffsetDateTime;
import java.util.UUID;
public record DriverWorkingTimeDrivingInterruptionInterval(
UUID sessionId,
String driverKey,
OffsetDateTime startedAt,
OffsetDateTime endedAt,
long durationSeconds,
String previousDrivingSourceIntervalId,
String nextDrivingSourceIntervalId,
String previousRegistrationKey,
String nextRegistrationKey,
String previousVehicleKey,
String nextVehicleKey
) {
}

View File

@ -0,0 +1,14 @@
package at.procon.eventhub.processing.driverworkingtime.model;
import java.time.OffsetDateTime;
public record DriverWorkingTimeGeoEvidenceEvent(
String eventId,
String eventDomain,
OffsetDateTime occurredAt,
Double latitude,
Double longitude,
Long distanceSeconds,
Long odometerKm
) {
}

View File

@ -0,0 +1,29 @@
package at.procon.eventhub.processing.driverworkingtime.model;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
public record DriverWorkingTimePotentialInVehicleTripInterval(
UUID sessionId,
String driverKey,
OffsetDateTime startedAt,
OffsetDateTime endedAt,
long durationSeconds,
String registrationKey,
String vehicleKey,
int containedPotentialInVehicleOvernightStayIntervalCount,
long containedPotentialInVehicleOvernightStayDurationSeconds,
long containedCardAbsentDurationSeconds,
OffsetDateTime firstPotentialInVehicleOvernightStayStartedAt,
OffsetDateTime lastPotentialInVehicleOvernightStayEndedAt,
String firstPreviousDrivingSourceIntervalId,
String lastNextDrivingSourceIntervalId,
List<DriverWorkingTimeRestCoverageInterval> potentialInVehicleOvernightStayIntervals
) {
public DriverWorkingTimePotentialInVehicleTripInterval {
potentialInVehicleOvernightStayIntervals = potentialInVehicleOvernightStayIntervals == null
? List.of()
: List.copyOf(potentialInVehicleOvernightStayIntervals);
}
}

View File

@ -0,0 +1,41 @@
package at.procon.eventhub.processing.driverworkingtime.model;
import java.time.OffsetDateTime;
import java.util.UUID;
public record DriverWorkingTimeRestCoverageInterval(
UUID sessionId,
String driverKey,
OffsetDateTime startedAt,
OffsetDateTime endedAt,
long durationSeconds,
long cardAbsentDurationSeconds,
double cardAbsentCoveragePercent,
String previousDrivingSourceIntervalId,
String nextDrivingSourceIntervalId,
String previousRegistrationKey,
String nextRegistrationKey,
String previousVehicleKey,
String nextVehicleKey,
Long beginBoundaryOdometerKm,
Long endBoundaryOdometerKm,
String beginGeoEventId,
String beginGeoEventDomain,
OffsetDateTime beginGeoOccurredAt,
Double beginLatitude,
Double beginLongitude,
Long beginGeoDistanceSeconds,
Long beginGeoOdometerKm,
String endGeoEventId,
String endGeoEventDomain,
OffsetDateTime endGeoOccurredAt,
Double endLatitude,
Double endLongitude,
Long endGeoDistanceSeconds,
Long endGeoOdometerKm,
Long geoEvidenceMovementMeters,
String geoEvidenceMovementCategory,
DriverWorkingTimeGeoEvidenceEvent beginGeoEvent,
DriverWorkingTimeGeoEvidenceEvent endGeoEvent
) {
}

View File

@ -0,0 +1,25 @@
package at.procon.eventhub.processing.driverworkingtime.model;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
public record DriverWorkingTimeSupportGeoEvent(
String eventId,
String driverKey,
OffsetDateTime occurredAt,
String eventDomain,
String eventType,
String eventLifecycle,
String registrationKey,
String vehicleKey,
String country,
String region,
String countryFrom,
String countryTo,
String operation,
BigDecimal latitude,
BigDecimal longitude,
Long odometerKm,
String rawRecordPath
) {
}

View File

@ -1,6 +1,5 @@
package at.procon.eventhub.processing.driverworkingtime.model;
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
@ -52,40 +51,6 @@ public record DriverWorkingTimeVehicleUsageInterval(
);
}
public static DriverWorkingTimeVehicleUsageInterval fromResolved(ResolvedVehicleUsageInterval interval) {
if (interval == null || interval.from() == null) {
return null;
}
return new DriverWorkingTimeVehicleUsageInterval(
interval.sessionId(),
interval.driverKey(),
interval.intervalId(),
firstSourceIntervalId(interval),
lastSourceIntervalId(interval),
interval.from(),
interval.to(),
interval.from().toEpochSecond(),
interval.to() == null ? null : interval.to().toEpochSecond(),
interval.durationSeconds(),
interval.odometerBeginKm(),
interval.odometerEndKm(),
interval.registrationKey(),
interval.vehicleKey(),
interval.sourceKind(),
interval.sourceIntervalIds()
);
}
private static String firstSourceIntervalId(ResolvedVehicleUsageInterval interval) {
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().get(0);
}
private static String lastSourceIntervalId(ResolvedVehicleUsageInterval interval) {
return interval.sourceIntervalIds().isEmpty()
? interval.intervalId()
: interval.sourceIntervalIds().get(interval.sourceIntervalIds().size() - 1);
}
@SuppressWarnings("unchecked")
private static List<String> castStringList(Object value) {
return value instanceof List<?> list ? (List<String>) list : List.of();

View File

@ -0,0 +1,19 @@
package at.procon.eventhub.processing.driverworkingtime.model;
import java.time.OffsetDateTime;
import java.util.UUID;
public record DriverWorkingTimeVuCardAbsentInterval(
UUID sessionId,
String driverKey,
OffsetDateTime startedAt,
OffsetDateTime endedAt,
long durationSeconds,
String previousUsageIntervalId,
String nextUsageIntervalId,
String previousRegistrationKey,
String nextRegistrationKey,
String previousVehicleKey,
String nextVehicleKey
) {
}

View File

@ -0,0 +1,23 @@
package at.procon.eventhub.processing.driverworkingtime.service;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDerivedProjectionBundle;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeProcessingInput;
import java.util.Objects;
import org.springframework.stereotype.Service;
@Service
public class DriverWorkingTimeDerivedProjectionEngine {
private final DriverWorkingTimeReusableProjectionBuilder projectionBuilder;
public DriverWorkingTimeDerivedProjectionEngine(
DriverWorkingTimeReusableProjectionBuilder projectionBuilder
) {
this.projectionBuilder = projectionBuilder;
}
public DriverWorkingTimeDerivedProjectionBundle build(DriverWorkingTimeProcessingInput input) {
Objects.requireNonNull(input, "input must not be null");
return projectionBuilder.buildDerivedProjectionBundle(input);
}
}

View File

@ -0,0 +1,769 @@
package at.procon.eventhub.processing.driverworkingtime.service;
import com.espertech.esper.common.client.EPCompiled;
import com.espertech.esper.common.client.EventBean;
import com.espertech.esper.common.client.configuration.Configuration;
import com.espertech.esper.compiler.client.CompilerArguments;
import com.espertech.esper.compiler.client.EPCompileException;
import com.espertech.esper.compiler.client.EPCompilerProvider;
import com.espertech.esper.runtime.client.EPDeployException;
import com.espertech.esper.runtime.client.EPDeployment;
import com.espertech.esper.runtime.client.EPRuntime;
import com.espertech.esper.runtime.client.EPRuntimeProvider;
import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeActivityInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDerivedProjectionBundle;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDrivingInterruptionInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeGeoEvidenceEvent;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimePotentialInVehicleTripInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeProcessingInput;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeRestCoverageInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVuCardAbsentInterval;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceEvent;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
@Service
public class DriverWorkingTimeReusableProjectionBuilder {
private static final AtomicLong RUNTIME_COUNTER = new AtomicLong();
private static final String DRIVING_DERIVED_PROJECTION_BUNDLE_EPL_TEMPLATE =
loadResource("esper/driver-working-time-derived-projections.epl");
private final EventHubProperties properties;
@Autowired
public DriverWorkingTimeReusableProjectionBuilder(
EventHubProperties properties
) {
this.properties = properties;
}
public DriverWorkingTimeDerivedProjectionBundle buildDerivedProjectionBundle(
DriverWorkingTimeProcessingInput input
) {
if (input == null) {
return emptyDerivedProjectionBundle();
}
return buildDerivedProjectionBundle(
buildActivityIntervalInputEvents(input.activityIntervals()),
buildVehicleUsageIntervalInputEventsCommon(input.vehicleUsageIntervals()),
buildSupportGeoInputEventsCommon(input.sessionId(), input.supportEvidenceEvents()),
input.significantDrivingMinutes(),
input.minimumRestPeriodMinutes()
);
}
private DriverWorkingTimeDerivedProjectionBundle buildDerivedProjectionBundle(
List<Map<String, Object>> activityInputEvents,
List<Map<String, Object>> vehicleUsageInputEvents,
List<Map<String, Object>> supportGeoInputEvents,
int significantDrivingMinutes,
int minimumRestPeriodMinutes
) {
if ((activityInputEvents == null || activityInputEvents.isEmpty())
&& (vehicleUsageInputEvents == null || vehicleUsageInputEvents.isEmpty())) {
return emptyDerivedProjectionBundle();
}
List<DriverWorkingTimeDrivingInterruptionInterval> drivingInterruptionIntervals = new ArrayList<>();
List<DriverWorkingTimeDrivingInterruptionInterval> dailyWeeklyRestCandidateIntervals = new ArrayList<>();
List<DriverWorkingTimeRestCoverageInterval> dailyWeeklyRestCandidateCoverageIntervals = new ArrayList<>();
List<DriverWorkingTimeRestCoverageInterval> unclassifiedDailyWeeklyRestCandidateCoverageIntervals = new ArrayList<>();
List<DriverWorkingTimeDrivingInterruptionInterval> drivingInterruptionVehicleChangeIntervals = new ArrayList<>();
List<DriverWorkingTimeVuCardAbsentInterval> vuCardAbsentIntervals = new ArrayList<>();
List<DriverWorkingTimeRestCoverageInterval> potentialHomeOvernightStayIntervals = new ArrayList<>();
List<DriverWorkingTimeRestCoverageInterval> potentialInVehicleOvernightStayIntervals = new ArrayList<>();
List<DriverWorkingTimePotentialInVehicleTripInterval> potentialInVehicleTripIntervals = new ArrayList<>();
executeWithRuntime(
configuration -> {
configuration.getCommon().addEventType(
"TachographActivityIntervalInputEvent",
activityIntervalInputDefinition()
);
configuration.getCommon().addEventType(
"TachographVehicleUsageIntervalInputEvent",
vehicleUsageIntervalInputDefinition()
);
configuration.getCommon().addEventType(
"TachographSupportGeoEvidenceInputEvent",
supportGeoEvidenceInputDefinition()
);
},
renderDrivingDerivedProjectionBundleEpl(significantDrivingMinutes, minimumRestPeriodMinutes),
Map.of(
"drivingInterruptionIntervals",
newData -> collectDrivingInterruptionIntervalModels(newData, drivingInterruptionIntervals),
"dailyWeeklyRestCandidateIntervals",
newData -> collectDrivingInterruptionIntervalModels(newData, dailyWeeklyRestCandidateIntervals),
"dailyWeeklyRestCandidateCoverageIntervals",
newData -> collectDailyWeeklyRestCandidateCoverageIntervalModels(newData, dailyWeeklyRestCandidateCoverageIntervals),
"unclassifiedDailyWeeklyRestCandidateCoverageIntervals",
newData -> collectDailyWeeklyRestCandidateCoverageIntervalModels(newData, unclassifiedDailyWeeklyRestCandidateCoverageIntervals),
"drivingInterruptionVehicleChangeIntervals",
newData -> collectDrivingInterruptionIntervalModels(newData, drivingInterruptionVehicleChangeIntervals),
"vuCardAbsentIntervals",
newData -> collectVuCardAbsentIntervalModels(newData, vuCardAbsentIntervals),
"potentialHomeOvernightStayIntervals",
newData -> collectPotentialHomeOvernightStayIntervalModels(newData, potentialHomeOvernightStayIntervals),
"potentialInVehicleOvernightStayIntervals",
newData -> collectPotentialInVehicleOvernightStayIntervalModels(newData, potentialInVehicleOvernightStayIntervals),
"potentialInVehicleTripIntervals",
newData -> collectPotentialInVehicleTripIntervalModels(newData, potentialInVehicleTripIntervals)
),
runtime -> {
if (supportGeoInputEvents != null) {
for (Map<String, Object> supportGeoEvidence : supportGeoInputEvents) {
runtime.getEventService().sendEventMap(
supportGeoEvidence,
"TachographSupportGeoEvidenceInputEvent"
);
}
}
if (vehicleUsageInputEvents != null) {
for (Map<String, Object> interval : vehicleUsageInputEvents) {
runtime.getEventService().sendEventMap(
interval,
"TachographVehicleUsageIntervalInputEvent"
);
}
}
if (activityInputEvents != null) {
for (Map<String, Object> interval : activityInputEvents) {
runtime.getEventService().sendEventMap(
interval,
"TachographActivityIntervalInputEvent"
);
}
}
}
);
List<DriverWorkingTimeRestCoverageInterval> sortedDailyWeeklyRestCandidateCoverageIntervals =
sortDailyWeeklyRestCandidateCoverageIntervalsCommon(dailyWeeklyRestCandidateCoverageIntervals);
List<DriverWorkingTimeRestCoverageInterval> sortedUnclassifiedDailyWeeklyRestCandidateCoverageIntervals =
sortDailyWeeklyRestCandidateCoverageIntervalsCommon(unclassifiedDailyWeeklyRestCandidateCoverageIntervals);
List<DriverWorkingTimeRestCoverageInterval> sortedPotentialHomeOvernightStayIntervals =
sortPotentialHomeOvernightStayIntervalsCommon(potentialHomeOvernightStayIntervals);
List<DriverWorkingTimeRestCoverageInterval> sortedPotentialInVehicleOvernightStayIntervals =
sortPotentialInVehicleOvernightStayIntervalsCommon(potentialInVehicleOvernightStayIntervals);
return new DriverWorkingTimeDerivedProjectionBundle(
sortDrivingInterruptionIntervalsCommon(drivingInterruptionIntervals),
sortDrivingInterruptionIntervalsCommon(dailyWeeklyRestCandidateIntervals),
sortedDailyWeeklyRestCandidateCoverageIntervals,
sortedUnclassifiedDailyWeeklyRestCandidateCoverageIntervals,
sortDrivingInterruptionIntervalsCommon(drivingInterruptionVehicleChangeIntervals),
sortVuCardAbsentIntervalsCommon(vuCardAbsentIntervals),
sortedPotentialHomeOvernightStayIntervals,
sortedPotentialInVehicleOvernightStayIntervals,
sortPotentialInVehicleTripIntervalsCommon(potentialInVehicleTripIntervals)
);
}
private List<Map<String, Object>> buildActivityIntervalInputEvents(
List<DriverWorkingTimeActivityInterval> activityIntervals
) {
return safeList(activityIntervals).stream()
.map(this::toActivityIntervalInputMap)
.filter(Objects::nonNull)
.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>> buildVehicleUsageIntervalInputEventsCommon(
List<DriverWorkingTimeVehicleUsageInterval> vehicleUsageIntervals
) {
return safeList(vehicleUsageIntervals).stream()
.map(this::toVehicleUsageIntervalInputMap)
.filter(Objects::nonNull)
.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>> buildSupportGeoInputEventsCommon(
UUID sessionId,
List<RuntimeSupportEvidenceEvent> 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 DriverWorkingTimeDerivedProjectionBundle emptyDerivedProjectionBundle() {
return new DriverWorkingTimeDerivedProjectionBundle(
List.of(),
List.of(),
List.of(),
List.of(),
List.of(),
List.of(),
List.of(),
List.of(),
List.of()
);
}
private void executeWithRuntime(
Consumer<Configuration> configurationSetup,
String epl,
Map<String, Consumer<EventBean[]>> listeners,
Consumer<EPRuntime> sender
) {
EPRuntime runtime = null;
try {
Configuration configuration = new Configuration();
configurationSetup.accept(configuration);
String runtimeUri = "eventhub-tachograph-reusable-projection-" + RUNTIME_COUNTER.incrementAndGet();
runtime = EPRuntimeProvider.getRuntime(runtimeUri, configuration);
CompilerArguments arguments = new CompilerArguments(configuration);
EPCompiled compiled = EPCompilerProvider.getCompiler().compile(epl, arguments);
EPDeployment deployment = runtime.getDeploymentService().deploy(compiled);
for (Map.Entry<String, Consumer<EventBean[]>> entry : listeners.entrySet()) {
runtime.getDeploymentService()
.getStatement(deployment.getDeploymentId(), entry.getKey())
.addListener((newData, oldData, statement, rt) -> entry.getValue().accept(newData));
}
sender.accept(runtime);
} catch (EPCompileException | EPDeployException e) {
throw new IllegalStateException("Cannot compile/deploy reusable tachograph projection EPL bundle", e);
} finally {
if (runtime != null) {
runtime.destroy();
}
}
}
private Map<String, Object> activityIntervalInputDefinition() {
Map<String, Object> definition = new LinkedHashMap<>();
definition.put("sessionId", UUID.class);
definition.put("driverKey", String.class);
definition.put("intervalId", String.class);
definition.put("activityType", String.class);
definition.put("cardSlot", String.class);
definition.put("cardStatus", String.class);
definition.put("drivingStatus", String.class);
definition.put("registrationKey", String.class);
definition.put("vehicleKey", String.class);
definition.put("sourceKind", String.class);
definition.put("firstSourceIntervalId", String.class);
definition.put("lastSourceIntervalId", String.class);
definition.put("startedAt", OffsetDateTime.class);
definition.put("endedAt", OffsetDateTime.class);
definition.put("startedAtEpochSecond", long.class);
definition.put("endedAtEpochSecond", long.class);
definition.put("durationSeconds", long.class);
definition.put("sourceIntervalIds", java.util.List.class);
definition.put("synthetic", boolean.class);
definition.put("clippedToRequestedPeriod", boolean.class);
definition.put("level", String.class);
return definition;
}
private Map<String, Object> vehicleUsageIntervalInputDefinition() {
Map<String, Object> definition = new LinkedHashMap<>();
definition.put("sessionId", UUID.class);
definition.put("driverKey", String.class);
definition.put("intervalId", String.class);
definition.put("firstSourceIntervalId", String.class);
definition.put("lastSourceIntervalId", String.class);
definition.put("startedAt", OffsetDateTime.class);
definition.put("endedAt", OffsetDateTime.class);
definition.put("startedAtEpochSecond", long.class);
definition.put("endedAtEpochSecond", Long.class);
definition.put("durationSeconds", long.class);
definition.put("odometerBeginKm", Long.class);
definition.put("odometerEndKm", Long.class);
definition.put("registrationKey", String.class);
definition.put("vehicleKey", String.class);
definition.put("sourceKind", String.class);
definition.put("sourceIntervalIds", java.util.List.class);
return definition;
}
private Map<String, Object> supportGeoEvidenceInputDefinition() {
Map<String, Object> definition = new LinkedHashMap<>();
definition.put("sessionId", UUID.class);
definition.put("driverKey", String.class);
definition.put("eventId", String.class);
definition.put("eventDomain", String.class);
definition.put("occurredAt", OffsetDateTime.class);
definition.put("occurredAtEpochSecond", long.class);
definition.put("registrationKey", String.class);
definition.put("vehicleKey", String.class);
definition.put("latitude", Double.class);
definition.put("longitude", Double.class);
definition.put("odometerKm", Long.class);
definition.put("priority", int.class);
return definition;
}
private Map<String, Object> toActivityIntervalInputMap(
DriverWorkingTimeActivityInterval interval
) {
if (interval == null || interval.startedAt() == null || interval.endedAt() == null) {
return null;
}
Map<String, Object> event = new LinkedHashMap<>();
event.put("sessionId", interval.sessionId());
event.put("driverKey", interval.driverKey());
event.put("intervalId", interval.intervalId());
event.put("activityType", interval.activityType());
event.put("cardSlot", interval.cardSlot());
event.put("cardStatus", interval.cardStatus());
event.put("drivingStatus", interval.drivingStatus());
event.put("registrationKey", interval.registrationKey());
event.put("vehicleKey", interval.vehicleKey());
event.put("sourceKind", interval.sourceKind());
event.put("firstSourceIntervalId", interval.firstSourceIntervalId());
event.put("lastSourceIntervalId", interval.lastSourceIntervalId());
event.put("startedAt", interval.startedAt());
event.put("endedAt", interval.endedAt());
event.put("startedAtEpochSecond", interval.startedAtEpochSecond());
event.put("endedAtEpochSecond", interval.endedAtEpochSecond());
event.put("durationSeconds", interval.durationSeconds());
event.put("sourceIntervalIds", interval.sourceIntervalIds());
event.put("synthetic", interval.synthetic());
event.put("clippedToRequestedPeriod", interval.clippedToRequestedPeriod());
event.put("level", interval.level());
return event;
}
private Map<String, Object> toVehicleUsageIntervalInputMap(DriverWorkingTimeVehicleUsageInterval interval) {
if (interval == null || interval.startedAt() == null) {
return null;
}
Map<String, Object> event = new LinkedHashMap<>();
event.put("sessionId", interval.sessionId());
event.put("driverKey", interval.driverKey());
event.put("intervalId", interval.intervalId());
event.put("firstSourceIntervalId", interval.firstSourceIntervalId());
event.put("lastSourceIntervalId", interval.lastSourceIntervalId());
event.put("startedAt", interval.startedAt());
event.put("endedAt", interval.endedAt());
event.put("startedAtEpochSecond", interval.startedAtEpochSecond());
event.put("endedAtEpochSecond", interval.endedAtEpochSecond());
event.put("durationSeconds", interval.durationSeconds());
event.put("odometerBeginKm", interval.odometerBeginKm());
event.put("odometerEndKm", interval.odometerEndKm());
event.put("registrationKey", interval.registrationKey());
event.put("vehicleKey", interval.vehicleKey());
event.put("sourceKind", interval.sourceKind());
event.put("sourceIntervalIds", interval.sourceIntervalIds());
return event;
}
private Map<String, Object> toSupportGeoEvidenceInputMap(
UUID sessionId,
RuntimeSupportEvidenceEvent supportEvent
) {
if (supportEvent == null
|| supportEvent.driverKey() == null
|| supportEvent.occurredAt() == null
|| supportEvent.latitude() == null
|| supportEvent.longitude() == null) {
return null;
}
int priority = supportGeoPriority(supportEvent.eventDomain());
if (priority <= 0) {
return null;
}
Map<String, Object> event = new LinkedHashMap<>();
event.put("sessionId", sessionId == null ? new UUID(0L, 0L) : sessionId);
event.put("driverKey", supportEvent.driverKey());
event.put("eventId", supportEvent.eventId());
event.put("eventDomain", supportEvent.eventDomain());
event.put("occurredAt", supportEvent.occurredAt());
event.put("occurredAtEpochSecond", supportEvent.occurredAt().toEpochSecond());
event.put("registrationKey", supportEvent.registrationKey());
event.put("vehicleKey", supportEvent.vehicleKey());
event.put("latitude", supportEvent.latitude().doubleValue());
event.put("longitude", supportEvent.longitude().doubleValue());
event.put("odometerKm", supportEvent.odometerKm());
event.put("priority", priority);
return event;
}
private int supportGeoPriority(String eventDomain) {
if (eventDomain == null || eventDomain.isBlank()) {
return 0;
}
return switch (eventDomain.trim().toUpperCase()) {
case "POSITION" -> 500;
case "PLACE" -> 400;
case "BORDER_CROSSING" -> 300;
case "LOAD_UNLOAD" -> 250;
default -> 0;
};
}
private void collectDrivingInterruptionIntervalModels(
EventBean[] newData,
List<DriverWorkingTimeDrivingInterruptionInterval> target
) {
if (newData == null) {
return;
}
for (EventBean event : newData) {
long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond");
long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond");
target.add(new DriverWorkingTimeDrivingInterruptionInterval(
(UUID) event.get("sessionId"),
(String) event.get("driverKey"),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
(Long) event.get("durationSeconds"),
(String) event.get("previousDrivingSourceIntervalId"),
(String) event.get("nextDrivingSourceIntervalId"),
(String) event.get("previousRegistrationKey"),
(String) event.get("nextRegistrationKey"),
(String) event.get("previousVehicleKey"),
(String) event.get("nextVehicleKey")
));
}
}
private void collectVuCardAbsentIntervalModels(
EventBean[] newData,
List<DriverWorkingTimeVuCardAbsentInterval> target
) {
if (newData == null) {
return;
}
for (EventBean event : newData) {
long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond");
long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond");
target.add(new DriverWorkingTimeVuCardAbsentInterval(
(UUID) event.get("sessionId"),
(String) event.get("driverKey"),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
(Long) event.get("durationSeconds"),
(String) event.get("previousUsageIntervalId"),
(String) event.get("nextUsageIntervalId"),
(String) event.get("previousRegistrationKey"),
(String) event.get("nextRegistrationKey"),
(String) event.get("previousVehicleKey"),
(String) event.get("nextVehicleKey")
));
}
}
private void collectDailyWeeklyRestCandidateCoverageIntervalModels(
EventBean[] newData,
List<DriverWorkingTimeRestCoverageInterval> target
) {
collectRestCoverageModels(newData, target);
}
private void collectPotentialHomeOvernightStayIntervalModels(
EventBean[] newData,
List<DriverWorkingTimeRestCoverageInterval> target
) {
collectRestCoverageModels(newData, target);
}
private void collectPotentialInVehicleOvernightStayIntervalModels(
EventBean[] newData,
List<DriverWorkingTimeRestCoverageInterval> target
) {
collectRestCoverageModels(newData, target);
}
private void collectRestCoverageModels(
EventBean[] newData,
List<DriverWorkingTimeRestCoverageInterval> target
) {
if (newData == null) {
return;
}
for (EventBean event : newData) {
long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond");
long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond");
DriverWorkingTimeGeoEvidenceEvent beginGeoEvent = geoEvidenceModel(
(String) event.get("beginGeoEventId"),
(String) event.get("beginGeoEventDomain"),
offsetDateTime(event.get("beginGeoOccurredAtEpochSecond")),
(Double) event.get("beginLatitude"),
(Double) event.get("beginLongitude"),
longOrNull(event.get("beginGeoDistanceSeconds")),
longOrNull(event.get("beginGeoOdometerKm"))
);
DriverWorkingTimeGeoEvidenceEvent endGeoEvent = geoEvidenceModel(
(String) event.get("endGeoEventId"),
(String) event.get("endGeoEventDomain"),
offsetDateTime(event.get("endGeoOccurredAtEpochSecond")),
(Double) event.get("endLatitude"),
(Double) event.get("endLongitude"),
longOrNull(event.get("endGeoDistanceSeconds")),
longOrNull(event.get("endGeoOdometerKm"))
);
target.add(new DriverWorkingTimeRestCoverageInterval(
(UUID) event.get("sessionId"),
(String) event.get("driverKey"),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
(Long) event.get("durationSeconds"),
(Long) event.get("cardAbsentDurationSeconds"),
(Double) event.get("cardAbsentCoveragePercent"),
(String) event.get("previousDrivingSourceIntervalId"),
(String) event.get("nextDrivingSourceIntervalId"),
(String) event.get("previousRegistrationKey"),
(String) event.get("nextRegistrationKey"),
(String) event.get("previousVehicleKey"),
(String) event.get("nextVehicleKey"),
longOrNull(event.get("beginBoundaryOdometerKm")),
longOrNull(event.get("endBoundaryOdometerKm")),
(String) event.get("beginGeoEventId"),
(String) event.get("beginGeoEventDomain"),
offsetDateTime(event.get("beginGeoOccurredAtEpochSecond")),
(Double) event.get("beginLatitude"),
(Double) event.get("beginLongitude"),
longOrNull(event.get("beginGeoDistanceSeconds")),
longOrNull(event.get("beginGeoOdometerKm")),
(String) event.get("endGeoEventId"),
(String) event.get("endGeoEventDomain"),
offsetDateTime(event.get("endGeoOccurredAtEpochSecond")),
(Double) event.get("endLatitude"),
(Double) event.get("endLongitude"),
longOrNull(event.get("endGeoDistanceSeconds")),
longOrNull(event.get("endGeoOdometerKm")),
longOrNull(event.get("geoEvidenceMovementMeters")),
(String) event.get("geoEvidenceMovementCategory"),
beginGeoEvent,
endGeoEvent
));
}
}
private void collectPotentialInVehicleTripIntervalModels(
EventBean[] newData,
List<DriverWorkingTimePotentialInVehicleTripInterval> target
) {
if (newData == null) {
return;
}
for (EventBean event : newData) {
long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond");
long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond");
target.add(new DriverWorkingTimePotentialInVehicleTripInterval(
(UUID) event.get("sessionId"),
(String) event.get("driverKey"),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
(Long) event.get("durationSeconds"),
(String) event.get("registrationKey"),
(String) event.get("vehicleKey"),
(Integer) event.get("containedPotentialInVehicleOvernightStayIntervalCount"),
(Long) event.get("containedPotentialInVehicleOvernightStayDurationSeconds"),
(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"),
(String) event.get("lastNextDrivingSourceIntervalId"),
List.of()
));
}
}
private List<DriverWorkingTimeDrivingInterruptionInterval> sortDrivingInterruptionIntervalsCommon(
List<DriverWorkingTimeDrivingInterruptionInterval> intervals
) {
return intervals.stream()
.sorted(Comparator.comparing(DriverWorkingTimeDrivingInterruptionInterval::startedAt)
.thenComparing(DriverWorkingTimeDrivingInterruptionInterval::endedAt))
.toList();
}
private List<DriverWorkingTimeVuCardAbsentInterval> sortVuCardAbsentIntervalsCommon(
List<DriverWorkingTimeVuCardAbsentInterval> intervals
) {
return intervals.stream()
.sorted(Comparator.comparing(DriverWorkingTimeVuCardAbsentInterval::startedAt)
.thenComparing(DriverWorkingTimeVuCardAbsentInterval::endedAt))
.toList();
}
private List<DriverWorkingTimeRestCoverageInterval> sortDailyWeeklyRestCandidateCoverageIntervalsCommon(
List<DriverWorkingTimeRestCoverageInterval> intervals
) {
return deduplicateRestCoverageIntervalsCommon(intervals).stream()
.sorted(Comparator.comparing(DriverWorkingTimeRestCoverageInterval::startedAt)
.thenComparing(DriverWorkingTimeRestCoverageInterval::endedAt))
.toList();
}
private List<DriverWorkingTimeRestCoverageInterval> sortPotentialHomeOvernightStayIntervalsCommon(
List<DriverWorkingTimeRestCoverageInterval> intervals
) {
return deduplicateRestCoverageIntervalsCommon(intervals).stream()
.sorted(Comparator.comparing(DriverWorkingTimeRestCoverageInterval::startedAt)
.thenComparing(DriverWorkingTimeRestCoverageInterval::endedAt))
.toList();
}
private List<DriverWorkingTimeRestCoverageInterval> sortPotentialInVehicleOvernightStayIntervalsCommon(
List<DriverWorkingTimeRestCoverageInterval> intervals
) {
return deduplicateRestCoverageIntervalsCommon(intervals).stream()
.sorted(Comparator.comparing(DriverWorkingTimeRestCoverageInterval::startedAt)
.thenComparing(DriverWorkingTimeRestCoverageInterval::endedAt))
.toList();
}
private List<DriverWorkingTimePotentialInVehicleTripInterval> sortPotentialInVehicleTripIntervalsCommon(
List<DriverWorkingTimePotentialInVehicleTripInterval> intervals
) {
return intervals.stream()
.sorted(Comparator.comparing(DriverWorkingTimePotentialInVehicleTripInterval::startedAt)
.thenComparing(DriverWorkingTimePotentialInVehicleTripInterval::endedAt))
.toList();
}
private List<DriverWorkingTimeRestCoverageInterval> deduplicateRestCoverageIntervalsCommon(
List<DriverWorkingTimeRestCoverageInterval> intervals
) {
Map<String, DriverWorkingTimeRestCoverageInterval> deduplicated = new LinkedHashMap<>();
for (DriverWorkingTimeRestCoverageInterval 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 DriverWorkingTimeGeoEvidenceEvent geoEvidenceModel(
String eventId,
String eventDomain,
OffsetDateTime occurredAt,
Double latitude,
Double longitude,
Long distanceSeconds,
Long odometerKm
) {
if (eventId == null
&& eventDomain == null
&& occurredAt == null
&& latitude == null
&& longitude == null
&& distanceSeconds == null
&& odometerKm == null) {
return null;
}
return new DriverWorkingTimeGeoEvidenceEvent(
eventId,
eventDomain,
occurredAt,
latitude,
longitude,
distanceSeconds,
odometerKm
);
}
private String renderDrivingDerivedProjectionBundleEpl(int significantDrivingMinutes, int minimumRestPeriodMinutes) {
return DRIVING_DERIVED_PROJECTION_BUNDLE_EPL_TEMPLATE
.replace(
"${SIGNIFICANT_DRIVING_THRESHOLD_SECONDS}",
Long.toString(Math.max(1, significantDrivingMinutes) * 60L)
)
.replace(
"${MINIMUM_REST_PERIOD_THRESHOLD_SECONDS}",
Long.toString(Math.max(1, minimumRestPeriodMinutes) * 60L)
)
.replace(
"${REST_GEO_LOOKBACK_SECONDS}",
Long.toString(Math.max(1, properties.getTachographFileSession().getProcessing().getRestCandidateGeoLookbackMinutes()) * 60L)
)
.replace(
"${REST_GEO_LOOKAHEAD_SECONDS}",
Long.toString(Math.max(1, properties.getTachographFileSession().getProcessing().getRestCandidateGeoLookaheadMinutes()) * 60L)
)
.replace(
"${REST_GEO_STATIONARY_MAX_METERS}",
Integer.toString(Math.max(0, properties.getTachographFileSession().getProcessing().getRestCandidateGeoStationaryMaxMeters()))
)
.replace(
"${REST_GEO_MINOR_MOVEMENT_MAX_METERS}",
Integer.toString(Math.max(
properties.getTachographFileSession().getProcessing().getRestCandidateGeoStationaryMaxMeters(),
properties.getTachographFileSession().getProcessing().getRestCandidateGeoMinorMovementMaxMeters()
))
);
}
private OffsetDateTime offsetDateTime(Object epochSecond) {
if (!(epochSecond instanceof Long value)) {
return null;
}
return OffsetDateTime.ofInstant(Instant.ofEpochSecond(value), ZoneOffset.UTC);
}
private Long longOrNull(Object value) {
return value instanceof Long number ? number : null;
}
private static String loadResource(String path) {
try {
ClassPathResource resource = new ClassPathResource(path);
return StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new IllegalStateException("Cannot load EPL resource: " + path, e);
}
}
}

View File

@ -10,6 +10,7 @@ import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScope
import at.procon.eventhub.processing.model.RuntimeDriverVehicleEvidenceAttachmentResult;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
import at.procon.eventhub.tachographfilesession.service.TachographDriverWorkingTimeAdapter;
import com.fasterxml.jackson.databind.JsonNode;
import java.time.OffsetDateTime;
import java.util.ArrayList;
@ -67,7 +68,7 @@ public class RuntimeDriverVehicleEvidenceAttachmentService {
);
List<DriverWorkingTimeVehicleUsageInterval> vehicleUsageIntervals = mergeVehicleUsageIntervals(timeline.vehicleUsageIntervals())
.stream()
.map(DriverWorkingTimeVehicleUsageInterval::fromResolved)
.map(TachographDriverWorkingTimeAdapter::toVehicleUsageInterval)
.filter(Objects::nonNull)
.toList();
return attachVehicleEvidence(

View File

@ -9,12 +9,16 @@ import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeActivityInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeProcessingInput;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval;
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeDerivedProjectionEngine;
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeProcessingCore;
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeReusableProjectionBuilder;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceNormalizationResult;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceEvent;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceNormalizer;
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto;
import at.procon.eventhub.tachographfilesession.service.TachographDriverWorkingTimeAdapter;
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
@ -27,9 +31,6 @@ import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialIn
import at.procon.eventhub.tachographfilesession.model.TachographEsperSupportGeoEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import at.procon.eventhub.tachographfilesession.service.DriverTimelineBuilder;
import at.procon.eventhub.tachographfilesession.service.DriverTimelineReusableProjectionBuilder;
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeProcessingCore;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
@ -46,8 +47,6 @@ public class UnifiedRuntimeDerivedProjectionService {
private final UnifiedRuntimeEventAssemblyService runtimeEventAssemblyService;
private final UnifiedEventTimelineReconstructor timelineReconstructor;
private final DriverTimelineBuilder driverTimelineBuilder;
private final DriverTimelineReusableProjectionBuilder reusableProjectionBuilder;
private final DriverWorkingTimeProcessingCore workingTimeProcessingCore;
private final RuntimeSupportEvidenceNormalizer supportEvidenceNormalizer;
private final EventHubProperties properties;
@ -55,18 +54,17 @@ public class UnifiedRuntimeDerivedProjectionService {
public UnifiedRuntimeDerivedProjectionService(
UnifiedRuntimeEventAssemblyService runtimeEventAssemblyService,
UnifiedEventTimelineReconstructor timelineReconstructor,
DriverTimelineBuilder driverTimelineBuilder,
DriverTimelineReusableProjectionBuilder reusableProjectionBuilder,
EventHubProperties properties,
DriverWorkingTimeReusableProjectionBuilder reusableProjectionBuilder,
RuntimeSupportEvidenceNormalizer supportEvidenceNormalizer
) {
this(
runtimeEventAssemblyService,
timelineReconstructor,
driverTimelineBuilder,
reusableProjectionBuilder,
properties,
new DriverWorkingTimeProcessingCore(driverTimelineBuilder, reusableProjectionBuilder, properties),
new DriverWorkingTimeProcessingCore(new DriverWorkingTimeDerivedProjectionEngine(
reusableProjectionBuilder
)),
supportEvidenceNormalizer
);
}
@ -75,16 +73,12 @@ public class UnifiedRuntimeDerivedProjectionService {
public UnifiedRuntimeDerivedProjectionService(
UnifiedRuntimeEventAssemblyService runtimeEventAssemblyService,
UnifiedEventTimelineReconstructor timelineReconstructor,
DriverTimelineBuilder driverTimelineBuilder,
DriverTimelineReusableProjectionBuilder reusableProjectionBuilder,
EventHubProperties properties,
DriverWorkingTimeProcessingCore workingTimeProcessingCore,
RuntimeSupportEvidenceNormalizer supportEvidenceNormalizer
) {
this.runtimeEventAssemblyService = runtimeEventAssemblyService;
this.timelineReconstructor = timelineReconstructor;
this.driverTimelineBuilder = driverTimelineBuilder;
this.reusableProjectionBuilder = reusableProjectionBuilder;
this.properties = properties;
this.workingTimeProcessingCore = workingTimeProcessingCore;
this.supportEvidenceNormalizer = supportEvidenceNormalizer;
@ -155,11 +149,15 @@ public class UnifiedRuntimeDerivedProjectionService {
significantDrivingMinutes,
minimumRestPeriodMinutes,
timeline.activityIntervals().stream()
.map(interval -> DriverWorkingTimeActivityInterval.fromResolved(runtimeSessionId(request), driverKey, interval))
.map(interval -> TachographDriverWorkingTimeAdapter.toActivityInterval(
runtimeSessionId(request),
driverKey,
interval
))
.filter(Objects::nonNull)
.toList(),
timeline.vehicleUsageIntervals().stream()
.map(DriverWorkingTimeVehicleUsageInterval::fromResolved)
.map(TachographDriverWorkingTimeAdapter::toVehicleUsageInterval)
.filter(Objects::nonNull)
.toList(),
timeline.supportEvents().stream()

View File

@ -1,9 +1,18 @@
package at.procon.eventhub.tachographfilesession.dto;
import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeActivityInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDrivingInterruptionInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeGeoEvidenceEvent;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimePotentialInVehicleTripInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeRestCoverageInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeSupportGeoEvent;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVuCardAbsentInterval;
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperGeoEvidenceEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleTripIntervalEvent;
@ -78,19 +87,19 @@ public record TachographEsperDriverProcessingResultDto(
result.vehicleUsageIntervalCount(),
result.vuCardAbsentIntervalCount(),
result.supportGeoEventCount(),
result.activityIntervals(),
result.drivingIntervals(),
result.drivingInterruptionIntervals(),
result.drivingInterruptionVehicleChangeIntervals(),
result.dailyWeeklyRestCandidateIntervals(),
result.dailyWeeklyRestCandidateCoverageIntervals(),
result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals(),
result.potentialHomeOvernightStayIntervals(),
result.potentialInVehicleOvernightStayIntervals(),
result.potentialInVehicleTripIntervals(),
result.vehicleUsageIntervals(),
result.vuCardAbsentIntervals(),
result.supportGeoEvents(),
mapActivityIntervalsToLegacy(result.activityIntervals()),
mapActivityIntervalsToLegacy(result.drivingIntervals()),
mapDrivingInterruptionsToLegacy(result.drivingInterruptionIntervals()),
mapDrivingInterruptionsToLegacy(result.drivingInterruptionVehicleChangeIntervals()),
mapDrivingInterruptionsToLegacy(result.dailyWeeklyRestCandidateIntervals()),
mapCoverageIntervalsToLegacy(result.dailyWeeklyRestCandidateCoverageIntervals()),
mapCoverageIntervalsToLegacy(result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals()),
mapPotentialHomeIntervalsToLegacy(result.potentialHomeOvernightStayIntervals()),
mapPotentialInVehicleIntervalsToLegacy(result.potentialInVehicleOvernightStayIntervals()),
mapPotentialTripsToLegacy(result.potentialInVehicleTripIntervals()),
mapVehicleUsageIntervalsToLegacy(result.vehicleUsageIntervals()),
mapVuCardAbsentIntervalsToLegacy(result.vuCardAbsentIntervals()),
mapSupportGeoEventsToLegacy(result.supportGeoEvents()),
result.notes()
);
}
@ -117,20 +126,689 @@ public record TachographEsperDriverProcessingResultDto(
vehicleUsageIntervalCount,
vuCardAbsentIntervalCount,
supportGeoEventCount,
activityIntervals,
drivingIntervals,
drivingInterruptionIntervals,
drivingInterruptionVehicleChangeIntervals,
dailyWeeklyRestCandidateIntervals,
dailyWeeklyRestCandidateCoverageIntervals,
unclassifiedDailyWeeklyRestCandidateCoverageIntervals,
potentialHomeOvernightStayIntervals,
potentialInVehicleOvernightStayIntervals,
potentialInVehicleTripIntervals,
vehicleUsageIntervals,
vuCardAbsentIntervals,
supportGeoEvents,
mapActivityIntervalsFromLegacy(activityIntervals),
mapActivityIntervalsFromLegacy(drivingIntervals),
mapDrivingInterruptionsFromLegacy(drivingInterruptionIntervals),
mapDrivingInterruptionsFromLegacy(drivingInterruptionVehicleChangeIntervals),
mapDrivingInterruptionsFromLegacy(dailyWeeklyRestCandidateIntervals),
mapCoverageIntervalsFromLegacy(dailyWeeklyRestCandidateCoverageIntervals),
mapCoverageIntervalsFromLegacy(unclassifiedDailyWeeklyRestCandidateCoverageIntervals),
mapPotentialHomeIntervalsFromLegacy(potentialHomeOvernightStayIntervals),
mapPotentialInVehicleIntervalsFromLegacy(potentialInVehicleOvernightStayIntervals),
mapPotentialTripsFromLegacy(potentialInVehicleTripIntervals),
mapVehicleUsageIntervalsFromLegacy(vehicleUsageIntervals),
mapVuCardAbsentIntervalsFromLegacy(vuCardAbsentIntervals),
mapSupportGeoEventsFromLegacy(supportGeoEvents),
notes
);
}
private static List<TachographEsperActivityIntervalEvent> mapActivityIntervalsToLegacy(
List<DriverWorkingTimeActivityInterval> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toLegacy).toList();
}
private static List<DriverWorkingTimeActivityInterval> mapActivityIntervalsFromLegacy(
List<TachographEsperActivityIntervalEvent> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toCommon).toList();
}
private static List<TachographEsperDrivingInterruptionIntervalEvent> mapDrivingInterruptionsToLegacy(
List<DriverWorkingTimeDrivingInterruptionInterval> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toLegacy).toList();
}
private static List<DriverWorkingTimeDrivingInterruptionInterval> mapDrivingInterruptionsFromLegacy(
List<TachographEsperDrivingInterruptionIntervalEvent> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toCommon).toList();
}
private static List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> mapCoverageIntervalsToLegacy(
List<DriverWorkingTimeRestCoverageInterval> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toLegacyCoverage).toList();
}
private static List<DriverWorkingTimeRestCoverageInterval> mapCoverageIntervalsFromLegacy(
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toCommon).toList();
}
private static List<TachographEsperPotentialHomeOvernightStayIntervalEvent> mapPotentialHomeIntervalsToLegacy(
List<DriverWorkingTimeRestCoverageInterval> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toLegacyPotentialHome).toList();
}
private static List<DriverWorkingTimeRestCoverageInterval> mapPotentialHomeIntervalsFromLegacy(
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toCommon).toList();
}
private static List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> mapPotentialInVehicleIntervalsToLegacy(
List<DriverWorkingTimeRestCoverageInterval> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toLegacyPotentialInVehicle).toList();
}
private static List<DriverWorkingTimeRestCoverageInterval> mapPotentialInVehicleIntervalsFromLegacy(
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toCommon).toList();
}
private static List<TachographEsperPotentialInVehicleTripIntervalEvent> mapPotentialTripsToLegacy(
List<DriverWorkingTimePotentialInVehicleTripInterval> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toLegacy).toList();
}
private static List<DriverWorkingTimePotentialInVehicleTripInterval> mapPotentialTripsFromLegacy(
List<TachographEsperPotentialInVehicleTripIntervalEvent> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toCommon).toList();
}
private static List<TachographEsperVehicleUsageIntervalEvent> mapVehicleUsageIntervalsToLegacy(
List<DriverWorkingTimeVehicleUsageInterval> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toLegacy).toList();
}
private static List<DriverWorkingTimeVehicleUsageInterval> mapVehicleUsageIntervalsFromLegacy(
List<TachographEsperVehicleUsageIntervalEvent> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toCommon).toList();
}
private static List<TachographEsperVuCardAbsentIntervalEvent> mapVuCardAbsentIntervalsToLegacy(
List<DriverWorkingTimeVuCardAbsentInterval> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toLegacy).toList();
}
private static List<DriverWorkingTimeVuCardAbsentInterval> mapVuCardAbsentIntervalsFromLegacy(
List<TachographEsperVuCardAbsentIntervalEvent> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toCommon).toList();
}
private static List<TachographEsperSupportGeoEvent> mapSupportGeoEventsToLegacy(
List<DriverWorkingTimeSupportGeoEvent> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toLegacy).toList();
}
private static List<DriverWorkingTimeSupportGeoEvent> mapSupportGeoEventsFromLegacy(
List<TachographEsperSupportGeoEvent> values
) {
return safe(values).stream().map(TachographEsperDriverProcessingResultDto::toCommon).toList();
}
private static DriverWorkingTimeActivityInterval toCommon(TachographEsperActivityIntervalEvent value) {
if (value == null) {
return null;
}
return new DriverWorkingTimeActivityInterval(
value.sessionId(),
value.driverKey(),
value.intervalId(),
value.activityType(),
value.cardSlot(),
value.cardStatus(),
value.drivingStatus(),
value.registrationKey(),
value.vehicleKey(),
value.sourceKind(),
firstSourceIntervalId(value.intervalId(), value.sourceIntervalIds()),
lastSourceIntervalId(value.intervalId(), value.sourceIntervalIds()),
value.startedAt(),
value.endedAt(),
epochSecond(value.startedAt()),
epochSecond(value.endedAt()),
value.durationSeconds(),
value.sourceIntervalIds(),
value.synthetic(),
value.clippedToRequestedPeriod(),
value.level()
);
}
private static TachographEsperActivityIntervalEvent toLegacy(DriverWorkingTimeActivityInterval value) {
return new TachographEsperActivityIntervalEvent(
value.sessionId(),
value.driverKey(),
value.intervalId(),
value.activityType(),
value.cardSlot(),
value.cardStatus(),
value.drivingStatus(),
value.registrationKey(),
value.vehicleKey(),
value.sourceKind(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.sourceIntervalIds(),
value.synthetic(),
value.clippedToRequestedPeriod(),
value.level()
);
}
private static DriverWorkingTimeDrivingInterruptionInterval toCommon(
TachographEsperDrivingInterruptionIntervalEvent value
) {
if (value == null) {
return null;
}
return new DriverWorkingTimeDrivingInterruptionInterval(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey()
);
}
private static TachographEsperDrivingInterruptionIntervalEvent toLegacy(
DriverWorkingTimeDrivingInterruptionInterval value
) {
return new TachographEsperDrivingInterruptionIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey()
);
}
private static DriverWorkingTimeRestCoverageInterval toCommon(
TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent value
) {
if (value == null) {
return null;
}
return new DriverWorkingTimeRestCoverageInterval(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.cardAbsentDurationSeconds(),
value.cardAbsentCoveragePercent(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey(),
value.beginBoundaryOdometerKm(),
value.endBoundaryOdometerKm(),
value.beginGeoEventId(),
value.beginGeoEventDomain(),
value.beginGeoOccurredAt(),
value.beginLatitude(),
value.beginLongitude(),
value.beginGeoDistanceSeconds(),
value.beginGeoOdometerKm(),
value.endGeoEventId(),
value.endGeoEventDomain(),
value.endGeoOccurredAt(),
value.endLatitude(),
value.endLongitude(),
value.endGeoDistanceSeconds(),
value.endGeoOdometerKm(),
value.geoEvidenceMovementMeters(),
value.geoEvidenceMovementCategory(),
toCommon(value.beginGeoEvent()),
toCommon(value.endGeoEvent())
);
}
private static DriverWorkingTimeRestCoverageInterval toCommon(
TachographEsperPotentialHomeOvernightStayIntervalEvent value
) {
if (value == null) {
return null;
}
return new DriverWorkingTimeRestCoverageInterval(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.cardAbsentDurationSeconds(),
value.cardAbsentCoveragePercent(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey(),
value.beginBoundaryOdometerKm(),
value.endBoundaryOdometerKm(),
value.beginGeoEventId(),
value.beginGeoEventDomain(),
value.beginGeoOccurredAt(),
value.beginLatitude(),
value.beginLongitude(),
value.beginGeoDistanceSeconds(),
value.beginGeoOdometerKm(),
value.endGeoEventId(),
value.endGeoEventDomain(),
value.endGeoOccurredAt(),
value.endLatitude(),
value.endLongitude(),
value.endGeoDistanceSeconds(),
value.endGeoOdometerKm(),
value.geoEvidenceMovementMeters(),
value.geoEvidenceMovementCategory(),
toCommon(value.beginGeoEvent()),
toCommon(value.endGeoEvent())
);
}
private static DriverWorkingTimeRestCoverageInterval toCommon(
TachographEsperPotentialInVehicleOvernightStayIntervalEvent value
) {
if (value == null) {
return null;
}
return new DriverWorkingTimeRestCoverageInterval(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.cardAbsentDurationSeconds(),
value.cardAbsentCoveragePercent(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey(),
value.beginBoundaryOdometerKm(),
value.endBoundaryOdometerKm(),
value.beginGeoEventId(),
value.beginGeoEventDomain(),
value.beginGeoOccurredAt(),
value.beginLatitude(),
value.beginLongitude(),
value.beginGeoDistanceSeconds(),
value.beginGeoOdometerKm(),
value.endGeoEventId(),
value.endGeoEventDomain(),
value.endGeoOccurredAt(),
value.endLatitude(),
value.endLongitude(),
value.endGeoDistanceSeconds(),
value.endGeoOdometerKm(),
value.geoEvidenceMovementMeters(),
value.geoEvidenceMovementCategory(),
toCommon(value.beginGeoEvent()),
toCommon(value.endGeoEvent())
);
}
private static TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent toLegacyCoverage(
DriverWorkingTimeRestCoverageInterval value
) {
return new TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.cardAbsentDurationSeconds(),
value.cardAbsentCoveragePercent(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey(),
value.beginBoundaryOdometerKm(),
value.endBoundaryOdometerKm(),
value.beginGeoEventId(),
value.beginGeoEventDomain(),
value.beginGeoOccurredAt(),
value.beginLatitude(),
value.beginLongitude(),
value.beginGeoDistanceSeconds(),
value.beginGeoOdometerKm(),
value.endGeoEventId(),
value.endGeoEventDomain(),
value.endGeoOccurredAt(),
value.endLatitude(),
value.endLongitude(),
value.endGeoDistanceSeconds(),
value.endGeoOdometerKm(),
value.geoEvidenceMovementMeters(),
value.geoEvidenceMovementCategory(),
toLegacy(value.beginGeoEvent()),
toLegacy(value.endGeoEvent())
);
}
private static TachographEsperPotentialHomeOvernightStayIntervalEvent toLegacyPotentialHome(
DriverWorkingTimeRestCoverageInterval value
) {
return new TachographEsperPotentialHomeOvernightStayIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.cardAbsentDurationSeconds(),
value.cardAbsentCoveragePercent(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey(),
value.beginBoundaryOdometerKm(),
value.endBoundaryOdometerKm(),
value.beginGeoEventId(),
value.beginGeoEventDomain(),
value.beginGeoOccurredAt(),
value.beginLatitude(),
value.beginLongitude(),
value.beginGeoDistanceSeconds(),
value.beginGeoOdometerKm(),
value.endGeoEventId(),
value.endGeoEventDomain(),
value.endGeoOccurredAt(),
value.endLatitude(),
value.endLongitude(),
value.endGeoDistanceSeconds(),
value.endGeoOdometerKm(),
value.geoEvidenceMovementMeters(),
value.geoEvidenceMovementCategory(),
toLegacy(value.beginGeoEvent()),
toLegacy(value.endGeoEvent())
);
}
private static TachographEsperPotentialInVehicleOvernightStayIntervalEvent toLegacyPotentialInVehicle(
DriverWorkingTimeRestCoverageInterval value
) {
return new TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.cardAbsentDurationSeconds(),
value.cardAbsentCoveragePercent(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey(),
value.beginBoundaryOdometerKm(),
value.endBoundaryOdometerKm(),
value.beginGeoEventId(),
value.beginGeoEventDomain(),
value.beginGeoOccurredAt(),
value.beginLatitude(),
value.beginLongitude(),
value.beginGeoDistanceSeconds(),
value.beginGeoOdometerKm(),
value.endGeoEventId(),
value.endGeoEventDomain(),
value.endGeoOccurredAt(),
value.endLatitude(),
value.endLongitude(),
value.endGeoDistanceSeconds(),
value.endGeoOdometerKm(),
value.geoEvidenceMovementMeters(),
value.geoEvidenceMovementCategory(),
toLegacy(value.beginGeoEvent()),
toLegacy(value.endGeoEvent())
);
}
private static DriverWorkingTimePotentialInVehicleTripInterval toCommon(
TachographEsperPotentialInVehicleTripIntervalEvent value
) {
if (value == null) {
return null;
}
return new DriverWorkingTimePotentialInVehicleTripInterval(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.registrationKey(),
value.vehicleKey(),
value.containedPotentialInVehicleOvernightStayIntervalCount(),
value.containedPotentialInVehicleOvernightStayDurationSeconds(),
value.containedCardAbsentDurationSeconds(),
value.firstPotentialInVehicleOvernightStayStartedAt(),
value.lastPotentialInVehicleOvernightStayEndedAt(),
value.firstPreviousDrivingSourceIntervalId(),
value.lastNextDrivingSourceIntervalId(),
safe(value.potentialInVehicleOvernightStayIntervals()).stream()
.map(TachographEsperDriverProcessingResultDto::toCommon)
.toList()
);
}
private static TachographEsperPotentialInVehicleTripIntervalEvent toLegacy(
DriverWorkingTimePotentialInVehicleTripInterval value
) {
return new TachographEsperPotentialInVehicleTripIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.registrationKey(),
value.vehicleKey(),
value.containedPotentialInVehicleOvernightStayIntervalCount(),
value.containedPotentialInVehicleOvernightStayDurationSeconds(),
value.containedCardAbsentDurationSeconds(),
value.firstPotentialInVehicleOvernightStayStartedAt(),
value.lastPotentialInVehicleOvernightStayEndedAt(),
value.firstPreviousDrivingSourceIntervalId(),
value.lastNextDrivingSourceIntervalId(),
safe(value.potentialInVehicleOvernightStayIntervals()).stream()
.map(TachographEsperDriverProcessingResultDto::toLegacyPotentialInVehicle)
.toList()
);
}
private static DriverWorkingTimeVehicleUsageInterval toCommon(TachographEsperVehicleUsageIntervalEvent value) {
if (value == null) {
return null;
}
return new DriverWorkingTimeVehicleUsageInterval(
value.sessionId(),
value.driverKey(),
value.intervalId(),
firstSourceIntervalId(value.intervalId(), value.sourceIntervalIds()),
lastSourceIntervalId(value.intervalId(), value.sourceIntervalIds()),
value.startedAt(),
value.endedAt(),
epochSecond(value.startedAt()),
value.endedAt() == null ? null : value.endedAt().toEpochSecond(),
value.durationSeconds(),
value.odometerBeginKm(),
value.odometerEndKm(),
value.registrationKey(),
value.vehicleKey(),
value.sourceKind(),
value.sourceIntervalIds()
);
}
private static TachographEsperVehicleUsageIntervalEvent toLegacy(DriverWorkingTimeVehicleUsageInterval value) {
return new TachographEsperVehicleUsageIntervalEvent(
value.sessionId(),
value.driverKey(),
value.intervalId(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.odometerBeginKm(),
value.odometerEndKm(),
value.registrationKey(),
value.vehicleKey(),
value.sourceKind(),
value.sourceIntervalIds()
);
}
private static DriverWorkingTimeVuCardAbsentInterval toCommon(TachographEsperVuCardAbsentIntervalEvent value) {
if (value == null) {
return null;
}
return new DriverWorkingTimeVuCardAbsentInterval(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.previousUsageIntervalId(),
value.nextUsageIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey()
);
}
private static TachographEsperVuCardAbsentIntervalEvent toLegacy(DriverWorkingTimeVuCardAbsentInterval value) {
return new TachographEsperVuCardAbsentIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.previousUsageIntervalId(),
value.nextUsageIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey()
);
}
private static DriverWorkingTimeSupportGeoEvent toCommon(TachographEsperSupportGeoEvent value) {
if (value == null) {
return null;
}
return new DriverWorkingTimeSupportGeoEvent(
value.eventId(),
value.driverKey(),
value.occurredAt(),
value.eventDomain(),
value.eventType(),
value.eventLifecycle(),
value.registrationKey(),
value.vehicleKey(),
value.country(),
value.region(),
value.countryFrom(),
value.countryTo(),
value.operation(),
value.latitude(),
value.longitude(),
value.odometerKm(),
value.rawRecordPath()
);
}
private static TachographEsperSupportGeoEvent toLegacy(DriverWorkingTimeSupportGeoEvent value) {
return new TachographEsperSupportGeoEvent(
value.eventId(),
value.driverKey(),
value.occurredAt(),
value.eventDomain(),
value.eventType(),
value.eventLifecycle(),
value.registrationKey(),
value.vehicleKey(),
value.country(),
value.region(),
value.countryFrom(),
value.countryTo(),
value.operation(),
value.latitude(),
value.longitude(),
value.odometerKm(),
value.rawRecordPath()
);
}
private static DriverWorkingTimeGeoEvidenceEvent toCommon(TachographEsperGeoEvidenceEvent value) {
if (value == null) {
return null;
}
return new DriverWorkingTimeGeoEvidenceEvent(
value.eventId(),
value.eventDomain(),
value.occurredAt(),
value.latitude(),
value.longitude(),
value.distanceSeconds(),
value.odometerKm()
);
}
private static TachographEsperGeoEvidenceEvent toLegacy(DriverWorkingTimeGeoEvidenceEvent value) {
if (value == null) {
return null;
}
return new TachographEsperGeoEvidenceEvent(
value.eventId(),
value.eventDomain(),
value.occurredAt(),
value.latitude(),
value.longitude(),
value.distanceSeconds(),
value.odometerKm()
);
}
private static String firstSourceIntervalId(String intervalId, List<String> sourceIntervalIds) {
return sourceIntervalIds == null || sourceIntervalIds.isEmpty() ? intervalId : sourceIntervalIds.getFirst();
}
private static String lastSourceIntervalId(String intervalId, List<String> sourceIntervalIds) {
return sourceIntervalIds == null || sourceIntervalIds.isEmpty()
? intervalId
: sourceIntervalIds.getLast();
}
private static long epochSecond(OffsetDateTime value) {
return value == null ? 0L : value.toEpochSecond();
}
private static <T> List<T> safe(List<T> values) {
return values == null ? List.of() : values;
}
}

View File

@ -0,0 +1,236 @@
package at.procon.eventhub.tachographfilesession.service;
import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDerivedProjectionBundle;
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeReusableProjectionBuilder;
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingDerivedProjectionBundle;
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Tachograph-owned compatibility facade that adapts legacy file-session and event-based callers
* onto the neutral driver working-time projection engine.
*/
@Component
public class TachographDerivedProjectionCompatibilityFacade {
private final DriverTimelineBuilder driverTimelineBuilder;
private final RawSourceDriverTimelineEventBuilder rawSourceEventBuilder;
private final TachographEventTimelineReconstructionHelper reconstructionHelper;
private final EventHubProperties properties;
private final DriverWorkingTimeReusableProjectionBuilder commonBuilder;
public TachographDerivedProjectionCompatibilityFacade(
DriverTimelineBuilder driverTimelineBuilder,
EventHubProperties properties
) {
this(
driverTimelineBuilder,
null,
new TachographEventTimelineReconstructionHelper(),
properties,
new DriverWorkingTimeReusableProjectionBuilder(properties)
);
}
public TachographDerivedProjectionCompatibilityFacade(
DriverTimelineBuilder driverTimelineBuilder,
RawSourceDriverTimelineEventBuilder rawSourceEventBuilder,
EventHubProperties properties
) {
this(
driverTimelineBuilder,
rawSourceEventBuilder,
new TachographEventTimelineReconstructionHelper(),
properties,
new DriverWorkingTimeReusableProjectionBuilder(properties)
);
}
@Autowired
public TachographDerivedProjectionCompatibilityFacade(
DriverTimelineBuilder driverTimelineBuilder,
RawSourceDriverTimelineEventBuilder rawSourceEventBuilder,
TachographEventTimelineReconstructionHelper reconstructionHelper,
EventHubProperties properties,
DriverWorkingTimeReusableProjectionBuilder commonBuilder
) {
this.driverTimelineBuilder = driverTimelineBuilder;
this.rawSourceEventBuilder = rawSourceEventBuilder;
this.reconstructionHelper = reconstructionHelper == null
? new TachographEventTimelineReconstructionHelper()
: reconstructionHelper;
this.properties = properties;
this.commonBuilder = commonBuilder;
}
public DriverWorkingTimeReusableProjectionBuilder commonBuilder() {
return commonBuilder;
}
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
TachographFileSession session,
DriverExtractionSession driverSession,
int significantDrivingMinutes,
int minimumRestPeriodMinutes
) {
if (session == null || driverSession == null || driverSession.driverKey() == null) {
return TachographDriverWorkingTimeAdapter.emptyLegacyBundle();
}
if (drivingDerivedProjectionInputMode() == EventHubProperties.DrivingDerivedProjectionInputMode.EVENTS) {
return buildEsperDrivingDerivedProjectionBundleFromEvents(
session.sessionId(),
driverSession.driverKey(),
rawSourceEventBuilder().buildRawEventBundle(session, driverSession).allEvents(),
significantDrivingMinutes,
minimumRestPeriodMinutes
);
}
ResolvedDriverTimeline timeline = driverTimelineBuilder.build(session, driverSession);
return buildEsperDrivingDerivedProjectionBundle(
session.sessionId(),
driverSession.driverKey(),
timeline,
significantDrivingMinutes,
minimumRestPeriodMinutes
);
}
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
TachographFileSession session,
int significantDrivingMinutes,
int minimumRestPeriodMinutes
) {
if (session == null || session.driversByKey() == null || session.driversByKey().isEmpty()) {
return TachographDriverWorkingTimeAdapter.emptyLegacyBundle();
}
List<TachographEsperDrivingDerivedProjectionBundle> bundles = session.driversByKey().values().stream()
.map(driverSession -> buildEsperDrivingDerivedProjectionBundle(
session,
driverSession,
significantDrivingMinutes,
minimumRestPeriodMinutes
))
.toList();
return TachographDriverWorkingTimeAdapter.mergeLegacyBundles(bundles);
}
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
UUID sessionId,
String driverKey,
ResolvedDriverTimeline timeline,
int significantDrivingMinutes,
int minimumRestPeriodMinutes
) {
if (timeline == null || driverKey == null || driverKey.isBlank()) {
return TachographDriverWorkingTimeAdapter.emptyLegacyBundle();
}
DriverWorkingTimeDerivedProjectionBundle bundle = commonBuilder.buildDerivedProjectionBundle(
TachographDriverWorkingTimeAdapter.toProcessingInput(
sessionId == null ? new UUID(0L, 0L) : sessionId,
driverKey,
timeline,
null,
null,
significantDrivingMinutes,
minimumRestPeriodMinutes,
List.of()
)
);
return TachographDriverWorkingTimeAdapter.toLegacyBundle(bundle);
}
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundleFromEvents(
List<EventHubEventDto> events,
int significantDrivingMinutes,
int minimumRestPeriodMinutes
) {
return buildEsperDrivingDerivedProjectionBundleFromEvents(
null,
null,
events,
significantDrivingMinutes,
minimumRestPeriodMinutes
);
}
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundleFromEvents(
UUID fallbackSessionId,
String fallbackDriverKey,
List<EventHubEventDto> events,
int significantDrivingMinutes,
int minimumRestPeriodMinutes
) {
if (fallbackDriverKey == null) {
Map<String, List<EventHubEventDto>> eventsByDriver = reconstructionHelper.groupEventsByDriverKey(events);
if (eventsByDriver.isEmpty()) {
return TachographDriverWorkingTimeAdapter.emptyLegacyBundle();
}
if (eventsByDriver.size() > 1) {
return buildEsperDrivingDerivedProjectionBundleFromGroupedEvents(
fallbackSessionId,
eventsByDriver,
significantDrivingMinutes,
minimumRestPeriodMinutes
);
}
Map.Entry<String, List<EventHubEventDto>> onlyDriver = eventsByDriver.entrySet().iterator().next();
fallbackDriverKey = onlyDriver.getKey();
events = onlyDriver.getValue();
}
ResolvedDriverTimeline timeline = reconstructionHelper.reconstructMergedTimelineFromEvents(
fallbackSessionId,
fallbackDriverKey,
events
);
return buildEsperDrivingDerivedProjectionBundle(
fallbackSessionId == null ? new UUID(0L, 0L) : fallbackSessionId,
fallbackDriverKey,
timeline,
significantDrivingMinutes,
minimumRestPeriodMinutes
);
}
private TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundleFromGroupedEvents(
UUID fallbackSessionId,
Map<String, List<EventHubEventDto>> eventsByDriver,
int significantDrivingMinutes,
int minimumRestPeriodMinutes
) {
List<TachographEsperDrivingDerivedProjectionBundle> bundles = eventsByDriver.entrySet().stream()
.map(entry -> buildEsperDrivingDerivedProjectionBundleFromEvents(
fallbackSessionId,
entry.getKey(),
entry.getValue(),
significantDrivingMinutes,
minimumRestPeriodMinutes
))
.toList();
return TachographDriverWorkingTimeAdapter.mergeLegacyBundles(bundles);
}
private EventHubProperties.DrivingDerivedProjectionInputMode drivingDerivedProjectionInputMode() {
return properties.getTachographFileSession().getProcessing().getDrivingDerivedProjectionInputMode();
}
private RawSourceDriverTimelineEventBuilder rawSourceEventBuilder() {
if (rawSourceEventBuilder == null) {
throw new IllegalStateException(
"Driving-derived projection input mode EVENTS requires RawSourceDriverTimelineEventBuilder"
);
}
return rawSourceEventBuilder;
}
}

View File

@ -0,0 +1,500 @@
package at.procon.eventhub.tachographfilesession.service;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeActivityInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDerivedProjectionBundle;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDrivingInterruptionInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeGeoEvidenceEvent;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimePotentialInVehicleTripInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeProcessingInput;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeRestCoverageInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVuCardAbsentInterval;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceEvent;
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingDerivedProjectionBundle;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperGeoEvidenceEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleTripIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
public final class TachographDriverWorkingTimeAdapter {
private TachographDriverWorkingTimeAdapter() {
}
public static DriverWorkingTimeProcessingInput toProcessingInput(
UUID sessionId,
String driverKey,
ResolvedDriverTimeline timeline,
OffsetDateTime requestedFrom,
OffsetDateTime requestedTo,
int significantDrivingMinutes,
int minimumRestPeriodMinutes,
List<String> notes
) {
return new DriverWorkingTimeProcessingInput(
sessionId,
driverKey,
timeline.sourceKind(),
timeline.loadedFrom(),
timeline.loadedTo(),
requestedFrom,
requestedTo,
significantDrivingMinutes,
minimumRestPeriodMinutes,
safe(timeline.activityIntervals()).stream()
.map(interval -> toActivityInterval(sessionId, driverKey, interval))
.toList(),
safe(timeline.vehicleUsageIntervals()).stream()
.map(TachographDriverWorkingTimeAdapter::toVehicleUsageInterval)
.toList(),
safe(timeline.supportEvents()).stream()
.map(TachographDriverWorkingTimeAdapter::toSupportEvidenceEvent)
.toList(),
notes
);
}
public static DriverWorkingTimeActivityInterval toActivityInterval(
UUID sessionId,
String driverKey,
ResolvedActivityInterval interval
) {
if (interval == null || interval.from() == null || interval.to() == null) {
return null;
}
return new DriverWorkingTimeActivityInterval(
sessionId,
driverKey,
interval.intervalId(),
interval.activityType(),
interval.slot(),
interval.cardStatus(),
interval.drivingStatus(),
interval.registrationKey(),
interval.vehicleKey(),
interval.sourceKind(),
firstSourceIntervalId(interval),
lastSourceIntervalId(interval),
interval.from(),
interval.to(),
interval.from().toEpochSecond(),
interval.to().toEpochSecond(),
interval.durationSeconds(),
interval.sourceIntervalIds(),
interval.synthetic(),
interval.clippedToRequestedPeriod(),
interval.level()
);
}
public static DriverWorkingTimeVehicleUsageInterval toVehicleUsageInterval(ResolvedVehicleUsageInterval interval) {
if (interval == null || interval.from() == null) {
return null;
}
return new DriverWorkingTimeVehicleUsageInterval(
interval.sessionId(),
interval.driverKey(),
interval.intervalId(),
firstSourceIntervalId(interval),
lastSourceIntervalId(interval),
interval.from(),
interval.to(),
interval.from().toEpochSecond(),
interval.to() == null ? null : interval.to().toEpochSecond(),
interval.durationSeconds(),
interval.odometerBeginKm(),
interval.odometerEndKm(),
interval.registrationKey(),
interval.vehicleKey(),
interval.sourceKind(),
interval.sourceIntervalIds()
);
}
public static RuntimeSupportEvidenceEvent toSupportEvidenceEvent(ExtractedSupportEvent supportEvent) {
if (supportEvent == null || supportEvent.occurredAt() == null) {
return null;
}
return new RuntimeSupportEvidenceEvent(
supportEvent.eventId(),
null,
null,
supportEvent.eventDomain(),
supportEvent.eventType(),
supportEvent.eventLifecycle(),
supportEvent.driverKey(),
supportEvent.vehicleKey(),
supportEvent.registrationKey(),
supportEvent.occurredAt(),
supportEvent.occurredAt().toEpochSecond(),
supportEvent.latitude(),
supportEvent.longitude(),
supportEvent.country(),
supportEvent.region(),
supportEvent.countryFrom(),
supportEvent.countryTo(),
supportEvent.operation(),
supportEvent.odometerKm(),
supportEvent.avgSpeedKmh(),
supportEvent.maxSpeedKmh(),
java.util.Map.of("rawRecordPath", supportEvent.rawRecordPath())
);
}
public static TachographEsperDrivingDerivedProjectionBundle toLegacyBundle(
DriverWorkingTimeDerivedProjectionBundle bundle
) {
if (bundle == null) {
return emptyLegacyBundle();
}
return new TachographEsperDrivingDerivedProjectionBundle(
safe(bundle.drivingInterruptionIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacy).toList(),
safe(bundle.dailyWeeklyRestCandidateIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacy).toList(),
safe(bundle.dailyWeeklyRestCandidateCoverageIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacyCoverage).toList(),
safe(bundle.unclassifiedDailyWeeklyRestCandidateCoverageIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacyCoverage).toList(),
safe(bundle.drivingInterruptionVehicleChangeIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacy).toList(),
safe(bundle.vuCardAbsentIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacy).toList(),
safe(bundle.potentialHomeOvernightStayIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacyPotentialHome).toList(),
safe(bundle.potentialInVehicleOvernightStayIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacyPotentialInVehicle).toList(),
safe(bundle.potentialInVehicleTripIntervals()).stream().map(TachographDriverWorkingTimeAdapter::toLegacy).toList()
);
}
public static TachographEsperDrivingDerivedProjectionBundle mergeLegacyBundles(
List<TachographEsperDrivingDerivedProjectionBundle> bundles
) {
if (bundles == null || bundles.isEmpty()) {
return emptyLegacyBundle();
}
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals = new ArrayList<>();
List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals = new ArrayList<>();
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> dailyWeeklyRestCandidateCoverageIntervals = new ArrayList<>();
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> unclassifiedDailyWeeklyRestCandidateCoverageIntervals = new ArrayList<>();
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals = new ArrayList<>();
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals = new ArrayList<>();
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals = new ArrayList<>();
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals = new ArrayList<>();
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals = new ArrayList<>();
for (TachographEsperDrivingDerivedProjectionBundle bundle : safe(bundles)) {
if (bundle == null) {
continue;
}
drivingInterruptionIntervals.addAll(safe(bundle.drivingInterruptionIntervals()));
dailyWeeklyRestCandidateIntervals.addAll(safe(bundle.dailyWeeklyRestCandidateIntervals()));
dailyWeeklyRestCandidateCoverageIntervals.addAll(safe(bundle.dailyWeeklyRestCandidateCoverageIntervals()));
unclassifiedDailyWeeklyRestCandidateCoverageIntervals.addAll(safe(bundle.unclassifiedDailyWeeklyRestCandidateCoverageIntervals()));
drivingInterruptionVehicleChangeIntervals.addAll(safe(bundle.drivingInterruptionVehicleChangeIntervals()));
vuCardAbsentIntervals.addAll(safe(bundle.vuCardAbsentIntervals()));
potentialHomeOvernightStayIntervals.addAll(safe(bundle.potentialHomeOvernightStayIntervals()));
potentialInVehicleOvernightStayIntervals.addAll(safe(bundle.potentialInVehicleOvernightStayIntervals()));
potentialInVehicleTripIntervals.addAll(safe(bundle.potentialInVehicleTripIntervals()));
}
return new TachographEsperDrivingDerivedProjectionBundle(
sortDrivingInterruptionIntervals(drivingInterruptionIntervals),
sortDrivingInterruptionIntervals(dailyWeeklyRestCandidateIntervals),
sortCoverageIntervals(dailyWeeklyRestCandidateCoverageIntervals),
sortCoverageIntervals(unclassifiedDailyWeeklyRestCandidateCoverageIntervals),
sortDrivingInterruptionIntervals(drivingInterruptionVehicleChangeIntervals),
sortVuCardAbsentIntervals(vuCardAbsentIntervals),
sortPotentialHomeIntervals(potentialHomeOvernightStayIntervals),
sortPotentialInVehicleIntervals(potentialInVehicleOvernightStayIntervals),
sortPotentialInVehicleTripIntervals(potentialInVehicleTripIntervals)
);
}
public static TachographEsperDrivingDerivedProjectionBundle emptyLegacyBundle() {
return new TachographEsperDrivingDerivedProjectionBundle(
List.of(),
List.of(),
List.of(),
List.of(),
List.of(),
List.of(),
List.of(),
List.of(),
List.of()
);
}
private static TachographEsperDrivingInterruptionIntervalEvent toLegacy(
DriverWorkingTimeDrivingInterruptionInterval value
) {
return new TachographEsperDrivingInterruptionIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey()
);
}
private static TachographEsperVuCardAbsentIntervalEvent toLegacy(DriverWorkingTimeVuCardAbsentInterval value) {
return new TachographEsperVuCardAbsentIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.previousUsageIntervalId(),
value.nextUsageIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey()
);
}
private static TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent toLegacyCoverage(
DriverWorkingTimeRestCoverageInterval value
) {
return new TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.cardAbsentDurationSeconds(),
value.cardAbsentCoveragePercent(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey(),
value.beginBoundaryOdometerKm(),
value.endBoundaryOdometerKm(),
value.beginGeoEventId(),
value.beginGeoEventDomain(),
value.beginGeoOccurredAt(),
value.beginLatitude(),
value.beginLongitude(),
value.beginGeoDistanceSeconds(),
value.beginGeoOdometerKm(),
value.endGeoEventId(),
value.endGeoEventDomain(),
value.endGeoOccurredAt(),
value.endLatitude(),
value.endLongitude(),
value.endGeoDistanceSeconds(),
value.endGeoOdometerKm(),
value.geoEvidenceMovementMeters(),
value.geoEvidenceMovementCategory(),
toLegacy(value.beginGeoEvent()),
toLegacy(value.endGeoEvent())
);
}
private static TachographEsperPotentialHomeOvernightStayIntervalEvent toLegacyPotentialHome(
DriverWorkingTimeRestCoverageInterval value
) {
return new TachographEsperPotentialHomeOvernightStayIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.cardAbsentDurationSeconds(),
value.cardAbsentCoveragePercent(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey(),
value.beginBoundaryOdometerKm(),
value.endBoundaryOdometerKm(),
value.beginGeoEventId(),
value.beginGeoEventDomain(),
value.beginGeoOccurredAt(),
value.beginLatitude(),
value.beginLongitude(),
value.beginGeoDistanceSeconds(),
value.beginGeoOdometerKm(),
value.endGeoEventId(),
value.endGeoEventDomain(),
value.endGeoOccurredAt(),
value.endLatitude(),
value.endLongitude(),
value.endGeoDistanceSeconds(),
value.endGeoOdometerKm(),
value.geoEvidenceMovementMeters(),
value.geoEvidenceMovementCategory(),
toLegacy(value.beginGeoEvent()),
toLegacy(value.endGeoEvent())
);
}
private static TachographEsperPotentialInVehicleOvernightStayIntervalEvent toLegacyPotentialInVehicle(
DriverWorkingTimeRestCoverageInterval value
) {
return new TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.cardAbsentDurationSeconds(),
value.cardAbsentCoveragePercent(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey(),
value.beginBoundaryOdometerKm(),
value.endBoundaryOdometerKm(),
value.beginGeoEventId(),
value.beginGeoEventDomain(),
value.beginGeoOccurredAt(),
value.beginLatitude(),
value.beginLongitude(),
value.beginGeoDistanceSeconds(),
value.beginGeoOdometerKm(),
value.endGeoEventId(),
value.endGeoEventDomain(),
value.endGeoOccurredAt(),
value.endLatitude(),
value.endLongitude(),
value.endGeoDistanceSeconds(),
value.endGeoOdometerKm(),
value.geoEvidenceMovementMeters(),
value.geoEvidenceMovementCategory(),
toLegacy(value.beginGeoEvent()),
toLegacy(value.endGeoEvent())
);
}
private static TachographEsperPotentialInVehicleTripIntervalEvent toLegacy(
DriverWorkingTimePotentialInVehicleTripInterval value
) {
return new TachographEsperPotentialInVehicleTripIntervalEvent(
value.sessionId(),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.registrationKey(),
value.vehicleKey(),
value.containedPotentialInVehicleOvernightStayIntervalCount(),
value.containedPotentialInVehicleOvernightStayDurationSeconds(),
value.containedCardAbsentDurationSeconds(),
value.firstPotentialInVehicleOvernightStayStartedAt(),
value.lastPotentialInVehicleOvernightStayEndedAt(),
value.firstPreviousDrivingSourceIntervalId(),
value.lastNextDrivingSourceIntervalId(),
safe(value.potentialInVehicleOvernightStayIntervals()).stream()
.map(TachographDriverWorkingTimeAdapter::toLegacyPotentialInVehicle)
.toList()
);
}
private static TachographEsperGeoEvidenceEvent toLegacy(DriverWorkingTimeGeoEvidenceEvent value) {
if (value == null) {
return null;
}
return new TachographEsperGeoEvidenceEvent(
value.eventId(),
value.eventDomain(),
value.occurredAt(),
value.latitude(),
value.longitude(),
value.distanceSeconds(),
value.odometerKm()
);
}
private static List<TachographEsperDrivingInterruptionIntervalEvent> sortDrivingInterruptionIntervals(
List<TachographEsperDrivingInterruptionIntervalEvent> intervals
) {
return safe(intervals).stream()
.sorted(Comparator.comparing(TachographEsperDrivingInterruptionIntervalEvent::startedAt)
.thenComparing(TachographEsperDrivingInterruptionIntervalEvent::endedAt))
.toList();
}
private static List<TachographEsperVuCardAbsentIntervalEvent> sortVuCardAbsentIntervals(
List<TachographEsperVuCardAbsentIntervalEvent> intervals
) {
return safe(intervals).stream()
.sorted(Comparator.comparing(TachographEsperVuCardAbsentIntervalEvent::startedAt)
.thenComparing(TachographEsperVuCardAbsentIntervalEvent::endedAt))
.toList();
}
private static List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> sortCoverageIntervals(
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> intervals
) {
return safe(intervals).stream()
.sorted(Comparator.comparing(TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent::startedAt)
.thenComparing(TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent::endedAt))
.toList();
}
private static List<TachographEsperPotentialHomeOvernightStayIntervalEvent> sortPotentialHomeIntervals(
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> intervals
) {
return safe(intervals).stream()
.sorted(Comparator.comparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::startedAt)
.thenComparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::endedAt))
.toList();
}
private static List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> sortPotentialInVehicleIntervals(
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> intervals
) {
return safe(intervals).stream()
.sorted(Comparator.comparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::startedAt)
.thenComparing(TachographEsperPotentialInVehicleOvernightStayIntervalEvent::endedAt))
.toList();
}
private static List<TachographEsperPotentialInVehicleTripIntervalEvent> sortPotentialInVehicleTripIntervals(
List<TachographEsperPotentialInVehicleTripIntervalEvent> intervals
) {
return safe(intervals).stream()
.sorted(Comparator.comparing(TachographEsperPotentialInVehicleTripIntervalEvent::startedAt)
.thenComparing(TachographEsperPotentialInVehicleTripIntervalEvent::endedAt))
.toList();
}
private static String firstSourceIntervalId(ResolvedActivityInterval interval) {
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().getFirst();
}
private static String lastSourceIntervalId(ResolvedActivityInterval interval) {
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().getLast();
}
private static String firstSourceIntervalId(ResolvedVehicleUsageInterval interval) {
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().getFirst();
}
private static String lastSourceIntervalId(ResolvedVehicleUsageInterval interval) {
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().getLast();
}
private static <T> List<T> safe(List<T> values) {
return values == null ? List.of() : values;
}
}

View File

@ -2,7 +2,9 @@ package at.procon.eventhub.tachographfilesession.service;
import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto;
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeDerivedProjectionEngine;
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeProcessingCore;
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeReusableProjectionBuilder;
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -22,19 +24,33 @@ public class TachographEsperProcessingCore {
this.delegate = delegate;
}
public TachographEsperProcessingCore(
DriverTimelineBuilder driverTimelineBuilder,
TachographDerivedProjectionCompatibilityFacade compatibilityFacade,
EventHubProperties properties
) {
this(new DriverWorkingTimeProcessingCore(new DriverWorkingTimeDerivedProjectionEngine(
compatibilityFacade.commonBuilder()
)));
}
public TachographEsperProcessingCore(
DriverTimelineBuilder driverTimelineBuilder,
DriverTimelineReusableProjectionBuilder reusableProjectionBuilder,
EventHubProperties properties
) {
this(new DriverWorkingTimeProcessingCore(driverTimelineBuilder, reusableProjectionBuilder, properties));
this(new DriverWorkingTimeProcessingCore(new DriverWorkingTimeDerivedProjectionEngine(
reusableProjectionBuilder.commonBuilder()
)));
}
public TachographEsperDriverProcessingResultDto process(TachographEsperProcessingInput input) {
return TachographEsperDriverProcessingResultDto.fromDriverWorkingTime(delegate.process(input));
return TachographEsperDriverProcessingResultDto.fromDriverWorkingTime(
delegate.process(TachographEsperProcessingInputAdapter.toDriverWorkingTimeInput(input))
);
}
public DriverWorkingTimeProcessingResultDto processDriverWorkingTime(TachographEsperProcessingInput input) {
return delegate.process(input);
return delegate.process(TachographEsperProcessingInputAdapter.toDriverWorkingTimeInput(input));
}
}

View File

@ -0,0 +1,24 @@
package at.procon.eventhub.tachographfilesession.service;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeProcessingInput;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
final class TachographEsperProcessingInputAdapter {
private TachographEsperProcessingInputAdapter() {
}
static DriverWorkingTimeProcessingInput toDriverWorkingTimeInput(TachographEsperProcessingInput input) {
ResolvedDriverTimeline timeline = input.timeline();
return TachographDriverWorkingTimeAdapter.toProcessingInput(
input.sessionId(),
input.driverKey(),
timeline,
input.requestedFrom(),
input.requestedTo(),
input.significantDrivingMinutes(),
input.minimumRestPeriodMinutes(),
input.notes()
);
}
}

View File

@ -0,0 +1,194 @@
package at.procon.eventhub.tachographfilesession.service;
import com.fasterxml.jackson.databind.JsonNode;
import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.processing.service.UnifiedEventTimelineReconstructor;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
import java.time.OffsetDateTime;
import java.util.ArrayList;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class TachographEventTimelineReconstructionHelper {
private final UnifiedEventTimelineReconstructor timelineReconstructor;
public TachographEventTimelineReconstructionHelper() {
this(new UnifiedEventTimelineReconstructor());
}
@Autowired
public TachographEventTimelineReconstructionHelper(
UnifiedEventTimelineReconstructor timelineReconstructor
) {
this.timelineReconstructor = timelineReconstructor == null
? new UnifiedEventTimelineReconstructor()
: timelineReconstructor;
}
public Map<String, List<EventHubEventDto>> groupEventsByDriverKey(List<EventHubEventDto> events) {
Map<String, List<EventHubEventDto>> grouped = new LinkedHashMap<>();
for (EventHubEventDto event : safe(events)) {
String driverKey = eventDriverKey(event);
if (driverKey == null || driverKey.isBlank()) {
continue;
}
grouped.computeIfAbsent(driverKey, ignored -> new ArrayList<>()).add(event);
}
return grouped;
}
public ResolvedDriverTimeline reconstructMergedTimelineFromEvents(
UUID fallbackSessionId,
String fallbackDriverKey,
List<EventHubEventDto> events
) {
ResolvedDriverTimeline reconstructed = timelineReconstructor.reconstruct(
fallbackSessionId == null ? new UUID(0L, 0L) : fallbackSessionId,
fallbackDriverKey,
safe(events)
);
return new ResolvedDriverTimeline(
reconstructed.sourceKind(),
reconstructed.loadedFrom(),
reconstructed.loadedTo(),
mergeVehicleUsageIntervals(reconstructed.vehicleUsageIntervals(), reconstructed.sourceKind()),
reconstructed.activityIntervals(),
reconstructed.supportEvents(),
reconstructed.warnings()
);
}
private String eventDriverKey(EventHubEventDto event) {
if (event == null) {
return null;
}
JsonNode payload = event.payload();
JsonNode raw = payload == null || payload.isNull() ? null : (payload.get("raw") == null ? payload : payload.get("raw"));
return firstNonBlank(text(raw, "driverKey"), driverKey(event));
}
private String driverKey(EventHubEventDto event) {
if (event.driverRef() == null) {
return null;
}
if (event.driverRef().driverCard() != null && event.driverRef().driverCard().hasValue()) {
return event.driverRef().driverCard().stableKey();
}
return event.driverRef().sourceEntityId();
}
private List<ResolvedVehicleUsageInterval> mergeVehicleUsageIntervals(
List<ResolvedVehicleUsageInterval> intervals,
String sourceKind
) {
List<ResolvedVehicleUsageInterval> sorted = safe(intervals).stream()
.sorted(Comparator.comparing(ResolvedVehicleUsageInterval::from)
.thenComparing(ResolvedVehicleUsageInterval::to, Comparator.nullsLast(Comparator.naturalOrder())))
.toList();
if (sorted.isEmpty()) {
return List.of();
}
List<ResolvedVehicleUsageInterval> result = new ArrayList<>();
ResolvedVehicleUsageInterval current = sorted.getFirst();
List<String> currentSources = new ArrayList<>(current.sourceIntervalIds());
for (int i = 1; i < sorted.size(); i++) {
ResolvedVehicleUsageInterval next = sorted.get(i);
if (canMergeVehicleUsage(current, next)) {
currentSources.addAll(next.sourceIntervalIds());
current = ResolvedVehicleUsageInterval.resolved(
current.sessionId(),
current.driverKey(),
current.intervalId() + "+" + next.intervalId(),
current.from(),
mergedTo(current.to(), next.to()),
current.odometerBeginKm(),
mergedOdometerEnd(current, next),
current.registrationKey(),
current.vehicleKey(),
sourceKind,
currentSources
);
} else {
result.add(current);
current = next;
currentSources = new ArrayList<>(current.sourceIntervalIds());
}
}
result.add(current);
return result;
}
private boolean canMergeVehicleUsage(ResolvedVehicleUsageInterval left, ResolvedVehicleUsageInterval right) {
return Objects.equals(left.registrationKey(), right.registrationKey())
&& Objects.equals(left.vehicleKey(), right.vehicleKey())
&& !right.from().isAfter(mergeBoundary(left.to()));
}
private Long mergedOdometerEnd(
ResolvedVehicleUsageInterval current,
ResolvedVehicleUsageInterval next
) {
if (current == null) {
return next == null ? null : next.odometerEndKm();
}
if (next == null) {
return current.odometerEndKm();
}
if (current.to() == null || next.to() == null) {
return next.odometerEndKm() != null ? next.odometerEndKm() : current.odometerEndKm();
}
if (next.to().isAfter(current.to()) || next.to().isEqual(current.to())) {
return next.odometerEndKm() != null ? next.odometerEndKm() : current.odometerEndKm();
}
return current.odometerEndKm() != null ? current.odometerEndKm() : next.odometerEndKm();
}
private OffsetDateTime mergedTo(OffsetDateTime left, OffsetDateTime right) {
if (left == null || right == null) {
return null;
}
return left.isAfter(right) ? left : right;
}
private OffsetDateTime mergeBoundary(OffsetDateTime endInclusive) {
return endInclusive == null ? OffsetDateTime.MAX : endInclusive.plusSeconds(1);
}
private String text(JsonNode node, String field) {
if (node == null || field == null) {
return null;
}
JsonNode value = node.get(field);
if (value == null || value.isNull()) {
return null;
}
String text = value.asText(null);
return text == null || text.isBlank() ? null : text;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value.trim();
}
}
return null;
}
private <T> List<T> safe(List<T> values) {
return values == null ? List.of() : values;
}
}

View File

@ -41,11 +41,26 @@ public class TachographFileSessionProcessingService {
private final TachographFileSessionRepository repository;
private final DriverTimelineBuilder driverTimelineBuilder;
private final DriverTimelineReusableProjectionBuilder reusableProjectionBuilder;
private final EventBackedDriverTimelineBuilder eventBackedDriverTimelineBuilder;
private final TachographEsperProcessingCore esperProcessingCore;
private final EventHubProperties properties;
public TachographFileSessionProcessingService(
TachographFileSessionRepository repository,
DriverTimelineBuilder driverTimelineBuilder,
TachographDerivedProjectionCompatibilityFacade compatibilityFacade,
EventBackedDriverTimelineBuilder eventBackedDriverTimelineBuilder,
EventHubProperties properties
) {
this(
repository,
driverTimelineBuilder,
eventBackedDriverTimelineBuilder,
properties,
new TachographEsperProcessingCore(driverTimelineBuilder, compatibilityFacade, properties)
);
}
public TachographFileSessionProcessingService(
TachographFileSessionRepository repository,
DriverTimelineBuilder driverTimelineBuilder,
@ -56,7 +71,6 @@ public class TachographFileSessionProcessingService {
this(
repository,
driverTimelineBuilder,
reusableProjectionBuilder,
eventBackedDriverTimelineBuilder,
properties,
new TachographEsperProcessingCore(driverTimelineBuilder, reusableProjectionBuilder, properties)
@ -67,14 +81,12 @@ public class TachographFileSessionProcessingService {
public TachographFileSessionProcessingService(
TachographFileSessionRepository repository,
DriverTimelineBuilder driverTimelineBuilder,
DriverTimelineReusableProjectionBuilder reusableProjectionBuilder,
EventBackedDriverTimelineBuilder eventBackedDriverTimelineBuilder,
EventHubProperties properties,
TachographEsperProcessingCore esperProcessingCore
) {
this.repository = repository;
this.driverTimelineBuilder = driverTimelineBuilder;
this.reusableProjectionBuilder = reusableProjectionBuilder;
this.eventBackedDriverTimelineBuilder = eventBackedDriverTimelineBuilder;
this.properties = properties;
this.esperProcessingCore = esperProcessingCore;

View File

@ -35,14 +35,14 @@ class TachographEsperProcessingCoreParityTest {
new EventDetailsFactory(new ObjectMapper())
);
RawSourceDriverTimelineEventBuilder rawSourceEventBuilder = new RawSourceDriverTimelineEventBuilder(intervalEventBuilder);
DriverTimelineReusableProjectionBuilder projectionBuilder = new DriverTimelineReusableProjectionBuilder(
TachographDerivedProjectionCompatibilityFacade projectionFacade = new TachographDerivedProjectionCompatibilityFacade(
driverTimelineBuilder,
rawSourceEventBuilder,
properties
);
TachographEsperProcessingCore core = new TachographEsperProcessingCore(
driverTimelineBuilder,
projectionBuilder,
projectionFacade,
properties
);

View File

@ -33,7 +33,7 @@ class TachographFileSessionProcessingServiceTest {
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
repository,
driverTimelineBuilder,
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
new TachographDerivedProjectionCompatibilityFacade(driverTimelineBuilder, new EventHubProperties()),
new EventBackedDriverTimelineBuilder(
new IntervalBackedDriverTimelineEventBuilder(
driverTimelineBuilder,
@ -119,7 +119,7 @@ class TachographFileSessionProcessingServiceTest {
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
repository,
driverTimelineBuilder,
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
new TachographDerivedProjectionCompatibilityFacade(driverTimelineBuilder, new EventHubProperties()),
new EventBackedDriverTimelineBuilder(
new IntervalBackedDriverTimelineEventBuilder(
driverTimelineBuilder,
@ -226,7 +226,7 @@ class TachographFileSessionProcessingServiceTest {
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
repository,
driverTimelineBuilder,
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
new TachographDerivedProjectionCompatibilityFacade(driverTimelineBuilder, new EventHubProperties()),
new EventBackedDriverTimelineBuilder(
new IntervalBackedDriverTimelineEventBuilder(
driverTimelineBuilder,
@ -315,7 +315,7 @@ class TachographFileSessionProcessingServiceTest {
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
repository,
driverTimelineBuilder,
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
new TachographDerivedProjectionCompatibilityFacade(driverTimelineBuilder, new EventHubProperties()),
new EventBackedDriverTimelineBuilder(
new IntervalBackedDriverTimelineEventBuilder(
driverTimelineBuilder,
@ -415,7 +415,7 @@ class TachographFileSessionProcessingServiceTest {
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
repository,
driverTimelineBuilder,
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
new TachographDerivedProjectionCompatibilityFacade(driverTimelineBuilder, new EventHubProperties()),
new EventBackedDriverTimelineBuilder(
new IntervalBackedDriverTimelineEventBuilder(
driverTimelineBuilder,
@ -496,7 +496,7 @@ class TachographFileSessionProcessingServiceTest {
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
repository,
driverTimelineBuilder,
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
new TachographDerivedProjectionCompatibilityFacade(driverTimelineBuilder, new EventHubProperties()),
new EventBackedDriverTimelineBuilder(
new IntervalBackedDriverTimelineEventBuilder(
driverTimelineBuilder,
@ -586,7 +586,7 @@ class TachographFileSessionProcessingServiceTest {
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
repository,
driverTimelineBuilder,
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
new TachographDerivedProjectionCompatibilityFacade(driverTimelineBuilder, new EventHubProperties()),
new EventBackedDriverTimelineBuilder(
new IntervalBackedDriverTimelineEventBuilder(
driverTimelineBuilder,
@ -660,7 +660,7 @@ class TachographFileSessionProcessingServiceTest {
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
repository,
driverTimelineBuilder,
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder, new EventHubProperties()),
new TachographDerivedProjectionCompatibilityFacade(driverTimelineBuilder, new EventHubProperties()),
new EventBackedDriverTimelineBuilder(
new IntervalBackedDriverTimelineEventBuilder(
driverTimelineBuilder,