Fix tachograph runtime identity and interval extraction

This commit is contained in:
trifonovt 2026-05-28 11:47:01 +02:00
parent 927ac3b903
commit 6dd1fb7447
38 changed files with 709 additions and 216 deletions

View File

@ -26,7 +26,7 @@ public record DriverRefDto(
public String stableKey() { public String stableKey() {
String cardKey = driverCard == null ? "" : driverCard.stableKey(); String cardKey = driverCard == null ? "" : driverCard.stableKey();
return (sourceEntityId == null ? "" : sourceEntityId) + "|" + cardKey; return /*(sourceEntityId == null ? "" : sourceEntityId) + "|" +*/ cardKey;
} }
private static String normalizeNullable(String value) { private static String normalizeNullable(String value) {

View File

@ -148,13 +148,13 @@ public class EventHubEventReadRepository {
event.source_package_entity_id, event.source_package_entity_id,
detail.detail_type, detail.detail_type,
detail.attributes, detail.attributes,
driver.source_driver_entity_id, driver_identity.source_driver_entity_id,
driver_card.nation as driver_card_nation, driver_card.nation as driver_card_nation,
driver_card.nation_numeric_code as driver_card_nation_numeric_code, driver_card.nation_numeric_code as driver_card_nation_numeric_code,
driver_card.card_number as driver_card_number, driver_card.card_number as driver_card_number,
vehicle.source_vehicle_entity_id, vehicle_identity.source_vehicle_entity_id,
vehicle.vin, vehicle.vin,
registration.source_registration_entity_id, registration_identity.source_registration_entity_id,
registration.nation as vehicle_registration_nation, registration.nation as vehicle_registration_nation,
registration.nation_numeric_code as vehicle_registration_nation_numeric_code, registration.nation_numeric_code as vehicle_registration_nation_numeric_code,
registration.registration_number as vehicle_registration_number, registration.registration_number as vehicle_registration_number,
@ -173,9 +173,36 @@ public class EventHubEventReadRepository {
limit 1 limit 1
) detail on true ) detail on true
left join eventhub.driver driver on driver.id = event.driver_id 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.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 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 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 = ? where package.tenant_key = ?
and source.provider_key = ? and source.provider_key = ?
""" """
@ -205,7 +232,18 @@ public class EventHubEventReadRepository {
params.add(occurredTo); params.add(occurredTo);
} }
if (driverSourceEntityId != null) { 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); params.add(driverSourceEntityId);
} }
if (driverCardNumber != null) { if (driverCardNumber != null) {
@ -224,7 +262,18 @@ public class EventHubEventReadRepository {
} }
} }
if (vehicleSourceEntityId != null) { 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); params.add(vehicleSourceEntityId);
} }
if (vin != null) { 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"); sql.append(" order by event.occurred_at, event.event_domain, event.event_type, event.lifecycle, event.id");

View File

@ -3,6 +3,7 @@ package at.procon.eventhub.processing.dto;
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily; import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBackend; import at.procon.eventhub.processing.model.UnifiedRuntimeEventBackend;
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest; import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
import at.procon.eventhub.processing.model.UnifiedTachographSourceKind;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -15,6 +16,7 @@ public record UnifiedRuntimeProcessingApiRequest(
String tenantKey, String tenantKey,
Set<UnifiedEventSourceFamily> sourceFamilies, Set<UnifiedEventSourceFamily> sourceFamilies,
UnifiedRuntimeEventBackend eventBackend, UnifiedRuntimeEventBackend eventBackend,
Set<UnifiedTachographSourceKind> tachographSourceKinds,
String driverKey, String driverKey,
Set<String> driverKeys, Set<String> driverKeys,
Boolean includeAllDrivers, Boolean includeAllDrivers,
@ -38,6 +40,7 @@ public record UnifiedRuntimeProcessingApiRequest(
tenantKey, tenantKey,
sourceFamilies, sourceFamilies,
eventBackend, eventBackend,
tachographSourceKinds,
driverKey, driverKey,
driverKeys, driverKeys,
includeAllDrivers != null && includeAllDrivers, includeAllDrivers != null && includeAllDrivers,

View File

@ -13,6 +13,7 @@ import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef;
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle; import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest; import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
import at.procon.eventhub.processing.service.RuntimeDriverVehicleEvidenceAttachmentService; import at.procon.eventhub.processing.service.RuntimeDriverVehicleEvidenceAttachmentService;
import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
@ -258,18 +259,7 @@ public class VehicleEvidenceAttachmentModule implements RuntimeProcessingModule
} }
private String driverKey(EventHubEventDto event) { private String driverKey(EventHubEventDto event) {
if (event == null) { return TachographRuntimeIdentityResolver.driverKey(event);
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;
} }
private JsonNode rawPayload(EventHubEventDto event) { private JsonNode rawPayload(EventHubEventDto event) {

View File

@ -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.RuntimeProcessingModuleContext;
import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleResult; import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleResult;
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle; import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.ArrayList; import java.util.ArrayList;
@ -151,7 +152,7 @@ public final class DriverWorkingTimeEplEventMapper {
JsonNode raw = rawPayload(sourceEvent); JsonNode raw = rawPayload(sourceEvent);
JsonNode attributes = attributes(sourceEvent); JsonNode attributes = attributes(sourceEvent);
String intervalId = firstNonBlank(text(raw, "intervalId"), text(raw, "sourceRowId"), sourceEvent.externalSourceEventId()); 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) { if (driverKey == null || intervalId == null) {
return null; return null;
} }
@ -169,8 +170,8 @@ public final class DriverWorkingTimeEplEventMapper {
event.put("cardSlot", firstNonBlank(text(raw, "cardSlot"), text(attributes, "cardSlot"))); event.put("cardSlot", firstNonBlank(text(raw, "cardSlot"), text(attributes, "cardSlot")));
event.put("cardStatus", firstNonBlank(text(raw, "cardStatus"), text(attributes, "cardStatus"))); event.put("cardStatus", firstNonBlank(text(raw, "cardStatus"), text(attributes, "cardStatus")));
event.put("drivingStatus", firstNonBlank(text(raw, "drivingStatus"), text(attributes, "drivingStatus"))); event.put("drivingStatus", firstNonBlank(text(raw, "drivingStatus"), text(attributes, "drivingStatus")));
event.put("registrationKey", firstNonBlank(text(raw, "registrationKey"), registrationKey(sourceEvent))); event.put("registrationKey", TachographRuntimeIdentityResolver.registrationKey(sourceEvent));
event.put("vehicleKey", firstNonBlank(text(raw, "vehicleKey"), vehicleKey(sourceEvent))); event.put("vehicleKey", TachographRuntimeIdentityResolver.vehicleKey(sourceEvent));
event.put("sourceKind", firstNonBlank(text(raw, "sourceKind"), sourceKind(sourceEvent))); event.put("sourceKind", firstNonBlank(text(raw, "sourceKind"), sourceKind(sourceEvent)));
event.put("synthetic", booleanValue(raw, "synthetic", false)); event.put("synthetic", booleanValue(raw, "synthetic", false));
event.put("clippedToRequestedPeriod", booleanValue(raw, "clippedToRequestedPeriod", false)); event.put("clippedToRequestedPeriod", booleanValue(raw, "clippedToRequestedPeriod", false));
@ -192,7 +193,7 @@ public final class DriverWorkingTimeEplEventMapper {
} }
JsonNode raw = rawPayload(sourceEvent); JsonNode raw = rawPayload(sourceEvent);
String intervalId = firstNonBlank(text(raw, "intervalId"), text(raw, "sourceRowId"), sourceEvent.externalSourceEventId()); 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) { if (driverKey == null || intervalId == null) {
return null; return null;
} }
@ -206,8 +207,8 @@ public final class DriverWorkingTimeEplEventMapper {
event.put("lifecycle", sourceEvent.lifecycle().name()); event.put("lifecycle", sourceEvent.lifecycle().name());
event.put("occurredAt", sourceEvent.occurredAt()); event.put("occurredAt", sourceEvent.occurredAt());
event.put("occurredAtEpochSecond", sourceEvent.occurredAt().toEpochSecond()); event.put("occurredAtEpochSecond", sourceEvent.occurredAt().toEpochSecond());
event.put("registrationKey", firstNonBlank(text(raw, "registrationKey"), registrationKey(sourceEvent))); event.put("registrationKey", TachographRuntimeIdentityResolver.registrationKey(sourceEvent));
event.put("vehicleKey", firstNonBlank(text(raw, "vehicleKey"), vehicleKey(sourceEvent))); event.put("vehicleKey", TachographRuntimeIdentityResolver.vehicleKey(sourceEvent));
event.put("sourceKind", firstNonBlank(text(raw, "sourceKind"), sourceKind(sourceEvent))); event.put("sourceKind", firstNonBlank(text(raw, "sourceKind"), sourceKind(sourceEvent)));
event.put("odometerKm", odometerKm(sourceEvent, raw)); event.put("odometerKm", odometerKm(sourceEvent, raw));
return event; return event;
@ -338,30 +339,6 @@ public final class DriverWorkingTimeEplEventMapper {
return new UUID(0L, 0L); 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) { private static String sourceKind(EventHubEventDto event) {
return event.packageInfo() == null || event.packageInfo().eventSource() == null return event.packageInfo() == null || event.packageInfo().eventSource() == null
? null ? null

View File

@ -382,6 +382,7 @@ public class DriverWorkingTimeRuntimeProcessingPlan implements RuntimeProcessing
sourceSelection.tenantKey(), sourceSelection.tenantKey(),
sourceSelection.sourceFamilies(), sourceSelection.sourceFamilies(),
sourceSelection.eventBackend(), sourceSelection.eventBackend(),
sourceSelection.tachographSourceKinds(),
sourceSelection.driverKey(), sourceSelection.driverKey(),
driverKeys, driverKeys,
includeAllDrivers, includeAllDrivers,

View File

@ -8,6 +8,7 @@ import at.procon.eventhub.dto.EventLifecycle;
import at.procon.eventhub.dto.EventType; import at.procon.eventhub.dto.EventType;
import at.procon.eventhub.dto.GeoPointDto; import at.procon.eventhub.dto.GeoPointDto;
import at.procon.eventhub.dto.VehicleRefDto; import at.procon.eventhub.dto.VehicleRefDto;
import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
@ -81,9 +82,9 @@ public class RuntimeSupportEvidenceNormalizer {
event.eventDomain() == null ? null : event.eventDomain().name(), event.eventDomain() == null ? null : event.eventDomain().name(),
event.eventType() == null ? null : event.eventType().name(), event.eventType() == null ? null : event.eventType().name(),
event.lifecycle() == null ? null : event.lifecycle().name(), event.lifecycle() == null ? null : event.lifecycle().name(),
firstNonBlank(text(raw, "driverKey"), fallbackDriverKey, driverKey(event)), firstNonBlank(TachographRuntimeIdentityResolver.driverKey(event), fallbackDriverKey),
firstNonBlank(text(raw, "vehicleKey"), vehicleKey(event)), TachographRuntimeIdentityResolver.vehicleKey(event),
firstNonBlank(text(raw, "registrationKey"), registrationKey(event)), TachographRuntimeIdentityResolver.registrationKey(event),
event.occurredAt(), event.occurredAt(),
event.occurredAt() == null ? null : event.occurredAt().toEpochSecond(), event.occurredAt() == null ? null : event.occurredAt().toEpochSecond(),
latitude, latitude,
@ -287,53 +288,7 @@ public class RuntimeSupportEvidenceNormalizer {
} }
private JsonNode rawPayload(EventHubEventDto event) { private JsonNode rawPayload(EventHubEventDto event) {
if (event == null || event.payload() == null || event.payload().isNull() || event.payload().isMissingNode()) { return TachographRuntimeIdentityResolver.rawPayload(event);
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();
} }
private String detailText(EventHubEventDto event, String field) { private String detailText(EventHubEventDto event, String field) {

View File

@ -166,6 +166,7 @@ public class RuntimeTachographParityValidationService {
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
UnifiedRuntimeEventBackend.SOURCE_DB, UnifiedRuntimeEventBackend.SOURCE_DB,
null, null,
null,
driverKeys, driverKeys,
request.includeAllDriversOrDefault(), request.includeAllDriversOrDefault(),
null, null,

View File

@ -2,11 +2,13 @@ package at.procon.eventhub.processing.model;
import at.procon.eventhub.reference.DriverCardNumberNormalizer; import at.procon.eventhub.reference.DriverCardNumberNormalizer;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
public record UnifiedDriverEventsRequest( public record UnifiedDriverEventsRequest(
UnifiedEventSourceFamily sourceFamily, UnifiedEventSourceFamily sourceFamily,
List<String> sourceKinds,
UUID sessionId, UUID sessionId,
String driverKey, String driverKey,
String tenantKey, String tenantKey,
@ -22,6 +24,7 @@ public record UnifiedDriverEventsRequest(
) { ) {
public UnifiedDriverEventsRequest { public UnifiedDriverEventsRequest {
Objects.requireNonNull(sourceFamily, "sourceFamily must not be null"); Objects.requireNonNull(sourceFamily, "sourceFamily must not be null");
sourceKinds = sourceKinds == null ? List.of() : List.copyOf(sourceKinds);
driverKey = normalize(driverKey); driverKey = normalize(driverKey);
tenantKey = normalize(tenantKey); tenantKey = normalize(tenantKey);
driverSourceEntityId = normalize(driverSourceEntityId); driverSourceEntityId = normalize(driverSourceEntityId);
@ -59,6 +62,7 @@ public record UnifiedDriverEventsRequest(
) { ) {
return new UnifiedDriverEventsRequest( return new UnifiedDriverEventsRequest(
UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION, UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION,
List.of(),
sessionId, sessionId,
driverKey, driverKey,
null, null,
@ -81,9 +85,30 @@ public record UnifiedDriverEventsRequest(
String driverCardNumber, String driverCardNumber,
OffsetDateTime occurredFrom, OffsetDateTime occurredFrom,
OffsetDateTime occurredTo 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<String> sourceKinds
) { ) {
return new UnifiedDriverEventsRequest( return new UnifiedDriverEventsRequest(
UnifiedEventSourceFamily.TACHOGRAPH_DB, UnifiedEventSourceFamily.TACHOGRAPH_DB,
sourceKinds,
null, null,
null, null,
tenantKey, tenantKey,
@ -110,6 +135,7 @@ public record UnifiedDriverEventsRequest(
) { ) {
return new UnifiedDriverEventsRequest( return new UnifiedDriverEventsRequest(
UnifiedEventSourceFamily.YELLOWFOX_DB, UnifiedEventSourceFamily.YELLOWFOX_DB,
List.of(),
null, null,
null, null,
tenantKey, tenantKey,
@ -135,6 +161,7 @@ public record UnifiedDriverEventsRequest(
) { ) {
return new UnifiedDriverEventsRequest( return new UnifiedDriverEventsRequest(
UnifiedEventSourceFamily.YELLOWFOX_DB, UnifiedEventSourceFamily.YELLOWFOX_DB,
List.of(),
null, null,
null, null,
tenantKey, tenantKey,

View File

@ -3,6 +3,7 @@ package at.procon.eventhub.processing.model;
import at.procon.eventhub.reference.DriverCardNumberNormalizer; import at.procon.eventhub.reference.DriverCardNumberNormalizer;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -15,6 +16,7 @@ public record UnifiedRuntimeProcessingRequest(
String tenantKey, String tenantKey,
Set<UnifiedEventSourceFamily> sourceFamilies, Set<UnifiedEventSourceFamily> sourceFamilies,
UnifiedRuntimeEventBackend eventBackend, UnifiedRuntimeEventBackend eventBackend,
Set<UnifiedTachographSourceKind> tachographSourceKinds,
String driverKey, String driverKey,
Set<String> driverKeys, Set<String> driverKeys,
boolean includeAllDrivers, boolean includeAllDrivers,
@ -34,6 +36,7 @@ public record UnifiedRuntimeProcessingRequest(
} }
sourceFamilies = Set.copyOf(sourceFamilies); sourceFamilies = Set.copyOf(sourceFamilies);
eventBackend = eventBackend == null ? UnifiedRuntimeEventBackend.SOURCE_DB : eventBackend; eventBackend = eventBackend == null ? UnifiedRuntimeEventBackend.SOURCE_DB : eventBackend;
tachographSourceKinds = normalizeTachographSourceKinds(tachographSourceKinds);
sessionIds = normalizeSessionIds(sessionId, sessionIds); sessionIds = normalizeSessionIds(sessionId, sessionIds);
if (sessionId == null && !sessionIds.isEmpty()) { if (sessionId == null && !sessionIds.isEmpty()) {
sessionId = sessionIds.get(0); sessionId = sessionIds.get(0);
@ -164,6 +167,7 @@ public record UnifiedRuntimeProcessingRequest(
sourceFamilies, sourceFamilies,
eventBackend, eventBackend,
null, null,
null,
Set.of(), Set.of(),
false, false,
Set.of(), Set.of(),
@ -197,6 +201,7 @@ public record UnifiedRuntimeProcessingRequest(
sourceFamilies, sourceFamilies,
UnifiedRuntimeEventBackend.EVENTHUB_DB, UnifiedRuntimeEventBackend.EVENTHUB_DB,
null, null,
null,
Set.of(), Set.of(),
false, false,
Set.of(), Set.of(),
@ -226,6 +231,7 @@ public record UnifiedRuntimeProcessingRequest(
null, null,
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
UnifiedRuntimeEventBackend.SOURCE_DB, UnifiedRuntimeEventBackend.SOURCE_DB,
null,
driverKey, driverKey,
Set.of(), Set.of(),
false, false,
@ -256,6 +262,7 @@ public record UnifiedRuntimeProcessingRequest(
null, null,
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
UnifiedRuntimeEventBackend.SOURCE_DB, UnifiedRuntimeEventBackend.SOURCE_DB,
null,
driverKey, driverKey,
Set.of(), Set.of(),
false, false,
@ -286,6 +293,7 @@ public record UnifiedRuntimeProcessingRequest(
null, null,
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
UnifiedRuntimeEventBackend.SOURCE_DB, UnifiedRuntimeEventBackend.SOURCE_DB,
null,
driverKey, driverKey,
Set.of(), Set.of(),
false, false,
@ -328,6 +336,7 @@ public record UnifiedRuntimeProcessingRequest(
tenantKey, tenantKey,
sourceFamilies, sourceFamilies,
eventBackend, eventBackend,
tachographSourceKinds,
value, value,
Set.of(), Set.of(),
false, false,
@ -347,6 +356,23 @@ public record UnifiedRuntimeProcessingRequest(
return includeAllDrivers || driverKeys.size() > 1 || (driverKey == null && !driverKeys.isEmpty()); 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<String> tachographSourceKindNames() {
return tachographSourceKinds.stream()
.map(UnifiedTachographSourceKind::name)
.toList();
}
private static Set<String> normalizeStrings(Set<String> values) { private static Set<String> normalizeStrings(Set<String> values) {
if (values == null || values.isEmpty()) { if (values == null || values.isEmpty()) {
return Set.of(); return Set.of();
@ -361,6 +387,15 @@ public record UnifiedRuntimeProcessingRequest(
return Set.copyOf(normalized); return Set.copyOf(normalized);
} }
private static Set<UnifiedTachographSourceKind> normalizeTachographSourceKinds(
Set<UnifiedTachographSourceKind> values
) {
if (values == null || values.isEmpty()) {
return Set.copyOf(Arrays.asList(UnifiedTachographSourceKind.values()));
}
return Set.copyOf(values);
}
private static String normalize(String value) { private static String normalize(String value) {
return value == null || value.isBlank() ? null : value.trim(); return value == null || value.isBlank() ? null : value.trim();
} }

View File

@ -0,0 +1,6 @@
package at.procon.eventhub.processing.model;
public enum UnifiedTachographSourceKind {
DRIVER_CARD,
VEHICLE_UNIT
}

View File

@ -1,11 +1,13 @@
package at.procon.eventhub.processing.model; package at.procon.eventhub.processing.model;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
public record UnifiedVehicleEventsRequest( public record UnifiedVehicleEventsRequest(
UnifiedEventSourceFamily sourceFamily, UnifiedEventSourceFamily sourceFamily,
List<String> sourceKinds,
UUID sessionId, UUID sessionId,
String tenantKey, String tenantKey,
String vehicleSourceEntityId, String vehicleSourceEntityId,
@ -17,6 +19,7 @@ public record UnifiedVehicleEventsRequest(
) { ) {
public UnifiedVehicleEventsRequest { public UnifiedVehicleEventsRequest {
Objects.requireNonNull(sourceFamily, "sourceFamily must not be null"); Objects.requireNonNull(sourceFamily, "sourceFamily must not be null");
sourceKinds = sourceKinds == null ? List.of() : List.copyOf(sourceKinds);
tenantKey = normalize(tenantKey); tenantKey = normalize(tenantKey);
vehicleSourceEntityId = normalize(vehicleSourceEntityId); vehicleSourceEntityId = normalize(vehicleSourceEntityId);
vin = normalizeUpper(vin); vin = normalizeUpper(vin);
@ -46,6 +49,7 @@ public record UnifiedVehicleEventsRequest(
) { ) {
return new UnifiedVehicleEventsRequest( return new UnifiedVehicleEventsRequest(
UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION, UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION,
List.of(),
sessionId, sessionId,
null, null,
vehicleSourceEntityId, vehicleSourceEntityId,
@ -65,9 +69,32 @@ public record UnifiedVehicleEventsRequest(
String registrationNumber, String registrationNumber,
OffsetDateTime occurredFrom, OffsetDateTime occurredFrom,
OffsetDateTime occurredTo 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<String> sourceKinds
) { ) {
return new UnifiedVehicleEventsRequest( return new UnifiedVehicleEventsRequest(
UnifiedEventSourceFamily.TACHOGRAPH_DB, UnifiedEventSourceFamily.TACHOGRAPH_DB,
sourceKinds,
null, null,
tenantKey, tenantKey,
vehicleSourceEntityId, vehicleSourceEntityId,
@ -90,6 +117,7 @@ public record UnifiedVehicleEventsRequest(
) { ) {
return new UnifiedVehicleEventsRequest( return new UnifiedVehicleEventsRequest(
UnifiedEventSourceFamily.YELLOWFOX_DB, UnifiedEventSourceFamily.YELLOWFOX_DB,
List.of(),
null, null,
tenantKey, tenantKey,
vehicleSourceEntityId, vehicleSourceEntityId,

View File

@ -70,7 +70,8 @@ public class EventHubRuntimeEventLoader implements RuntimeDriverEventLoader, Run
request.driverCardNation(), request.driverCardNation(),
request.driverCardNumber(), request.driverCardNumber(),
request.occurredFrom(), request.occurredFrom(),
request.occurredTo() request.occurredTo(),
request.tachographSourceKindNames()
); );
case YELLOWFOX_DB -> UnifiedDriverEventsRequest.forYellowFoxDbDriver( case YELLOWFOX_DB -> UnifiedDriverEventsRequest.forYellowFoxDbDriver(
request.tenantKey(), request.tenantKey(),
@ -97,7 +98,8 @@ public class EventHubRuntimeEventLoader implements RuntimeDriverEventLoader, Run
vehicleRef.registrationNation(), vehicleRef.registrationNation(),
vehicleRef.registrationNumber(), vehicleRef.registrationNumber(),
request.vehicleOccurredFrom(), request.vehicleOccurredFrom(),
request.vehicleOccurredTo() request.vehicleOccurredTo(),
request.tachographSourceKindNames()
); );
case YELLOWFOX_DB -> UnifiedVehicleEventsRequest.forYellowFoxDb( case YELLOWFOX_DB -> UnifiedVehicleEventsRequest.forYellowFoxDb(
request.tenantKey(), request.tenantKey(),

View File

@ -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.RuntimeEventScopeClassifier;
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeType; import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeType;
import at.procon.eventhub.processing.model.RuntimeDriverVehicleEvidenceAttachmentResult; 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.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval; import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
import at.procon.eventhub.tachographfilesession.service.TachographDriverWorkingTimeAdapter; import at.procon.eventhub.tachographfilesession.service.TachographDriverWorkingTimeAdapter;
@ -275,22 +276,15 @@ public class RuntimeDriverVehicleEvidenceAttachmentService {
private Set<String> vehicleKeys(EventHubEventDto event) { private Set<String> vehicleKeys(EventHubEventDto event) {
LinkedHashSet<String> result = new LinkedHashSet<>(); LinkedHashSet<String> result = new LinkedHashSet<>();
JsonNode raw = rawPayload(event); String vehicleKey = TachographRuntimeIdentityResolver.vehicleKey(event);
add(result, text(raw, "vehicleKey")); String registrationKey = TachographRuntimeIdentityResolver.registrationKey(event);
add(result, text(raw, "registrationKey")); add(result, vehicleKey);
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, registrationKey);
add(result, "VR:" + registrationKey); if (vehicleKey != null) {
add(result, "VIN:" + vehicleKey);
} }
if (registrationKey != null) {
add(result, "VR:" + registrationKey);
} }
return Set.copyOf(result); return Set.copyOf(result);
} }

View File

@ -1,6 +1,5 @@
package at.procon.eventhub.processing.service; package at.procon.eventhub.processing.service;
import at.procon.eventhub.dto.DriverRefDto;
import at.procon.eventhub.dto.EventHubEventDto; import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.dto.VehicleRefDto; import at.procon.eventhub.dto.VehicleRefDto;
import at.procon.eventhub.processing.dto.RuntimeDriverPartitionDebugDto; 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.RuntimeDriverVehicleEvidenceAttachmentResult;
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle; import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest; import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
@ -282,18 +282,7 @@ public class RuntimeDriverWorkingTimeScopeProcessingService {
} }
private String driverKey(EventHubEventDto event) { private String driverKey(EventHubEventDto event) {
if (event == null) { return TachographRuntimeIdentityResolver.driverKey(event);
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;
} }
private JsonNode rawPayload(EventHubEventDto event) { private JsonNode rawPayload(EventHubEventDto event) {

View File

@ -37,7 +37,6 @@ import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
@Component @Component
@ConditionalOnBean(name = "tachographNamedParameterJdbcTemplate")
@ConditionalOnExpression("T(org.springframework.util.StringUtils).hasText('${eventhub.tachograph.datasource.jdbc-url:}')") @ConditionalOnExpression("T(org.springframework.util.StringUtils).hasText('${eventhub.tachograph.datasource.jdbc-url:}')")
public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader, RuntimeVehicleEventLoader { public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader, RuntimeVehicleEventLoader {
@ -64,7 +63,7 @@ public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader,
@Override @Override
public List<EventHubEventDto> loadDriverEvents(UnifiedRuntimeProcessingRequest request) { public List<EventHubEventDto> loadDriverEvents(UnifiedRuntimeProcessingRequest request) {
List<EventHubEventDto> result = new ArrayList<>(); List<EventHubEventDto> result = new ArrayList<>();
for (ExtractionDefinition<TachographImportRequest> definition : driverDefinitions()) { for (ExtractionDefinition<TachographImportRequest> definition : driverDefinitions(request)) {
result.addAll(queryDefinition( result.addAll(queryDefinition(
definition, definition,
request, request,
@ -74,6 +73,7 @@ public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader,
)); ));
} }
return List.copyOf(result); return List.copyOf(result);
} }
@Override @Override
@ -82,7 +82,7 @@ public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader,
UnifiedDiscoveredVehicleRef vehicleRef UnifiedDiscoveredVehicleRef vehicleRef
) { ) {
List<EventHubEventDto> result = new ArrayList<>(); List<EventHubEventDto> result = new ArrayList<>();
for (ExtractionDefinition<TachographImportRequest> definition : vehicleDefinitions()) { for (ExtractionDefinition<TachographImportRequest> definition : vehicleDefinitions(request)) {
result.addAll(queryDefinition( result.addAll(queryDefinition(
definition, definition,
request, request,
@ -116,15 +116,21 @@ public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader,
return events; return events;
} }
private List<ExtractionDefinition<TachographImportRequest>> driverDefinitions() { private List<ExtractionDefinition<TachographImportRequest>> driverDefinitions(
UnifiedRuntimeProcessingRequest request
) {
return definitionRegistry.definitions().stream() return definitionRegistry.definitions().stream()
.filter(definition -> !"VEHICLE".equals(definition.entityAxis())) .filter(definition -> !"VEHICLE".equals(definition.entityAxis()))
.filter(definition -> request.includesTachographSourceKind(definition.sourceKind()))
.toList(); .toList();
} }
private List<ExtractionDefinition<TachographImportRequest>> vehicleDefinitions() { private List<ExtractionDefinition<TachographImportRequest>> vehicleDefinitions(
UnifiedRuntimeProcessingRequest request
) {
return definitionRegistry.definitions().stream() return definitionRegistry.definitions().stream()
.filter(definition -> !"DRIVER".equals(definition.entityAxis())) .filter(definition -> !"DRIVER".equals(definition.entityAxis()))
.filter(definition -> request.includesTachographSourceKind(definition.sourceKind()))
.toList(); .toList();
} }
@ -214,33 +220,31 @@ public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader,
UnifiedRuntimeProcessingRequest request, UnifiedRuntimeProcessingRequest request,
UnifiedDiscoveredVehicleRef vehicleRef UnifiedDiscoveredVehicleRef vehicleRef
) { ) {
StringBuilder sql = new StringBuilder("select * from ("); StringBuilder sql = new StringBuilder(baseSql.stripTrailing());
sql.append(baseSql);
sql.append("\n) runtime where 1 = 1");
if (request.driverSourceEntityId() != null) { 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) { 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) { 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 != null) {
if (vehicleRef.sourceVehicleEntityId() != 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) { if (vehicleRef.vin() != null) {
sql.append("\n and runtime.vehicle_vin = :vehicleVin"); sql.append("\n and extracted.vehicle_vin = :vehicleVin");
} }
if (vehicleRef.registrationNumber() != null) { 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) { 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(); return sql.toString();
} }

View File

@ -25,6 +25,7 @@ public class TachographDbUnifiedDriverEventSource implements UnifiedDriverEventS
@Override @Override
public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) { public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) {
return repository.findEvents(request, "TACHOGRAPH", SOURCE_KINDS); List<String> sourceKinds = request.sourceKinds().isEmpty() ? SOURCE_KINDS : request.sourceKinds();
return repository.findEvents(request, "TACHOGRAPH", sourceKinds);
} }
} }

View File

@ -25,6 +25,7 @@ public class TachographDbUnifiedVehicleEventSource implements UnifiedVehicleEven
@Override @Override
public List<EventHubEventDto> loadVehicleEvents(UnifiedVehicleEventsRequest request) { public List<EventHubEventDto> loadVehicleEvents(UnifiedVehicleEventsRequest request) {
return repository.findEventsByVehicle(request, "TACHOGRAPH", SOURCE_KINDS); List<String> sourceKinds = request.sourceKinds().isEmpty() ? SOURCE_KINDS : request.sourceKinds();
return repository.findEventsByVehicle(request, "TACHOGRAPH", sourceKinds);
} }
} }

View File

@ -4,6 +4,7 @@ import at.procon.eventhub.dto.EventDomain;
import at.procon.eventhub.dto.EventHubEventDto; import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.dto.EventLifecycle; import at.procon.eventhub.dto.EventLifecycle;
import at.procon.eventhub.dto.EventType; 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.ExtractedSupportEvent;
import at.procon.eventhub.tachographfilesession.model.ExtractionWarning; import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval; import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
@ -136,14 +137,14 @@ public class UnifiedEventTimelineReconstructor {
BigDecimal longitude = event.position() == null ? null : event.position().longitude(); BigDecimal longitude = event.position() == null ? null : event.position().longitude();
result.add(new ExtractedSupportEvent( result.add(new ExtractedSupportEvent(
eventId, eventId,
text(raw, "driverKey"), TachographRuntimeIdentityResolver.driverKey(event),
event.occurredAt(), event.occurredAt(),
event.eventDomain().name(), event.eventDomain().name(),
text(raw, "supportEventType") == null ? event.eventType().name() : text(raw, "supportEventType"), text(raw, "supportEventType") == null ? event.eventType().name() : text(raw, "supportEventType"),
event.lifecycle().name(), event.lifecycle().name(),
text(raw, "slot"), text(raw, "slot"),
text(raw, "registrationKey"), TachographRuntimeIdentityResolver.registrationKey(event),
text(raw, "vehicleKey"), TachographRuntimeIdentityResolver.vehicleKey(event),
firstNonBlank(text(raw, "country"), detailText(event, "country")), firstNonBlank(text(raw, "country"), detailText(event, "country")),
text(raw, "region"), text(raw, "region"),
firstNonBlank(text(raw, "countryFrom"), detailText(event, "countryFrom")), firstNonBlank(text(raw, "countryFrom"), detailText(event, "countryFrom")),
@ -381,8 +382,8 @@ public class UnifiedEventTimelineReconstructor {
detailText(sample, "cardSlot"), detailText(sample, "cardSlot"),
detailText(sample, "cardStatus"), detailText(sample, "cardStatus"),
detailText(sample, "drivingStatus"), detailText(sample, "drivingStatus"),
text(raw, "registrationKey"), TachographRuntimeIdentityResolver.registrationKey(sample),
text(raw, "vehicleKey"), TachographRuntimeIdentityResolver.vehicleKey(sample),
text(raw, "sourceKind"), text(raw, "sourceKind"),
stringList(raw, "sourceRowIds"), stringList(raw, "sourceRowIds"),
booleanValue(raw, "synthetic"), booleanValue(raw, "synthetic"),
@ -400,6 +401,7 @@ public class UnifiedEventTimelineReconstructor {
private OffsetDateTime endedAt; private OffsetDateTime endedAt;
private Long odometerBeginKm; private Long odometerBeginKm;
private Long odometerEndKm; private Long odometerEndKm;
private EventHubEventDto sample;
private JsonNode raw; private JsonNode raw;
private VehicleUsageAccumulator(UUID sessionId, String driverKey, String intervalId) { private VehicleUsageAccumulator(UUID sessionId, String driverKey, String intervalId) {
@ -409,6 +411,9 @@ public class UnifiedEventTimelineReconstructor {
} }
private void accept(EventHubEventDto event, JsonNode raw) { private void accept(EventHubEventDto event, JsonNode raw) {
if (sample == null) {
sample = event;
}
if (this.raw == null) { if (this.raw == null) {
this.raw = raw; this.raw = raw;
} }
@ -433,8 +438,8 @@ public class UnifiedEventTimelineReconstructor {
endedAt, endedAt,
odometerBeginKm, odometerBeginKm,
odometerEndKm, odometerEndKm,
text(raw, "registrationKey"), sample == null ? null : TachographRuntimeIdentityResolver.registrationKey(sample),
text(raw, "vehicleKey"), sample == null ? null : TachographRuntimeIdentityResolver.vehicleKey(sample),
text(raw, "sourceKind"), text(raw, "sourceKind"),
stringList(raw, "sourceRowIds") stringList(raw, "sourceRowIds")
); );

View File

@ -31,6 +31,7 @@ import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialIn
import at.procon.eventhub.tachographfilesession.model.TachographEsperSupportGeoEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperSupportGeoEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent; import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver;
import java.time.Duration; import java.time.Duration;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
@ -234,22 +235,7 @@ public class UnifiedRuntimeDerivedProjectionService {
UnifiedRuntimeProcessingRequest request, UnifiedRuntimeProcessingRequest request,
List<EventHubEventDto> events List<EventHubEventDto> events
) { ) {
if (request.driverKey() != null) { return TachographRuntimeIdentityResolver.requestDriverKey(request, events);
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();
} }

View File

@ -1,9 +1,9 @@
package at.procon.eventhub.processing.service; package at.procon.eventhub.processing.service;
import at.procon.eventhub.dto.DriverRefDto;
import at.procon.eventhub.dto.EventHubEventDto; import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle; import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest; import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
import at.procon.eventhub.processing.support.TachographRuntimeIdentityResolver;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline; import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import java.util.List; import java.util.List;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -35,21 +35,6 @@ public class UnifiedRuntimeDriverTimelineService {
UnifiedRuntimeProcessingRequest request, UnifiedRuntimeProcessingRequest request,
List<EventHubEventDto> events List<EventHubEventDto> events
) { ) {
if (request.driverKey() != null) { return TachographRuntimeIdentityResolver.requestDriverKey(request, events);
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();
} }
} }

View File

@ -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<EventHubEventDto> 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<EventHubEventDto> events) {
for (EventHubEventDto event : events == null ? List.<EventHubEventDto>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();
}
}

View File

@ -77,7 +77,7 @@ final class TachographRawPayloadSupport {
if (vehicleRef == null || !vehicleRef.hasAnyReference()) { if (vehicleRef == null || !vehicleRef.hasAnyReference()) {
return; return;
} }
put(raw, "vehicleKey", vehicleRef.stableKey()); put(raw, "vehicleKey", vehicleRef.vin());
put(raw, "vehicleSourceEntityId", vehicleRef.sourceVehicleEntityId()); put(raw, "vehicleSourceEntityId", vehicleRef.sourceVehicleEntityId());
put(raw, "vehicleVin", vehicleRef.vin()); put(raw, "vehicleVin", vehicleRef.vin());
put(raw, "vehicleRegistrationSourceEntityId", vehicleRef.sourceRegistrationEntityId()); put(raw, "vehicleRegistrationSourceEntityId", vehicleRef.sourceRegistrationEntityId());

View File

@ -257,9 +257,11 @@ public class DriverCardXmlExtractionService {
)); ));
} }
parsedChanges.sort(Comparator.comparing(ActivityChange::from)); 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); 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)) { if (!current.from().isBefore(to)) {
continue; continue;
} }

View File

@ -282,9 +282,11 @@ public class VehicleUnitXmlExtractionService {
)); ));
} }
parsedChanges.sort(Comparator.comparing(ActivityChange::from)); 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); 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)) { if (!current.from().isBefore(to)) {
continue; continue;
} }

View File

@ -35,6 +35,7 @@ class RuntimeEventProcessingServiceTest {
null, null,
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
null, null,
null,
"12:DRIVER-1", "12:DRIVER-1",
Set.of(), Set.of(),
false, false,

View File

@ -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<String, Object> activityPoint = DriverWorkingTimeEplEventMapper.activityPointEvents(List.of(activityEvent)).getFirst();
Map<String, Object> 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
);
}
}

View File

@ -58,6 +58,7 @@ class TachographDriverEsperRuntimeEventProcessingProfileTest {
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
null, null,
null, null,
null,
Set.of(), Set.of(),
false, false,
Set.of(), Set.of(),
@ -103,6 +104,7 @@ class TachographDriverEsperRuntimeEventProcessingProfileTest {
null, null,
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
null, null,
null,
"12:DRIVER-1", "12:DRIVER-1",
Set.of("12:DRIVER-1"), Set.of("12:DRIVER-1"),
false, false,

View File

@ -86,6 +86,7 @@ class RuntimeMixedSourceEvidenceValidationServiceTest {
"default", "default",
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
UnifiedRuntimeEventBackend.SOURCE_DB, UnifiedRuntimeEventBackend.SOURCE_DB,
null,
"DRIVER:1", "DRIVER:1",
Set.of(), Set.of(),
false, false,

View File

@ -72,6 +72,7 @@ class UnifiedDriverEventsRequestTest {
UnifiedEventSourceFamily.TACHOGRAPH_DB, UnifiedEventSourceFamily.TACHOGRAPH_DB,
null, null,
null, null,
null,
"default", "default",
null, null,
null, null,

View File

@ -54,6 +54,52 @@ class UnifiedRuntimeProcessingRequestTest {
assertThat(request.vehicleExpansionPaddingMinutes()).isEqualTo(30); 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 @Test
void canBuildEventHubBackedRuntimeRequest() { void canBuildEventHubBackedRuntimeRequest() {
UnifiedRuntimeProcessingRequest request = UnifiedRuntimeProcessingRequest.forDriverFromEventHub( UnifiedRuntimeProcessingRequest request = UnifiedRuntimeProcessingRequest.forDriverFromEventHub(
@ -138,6 +184,7 @@ class UnifiedRuntimeProcessingRequestTest {
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
UnifiedRuntimeEventBackend.SOURCE_DB, UnifiedRuntimeEventBackend.SOURCE_DB,
null, null,
null,
Set.of("12:123", "12:456"), Set.of("12:123", "12:456"),
false, false,
Set.of(), Set.of(),
@ -166,6 +213,7 @@ class UnifiedRuntimeProcessingRequestTest {
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
UnifiedRuntimeEventBackend.SOURCE_DB, UnifiedRuntimeEventBackend.SOURCE_DB,
null, null,
null,
Set.of(), Set.of(),
true, true,
Set.of(), Set.of(),
@ -194,6 +242,7 @@ class UnifiedRuntimeProcessingRequestTest {
null, null,
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
UnifiedRuntimeEventBackend.SOURCE_DB, UnifiedRuntimeEventBackend.SOURCE_DB,
null,
"12:123", "12:123",
Set.of(), Set.of(),
false, false,
@ -221,6 +270,7 @@ class UnifiedRuntimeProcessingRequestTest {
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_DB), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_DB),
UnifiedRuntimeEventBackend.SOURCE_DB, UnifiedRuntimeEventBackend.SOURCE_DB,
null, null,
null,
Set.of(), Set.of(),
false, false,
Set.of(), Set.of(),

View File

@ -56,6 +56,7 @@ class UnifiedVehicleEventsRequestTest {
assertThatThrownBy(() -> new UnifiedVehicleEventsRequest( assertThatThrownBy(() -> new UnifiedVehicleEventsRequest(
UnifiedEventSourceFamily.YELLOWFOX_DB, UnifiedEventSourceFamily.YELLOWFOX_DB,
null, null,
null,
"default", "default",
null, null,
null, null,

View File

@ -148,6 +148,53 @@ class RuntimeDriverVehicleEvidenceAttachmentServiceTest {
assertThat(result.toPartitionDebug().vehicleEvidenceDecisions()).hasSameSizeAs(result.vehicleEvidenceDecisions()); assertThat(result.toPartitionDebug().vehicleEvidenceDecisions()).hasSameSizeAs(result.vehicleEvidenceDecisions());
} }
@Test
void attachesEvidenceUsingCanonicalVinWhenDbRawVehicleKeyIsSourceSpecific() {
List<EventHubEventDto> 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( private EventHubEventDto cardEvent(
String externalId, String externalId,
EventType eventType, 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( private EventHubEventDto vehicleOnlyEvent(
String externalId, String externalId,
String vehicleKey, 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) { private VehicleRefDto vehicleRef(String vehicleKey, String registrationKey) {
String[] registrationParts = registrationKey.split(":", 2); String[] registrationParts = registrationKey == null ? null : registrationKey.split(":", 2);
return new VehicleRefDto( return new VehicleRefDto(
"VIN:" + vehicleKey, "VIN:" + vehicleKey,
vehicleKey, vehicleKey,
"VR:" + registrationKey, registrationKey == null ? null : "VR:" + registrationKey,
new VehicleRegistrationRefDto(registrationParts[0], registrationParts[1]) registrationParts == null ? null : new VehicleRegistrationRefDto(registrationParts[0], registrationParts[1])
); );
} }
private JsonNode raw(String driverKey, String intervalId, String vehicleKey, String registrationKey) { 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 root = OBJECT_MAPPER.createObjectNode();
ObjectNode raw = root.putObject("raw"); ObjectNode raw = root.putObject("raw");
if (driverKey != null) { if (driverKey != null) {
@ -226,8 +343,15 @@ class RuntimeDriverVehicleEvidenceAttachmentServiceTest {
raw.put("intervalId", intervalId); raw.put("intervalId", intervalId);
raw.put("sourceRowId", intervalId); raw.put("sourceRowId", intervalId);
} }
raw.put("vehicleKey", vehicleKey); if (rawVehicleKey != null) {
raw.put("vehicleKey", rawVehicleKey);
}
if (vehicleVin != null) {
raw.put("vehicleVin", vehicleVin);
}
if (registrationKey != null) {
raw.put("registrationKey", registrationKey); raw.put("registrationKey", registrationKey);
}
raw.put("sourceKind", "TEST"); raw.put("sourceKind", "TEST");
return root; return root;
} }

View File

@ -2,6 +2,7 @@ package at.procon.eventhub.processing.service;
import static org.assertj.core.api.Assertions.assertThat; 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.DriverRefDto;
import at.procon.eventhub.dto.EventDomain; import at.procon.eventhub.dto.EventDomain;
import at.procon.eventhub.dto.EventHubEventDto; import at.procon.eventhub.dto.EventHubEventDto;
@ -48,6 +49,7 @@ class UnifiedRuntimeDriverTimelineServiceTest {
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_DB), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_DB),
UnifiedRuntimeEventBackend.SOURCE_DB, UnifiedRuntimeEventBackend.SOURCE_DB,
null, null,
null,
Set.of(), Set.of(),
false, false,
Set.of(), Set.of(),
@ -68,6 +70,7 @@ class UnifiedRuntimeDriverTimelineServiceTest {
assertThat(timeline.activityIntervals()).hasSize(1); assertThat(timeline.activityIntervals()).hasSize(1);
assertThat(timeline.activityIntervals().get(0).intervalId()).isEqualTo("ACT-1"); assertThat(timeline.activityIntervals().get(0).intervalId()).isEqualTo("ACT-1");
assertThat(timeline.vehicleUsageIntervals()).hasSize(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.vehicleUsageIntervals().get(0).intervalId()).isEqualTo("CVU-1");
assertThat(timeline.supportEvents()).isEmpty(); assertThat(timeline.supportEvents()).isEmpty();
} }
@ -109,7 +112,7 @@ class UnifiedRuntimeDriverTimelineServiceTest {
return new EventHubEventDto( return new EventHubEventDto(
UUID.randomUUID(), UUID.randomUUID(),
intervalId + ":" + lifecycle.name(), intervalId + ":" + lifecycle.name(),
new DriverRefDto("DRIVER:42", null), driverRef(),
vehicleRef(), vehicleRef(),
OffsetDateTime.parse(occurredAt), OffsetDateTime.parse(occurredAt),
null, null,
@ -144,7 +147,7 @@ class UnifiedRuntimeDriverTimelineServiceTest {
return new EventHubEventDto( return new EventHubEventDto(
UUID.randomUUID(), UUID.randomUUID(),
intervalId + ":" + eventType.name(), intervalId + ":" + eventType.name(),
new DriverRefDto("DRIVER:42", null), driverRef(),
vehicleRef(), vehicleRef(),
OffsetDateTime.parse(occurredAt), OffsetDateTime.parse(occurredAt),
null, null,
@ -166,6 +169,10 @@ class UnifiedRuntimeDriverTimelineServiceTest {
return new VehicleRefDto("VEH-1", "VIN-1", "VR-1", new VehicleRegistrationRefDto("12", 12, "REG-1")); 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) { private EventHubPackageRequest packageInfo(EventDomain eventDomain) {
EventSourceDto source = new EventSourceDto("TACHOGRAPH", "DRIVER_CARD", "SOURCE", null, null, null); EventSourceDto source = new EventSourceDto("TACHOGRAPH", "DRIVER_CARD", "SOURCE", null, null, null);
return new EventHubPackageRequest( return new EventHubPackageRequest(

View File

@ -41,6 +41,7 @@ class UnifiedRuntimeEventAssemblyServiceTest {
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_DB), Set.of(UnifiedEventSourceFamily.TACHOGRAPH_DB),
UnifiedRuntimeEventBackend.SOURCE_DB, UnifiedRuntimeEventBackend.SOURCE_DB,
null, null,
null,
Set.of(), Set.of(),
false, false,
Set.of(), Set.of(),

View File

@ -40,11 +40,11 @@ class TachographDbRowMapperTimelineMetadataTest {
assertThat(raw.get("sourceKind").asText()).isEqualTo("DRIVER_CARD"); assertThat(raw.get("sourceKind").asText()).isEqualTo("DRIVER_CARD");
assertThat(raw.get("extractionCode").asText()).isEqualTo("CARD_ACTIVITY"); assertThat(raw.get("extractionCode").asText()).isEqualTo("CARD_ACTIVITY");
assertThat(raw.get("sourceRowIds").get(0).asText()).isEqualTo("CA-1"); assertThat(raw.get("sourceRowIds").get(0).asText()).isEqualTo("CA-1");
assertThat(raw.get("driverKey").asText()).contains("driver-1"); assertThat(raw.get("driverKey").asText()).isEqualTo(started.driverRef().stableKey());
assertThat(raw.get("vehicleKey").asText()).contains("VIN123"); assertThat(raw.get("vehicleKey").asText()).isEqualTo("VIN123");
assertThat(raw.get("registrationKey").asText()).contains("W-12345"); 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); assertThat(timeline.activityIntervals()).hasSize(1);
var interval = timeline.activityIntervals().getFirst(); var interval = timeline.activityIntervals().getFirst();
@ -52,8 +52,8 @@ class TachographDbRowMapperTimelineMetadataTest {
assertThat(interval.activityType()).isEqualTo("DRIVE"); assertThat(interval.activityType()).isEqualTo("DRIVE");
assertThat(interval.sourceKind()).isEqualTo("DRIVER_CARD"); assertThat(interval.sourceKind()).isEqualTo("DRIVER_CARD");
assertThat(interval.sourceIntervalIds()).containsExactly("CA-1"); assertThat(interval.sourceIntervalIds()).containsExactly("CA-1");
assertThat(interval.registrationKey()).contains("W-12345"); assertThat(interval.registrationKey()).isEqualTo(started.vehicleRef().vehicleRegistration().stableKey());
assertThat(interval.vehicleKey()).contains("VIN123"); assertThat(interval.vehicleKey()).isEqualTo("VIN123");
assertThat(interval.durationSeconds()).isEqualTo(4_500L); assertThat(interval.durationSeconds()).isEqualTo(4_500L);
} }
@ -67,17 +67,17 @@ class TachographDbRowMapperTimelineMetadataTest {
JsonNode raw = inserted.payload().get("raw"); JsonNode raw = inserted.payload().get("raw");
assertThat(raw.get("intervalId").asText()).isEqualTo("TACHOGRAPH:CARD_VEHICLES_USED:CVU-1"); 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("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); assertThat(timeline.vehicleUsageIntervals()).hasSize(1);
var usage = timeline.vehicleUsageIntervals().getFirst(); var usage = timeline.vehicleUsageIntervals().getFirst();
assertThat(usage.intervalId()).isEqualTo("TACHOGRAPH:CARD_VEHICLES_USED:CVU-1"); assertThat(usage.intervalId()).isEqualTo("TACHOGRAPH:CARD_VEHICLES_USED:CVU-1");
assertThat(usage.sourceKind()).isEqualTo("DRIVER_CARD"); assertThat(usage.sourceKind()).isEqualTo("DRIVER_CARD");
assertThat(usage.sourceIntervalIds()).containsExactly("CVU-1"); assertThat(usage.sourceIntervalIds()).containsExactly("CVU-1");
assertThat(usage.registrationKey()).contains("W-12345"); assertThat(usage.registrationKey()).isEqualTo(inserted.vehicleRef().vehicleRegistration().stableKey());
assertThat(usage.vehicleKey()).contains("VIN123"); assertThat(usage.vehicleKey()).isEqualTo("VIN123");
assertThat(usage.odometerBeginKm()).isEqualTo(120L); assertThat(usage.odometerBeginKm()).isEqualTo(120L);
assertThat(usage.odometerEndKm()).isEqualTo(120L); assertThat(usage.odometerEndKm()).isEqualTo(120L);
assertThat(usage.durationSeconds()).isEqualTo(43_200L); assertThat(usage.durationSeconds()).isEqualTo(43_200L);

View File

@ -6,6 +6,7 @@ import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
import at.procon.eventhub.tachographfilesession.model.TachographFileSession; import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata; import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -45,10 +46,14 @@ class DriverCardXmlExtractionServiceTest {
assertThat(driver.vehicleRegistrations()).hasSize(2); assertThat(driver.vehicleRegistrations()).hasSize(2);
assertThat(driver.vehicles()).hasSize(2); assertThat(driver.vehicles()).hasSize(2);
assertThat(driver.cardVehicleUsageIntervals()).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(0).registrationKey()).isEqualTo("12:W-12345A");
assertThat(driver.cardActivityIntervals().get(1).to()).isEqualTo(driver.cardVehicleUsageIntervals().get(0).to().plusSeconds(1)); 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(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()).hasSize(5);
assertThat(driver.supportEvents()).extracting("eventDomain") assertThat(driver.supportEvents()).extracting("eventDomain")
.containsExactly("PLACE", "POSITION", "SPECIFIC_CONDITION", "BORDER_CROSSING", "LOAD_UNLOAD"); .containsExactly("PLACE", "POSITION", "SPECIFIC_CONDITION", "BORDER_CROSSING", "LOAD_UNLOAD");
@ -79,10 +84,10 @@ class DriverCardXmlExtractionServiceTest {
); );
DriverExtractionSession driver = session.driversByKey().values().iterator().next(); DriverExtractionSession driver = session.driversByKey().values().iterator().next();
assertThat(driver.cardActivityIntervals()).hasSize(3); assertThat(driver.cardActivityIntervals()).hasSize(5);
assertThat(driver.cardActivityIntervals()) assertThat(driver.cardActivityIntervals())
.extracting(interval -> interval.from().toString()) .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 @Test
@ -108,8 +113,11 @@ class DriverCardXmlExtractionServiceTest {
DriverExtractionSession driver = session.driversByKey().values().iterator().next(); DriverExtractionSession driver = session.driversByKey().values().iterator().next();
assertThat(driver.cardVehicleUsageIntervals()).hasSize(2); assertThat(driver.cardVehicleUsageIntervals()).hasSize(2);
assertThat(driver.cardVehicleUsageIntervals().get(1).to()).isNull(); 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(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()).hasSize(5);
assertThat(driver.supportEvents().stream() assertThat(driver.supportEvents().stream()
.filter(event -> "12:W-54321B".equals(event.registrationKey())) .filter(event -> "12:W-54321B".equals(event.registrationKey()))

View File

@ -7,6 +7,7 @@ import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata; import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
import java.io.StringReader; import java.io.StringReader;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -52,9 +53,11 @@ class VehicleUnitXmlExtractionServiceTest {
assertThat(firstDriver.cardVehicleUsageIntervals()).hasSize(1); assertThat(firstDriver.cardVehicleUsageIntervals()).hasSize(1);
assertThat(firstDriver.cardVehicleUsageIntervals().get(0).from().toString()).isEqualTo("2026-04-01T08:00Z"); 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.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(0).activityType()).isEqualTo("WORK");
assertThat(firstDriver.cardActivityIntervals().get(1).activityType()).isEqualTo("DRIVE"); 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()).hasSize(1);
assertThat(firstDriver.supportEvents().get(0).eventDomain()).isEqualTo("PLACE"); assertThat(firstDriver.supportEvents().get(0).eventDomain()).isEqualTo("PLACE");
assertThat(firstDriver.supportEvents().get(0).eventType()).isEqualTo("BEGIN_DAILY_WORK_PERIOD"); 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()).hasSize(1);
assertThat(secondDriver.cardVehicleUsageIntervals().get(0).to()).isNull(); 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(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()).hasSize(6);
assertThat(secondDriver.supportEvents()).extracting("eventDomain") assertThat(secondDriver.supportEvents()).extracting("eventDomain")
.containsExactly("POSITION", "SPECIFIC_CONDITION", "BORDER_CROSSING", "LOAD_UNLOAD", "SPEEDING", "SPEEDING"); .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(4).eventLifecycle()).isEqualTo("BEGIN");
assertThat(secondDriver.supportEvents().get(5).eventLifecycle()).isEqualTo("END"); assertThat(secondDriver.supportEvents().get(5).eventLifecycle()).isEqualTo("END");
assertThat(session.warnings()).isEmpty(); assertThat(session.warnings()).extracting("code").containsExactly("VU_ACTIVITY_UNASSIGNED");
} }
@Test @Test