From cd090010c620932ffbc30c846b9724838450f8bf Mon Sep 17 00:00:00 2001 From: trifonovt <87468028+TihomirTrifonov@users.noreply.github.com> Date: Tue, 26 May 2026 18:17:49 +0200 Subject: [PATCH] Extract neutral working-time projection engine --- ...verWorkingTimeDerivedProjectionBundle.java | 31 + ...orkingTimeDrivingInterruptionInterval.java | 19 + .../DriverWorkingTimeGeoEvidenceEvent.java | 14 + ...ingTimePotentialInVehicleTripInterval.java | 29 + ...DriverWorkingTimeRestCoverageInterval.java | 41 ++ .../DriverWorkingTimeSupportGeoEvent.java | 25 + ...DriverWorkingTimeVuCardAbsentInterval.java | 19 + ...verWorkingTimeDerivedProjectionEngine.java | 426 +++++++++++++ .../DriverWorkingTimeProcessingCore.java | 571 ++++++++++-------- ...nifiedRuntimeDerivedProjectionService.java | 3 +- .../TachographEsperProcessingCore.java | 9 +- ...TachographEsperProcessingInputAdapter.java | 71 +++ 12 files changed, 996 insertions(+), 262 deletions(-) create mode 100644 src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeDerivedProjectionBundle.java create mode 100644 src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeDrivingInterruptionInterval.java create mode 100644 src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeGeoEvidenceEvent.java create mode 100644 src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimePotentialInVehicleTripInterval.java create mode 100644 src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeRestCoverageInterval.java create mode 100644 src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeSupportGeoEvent.java create mode 100644 src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeVuCardAbsentInterval.java create mode 100644 src/main/java/at/procon/eventhub/processing/driverworkingtime/service/DriverWorkingTimeDerivedProjectionEngine.java create mode 100644 src/main/java/at/procon/eventhub/tachographfilesession/service/TachographEsperProcessingInputAdapter.java diff --git a/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeDerivedProjectionBundle.java b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeDerivedProjectionBundle.java new file mode 100644 index 0000000..6617e96 --- /dev/null +++ b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeDerivedProjectionBundle.java @@ -0,0 +1,31 @@ +package at.procon.eventhub.processing.driverworkingtime.model; + +import java.util.List; + +public record DriverWorkingTimeDerivedProjectionBundle( + List drivingInterruptionIntervals, + List dailyWeeklyRestCandidateIntervals, + List dailyWeeklyRestCandidateCoverageIntervals, + List unclassifiedDailyWeeklyRestCandidateCoverageIntervals, + List drivingInterruptionVehicleChangeIntervals, + List vuCardAbsentIntervals, + List potentialHomeOvernightStayIntervals, + List potentialInVehicleOvernightStayIntervals, + List 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 List safe(List values) { + return values == null ? List.of() : List.copyOf(values); + } +} diff --git a/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeDrivingInterruptionInterval.java b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeDrivingInterruptionInterval.java new file mode 100644 index 0000000..782a3ab --- /dev/null +++ b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeDrivingInterruptionInterval.java @@ -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 +) { +} diff --git a/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeGeoEvidenceEvent.java b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeGeoEvidenceEvent.java new file mode 100644 index 0000000..f25fcf3 --- /dev/null +++ b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeGeoEvidenceEvent.java @@ -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 +) { +} diff --git a/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimePotentialInVehicleTripInterval.java b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimePotentialInVehicleTripInterval.java new file mode 100644 index 0000000..a04dc5d --- /dev/null +++ b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimePotentialInVehicleTripInterval.java @@ -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 potentialInVehicleOvernightStayIntervals +) { + public DriverWorkingTimePotentialInVehicleTripInterval { + potentialInVehicleOvernightStayIntervals = potentialInVehicleOvernightStayIntervals == null + ? List.of() + : List.copyOf(potentialInVehicleOvernightStayIntervals); + } +} diff --git a/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeRestCoverageInterval.java b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeRestCoverageInterval.java new file mode 100644 index 0000000..4dd3e50 --- /dev/null +++ b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeRestCoverageInterval.java @@ -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 +) { +} diff --git a/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeSupportGeoEvent.java b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeSupportGeoEvent.java new file mode 100644 index 0000000..860d756 --- /dev/null +++ b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeSupportGeoEvent.java @@ -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 +) { +} diff --git a/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeVuCardAbsentInterval.java b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeVuCardAbsentInterval.java new file mode 100644 index 0000000..278b273 --- /dev/null +++ b/src/main/java/at/procon/eventhub/processing/driverworkingtime/model/DriverWorkingTimeVuCardAbsentInterval.java @@ -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 +) { +} diff --git a/src/main/java/at/procon/eventhub/processing/driverworkingtime/service/DriverWorkingTimeDerivedProjectionEngine.java b/src/main/java/at/procon/eventhub/processing/driverworkingtime/service/DriverWorkingTimeDerivedProjectionEngine.java new file mode 100644 index 0000000..e245d64 --- /dev/null +++ b/src/main/java/at/procon/eventhub/processing/driverworkingtime/service/DriverWorkingTimeDerivedProjectionEngine.java @@ -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 activityIntervals = input.activityIntervals().stream() + .map(this::toResolvedActivityInterval) + .filter(Objects::nonNull) + .toList(); + List vehicleUsageIntervals = input.vehicleUsageIntervals().stream() + .map(this::toResolvedVehicleUsageInterval) + .filter(Objects::nonNull) + .toList(); + List 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 activityIntervals, + List vehicleUsageIntervals, + List 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 activityIntervals, + List vehicleUsageIntervals, + List 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; + } +} diff --git a/src/main/java/at/procon/eventhub/processing/driverworkingtime/service/DriverWorkingTimeProcessingCore.java b/src/main/java/at/procon/eventhub/processing/driverworkingtime/service/DriverWorkingTimeProcessingCore.java index 8da9dd2..d97ae58 100644 --- a/src/main/java/at/procon/eventhub/processing/driverworkingtime/service/DriverWorkingTimeProcessingCore.java +++ b/src/main/java/at/procon/eventhub/processing/driverworkingtime/service/DriverWorkingTimeProcessingCore.java @@ -1,21 +1,22 @@ 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.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.*; -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.OffsetDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.UUID; import org.springframework.stereotype.Service; @@ -31,76 +32,59 @@ import org.springframework.stereotype.Service; @Service public class DriverWorkingTimeProcessingCore { - private final DriverTimelineBuilder driverTimelineBuilder; - private final DriverTimelineReusableProjectionBuilder reusableProjectionBuilder; - private final EventHubProperties properties; + private final DriverWorkingTimeDerivedProjectionEngine derivedProjectionEngine; public DriverWorkingTimeProcessingCore( - DriverTimelineBuilder driverTimelineBuilder, - DriverTimelineReusableProjectionBuilder reusableProjectionBuilder, - EventHubProperties properties + DriverWorkingTimeDerivedProjectionEngine derivedProjectionEngine ) { - this.driverTimelineBuilder = driverTimelineBuilder; - this.reusableProjectionBuilder = reusableProjectionBuilder; - this.properties = properties; - } - - public DriverWorkingTimeProcessingResultDto process(TachographEsperProcessingInput input) { - Objects.requireNonNull(input, "input must not be null"); - return process(toSourceNeutralInput(input)); + this.derivedProjectionEngine = derivedProjectionEngine; } public DriverWorkingTimeProcessingResultDto process(DriverWorkingTimeProcessingInput input) { Objects.requireNonNull(input, "input must not be null"); - ResolvedDriverTimeline timeline = toResolvedTimeline(input); + OffsetDateTime loadedFrom = input.loadedFrom(); + OffsetDateTime loadedTo = input.loadedTo(); String driverKey = input.driverKey(); - OffsetDateTime requestedFrom = input.requestedFrom() == null ? timeline.loadedFrom() : utc(input.requestedFrom()); - OffsetDateTime requestedTo = input.requestedTo() == null ? timeline.loadedTo() : utc(input.requestedTo()); + OffsetDateTime requestedFrom = input.requestedFrom() == null ? loadedFrom : utc(input.requestedFrom()); + OffsetDateTime requestedTo = input.requestedTo() == null ? loadedTo : utc(input.requestedTo()); if (requestedFrom != null && requestedTo != null && requestedTo.isBefore(requestedFrom)) { throw new IllegalArgumentException("occurredTo must not be before occurredFrom."); } List activityIntervals = clipEsperActivityIntervalEvents( - driverTimelineBuilder.buildEsperActivityIntervalEvents(input.sessionId(), driverKey, timeline), + buildEsperActivityIntervalEvents(input.activityIntervals()), requestedFrom, requestedTo ); List drivingIntervals = clipEsperActivityIntervalEvents( - driverTimelineBuilder.buildEsperDrivingIntervalEvents(input.sessionId(), driverKey, timeline), + buildEsperDrivingIntervalEvents(input.activityIntervals()), requestedFrom, requestedTo ); - TachographEsperDrivingDerivedProjectionBundle derivedProjectionBundle = - reusableProjectionBuilder.buildEsperDrivingDerivedProjectionBundle( - safeSessionId(input.sessionId()), - driverKey, - timeline, - input.significantDrivingMinutes(), - input.minimumRestPeriodMinutes() - ); + DriverWorkingTimeDerivedProjectionBundle derivedProjectionBundle = derivedProjectionEngine.build(input); List rawDrivingInterruptionIntervals = - derivedProjectionBundle.drivingInterruptionIntervals(); + derivedProjectionBundle.drivingInterruptionIntervals().stream().map(this::toLegacy).toList(); List drivingInterruptionIntervals = clipEsperDrivingInterruptionIntervalEvents(rawDrivingInterruptionIntervals, requestedFrom, requestedTo); List rawDailyWeeklyRestCandidateIntervals = - derivedProjectionBundle.dailyWeeklyRestCandidateIntervals(); + derivedProjectionBundle.dailyWeeklyRestCandidateIntervals().stream().map(this::toLegacy).toList(); List dailyWeeklyRestCandidateIntervals = clipEsperDrivingInterruptionIntervalEvents(rawDailyWeeklyRestCandidateIntervals, requestedFrom, requestedTo); List rawDrivingInterruptionVehicleChangeIntervals = - derivedProjectionBundle.drivingInterruptionVehicleChangeIntervals(); + derivedProjectionBundle.drivingInterruptionVehicleChangeIntervals().stream().map(this::toLegacy).toList(); List drivingInterruptionVehicleChangeIntervals = clipEsperDrivingInterruptionIntervalEvents(rawDrivingInterruptionVehicleChangeIntervals, requestedFrom, requestedTo); List rawVehicleUsageIntervals = - driverTimelineBuilder.buildEsperVehicleUsageIntervalEvents(timeline); + buildEsperVehicleUsageIntervalEvents(input.vehicleUsageIntervals()); List rawVuCardAbsentIntervals = - derivedProjectionBundle.vuCardAbsentIntervals(); + derivedProjectionBundle.vuCardAbsentIntervals().stream().map(this::toLegacy).toList(); List potentialHomeOvernightStayIntervals = clipEsperPotentialHomeOvernightStayIntervalEvents( - derivedProjectionBundle.potentialHomeOvernightStayIntervals(), + derivedProjectionBundle.potentialHomeOvernightStayIntervals().stream().map(this::toLegacyPotentialHome).toList(), rawVuCardAbsentIntervals, rawVehicleUsageIntervals, requestedFrom, @@ -108,7 +92,7 @@ public class DriverWorkingTimeProcessingCore { ); List dailyWeeklyRestCandidateCoverageIntervals = clipEsperDailyWeeklyRestCandidateCoverageIntervalEvents( - derivedProjectionBundle.dailyWeeklyRestCandidateCoverageIntervals(), + derivedProjectionBundle.dailyWeeklyRestCandidateCoverageIntervals().stream().map(this::toLegacyCoverage).toList(), rawVuCardAbsentIntervals, rawVehicleUsageIntervals, requestedFrom, @@ -116,7 +100,7 @@ public class DriverWorkingTimeProcessingCore { ); List unclassifiedDailyWeeklyRestCandidateCoverageIntervals = clipEsperDailyWeeklyRestCandidateCoverageIntervalEvents( - derivedProjectionBundle.unclassifiedDailyWeeklyRestCandidateCoverageIntervals(), + derivedProjectionBundle.unclassifiedDailyWeeklyRestCandidateCoverageIntervals().stream().map(this::toLegacyCoverage).toList(), rawVuCardAbsentIntervals, rawVehicleUsageIntervals, requestedFrom, @@ -124,7 +108,7 @@ public class DriverWorkingTimeProcessingCore { ); List potentialInVehicleOvernightStayIntervals = clipEsperPotentialInVehicleOvernightStayIntervalEvents( - derivedProjectionBundle.potentialInVehicleOvernightStayIntervals(), + derivedProjectionBundle.potentialInVehicleOvernightStayIntervals().stream().map(this::toLegacyPotentialInVehicle).toList(), rawVuCardAbsentIntervals, rawVehicleUsageIntervals, requestedFrom, @@ -132,7 +116,7 @@ public class DriverWorkingTimeProcessingCore { ); List potentialInVehicleTripIntervals = clipEsperPotentialInVehicleTripIntervalEvents( - derivedProjectionBundle.potentialInVehicleTripIntervals(), + derivedProjectionBundle.potentialInVehicleTripIntervals().stream().map(this::toLegacy).toList(), potentialInVehicleOvernightStayIntervals, requestedFrom, requestedTo @@ -148,7 +132,7 @@ public class DriverWorkingTimeProcessingCore { requestedTo ); List supportGeoEvents = clipEsperSupportGeoEvents( - timeline.supportEvents(), + buildSupportGeoEvents(input.supportEvidenceEvents()), driverKey, requestedFrom, requestedTo @@ -157,9 +141,9 @@ public class DriverWorkingTimeProcessingCore { return new DriverWorkingTimeProcessingResultDto( input.sessionId(), driverKey, - timeline.sourceKind(), - timeline.loadedFrom(), - timeline.loadedTo(), + input.sourceKind(), + loadedFrom, + loadedTo, requestedFrom, requestedTo, activityIntervals.size(), @@ -192,207 +176,297 @@ public class DriverWorkingTimeProcessingCore { ); } - public DriverWorkingTimeProcessingResultDto processDriverWorkingTime(TachographEsperProcessingInput input) { + public DriverWorkingTimeProcessingResultDto processDriverWorkingTime(DriverWorkingTimeProcessingInput input) { return process(input); } - private DriverWorkingTimeProcessingInput toSourceNeutralInput(TachographEsperProcessingInput input) { - ResolvedDriverTimeline timeline = Objects.requireNonNull(input.timeline(), "timeline must not be null"); - 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 activityIntervals = input.activityIntervals().stream() - .map(this::toResolvedActivityInterval) - .filter(Objects::nonNull) - .toList(); - List vehicleUsageIntervals = input.vehicleUsageIntervals().stream() - .map(this::toResolvedVehicleUsageInterval) - .filter(Objects::nonNull) - .toList(); - List 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 activityIntervals, - List vehicleUsageIntervals, - List supportEvents + private List buildEsperActivityIntervalEvents( + List intervals ) { - 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; + return safeList(intervals).stream() + .filter(interval -> interval.startedAt() != null && interval.endedAt() != null) + .map(interval -> new TachographEsperActivityIntervalEvent( + safeSessionId(interval.sessionId()), + interval.driverKey(), + interval.intervalId(), + interval.activityType(), + interval.cardSlot(), + interval.cardStatus(), + interval.drivingStatus(), + 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( - List activityIntervals, - List vehicleUsageIntervals, - List supportEvents + private List buildEsperDrivingIntervalEvents( + List intervals ) { - OffsetDateTime latest = null; - for (ResolvedActivityInterval interval : activityIntervals) { - latest = max(latest, interval.to()); + return buildEsperActivityIntervalEvents(intervals).stream() + .filter(interval -> Objects.equals("DRIVE", interval.activityType())) + .toList(); + } + + private List buildEsperVehicleUsageIntervalEvents( + List 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 buildSupportGeoEvents( + List 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) { - latest = max(latest, interval.to()); + Object value = event.rawAttributes().get("rawRecordPath"); + 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) { - latest = max(latest, event.occurredAt()); - } - return latest; + return new TachographEsperGeoEvidenceEvent( + value.eventId(), + value.eventDomain(), + value.occurredAt(), + value.latitude(), + value.longitude(), + value.distanceSeconds(), + value.odometerKm() + ); } private UUID safeSessionId(UUID sessionId) { @@ -498,7 +572,7 @@ public class DriverWorkingTimeProcessingCore { } private List clipEsperSupportGeoEvents( - List supportEvents, + List supportEvents, String driverKey, OffsetDateTime requestedFrom, OffsetDateTime requestedTo @@ -511,25 +585,6 @@ public class DriverWorkingTimeProcessingCore { .filter(event -> event.occurredAt() != null) .filter(event -> event.latitude() != null && event.longitude() != null) .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) .thenComparing(TachographEsperSupportGeoEvent::eventDomain, Comparator.nullsLast(String::compareTo)) .thenComparing(TachographEsperSupportGeoEvent::eventId, Comparator.nullsLast(String::compareTo))) diff --git a/src/main/java/at/procon/eventhub/processing/service/UnifiedRuntimeDerivedProjectionService.java b/src/main/java/at/procon/eventhub/processing/service/UnifiedRuntimeDerivedProjectionService.java index 98b7688..214a947 100644 --- a/src/main/java/at/procon/eventhub/processing/service/UnifiedRuntimeDerivedProjectionService.java +++ b/src/main/java/at/procon/eventhub/processing/service/UnifiedRuntimeDerivedProjectionService.java @@ -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.DriverWorkingTimeProcessingInput; 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.RuntimeSupportEvidenceEvent; import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceNormalizer; @@ -66,7 +67,7 @@ public class UnifiedRuntimeDerivedProjectionService { driverTimelineBuilder, reusableProjectionBuilder, properties, - new DriverWorkingTimeProcessingCore(driverTimelineBuilder, reusableProjectionBuilder, properties), + new DriverWorkingTimeProcessingCore(new DriverWorkingTimeDerivedProjectionEngine(reusableProjectionBuilder)), supportEvidenceNormalizer ); } diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/service/TachographEsperProcessingCore.java b/src/main/java/at/procon/eventhub/tachographfilesession/service/TachographEsperProcessingCore.java index b2d7b96..4aa1d4d 100644 --- a/src/main/java/at/procon/eventhub/tachographfilesession/service/TachographEsperProcessingCore.java +++ b/src/main/java/at/procon/eventhub/tachographfilesession/service/TachographEsperProcessingCore.java @@ -2,6 +2,7 @@ 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.tachographfilesession.dto.TachographEsperDriverProcessingResultDto; import org.springframework.beans.factory.annotation.Autowired; @@ -27,14 +28,16 @@ public class TachographEsperProcessingCore { DriverTimelineReusableProjectionBuilder reusableProjectionBuilder, EventHubProperties properties ) { - this(new DriverWorkingTimeProcessingCore(driverTimelineBuilder, reusableProjectionBuilder, properties)); + this(new DriverWorkingTimeProcessingCore(new DriverWorkingTimeDerivedProjectionEngine(reusableProjectionBuilder))); } 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)); } } diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/service/TachographEsperProcessingInputAdapter.java b/src/main/java/at/procon/eventhub/tachographfilesession/service/TachographEsperProcessingInputAdapter.java new file mode 100644 index 0000000..685fc8b --- /dev/null +++ b/src/main/java/at/procon/eventhub/tachographfilesession/service/TachographEsperProcessingInputAdapter.java @@ -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()) + ); + } +}