From 6dd1fb744723e7b71984e0ef9ff1fd4d4b13d694 Mon Sep 17 00:00:00 2001 From: trifonovt <87468028+TihomirTrifonov@users.noreply.github.com> Date: Thu, 28 May 2026 11:47:01 +0200 Subject: [PATCH] Fix tachograph runtime identity and interval extraction --- .../at/procon/eventhub/dto/DriverRefDto.java | 2 +- .../EventHubEventReadRepository.java | 70 ++++++++- .../UnifiedRuntimeProcessingApiRequest.java | 3 + .../VehicleEvidenceAttachmentModule.java | 14 +- .../epl/DriverWorkingTimeEplEventMapper.java | 37 +---- ...riverWorkingTimeRuntimeProcessingPlan.java | 1 + .../RuntimeSupportEvidenceNormalizer.java | 55 +------ ...timeTachographParityValidationService.java | 1 + .../model/UnifiedDriverEventsRequest.java | 27 ++++ .../UnifiedRuntimeProcessingRequest.java | 35 +++++ .../model/UnifiedTachographSourceKind.java | 6 + .../model/UnifiedVehicleEventsRequest.java | 28 ++++ .../service/EventHubRuntimeEventLoader.java | 6 +- ...riverVehicleEvidenceAttachmentService.java | 26 ++-- ...iverWorkingTimeScopeProcessingService.java | 15 +- .../TachographDbRuntimeEventLoader.java | 36 +++-- .../TachographDbUnifiedDriverEventSource.java | 3 +- ...TachographDbUnifiedVehicleEventSource.java | 3 +- .../UnifiedEventTimelineReconstructor.java | 19 ++- ...nifiedRuntimeDerivedProjectionService.java | 18 +-- .../UnifiedRuntimeDriverTimelineService.java | 19 +-- .../TachographRuntimeIdentityResolver.java | 144 ++++++++++++++++++ .../service/TachographRawPayloadSupport.java | 2 +- .../DriverCardXmlExtractionService.java | 6 +- .../VehicleUnitXmlExtractionService.java | 6 +- .../RuntimeEventProcessingServiceTest.java | 1 + .../DriverWorkingTimeEplEventMapperTest.java | 94 ++++++++++++ ...sperRuntimeEventProcessingProfileTest.java | 2 + ...edSourceEvidenceValidationServiceTest.java | 1 + .../model/UnifiedDriverEventsRequestTest.java | 1 + .../UnifiedRuntimeProcessingRequestTest.java | 50 ++++++ .../UnifiedVehicleEventsRequestTest.java | 1 + ...rVehicleEvidenceAttachmentServiceTest.java | 134 +++++++++++++++- ...ifiedRuntimeDriverTimelineServiceTest.java | 11 +- ...nifiedRuntimeEventAssemblyServiceTest.java | 1 + ...ographDbRowMapperTimelineMetadataTest.java | 20 +-- .../DriverCardXmlExtractionServiceTest.java | 16 +- .../VehicleUnitXmlExtractionServiceTest.java | 11 +- 38 files changed, 709 insertions(+), 216 deletions(-) create mode 100644 src/main/java/at/procon/eventhub/processing/model/UnifiedTachographSourceKind.java create mode 100644 src/main/java/at/procon/eventhub/processing/support/TachographRuntimeIdentityResolver.java create mode 100644 src/test/java/at/procon/eventhub/processing/eventprocessing/module/epl/DriverWorkingTimeEplEventMapperTest.java diff --git a/src/main/java/at/procon/eventhub/dto/DriverRefDto.java b/src/main/java/at/procon/eventhub/dto/DriverRefDto.java index 11f7fdb..1c0cd30 100644 --- a/src/main/java/at/procon/eventhub/dto/DriverRefDto.java +++ b/src/main/java/at/procon/eventhub/dto/DriverRefDto.java @@ -26,7 +26,7 @@ public record DriverRefDto( public String stableKey() { String cardKey = driverCard == null ? "" : driverCard.stableKey(); - return (sourceEntityId == null ? "" : sourceEntityId) + "|" + cardKey; + return /*(sourceEntityId == null ? "" : sourceEntityId) + "|" +*/ cardKey; } private static String normalizeNullable(String value) { diff --git a/src/main/java/at/procon/eventhub/persistence/EventHubEventReadRepository.java b/src/main/java/at/procon/eventhub/persistence/EventHubEventReadRepository.java index 7fee1b5..e7f10d4 100644 --- a/src/main/java/at/procon/eventhub/persistence/EventHubEventReadRepository.java +++ b/src/main/java/at/procon/eventhub/persistence/EventHubEventReadRepository.java @@ -148,13 +148,13 @@ public class EventHubEventReadRepository { event.source_package_entity_id, detail.detail_type, detail.attributes, - driver.source_driver_entity_id, + driver_identity.source_driver_entity_id, driver_card.nation as driver_card_nation, driver_card.nation_numeric_code as driver_card_nation_numeric_code, driver_card.card_number as driver_card_number, - vehicle.source_vehicle_entity_id, + vehicle_identity.source_vehicle_entity_id, vehicle.vin, - registration.source_registration_entity_id, + registration_identity.source_registration_entity_id, registration.nation as vehicle_registration_nation, registration.nation_numeric_code as vehicle_registration_nation_numeric_code, registration.registration_number as vehicle_registration_number, @@ -173,9 +173,36 @@ public class EventHubEventReadRepository { limit 1 ) detail on true left join eventhub.driver driver on driver.id = event.driver_id + left join lateral ( + select identity.source_driver_entity_id + from eventhub.source_driver_identity identity + where identity.driver_id = event.driver_id + and identity.tenant_key = package.tenant_key + and identity.event_source_id = event.event_source_id + order by identity.updated_at desc, identity.created_at desc, identity.id + limit 1 + ) driver_identity on true left join eventhub.driver_card driver_card on driver_card.id = event.driver_card_id left join eventhub.vehicle vehicle on vehicle.id = event.vehicle_id + left join lateral ( + select identity.source_vehicle_entity_id + from eventhub.source_vehicle_identity identity + where identity.vehicle_id = event.vehicle_id + and identity.tenant_key = package.tenant_key + and identity.event_source_id = event.event_source_id + order by identity.updated_at desc, identity.created_at desc, identity.id + limit 1 + ) vehicle_identity on true left join eventhub.vehicle_registration registration on registration.id = event.vehicle_registration_id + left join lateral ( + select identity.source_registration_entity_id + from eventhub.source_vehicle_registration_identity identity + where identity.vehicle_registration_id = event.vehicle_registration_id + and identity.tenant_key = package.tenant_key + and identity.event_source_id = event.event_source_id + order by identity.updated_at desc, identity.created_at desc, identity.id + limit 1 + ) registration_identity on true where package.tenant_key = ? and source.provider_key = ? """ @@ -205,7 +232,18 @@ public class EventHubEventReadRepository { params.add(occurredTo); } if (driverSourceEntityId != null) { - sql.append(" and driver.source_driver_entity_id = ?"); + sql.append( + """ + and exists ( + select 1 + from eventhub.source_driver_identity identity + where identity.driver_id = event.driver_id + and identity.tenant_key = package.tenant_key + and identity.event_source_id = event.event_source_id + and identity.source_driver_entity_id = ? + ) + """ + ); params.add(driverSourceEntityId); } if (driverCardNumber != null) { @@ -224,7 +262,18 @@ public class EventHubEventReadRepository { } } if (vehicleSourceEntityId != null) { - sql.append(" and vehicle.source_vehicle_entity_id = ?"); + sql.append( + """ + and exists ( + select 1 + from eventhub.source_vehicle_identity identity + where identity.vehicle_id = event.vehicle_id + and identity.tenant_key = package.tenant_key + and identity.event_source_id = event.event_source_id + and identity.source_vehicle_entity_id = ? + ) + """ + ); params.add(vehicleSourceEntityId); } if (vin != null) { @@ -246,6 +295,17 @@ public class EventHubEventReadRepository { } } } + if (registrationNumber == null && registrationNation != null) { + TachographNationRegistry.NationResolution registrationNationResolution = + TachographNationRegistry.resolve(registrationNation, null); + if (registrationNationResolution.numericCode() != null) { + sql.append(" and registration.nation_numeric_code = ?"); + params.add(registrationNationResolution.numericCode()); + } else { + sql.append(" and registration.nation = ?"); + params.add(registrationNationResolution.legacyNation()); + } + } sql.append(" order by event.occurred_at, event.event_domain, event.event_type, event.lifecycle, event.id"); diff --git a/src/main/java/at/procon/eventhub/processing/dto/UnifiedRuntimeProcessingApiRequest.java b/src/main/java/at/procon/eventhub/processing/dto/UnifiedRuntimeProcessingApiRequest.java index 3d6c3dc..fc6f987 100644 --- a/src/main/java/at/procon/eventhub/processing/dto/UnifiedRuntimeProcessingApiRequest.java +++ b/src/main/java/at/procon/eventhub/processing/dto/UnifiedRuntimeProcessingApiRequest.java @@ -3,6 +3,7 @@ package at.procon.eventhub.processing.dto; import at.procon.eventhub.processing.model.UnifiedEventSourceFamily; import at.procon.eventhub.processing.model.UnifiedRuntimeEventBackend; import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest; +import at.procon.eventhub.processing.model.UnifiedTachographSourceKind; import java.time.OffsetDateTime; import java.util.List; import java.util.Set; @@ -15,6 +16,7 @@ public record UnifiedRuntimeProcessingApiRequest( String tenantKey, Set sourceFamilies, UnifiedRuntimeEventBackend eventBackend, + Set tachographSourceKinds, String driverKey, Set driverKeys, Boolean includeAllDrivers, @@ -38,6 +40,7 @@ public record UnifiedRuntimeProcessingApiRequest( tenantKey, sourceFamilies, eventBackend, + tachographSourceKinds, driverKey, driverKeys, includeAllDrivers != null && includeAllDrivers, diff --git a/src/main/java/at/procon/eventhub/processing/eventprocessing/module/VehicleEvidenceAttachmentModule.java b/src/main/java/at/procon/eventhub/processing/eventprocessing/module/VehicleEvidenceAttachmentModule.java index 8b3ef40..f4d91e0 100644 --- a/src/main/java/at/procon/eventhub/processing/eventprocessing/module/VehicleEvidenceAttachmentModule.java +++ b/src/main/java/at/procon/eventhub/processing/eventprocessing/module/VehicleEvidenceAttachmentModule.java @@ -13,6 +13,7 @@ import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef; import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle; import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest; import at.procon.eventhub.processing.service.RuntimeDriverVehicleEvidenceAttachmentService; +import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver; import com.fasterxml.jackson.databind.JsonNode; import java.util.ArrayList; import java.util.Comparator; @@ -258,18 +259,7 @@ public class VehicleEvidenceAttachmentModule implements RuntimeProcessingModule } private String driverKey(EventHubEventDto event) { - if (event == null) { - return null; - } - String rawDriverKey = text(rawPayload(event), "driverKey"); - if (rawDriverKey != null) { - return rawDriverKey; - } - DriverRefDto driverRef = event.driverRef(); - if (driverRef != null && driverRef.hasAnyReference()) { - return driverRef.stableKey(); - } - return null; + return TachographRuntimeIdentityResolver.driverKey(event); } private JsonNode rawPayload(EventHubEventDto event) { diff --git a/src/main/java/at/procon/eventhub/processing/eventprocessing/module/epl/DriverWorkingTimeEplEventMapper.java b/src/main/java/at/procon/eventhub/processing/eventprocessing/module/epl/DriverWorkingTimeEplEventMapper.java index 5bfbb6d..fb27a9d 100644 --- a/src/main/java/at/procon/eventhub/processing/eventprocessing/module/epl/DriverWorkingTimeEplEventMapper.java +++ b/src/main/java/at/procon/eventhub/processing/eventprocessing/module/epl/DriverWorkingTimeEplEventMapper.java @@ -8,6 +8,7 @@ import at.procon.eventhub.processing.eventprocessing.module.DriverWorkingTimeMod import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleContext; import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleResult; import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle; +import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver; import com.fasterxml.jackson.databind.JsonNode; import java.time.OffsetDateTime; import java.util.ArrayList; @@ -151,7 +152,7 @@ public final class DriverWorkingTimeEplEventMapper { JsonNode raw = rawPayload(sourceEvent); JsonNode attributes = attributes(sourceEvent); String intervalId = firstNonBlank(text(raw, "intervalId"), text(raw, "sourceRowId"), sourceEvent.externalSourceEventId()); - String driverKey = firstNonBlank(text(raw, "driverKey"), driverKey(sourceEvent)); + String driverKey = TachographRuntimeIdentityResolver.driverKey(sourceEvent); if (driverKey == null || intervalId == null) { return null; } @@ -169,8 +170,8 @@ public final class DriverWorkingTimeEplEventMapper { event.put("cardSlot", firstNonBlank(text(raw, "cardSlot"), text(attributes, "cardSlot"))); event.put("cardStatus", firstNonBlank(text(raw, "cardStatus"), text(attributes, "cardStatus"))); event.put("drivingStatus", firstNonBlank(text(raw, "drivingStatus"), text(attributes, "drivingStatus"))); - event.put("registrationKey", firstNonBlank(text(raw, "registrationKey"), registrationKey(sourceEvent))); - event.put("vehicleKey", firstNonBlank(text(raw, "vehicleKey"), vehicleKey(sourceEvent))); + event.put("registrationKey", TachographRuntimeIdentityResolver.registrationKey(sourceEvent)); + event.put("vehicleKey", TachographRuntimeIdentityResolver.vehicleKey(sourceEvent)); event.put("sourceKind", firstNonBlank(text(raw, "sourceKind"), sourceKind(sourceEvent))); event.put("synthetic", booleanValue(raw, "synthetic", false)); event.put("clippedToRequestedPeriod", booleanValue(raw, "clippedToRequestedPeriod", false)); @@ -192,7 +193,7 @@ public final class DriverWorkingTimeEplEventMapper { } JsonNode raw = rawPayload(sourceEvent); String intervalId = firstNonBlank(text(raw, "intervalId"), text(raw, "sourceRowId"), sourceEvent.externalSourceEventId()); - String driverKey = firstNonBlank(text(raw, "driverKey"), driverKey(sourceEvent)); + String driverKey = TachographRuntimeIdentityResolver.driverKey(sourceEvent); if (driverKey == null || intervalId == null) { return null; } @@ -206,8 +207,8 @@ public final class DriverWorkingTimeEplEventMapper { event.put("lifecycle", sourceEvent.lifecycle().name()); event.put("occurredAt", sourceEvent.occurredAt()); event.put("occurredAtEpochSecond", sourceEvent.occurredAt().toEpochSecond()); - event.put("registrationKey", firstNonBlank(text(raw, "registrationKey"), registrationKey(sourceEvent))); - event.put("vehicleKey", firstNonBlank(text(raw, "vehicleKey"), vehicleKey(sourceEvent))); + event.put("registrationKey", TachographRuntimeIdentityResolver.registrationKey(sourceEvent)); + event.put("vehicleKey", TachographRuntimeIdentityResolver.vehicleKey(sourceEvent)); event.put("sourceKind", firstNonBlank(text(raw, "sourceKind"), sourceKind(sourceEvent))); event.put("odometerKm", odometerKm(sourceEvent, raw)); return event; @@ -338,30 +339,6 @@ public final class DriverWorkingTimeEplEventMapper { return new UUID(0L, 0L); } - private static String driverKey(EventHubEventDto event) { - if (event.driverRef() == null) { - return null; - } - if (event.driverRef().driverCard() != null && event.driverRef().driverCard().hasValue()) { - return event.driverRef().driverCard().stableKey(); - } - return event.driverRef().sourceEntityId(); - } - - private static String registrationKey(EventHubEventDto event) { - if (event.vehicleRef() == null || event.vehicleRef().vehicleRegistration() == null) { - return null; - } - return event.vehicleRef().vehicleRegistration().stableKey(); - } - - private static String vehicleKey(EventHubEventDto event) { - if (event.vehicleRef() == null) { - return null; - } - return firstNonBlank(event.vehicleRef().vin(), event.vehicleRef().sourceVehicleEntityId()); - } - private static String sourceKind(EventHubEventDto event) { return event.packageInfo() == null || event.packageInfo().eventSource() == null ? null diff --git a/src/main/java/at/procon/eventhub/processing/eventprocessing/plan/DriverWorkingTimeRuntimeProcessingPlan.java b/src/main/java/at/procon/eventhub/processing/eventprocessing/plan/DriverWorkingTimeRuntimeProcessingPlan.java index 355f7a8..17fca6b 100644 --- a/src/main/java/at/procon/eventhub/processing/eventprocessing/plan/DriverWorkingTimeRuntimeProcessingPlan.java +++ b/src/main/java/at/procon/eventhub/processing/eventprocessing/plan/DriverWorkingTimeRuntimeProcessingPlan.java @@ -382,6 +382,7 @@ public class DriverWorkingTimeRuntimeProcessingPlan implements RuntimeProcessing sourceSelection.tenantKey(), sourceSelection.sourceFamilies(), sourceSelection.eventBackend(), + sourceSelection.tachographSourceKinds(), sourceSelection.driverKey(), driverKeys, includeAllDrivers, diff --git a/src/main/java/at/procon/eventhub/processing/eventprocessing/support/RuntimeSupportEvidenceNormalizer.java b/src/main/java/at/procon/eventhub/processing/eventprocessing/support/RuntimeSupportEvidenceNormalizer.java index 96da6cc..10f6b95 100644 --- a/src/main/java/at/procon/eventhub/processing/eventprocessing/support/RuntimeSupportEvidenceNormalizer.java +++ b/src/main/java/at/procon/eventhub/processing/eventprocessing/support/RuntimeSupportEvidenceNormalizer.java @@ -8,6 +8,7 @@ import at.procon.eventhub.dto.EventLifecycle; import at.procon.eventhub.dto.EventType; import at.procon.eventhub.dto.GeoPointDto; import at.procon.eventhub.dto.VehicleRefDto; +import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -81,9 +82,9 @@ public class RuntimeSupportEvidenceNormalizer { event.eventDomain() == null ? null : event.eventDomain().name(), event.eventType() == null ? null : event.eventType().name(), event.lifecycle() == null ? null : event.lifecycle().name(), - firstNonBlank(text(raw, "driverKey"), fallbackDriverKey, driverKey(event)), - firstNonBlank(text(raw, "vehicleKey"), vehicleKey(event)), - firstNonBlank(text(raw, "registrationKey"), registrationKey(event)), + firstNonBlank(TachographRuntimeIdentityResolver.driverKey(event), fallbackDriverKey), + TachographRuntimeIdentityResolver.vehicleKey(event), + TachographRuntimeIdentityResolver.registrationKey(event), event.occurredAt(), event.occurredAt() == null ? null : event.occurredAt().toEpochSecond(), latitude, @@ -287,53 +288,7 @@ public class RuntimeSupportEvidenceNormalizer { } private JsonNode rawPayload(EventHubEventDto event) { - if (event == null || event.payload() == null || event.payload().isNull() || event.payload().isMissingNode()) { - return null; - } - JsonNode raw = event.payload().get("raw"); - return raw == null || raw.isNull() ? event.payload() : raw; - } - - private String driverKey(EventHubEventDto event) { - if (event == null || event.driverRef() == null || !event.driverRef().hasAnyReference()) { - return null; - } - return event.driverRef().stableKey(); - } - - private String vehicleKey(EventHubEventDto event) { - JsonNode raw = rawPayload(event); - String rawVehicleKey = text(raw, "vehicleKey"); - if (rawVehicleKey != null) { - return rawVehicleKey; - } - VehicleRefDto vehicleRef = event == null ? null : event.vehicleRef(); - if (vehicleRef == null) { - return null; - } - if (vehicleRef.vin() != null) { - return vehicleRef.vin(); - } - if (vehicleRef.sourceVehicleEntityId() != null) { - return vehicleRef.sourceVehicleEntityId(); - } - return null; - } - - private String registrationKey(EventHubEventDto event) { - JsonNode raw = rawPayload(event); - String rawRegistrationKey = text(raw, "registrationKey"); - if (rawRegistrationKey != null) { - return rawRegistrationKey; - } - VehicleRefDto vehicleRef = event == null ? null : event.vehicleRef(); - if (vehicleRef == null) { - return null; - } - if (vehicleRef.vehicleRegistration() != null && vehicleRef.vehicleRegistration().hasValue()) { - return vehicleRef.vehicleRegistration().stableKey(); - } - return vehicleRef.sourceRegistrationEntityId(); + return TachographRuntimeIdentityResolver.rawPayload(event); } private String detailText(EventHubEventDto event, String field) { diff --git a/src/main/java/at/procon/eventhub/processing/eventprocessing/validation/RuntimeTachographParityValidationService.java b/src/main/java/at/procon/eventhub/processing/eventprocessing/validation/RuntimeTachographParityValidationService.java index b4bea01..a363b50 100644 --- a/src/main/java/at/procon/eventhub/processing/eventprocessing/validation/RuntimeTachographParityValidationService.java +++ b/src/main/java/at/procon/eventhub/processing/eventprocessing/validation/RuntimeTachographParityValidationService.java @@ -166,6 +166,7 @@ public class RuntimeTachographParityValidationService { Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), UnifiedRuntimeEventBackend.SOURCE_DB, null, + null, driverKeys, request.includeAllDriversOrDefault(), null, diff --git a/src/main/java/at/procon/eventhub/processing/model/UnifiedDriverEventsRequest.java b/src/main/java/at/procon/eventhub/processing/model/UnifiedDriverEventsRequest.java index 1dc875e..b74d7fe 100644 --- a/src/main/java/at/procon/eventhub/processing/model/UnifiedDriverEventsRequest.java +++ b/src/main/java/at/procon/eventhub/processing/model/UnifiedDriverEventsRequest.java @@ -2,11 +2,13 @@ package at.procon.eventhub.processing.model; import at.procon.eventhub.reference.DriverCardNumberNormalizer; import java.time.OffsetDateTime; +import java.util.List; import java.util.Objects; import java.util.UUID; public record UnifiedDriverEventsRequest( UnifiedEventSourceFamily sourceFamily, + List sourceKinds, UUID sessionId, String driverKey, String tenantKey, @@ -22,6 +24,7 @@ public record UnifiedDriverEventsRequest( ) { public UnifiedDriverEventsRequest { Objects.requireNonNull(sourceFamily, "sourceFamily must not be null"); + sourceKinds = sourceKinds == null ? List.of() : List.copyOf(sourceKinds); driverKey = normalize(driverKey); tenantKey = normalize(tenantKey); driverSourceEntityId = normalize(driverSourceEntityId); @@ -59,6 +62,7 @@ public record UnifiedDriverEventsRequest( ) { return new UnifiedDriverEventsRequest( UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION, + List.of(), sessionId, driverKey, null, @@ -81,9 +85,30 @@ public record UnifiedDriverEventsRequest( String driverCardNumber, OffsetDateTime occurredFrom, OffsetDateTime occurredTo + ) { + return forTachographDbDriver( + tenantKey, + driverSourceEntityId, + driverCardNation, + driverCardNumber, + occurredFrom, + occurredTo, + List.of() + ); + } + + public static UnifiedDriverEventsRequest forTachographDbDriver( + String tenantKey, + String driverSourceEntityId, + String driverCardNation, + String driverCardNumber, + OffsetDateTime occurredFrom, + OffsetDateTime occurredTo, + List sourceKinds ) { return new UnifiedDriverEventsRequest( UnifiedEventSourceFamily.TACHOGRAPH_DB, + sourceKinds, null, null, tenantKey, @@ -110,6 +135,7 @@ public record UnifiedDriverEventsRequest( ) { return new UnifiedDriverEventsRequest( UnifiedEventSourceFamily.YELLOWFOX_DB, + List.of(), null, null, tenantKey, @@ -135,6 +161,7 @@ public record UnifiedDriverEventsRequest( ) { return new UnifiedDriverEventsRequest( UnifiedEventSourceFamily.YELLOWFOX_DB, + List.of(), null, null, tenantKey, diff --git a/src/main/java/at/procon/eventhub/processing/model/UnifiedRuntimeProcessingRequest.java b/src/main/java/at/procon/eventhub/processing/model/UnifiedRuntimeProcessingRequest.java index ff72a6c..0712977 100644 --- a/src/main/java/at/procon/eventhub/processing/model/UnifiedRuntimeProcessingRequest.java +++ b/src/main/java/at/procon/eventhub/processing/model/UnifiedRuntimeProcessingRequest.java @@ -3,6 +3,7 @@ package at.procon.eventhub.processing.model; import at.procon.eventhub.reference.DriverCardNumberNormalizer; import java.time.OffsetDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -15,6 +16,7 @@ public record UnifiedRuntimeProcessingRequest( String tenantKey, Set sourceFamilies, UnifiedRuntimeEventBackend eventBackend, + Set tachographSourceKinds, String driverKey, Set driverKeys, boolean includeAllDrivers, @@ -34,6 +36,7 @@ public record UnifiedRuntimeProcessingRequest( } sourceFamilies = Set.copyOf(sourceFamilies); eventBackend = eventBackend == null ? UnifiedRuntimeEventBackend.SOURCE_DB : eventBackend; + tachographSourceKinds = normalizeTachographSourceKinds(tachographSourceKinds); sessionIds = normalizeSessionIds(sessionId, sessionIds); if (sessionId == null && !sessionIds.isEmpty()) { sessionId = sessionIds.get(0); @@ -164,6 +167,7 @@ public record UnifiedRuntimeProcessingRequest( sourceFamilies, eventBackend, null, + null, Set.of(), false, Set.of(), @@ -197,6 +201,7 @@ public record UnifiedRuntimeProcessingRequest( sourceFamilies, UnifiedRuntimeEventBackend.EVENTHUB_DB, null, + null, Set.of(), false, Set.of(), @@ -226,6 +231,7 @@ public record UnifiedRuntimeProcessingRequest( null, Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), UnifiedRuntimeEventBackend.SOURCE_DB, + null, driverKey, Set.of(), false, @@ -256,6 +262,7 @@ public record UnifiedRuntimeProcessingRequest( null, Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), UnifiedRuntimeEventBackend.SOURCE_DB, + null, driverKey, Set.of(), false, @@ -286,6 +293,7 @@ public record UnifiedRuntimeProcessingRequest( null, Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), UnifiedRuntimeEventBackend.SOURCE_DB, + null, driverKey, Set.of(), false, @@ -328,6 +336,7 @@ public record UnifiedRuntimeProcessingRequest( tenantKey, sourceFamilies, eventBackend, + tachographSourceKinds, value, Set.of(), false, @@ -347,6 +356,23 @@ public record UnifiedRuntimeProcessingRequest( return includeAllDrivers || driverKeys.size() > 1 || (driverKey == null && !driverKeys.isEmpty()); } + public boolean includesTachographSourceKind(String sourceKind) { + if (sourceKind == null || sourceKind.isBlank()) { + return true; + } + try { + return tachographSourceKinds.contains(UnifiedTachographSourceKind.valueOf(sourceKind.trim().toUpperCase())); + } catch (IllegalArgumentException ex) { + return false; + } + } + + public List tachographSourceKindNames() { + return tachographSourceKinds.stream() + .map(UnifiedTachographSourceKind::name) + .toList(); + } + private static Set normalizeStrings(Set values) { if (values == null || values.isEmpty()) { return Set.of(); @@ -361,6 +387,15 @@ public record UnifiedRuntimeProcessingRequest( return Set.copyOf(normalized); } + private static Set normalizeTachographSourceKinds( + Set values + ) { + if (values == null || values.isEmpty()) { + return Set.copyOf(Arrays.asList(UnifiedTachographSourceKind.values())); + } + return Set.copyOf(values); + } + private static String normalize(String value) { return value == null || value.isBlank() ? null : value.trim(); } diff --git a/src/main/java/at/procon/eventhub/processing/model/UnifiedTachographSourceKind.java b/src/main/java/at/procon/eventhub/processing/model/UnifiedTachographSourceKind.java new file mode 100644 index 0000000..b52225e --- /dev/null +++ b/src/main/java/at/procon/eventhub/processing/model/UnifiedTachographSourceKind.java @@ -0,0 +1,6 @@ +package at.procon.eventhub.processing.model; + +public enum UnifiedTachographSourceKind { + DRIVER_CARD, + VEHICLE_UNIT +} diff --git a/src/main/java/at/procon/eventhub/processing/model/UnifiedVehicleEventsRequest.java b/src/main/java/at/procon/eventhub/processing/model/UnifiedVehicleEventsRequest.java index 9338c17..0240e85 100644 --- a/src/main/java/at/procon/eventhub/processing/model/UnifiedVehicleEventsRequest.java +++ b/src/main/java/at/procon/eventhub/processing/model/UnifiedVehicleEventsRequest.java @@ -1,11 +1,13 @@ package at.procon.eventhub.processing.model; import java.time.OffsetDateTime; +import java.util.List; import java.util.Objects; import java.util.UUID; public record UnifiedVehicleEventsRequest( UnifiedEventSourceFamily sourceFamily, + List sourceKinds, UUID sessionId, String tenantKey, String vehicleSourceEntityId, @@ -17,6 +19,7 @@ public record UnifiedVehicleEventsRequest( ) { public UnifiedVehicleEventsRequest { Objects.requireNonNull(sourceFamily, "sourceFamily must not be null"); + sourceKinds = sourceKinds == null ? List.of() : List.copyOf(sourceKinds); tenantKey = normalize(tenantKey); vehicleSourceEntityId = normalize(vehicleSourceEntityId); vin = normalizeUpper(vin); @@ -46,6 +49,7 @@ public record UnifiedVehicleEventsRequest( ) { return new UnifiedVehicleEventsRequest( UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION, + List.of(), sessionId, null, vehicleSourceEntityId, @@ -65,9 +69,32 @@ public record UnifiedVehicleEventsRequest( String registrationNumber, OffsetDateTime occurredFrom, OffsetDateTime occurredTo + ) { + return forTachographDb( + tenantKey, + vehicleSourceEntityId, + vin, + registrationNation, + registrationNumber, + occurredFrom, + occurredTo, + List.of() + ); + } + + public static UnifiedVehicleEventsRequest forTachographDb( + String tenantKey, + String vehicleSourceEntityId, + String vin, + String registrationNation, + String registrationNumber, + OffsetDateTime occurredFrom, + OffsetDateTime occurredTo, + List sourceKinds ) { return new UnifiedVehicleEventsRequest( UnifiedEventSourceFamily.TACHOGRAPH_DB, + sourceKinds, null, tenantKey, vehicleSourceEntityId, @@ -90,6 +117,7 @@ public record UnifiedVehicleEventsRequest( ) { return new UnifiedVehicleEventsRequest( UnifiedEventSourceFamily.YELLOWFOX_DB, + List.of(), null, tenantKey, vehicleSourceEntityId, diff --git a/src/main/java/at/procon/eventhub/processing/service/EventHubRuntimeEventLoader.java b/src/main/java/at/procon/eventhub/processing/service/EventHubRuntimeEventLoader.java index e684bca..91702e1 100644 --- a/src/main/java/at/procon/eventhub/processing/service/EventHubRuntimeEventLoader.java +++ b/src/main/java/at/procon/eventhub/processing/service/EventHubRuntimeEventLoader.java @@ -70,7 +70,8 @@ public class EventHubRuntimeEventLoader implements RuntimeDriverEventLoader, Run request.driverCardNation(), request.driverCardNumber(), request.occurredFrom(), - request.occurredTo() + request.occurredTo(), + request.tachographSourceKindNames() ); case YELLOWFOX_DB -> UnifiedDriverEventsRequest.forYellowFoxDbDriver( request.tenantKey(), @@ -97,7 +98,8 @@ public class EventHubRuntimeEventLoader implements RuntimeDriverEventLoader, Run vehicleRef.registrationNation(), vehicleRef.registrationNumber(), request.vehicleOccurredFrom(), - request.vehicleOccurredTo() + request.vehicleOccurredTo(), + request.tachographSourceKindNames() ); case YELLOWFOX_DB -> UnifiedVehicleEventsRequest.forYellowFoxDb( request.tenantKey(), diff --git a/src/main/java/at/procon/eventhub/processing/service/RuntimeDriverVehicleEvidenceAttachmentService.java b/src/main/java/at/procon/eventhub/processing/service/RuntimeDriverVehicleEvidenceAttachmentService.java index 6df4b19..1de2f49 100644 --- a/src/main/java/at/procon/eventhub/processing/service/RuntimeDriverVehicleEvidenceAttachmentService.java +++ b/src/main/java/at/procon/eventhub/processing/service/RuntimeDriverVehicleEvidenceAttachmentService.java @@ -8,6 +8,7 @@ import at.procon.eventhub.processing.dto.RuntimeVehicleUsageIntervalDebugDto; import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeClassifier; import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeType; import at.procon.eventhub.processing.model.RuntimeDriverVehicleEvidenceAttachmentResult; +import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver; import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline; import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval; import at.procon.eventhub.tachographfilesession.service.TachographDriverWorkingTimeAdapter; @@ -275,22 +276,15 @@ public class RuntimeDriverVehicleEvidenceAttachmentService { private Set vehicleKeys(EventHubEventDto event) { LinkedHashSet result = new LinkedHashSet<>(); - JsonNode raw = rawPayload(event); - add(result, text(raw, "vehicleKey")); - add(result, text(raw, "registrationKey")); - VehicleRefDto vehicleRef = event.vehicleRef(); - if (vehicleRef != null) { - add(result, vehicleRef.sourceVehicleEntityId()); - add(result, vehicleRef.sourceRegistrationEntityId()); - add(result, vehicleRef.vin()); - if (vehicleRef.vin() != null) { - add(result, "VIN:" + vehicleRef.vin()); - } - if (vehicleRef.vehicleRegistration() != null) { - String registrationKey = vehicleRef.vehicleRegistration().stableKey(); - add(result, registrationKey); - add(result, "VR:" + registrationKey); - } + String vehicleKey = TachographRuntimeIdentityResolver.vehicleKey(event); + String registrationKey = TachographRuntimeIdentityResolver.registrationKey(event); + add(result, vehicleKey); + add(result, registrationKey); + if (vehicleKey != null) { + add(result, "VIN:" + vehicleKey); + } + if (registrationKey != null) { + add(result, "VR:" + registrationKey); } return Set.copyOf(result); } diff --git a/src/main/java/at/procon/eventhub/processing/service/RuntimeDriverWorkingTimeScopeProcessingService.java b/src/main/java/at/procon/eventhub/processing/service/RuntimeDriverWorkingTimeScopeProcessingService.java index 8234a28..9aedd3a 100644 --- a/src/main/java/at/procon/eventhub/processing/service/RuntimeDriverWorkingTimeScopeProcessingService.java +++ b/src/main/java/at/procon/eventhub/processing/service/RuntimeDriverWorkingTimeScopeProcessingService.java @@ -1,6 +1,5 @@ package at.procon.eventhub.processing.service; -import at.procon.eventhub.dto.DriverRefDto; import at.procon.eventhub.dto.EventHubEventDto; import at.procon.eventhub.dto.VehicleRefDto; import at.procon.eventhub.processing.dto.RuntimeDriverPartitionDebugDto; @@ -11,6 +10,7 @@ import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef; import at.procon.eventhub.processing.model.RuntimeDriverVehicleEvidenceAttachmentResult; import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle; import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest; +import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver; import com.fasterxml.jackson.databind.JsonNode; import java.util.ArrayList; import java.util.Comparator; @@ -282,18 +282,7 @@ public class RuntimeDriverWorkingTimeScopeProcessingService { } private String driverKey(EventHubEventDto event) { - if (event == null) { - return null; - } - String rawDriverKey = text(rawPayload(event), "driverKey"); - if (rawDriverKey != null) { - return rawDriverKey; - } - DriverRefDto driverRef = event.driverRef(); - if (driverRef != null && driverRef.hasAnyReference()) { - return driverRef.stableKey(); - } - return null; + return TachographRuntimeIdentityResolver.driverKey(event); } private JsonNode rawPayload(EventHubEventDto event) { diff --git a/src/main/java/at/procon/eventhub/processing/service/TachographDbRuntimeEventLoader.java b/src/main/java/at/procon/eventhub/processing/service/TachographDbRuntimeEventLoader.java index 07dc1c0..22bee72 100644 --- a/src/main/java/at/procon/eventhub/processing/service/TachographDbRuntimeEventLoader.java +++ b/src/main/java/at/procon/eventhub/processing/service/TachographDbRuntimeEventLoader.java @@ -37,7 +37,6 @@ import org.springframework.stereotype.Component; import org.springframework.util.StreamUtils; @Component -@ConditionalOnBean(name = "tachographNamedParameterJdbcTemplate") @ConditionalOnExpression("T(org.springframework.util.StringUtils).hasText('${eventhub.tachograph.datasource.jdbc-url:}')") public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader, RuntimeVehicleEventLoader { @@ -64,7 +63,7 @@ public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader, @Override public List loadDriverEvents(UnifiedRuntimeProcessingRequest request) { List result = new ArrayList<>(); - for (ExtractionDefinition definition : driverDefinitions()) { + for (ExtractionDefinition definition : driverDefinitions(request)) { result.addAll(queryDefinition( definition, request, @@ -74,6 +73,7 @@ public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader, )); } return List.copyOf(result); + } @Override @@ -82,7 +82,7 @@ public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader, UnifiedDiscoveredVehicleRef vehicleRef ) { List result = new ArrayList<>(); - for (ExtractionDefinition definition : vehicleDefinitions()) { + for (ExtractionDefinition definition : vehicleDefinitions(request)) { result.addAll(queryDefinition( definition, request, @@ -116,15 +116,21 @@ public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader, return events; } - private List> driverDefinitions() { + private List> driverDefinitions( + UnifiedRuntimeProcessingRequest request + ) { return definitionRegistry.definitions().stream() .filter(definition -> !"VEHICLE".equals(definition.entityAxis())) + .filter(definition -> request.includesTachographSourceKind(definition.sourceKind())) .toList(); } - private List> vehicleDefinitions() { + private List> vehicleDefinitions( + UnifiedRuntimeProcessingRequest request + ) { return definitionRegistry.definitions().stream() .filter(definition -> !"DRIVER".equals(definition.entityAxis())) + .filter(definition -> request.includesTachographSourceKind(definition.sourceKind())) .toList(); } @@ -214,33 +220,31 @@ public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader, UnifiedRuntimeProcessingRequest request, UnifiedDiscoveredVehicleRef vehicleRef ) { - StringBuilder sql = new StringBuilder("select * from ("); - sql.append(baseSql); - sql.append("\n) runtime where 1 = 1"); + StringBuilder sql = new StringBuilder(baseSql.stripTrailing()); if (request.driverSourceEntityId() != null) { - sql.append("\n and runtime.driver_source_entity_id = :driverSourceEntityId"); + sql.append("\n and extracted.driver_source_entity_id = :driverSourceEntityId"); } if (request.driverCardNumber() != null) { - sql.append("\n and runtime.driver_card_number = :driverCardNumber"); + sql.append("\n and extracted.driver_card_number = :driverCardNumber"); if (request.driverCardNation() != null) { - sql.append("\n and runtime.driver_card_nation = :driverCardNation"); + sql.append("\n and extracted.driver_card_nation = :driverCardNation"); } } if (vehicleRef != null) { if (vehicleRef.sourceVehicleEntityId() != null) { - sql.append("\n and runtime.vehicle_source_entity_id = :vehicleSourceEntityId"); + sql.append("\n and extracted.vehicle_source_entity_id = :vehicleSourceEntityId"); } if (vehicleRef.vin() != null) { - sql.append("\n and runtime.vehicle_vin = :vehicleVin"); + sql.append("\n and extracted.vehicle_vin = :vehicleVin"); } if (vehicleRef.registrationNumber() != null) { - sql.append("\n and runtime.vehicle_registration_number = :vehicleRegistrationNumber"); + sql.append("\n and extracted.vehicle_registration_number = :vehicleRegistrationNumber"); if (vehicleRef.registrationNation() != null) { - sql.append("\n and runtime.vehicle_registration_nation = :vehicleRegistrationNation"); + sql.append("\n and extracted.vehicle_registration_nation = :vehicleRegistrationNation"); } } } - sql.append("\norder by runtime.occurred_at, runtime.external_source_event_id"); + sql.append("\norder by extracted.occurred_at, extracted.external_source_event_id"); return sql.toString(); } diff --git a/src/main/java/at/procon/eventhub/processing/service/TachographDbUnifiedDriverEventSource.java b/src/main/java/at/procon/eventhub/processing/service/TachographDbUnifiedDriverEventSource.java index 79bce1c..3f6c53f 100644 --- a/src/main/java/at/procon/eventhub/processing/service/TachographDbUnifiedDriverEventSource.java +++ b/src/main/java/at/procon/eventhub/processing/service/TachographDbUnifiedDriverEventSource.java @@ -25,6 +25,7 @@ public class TachographDbUnifiedDriverEventSource implements UnifiedDriverEventS @Override public List loadDriverEvents(UnifiedDriverEventsRequest request) { - return repository.findEvents(request, "TACHOGRAPH", SOURCE_KINDS); + List sourceKinds = request.sourceKinds().isEmpty() ? SOURCE_KINDS : request.sourceKinds(); + return repository.findEvents(request, "TACHOGRAPH", sourceKinds); } } diff --git a/src/main/java/at/procon/eventhub/processing/service/TachographDbUnifiedVehicleEventSource.java b/src/main/java/at/procon/eventhub/processing/service/TachographDbUnifiedVehicleEventSource.java index 0a85dc8..845a2d8 100644 --- a/src/main/java/at/procon/eventhub/processing/service/TachographDbUnifiedVehicleEventSource.java +++ b/src/main/java/at/procon/eventhub/processing/service/TachographDbUnifiedVehicleEventSource.java @@ -25,6 +25,7 @@ public class TachographDbUnifiedVehicleEventSource implements UnifiedVehicleEven @Override public List loadVehicleEvents(UnifiedVehicleEventsRequest request) { - return repository.findEventsByVehicle(request, "TACHOGRAPH", SOURCE_KINDS); + List sourceKinds = request.sourceKinds().isEmpty() ? SOURCE_KINDS : request.sourceKinds(); + return repository.findEventsByVehicle(request, "TACHOGRAPH", sourceKinds); } } diff --git a/src/main/java/at/procon/eventhub/processing/service/UnifiedEventTimelineReconstructor.java b/src/main/java/at/procon/eventhub/processing/service/UnifiedEventTimelineReconstructor.java index d59bf5e..6e1e1ab 100644 --- a/src/main/java/at/procon/eventhub/processing/service/UnifiedEventTimelineReconstructor.java +++ b/src/main/java/at/procon/eventhub/processing/service/UnifiedEventTimelineReconstructor.java @@ -4,6 +4,7 @@ import at.procon.eventhub.dto.EventDomain; import at.procon.eventhub.dto.EventHubEventDto; import at.procon.eventhub.dto.EventLifecycle; import at.procon.eventhub.dto.EventType; +import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver; import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent; import at.procon.eventhub.tachographfilesession.model.ExtractionWarning; import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval; @@ -136,14 +137,14 @@ public class UnifiedEventTimelineReconstructor { BigDecimal longitude = event.position() == null ? null : event.position().longitude(); result.add(new ExtractedSupportEvent( eventId, - text(raw, "driverKey"), + TachographRuntimeIdentityResolver.driverKey(event), event.occurredAt(), event.eventDomain().name(), text(raw, "supportEventType") == null ? event.eventType().name() : text(raw, "supportEventType"), event.lifecycle().name(), text(raw, "slot"), - text(raw, "registrationKey"), - text(raw, "vehicleKey"), + TachographRuntimeIdentityResolver.registrationKey(event), + TachographRuntimeIdentityResolver.vehicleKey(event), firstNonBlank(text(raw, "country"), detailText(event, "country")), text(raw, "region"), firstNonBlank(text(raw, "countryFrom"), detailText(event, "countryFrom")), @@ -381,8 +382,8 @@ public class UnifiedEventTimelineReconstructor { detailText(sample, "cardSlot"), detailText(sample, "cardStatus"), detailText(sample, "drivingStatus"), - text(raw, "registrationKey"), - text(raw, "vehicleKey"), + TachographRuntimeIdentityResolver.registrationKey(sample), + TachographRuntimeIdentityResolver.vehicleKey(sample), text(raw, "sourceKind"), stringList(raw, "sourceRowIds"), booleanValue(raw, "synthetic"), @@ -400,6 +401,7 @@ public class UnifiedEventTimelineReconstructor { private OffsetDateTime endedAt; private Long odometerBeginKm; private Long odometerEndKm; + private EventHubEventDto sample; private JsonNode raw; private VehicleUsageAccumulator(UUID sessionId, String driverKey, String intervalId) { @@ -409,6 +411,9 @@ public class UnifiedEventTimelineReconstructor { } private void accept(EventHubEventDto event, JsonNode raw) { + if (sample == null) { + sample = event; + } if (this.raw == null) { this.raw = raw; } @@ -433,8 +438,8 @@ public class UnifiedEventTimelineReconstructor { endedAt, odometerBeginKm, odometerEndKm, - text(raw, "registrationKey"), - text(raw, "vehicleKey"), + sample == null ? null : TachographRuntimeIdentityResolver.registrationKey(sample), + sample == null ? null : TachographRuntimeIdentityResolver.vehicleKey(sample), text(raw, "sourceKind"), stringList(raw, "sourceRowIds") ); 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 1139b7a..4aa1729 100644 --- a/src/main/java/at/procon/eventhub/processing/service/UnifiedRuntimeDerivedProjectionService.java +++ b/src/main/java/at/procon/eventhub/processing/service/UnifiedRuntimeDerivedProjectionService.java @@ -31,6 +31,7 @@ import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialIn import at.procon.eventhub.tachographfilesession.model.TachographEsperSupportGeoEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent; +import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver; import java.time.Duration; import java.time.OffsetDateTime; import java.time.ZoneOffset; @@ -234,22 +235,7 @@ public class UnifiedRuntimeDerivedProjectionService { UnifiedRuntimeProcessingRequest request, List events ) { - if (request.driverKey() != null) { - return request.driverKey(); - } - if (request.driverSourceEntityId() != null) { - return request.driverSourceEntityId(); - } - for (EventHubEventDto event : events) { - DriverRefDto driverRef = event.driverRef(); - if (driverRef != null && driverRef.hasAnyReference()) { - return driverRef.stableKey(); - } - } - if (request.driverCardNation() != null && request.driverCardNumber() != null) { - return request.driverCardNation() + ":" + request.driverCardNumber(); - } - return request.driverCardNumber(); + return TachographRuntimeIdentityResolver.requestDriverKey(request, events); } diff --git a/src/main/java/at/procon/eventhub/processing/service/UnifiedRuntimeDriverTimelineService.java b/src/main/java/at/procon/eventhub/processing/service/UnifiedRuntimeDriverTimelineService.java index a8b96f7..c85f20e 100644 --- a/src/main/java/at/procon/eventhub/processing/service/UnifiedRuntimeDriverTimelineService.java +++ b/src/main/java/at/procon/eventhub/processing/service/UnifiedRuntimeDriverTimelineService.java @@ -1,9 +1,9 @@ package at.procon.eventhub.processing.service; -import at.procon.eventhub.dto.DriverRefDto; import at.procon.eventhub.dto.EventHubEventDto; import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle; import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest; +import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver; import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline; import java.util.List; import org.springframework.stereotype.Service; @@ -35,21 +35,6 @@ public class UnifiedRuntimeDriverTimelineService { UnifiedRuntimeProcessingRequest request, List events ) { - if (request.driverKey() != null) { - return request.driverKey(); - } - if (request.driverSourceEntityId() != null) { - return request.driverSourceEntityId(); - } - for (EventHubEventDto event : events) { - DriverRefDto driverRef = event.driverRef(); - if (driverRef != null && driverRef.hasAnyReference()) { - return driverRef.stableKey(); - } - } - if (request.driverCardNation() != null && request.driverCardNumber() != null) { - return request.driverCardNation() + ":" + request.driverCardNumber(); - } - return request.driverCardNumber(); + return TachographRuntimeIdentityResolver.requestDriverKey(request, events); } } diff --git a/src/main/java/at/procon/eventhub/processing/support/TachographRuntimeIdentityResolver.java b/src/main/java/at/procon/eventhub/processing/support/TachographRuntimeIdentityResolver.java new file mode 100644 index 0000000..62cbbbb --- /dev/null +++ b/src/main/java/at/procon/eventhub/processing/support/TachographRuntimeIdentityResolver.java @@ -0,0 +1,144 @@ +package at.procon.eventhub.processing.support; + +import at.procon.eventhub.dto.DriverCardRefDto; +import at.procon.eventhub.dto.DriverRefDto; +import at.procon.eventhub.dto.EventHubEventDto; +import at.procon.eventhub.dto.VehicleRefDto; +import at.procon.eventhub.dto.VehicleRegistrationRefDto; +import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest; +import com.fasterxml.jackson.databind.JsonNode; +import java.util.List; + +public final class TachographRuntimeIdentityResolver { + + private TachographRuntimeIdentityResolver() { + } + + public static String driverKey(EventHubEventDto event) { + JsonNode raw = rawPayload(event); + String rawDriverKey = firstNonBlank(text(raw, "driverKey"), text(raw, "driverCardKey")); + if (rawDriverKey != null) { + return rawDriverKey; + } + DriverRefDto driverRef = event == null ? null : event.driverRef(); + DriverCardRefDto driverCard = driverRef == null ? null : driverRef.driverCard(); + return driverCard != null && driverCard.hasValue() ? normalize(driverCard.stableKey()) : null; + } + + public static String registrationKey(EventHubEventDto event) { + JsonNode raw = rawPayload(event); + String rawRegistrationKey = canonicalRegistrationKey(text(raw, "registrationKey")); + if (rawRegistrationKey != null) { + return rawRegistrationKey; + } + VehicleRefDto vehicleRef = event == null ? null : event.vehicleRef(); + VehicleRegistrationRefDto registration = vehicleRef == null ? null : vehicleRef.vehicleRegistration(); + return registration != null && registration.hasValue() ? normalize(registration.stableKey()) : null; + } + + public static String vehicleKey(EventHubEventDto event) { + JsonNode raw = rawPayload(event); + String rawVehicleVin = canonicalVehicleKey(text(raw, "vehicleVin")); + if (rawVehicleVin != null) { + return rawVehicleVin; + } + String rawVehicleKey = canonicalVehicleKey(text(raw, "vehicleKey")); + if (rawVehicleKey != null) { + return rawVehicleKey; + } + VehicleRefDto vehicleRef = event == null ? null : event.vehicleRef(); + return vehicleRef == null ? null : canonicalVehicleKey(vehicleRef.vin()); + } + + public static String requestDriverKey( + UnifiedRuntimeProcessingRequest request, + List events + ) { + if (request == null) { + return firstEventDriverKey(events); + } + if (request.driverKey() != null) { + return request.driverKey(); + } + if (request.driverCardNation() != null && request.driverCardNumber() != null) { + return request.driverCardNation() + ":" + request.driverCardNumber(); + } + String eventDriverKey = firstEventDriverKey(events); + if (eventDriverKey != null) { + return eventDriverKey; + } + return request.driverCardNumber(); + } + + public static JsonNode rawPayload(EventHubEventDto event) { + if (event == null || event.payload() == null || event.payload().isNull() || event.payload().isMissingNode()) { + return null; + } + JsonNode raw = event.payload().get("raw"); + return raw == null || raw.isNull() ? event.payload() : raw; + } + + public static String text(JsonNode node, String field) { + if (node == null || field == null) { + return null; + } + JsonNode value = node.get(field); + if (value == null || value.isNull()) { + return null; + } + String text = value.asText(null); + return text == null || text.isBlank() ? null : text.trim(); + } + + private static String firstEventDriverKey(List events) { + for (EventHubEventDto event : events == null ? List.of() : events) { + String driverKey = driverKey(event); + if (driverKey != null) { + return driverKey; + } + } + return null; + } + + private static String canonicalVehicleKey(String value) { + String normalized = normalize(value); + if (normalized == null) { + return null; + } + if (normalized.startsWith("VIN:")) { + return normalize(normalized.substring("VIN:".length())); + } + if (normalized.startsWith("SOURCE_VEHICLE:") + || normalized.startsWith("REG:") + || normalized.startsWith("VR:") + || normalized.contains("|")) { + return null; + } + return normalized.toUpperCase(); + } + + private static String canonicalRegistrationKey(String value) { + String normalized = normalize(value); + if (normalized == null) { + return null; + } + return normalized.startsWith("VR:") ? normalize(normalized.substring("VR:".length())) : normalized; + } + + private static String firstNonBlank(String... values) { + if (values == null) { + return null; + } + for (String value : values) { + String normalized = normalize(value); + if (normalized != null) { + return normalized; + } + } + return null; + } + + private static String normalize(String value) { + return value == null || value.isBlank() ? null : value.trim(); + } +} diff --git a/src/main/java/at/procon/eventhub/tachograph/service/TachographRawPayloadSupport.java b/src/main/java/at/procon/eventhub/tachograph/service/TachographRawPayloadSupport.java index 5f75550..6c928f7 100644 --- a/src/main/java/at/procon/eventhub/tachograph/service/TachographRawPayloadSupport.java +++ b/src/main/java/at/procon/eventhub/tachograph/service/TachographRawPayloadSupport.java @@ -77,7 +77,7 @@ final class TachographRawPayloadSupport { if (vehicleRef == null || !vehicleRef.hasAnyReference()) { return; } - put(raw, "vehicleKey", vehicleRef.stableKey()); + put(raw, "vehicleKey", vehicleRef.vin()); put(raw, "vehicleSourceEntityId", vehicleRef.sourceVehicleEntityId()); put(raw, "vehicleVin", vehicleRef.vin()); put(raw, "vehicleRegistrationSourceEntityId", vehicleRef.sourceRegistrationEntityId()); diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverCardXmlExtractionService.java b/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverCardXmlExtractionService.java index 09bcca6..4fe56d0 100644 --- a/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverCardXmlExtractionService.java +++ b/src/main/java/at/procon/eventhub/tachographfilesession/service/DriverCardXmlExtractionService.java @@ -257,9 +257,11 @@ public class DriverCardXmlExtractionService { )); } parsedChanges.sort(Comparator.comparing(ActivityChange::from)); - for (int i = 0; i + 1 < parsedChanges.size(); i++) { + for (int i = 0; i < parsedChanges.size(); i++) { ActivityChange current = parsedChanges.get(i); - OffsetDateTime to = parsedChanges.get(i + 1).from(); + OffsetDateTime to = i + 1 < parsedChanges.size() + ? parsedChanges.get(i + 1).from() + : OffsetDateTime.of(date.plusDays(1), LocalTime.MIDNIGHT, ZoneOffset.UTC); if (!current.from().isBefore(to)) { continue; } diff --git a/src/main/java/at/procon/eventhub/tachographfilesession/service/VehicleUnitXmlExtractionService.java b/src/main/java/at/procon/eventhub/tachographfilesession/service/VehicleUnitXmlExtractionService.java index a8f8c91..1563b3c 100644 --- a/src/main/java/at/procon/eventhub/tachographfilesession/service/VehicleUnitXmlExtractionService.java +++ b/src/main/java/at/procon/eventhub/tachographfilesession/service/VehicleUnitXmlExtractionService.java @@ -282,9 +282,11 @@ public class VehicleUnitXmlExtractionService { )); } parsedChanges.sort(Comparator.comparing(ActivityChange::from)); - for (int i = 0; i + 1 < parsedChanges.size(); i++) { + for (int i = 0; i < parsedChanges.size(); i++) { ActivityChange current = parsedChanges.get(i); - OffsetDateTime to = parsedChanges.get(i + 1).from(); + OffsetDateTime to = i + 1 < parsedChanges.size() + ? parsedChanges.get(i + 1).from() + : OffsetDateTime.of(date.plusDays(1), LocalTime.MIDNIGHT, ZoneOffset.UTC); if (!current.from().isBefore(to)) { continue; } diff --git a/src/test/java/at/procon/eventhub/processing/eventprocessing/RuntimeEventProcessingServiceTest.java b/src/test/java/at/procon/eventhub/processing/eventprocessing/RuntimeEventProcessingServiceTest.java index f622513..a0db82e 100644 --- a/src/test/java/at/procon/eventhub/processing/eventprocessing/RuntimeEventProcessingServiceTest.java +++ b/src/test/java/at/procon/eventhub/processing/eventprocessing/RuntimeEventProcessingServiceTest.java @@ -35,6 +35,7 @@ class RuntimeEventProcessingServiceTest { null, Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), null, + null, "12:DRIVER-1", Set.of(), false, diff --git a/src/test/java/at/procon/eventhub/processing/eventprocessing/module/epl/DriverWorkingTimeEplEventMapperTest.java b/src/test/java/at/procon/eventhub/processing/eventprocessing/module/epl/DriverWorkingTimeEplEventMapperTest.java new file mode 100644 index 0000000..ea638f3 --- /dev/null +++ b/src/test/java/at/procon/eventhub/processing/eventprocessing/module/epl/DriverWorkingTimeEplEventMapperTest.java @@ -0,0 +1,94 @@ +package at.procon.eventhub.processing.eventprocessing.module.epl; + +import static org.assertj.core.api.Assertions.assertThat; + +import at.procon.eventhub.dto.DriverCardRefDto; +import at.procon.eventhub.dto.DriverRefDto; +import at.procon.eventhub.dto.EventDomain; +import at.procon.eventhub.dto.EventHubEventDto; +import at.procon.eventhub.dto.EventLifecycle; +import at.procon.eventhub.dto.EventType; +import at.procon.eventhub.dto.VehicleRefDto; +import at.procon.eventhub.dto.VehicleRegistrationRefDto; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +class DriverWorkingTimeEplEventMapperTest { + + @Test + void usesCanonicalTachographKeysInsteadOfSourceSpecificRawVehicleIdentifiers() { + EventHubEventDto activityEvent = event( + EventDomain.DRIVER_ACTIVITY, + EventType.DRIVE, + EventLifecycle.START, + "ACT-1", + "2026-05-01T08:00:00Z" + ); + EventHubEventDto cardEvent = event( + EventDomain.DRIVER_CARD, + EventType.CARD_INSERTED, + EventLifecycle.INSERT, + "CVU-1", + "2026-05-01T08:00:00Z" + ); + + Map activityPoint = DriverWorkingTimeEplEventMapper.activityPointEvents(List.of(activityEvent)).getFirst(); + Map vehicleUsagePoint = DriverWorkingTimeEplEventMapper.vehicleUsagePointEvents(List.of(cardEvent)).getFirst(); + + assertThat(activityPoint.get("driverKey")).isEqualTo("1:12345678901234"); + assertThat(activityPoint.get("registrationKey")).isEqualTo("1:LL-158TE"); + assertThat(activityPoint.get("vehicleKey")).isEqualTo("VIN123456789"); + + assertThat(vehicleUsagePoint.get("driverKey")).isEqualTo("1:12345678901234"); + assertThat(vehicleUsagePoint.get("registrationKey")).isEqualTo("1:LL-158TE"); + assertThat(vehicleUsagePoint.get("vehicleKey")).isEqualTo("VIN123456789"); + } + + private EventHubEventDto event( + EventDomain domain, + EventType eventType, + EventLifecycle lifecycle, + String intervalId, + String occurredAt + ) { + ObjectNode raw = JsonNodeFactory.instance.objectNode(); + raw.put("intervalId", intervalId); + raw.put("sourceRowId", intervalId); + raw.put("driverKey", "1:12345678901234"); + raw.put("vehicleKey", "3560|VIN123456789|9876|1:LL-158TE"); + raw.put("vehicleVin", "VIN123456789"); + raw.put("registrationKey", "1:LL-158TE"); + raw.put("sourceKind", "DRIVER_CARD"); + ObjectNode payload = JsonNodeFactory.instance.objectNode(); + payload.set("raw", raw); + return new EventHubEventDto( + UUID.randomUUID(), + intervalId + ":" + lifecycle.name(), + new DriverRefDto("3560", new DriverCardRefDto("1", 1, "12345678901234")), + new VehicleRefDto( + "3560", + "VIN123456789", + "9876", + new VehicleRegistrationRefDto("1", 1, "LL-158TE") + ), + OffsetDateTime.parse(occurredAt), + null, + OffsetDateTime.parse(occurredAt), + domain, + eventType, + lifecycle, + null, + null, + null, + null, + payload, + false, + null + ); + } +} diff --git a/src/test/java/at/procon/eventhub/processing/eventprocessing/profile/TachographDriverEsperRuntimeEventProcessingProfileTest.java b/src/test/java/at/procon/eventhub/processing/eventprocessing/profile/TachographDriverEsperRuntimeEventProcessingProfileTest.java index 47dc511..d96f61d 100644 --- a/src/test/java/at/procon/eventhub/processing/eventprocessing/profile/TachographDriverEsperRuntimeEventProcessingProfileTest.java +++ b/src/test/java/at/procon/eventhub/processing/eventprocessing/profile/TachographDriverEsperRuntimeEventProcessingProfileTest.java @@ -58,6 +58,7 @@ class TachographDriverEsperRuntimeEventProcessingProfileTest { Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), null, null, + null, Set.of(), false, Set.of(), @@ -103,6 +104,7 @@ class TachographDriverEsperRuntimeEventProcessingProfileTest { null, Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), null, + null, "12:DRIVER-1", Set.of("12:DRIVER-1"), false, diff --git a/src/test/java/at/procon/eventhub/processing/eventprocessing/validation/RuntimeMixedSourceEvidenceValidationServiceTest.java b/src/test/java/at/procon/eventhub/processing/eventprocessing/validation/RuntimeMixedSourceEvidenceValidationServiceTest.java index 2ab2092..d30af0d 100644 --- a/src/test/java/at/procon/eventhub/processing/eventprocessing/validation/RuntimeMixedSourceEvidenceValidationServiceTest.java +++ b/src/test/java/at/procon/eventhub/processing/eventprocessing/validation/RuntimeMixedSourceEvidenceValidationServiceTest.java @@ -86,6 +86,7 @@ class RuntimeMixedSourceEvidenceValidationServiceTest { "default", Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), UnifiedRuntimeEventBackend.SOURCE_DB, + null, "DRIVER:1", Set.of(), false, diff --git a/src/test/java/at/procon/eventhub/processing/model/UnifiedDriverEventsRequestTest.java b/src/test/java/at/procon/eventhub/processing/model/UnifiedDriverEventsRequestTest.java index 5cfb043..75a2fd4 100644 --- a/src/test/java/at/procon/eventhub/processing/model/UnifiedDriverEventsRequestTest.java +++ b/src/test/java/at/procon/eventhub/processing/model/UnifiedDriverEventsRequestTest.java @@ -72,6 +72,7 @@ class UnifiedDriverEventsRequestTest { UnifiedEventSourceFamily.TACHOGRAPH_DB, null, null, + null, "default", null, null, diff --git a/src/test/java/at/procon/eventhub/processing/model/UnifiedRuntimeProcessingRequestTest.java b/src/test/java/at/procon/eventhub/processing/model/UnifiedRuntimeProcessingRequestTest.java index 68acd4e..11eeea9 100644 --- a/src/test/java/at/procon/eventhub/processing/model/UnifiedRuntimeProcessingRequestTest.java +++ b/src/test/java/at/procon/eventhub/processing/model/UnifiedRuntimeProcessingRequestTest.java @@ -54,6 +54,52 @@ class UnifiedRuntimeProcessingRequestTest { assertThat(request.vehicleExpansionPaddingMinutes()).isEqualTo(30); } + @Test + void defaultsToBothTachographSourceKindsAndCanRestrictToOne() { + UnifiedRuntimeProcessingRequest defaultRequest = UnifiedRuntimeProcessingRequest.forDriver( + "default", + Set.of(UnifiedEventSourceFamily.TACHOGRAPH_DB), + "DRIVER:42", + null, + null, + OffsetDateTime.parse("2026-05-01T00:00:00Z"), + OffsetDateTime.parse("2026-05-02T00:00:00Z") + ); + + assertThat(defaultRequest.tachographSourceKinds()).containsExactlyInAnyOrder( + UnifiedTachographSourceKind.DRIVER_CARD, + UnifiedTachographSourceKind.VEHICLE_UNIT + ); + assertThat(defaultRequest.includesTachographSourceKind("DRIVER_CARD")).isTrue(); + assertThat(defaultRequest.includesTachographSourceKind("VEHICLE_UNIT")).isTrue(); + + UnifiedRuntimeProcessingRequest driverCardOnlyRequest = new UnifiedRuntimeProcessingRequest( + null, + List.of(), + null, + "default", + Set.of(UnifiedEventSourceFamily.TACHOGRAPH_DB), + UnifiedRuntimeEventBackend.SOURCE_DB, + Set.of(UnifiedTachographSourceKind.DRIVER_CARD), + null, + Set.of(), + false, + Set.of(), + false, + "DRIVER:42", + null, + null, + OffsetDateTime.parse("2026-05-01T00:00:00Z"), + OffsetDateTime.parse("2026-05-02T00:00:00Z"), + true, + 0 + ); + + assertThat(driverCardOnlyRequest.tachographSourceKinds()).containsExactly(UnifiedTachographSourceKind.DRIVER_CARD); + assertThat(driverCardOnlyRequest.includesTachographSourceKind("DRIVER_CARD")).isTrue(); + assertThat(driverCardOnlyRequest.includesTachographSourceKind("VEHICLE_UNIT")).isFalse(); + } + @Test void canBuildEventHubBackedRuntimeRequest() { UnifiedRuntimeProcessingRequest request = UnifiedRuntimeProcessingRequest.forDriverFromEventHub( @@ -138,6 +184,7 @@ class UnifiedRuntimeProcessingRequestTest { Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), UnifiedRuntimeEventBackend.SOURCE_DB, null, + null, Set.of("12:123", "12:456"), false, Set.of(), @@ -166,6 +213,7 @@ class UnifiedRuntimeProcessingRequestTest { Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), UnifiedRuntimeEventBackend.SOURCE_DB, null, + null, Set.of(), true, Set.of(), @@ -194,6 +242,7 @@ class UnifiedRuntimeProcessingRequestTest { null, Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), UnifiedRuntimeEventBackend.SOURCE_DB, + null, "12:123", Set.of(), false, @@ -221,6 +270,7 @@ class UnifiedRuntimeProcessingRequestTest { Set.of(UnifiedEventSourceFamily.TACHOGRAPH_DB), UnifiedRuntimeEventBackend.SOURCE_DB, null, + null, Set.of(), false, Set.of(), diff --git a/src/test/java/at/procon/eventhub/processing/model/UnifiedVehicleEventsRequestTest.java b/src/test/java/at/procon/eventhub/processing/model/UnifiedVehicleEventsRequestTest.java index cc08da1..3f2b7f6 100644 --- a/src/test/java/at/procon/eventhub/processing/model/UnifiedVehicleEventsRequestTest.java +++ b/src/test/java/at/procon/eventhub/processing/model/UnifiedVehicleEventsRequestTest.java @@ -56,6 +56,7 @@ class UnifiedVehicleEventsRequestTest { assertThatThrownBy(() -> new UnifiedVehicleEventsRequest( UnifiedEventSourceFamily.YELLOWFOX_DB, null, + null, "default", null, null, diff --git a/src/test/java/at/procon/eventhub/processing/service/RuntimeDriverVehicleEvidenceAttachmentServiceTest.java b/src/test/java/at/procon/eventhub/processing/service/RuntimeDriverVehicleEvidenceAttachmentServiceTest.java index e2e4094..db14922 100644 --- a/src/test/java/at/procon/eventhub/processing/service/RuntimeDriverVehicleEvidenceAttachmentServiceTest.java +++ b/src/test/java/at/procon/eventhub/processing/service/RuntimeDriverVehicleEvidenceAttachmentServiceTest.java @@ -148,6 +148,53 @@ class RuntimeDriverVehicleEvidenceAttachmentServiceTest { assertThat(result.toPartitionDebug().vehicleEvidenceDecisions()).hasSameSizeAs(result.vehicleEvidenceDecisions()); } + @Test + void attachesEvidenceUsingCanonicalVinWhenDbRawVehicleKeyIsSourceSpecific() { + List driverEvents = List.of( + cardEventWithRawVehicleKey( + "card-1-in", + EventType.CARD_INSERTED, + "1:12345678901234", + "interval-1", + "SOURCE-VEH-1|VIN-1|SOURCE-REG-1|AT:W-1", + "VIN-1", + "AT:W-1", + "2026-05-01T08:00:00Z" + ), + cardEventWithRawVehicleKey( + "card-1-out", + EventType.CARD_WITHDRAWN, + "1:12345678901234", + "interval-1", + "SOURCE-VEH-1|VIN-1|SOURCE-REG-1|AT:W-1", + "VIN-1", + "AT:W-1", + "2026-05-01T18:00:00Z" + ) + ); + EventHubEventDto vehicleEvidence = vehicleOnlyEventWithRawVehicleKey( + "pos-inside", + "VIN-1", + "VIN-1", + null, + "2026-05-01T12:00:00Z" + ); + + RuntimeDriverVehicleEvidenceAttachmentResult result = service.attachVehicleEvidence( + "1:12345678901234", + driverEvents, + List.of(driverEvents.get(0), driverEvents.get(1), vehicleEvidence), + true, + 0 + ); + + assertThat(result.vehicleUsageIntervalCount()).isEqualTo(1); + assertThat(result.attachedVehicleEvidenceEvents()).extracting(EventHubEventDto::externalSourceEventId) + .containsExactly("pos-inside"); + assertThat(result.mergedEvents()).extracting(EventHubEventDto::externalSourceEventId) + .containsExactly("card-1-in", "pos-inside", "card-1-out"); + } + private EventHubEventDto cardEvent( String externalId, EventType eventType, @@ -179,6 +226,38 @@ class RuntimeDriverVehicleEvidenceAttachmentServiceTest { ); } + private EventHubEventDto cardEventWithRawVehicleKey( + String externalId, + EventType eventType, + String driverKey, + String intervalId, + String rawVehicleKey, + String vin, + String registrationKey, + String occurredAt + ) { + EventLifecycle lifecycle = eventType == EventType.CARD_INSERTED ? EventLifecycle.INSERT : EventLifecycle.WITHDRAW; + return new EventHubEventDto( + UUID.randomUUID(), + externalId, + new DriverRefDto(driverKey, null), + vehicleRef(vin, registrationKey), + OffsetDateTime.parse(occurredAt), + null, + OffsetDateTime.parse(occurredAt), + EventDomain.DRIVER_CARD, + eventType, + lifecycle, + null, + null, + null, + null, + raw(driverKey, intervalId, rawVehicleKey, vin, registrationKey), + false, + null + ); + } + private EventHubEventDto vehicleOnlyEvent( String externalId, String vehicleKey, @@ -206,17 +285,55 @@ class RuntimeDriverVehicleEvidenceAttachmentServiceTest { ); } + private EventHubEventDto vehicleOnlyEventWithRawVehicleKey( + String externalId, + String rawVehicleKey, + String vin, + String registrationKey, + String occurredAt + ) { + return new EventHubEventDto( + UUID.randomUUID(), + externalId, + null, + vehicleRef(vin, registrationKey), + OffsetDateTime.parse(occurredAt), + null, + OffsetDateTime.parse(occurredAt), + EventDomain.POSITION, + EventType.POSITION_RECORDED, + EventLifecycle.SNAPSHOT, + null, + null, + null, + null, + raw(null, null, rawVehicleKey, vin, registrationKey), + false, + null + ); + } + private VehicleRefDto vehicleRef(String vehicleKey, String registrationKey) { - String[] registrationParts = registrationKey.split(":", 2); + String[] registrationParts = registrationKey == null ? null : registrationKey.split(":", 2); return new VehicleRefDto( "VIN:" + vehicleKey, vehicleKey, - "VR:" + registrationKey, - new VehicleRegistrationRefDto(registrationParts[0], registrationParts[1]) + registrationKey == null ? null : "VR:" + registrationKey, + registrationParts == null ? null : new VehicleRegistrationRefDto(registrationParts[0], registrationParts[1]) ); } private JsonNode raw(String driverKey, String intervalId, String vehicleKey, String registrationKey) { + return raw(driverKey, intervalId, vehicleKey, vehicleKey, registrationKey); + } + + private JsonNode raw( + String driverKey, + String intervalId, + String rawVehicleKey, + String vehicleVin, + String registrationKey + ) { ObjectNode root = OBJECT_MAPPER.createObjectNode(); ObjectNode raw = root.putObject("raw"); if (driverKey != null) { @@ -226,8 +343,15 @@ class RuntimeDriverVehicleEvidenceAttachmentServiceTest { raw.put("intervalId", intervalId); raw.put("sourceRowId", intervalId); } - raw.put("vehicleKey", vehicleKey); - raw.put("registrationKey", registrationKey); + if (rawVehicleKey != null) { + raw.put("vehicleKey", rawVehicleKey); + } + if (vehicleVin != null) { + raw.put("vehicleVin", vehicleVin); + } + if (registrationKey != null) { + raw.put("registrationKey", registrationKey); + } raw.put("sourceKind", "TEST"); return root; } diff --git a/src/test/java/at/procon/eventhub/processing/service/UnifiedRuntimeDriverTimelineServiceTest.java b/src/test/java/at/procon/eventhub/processing/service/UnifiedRuntimeDriverTimelineServiceTest.java index d6744be..683f8fe 100644 --- a/src/test/java/at/procon/eventhub/processing/service/UnifiedRuntimeDriverTimelineServiceTest.java +++ b/src/test/java/at/procon/eventhub/processing/service/UnifiedRuntimeDriverTimelineServiceTest.java @@ -2,6 +2,7 @@ package at.procon.eventhub.processing.service; import static org.assertj.core.api.Assertions.assertThat; +import at.procon.eventhub.dto.DriverCardRefDto; import at.procon.eventhub.dto.DriverRefDto; import at.procon.eventhub.dto.EventDomain; import at.procon.eventhub.dto.EventHubEventDto; @@ -48,6 +49,7 @@ class UnifiedRuntimeDriverTimelineServiceTest { Set.of(UnifiedEventSourceFamily.TACHOGRAPH_DB), UnifiedRuntimeEventBackend.SOURCE_DB, null, + null, Set.of(), false, Set.of(), @@ -68,6 +70,7 @@ class UnifiedRuntimeDriverTimelineServiceTest { assertThat(timeline.activityIntervals()).hasSize(1); assertThat(timeline.activityIntervals().get(0).intervalId()).isEqualTo("ACT-1"); assertThat(timeline.vehicleUsageIntervals()).hasSize(1); + assertThat(timeline.vehicleUsageIntervals().get(0).driverKey()).isEqualTo("12:12345678901234"); assertThat(timeline.vehicleUsageIntervals().get(0).intervalId()).isEqualTo("CVU-1"); assertThat(timeline.supportEvents()).isEmpty(); } @@ -109,7 +112,7 @@ class UnifiedRuntimeDriverTimelineServiceTest { return new EventHubEventDto( UUID.randomUUID(), intervalId + ":" + lifecycle.name(), - new DriverRefDto("DRIVER:42", null), + driverRef(), vehicleRef(), OffsetDateTime.parse(occurredAt), null, @@ -144,7 +147,7 @@ class UnifiedRuntimeDriverTimelineServiceTest { return new EventHubEventDto( UUID.randomUUID(), intervalId + ":" + eventType.name(), - new DriverRefDto("DRIVER:42", null), + driverRef(), vehicleRef(), OffsetDateTime.parse(occurredAt), null, @@ -166,6 +169,10 @@ class UnifiedRuntimeDriverTimelineServiceTest { return new VehicleRefDto("VEH-1", "VIN-1", "VR-1", new VehicleRegistrationRefDto("12", 12, "REG-1")); } + private DriverRefDto driverRef() { + return new DriverRefDto("DRIVER:42", new DriverCardRefDto("12", 12, "12345678901234")); + } + private EventHubPackageRequest packageInfo(EventDomain eventDomain) { EventSourceDto source = new EventSourceDto("TACHOGRAPH", "DRIVER_CARD", "SOURCE", null, null, null); return new EventHubPackageRequest( diff --git a/src/test/java/at/procon/eventhub/processing/service/UnifiedRuntimeEventAssemblyServiceTest.java b/src/test/java/at/procon/eventhub/processing/service/UnifiedRuntimeEventAssemblyServiceTest.java index ec9f520..b12d8ee 100644 --- a/src/test/java/at/procon/eventhub/processing/service/UnifiedRuntimeEventAssemblyServiceTest.java +++ b/src/test/java/at/procon/eventhub/processing/service/UnifiedRuntimeEventAssemblyServiceTest.java @@ -41,6 +41,7 @@ class UnifiedRuntimeEventAssemblyServiceTest { Set.of(UnifiedEventSourceFamily.TACHOGRAPH_DB), UnifiedRuntimeEventBackend.SOURCE_DB, null, + null, Set.of(), false, Set.of(), diff --git a/src/test/java/at/procon/eventhub/tachograph/service/TachographDbRowMapperTimelineMetadataTest.java b/src/test/java/at/procon/eventhub/tachograph/service/TachographDbRowMapperTimelineMetadataTest.java index 6494bef..70a3a94 100644 --- a/src/test/java/at/procon/eventhub/tachograph/service/TachographDbRowMapperTimelineMetadataTest.java +++ b/src/test/java/at/procon/eventhub/tachograph/service/TachographDbRowMapperTimelineMetadataTest.java @@ -40,11 +40,11 @@ class TachographDbRowMapperTimelineMetadataTest { assertThat(raw.get("sourceKind").asText()).isEqualTo("DRIVER_CARD"); assertThat(raw.get("extractionCode").asText()).isEqualTo("CARD_ACTIVITY"); assertThat(raw.get("sourceRowIds").get(0).asText()).isEqualTo("CA-1"); - assertThat(raw.get("driverKey").asText()).contains("driver-1"); - assertThat(raw.get("vehicleKey").asText()).contains("VIN123"); - assertThat(raw.get("registrationKey").asText()).contains("W-12345"); + assertThat(raw.get("driverKey").asText()).isEqualTo(started.driverRef().stableKey()); + assertThat(raw.get("vehicleKey").asText()).isEqualTo("VIN123"); + assertThat(raw.get("registrationKey").asText()).isEqualTo(started.vehicleRef().vehicleRegistration().stableKey()); - var timeline = reconstructor.reconstruct(UUID.randomUUID(), "driver-1", List.of(started, ended)); + var timeline = reconstructor.reconstruct(UUID.randomUUID(), started.driverRef().stableKey(), List.of(started, ended)); assertThat(timeline.activityIntervals()).hasSize(1); var interval = timeline.activityIntervals().getFirst(); @@ -52,8 +52,8 @@ class TachographDbRowMapperTimelineMetadataTest { assertThat(interval.activityType()).isEqualTo("DRIVE"); assertThat(interval.sourceKind()).isEqualTo("DRIVER_CARD"); assertThat(interval.sourceIntervalIds()).containsExactly("CA-1"); - assertThat(interval.registrationKey()).contains("W-12345"); - assertThat(interval.vehicleKey()).contains("VIN123"); + assertThat(interval.registrationKey()).isEqualTo(started.vehicleRef().vehicleRegistration().stableKey()); + assertThat(interval.vehicleKey()).isEqualTo("VIN123"); assertThat(interval.durationSeconds()).isEqualTo(4_500L); } @@ -67,17 +67,17 @@ class TachographDbRowMapperTimelineMetadataTest { JsonNode raw = inserted.payload().get("raw"); assertThat(raw.get("intervalId").asText()).isEqualTo("TACHOGRAPH:CARD_VEHICLES_USED:CVU-1"); assertThat(raw.get("sourceRowIds").get(0).asText()).isEqualTo("CVU-1"); - assertThat(raw.get("registrationKey").asText()).contains("W-12345"); + assertThat(raw.get("registrationKey").asText()).isEqualTo(inserted.vehicleRef().vehicleRegistration().stableKey()); - var timeline = reconstructor.reconstruct(UUID.randomUUID(), "driver-1", List.of(inserted, withdrawn)); + var timeline = reconstructor.reconstruct(UUID.randomUUID(), inserted.driverRef().stableKey(), List.of(inserted, withdrawn)); assertThat(timeline.vehicleUsageIntervals()).hasSize(1); var usage = timeline.vehicleUsageIntervals().getFirst(); assertThat(usage.intervalId()).isEqualTo("TACHOGRAPH:CARD_VEHICLES_USED:CVU-1"); assertThat(usage.sourceKind()).isEqualTo("DRIVER_CARD"); assertThat(usage.sourceIntervalIds()).containsExactly("CVU-1"); - assertThat(usage.registrationKey()).contains("W-12345"); - assertThat(usage.vehicleKey()).contains("VIN123"); + assertThat(usage.registrationKey()).isEqualTo(inserted.vehicleRef().vehicleRegistration().stableKey()); + assertThat(usage.vehicleKey()).isEqualTo("VIN123"); assertThat(usage.odometerBeginKm()).isEqualTo(120L); assertThat(usage.odometerEndKm()).isEqualTo(120L); assertThat(usage.durationSeconds()).isEqualTo(43_200L); diff --git a/src/test/java/at/procon/eventhub/tachographfilesession/service/DriverCardXmlExtractionServiceTest.java b/src/test/java/at/procon/eventhub/tachographfilesession/service/DriverCardXmlExtractionServiceTest.java index b29cbd8..e537cec 100644 --- a/src/test/java/at/procon/eventhub/tachographfilesession/service/DriverCardXmlExtractionServiceTest.java +++ b/src/test/java/at/procon/eventhub/tachographfilesession/service/DriverCardXmlExtractionServiceTest.java @@ -6,6 +6,7 @@ import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession; import at.procon.eventhub.tachographfilesession.model.TachographFileSession; import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata; import java.time.Instant; +import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import org.junit.jupiter.api.Test; @@ -45,10 +46,14 @@ class DriverCardXmlExtractionServiceTest { assertThat(driver.vehicleRegistrations()).hasSize(2); assertThat(driver.vehicles()).hasSize(2); assertThat(driver.cardVehicleUsageIntervals()).hasSize(2); - assertThat(driver.cardActivityIntervals()).hasSize(3); + assertThat(driver.cardActivityIntervals()).hasSize(5); assertThat(driver.cardActivityIntervals().get(0).registrationKey()).isEqualTo("12:W-12345A"); assertThat(driver.cardActivityIntervals().get(1).to()).isEqualTo(driver.cardVehicleUsageIntervals().get(0).to().plusSeconds(1)); assertThat(driver.cardActivityIntervals().get(2).registrationKey()).isEqualTo("12:W-54321B"); + assertThat(driver.cardActivityIntervals().get(3).activityType()).isEqualTo("BREAK_REST"); + assertThat(driver.cardActivityIntervals().get(3).to()).isEqualTo(OffsetDateTime.parse("2026-04-01T18:00:01Z")); + assertThat(driver.cardActivityIntervals().get(4).to()).isEqualTo(OffsetDateTime.parse("2026-04-02T00:00:00Z")); + assertThat(driver.cardActivityIntervals().get(4).registrationKey()).isNull(); assertThat(driver.supportEvents()).hasSize(5); assertThat(driver.supportEvents()).extracting("eventDomain") .containsExactly("PLACE", "POSITION", "SPECIFIC_CONDITION", "BORDER_CROSSING", "LOAD_UNLOAD"); @@ -79,10 +84,10 @@ class DriverCardXmlExtractionServiceTest { ); DriverExtractionSession driver = session.driversByKey().values().iterator().next(); - assertThat(driver.cardActivityIntervals()).hasSize(3); + assertThat(driver.cardActivityIntervals()).hasSize(5); assertThat(driver.cardActivityIntervals()) .extracting(interval -> interval.from().toString()) - .contains("2026-04-01T08:00Z", "2026-04-01T12:00Z"); + .contains("2026-04-01T08:00Z", "2026-04-01T12:00Z", "2026-04-01T18:00:01Z"); } @Test @@ -108,8 +113,11 @@ class DriverCardXmlExtractionServiceTest { DriverExtractionSession driver = session.driversByKey().values().iterator().next(); assertThat(driver.cardVehicleUsageIntervals()).hasSize(2); assertThat(driver.cardVehicleUsageIntervals().get(1).to()).isNull(); - assertThat(driver.cardActivityIntervals()).hasSize(3); + assertThat(driver.cardActivityIntervals()).hasSize(4); assertThat(driver.cardActivityIntervals().get(2).registrationKey()).isEqualTo("12:W-54321B"); + assertThat(driver.cardActivityIntervals().get(3).activityType()).isEqualTo("BREAK_REST"); + assertThat(driver.cardActivityIntervals().get(3).to()).isEqualTo(OffsetDateTime.parse("2026-04-02T00:00:00Z")); + assertThat(driver.cardActivityIntervals().get(3).registrationKey()).isEqualTo("12:W-54321B"); assertThat(driver.supportEvents()).hasSize(5); assertThat(driver.supportEvents().stream() .filter(event -> "12:W-54321B".equals(event.registrationKey())) diff --git a/src/test/java/at/procon/eventhub/tachographfilesession/service/VehicleUnitXmlExtractionServiceTest.java b/src/test/java/at/procon/eventhub/tachographfilesession/service/VehicleUnitXmlExtractionServiceTest.java index 61983d4..02f4d39 100644 --- a/src/test/java/at/procon/eventhub/tachographfilesession/service/VehicleUnitXmlExtractionServiceTest.java +++ b/src/test/java/at/procon/eventhub/tachographfilesession/service/VehicleUnitXmlExtractionServiceTest.java @@ -7,6 +7,7 @@ import at.procon.eventhub.tachographfilesession.model.TachographFileSession; import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata; import java.io.StringReader; import java.time.Instant; +import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import javax.xml.parsers.DocumentBuilderFactory; import org.junit.jupiter.api.Test; @@ -52,9 +53,11 @@ class VehicleUnitXmlExtractionServiceTest { assertThat(firstDriver.cardVehicleUsageIntervals()).hasSize(1); assertThat(firstDriver.cardVehicleUsageIntervals().get(0).from().toString()).isEqualTo("2026-04-01T08:00Z"); assertThat(firstDriver.cardVehicleUsageIntervals().get(0).to().toString()).isEqualTo("2026-04-01T11:00Z"); - assertThat(firstDriver.cardActivityIntervals()).hasSize(2); + assertThat(firstDriver.cardActivityIntervals()).hasSize(3); assertThat(firstDriver.cardActivityIntervals().get(0).activityType()).isEqualTo("WORK"); assertThat(firstDriver.cardActivityIntervals().get(1).activityType()).isEqualTo("DRIVE"); + assertThat(firstDriver.cardActivityIntervals().get(2).activityType()).isEqualTo("BREAK_REST"); + assertThat(firstDriver.cardActivityIntervals().get(2).to()).isEqualTo(OffsetDateTime.parse("2026-04-01T11:00:01Z")); assertThat(firstDriver.supportEvents()).hasSize(1); assertThat(firstDriver.supportEvents().get(0).eventDomain()).isEqualTo("PLACE"); assertThat(firstDriver.supportEvents().get(0).eventType()).isEqualTo("BEGIN_DAILY_WORK_PERIOD"); @@ -63,8 +66,10 @@ class VehicleUnitXmlExtractionServiceTest { assertThat(secondDriver.cardVehicleUsageIntervals()).hasSize(1); assertThat(secondDriver.cardVehicleUsageIntervals().get(0).to()).isNull(); - assertThat(secondDriver.cardActivityIntervals()).hasSize(1); + assertThat(secondDriver.cardActivityIntervals()).hasSize(2); assertThat(secondDriver.cardActivityIntervals().get(0).from().toString()).isEqualTo("2026-04-02T07:30Z"); + assertThat(secondDriver.cardActivityIntervals().get(1).activityType()).isEqualTo("DRIVE"); + assertThat(secondDriver.cardActivityIntervals().get(1).to()).isEqualTo(OffsetDateTime.parse("2026-04-03T00:00:00Z")); assertThat(secondDriver.supportEvents()).hasSize(6); assertThat(secondDriver.supportEvents()).extracting("eventDomain") .containsExactly("POSITION", "SPECIFIC_CONDITION", "BORDER_CROSSING", "LOAD_UNLOAD", "SPEEDING", "SPEEDING"); @@ -77,7 +82,7 @@ class VehicleUnitXmlExtractionServiceTest { assertThat(secondDriver.supportEvents().get(4).eventLifecycle()).isEqualTo("BEGIN"); assertThat(secondDriver.supportEvents().get(5).eventLifecycle()).isEqualTo("END"); - assertThat(session.warnings()).isEmpty(); + assertThat(session.warnings()).extracting("code").containsExactly("VU_ACTIVITY_UNASSIGNED"); } @Test