Add runtime tachograph parity validation
This commit is contained in:
parent
e68047feab
commit
471726c4cc
|
|
@ -294,3 +294,60 @@ This prevents unrelated vehicle events from being copied into a driver result si
|
||||||
|
|
||||||
`parameters` take precedence in the tachograph profile. The compatibility endpoint maps these values to `expandVehicleEvents` and `vehicleExpansionPaddingMinutes`.
|
`parameters` take precedence in the tachograph profile. The compatibility endpoint maps these values to `expandVehicleEvents` and `vehicleExpansionPaddingMinutes`.
|
||||||
|
|
||||||
|
|
||||||
|
## Tachograph parity validation
|
||||||
|
|
||||||
|
A validation endpoint is available to compare the legacy tachograph file-session Esper endpoint with the generic runtime event-processing profile:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/eventhub/runtime-processing/event-processing/validation/tachograph-parity
|
||||||
|
```
|
||||||
|
|
||||||
|
This endpoint runs both paths for the selected session(s) and driver(s):
|
||||||
|
|
||||||
|
```text
|
||||||
|
legacy file-session path
|
||||||
|
/api/eventhub/tachograph-file-sessions/{sessionId}/drivers/{driverKey}/processing/esper-events
|
||||||
|
|
||||||
|
runtime event-processing path
|
||||||
|
/api/eventhub/runtime-processing/event-processing
|
||||||
|
profileKey = tachograph-driver-esper-v1
|
||||||
|
```
|
||||||
|
|
||||||
|
Example request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sessionId": "{{sessionId}}",
|
||||||
|
"driverKey": "{{driverKey}}",
|
||||||
|
"occurredFrom": "2026-05-01T00:00:00Z",
|
||||||
|
"occurredTo": "2026-05-31T23:59:59Z",
|
||||||
|
"expandVehicleEvents": true,
|
||||||
|
"vehicleExpansionPaddingMinutes": 15,
|
||||||
|
"significantDrivingMinutes": 3,
|
||||||
|
"minimumRestPeriodMinutes": 720,
|
||||||
|
"includeDebug": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For multiple uploaded tachograph sessions, use `sessionIds` or `compositeSessionId`. The validation service resolves the selected drivers, executes the generic runtime profile, then compares count-level parity per driver.
|
||||||
|
|
||||||
|
Compared categories include:
|
||||||
|
|
||||||
|
```text
|
||||||
|
activityIntervals
|
||||||
|
drivingIntervals
|
||||||
|
drivingInterruptionIntervals
|
||||||
|
drivingInterruptionVehicleChangeIntervals
|
||||||
|
dailyWeeklyRestCandidateIntervals
|
||||||
|
dailyWeeklyRestCandidateCoverageIntervals
|
||||||
|
unclassifiedDailyWeeklyRestCandidateCoverageIntervals
|
||||||
|
potentialHomeOvernightStayIntervals
|
||||||
|
potentialInVehicleOvernightStayIntervals
|
||||||
|
potentialInVehicleTripIntervals
|
||||||
|
vehicleUsageIntervals
|
||||||
|
vuCardAbsentIntervals
|
||||||
|
supportGeoEvents
|
||||||
|
```
|
||||||
|
|
||||||
|
For a single session, this is a direct parity check against the original file-session endpoint. For multiple sessions, the reference side is the sum of the individual file-session endpoint results per driver; runtime processing may intentionally deduplicate or merge across session boundaries, so differences should be reviewed with the debug/audit output.
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,36 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Validate tachograph profile parity - single session",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing/validation/tachograph-parity",
|
||||||
|
"host": [
|
||||||
|
"{{baseUrl}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"api",
|
||||||
|
"eventhub",
|
||||||
|
"runtime-processing",
|
||||||
|
"event-processing",
|
||||||
|
"validation",
|
||||||
|
"tachograph-parity"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"sessionId\": \"{{sessionId}}\",\n \"driverKey\": \"{{driverKey}}\",\n \"occurredFrom\": \"2026-05-01T00:00:00Z\",\n \"occurredTo\": \"2026-05-31T23:59:59Z\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15,\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"includeDebug\": true\n}"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,9 @@ import at.procon.eventhub.processing.eventprocessing.RuntimeEventProcessingServi
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingApiRequest;
|
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingApiRequest;
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
|
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingProfileDescriptorDto;
|
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingProfileDescriptorDto;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.validation.RuntimeTachographParityValidationApiRequest;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.validation.RuntimeTachographParityValidationResultDto;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.validation.RuntimeTachographParityValidationService;
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
|
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
|
||||||
import at.procon.eventhub.processing.service.UnifiedRuntimeDerivedProjectionService;
|
import at.procon.eventhub.processing.service.UnifiedRuntimeDerivedProjectionService;
|
||||||
import at.procon.eventhub.processing.service.UnifiedRuntimeDriverTimelineService;
|
import at.procon.eventhub.processing.service.UnifiedRuntimeDriverTimelineService;
|
||||||
|
|
@ -31,13 +34,25 @@ public class UnifiedRuntimeProcessingController {
|
||||||
private final UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService;
|
private final UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService;
|
||||||
private final UnifiedRuntimeTachographEsperScopeProcessingService tachographEsperScopeProcessingService;
|
private final UnifiedRuntimeTachographEsperScopeProcessingService tachographEsperScopeProcessingService;
|
||||||
private final RuntimeEventProcessingService runtimeEventProcessingService;
|
private final RuntimeEventProcessingService runtimeEventProcessingService;
|
||||||
|
private final RuntimeTachographParityValidationService tachographParityValidationService;
|
||||||
|
|
||||||
public UnifiedRuntimeProcessingController(
|
public UnifiedRuntimeProcessingController(
|
||||||
UnifiedRuntimeEventAssemblyService eventAssemblyService,
|
UnifiedRuntimeEventAssemblyService eventAssemblyService,
|
||||||
UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService,
|
UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService,
|
||||||
UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService
|
UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService
|
||||||
) {
|
) {
|
||||||
this(eventAssemblyService, runtimeDriverTimelineService, runtimeDerivedProjectionService, null, null);
|
this(eventAssemblyService, runtimeDriverTimelineService, runtimeDerivedProjectionService, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnifiedRuntimeProcessingController(
|
||||||
|
UnifiedRuntimeEventAssemblyService eventAssemblyService,
|
||||||
|
UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService,
|
||||||
|
UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService,
|
||||||
|
UnifiedRuntimeTachographEsperScopeProcessingService tachographEsperScopeProcessingService,
|
||||||
|
RuntimeEventProcessingService runtimeEventProcessingService
|
||||||
|
) {
|
||||||
|
this(eventAssemblyService, runtimeDriverTimelineService, runtimeDerivedProjectionService,
|
||||||
|
tachographEsperScopeProcessingService, runtimeEventProcessingService, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
@ -46,13 +61,15 @@ public class UnifiedRuntimeProcessingController {
|
||||||
UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService,
|
UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService,
|
||||||
UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService,
|
UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService,
|
||||||
UnifiedRuntimeTachographEsperScopeProcessingService tachographEsperScopeProcessingService,
|
UnifiedRuntimeTachographEsperScopeProcessingService tachographEsperScopeProcessingService,
|
||||||
RuntimeEventProcessingService runtimeEventProcessingService
|
RuntimeEventProcessingService runtimeEventProcessingService,
|
||||||
|
RuntimeTachographParityValidationService tachographParityValidationService
|
||||||
) {
|
) {
|
||||||
this.eventAssemblyService = eventAssemblyService;
|
this.eventAssemblyService = eventAssemblyService;
|
||||||
this.runtimeDriverTimelineService = runtimeDriverTimelineService;
|
this.runtimeDriverTimelineService = runtimeDriverTimelineService;
|
||||||
this.runtimeDerivedProjectionService = runtimeDerivedProjectionService;
|
this.runtimeDerivedProjectionService = runtimeDerivedProjectionService;
|
||||||
this.tachographEsperScopeProcessingService = tachographEsperScopeProcessingService;
|
this.tachographEsperScopeProcessingService = tachographEsperScopeProcessingService;
|
||||||
this.runtimeEventProcessingService = runtimeEventProcessingService;
|
this.runtimeEventProcessingService = runtimeEventProcessingService;
|
||||||
|
this.tachographParityValidationService = tachographParityValidationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/driver-events")
|
@PostMapping("/driver-events")
|
||||||
|
|
@ -94,6 +111,17 @@ public class UnifiedRuntimeProcessingController {
|
||||||
return ResponseEntity.ok(runtimeEventProcessingService.process(request));
|
return ResponseEntity.ok(runtimeEventProcessingService.process(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/event-processing/validation/tachograph-parity")
|
||||||
|
public ResponseEntity<RuntimeTachographParityValidationResultDto> validateTachographParity(
|
||||||
|
@RequestBody RuntimeTachographParityValidationApiRequest request
|
||||||
|
) {
|
||||||
|
if (tachographParityValidationService == null) {
|
||||||
|
throw new IllegalStateException("Runtime tachograph parity validation service is not configured.");
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(tachographParityValidationService.validate(request));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/tachograph/esper-processing")
|
@PostMapping("/tachograph/esper-processing")
|
||||||
public ResponseEntity<UnifiedRuntimeTachographEsperScopeResultDto> runTachographEsperProcessing(
|
public ResponseEntity<UnifiedRuntimeTachographEsperScopeResultDto> runTachographEsperProcessing(
|
||||||
@RequestBody UnifiedRuntimeProcessingApiRequest request
|
@RequestBody UnifiedRuntimeProcessingApiRequest request
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.validation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record RuntimeTachographDriverParityResultDto(
|
||||||
|
String driverKey,
|
||||||
|
String status,
|
||||||
|
String referenceMode,
|
||||||
|
int referenceSessionCount,
|
||||||
|
boolean runtimePartitionPresent,
|
||||||
|
List<RuntimeTachographParityCategoryComparisonDto> comparisons,
|
||||||
|
List<String> notes,
|
||||||
|
List<String> warnings
|
||||||
|
) {
|
||||||
|
public RuntimeTachographDriverParityResultDto {
|
||||||
|
comparisons = comparisons == null ? List.of() : List.copyOf(comparisons);
|
||||||
|
notes = notes == null ? List.of() : List.copyOf(notes);
|
||||||
|
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.validation;
|
||||||
|
|
||||||
|
public record RuntimeTachographParityCategoryComparisonDto(
|
||||||
|
String category,
|
||||||
|
int fileSessionCount,
|
||||||
|
int runtimeCount,
|
||||||
|
boolean equal
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.validation;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record RuntimeTachographParityValidationApiRequest(
|
||||||
|
UUID sessionId,
|
||||||
|
List<UUID> sessionIds,
|
||||||
|
UUID compositeSessionId,
|
||||||
|
String driverKey,
|
||||||
|
Set<String> driverKeys,
|
||||||
|
Boolean includeAllDrivers,
|
||||||
|
OffsetDateTime occurredFrom,
|
||||||
|
OffsetDateTime occurredTo,
|
||||||
|
Boolean expandVehicleEvents,
|
||||||
|
Integer vehicleExpansionPaddingMinutes,
|
||||||
|
Integer significantDrivingMinutes,
|
||||||
|
Integer minimumRestPeriodMinutes,
|
||||||
|
Boolean includeDebug
|
||||||
|
) {
|
||||||
|
public RuntimeTachographParityValidationApiRequest {
|
||||||
|
sessionIds = sessionIds == null ? List.of() : List.copyOf(sessionIds);
|
||||||
|
driverKeys = driverKeys == null ? Set.of() : Set.copyOf(driverKeys);
|
||||||
|
vehicleExpansionPaddingMinutes = vehicleExpansionPaddingMinutes == null
|
||||||
|
? null
|
||||||
|
: Math.max(0, vehicleExpansionPaddingMinutes);
|
||||||
|
significantDrivingMinutes = significantDrivingMinutes == null ? null : Math.max(1, significantDrivingMinutes);
|
||||||
|
minimumRestPeriodMinutes = minimumRestPeriodMinutes == null ? null : Math.max(1, minimumRestPeriodMinutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> requestedDriverKeys() {
|
||||||
|
LinkedHashSet<String> result = new LinkedHashSet<>();
|
||||||
|
if (driverKey != null && !driverKey.isBlank()) {
|
||||||
|
result.add(driverKey.trim());
|
||||||
|
}
|
||||||
|
driverKeys.stream()
|
||||||
|
.filter(value -> value != null && !value.isBlank())
|
||||||
|
.map(String::trim)
|
||||||
|
.forEach(result::add);
|
||||||
|
return Set.copyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean includeAllDriversOrDefault() {
|
||||||
|
return includeAllDrivers == null ? requestedDriverKeys().isEmpty() : includeAllDrivers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean expandVehicleEventsOrDefault() {
|
||||||
|
return expandVehicleEvents == null || expandVehicleEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean includeDebugOrDefault() {
|
||||||
|
return includeDebug != null && includeDebug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int vehicleExpansionPaddingMinutesOrDefault() {
|
||||||
|
return vehicleExpansionPaddingMinutes == null ? 0 : Math.max(0, vehicleExpansionPaddingMinutes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.validation;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record RuntimeTachographParityValidationResultDto(
|
||||||
|
String profileKey,
|
||||||
|
String status,
|
||||||
|
List<UUID> sessionIds,
|
||||||
|
int selectedDriverCount,
|
||||||
|
Map<String, RuntimeTachographDriverParityResultDto> driverResults,
|
||||||
|
List<String> notes,
|
||||||
|
List<String> warnings
|
||||||
|
) {
|
||||||
|
public RuntimeTachographParityValidationResultDto {
|
||||||
|
sessionIds = sessionIds == null ? List.of() : List.copyOf(sessionIds);
|
||||||
|
driverResults = driverResults == null ? Map.of() : Map.copyOf(new LinkedHashMap<>(driverResults));
|
||||||
|
notes = notes == null ? List.of() : List.copyOf(notes);
|
||||||
|
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,388 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.validation;
|
||||||
|
|
||||||
|
import at.procon.eventhub.processing.dto.UnifiedRuntimeDerivedProjectionResultDto;
|
||||||
|
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.RuntimeEventProcessingService;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventPartitioningApiRequest;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingApiRequest;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingPartitionResultDto;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.profile.TachographDriverEsperRuntimeEventProcessingProfile;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBackend;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographEsperEventsProcessingRequest;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographCompositeSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographCompositeSessionNotFoundException;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographCompositeSessionRepository;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionNotFoundException;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionProcessingService;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionRepository;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class RuntimeTachographParityValidationService {
|
||||||
|
|
||||||
|
private final TachographFileSessionRepository fileSessionRepository;
|
||||||
|
private final TachographCompositeSessionRepository compositeSessionRepository;
|
||||||
|
private final TachographFileSessionProcessingService fileSessionProcessingService;
|
||||||
|
private final RuntimeEventProcessingService runtimeEventProcessingService;
|
||||||
|
|
||||||
|
public RuntimeTachographParityValidationService(
|
||||||
|
TachographFileSessionRepository fileSessionRepository,
|
||||||
|
TachographCompositeSessionRepository compositeSessionRepository,
|
||||||
|
TachographFileSessionProcessingService fileSessionProcessingService,
|
||||||
|
RuntimeEventProcessingService runtimeEventProcessingService
|
||||||
|
) {
|
||||||
|
this.fileSessionRepository = fileSessionRepository;
|
||||||
|
this.compositeSessionRepository = compositeSessionRepository;
|
||||||
|
this.fileSessionProcessingService = fileSessionProcessingService;
|
||||||
|
this.runtimeEventProcessingService = runtimeEventProcessingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RuntimeTachographParityValidationResultDto validate(RuntimeTachographParityValidationApiRequest request) {
|
||||||
|
RuntimeTachographParityValidationApiRequest effectiveRequest = request == null
|
||||||
|
? new RuntimeTachographParityValidationApiRequest(null, List.of(), null, null, Set.of(), true,
|
||||||
|
null, null, true, 0, null, null, false)
|
||||||
|
: request;
|
||||||
|
List<UUID> sessionIds = resolveSessionIds(effectiveRequest);
|
||||||
|
Set<String> driverKeys = resolveDriverKeys(effectiveRequest, sessionIds);
|
||||||
|
if (driverKeys.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("No driver keys could be resolved for tachograph parity validation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeEventProcessingResultDto runtimeResult = runtimeEventProcessingService.process(genericRuntimeRequest(
|
||||||
|
effectiveRequest,
|
||||||
|
sessionIds,
|
||||||
|
driverKeys
|
||||||
|
));
|
||||||
|
|
||||||
|
LinkedHashMap<String, RuntimeTachographDriverParityResultDto> driverResults = new LinkedHashMap<>();
|
||||||
|
List<String> warnings = new ArrayList<>(runtimeResult.warnings());
|
||||||
|
for (String driverKey : driverKeys) {
|
||||||
|
RuntimeTachographDriverParityResultDto driverResult = compareDriver(
|
||||||
|
effectiveRequest,
|
||||||
|
sessionIds,
|
||||||
|
driverKey,
|
||||||
|
runtimeResult
|
||||||
|
);
|
||||||
|
driverResults.put(driverKey, driverResult);
|
||||||
|
warnings.addAll(driverResult.warnings());
|
||||||
|
}
|
||||||
|
|
||||||
|
String status = driverResults.values().stream().allMatch(result -> "EQUAL".equals(result.status()))
|
||||||
|
? "EQUAL"
|
||||||
|
: "DIFFERENT";
|
||||||
|
List<String> notes = new ArrayList<>(runtimeResult.notes());
|
||||||
|
notes.add("Validation compares the legacy tachograph file-session esper-events path with the generic runtime event-processing profile '"
|
||||||
|
+ TachographDriverEsperRuntimeEventProcessingProfile.PROFILE_KEY + "'.");
|
||||||
|
if (sessionIds.size() > 1) {
|
||||||
|
notes.add("For multiple sessions, file-session reference counts are summed per driver. Runtime processing may intentionally merge or deduplicate intervals across session boundaries, so category differences need domain review.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RuntimeTachographParityValidationResultDto(
|
||||||
|
TachographDriverEsperRuntimeEventProcessingProfile.PROFILE_KEY,
|
||||||
|
status,
|
||||||
|
sessionIds,
|
||||||
|
driverResults.size(),
|
||||||
|
driverResults,
|
||||||
|
notes,
|
||||||
|
warnings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimeTachographDriverParityResultDto compareDriver(
|
||||||
|
RuntimeTachographParityValidationApiRequest request,
|
||||||
|
List<UUID> sessionIds,
|
||||||
|
String driverKey,
|
||||||
|
RuntimeEventProcessingResultDto runtimeResult
|
||||||
|
) {
|
||||||
|
RuntimeProjectionCounts runtimeCounts = runtimeCounts(runtimeResult, driverKey);
|
||||||
|
ReferenceProjectionCounts referenceCounts = referenceCounts(request, sessionIds, driverKey);
|
||||||
|
List<RuntimeTachographParityCategoryComparisonDto> comparisons = comparisons(referenceCounts.counts(), runtimeCounts.counts());
|
||||||
|
boolean runtimePresent = runtimeCounts.present();
|
||||||
|
boolean referencePresent = referenceCounts.referenceSessionCount() > 0;
|
||||||
|
boolean allEqual = comparisons.stream().allMatch(RuntimeTachographParityCategoryComparisonDto::equal);
|
||||||
|
|
||||||
|
String status;
|
||||||
|
if (!referencePresent) {
|
||||||
|
status = "REFERENCE_MISSING";
|
||||||
|
} else if (!runtimePresent) {
|
||||||
|
status = "RUNTIME_MISSING";
|
||||||
|
} else if (allEqual) {
|
||||||
|
status = "EQUAL";
|
||||||
|
} else {
|
||||||
|
status = "DIFFERENT";
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> notes = new ArrayList<>();
|
||||||
|
notes.add("Reference mode: " + referenceCounts.referenceMode() + ".");
|
||||||
|
notes.add("Reference session count: " + referenceCounts.referenceSessionCount() + ".");
|
||||||
|
if (sessionIds.size() > 1) {
|
||||||
|
notes.add("Reference counts are summed from individual file-session endpoint results for this driver.");
|
||||||
|
}
|
||||||
|
List<String> warnings = new ArrayList<>();
|
||||||
|
if (!referencePresent) {
|
||||||
|
warnings.add("Driver " + driverKey + " was not found in any selected tachograph file session.");
|
||||||
|
}
|
||||||
|
if (!runtimePresent) {
|
||||||
|
warnings.add("Generic runtime event-processing profile did not return a partition for driver " + driverKey + ".");
|
||||||
|
}
|
||||||
|
if (runtimePresent && referencePresent && !allEqual) {
|
||||||
|
warnings.add("Runtime result differs from file-session reference result for driver " + driverKey + ".");
|
||||||
|
}
|
||||||
|
return new RuntimeTachographDriverParityResultDto(
|
||||||
|
driverKey,
|
||||||
|
status,
|
||||||
|
referenceCounts.referenceMode(),
|
||||||
|
referenceCounts.referenceSessionCount(),
|
||||||
|
runtimePresent,
|
||||||
|
comparisons,
|
||||||
|
notes,
|
||||||
|
warnings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimeEventProcessingApiRequest genericRuntimeRequest(
|
||||||
|
RuntimeTachographParityValidationApiRequest request,
|
||||||
|
List<UUID> sessionIds,
|
||||||
|
Set<String> driverKeys
|
||||||
|
) {
|
||||||
|
UnifiedRuntimeProcessingApiRequest scope = new UnifiedRuntimeProcessingApiRequest(
|
||||||
|
null,
|
||||||
|
sessionIds,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
|
||||||
|
UnifiedRuntimeEventBackend.SOURCE_DB,
|
||||||
|
null,
|
||||||
|
driverKeys,
|
||||||
|
request.includeAllDriversOrDefault(),
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
request.occurredFrom(),
|
||||||
|
request.occurredTo(),
|
||||||
|
request.expandVehicleEventsOrDefault(),
|
||||||
|
request.vehicleExpansionPaddingMinutesOrDefault(),
|
||||||
|
request.significantDrivingMinutes(),
|
||||||
|
request.minimumRestPeriodMinutes()
|
||||||
|
);
|
||||||
|
RuntimeEventPartitioningApiRequest partitioning = new RuntimeEventPartitioningApiRequest(
|
||||||
|
RuntimeEventPartitioningStrategy.DRIVER,
|
||||||
|
null,
|
||||||
|
request.includeAllDriversOrDefault(),
|
||||||
|
driverKeys,
|
||||||
|
request.includeAllDriversOrDefault(),
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
request.expandVehicleEventsOrDefault(),
|
||||||
|
request.vehicleExpansionPaddingMinutesOrDefault(),
|
||||||
|
request.includeDebugOrDefault()
|
||||||
|
);
|
||||||
|
Map<String, Object> parameters = new LinkedHashMap<>();
|
||||||
|
if (request.significantDrivingMinutes() != null) {
|
||||||
|
parameters.put("significantDrivingMinutes", request.significantDrivingMinutes());
|
||||||
|
}
|
||||||
|
if (request.minimumRestPeriodMinutes() != null) {
|
||||||
|
parameters.put("minimumRestPeriodMinutes", request.minimumRestPeriodMinutes());
|
||||||
|
}
|
||||||
|
parameters.put("attachVehicleOnlyEvents", request.expandVehicleEventsOrDefault());
|
||||||
|
parameters.put("vehicleEvidencePaddingMinutes", request.vehicleExpansionPaddingMinutesOrDefault());
|
||||||
|
parameters.put("includePartitionDebug", request.includeDebugOrDefault());
|
||||||
|
return new RuntimeEventProcessingApiRequest(
|
||||||
|
TachographDriverEsperRuntimeEventProcessingProfile.PROFILE_KEY,
|
||||||
|
scope,
|
||||||
|
partitioning,
|
||||||
|
parameters
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<UUID> resolveSessionIds(RuntimeTachographParityValidationApiRequest request) {
|
||||||
|
LinkedHashSet<UUID> result = new LinkedHashSet<>();
|
||||||
|
if (request.sessionId() != null) {
|
||||||
|
result.add(request.sessionId());
|
||||||
|
}
|
||||||
|
result.addAll(request.sessionIds());
|
||||||
|
if (request.compositeSessionId() != null) {
|
||||||
|
TachographCompositeSession compositeSession = compositeSessionRepository.find(request.compositeSessionId())
|
||||||
|
.orElseThrow(() -> new TachographCompositeSessionNotFoundException(request.compositeSessionId()));
|
||||||
|
result.addAll(compositeSession.memberSessionIds());
|
||||||
|
}
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("sessionId, sessionIds, or compositeSessionId is required for tachograph parity validation.");
|
||||||
|
}
|
||||||
|
result.forEach(this::requireFileSession);
|
||||||
|
return List.copyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> resolveDriverKeys(RuntimeTachographParityValidationApiRequest request, List<UUID> sessionIds) {
|
||||||
|
LinkedHashSet<String> result = new LinkedHashSet<>(request.requestedDriverKeys());
|
||||||
|
if (request.includeAllDriversOrDefault() || result.isEmpty()) {
|
||||||
|
for (UUID sessionId : sessionIds) {
|
||||||
|
result.addAll(requireFileSession(sessionId).driversByKey().keySet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Set.copyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TachographFileSession requireFileSession(UUID sessionId) {
|
||||||
|
return fileSessionRepository.find(sessionId)
|
||||||
|
.orElseThrow(() -> new TachographFileSessionNotFoundException(sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReferenceProjectionCounts referenceCounts(
|
||||||
|
RuntimeTachographParityValidationApiRequest request,
|
||||||
|
List<UUID> sessionIds,
|
||||||
|
String driverKey
|
||||||
|
) {
|
||||||
|
ProjectionCounts aggregate = ProjectionCounts.empty();
|
||||||
|
int referenceSessionCount = 0;
|
||||||
|
TachographEsperEventsProcessingRequest fileSessionRequest = new TachographEsperEventsProcessingRequest(
|
||||||
|
request.occurredFrom(),
|
||||||
|
request.occurredTo(),
|
||||||
|
request.significantDrivingMinutes(),
|
||||||
|
request.minimumRestPeriodMinutes()
|
||||||
|
);
|
||||||
|
for (UUID sessionId : sessionIds) {
|
||||||
|
TachographFileSession session = requireFileSession(sessionId);
|
||||||
|
if (!session.driversByKey().containsKey(driverKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TachographEsperDriverProcessingResultDto projection = fileSessionProcessingService.getEsperDriverProcessingResults(
|
||||||
|
sessionId,
|
||||||
|
driverKey,
|
||||||
|
fileSessionRequest
|
||||||
|
);
|
||||||
|
aggregate = aggregate.plus(ProjectionCounts.from(projection));
|
||||||
|
referenceSessionCount++;
|
||||||
|
}
|
||||||
|
String referenceMode = sessionIds.size() == 1 ? "SINGLE_FILE_SESSION" : "SUM_OF_FILE_SESSION_RESULTS";
|
||||||
|
return new ReferenceProjectionCounts(aggregate, referenceMode, referenceSessionCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimeProjectionCounts runtimeCounts(RuntimeEventProcessingResultDto runtimeResult, String driverKey) {
|
||||||
|
RuntimeEventProcessingPartitionResultDto partitionResult = runtimeResult.partitionResults().get(driverKey);
|
||||||
|
if (partitionResult == null) {
|
||||||
|
return new RuntimeProjectionCounts(ProjectionCounts.empty(), false);
|
||||||
|
}
|
||||||
|
Object result = partitionResult.result();
|
||||||
|
if (result instanceof UnifiedRuntimeDerivedProjectionResultDto projectionResult) {
|
||||||
|
return new RuntimeProjectionCounts(ProjectionCounts.from(projectionResult.projection()), projectionResult.projection() != null);
|
||||||
|
}
|
||||||
|
return new RuntimeProjectionCounts(ProjectionCounts.empty(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RuntimeTachographParityCategoryComparisonDto> comparisons(
|
||||||
|
ProjectionCounts reference,
|
||||||
|
ProjectionCounts runtime
|
||||||
|
) {
|
||||||
|
return List.of(
|
||||||
|
comparison("activityIntervals", reference.activityIntervalCount(), runtime.activityIntervalCount()),
|
||||||
|
comparison("drivingIntervals", reference.drivingIntervalCount(), runtime.drivingIntervalCount()),
|
||||||
|
comparison("drivingInterruptionIntervals", reference.drivingInterruptionIntervalCount(), runtime.drivingInterruptionIntervalCount()),
|
||||||
|
comparison("drivingInterruptionVehicleChangeIntervals", reference.drivingInterruptionVehicleChangeIntervalCount(), runtime.drivingInterruptionVehicleChangeIntervalCount()),
|
||||||
|
comparison("dailyWeeklyRestCandidateIntervals", reference.dailyWeeklyRestCandidateIntervalCount(), runtime.dailyWeeklyRestCandidateIntervalCount()),
|
||||||
|
comparison("dailyWeeklyRestCandidateCoverageIntervals", reference.dailyWeeklyRestCandidateCoverageIntervalCount(), runtime.dailyWeeklyRestCandidateCoverageIntervalCount()),
|
||||||
|
comparison("unclassifiedDailyWeeklyRestCandidateCoverageIntervals", reference.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount(), runtime.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount()),
|
||||||
|
comparison("potentialHomeOvernightStayIntervals", reference.potentialHomeOvernightStayIntervalCount(), runtime.potentialHomeOvernightStayIntervalCount()),
|
||||||
|
comparison("potentialInVehicleOvernightStayIntervals", reference.potentialInVehicleOvernightStayIntervalCount(), runtime.potentialInVehicleOvernightStayIntervalCount()),
|
||||||
|
comparison("potentialInVehicleTripIntervals", reference.potentialInVehicleTripIntervalCount(), runtime.potentialInVehicleTripIntervalCount()),
|
||||||
|
comparison("vehicleUsageIntervals", reference.vehicleUsageIntervalCount(), runtime.vehicleUsageIntervalCount()),
|
||||||
|
comparison("vuCardAbsentIntervals", reference.vuCardAbsentIntervalCount(), runtime.vuCardAbsentIntervalCount()),
|
||||||
|
comparison("supportGeoEvents", reference.supportGeoEventCount(), runtime.supportGeoEventCount())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimeTachographParityCategoryComparisonDto comparison(String category, int fileSessionCount, int runtimeCount) {
|
||||||
|
return new RuntimeTachographParityCategoryComparisonDto(
|
||||||
|
category,
|
||||||
|
fileSessionCount,
|
||||||
|
runtimeCount,
|
||||||
|
fileSessionCount == runtimeCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ReferenceProjectionCounts(
|
||||||
|
ProjectionCounts counts,
|
||||||
|
String referenceMode,
|
||||||
|
int referenceSessionCount
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record RuntimeProjectionCounts(
|
||||||
|
ProjectionCounts counts,
|
||||||
|
boolean present
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ProjectionCounts(
|
||||||
|
int activityIntervalCount,
|
||||||
|
int drivingIntervalCount,
|
||||||
|
int drivingInterruptionIntervalCount,
|
||||||
|
int drivingInterruptionVehicleChangeIntervalCount,
|
||||||
|
int dailyWeeklyRestCandidateIntervalCount,
|
||||||
|
int dailyWeeklyRestCandidateCoverageIntervalCount,
|
||||||
|
int unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount,
|
||||||
|
int potentialHomeOvernightStayIntervalCount,
|
||||||
|
int potentialInVehicleOvernightStayIntervalCount,
|
||||||
|
int potentialInVehicleTripIntervalCount,
|
||||||
|
int vehicleUsageIntervalCount,
|
||||||
|
int vuCardAbsentIntervalCount,
|
||||||
|
int supportGeoEventCount
|
||||||
|
) {
|
||||||
|
static ProjectionCounts empty() {
|
||||||
|
return new ProjectionCounts(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ProjectionCounts from(TachographEsperDriverProcessingResultDto projection) {
|
||||||
|
if (projection == null) {
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
return new ProjectionCounts(
|
||||||
|
projection.activityIntervalCount(),
|
||||||
|
projection.drivingIntervalCount(),
|
||||||
|
projection.drivingInterruptionIntervalCount(),
|
||||||
|
projection.drivingInterruptionVehicleChangeIntervalCount(),
|
||||||
|
projection.dailyWeeklyRestCandidateIntervalCount(),
|
||||||
|
projection.dailyWeeklyRestCandidateCoverageIntervalCount(),
|
||||||
|
projection.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount(),
|
||||||
|
projection.potentialHomeOvernightStayIntervalCount(),
|
||||||
|
projection.potentialInVehicleOvernightStayIntervalCount(),
|
||||||
|
projection.potentialInVehicleTripIntervalCount(),
|
||||||
|
projection.vehicleUsageIntervalCount(),
|
||||||
|
projection.vuCardAbsentIntervalCount(),
|
||||||
|
projection.supportGeoEventCount()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectionCounts plus(ProjectionCounts other) {
|
||||||
|
return new ProjectionCounts(
|
||||||
|
activityIntervalCount + other.activityIntervalCount,
|
||||||
|
drivingIntervalCount + other.drivingIntervalCount,
|
||||||
|
drivingInterruptionIntervalCount + other.drivingInterruptionIntervalCount,
|
||||||
|
drivingInterruptionVehicleChangeIntervalCount + other.drivingInterruptionVehicleChangeIntervalCount,
|
||||||
|
dailyWeeklyRestCandidateIntervalCount + other.dailyWeeklyRestCandidateIntervalCount,
|
||||||
|
dailyWeeklyRestCandidateCoverageIntervalCount + other.dailyWeeklyRestCandidateCoverageIntervalCount,
|
||||||
|
unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount + other.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount,
|
||||||
|
potentialHomeOvernightStayIntervalCount + other.potentialHomeOvernightStayIntervalCount,
|
||||||
|
potentialInVehicleOvernightStayIntervalCount + other.potentialInVehicleOvernightStayIntervalCount,
|
||||||
|
potentialInVehicleTripIntervalCount + other.potentialInVehicleTripIntervalCount,
|
||||||
|
vehicleUsageIntervalCount + other.vehicleUsageIntervalCount,
|
||||||
|
vuCardAbsentIntervalCount + other.vuCardAbsentIntervalCount,
|
||||||
|
supportGeoEventCount + other.supportGeoEventCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,10 @@ import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingR
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingProfileDescriptorDto;
|
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingProfileDescriptorDto;
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingPartitionResultDto;
|
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingPartitionResultDto;
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.validation.RuntimeTachographDriverParityResultDto;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.validation.RuntimeTachographParityCategoryComparisonDto;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.validation.RuntimeTachographParityValidationResultDto;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.validation.RuntimeTachographParityValidationService;
|
||||||
import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef;
|
import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef;
|
||||||
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;
|
||||||
|
|
@ -447,6 +451,71 @@ class UnifiedRuntimeProcessingControllerTest {
|
||||||
.andExpect(jsonPath("$.notes[0]").value("generic adapter"));
|
.andExpect(jsonPath("$.notes[0]").value("generic adapter"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validatesTachographParityViaRuntimeApi() throws Exception {
|
||||||
|
UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
|
||||||
|
UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
|
||||||
|
UnifiedRuntimeDerivedProjectionService derivedProjectionService = org.mockito.Mockito.mock(UnifiedRuntimeDerivedProjectionService.class);
|
||||||
|
RuntimeEventProcessingService runtimeEventProcessingService = org.mockito.Mockito.mock(RuntimeEventProcessingService.class);
|
||||||
|
RuntimeTachographParityValidationService parityValidationService = org.mockito.Mockito.mock(RuntimeTachographParityValidationService.class);
|
||||||
|
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(
|
||||||
|
eventAssemblyService,
|
||||||
|
timelineService,
|
||||||
|
derivedProjectionService,
|
||||||
|
null,
|
||||||
|
runtimeEventProcessingService,
|
||||||
|
parityValidationService
|
||||||
|
))
|
||||||
|
.setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
|
||||||
|
.setControllerAdvice(new UnifiedRuntimeProcessingExceptionHandler())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
UUID sessionId = UUID.randomUUID();
|
||||||
|
when(parityValidationService.validate(any()))
|
||||||
|
.thenReturn(new RuntimeTachographParityValidationResultDto(
|
||||||
|
"tachograph-driver-esper-v1",
|
||||||
|
"EQUAL",
|
||||||
|
List.of(sessionId),
|
||||||
|
1,
|
||||||
|
Map.of("12:123", new RuntimeTachographDriverParityResultDto(
|
||||||
|
"12:123",
|
||||||
|
"EQUAL",
|
||||||
|
"SINGLE_FILE_SESSION",
|
||||||
|
1,
|
||||||
|
true,
|
||||||
|
List.of(new RuntimeTachographParityCategoryComparisonDto(
|
||||||
|
"activityIntervals",
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
true
|
||||||
|
)),
|
||||||
|
List.of("validated"),
|
||||||
|
List.of()
|
||||||
|
)),
|
||||||
|
List.of("validation complete"),
|
||||||
|
List.of()
|
||||||
|
));
|
||||||
|
|
||||||
|
mockMvc.perform(post("/api/eventhub/runtime-processing/event-processing/validation/tachograph-parity")
|
||||||
|
.contentType("application/json")
|
||||||
|
.content("""
|
||||||
|
{
|
||||||
|
"sessionId": "%s",
|
||||||
|
"driverKey": "12:123",
|
||||||
|
"occurredFrom": "2026-05-01T08:00:00Z",
|
||||||
|
"occurredTo": "2026-05-01T10:00:00Z",
|
||||||
|
"includeDebug": true
|
||||||
|
}
|
||||||
|
""".formatted(sessionId)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.profileKey").value("tachograph-driver-esper-v1"))
|
||||||
|
.andExpect(jsonPath("$.status").value("EQUAL"))
|
||||||
|
.andExpect(jsonPath("$.driverResults['12:123'].status").value("EQUAL"))
|
||||||
|
.andExpect(jsonPath("$.driverResults['12:123'].comparisons[0].category").value("activityIntervals"))
|
||||||
|
.andExpect(jsonPath("$.driverResults['12:123'].comparisons[0].equal").value(true));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void returnsBadRequestForInvalidRuntimeRequest() throws Exception {
|
void returnsBadRequestForInvalidRuntimeRequest() throws Exception {
|
||||||
UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
|
UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.validation;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import at.procon.eventhub.processing.dto.UnifiedRuntimeDerivedProjectionResultDto;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.RuntimeEventProcessingService;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingPartitionResultDto;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.profile.TachographDriverEsperRuntimeEventProcessingProfile;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
||||||
|
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographCompositeSessionRepository;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionProcessingService;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionRepository;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class RuntimeTachographParityValidationServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reportsEqualWhenRuntimeProfileMatchesFileSessionReferenceCounts() {
|
||||||
|
TachographFileSessionRepository fileSessionRepository = org.mockito.Mockito.mock(TachographFileSessionRepository.class);
|
||||||
|
TachographCompositeSessionRepository compositeSessionRepository = org.mockito.Mockito.mock(TachographCompositeSessionRepository.class);
|
||||||
|
TachographFileSessionProcessingService fileSessionProcessingService = org.mockito.Mockito.mock(TachographFileSessionProcessingService.class);
|
||||||
|
RuntimeEventProcessingService runtimeEventProcessingService = org.mockito.Mockito.mock(RuntimeEventProcessingService.class);
|
||||||
|
RuntimeTachographParityValidationService service = new RuntimeTachographParityValidationService(
|
||||||
|
fileSessionRepository,
|
||||||
|
compositeSessionRepository,
|
||||||
|
fileSessionProcessingService,
|
||||||
|
runtimeEventProcessingService
|
||||||
|
);
|
||||||
|
|
||||||
|
UUID sessionId = UUID.randomUUID();
|
||||||
|
String driverKey = "12:123";
|
||||||
|
TachographFileSession session = session(sessionId, driverKey);
|
||||||
|
when(fileSessionRepository.find(sessionId)).thenReturn(Optional.of(session));
|
||||||
|
when(fileSessionProcessingService.getEsperDriverProcessingResults(eq(sessionId), eq(driverKey), any()))
|
||||||
|
.thenReturn(projection(sessionId, driverKey, 2, 1, 1));
|
||||||
|
|
||||||
|
UnifiedRuntimeProcessingRequest runtimeRequest = UnifiedRuntimeProcessingRequest.forTachographFileSession(
|
||||||
|
sessionId,
|
||||||
|
driverKey,
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T10:00:00Z"),
|
||||||
|
true,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
UnifiedRuntimeDerivedProjectionResultDto runtimeDriverResult = new UnifiedRuntimeDerivedProjectionResultDto(
|
||||||
|
runtimeRequest,
|
||||||
|
4,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
4,
|
||||||
|
List.of(),
|
||||||
|
projection(sessionId, driverKey, 2, 1, 1),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
when(runtimeEventProcessingService.process(any()))
|
||||||
|
.thenReturn(new RuntimeEventProcessingResultDto(
|
||||||
|
TachographDriverEsperRuntimeEventProcessingProfile.PROFILE_KEY,
|
||||||
|
RuntimeEventPartitioningStrategy.DRIVER,
|
||||||
|
runtimeRequest,
|
||||||
|
4,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
List.of(),
|
||||||
|
Map.of(driverKey, new RuntimeEventProcessingPartitionResultDto(
|
||||||
|
"DRIVER",
|
||||||
|
driverKey,
|
||||||
|
"UnifiedRuntimeDerivedProjectionResultDto",
|
||||||
|
runtimeDriverResult,
|
||||||
|
Map.of()
|
||||||
|
)),
|
||||||
|
List.of(),
|
||||||
|
List.of()
|
||||||
|
));
|
||||||
|
|
||||||
|
RuntimeTachographParityValidationResultDto result = service.validate(new RuntimeTachographParityValidationApiRequest(
|
||||||
|
sessionId,
|
||||||
|
List.of(),
|
||||||
|
null,
|
||||||
|
driverKey,
|
||||||
|
Set.of(),
|
||||||
|
false,
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T10:00:00Z"),
|
||||||
|
true,
|
||||||
|
0,
|
||||||
|
3,
|
||||||
|
720,
|
||||||
|
true
|
||||||
|
));
|
||||||
|
|
||||||
|
assertThat(result.status()).isEqualTo("EQUAL");
|
||||||
|
assertThat(result.driverResults()).containsKey(driverKey);
|
||||||
|
assertThat(result.driverResults().get(driverKey).comparisons())
|
||||||
|
.anySatisfy(comparison -> {
|
||||||
|
assertThat(comparison.category()).isEqualTo("activityIntervals");
|
||||||
|
assertThat(comparison.fileSessionCount()).isEqualTo(2);
|
||||||
|
assertThat(comparison.runtimeCount()).isEqualTo(2);
|
||||||
|
assertThat(comparison.equal()).isTrue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private TachographFileSession session(UUID sessionId, String driverKey) {
|
||||||
|
DriverExtractionSession driver = new DriverExtractionSession(
|
||||||
|
driverKey,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
return new TachographFileSession(
|
||||||
|
sessionId,
|
||||||
|
null,
|
||||||
|
Map.of(driverKey, driver),
|
||||||
|
new ExtractionStats(1, 0, 0, 0, 0, 0),
|
||||||
|
List.of(),
|
||||||
|
Instant.now(),
|
||||||
|
Instant.now().plusSeconds(3600)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TachographEsperDriverProcessingResultDto projection(
|
||||||
|
UUID sessionId,
|
||||||
|
String driverKey,
|
||||||
|
int activityCount,
|
||||||
|
int drivingCount,
|
||||||
|
int interruptionCount
|
||||||
|
) {
|
||||||
|
return new TachographEsperDriverProcessingResultDto(
|
||||||
|
sessionId,
|
||||||
|
driverKey,
|
||||||
|
"DRIVER_CARD",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T10:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T10:00:00Z"),
|
||||||
|
activityCount,
|
||||||
|
drivingCount,
|
||||||
|
interruptionCount,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue