Fix VU activity date extraction from downloadTime
This commit is contained in:
parent
8a106c02c5
commit
08b3cbf073
|
|
@ -226,39 +226,34 @@ public class VehicleUnitXmlExtractionService {
|
|||
VehicleContext vehicleContext,
|
||||
List<ExtractionWarning> warnings
|
||||
) {
|
||||
NodeList dayRecords = nodes(document, "/VehicleUnit/Activities/vuActivityDailyData");
|
||||
if (dayRecords.getLength() == 0) {
|
||||
NodeList activitySections = nodes(document, "/VehicleUnit/Activities");
|
||||
if (activitySections.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;
|
||||
int fallbackDayIndex = 0;
|
||||
for (int activityIndex = 0; activityIndex < activitySections.getLength(); activityIndex++) {
|
||||
Element activities = (Element) activitySections.item(activityIndex);
|
||||
NodeList dayRecords = nodes(activities, "vuActivityDailyData");
|
||||
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";
|
||||
String dayPath = dayRecords.getLength() == 1
|
||||
? "/VehicleUnit/Activities[" + (activityIndex + 1) + "]/vuActivityDailyData"
|
||||
: "/VehicleUnit/Activities[" + (activityIndex + 1) + "]/vuActivityDailyData[" + (dayIndex + 1) + "]";
|
||||
LocalDate date = resolveActivityDate(
|
||||
activities,
|
||||
vehicleContext,
|
||||
activityIndex,
|
||||
fallbackDayIndex + dayIndex,
|
||||
dayPath,
|
||||
warnings
|
||||
);
|
||||
if (date == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NodeList changes = nodes(dayRecord, "activityChangeInfos");
|
||||
List<ActivityChange> parsedChanges = new ArrayList<>();
|
||||
for (int changeIndex = 0; changeIndex < changes.getLength(); changeIndex++) {
|
||||
|
|
@ -305,11 +300,48 @@ public class VehicleUnitXmlExtractionService {
|
|||
));
|
||||
}
|
||||
}
|
||||
fallbackDayIndex += dayRecords.getLength();
|
||||
}
|
||||
|
||||
intervals.sort(Comparator.comparing(ExtractedCardActivityInterval::from));
|
||||
return intervals;
|
||||
}
|
||||
|
||||
private LocalDate resolveActivityDate(
|
||||
Element activities,
|
||||
VehicleContext vehicleContext,
|
||||
int activityIndex,
|
||||
int fallbackDayIndex,
|
||||
String dayPath,
|
||||
List<ExtractionWarning> warnings
|
||||
) {
|
||||
OffsetDateTime downloadTime = offsetDateTime(text(activities, "downloadTime"));
|
||||
if (downloadTime != null) {
|
||||
return downloadTime.withOffsetSameInstant(ZoneOffset.UTC).toLocalDate();
|
||||
}
|
||||
|
||||
LocalDate fallbackDate = vehicleContext.defaultActivityStartDate();
|
||||
if (fallbackDate == null) {
|
||||
warnings.add(new ExtractionWarning(
|
||||
"VU_ACTIVITY_DATE_INFERENCE_FAILED",
|
||||
"Vehicle-unit activity daily data is present but neither Activities/downloadTime nor the VU downloadable period can provide its date.",
|
||||
dayPath
|
||||
));
|
||||
return null;
|
||||
}
|
||||
|
||||
LocalDate resolvedDate = fallbackDate.plusDays(fallbackDayIndex);
|
||||
LocalDate maxDate = vehicleContext.defaultActivityEndDate();
|
||||
if (maxDate != null && resolvedDate.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[" + (activityIndex + 1) + "]"
|
||||
));
|
||||
}
|
||||
return resolvedDate;
|
||||
}
|
||||
|
||||
private void assignActivityCoverage(
|
||||
List<ExtractedCardActivityInterval> vuActivityIntervals,
|
||||
List<VuCardIwInterval> vuCardIwIntervals,
|
||||
|
|
|
|||
|
|
@ -111,6 +111,35 @@ class VehicleUnitXmlExtractionServiceTest {
|
|||
assertThat(secondDriver.cardActivityIntervals().get(0).from().toString()).isEqualTo("2026-04-02T07:30Z");
|
||||
}
|
||||
|
||||
@Test
|
||||
void usesActivitiesDownloadTimeAsVuActivityRecordDate() throws Exception {
|
||||
TachographFileSession session = service.extract(
|
||||
new TachographXmlParser.ParsedTachographXml(document(VehicleUnitXmlSamples.vehicleUnitXmlWithActivityDownloadTime()), "VehicleUnit"),
|
||||
new TachographFileSessionMetadata(
|
||||
"default",
|
||||
"legalrequirements-vehicleunit",
|
||||
"sample-vu",
|
||||
"sample-vu.ddd",
|
||||
"abc",
|
||||
10,
|
||||
"42",
|
||||
"def",
|
||||
false,
|
||||
null
|
||||
),
|
||||
Instant.now(),
|
||||
Instant.now().plus(4, ChronoUnit.HOURS)
|
||||
);
|
||||
|
||||
DriverExtractionSession driver = session.driversByKey().get("12:12345678901200");
|
||||
assertThat(driver).isNotNull();
|
||||
assertThat(driver.cardActivityIntervals()).hasSize(2);
|
||||
assertThat(driver.cardActivityIntervals().get(0).from()).isEqualTo(OffsetDateTime.parse("2026-05-25T08:00:00Z"));
|
||||
assertThat(driver.cardActivityIntervals().get(1).from()).isEqualTo(OffsetDateTime.parse("2026-05-25T09:00:00Z"));
|
||||
assertThat(driver.cardActivityIntervals().get(1).to()).isEqualTo(OffsetDateTime.parse("2026-05-26T00:00:00Z"));
|
||||
assertThat(session.warnings()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsOpenVuCardUsageRecordWithoutClosingItAtDownloadPeriodEnd() throws Exception {
|
||||
TachographFileSession session = service.extract(
|
||||
|
|
|
|||
|
|
@ -254,4 +254,64 @@ final class VehicleUnitXmlSamples {
|
|||
.replace("<timeOfChange>11:00:00Z</timeOfChange>", "<timeOfChange>11:00:00</timeOfChange>")
|
||||
.replace("<timeOfChange>07:30:00Z</timeOfChange>", "<timeOfChange>07:30:00</timeOfChange>");
|
||||
}
|
||||
|
||||
static String vehicleUnitXmlWithActivityDownloadTime() {
|
||||
return """
|
||||
<VehicleUnit>
|
||||
<Overview>
|
||||
<vehicleIdentificationNumber>VINVU123456789012</vehicleIdentificationNumber>
|
||||
<vehicleRegistrationIdentification>
|
||||
<vehicleRegistrationNation>12</vehicleRegistrationNation>
|
||||
<vehicleRegistrationNumber><vehicleRegNumber>W-1000V</vehicleRegNumber></vehicleRegistrationNumber>
|
||||
</vehicleRegistrationIdentification>
|
||||
<vuDownloadablePeriod>
|
||||
<minDownloadableTime>2026-05-20T00:00:00Z</minDownloadableTime>
|
||||
<maxDownloadableTime>2026-05-20T23:59:59Z</maxDownloadableTime>
|
||||
</vuDownloadablePeriod>
|
||||
</Overview>
|
||||
<Activities Generation="3">
|
||||
<downloadTime>2026-05-25T23:59:59Z</downloadTime>
|
||||
<odometerValueMidnight>3756</odometerValueMidnight>
|
||||
<vuCardIWData>
|
||||
<vuCardIWRecords>
|
||||
<cardHolderName>
|
||||
<holderSurname><name>Muster</name></holderSurname>
|
||||
<holderFirstNames><name>Max</name></holderFirstNames>
|
||||
</cardHolderName>
|
||||
<fullCardNumber>
|
||||
<cardType>DRIVER_CARD</cardType>
|
||||
<cardIssuingMemberState>12</cardIssuingMemberState>
|
||||
<cardNumber>
|
||||
<driverIdentification>123456789012</driverIdentification>
|
||||
<cardReplacementIndex>0</cardReplacementIndex>
|
||||
<cardRenewalIndex>0</cardRenewalIndex>
|
||||
</cardNumber>
|
||||
<generation>3</generation>
|
||||
</fullCardNumber>
|
||||
<cardInsertionTime>2026-05-25T08:00:00Z</cardInsertionTime>
|
||||
<vehicleOdometerValueAtInsertion>3700</vehicleOdometerValueAtInsertion>
|
||||
<cardSlotNumber>DRIVER</cardSlotNumber>
|
||||
</vuCardIWRecords>
|
||||
</vuCardIWData>
|
||||
<vuActivityDailyData>
|
||||
<noOfActivityChanges>2</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>
|
||||
</vuActivityDailyData>
|
||||
</Activities>
|
||||
</VehicleUnit>
|
||||
""";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue