Add runtime vehicle evidence debug output
This commit is contained in:
parent
b04b333db7
commit
e68047feab
|
|
@ -0,0 +1,17 @@
|
||||||
|
# EventHub fix-list patch
|
||||||
|
|
||||||
|
This patch implements the requested remaining items from the fix list, excluding build verification and SQL Server 2008 SQL rewriting.
|
||||||
|
|
||||||
|
## Apply notes
|
||||||
|
|
||||||
|
1. Copy the files from this archive into the project root.
|
||||||
|
2. Delete the old migration files listed in `DELETE_FILES.txt`.
|
||||||
|
3. Run the test suite locally with Maven/Java 21.
|
||||||
|
|
||||||
|
## Main changes
|
||||||
|
|
||||||
|
- Renumbered Flyway migrations to remove duplicate `V9` and `V10` versions.
|
||||||
|
- Removed the duplicated Timescale/Event source record migration.
|
||||||
|
- Switched local Docker Compose DB from plain PostgreSQL to a TimescaleDB/PostGIS-capable image.
|
||||||
|
- Added normalized raw tachograph payload metadata for DB-extracted EventHub events.
|
||||||
|
- Added tests for Flyway version uniqueness and tachograph DB mapper → timeline reconstruction metadata.
|
||||||
|
|
@ -29,7 +29,8 @@ Example response:
|
||||||
"significantDrivingMinutes",
|
"significantDrivingMinutes",
|
||||||
"minimumRestPeriodMinutes",
|
"minimumRestPeriodMinutes",
|
||||||
"attachVehicleOnlyEvents",
|
"attachVehicleOnlyEvents",
|
||||||
"vehicleEvidencePaddingMinutes"
|
"vehicleEvidencePaddingMinutes",
|
||||||
|
"includePartitionDebug"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -63,13 +64,15 @@ POST /api/eventhub/runtime-processing/event-processing
|
||||||
"strategy": "DRIVER",
|
"strategy": "DRIVER",
|
||||||
"includeAllPartitions": true,
|
"includeAllPartitions": true,
|
||||||
"attachVehicleEvidence": true,
|
"attachVehicleEvidence": true,
|
||||||
"vehicleEvidencePaddingMinutes": 15
|
"vehicleEvidencePaddingMinutes": 15,
|
||||||
|
"includeDebug": true
|
||||||
},
|
},
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"significantDrivingMinutes": 3,
|
"significantDrivingMinutes": 3,
|
||||||
"minimumRestPeriodMinutes": 720,
|
"minimumRestPeriodMinutes": 720,
|
||||||
"attachVehicleOnlyEvents": true,
|
"attachVehicleOnlyEvents": true,
|
||||||
"vehicleEvidencePaddingMinutes": 15
|
"vehicleEvidencePaddingMinutes": 15,
|
||||||
|
"includePartitionDebug": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -182,6 +185,51 @@ CUSTOM_PROFILE
|
||||||
|
|
||||||
The first tachograph profile currently supports `DRIVER` partitioning. The service partitions mixed event scopes in Java before invoking Esper so that existing single-driver EPL windows cannot mix driver states.
|
The first tachograph profile currently supports `DRIVER` partitioning. The service partitions mixed event scopes in Java before invoking Esper so that existing single-driver EPL windows cannot mix driver states.
|
||||||
|
|
||||||
|
## Partition debug / audit output
|
||||||
|
|
||||||
|
For mixed-source scopes, request debug output when validating why events were or were not attached to a driver partition:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"partitioning": {
|
||||||
|
"strategy": "DRIVER",
|
||||||
|
"includeAllPartitions": true,
|
||||||
|
"attachVehicleEvidence": true,
|
||||||
|
"vehicleEvidencePaddingMinutes": 15,
|
||||||
|
"includeDebug": true
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"includePartitionDebug": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When enabled, each generic `partitionResults[*].metadata.partitionDebug` contains:
|
||||||
|
|
||||||
|
```text
|
||||||
|
directDriverEventCount
|
||||||
|
vehicleUsageIntervalCount
|
||||||
|
candidateVehicleEvidenceEventCount
|
||||||
|
attachedVehicleEvidenceEventCount
|
||||||
|
ignoredVehicleEvidenceEventCount
|
||||||
|
mergedEventCount
|
||||||
|
vehicleUsageIntervals
|
||||||
|
vehicleEvidenceDecisions
|
||||||
|
notes
|
||||||
|
warnings
|
||||||
|
```
|
||||||
|
|
||||||
|
`vehicleEvidenceDecisions` explains every relevant decision, for example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DIRECT_DRIVER_EVENT
|
||||||
|
ATTACHED_VEHICLE_EVIDENCE
|
||||||
|
IGNORED_NO_OVERLAPPING_VEHICLE_USAGE
|
||||||
|
IGNORED_ATTACHMENT_DISABLED
|
||||||
|
```
|
||||||
|
|
||||||
|
The compatibility response type also has `partitionDebugByDriver`; use the generic endpoint when you need to enable debug output explicitly. Keep debug disabled for high-volume production requests unless you need attribution diagnostics, because the decision list can be large.
|
||||||
|
|
||||||
## Compatibility endpoint
|
## Compatibility endpoint
|
||||||
|
|
||||||
The old tachograph endpoint remains available:
|
The old tachograph endpoint remains available:
|
||||||
|
|
@ -233,11 +281,13 @@ This prevents unrelated vehicle events from being copied into a driver result si
|
||||||
{
|
{
|
||||||
"partitioning": {
|
"partitioning": {
|
||||||
"attachVehicleEvidence": true,
|
"attachVehicleEvidence": true,
|
||||||
"vehicleEvidencePaddingMinutes": 15
|
"vehicleEvidencePaddingMinutes": 15,
|
||||||
|
"includeDebug": true
|
||||||
},
|
},
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"attachVehicleOnlyEvents": true,
|
"attachVehicleOnlyEvents": true,
|
||||||
"vehicleEvidencePaddingMinutes": 15
|
"vehicleEvidencePaddingMinutes": 15,
|
||||||
|
"includePartitionDebug": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -47,3 +47,8 @@ to the generic profile behavior:
|
||||||
```
|
```
|
||||||
|
|
||||||
Attachment is temporal: a vehicle-only event must match a reconstructed driver vehicle-usage interval and occur inside the interval plus configured padding.
|
Attachment is temporal: a vehicle-only event must match a reconstructed driver vehicle-usage interval and occur inside the interval plus configured padding.
|
||||||
|
|
||||||
|
|
||||||
|
## Debugging vehicle evidence attachment
|
||||||
|
|
||||||
|
Prefer the generic `/api/eventhub/runtime-processing/event-processing` endpoint with `partitioning.includeDebug=true` or `parameters.includePartitionDebug=true`. The compatibility response type has `partitionDebugByDriver`, but the generic endpoint is the preferred way to enable debug output explicitly. The generic response exposes debug data under `partitionResults[*].metadata.partitionDebug`.
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@
|
||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"profileKey\": \"tachograph-driver-esper-v1\",\n \"scope\": {\n \"sessionId\": \"{{sessionId}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"driverKey\": \"{{driverKey}}\",\n \"occurredFrom\": \"2026-05-01T00:00:00Z\",\n \"occurredTo\": \"2026-05-31T23:59:59Z\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n },\n \"partitioning\": {\n \"strategy\": \"DRIVER\",\n \"partitionKeys\": [\n \"{{driverKey}}\"\n ],\n \"attachVehicleEvidence\": true,\n \"vehicleEvidencePaddingMinutes\": 15\n },\n \"parameters\": {\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"attachVehicleOnlyEvents\": true,\n \"vehicleEvidencePaddingMinutes\": 15\n }\n}"
|
"raw": "{\n \"profileKey\": \"tachograph-driver-esper-v1\",\n \"scope\": {\n \"sessionId\": \"{{sessionId}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"driverKey\": \"{{driverKey}}\",\n \"occurredFrom\": \"2026-05-01T00:00:00Z\",\n \"occurredTo\": \"2026-05-31T23:59:59Z\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n },\n \"partitioning\": {\n \"strategy\": \"DRIVER\",\n \"partitionKeys\": [\n \"{{driverKey}}\"\n ],\n \"attachVehicleEvidence\": true,\n \"vehicleEvidencePaddingMinutes\": 15,\n \"includeDebug\": true\n },\n \"parameters\": {\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"attachVehicleOnlyEvents\": true,\n \"vehicleEvidencePaddingMinutes\": 15,\n \"includePartitionDebug\": true\n }\n}"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing",
|
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing",
|
||||||
|
|
@ -87,7 +87,7 @@
|
||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"profileKey\": \"tachograph-driver-esper-v1\",\n \"scope\": {\n \"sessionIds\": [\n \"{{sessionId}}\",\n \"{{sessionId2}}\"\n ],\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"occurredFrom\": \"2026-05-01T00:00:00Z\",\n \"occurredTo\": \"2026-05-31T23:59:59Z\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n },\n \"partitioning\": {\n \"strategy\": \"DRIVER\",\n \"includeAllPartitions\": true,\n \"attachVehicleEvidence\": true,\n \"vehicleEvidencePaddingMinutes\": 15\n },\n \"parameters\": {\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"attachVehicleOnlyEvents\": true,\n \"vehicleEvidencePaddingMinutes\": 15\n }\n}"
|
"raw": "{\n \"profileKey\": \"tachograph-driver-esper-v1\",\n \"scope\": {\n \"sessionIds\": [\n \"{{sessionId}}\",\n \"{{sessionId2}}\"\n ],\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"occurredFrom\": \"2026-05-01T00:00:00Z\",\n \"occurredTo\": \"2026-05-31T23:59:59Z\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n },\n \"partitioning\": {\n \"strategy\": \"DRIVER\",\n \"includeAllPartitions\": true,\n \"attachVehicleEvidence\": true,\n \"vehicleEvidencePaddingMinutes\": 15,\n \"includeDebug\": true\n },\n \"parameters\": {\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"attachVehicleOnlyEvents\": true,\n \"vehicleEvidencePaddingMinutes\": 15,\n \"includePartitionDebug\": true\n }\n}"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing",
|
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing",
|
||||||
|
|
@ -115,7 +115,7 @@
|
||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"profileKey\": \"tachograph-driver-esper-v1\",\n \"scope\": {\n \"compositeSessionId\": \"{{compositeSessionId}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"occurredFrom\": \"2026-05-01T00:00:00Z\",\n \"occurredTo\": \"2026-05-31T23:59:59Z\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n },\n \"partitioning\": {\n \"strategy\": \"DRIVER\",\n \"includeAllPartitions\": true,\n \"attachVehicleEvidence\": true,\n \"vehicleEvidencePaddingMinutes\": 15\n },\n \"parameters\": {\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"attachVehicleOnlyEvents\": true,\n \"vehicleEvidencePaddingMinutes\": 15\n }\n}"
|
"raw": "{\n \"profileKey\": \"tachograph-driver-esper-v1\",\n \"scope\": {\n \"compositeSessionId\": \"{{compositeSessionId}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"occurredFrom\": \"2026-05-01T00:00:00Z\",\n \"occurredTo\": \"2026-05-31T23:59:59Z\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n },\n \"partitioning\": {\n \"strategy\": \"DRIVER\",\n \"includeAllPartitions\": true,\n \"attachVehicleEvidence\": true,\n \"vehicleEvidencePaddingMinutes\": 15,\n \"includeDebug\": true\n },\n \"parameters\": {\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"attachVehicleOnlyEvents\": true,\n \"vehicleEvidencePaddingMinutes\": 15,\n \"includePartitionDebug\": true\n }\n}"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing",
|
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package at.procon.eventhub.processing.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record RuntimeDriverPartitionDebugDto(
|
||||||
|
String driverKey,
|
||||||
|
int directDriverEventCount,
|
||||||
|
int vehicleUsageIntervalCount,
|
||||||
|
int candidateVehicleEvidenceEventCount,
|
||||||
|
int attachedVehicleEvidenceEventCount,
|
||||||
|
int ignoredVehicleEvidenceEventCount,
|
||||||
|
int mergedEventCount,
|
||||||
|
List<RuntimeVehicleUsageIntervalDebugDto> vehicleUsageIntervals,
|
||||||
|
List<RuntimeVehicleEvidenceAttachmentDecisionDto> vehicleEvidenceDecisions,
|
||||||
|
List<String> notes,
|
||||||
|
List<String> warnings
|
||||||
|
) {
|
||||||
|
public RuntimeDriverPartitionDebugDto {
|
||||||
|
vehicleUsageIntervals = vehicleUsageIntervals == null ? List.of() : List.copyOf(vehicleUsageIntervals);
|
||||||
|
vehicleEvidenceDecisions = vehicleEvidenceDecisions == null ? List.of() : List.copyOf(vehicleEvidenceDecisions);
|
||||||
|
notes = notes == null ? List.of() : List.copyOf(notes);
|
||||||
|
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package at.procon.eventhub.processing.dto;
|
||||||
|
|
||||||
|
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeType;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public record RuntimeVehicleEvidenceAttachmentDecisionDto(
|
||||||
|
String decision,
|
||||||
|
String reason,
|
||||||
|
String eventKey,
|
||||||
|
String externalSourceEventId,
|
||||||
|
OffsetDateTime occurredAt,
|
||||||
|
String eventDomain,
|
||||||
|
String eventType,
|
||||||
|
String lifecycle,
|
||||||
|
RuntimeEventScopeType scopeType,
|
||||||
|
Set<String> vehicleKeys,
|
||||||
|
List<String> matchingVehicleUsageIntervalIds
|
||||||
|
) {
|
||||||
|
public RuntimeVehicleEvidenceAttachmentDecisionDto {
|
||||||
|
vehicleKeys = vehicleKeys == null ? Set.of() : Set.copyOf(vehicleKeys);
|
||||||
|
matchingVehicleUsageIntervalIds = matchingVehicleUsageIntervalIds == null ? List.of() : List.copyOf(matchingVehicleUsageIntervalIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package at.procon.eventhub.processing.dto;
|
||||||
|
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
|
||||||
|
public record RuntimeVehicleUsageIntervalDebugDto(
|
||||||
|
String intervalId,
|
||||||
|
OffsetDateTime from,
|
||||||
|
OffsetDateTime to,
|
||||||
|
String registrationKey,
|
||||||
|
String vehicleKey,
|
||||||
|
String sourceKind
|
||||||
|
) {
|
||||||
|
public static RuntimeVehicleUsageIntervalDebugDto from(ResolvedVehicleUsageInterval interval) {
|
||||||
|
if (interval == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new RuntimeVehicleUsageIntervalDebugDto(
|
||||||
|
interval.intervalId(),
|
||||||
|
interval.from(),
|
||||||
|
interval.to(),
|
||||||
|
interval.registrationKey(),
|
||||||
|
interval.vehicleKey(),
|
||||||
|
interval.sourceKind()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,10 +13,48 @@ public record UnifiedRuntimeDerivedProjectionResultDto(
|
||||||
int mergedEventCount,
|
int mergedEventCount,
|
||||||
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
||||||
TachographEsperDriverProcessingResultDto projection,
|
TachographEsperDriverProcessingResultDto projection,
|
||||||
List<String> notes
|
List<String> notes,
|
||||||
|
RuntimeDriverPartitionDebugDto partitionDebug
|
||||||
) {
|
) {
|
||||||
public UnifiedRuntimeDerivedProjectionResultDto {
|
public UnifiedRuntimeDerivedProjectionResultDto {
|
||||||
discoveredVehicles = discoveredVehicles == null ? List.of() : List.copyOf(discoveredVehicles);
|
discoveredVehicles = discoveredVehicles == null ? List.of() : List.copyOf(discoveredVehicles);
|
||||||
notes = notes == null ? List.of() : List.copyOf(notes);
|
notes = notes == null ? List.of() : List.copyOf(notes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UnifiedRuntimeDerivedProjectionResultDto(
|
||||||
|
UnifiedRuntimeProcessingRequest request,
|
||||||
|
int driverSeedEventCount,
|
||||||
|
int discoveredVehicleCount,
|
||||||
|
int expandedVehicleEventCount,
|
||||||
|
int mergedEventCount,
|
||||||
|
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
||||||
|
TachographEsperDriverProcessingResultDto projection,
|
||||||
|
List<String> notes
|
||||||
|
) {
|
||||||
|
this(
|
||||||
|
request,
|
||||||
|
driverSeedEventCount,
|
||||||
|
discoveredVehicleCount,
|
||||||
|
expandedVehicleEventCount,
|
||||||
|
mergedEventCount,
|
||||||
|
discoveredVehicles,
|
||||||
|
projection,
|
||||||
|
notes,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnifiedRuntimeDerivedProjectionResultDto withPartitionDebug(RuntimeDriverPartitionDebugDto debug) {
|
||||||
|
return new UnifiedRuntimeDerivedProjectionResultDto(
|
||||||
|
request,
|
||||||
|
driverSeedEventCount,
|
||||||
|
discoveredVehicleCount,
|
||||||
|
expandedVehicleEventCount,
|
||||||
|
mergedEventCount,
|
||||||
|
discoveredVehicles,
|
||||||
|
projection,
|
||||||
|
notes,
|
||||||
|
debug
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,14 @@ public record UnifiedRuntimeTachographEsperScopeResultDto(
|
||||||
int discoveredVehicleCount,
|
int discoveredVehicleCount,
|
||||||
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
||||||
Map<String, UnifiedRuntimeDerivedProjectionResultDto> driverResults,
|
Map<String, UnifiedRuntimeDerivedProjectionResultDto> driverResults,
|
||||||
|
Map<String, RuntimeDriverPartitionDebugDto> partitionDebugByDriver,
|
||||||
List<String> notes,
|
List<String> notes,
|
||||||
List<String> warnings
|
List<String> warnings
|
||||||
) {
|
) {
|
||||||
public UnifiedRuntimeTachographEsperScopeResultDto {
|
public UnifiedRuntimeTachographEsperScopeResultDto {
|
||||||
discoveredVehicles = discoveredVehicles == null ? List.of() : List.copyOf(discoveredVehicles);
|
discoveredVehicles = discoveredVehicles == null ? List.of() : List.copyOf(discoveredVehicles);
|
||||||
driverResults = driverResults == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(driverResults));
|
driverResults = driverResults == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(driverResults));
|
||||||
|
partitionDebugByDriver = partitionDebugByDriver == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(partitionDebugByDriver));
|
||||||
notes = notes == null ? List.of() : List.copyOf(notes);
|
notes = notes == null ? List.of() : List.copyOf(notes);
|
||||||
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
||||||
}
|
}
|
||||||
|
|
@ -50,9 +52,23 @@ public record UnifiedRuntimeTachographEsperScopeResultDto(
|
||||||
genericResult.discoveredVehicleCount(),
|
genericResult.discoveredVehicleCount(),
|
||||||
genericResult.discoveredVehicles(),
|
genericResult.discoveredVehicles(),
|
||||||
driverResults,
|
driverResults,
|
||||||
|
extractPartitionDebug(genericResult),
|
||||||
genericResult.notes(),
|
genericResult.notes(),
|
||||||
genericResult.warnings()
|
genericResult.warnings()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Map<String, RuntimeDriverPartitionDebugDto> extractPartitionDebug(
|
||||||
|
RuntimeEventProcessingResultDto genericResult
|
||||||
|
) {
|
||||||
|
LinkedHashMap<String, RuntimeDriverPartitionDebugDto> debugByDriver = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, RuntimeEventProcessingPartitionResultDto> entry : genericResult.partitionResults().entrySet()) {
|
||||||
|
Object debug = entry.getValue().metadata().get("partitionDebug");
|
||||||
|
if (debug instanceof RuntimeDriverPartitionDebugDto partitionDebug) {
|
||||||
|
debugByDriver.put(entry.getKey(), partitionDebug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return debugByDriver;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ public record RuntimeEventPartitioningApiRequest(
|
||||||
Set<String> vehicleKeys,
|
Set<String> vehicleKeys,
|
||||||
Boolean includeAllVehicles,
|
Boolean includeAllVehicles,
|
||||||
Boolean attachVehicleEvidence,
|
Boolean attachVehicleEvidence,
|
||||||
Integer vehicleEvidencePaddingMinutes
|
Integer vehicleEvidencePaddingMinutes,
|
||||||
|
Boolean includeDebug
|
||||||
) {
|
) {
|
||||||
public RuntimeEventPartitioningApiRequest {
|
public RuntimeEventPartitioningApiRequest {
|
||||||
strategy = strategy == null ? RuntimeEventPartitioningStrategy.CUSTOM_PROFILE : strategy;
|
strategy = strategy == null ? RuntimeEventPartitioningStrategy.CUSTOM_PROFILE : strategy;
|
||||||
|
|
@ -43,4 +44,8 @@ public record RuntimeEventPartitioningApiRequest(
|
||||||
public int vehicleEvidencePaddingMinutesOrDefault(int fallback) {
|
public int vehicleEvidencePaddingMinutesOrDefault(int fallback) {
|
||||||
return vehicleEvidencePaddingMinutes == null ? Math.max(0, fallback) : Math.max(0, vehicleEvidencePaddingMinutes);
|
return vehicleEvidencePaddingMinutes == null ? Math.max(0, fallback) : Math.max(0, vehicleEvidencePaddingMinutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean includeDebugOrDefault() {
|
||||||
|
return includeDebug != null && includeDebug;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public record RuntimeEventProcessingApiRequest(
|
||||||
throw new IllegalArgumentException("scope must not be null");
|
throw new IllegalArgumentException("scope must not be null");
|
||||||
}
|
}
|
||||||
partitioning = partitioning == null
|
partitioning = partitioning == null
|
||||||
? new RuntimeEventPartitioningApiRequest(null, null, null, null, null, null, null, null, null)
|
? new RuntimeEventPartitioningApiRequest(null, null, null, null, null, null, null, null, null, null)
|
||||||
: partitioning;
|
: partitioning;
|
||||||
parameters = parameters == null
|
parameters = parameters == null
|
||||||
? Map.of()
|
? Map.of()
|
||||||
|
|
@ -40,7 +40,8 @@ public record RuntimeEventProcessingApiRequest(
|
||||||
scope != null ? scope.vehicleKeys() : null,
|
scope != null ? scope.vehicleKeys() : null,
|
||||||
scope != null ? scope.includeAllVehicles() : null,
|
scope != null ? scope.includeAllVehicles() : null,
|
||||||
null,
|
null,
|
||||||
scope != null ? scope.vehicleExpansionPaddingMinutes() : null
|
scope != null ? scope.vehicleExpansionPaddingMinutes() : null,
|
||||||
|
null
|
||||||
),
|
),
|
||||||
Map.of()
|
Map.of()
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -55,30 +55,49 @@ public class TachographDriverEsperRuntimeEventProcessingProfile implements Runti
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> optionalParameters() {
|
public Set<String> optionalParameters() {
|
||||||
return Set.of("significantDrivingMinutes", "minimumRestPeriodMinutes", "attachVehicleOnlyEvents", "vehicleEvidencePaddingMinutes");
|
return Set.of(
|
||||||
|
"significantDrivingMinutes",
|
||||||
|
"minimumRestPeriodMinutes",
|
||||||
|
"attachVehicleOnlyEvents",
|
||||||
|
"vehicleEvidencePaddingMinutes",
|
||||||
|
"includePartitionDebug"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RuntimeEventProcessingResultDto process(RuntimeEventProcessingApiRequest request) {
|
public RuntimeEventProcessingResultDto process(RuntimeEventProcessingApiRequest request) {
|
||||||
|
boolean includePartitionDebug = booleanParameter(
|
||||||
|
request.parameters(),
|
||||||
|
"includePartitionDebug",
|
||||||
|
request.partitioning() != null && request.partitioning().includeDebugOrDefault()
|
||||||
|
);
|
||||||
UnifiedRuntimeProcessingApiRequest tachographScopeRequest = applyGenericRequest(request.scope(), request.partitioning(), request.parameters());
|
UnifiedRuntimeProcessingApiRequest tachographScopeRequest = applyGenericRequest(request.scope(), request.partitioning(), request.parameters());
|
||||||
UnifiedRuntimeTachographEsperScopeResultDto tachographResult = tachographScopeProcessingService.processScope(tachographScopeRequest);
|
UnifiedRuntimeTachographEsperScopeResultDto tachographResult = tachographScopeProcessingService.processScope(
|
||||||
|
tachographScopeRequest,
|
||||||
|
includePartitionDebug
|
||||||
|
);
|
||||||
|
|
||||||
Map<String, RuntimeEventProcessingPartitionResultDto> partitionResults = new LinkedHashMap<>();
|
Map<String, RuntimeEventProcessingPartitionResultDto> partitionResults = new LinkedHashMap<>();
|
||||||
tachographResult.driverResults().forEach((driverKey, driverResult) -> partitionResults.put(
|
tachographResult.driverResults().forEach((driverKey, driverResult) -> {
|
||||||
driverKey,
|
Map<String, Object> metadata = new LinkedHashMap<>();
|
||||||
new RuntimeEventProcessingPartitionResultDto(
|
metadata.put("projectionResultType", driverResult.projection() == null ? "NONE" : "TachographEsperDriverProcessingResultDto");
|
||||||
"DRIVER",
|
metadata.put("driverSeedEventCount", driverResult.driverSeedEventCount());
|
||||||
driverKey,
|
metadata.put("expandedVehicleEventCount", driverResult.expandedVehicleEventCount());
|
||||||
"UnifiedRuntimeDerivedProjectionResultDto",
|
metadata.put("mergedEventCount", driverResult.mergedEventCount());
|
||||||
driverResult,
|
if (driverResult.partitionDebug() != null) {
|
||||||
Map.of(
|
metadata.put("partitionDebug", driverResult.partitionDebug());
|
||||||
"projectionResultType", driverResult.projection() == null ? "NONE" : "TachographEsperDriverProcessingResultDto",
|
}
|
||||||
"driverSeedEventCount", driverResult.driverSeedEventCount(),
|
partitionResults.put(
|
||||||
"expandedVehicleEventCount", driverResult.expandedVehicleEventCount(),
|
driverKey,
|
||||||
"mergedEventCount", driverResult.mergedEventCount()
|
new RuntimeEventProcessingPartitionResultDto(
|
||||||
)
|
"DRIVER",
|
||||||
)
|
driverKey,
|
||||||
));
|
"UnifiedRuntimeDerivedProjectionResultDto",
|
||||||
|
driverResult,
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return new RuntimeEventProcessingResultDto(
|
return new RuntimeEventProcessingResultDto(
|
||||||
profileKey(),
|
profileKey(),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package at.procon.eventhub.processing.model;
|
package at.procon.eventhub.processing.model;
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.processing.dto.RuntimeDriverPartitionDebugDto;
|
||||||
|
import at.procon.eventhub.processing.dto.RuntimeVehicleEvidenceAttachmentDecisionDto;
|
||||||
|
import at.procon.eventhub.processing.dto.RuntimeVehicleUsageIntervalDebugDto;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public record RuntimeDriverVehicleEvidenceAttachmentResult(
|
public record RuntimeDriverVehicleEvidenceAttachmentResult(
|
||||||
|
|
@ -11,6 +14,8 @@ public record RuntimeDriverVehicleEvidenceAttachmentResult(
|
||||||
int vehicleUsageIntervalCount,
|
int vehicleUsageIntervalCount,
|
||||||
int candidateVehicleEvidenceEventCount,
|
int candidateVehicleEvidenceEventCount,
|
||||||
int ignoredVehicleEvidenceEventCount,
|
int ignoredVehicleEvidenceEventCount,
|
||||||
|
List<RuntimeVehicleUsageIntervalDebugDto> vehicleUsageIntervals,
|
||||||
|
List<RuntimeVehicleEvidenceAttachmentDecisionDto> vehicleEvidenceDecisions,
|
||||||
List<String> notes,
|
List<String> notes,
|
||||||
List<String> warnings
|
List<String> warnings
|
||||||
) {
|
) {
|
||||||
|
|
@ -18,7 +23,25 @@ public record RuntimeDriverVehicleEvidenceAttachmentResult(
|
||||||
directDriverEvents = directDriverEvents == null ? List.of() : List.copyOf(directDriverEvents);
|
directDriverEvents = directDriverEvents == null ? List.of() : List.copyOf(directDriverEvents);
|
||||||
attachedVehicleEvidenceEvents = attachedVehicleEvidenceEvents == null ? List.of() : List.copyOf(attachedVehicleEvidenceEvents);
|
attachedVehicleEvidenceEvents = attachedVehicleEvidenceEvents == null ? List.of() : List.copyOf(attachedVehicleEvidenceEvents);
|
||||||
mergedEvents = mergedEvents == null ? List.of() : List.copyOf(mergedEvents);
|
mergedEvents = mergedEvents == null ? List.of() : List.copyOf(mergedEvents);
|
||||||
|
vehicleUsageIntervals = vehicleUsageIntervals == null ? List.of() : List.copyOf(vehicleUsageIntervals);
|
||||||
|
vehicleEvidenceDecisions = vehicleEvidenceDecisions == null ? List.of() : List.copyOf(vehicleEvidenceDecisions);
|
||||||
notes = notes == null ? List.of() : List.copyOf(notes);
|
notes = notes == null ? List.of() : List.copyOf(notes);
|
||||||
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RuntimeDriverPartitionDebugDto toPartitionDebug() {
|
||||||
|
return new RuntimeDriverPartitionDebugDto(
|
||||||
|
driverKey,
|
||||||
|
directDriverEvents.size(),
|
||||||
|
vehicleUsageIntervalCount,
|
||||||
|
candidateVehicleEvidenceEventCount,
|
||||||
|
attachedVehicleEvidenceEvents.size(),
|
||||||
|
ignoredVehicleEvidenceEventCount,
|
||||||
|
mergedEvents.size(),
|
||||||
|
vehicleUsageIntervals,
|
||||||
|
vehicleEvidenceDecisions,
|
||||||
|
notes,
|
||||||
|
warnings
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package at.procon.eventhub.processing.service;
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
import at.procon.eventhub.dto.VehicleRefDto;
|
import at.procon.eventhub.dto.VehicleRefDto;
|
||||||
|
import at.procon.eventhub.processing.dto.RuntimeVehicleEvidenceAttachmentDecisionDto;
|
||||||
|
import at.procon.eventhub.processing.dto.RuntimeVehicleUsageIntervalDebugDto;
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeClassifier;
|
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeClassifier;
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeType;
|
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeType;
|
||||||
import at.procon.eventhub.processing.model.RuntimeDriverVehicleEvidenceAttachmentResult;
|
import at.procon.eventhub.processing.model.RuntimeDriverVehicleEvidenceAttachmentResult;
|
||||||
|
|
@ -38,6 +40,24 @@ public class RuntimeDriverVehicleEvidenceAttachmentService {
|
||||||
List<EventHubEventDto> runtimeScopeEvents,
|
List<EventHubEventDto> runtimeScopeEvents,
|
||||||
boolean attachVehicleOnlyEvents,
|
boolean attachVehicleOnlyEvents,
|
||||||
int vehicleEvidencePaddingMinutes
|
int vehicleEvidencePaddingMinutes
|
||||||
|
) {
|
||||||
|
return attachVehicleEvidence(
|
||||||
|
driverKey,
|
||||||
|
directDriverEvents,
|
||||||
|
runtimeScopeEvents,
|
||||||
|
attachVehicleOnlyEvents,
|
||||||
|
vehicleEvidencePaddingMinutes,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RuntimeDriverVehicleEvidenceAttachmentResult attachVehicleEvidence(
|
||||||
|
String driverKey,
|
||||||
|
List<EventHubEventDto> directDriverEvents,
|
||||||
|
List<EventHubEventDto> runtimeScopeEvents,
|
||||||
|
boolean attachVehicleOnlyEvents,
|
||||||
|
int vehicleEvidencePaddingMinutes,
|
||||||
|
boolean includeDebug
|
||||||
) {
|
) {
|
||||||
List<EventHubEventDto> safeDriverEvents = directDriverEvents == null ? List.of() : List.copyOf(directDriverEvents);
|
List<EventHubEventDto> safeDriverEvents = directDriverEvents == null ? List.of() : List.copyOf(directDriverEvents);
|
||||||
List<EventHubEventDto> safeScopeEvents = runtimeScopeEvents == null ? List.of() : List.copyOf(runtimeScopeEvents);
|
List<EventHubEventDto> safeScopeEvents = runtimeScopeEvents == null ? List.of() : List.copyOf(runtimeScopeEvents);
|
||||||
|
|
@ -47,14 +67,19 @@ public class RuntimeDriverVehicleEvidenceAttachmentService {
|
||||||
List<String> warnings = new ArrayList<>();
|
List<String> warnings = new ArrayList<>();
|
||||||
if (!attachVehicleOnlyEvents) {
|
if (!attachVehicleOnlyEvents) {
|
||||||
notes.add("Vehicle-only evidence attachment is disabled for driver partition " + driverKey + ".");
|
notes.add("Vehicle-only evidence attachment is disabled for driver partition " + driverKey + ".");
|
||||||
|
List<RuntimeVehicleEvidenceAttachmentDecisionDto> disabledDecisions = includeDebug
|
||||||
|
? disabledDecisions(safeScopeEvents)
|
||||||
|
: List.of();
|
||||||
return new RuntimeDriverVehicleEvidenceAttachmentResult(
|
return new RuntimeDriverVehicleEvidenceAttachmentResult(
|
||||||
driverKey,
|
driverKey,
|
||||||
safeDriverEvents,
|
safeDriverEvents,
|
||||||
List.of(),
|
List.of(),
|
||||||
deduplicateAndSort(safeDriverEvents, List.of()),
|
deduplicateAndSort(safeDriverEvents, List.of()),
|
||||||
0,
|
0,
|
||||||
0,
|
disabledDecisions.size(),
|
||||||
0,
|
disabledDecisions.size(),
|
||||||
|
List.of(),
|
||||||
|
disabledDecisions,
|
||||||
notes,
|
notes,
|
||||||
warnings
|
warnings
|
||||||
);
|
);
|
||||||
|
|
@ -62,6 +87,12 @@ public class RuntimeDriverVehicleEvidenceAttachmentService {
|
||||||
|
|
||||||
ResolvedDriverTimeline timeline = timelineReconstructor.reconstruct(null, driverKey, safeDriverEvents);
|
ResolvedDriverTimeline timeline = timelineReconstructor.reconstruct(null, driverKey, safeDriverEvents);
|
||||||
List<ResolvedVehicleUsageInterval> usageIntervals = mergeVehicleUsageIntervals(timeline.vehicleUsageIntervals());
|
List<ResolvedVehicleUsageInterval> usageIntervals = mergeVehicleUsageIntervals(timeline.vehicleUsageIntervals());
|
||||||
|
List<RuntimeVehicleUsageIntervalDebugDto> usageIntervalDebug = includeDebug
|
||||||
|
? usageIntervals.stream().map(RuntimeVehicleUsageIntervalDebugDto::from).toList()
|
||||||
|
: List.of();
|
||||||
|
List<RuntimeVehicleEvidenceAttachmentDecisionDto> decisions = includeDebug
|
||||||
|
? directDriverDecisions(safeDriverEvents)
|
||||||
|
: new ArrayList<>();
|
||||||
List<EventHubEventDto> candidateVehicleEvidence = safeScopeEvents.stream()
|
List<EventHubEventDto> candidateVehicleEvidence = safeScopeEvents.stream()
|
||||||
.filter(event -> scopeClassifier.classify(event) == RuntimeEventScopeType.VEHICLE_SCOPED)
|
.filter(event -> scopeClassifier.classify(event) == RuntimeEventScopeType.VEHICLE_SCOPED)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
@ -71,9 +102,25 @@ public class RuntimeDriverVehicleEvidenceAttachmentService {
|
||||||
List<ResolvedVehicleUsageInterval> matchingIntervals = matchingUsageIntervals(vehicleEvent, usageIntervals, paddingMinutes);
|
List<ResolvedVehicleUsageInterval> matchingIntervals = matchingUsageIntervals(vehicleEvent, usageIntervals, paddingMinutes);
|
||||||
if (matchingIntervals.isEmpty()) {
|
if (matchingIntervals.isEmpty()) {
|
||||||
ignored++;
|
ignored++;
|
||||||
|
if (includeDebug) {
|
||||||
|
decisions.add(decision(
|
||||||
|
"IGNORED_NO_OVERLAPPING_VEHICLE_USAGE",
|
||||||
|
"Vehicle-scoped event did not overlap any reconstructed vehicle-usage interval for driver " + driverKey + ".",
|
||||||
|
vehicleEvent,
|
||||||
|
List.of()
|
||||||
|
));
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
attached.add(vehicleEvent);
|
attached.add(vehicleEvent);
|
||||||
|
if (includeDebug) {
|
||||||
|
decisions.add(decision(
|
||||||
|
"ATTACHED_VEHICLE_EVIDENCE",
|
||||||
|
"Vehicle-scoped event overlapped driver vehicle usage interval(s).",
|
||||||
|
vehicleEvent,
|
||||||
|
matchingIntervals
|
||||||
|
));
|
||||||
|
}
|
||||||
if (matchingIntervals.size() > 1) {
|
if (matchingIntervals.size() > 1) {
|
||||||
warnings.add("Vehicle-only event " + vehicleEvent.externalSourceEventId()
|
warnings.add("Vehicle-only event " + vehicleEvent.externalSourceEventId()
|
||||||
+ " matched multiple vehicle-usage intervals for driver " + driverKey
|
+ " matched multiple vehicle-usage intervals for driver " + driverKey
|
||||||
|
|
@ -100,11 +147,61 @@ public class RuntimeDriverVehicleEvidenceAttachmentService {
|
||||||
usageIntervals.size(),
|
usageIntervals.size(),
|
||||||
candidateVehicleEvidence.size(),
|
candidateVehicleEvidence.size(),
|
||||||
ignored,
|
ignored,
|
||||||
|
usageIntervalDebug,
|
||||||
|
includeDebug ? decisions : List.of(),
|
||||||
notes,
|
notes,
|
||||||
warnings
|
warnings
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<RuntimeVehicleEvidenceAttachmentDecisionDto> disabledDecisions(List<EventHubEventDto> runtimeScopeEvents) {
|
||||||
|
return (runtimeScopeEvents == null ? List.<EventHubEventDto>of() : runtimeScopeEvents).stream()
|
||||||
|
.filter(event -> scopeClassifier.classify(event) == RuntimeEventScopeType.VEHICLE_SCOPED)
|
||||||
|
.map(event -> decision(
|
||||||
|
"IGNORED_ATTACHMENT_DISABLED",
|
||||||
|
"Vehicle evidence attachment was disabled for this partition.",
|
||||||
|
event,
|
||||||
|
List.of()
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RuntimeVehicleEvidenceAttachmentDecisionDto> directDriverDecisions(List<EventHubEventDto> directDriverEvents) {
|
||||||
|
return (directDriverEvents == null ? List.<EventHubEventDto>of() : directDriverEvents).stream()
|
||||||
|
.map(event -> decision(
|
||||||
|
"DIRECT_DRIVER_EVENT",
|
||||||
|
"Event already carries the selected driver reference and belongs directly to the driver partition.",
|
||||||
|
event,
|
||||||
|
List.of()
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimeVehicleEvidenceAttachmentDecisionDto decision(
|
||||||
|
String decision,
|
||||||
|
String reason,
|
||||||
|
EventHubEventDto event,
|
||||||
|
List<ResolvedVehicleUsageInterval> matchingIntervals
|
||||||
|
) {
|
||||||
|
List<String> intervalIds = (matchingIntervals == null ? List.<ResolvedVehicleUsageInterval>of() : matchingIntervals).stream()
|
||||||
|
.map(ResolvedVehicleUsageInterval::intervalId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
return new RuntimeVehicleEvidenceAttachmentDecisionDto(
|
||||||
|
decision,
|
||||||
|
reason,
|
||||||
|
dedupKey(event),
|
||||||
|
event == null ? null : event.externalSourceEventId(),
|
||||||
|
event == null ? null : event.occurredAt(),
|
||||||
|
event == null || event.eventDomain() == null ? null : event.eventDomain().name(),
|
||||||
|
event == null || event.eventType() == null ? null : event.eventType().name(),
|
||||||
|
event == null || event.lifecycle() == null ? null : event.lifecycle().name(),
|
||||||
|
scopeClassifier.classify(event),
|
||||||
|
vehicleKeys(event),
|
||||||
|
intervalIds
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private List<ResolvedVehicleUsageInterval> matchingUsageIntervals(
|
private List<ResolvedVehicleUsageInterval> matchingUsageIntervals(
|
||||||
EventHubEventDto vehicleEvent,
|
EventHubEventDto vehicleEvent,
|
||||||
List<ResolvedVehicleUsageInterval> usageIntervals,
|
List<ResolvedVehicleUsageInterval> usageIntervals,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package at.procon.eventhub.processing.service;
|
||||||
import at.procon.eventhub.dto.DriverRefDto;
|
import at.procon.eventhub.dto.DriverRefDto;
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
import at.procon.eventhub.dto.VehicleRefDto;
|
import at.procon.eventhub.dto.VehicleRefDto;
|
||||||
|
import at.procon.eventhub.processing.dto.RuntimeDriverPartitionDebugDto;
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeDerivedProjectionResultDto;
|
import at.procon.eventhub.processing.dto.UnifiedRuntimeDerivedProjectionResultDto;
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
|
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeTachographEsperScopeResultDto;
|
import at.procon.eventhub.processing.dto.UnifiedRuntimeTachographEsperScopeResultDto;
|
||||||
|
|
@ -39,6 +40,13 @@ public class UnifiedRuntimeTachographEsperScopeProcessingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public UnifiedRuntimeTachographEsperScopeResultDto processScope(UnifiedRuntimeProcessingApiRequest apiRequest) {
|
public UnifiedRuntimeTachographEsperScopeResultDto processScope(UnifiedRuntimeProcessingApiRequest apiRequest) {
|
||||||
|
return processScope(apiRequest, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnifiedRuntimeTachographEsperScopeResultDto processScope(
|
||||||
|
UnifiedRuntimeProcessingApiRequest apiRequest,
|
||||||
|
boolean includePartitionDebug
|
||||||
|
) {
|
||||||
UnifiedRuntimeProcessingRequest request = apiRequest.toRuntimeRequest();
|
UnifiedRuntimeProcessingRequest request = apiRequest.toRuntimeRequest();
|
||||||
UnifiedRuntimeEventBundle broadBundle = eventAssemblyService.assembleDriverScopedEvents(request);
|
UnifiedRuntimeEventBundle broadBundle = eventAssemblyService.assembleDriverScopedEvents(request);
|
||||||
LinkedHashSet<String> selectedDriverKeys = selectedDriverKeys(request, broadBundle.mergedEvents());
|
LinkedHashSet<String> selectedDriverKeys = selectedDriverKeys(request, broadBundle.mergedEvents());
|
||||||
|
|
@ -47,10 +55,15 @@ public class UnifiedRuntimeTachographEsperScopeProcessingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, UnifiedRuntimeDerivedProjectionResultDto> driverResults = new LinkedHashMap<>();
|
Map<String, UnifiedRuntimeDerivedProjectionResultDto> driverResults = new LinkedHashMap<>();
|
||||||
|
Map<String, RuntimeDriverPartitionDebugDto> partitionDebugByDriver = new LinkedHashMap<>();
|
||||||
Map<String, List<String>> attachedVehicleEvidenceByEvent = new LinkedHashMap<>();
|
Map<String, List<String>> attachedVehicleEvidenceByEvent = new LinkedHashMap<>();
|
||||||
List<String> warnings = new ArrayList<>();
|
List<String> warnings = new ArrayList<>();
|
||||||
for (String driverKey : selectedDriverKeys) {
|
for (String driverKey : selectedDriverKeys) {
|
||||||
UnifiedRuntimeEventBundle driverBundle = partitionForDriver(request, broadBundle, driverKey);
|
DriverPartition driverPartition = partitionForDriver(request, broadBundle, driverKey, includePartitionDebug);
|
||||||
|
UnifiedRuntimeEventBundle driverBundle = driverPartition.bundle();
|
||||||
|
if (driverPartition.debug() != null) {
|
||||||
|
partitionDebugByDriver.put(driverKey, driverPartition.debug());
|
||||||
|
}
|
||||||
for (EventHubEventDto attachedEvent : driverBundle.expandedVehicleEvents()) {
|
for (EventHubEventDto attachedEvent : driverBundle.expandedVehicleEvents()) {
|
||||||
attachedVehicleEvidenceByEvent
|
attachedVehicleEvidenceByEvent
|
||||||
.computeIfAbsent(dedupKey(attachedEvent), ignored -> new ArrayList<>())
|
.computeIfAbsent(dedupKey(attachedEvent), ignored -> new ArrayList<>())
|
||||||
|
|
@ -71,7 +84,7 @@ public class UnifiedRuntimeTachographEsperScopeProcessingService {
|
||||||
driverBundle,
|
driverBundle,
|
||||||
driverKey
|
driverKey
|
||||||
);
|
);
|
||||||
driverResults.put(driverKey, driverResult);
|
driverResults.put(driverKey, driverPartition.debug() == null ? driverResult : driverResult.withPartitionDebug(driverPartition.debug()));
|
||||||
}
|
}
|
||||||
attachedVehicleEvidenceByEvent.forEach((eventKey, drivers) -> {
|
attachedVehicleEvidenceByEvent.forEach((eventKey, drivers) -> {
|
||||||
if (drivers.size() > 1) {
|
if (drivers.size() > 1) {
|
||||||
|
|
@ -98,6 +111,7 @@ public class UnifiedRuntimeTachographEsperScopeProcessingService {
|
||||||
broadBundle.discoveredVehicles().size(),
|
broadBundle.discoveredVehicles().size(),
|
||||||
broadBundle.discoveredVehicles(),
|
broadBundle.discoveredVehicles(),
|
||||||
driverResults,
|
driverResults,
|
||||||
|
partitionDebugByDriver,
|
||||||
notes,
|
notes,
|
||||||
warnings
|
warnings
|
||||||
);
|
);
|
||||||
|
|
@ -130,10 +144,11 @@ public class UnifiedRuntimeTachographEsperScopeProcessingService {
|
||||||
return selected;
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnifiedRuntimeEventBundle partitionForDriver(
|
private DriverPartition partitionForDriver(
|
||||||
UnifiedRuntimeProcessingRequest request,
|
UnifiedRuntimeProcessingRequest request,
|
||||||
UnifiedRuntimeEventBundle broadBundle,
|
UnifiedRuntimeEventBundle broadBundle,
|
||||||
String driverKey
|
String driverKey,
|
||||||
|
boolean includePartitionDebug
|
||||||
) {
|
) {
|
||||||
List<EventHubEventDto> directDriverEvents = broadBundle.mergedEvents().stream()
|
List<EventHubEventDto> directDriverEvents = broadBundle.mergedEvents().stream()
|
||||||
.filter(event -> Objects.equals(driverKey(event), driverKey))
|
.filter(event -> Objects.equals(driverKey(event), driverKey))
|
||||||
|
|
@ -143,7 +158,8 @@ public class UnifiedRuntimeTachographEsperScopeProcessingService {
|
||||||
directDriverEvents,
|
directDriverEvents,
|
||||||
broadBundle.mergedEvents(),
|
broadBundle.mergedEvents(),
|
||||||
request.expandVehicleEvents(),
|
request.expandVehicleEvents(),
|
||||||
request.vehicleExpansionPaddingMinutes()
|
request.vehicleExpansionPaddingMinutes(),
|
||||||
|
includePartitionDebug
|
||||||
);
|
);
|
||||||
List<UnifiedDiscoveredVehicleRef> driverVehicles = discoverVehicles(attachmentResult.mergedEvents());
|
List<UnifiedDiscoveredVehicleRef> driverVehicles = discoverVehicles(attachmentResult.mergedEvents());
|
||||||
List<String> notes = new ArrayList<>(broadBundle.notes());
|
List<String> notes = new ArrayList<>(broadBundle.notes());
|
||||||
|
|
@ -153,7 +169,7 @@ public class UnifiedRuntimeTachographEsperScopeProcessingService {
|
||||||
notes.add("Vehicle-usage intervals used for temporal evidence attachment: " + attachmentResult.vehicleUsageIntervalCount() + ".");
|
notes.add("Vehicle-usage intervals used for temporal evidence attachment: " + attachmentResult.vehicleUsageIntervalCount() + ".");
|
||||||
notes.addAll(attachmentResult.notes());
|
notes.addAll(attachmentResult.notes());
|
||||||
attachmentResult.warnings().forEach(warning -> notes.add("WARNING: " + warning));
|
attachmentResult.warnings().forEach(warning -> notes.add("WARNING: " + warning));
|
||||||
return new UnifiedRuntimeEventBundle(
|
UnifiedRuntimeEventBundle bundle = new UnifiedRuntimeEventBundle(
|
||||||
request.withDriverKey(driverKey),
|
request.withDriverKey(driverKey),
|
||||||
attachmentResult.directDriverEvents(),
|
attachmentResult.directDriverEvents(),
|
||||||
driverVehicles,
|
driverVehicles,
|
||||||
|
|
@ -161,6 +177,16 @@ public class UnifiedRuntimeTachographEsperScopeProcessingService {
|
||||||
attachmentResult.mergedEvents(),
|
attachmentResult.mergedEvents(),
|
||||||
notes
|
notes
|
||||||
);
|
);
|
||||||
|
return new DriverPartition(
|
||||||
|
bundle,
|
||||||
|
includePartitionDebug ? attachmentResult.toPartitionDebug() : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record DriverPartition(
|
||||||
|
UnifiedRuntimeEventBundle bundle,
|
||||||
|
RuntimeDriverPartitionDebugDto debug
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private LinkedHashSet<String> discoverDriverKeys(List<EventHubEventDto> events) {
|
private LinkedHashSet<String> discoverDriverKeys(List<EventHubEventDto> events) {
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ class RuntimeEventProcessingServiceTest {
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
),
|
),
|
||||||
new RuntimeEventPartitioningApiRequest(RuntimeEventPartitioningStrategy.DRIVER, null, false, null, false, null, false, null, null),
|
new RuntimeEventPartitioningApiRequest(RuntimeEventPartitioningStrategy.DRIVER, null, false, null, false, null, false, null, null, null),
|
||||||
Map.of()
|
Map.of()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package at.procon.eventhub.processing.eventprocessing.profile;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
|
@ -34,7 +35,13 @@ class TachographDriverEsperRuntimeEventProcessingProfileTest {
|
||||||
assertThat(profile.displayName()).isEqualTo("Tachograph Driver Esper Processing");
|
assertThat(profile.displayName()).isEqualTo("Tachograph Driver Esper Processing");
|
||||||
assertThat(profile.defaultPartitioningStrategy()).isEqualTo(RuntimeEventPartitioningStrategy.DRIVER);
|
assertThat(profile.defaultPartitioningStrategy()).isEqualTo(RuntimeEventPartitioningStrategy.DRIVER);
|
||||||
assertThat(profile.supportedPartitioningStrategies()).containsExactly(RuntimeEventPartitioningStrategy.DRIVER);
|
assertThat(profile.supportedPartitioningStrategies()).containsExactly(RuntimeEventPartitioningStrategy.DRIVER);
|
||||||
assertThat(profile.optionalParameters()).containsExactlyInAnyOrder("significantDrivingMinutes", "minimumRestPeriodMinutes", "attachVehicleOnlyEvents", "vehicleEvidencePaddingMinutes");
|
assertThat(profile.optionalParameters()).containsExactlyInAnyOrder(
|
||||||
|
"significantDrivingMinutes",
|
||||||
|
"minimumRestPeriodMinutes",
|
||||||
|
"attachVehicleOnlyEvents",
|
||||||
|
"vehicleEvidencePaddingMinutes",
|
||||||
|
"includePartitionDebug"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -77,13 +84,15 @@ class TachographDriverEsperRuntimeEventProcessingProfileTest {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
null
|
null
|
||||||
),
|
),
|
||||||
Map.of(
|
Map.of(
|
||||||
"significantDrivingMinutes", 5,
|
"significantDrivingMinutes", 5,
|
||||||
"minimumRestPeriodMinutes", "600",
|
"minimumRestPeriodMinutes", "600",
|
||||||
"vehicleEvidencePaddingMinutes", 20,
|
"vehicleEvidencePaddingMinutes", 20,
|
||||||
"attachVehicleOnlyEvents", true
|
"attachVehicleOnlyEvents", true,
|
||||||
|
"includePartitionDebug", true
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -117,7 +126,7 @@ class TachographDriverEsperRuntimeEventProcessingProfileTest {
|
||||||
null,
|
null,
|
||||||
List.of("driver processed")
|
List.of("driver processed")
|
||||||
);
|
);
|
||||||
when(scopeService.processScope(any()))
|
when(scopeService.processScope(any(), anyBoolean()))
|
||||||
.thenReturn(new UnifiedRuntimeTachographEsperScopeResultDto(
|
.thenReturn(new UnifiedRuntimeTachographEsperScopeResultDto(
|
||||||
processedRequest,
|
processedRequest,
|
||||||
5,
|
5,
|
||||||
|
|
@ -125,6 +134,7 @@ class TachographDriverEsperRuntimeEventProcessingProfileTest {
|
||||||
1,
|
1,
|
||||||
List.of(),
|
List.of(),
|
||||||
Map.of("12:DRIVER-1", driverResult),
|
Map.of("12:DRIVER-1", driverResult),
|
||||||
|
Map.of(),
|
||||||
List.of("scope processed"),
|
List.of("scope processed"),
|
||||||
List.of()
|
List.of()
|
||||||
));
|
));
|
||||||
|
|
@ -138,12 +148,14 @@ class TachographDriverEsperRuntimeEventProcessingProfileTest {
|
||||||
assertThat(result.partitionResults().get("12:DRIVER-1").result()).isSameAs(driverResult);
|
assertThat(result.partitionResults().get("12:DRIVER-1").result()).isSameAs(driverResult);
|
||||||
|
|
||||||
ArgumentCaptor<UnifiedRuntimeProcessingApiRequest> captor = ArgumentCaptor.forClass(UnifiedRuntimeProcessingApiRequest.class);
|
ArgumentCaptor<UnifiedRuntimeProcessingApiRequest> captor = ArgumentCaptor.forClass(UnifiedRuntimeProcessingApiRequest.class);
|
||||||
verify(scopeService).processScope(captor.capture());
|
ArgumentCaptor<Boolean> debugCaptor = ArgumentCaptor.forClass(Boolean.class);
|
||||||
|
verify(scopeService).processScope(captor.capture(), debugCaptor.capture());
|
||||||
UnifiedRuntimeProcessingApiRequest delegated = captor.getValue();
|
UnifiedRuntimeProcessingApiRequest delegated = captor.getValue();
|
||||||
assertThat(delegated.driverKeys()).containsExactly("12:DRIVER-1");
|
assertThat(delegated.driverKeys()).containsExactly("12:DRIVER-1");
|
||||||
assertThat(delegated.significantDrivingMinutes()).isEqualTo(5);
|
assertThat(delegated.significantDrivingMinutes()).isEqualTo(5);
|
||||||
assertThat(delegated.minimumRestPeriodMinutes()).isEqualTo(600);
|
assertThat(delegated.minimumRestPeriodMinutes()).isEqualTo(600);
|
||||||
assertThat(delegated.vehicleExpansionPaddingMinutes()).isEqualTo(20);
|
assertThat(delegated.vehicleExpansionPaddingMinutes()).isEqualTo(20);
|
||||||
assertThat(delegated.expandVehicleEvents()).isTrue();
|
assertThat(delegated.expandVehicleEvents()).isTrue();
|
||||||
|
assertThat(debugCaptor.getValue()).isTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import at.procon.eventhub.dto.EventLifecycle;
|
||||||
import at.procon.eventhub.dto.EventType;
|
import at.procon.eventhub.dto.EventType;
|
||||||
import at.procon.eventhub.dto.VehicleRefDto;
|
import at.procon.eventhub.dto.VehicleRefDto;
|
||||||
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
|
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
|
||||||
|
import at.procon.eventhub.processing.dto.RuntimeVehicleEvidenceAttachmentDecisionDto;
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeClassifier;
|
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventScopeClassifier;
|
||||||
import at.procon.eventhub.processing.model.RuntimeDriverVehicleEvidenceAttachmentResult;
|
import at.procon.eventhub.processing.model.RuntimeDriverVehicleEvidenceAttachmentResult;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
@ -119,6 +120,34 @@ class RuntimeDriverVehicleEvidenceAttachmentServiceTest {
|
||||||
.containsExactly("pos-midnight");
|
.containsExactly("pos-midnight");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void producesDebugDecisionsForDirectAttachedAndIgnoredEvents() {
|
||||||
|
List<EventHubEventDto> driverEvents = List.of(
|
||||||
|
cardEvent("card-1-in", EventType.CARD_INSERTED, "DRIVER-1", "interval-1", "VIN-1", "AT:W-1", "2026-05-01T08:00:00Z"),
|
||||||
|
cardEvent("card-1-out", EventType.CARD_WITHDRAWN, "DRIVER-1", "interval-1", "VIN-1", "AT:W-1", "2026-05-01T18:00:00Z")
|
||||||
|
);
|
||||||
|
EventHubEventDto inside = vehicleOnlyEvent("pos-inside", "VIN-1", "AT:W-1", "2026-05-01T12:00:00Z");
|
||||||
|
EventHubEventDto outside = vehicleOnlyEvent("pos-outside", "VIN-1", "AT:W-1", "2026-05-01T20:00:00Z");
|
||||||
|
|
||||||
|
RuntimeDriverVehicleEvidenceAttachmentResult result = service.attachVehicleEvidence(
|
||||||
|
"DRIVER-1",
|
||||||
|
driverEvents,
|
||||||
|
List.of(driverEvents.get(0), driverEvents.get(1), inside, outside),
|
||||||
|
true,
|
||||||
|
0,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(result.vehicleUsageIntervals()).hasSize(1);
|
||||||
|
assertThat(result.vehicleEvidenceDecisions()).extracting(RuntimeVehicleEvidenceAttachmentDecisionDto::decision)
|
||||||
|
.contains(
|
||||||
|
"DIRECT_DRIVER_EVENT",
|
||||||
|
"ATTACHED_VEHICLE_EVIDENCE",
|
||||||
|
"IGNORED_NO_OVERLAPPING_VEHICLE_USAGE"
|
||||||
|
);
|
||||||
|
assertThat(result.toPartitionDebug().vehicleEvidenceDecisions()).hasSameSizeAs(result.vehicleEvidenceDecisions());
|
||||||
|
}
|
||||||
|
|
||||||
private EventHubEventDto cardEvent(
|
private EventHubEventDto cardEvent(
|
||||||
String externalId,
|
String externalId,
|
||||||
EventType eventType,
|
EventType eventType,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue