Extract VU support events into driver sessions

This commit is contained in:
trifonovt 2026-05-12 15:35:31 +02:00
parent e30f98b2c0
commit 4ad6fd7dac
12 changed files with 451 additions and 5 deletions

View File

@ -351,19 +351,21 @@ public class EventHubProperties {
}
public static class LegalRequirements {
private String baseUrl = "https://legalrequirements.services.bytebar.eu/ODataV4/LR";
private static final String DEFAULT_BASE_URL = "https://legalrequirements.services.bytebar.eu/ODataV4/LR";
private String baseUrl = DEFAULT_BASE_URL;
private String username;
private String password;
private Duration connectTimeout = Duration.ofSeconds(20);
private Duration readTimeout = Duration.ofMinutes(2);
private boolean resetSessionAfterUse = true;
private boolean resetSessionAfterUse = false;
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
this.baseUrl = normalizeBaseUrl(baseUrl);
}
public String getUsername() {
@ -409,6 +411,22 @@ public class EventHubProperties {
public void setResetSessionAfterUse(boolean resetSessionAfterUse) {
this.resetSessionAfterUse = resetSessionAfterUse;
}
private String normalizeBaseUrl(String baseUrl) {
if (baseUrl == null || baseUrl.isBlank()) {
return DEFAULT_BASE_URL;
}
String normalized = baseUrl.trim();
while (normalized.startsWith(":")) {
normalized = normalized.substring(1).trim();
}
while (normalized.endsWith("/")) {
normalized = normalized.substring(0, normalized.length() - 1).trim();
}
return normalized.isBlank() ? DEFAULT_BASE_URL : normalized;
}
}
public static class TachographDataSource {

View File

@ -4,6 +4,7 @@ import at.procon.eventhub.tachographfilesession.model.ExtractedCardActivityInter
import at.procon.eventhub.tachographfilesession.model.ExtractedCardVehicleUsageInterval;
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
import at.procon.eventhub.tachographfilesession.model.ExtractedDriverCard;
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicle;
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicleRegistration;
import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
@ -19,6 +20,7 @@ public record TachographFileDriverDetailDto(
List<ExtractedVehicle> vehicles,
List<ExtractedCardVehicleUsageInterval> cardVehicleUsageIntervals,
List<ExtractedCardActivityInterval> cardActivityIntervals,
List<ExtractedSupportEvent> supportEvents,
List<ExtractionWarning> warnings
) {
}

View File

@ -10,6 +10,7 @@ public record DriverExtractionSession(
List<ExtractedVehicle> vehicles,
List<ExtractedCardVehicleUsageInterval> cardVehicleUsageIntervals,
List<ExtractedCardActivityInterval> cardActivityIntervals,
List<ExtractedSupportEvent> supportEvents,
List<ExtractionWarning> warnings
) {
}

View File

@ -0,0 +1,23 @@
package at.procon.eventhub.tachographfilesession.model;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
public record ExtractedSupportEvent(
String eventId,
OffsetDateTime occurredAt,
String eventDomain,
String eventType,
String slot,
String registrationKey,
String vehicleKey,
String country,
String region,
BigDecimal latitude,
BigDecimal longitude,
String authenticationStatus,
Long odometerKm,
String code,
String rawRecordPath
) {
}

View File

@ -87,6 +87,7 @@ public class DriverCardXmlExtractionService {
List.copyOf(vehiclesByKey.values()),
List.copyOf(vehicleUsageIntervals),
List.copyOf(activityIntervals),
List.of(),
List.copyOf(warnings)
);
Map<String, DriverExtractionSession> driversByKey = Map.of(driverKey, driverSession);

View File

@ -111,6 +111,7 @@ public class TachographFileSessionService {
driver.vehicles(),
driver.cardVehicleUsageIntervals(),
driver.cardActivityIntervals(),
driver.supportEvents(),
driver.warnings()
);
}

View File

@ -5,12 +5,14 @@ import at.procon.eventhub.tachographfilesession.model.ExtractedCardActivityInter
import at.procon.eventhub.tachographfilesession.model.ExtractedCardVehicleUsageInterval;
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
import at.procon.eventhub.tachographfilesession.model.ExtractedDriverCard;
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicle;
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicleRegistration;
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
@ -150,6 +152,7 @@ public class VehicleUnitXmlExtractionService {
List<ExtractedCardActivityInterval> vuActivityIntervals = extractActivityIntervals(document, vehicleContext, sessionWarnings);
assignActivityCoverage(vuActivityIntervals, vuCardIwIntervals, vehicleContext, driversByKey, sessionWarnings);
extractSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, sessionWarnings);
List<ExtractionWarning> allWarnings = new ArrayList<>(sessionWarnings);
Map<String, DriverExtractionSession> driverSessions = new LinkedHashMap<>();
@ -369,6 +372,205 @@ public class VehicleUnitXmlExtractionService {
}
}
private void extractSupportEvents(
Document document,
VehicleContext vehicleContext,
List<VuCardIwInterval> vuCardIwIntervals,
Map<String, DriverExtractionBuilder> driversByKey,
List<ExtractionWarning> warnings
) {
extractVuPlaceSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
extractVuGnssSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
extractVuSpecificConditionSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
}
private void extractVuPlaceSupportEvents(
Document document,
VehicleContext vehicleContext,
List<VuCardIwInterval> vuCardIwIntervals,
Map<String, DriverExtractionBuilder> driversByKey,
List<ExtractionWarning> warnings
) {
NodeList records = nodes(document, "/VehicleUnit/Activities/vuPlaceDailyWorkPeriodData/vuPlaceDailyWorkPeriodRecords");
for (int i = 0; i < records.getLength(); i++) {
Element record = (Element) records.item(i);
String path = "/VehicleUnit/Activities/vuPlaceDailyWorkPeriodData/vuPlaceDailyWorkPeriodRecords[" + (i + 1) + "]";
OffsetDateTime occurredAt = offsetDateTime(text(record, "placeRecord/entryTime"));
if (occurredAt == null) {
warnings.add(new ExtractionWarning("VU_PLACE_MISSING_TIME", "Vehicle-unit place record is missing entryTime.", path));
continue;
}
String entryType = text(record, "placeRecord/entryTypeDailyWorkPeriod");
List<DriverAssignment> assignments = resolveDriverAssignments(
occurredAt,
driverKeyFromCardNode(record, "fullCardNumber"),
null,
vuCardIwIntervals
);
if (assignments.isEmpty()) {
warnings.add(new ExtractionWarning("VU_PLACE_UNASSIGNED", "Vehicle-unit place record could not be assigned to a driver session.", path));
continue;
}
if (assignments.size() > 1) {
warnings.add(new ExtractionWarning("VU_PLACE_AMBIGUOUS", "Vehicle-unit place record matched multiple active driver sessions.", path));
}
BigDecimal latitude = geoCoordinate(record, "placeRecord/entryGnssPlaceRecord/geoCoordinates/latitude", true);
BigDecimal longitude = geoCoordinate(record, "placeRecord/entryGnssPlaceRecord/geoCoordinates/longitude", false);
String authenticationStatus = normalizeToken(text(record, "placeRecord/entryGnssPlaceRecord/authenticationStatus"));
for (DriverAssignment assignment : assignments) {
addSupportEvent(
driversByKey,
assignment,
new ExtractedSupportEvent(
"VUPLACE-" + (i + 1) + "-" + assignment.driverKey(),
occurredAt,
"PLACE",
mapPlaceEntryType(entryType),
assignment.slot(),
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
text(record, "placeRecord/dailyWorkPeriodCountry"),
text(record, "placeRecord/dailyWorkPeriodRegion"),
latitude,
longitude,
authenticationStatus,
longValue(text(record, "placeRecord/vehicleOdometerValue")),
normalizeToken(entryType),
path
),
warnings
);
}
}
}
private void extractVuGnssSupportEvents(
Document document,
VehicleContext vehicleContext,
List<VuCardIwInterval> vuCardIwIntervals,
Map<String, DriverExtractionBuilder> driversByKey,
List<ExtractionWarning> warnings
) {
NodeList records = nodes(document, "/VehicleUnit/Activities/vuGnssADData/vuGnssADRecord");
for (int i = 0; i < records.getLength(); i++) {
Element record = (Element) records.item(i);
String path = "/VehicleUnit/Activities/vuGnssADData/vuGnssADRecord[" + (i + 1) + "]";
OffsetDateTime occurredAt = offsetDateTime(text(record, "timeStamp"));
if (occurredAt == null) {
warnings.add(new ExtractionWarning("VU_GNSS_MISSING_TIME", "Vehicle-unit GNSS record is missing timeStamp.", path));
continue;
}
List<DriverAssignment> assignments = new ArrayList<>();
assignments.addAll(resolveDriverAssignments(
occurredAt,
driverKeyFromCardNode(record, "cardNumberDriverSlot"),
"DRIVER",
vuCardIwIntervals
));
assignments.addAll(resolveDriverAssignments(
occurredAt,
driverKeyFromCardNode(record, "cardNumberCodriverSlot"),
"CO_DRIVER",
vuCardIwIntervals
));
assignments = distinctAssignments(assignments);
if (assignments.isEmpty()) {
warnings.add(new ExtractionWarning("VU_GNSS_UNASSIGNED", "Vehicle-unit GNSS record could not be assigned to a driver session.", path));
continue;
}
BigDecimal latitude = geoCoordinate(record, "gnssPlaceRecord/geoCoordinates/latitude", true);
BigDecimal longitude = geoCoordinate(record, "gnssPlaceRecord/geoCoordinates/longitude", false);
String authenticationStatus = normalizeToken(text(record, "gnssPlaceRecord/authenticationStatus"));
for (DriverAssignment assignment : assignments) {
addSupportEvent(
driversByKey,
assignment,
new ExtractedSupportEvent(
"VUGNSS-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
occurredAt,
"POSITION",
"GNSS_ACCUMULATED_DRIVING",
assignment.slot(),
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
null,
null,
latitude,
longitude,
authenticationStatus,
longValue(text(record, "vehicleOdometerValue")),
null,
path
),
warnings
);
}
}
}
private void extractVuSpecificConditionSupportEvents(
Document document,
VehicleContext vehicleContext,
List<VuCardIwInterval> vuCardIwIntervals,
Map<String, DriverExtractionBuilder> driversByKey,
List<ExtractionWarning> warnings
) {
NodeList records = nodes(document, "/VehicleUnit/Activities/vuSpecificConditionData/specificConditionRecords");
for (int i = 0; i < records.getLength(); i++) {
Element record = (Element) records.item(i);
String path = "/VehicleUnit/Activities/vuSpecificConditionData/specificConditionRecords[" + (i + 1) + "]";
OffsetDateTime occurredAt = offsetDateTime(text(record, "entryTime"));
if (occurredAt == null) {
warnings.add(new ExtractionWarning("VU_SPECIFIC_CONDITION_MISSING_TIME", "Vehicle-unit specific-condition record is missing entryTime.", path));
continue;
}
List<DriverAssignment> assignments = distinctAssignments(resolveDriverAssignments(
occurredAt,
null,
null,
vuCardIwIntervals
));
if (assignments.isEmpty()) {
warnings.add(new ExtractionWarning("VU_SPECIFIC_CONDITION_UNASSIGNED", "Vehicle-unit specific-condition record could not be assigned to an active driver session.", path));
continue;
}
if (assignments.size() > 1) {
warnings.add(new ExtractionWarning("VU_SPECIFIC_CONDITION_AMBIGUOUS", "Vehicle-unit specific-condition record matched multiple active driver sessions.", path));
}
String conditionCode = normalizeToken(text(record, "specificConditionType"));
for (DriverAssignment assignment : assignments) {
addSupportEvent(
driversByKey,
assignment,
new ExtractedSupportEvent(
"VUSC-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
occurredAt,
"SPECIFIC_CONDITION",
"SPECIFIC_CONDITION",
assignment.slot(),
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
null,
null,
null,
null,
null,
null,
conditionCode,
path
),
warnings
);
}
}
}
private boolean isPartiallyCovered(ExtractedCardActivityInterval interval, List<ActivitySegment> segments) {
if (segments.isEmpty()) {
return true;
@ -429,6 +631,54 @@ public class VehicleUnitXmlExtractionService {
return segments;
}
private void addSupportEvent(
Map<String, DriverExtractionBuilder> driversByKey,
DriverAssignment assignment,
ExtractedSupportEvent supportEvent,
List<ExtractionWarning> warnings
) {
DriverExtractionBuilder builder = driversByKey.get(assignment.driverKey());
if (builder == null) {
warnings.add(new ExtractionWarning(
"VU_SUPPORT_EVENT_DRIVER_MISSING",
"Support event matched a driver key without an initialized driver session.",
supportEvent.rawRecordPath()
));
return;
}
builder.supportEvents.add(supportEvent);
}
private List<DriverAssignment> resolveDriverAssignments(
OffsetDateTime occurredAt,
String explicitDriverKey,
String explicitSlot,
List<VuCardIwInterval> vuCardIwIntervals
) {
if (explicitDriverKey != null) {
return List.of(new DriverAssignment(explicitDriverKey, explicitSlot));
}
return vuCardIwIntervals.stream()
.filter(iw -> explicitSlot == null || explicitSlot.equals(iw.slot()))
.filter(iw -> iw.covers(occurredAt))
.map(iw -> new DriverAssignment(iw.driverKey(), iw.slot()))
.toList();
}
private List<DriverAssignment> distinctAssignments(List<DriverAssignment> assignments) {
Map<String, DriverAssignment> unique = new LinkedHashMap<>();
for (DriverAssignment assignment : assignments) {
unique.putIfAbsent(assignment.driverKey() + "|" + assignment.slot(), assignment);
}
return List.copyOf(unique.values());
}
private String driverKeyFromCardNode(Element node, String basePath) {
String cardNation = text(node, basePath + "/cardIssuingMemberState");
String cardNumber = joinCardNumber(node, basePath + "/cardNumber");
return cardNumber == null ? null : driverKeyFactory.createDriverKey(cardNation, cardNumber);
}
private Element firstElement(Object node, String expression) {
NodeList nodes = nodes(node, expression);
if (nodes.getLength() == 0) {
@ -470,6 +720,26 @@ public class VehicleUnitXmlExtractionService {
return Long.parseLong(value.trim());
}
private BigDecimal geoCoordinate(Object node, String expression, boolean latitude) {
String value = text(node, expression);
if (value == null) {
return null;
}
BigDecimal raw = new BigDecimal(value);
BigDecimal abs = raw.abs();
BigDecimal degreeThreshold = BigDecimal.valueOf(latitude ? 90D : 180D);
if (abs.compareTo(degreeThreshold) <= 0) {
return raw;
}
BigDecimal sign = raw.signum() < 0 ? BigDecimal.valueOf(-1L) : BigDecimal.ONE;
BigDecimal absolute = raw.abs();
BigDecimal[] degreeAndRemainder = absolute.divideAndRemainder(BigDecimal.valueOf(1000L));
BigDecimal degrees = degreeAndRemainder[0];
BigDecimal minutes = degreeAndRemainder[1].divide(BigDecimal.TEN);
BigDecimal decimalDegrees = degrees.add(minutes.divide(BigDecimal.valueOf(60L), 8, java.math.RoundingMode.HALF_UP));
return decimalDegrees.multiply(sign);
}
private OffsetDateTime combine(LocalDate date, String timeText) {
if (date == null || timeText == null || timeText.isBlank()) {
return null;
@ -499,6 +769,24 @@ public class VehicleUnitXmlExtractionService {
return value.trim().toUpperCase().replace('-', '_').replace(' ', '_');
}
private String mapPlaceEntryType(String entryType) {
String normalized = normalizeToken(entryType);
if (normalized == null) {
return "DAILY_WORK_PERIOD_PLACE";
}
return switch (normalized) {
case "0" -> "BEGIN_DAILY_WORK_PERIOD";
case "1" -> "END_DAILY_WORK_PERIOD";
case "2" -> "BEGIN_MANUAL_DAILY_WORK_PERIOD";
case "3" -> "END_MANUAL_DAILY_WORK_PERIOD";
case "4" -> "BEGIN_ASSUMED_DAILY_WORK_PERIOD";
case "5" -> "END_ASSUMED_DAILY_WORK_PERIOD";
case "6" -> "BEGIN_GNSS_DAILY_WORK_PERIOD";
case "7" -> "END_GNSS_DAILY_WORK_PERIOD";
default -> "DAILY_WORK_PERIOD_PLACE";
};
}
private String joinCardNumber(Element node, String basePath) {
String driverIdentification = text(node, basePath + "/driverIdentification");
if (driverIdentification == null) {
@ -525,6 +813,7 @@ public class VehicleUnitXmlExtractionService {
private final Map<String, ExtractedVehicle> vehiclesByKey = new LinkedHashMap<>();
private final List<ExtractedCardVehicleUsageInterval> vehicleUsageIntervals = new ArrayList<>();
private final List<ExtractedCardActivityInterval> cardActivityIntervals = new ArrayList<>();
private final List<ExtractedSupportEvent> supportEvents = new ArrayList<>();
private final List<ExtractionWarning> warnings = new ArrayList<>();
private DriverExtractionBuilder(String driverKey, String sourceDriverId) {
@ -588,6 +877,7 @@ public class VehicleUnitXmlExtractionService {
List.copyOf(vehiclesByKey.values()),
List.copyOf(vehicleUsageIntervals),
List.copyOf(cardActivityIntervals),
List.copyOf(supportEvents),
List.copyOf(warnings)
);
}
@ -644,4 +934,10 @@ public class VehicleUnitXmlExtractionService {
OffsetDateTime to
) {
}
private record DriverAssignment(
String driverKey,
String slot
) {
}
}

View File

@ -131,7 +131,7 @@ eventhub:
password: ${LEGAL_REQUIREMENTS_PASSWORD:}
connect-timeout: 20s
read-timeout: 2m
reset-session-after-use: true
reset-session-after-use: false
esper-poc:
activity-merge-mode: JAVA

View File

@ -0,0 +1,34 @@
package at.procon.eventhub.config;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import org.junit.jupiter.api.Test;
class EventHubPropertiesTest {
@Test
void legalRequirementsBaseUrlIsNormalizedWhenPrefixedWithColonAndWhitespace() {
EventHubProperties.LegalRequirements legalRequirements = new EventHubProperties.LegalRequirements();
legalRequirements.setBaseUrl(": https://legalrequirements.services.bytebar.eu/ODataV4/LR/");
assertEquals("https://legalrequirements.services.bytebar.eu/ODataV4/LR", legalRequirements.getBaseUrl());
}
@Test
void legalRequirementsBaseUrlFallsBackToDefaultWhenBlank() {
EventHubProperties.LegalRequirements legalRequirements = new EventHubProperties.LegalRequirements();
legalRequirements.setBaseUrl(" ");
assertEquals("https://legalrequirements.services.bytebar.eu/ODataV4/LR", legalRequirements.getBaseUrl());
}
@Test
void legalRequirementsResetSessionIsDisabledByDefault() {
EventHubProperties.LegalRequirements legalRequirements = new EventHubProperties.LegalRequirements();
assertFalse(legalRequirements.isResetSessionAfterUse());
}
}

View File

@ -52,7 +52,7 @@ class TachographFileSessionControllerTest {
.thenReturn(new CreateTachographFileSessionResponse(summary));
when(service.getSession(sessionId)).thenReturn(summary);
when(service.listDrivers(sessionId)).thenReturn(new TachographFileSessionListDriversResponse(sessionId, List.of(driver)));
when(service.getDriver(sessionId, "12:123")).thenReturn(new TachographFileDriverDetailDto(sessionId, "12:123", null, null, List.of(), List.of(), List.of(), List.of(), List.of()));
when(service.getDriver(sessionId, "12:123")).thenReturn(new TachographFileDriverDetailDto(sessionId, "12:123", null, null, List.of(), List.of(), List.of(), List.of(), List.of(), List.of()));
when(service.deleteSession(sessionId)).thenReturn(new TachographFileSessionDeleteResponse(sessionId, true));
mockMvc.perform(multipart("/api/eventhub/tachograph-file-sessions")

View File

@ -56,12 +56,21 @@ class VehicleUnitXmlExtractionServiceTest {
assertThat(firstDriver.cardActivityIntervals().get(0).activityType()).isEqualTo("WORK");
assertThat(firstDriver.cardActivityIntervals().get(1).activityType()).isEqualTo("DRIVE");
assertThat(firstDriver.cardActivityIntervals().get(2).to().toString()).isEqualTo("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");
assertThat(firstDriver.supportEvents().get(0).country()).isEqualTo("12");
assertThat(firstDriver.supportEvents().get(0).latitude()).isNotNull();
assertThat(secondDriver.cardVehicleUsageIntervals()).hasSize(1);
assertThat(secondDriver.cardVehicleUsageIntervals().get(0).to().toString()).isEqualTo("2026-04-02T10:00Z");
assertThat(secondDriver.cardActivityIntervals()).hasSize(2);
assertThat(secondDriver.cardActivityIntervals().get(0).from().toString()).isEqualTo("2026-04-02T07:30Z");
assertThat(secondDriver.cardActivityIntervals().get(1).to().toString()).isEqualTo("2026-04-02T10:00:01Z");
assertThat(secondDriver.supportEvents()).hasSize(2);
assertThat(secondDriver.supportEvents()).extracting("eventDomain").containsExactly("POSITION", "SPECIFIC_CONDITION");
assertThat(secondDriver.supportEvents().get(0).latitude()).isNotNull();
assertThat(secondDriver.supportEvents().get(1).code()).isEqualTo("1");
assertThat(session.warnings()).extracting("code")
.contains("OPEN_VU_CARD_INTERVAL", "VU_ACTIVITY_UNASSIGNED");

View File

@ -108,6 +108,67 @@ final class VehicleUnitXmlSamples {
<timeOfChange>08:00:00Z</timeOfChange>
</activityChangeInfos>
</vuActivityDailyData>
<vuPlaceDailyWorkPeriodData>
<vuPlaceDailyWorkPeriodRecords>
<fullCardNumber>
<cardType>DRIVER_CARD</cardType>
<cardIssuingMemberState>12</cardIssuingMemberState>
<cardNumber>
<driverIdentification>123456789012</driverIdentification>
<cardReplacementIndex>0</cardReplacementIndex>
<cardRenewalIndex>0</cardRenewalIndex>
</cardNumber>
<generation>2</generation>
</fullCardNumber>
<placeRecord>
<entryTime>2026-04-01T08:00:00Z</entryTime>
<entryTypeDailyWorkPeriod>0</entryTypeDailyWorkPeriod>
<dailyWorkPeriodCountry>12</dailyWorkPeriodCountry>
<dailyWorkPeriodRegion>9</dailyWorkPeriodRegion>
<vehicleOdometerValue>1000</vehicleOdometerValue>
<entryGnssPlaceRecord>
<timeStamp>2026-04-01T08:00:00Z</timeStamp>
<gnssAccuracy>5</gnssAccuracy>
<geoCoordinates>
<latitude>48.2082</latitude>
<longitude>16.3738</longitude>
</geoCoordinates>
<authenticationStatus>AUTHENTICATED</authenticationStatus>
</entryGnssPlaceRecord>
</placeRecord>
</vuPlaceDailyWorkPeriodRecords>
</vuPlaceDailyWorkPeriodData>
<vuGnssADData>
<vuGnssADRecord>
<timeStamp>2026-04-02T08:00:00Z</timeStamp>
<cardNumberDriverSlot>
<cardType>DRIVER_CARD</cardType>
<cardIssuingMemberState>12</cardIssuingMemberState>
<cardNumber>
<driverIdentification>999999999999</driverIdentification>
<cardReplacementIndex>1</cardReplacementIndex>
<cardRenewalIndex>1</cardRenewalIndex>
</cardNumber>
<generation>2</generation>
</cardNumberDriverSlot>
<gnssPlaceRecord>
<timeStamp>2026-04-02T08:00:00Z</timeStamp>
<gnssAccuracy>4</gnssAccuracy>
<geoCoordinates>
<latitude>48012</latitude>
<longitude>16373</longitude>
</geoCoordinates>
<authenticationStatus>NOT_AUTHENTICATED</authenticationStatus>
</gnssPlaceRecord>
<vehicleOdometerValue>1210</vehicleOdometerValue>
</vuGnssADRecord>
</vuGnssADData>
<vuSpecificConditionData>
<specificConditionRecords>
<entryTime>2026-04-02T09:00:00Z</entryTime>
<specificConditionType>1</specificConditionType>
</specificConditionRecords>
</vuSpecificConditionData>
</Activities>
</VehicleUnit>
""";