Extract VU activity intervals into driver sessions
This commit is contained in:
parent
6b43a4b0e8
commit
e30f98b2c0
|
|
@ -12,6 +12,8 @@ import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
|
||||||
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.LocalDate;
|
||||||
|
import java.time.LocalTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -19,6 +21,7 @@ import java.util.Comparator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import javax.xml.xpath.XPath;
|
import javax.xml.xpath.XPath;
|
||||||
import javax.xml.xpath.XPathConstants;
|
import javax.xml.xpath.XPathConstants;
|
||||||
|
|
@ -51,14 +54,7 @@ public class VehicleUnitXmlExtractionService {
|
||||||
|
|
||||||
VehicleContext vehicleContext = extractVehicleContext(document, sessionWarnings);
|
VehicleContext vehicleContext = extractVehicleContext(document, sessionWarnings);
|
||||||
Map<String, DriverExtractionBuilder> driversByKey = new LinkedHashMap<>();
|
Map<String, DriverExtractionBuilder> driversByKey = new LinkedHashMap<>();
|
||||||
|
List<VuCardIwInterval> vuCardIwIntervals = new ArrayList<>();
|
||||||
if (nodes(document, "/VehicleUnit/Activities/vuActivityDailyData").getLength() > 0) {
|
|
||||||
sessionWarnings.add(new ExtractionWarning(
|
|
||||||
"VU_ACTIVITY_NOT_EXTRACTED",
|
|
||||||
"Vehicle-unit daily activity data is present but is not yet mapped into card activity intervals in this phase.",
|
|
||||||
"/VehicleUnit/Activities/vuActivityDailyData"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeList records = nodes(document, "/VehicleUnit/Activities/vuCardIWData/vuCardIWRecords");
|
NodeList records = nodes(document, "/VehicleUnit/Activities/vuCardIWData/vuCardIWRecords");
|
||||||
for (int i = 0; i < records.getLength(); i++) {
|
for (int i = 0; i < records.getLength(); i++) {
|
||||||
|
|
@ -135,6 +131,13 @@ public class VehicleUnitXmlExtractionService {
|
||||||
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
||||||
path
|
path
|
||||||
));
|
));
|
||||||
|
vuCardIwIntervals.add(new VuCardIwInterval(
|
||||||
|
driverKey,
|
||||||
|
normalizeToken(text(record, "cardSlotNumber")),
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
path
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (driversByKey.isEmpty()) {
|
if (driversByKey.isEmpty()) {
|
||||||
|
|
@ -145,6 +148,9 @@ public class VehicleUnitXmlExtractionService {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<ExtractedCardActivityInterval> vuActivityIntervals = extractActivityIntervals(document, vehicleContext, sessionWarnings);
|
||||||
|
assignActivityCoverage(vuActivityIntervals, vuCardIwIntervals, vehicleContext, driversByKey, sessionWarnings);
|
||||||
|
|
||||||
List<ExtractionWarning> allWarnings = new ArrayList<>(sessionWarnings);
|
List<ExtractionWarning> allWarnings = new ArrayList<>(sessionWarnings);
|
||||||
Map<String, DriverExtractionSession> driverSessions = new LinkedHashMap<>();
|
Map<String, DriverExtractionSession> driverSessions = new LinkedHashMap<>();
|
||||||
int activityCount = 0;
|
int activityCount = 0;
|
||||||
|
|
@ -187,7 +193,7 @@ public class VehicleUnitXmlExtractionService {
|
||||||
"Vehicle-unit XML does not contain an Overview block.",
|
"Vehicle-unit XML does not contain an Overview block.",
|
||||||
"/VehicleUnit"
|
"/VehicleUnit"
|
||||||
));
|
));
|
||||||
return new VehicleContext(null, null, null);
|
return new VehicleContext(null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
String registrationNation = text(overview, "vehicleRegistrationIdentification/vehicleRegistrationNation");
|
String registrationNation = text(overview, "vehicleRegistrationIdentification/vehicleRegistrationNation");
|
||||||
|
|
@ -217,7 +223,210 @@ public class VehicleUnitXmlExtractionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
OffsetDateTime defaultEnd = offsetDateTime(text(overview, "vuDownloadablePeriod/maxDownloadableTime"));
|
OffsetDateTime defaultEnd = offsetDateTime(text(overview, "vuDownloadablePeriod/maxDownloadableTime"));
|
||||||
return new VehicleContext(registration, vehicle, defaultEnd);
|
OffsetDateTime defaultStart = offsetDateTime(text(overview, "vuDownloadablePeriod/minDownloadableTime"));
|
||||||
|
return new VehicleContext(registration, vehicle, defaultStart, defaultEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ExtractedCardActivityInterval> extractActivityIntervals(
|
||||||
|
Document document,
|
||||||
|
VehicleContext vehicleContext,
|
||||||
|
List<ExtractionWarning> warnings
|
||||||
|
) {
|
||||||
|
NodeList dayRecords = nodes(document, "/VehicleUnit/Activities/vuActivityDailyData");
|
||||||
|
if (dayRecords.getLength() == 0) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDate startDate = vehicleContext.defaultActivityStartDate();
|
||||||
|
if (startDate == null) {
|
||||||
|
warnings.add(new ExtractionWarning(
|
||||||
|
"VU_ACTIVITY_DATE_INFERENCE_FAILED",
|
||||||
|
"Vehicle-unit activity daily data is present but no base date could be inferred from the VU downloadable period.",
|
||||||
|
"/VehicleUnit/Activities/vuActivityDailyData"
|
||||||
|
));
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDate maxDate = vehicleContext.defaultActivityEndDate();
|
||||||
|
if (maxDate != null) {
|
||||||
|
LocalDate inferredEndDate = startDate.plusDays(dayRecords.getLength() - 1L);
|
||||||
|
if (inferredEndDate.isAfter(maxDate)) {
|
||||||
|
warnings.add(new ExtractionWarning(
|
||||||
|
"VU_ACTIVITY_DATE_INFERENCE_RANGE",
|
||||||
|
"Vehicle-unit activity daily records exceed the VU downloadable-period range when mapped by sequence order.",
|
||||||
|
"/VehicleUnit/Activities/vuActivityDailyData"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ExtractedCardActivityInterval> intervals = new ArrayList<>();
|
||||||
|
int intervalNo = 0;
|
||||||
|
for (int dayIndex = 0; dayIndex < dayRecords.getLength(); dayIndex++) {
|
||||||
|
Element dayRecord = (Element) dayRecords.item(dayIndex);
|
||||||
|
LocalDate date = startDate.plusDays(dayIndex);
|
||||||
|
String dayPath = "/VehicleUnit/Activities[" + (dayIndex + 1) + "]/vuActivityDailyData";
|
||||||
|
NodeList changes = nodes(dayRecord, "activityChangeInfos");
|
||||||
|
List<ActivityChange> parsedChanges = new ArrayList<>();
|
||||||
|
for (int changeIndex = 0; changeIndex < changes.getLength(); changeIndex++) {
|
||||||
|
Element change = (Element) changes.item(changeIndex);
|
||||||
|
OffsetDateTime from = combine(date, text(change, "timeOfChange"));
|
||||||
|
if (from == null) {
|
||||||
|
warnings.add(new ExtractionWarning(
|
||||||
|
"INVALID_VU_ACTIVITY_CHANGE_TIME",
|
||||||
|
"Vehicle-unit activity change has invalid timeOfChange.",
|
||||||
|
dayPath + "/activityChangeInfos[" + (changeIndex + 1) + "]"
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
parsedChanges.add(new ActivityChange(
|
||||||
|
from,
|
||||||
|
normalizeActivity(text(change, "activity")),
|
||||||
|
normalizeToken(text(change, "slot")),
|
||||||
|
normalizeToken(text(change, "cardStatus")),
|
||||||
|
normalizeToken(text(change, "drivingStatus")),
|
||||||
|
dayPath + "/activityChangeInfos[" + (changeIndex + 1) + "]"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
parsedChanges.sort(Comparator.comparing(ActivityChange::from));
|
||||||
|
for (int i = 0; i < parsedChanges.size(); i++) {
|
||||||
|
ActivityChange current = parsedChanges.get(i);
|
||||||
|
OffsetDateTime to = i + 1 < parsedChanges.size()
|
||||||
|
? parsedChanges.get(i + 1).from()
|
||||||
|
: date.plusDays(1).atStartOfDay().atOffset(ZoneOffset.UTC);
|
||||||
|
if (!current.from().isBefore(to)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
intervalNo++;
|
||||||
|
intervals.add(new ExtractedCardActivityInterval(
|
||||||
|
"VUACT-" + intervalNo,
|
||||||
|
current.from(),
|
||||||
|
to,
|
||||||
|
current.activityType(),
|
||||||
|
current.slot(),
|
||||||
|
current.cardStatus(),
|
||||||
|
current.drivingStatus(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
current.rawRecordPath()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
intervals.sort(Comparator.comparing(ExtractedCardActivityInterval::from));
|
||||||
|
return intervals;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assignActivityCoverage(
|
||||||
|
List<ExtractedCardActivityInterval> vuActivityIntervals,
|
||||||
|
List<VuCardIwInterval> vuCardIwIntervals,
|
||||||
|
VehicleContext vehicleContext,
|
||||||
|
Map<String, DriverExtractionBuilder> driversByKey,
|
||||||
|
List<ExtractionWarning> warnings
|
||||||
|
) {
|
||||||
|
for (ExtractedCardActivityInterval interval : vuActivityIntervals) {
|
||||||
|
List<ActivitySegment> segments = splitByDriverCoverage(interval, vuCardIwIntervals);
|
||||||
|
if (segments.isEmpty()) {
|
||||||
|
warnings.add(new ExtractionWarning(
|
||||||
|
"VU_ACTIVITY_UNASSIGNED",
|
||||||
|
"Vehicle-unit activity interval could not be assigned to a driver-card insertion/withdrawal interval.",
|
||||||
|
interval.rawRecordPath()
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isPartiallyCovered(interval, segments)) {
|
||||||
|
warnings.add(new ExtractionWarning(
|
||||||
|
"VU_ACTIVITY_UNASSIGNED",
|
||||||
|
"Vehicle-unit activity interval could only be partially assigned to driver-card insertion/withdrawal intervals.",
|
||||||
|
interval.rawRecordPath()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < segments.size(); i++) {
|
||||||
|
ActivitySegment segment = segments.get(i);
|
||||||
|
DriverExtractionBuilder builder = driversByKey.get(segment.driverKey());
|
||||||
|
if (builder == null) {
|
||||||
|
warnings.add(new ExtractionWarning(
|
||||||
|
"VU_ACTIVITY_DRIVER_MISSING",
|
||||||
|
"Vehicle-unit activity interval matched a driver key without an initialized driver session.",
|
||||||
|
interval.rawRecordPath()
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String intervalId = segments.size() == 1 ? interval.intervalId() : interval.intervalId() + "-" + (i + 1);
|
||||||
|
builder.cardActivityIntervals.add(new ExtractedCardActivityInterval(
|
||||||
|
intervalId,
|
||||||
|
segment.from(),
|
||||||
|
segment.to(),
|
||||||
|
interval.activityType(),
|
||||||
|
interval.slot(),
|
||||||
|
interval.cardStatus(),
|
||||||
|
interval.drivingStatus(),
|
||||||
|
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
|
||||||
|
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
||||||
|
interval.rawRecordPath()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPartiallyCovered(ExtractedCardActivityInterval interval, List<ActivitySegment> segments) {
|
||||||
|
if (segments.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!segments.get(0).from().equals(interval.from())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!segments.get(segments.size() - 1).to().equals(interval.to())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (int i = 1; i < segments.size(); i++) {
|
||||||
|
if (!segments.get(i - 1).to().equals(segments.get(i).from())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ActivitySegment> splitByDriverCoverage(
|
||||||
|
ExtractedCardActivityInterval interval,
|
||||||
|
List<VuCardIwInterval> vuCardIwIntervals
|
||||||
|
) {
|
||||||
|
TreeSet<OffsetDateTime> cutPoints = new TreeSet<>();
|
||||||
|
cutPoints.add(interval.from());
|
||||||
|
cutPoints.add(interval.to());
|
||||||
|
|
||||||
|
List<VuCardIwInterval> matchingIntervals = vuCardIwIntervals.stream()
|
||||||
|
.filter(iw -> iw.slot() == null || interval.slot() == null || iw.slot().equals(interval.slot()))
|
||||||
|
.filter(iw -> iw.overlaps(interval.from(), interval.to()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
for (VuCardIwInterval iw : matchingIntervals) {
|
||||||
|
if (iw.from().isAfter(interval.from()) && iw.from().isBefore(interval.to())) {
|
||||||
|
cutPoints.add(iw.from());
|
||||||
|
}
|
||||||
|
OffsetDateTime iwEndExclusive = iw.endExclusive();
|
||||||
|
if (iwEndExclusive.isAfter(interval.from()) && iwEndExclusive.isBefore(interval.to())) {
|
||||||
|
cutPoints.add(iwEndExclusive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<OffsetDateTime> orderedCutPoints = List.copyOf(cutPoints);
|
||||||
|
List<ActivitySegment> segments = new ArrayList<>();
|
||||||
|
for (int i = 0; i < orderedCutPoints.size() - 1; i++) {
|
||||||
|
OffsetDateTime segmentFrom = orderedCutPoints.get(i);
|
||||||
|
OffsetDateTime segmentTo = orderedCutPoints.get(i + 1);
|
||||||
|
if (!segmentFrom.isBefore(segmentTo)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
VuCardIwInterval covering = matchingIntervals.stream()
|
||||||
|
.filter(iw -> iw.covers(segmentFrom))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (covering != null) {
|
||||||
|
segments.add(new ActivitySegment(covering.driverKey(), segmentFrom, segmentTo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Element firstElement(Object node, String expression) {
|
private Element firstElement(Object node, String expression) {
|
||||||
|
|
@ -261,6 +470,35 @@ public class VehicleUnitXmlExtractionService {
|
||||||
return Long.parseLong(value.trim());
|
return Long.parseLong(value.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OffsetDateTime combine(LocalDate date, String timeText) {
|
||||||
|
if (date == null || timeText == null || timeText.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LocalTime time = java.time.OffsetTime.parse(timeText.trim()).withOffsetSameInstant(ZoneOffset.UTC).toLocalTime();
|
||||||
|
return date.atTime(time).atOffset(ZoneOffset.UTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeActivity(String value) {
|
||||||
|
String normalized = normalizeToken(value);
|
||||||
|
if (normalized == null) {
|
||||||
|
return "UNKNOWN_ACTIVITY";
|
||||||
|
}
|
||||||
|
return switch (normalized) {
|
||||||
|
case "DRIVING", "DRIVE" -> "DRIVE";
|
||||||
|
case "WORK" -> "WORK";
|
||||||
|
case "AVAILABILITY", "AVAILABLE" -> "AVAILABILITY";
|
||||||
|
case "BREAK_REST", "BREAK/REST", "REST" -> "BREAK_REST";
|
||||||
|
default -> "UNKNOWN_ACTIVITY";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeToken(String value) {
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value.trim().toUpperCase().replace('-', '_').replace(' ', '_');
|
||||||
|
}
|
||||||
|
|
||||||
private String joinCardNumber(Element node, String basePath) {
|
private String joinCardNumber(Element node, String basePath) {
|
||||||
String driverIdentification = text(node, basePath + "/driverIdentification");
|
String driverIdentification = text(node, basePath + "/driverIdentification");
|
||||||
if (driverIdentification == null) {
|
if (driverIdentification == null) {
|
||||||
|
|
@ -358,7 +596,52 @@ public class VehicleUnitXmlExtractionService {
|
||||||
private record VehicleContext(
|
private record VehicleContext(
|
||||||
ExtractedVehicleRegistration registration,
|
ExtractedVehicleRegistration registration,
|
||||||
ExtractedVehicle vehicle,
|
ExtractedVehicle vehicle,
|
||||||
|
OffsetDateTime defaultActivityStart,
|
||||||
OffsetDateTime defaultOpenIntervalEnd
|
OffsetDateTime defaultOpenIntervalEnd
|
||||||
|
) {
|
||||||
|
private LocalDate defaultActivityStartDate() {
|
||||||
|
return defaultActivityStart == null ? null : defaultActivityStart.withOffsetSameInstant(ZoneOffset.UTC).toLocalDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDate defaultActivityEndDate() {
|
||||||
|
return defaultOpenIntervalEnd == null ? null : defaultOpenIntervalEnd.withOffsetSameInstant(ZoneOffset.UTC).toLocalDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ActivityChange(
|
||||||
|
OffsetDateTime from,
|
||||||
|
String activityType,
|
||||||
|
String slot,
|
||||||
|
String cardStatus,
|
||||||
|
String drivingStatus,
|
||||||
|
String rawRecordPath
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record VuCardIwInterval(
|
||||||
|
String driverKey,
|
||||||
|
String slot,
|
||||||
|
OffsetDateTime from,
|
||||||
|
OffsetDateTime to,
|
||||||
|
String rawRecordPath
|
||||||
|
) {
|
||||||
|
private OffsetDateTime endExclusive() {
|
||||||
|
return to.plusSeconds(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean covers(OffsetDateTime timestamp) {
|
||||||
|
return !from.isAfter(timestamp) && timestamp.isBefore(endExclusive());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean overlaps(OffsetDateTime rangeStart, OffsetDateTime rangeEnd) {
|
||||||
|
return endExclusive().isAfter(rangeStart) && from.isBefore(rangeEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ActivitySegment(
|
||||||
|
String driverKey,
|
||||||
|
OffsetDateTime from,
|
||||||
|
OffsetDateTime to
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,12 +52,19 @@ 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(3);
|
||||||
|
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(secondDriver.cardVehicleUsageIntervals()).hasSize(1);
|
assertThat(secondDriver.cardVehicleUsageIntervals()).hasSize(1);
|
||||||
assertThat(secondDriver.cardVehicleUsageIntervals().get(0).to().toString()).isEqualTo("2026-04-02T10:00Z");
|
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(session.warnings()).extracting("code")
|
assertThat(session.warnings()).extracting("code")
|
||||||
.contains("VU_ACTIVITY_NOT_EXTRACTED", "OPEN_VU_CARD_INTERVAL");
|
.contains("OPEN_VU_CARD_INTERVAL", "VU_ACTIVITY_UNASSIGNED");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Document document(String xml) throws Exception {
|
private Document document(String xml) throws Exception {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ final class VehicleUnitXmlSamples {
|
||||||
<vehicleRegistrationNumber><vehicleRegNumber>W-1000V</vehicleRegNumber></vehicleRegistrationNumber>
|
<vehicleRegistrationNumber><vehicleRegNumber>W-1000V</vehicleRegNumber></vehicleRegistrationNumber>
|
||||||
</vehicleRegistrationIdentification>
|
</vehicleRegistrationIdentification>
|
||||||
<vuDownloadablePeriod>
|
<vuDownloadablePeriod>
|
||||||
|
<minDownloadableTime>2026-04-01T00:00:00Z</minDownloadableTime>
|
||||||
<maxDownloadableTime>2026-04-02T10:00:00Z</maxDownloadableTime>
|
<maxDownloadableTime>2026-04-02T10:00:00Z</maxDownloadableTime>
|
||||||
</vuDownloadablePeriod>
|
</vuDownloadablePeriod>
|
||||||
</Overview>
|
</Overview>
|
||||||
|
|
@ -64,7 +65,49 @@ final class VehicleUnitXmlSamples {
|
||||||
<manualInputFlag>MANUAL_ENTRIES</manualInputFlag>
|
<manualInputFlag>MANUAL_ENTRIES</manualInputFlag>
|
||||||
</vuCardIWRecords>
|
</vuCardIWRecords>
|
||||||
</vuCardIWData>
|
</vuCardIWData>
|
||||||
<vuActivityDailyData/>
|
<vuActivityDailyData>
|
||||||
|
<noOfActivityChanges>3</noOfActivityChanges>
|
||||||
|
<activityChangeInfos>
|
||||||
|
<slot>DRIVER</slot>
|
||||||
|
<drivingStatus>SINGLE</drivingStatus>
|
||||||
|
<cardStatus>INSERTED</cardStatus>
|
||||||
|
<activity>WORK</activity>
|
||||||
|
<timeOfChange>08:00:00Z</timeOfChange>
|
||||||
|
</activityChangeInfos>
|
||||||
|
<activityChangeInfos>
|
||||||
|
<slot>DRIVER</slot>
|
||||||
|
<drivingStatus>SINGLE</drivingStatus>
|
||||||
|
<cardStatus>INSERTED</cardStatus>
|
||||||
|
<activity>DRIVING</activity>
|
||||||
|
<timeOfChange>09:00:00Z</timeOfChange>
|
||||||
|
</activityChangeInfos>
|
||||||
|
<activityChangeInfos>
|
||||||
|
<slot>DRIVER</slot>
|
||||||
|
<drivingStatus>SINGLE</drivingStatus>
|
||||||
|
<cardStatus>INSERTED</cardStatus>
|
||||||
|
<activity>BREAK/REST</activity>
|
||||||
|
<timeOfChange>11:00:00Z</timeOfChange>
|
||||||
|
</activityChangeInfos>
|
||||||
|
</vuActivityDailyData>
|
||||||
|
</Activities>
|
||||||
|
<Activities>
|
||||||
|
<vuActivityDailyData>
|
||||||
|
<noOfActivityChanges>2</noOfActivityChanges>
|
||||||
|
<activityChangeInfos>
|
||||||
|
<slot>DRIVER</slot>
|
||||||
|
<drivingStatus>SINGLE</drivingStatus>
|
||||||
|
<cardStatus>INSERTED</cardStatus>
|
||||||
|
<activity>WORK</activity>
|
||||||
|
<timeOfChange>07:30:00Z</timeOfChange>
|
||||||
|
</activityChangeInfos>
|
||||||
|
<activityChangeInfos>
|
||||||
|
<slot>DRIVER</slot>
|
||||||
|
<drivingStatus>SINGLE</drivingStatus>
|
||||||
|
<cardStatus>INSERTED</cardStatus>
|
||||||
|
<activity>DRIVING</activity>
|
||||||
|
<timeOfChange>08:00:00Z</timeOfChange>
|
||||||
|
</activityChangeInfos>
|
||||||
|
</vuActivityDailyData>
|
||||||
</Activities>
|
</Activities>
|
||||||
</VehicleUnit>
|
</VehicleUnit>
|
||||||
""";
|
""";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue