Extract neutral working-time projection engine

This commit is contained in:
trifonovt 2026-05-26 18:17:49 +02:00
parent 4b5691d20f
commit cd090010c6
12 changed files with 996 additions and 262 deletions

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

@ -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,426 @@
package at.procon.eventhub.processing.driverworkingtime.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 at.procon.eventhub.tachographfilesession.service.DriverTimelineReusableProjectionBuilder;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import org.springframework.stereotype.Service;
@Service
public class DriverWorkingTimeDerivedProjectionEngine {
private final DriverTimelineReusableProjectionBuilder legacyProjectionBuilder;
public DriverWorkingTimeDerivedProjectionEngine(
DriverTimelineReusableProjectionBuilder legacyProjectionBuilder
) {
this.legacyProjectionBuilder = legacyProjectionBuilder;
}
public DriverWorkingTimeDerivedProjectionBundle build(DriverWorkingTimeProcessingInput input) {
Objects.requireNonNull(input, "input must not be null");
ResolvedDriverTimeline timeline = toResolvedTimeline(input);
TachographEsperDrivingDerivedProjectionBundle legacyBundle =
legacyProjectionBuilder.buildEsperDrivingDerivedProjectionBundle(
safeSessionId(input.sessionId()),
input.driverKey(),
timeline,
input.significantDrivingMinutes(),
input.minimumRestPeriodMinutes()
);
return new DriverWorkingTimeDerivedProjectionBundle(
legacyBundle.drivingInterruptionIntervals().stream().map(this::mapDrivingInterruption).toList(),
legacyBundle.dailyWeeklyRestCandidateIntervals().stream().map(this::mapDrivingInterruption).toList(),
legacyBundle.dailyWeeklyRestCandidateCoverageIntervals().stream().map(this::mapRestCoverage).toList(),
legacyBundle.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().stream().map(this::mapRestCoverage).toList(),
legacyBundle.drivingInterruptionVehicleChangeIntervals().stream().map(this::mapDrivingInterruption).toList(),
legacyBundle.vuCardAbsentIntervals().stream().map(this::mapVuCardAbsent).toList(),
legacyBundle.potentialHomeOvernightStayIntervals().stream().map(this::mapPotentialHome).toList(),
legacyBundle.potentialInVehicleOvernightStayIntervals().stream().map(this::mapPotentialInVehicle).toList(),
legacyBundle.potentialInVehicleTripIntervals().stream().map(this::mapPotentialTrip).toList()
);
}
private ResolvedDriverTimeline toResolvedTimeline(DriverWorkingTimeProcessingInput input) {
List<ResolvedActivityInterval> activityIntervals = input.activityIntervals().stream()
.map(this::toResolvedActivityInterval)
.filter(Objects::nonNull)
.toList();
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals = input.vehicleUsageIntervals().stream()
.map(this::toResolvedVehicleUsageInterval)
.filter(Objects::nonNull)
.toList();
List<ExtractedSupportEvent> supportEvents = input.supportEvidenceEvents().stream()
.map(this::toExtractedSupportEvent)
.filter(Objects::nonNull)
.toList();
OffsetDateTime loadedFrom = input.loadedFrom() == null
? earliest(activityIntervals, vehicleUsageIntervals, supportEvents)
: utc(input.loadedFrom());
OffsetDateTime loadedTo = input.loadedTo() == null
? latest(activityIntervals, vehicleUsageIntervals, supportEvents)
: utc(input.loadedTo());
return new ResolvedDriverTimeline(
input.sourceKind(),
loadedFrom,
loadedTo,
vehicleUsageIntervals,
activityIntervals,
supportEvents,
List.of()
);
}
private ResolvedActivityInterval toResolvedActivityInterval(DriverWorkingTimeActivityInterval interval) {
if (interval == null || interval.startedAt() == null || interval.endedAt() == null) {
return null;
}
return new ResolvedActivityInterval(
interval.intervalId(),
utc(interval.startedAt()),
utc(interval.endedAt()),
interval.durationSeconds(),
interval.activityType(),
interval.cardSlot(),
interval.cardStatus(),
interval.drivingStatus(),
interval.registrationKey(),
interval.vehicleKey(),
interval.sourceKind(),
interval.sourceIntervalIds(),
interval.synthetic(),
interval.clippedToRequestedPeriod(),
interval.level()
);
}
private ResolvedVehicleUsageInterval toResolvedVehicleUsageInterval(DriverWorkingTimeVehicleUsageInterval interval) {
if (interval == null || interval.startedAt() == null) {
return null;
}
return new ResolvedVehicleUsageInterval(
safeSessionId(interval.sessionId()),
interval.driverKey(),
interval.intervalId(),
utc(interval.startedAt()),
utc(interval.endedAt()),
interval.durationSeconds(),
interval.odometerBeginKm(),
interval.odometerEndKm(),
interval.registrationKey(),
interval.vehicleKey(),
interval.sourceKind(),
interval.sourceIntervalIds()
);
}
private ExtractedSupportEvent toExtractedSupportEvent(RuntimeSupportEvidenceEvent supportEvent) {
if (supportEvent == null || supportEvent.occurredAt() == null) {
return null;
}
Object rawRecordPath = supportEvent.rawAttributes().get("rawRecordPath");
return new ExtractedSupportEvent(
supportEvent.eventId(),
supportEvent.driverKey(),
utc(supportEvent.occurredAt()),
supportEvent.eventDomain(),
supportEvent.eventType(),
supportEvent.lifecycle(),
null,
supportEvent.registrationKey(),
supportEvent.vehicleKey(),
supportEvent.countryCode(),
supportEvent.regionCode(),
supportEvent.countryFrom(),
supportEvent.countryTo(),
supportEvent.operation(),
supportEvent.latitude(),
supportEvent.longitude(),
null,
supportEvent.odometerKm(),
null,
supportEvent.speedKmh(),
supportEvent.maxSpeedKmh(),
rawRecordPath == null ? null : rawRecordPath.toString()
);
}
private DriverWorkingTimeDrivingInterruptionInterval mapDrivingInterruption(
TachographEsperDrivingInterruptionIntervalEvent value
) {
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 DriverWorkingTimeVuCardAbsentInterval mapVuCardAbsent(TachographEsperVuCardAbsentIntervalEvent value) {
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 DriverWorkingTimeRestCoverageInterval mapRestCoverage(
TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent value
) {
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(),
mapGeoEvidence(value.beginGeoEvent()),
mapGeoEvidence(value.endGeoEvent())
);
}
private DriverWorkingTimeRestCoverageInterval mapPotentialHome(
TachographEsperPotentialHomeOvernightStayIntervalEvent value
) {
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(),
mapGeoEvidence(value.beginGeoEvent()),
mapGeoEvidence(value.endGeoEvent())
);
}
private DriverWorkingTimeRestCoverageInterval mapPotentialInVehicle(
TachographEsperPotentialInVehicleOvernightStayIntervalEvent value
) {
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(),
mapGeoEvidence(value.beginGeoEvent()),
mapGeoEvidence(value.endGeoEvent())
);
}
private DriverWorkingTimePotentialInVehicleTripInterval mapPotentialTrip(
TachographEsperPotentialInVehicleTripIntervalEvent value
) {
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(),
value.potentialInVehicleOvernightStayIntervals().stream().map(this::mapPotentialInVehicle).toList()
);
}
private DriverWorkingTimeGeoEvidenceEvent mapGeoEvidence(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 OffsetDateTime earliest(
List<ResolvedActivityInterval> activityIntervals,
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
List<ExtractedSupportEvent> supportEvents
) {
OffsetDateTime earliest = null;
for (ResolvedActivityInterval interval : activityIntervals) {
earliest = min(earliest, interval.from());
}
for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) {
earliest = min(earliest, interval.from());
}
for (ExtractedSupportEvent event : supportEvents) {
earliest = min(earliest, event.occurredAt());
}
return earliest;
}
private OffsetDateTime latest(
List<ResolvedActivityInterval> activityIntervals,
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
List<ExtractedSupportEvent> supportEvents
) {
OffsetDateTime latest = null;
for (ResolvedActivityInterval interval : activityIntervals) {
latest = max(latest, interval.to());
}
for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) {
latest = max(latest, interval.to());
}
for (ExtractedSupportEvent event : supportEvents) {
latest = max(latest, event.occurredAt());
}
return latest;
}
private UUID safeSessionId(UUID sessionId) {
return sessionId == null ? new UUID(0L, 0L) : sessionId;
}
private OffsetDateTime utc(OffsetDateTime value) {
return value == null ? null : value.withOffsetSameInstant(java.time.ZoneOffset.UTC);
}
private OffsetDateTime max(OffsetDateTime left, OffsetDateTime right) {
if (left == null) {
return right;
}
if (right == null) {
return left;
}
return left.isAfter(right) ? left : right;
}
private OffsetDateTime min(OffsetDateTime left, OffsetDateTime right) {
if (left == null) {
return right;
}
if (right == null) {
return left;
}
return left.isBefore(right) ? left : right;
}
}

View File

@ -1,21 +1,22 @@
package at.procon.eventhub.processing.driverworkingtime.service; package at.procon.eventhub.processing.driverworkingtime.service;
import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto; import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeActivityInterval; 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.DriverWorkingTimeProcessingInput;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeRestCoverageInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval; 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.processing.eventprocessing.support.RuntimeSupportEvidenceEvent;
import at.procon.eventhub.tachographfilesession.model.*; import at.procon.eventhub.tachographfilesession.model.*;
import at.procon.eventhub.tachographfilesession.service.DriverTimelineBuilder;
import at.procon.eventhub.tachographfilesession.service.DriverTimelineReusableProjectionBuilder;
import at.procon.eventhub.tachographfilesession.service.TachographEsperProcessingInput;
import java.time.Duration; import java.time.Duration;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -31,76 +32,59 @@ import org.springframework.stereotype.Service;
@Service @Service
public class DriverWorkingTimeProcessingCore { public class DriverWorkingTimeProcessingCore {
private final DriverTimelineBuilder driverTimelineBuilder; private final DriverWorkingTimeDerivedProjectionEngine derivedProjectionEngine;
private final DriverTimelineReusableProjectionBuilder reusableProjectionBuilder;
private final EventHubProperties properties;
public DriverWorkingTimeProcessingCore( public DriverWorkingTimeProcessingCore(
DriverTimelineBuilder driverTimelineBuilder, DriverWorkingTimeDerivedProjectionEngine derivedProjectionEngine
DriverTimelineReusableProjectionBuilder reusableProjectionBuilder,
EventHubProperties properties
) { ) {
this.driverTimelineBuilder = driverTimelineBuilder; this.derivedProjectionEngine = derivedProjectionEngine;
this.reusableProjectionBuilder = reusableProjectionBuilder;
this.properties = properties;
}
public DriverWorkingTimeProcessingResultDto process(TachographEsperProcessingInput input) {
Objects.requireNonNull(input, "input must not be null");
return process(toSourceNeutralInput(input));
} }
public DriverWorkingTimeProcessingResultDto process(DriverWorkingTimeProcessingInput input) { public DriverWorkingTimeProcessingResultDto process(DriverWorkingTimeProcessingInput input) {
Objects.requireNonNull(input, "input must not be null"); Objects.requireNonNull(input, "input must not be null");
ResolvedDriverTimeline timeline = toResolvedTimeline(input); OffsetDateTime loadedFrom = input.loadedFrom();
OffsetDateTime loadedTo = input.loadedTo();
String driverKey = input.driverKey(); String driverKey = input.driverKey();
OffsetDateTime requestedFrom = input.requestedFrom() == null ? timeline.loadedFrom() : utc(input.requestedFrom()); OffsetDateTime requestedFrom = input.requestedFrom() == null ? loadedFrom : utc(input.requestedFrom());
OffsetDateTime requestedTo = input.requestedTo() == null ? timeline.loadedTo() : utc(input.requestedTo()); OffsetDateTime requestedTo = input.requestedTo() == null ? loadedTo : utc(input.requestedTo());
if (requestedFrom != null && requestedTo != null && requestedTo.isBefore(requestedFrom)) { if (requestedFrom != null && requestedTo != null && requestedTo.isBefore(requestedFrom)) {
throw new IllegalArgumentException("occurredTo must not be before occurredFrom."); throw new IllegalArgumentException("occurredTo must not be before occurredFrom.");
} }
List<TachographEsperActivityIntervalEvent> activityIntervals = clipEsperActivityIntervalEvents( List<TachographEsperActivityIntervalEvent> activityIntervals = clipEsperActivityIntervalEvents(
driverTimelineBuilder.buildEsperActivityIntervalEvents(input.sessionId(), driverKey, timeline), buildEsperActivityIntervalEvents(input.activityIntervals()),
requestedFrom, requestedFrom,
requestedTo requestedTo
); );
List<TachographEsperActivityIntervalEvent> drivingIntervals = clipEsperActivityIntervalEvents( List<TachographEsperActivityIntervalEvent> drivingIntervals = clipEsperActivityIntervalEvents(
driverTimelineBuilder.buildEsperDrivingIntervalEvents(input.sessionId(), driverKey, timeline), buildEsperDrivingIntervalEvents(input.activityIntervals()),
requestedFrom, requestedFrom,
requestedTo requestedTo
); );
TachographEsperDrivingDerivedProjectionBundle derivedProjectionBundle = DriverWorkingTimeDerivedProjectionBundle derivedProjectionBundle = derivedProjectionEngine.build(input);
reusableProjectionBuilder.buildEsperDrivingDerivedProjectionBundle(
safeSessionId(input.sessionId()),
driverKey,
timeline,
input.significantDrivingMinutes(),
input.minimumRestPeriodMinutes()
);
List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionIntervals = List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionIntervals =
derivedProjectionBundle.drivingInterruptionIntervals(); derivedProjectionBundle.drivingInterruptionIntervals().stream().map(this::toLegacy).toList();
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals = List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals =
clipEsperDrivingInterruptionIntervalEvents(rawDrivingInterruptionIntervals, requestedFrom, requestedTo); clipEsperDrivingInterruptionIntervalEvents(rawDrivingInterruptionIntervals, requestedFrom, requestedTo);
List<TachographEsperDrivingInterruptionIntervalEvent> rawDailyWeeklyRestCandidateIntervals = List<TachographEsperDrivingInterruptionIntervalEvent> rawDailyWeeklyRestCandidateIntervals =
derivedProjectionBundle.dailyWeeklyRestCandidateIntervals(); derivedProjectionBundle.dailyWeeklyRestCandidateIntervals().stream().map(this::toLegacy).toList();
List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals = List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals =
clipEsperDrivingInterruptionIntervalEvents(rawDailyWeeklyRestCandidateIntervals, requestedFrom, requestedTo); clipEsperDrivingInterruptionIntervalEvents(rawDailyWeeklyRestCandidateIntervals, requestedFrom, requestedTo);
List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionVehicleChangeIntervals = List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionVehicleChangeIntervals =
derivedProjectionBundle.drivingInterruptionVehicleChangeIntervals(); derivedProjectionBundle.drivingInterruptionVehicleChangeIntervals().stream().map(this::toLegacy).toList();
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals = List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals =
clipEsperDrivingInterruptionIntervalEvents(rawDrivingInterruptionVehicleChangeIntervals, requestedFrom, requestedTo); clipEsperDrivingInterruptionIntervalEvents(rawDrivingInterruptionVehicleChangeIntervals, requestedFrom, requestedTo);
List<TachographEsperVehicleUsageIntervalEvent> rawVehicleUsageIntervals = List<TachographEsperVehicleUsageIntervalEvent> rawVehicleUsageIntervals =
driverTimelineBuilder.buildEsperVehicleUsageIntervalEvents(timeline); buildEsperVehicleUsageIntervalEvents(input.vehicleUsageIntervals());
List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals = List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals =
derivedProjectionBundle.vuCardAbsentIntervals(); derivedProjectionBundle.vuCardAbsentIntervals().stream().map(this::toLegacy).toList();
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals = List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals =
clipEsperPotentialHomeOvernightStayIntervalEvents( clipEsperPotentialHomeOvernightStayIntervalEvents(
derivedProjectionBundle.potentialHomeOvernightStayIntervals(), derivedProjectionBundle.potentialHomeOvernightStayIntervals().stream().map(this::toLegacyPotentialHome).toList(),
rawVuCardAbsentIntervals, rawVuCardAbsentIntervals,
rawVehicleUsageIntervals, rawVehicleUsageIntervals,
requestedFrom, requestedFrom,
@ -108,7 +92,7 @@ public class DriverWorkingTimeProcessingCore {
); );
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> dailyWeeklyRestCandidateCoverageIntervals = List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> dailyWeeklyRestCandidateCoverageIntervals =
clipEsperDailyWeeklyRestCandidateCoverageIntervalEvents( clipEsperDailyWeeklyRestCandidateCoverageIntervalEvents(
derivedProjectionBundle.dailyWeeklyRestCandidateCoverageIntervals(), derivedProjectionBundle.dailyWeeklyRestCandidateCoverageIntervals().stream().map(this::toLegacyCoverage).toList(),
rawVuCardAbsentIntervals, rawVuCardAbsentIntervals,
rawVehicleUsageIntervals, rawVehicleUsageIntervals,
requestedFrom, requestedFrom,
@ -116,7 +100,7 @@ public class DriverWorkingTimeProcessingCore {
); );
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> unclassifiedDailyWeeklyRestCandidateCoverageIntervals = List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> unclassifiedDailyWeeklyRestCandidateCoverageIntervals =
clipEsperDailyWeeklyRestCandidateCoverageIntervalEvents( clipEsperDailyWeeklyRestCandidateCoverageIntervalEvents(
derivedProjectionBundle.unclassifiedDailyWeeklyRestCandidateCoverageIntervals(), derivedProjectionBundle.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().stream().map(this::toLegacyCoverage).toList(),
rawVuCardAbsentIntervals, rawVuCardAbsentIntervals,
rawVehicleUsageIntervals, rawVehicleUsageIntervals,
requestedFrom, requestedFrom,
@ -124,7 +108,7 @@ public class DriverWorkingTimeProcessingCore {
); );
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals = List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals =
clipEsperPotentialInVehicleOvernightStayIntervalEvents( clipEsperPotentialInVehicleOvernightStayIntervalEvents(
derivedProjectionBundle.potentialInVehicleOvernightStayIntervals(), derivedProjectionBundle.potentialInVehicleOvernightStayIntervals().stream().map(this::toLegacyPotentialInVehicle).toList(),
rawVuCardAbsentIntervals, rawVuCardAbsentIntervals,
rawVehicleUsageIntervals, rawVehicleUsageIntervals,
requestedFrom, requestedFrom,
@ -132,7 +116,7 @@ public class DriverWorkingTimeProcessingCore {
); );
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals = List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals =
clipEsperPotentialInVehicleTripIntervalEvents( clipEsperPotentialInVehicleTripIntervalEvents(
derivedProjectionBundle.potentialInVehicleTripIntervals(), derivedProjectionBundle.potentialInVehicleTripIntervals().stream().map(this::toLegacy).toList(),
potentialInVehicleOvernightStayIntervals, potentialInVehicleOvernightStayIntervals,
requestedFrom, requestedFrom,
requestedTo requestedTo
@ -148,7 +132,7 @@ public class DriverWorkingTimeProcessingCore {
requestedTo requestedTo
); );
List<TachographEsperSupportGeoEvent> supportGeoEvents = clipEsperSupportGeoEvents( List<TachographEsperSupportGeoEvent> supportGeoEvents = clipEsperSupportGeoEvents(
timeline.supportEvents(), buildSupportGeoEvents(input.supportEvidenceEvents()),
driverKey, driverKey,
requestedFrom, requestedFrom,
requestedTo requestedTo
@ -157,9 +141,9 @@ public class DriverWorkingTimeProcessingCore {
return new DriverWorkingTimeProcessingResultDto( return new DriverWorkingTimeProcessingResultDto(
input.sessionId(), input.sessionId(),
driverKey, driverKey,
timeline.sourceKind(), input.sourceKind(),
timeline.loadedFrom(), loadedFrom,
timeline.loadedTo(), loadedTo,
requestedFrom, requestedFrom,
requestedTo, requestedTo,
activityIntervals.size(), activityIntervals.size(),
@ -192,207 +176,297 @@ public class DriverWorkingTimeProcessingCore {
); );
} }
public DriverWorkingTimeProcessingResultDto processDriverWorkingTime(TachographEsperProcessingInput input) { public DriverWorkingTimeProcessingResultDto processDriverWorkingTime(DriverWorkingTimeProcessingInput input) {
return process(input); return process(input);
} }
private DriverWorkingTimeProcessingInput toSourceNeutralInput(TachographEsperProcessingInput input) { private List<TachographEsperActivityIntervalEvent> buildEsperActivityIntervalEvents(
ResolvedDriverTimeline timeline = Objects.requireNonNull(input.timeline(), "timeline must not be null"); List<DriverWorkingTimeActivityInterval> intervals
String driverKey = input.driverKey();
return new DriverWorkingTimeProcessingInput(
input.sessionId(),
driverKey,
timeline.sourceKind(),
timeline.loadedFrom(),
timeline.loadedTo(),
input.requestedFrom(),
input.requestedTo(),
input.significantDrivingMinutes(),
input.minimumRestPeriodMinutes(),
safeList(timeline.activityIntervals()).stream()
.map(interval -> DriverWorkingTimeActivityInterval.fromResolved(input.sessionId(), driverKey, interval))
.filter(Objects::nonNull)
.toList(),
safeList(timeline.vehicleUsageIntervals()).stream()
.map(DriverWorkingTimeVehicleUsageInterval::fromResolved)
.filter(Objects::nonNull)
.toList(),
safeList(timeline.supportEvents()).stream()
.map(this::toSupportEvidenceEvent)
.filter(Objects::nonNull)
.toList(),
input.notes()
);
}
private ResolvedDriverTimeline toResolvedTimeline(DriverWorkingTimeProcessingInput input) {
List<ResolvedActivityInterval> activityIntervals = input.activityIntervals().stream()
.map(this::toResolvedActivityInterval)
.filter(Objects::nonNull)
.toList();
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals = input.vehicleUsageIntervals().stream()
.map(this::toResolvedVehicleUsageInterval)
.filter(Objects::nonNull)
.toList();
List<ExtractedSupportEvent> supportEvents = input.supportEvidenceEvents().stream()
.map(this::toExtractedSupportEvent)
.filter(Objects::nonNull)
.toList();
OffsetDateTime loadedFrom = input.loadedFrom() == null
? earliest(activityIntervals, vehicleUsageIntervals, supportEvents)
: utc(input.loadedFrom());
OffsetDateTime loadedTo = input.loadedTo() == null
? latest(activityIntervals, vehicleUsageIntervals, supportEvents)
: utc(input.loadedTo());
return new ResolvedDriverTimeline(
input.sourceKind(),
loadedFrom,
loadedTo,
vehicleUsageIntervals,
activityIntervals,
supportEvents,
List.of()
);
}
private ResolvedActivityInterval toResolvedActivityInterval(DriverWorkingTimeActivityInterval interval) {
if (interval == null || interval.startedAt() == null || interval.endedAt() == null) {
return null;
}
return new ResolvedActivityInterval(
interval.intervalId(),
utc(interval.startedAt()),
utc(interval.endedAt()),
interval.durationSeconds(),
interval.activityType(),
interval.cardSlot(),
interval.cardStatus(),
interval.drivingStatus(),
interval.registrationKey(),
interval.vehicleKey(),
interval.sourceKind(),
interval.sourceIntervalIds(),
interval.synthetic(),
interval.clippedToRequestedPeriod(),
interval.level()
);
}
private ResolvedVehicleUsageInterval toResolvedVehicleUsageInterval(DriverWorkingTimeVehicleUsageInterval interval) {
if (interval == null || interval.startedAt() == null) {
return null;
}
return new ResolvedVehicleUsageInterval(
safeSessionId(interval.sessionId()),
interval.driverKey(),
interval.intervalId(),
utc(interval.startedAt()),
utc(interval.endedAt()),
interval.durationSeconds(),
interval.odometerBeginKm(),
interval.odometerEndKm(),
interval.registrationKey(),
interval.vehicleKey(),
interval.sourceKind(),
interval.sourceIntervalIds()
);
}
private 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(),
utc(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(),
Map.of("rawRecordPath", supportEvent.rawRecordPath())
);
}
private ExtractedSupportEvent toExtractedSupportEvent(RuntimeSupportEvidenceEvent supportEvent) {
if (supportEvent == null || supportEvent.occurredAt() == null) {
return null;
}
Object rawRecordPath = supportEvent.rawAttributes().get("rawRecordPath");
return new ExtractedSupportEvent(
supportEvent.eventId(),
supportEvent.driverKey(),
utc(supportEvent.occurredAt()),
supportEvent.eventDomain(),
supportEvent.eventType(),
supportEvent.lifecycle(),
null,
supportEvent.registrationKey(),
supportEvent.vehicleKey(),
supportEvent.countryCode(),
supportEvent.regionCode(),
supportEvent.countryFrom(),
supportEvent.countryTo(),
supportEvent.operation(),
supportEvent.latitude(),
supportEvent.longitude(),
null,
supportEvent.odometerKm(),
null,
supportEvent.speedKmh(),
supportEvent.maxSpeedKmh(),
rawRecordPath == null ? null : rawRecordPath.toString()
);
}
private OffsetDateTime earliest(
List<ResolvedActivityInterval> activityIntervals,
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
List<ExtractedSupportEvent> supportEvents
) { ) {
OffsetDateTime earliest = null; return safeList(intervals).stream()
for (ResolvedActivityInterval interval : activityIntervals) { .filter(interval -> interval.startedAt() != null && interval.endedAt() != null)
earliest = min(earliest, interval.from()); .map(interval -> new TachographEsperActivityIntervalEvent(
} safeSessionId(interval.sessionId()),
for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) { interval.driverKey(),
earliest = min(earliest, interval.from()); interval.intervalId(),
} interval.activityType(),
for (ExtractedSupportEvent event : supportEvents) { interval.cardSlot(),
earliest = min(earliest, event.occurredAt()); interval.cardStatus(),
} interval.drivingStatus(),
return earliest; interval.registrationKey(),
interval.vehicleKey(),
interval.sourceKind(),
utc(interval.startedAt()),
utc(interval.endedAt()),
interval.durationSeconds(),
interval.sourceIntervalIds(),
interval.synthetic(),
interval.clippedToRequestedPeriod(),
interval.level()
))
.sorted(Comparator.comparing(TachographEsperActivityIntervalEvent::startedAt)
.thenComparing(TachographEsperActivityIntervalEvent::endedAt))
.toList();
} }
private OffsetDateTime latest( private List<TachographEsperActivityIntervalEvent> buildEsperDrivingIntervalEvents(
List<ResolvedActivityInterval> activityIntervals, List<DriverWorkingTimeActivityInterval> intervals
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
List<ExtractedSupportEvent> supportEvents
) { ) {
OffsetDateTime latest = null; return buildEsperActivityIntervalEvents(intervals).stream()
for (ResolvedActivityInterval interval : activityIntervals) { .filter(interval -> Objects.equals("DRIVE", interval.activityType()))
latest = max(latest, interval.to()); .toList();
}
private List<TachographEsperVehicleUsageIntervalEvent> buildEsperVehicleUsageIntervalEvents(
List<DriverWorkingTimeVehicleUsageInterval> intervals
) {
return safeList(intervals).stream()
.filter(interval -> interval.startedAt() != null)
.map(interval -> new TachographEsperVehicleUsageIntervalEvent(
safeSessionId(interval.sessionId()),
interval.driverKey(),
interval.intervalId(),
utc(interval.startedAt()),
utc(interval.endedAt()),
interval.durationSeconds(),
interval.odometerBeginKm(),
interval.odometerEndKm(),
interval.registrationKey(),
interval.vehicleKey(),
interval.sourceKind(),
interval.sourceIntervalIds()
))
.sorted(Comparator.comparing(TachographEsperVehicleUsageIntervalEvent::startedAt)
.thenComparing(TachographEsperVehicleUsageIntervalEvent::endedAt, Comparator.nullsLast(Comparator.naturalOrder())))
.toList();
}
private List<TachographEsperSupportGeoEvent> buildSupportGeoEvents(
List<RuntimeSupportEvidenceEvent> supportEvents
) {
return safeList(supportEvents).stream()
.filter(event -> event.occurredAt() != null)
.map(event -> new TachographEsperSupportGeoEvent(
event.eventId(),
event.driverKey(),
utc(event.occurredAt()),
event.eventDomain(),
event.eventType(),
event.lifecycle(),
event.registrationKey(),
event.vehicleKey(),
event.countryCode(),
event.regionCode(),
event.countryFrom(),
event.countryTo(),
event.operation(),
event.latitude(),
event.longitude(),
event.odometerKm(),
rawRecordPath(event)
))
.sorted(Comparator.comparing(TachographEsperSupportGeoEvent::occurredAt)
.thenComparing(TachographEsperSupportGeoEvent::eventId, Comparator.nullsLast(String::compareTo)))
.toList();
}
private String rawRecordPath(RuntimeSupportEvidenceEvent event) {
if (event == null) {
return null;
} }
for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) { Object value = event.rawAttributes().get("rawRecordPath");
latest = max(latest, interval.to()); return value == null ? null : value.toString();
}
private TachographEsperDrivingInterruptionIntervalEvent toLegacy(DriverWorkingTimeDrivingInterruptionInterval value) {
return new TachographEsperDrivingInterruptionIntervalEvent(
safeSessionId(value.sessionId()),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.previousDrivingSourceIntervalId(),
value.nextDrivingSourceIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey()
);
}
private TachographEsperVuCardAbsentIntervalEvent toLegacy(DriverWorkingTimeVuCardAbsentInterval value) {
return new TachographEsperVuCardAbsentIntervalEvent(
safeSessionId(value.sessionId()),
value.driverKey(),
value.startedAt(),
value.endedAt(),
value.durationSeconds(),
value.previousUsageIntervalId(),
value.nextUsageIntervalId(),
value.previousRegistrationKey(),
value.nextRegistrationKey(),
value.previousVehicleKey(),
value.nextVehicleKey()
);
}
private TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent toLegacyCoverage(
DriverWorkingTimeRestCoverageInterval value
) {
return new TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent(
safeSessionId(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 TachographEsperPotentialHomeOvernightStayIntervalEvent toLegacyPotentialHome(
DriverWorkingTimeRestCoverageInterval value
) {
return new TachographEsperPotentialHomeOvernightStayIntervalEvent(
safeSessionId(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 TachographEsperPotentialInVehicleOvernightStayIntervalEvent toLegacyPotentialInVehicle(
DriverWorkingTimeRestCoverageInterval value
) {
return new TachographEsperPotentialInVehicleOvernightStayIntervalEvent(
safeSessionId(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 TachographEsperPotentialInVehicleTripIntervalEvent toLegacy(
DriverWorkingTimePotentialInVehicleTripInterval value
) {
return new TachographEsperPotentialInVehicleTripIntervalEvent(
safeSessionId(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(),
value.potentialInVehicleOvernightStayIntervals().stream()
.map(this::toLegacyPotentialInVehicle)
.toList()
);
}
private TachographEsperGeoEvidenceEvent toLegacy(DriverWorkingTimeGeoEvidenceEvent value) {
if (value == null) {
return null;
} }
for (ExtractedSupportEvent event : supportEvents) { return new TachographEsperGeoEvidenceEvent(
latest = max(latest, event.occurredAt()); value.eventId(),
} value.eventDomain(),
return latest; value.occurredAt(),
value.latitude(),
value.longitude(),
value.distanceSeconds(),
value.odometerKm()
);
} }
private UUID safeSessionId(UUID sessionId) { private UUID safeSessionId(UUID sessionId) {
@ -498,7 +572,7 @@ public class DriverWorkingTimeProcessingCore {
} }
private List<TachographEsperSupportGeoEvent> clipEsperSupportGeoEvents( private List<TachographEsperSupportGeoEvent> clipEsperSupportGeoEvents(
List<ExtractedSupportEvent> supportEvents, List<TachographEsperSupportGeoEvent> supportEvents,
String driverKey, String driverKey,
OffsetDateTime requestedFrom, OffsetDateTime requestedFrom,
OffsetDateTime requestedTo OffsetDateTime requestedTo
@ -511,25 +585,6 @@ public class DriverWorkingTimeProcessingCore {
.filter(event -> event.occurredAt() != null) .filter(event -> event.occurredAt() != null)
.filter(event -> event.latitude() != null && event.longitude() != null) .filter(event -> event.latitude() != null && event.longitude() != null)
.filter(event -> !event.occurredAt().isBefore(requestedFrom) && !event.occurredAt().isAfter(requestedTo)) .filter(event -> !event.occurredAt().isBefore(requestedFrom) && !event.occurredAt().isAfter(requestedTo))
.map(event -> new TachographEsperSupportGeoEvent(
event.eventId(),
event.driverKey(),
event.occurredAt(),
event.eventDomain(),
event.eventType(),
event.eventLifecycle(),
event.registrationKey(),
event.vehicleKey(),
event.country(),
event.region(),
event.countryFrom(),
event.countryTo(),
event.operation(),
event.latitude(),
event.longitude(),
event.odometerKm(),
event.rawRecordPath()
))
.sorted(Comparator.comparing(TachographEsperSupportGeoEvent::occurredAt) .sorted(Comparator.comparing(TachographEsperSupportGeoEvent::occurredAt)
.thenComparing(TachographEsperSupportGeoEvent::eventDomain, Comparator.nullsLast(String::compareTo)) .thenComparing(TachographEsperSupportGeoEvent::eventDomain, Comparator.nullsLast(String::compareTo))
.thenComparing(TachographEsperSupportGeoEvent::eventId, Comparator.nullsLast(String::compareTo))) .thenComparing(TachographEsperSupportGeoEvent::eventId, Comparator.nullsLast(String::compareTo)))

View File

@ -9,6 +9,7 @@ import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeActivityInterval; import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeActivityInterval;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeProcessingInput; import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeProcessingInput;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval; import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval;
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeDerivedProjectionEngine;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceNormalizationResult; import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceNormalizationResult;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceEvent; import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceEvent;
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceNormalizer; import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceNormalizer;
@ -66,7 +67,7 @@ public class UnifiedRuntimeDerivedProjectionService {
driverTimelineBuilder, driverTimelineBuilder,
reusableProjectionBuilder, reusableProjectionBuilder,
properties, properties,
new DriverWorkingTimeProcessingCore(driverTimelineBuilder, reusableProjectionBuilder, properties), new DriverWorkingTimeProcessingCore(new DriverWorkingTimeDerivedProjectionEngine(reusableProjectionBuilder)),
supportEvidenceNormalizer supportEvidenceNormalizer
); );
} }

View File

@ -2,6 +2,7 @@ package at.procon.eventhub.tachographfilesession.service;
import at.procon.eventhub.config.EventHubProperties; import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto; 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.DriverWorkingTimeProcessingCore;
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto; import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -27,14 +28,16 @@ public class TachographEsperProcessingCore {
DriverTimelineReusableProjectionBuilder reusableProjectionBuilder, DriverTimelineReusableProjectionBuilder reusableProjectionBuilder,
EventHubProperties properties EventHubProperties properties
) { ) {
this(new DriverWorkingTimeProcessingCore(driverTimelineBuilder, reusableProjectionBuilder, properties)); this(new DriverWorkingTimeProcessingCore(new DriverWorkingTimeDerivedProjectionEngine(reusableProjectionBuilder)));
} }
public TachographEsperDriverProcessingResultDto process(TachographEsperProcessingInput input) { public TachographEsperDriverProcessingResultDto process(TachographEsperProcessingInput input) {
return TachographEsperDriverProcessingResultDto.fromDriverWorkingTime(delegate.process(input)); return TachographEsperDriverProcessingResultDto.fromDriverWorkingTime(
delegate.process(TachographEsperProcessingInputAdapter.toDriverWorkingTimeInput(input))
);
} }
public DriverWorkingTimeProcessingResultDto processDriverWorkingTime(TachographEsperProcessingInput input) { public DriverWorkingTimeProcessingResultDto processDriverWorkingTime(TachographEsperProcessingInput input) {
return delegate.process(input); return delegate.process(TachographEsperProcessingInputAdapter.toDriverWorkingTimeInput(input));
} }
} }

View File

@ -0,0 +1,71 @@
package at.procon.eventhub.tachographfilesession.service;
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.eventprocessing.support.RuntimeSupportEvidenceEvent;
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import java.util.List;
final class TachographEsperProcessingInputAdapter {
private TachographEsperProcessingInputAdapter() {
}
static DriverWorkingTimeProcessingInput toDriverWorkingTimeInput(TachographEsperProcessingInput input) {
ResolvedDriverTimeline timeline = input.timeline();
String driverKey = input.driverKey();
return new DriverWorkingTimeProcessingInput(
input.sessionId(),
driverKey,
timeline.sourceKind(),
timeline.loadedFrom(),
timeline.loadedTo(),
input.requestedFrom(),
input.requestedTo(),
input.significantDrivingMinutes(),
input.minimumRestPeriodMinutes(),
timeline.activityIntervals().stream()
.map(interval -> DriverWorkingTimeActivityInterval.fromResolved(input.sessionId(), driverKey, interval))
.toList(),
timeline.vehicleUsageIntervals().stream()
.map(DriverWorkingTimeVehicleUsageInterval::fromResolved)
.toList(),
timeline.supportEvents().stream()
.map(TachographEsperProcessingInputAdapter::toSupportEvidenceEvent)
.toList(),
input.notes()
);
}
private 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())
);
}
}