Compare commits
No commits in common. "82e2bd086033f1ff13c70c3c6e93ea7fe4103c4a" and "dd9c33c5fd99ef0104b398c244af4f01ef33fb8c" have entirely different histories.
82e2bd0860
...
dd9c33c5fd
|
|
@ -1,17 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
# The Flyway migrations enable both TimescaleDB and PostGIS. The TimescaleDB HA
|
image: postgres:16
|
||||||
# image is used for local development because it includes these PostgreSQL
|
|
||||||
# extensions, unlike the plain postgres image.
|
|
||||||
image: timescale/timescaledb-ha:pg16
|
|
||||||
container_name: eventhub-postgres
|
container_name: eventhub-postgres
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: eventhub
|
POSTGRES_DB: eventhub
|
||||||
|
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
# Runtime-derived tachograph projections
|
|
||||||
|
|
||||||
Runtime Processing now exposes the tachograph driving-derived Esper bundle through the unified runtime event assembly layer.
|
|
||||||
|
|
||||||
## Endpoint
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/eventhub/runtime-processing/driver-derived-projections
|
|
||||||
```
|
|
||||||
|
|
||||||
The request body uses the same selector fields as the existing runtime endpoints:
|
|
||||||
|
|
||||||
- `sessionId` for one uploaded tachograph file session
|
|
||||||
- `sessionIds` for multiple uploaded tachograph file sessions
|
|
||||||
- `compositeSessionId` for an existing tachograph composite session
|
|
||||||
- `tenantKey` + driver selector for tachograph DB / YellowFox DB runtime sources
|
|
||||||
- `eventBackend` with `SOURCE_DB` or `EVENTHUB_DB` where supported
|
|
||||||
- `sourceFamilies`, for example `TACHOGRAPH_FILE_SESSION`, `TACHOGRAPH_DB`, `YELLOWFOX_DB`
|
|
||||||
- `driverKey`, `driverSourceEntityId`, `driverCardNation`, `driverCardNumber`
|
|
||||||
- `occurredFrom`, `occurredTo`
|
|
||||||
- `expandVehicleEvents`
|
|
||||||
- `vehicleExpansionPaddingMinutes`
|
|
||||||
|
|
||||||
Additional Esper thresholds are optional:
|
|
||||||
|
|
||||||
- `significantDrivingMinutes`
|
|
||||||
- `minimumRestPeriodMinutes`
|
|
||||||
|
|
||||||
When omitted, the defaults are read from:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
eventhub:
|
|
||||||
tachograph-file-session:
|
|
||||||
processing:
|
|
||||||
significant-driving-minutes: 3
|
|
||||||
minimum-rest-period-minutes: 720
|
|
||||||
```
|
|
||||||
|
|
||||||
## Flow
|
|
||||||
|
|
||||||
```text
|
|
||||||
runtime request
|
|
||||||
-> UnifiedRuntimeEventAssemblyService
|
|
||||||
-> driver seed events
|
|
||||||
-> discovered vehicles
|
|
||||||
-> optional vehicle-expanded events
|
|
||||||
-> merged runtime event stream
|
|
||||||
-> UnifiedEventTimelineReconstructor
|
|
||||||
-> DriverTimelineReusableProjectionBuilder.buildEsperDrivingDerivedProjectionBundleFromEvents(...)
|
|
||||||
-> TachographEsperDriverProcessingResultDto
|
|
||||||
```
|
|
||||||
|
|
||||||
The derived part always uses the event-input Esper path. This means the runtime stream is passed as point events to Esper, where activity and card-vehicle-usage intervals are paired and vehicle-usage intervals are merged before the existing driving-derived rules run.
|
|
||||||
|
|
||||||
## Example: composite tachograph file session
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"compositeSessionId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
|
||||||
"sourceFamilies": ["TACHOGRAPH_FILE_SESSION"],
|
|
||||||
"driverKey": "12:12345678901234",
|
|
||||||
"occurredFrom": "2026-05-01T00:00:00Z",
|
|
||||||
"occurredTo": "2026-05-31T23:59:59Z",
|
|
||||||
"expandVehicleEvents": true,
|
|
||||||
"vehicleExpansionPaddingMinutes": 15,
|
|
||||||
"significantDrivingMinutes": 3,
|
|
||||||
"minimumRestPeriodMinutes": 720
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Response
|
|
||||||
|
|
||||||
The response contains runtime assembly metadata and the tachograph Esper result:
|
|
||||||
|
|
||||||
```text
|
|
||||||
request
|
|
||||||
driverSeedEventCount
|
|
||||||
discoveredVehicleCount
|
|
||||||
expandedVehicleEventCount
|
|
||||||
mergedEventCount
|
|
||||||
discoveredVehicles
|
|
||||||
projection
|
|
||||||
notes
|
|
||||||
```
|
|
||||||
|
|
||||||
`projection` is the same high-level structure returned by the tachograph file-session Esper endpoint, including:
|
|
||||||
|
|
||||||
- activity interval count/list
|
|
||||||
- driving interval count/list
|
|
||||||
- driving interruption intervals
|
|
||||||
- daily/weekly rest candidate intervals
|
|
||||||
- daily/weekly rest candidate coverage intervals
|
|
||||||
- unclassified rest candidate coverage intervals
|
|
||||||
- potential home overnight stays
|
|
||||||
- potential in-vehicle overnight stays
|
|
||||||
- potential in-vehicle trips
|
|
||||||
- vehicle usage intervals
|
|
||||||
- VU card absent intervals
|
|
||||||
- support geo events
|
|
||||||
|
|
||||||
## Boundary note
|
|
||||||
|
|
||||||
Runtime processing works with point events. If `occurredFrom`/`occurredTo` cuts through a source interval, the matching START/END or INSERT/WITHDRAW point may be outside the selected window. For evaluations near boundaries, request a wider event window or use vehicle expansion padding so Esper receives enough point events to reconstruct the interval.
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
# Runtime driver working-time processing
|
|
||||||
|
|
||||||
Runtime driver working-time processing is a source-neutral processing plan over canonical EventHub events.
|
|
||||||
|
|
||||||
The preferred runtime execution endpoint is:
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/eventhub/runtime-processing/executions
|
|
||||||
```
|
|
||||||
|
|
||||||
Use:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"processingPlanKey": "driver-working-time-v1"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Canonical input idea
|
|
||||||
|
|
||||||
The plan should work with events from any source once they are normalized into canonical EventHub event semantics:
|
|
||||||
|
|
||||||
```text
|
|
||||||
DRIVER_ACTIVITY START/END
|
|
||||||
DRIVER_CARD or DRIVER_VEHICLE_USAGE INSERT/WITHDRAW
|
|
||||||
POSITION / PLACE / BORDER_CROSSING / LOAD_UNLOAD / IGNITION / ODOMETER support evidence
|
|
||||||
```
|
|
||||||
|
|
||||||
Tachograph files and tachograph databases are only two possible sources. YellowFox and future telematics providers can contribute vehicle-only evidence that is attached to driver partitions by vehicle/time overlap.
|
|
||||||
|
|
||||||
## New source-neutral artifacts
|
|
||||||
|
|
||||||
```text
|
|
||||||
DriverWorkingTimeProcessingResultDto
|
|
||||||
DriverWorkingTimeProcessingCore
|
|
||||||
RuntimeDriverWorkingTimeScopeProcessingService
|
|
||||||
DriverWorkingTimeRuntimeProcessingPlan
|
|
||||||
runtime-driver-event-interval-preprocessor.epl
|
|
||||||
driver-working-time-derived-projections.epl
|
|
||||||
```
|
|
||||||
|
|
||||||
## Compatibility artifacts
|
|
||||||
|
|
||||||
The following names are kept only for backward compatibility with existing file-session APIs and older Postman calls:
|
|
||||||
|
|
||||||
```text
|
|
||||||
TachographEsperDriverProcessingResultDto
|
|
||||||
TachographEsperProcessingCore
|
|
||||||
UnifiedRuntimeTachographEsperScopeProcessingService
|
|
||||||
tachograph-driving-derived-projection-events-preprocessor.epl
|
|
||||||
tachograph-driving-derived-projection-bundle.epl
|
|
||||||
```
|
|
||||||
|
|
||||||
New runtime-processing code should use the driver-working-time names.
|
|
||||||
|
|
||||||
## EPL-backed phase modules
|
|
||||||
|
|
||||||
The driver working-time plan now contains first-class EPL-backed phase modules for event-to-interval conversion:
|
|
||||||
|
|
||||||
```text
|
|
||||||
event-to-activity-intervals
|
|
||||||
EventHub DRIVER_ACTIVITY START/END events
|
|
||||||
-> DriverActivityIntervalEvent
|
|
||||||
|
|
||||||
event-to-vehicle-usage-intervals
|
|
||||||
EventHub DRIVER_CARD INSERT/WITHDRAW events
|
|
||||||
-> DriverVehicleUsageIntervalEvent
|
|
||||||
```
|
|
||||||
|
|
||||||
The final derived projection module still delegates to the shared working-time projection service for parity with existing file-session processing. This lets us migrate the pipeline gradually: common event-to-interval conversion is now EPL-module based, while the larger rest/trip/overnight projection bundle remains compatibility-safe.
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
# Runtime event processing
|
|
||||||
|
|
||||||
Runtime processing is now centered on **processing executions**. An execution loads canonical EventHub runtime events from selected sources, optionally partitions the event set, then runs a configured processing plan made of common EPL and/or Java modules.
|
|
||||||
|
|
||||||
The preferred endpoint is:
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/eventhub/runtime-processing/executions
|
|
||||||
```
|
|
||||||
|
|
||||||
List available plans:
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/eventhub/runtime-processing/executions/plans
|
|
||||||
```
|
|
||||||
|
|
||||||
The older endpoint is still available for compatibility:
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/eventhub/runtime-processing/event-processing
|
|
||||||
```
|
|
||||||
|
|
||||||
but it should be treated as a legacy profile adapter. New clients should use `processingPlanKey`, not `profileKey`.
|
|
||||||
|
|
||||||
## First predefined plan
|
|
||||||
|
|
||||||
The first predefined plan is:
|
|
||||||
|
|
||||||
```text
|
|
||||||
driver-working-time-v1
|
|
||||||
```
|
|
||||||
|
|
||||||
Legacy alias:
|
|
||||||
|
|
||||||
```text
|
|
||||||
tachograph-driver-esper-v1
|
|
||||||
```
|
|
||||||
|
|
||||||
This plan runs the source-neutral driver working-time chain over canonical runtime events. Tachograph file/database data is only one possible source of those canonical events. Its modules include:
|
|
||||||
|
|
||||||
```text
|
|
||||||
event-to-activity-intervals
|
|
||||||
event-to-vehicle-usage-intervals
|
|
||||||
vehicle-evidence-attachment
|
|
||||||
support-evidence-normalization
|
|
||||||
driving-derived-projections
|
|
||||||
```
|
|
||||||
|
|
||||||
The important design point is that runtime processing is not tachograph-specific. Tachograph is now treated as a source/compatibility layer; the working-time plan consumes canonical driver activity, vehicle usage, and support-evidence events from any supported source.
|
|
||||||
|
|
||||||
## Example execution
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"processingPlanKey": "driver-working-time-v1",
|
|
||||||
"sourceSelection": {
|
|
||||||
"sessionIds": [
|
|
||||||
"11111111-1111-1111-1111-111111111111",
|
|
||||||
"22222222-2222-2222-2222-222222222222"
|
|
||||||
],
|
|
||||||
"sourceFamilies": ["TACHOGRAPH_FILE_SESSION", "YELLOWFOX_DB"],
|
|
||||||
"occurredFrom": "2026-05-01T00:00:00Z",
|
|
||||||
"occurredTo": "2026-05-31T23:59:59Z",
|
|
||||||
"expandVehicleEvents": true
|
|
||||||
},
|
|
||||||
"partitioning": {
|
|
||||||
"strategy": "DRIVER",
|
|
||||||
"includeAllPartitions": true,
|
|
||||||
"attachVehicleEvidence": true,
|
|
||||||
"vehicleEvidencePaddingMinutes": 15,
|
|
||||||
"includeDebug": true
|
|
||||||
},
|
|
||||||
"parameters": {
|
|
||||||
"significantDrivingMinutes": 3,
|
|
||||||
"minimumRestPeriodMinutes": 720,
|
|
||||||
"includePartitionDebug": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Conceptual flow
|
|
||||||
|
|
||||||
```text
|
|
||||||
source selection
|
|
||||||
-> runtime event loaders
|
|
||||||
-> canonical EventHub events
|
|
||||||
-> partitioning
|
|
||||||
-> vehicle evidence attachment
|
|
||||||
-> support evidence normalization
|
|
||||||
-> event-to-interval EPL modules
|
|
||||||
-> driver working-time derived projection EPL/Java modules
|
|
||||||
-> partition result map
|
|
||||||
```
|
|
||||||
|
|
||||||
## Why processing plans instead of profiles
|
|
||||||
|
|
||||||
A plan describes **which modules should run over a runtime event set**. This matches the EventHub goal better than domain-oriented profiles.
|
|
||||||
|
|
||||||
Future plans can reuse the same event-loading infrastructure:
|
|
||||||
|
|
||||||
```text
|
|
||||||
vehicle-trip-detection-v1
|
|
||||||
vehicle-stop-detection-v1
|
|
||||||
driver-settlement-v1
|
|
||||||
mixed-driver-vehicle-correlation-v1
|
|
||||||
telematics-poi-clustering-v1
|
|
||||||
```
|
|
||||||
|
|
||||||
Each plan may use different partitioning and modules, but the source loading and canonical event model remain common.
|
|
||||||
|
|
||||||
## Compatibility
|
|
||||||
|
|
||||||
Legacy profile endpoint:
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/eventhub/runtime-processing/event-processing
|
|
||||||
```
|
|
||||||
|
|
||||||
with:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"profileKey": "tachograph-driver-esper-v1",
|
|
||||||
"scope": {},
|
|
||||||
"partitioning": {},
|
|
||||||
"parameters": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
internally delegates to:
|
|
||||||
|
|
||||||
```text
|
|
||||||
processingPlanKey = driver-working-time-v1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Source-neutral driver working-time modules
|
|
||||||
|
|
||||||
The former tachograph-named processing artifacts are now represented by source-neutral driver working-time names:
|
|
||||||
|
|
||||||
```text
|
|
||||||
runtime-driver-event-interval-preprocessor.epl
|
|
||||||
driver-working-time-derived-projections.epl
|
|
||||||
DriverWorkingTimeProcessingResultDto
|
|
||||||
DriverWorkingTimeProcessingCore
|
|
||||||
RuntimeDriverWorkingTimeScopeProcessingService
|
|
||||||
```
|
|
||||||
|
|
||||||
Legacy tachograph names are kept as compatibility adapters for existing file-session endpoints and Postman requests. New runtime code should use the `driver-working-time-*` classes/resources and the `driver-working-time-v1` processing plan.
|
|
||||||
|
|
||||||
## Module execution results
|
|
||||||
|
|
||||||
`/api/eventhub/runtime-processing/executions` now exposes module execution metadata explicitly.
|
|
||||||
|
|
||||||
Top-level execution response includes:
|
|
||||||
|
|
||||||
```text
|
|
||||||
moduleResults
|
|
||||||
```
|
|
||||||
|
|
||||||
This map contains one entry per executed module. The top-level entries are intentionally sanitized: they expose status, warnings, and metadata without duplicating the full partition output payload.
|
|
||||||
|
|
||||||
Each partition result also includes:
|
|
||||||
|
|
||||||
```text
|
|
||||||
partitionResults[*].moduleResults
|
|
||||||
```
|
|
||||||
|
|
||||||
For `driver-working-time-v1`, the partition-level module results currently expose:
|
|
||||||
|
|
||||||
```text
|
|
||||||
vehicle-evidence-attachment
|
|
||||||
support-evidence-normalization
|
|
||||||
driving-derived-projections
|
|
||||||
```
|
|
||||||
|
|
||||||
The first implementation uses wrapper modules around the existing validated driver-working-time scope service. Logical phase modules such as `event-to-activity-intervals`, `event-to-vehicle-usage-intervals`, and `vehicle-usage-merge` are registered and executed as delegated modules so they can later be split into true standalone EPL/Java modules without changing the external processing-plan contract.
|
|
||||||
|
|
||||||
## First-class EPL modules
|
|
||||||
|
|
||||||
Runtime processing now supports modules implemented directly with Esper EPL through the common `RuntimeEplModuleExecutor`.
|
|
||||||
|
|
||||||
The first source-neutral EPL modules are:
|
|
||||||
|
|
||||||
| Module key | Engine | EPL resource | Output statement |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `event-to-activity-intervals` | `EPL` | `esper/runtime-driver-activity-intervals.epl` | `driverActivityIntervals` |
|
|
||||||
| `event-to-vehicle-usage-intervals` | `EPL` | `esper/runtime-driver-vehicle-usage-intervals.epl` | `driverVehicleUsageIntervals` |
|
|
||||||
|
|
||||||
These modules operate on canonical EventHub runtime events, not on tachograph-specific source rows. They are currently used as first-class phase modules in `driver-working-time-v1`; the final `driving-derived-projections` module remains a compatibility adapter over the validated working-time projection service until the remaining projection stages are split into direct EPL modules.
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
# Runtime tachograph Esper scope processing
|
|
||||||
|
|
||||||
This document is kept for compatibility. The preferred architecture is now the common Runtime Processing execution model.
|
|
||||||
|
|
||||||
Preferred endpoint:
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/eventhub/runtime-processing/executions
|
|
||||||
```
|
|
||||||
|
|
||||||
Use:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"processingPlanKey": "driver-working-time-v1"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Legacy compatibility endpoint:
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/eventhub/runtime-processing/tachograph/esper-processing
|
|
||||||
```
|
|
||||||
|
|
||||||
still exists and delegates through the common runtime processing infrastructure.
|
|
||||||
|
|
||||||
Legacy profile endpoint:
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/eventhub/runtime-processing/event-processing
|
|
||||||
```
|
|
||||||
|
|
||||||
with:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"profileKey": "tachograph-driver-esper-v1"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
also remains available, but new clients should use `processingPlanKey` and `/executions`.
|
|
||||||
|
|
||||||
The current `driver-working-time-v1` plan uses these modules:
|
|
||||||
|
|
||||||
```text
|
|
||||||
event-to-activity-intervals
|
|
||||||
event-to-vehicle-usage-intervals
|
|
||||||
vehicle-evidence-attachment
|
|
||||||
support-evidence-normalization
|
|
||||||
driving-derived-projections
|
|
||||||
```
|
|
||||||
|
|
||||||
It can load runtime events from multiple sources and sessions, partition them by driver, attach vehicle-only evidence by vehicle/time overlap, normalize support evidence, and run the existing tachograph Esper/Java processing chain per driver partition.
|
|
||||||
|
|
@ -1,552 +0,0 @@
|
||||||
{
|
|
||||||
"info": {
|
|
||||||
"name": "EventHub Runtime Event Processing",
|
|
||||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
|
||||||
"description": "Examples for source-neutral Runtime Event Processing and the tachograph-driver-esper-v1 profile. Includes plan execution, compatibility profile execution, validation endpoints, and legacy runtime diagnostic endpoints (driver-events, driver-timeline, driver-derived-projections)."
|
|
||||||
},
|
|
||||||
"variable": [
|
|
||||||
{
|
|
||||||
"key": "baseUrl",
|
|
||||||
"value": "http://localhost:8085"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "sessionId",
|
|
||||||
"value": "11111111-1111-1111-1111-111111111111"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "sessionId2",
|
|
||||||
"value": "22222222-2222-2222-2222-222222222222"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "compositeSessionId",
|
|
||||||
"value": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "driverKey",
|
|
||||||
"value": "12:12345678901234"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"item": [
|
|
||||||
{
|
|
||||||
"name": "List runtime processing plans",
|
|
||||||
"request": {
|
|
||||||
"method": "GET",
|
|
||||||
"header": [],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/executions/plans",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"executions",
|
|
||||||
"plans"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Execute processing plan - driver working time single session",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/executions",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"executions"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"processingPlanKey\": \"driver-working-time-v1\",\n \"sourceSelection\": {\n \"sessionId\": \"{{sessionId}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"driverKey\": \"{{driverKey}}\",\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"expandVehicleEvents\": true\n },\n \"partitioning\": {\n \"strategy\": \"DRIVER\",\n \"includeAllPartitions\": false,\n \"attachVehicleEvidence\": true,\n \"vehicleEvidencePaddingMinutes\": 15,\n \"includeDebug\": true\n },\n \"parameters\": {\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"includePartitionDebug\": true\n }\n}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Execute processing plan - driver working time multiple sessions all drivers",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/executions",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"executions"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"processingPlanKey\": \"driver-working-time-v1\",\n \"sourceSelection\": {\n \"sessionIds\": [\n \"{{sessionId}}\",\n \"{{sessionId2}}\"\n ],\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"expandVehicleEvents\": true\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 \"includePartitionDebug\": true\n }\n}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Execute processing plan - driver working time composite session all drivers",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/executions",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"executions"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"processingPlanKey\": \"driver-working-time-v1\",\n \"sourceSelection\": {\n \"compositeSessionId\": \"{{compositeSessionId}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"expandVehicleEvents\": true\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 \"includePartitionDebug\": true\n }\n}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Execute processing plan - driver working time source DB mixed sources",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/executions",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"executions"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"processingPlanKey\": \"driver-working-time-v1\",\n \"sourceSelection\": {\n \"tenantKey\": \"{{tenantKey}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_DB\",\n \"YELLOWFOX_DB\"\n ],\n \"eventBackend\": \"SOURCE_DB\",\n \"driverSourceEntityId\": \"{{driverSourceEntityId}}\",\n \"driverCardNation\": \"{{driverCardNation}}\",\n \"driverCardNumber\": \"{{driverCardNumber}}\",\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n },\n \"partitioning\": {\n \"strategy\": \"DRIVER\",\n \"includeAllPartitions\": false,\n \"attachVehicleEvidence\": true,\n \"vehicleEvidencePaddingMinutes\": 15,\n \"includeDebug\": true\n },\n \"parameters\": {\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"includePartitionDebug\": true\n }\n}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "List runtime event-processing profiles",
|
|
||||||
"request": {
|
|
||||||
"method": "GET",
|
|
||||||
"header": [],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing/profiles",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"event-processing",
|
|
||||||
"profiles"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Execute tachograph profile - single session and one driver",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"body": {
|
|
||||||
"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 \"includeDebug\": true\n },\n \"parameters\": {\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"attachVehicleOnlyEvents\": true,\n \"vehicleEvidencePaddingMinutes\": 15,\n \"includePartitionDebug\": true\n }\n}"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"event-processing"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Execute tachograph profile - multiple sessions all drivers",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"body": {
|
|
||||||
"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 \"includeDebug\": true\n },\n \"parameters\": {\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"attachVehicleOnlyEvents\": true,\n \"vehicleEvidencePaddingMinutes\": 15,\n \"includePartitionDebug\": true\n }\n}"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"event-processing"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Execute tachograph profile - composite session all drivers",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"body": {
|
|
||||||
"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 \"includeDebug\": true\n },\n \"parameters\": {\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720,\n \"attachVehicleOnlyEvents\": true,\n \"vehicleEvidencePaddingMinutes\": 15,\n \"includePartitionDebug\": true\n }\n}"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"event-processing"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Compatibility endpoint - tachograph esper processing",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"sessionIds\": [\n \"{{sessionId}}\",\n \"{{sessionId2}}\"\n ],\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"includeAllDrivers\": true,\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}"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/tachograph/esper-processing",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"tachograph",
|
|
||||||
"esper-processing"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Validate mixed-source vehicle evidence attachment and normalization",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"processingRequest\": {\n \"profileKey\": \"tachograph-driver-esper-v1\",\n \"scope\": {\n \"sessionIds\": [\n \"{{sessionId1}}\",\n \"{{sessionId2}}\"\n ],\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\",\n \"YELLOWFOX_DB\"\n ],\n \"eventBackend\": \"SOURCE_DB\",\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 \"includePartitionDebug\": true\n }\n },\n \"minimumAttachedVehicleEvidenceEvents\": 1,\n \"minimumNormalizedSupportEvidenceEvents\": 1,\n \"failWhenPartitionDebugMissing\": true\n}"
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/event-processing/validation/mixed-source-evidence",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"event-processing",
|
|
||||||
"validation",
|
|
||||||
"mixed-source-evidence"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Runtime diagnostics - driver events from single file session",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/driver-events",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"driver-events"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"sessionId\": \"{{sessionId}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"eventBackend\": \"SOURCE_DB\",\n \"driverKey\": \"{{driverKey}}\",\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Runtime diagnostics - driver timeline from single file session",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/driver-timeline",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"driver-timeline"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"sessionId\": \"{{sessionId}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"eventBackend\": \"SOURCE_DB\",\n \"driverKey\": \"{{driverKey}}\",\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Runtime diagnostics - driver derived projections from single file session",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/driver-derived-projections",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"driver-derived-projections"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"sessionId\": \"{{sessionId}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"eventBackend\": \"SOURCE_DB\",\n \"driverKey\": \"{{driverKey}}\",\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15,\n \"significantDrivingMinutes\": 3,\n \"minimumRestPeriodMinutes\": 720\n}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Runtime diagnostics - driver events from multiple file sessions",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/driver-events",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"driver-events"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"sessionIds\": [\n \"{{sessionId}}\",\n \"{{sessionId2}}\"\n ],\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"eventBackend\": \"SOURCE_DB\",\n \"driverKey\": \"{{driverKey}}\",\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Runtime diagnostics - driver timeline from composite session",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/driver-timeline",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"driver-timeline"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"compositeSessionId\": \"{{compositeSessionId}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_FILE_SESSION\"\n ],\n \"eventBackend\": \"SOURCE_DB\",\n \"driverKey\": \"{{driverKey}}\",\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Runtime diagnostics - driver events from source DB",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/driver-events",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"driver-events"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"tenantKey\": \"{{tenantKey}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_DB\",\n \"YELLOWFOX_DB\"\n ],\n \"eventBackend\": \"SOURCE_DB\",\n \"driverSourceEntityId\": \"{{driverSourceEntityId}}\",\n \"driverCardNation\": \"{{driverCardNation}}\",\n \"driverCardNumber\": \"{{driverCardNumber}}\",\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Runtime diagnostics - driver timeline from EventHub DB",
|
|
||||||
"request": {
|
|
||||||
"method": "POST",
|
|
||||||
"header": [
|
|
||||||
{
|
|
||||||
"key": "Content-Type",
|
|
||||||
"value": "application/json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"url": {
|
|
||||||
"raw": "{{baseUrl}}/api/eventhub/runtime-processing/driver-timeline",
|
|
||||||
"host": [
|
|
||||||
"{{baseUrl}}"
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
"api",
|
|
||||||
"eventhub",
|
|
||||||
"runtime-processing",
|
|
||||||
"driver-timeline"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"tenantKey\": \"{{tenantKey}}\",\n \"sourceFamilies\": [\n \"TACHOGRAPH_DB\",\n \"YELLOWFOX_DB\"\n ],\n \"eventBackend\": \"EVENTHUB_DB\",\n \"driverCardNation\": \"{{driverCardNation}}\",\n \"driverCardNumber\": \"{{driverCardNumber}}\",\n \"occurredFrom\": \"{{occurredFrom}}\",\n \"occurredTo\": \"{{occurredTo}}\",\n \"expandVehicleEvents\": true,\n \"vehicleExpansionPaddingMinutes\": 15\n}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -357,7 +357,6 @@ public class EventHubProperties {
|
||||||
|
|
||||||
public static class Processing {
|
public static class Processing {
|
||||||
private TimelineInputMode timelineInputMode = TimelineInputMode.INTERVALS;
|
private TimelineInputMode timelineInputMode = TimelineInputMode.INTERVALS;
|
||||||
private DrivingDerivedProjectionInputMode drivingDerivedProjectionInputMode = DrivingDerivedProjectionInputMode.INTERVALS;
|
|
||||||
private int operatingSplitIdleHours = 7;
|
private int operatingSplitIdleHours = 7;
|
||||||
private int significantDrivingMinutes = 3;
|
private int significantDrivingMinutes = 3;
|
||||||
private int minimumRestPeriodMinutes = 720;
|
private int minimumRestPeriodMinutes = 720;
|
||||||
|
|
@ -378,16 +377,6 @@ public class EventHubProperties {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrivingDerivedProjectionInputMode getDrivingDerivedProjectionInputMode() {
|
|
||||||
return drivingDerivedProjectionInputMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDrivingDerivedProjectionInputMode(DrivingDerivedProjectionInputMode drivingDerivedProjectionInputMode) {
|
|
||||||
if (drivingDerivedProjectionInputMode != null) {
|
|
||||||
this.drivingDerivedProjectionInputMode = drivingDerivedProjectionInputMode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getOperatingSplitIdleHours() {
|
public int getOperatingSplitIdleHours() {
|
||||||
return operatingSplitIdleHours;
|
return operatingSplitIdleHours;
|
||||||
}
|
}
|
||||||
|
|
@ -467,14 +456,6 @@ public class EventHubProperties {
|
||||||
EVENTS
|
EVENTS
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DrivingDerivedProjectionInputMode {
|
|
||||||
/** Existing stable path: Java resolves intervals and EPL receives interval input streams. */
|
|
||||||
INTERVALS,
|
|
||||||
|
|
||||||
/** New path: EPL receives EventHub point events and reconstructs interval streams internally. */
|
|
||||||
EVENTS
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LegalRequirements {
|
public static class LegalRequirements {
|
||||||
private static final String DEFAULT_BASE_URL = "https://legalrequirements.services.bytebar.eu/ODataV4/LR";
|
private static final String DEFAULT_BASE_URL = "https://legalrequirements.services.bytebar.eu/ODataV4/LR";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package at.procon.eventhub.config;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import java.util.List;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
|
||||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class WebMvcJsonConfig implements WebMvcConfigurer {
|
|
||||||
|
|
||||||
private final ObjectMapper objectMapper;
|
|
||||||
|
|
||||||
public WebMvcJsonConfig(ObjectMapper objectMapper) {
|
|
||||||
this.objectMapper = objectMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
|
|
||||||
converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
|
|
||||||
converters.add(0, new MappingJackson2HttpMessageConverter(objectMapper));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +1,15 @@
|
||||||
package at.procon.eventhub.processing.api;
|
package at.procon.eventhub.processing.api;
|
||||||
|
|
||||||
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.eventprocessing.RuntimeEventProcessingService;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingProfileDescriptorDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingExecutionApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingExecutionResultDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingExecutionService;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingPlanDescriptorDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.validation.RuntimeMixedSourceEvidenceValidationApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.validation.RuntimeMixedSourceEvidenceValidationResultDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.validation.RuntimeMixedSourceEvidenceValidationService;
|
|
||||||
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.UnifiedRuntimeDriverTimelineService;
|
import at.procon.eventhub.processing.service.UnifiedRuntimeDriverTimelineService;
|
||||||
import at.procon.eventhub.processing.service.UnifiedRuntimeEventAssemblyService;
|
import at.procon.eventhub.processing.service.UnifiedRuntimeEventAssemblyService;
|
||||||
import at.procon.eventhub.processing.service.RuntimeDriverWorkingTimeScopeProcessingService;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/eventhub/runtime-processing")
|
@RequestMapping("/api/eventhub/runtime-processing")
|
||||||
|
|
@ -38,64 +17,13 @@ public class UnifiedRuntimeProcessingController {
|
||||||
|
|
||||||
private final UnifiedRuntimeEventAssemblyService eventAssemblyService;
|
private final UnifiedRuntimeEventAssemblyService eventAssemblyService;
|
||||||
private final UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService;
|
private final UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService;
|
||||||
private final UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService;
|
|
||||||
private final RuntimeDriverWorkingTimeScopeProcessingService tachographEsperScopeProcessingService;
|
|
||||||
private final RuntimeEventProcessingService runtimeEventProcessingService;
|
|
||||||
private final RuntimeProcessingExecutionService runtimeProcessingExecutionService;
|
|
||||||
private final RuntimeTachographParityValidationService tachographParityValidationService;
|
|
||||||
private final RuntimeMixedSourceEvidenceValidationService mixedSourceEvidenceValidationService;
|
|
||||||
|
|
||||||
public UnifiedRuntimeProcessingController(
|
public UnifiedRuntimeProcessingController(
|
||||||
UnifiedRuntimeEventAssemblyService eventAssemblyService,
|
UnifiedRuntimeEventAssemblyService eventAssemblyService,
|
||||||
UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService,
|
UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService
|
||||||
UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService
|
|
||||||
) {
|
|
||||||
this(eventAssemblyService, runtimeDriverTimelineService, runtimeDerivedProjectionService, null, null, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeProcessingController(
|
|
||||||
UnifiedRuntimeEventAssemblyService eventAssemblyService,
|
|
||||||
UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService,
|
|
||||||
UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService,
|
|
||||||
RuntimeDriverWorkingTimeScopeProcessingService tachographEsperScopeProcessingService,
|
|
||||||
RuntimeEventProcessingService runtimeEventProcessingService
|
|
||||||
) {
|
|
||||||
this(eventAssemblyService, runtimeDriverTimelineService, runtimeDerivedProjectionService,
|
|
||||||
tachographEsperScopeProcessingService, runtimeEventProcessingService, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public UnifiedRuntimeProcessingController(
|
|
||||||
UnifiedRuntimeEventAssemblyService eventAssemblyService,
|
|
||||||
UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService,
|
|
||||||
UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService,
|
|
||||||
RuntimeDriverWorkingTimeScopeProcessingService tachographEsperScopeProcessingService,
|
|
||||||
RuntimeEventProcessingService runtimeEventProcessingService,
|
|
||||||
RuntimeTachographParityValidationService tachographParityValidationService,
|
|
||||||
RuntimeMixedSourceEvidenceValidationService mixedSourceEvidenceValidationService,
|
|
||||||
RuntimeProcessingExecutionService runtimeProcessingExecutionService
|
|
||||||
) {
|
) {
|
||||||
this.eventAssemblyService = eventAssemblyService;
|
this.eventAssemblyService = eventAssemblyService;
|
||||||
this.runtimeDriverTimelineService = runtimeDriverTimelineService;
|
this.runtimeDriverTimelineService = runtimeDriverTimelineService;
|
||||||
this.runtimeDerivedProjectionService = runtimeDerivedProjectionService;
|
|
||||||
this.tachographEsperScopeProcessingService = tachographEsperScopeProcessingService;
|
|
||||||
this.runtimeEventProcessingService = runtimeEventProcessingService;
|
|
||||||
this.runtimeProcessingExecutionService = runtimeProcessingExecutionService;
|
|
||||||
this.tachographParityValidationService = tachographParityValidationService;
|
|
||||||
this.mixedSourceEvidenceValidationService = mixedSourceEvidenceValidationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeProcessingController(
|
|
||||||
UnifiedRuntimeEventAssemblyService eventAssemblyService,
|
|
||||||
UnifiedRuntimeDriverTimelineService runtimeDriverTimelineService,
|
|
||||||
UnifiedRuntimeDerivedProjectionService runtimeDerivedProjectionService,
|
|
||||||
RuntimeDriverWorkingTimeScopeProcessingService tachographEsperScopeProcessingService,
|
|
||||||
RuntimeEventProcessingService runtimeEventProcessingService,
|
|
||||||
RuntimeTachographParityValidationService tachographParityValidationService
|
|
||||||
) {
|
|
||||||
this(eventAssemblyService, runtimeDriverTimelineService, runtimeDerivedProjectionService,
|
|
||||||
tachographEsperScopeProcessingService, runtimeEventProcessingService,
|
|
||||||
tachographParityValidationService, null, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/driver-events")
|
@PostMapping("/driver-events")
|
||||||
|
|
@ -111,88 +39,4 @@ public class UnifiedRuntimeProcessingController {
|
||||||
) {
|
) {
|
||||||
return ResponseEntity.ok(runtimeDriverTimelineService.loadDriverTimeline(request.toRuntimeRequest()));
|
return ResponseEntity.ok(runtimeDriverTimelineService.loadDriverTimeline(request.toRuntimeRequest()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/driver-derived-projections")
|
|
||||||
public ResponseEntity<UnifiedRuntimeDerivedProjectionResultDto> loadDriverDerivedProjections(
|
|
||||||
@RequestBody UnifiedRuntimeProcessingApiRequest request
|
|
||||||
) {
|
|
||||||
return ResponseEntity.ok(runtimeDerivedProjectionService.loadDriverDerivedProjections(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/executions/plans")
|
|
||||||
public ResponseEntity<List<RuntimeProcessingPlanDescriptorDto>> listRuntimeProcessingPlans() {
|
|
||||||
if (runtimeProcessingExecutionService == null) {
|
|
||||||
throw new IllegalStateException("Runtime processing execution service is not configured.");
|
|
||||||
}
|
|
||||||
return ResponseEntity.ok(runtimeProcessingExecutionService.listPlans());
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/executions")
|
|
||||||
public ResponseEntity<RuntimeProcessingExecutionResultDto> runRuntimeProcessingExecution(
|
|
||||||
@RequestBody RuntimeProcessingExecutionApiRequest request
|
|
||||||
) {
|
|
||||||
if (runtimeProcessingExecutionService == null) {
|
|
||||||
throw new IllegalStateException("Runtime processing execution service is not configured.");
|
|
||||||
}
|
|
||||||
return ResponseEntity.ok(runtimeProcessingExecutionService.execute(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/event-processing/profiles")
|
|
||||||
public ResponseEntity<List<RuntimeEventProcessingProfileDescriptorDto>> listEventProcessingProfiles() {
|
|
||||||
if (runtimeEventProcessingService == null) {
|
|
||||||
throw new IllegalStateException("Runtime event processing service is not configured.");
|
|
||||||
}
|
|
||||||
return ResponseEntity.ok(runtimeEventProcessingService.listProfiles());
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/event-processing")
|
|
||||||
public ResponseEntity<RuntimeEventProcessingResultDto> runEventProcessing(
|
|
||||||
@RequestBody RuntimeEventProcessingApiRequest request
|
|
||||||
) {
|
|
||||||
if (runtimeEventProcessingService == null) {
|
|
||||||
throw new IllegalStateException("Runtime event processing service is not configured.");
|
|
||||||
}
|
|
||||||
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("/event-processing/validation/mixed-source-evidence")
|
|
||||||
public ResponseEntity<RuntimeMixedSourceEvidenceValidationResultDto> validateMixedSourceEvidence(
|
|
||||||
@RequestBody RuntimeMixedSourceEvidenceValidationApiRequest request
|
|
||||||
) {
|
|
||||||
if (mixedSourceEvidenceValidationService == null) {
|
|
||||||
throw new IllegalStateException("Runtime mixed-source evidence validation service is not configured.");
|
|
||||||
}
|
|
||||||
return ResponseEntity.ok(mixedSourceEvidenceValidationService.validate(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/tachograph/esper-processing")
|
|
||||||
public ResponseEntity<UnifiedRuntimeTachographEsperScopeResultDto> runTachographEsperProcessing(
|
|
||||||
@RequestBody UnifiedRuntimeProcessingApiRequest request
|
|
||||||
) {
|
|
||||||
if (runtimeEventProcessingService != null) {
|
|
||||||
RuntimeEventProcessingResultDto genericResult = runtimeEventProcessingService.process(
|
|
||||||
RuntimeEventProcessingApiRequest.tachographDriverEsper(request)
|
|
||||||
);
|
|
||||||
return ResponseEntity.ok(UnifiedRuntimeTachographEsperScopeResultDto.fromGenericRuntimeEventProcessingResult(genericResult));
|
|
||||||
}
|
|
||||||
if (tachographEsperScopeProcessingService == null) {
|
|
||||||
throw new IllegalStateException("Tachograph Esper scope processing service is not configured.");
|
|
||||||
}
|
|
||||||
return ResponseEntity.ok(UnifiedRuntimeTachographEsperScopeResultDto.fromDriverWorkingTime(
|
|
||||||
tachographEsperScopeProcessingService.processScope(request)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package at.procon.eventhub.processing.api;
|
package at.procon.eventhub.processing.api;
|
||||||
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.DriverNotFoundInCompositeSessionException;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.DriverNotFoundInSessionException;
|
import at.procon.eventhub.tachographfilesession.service.DriverNotFoundInSessionException;
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographCompositeSessionNotFoundException;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionNotFoundException;
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionNotFoundException;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -16,9 +14,7 @@ public class UnifiedRuntimeProcessingExceptionHandler {
|
||||||
|
|
||||||
@ExceptionHandler({
|
@ExceptionHandler({
|
||||||
TachographFileSessionNotFoundException.class,
|
TachographFileSessionNotFoundException.class,
|
||||||
TachographCompositeSessionNotFoundException.class,
|
DriverNotFoundInSessionException.class
|
||||||
DriverNotFoundInSessionException.class,
|
|
||||||
DriverNotFoundInCompositeSessionException.class
|
|
||||||
})
|
})
|
||||||
public ResponseEntity<Map<String, Object>> notFound(RuntimeException exception) {
|
public ResponseEntity<Map<String, Object>> notFound(RuntimeException exception) {
|
||||||
return error(HttpStatus.NOT_FOUND, exception);
|
return error(HttpStatus.NOT_FOUND, exception);
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package at.procon.eventhub.processing.driverworkingtime.dto;
|
|
||||||
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleTripIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperSupportGeoEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public record DriverWorkingTimeProcessingResultDto(
|
|
||||||
UUID sessionId,
|
|
||||||
String driverKey,
|
|
||||||
String sourceKind,
|
|
||||||
OffsetDateTime loadedFrom,
|
|
||||||
OffsetDateTime loadedTo,
|
|
||||||
OffsetDateTime requestedFrom,
|
|
||||||
OffsetDateTime requestedTo,
|
|
||||||
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,
|
|
||||||
List<TachographEsperActivityIntervalEvent> activityIntervals,
|
|
||||||
List<TachographEsperActivityIntervalEvent> drivingIntervals,
|
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals,
|
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals,
|
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals,
|
|
||||||
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> dailyWeeklyRestCandidateCoverageIntervals,
|
|
||||||
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> unclassifiedDailyWeeklyRestCandidateCoverageIntervals,
|
|
||||||
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals,
|
|
||||||
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals,
|
|
||||||
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals,
|
|
||||||
List<TachographEsperVehicleUsageIntervalEvent> vehicleUsageIntervals,
|
|
||||||
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals,
|
|
||||||
List<TachographEsperSupportGeoEvent> supportGeoEvents,
|
|
||||||
List<String> notes
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package at.procon.eventhub.processing.driverworkingtime.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographEsperProcessingCore;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographEsperProcessingInput;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Source-neutral driver working-time processing core.
|
|
||||||
*
|
|
||||||
* <p>Tachograph file/database data is only one source of the canonical driver activity,
|
|
||||||
* vehicle-usage, and support-evidence event streams consumed here. The legacy
|
|
||||||
* TachographEsperProcessingCore delegates to the same processing logic for backward
|
|
||||||
* compatibility.</p>
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class DriverWorkingTimeProcessingCore {
|
|
||||||
|
|
||||||
private final TachographEsperProcessingCore delegate;
|
|
||||||
|
|
||||||
public DriverWorkingTimeProcessingCore(TachographEsperProcessingCore delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DriverWorkingTimeProcessingResultDto process(TachographEsperProcessingInput input) {
|
|
||||||
return delegate.processDriverWorkingTime(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package at.procon.eventhub.processing.dto;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record RuntimeSupportEvidenceNormalizationDebugDto(
|
|
||||||
int inputEventCount,
|
|
||||||
int normalizedSupportEvidenceEventCount,
|
|
||||||
int unchangedEventCount,
|
|
||||||
List<String> notes
|
|
||||||
) {
|
|
||||||
public RuntimeSupportEvidenceNormalizationDebugDto {
|
|
||||||
inputEventCount = Math.max(0, inputEventCount);
|
|
||||||
normalizedSupportEvidenceEventCount = Math.max(0, normalizedSupportEvidenceEventCount);
|
|
||||||
unchangedEventCount = Math.max(0, unchangedEventCount);
|
|
||||||
notes = notes == null ? List.of() : List.copyOf(notes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
package at.procon.eventhub.processing.dto;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
|
||||||
import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record UnifiedRuntimeDerivedProjectionResultDto(
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
int driverSeedEventCount,
|
|
||||||
int discoveredVehicleCount,
|
|
||||||
int expandedVehicleEventCount,
|
|
||||||
int mergedEventCount,
|
|
||||||
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
|
||||||
DriverWorkingTimeProcessingResultDto projection,
|
|
||||||
List<String> notes,
|
|
||||||
RuntimeSupportEvidenceNormalizationDebugDto supportEvidenceNormalization,
|
|
||||||
RuntimeDriverPartitionDebugDto partitionDebug
|
|
||||||
) {
|
|
||||||
public UnifiedRuntimeDerivedProjectionResultDto {
|
|
||||||
discoveredVehicles = discoveredVehicles == null ? List.of() : List.copyOf(discoveredVehicles);
|
|
||||||
notes = notes == null ? List.of() : List.copyOf(notes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeDerivedProjectionResultDto(
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
int driverSeedEventCount,
|
|
||||||
int discoveredVehicleCount,
|
|
||||||
int expandedVehicleEventCount,
|
|
||||||
int mergedEventCount,
|
|
||||||
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
|
||||||
DriverWorkingTimeProcessingResultDto projection,
|
|
||||||
List<String> notes
|
|
||||||
) {
|
|
||||||
this(
|
|
||||||
request,
|
|
||||||
driverSeedEventCount,
|
|
||||||
discoveredVehicleCount,
|
|
||||||
expandedVehicleEventCount,
|
|
||||||
mergedEventCount,
|
|
||||||
discoveredVehicles,
|
|
||||||
projection,
|
|
||||||
notes,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeDerivedProjectionResultDto(
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
int driverSeedEventCount,
|
|
||||||
int discoveredVehicleCount,
|
|
||||||
int expandedVehicleEventCount,
|
|
||||||
int mergedEventCount,
|
|
||||||
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
|
||||||
DriverWorkingTimeProcessingResultDto projection,
|
|
||||||
List<String> notes,
|
|
||||||
RuntimeSupportEvidenceNormalizationDebugDto supportEvidenceNormalization
|
|
||||||
) {
|
|
||||||
this(
|
|
||||||
request,
|
|
||||||
driverSeedEventCount,
|
|
||||||
discoveredVehicleCount,
|
|
||||||
expandedVehicleEventCount,
|
|
||||||
mergedEventCount,
|
|
||||||
discoveredVehicles,
|
|
||||||
projection,
|
|
||||||
notes,
|
|
||||||
supportEvidenceNormalization,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public UnifiedRuntimeDerivedProjectionResultDto withPartitionDebug(RuntimeDriverPartitionDebugDto debug) {
|
|
||||||
return new UnifiedRuntimeDerivedProjectionResultDto(
|
|
||||||
request,
|
|
||||||
driverSeedEventCount,
|
|
||||||
discoveredVehicleCount,
|
|
||||||
expandedVehicleEventCount,
|
|
||||||
mergedEventCount,
|
|
||||||
discoveredVehicles,
|
|
||||||
projection,
|
|
||||||
notes,
|
|
||||||
supportEvidenceNormalization,
|
|
||||||
debug
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
package at.procon.eventhub.processing.dto;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingPartitionResultDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record UnifiedRuntimeDriverWorkingTimeScopeResultDto(
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
int inputEventCount,
|
|
||||||
int selectedDriverCount,
|
|
||||||
int discoveredVehicleCount,
|
|
||||||
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
|
||||||
Map<String, UnifiedRuntimeDerivedProjectionResultDto> driverResults,
|
|
||||||
Map<String, RuntimeDriverPartitionDebugDto> partitionDebugByDriver,
|
|
||||||
List<String> notes,
|
|
||||||
List<String> warnings
|
|
||||||
) {
|
|
||||||
public UnifiedRuntimeDriverWorkingTimeScopeResultDto {
|
|
||||||
discoveredVehicles = discoveredVehicles == null ? List.of() : List.copyOf(discoveredVehicles);
|
|
||||||
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);
|
|
||||||
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UnifiedRuntimeDriverWorkingTimeScopeResultDto fromGenericRuntimeEventProcessingResult(
|
|
||||||
RuntimeEventProcessingResultDto genericResult
|
|
||||||
) {
|
|
||||||
if (genericResult == null) {
|
|
||||||
throw new IllegalArgumentException("genericResult must not be null");
|
|
||||||
}
|
|
||||||
LinkedHashMap<String, UnifiedRuntimeDerivedProjectionResultDto> driverResults = new LinkedHashMap<>();
|
|
||||||
for (Map.Entry<String, RuntimeEventProcessingPartitionResultDto> entry : genericResult.partitionResults().entrySet()) {
|
|
||||||
Object value = entry.getValue().result();
|
|
||||||
if (value instanceof UnifiedRuntimeDerivedProjectionResultDto projectionResult) {
|
|
||||||
driverResults.put(entry.getKey(), projectionResult);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Cannot convert generic partition result for key "
|
|
||||||
+ entry.getKey() + " to UnifiedRuntimeDerivedProjectionResultDto: "
|
|
||||||
+ (value == null ? "null" : value.getClass().getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new UnifiedRuntimeDriverWorkingTimeScopeResultDto(
|
|
||||||
genericResult.request(),
|
|
||||||
genericResult.inputEventCount(),
|
|
||||||
genericResult.selectedPartitionCount(),
|
|
||||||
genericResult.discoveredVehicleCount(),
|
|
||||||
genericResult.discoveredVehicles(),
|
|
||||||
driverResults,
|
|
||||||
extractPartitionDebug(genericResult),
|
|
||||||
genericResult.notes(),
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -4,45 +4,30 @@ import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBackend;
|
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBackend;
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public record UnifiedRuntimeProcessingApiRequest(
|
public record UnifiedRuntimeProcessingApiRequest(
|
||||||
UUID sessionId,
|
UUID sessionId,
|
||||||
List<UUID> sessionIds,
|
|
||||||
UUID compositeSessionId,
|
|
||||||
String tenantKey,
|
String tenantKey,
|
||||||
Set<UnifiedEventSourceFamily> sourceFamilies,
|
Set<UnifiedEventSourceFamily> sourceFamilies,
|
||||||
UnifiedRuntimeEventBackend eventBackend,
|
UnifiedRuntimeEventBackend eventBackend,
|
||||||
String driverKey,
|
String driverKey,
|
||||||
Set<String> driverKeys,
|
|
||||||
Boolean includeAllDrivers,
|
|
||||||
Set<String> vehicleKeys,
|
|
||||||
Boolean includeAllVehicles,
|
|
||||||
String driverSourceEntityId,
|
String driverSourceEntityId,
|
||||||
String driverCardNation,
|
String driverCardNation,
|
||||||
String driverCardNumber,
|
String driverCardNumber,
|
||||||
OffsetDateTime occurredFrom,
|
OffsetDateTime occurredFrom,
|
||||||
OffsetDateTime occurredTo,
|
OffsetDateTime occurredTo,
|
||||||
Boolean expandVehicleEvents,
|
Boolean expandVehicleEvents,
|
||||||
Integer vehicleExpansionPaddingMinutes,
|
Integer vehicleExpansionPaddingMinutes
|
||||||
Integer significantDrivingMinutes,
|
|
||||||
Integer minimumRestPeriodMinutes
|
|
||||||
) {
|
) {
|
||||||
public UnifiedRuntimeProcessingRequest toRuntimeRequest() {
|
public UnifiedRuntimeProcessingRequest toRuntimeRequest() {
|
||||||
return new UnifiedRuntimeProcessingRequest(
|
return new UnifiedRuntimeProcessingRequest(
|
||||||
sessionId,
|
sessionId,
|
||||||
sessionIds,
|
|
||||||
compositeSessionId,
|
|
||||||
tenantKey,
|
tenantKey,
|
||||||
sourceFamilies,
|
sourceFamilies,
|
||||||
eventBackend,
|
eventBackend,
|
||||||
driverKey,
|
driverKey,
|
||||||
driverKeys,
|
|
||||||
includeAllDrivers != null && includeAllDrivers,
|
|
||||||
vehicleKeys,
|
|
||||||
includeAllVehicles != null && includeAllVehicles,
|
|
||||||
driverSourceEntityId,
|
driverSourceEntityId,
|
||||||
driverCardNation,
|
driverCardNation,
|
||||||
driverCardNumber,
|
driverCardNumber,
|
||||||
|
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
package at.procon.eventhub.processing.dto;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingPartitionResultDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record UnifiedRuntimeTachographEsperScopeResultDto(
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
int inputEventCount,
|
|
||||||
int selectedDriverCount,
|
|
||||||
int discoveredVehicleCount,
|
|
||||||
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
|
||||||
Map<String, UnifiedRuntimeDerivedProjectionResultDto> driverResults,
|
|
||||||
Map<String, RuntimeDriverPartitionDebugDto> partitionDebugByDriver,
|
|
||||||
List<String> notes,
|
|
||||||
List<String> warnings
|
|
||||||
) {
|
|
||||||
public UnifiedRuntimeTachographEsperScopeResultDto {
|
|
||||||
discoveredVehicles = discoveredVehicles == null ? List.of() : List.copyOf(discoveredVehicles);
|
|
||||||
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);
|
|
||||||
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UnifiedRuntimeTachographEsperScopeResultDto fromDriverWorkingTime(
|
|
||||||
UnifiedRuntimeDriverWorkingTimeScopeResultDto result
|
|
||||||
) {
|
|
||||||
if (result == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new UnifiedRuntimeTachographEsperScopeResultDto(
|
|
||||||
result.request(),
|
|
||||||
result.inputEventCount(),
|
|
||||||
result.selectedDriverCount(),
|
|
||||||
result.discoveredVehicleCount(),
|
|
||||||
result.discoveredVehicles(),
|
|
||||||
result.driverResults(),
|
|
||||||
result.partitionDebugByDriver(),
|
|
||||||
result.notes(),
|
|
||||||
result.warnings()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UnifiedRuntimeTachographEsperScopeResultDto fromGenericRuntimeEventProcessingResult(
|
|
||||||
RuntimeEventProcessingResultDto genericResult
|
|
||||||
) {
|
|
||||||
if (genericResult == null) {
|
|
||||||
throw new IllegalArgumentException("genericResult must not be null");
|
|
||||||
}
|
|
||||||
LinkedHashMap<String, UnifiedRuntimeDerivedProjectionResultDto> driverResults = new LinkedHashMap<>();
|
|
||||||
for (Map.Entry<String, RuntimeEventProcessingPartitionResultDto> entry : genericResult.partitionResults().entrySet()) {
|
|
||||||
Object value = entry.getValue().result();
|
|
||||||
if (value instanceof UnifiedRuntimeDerivedProjectionResultDto projectionResult) {
|
|
||||||
driverResults.put(entry.getKey(), projectionResult);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Cannot convert generic partition result for key "
|
|
||||||
+ entry.getKey() + " to UnifiedRuntimeDerivedProjectionResultDto: "
|
|
||||||
+ (value == null ? "null" : value.getClass().getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new UnifiedRuntimeTachographEsperScopeResultDto(
|
|
||||||
genericResult.request(),
|
|
||||||
genericResult.inputEventCount(),
|
|
||||||
genericResult.selectedPartitionCount(),
|
|
||||||
genericResult.discoveredVehicleCount(),
|
|
||||||
genericResult.discoveredVehicles(),
|
|
||||||
driverResults,
|
|
||||||
extractPartitionDebug(genericResult),
|
|
||||||
genericResult.notes(),
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingProfileDescriptorDto;
|
|
||||||
import java.util.List;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.profile.RuntimeEventProcessingProfileRegistry;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class RuntimeEventProcessingService {
|
|
||||||
|
|
||||||
private final RuntimeEventProcessingProfileRegistry profileRegistry;
|
|
||||||
|
|
||||||
public RuntimeEventProcessingService(RuntimeEventProcessingProfileRegistry profileRegistry) {
|
|
||||||
this.profileRegistry = profileRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeEventProcessingResultDto process(RuntimeEventProcessingApiRequest request) {
|
|
||||||
return profileRegistry.require(request.profileKey()).process(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<RuntimeEventProcessingProfileDescriptorDto> listProfiles() {
|
|
||||||
return profileRegistry.profileDescriptors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.dto;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public record RuntimeEventPartitioningApiRequest(
|
|
||||||
RuntimeEventPartitioningStrategy strategy,
|
|
||||||
Set<String> partitionKeys,
|
|
||||||
Boolean includeAllPartitions,
|
|
||||||
Set<String> driverKeys,
|
|
||||||
Boolean includeAllDrivers,
|
|
||||||
Set<String> vehicleKeys,
|
|
||||||
Boolean includeAllVehicles,
|
|
||||||
Boolean attachVehicleEvidence,
|
|
||||||
Integer vehicleEvidencePaddingMinutes,
|
|
||||||
Boolean includeDebug
|
|
||||||
) {
|
|
||||||
public RuntimeEventPartitioningApiRequest {
|
|
||||||
strategy = strategy == null ? RuntimeEventPartitioningStrategy.CUSTOM_PROFILE : strategy;
|
|
||||||
partitionKeys = partitionKeys == null ? Set.of() : Set.copyOf(partitionKeys);
|
|
||||||
driverKeys = driverKeys == null ? Set.of() : Set.copyOf(driverKeys);
|
|
||||||
vehicleKeys = vehicleKeys == null ? Set.of() : Set.copyOf(vehicleKeys);
|
|
||||||
if (vehicleEvidencePaddingMinutes != null && vehicleEvidencePaddingMinutes < 0) {
|
|
||||||
throw new IllegalArgumentException("vehicleEvidencePaddingMinutes must not be negative");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean allPartitions() {
|
|
||||||
return includeAllPartitions != null && includeAllPartitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean allDrivers() {
|
|
||||||
return includeAllDrivers != null && includeAllDrivers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean allVehicles() {
|
|
||||||
return includeAllVehicles != null && includeAllVehicles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean attachVehicleEvidenceOrDefault() {
|
|
||||||
return attachVehicleEvidence == null || attachVehicleEvidence;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int vehicleEvidencePaddingMinutesOrDefault(int fallback) {
|
|
||||||
return vehicleEvidencePaddingMinutes == null ? Math.max(0, fallback) : Math.max(0, vehicleEvidencePaddingMinutes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean includeDebugOrDefault() {
|
|
||||||
return includeDebug != null && includeDebug;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.dto;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record RuntimeEventProcessingApiRequest(
|
|
||||||
String profileKey,
|
|
||||||
UnifiedRuntimeProcessingApiRequest scope,
|
|
||||||
RuntimeEventPartitioningApiRequest partitioning,
|
|
||||||
Map<String, Object> parameters
|
|
||||||
) {
|
|
||||||
public RuntimeEventProcessingApiRequest {
|
|
||||||
if (profileKey == null || profileKey.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("profileKey must not be blank");
|
|
||||||
}
|
|
||||||
profileKey = profileKey.trim();
|
|
||||||
if (scope == null) {
|
|
||||||
throw new IllegalArgumentException("scope must not be null");
|
|
||||||
}
|
|
||||||
partitioning = partitioning == null
|
|
||||||
? new RuntimeEventPartitioningApiRequest(null, null, null, null, null, null, null, null, null, null)
|
|
||||||
: partitioning;
|
|
||||||
parameters = parameters == null
|
|
||||||
? Map.of()
|
|
||||||
: Collections.unmodifiableMap(new LinkedHashMap<>(parameters));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RuntimeEventProcessingApiRequest tachographDriverEsper(UnifiedRuntimeProcessingApiRequest scope) {
|
|
||||||
return new RuntimeEventProcessingApiRequest(
|
|
||||||
"tachograph-driver-esper-v1",
|
|
||||||
scope,
|
|
||||||
new RuntimeEventPartitioningApiRequest(
|
|
||||||
at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy.DRIVER,
|
|
||||||
null,
|
|
||||||
scope != null ? scope.includeAllDrivers() : null,
|
|
||||||
scope != null ? scope.driverKeys() : null,
|
|
||||||
scope != null ? scope.includeAllDrivers() : null,
|
|
||||||
scope != null ? scope.vehicleKeys() : null,
|
|
||||||
scope != null ? scope.includeAllVehicles() : null,
|
|
||||||
null,
|
|
||||||
scope != null ? scope.vehicleExpansionPaddingMinutes() : null,
|
|
||||||
null
|
|
||||||
),
|
|
||||||
Map.of()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.dto;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleResult;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record RuntimeEventProcessingPartitionResultDto(
|
|
||||||
String partitionType,
|
|
||||||
String partitionKey,
|
|
||||||
String resultType,
|
|
||||||
Object result,
|
|
||||||
Map<String, Object> metadata,
|
|
||||||
Map<String, RuntimeProcessingModuleResult> moduleResults
|
|
||||||
) {
|
|
||||||
public RuntimeEventProcessingPartitionResultDto {
|
|
||||||
metadata = metadata == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(metadata));
|
|
||||||
moduleResults = moduleResults == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(moduleResults));
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeEventProcessingPartitionResultDto(
|
|
||||||
String partitionType,
|
|
||||||
String partitionKey,
|
|
||||||
String resultType,
|
|
||||||
Object result,
|
|
||||||
Map<String, Object> metadata
|
|
||||||
) {
|
|
||||||
this(partitionType, partitionKey, resultType, result, metadata, Map.of());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.dto;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.profile.RuntimeEventProcessingProfile;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public record RuntimeEventProcessingProfileDescriptorDto(
|
|
||||||
String profileKey,
|
|
||||||
String displayName,
|
|
||||||
String description,
|
|
||||||
RuntimeEventPartitioningStrategy defaultPartitioningStrategy,
|
|
||||||
List<RuntimeEventPartitioningStrategy> supportedPartitioningStrategies,
|
|
||||||
Set<String> requiredParameters,
|
|
||||||
Set<String> optionalParameters
|
|
||||||
) {
|
|
||||||
public RuntimeEventProcessingProfileDescriptorDto {
|
|
||||||
supportedPartitioningStrategies = supportedPartitioningStrategies == null
|
|
||||||
? List.of()
|
|
||||||
: List.copyOf(supportedPartitioningStrategies);
|
|
||||||
requiredParameters = requiredParameters == null ? Set.of() : Set.copyOf(requiredParameters);
|
|
||||||
optionalParameters = optionalParameters == null ? Set.of() : Set.copyOf(optionalParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RuntimeEventProcessingProfileDescriptorDto from(RuntimeEventProcessingProfile profile) {
|
|
||||||
if (profile == null) {
|
|
||||||
throw new IllegalArgumentException("profile must not be null");
|
|
||||||
}
|
|
||||||
return new RuntimeEventProcessingProfileDescriptorDto(
|
|
||||||
profile.profileKey(),
|
|
||||||
profile.displayName(),
|
|
||||||
profile.description(),
|
|
||||||
profile.defaultPartitioningStrategy(),
|
|
||||||
profile.supportedPartitioningStrategies(),
|
|
||||||
profile.requiredParameters(),
|
|
||||||
profile.optionalParameters()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.dto;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingExecutionResultDto;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record RuntimeEventProcessingResultDto(
|
|
||||||
String profileKey,
|
|
||||||
RuntimeEventPartitioningStrategy partitioningStrategy,
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
int inputEventCount,
|
|
||||||
int selectedPartitionCount,
|
|
||||||
int discoveredVehicleCount,
|
|
||||||
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
|
||||||
Map<String, RuntimeEventProcessingPartitionResultDto> partitionResults,
|
|
||||||
List<String> notes,
|
|
||||||
List<String> warnings
|
|
||||||
) {
|
|
||||||
public RuntimeEventProcessingResultDto {
|
|
||||||
discoveredVehicles = discoveredVehicles == null ? List.of() : List.copyOf(discoveredVehicles);
|
|
||||||
partitionResults = partitionResults == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(partitionResults));
|
|
||||||
notes = notes == null ? List.of() : List.copyOf(notes);
|
|
||||||
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RuntimeEventProcessingResultDto fromExecution(
|
|
||||||
RuntimeProcessingExecutionResultDto result,
|
|
||||||
String legacyProfileKey
|
|
||||||
) {
|
|
||||||
if (result == null) {
|
|
||||||
throw new IllegalArgumentException("result must not be null");
|
|
||||||
}
|
|
||||||
return new RuntimeEventProcessingResultDto(
|
|
||||||
legacyProfileKey == null || legacyProfileKey.isBlank() ? result.processingPlanKey() : legacyProfileKey,
|
|
||||||
result.partitioningStrategy(),
|
|
||||||
result.request(),
|
|
||||||
result.inputEventCount(),
|
|
||||||
result.selectedPartitionCount(),
|
|
||||||
result.discoveredVehicleCount(),
|
|
||||||
result.discoveredVehicles(),
|
|
||||||
result.partitionResults(),
|
|
||||||
result.notes(),
|
|
||||||
result.warnings()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingModuleDescriptorDto;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
abstract class AbstractDriverWorkingTimePhaseModule implements RuntimeProcessingModule {
|
|
||||||
|
|
||||||
private final String moduleKey;
|
|
||||||
private final String displayName;
|
|
||||||
private final String description;
|
|
||||||
private final String engine;
|
|
||||||
private final Set<String> outputTypes;
|
|
||||||
|
|
||||||
protected AbstractDriverWorkingTimePhaseModule(
|
|
||||||
String moduleKey,
|
|
||||||
String displayName,
|
|
||||||
String description,
|
|
||||||
String engine,
|
|
||||||
Set<String> outputTypes
|
|
||||||
) {
|
|
||||||
this.moduleKey = moduleKey;
|
|
||||||
this.displayName = displayName;
|
|
||||||
this.description = description;
|
|
||||||
this.engine = engine;
|
|
||||||
this.outputTypes = outputTypes == null ? Set.of() : Set.copyOf(outputTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String moduleKey() {
|
|
||||||
return moduleKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeProcessingModuleDescriptorDto descriptor() {
|
|
||||||
return new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
moduleKey,
|
|
||||||
displayName,
|
|
||||||
description,
|
|
||||||
engine,
|
|
||||||
outputTypes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeProcessingModuleResult execute(RuntimeProcessingModuleContext context) {
|
|
||||||
Map<String, Object> metadata = new LinkedHashMap<>();
|
|
||||||
metadata.put("executionModel", "delegated");
|
|
||||||
metadata.put("delegatedTo", DriverWorkingTimeModuleKeys.DRIVING_DERIVED_PROJECTIONS);
|
|
||||||
metadata.put("note", "This logical module is currently executed inside the driver-working-time derived projections adapter. It is registered separately so it can be split into a standalone EPL/Java module without changing the processing plan contract.");
|
|
||||||
return new RuntimeProcessingModuleResult(
|
|
||||||
moduleKey,
|
|
||||||
RuntimeProcessingModuleStatus.SUCCESS,
|
|
||||||
null,
|
|
||||||
metadata,
|
|
||||||
java.util.List.of()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.DriverWorkingTimeEplEventMapper;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.RuntimeEplInputEventStream;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.RuntimeEplModule;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.RuntimeEplModuleDefinition;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.RuntimeEplModuleExecutionResult;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.RuntimeEplModuleExecutor;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingModuleDescriptorDto;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class DriverActivityIntervalsModule implements RuntimeEplModule {
|
|
||||||
|
|
||||||
public static final String OUTPUT_STATEMENT = "driverActivityIntervals";
|
|
||||||
private static final String EPL_RESOURCE = "esper/runtime-driver-activity-intervals.epl";
|
|
||||||
|
|
||||||
private final RuntimeEplModuleExecutor eplModuleExecutor;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public DriverActivityIntervalsModule(RuntimeEplModuleExecutor eplModuleExecutor) {
|
|
||||||
this.eplModuleExecutor = eplModuleExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Compatibility constructor for legacy tests that instantiate a local module registry. */
|
|
||||||
public DriverActivityIntervalsModule() {
|
|
||||||
this(new RuntimeEplModuleExecutor());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String moduleKey() {
|
|
||||||
return DriverWorkingTimeModuleKeys.EVENT_TO_ACTIVITY_INTERVALS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeProcessingModuleDescriptorDto descriptor() {
|
|
||||||
return new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
moduleKey(),
|
|
||||||
"Event to activity intervals",
|
|
||||||
"EPL module that pairs canonical DRIVER_ACTIVITY START/END point events into source-neutral driver activity intervals.",
|
|
||||||
engine(),
|
|
||||||
Set.of("DriverActivityIntervalEvent")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeProcessingModuleResult execute(RuntimeProcessingModuleContext context) {
|
|
||||||
List<EventHubEventDto> sourceEvents = DriverWorkingTimeEplEventMapper.sourceEvents(context);
|
|
||||||
List<Map<String, Object>> pointEvents = DriverWorkingTimeEplEventMapper.activityPointEvents(sourceEvents);
|
|
||||||
RuntimeEplModuleExecutionResult eplResult = eplModuleExecutor.execute(new RuntimeEplModuleDefinition(
|
|
||||||
moduleKey(),
|
|
||||||
Map.of(
|
|
||||||
DriverWorkingTimeEplEventMapper.DRIVER_ACTIVITY_POINT_EVENT,
|
|
||||||
DriverWorkingTimeEplEventMapper.activityPointEventDefinition()
|
|
||||||
),
|
|
||||||
List.of(EPL_RESOURCE),
|
|
||||||
List.of(OUTPUT_STATEMENT),
|
|
||||||
List.of(new RuntimeEplInputEventStream(
|
|
||||||
DriverWorkingTimeEplEventMapper.DRIVER_ACTIVITY_POINT_EVENT,
|
|
||||||
pointEvents
|
|
||||||
))
|
|
||||||
));
|
|
||||||
List<Map<String, Object>> intervals = eplResult.output(OUTPUT_STATEMENT);
|
|
||||||
Map<String, Object> metadata = new LinkedHashMap<>(eplResult.metadata());
|
|
||||||
metadata.put("inputEventCount", sourceEvents.size());
|
|
||||||
metadata.put("activityPointEventCount", pointEvents.size());
|
|
||||||
metadata.put("activityIntervalCount", intervals.size());
|
|
||||||
return new RuntimeProcessingModuleResult(
|
|
||||||
moduleKey(),
|
|
||||||
RuntimeProcessingModuleStatus.SUCCESS,
|
|
||||||
intervals,
|
|
||||||
metadata,
|
|
||||||
List.of()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.DriverWorkingTimeEplEventMapper;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.RuntimeEplInputEventStream;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.RuntimeEplModule;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.RuntimeEplModuleDefinition;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.RuntimeEplModuleExecutionResult;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.epl.RuntimeEplModuleExecutor;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingModuleDescriptorDto;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class DriverVehicleUsageIntervalsModule implements RuntimeEplModule {
|
|
||||||
|
|
||||||
public static final String OUTPUT_STATEMENT = "driverVehicleUsageIntervals";
|
|
||||||
private static final String EPL_RESOURCE = "esper/runtime-driver-vehicle-usage-intervals.epl";
|
|
||||||
|
|
||||||
private final RuntimeEplModuleExecutor eplModuleExecutor;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public DriverVehicleUsageIntervalsModule(RuntimeEplModuleExecutor eplModuleExecutor) {
|
|
||||||
this.eplModuleExecutor = eplModuleExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Compatibility constructor for legacy tests that instantiate a local module registry. */
|
|
||||||
public DriverVehicleUsageIntervalsModule() {
|
|
||||||
this(new RuntimeEplModuleExecutor());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String moduleKey() {
|
|
||||||
return DriverWorkingTimeModuleKeys.EVENT_TO_VEHICLE_USAGE_INTERVALS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeProcessingModuleDescriptorDto descriptor() {
|
|
||||||
return new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
moduleKey(),
|
|
||||||
"Event to vehicle usage intervals",
|
|
||||||
"EPL module that pairs canonical driver/card INSERT/WITHDRAW point events into source-neutral driver vehicle-usage intervals.",
|
|
||||||
engine(),
|
|
||||||
Set.of("DriverVehicleUsageIntervalEvent")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeProcessingModuleResult execute(RuntimeProcessingModuleContext context) {
|
|
||||||
List<EventHubEventDto> sourceEvents = DriverWorkingTimeEplEventMapper.sourceEvents(context);
|
|
||||||
List<Map<String, Object>> pointEvents = DriverWorkingTimeEplEventMapper.vehicleUsagePointEvents(sourceEvents);
|
|
||||||
RuntimeEplModuleExecutionResult eplResult = eplModuleExecutor.execute(new RuntimeEplModuleDefinition(
|
|
||||||
moduleKey(),
|
|
||||||
Map.of(
|
|
||||||
DriverWorkingTimeEplEventMapper.DRIVER_VEHICLE_USAGE_POINT_EVENT,
|
|
||||||
DriverWorkingTimeEplEventMapper.vehicleUsagePointEventDefinition()
|
|
||||||
),
|
|
||||||
List.of(EPL_RESOURCE),
|
|
||||||
List.of(OUTPUT_STATEMENT),
|
|
||||||
List.of(new RuntimeEplInputEventStream(
|
|
||||||
DriverWorkingTimeEplEventMapper.DRIVER_VEHICLE_USAGE_POINT_EVENT,
|
|
||||||
pointEvents
|
|
||||||
))
|
|
||||||
));
|
|
||||||
List<Map<String, Object>> intervals = eplResult.output(OUTPUT_STATEMENT);
|
|
||||||
Map<String, Object> metadata = new LinkedHashMap<>(eplResult.metadata());
|
|
||||||
metadata.put("inputEventCount", sourceEvents.size());
|
|
||||||
metadata.put("vehicleUsagePointEventCount", pointEvents.size());
|
|
||||||
metadata.put("vehicleUsageIntervalCount", intervals.size());
|
|
||||||
return new RuntimeProcessingModuleResult(
|
|
||||||
moduleKey(),
|
|
||||||
RuntimeProcessingModuleStatus.SUCCESS,
|
|
||||||
intervals,
|
|
||||||
metadata,
|
|
||||||
List.of()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class DriverVehicleUsageMergeModule extends AbstractDriverWorkingTimePhaseModule {
|
|
||||||
|
|
||||||
public DriverVehicleUsageMergeModule() {
|
|
||||||
super(
|
|
||||||
DriverWorkingTimeModuleKeys.VEHICLE_USAGE_MERGE,
|
|
||||||
"Vehicle usage merge",
|
|
||||||
"Merges adjacent or continuous same-driver/same-vehicle usage intervals, including 23:59:59 to 00:00:00 continuations.",
|
|
||||||
"JAVA/ESPER",
|
|
||||||
Set.of("DriverVehicleUsageIntervalInputEvent")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeDriverWorkingTimeScopeResultDto;
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingModuleDescriptorDto;
|
|
||||||
import at.procon.eventhub.processing.service.RuntimeDriverWorkingTimeScopeProcessingService;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class DriverWorkingTimeDerivedProjectionsModule implements RuntimeProcessingModule {
|
|
||||||
|
|
||||||
private final RuntimeDriverWorkingTimeScopeProcessingService scopeProcessingService;
|
|
||||||
|
|
||||||
public DriverWorkingTimeDerivedProjectionsModule(
|
|
||||||
RuntimeDriverWorkingTimeScopeProcessingService scopeProcessingService
|
|
||||||
) {
|
|
||||||
this.scopeProcessingService = scopeProcessingService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String moduleKey() {
|
|
||||||
return DriverWorkingTimeModuleKeys.DRIVING_DERIVED_PROJECTIONS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeProcessingModuleDescriptorDto descriptor() {
|
|
||||||
return new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
moduleKey(),
|
|
||||||
"Driving-derived projections",
|
|
||||||
"Executes the shared driver working-time pipeline for driving interruptions, rest candidates, card-absence coverage, overnight candidates, and trip candidates.",
|
|
||||||
"ESPER+JAVA",
|
|
||||||
Set.of("DriverWorkingTimeProcessingResultDto")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeProcessingModuleResult execute(RuntimeProcessingModuleContext context) {
|
|
||||||
UnifiedRuntimeProcessingApiRequest scopeRequest = scopeRequest(context);
|
|
||||||
boolean includePartitionDebug = booleanAttribute(context, "includePartitionDebug", false);
|
|
||||||
UnifiedRuntimeDriverWorkingTimeScopeResultDto result = scopeProcessingService.processScope(
|
|
||||||
scopeRequest,
|
|
||||||
includePartitionDebug
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, Object> metadata = new LinkedHashMap<>();
|
|
||||||
metadata.put("inputEventCount", result.inputEventCount());
|
|
||||||
metadata.put("selectedDriverCount", result.selectedDriverCount());
|
|
||||||
metadata.put("discoveredVehicleCount", result.discoveredVehicleCount());
|
|
||||||
metadata.put("driverResultCount", result.driverResults().size());
|
|
||||||
return new RuntimeProcessingModuleResult(
|
|
||||||
moduleKey(),
|
|
||||||
RuntimeProcessingModuleStatus.SUCCESS,
|
|
||||||
result,
|
|
||||||
metadata,
|
|
||||||
result.warnings()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnifiedRuntimeProcessingApiRequest scopeRequest(RuntimeProcessingModuleContext context) {
|
|
||||||
Object value = context.attributes().get("runtimeScopeApiRequest");
|
|
||||||
if (value instanceof UnifiedRuntimeProcessingApiRequest request) {
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
return context.request().sourceSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean booleanAttribute(RuntimeProcessingModuleContext context, String key, boolean fallback) {
|
|
||||||
Object value = context.attributes().get(key);
|
|
||||||
if (value instanceof Boolean booleanValue) {
|
|
||||||
return booleanValue;
|
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
public final class DriverWorkingTimeModuleKeys {
|
|
||||||
|
|
||||||
public static final String RUNTIME_EVENT_ASSEMBLY = "runtime-event-assembly";
|
|
||||||
public static final String EVENT_TO_ACTIVITY_INTERVALS = "event-to-activity-intervals";
|
|
||||||
public static final String EVENT_TO_VEHICLE_USAGE_INTERVALS = "event-to-vehicle-usage-intervals";
|
|
||||||
public static final String VEHICLE_USAGE_MERGE = "vehicle-usage-merge";
|
|
||||||
public static final String VEHICLE_EVIDENCE_ATTACHMENT = "vehicle-evidence-attachment";
|
|
||||||
public static final String SUPPORT_EVIDENCE_NORMALIZATION = "support-evidence-normalization";
|
|
||||||
public static final String DRIVING_DERIVED_PROJECTIONS = "driving-derived-projections";
|
|
||||||
|
|
||||||
private DriverWorkingTimeModuleKeys() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingModuleDescriptorDto;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
|
|
||||||
import at.procon.eventhub.processing.service.UnifiedRuntimeEventAssemblyService;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class RuntimeEventAssemblyModule implements RuntimeProcessingModule {
|
|
||||||
|
|
||||||
private final UnifiedRuntimeEventAssemblyService eventAssemblyService;
|
|
||||||
|
|
||||||
public RuntimeEventAssemblyModule(UnifiedRuntimeEventAssemblyService eventAssemblyService) {
|
|
||||||
this.eventAssemblyService = eventAssemblyService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String moduleKey() {
|
|
||||||
return DriverWorkingTimeModuleKeys.RUNTIME_EVENT_ASSEMBLY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeProcessingModuleDescriptorDto descriptor() {
|
|
||||||
return new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
moduleKey(),
|
|
||||||
"Runtime event assembly",
|
|
||||||
"Loads and merges canonical runtime events from the selected source scope before plan-specific processing.",
|
|
||||||
"JAVA",
|
|
||||||
Set.of("UnifiedRuntimeEventBundle")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeProcessingModuleResult execute(RuntimeProcessingModuleContext context) {
|
|
||||||
UnifiedRuntimeProcessingApiRequest scopeRequest = scopeRequest(context);
|
|
||||||
UnifiedRuntimeEventBundle bundle = eventAssemblyService.assembleDriverScopedEvents(scopeRequest.toRuntimeRequest());
|
|
||||||
Map<String, Object> metadata = new LinkedHashMap<>();
|
|
||||||
metadata.put("driverSeedEventCount", bundle.driverSeedEvents().size());
|
|
||||||
metadata.put("expandedVehicleEventCount", bundle.expandedVehicleEvents().size());
|
|
||||||
metadata.put("mergedEventCount", bundle.mergedEvents().size());
|
|
||||||
metadata.put("discoveredVehicleCount", bundle.discoveredVehicles().size());
|
|
||||||
return new RuntimeProcessingModuleResult(
|
|
||||||
moduleKey(),
|
|
||||||
RuntimeProcessingModuleStatus.SUCCESS,
|
|
||||||
bundle,
|
|
||||||
metadata,
|
|
||||||
bundle.notes()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnifiedRuntimeProcessingApiRequest scopeRequest(RuntimeProcessingModuleContext context) {
|
|
||||||
Object value = context.attributes().get("runtimeScopeApiRequest");
|
|
||||||
if (value instanceof UnifiedRuntimeProcessingApiRequest request) {
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
return context.request().sourceSelection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingModuleDescriptorDto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Source-neutral runtime processing module.
|
|
||||||
*
|
|
||||||
* <p>Modules are the executable building blocks used by processing plans. A module can be
|
|
||||||
* implemented in EPL, Java, or a combination of both. The first predefined driver-working-time
|
|
||||||
* plan still uses an adapter over the existing processing services, but its descriptor now
|
|
||||||
* exposes the logical module sequence so the plan can be split into concrete modules incrementally.</p>
|
|
||||||
*/
|
|
||||||
public interface RuntimeProcessingModule {
|
|
||||||
|
|
||||||
String moduleKey();
|
|
||||||
|
|
||||||
RuntimeProcessingModuleDescriptorDto descriptor();
|
|
||||||
|
|
||||||
RuntimeProcessingModuleResult execute(RuntimeProcessingModuleContext context);
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingExecutionApiRequest;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record RuntimeProcessingModuleContext(
|
|
||||||
RuntimeProcessingExecutionApiRequest request,
|
|
||||||
List<EventHubEventDto> events,
|
|
||||||
Map<String, Object> attributes,
|
|
||||||
Map<String, RuntimeProcessingModuleResult> previousResults
|
|
||||||
) {
|
|
||||||
public RuntimeProcessingModuleContext {
|
|
||||||
events = events == null ? List.of() : List.copyOf(events);
|
|
||||||
attributes = attributes == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
|
|
||||||
previousResults = previousResults == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(previousResults));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingModuleDescriptorDto;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class RuntimeProcessingModuleRegistry {
|
|
||||||
|
|
||||||
private final Map<String, RuntimeProcessingModule> modulesByKey;
|
|
||||||
|
|
||||||
public RuntimeProcessingModuleRegistry(List<RuntimeProcessingModule> modules) {
|
|
||||||
LinkedHashMap<String, RuntimeProcessingModule> byKey = new LinkedHashMap<>();
|
|
||||||
if (modules != null) {
|
|
||||||
for (RuntimeProcessingModule module : modules) {
|
|
||||||
if (module == null || module.moduleKey() == null || module.moduleKey().isBlank()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
RuntimeProcessingModule previous = byKey.putIfAbsent(module.moduleKey(), module);
|
|
||||||
if (previous != null) {
|
|
||||||
throw new IllegalStateException("Duplicate runtime processing module key: " + module.moduleKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.modulesByKey = Map.copyOf(byKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<RuntimeProcessingModule> modules() {
|
|
||||||
return modulesByKey.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<RuntimeProcessingModuleDescriptorDto> descriptors() {
|
|
||||||
return modulesByKey.values().stream()
|
|
||||||
.map(RuntimeProcessingModule::descriptor)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeProcessingModule require(String moduleKey) {
|
|
||||||
RuntimeProcessingModule module = modulesByKey.get(moduleKey);
|
|
||||||
if (module == null) {
|
|
||||||
throw new IllegalArgumentException("Unknown runtime processing module: " + moduleKey);
|
|
||||||
}
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record RuntimeProcessingModuleResult(
|
|
||||||
String moduleKey,
|
|
||||||
RuntimeProcessingModuleStatus status,
|
|
||||||
Object output,
|
|
||||||
Map<String, Object> metadata,
|
|
||||||
List<String> warnings
|
|
||||||
) {
|
|
||||||
public RuntimeProcessingModuleResult {
|
|
||||||
metadata = metadata == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(metadata));
|
|
||||||
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
public enum RuntimeProcessingModuleStatus {
|
|
||||||
SUCCESS,
|
|
||||||
SKIPPED,
|
|
||||||
FAILED
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingExecutionApiRequest;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class RuntimeProcessingPipelineExecutor {
|
|
||||||
|
|
||||||
private final RuntimeProcessingModuleRegistry moduleRegistry;
|
|
||||||
|
|
||||||
public RuntimeProcessingPipelineExecutor(RuntimeProcessingModuleRegistry moduleRegistry) {
|
|
||||||
this.moduleRegistry = moduleRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, RuntimeProcessingModuleResult> execute(
|
|
||||||
RuntimeProcessingExecutionApiRequest request,
|
|
||||||
List<String> moduleKeys,
|
|
||||||
RuntimeProcessingModuleContext initialContext
|
|
||||||
) {
|
|
||||||
LinkedHashMap<String, RuntimeProcessingModuleResult> results = new LinkedHashMap<>();
|
|
||||||
RuntimeProcessingModuleContext context = initialContext == null
|
|
||||||
? new RuntimeProcessingModuleContext(request, List.of(), Map.of(), Map.of())
|
|
||||||
: initialContext;
|
|
||||||
for (String moduleKey : moduleKeys == null ? List.<String>of() : moduleKeys) {
|
|
||||||
if (moduleKey == null || moduleKey.isBlank()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
RuntimeProcessingModule module = moduleRegistry.require(moduleKey.trim());
|
|
||||||
RuntimeProcessingModuleResult result = module.execute(context);
|
|
||||||
results.put(module.moduleKey(), result);
|
|
||||||
context = new RuntimeProcessingModuleContext(
|
|
||||||
request,
|
|
||||||
context.events(),
|
|
||||||
context.attributes(),
|
|
||||||
results
|
|
||||||
);
|
|
||||||
if (result.status() == RuntimeProcessingModuleStatus.FAILED) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Map.copyOf(results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class SupportEvidenceNormalizationModule extends AbstractDriverWorkingTimePhaseModule {
|
|
||||||
|
|
||||||
public SupportEvidenceNormalizationModule() {
|
|
||||||
super(
|
|
||||||
DriverWorkingTimeModuleKeys.SUPPORT_EVIDENCE_NORMALIZATION,
|
|
||||||
"Support evidence normalization",
|
|
||||||
"Normalizes mixed-source support evidence for driver working-time processing.",
|
|
||||||
"JAVA",
|
|
||||||
Set.of("RuntimeSupportEvidenceNormalizationDebugDto")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class VehicleEvidenceAttachmentModule extends AbstractDriverWorkingTimePhaseModule {
|
|
||||||
|
|
||||||
public VehicleEvidenceAttachmentModule() {
|
|
||||||
super(
|
|
||||||
DriverWorkingTimeModuleKeys.VEHICLE_EVIDENCE_ATTACHMENT,
|
|
||||||
"Vehicle evidence attachment",
|
|
||||||
"Attaches vehicle-only evidence to driver partitions by overlapping driver vehicle-usage intervals.",
|
|
||||||
"JAVA",
|
|
||||||
Set.of("RuntimeDriverPartitionDebugDto")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,386 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module.epl;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventDomain;
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.dto.EventLifecycle;
|
|
||||||
import at.procon.eventhub.dto.EventType;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.DriverWorkingTimeModuleKeys;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleContext;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleResult;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public final class DriverWorkingTimeEplEventMapper {
|
|
||||||
|
|
||||||
public static final String DRIVER_ACTIVITY_POINT_EVENT = "DriverActivityPointEvent";
|
|
||||||
public static final String DRIVER_ACTIVITY_INTERVAL_EVENT = "DriverActivityIntervalEvent";
|
|
||||||
public static final String DRIVER_VEHICLE_USAGE_POINT_EVENT = "DriverVehicleUsagePointEvent";
|
|
||||||
public static final String DRIVER_VEHICLE_USAGE_INTERVAL_EVENT = "DriverVehicleUsageIntervalEvent";
|
|
||||||
|
|
||||||
private DriverWorkingTimeEplEventMapper() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<EventHubEventDto> sourceEvents(RuntimeProcessingModuleContext context) {
|
|
||||||
RuntimeProcessingModuleResult assemblyResult = context.previousResults().get(DriverWorkingTimeModuleKeys.RUNTIME_EVENT_ASSEMBLY);
|
|
||||||
if (assemblyResult != null && assemblyResult.output() instanceof UnifiedRuntimeEventBundle bundle) {
|
|
||||||
return safeList(bundle.mergedEvents());
|
|
||||||
}
|
|
||||||
return safeList(context.events());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Map<String, Object>> activityPointEvents(List<EventHubEventDto> sourceEvents) {
|
|
||||||
return safeList(sourceEvents).stream()
|
|
||||||
.map(DriverWorkingTimeEplEventMapper::toActivityPointEvent)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.sorted(pointEventComparator())
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Map<String, Object>> vehicleUsagePointEvents(List<EventHubEventDto> sourceEvents) {
|
|
||||||
return safeList(sourceEvents).stream()
|
|
||||||
.map(DriverWorkingTimeEplEventMapper::toVehicleUsagePointEvent)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.sorted(pointEventComparator())
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Object> activityPointEventDefinition() {
|
|
||||||
Map<String, Object> definition = new LinkedHashMap<>();
|
|
||||||
definition.put("sessionId", UUID.class);
|
|
||||||
definition.put("driverKey", String.class);
|
|
||||||
definition.put("eventId", String.class);
|
|
||||||
definition.put("intervalId", String.class);
|
|
||||||
definition.put("sourceRowId", String.class);
|
|
||||||
definition.put("sourceRowIds", java.util.List.class);
|
|
||||||
definition.put("activityType", String.class);
|
|
||||||
definition.put("lifecycle", String.class);
|
|
||||||
definition.put("occurredAt", OffsetDateTime.class);
|
|
||||||
definition.put("occurredAtEpochSecond", long.class);
|
|
||||||
definition.put("cardSlot", String.class);
|
|
||||||
definition.put("cardStatus", String.class);
|
|
||||||
definition.put("drivingStatus", String.class);
|
|
||||||
definition.put("registrationKey", String.class);
|
|
||||||
definition.put("vehicleKey", String.class);
|
|
||||||
definition.put("sourceKind", String.class);
|
|
||||||
definition.put("synthetic", boolean.class);
|
|
||||||
definition.put("clippedToRequestedPeriod", boolean.class);
|
|
||||||
definition.put("level", String.class);
|
|
||||||
return definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Object> activityIntervalEventDefinition() {
|
|
||||||
Map<String, Object> definition = new LinkedHashMap<>();
|
|
||||||
definition.put("sessionId", UUID.class);
|
|
||||||
definition.put("driverKey", String.class);
|
|
||||||
definition.put("intervalId", String.class);
|
|
||||||
definition.put("activityType", String.class);
|
|
||||||
definition.put("cardSlot", String.class);
|
|
||||||
definition.put("cardStatus", String.class);
|
|
||||||
definition.put("drivingStatus", String.class);
|
|
||||||
definition.put("registrationKey", String.class);
|
|
||||||
definition.put("vehicleKey", String.class);
|
|
||||||
definition.put("sourceKind", String.class);
|
|
||||||
definition.put("firstSourceIntervalId", String.class);
|
|
||||||
definition.put("lastSourceIntervalId", String.class);
|
|
||||||
definition.put("startedAt", OffsetDateTime.class);
|
|
||||||
definition.put("endedAt", OffsetDateTime.class);
|
|
||||||
definition.put("startedAtEpochSecond", long.class);
|
|
||||||
definition.put("endedAtEpochSecond", long.class);
|
|
||||||
definition.put("durationSeconds", long.class);
|
|
||||||
definition.put("sourceIntervalIds", java.util.List.class);
|
|
||||||
definition.put("synthetic", boolean.class);
|
|
||||||
definition.put("clippedToRequestedPeriod", boolean.class);
|
|
||||||
definition.put("level", String.class);
|
|
||||||
return definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Object> vehicleUsagePointEventDefinition() {
|
|
||||||
Map<String, Object> definition = new LinkedHashMap<>();
|
|
||||||
definition.put("sessionId", UUID.class);
|
|
||||||
definition.put("driverKey", String.class);
|
|
||||||
definition.put("eventId", String.class);
|
|
||||||
definition.put("intervalId", String.class);
|
|
||||||
definition.put("sourceRowId", String.class);
|
|
||||||
definition.put("sourceRowIds", java.util.List.class);
|
|
||||||
definition.put("lifecycle", String.class);
|
|
||||||
definition.put("occurredAt", OffsetDateTime.class);
|
|
||||||
definition.put("occurredAtEpochSecond", long.class);
|
|
||||||
definition.put("registrationKey", String.class);
|
|
||||||
definition.put("vehicleKey", String.class);
|
|
||||||
definition.put("sourceKind", String.class);
|
|
||||||
definition.put("odometerKm", Long.class);
|
|
||||||
return definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Object> vehicleUsageIntervalEventDefinition() {
|
|
||||||
Map<String, Object> definition = new LinkedHashMap<>();
|
|
||||||
definition.put("sessionId", UUID.class);
|
|
||||||
definition.put("driverKey", String.class);
|
|
||||||
definition.put("intervalId", String.class);
|
|
||||||
definition.put("firstSourceIntervalId", String.class);
|
|
||||||
definition.put("lastSourceIntervalId", String.class);
|
|
||||||
definition.put("startedAt", OffsetDateTime.class);
|
|
||||||
definition.put("endedAt", OffsetDateTime.class);
|
|
||||||
definition.put("startedAtEpochSecond", long.class);
|
|
||||||
definition.put("endedAtEpochSecond", Long.class);
|
|
||||||
definition.put("durationSeconds", long.class);
|
|
||||||
definition.put("odometerBeginKm", Long.class);
|
|
||||||
definition.put("odometerEndKm", Long.class);
|
|
||||||
definition.put("registrationKey", String.class);
|
|
||||||
definition.put("vehicleKey", String.class);
|
|
||||||
definition.put("sourceKind", String.class);
|
|
||||||
definition.put("sourceIntervalIds", java.util.List.class);
|
|
||||||
return definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<String, Object> toActivityPointEvent(EventHubEventDto sourceEvent) {
|
|
||||||
if (sourceEvent == null
|
|
||||||
|| sourceEvent.eventDomain() != EventDomain.DRIVER_ACTIVITY
|
|
||||||
|| (sourceEvent.lifecycle() != EventLifecycle.START && sourceEvent.lifecycle() != EventLifecycle.END)
|
|
||||||
|| sourceEvent.occurredAt() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = rawPayload(sourceEvent);
|
|
||||||
JsonNode attributes = attributes(sourceEvent);
|
|
||||||
String intervalId = firstNonBlank(text(raw, "intervalId"), text(raw, "sourceRowId"), sourceEvent.externalSourceEventId());
|
|
||||||
String driverKey = firstNonBlank(text(raw, "driverKey"), driverKey(sourceEvent));
|
|
||||||
if (driverKey == null || intervalId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Map<String, Object> event = new LinkedHashMap<>();
|
|
||||||
event.put("sessionId", sessionId(raw, sourceEvent));
|
|
||||||
event.put("driverKey", driverKey);
|
|
||||||
event.put("eventId", sourceEvent.externalSourceEventId());
|
|
||||||
event.put("intervalId", intervalId);
|
|
||||||
event.put("sourceRowId", firstNonBlank(text(raw, "sourceRowId"), intervalId));
|
|
||||||
event.put("sourceRowIds", stringList(raw, "sourceRowIds", intervalId));
|
|
||||||
event.put("activityType", firstNonBlank(text(raw, "activityType"), eventTypeAsActivity(sourceEvent.eventType())));
|
|
||||||
event.put("lifecycle", sourceEvent.lifecycle().name());
|
|
||||||
event.put("occurredAt", sourceEvent.occurredAt());
|
|
||||||
event.put("occurredAtEpochSecond", sourceEvent.occurredAt().toEpochSecond());
|
|
||||||
event.put("cardSlot", firstNonBlank(text(raw, "cardSlot"), text(attributes, "cardSlot")));
|
|
||||||
event.put("cardStatus", firstNonBlank(text(raw, "cardStatus"), text(attributes, "cardStatus")));
|
|
||||||
event.put("drivingStatus", firstNonBlank(text(raw, "drivingStatus"), text(attributes, "drivingStatus")));
|
|
||||||
event.put("registrationKey", firstNonBlank(text(raw, "registrationKey"), registrationKey(sourceEvent)));
|
|
||||||
event.put("vehicleKey", firstNonBlank(text(raw, "vehicleKey"), vehicleKey(sourceEvent)));
|
|
||||||
event.put("sourceKind", firstNonBlank(text(raw, "sourceKind"), sourceKind(sourceEvent)));
|
|
||||||
event.put("synthetic", booleanValue(raw, "synthetic", false));
|
|
||||||
event.put("clippedToRequestedPeriod", booleanValue(raw, "clippedToRequestedPeriod", false));
|
|
||||||
event.put("level", firstNonBlank(text(raw, "level"), "RAW_EVENT"));
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<String, Object> toVehicleUsagePointEvent(EventHubEventDto sourceEvent) {
|
|
||||||
if (sourceEvent == null
|
|
||||||
|| sourceEvent.eventDomain() != EventDomain.DRIVER_CARD
|
|
||||||
|| (sourceEvent.lifecycle() != EventLifecycle.INSERT && sourceEvent.lifecycle() != EventLifecycle.WITHDRAW)
|
|
||||||
|| sourceEvent.occurredAt() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
boolean supportedType = sourceEvent.eventType() == EventType.CARD_INSERTED
|
|
||||||
|| sourceEvent.eventType() == EventType.CARD_WITHDRAWN;
|
|
||||||
if (!supportedType) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = rawPayload(sourceEvent);
|
|
||||||
String intervalId = firstNonBlank(text(raw, "intervalId"), text(raw, "sourceRowId"), sourceEvent.externalSourceEventId());
|
|
||||||
String driverKey = firstNonBlank(text(raw, "driverKey"), driverKey(sourceEvent));
|
|
||||||
if (driverKey == null || intervalId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Map<String, Object> event = new LinkedHashMap<>();
|
|
||||||
event.put("sessionId", sessionId(raw, sourceEvent));
|
|
||||||
event.put("driverKey", driverKey);
|
|
||||||
event.put("eventId", sourceEvent.externalSourceEventId());
|
|
||||||
event.put("intervalId", intervalId);
|
|
||||||
event.put("sourceRowId", firstNonBlank(text(raw, "sourceRowId"), intervalId));
|
|
||||||
event.put("sourceRowIds", stringList(raw, "sourceRowIds", intervalId));
|
|
||||||
event.put("lifecycle", sourceEvent.lifecycle().name());
|
|
||||||
event.put("occurredAt", sourceEvent.occurredAt());
|
|
||||||
event.put("occurredAtEpochSecond", sourceEvent.occurredAt().toEpochSecond());
|
|
||||||
event.put("registrationKey", firstNonBlank(text(raw, "registrationKey"), registrationKey(sourceEvent)));
|
|
||||||
event.put("vehicleKey", firstNonBlank(text(raw, "vehicleKey"), vehicleKey(sourceEvent)));
|
|
||||||
event.put("sourceKind", firstNonBlank(text(raw, "sourceKind"), sourceKind(sourceEvent)));
|
|
||||||
event.put("odometerKm", odometerKm(sourceEvent, raw));
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Comparator<Map<String, Object>> pointEventComparator() {
|
|
||||||
return Comparator
|
|
||||||
.comparing((Map<String, Object> event) -> (Long) event.get("occurredAtEpochSecond"))
|
|
||||||
.thenComparing(event -> lifecycleOrder(Objects.toString(event.get("lifecycle"), "")))
|
|
||||||
.thenComparing(event -> Objects.toString(event.get("driverKey"), ""))
|
|
||||||
.thenComparing(event -> Objects.toString(event.get("intervalId"), ""))
|
|
||||||
.thenComparing(event -> Objects.toString(event.get("eventId"), ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int lifecycleOrder(String lifecycle) {
|
|
||||||
return switch (lifecycle) {
|
|
||||||
case "INSERT", "START" -> 0;
|
|
||||||
case "WITHDRAW", "END" -> 1;
|
|
||||||
default -> 2;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JsonNode rawPayload(EventHubEventDto event) {
|
|
||||||
JsonNode payload = event.payload();
|
|
||||||
if (payload == null || payload.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = payload.get("raw");
|
|
||||||
return raw == null || raw.isNull() ? payload : raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JsonNode attributes(EventHubEventDto event) {
|
|
||||||
return event.eventDetails() == null ? null : event.eventDetails().attributes();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String text(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String text = value.asText(null);
|
|
||||||
return text == null || text.isBlank() ? null : text;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean booleanValue(JsonNode node, String field, boolean fallback) {
|
|
||||||
if (node == null || field == null || node.get(field) == null || node.get(field).isNull()) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
return node.get(field).asBoolean(fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Long longValue(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null || node.get(field) == null || node.get(field).isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value.isNumber()) {
|
|
||||||
return value.asLong();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Long.parseLong(value.asText());
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<String> stringList(JsonNode node, String field, String fallback) {
|
|
||||||
JsonNode value = node == null || field == null ? null : node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return fallback == null ? List.of() : List.of(fallback);
|
|
||||||
}
|
|
||||||
if (value.isArray()) {
|
|
||||||
List<String> result = new ArrayList<>();
|
|
||||||
value.forEach(item -> {
|
|
||||||
if (item != null && !item.isNull()) {
|
|
||||||
String text = item.asText(null);
|
|
||||||
if (text != null && !text.isBlank()) {
|
|
||||||
result.add(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result.isEmpty() && fallback != null ? List.of(fallback) : List.copyOf(result);
|
|
||||||
}
|
|
||||||
String text = value.asText(null);
|
|
||||||
return text == null || text.isBlank() ? (fallback == null ? List.of() : List.of(fallback)) : List.of(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String firstNonBlank(String... values) {
|
|
||||||
if (values == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (String value : values) {
|
|
||||||
if (value != null && !value.isBlank()) {
|
|
||||||
return value.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String eventTypeAsActivity(EventType eventType) {
|
|
||||||
if (eventType == null) {
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
return switch (eventType) {
|
|
||||||
case DRIVE -> "DRIVE";
|
|
||||||
case WORK -> "WORK";
|
|
||||||
case AVAILABILITY -> "AVAILABILITY";
|
|
||||||
case BREAK_REST -> "BREAK_REST";
|
|
||||||
default -> eventType.name();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static UUID sessionId(JsonNode raw, EventHubEventDto event) {
|
|
||||||
String rawSessionId = firstNonBlank(
|
|
||||||
text(raw, "sessionId"),
|
|
||||||
event.sourcePackageRef() == null ? null : event.sourcePackageRef().sourcePackageId()
|
|
||||||
);
|
|
||||||
if (rawSessionId != null) {
|
|
||||||
try {
|
|
||||||
return UUID.fromString(rawSessionId);
|
|
||||||
} catch (IllegalArgumentException ignored) {
|
|
||||||
// DB-acquired source packages need not be UUID-based file sessions.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new UUID(0L, 0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String driverKey(EventHubEventDto event) {
|
|
||||||
if (event.driverRef() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (event.driverRef().driverCard() != null && event.driverRef().driverCard().hasValue()) {
|
|
||||||
return event.driverRef().driverCard().stableKey();
|
|
||||||
}
|
|
||||||
return event.driverRef().sourceEntityId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String registrationKey(EventHubEventDto event) {
|
|
||||||
if (event.vehicleRef() == null || event.vehicleRef().vehicleRegistration() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return event.vehicleRef().vehicleRegistration().stableKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String vehicleKey(EventHubEventDto event) {
|
|
||||||
if (event.vehicleRef() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return firstNonBlank(event.vehicleRef().vin(), event.vehicleRef().sourceVehicleEntityId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String sourceKind(EventHubEventDto event) {
|
|
||||||
return event.packageInfo() == null || event.packageInfo().eventSource() == null
|
|
||||||
? null
|
|
||||||
: event.packageInfo().eventSource().sourceKind();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Long odometerKm(EventHubEventDto event, JsonNode raw) {
|
|
||||||
Long explicit = longValue(raw, event.lifecycle() == EventLifecycle.WITHDRAW ? "odometerEndKm" : "odometerBeginKm");
|
|
||||||
if (explicit != null) {
|
|
||||||
return explicit;
|
|
||||||
}
|
|
||||||
explicit = longValue(raw, "odometerKm");
|
|
||||||
if (explicit != null) {
|
|
||||||
return explicit;
|
|
||||||
}
|
|
||||||
return event.odometerM() == null ? null : event.odometerM() / 1_000L;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> List<T> safeList(List<T> values) {
|
|
||||||
return values == null ? List.of() : values;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module.epl;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record RuntimeEplInputEventStream(
|
|
||||||
String eventTypeName,
|
|
||||||
List<Map<String, Object>> events
|
|
||||||
) {
|
|
||||||
public RuntimeEplInputEventStream {
|
|
||||||
if (eventTypeName == null || eventTypeName.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("eventTypeName must not be blank");
|
|
||||||
}
|
|
||||||
eventTypeName = eventTypeName.trim();
|
|
||||||
events = events == null ? List.of() : List.copyOf(events);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module.epl;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModule;
|
|
||||||
|
|
||||||
public interface RuntimeEplModule extends RuntimeProcessingModule {
|
|
||||||
|
|
||||||
default String engine() {
|
|
||||||
return "EPL";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module.epl;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record RuntimeEplModuleDefinition(
|
|
||||||
String moduleKey,
|
|
||||||
Map<String, Map<String, Object>> eventTypes,
|
|
||||||
List<String> eplResources,
|
|
||||||
List<String> outputStatementNames,
|
|
||||||
List<RuntimeEplInputEventStream> inputStreams
|
|
||||||
) {
|
|
||||||
public RuntimeEplModuleDefinition {
|
|
||||||
if (moduleKey == null || moduleKey.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("moduleKey must not be blank");
|
|
||||||
}
|
|
||||||
moduleKey = moduleKey.trim();
|
|
||||||
eventTypes = eventTypes == null ? Map.of() : immutableNestedMap(eventTypes);
|
|
||||||
eplResources = eplResources == null ? List.of() : List.copyOf(eplResources);
|
|
||||||
outputStatementNames = outputStatementNames == null ? List.of() : List.copyOf(outputStatementNames);
|
|
||||||
inputStreams = inputStreams == null ? List.of() : List.copyOf(inputStreams);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<String, Map<String, Object>> immutableNestedMap(Map<String, Map<String, Object>> source) {
|
|
||||||
LinkedHashMap<String, Map<String, Object>> copy = new LinkedHashMap<>();
|
|
||||||
source.forEach((key, value) -> copy.put(key, value == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(value))));
|
|
||||||
return Collections.unmodifiableMap(copy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module.epl;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record RuntimeEplModuleExecutionResult(
|
|
||||||
Map<String, List<Map<String, Object>>> outputsByStatement,
|
|
||||||
Map<String, Object> metadata
|
|
||||||
) {
|
|
||||||
public RuntimeEplModuleExecutionResult {
|
|
||||||
outputsByStatement = outputsByStatement == null ? Map.of() : immutableOutputMap(outputsByStatement);
|
|
||||||
metadata = metadata == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(metadata));
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Map<String, Object>> output(String statementName) {
|
|
||||||
return outputsByStatement.getOrDefault(statementName, List.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<String, List<Map<String, Object>>> immutableOutputMap(Map<String, List<Map<String, Object>>> source) {
|
|
||||||
LinkedHashMap<String, List<Map<String, Object>>> copy = new LinkedHashMap<>();
|
|
||||||
source.forEach((key, value) -> copy.put(key, value == null ? List.of() : List.copyOf(value)));
|
|
||||||
return Collections.unmodifiableMap(copy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.module.epl;
|
|
||||||
|
|
||||||
import com.espertech.esper.common.client.EPCompiled;
|
|
||||||
import com.espertech.esper.common.client.EventBean;
|
|
||||||
import com.espertech.esper.common.client.configuration.Configuration;
|
|
||||||
import com.espertech.esper.compiler.client.CompilerArguments;
|
|
||||||
import com.espertech.esper.compiler.client.EPCompileException;
|
|
||||||
import com.espertech.esper.compiler.client.EPCompilerProvider;
|
|
||||||
import com.espertech.esper.runtime.client.EPDeployException;
|
|
||||||
import com.espertech.esper.runtime.client.EPDeployment;
|
|
||||||
import com.espertech.esper.runtime.client.EPRuntime;
|
|
||||||
import com.espertech.esper.runtime.client.EPRuntimeProvider;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.StreamUtils;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class RuntimeEplModuleExecutor {
|
|
||||||
|
|
||||||
private static final AtomicLong RUNTIME_COUNTER = new AtomicLong();
|
|
||||||
|
|
||||||
public RuntimeEplModuleExecutionResult execute(RuntimeEplModuleDefinition definition) {
|
|
||||||
EPRuntime runtime = null;
|
|
||||||
try {
|
|
||||||
Configuration configuration = new Configuration();
|
|
||||||
definition.eventTypes().forEach((eventTypeName, eventTypeDefinition) ->
|
|
||||||
configuration.getCommon().addEventType(eventTypeName, eventTypeDefinition));
|
|
||||||
|
|
||||||
String runtimeUri = "eventhub-runtime-epl-module-"
|
|
||||||
+ definition.moduleKey().replaceAll("[^A-Za-z0-9_-]", "-")
|
|
||||||
+ "-" + RUNTIME_COUNTER.incrementAndGet();
|
|
||||||
runtime = EPRuntimeProvider.getRuntime(runtimeUri, configuration);
|
|
||||||
|
|
||||||
EPCompiled compiled = EPCompilerProvider.getCompiler().compile(renderEpl(definition.eplResources()), new CompilerArguments(configuration));
|
|
||||||
EPDeployment deployment = runtime.getDeploymentService().deploy(compiled);
|
|
||||||
|
|
||||||
Map<String, List<Map<String, Object>>> outputs = new LinkedHashMap<>();
|
|
||||||
for (String statementName : definition.outputStatementNames()) {
|
|
||||||
outputs.put(statementName, new ArrayList<>());
|
|
||||||
var statement = runtime.getDeploymentService().getStatement(deployment.getDeploymentId(), statementName);
|
|
||||||
if (statement == null) {
|
|
||||||
throw new IllegalStateException("EPL module " + definition.moduleKey()
|
|
||||||
+ " did not deploy expected statement '" + statementName + "'.");
|
|
||||||
}
|
|
||||||
statement.addListener((newData, oldData, stmt, rt) -> collect(newData, outputs.get(statementName)));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (RuntimeEplInputEventStream inputStream : definition.inputStreams()) {
|
|
||||||
for (Map<String, Object> event : inputStream.events()) {
|
|
||||||
runtime.getEventService().sendEventMap(event, inputStream.eventTypeName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> metadata = new LinkedHashMap<>();
|
|
||||||
metadata.put("engine", "EPL");
|
|
||||||
metadata.put("eplResources", definition.eplResources());
|
|
||||||
Map<String, Integer> inputCounts = new LinkedHashMap<>();
|
|
||||||
for (RuntimeEplInputEventStream inputStream : definition.inputStreams()) {
|
|
||||||
inputCounts.merge(inputStream.eventTypeName(), inputStream.events().size(), Integer::sum);
|
|
||||||
}
|
|
||||||
metadata.put("inputCounts", inputCounts);
|
|
||||||
Map<String, Integer> outputCounts = new LinkedHashMap<>();
|
|
||||||
outputs.forEach((statement, events) -> outputCounts.put(statement, events.size()));
|
|
||||||
metadata.put("outputCounts", outputCounts);
|
|
||||||
return new RuntimeEplModuleExecutionResult(outputs, metadata);
|
|
||||||
} catch (EPCompileException | EPDeployException e) {
|
|
||||||
throw new IllegalStateException("Cannot compile/deploy runtime EPL module " + definition.moduleKey(), e);
|
|
||||||
} finally {
|
|
||||||
if (runtime != null) {
|
|
||||||
runtime.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String renderEpl(List<String> resources) {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
for (String resource : resources) {
|
|
||||||
if (resource == null || resource.isBlank()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!builder.isEmpty()) {
|
|
||||||
builder.append("\n\n");
|
|
||||||
}
|
|
||||||
builder.append(loadResource(resource.trim()));
|
|
||||||
}
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String loadResource(String path) {
|
|
||||||
try {
|
|
||||||
ClassPathResource resource = new ClassPathResource(path);
|
|
||||||
return StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException("Cannot load EPL resource: " + path, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void collect(EventBean[] newData, List<Map<String, Object>> target) {
|
|
||||||
if (newData == null || target == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (EventBean eventBean : newData) {
|
|
||||||
LinkedHashMap<String, Object> values = new LinkedHashMap<>();
|
|
||||||
for (String propertyName : eventBean.getEventType().getPropertyNames()) {
|
|
||||||
if (propertyName == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
values.put(propertyName, eventBean.get(propertyName));
|
|
||||||
}
|
|
||||||
target.add(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.partition;
|
|
||||||
|
|
||||||
public enum RuntimeEventPartitioningStrategy {
|
|
||||||
NONE,
|
|
||||||
DRIVER,
|
|
||||||
VEHICLE,
|
|
||||||
DRIVER_VEHICLE,
|
|
||||||
SOURCE_FAMILY,
|
|
||||||
CUSTOM_PROFILE
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.partition;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.DriverRefDto;
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.dto.VehicleRefDto;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class RuntimeEventScopeClassifier {
|
|
||||||
|
|
||||||
public RuntimeEventScopeType classify(EventHubEventDto event) {
|
|
||||||
if (event == null) {
|
|
||||||
return RuntimeEventScopeType.UNKNOWN;
|
|
||||||
}
|
|
||||||
boolean hasDriver = hasDriver(event);
|
|
||||||
boolean hasVehicle = hasVehicle(event);
|
|
||||||
if (hasDriver && hasVehicle) {
|
|
||||||
return RuntimeEventScopeType.DRIVER_VEHICLE_SCOPED;
|
|
||||||
}
|
|
||||||
if (hasDriver) {
|
|
||||||
return RuntimeEventScopeType.DRIVER_SCOPED;
|
|
||||||
}
|
|
||||||
if (hasVehicle) {
|
|
||||||
return RuntimeEventScopeType.VEHICLE_SCOPED;
|
|
||||||
}
|
|
||||||
return RuntimeEventScopeType.GLOBAL_SUPPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasDriver(EventHubEventDto event) {
|
|
||||||
if (event == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (text(rawPayload(event), "driverKey") != null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
DriverRefDto driverRef = event.driverRef();
|
|
||||||
return driverRef != null && driverRef.hasAnyReference();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasVehicle(EventHubEventDto event) {
|
|
||||||
if (event == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
JsonNode raw = rawPayload(event);
|
|
||||||
if (text(raw, "vehicleKey") != null || text(raw, "registrationKey") != null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
VehicleRefDto vehicleRef = event.vehicleRef();
|
|
||||||
return vehicleRef != null && vehicleRef.hasAnyReference();
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonNode rawPayload(EventHubEventDto event) {
|
|
||||||
JsonNode payload = event.payload();
|
|
||||||
if (payload == null || payload.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = payload.get("raw");
|
|
||||||
return raw == null || raw.isNull() ? payload : raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String text(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String text = value.asText(null);
|
|
||||||
return text == null || text.isBlank() ? null : text.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.partition;
|
|
||||||
|
|
||||||
public enum RuntimeEventScopeType {
|
|
||||||
DRIVER_SCOPED,
|
|
||||||
VEHICLE_SCOPED,
|
|
||||||
DRIVER_VEHICLE_SCOPED,
|
|
||||||
GLOBAL_SUPPORT,
|
|
||||||
UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
@ -1,480 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.plan;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeDerivedProjectionResultDto;
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeDriverWorkingTimeScopeResultDto;
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventPartitioningApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingPartitionResultDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.DriverWorkingTimeModuleKeys;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleContext;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleResult;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleStatus;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingPipelineExecutor;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleRegistry;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.DriverActivityIntervalsModule;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.DriverVehicleUsageIntervalsModule;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.DriverVehicleUsageMergeModule;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.VehicleEvidenceAttachmentModule;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.SupportEvidenceNormalizationModule;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.DriverWorkingTimeDerivedProjectionsModule;
|
|
||||||
import at.procon.eventhub.processing.service.RuntimeDriverWorkingTimeScopeProcessingService;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class DriverWorkingTimeRuntimeProcessingPlan implements RuntimeProcessingPlan {
|
|
||||||
|
|
||||||
public static final String PLAN_KEY = "driver-working-time-v1";
|
|
||||||
public static final String LEGACY_PROFILE_ALIAS = "tachograph-driver-esper-v1";
|
|
||||||
|
|
||||||
private final RuntimeProcessingPipelineExecutor pipelineExecutor;
|
|
||||||
private final boolean includeRuntimeEventAssemblyModule;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public DriverWorkingTimeRuntimeProcessingPlan(RuntimeProcessingPipelineExecutor pipelineExecutor) {
|
|
||||||
this.pipelineExecutor = pipelineExecutor;
|
|
||||||
this.includeRuntimeEventAssemblyModule = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compatibility constructor for older unit tests and legacy adapters that instantiated the
|
|
||||||
* driver-working-time plan directly from the old scope service. This constructor builds a
|
|
||||||
* small local module registry around the existing scope-processing adapter. It intentionally
|
|
||||||
* omits the runtime-event-assembly module because the scope-processing adapter performs that
|
|
||||||
* assembly internally.
|
|
||||||
*/
|
|
||||||
public DriverWorkingTimeRuntimeProcessingPlan(
|
|
||||||
RuntimeDriverWorkingTimeScopeProcessingService scopeProcessingService
|
|
||||||
) {
|
|
||||||
this(new RuntimeProcessingPipelineExecutor(new RuntimeProcessingModuleRegistry(List.of(
|
|
||||||
new DriverActivityIntervalsModule(),
|
|
||||||
new DriverVehicleUsageIntervalsModule(),
|
|
||||||
new DriverVehicleUsageMergeModule(),
|
|
||||||
new VehicleEvidenceAttachmentModule(),
|
|
||||||
new SupportEvidenceNormalizationModule(),
|
|
||||||
new DriverWorkingTimeDerivedProjectionsModule(scopeProcessingService)
|
|
||||||
))), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DriverWorkingTimeRuntimeProcessingPlan(
|
|
||||||
RuntimeProcessingPipelineExecutor pipelineExecutor,
|
|
||||||
boolean includeRuntimeEventAssemblyModule
|
|
||||||
) {
|
|
||||||
this.pipelineExecutor = pipelineExecutor;
|
|
||||||
this.includeRuntimeEventAssemblyModule = includeRuntimeEventAssemblyModule;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String processingPlanKey() {
|
|
||||||
return PLAN_KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> aliases() {
|
|
||||||
return Set.of(LEGACY_PROFILE_ALIAS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeEventPartitioningStrategy defaultPartitioningStrategy() {
|
|
||||||
return RuntimeEventPartitioningStrategy.DRIVER;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String displayName() {
|
|
||||||
return "Driver working-time processing";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String description() {
|
|
||||||
return "Loads canonical EventHub runtime events from selected sources, partitions them by driver, "
|
|
||||||
+ "attaches vehicle-only evidence by vehicle/time, normalizes mixed-source support evidence, "
|
|
||||||
+ "then executes source-neutral driver working-time processing modules for driving intervals, "
|
|
||||||
+ "interruptions, rest candidates, overnight candidates, trips, vehicle usage, and card-absence coverage.";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<RuntimeEventPartitioningStrategy> supportedPartitioningStrategies() {
|
|
||||||
return List.of(RuntimeEventPartitioningStrategy.DRIVER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<RuntimeProcessingModuleDescriptorDto> modules() {
|
|
||||||
List<RuntimeProcessingModuleDescriptorDto> descriptors = new java.util.ArrayList<>();
|
|
||||||
if (includeRuntimeEventAssemblyModule) {
|
|
||||||
descriptors.add(new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
DriverWorkingTimeModuleKeys.RUNTIME_EVENT_ASSEMBLY,
|
|
||||||
"Runtime event assembly",
|
|
||||||
"Loads and merges canonical runtime events from the selected source scope before plan-specific processing.",
|
|
||||||
"JAVA",
|
|
||||||
Set.of("UnifiedRuntimeEventBundle")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
descriptors.addAll(List.of(
|
|
||||||
new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
DriverWorkingTimeModuleKeys.EVENT_TO_ACTIVITY_INTERVALS,
|
|
||||||
"Event to activity intervals",
|
|
||||||
"Pairs canonical DRIVER_ACTIVITY START/END point events into source-neutral activity intervals using a first-class EPL module.",
|
|
||||||
"ESPER",
|
|
||||||
Set.of("DriverActivityIntervalEvent")
|
|
||||||
),
|
|
||||||
new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
DriverWorkingTimeModuleKeys.EVENT_TO_VEHICLE_USAGE_INTERVALS,
|
|
||||||
"Event to vehicle usage intervals",
|
|
||||||
"Pairs canonical driver/card INSERT/WITHDRAW point events into source-neutral vehicle-usage intervals using a first-class EPL module.",
|
|
||||||
"ESPER",
|
|
||||||
Set.of("DriverVehicleUsageIntervalEvent")
|
|
||||||
),
|
|
||||||
new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
DriverWorkingTimeModuleKeys.VEHICLE_USAGE_MERGE,
|
|
||||||
"Vehicle usage merge",
|
|
||||||
"Merges adjacent same-driver/same-vehicle usage intervals, including 23:59:59 to 00:00:00 continuations. Currently delegated to the derived projections adapter.",
|
|
||||||
"JAVA/ESPER",
|
|
||||||
Set.of("DriverVehicleUsageIntervalEvent")
|
|
||||||
),
|
|
||||||
new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
DriverWorkingTimeModuleKeys.VEHICLE_EVIDENCE_ATTACHMENT,
|
|
||||||
"Vehicle evidence attachment",
|
|
||||||
"Attaches vehicle-only events to driver partitions when they overlap driver vehicle-usage intervals.",
|
|
||||||
"JAVA",
|
|
||||||
Set.of("RuntimeDriverPartitionDebugDto")
|
|
||||||
),
|
|
||||||
new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
DriverWorkingTimeModuleKeys.SUPPORT_EVIDENCE_NORMALIZATION,
|
|
||||||
"Support evidence normalization",
|
|
||||||
"Normalizes attached mixed-source support evidence for driver working-time processing.",
|
|
||||||
"JAVA",
|
|
||||||
Set.of("RuntimeSupportEvidenceNormalizationDebugDto")
|
|
||||||
),
|
|
||||||
new RuntimeProcessingModuleDescriptorDto(
|
|
||||||
DriverWorkingTimeModuleKeys.DRIVING_DERIVED_PROJECTIONS,
|
|
||||||
"Driving-derived projections",
|
|
||||||
"Runs the shared driver working-time derived projection module for driving interruptions, rest candidates, trips, and overnight candidates.",
|
|
||||||
"ESPER+JAVA",
|
|
||||||
Set.of("DriverWorkingTimeProcessingResultDto")
|
|
||||||
)
|
|
||||||
));
|
|
||||||
return List.copyOf(descriptors);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> optionalParameters() {
|
|
||||||
return Set.of(
|
|
||||||
"significantDrivingMinutes",
|
|
||||||
"minimumRestPeriodMinutes",
|
|
||||||
"attachVehicleOnlyEvents",
|
|
||||||
"vehicleEvidencePaddingMinutes",
|
|
||||||
"includePartitionDebug"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeProcessingExecutionResultDto execute(RuntimeProcessingExecutionApiRequest request) {
|
|
||||||
boolean includePartitionDebug = booleanParameter(
|
|
||||||
request.parameters(),
|
|
||||||
"includePartitionDebug",
|
|
||||||
request.partitioning() != null && request.partitioning().includeDebugOrDefault()
|
|
||||||
);
|
|
||||||
UnifiedRuntimeProcessingApiRequest scopeRequest = applyExecutionRequest(
|
|
||||||
request.sourceSelection(),
|
|
||||||
request.partitioning(),
|
|
||||||
request.parameters()
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, Object> attributes = new LinkedHashMap<>();
|
|
||||||
attributes.put("runtimeScopeApiRequest", scopeRequest);
|
|
||||||
attributes.put("includePartitionDebug", includePartitionDebug);
|
|
||||||
RuntimeProcessingModuleContext initialContext = new RuntimeProcessingModuleContext(
|
|
||||||
request,
|
|
||||||
List.of(),
|
|
||||||
attributes,
|
|
||||||
Map.of()
|
|
||||||
);
|
|
||||||
|
|
||||||
List<String> executedModules = requestedOrDefaultModules(request.modules());
|
|
||||||
Map<String, RuntimeProcessingModuleResult> moduleResults = pipelineExecutor.execute(
|
|
||||||
request,
|
|
||||||
executedModules,
|
|
||||||
initialContext
|
|
||||||
);
|
|
||||||
UnifiedRuntimeDriverWorkingTimeScopeResultDto workingTimeResult = extractWorkingTimeResult(moduleResults);
|
|
||||||
|
|
||||||
Map<String, RuntimeEventProcessingPartitionResultDto> partitionResults = new LinkedHashMap<>();
|
|
||||||
workingTimeResult.driverResults().forEach((driverKey, driverResult) -> {
|
|
||||||
Map<String, Object> metadata = new LinkedHashMap<>();
|
|
||||||
metadata.put("projectionResultType", driverResult.projection() == null ? "NONE" : "DriverWorkingTimeProcessingResultDto");
|
|
||||||
metadata.put("driverSeedEventCount", driverResult.driverSeedEventCount());
|
|
||||||
metadata.put("expandedVehicleEventCount", driverResult.expandedVehicleEventCount());
|
|
||||||
metadata.put("mergedEventCount", driverResult.mergedEventCount());
|
|
||||||
if (driverResult.supportEvidenceNormalization() != null) {
|
|
||||||
metadata.put("supportEvidenceNormalization", driverResult.supportEvidenceNormalization());
|
|
||||||
}
|
|
||||||
if (driverResult.partitionDebug() != null) {
|
|
||||||
metadata.put("partitionDebug", driverResult.partitionDebug());
|
|
||||||
}
|
|
||||||
partitionResults.put(
|
|
||||||
driverKey,
|
|
||||||
new RuntimeEventProcessingPartitionResultDto(
|
|
||||||
"DRIVER",
|
|
||||||
driverKey,
|
|
||||||
"UnifiedRuntimeDerivedProjectionResultDto",
|
|
||||||
driverResult,
|
|
||||||
metadata,
|
|
||||||
partitionModuleResults(driverResult)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return new RuntimeProcessingExecutionResultDto(
|
|
||||||
processingPlanKey(),
|
|
||||||
executedModules,
|
|
||||||
RuntimeEventPartitioningStrategy.DRIVER,
|
|
||||||
workingTimeResult.request(),
|
|
||||||
workingTimeResult.inputEventCount(),
|
|
||||||
workingTimeResult.selectedDriverCount(),
|
|
||||||
workingTimeResult.discoveredVehicleCount(),
|
|
||||||
workingTimeResult.discoveredVehicles(),
|
|
||||||
sanitizeExecutionModuleResults(moduleResults),
|
|
||||||
partitionResults,
|
|
||||||
workingTimeResult.notes(),
|
|
||||||
workingTimeResult.warnings()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnifiedRuntimeDriverWorkingTimeScopeResultDto extractWorkingTimeResult(
|
|
||||||
Map<String, RuntimeProcessingModuleResult> moduleResults
|
|
||||||
) {
|
|
||||||
RuntimeProcessingModuleResult finalResult = moduleResults.get(DriverWorkingTimeModuleKeys.DRIVING_DERIVED_PROJECTIONS);
|
|
||||||
if (finalResult == null) {
|
|
||||||
throw new IllegalArgumentException("Processing plan " + PLAN_KEY
|
|
||||||
+ " requires module " + DriverWorkingTimeModuleKeys.DRIVING_DERIVED_PROJECTIONS + ".");
|
|
||||||
}
|
|
||||||
if (finalResult.status() == RuntimeProcessingModuleStatus.FAILED) {
|
|
||||||
throw new IllegalStateException("Processing module " + finalResult.moduleKey() + " failed: " + finalResult.warnings());
|
|
||||||
}
|
|
||||||
Object output = finalResult.output();
|
|
||||||
if (output instanceof UnifiedRuntimeDriverWorkingTimeScopeResultDto result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("Processing module " + DriverWorkingTimeModuleKeys.DRIVING_DERIVED_PROJECTIONS
|
|
||||||
+ " did not return UnifiedRuntimeDriverWorkingTimeScopeResultDto. Actual: "
|
|
||||||
+ (output == null ? "null" : output.getClass().getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, RuntimeProcessingModuleResult> sanitizeExecutionModuleResults(
|
|
||||||
Map<String, RuntimeProcessingModuleResult> moduleResults
|
|
||||||
) {
|
|
||||||
LinkedHashMap<String, RuntimeProcessingModuleResult> sanitized = new LinkedHashMap<>();
|
|
||||||
for (Map.Entry<String, RuntimeProcessingModuleResult> entry : moduleResults.entrySet()) {
|
|
||||||
RuntimeProcessingModuleResult result = entry.getValue();
|
|
||||||
sanitized.put(entry.getKey(), new RuntimeProcessingModuleResult(
|
|
||||||
result.moduleKey(),
|
|
||||||
result.status(),
|
|
||||||
null,
|
|
||||||
result.metadata(),
|
|
||||||
result.warnings()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return sanitized;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, RuntimeProcessingModuleResult> partitionModuleResults(
|
|
||||||
UnifiedRuntimeDerivedProjectionResultDto driverResult
|
|
||||||
) {
|
|
||||||
LinkedHashMap<String, RuntimeProcessingModuleResult> results = new LinkedHashMap<>();
|
|
||||||
Map<String, Object> attachmentMetadata = new LinkedHashMap<>();
|
|
||||||
attachmentMetadata.put("driverSeedEventCount", driverResult.driverSeedEventCount());
|
|
||||||
attachmentMetadata.put("expandedVehicleEventCount", driverResult.expandedVehicleEventCount());
|
|
||||||
attachmentMetadata.put("mergedEventCount", driverResult.mergedEventCount());
|
|
||||||
results.put(
|
|
||||||
DriverWorkingTimeModuleKeys.VEHICLE_EVIDENCE_ATTACHMENT,
|
|
||||||
new RuntimeProcessingModuleResult(
|
|
||||||
DriverWorkingTimeModuleKeys.VEHICLE_EVIDENCE_ATTACHMENT,
|
|
||||||
RuntimeProcessingModuleStatus.SUCCESS,
|
|
||||||
driverResult.partitionDebug(),
|
|
||||||
attachmentMetadata,
|
|
||||||
List.of()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
Map<String, Object> normalizationMetadata = new LinkedHashMap<>();
|
|
||||||
if (driverResult.supportEvidenceNormalization() != null) {
|
|
||||||
normalizationMetadata.put("inputEventCount", driverResult.supportEvidenceNormalization().inputEventCount());
|
|
||||||
normalizationMetadata.put("normalizedSupportEvidenceEventCount", driverResult.supportEvidenceNormalization().normalizedSupportEvidenceEventCount());
|
|
||||||
normalizationMetadata.put("unchangedEventCount", driverResult.supportEvidenceNormalization().unchangedEventCount());
|
|
||||||
}
|
|
||||||
results.put(
|
|
||||||
DriverWorkingTimeModuleKeys.SUPPORT_EVIDENCE_NORMALIZATION,
|
|
||||||
new RuntimeProcessingModuleResult(
|
|
||||||
DriverWorkingTimeModuleKeys.SUPPORT_EVIDENCE_NORMALIZATION,
|
|
||||||
RuntimeProcessingModuleStatus.SUCCESS,
|
|
||||||
driverResult.supportEvidenceNormalization(),
|
|
||||||
normalizationMetadata,
|
|
||||||
List.of()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
results.put(
|
|
||||||
DriverWorkingTimeModuleKeys.DRIVING_DERIVED_PROJECTIONS,
|
|
||||||
new RuntimeProcessingModuleResult(
|
|
||||||
DriverWorkingTimeModuleKeys.DRIVING_DERIVED_PROJECTIONS,
|
|
||||||
RuntimeProcessingModuleStatus.SUCCESS,
|
|
||||||
driverResult.projection(),
|
|
||||||
Map.of(
|
|
||||||
"resultType", driverResult.projection() == null ? "NONE" : "DriverWorkingTimeProcessingResultDto",
|
|
||||||
"noteCount", driverResult.notes().size()
|
|
||||||
),
|
|
||||||
List.of()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeProcessingApiRequest applyExecutionRequest(
|
|
||||||
UnifiedRuntimeProcessingApiRequest sourceSelection,
|
|
||||||
RuntimeEventPartitioningApiRequest partitioning,
|
|
||||||
Map<String, Object> parameters
|
|
||||||
) {
|
|
||||||
if (sourceSelection == null) {
|
|
||||||
throw new IllegalArgumentException("sourceSelection must not be null for processing plan " + PLAN_KEY + ".");
|
|
||||||
}
|
|
||||||
boolean includeAllDrivers = sourceSelection.includeAllDrivers() != null && sourceSelection.includeAllDrivers();
|
|
||||||
java.util.Set<String> driverKeys = sourceSelection.driverKeys();
|
|
||||||
boolean includeAllVehicles = sourceSelection.includeAllVehicles() != null && sourceSelection.includeAllVehicles();
|
|
||||||
java.util.Set<String> vehicleKeys = sourceSelection.vehicleKeys();
|
|
||||||
|
|
||||||
if (partitioning != null) {
|
|
||||||
RuntimeEventPartitioningStrategy strategy = partitioning.strategy() == null
|
|
||||||
? RuntimeEventPartitioningStrategy.DRIVER
|
|
||||||
: partitioning.strategy();
|
|
||||||
if (strategy != RuntimeEventPartitioningStrategy.DRIVER
|
|
||||||
&& strategy != RuntimeEventPartitioningStrategy.CUSTOM_PROFILE) {
|
|
||||||
throw new IllegalArgumentException("Processing plan " + PLAN_KEY
|
|
||||||
+ " currently supports DRIVER partitioning only. Requested: " + strategy);
|
|
||||||
}
|
|
||||||
includeAllDrivers = includeAllDrivers || partitioning.allPartitions() || partitioning.allDrivers();
|
|
||||||
if (!partitioning.driverKeys().isEmpty()) {
|
|
||||||
driverKeys = partitioning.driverKeys();
|
|
||||||
} else if (!partitioning.partitionKeys().isEmpty()) {
|
|
||||||
driverKeys = partitioning.partitionKeys();
|
|
||||||
}
|
|
||||||
includeAllVehicles = includeAllVehicles || partitioning.allVehicles();
|
|
||||||
if (!partitioning.vehicleKeys().isEmpty()) {
|
|
||||||
vehicleKeys = partitioning.vehicleKeys();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Integer significantDrivingMinutes = integerParameter(parameters, "significantDrivingMinutes", sourceSelection.significantDrivingMinutes());
|
|
||||||
Integer minimumRestPeriodMinutes = integerParameter(parameters, "minimumRestPeriodMinutes", sourceSelection.minimumRestPeriodMinutes());
|
|
||||||
boolean attachVehicleOnlyEvents = booleanParameter(parameters, "attachVehicleOnlyEvents",
|
|
||||||
partitioning == null ? sourceSelection.expandVehicleEvents() == null || sourceSelection.expandVehicleEvents() : partitioning.attachVehicleEvidenceOrDefault());
|
|
||||||
Integer vehicleEvidencePaddingMinutes = nonNegativeIntegerParameter(parameters, "vehicleEvidencePaddingMinutes",
|
|
||||||
partitioning == null
|
|
||||||
? sourceSelection.vehicleExpansionPaddingMinutes()
|
|
||||||
: partitioning.vehicleEvidencePaddingMinutesOrDefault(sourceSelection.vehicleExpansionPaddingMinutes() == null ? 0 : sourceSelection.vehicleExpansionPaddingMinutes()));
|
|
||||||
|
|
||||||
return new UnifiedRuntimeProcessingApiRequest(
|
|
||||||
sourceSelection.sessionId(),
|
|
||||||
sourceSelection.sessionIds(),
|
|
||||||
sourceSelection.compositeSessionId(),
|
|
||||||
sourceSelection.tenantKey(),
|
|
||||||
sourceSelection.sourceFamilies(),
|
|
||||||
sourceSelection.eventBackend(),
|
|
||||||
sourceSelection.driverKey(),
|
|
||||||
driverKeys,
|
|
||||||
includeAllDrivers,
|
|
||||||
vehicleKeys,
|
|
||||||
includeAllVehicles,
|
|
||||||
sourceSelection.driverSourceEntityId(),
|
|
||||||
sourceSelection.driverCardNation(),
|
|
||||||
sourceSelection.driverCardNumber(),
|
|
||||||
sourceSelection.occurredFrom(),
|
|
||||||
sourceSelection.occurredTo(),
|
|
||||||
attachVehicleOnlyEvents,
|
|
||||||
vehicleEvidencePaddingMinutes,
|
|
||||||
significantDrivingMinutes,
|
|
||||||
minimumRestPeriodMinutes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> requestedOrDefaultModules(List<String> requestedModules) {
|
|
||||||
if (requestedModules != null && !requestedModules.isEmpty()) {
|
|
||||||
LinkedHashMap<String, String> requested = new LinkedHashMap<>();
|
|
||||||
for (String module : requestedModules) {
|
|
||||||
if (module != null && !module.isBlank()) {
|
|
||||||
requested.put(module.trim(), module.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requested.putIfAbsent(DriverWorkingTimeModuleKeys.DRIVING_DERIVED_PROJECTIONS,
|
|
||||||
DriverWorkingTimeModuleKeys.DRIVING_DERIVED_PROJECTIONS);
|
|
||||||
return List.copyOf(requested.values());
|
|
||||||
}
|
|
||||||
return modules().stream()
|
|
||||||
.map(RuntimeProcessingModuleDescriptorDto::moduleKey)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean booleanParameter(Map<String, Object> parameters, String key, boolean fallback) {
|
|
||||||
if (parameters == null || !parameters.containsKey(key)) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
Object value = parameters.get(key);
|
|
||||||
if (value == null) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
if (value instanceof Boolean booleanValue) {
|
|
||||||
return booleanValue;
|
|
||||||
}
|
|
||||||
String text = value.toString();
|
|
||||||
if (text == null || text.isBlank()) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
return Boolean.parseBoolean(text.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer nonNegativeIntegerParameter(Map<String, Object> parameters, String key, Integer fallback) {
|
|
||||||
if (parameters == null || !parameters.containsKey(key)) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
Object value = parameters.get(key);
|
|
||||||
if (value == null) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
if (value instanceof Number number) {
|
|
||||||
return Math.max(0, number.intValue());
|
|
||||||
}
|
|
||||||
String text = value.toString();
|
|
||||||
if (text == null || text.isBlank()) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Math.max(0, Integer.parseInt(text.trim()));
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new IllegalArgumentException("Parameter '" + key + "' must be an integer.", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer integerParameter(Map<String, Object> parameters, String key, Integer fallback) {
|
|
||||||
if (parameters == null || !parameters.containsKey(key)) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
Object value = parameters.get(key);
|
|
||||||
if (value == null) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
if (value instanceof Number number) {
|
|
||||||
return Math.max(1, number.intValue());
|
|
||||||
}
|
|
||||||
String text = value.toString();
|
|
||||||
if (text == null || text.isBlank()) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Math.max(1, Integer.parseInt(text.trim()));
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new IllegalArgumentException("Parameter '" + key + "' must be an integer.", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.plan;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventPartitioningApiRequest;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record RuntimeProcessingExecutionApiRequest(
|
|
||||||
String processingPlanKey,
|
|
||||||
@JsonAlias("scope") UnifiedRuntimeProcessingApiRequest sourceSelection,
|
|
||||||
RuntimeEventPartitioningApiRequest partitioning,
|
|
||||||
List<String> modules,
|
|
||||||
Map<String, Object> parameters
|
|
||||||
) {
|
|
||||||
public RuntimeProcessingExecutionApiRequest {
|
|
||||||
if (processingPlanKey == null || processingPlanKey.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("processingPlanKey must not be blank");
|
|
||||||
}
|
|
||||||
processingPlanKey = processingPlanKey.trim();
|
|
||||||
if (sourceSelection == null) {
|
|
||||||
throw new IllegalArgumentException("sourceSelection must not be null");
|
|
||||||
}
|
|
||||||
partitioning = partitioning == null
|
|
||||||
? new RuntimeEventPartitioningApiRequest(null, null, null, null, null, null, null, null, null, null)
|
|
||||||
: partitioning;
|
|
||||||
modules = modules == null ? List.of() : List.copyOf(modules);
|
|
||||||
parameters = parameters == null
|
|
||||||
? Map.of()
|
|
||||||
: Collections.unmodifiableMap(new LinkedHashMap<>(parameters));
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeProcessingApiRequest scope() {
|
|
||||||
return sourceSelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RuntimeProcessingExecutionApiRequest driverWorkingTime(UnifiedRuntimeProcessingApiRequest sourceSelection) {
|
|
||||||
return new RuntimeProcessingExecutionApiRequest(
|
|
||||||
DriverWorkingTimeRuntimeProcessingPlan.PLAN_KEY,
|
|
||||||
sourceSelection,
|
|
||||||
new RuntimeEventPartitioningApiRequest(
|
|
||||||
at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy.DRIVER,
|
|
||||||
null,
|
|
||||||
sourceSelection != null ? sourceSelection.includeAllDrivers() : null,
|
|
||||||
sourceSelection != null ? sourceSelection.driverKeys() : null,
|
|
||||||
sourceSelection != null ? sourceSelection.includeAllDrivers() : null,
|
|
||||||
sourceSelection != null ? sourceSelection.vehicleKeys() : null,
|
|
||||||
sourceSelection != null ? sourceSelection.includeAllVehicles() : null,
|
|
||||||
null,
|
|
||||||
sourceSelection != null ? sourceSelection.vehicleExpansionPaddingMinutes() : null,
|
|
||||||
null
|
|
||||||
),
|
|
||||||
List.of(),
|
|
||||||
Map.of()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.plan;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingPartitionResultDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.module.RuntimeProcessingModuleResult;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record RuntimeProcessingExecutionResultDto(
|
|
||||||
String processingPlanKey,
|
|
||||||
List<String> executedModules,
|
|
||||||
RuntimeEventPartitioningStrategy partitioningStrategy,
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
int inputEventCount,
|
|
||||||
int selectedPartitionCount,
|
|
||||||
int discoveredVehicleCount,
|
|
||||||
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
|
||||||
Map<String, RuntimeProcessingModuleResult> moduleResults,
|
|
||||||
Map<String, RuntimeEventProcessingPartitionResultDto> partitionResults,
|
|
||||||
List<String> notes,
|
|
||||||
List<String> warnings
|
|
||||||
) {
|
|
||||||
public RuntimeProcessingExecutionResultDto {
|
|
||||||
executedModules = executedModules == null ? List.of() : List.copyOf(executedModules);
|
|
||||||
discoveredVehicles = discoveredVehicles == null ? List.of() : List.copyOf(discoveredVehicles);
|
|
||||||
moduleResults = moduleResults == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(moduleResults));
|
|
||||||
partitionResults = partitionResults == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(partitionResults));
|
|
||||||
notes = notes == null ? List.of() : List.copyOf(notes);
|
|
||||||
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeProcessingExecutionResultDto(
|
|
||||||
String processingPlanKey,
|
|
||||||
List<String> executedModules,
|
|
||||||
RuntimeEventPartitioningStrategy partitioningStrategy,
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
int inputEventCount,
|
|
||||||
int selectedPartitionCount,
|
|
||||||
int discoveredVehicleCount,
|
|
||||||
List<UnifiedDiscoveredVehicleRef> discoveredVehicles,
|
|
||||||
Map<String, RuntimeEventProcessingPartitionResultDto> partitionResults,
|
|
||||||
List<String> notes,
|
|
||||||
List<String> warnings
|
|
||||||
) {
|
|
||||||
this(processingPlanKey, executedModules, partitioningStrategy, request, inputEventCount,
|
|
||||||
selectedPartitionCount, discoveredVehicleCount, discoveredVehicles, Map.of(), partitionResults,
|
|
||||||
notes, warnings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.plan;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class RuntimeProcessingExecutionService {
|
|
||||||
|
|
||||||
private final RuntimeProcessingPlanRegistry planRegistry;
|
|
||||||
|
|
||||||
public RuntimeProcessingExecutionService(RuntimeProcessingPlanRegistry planRegistry) {
|
|
||||||
this.planRegistry = planRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeProcessingExecutionResultDto execute(RuntimeProcessingExecutionApiRequest request) {
|
|
||||||
RuntimeProcessingPlan plan = planRegistry.require(request.processingPlanKey());
|
|
||||||
plan.validatePartitioning(request.partitioning());
|
|
||||||
return plan.execute(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<RuntimeProcessingPlanDescriptorDto> listPlans() {
|
|
||||||
return planRegistry.planDescriptors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.plan;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public record RuntimeProcessingModuleDescriptorDto(
|
|
||||||
String moduleKey,
|
|
||||||
String displayName,
|
|
||||||
String description,
|
|
||||||
String engine,
|
|
||||||
Set<String> producedStreams
|
|
||||||
) {
|
|
||||||
public RuntimeProcessingModuleDescriptorDto {
|
|
||||||
if (moduleKey == null || moduleKey.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("moduleKey must not be blank");
|
|
||||||
}
|
|
||||||
moduleKey = moduleKey.trim();
|
|
||||||
displayName = displayName == null || displayName.isBlank() ? moduleKey : displayName.trim();
|
|
||||||
description = description == null ? "" : description;
|
|
||||||
engine = engine == null || engine.isBlank() ? "UNKNOWN" : engine.trim();
|
|
||||||
producedStreams = producedStreams == null ? Set.of() : Set.copyOf(producedStreams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.plan;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventPartitioningApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public interface RuntimeProcessingPlan {
|
|
||||||
|
|
||||||
String processingPlanKey();
|
|
||||||
|
|
||||||
default Set<String> aliases() {
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeEventPartitioningStrategy defaultPartitioningStrategy();
|
|
||||||
|
|
||||||
default String displayName() {
|
|
||||||
return processingPlanKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
default String description() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
default List<RuntimeEventPartitioningStrategy> supportedPartitioningStrategies() {
|
|
||||||
RuntimeEventPartitioningStrategy defaultStrategy = defaultPartitioningStrategy();
|
|
||||||
return defaultStrategy == null ? List.of() : List.of(defaultStrategy);
|
|
||||||
}
|
|
||||||
|
|
||||||
default List<RuntimeProcessingModuleDescriptorDto> modules() {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
default Set<String> requiredParameters() {
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
default Set<String> optionalParameters() {
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeProcessingExecutionResultDto execute(RuntimeProcessingExecutionApiRequest request);
|
|
||||||
|
|
||||||
default void validatePartitioning(RuntimeEventPartitioningApiRequest partitioning) {
|
|
||||||
RuntimeEventPartitioningStrategy requested = partitioning == null || partitioning.strategy() == null
|
|
||||||
? defaultPartitioningStrategy()
|
|
||||||
: partitioning.strategy();
|
|
||||||
if (requested != null && !supportedPartitioningStrategies().contains(requested)) {
|
|
||||||
throw new IllegalArgumentException("Processing plan " + processingPlanKey()
|
|
||||||
+ " does not support partitioning strategy " + requested
|
|
||||||
+ ". Supported: " + supportedPartitioningStrategies());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.plan;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public record RuntimeProcessingPlanDescriptorDto(
|
|
||||||
String processingPlanKey,
|
|
||||||
Set<String> aliases,
|
|
||||||
String displayName,
|
|
||||||
String description,
|
|
||||||
RuntimeEventPartitioningStrategy defaultPartitioningStrategy,
|
|
||||||
List<RuntimeEventPartitioningStrategy> supportedPartitioningStrategies,
|
|
||||||
List<RuntimeProcessingModuleDescriptorDto> modules,
|
|
||||||
Set<String> requiredParameters,
|
|
||||||
Set<String> optionalParameters
|
|
||||||
) {
|
|
||||||
public RuntimeProcessingPlanDescriptorDto {
|
|
||||||
supportedPartitioningStrategies = supportedPartitioningStrategies == null
|
|
||||||
? List.of()
|
|
||||||
: List.copyOf(supportedPartitioningStrategies);
|
|
||||||
modules = modules == null ? List.of() : List.copyOf(modules);
|
|
||||||
aliases = aliases == null ? Set.of() : Set.copyOf(aliases);
|
|
||||||
requiredParameters = requiredParameters == null ? Set.of() : Set.copyOf(requiredParameters);
|
|
||||||
optionalParameters = optionalParameters == null ? Set.of() : Set.copyOf(optionalParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RuntimeProcessingPlanDescriptorDto from(RuntimeProcessingPlan plan) {
|
|
||||||
if (plan == null) {
|
|
||||||
throw new IllegalArgumentException("plan must not be null");
|
|
||||||
}
|
|
||||||
return new RuntimeProcessingPlanDescriptorDto(
|
|
||||||
plan.processingPlanKey(),
|
|
||||||
plan.aliases(),
|
|
||||||
plan.displayName(),
|
|
||||||
plan.description(),
|
|
||||||
plan.defaultPartitioningStrategy(),
|
|
||||||
plan.supportedPartitioningStrategies(),
|
|
||||||
plan.modules(),
|
|
||||||
plan.requiredParameters(),
|
|
||||||
plan.optionalParameters()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.plan;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class RuntimeProcessingPlanRegistry {
|
|
||||||
|
|
||||||
private final Map<String, RuntimeProcessingPlan> plansByKey;
|
|
||||||
private final Map<String, RuntimeProcessingPlan> plansByKeyAndAlias;
|
|
||||||
|
|
||||||
public RuntimeProcessingPlanRegistry(List<RuntimeProcessingPlan> plans) {
|
|
||||||
LinkedHashMap<String, RuntimeProcessingPlan> byKey = new LinkedHashMap<>();
|
|
||||||
LinkedHashMap<String, RuntimeProcessingPlan> byKeyAndAlias = new LinkedHashMap<>();
|
|
||||||
for (RuntimeProcessingPlan plan : plans == null ? List.<RuntimeProcessingPlan>of() : plans) {
|
|
||||||
String key = normalize(plan.processingPlanKey());
|
|
||||||
if (key == null) {
|
|
||||||
throw new IllegalStateException("Runtime processing plan returned a blank processingPlanKey: " + plan.getClass().getName());
|
|
||||||
}
|
|
||||||
RuntimeProcessingPlan previous = byKey.putIfAbsent(key, plan);
|
|
||||||
if (previous != null) {
|
|
||||||
throw new IllegalStateException("Duplicate runtime processingPlanKey: " + key);
|
|
||||||
}
|
|
||||||
putAlias(byKeyAndAlias, key, plan);
|
|
||||||
for (String alias : plan.aliases()) {
|
|
||||||
String normalizedAlias = normalize(alias);
|
|
||||||
if (normalizedAlias != null) {
|
|
||||||
putAlias(byKeyAndAlias, normalizedAlias, plan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.plansByKey = Collections.unmodifiableMap(byKey);
|
|
||||||
this.plansByKeyAndAlias = Collections.unmodifiableMap(byKeyAndAlias);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeProcessingPlan require(String processingPlanKey) {
|
|
||||||
String normalized = normalize(processingPlanKey);
|
|
||||||
RuntimeProcessingPlan plan = normalized == null ? null : plansByKeyAndAlias.get(normalized);
|
|
||||||
if (plan == null) {
|
|
||||||
throw new IllegalArgumentException("Unknown runtime processingPlanKey: " + processingPlanKey
|
|
||||||
+ ". Available plans: " + plansByKey.keySet());
|
|
||||||
}
|
|
||||||
return plan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, RuntimeProcessingPlan> plansByKey() {
|
|
||||||
return plansByKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<RuntimeProcessingPlanDescriptorDto> planDescriptors() {
|
|
||||||
return plansByKey.values().stream()
|
|
||||||
.map(RuntimeProcessingPlanDescriptorDto::from)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void putAlias(Map<String, RuntimeProcessingPlan> target, String key, RuntimeProcessingPlan plan) {
|
|
||||||
RuntimeProcessingPlan previous = target.putIfAbsent(key, plan);
|
|
||||||
if (previous != null && previous != plan) {
|
|
||||||
throw new IllegalStateException("Duplicate runtime processing plan alias/key: " + key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalize(String key) {
|
|
||||||
if (key == null || key.isBlank()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return key.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.plan;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.service.RuntimeDriverWorkingTimeScopeProcessingService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Compatibility adapter. Use {@link DriverWorkingTimeRuntimeProcessingPlan}.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false)
|
|
||||||
public class TachographDriverWorkingTimeRuntimeProcessingPlan extends DriverWorkingTimeRuntimeProcessingPlan {
|
|
||||||
|
|
||||||
public TachographDriverWorkingTimeRuntimeProcessingPlan(
|
|
||||||
RuntimeDriverWorkingTimeScopeProcessingService driverWorkingTimeScopeProcessingService
|
|
||||||
) {
|
|
||||||
super(driverWorkingTimeScopeProcessingService);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.profile;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public interface RuntimeEventProcessingProfile {
|
|
||||||
|
|
||||||
String profileKey();
|
|
||||||
|
|
||||||
RuntimeEventPartitioningStrategy defaultPartitioningStrategy();
|
|
||||||
|
|
||||||
default String displayName() {
|
|
||||||
return profileKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
default String description() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
default List<RuntimeEventPartitioningStrategy> supportedPartitioningStrategies() {
|
|
||||||
RuntimeEventPartitioningStrategy defaultStrategy = defaultPartitioningStrategy();
|
|
||||||
return defaultStrategy == null ? List.of() : List.of(defaultStrategy);
|
|
||||||
}
|
|
||||||
|
|
||||||
default Set<String> requiredParameters() {
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
default Set<String> optionalParameters() {
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeEventProcessingResultDto process(RuntimeEventProcessingApiRequest request);
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.profile;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingProfileDescriptorDto;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class RuntimeEventProcessingProfileRegistry {
|
|
||||||
|
|
||||||
private final Map<String, RuntimeEventProcessingProfile> profilesByKey;
|
|
||||||
|
|
||||||
public RuntimeEventProcessingProfileRegistry(List<RuntimeEventProcessingProfile> profiles) {
|
|
||||||
LinkedHashMap<String, RuntimeEventProcessingProfile> byKey = new LinkedHashMap<>();
|
|
||||||
for (RuntimeEventProcessingProfile profile : profiles == null ? List.<RuntimeEventProcessingProfile>of() : profiles) {
|
|
||||||
String key = profile.profileKey();
|
|
||||||
if (key == null || key.isBlank()) {
|
|
||||||
throw new IllegalStateException("Runtime event processing profile returned a blank profileKey: " + profile.getClass().getName());
|
|
||||||
}
|
|
||||||
RuntimeEventProcessingProfile previous = byKey.putIfAbsent(key, profile);
|
|
||||||
if (previous != null) {
|
|
||||||
throw new IllegalStateException("Duplicate runtime event processing profileKey: " + key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.profilesByKey = Collections.unmodifiableMap(byKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeEventProcessingProfile require(String profileKey) {
|
|
||||||
String normalized = profileKey == null ? null : profileKey.trim();
|
|
||||||
RuntimeEventProcessingProfile profile = normalized == null ? null : profilesByKey.get(normalized);
|
|
||||||
if (profile == null) {
|
|
||||||
throw new IllegalArgumentException("Unknown runtime event processing profileKey: " + profileKey
|
|
||||||
+ ". Available profiles: " + profilesByKey.keySet());
|
|
||||||
}
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, RuntimeEventProcessingProfile> profilesByKey() {
|
|
||||||
return profilesByKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<RuntimeEventProcessingProfileDescriptorDto> profileDescriptors() {
|
|
||||||
return profilesByKey.values().stream()
|
|
||||||
.map(RuntimeEventProcessingProfileDescriptorDto::from)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.profile;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingResultDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.partition.RuntimeEventPartitioningStrategy;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingExecutionApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingExecutionResultDto;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.plan.DriverWorkingTimeRuntimeProcessingPlan;
|
|
||||||
import at.procon.eventhub.processing.service.RuntimeDriverWorkingTimeScopeProcessingService;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class TachographDriverEsperRuntimeEventProcessingProfile implements RuntimeEventProcessingProfile {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Legacy compatibility key. New clients should use processingPlanKey=driver-working-time-v1
|
|
||||||
* via /api/eventhub/runtime-processing/executions.
|
|
||||||
*/
|
|
||||||
public static final String PROFILE_KEY = DriverWorkingTimeRuntimeProcessingPlan.LEGACY_PROFILE_ALIAS;
|
|
||||||
|
|
||||||
private final DriverWorkingTimeRuntimeProcessingPlan plan;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public TachographDriverEsperRuntimeEventProcessingProfile(DriverWorkingTimeRuntimeProcessingPlan plan) {
|
|
||||||
this.plan = plan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TachographDriverEsperRuntimeEventProcessingProfile(RuntimeDriverWorkingTimeScopeProcessingService scopeProcessingService) {
|
|
||||||
this(new DriverWorkingTimeRuntimeProcessingPlan(scopeProcessingService));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String profileKey() {
|
|
||||||
return PROFILE_KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeEventPartitioningStrategy defaultPartitioningStrategy() {
|
|
||||||
return plan.defaultPartitioningStrategy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String displayName() {
|
|
||||||
return "Tachograph Driver Esper Processing";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String description() {
|
|
||||||
return "Compatibility adapter for legacy profileKey=" + PROFILE_KEY
|
|
||||||
+ ". New clients should use processingPlanKey=" + plan.processingPlanKey() + ". "
|
|
||||||
+ plan.description();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<RuntimeEventPartitioningStrategy> supportedPartitioningStrategies() {
|
|
||||||
return plan.supportedPartitioningStrategies();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> optionalParameters() {
|
|
||||||
return plan.optionalParameters();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> requiredParameters() {
|
|
||||||
return plan.requiredParameters();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RuntimeEventProcessingResultDto process(RuntimeEventProcessingApiRequest request) {
|
|
||||||
RuntimeProcessingExecutionResultDto executionResult = plan.execute(new RuntimeProcessingExecutionApiRequest(
|
|
||||||
plan.processingPlanKey(),
|
|
||||||
request.scope(),
|
|
||||||
request.partitioning(),
|
|
||||||
List.of(),
|
|
||||||
request.parameters()
|
|
||||||
));
|
|
||||||
return RuntimeEventProcessingResultDto.fromExecution(executionResult, profileKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.support;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Source-neutral support-evidence view used before adapting mixed provider
|
|
||||||
* events into a concrete processing profile. The current tachograph Esper
|
|
||||||
* profile consumes this as geo support evidence when latitude/longitude are
|
|
||||||
* available, while the original provider semantics remain in rawAttributes.
|
|
||||||
*/
|
|
||||||
public record RuntimeSupportEvidenceEvent(
|
|
||||||
String eventId,
|
|
||||||
String sourceFamily,
|
|
||||||
String sourceKind,
|
|
||||||
String eventDomain,
|
|
||||||
String eventType,
|
|
||||||
String lifecycle,
|
|
||||||
String driverKey,
|
|
||||||
String vehicleKey,
|
|
||||||
String registrationKey,
|
|
||||||
OffsetDateTime occurredAt,
|
|
||||||
Long occurredAtEpochSecond,
|
|
||||||
BigDecimal latitude,
|
|
||||||
BigDecimal longitude,
|
|
||||||
String countryCode,
|
|
||||||
String regionCode,
|
|
||||||
String countryFrom,
|
|
||||||
String countryTo,
|
|
||||||
String operation,
|
|
||||||
Long odometerKm,
|
|
||||||
BigDecimal speedKmh,
|
|
||||||
BigDecimal maxSpeedKmh,
|
|
||||||
Map<String, Object> rawAttributes
|
|
||||||
) {
|
|
||||||
public RuntimeSupportEvidenceEvent {
|
|
||||||
rawAttributes = rawAttributes == null ? Map.of() : Map.copyOf(rawAttributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.support;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record RuntimeSupportEvidenceNormalizationResult(
|
|
||||||
List<EventHubEventDto> normalizedEvents,
|
|
||||||
int inputEventCount,
|
|
||||||
int normalizedSupportEvidenceEventCount,
|
|
||||||
int unchangedEventCount,
|
|
||||||
List<String> notes
|
|
||||||
) {
|
|
||||||
public RuntimeSupportEvidenceNormalizationResult {
|
|
||||||
normalizedEvents = normalizedEvents == null ? List.of() : List.copyOf(normalizedEvents);
|
|
||||||
normalizedSupportEvidenceEventCount = Math.max(0, normalizedSupportEvidenceEventCount);
|
|
||||||
unchangedEventCount = Math.max(0, unchangedEventCount);
|
|
||||||
notes = notes == null ? List.of() : List.copyOf(notes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,436 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.support;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.DriverRefDto;
|
|
||||||
import at.procon.eventhub.dto.EventDetailsDto;
|
|
||||||
import at.procon.eventhub.dto.EventDomain;
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.dto.EventLifecycle;
|
|
||||||
import at.procon.eventhub.dto.EventType;
|
|
||||||
import at.procon.eventhub.dto.GeoPointDto;
|
|
||||||
import at.procon.eventhub.dto.VehicleRefDto;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class RuntimeSupportEvidenceNormalizer {
|
|
||||||
|
|
||||||
private static final LinkedHashSet<EventDomain> DIRECT_TACHOGRAPH_SUPPORT_DOMAINS = new LinkedHashSet<>(List.of(
|
|
||||||
EventDomain.POSITION,
|
|
||||||
EventDomain.PLACE,
|
|
||||||
EventDomain.BORDER_CROSSING,
|
|
||||||
EventDomain.LOAD_UNLOAD
|
|
||||||
));
|
|
||||||
|
|
||||||
private final ObjectMapper objectMapper;
|
|
||||||
|
|
||||||
public RuntimeSupportEvidenceNormalizer(ObjectMapper objectMapper) {
|
|
||||||
this.objectMapper = objectMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeSupportEvidenceNormalizationResult normalizeForTachographDriver(
|
|
||||||
String driverKey,
|
|
||||||
List<EventHubEventDto> events
|
|
||||||
) {
|
|
||||||
List<EventHubEventDto> safeEvents = events == null ? List.of() : List.copyOf(events);
|
|
||||||
List<EventHubEventDto> normalizedEvents = new ArrayList<>(safeEvents.size());
|
|
||||||
int normalizedSupportEvidence = 0;
|
|
||||||
int unchanged = 0;
|
|
||||||
for (EventHubEventDto event : safeEvents) {
|
|
||||||
EventHubEventDto normalized = normalizeOneForTachographDriver(driverKey, event);
|
|
||||||
normalizedEvents.add(normalized);
|
|
||||||
if (normalized != event) {
|
|
||||||
normalizedSupportEvidence++;
|
|
||||||
} else {
|
|
||||||
unchanged++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<String> notes = new ArrayList<>();
|
|
||||||
notes.add("Runtime support evidence normalization inspected " + safeEvents.size() + " event(s).");
|
|
||||||
notes.add("Runtime support evidence normalization adapted " + normalizedSupportEvidence
|
|
||||||
+ " support/vehicle event(s) for the tachograph Esper profile.");
|
|
||||||
return new RuntimeSupportEvidenceNormalizationResult(
|
|
||||||
normalizedEvents,
|
|
||||||
safeEvents.size(),
|
|
||||||
normalizedSupportEvidence,
|
|
||||||
unchanged,
|
|
||||||
notes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeSupportEvidenceEvent toSupportEvidenceEvent(String fallbackDriverKey, EventHubEventDto event) {
|
|
||||||
if (event == null || isDriverActivityOrCardUsage(event)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = rawPayload(event);
|
|
||||||
GeoPointDto position = event.position();
|
|
||||||
BigDecimal latitude = position == null ? decimal(raw, "latitude") : position.latitude();
|
|
||||||
BigDecimal longitude = position == null ? decimal(raw, "longitude") : position.longitude();
|
|
||||||
Long odometerKm = firstNonNull(longValue(raw, "odometerKm"), toKilometers(event.odometerM()));
|
|
||||||
return new RuntimeSupportEvidenceEvent(
|
|
||||||
firstNonBlank(text(raw, "supportEventId"), text(raw, "sourceRowId"), event.externalSourceEventId()),
|
|
||||||
sourceFamily(event),
|
|
||||||
firstNonBlank(text(raw, "sourceKind"), sourceKind(event)),
|
|
||||||
event.eventDomain() == null ? null : event.eventDomain().name(),
|
|
||||||
event.eventType() == null ? null : event.eventType().name(),
|
|
||||||
event.lifecycle() == null ? null : event.lifecycle().name(),
|
|
||||||
firstNonBlank(text(raw, "driverKey"), fallbackDriverKey, driverKey(event)),
|
|
||||||
firstNonBlank(text(raw, "vehicleKey"), vehicleKey(event)),
|
|
||||||
firstNonBlank(text(raw, "registrationKey"), registrationKey(event)),
|
|
||||||
event.occurredAt(),
|
|
||||||
event.occurredAt() == null ? null : event.occurredAt().toEpochSecond(),
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
firstNonBlank(text(raw, "country"), detailText(event, "country")),
|
|
||||||
firstNonBlank(text(raw, "region"), detailText(event, "region")),
|
|
||||||
firstNonBlank(text(raw, "countryFrom"), detailText(event, "countryFrom")),
|
|
||||||
firstNonBlank(text(raw, "countryTo"), detailText(event, "countryTo")),
|
|
||||||
firstNonBlank(text(raw, "operation"), detailText(event, "operation")),
|
|
||||||
odometerKm,
|
|
||||||
decimal(raw, "avgSpeedKmh"),
|
|
||||||
decimal(raw, "maxSpeedKmh"),
|
|
||||||
rawAttributes(event, raw)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventHubEventDto normalizeOneForTachographDriver(String fallbackDriverKey, EventHubEventDto event) {
|
|
||||||
if (event == null || isDriverActivityOrCardUsage(event)) {
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
RuntimeSupportEvidenceEvent support = toSupportEvidenceEvent(fallbackDriverKey, event);
|
|
||||||
if (support == null || !hasGeoOrOdometerEvidence(support)) {
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
EventDomain normalizedDomain = normalizedDomain(event);
|
|
||||||
EventType normalizedType = normalizedType(normalizedDomain, event.eventType());
|
|
||||||
EventLifecycle normalizedLifecycle = normalizedLifecycle(normalizedDomain, event.lifecycle());
|
|
||||||
JsonNode payload = normalizedPayload(fallbackDriverKey, event, support, normalizedDomain);
|
|
||||||
EventDetailsDto details = normalizedDetails(event, support, normalizedDomain);
|
|
||||||
DriverRefDto driverRef = event.driverRef();
|
|
||||||
if ((driverRef == null || !driverRef.hasAnyReference()) && support.driverKey() != null) {
|
|
||||||
driverRef = new DriverRefDto(support.driverKey(), null);
|
|
||||||
}
|
|
||||||
return new EventHubEventDto(
|
|
||||||
event.eventId(),
|
|
||||||
event.externalSourceEventId(),
|
|
||||||
driverRef,
|
|
||||||
event.vehicleRef(),
|
|
||||||
event.occurredAt(),
|
|
||||||
event.receivedPartnerAt(),
|
|
||||||
event.receivedHubAt(),
|
|
||||||
normalizedDomain,
|
|
||||||
normalizedType,
|
|
||||||
normalizedLifecycle,
|
|
||||||
event.odometerM(),
|
|
||||||
normalizedPosition(event, support),
|
|
||||||
details,
|
|
||||||
event.sourcePackageRef(),
|
|
||||||
payload,
|
|
||||||
event.manualEntry(),
|
|
||||||
event.packageInfo()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private GeoPointDto normalizedPosition(EventHubEventDto original, RuntimeSupportEvidenceEvent support) {
|
|
||||||
if (original != null && original.position() != null) {
|
|
||||||
return original.position();
|
|
||||||
}
|
|
||||||
if (support == null || support.latitude() == null || support.longitude() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new GeoPointDto(support.latitude(), support.longitude());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isDriverActivityOrCardUsage(EventHubEventDto event) {
|
|
||||||
if (event == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (event.eventDomain() == EventDomain.DRIVER_ACTIVITY) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return event.eventDomain() == EventDomain.DRIVER_CARD
|
|
||||||
&& (event.eventType() == EventType.CARD_INSERTED || event.eventType() == EventType.CARD_WITHDRAWN);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasGeoOrOdometerEvidence(RuntimeSupportEvidenceEvent support) {
|
|
||||||
return support != null
|
|
||||||
&& ((support.latitude() != null && support.longitude() != null) || support.odometerKm() != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventDomain normalizedDomain(EventHubEventDto event) {
|
|
||||||
if (event.eventDomain() != null && DIRECT_TACHOGRAPH_SUPPORT_DOMAINS.contains(event.eventDomain())) {
|
|
||||||
return event.eventDomain();
|
|
||||||
}
|
|
||||||
return EventDomain.POSITION;
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventType normalizedType(EventDomain normalizedDomain, EventType originalType) {
|
|
||||||
if (normalizedDomain == EventDomain.POSITION) {
|
|
||||||
return EventType.POSITION_RECORDED;
|
|
||||||
}
|
|
||||||
if (normalizedDomain == EventDomain.PLACE) {
|
|
||||||
return EventType.WORKING_DAY_PLACE_RECORDED;
|
|
||||||
}
|
|
||||||
if (normalizedDomain == EventDomain.BORDER_CROSSING) {
|
|
||||||
return originalType == null ? EventType.BORDER_INBOUND : originalType;
|
|
||||||
}
|
|
||||||
if (normalizedDomain == EventDomain.LOAD_UNLOAD) {
|
|
||||||
return originalType == null ? EventType.LOAD_UNLOAD : originalType;
|
|
||||||
}
|
|
||||||
return originalType == null ? EventType.UNKNOWN_EVENT : originalType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventLifecycle normalizedLifecycle(EventDomain normalizedDomain, EventLifecycle originalLifecycle) {
|
|
||||||
if (normalizedDomain == EventDomain.POSITION || normalizedDomain == EventDomain.PLACE) {
|
|
||||||
return EventLifecycle.SNAPSHOT;
|
|
||||||
}
|
|
||||||
return originalLifecycle == null ? EventLifecycle.SNAPSHOT : originalLifecycle;
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventDetailsDto normalizedDetails(
|
|
||||||
EventHubEventDto original,
|
|
||||||
RuntimeSupportEvidenceEvent support,
|
|
||||||
EventDomain normalizedDomain
|
|
||||||
) {
|
|
||||||
Map<String, Object> attributes = new LinkedHashMap<>();
|
|
||||||
put(attributes, "normalizedSupportEvidence", true);
|
|
||||||
put(attributes, "normalizedForProfile", "tachograph-driver-esper-v1");
|
|
||||||
put(attributes, "originalEventDomain", support.eventDomain());
|
|
||||||
put(attributes, "originalEventType", support.eventType());
|
|
||||||
put(attributes, "originalLifecycle", support.lifecycle());
|
|
||||||
put(attributes, "sourceFamily", support.sourceFamily());
|
|
||||||
put(attributes, "sourceKind", support.sourceKind());
|
|
||||||
put(attributes, "country", support.countryCode());
|
|
||||||
put(attributes, "region", support.regionCode());
|
|
||||||
put(attributes, "countryFrom", support.countryFrom());
|
|
||||||
put(attributes, "countryTo", support.countryTo());
|
|
||||||
put(attributes, "operation", support.operation());
|
|
||||||
if (original.eventDetails() != null && original.eventDetails().attributes() != null) {
|
|
||||||
attributes.put("originalAttributes", original.eventDetails().attributes());
|
|
||||||
}
|
|
||||||
String type = normalizedDomain == EventDomain.POSITION ? "POSITION" : normalizedDomain.name();
|
|
||||||
return new EventDetailsDto(type, objectMapper.valueToTree(attributes));
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonNode normalizedPayload(
|
|
||||||
String fallbackDriverKey,
|
|
||||||
EventHubEventDto original,
|
|
||||||
RuntimeSupportEvidenceEvent support,
|
|
||||||
EventDomain normalizedDomain
|
|
||||||
) {
|
|
||||||
ObjectNode root = objectMapper.createObjectNode();
|
|
||||||
ObjectNode raw = root.putObject("raw");
|
|
||||||
JsonNode originalRaw = rawPayload(original);
|
|
||||||
if (originalRaw != null && originalRaw.isObject()) {
|
|
||||||
originalRaw.fields().forEachRemaining(entry -> raw.set(entry.getKey(), entry.getValue()));
|
|
||||||
}
|
|
||||||
put(raw, "normalizedSupportEvidence", true);
|
|
||||||
put(raw, "normalizedForProfile", "tachograph-driver-esper-v1");
|
|
||||||
put(raw, "supportEventId", support.eventId());
|
|
||||||
put(raw, "supportEventDomain", support.eventDomain());
|
|
||||||
put(raw, "supportEventType", support.eventType());
|
|
||||||
put(raw, "supportEventLifecycle", support.lifecycle());
|
|
||||||
put(raw, "originalEventDomain", support.eventDomain());
|
|
||||||
put(raw, "originalEventType", support.eventType());
|
|
||||||
put(raw, "originalLifecycle", support.lifecycle());
|
|
||||||
put(raw, "normalizedEventDomain", normalizedDomain.name());
|
|
||||||
put(raw, "driverKey", firstNonBlank(support.driverKey(), fallbackDriverKey));
|
|
||||||
put(raw, "vehicleKey", support.vehicleKey());
|
|
||||||
put(raw, "registrationKey", support.registrationKey());
|
|
||||||
put(raw, "sourceFamily", support.sourceFamily());
|
|
||||||
put(raw, "sourceKind", support.sourceKind());
|
|
||||||
put(raw, "country", support.countryCode());
|
|
||||||
put(raw, "region", support.regionCode());
|
|
||||||
put(raw, "countryFrom", support.countryFrom());
|
|
||||||
put(raw, "countryTo", support.countryTo());
|
|
||||||
put(raw, "operation", support.operation());
|
|
||||||
put(raw, "odometerKm", support.odometerKm());
|
|
||||||
put(raw, "avgSpeedKmh", support.speedKmh());
|
|
||||||
put(raw, "maxSpeedKmh", support.maxSpeedKmh());
|
|
||||||
put(raw, "latitude", support.latitude());
|
|
||||||
put(raw, "longitude", support.longitude());
|
|
||||||
put(raw, "rawRecordPath", firstNonBlank(text(originalRaw, "rawRecordPath"), original.externalSourceEventId()));
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> rawAttributes(EventHubEventDto event, JsonNode raw) {
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
|
||||||
put(result, "externalSourceEventId", event == null ? null : event.externalSourceEventId());
|
|
||||||
put(result, "eventDomain", event == null || event.eventDomain() == null ? null : event.eventDomain().name());
|
|
||||||
put(result, "eventType", event == null || event.eventType() == null ? null : event.eventType().name());
|
|
||||||
put(result, "lifecycle", event == null || event.lifecycle() == null ? null : event.lifecycle().name());
|
|
||||||
if (raw != null) {
|
|
||||||
result.put("raw", raw);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String sourceFamily(EventHubEventDto event) {
|
|
||||||
if (event == null || event.packageInfo() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return event.packageInfo().eventFamily();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String sourceKind(EventHubEventDto event) {
|
|
||||||
if (event == null || event.packageInfo() == null || event.packageInfo().eventSource() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return event.packageInfo().eventSource().sourceKind();
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonNode rawPayload(EventHubEventDto event) {
|
|
||||||
if (event == null || event.payload() == null || event.payload().isNull() || event.payload().isMissingNode()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = event.payload().get("raw");
|
|
||||||
return raw == null || raw.isNull() ? event.payload() : raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String driverKey(EventHubEventDto event) {
|
|
||||||
if (event == null || event.driverRef() == null || !event.driverRef().hasAnyReference()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return event.driverRef().stableKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String vehicleKey(EventHubEventDto event) {
|
|
||||||
JsonNode raw = rawPayload(event);
|
|
||||||
String rawVehicleKey = text(raw, "vehicleKey");
|
|
||||||
if (rawVehicleKey != null) {
|
|
||||||
return rawVehicleKey;
|
|
||||||
}
|
|
||||||
VehicleRefDto vehicleRef = event == null ? null : event.vehicleRef();
|
|
||||||
if (vehicleRef == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (vehicleRef.vin() != null) {
|
|
||||||
return vehicleRef.vin();
|
|
||||||
}
|
|
||||||
if (vehicleRef.sourceVehicleEntityId() != null) {
|
|
||||||
return vehicleRef.sourceVehicleEntityId();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String registrationKey(EventHubEventDto event) {
|
|
||||||
JsonNode raw = rawPayload(event);
|
|
||||||
String rawRegistrationKey = text(raw, "registrationKey");
|
|
||||||
if (rawRegistrationKey != null) {
|
|
||||||
return rawRegistrationKey;
|
|
||||||
}
|
|
||||||
VehicleRefDto vehicleRef = event == null ? null : event.vehicleRef();
|
|
||||||
if (vehicleRef == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (vehicleRef.vehicleRegistration() != null && vehicleRef.vehicleRegistration().hasValue()) {
|
|
||||||
return vehicleRef.vehicleRegistration().stableKey();
|
|
||||||
}
|
|
||||||
return vehicleRef.sourceRegistrationEntityId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String detailText(EventHubEventDto event, String field) {
|
|
||||||
if (event == null || event.eventDetails() == null || event.eventDetails().attributes() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = event.eventDetails().attributes().get(field);
|
|
||||||
return value == null || value.isNull() ? null : value.asText(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String text(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String text = value.asText(null);
|
|
||||||
return text == null || text.isBlank() ? null : text.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long longValue(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return value.isNumber() ? value.asLong() : Long.parseLong(value.asText());
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal decimal(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (value.isNumber()) {
|
|
||||||
return value.decimalValue();
|
|
||||||
}
|
|
||||||
String text = value.asText(null);
|
|
||||||
if (text == null || text.isBlank()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return new BigDecimal(text.trim());
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long toKilometers(Long meters) {
|
|
||||||
return meters == null ? null : meters / 1_000L;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
private final <T> T firstNonNull(T... values) {
|
|
||||||
if (values == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (T value : values) {
|
|
||||||
if (value != null) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String firstNonBlank(String... values) {
|
|
||||||
if (values == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (String value : values) {
|
|
||||||
if (value != null && !value.isBlank()) {
|
|
||||||
return value.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void put(ObjectNode node, String field, Object value) {
|
|
||||||
if (node != null && field != null && value != null) {
|
|
||||||
node.set(field, objectMapper.valueToTree(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void put(Map<String, Object> target, String field, Object value) {
|
|
||||||
if (target != null && field != null && value != null) {
|
|
||||||
target.put(field, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.validation;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record RuntimeMixedSourceEvidencePartitionValidationDto(
|
|
||||||
String partitionKey,
|
|
||||||
String status,
|
|
||||||
int directDriverEventCount,
|
|
||||||
int attachedVehicleEvidenceEventCount,
|
|
||||||
int ignoredVehicleEvidenceEventCount,
|
|
||||||
int mergedEventCount,
|
|
||||||
int normalizedSupportEvidenceEventCount,
|
|
||||||
int unchangedEventCount,
|
|
||||||
int supportGeoEventCount,
|
|
||||||
boolean partitionDebugPresent,
|
|
||||||
List<String> notes,
|
|
||||||
List<String> warnings
|
|
||||||
) {
|
|
||||||
public RuntimeMixedSourceEvidencePartitionValidationDto {
|
|
||||||
notes = notes == null ? List.of() : List.copyOf(notes);
|
|
||||||
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.validation;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.dto.RuntimeEventProcessingApiRequest;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public record RuntimeMixedSourceEvidenceValidationApiRequest(
|
|
||||||
RuntimeEventProcessingApiRequest processingRequest,
|
|
||||||
Set<String> expectedPartitionKeys,
|
|
||||||
Integer minimumAttachedVehicleEvidenceEvents,
|
|
||||||
Integer minimumNormalizedSupportEvidenceEvents,
|
|
||||||
Boolean failWhenPartitionDebugMissing
|
|
||||||
) {
|
|
||||||
public RuntimeMixedSourceEvidenceValidationApiRequest {
|
|
||||||
if (processingRequest == null) {
|
|
||||||
throw new IllegalArgumentException("processingRequest must not be null.");
|
|
||||||
}
|
|
||||||
expectedPartitionKeys = expectedPartitionKeys == null ? Set.of() : Set.copyOf(expectedPartitionKeys);
|
|
||||||
if (minimumAttachedVehicleEvidenceEvents != null && minimumAttachedVehicleEvidenceEvents < 0) {
|
|
||||||
throw new IllegalArgumentException("minimumAttachedVehicleEvidenceEvents must not be negative.");
|
|
||||||
}
|
|
||||||
if (minimumNormalizedSupportEvidenceEvents != null && minimumNormalizedSupportEvidenceEvents < 0) {
|
|
||||||
throw new IllegalArgumentException("minimumNormalizedSupportEvidenceEvents must not be negative.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int minimumAttachedVehicleEvidenceEventsOrDefault() {
|
|
||||||
return minimumAttachedVehicleEvidenceEvents == null ? 0 : Math.max(0, minimumAttachedVehicleEvidenceEvents);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int minimumNormalizedSupportEvidenceEventsOrDefault() {
|
|
||||||
return minimumNormalizedSupportEvidenceEvents == null ? 0 : Math.max(0, minimumNormalizedSupportEvidenceEvents);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean failWhenPartitionDebugMissingOrDefault() {
|
|
||||||
return failWhenPartitionDebugMissing == null || failWhenPartitionDebugMissing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.validation;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record RuntimeMixedSourceEvidenceValidationResultDto(
|
|
||||||
String profileKey,
|
|
||||||
String status,
|
|
||||||
int partitionCount,
|
|
||||||
int totalAttachedVehicleEvidenceEventCount,
|
|
||||||
int totalIgnoredVehicleEvidenceEventCount,
|
|
||||||
int totalNormalizedSupportEvidenceEventCount,
|
|
||||||
int totalSupportGeoEventCount,
|
|
||||||
Map<String, RuntimeMixedSourceEvidencePartitionValidationDto> partitions,
|
|
||||||
List<String> notes,
|
|
||||||
List<String> warnings
|
|
||||||
) {
|
|
||||||
public RuntimeMixedSourceEvidenceValidationResultDto {
|
|
||||||
partitions = partitions == null ? Map.of() : Collections.unmodifiableMap(new LinkedHashMap<>(partitions));
|
|
||||||
notes = notes == null ? List.of() : List.copyOf(notes);
|
|
||||||
warnings = warnings == null ? List.of() : List.copyOf(warnings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.validation;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.dto.RuntimeDriverPartitionDebugDto;
|
|
||||||
import at.procon.eventhub.processing.dto.RuntimeSupportEvidenceNormalizationDebugDto;
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeDerivedProjectionResultDto;
|
|
||||||
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 java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class RuntimeMixedSourceEvidenceValidationService {
|
|
||||||
|
|
||||||
private final RuntimeEventProcessingService runtimeEventProcessingService;
|
|
||||||
|
|
||||||
public RuntimeMixedSourceEvidenceValidationService(RuntimeEventProcessingService runtimeEventProcessingService) {
|
|
||||||
this.runtimeEventProcessingService = runtimeEventProcessingService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeMixedSourceEvidenceValidationResultDto validate(RuntimeMixedSourceEvidenceValidationApiRequest request) {
|
|
||||||
RuntimeEventProcessingApiRequest debugRequest = withDebugEnabled(request.processingRequest());
|
|
||||||
RuntimeEventProcessingResultDto runtimeResult = runtimeEventProcessingService.process(debugRequest);
|
|
||||||
|
|
||||||
LinkedHashMap<String, RuntimeMixedSourceEvidencePartitionValidationDto> partitionResults = new LinkedHashMap<>();
|
|
||||||
List<String> warnings = new ArrayList<>(runtimeResult.warnings());
|
|
||||||
List<String> notes = new ArrayList<>(runtimeResult.notes());
|
|
||||||
notes.add("Mixed-source evidence validation executed runtime event processing with partition debug enabled.");
|
|
||||||
notes.add("Validation checks vehicle-only evidence attachment and support-evidence normalization counts; it does not compare legal tachograph result semantics.");
|
|
||||||
|
|
||||||
int totalAttached = 0;
|
|
||||||
int totalIgnored = 0;
|
|
||||||
int totalNormalized = 0;
|
|
||||||
int totalSupportGeo = 0;
|
|
||||||
|
|
||||||
for (Map.Entry<String, RuntimeEventProcessingPartitionResultDto> entry : runtimeResult.partitionResults().entrySet()) {
|
|
||||||
RuntimeMixedSourceEvidencePartitionValidationDto partition = partitionValidation(
|
|
||||||
entry.getKey(),
|
|
||||||
entry.getValue(),
|
|
||||||
request.failWhenPartitionDebugMissingOrDefault()
|
|
||||||
);
|
|
||||||
partitionResults.put(entry.getKey(), partition);
|
|
||||||
totalAttached += partition.attachedVehicleEvidenceEventCount();
|
|
||||||
totalIgnored += partition.ignoredVehicleEvidenceEventCount();
|
|
||||||
totalNormalized += partition.normalizedSupportEvidenceEventCount();
|
|
||||||
totalSupportGeo += partition.supportGeoEventCount();
|
|
||||||
warnings.addAll(partition.warnings());
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> missingPartitions = new LinkedHashSet<>(request.expectedPartitionKeys());
|
|
||||||
missingPartitions.removeAll(partitionResults.keySet());
|
|
||||||
for (String missingPartition : missingPartitions) {
|
|
||||||
warnings.add("Expected partition " + missingPartition + " was not returned by runtime event processing.");
|
|
||||||
}
|
|
||||||
if (totalAttached < request.minimumAttachedVehicleEvidenceEventsOrDefault()) {
|
|
||||||
warnings.add("Attached vehicle evidence count " + totalAttached + " is below expected minimum "
|
|
||||||
+ request.minimumAttachedVehicleEvidenceEventsOrDefault() + ".");
|
|
||||||
}
|
|
||||||
if (totalNormalized < request.minimumNormalizedSupportEvidenceEventsOrDefault()) {
|
|
||||||
warnings.add("Normalized support evidence count " + totalNormalized + " is below expected minimum "
|
|
||||||
+ request.minimumNormalizedSupportEvidenceEventsOrDefault() + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean failed = !missingPartitions.isEmpty()
|
|
||||||
|| totalAttached < request.minimumAttachedVehicleEvidenceEventsOrDefault()
|
|
||||||
|| totalNormalized < request.minimumNormalizedSupportEvidenceEventsOrDefault()
|
|
||||||
|| partitionResults.values().stream().anyMatch(partition -> "FAILED".equals(partition.status()));
|
|
||||||
|
|
||||||
return new RuntimeMixedSourceEvidenceValidationResultDto(
|
|
||||||
runtimeResult.profileKey(),
|
|
||||||
failed ? "FAILED" : "PASSED",
|
|
||||||
partitionResults.size(),
|
|
||||||
totalAttached,
|
|
||||||
totalIgnored,
|
|
||||||
totalNormalized,
|
|
||||||
totalSupportGeo,
|
|
||||||
partitionResults,
|
|
||||||
notes,
|
|
||||||
warnings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RuntimeMixedSourceEvidencePartitionValidationDto partitionValidation(
|
|
||||||
String partitionKey,
|
|
||||||
RuntimeEventProcessingPartitionResultDto partitionResult,
|
|
||||||
boolean failWhenPartitionDebugMissing
|
|
||||||
) {
|
|
||||||
UnifiedRuntimeDerivedProjectionResultDto result = partitionResult.result() instanceof UnifiedRuntimeDerivedProjectionResultDto derived
|
|
||||||
? derived
|
|
||||||
: null;
|
|
||||||
RuntimeDriverPartitionDebugDto debug = result == null ? null : result.partitionDebug();
|
|
||||||
RuntimeSupportEvidenceNormalizationDebugDto normalization = result == null ? null : result.supportEvidenceNormalization();
|
|
||||||
|
|
||||||
List<String> notes = new ArrayList<>();
|
|
||||||
List<String> warnings = new ArrayList<>();
|
|
||||||
if (result == null) {
|
|
||||||
warnings.add("Partition " + partitionKey + " did not return UnifiedRuntimeDerivedProjectionResultDto; resultType="
|
|
||||||
+ partitionResult.resultType() + ".");
|
|
||||||
}
|
|
||||||
if (debug == null) {
|
|
||||||
String message = "Partition " + partitionKey + " has no partition debug metadata.";
|
|
||||||
if (failWhenPartitionDebugMissing) {
|
|
||||||
warnings.add(message);
|
|
||||||
} else {
|
|
||||||
notes.add(message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
notes.addAll(debug.notes());
|
|
||||||
warnings.addAll(debug.warnings());
|
|
||||||
}
|
|
||||||
if (normalization == null) {
|
|
||||||
warnings.add("Partition " + partitionKey + " has no support-evidence normalization metadata.");
|
|
||||||
} else {
|
|
||||||
notes.addAll(normalization.notes());
|
|
||||||
}
|
|
||||||
|
|
||||||
int directDriverEventCount = debug == null ? (result == null ? 0 : result.driverSeedEventCount()) : debug.directDriverEventCount();
|
|
||||||
int attached = debug == null ? (result == null ? 0 : result.expandedVehicleEventCount()) : debug.attachedVehicleEvidenceEventCount();
|
|
||||||
int ignored = debug == null ? 0 : debug.ignoredVehicleEvidenceEventCount();
|
|
||||||
int merged = debug == null ? (result == null ? 0 : result.mergedEventCount()) : debug.mergedEventCount();
|
|
||||||
int normalized = normalization == null ? 0 : normalization.normalizedSupportEvidenceEventCount();
|
|
||||||
int unchanged = normalization == null ? 0 : normalization.unchangedEventCount();
|
|
||||||
int supportGeo = result == null || result.projection() == null ? 0 : result.projection().supportGeoEventCount();
|
|
||||||
|
|
||||||
String status = warnings.isEmpty() || (!failWhenPartitionDebugMissing && debug == null && warnings.size() == 1)
|
|
||||||
? "PASSED"
|
|
||||||
: "FAILED";
|
|
||||||
return new RuntimeMixedSourceEvidencePartitionValidationDto(
|
|
||||||
partitionKey,
|
|
||||||
status,
|
|
||||||
directDriverEventCount,
|
|
||||||
attached,
|
|
||||||
ignored,
|
|
||||||
merged,
|
|
||||||
normalized,
|
|
||||||
unchanged,
|
|
||||||
supportGeo,
|
|
||||||
debug != null,
|
|
||||||
notes,
|
|
||||||
warnings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RuntimeEventProcessingApiRequest withDebugEnabled(RuntimeEventProcessingApiRequest request) {
|
|
||||||
RuntimeEventPartitioningApiRequest partitioning = request.partitioning();
|
|
||||||
RuntimeEventPartitioningApiRequest debugPartitioning = new RuntimeEventPartitioningApiRequest(
|
|
||||||
partitioning.strategy(),
|
|
||||||
partitioning.partitionKeys(),
|
|
||||||
partitioning.includeAllPartitions(),
|
|
||||||
partitioning.driverKeys(),
|
|
||||||
partitioning.includeAllDrivers(),
|
|
||||||
partitioning.vehicleKeys(),
|
|
||||||
partitioning.includeAllVehicles(),
|
|
||||||
partitioning.attachVehicleEvidence(),
|
|
||||||
partitioning.vehicleEvidencePaddingMinutes(),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
Map<String, Object> parameters = new LinkedHashMap<>(request.parameters());
|
|
||||||
parameters.put("includePartitionDebug", true);
|
|
||||||
return new RuntimeEventProcessingApiRequest(
|
|
||||||
request.profileKey(),
|
|
||||||
request.scope(),
|
|
||||||
debugPartitioning,
|
|
||||||
parameters
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.validation;
|
|
||||||
|
|
||||||
public record RuntimeTachographParityCategoryComparisonDto(
|
|
||||||
String category,
|
|
||||||
int fileSessionCount,
|
|
||||||
int runtimeCount,
|
|
||||||
boolean equal
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,393 +0,0 @@
|
||||||
package at.procon.eventhub.processing.eventprocessing.validation;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeDerivedProjectionResultDto;
|
|
||||||
import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto;
|
|
||||||
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) {
|
|
||||||
return projection == null ? empty() : from(projection.toDriverWorkingTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
static ProjectionCounts from(DriverWorkingTimeProcessingResultDto 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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
package at.procon.eventhub.processing.model;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
public record RuntimeDriverVehicleEvidenceAttachmentResult(
|
|
||||||
String driverKey,
|
|
||||||
List<EventHubEventDto> directDriverEvents,
|
|
||||||
List<EventHubEventDto> attachedVehicleEvidenceEvents,
|
|
||||||
List<EventHubEventDto> mergedEvents,
|
|
||||||
int vehicleUsageIntervalCount,
|
|
||||||
int candidateVehicleEvidenceEventCount,
|
|
||||||
int ignoredVehicleEvidenceEventCount,
|
|
||||||
List<RuntimeVehicleUsageIntervalDebugDto> vehicleUsageIntervals,
|
|
||||||
List<RuntimeVehicleEvidenceAttachmentDecisionDto> vehicleEvidenceDecisions,
|
|
||||||
List<String> notes,
|
|
||||||
List<String> warnings
|
|
||||||
) {
|
|
||||||
public RuntimeDriverVehicleEvidenceAttachmentResult {
|
|
||||||
directDriverEvents = directDriverEvents == null ? List.of() : List.copyOf(directDriverEvents);
|
|
||||||
attachedVehicleEvidenceEvents = attachedVehicleEvidenceEvents == null ? List.of() : List.copyOf(attachedVehicleEvidenceEvents);
|
|
||||||
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);
|
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -36,6 +36,9 @@ public record UnifiedDriverEventsRequest(
|
||||||
}
|
}
|
||||||
if (sourceFamily == UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION) {
|
if (sourceFamily == UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION) {
|
||||||
Objects.requireNonNull(sessionId, "sessionId must not be null");
|
Objects.requireNonNull(sessionId, "sessionId must not be null");
|
||||||
|
if (driverKey == null) {
|
||||||
|
throw new IllegalArgumentException("driverKey must not be blank");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (tenantKey == null) {
|
if (tenantKey == null) {
|
||||||
throw new IllegalArgumentException("tenantKey must not be blank");
|
throw new IllegalArgumentException("tenantKey must not be blank");
|
||||||
|
|
@ -47,10 +50,6 @@ public record UnifiedDriverEventsRequest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* File-session requests may omit driverKey when the caller intentionally wants
|
|
||||||
* to load all drivers from the session and partition them later in runtime scope processing.
|
|
||||||
*/
|
|
||||||
public static UnifiedDriverEventsRequest forTachographFileSession(
|
public static UnifiedDriverEventsRequest forTachographFileSession(
|
||||||
UUID sessionId,
|
UUID sessionId,
|
||||||
String driverKey,
|
String driverKey,
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,15 @@ package at.procon.eventhub.processing.model;
|
||||||
|
|
||||||
import at.procon.eventhub.reference.DriverCardNumberNormalizer;
|
import at.procon.eventhub.reference.DriverCardNumberNormalizer;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public record UnifiedRuntimeProcessingRequest(
|
public record UnifiedRuntimeProcessingRequest(
|
||||||
UUID sessionId,
|
UUID sessionId,
|
||||||
List<UUID> sessionIds,
|
|
||||||
UUID compositeSessionId,
|
|
||||||
String tenantKey,
|
String tenantKey,
|
||||||
Set<UnifiedEventSourceFamily> sourceFamilies,
|
Set<UnifiedEventSourceFamily> sourceFamilies,
|
||||||
UnifiedRuntimeEventBackend eventBackend,
|
UnifiedRuntimeEventBackend eventBackend,
|
||||||
String driverKey,
|
String driverKey,
|
||||||
Set<String> driverKeys,
|
|
||||||
boolean includeAllDrivers,
|
|
||||||
Set<String> vehicleKeys,
|
|
||||||
boolean includeAllVehicles,
|
|
||||||
String driverSourceEntityId,
|
String driverSourceEntityId,
|
||||||
String driverCardNation,
|
String driverCardNation,
|
||||||
String driverCardNumber,
|
String driverCardNumber,
|
||||||
|
|
@ -29,64 +20,32 @@ public record UnifiedRuntimeProcessingRequest(
|
||||||
int vehicleExpansionPaddingMinutes
|
int vehicleExpansionPaddingMinutes
|
||||||
) {
|
) {
|
||||||
public UnifiedRuntimeProcessingRequest {
|
public UnifiedRuntimeProcessingRequest {
|
||||||
if (sourceFamilies == null || sourceFamilies.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("sourceFamilies must not be empty");
|
|
||||||
}
|
|
||||||
sourceFamilies = Set.copyOf(sourceFamilies);
|
|
||||||
eventBackend = eventBackend == null ? UnifiedRuntimeEventBackend.SOURCE_DB : eventBackend;
|
|
||||||
sessionIds = normalizeSessionIds(sessionId, sessionIds);
|
|
||||||
if (sessionId == null && !sessionIds.isEmpty()) {
|
|
||||||
sessionId = sessionIds.get(0);
|
|
||||||
}
|
|
||||||
if (compositeSessionId != null && !sessionIds.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Use either compositeSessionId or sessionId/sessionIds, not both.");
|
|
||||||
}
|
|
||||||
driverKey = normalize(driverKey);
|
driverKey = normalize(driverKey);
|
||||||
driverKeys = normalizeStrings(driverKeys);
|
|
||||||
if (driverKey != null) {
|
|
||||||
LinkedHashSet<String> mergedDriverKeys = new LinkedHashSet<>(driverKeys);
|
|
||||||
mergedDriverKeys.add(driverKey);
|
|
||||||
driverKeys = Set.copyOf(mergedDriverKeys);
|
|
||||||
}
|
|
||||||
if (driverKey == null && driverKeys.size() == 1) {
|
|
||||||
driverKey = driverKeys.iterator().next();
|
|
||||||
}
|
|
||||||
vehicleKeys = normalizeStrings(vehicleKeys);
|
|
||||||
tenantKey = normalize(tenantKey);
|
tenantKey = normalize(tenantKey);
|
||||||
driverSourceEntityId = normalize(driverSourceEntityId);
|
driverSourceEntityId = normalize(driverSourceEntityId);
|
||||||
driverCardNation = normalizeUpper(driverCardNation);
|
driverCardNation = normalizeUpper(driverCardNation);
|
||||||
driverCardNumber = normalizeDriverCardNumber(driverCardNumber);
|
driverCardNumber = normalizeDriverCardNumber(driverCardNumber);
|
||||||
boolean includesFileSession = sourceFamilies.contains(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION);
|
boolean includesFileSession = sourceFamilies != null && sourceFamilies.contains(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION);
|
||||||
boolean includesExternalDb = sourceFamilies.stream()
|
boolean includesExternalDb = sourceFamilies != null && sourceFamilies.stream()
|
||||||
.anyMatch(family -> family == UnifiedEventSourceFamily.TACHOGRAPH_DB || family == UnifiedEventSourceFamily.YELLOWFOX_DB);
|
.anyMatch(family -> family == UnifiedEventSourceFamily.TACHOGRAPH_DB || family == UnifiedEventSourceFamily.YELLOWFOX_DB);
|
||||||
if (includesFileSession && eventBackend == UnifiedRuntimeEventBackend.EVENTHUB_DB) {
|
|
||||||
throw new IllegalArgumentException("TACHOGRAPH_FILE_SESSION runtime processing currently supports SOURCE_DB backend only.");
|
|
||||||
}
|
|
||||||
if (tenantKey == null) {
|
if (tenantKey == null) {
|
||||||
if (!includesFileSession || includesExternalDb) {
|
if (!includesFileSession || includesExternalDb) {
|
||||||
throw new IllegalArgumentException("tenantKey must not be blank");
|
throw new IllegalArgumentException("tenantKey must not be blank");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (includesFileSession && compositeSessionId == null && sessionIds.isEmpty()) {
|
if (sourceFamilies == null || sourceFamilies.isEmpty()) {
|
||||||
throw new IllegalArgumentException("sessionId, sessionIds or compositeSessionId must be provided when TACHOGRAPH_FILE_SESSION is selected.");
|
throw new IllegalArgumentException("sourceFamilies must not be empty");
|
||||||
}
|
}
|
||||||
if (includesFileSession && driverKey == null && driverKeys.isEmpty() && !includeAllDrivers) {
|
sourceFamilies = Set.copyOf(sourceFamilies);
|
||||||
throw new IllegalArgumentException("driverKey, driverKeys or includeAllDrivers must be provided when TACHOGRAPH_FILE_SESSION is selected.");
|
eventBackend = eventBackend == null ? UnifiedRuntimeEventBackend.SOURCE_DB : eventBackend;
|
||||||
|
if (includesFileSession && sessionId == null) {
|
||||||
|
throw new IllegalArgumentException("sessionId must not be null when TACHOGRAPH_FILE_SESSION is selected.");
|
||||||
}
|
}
|
||||||
if (includesExternalDb && driverSourceEntityId == null && driverCardNumber == null
|
if (includesFileSession && driverKey == null) {
|
||||||
&& driverKeys.isEmpty() && !includeAllDrivers) {
|
throw new IllegalArgumentException("driverKey must not be blank when TACHOGRAPH_FILE_SESSION is selected.");
|
||||||
throw new IllegalArgumentException("At least one driver selector, driverKeys or includeAllDrivers must be provided.");
|
|
||||||
}
|
}
|
||||||
if (includesExternalDb && (includeAllDrivers || !driverKeys.isEmpty())
|
if (includesExternalDb && driverSourceEntityId == null && driverCardNumber == null) {
|
||||||
&& (occurredFrom == null || occurredTo == null)) {
|
throw new IllegalArgumentException("At least one driver selector must be provided.");
|
||||||
throw new IllegalArgumentException("occurredFrom and occurredTo are required when loading broad external DB runtime scopes.");
|
|
||||||
}
|
|
||||||
if (eventBackend == UnifiedRuntimeEventBackend.EVENTHUB_DB
|
|
||||||
&& includesExternalDb
|
|
||||||
&& driverSourceEntityId == null
|
|
||||||
&& driverCardNumber == null
|
|
||||||
&& (includeAllDrivers || !driverKeys.isEmpty())) {
|
|
||||||
throw new IllegalArgumentException("Broad multi-driver EVENTHUB_DB runtime scopes are not supported yet; provide a concrete EventHub driver selector or use SOURCE_DB.");
|
|
||||||
}
|
}
|
||||||
if (occurredFrom != null && occurredTo != null && occurredTo.isBefore(occurredFrom)) {
|
if (occurredFrom != null && occurredTo != null && occurredTo.isBefore(occurredFrom)) {
|
||||||
throw new IllegalArgumentException("occurredTo must not be before occurredFrom");
|
throw new IllegalArgumentException("occurredTo must not be before occurredFrom");
|
||||||
|
|
@ -157,17 +116,11 @@ public record UnifiedRuntimeProcessingRequest(
|
||||||
int vehicleExpansionPaddingMinutes
|
int vehicleExpansionPaddingMinutes
|
||||||
) {
|
) {
|
||||||
return new UnifiedRuntimeProcessingRequest(
|
return new UnifiedRuntimeProcessingRequest(
|
||||||
null,
|
|
||||||
List.of(),
|
|
||||||
null,
|
null,
|
||||||
tenantKey,
|
tenantKey,
|
||||||
sourceFamilies,
|
sourceFamilies,
|
||||||
eventBackend,
|
eventBackend,
|
||||||
null,
|
null,
|
||||||
Set.of(),
|
|
||||||
false,
|
|
||||||
Set.of(),
|
|
||||||
false,
|
|
||||||
driverSourceEntityId,
|
driverSourceEntityId,
|
||||||
driverCardNation,
|
driverCardNation,
|
||||||
driverCardNumber,
|
driverCardNumber,
|
||||||
|
|
@ -190,17 +143,11 @@ public record UnifiedRuntimeProcessingRequest(
|
||||||
int vehicleExpansionPaddingMinutes
|
int vehicleExpansionPaddingMinutes
|
||||||
) {
|
) {
|
||||||
return new UnifiedRuntimeProcessingRequest(
|
return new UnifiedRuntimeProcessingRequest(
|
||||||
null,
|
|
||||||
List.of(),
|
|
||||||
null,
|
null,
|
||||||
tenantKey,
|
tenantKey,
|
||||||
sourceFamilies,
|
sourceFamilies,
|
||||||
UnifiedRuntimeEventBackend.EVENTHUB_DB,
|
UnifiedRuntimeEventBackend.EVENTHUB_DB,
|
||||||
null,
|
null,
|
||||||
Set.of(),
|
|
||||||
false,
|
|
||||||
Set.of(),
|
|
||||||
false,
|
|
||||||
driverSourceEntityId,
|
driverSourceEntityId,
|
||||||
driverCardNation,
|
driverCardNation,
|
||||||
driverCardNumber,
|
driverCardNumber,
|
||||||
|
|
@ -221,76 +168,10 @@ public record UnifiedRuntimeProcessingRequest(
|
||||||
) {
|
) {
|
||||||
return new UnifiedRuntimeProcessingRequest(
|
return new UnifiedRuntimeProcessingRequest(
|
||||||
sessionId,
|
sessionId,
|
||||||
List.of(),
|
|
||||||
null,
|
|
||||||
null,
|
null,
|
||||||
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
|
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
|
||||||
UnifiedRuntimeEventBackend.SOURCE_DB,
|
UnifiedRuntimeEventBackend.SOURCE_DB,
|
||||||
driverKey,
|
driverKey,
|
||||||
Set.of(),
|
|
||||||
false,
|
|
||||||
Set.of(),
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
occurredFrom,
|
|
||||||
occurredTo,
|
|
||||||
expandVehicleEvents,
|
|
||||||
vehicleExpansionPaddingMinutes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UnifiedRuntimeProcessingRequest forTachographFileSessions(
|
|
||||||
List<UUID> sessionIds,
|
|
||||||
String driverKey,
|
|
||||||
OffsetDateTime occurredFrom,
|
|
||||||
OffsetDateTime occurredTo,
|
|
||||||
boolean expandVehicleEvents,
|
|
||||||
int vehicleExpansionPaddingMinutes
|
|
||||||
) {
|
|
||||||
return new UnifiedRuntimeProcessingRequest(
|
|
||||||
null,
|
|
||||||
sessionIds,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
|
|
||||||
UnifiedRuntimeEventBackend.SOURCE_DB,
|
|
||||||
driverKey,
|
|
||||||
Set.of(),
|
|
||||||
false,
|
|
||||||
Set.of(),
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
occurredFrom,
|
|
||||||
occurredTo,
|
|
||||||
expandVehicleEvents,
|
|
||||||
vehicleExpansionPaddingMinutes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UnifiedRuntimeProcessingRequest forTachographCompositeSession(
|
|
||||||
UUID compositeSessionId,
|
|
||||||
String driverKey,
|
|
||||||
OffsetDateTime occurredFrom,
|
|
||||||
OffsetDateTime occurredTo,
|
|
||||||
boolean expandVehicleEvents,
|
|
||||||
int vehicleExpansionPaddingMinutes
|
|
||||||
) {
|
|
||||||
return new UnifiedRuntimeProcessingRequest(
|
|
||||||
null,
|
|
||||||
List.of(),
|
|
||||||
compositeSessionId,
|
|
||||||
null,
|
|
||||||
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
|
|
||||||
UnifiedRuntimeEventBackend.SOURCE_DB,
|
|
||||||
driverKey,
|
|
||||||
Set.of(),
|
|
||||||
false,
|
|
||||||
Set.of(),
|
|
||||||
false,
|
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
|
@ -309,58 +190,6 @@ public record UnifiedRuntimeProcessingRequest(
|
||||||
return occurredTo == null ? null : occurredTo.plusMinutes(vehicleExpansionPaddingMinutes);
|
return occurredTo == null ? null : occurredTo.plusMinutes(vehicleExpansionPaddingMinutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<UUID> normalizeSessionIds(UUID sessionId, List<UUID> sessionIds) {
|
|
||||||
LinkedHashSet<UUID> result = new LinkedHashSet<>();
|
|
||||||
if (sessionId != null) {
|
|
||||||
result.add(sessionId);
|
|
||||||
}
|
|
||||||
if (sessionIds != null) {
|
|
||||||
result.addAll(sessionIds.stream().filter(value -> value != null).toList());
|
|
||||||
}
|
|
||||||
return List.copyOf(new ArrayList<>(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeProcessingRequest withDriverKey(String value) {
|
|
||||||
return new UnifiedRuntimeProcessingRequest(
|
|
||||||
sessionId,
|
|
||||||
sessionIds,
|
|
||||||
compositeSessionId,
|
|
||||||
tenantKey,
|
|
||||||
sourceFamilies,
|
|
||||||
eventBackend,
|
|
||||||
value,
|
|
||||||
Set.of(),
|
|
||||||
false,
|
|
||||||
vehicleKeys,
|
|
||||||
includeAllVehicles,
|
|
||||||
driverSourceEntityId,
|
|
||||||
driverCardNation,
|
|
||||||
driverCardNumber,
|
|
||||||
occurredFrom,
|
|
||||||
occurredTo,
|
|
||||||
expandVehicleEvents,
|
|
||||||
vehicleExpansionPaddingMinutes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean scopeDriverSelectionRequested() {
|
|
||||||
return includeAllDrivers || driverKeys.size() > 1 || (driverKey == null && !driverKeys.isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set<String> normalizeStrings(Set<String> values) {
|
|
||||||
if (values == null || values.isEmpty()) {
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
LinkedHashSet<String> normalized = new LinkedHashSet<>();
|
|
||||||
for (String value : values) {
|
|
||||||
String normalizedValue = normalize(value);
|
|
||||||
if (normalizedValue != null) {
|
|
||||||
normalized.add(normalizedValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Set.copyOf(normalized);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String normalize(String value) {
|
private static String normalize(String value) {
|
||||||
return value == null || value.isBlank() ? null : value.trim();
|
return value == null || value.isBlank() ? null : value.trim();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,399 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
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.RuntimeEventScopeType;
|
|
||||||
import at.procon.eventhub.processing.model.RuntimeDriverVehicleEvidenceAttachmentResult;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class RuntimeDriverVehicleEvidenceAttachmentService {
|
|
||||||
|
|
||||||
private final UnifiedEventTimelineReconstructor timelineReconstructor;
|
|
||||||
private final RuntimeEventScopeClassifier scopeClassifier;
|
|
||||||
|
|
||||||
public RuntimeDriverVehicleEvidenceAttachmentService(
|
|
||||||
UnifiedEventTimelineReconstructor timelineReconstructor,
|
|
||||||
RuntimeEventScopeClassifier scopeClassifier
|
|
||||||
) {
|
|
||||||
this.timelineReconstructor = timelineReconstructor;
|
|
||||||
this.scopeClassifier = scopeClassifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RuntimeDriverVehicleEvidenceAttachmentResult attachVehicleEvidence(
|
|
||||||
String driverKey,
|
|
||||||
List<EventHubEventDto> directDriverEvents,
|
|
||||||
List<EventHubEventDto> runtimeScopeEvents,
|
|
||||||
boolean attachVehicleOnlyEvents,
|
|
||||||
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> safeScopeEvents = runtimeScopeEvents == null ? List.of() : List.copyOf(runtimeScopeEvents);
|
|
||||||
int paddingMinutes = Math.max(0, vehicleEvidencePaddingMinutes);
|
|
||||||
|
|
||||||
List<String> notes = new ArrayList<>();
|
|
||||||
List<String> warnings = new ArrayList<>();
|
|
||||||
if (!attachVehicleOnlyEvents) {
|
|
||||||
notes.add("Vehicle-only evidence attachment is disabled for driver partition " + driverKey + ".");
|
|
||||||
List<RuntimeVehicleEvidenceAttachmentDecisionDto> disabledDecisions = includeDebug
|
|
||||||
? disabledDecisions(safeScopeEvents)
|
|
||||||
: List.of();
|
|
||||||
return new RuntimeDriverVehicleEvidenceAttachmentResult(
|
|
||||||
driverKey,
|
|
||||||
safeDriverEvents,
|
|
||||||
List.of(),
|
|
||||||
deduplicateAndSort(safeDriverEvents, List.of()),
|
|
||||||
0,
|
|
||||||
disabledDecisions.size(),
|
|
||||||
disabledDecisions.size(),
|
|
||||||
List.of(),
|
|
||||||
disabledDecisions,
|
|
||||||
notes,
|
|
||||||
warnings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ResolvedDriverTimeline timeline = timelineReconstructor.reconstruct(null, driverKey, safeDriverEvents);
|
|
||||||
List<ResolvedVehicleUsageInterval> usageIntervals = mergeVehicleUsageIntervals(timeline.vehicleUsageIntervals());
|
|
||||||
List<RuntimeVehicleUsageIntervalDebugDto> usageIntervalDebug = includeDebug
|
|
||||||
? usageIntervals.stream().map(RuntimeVehicleUsageIntervalDebugDto::from).toList()
|
|
||||||
: List.of();
|
|
||||||
List<RuntimeVehicleEvidenceAttachmentDecisionDto> decisions = includeDebug
|
|
||||||
? new ArrayList<>(directDriverDecisions(safeDriverEvents))
|
|
||||||
: new ArrayList<>();
|
|
||||||
List<EventHubEventDto> candidateVehicleEvidence = safeScopeEvents.stream()
|
|
||||||
.filter(event -> scopeClassifier.classify(event) == RuntimeEventScopeType.VEHICLE_SCOPED)
|
|
||||||
.toList();
|
|
||||||
List<EventHubEventDto> attached = new ArrayList<>();
|
|
||||||
int ignored = 0;
|
|
||||||
for (EventHubEventDto vehicleEvent : candidateVehicleEvidence) {
|
|
||||||
List<ResolvedVehicleUsageInterval> matchingIntervals = matchingUsageIntervals(vehicleEvent, usageIntervals, paddingMinutes);
|
|
||||||
if (matchingIntervals.isEmpty()) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
warnings.add("Vehicle-only event " + vehicleEvent.externalSourceEventId()
|
|
||||||
+ " matched multiple vehicle-usage intervals for driver " + driverKey
|
|
||||||
+ "; it was attached once after deduplication.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notes.add("Vehicle-only evidence attachment used " + usageIntervals.size()
|
|
||||||
+ " reconstructed vehicle-usage interval(s) for driver " + driverKey + ".");
|
|
||||||
notes.add("Vehicle-only evidence padding minutes: " + paddingMinutes + ".");
|
|
||||||
notes.add("Candidate vehicle-only evidence events: " + candidateVehicleEvidence.size() + ".");
|
|
||||||
notes.add("Attached vehicle-only evidence events: " + attached.size() + ".");
|
|
||||||
notes.add("Ignored vehicle-only evidence events: " + ignored + ".");
|
|
||||||
if (usageIntervals.isEmpty() && !candidateVehicleEvidence.isEmpty()) {
|
|
||||||
warnings.add("Vehicle-only evidence was available for driver " + driverKey
|
|
||||||
+ ", but no driver vehicle-usage intervals were reconstructed; no vehicle-only evidence was attached.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new RuntimeDriverVehicleEvidenceAttachmentResult(
|
|
||||||
driverKey,
|
|
||||||
safeDriverEvents,
|
|
||||||
attached,
|
|
||||||
deduplicateAndSort(safeDriverEvents, attached),
|
|
||||||
usageIntervals.size(),
|
|
||||||
candidateVehicleEvidence.size(),
|
|
||||||
ignored,
|
|
||||||
usageIntervalDebug,
|
|
||||||
includeDebug ? decisions : List.of(),
|
|
||||||
notes,
|
|
||||||
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(
|
|
||||||
EventHubEventDto vehicleEvent,
|
|
||||||
List<ResolvedVehicleUsageInterval> usageIntervals,
|
|
||||||
int paddingMinutes
|
|
||||||
) {
|
|
||||||
if (vehicleEvent == null || vehicleEvent.occurredAt() == null || usageIntervals == null || usageIntervals.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
List<ResolvedVehicleUsageInterval> result = new ArrayList<>();
|
|
||||||
for (ResolvedVehicleUsageInterval interval : usageIntervals) {
|
|
||||||
if (matchesVehicle(vehicleEvent, interval) && timeInside(vehicleEvent.occurredAt(), interval, paddingMinutes)) {
|
|
||||||
result.add(interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return List.copyOf(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean timeInside(OffsetDateTime occurredAt, ResolvedVehicleUsageInterval interval, int paddingMinutes) {
|
|
||||||
if (occurredAt == null || interval == null || interval.from() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
OffsetDateTime from = interval.from().minusMinutes(paddingMinutes);
|
|
||||||
OffsetDateTime to = interval.to() == null ? OffsetDateTime.MAX : interval.to().plusMinutes(paddingMinutes);
|
|
||||||
return !occurredAt.isBefore(from) && !occurredAt.isAfter(to);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean matchesVehicle(EventHubEventDto event, ResolvedVehicleUsageInterval interval) {
|
|
||||||
if (event == null || interval == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Set<String> eventKeys = vehicleKeys(event);
|
|
||||||
Set<String> intervalKeys = vehicleKeys(interval);
|
|
||||||
if (eventKeys.isEmpty() || intervalKeys.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return eventKeys.stream().anyMatch(intervalKeys::contains);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> vehicleKeys(EventHubEventDto event) {
|
|
||||||
LinkedHashSet<String> result = new LinkedHashSet<>();
|
|
||||||
JsonNode raw = rawPayload(event);
|
|
||||||
add(result, text(raw, "vehicleKey"));
|
|
||||||
add(result, text(raw, "registrationKey"));
|
|
||||||
VehicleRefDto vehicleRef = event.vehicleRef();
|
|
||||||
if (vehicleRef != null) {
|
|
||||||
add(result, vehicleRef.sourceVehicleEntityId());
|
|
||||||
add(result, vehicleRef.sourceRegistrationEntityId());
|
|
||||||
add(result, vehicleRef.vin());
|
|
||||||
if (vehicleRef.vin() != null) {
|
|
||||||
add(result, "VIN:" + vehicleRef.vin());
|
|
||||||
}
|
|
||||||
if (vehicleRef.vehicleRegistration() != null) {
|
|
||||||
String registrationKey = vehicleRef.vehicleRegistration().stableKey();
|
|
||||||
add(result, registrationKey);
|
|
||||||
add(result, "VR:" + registrationKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Set.copyOf(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> vehicleKeys(ResolvedVehicleUsageInterval interval) {
|
|
||||||
LinkedHashSet<String> result = new LinkedHashSet<>();
|
|
||||||
add(result, interval.vehicleKey());
|
|
||||||
add(result, interval.registrationKey());
|
|
||||||
if (interval.vehicleKey() != null) {
|
|
||||||
add(result, "VIN:" + interval.vehicleKey());
|
|
||||||
}
|
|
||||||
if (interval.registrationKey() != null) {
|
|
||||||
add(result, "VR:" + interval.registrationKey());
|
|
||||||
}
|
|
||||||
return Set.copyOf(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add(Set<String> keys, String value) {
|
|
||||||
if (value != null && !value.isBlank()) {
|
|
||||||
keys.add(value.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ResolvedVehicleUsageInterval> mergeVehicleUsageIntervals(List<ResolvedVehicleUsageInterval> intervals) {
|
|
||||||
if (intervals == null || intervals.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
List<ResolvedVehicleUsageInterval> sorted = intervals.stream()
|
|
||||||
.sorted(Comparator.comparing(ResolvedVehicleUsageInterval::from, Comparator.nullsLast(Comparator.naturalOrder()))
|
|
||||||
.thenComparing(ResolvedVehicleUsageInterval::to, Comparator.nullsLast(Comparator.naturalOrder()))
|
|
||||||
.thenComparing(ResolvedVehicleUsageInterval::intervalId, Comparator.nullsLast(String::compareTo)))
|
|
||||||
.toList();
|
|
||||||
List<ResolvedVehicleUsageInterval> merged = new ArrayList<>();
|
|
||||||
for (ResolvedVehicleUsageInterval next : sorted) {
|
|
||||||
if (merged.isEmpty()) {
|
|
||||||
merged.add(next);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ResolvedVehicleUsageInterval current = merged.get(merged.size() - 1);
|
|
||||||
if (canMerge(current, next)) {
|
|
||||||
merged.set(merged.size() - 1, merge(current, next));
|
|
||||||
} else {
|
|
||||||
merged.add(next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return List.copyOf(merged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean canMerge(ResolvedVehicleUsageInterval left, ResolvedVehicleUsageInterval right) {
|
|
||||||
if (left == null || right == null || left.to() == null || right.from() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return Objects.equals(left.driverKey(), right.driverKey())
|
|
||||||
&& Objects.equals(left.registrationKey(), right.registrationKey())
|
|
||||||
&& Objects.equals(left.vehicleKey(), right.vehicleKey())
|
|
||||||
&& !right.from().isAfter(left.to().plusSeconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResolvedVehicleUsageInterval merge(ResolvedVehicleUsageInterval left, ResolvedVehicleUsageInterval right) {
|
|
||||||
LinkedHashSet<String> sourceIntervalIds = new LinkedHashSet<>();
|
|
||||||
if (left.sourceIntervalIds() != null) {
|
|
||||||
sourceIntervalIds.addAll(left.sourceIntervalIds());
|
|
||||||
}
|
|
||||||
if (right.sourceIntervalIds() != null) {
|
|
||||||
sourceIntervalIds.addAll(right.sourceIntervalIds());
|
|
||||||
}
|
|
||||||
OffsetDateTime end = left.to();
|
|
||||||
if (right.to() != null && (end == null || right.to().isAfter(end))) {
|
|
||||||
end = right.to();
|
|
||||||
}
|
|
||||||
return ResolvedVehicleUsageInterval.resolved(
|
|
||||||
left.sessionId(),
|
|
||||||
left.driverKey(),
|
|
||||||
left.intervalId(),
|
|
||||||
left.from(),
|
|
||||||
end,
|
|
||||||
left.odometerBeginKm(),
|
|
||||||
right.odometerEndKm() == null ? left.odometerEndKm() : right.odometerEndKm(),
|
|
||||||
left.registrationKey(),
|
|
||||||
left.vehicleKey(),
|
|
||||||
left.sourceKind(),
|
|
||||||
List.copyOf(sourceIntervalIds)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<EventHubEventDto> deduplicateAndSort(
|
|
||||||
List<EventHubEventDto> directDriverEvents,
|
|
||||||
List<EventHubEventDto> vehicleEvidenceEvents
|
|
||||||
) {
|
|
||||||
LinkedHashMap<String, EventHubEventDto> byKey = new LinkedHashMap<>();
|
|
||||||
appendDeduplicated(byKey, directDriverEvents);
|
|
||||||
appendDeduplicated(byKey, vehicleEvidenceEvents);
|
|
||||||
return sort(new ArrayList<>(byKey.values()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendDeduplicated(LinkedHashMap<String, EventHubEventDto> byKey, List<EventHubEventDto> events) {
|
|
||||||
for (EventHubEventDto event : events == null ? List.<EventHubEventDto>of() : events) {
|
|
||||||
byKey.putIfAbsent(dedupKey(event), event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String dedupKey(EventHubEventDto event) {
|
|
||||||
String sourceKey = event.packageInfo() != null && event.packageInfo().eventSource() != null
|
|
||||||
? event.packageInfo().eventSource().stableKey()
|
|
||||||
: "NO_SOURCE";
|
|
||||||
return sourceKey + "|" + event.externalSourceEventId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<EventHubEventDto> sort(List<EventHubEventDto> events) {
|
|
||||||
return (events == null ? List.<EventHubEventDto>of() : events).stream()
|
|
||||||
.sorted(Comparator.comparing(EventHubEventDto::occurredAt, Comparator.nullsLast(Comparator.naturalOrder()))
|
|
||||||
.thenComparing(event -> event.eventDomain() == null ? "" : event.eventDomain().name())
|
|
||||||
.thenComparing(event -> event.eventType() == null ? "" : event.eventType().name())
|
|
||||||
.thenComparing(event -> event.lifecycle() == null ? "" : event.lifecycle().name())
|
|
||||||
.thenComparing(EventHubEventDto::externalSourceEventId, Comparator.nullsLast(String::compareTo)))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonNode rawPayload(EventHubEventDto event) {
|
|
||||||
JsonNode payload = event.payload();
|
|
||||||
if (payload == null || payload.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = payload.get("raw");
|
|
||||||
return raw == null || raw.isNull() ? payload : raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String text(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String text = value.asText(null);
|
|
||||||
return text == null || text.isBlank() ? null : text.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,319 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.DriverRefDto;
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
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.UnifiedRuntimeProcessingApiRequest;
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeDriverWorkingTimeScopeResultDto;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef;
|
|
||||||
import at.procon.eventhub.processing.model.RuntimeDriverVehicleEvidenceAttachmentResult;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class RuntimeDriverWorkingTimeScopeProcessingService {
|
|
||||||
|
|
||||||
private final UnifiedRuntimeEventAssemblyService eventAssemblyService;
|
|
||||||
private final UnifiedRuntimeDerivedProjectionService derivedProjectionService;
|
|
||||||
private final RuntimeDriverVehicleEvidenceAttachmentService vehicleEvidenceAttachmentService;
|
|
||||||
|
|
||||||
public RuntimeDriverWorkingTimeScopeProcessingService(
|
|
||||||
UnifiedRuntimeEventAssemblyService eventAssemblyService,
|
|
||||||
UnifiedRuntimeDerivedProjectionService derivedProjectionService,
|
|
||||||
RuntimeDriverVehicleEvidenceAttachmentService vehicleEvidenceAttachmentService
|
|
||||||
) {
|
|
||||||
this.eventAssemblyService = eventAssemblyService;
|
|
||||||
this.derivedProjectionService = derivedProjectionService;
|
|
||||||
this.vehicleEvidenceAttachmentService = vehicleEvidenceAttachmentService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeDriverWorkingTimeScopeResultDto processScope(UnifiedRuntimeProcessingApiRequest apiRequest) {
|
|
||||||
return processScope(apiRequest, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeDriverWorkingTimeScopeResultDto processScope(
|
|
||||||
UnifiedRuntimeProcessingApiRequest apiRequest,
|
|
||||||
boolean includePartitionDebug
|
|
||||||
) {
|
|
||||||
UnifiedRuntimeProcessingRequest request = apiRequest.toRuntimeRequest();
|
|
||||||
UnifiedRuntimeEventBundle broadBundle = eventAssemblyService.assembleDriverScopedEvents(request);
|
|
||||||
LinkedHashSet<String> selectedDriverKeys = selectedDriverKeys(request, broadBundle.mergedEvents());
|
|
||||||
if (selectedDriverKeys.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("No driver partitions could be resolved from the runtime event scope.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, UnifiedRuntimeDerivedProjectionResultDto> driverResults = new LinkedHashMap<>();
|
|
||||||
Map<String, RuntimeDriverPartitionDebugDto> partitionDebugByDriver = new LinkedHashMap<>();
|
|
||||||
Map<String, List<String>> attachedVehicleEvidenceByEvent = new LinkedHashMap<>();
|
|
||||||
List<String> warnings = new ArrayList<>();
|
|
||||||
for (String driverKey : selectedDriverKeys) {
|
|
||||||
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()) {
|
|
||||||
attachedVehicleEvidenceByEvent
|
|
||||||
.computeIfAbsent(dedupKey(attachedEvent), ignored -> new ArrayList<>())
|
|
||||||
.add(driverKey);
|
|
||||||
}
|
|
||||||
driverBundle.notes().stream()
|
|
||||||
.filter(note -> note.startsWith("WARNING:"))
|
|
||||||
.map(note -> note.substring("WARNING:".length()).trim())
|
|
||||||
.forEach(warnings::add);
|
|
||||||
if (driverBundle.mergedEvents().isEmpty()) {
|
|
||||||
warnings.add("No events remained after partitioning runtime scope for driver " + driverKey + ".");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
UnifiedRuntimeProcessingRequest driverRequest = request.withDriverKey(driverKey);
|
|
||||||
UnifiedRuntimeDerivedProjectionResultDto driverResult = derivedProjectionService.buildDriverDerivedProjection(
|
|
||||||
apiRequest,
|
|
||||||
driverRequest,
|
|
||||||
driverBundle,
|
|
||||||
driverKey
|
|
||||||
);
|
|
||||||
driverResults.put(driverKey, driverPartition.debug() == null ? driverResult : driverResult.withPartitionDebug(driverPartition.debug()));
|
|
||||||
}
|
|
||||||
attachedVehicleEvidenceByEvent.forEach((eventKey, drivers) -> {
|
|
||||||
if (drivers.size() > 1) {
|
|
||||||
warnings.add("Vehicle-only event " + eventKey + " was attached to multiple driver partitions "
|
|
||||||
+ drivers + "; check overlapping vehicle-usage intervals.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
List<String> notes = new ArrayList<>(broadBundle.notes());
|
|
||||||
notes.add("Runtime driver working-time processing used Java-side driver partitioning before calling the common event-input processing pipeline.");
|
|
||||||
notes.add("Selected driver partitions: " + selectedDriverKeys.size() + ".");
|
|
||||||
if (!request.includeAllDrivers() && !request.driverKeys().isEmpty()) {
|
|
||||||
notes.add("The broad runtime event set was filtered to the requested driverKeys.");
|
|
||||||
}
|
|
||||||
if (!request.vehicleKeys().isEmpty() || request.includeAllVehicles()) {
|
|
||||||
notes.add("vehicleKeys/includeAllVehicles are accepted in the request model for source-neutral scopes; driver partitions are enriched only with vehicle evidence that overlaps reconstructed driver vehicle-usage intervals.");
|
|
||||||
}
|
|
||||||
notes.add("Vehicle-only evidence attachment is controlled by expandVehicleEvents/attachVehicleOnlyEvents and vehicleExpansionPaddingMinutes/vehicleEvidencePaddingMinutes.");
|
|
||||||
|
|
||||||
return new UnifiedRuntimeDriverWorkingTimeScopeResultDto(
|
|
||||||
request,
|
|
||||||
broadBundle.mergedEvents().size(),
|
|
||||||
driverResults.size(),
|
|
||||||
broadBundle.discoveredVehicles().size(),
|
|
||||||
broadBundle.discoveredVehicles(),
|
|
||||||
driverResults,
|
|
||||||
partitionDebugByDriver,
|
|
||||||
notes,
|
|
||||||
warnings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LinkedHashSet<String> selectedDriverKeys(
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
List<EventHubEventDto> events
|
|
||||||
) {
|
|
||||||
LinkedHashSet<String> allDrivers = discoverDriverKeys(events);
|
|
||||||
if (request.includeAllDrivers()) {
|
|
||||||
return allDrivers;
|
|
||||||
}
|
|
||||||
LinkedHashSet<String> requested = new LinkedHashSet<>(request.driverKeys());
|
|
||||||
if (request.driverKey() != null) {
|
|
||||||
requested.add(request.driverKey());
|
|
||||||
}
|
|
||||||
if (requested.isEmpty()) {
|
|
||||||
return allDrivers;
|
|
||||||
}
|
|
||||||
LinkedHashSet<String> selected = new LinkedHashSet<>();
|
|
||||||
for (String driverKey : allDrivers) {
|
|
||||||
if (requested.contains(driverKey)) {
|
|
||||||
selected.add(driverKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String driverKey : requested) {
|
|
||||||
selected.add(driverKey);
|
|
||||||
}
|
|
||||||
return selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DriverPartition partitionForDriver(
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
UnifiedRuntimeEventBundle broadBundle,
|
|
||||||
String driverKey,
|
|
||||||
boolean includePartitionDebug
|
|
||||||
) {
|
|
||||||
List<EventHubEventDto> directDriverEvents = broadBundle.mergedEvents().stream()
|
|
||||||
.filter(event -> Objects.equals(driverKey(event), driverKey))
|
|
||||||
.toList();
|
|
||||||
RuntimeDriverVehicleEvidenceAttachmentResult attachmentResult = vehicleEvidenceAttachmentService.attachVehicleEvidence(
|
|
||||||
driverKey,
|
|
||||||
directDriverEvents,
|
|
||||||
broadBundle.mergedEvents(),
|
|
||||||
request.expandVehicleEvents(),
|
|
||||||
request.vehicleExpansionPaddingMinutes(),
|
|
||||||
includePartitionDebug
|
|
||||||
);
|
|
||||||
List<UnifiedDiscoveredVehicleRef> driverVehicles = discoverVehicles(attachmentResult.mergedEvents());
|
|
||||||
List<String> notes = new ArrayList<>(broadBundle.notes());
|
|
||||||
notes.add("Partitioned mixed runtime event scope for driver " + driverKey + ".");
|
|
||||||
notes.add("Driver direct events: " + attachmentResult.directDriverEvents().size() + ".");
|
|
||||||
notes.add("Vehicle-only evidence events attached to driver partition: " + attachmentResult.attachedVehicleEvidenceEvents().size() + ".");
|
|
||||||
notes.add("Vehicle-usage intervals used for temporal evidence attachment: " + attachmentResult.vehicleUsageIntervalCount() + ".");
|
|
||||||
notes.addAll(attachmentResult.notes());
|
|
||||||
attachmentResult.warnings().forEach(warning -> notes.add("WARNING: " + warning));
|
|
||||||
UnifiedRuntimeEventBundle bundle = new UnifiedRuntimeEventBundle(
|
|
||||||
request.withDriverKey(driverKey),
|
|
||||||
attachmentResult.directDriverEvents(),
|
|
||||||
driverVehicles,
|
|
||||||
attachmentResult.attachedVehicleEvidenceEvents(),
|
|
||||||
attachmentResult.mergedEvents(),
|
|
||||||
notes
|
|
||||||
);
|
|
||||||
return new DriverPartition(
|
|
||||||
bundle,
|
|
||||||
includePartitionDebug ? attachmentResult.toPartitionDebug() : null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private record DriverPartition(
|
|
||||||
UnifiedRuntimeEventBundle bundle,
|
|
||||||
RuntimeDriverPartitionDebugDto debug
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private LinkedHashSet<String> discoverDriverKeys(List<EventHubEventDto> events) {
|
|
||||||
LinkedHashSet<String> result = new LinkedHashSet<>();
|
|
||||||
for (EventHubEventDto event : sort(events)) {
|
|
||||||
String driverKey = driverKey(event);
|
|
||||||
if (driverKey != null) {
|
|
||||||
result.add(driverKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<UnifiedDiscoveredVehicleRef> discoverVehicles(List<EventHubEventDto> events) {
|
|
||||||
List<UnifiedDiscoveredVehicleRef> result = new ArrayList<>();
|
|
||||||
for (EventHubEventDto event : events) {
|
|
||||||
UnifiedDiscoveredVehicleRef candidate = vehicleRef(event.vehicleRef());
|
|
||||||
if (candidate == null || !candidate.hasAnyReference()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
boolean merged = false;
|
|
||||||
for (int i = 0; i < result.size(); i++) {
|
|
||||||
UnifiedDiscoveredVehicleRef existing = result.get(i);
|
|
||||||
if (existing.matches(candidate)) {
|
|
||||||
result.set(i, existing.merge(candidate));
|
|
||||||
merged = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!merged) {
|
|
||||||
result.add(candidate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.sort(Comparator.comparing(UnifiedDiscoveredVehicleRef::stableKey));
|
|
||||||
return List.copyOf(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean matchesAnyVehicle(VehicleRefDto vehicleRef, List<UnifiedDiscoveredVehicleRef> vehicles) {
|
|
||||||
UnifiedDiscoveredVehicleRef candidate = vehicleRef(vehicleRef);
|
|
||||||
if (candidate == null || !candidate.hasAnyReference()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return vehicles.stream().anyMatch(vehicle -> vehicle.matches(candidate));
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnifiedDiscoveredVehicleRef vehicleRef(VehicleRefDto vehicleRef) {
|
|
||||||
if (vehicleRef == null || !vehicleRef.hasAnyReference()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new UnifiedDiscoveredVehicleRef(
|
|
||||||
vehicleRef.sourceVehicleEntityId(),
|
|
||||||
vehicleRef.vin(),
|
|
||||||
vehicleRef.vehicleRegistration() == null
|
|
||||||
? null
|
|
||||||
: vehicleRef.vehicleRegistration().nationNumericCode() == null
|
|
||||||
? vehicleRef.vehicleRegistration().nation()
|
|
||||||
: vehicleRef.vehicleRegistration().nationNumericCode().toString(),
|
|
||||||
vehicleRef.vehicleRegistration() == null ? null : vehicleRef.vehicleRegistration().number()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<EventHubEventDto> deduplicateAndSort(
|
|
||||||
List<EventHubEventDto> directDriverEvents,
|
|
||||||
List<EventHubEventDto> vehicleEvidenceEvents
|
|
||||||
) {
|
|
||||||
LinkedHashMap<String, EventHubEventDto> byKey = new LinkedHashMap<>();
|
|
||||||
appendDeduplicated(byKey, directDriverEvents);
|
|
||||||
appendDeduplicated(byKey, vehicleEvidenceEvents);
|
|
||||||
return sort(new ArrayList<>(byKey.values()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendDeduplicated(LinkedHashMap<String, EventHubEventDto> byKey, List<EventHubEventDto> events) {
|
|
||||||
for (EventHubEventDto event : events) {
|
|
||||||
byKey.putIfAbsent(dedupKey(event), event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String dedupKey(EventHubEventDto event) {
|
|
||||||
String sourceKey = event.packageInfo() != null && event.packageInfo().eventSource() != null
|
|
||||||
? event.packageInfo().eventSource().stableKey()
|
|
||||||
: "NO_SOURCE";
|
|
||||||
return sourceKey + "|" + event.externalSourceEventId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<EventHubEventDto> sort(List<EventHubEventDto> events) {
|
|
||||||
return (events == null ? List.<EventHubEventDto>of() : events).stream()
|
|
||||||
.sorted(Comparator.comparing(EventHubEventDto::occurredAt, Comparator.nullsLast(Comparator.naturalOrder()))
|
|
||||||
.thenComparing(event -> event.eventDomain() == null ? "" : event.eventDomain().name())
|
|
||||||
.thenComparing(event -> event.eventType() == null ? "" : event.eventType().name())
|
|
||||||
.thenComparing(event -> event.lifecycle() == null ? "" : event.lifecycle().name())
|
|
||||||
.thenComparing(EventHubEventDto::externalSourceEventId, Comparator.nullsLast(String::compareTo)))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String driverKey(EventHubEventDto event) {
|
|
||||||
if (event == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String rawDriverKey = text(rawPayload(event), "driverKey");
|
|
||||||
if (rawDriverKey != null) {
|
|
||||||
return rawDriverKey;
|
|
||||||
}
|
|
||||||
DriverRefDto driverRef = event.driverRef();
|
|
||||||
if (driverRef != null && driverRef.hasAnyReference()) {
|
|
||||||
return driverRef.stableKey();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonNode rawPayload(EventHubEventDto event) {
|
|
||||||
JsonNode payload = event.payload();
|
|
||||||
if (payload == null || payload.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = payload.get("raw");
|
|
||||||
return raw == null || raw.isNull() ? payload : raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String text(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String text = value.asText(null);
|
|
||||||
return text == null || text.isBlank() ? null : text.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,14 +7,7 @@ import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBackend;
|
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBackend;
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
||||||
import at.procon.eventhub.processing.model.UnifiedVehicleEventsRequest;
|
import at.procon.eventhub.processing.model.UnifiedVehicleEventsRequest;
|
||||||
import at.procon.eventhub.service.EventAcquisitionRecordKeyService;
|
|
||||||
import at.procon.eventhub.service.EventHubEventSorter;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographCompositeSessionNotFoundException;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographCompositeSessionRepository;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
|
@ -22,22 +15,13 @@ public class TachographFileSessionRuntimeEventLoader implements RuntimeDriverEve
|
||||||
|
|
||||||
private final UnifiedDriverEventSourceService driverEventSourceService;
|
private final UnifiedDriverEventSourceService driverEventSourceService;
|
||||||
private final UnifiedVehicleEventSourceService vehicleEventSourceService;
|
private final UnifiedVehicleEventSourceService vehicleEventSourceService;
|
||||||
private final TachographCompositeSessionRepository compositeSessionRepository;
|
|
||||||
private final EventAcquisitionRecordKeyService eventKeyService;
|
|
||||||
private final EventHubEventSorter eventSorter;
|
|
||||||
|
|
||||||
public TachographFileSessionRuntimeEventLoader(
|
public TachographFileSessionRuntimeEventLoader(
|
||||||
UnifiedDriverEventSourceService driverEventSourceService,
|
UnifiedDriverEventSourceService driverEventSourceService,
|
||||||
UnifiedVehicleEventSourceService vehicleEventSourceService,
|
UnifiedVehicleEventSourceService vehicleEventSourceService
|
||||||
TachographCompositeSessionRepository compositeSessionRepository,
|
|
||||||
EventAcquisitionRecordKeyService eventKeyService,
|
|
||||||
EventHubEventSorter eventSorter
|
|
||||||
) {
|
) {
|
||||||
this.driverEventSourceService = driverEventSourceService;
|
this.driverEventSourceService = driverEventSourceService;
|
||||||
this.vehicleEventSourceService = vehicleEventSourceService;
|
this.vehicleEventSourceService = vehicleEventSourceService;
|
||||||
this.compositeSessionRepository = compositeSessionRepository;
|
|
||||||
this.eventKeyService = eventKeyService;
|
|
||||||
this.eventSorter = eventSorter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -48,18 +32,14 @@ public class TachographFileSessionRuntimeEventLoader implements RuntimeDriverEve
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<EventHubEventDto> loadDriverEvents(UnifiedRuntimeProcessingRequest request) {
|
public List<EventHubEventDto> loadDriverEvents(UnifiedRuntimeProcessingRequest request) {
|
||||||
List<EventHubEventDto> result = new ArrayList<>();
|
return driverEventSourceService.loadDriverEvents(
|
||||||
for (UUID sessionId : resolveSessionIds(request)) {
|
UnifiedDriverEventsRequest.forTachographFileSession(
|
||||||
result.addAll(driverEventSourceService.loadDriverEvents(
|
request.sessionId(),
|
||||||
UnifiedDriverEventsRequest.forTachographFileSession(
|
request.driverKey(),
|
||||||
sessionId,
|
request.occurredFrom(),
|
||||||
request.driverKey(),
|
request.occurredTo()
|
||||||
request.occurredFrom(),
|
)
|
||||||
request.occurredTo()
|
);
|
||||||
)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return deduplicateBySignatureAndSort(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -67,37 +47,16 @@ public class TachographFileSessionRuntimeEventLoader implements RuntimeDriverEve
|
||||||
UnifiedRuntimeProcessingRequest request,
|
UnifiedRuntimeProcessingRequest request,
|
||||||
UnifiedDiscoveredVehicleRef vehicleRef
|
UnifiedDiscoveredVehicleRef vehicleRef
|
||||||
) {
|
) {
|
||||||
List<EventHubEventDto> result = new ArrayList<>();
|
return vehicleEventSourceService.loadVehicleEvents(
|
||||||
for (UUID sessionId : resolveSessionIds(request)) {
|
UnifiedVehicleEventsRequest.forTachographFileSession(
|
||||||
result.addAll(vehicleEventSourceService.loadVehicleEvents(
|
request.sessionId(),
|
||||||
UnifiedVehicleEventsRequest.forTachographFileSession(
|
vehicleRef.sourceVehicleEntityId(),
|
||||||
sessionId,
|
vehicleRef.vin(),
|
||||||
vehicleRef.sourceVehicleEntityId(),
|
vehicleRef.registrationNation(),
|
||||||
vehicleRef.vin(),
|
vehicleRef.registrationNumber(),
|
||||||
vehicleRef.registrationNation(),
|
request.vehicleOccurredFrom(),
|
||||||
vehicleRef.registrationNumber(),
|
request.vehicleOccurredTo()
|
||||||
request.vehicleOccurredFrom(),
|
)
|
||||||
request.vehicleOccurredTo()
|
);
|
||||||
)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return deduplicateBySignatureAndSort(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<UUID> resolveSessionIds(UnifiedRuntimeProcessingRequest request) {
|
|
||||||
if (request.compositeSessionId() != null) {
|
|
||||||
return compositeSessionRepository.find(request.compositeSessionId())
|
|
||||||
.orElseThrow(() -> new TachographCompositeSessionNotFoundException(request.compositeSessionId()))
|
|
||||||
.memberSessionIds();
|
|
||||||
}
|
|
||||||
return request.sessionIds();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<EventHubEventDto> deduplicateBySignatureAndSort(List<EventHubEventDto> events) {
|
|
||||||
LinkedHashMap<String, EventHubEventDto> bySignature = new LinkedHashMap<>();
|
|
||||||
for (EventHubEventDto event : events) {
|
|
||||||
bySignature.putIfAbsent(eventKeyService.buildEventSignatureHash(event), event);
|
|
||||||
}
|
|
||||||
return eventSorter.sort(new ArrayList<>(bySignature.values()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,6 @@ public class TachographFileSessionUnifiedDriverEventSource implements UnifiedDri
|
||||||
public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) {
|
public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) {
|
||||||
TachographFileSession session = repository.find(request.sessionId())
|
TachographFileSession session = repository.find(request.sessionId())
|
||||||
.orElseThrow(() -> new TachographFileSessionNotFoundException(request.sessionId()));
|
.orElseThrow(() -> new TachographFileSessionNotFoundException(request.sessionId()));
|
||||||
if (request.driverKey() == null) {
|
|
||||||
return session.driversByKey().values().stream()
|
|
||||||
.flatMap(driver -> eventBuilder.buildEvents(session, driver).stream())
|
|
||||||
.filter(event -> withinWindow(event.occurredAt(), request.occurredFrom(), request.occurredTo()))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
DriverExtractionSession driver = session.driversByKey().get(request.driverKey());
|
DriverExtractionSession driver = session.driversByKey().get(request.driverKey());
|
||||||
if (driver == null) {
|
if (driver == null) {
|
||||||
throw new DriverNotFoundInSessionException(request.sessionId(), request.driverKey());
|
throw new DriverNotFoundInSessionException(request.sessionId(), request.driverKey());
|
||||||
|
|
|
||||||
|
|
@ -1,494 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.config.EventHubProperties;
|
|
||||||
import at.procon.eventhub.dto.DriverRefDto;
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeDerivedProjectionResultDto;
|
|
||||||
import at.procon.eventhub.processing.dto.RuntimeSupportEvidenceNormalizationDebugDto;
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceNormalizationResult;
|
|
||||||
import at.procon.eventhub.processing.eventprocessing.support.RuntimeSupportEvidenceNormalizer;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBundle;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
|
|
||||||
import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingDerivedProjectionBundle;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleOvernightStayIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialInVehicleTripIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperSupportGeoEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVehicleUsageIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.DriverTimelineBuilder;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.DriverTimelineReusableProjectionBuilder;
|
|
||||||
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeProcessingCore;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographEsperProcessingInput;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class UnifiedRuntimeDerivedProjectionService {
|
|
||||||
|
|
||||||
private final UnifiedRuntimeEventAssemblyService runtimeEventAssemblyService;
|
|
||||||
private final UnifiedEventTimelineReconstructor timelineReconstructor;
|
|
||||||
private final DriverTimelineBuilder driverTimelineBuilder;
|
|
||||||
private final DriverTimelineReusableProjectionBuilder reusableProjectionBuilder;
|
|
||||||
private final DriverWorkingTimeProcessingCore workingTimeProcessingCore;
|
|
||||||
private final RuntimeSupportEvidenceNormalizer supportEvidenceNormalizer;
|
|
||||||
private final EventHubProperties properties;
|
|
||||||
|
|
||||||
public UnifiedRuntimeDerivedProjectionService(
|
|
||||||
UnifiedRuntimeEventAssemblyService runtimeEventAssemblyService,
|
|
||||||
UnifiedEventTimelineReconstructor timelineReconstructor,
|
|
||||||
DriverTimelineBuilder driverTimelineBuilder,
|
|
||||||
DriverTimelineReusableProjectionBuilder reusableProjectionBuilder,
|
|
||||||
EventHubProperties properties,
|
|
||||||
RuntimeSupportEvidenceNormalizer supportEvidenceNormalizer
|
|
||||||
) {
|
|
||||||
this(
|
|
||||||
runtimeEventAssemblyService,
|
|
||||||
timelineReconstructor,
|
|
||||||
driverTimelineBuilder,
|
|
||||||
reusableProjectionBuilder,
|
|
||||||
properties,
|
|
||||||
new DriverWorkingTimeProcessingCore(new at.procon.eventhub.tachographfilesession.service.TachographEsperProcessingCore(driverTimelineBuilder, reusableProjectionBuilder, properties)),
|
|
||||||
supportEvidenceNormalizer
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public UnifiedRuntimeDerivedProjectionService(
|
|
||||||
UnifiedRuntimeEventAssemblyService runtimeEventAssemblyService,
|
|
||||||
UnifiedEventTimelineReconstructor timelineReconstructor,
|
|
||||||
DriverTimelineBuilder driverTimelineBuilder,
|
|
||||||
DriverTimelineReusableProjectionBuilder reusableProjectionBuilder,
|
|
||||||
EventHubProperties properties,
|
|
||||||
DriverWorkingTimeProcessingCore workingTimeProcessingCore,
|
|
||||||
RuntimeSupportEvidenceNormalizer supportEvidenceNormalizer
|
|
||||||
) {
|
|
||||||
this.runtimeEventAssemblyService = runtimeEventAssemblyService;
|
|
||||||
this.timelineReconstructor = timelineReconstructor;
|
|
||||||
this.driverTimelineBuilder = driverTimelineBuilder;
|
|
||||||
this.reusableProjectionBuilder = reusableProjectionBuilder;
|
|
||||||
this.properties = properties;
|
|
||||||
this.workingTimeProcessingCore = workingTimeProcessingCore;
|
|
||||||
this.supportEvidenceNormalizer = supportEvidenceNormalizer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeDerivedProjectionResultDto loadDriverDerivedProjections(
|
|
||||||
UnifiedRuntimeProcessingApiRequest apiRequest
|
|
||||||
) {
|
|
||||||
UnifiedRuntimeProcessingRequest request = apiRequest.toRuntimeRequest();
|
|
||||||
UnifiedRuntimeEventBundle eventBundle = runtimeEventAssemblyService.assembleDriverScopedEvents(request);
|
|
||||||
return buildDriverDerivedProjection(apiRequest, request, eventBundle, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeDerivedProjectionResultDto buildDriverDerivedProjection(
|
|
||||||
UnifiedRuntimeProcessingApiRequest apiRequest,
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
UnifiedRuntimeEventBundle eventBundle,
|
|
||||||
String explicitDriverKey
|
|
||||||
) {
|
|
||||||
String driverKey = explicitDriverKey == null
|
|
||||||
? resolveDriverKey(request, eventBundle.mergedEvents())
|
|
||||||
: explicitDriverKey;
|
|
||||||
RuntimeSupportEvidenceNormalizationResult normalizationResult = supportEvidenceNormalizer.normalizeForTachographDriver(
|
|
||||||
driverKey,
|
|
||||||
eventBundle.mergedEvents()
|
|
||||||
);
|
|
||||||
List<EventHubEventDto> normalizedEvents = normalizationResult.normalizedEvents();
|
|
||||||
ResolvedDriverTimeline timeline = timelineReconstructor.reconstruct(
|
|
||||||
runtimeSessionId(request),
|
|
||||||
driverKey,
|
|
||||||
normalizedEvents
|
|
||||||
);
|
|
||||||
|
|
||||||
OffsetDateTime requestedFrom = apiRequest.occurredFrom() == null
|
|
||||||
? timeline.loadedFrom()
|
|
||||||
: utc(apiRequest.occurredFrom());
|
|
||||||
OffsetDateTime requestedTo = apiRequest.occurredTo() == null
|
|
||||||
? timeline.loadedTo()
|
|
||||||
: utc(apiRequest.occurredTo());
|
|
||||||
if (requestedFrom != null && requestedTo != null && requestedTo.isBefore(requestedFrom)) {
|
|
||||||
throw new IllegalArgumentException("occurredTo must not be before occurredFrom.");
|
|
||||||
}
|
|
||||||
|
|
||||||
int significantDrivingMinutes = apiRequest.significantDrivingMinutes() == null
|
|
||||||
? processingProperties().getSignificantDrivingMinutes()
|
|
||||||
: Math.max(1, apiRequest.significantDrivingMinutes());
|
|
||||||
int minimumRestPeriodMinutes = apiRequest.minimumRestPeriodMinutes() == null
|
|
||||||
? processingProperties().getMinimumRestPeriodMinutes()
|
|
||||||
: Math.max(1, apiRequest.minimumRestPeriodMinutes());
|
|
||||||
|
|
||||||
List<String> notes = new ArrayList<>(eventBundle.notes());
|
|
||||||
notes.addAll(normalizationResult.notes());
|
|
||||||
notes.add("Runtime derived projections were evaluated from the unified merged event stream using normalized support evidence and the shared driver working-time processing core.");
|
|
||||||
notes.add("Significant driving threshold minutes: " + significantDrivingMinutes + ".");
|
|
||||||
notes.add("Minimum rest candidate period minutes: " + minimumRestPeriodMinutes + ".");
|
|
||||||
if (request.occurredFrom() != null || request.occurredTo() != null) {
|
|
||||||
notes.add("Projection results are filtered to the requested runtime window. For intervals crossing the boundary, include enough source-event padding in the request.");
|
|
||||||
}
|
|
||||||
|
|
||||||
DriverWorkingTimeProcessingResultDto projection = workingTimeProcessingCore.process(TachographEsperProcessingInput.fromEvents(
|
|
||||||
runtimeSessionId(request),
|
|
||||||
driverKey,
|
|
||||||
timeline,
|
|
||||||
normalizedEvents,
|
|
||||||
requestedFrom,
|
|
||||||
requestedTo,
|
|
||||||
significantDrivingMinutes,
|
|
||||||
minimumRestPeriodMinutes,
|
|
||||||
notes
|
|
||||||
));
|
|
||||||
notes = projection.notes();
|
|
||||||
|
|
||||||
RuntimeSupportEvidenceNormalizationDebugDto normalizationDebug = new RuntimeSupportEvidenceNormalizationDebugDto(
|
|
||||||
normalizationResult.inputEventCount(),
|
|
||||||
normalizationResult.normalizedSupportEvidenceEventCount(),
|
|
||||||
normalizationResult.unchangedEventCount(),
|
|
||||||
normalizationResult.notes()
|
|
||||||
);
|
|
||||||
|
|
||||||
return new UnifiedRuntimeDerivedProjectionResultDto(
|
|
||||||
request,
|
|
||||||
eventBundle.driverSeedEvents().size(),
|
|
||||||
eventBundle.discoveredVehicles().size(),
|
|
||||||
eventBundle.expandedVehicleEvents().size(),
|
|
||||||
eventBundle.mergedEvents().size(),
|
|
||||||
eventBundle.discoveredVehicles(),
|
|
||||||
projection,
|
|
||||||
notes,
|
|
||||||
normalizationDebug
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventHubProperties.Processing processingProperties() {
|
|
||||||
return properties.getTachographFileSession().getProcessing();
|
|
||||||
}
|
|
||||||
|
|
||||||
private UUID runtimeSessionId(UnifiedRuntimeProcessingRequest request) {
|
|
||||||
if (request.compositeSessionId() != null || request.sessionIds().size() > 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return request.sessionIds().size() == 1 ? request.sessionIds().get(0) : request.sessionId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String resolveDriverKey(
|
|
||||||
UnifiedRuntimeProcessingRequest request,
|
|
||||||
List<EventHubEventDto> events
|
|
||||||
) {
|
|
||||||
if (request.driverKey() != null) {
|
|
||||||
return request.driverKey();
|
|
||||||
}
|
|
||||||
if (request.driverSourceEntityId() != null) {
|
|
||||||
return request.driverSourceEntityId();
|
|
||||||
}
|
|
||||||
for (EventHubEventDto event : events) {
|
|
||||||
DriverRefDto driverRef = event.driverRef();
|
|
||||||
if (driverRef != null && driverRef.hasAnyReference()) {
|
|
||||||
return driverRef.stableKey();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (request.driverCardNation() != null && request.driverCardNumber() != null) {
|
|
||||||
return request.driverCardNation() + ":" + request.driverCardNumber();
|
|
||||||
}
|
|
||||||
return request.driverCardNumber();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private List<TachographEsperVehicleUsageIntervalEvent> mergeVehicleUsageIntervals(
|
|
||||||
List<TachographEsperVehicleUsageIntervalEvent> intervals
|
|
||||||
) {
|
|
||||||
if (intervals == null || intervals.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
List<TachographEsperVehicleUsageIntervalEvent> sorted = intervals.stream()
|
|
||||||
.sorted(Comparator.comparing(TachographEsperVehicleUsageIntervalEvent::startedAt)
|
|
||||||
.thenComparing(TachographEsperVehicleUsageIntervalEvent::endedAt, Comparator.nullsLast(Comparator.naturalOrder()))
|
|
||||||
.thenComparing(TachographEsperVehicleUsageIntervalEvent::intervalId, Comparator.nullsLast(String::compareTo)))
|
|
||||||
.toList();
|
|
||||||
List<TachographEsperVehicleUsageIntervalEvent> merged = new ArrayList<>();
|
|
||||||
for (TachographEsperVehicleUsageIntervalEvent next : sorted) {
|
|
||||||
if (merged.isEmpty()) {
|
|
||||||
merged.add(next);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
TachographEsperVehicleUsageIntervalEvent current = merged.get(merged.size() - 1);
|
|
||||||
if (canMergeVehicleUsage(current, next)) {
|
|
||||||
merged.set(merged.size() - 1, mergeVehicleUsage(current, next));
|
|
||||||
} else {
|
|
||||||
merged.add(next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return List.copyOf(merged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean canMergeVehicleUsage(
|
|
||||||
TachographEsperVehicleUsageIntervalEvent left,
|
|
||||||
TachographEsperVehicleUsageIntervalEvent right
|
|
||||||
) {
|
|
||||||
if (left == null || right == null || left.endedAt() == null || right.startedAt() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return Objects.equals(left.driverKey(), right.driverKey())
|
|
||||||
&& Objects.equals(left.registrationKey(), right.registrationKey())
|
|
||||||
&& Objects.equals(left.vehicleKey(), right.vehicleKey())
|
|
||||||
&& !right.startedAt().isAfter(left.endedAt().plusSeconds(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
private TachographEsperVehicleUsageIntervalEvent mergeVehicleUsage(
|
|
||||||
TachographEsperVehicleUsageIntervalEvent left,
|
|
||||||
TachographEsperVehicleUsageIntervalEvent right
|
|
||||||
) {
|
|
||||||
List<String> sourceIntervalIds = new ArrayList<>();
|
|
||||||
if (left.sourceIntervalIds() != null) {
|
|
||||||
sourceIntervalIds.addAll(left.sourceIntervalIds());
|
|
||||||
}
|
|
||||||
if (right.sourceIntervalIds() != null) {
|
|
||||||
for (String sourceIntervalId : right.sourceIntervalIds()) {
|
|
||||||
if (!sourceIntervalIds.contains(sourceIntervalId)) {
|
|
||||||
sourceIntervalIds.add(sourceIntervalId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OffsetDateTime end = right.endedAt() == null || right.endedAt().isBefore(left.endedAt())
|
|
||||||
? left.endedAt()
|
|
||||||
: right.endedAt();
|
|
||||||
return new TachographEsperVehicleUsageIntervalEvent(
|
|
||||||
left.sessionId(),
|
|
||||||
left.driverKey(),
|
|
||||||
left.intervalId(),
|
|
||||||
left.startedAt(),
|
|
||||||
end,
|
|
||||||
Duration.between(left.startedAt(), end).getSeconds(),
|
|
||||||
left.odometerBeginKm(),
|
|
||||||
right.odometerEndKm() == null ? left.odometerEndKm() : right.odometerEndKm(),
|
|
||||||
left.registrationKey(),
|
|
||||||
left.vehicleKey(),
|
|
||||||
left.sourceKind(),
|
|
||||||
sourceIntervalIds
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TachographEsperActivityIntervalEvent> clipActivityIntervals(
|
|
||||||
List<TachographEsperActivityIntervalEvent> intervals,
|
|
||||||
OffsetDateTime requestedFrom,
|
|
||||||
OffsetDateTime requestedTo
|
|
||||||
) {
|
|
||||||
if (requestedFrom == null || requestedTo == null) {
|
|
||||||
return intervals == null ? List.of() : List.copyOf(intervals);
|
|
||||||
}
|
|
||||||
return (intervals == null ? List.<TachographEsperActivityIntervalEvent>of() : intervals).stream()
|
|
||||||
.map(interval -> {
|
|
||||||
if (!intersects(interval.startedAt(), interval.endedAt(), requestedFrom, requestedTo)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
OffsetDateTime start = max(interval.startedAt(), requestedFrom);
|
|
||||||
OffsetDateTime end = min(interval.endedAt(), requestedTo);
|
|
||||||
if (start == null || end == null || !end.isAfter(start)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
boolean clipped = interval.clippedToRequestedPeriod()
|
|
||||||
|| !Objects.equals(start, interval.startedAt())
|
|
||||||
|| !Objects.equals(end, interval.endedAt());
|
|
||||||
return new TachographEsperActivityIntervalEvent(
|
|
||||||
interval.sessionId(),
|
|
||||||
interval.driverKey(),
|
|
||||||
interval.intervalId(),
|
|
||||||
interval.activityType(),
|
|
||||||
interval.cardSlot(),
|
|
||||||
interval.cardStatus(),
|
|
||||||
interval.drivingStatus(),
|
|
||||||
interval.registrationKey(),
|
|
||||||
interval.vehicleKey(),
|
|
||||||
interval.sourceKind(),
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
Duration.between(start, end).getSeconds(),
|
|
||||||
interval.sourceIntervalIds(),
|
|
||||||
interval.synthetic(),
|
|
||||||
clipped,
|
|
||||||
interval.level()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.sorted(Comparator.comparing(TachographEsperActivityIntervalEvent::startedAt)
|
|
||||||
.thenComparing(TachographEsperActivityIntervalEvent::endedAt))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TachographEsperVehicleUsageIntervalEvent> clipVehicleUsageIntervals(
|
|
||||||
List<TachographEsperVehicleUsageIntervalEvent> intervals,
|
|
||||||
OffsetDateTime requestedFrom,
|
|
||||||
OffsetDateTime requestedTo
|
|
||||||
) {
|
|
||||||
if (requestedFrom == null || requestedTo == null) {
|
|
||||||
return intervals == null ? List.of() : List.copyOf(intervals);
|
|
||||||
}
|
|
||||||
return (intervals == null ? List.<TachographEsperVehicleUsageIntervalEvent>of() : intervals).stream()
|
|
||||||
.map(interval -> {
|
|
||||||
if (!intersects(interval.startedAt(), interval.endedAt(), requestedFrom, requestedTo)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
OffsetDateTime start = max(interval.startedAt(), requestedFrom);
|
|
||||||
OffsetDateTime end = min(interval.endedAt(), requestedTo);
|
|
||||||
if (start == null || end == null || !end.isAfter(start)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
boolean startClipped = !Objects.equals(start, interval.startedAt());
|
|
||||||
boolean endClipped = !Objects.equals(end, interval.endedAt());
|
|
||||||
return new TachographEsperVehicleUsageIntervalEvent(
|
|
||||||
interval.sessionId(),
|
|
||||||
interval.driverKey(),
|
|
||||||
interval.intervalId(),
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
Duration.between(start, end).getSeconds(),
|
|
||||||
startClipped ? null : interval.odometerBeginKm(),
|
|
||||||
endClipped ? null : interval.odometerEndKm(),
|
|
||||||
interval.registrationKey(),
|
|
||||||
interval.vehicleKey(),
|
|
||||||
interval.sourceKind(),
|
|
||||||
interval.sourceIntervalIds()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.sorted(Comparator.comparing(TachographEsperVehicleUsageIntervalEvent::startedAt)
|
|
||||||
.thenComparing(TachographEsperVehicleUsageIntervalEvent::endedAt))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TachographEsperDrivingInterruptionIntervalEvent> clipDrivingIntervals(
|
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> intervals,
|
|
||||||
OffsetDateTime requestedFrom,
|
|
||||||
OffsetDateTime requestedTo
|
|
||||||
) {
|
|
||||||
return filterIntersecting(
|
|
||||||
intervals,
|
|
||||||
requestedFrom,
|
|
||||||
requestedTo,
|
|
||||||
TachographEsperDrivingInterruptionIntervalEvent::startedAt,
|
|
||||||
TachographEsperDrivingInterruptionIntervalEvent::endedAt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TachographEsperVuCardAbsentIntervalEvent> clipVuCardAbsentIntervals(
|
|
||||||
List<TachographEsperVuCardAbsentIntervalEvent> intervals,
|
|
||||||
OffsetDateTime requestedFrom,
|
|
||||||
OffsetDateTime requestedTo
|
|
||||||
) {
|
|
||||||
return filterIntersecting(
|
|
||||||
intervals,
|
|
||||||
requestedFrom,
|
|
||||||
requestedTo,
|
|
||||||
TachographEsperVuCardAbsentIntervalEvent::startedAt,
|
|
||||||
TachographEsperVuCardAbsentIntervalEvent::endedAt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TachographEsperSupportGeoEvent> clipSupportGeoEvents(
|
|
||||||
List<ExtractedSupportEvent> supportEvents,
|
|
||||||
String driverKey,
|
|
||||||
OffsetDateTime requestedFrom,
|
|
||||||
OffsetDateTime requestedTo
|
|
||||||
) {
|
|
||||||
return (supportEvents == null ? List.<ExtractedSupportEvent>of() : supportEvents).stream()
|
|
||||||
.filter(event -> event.occurredAt() != null)
|
|
||||||
.filter(event -> driverKey == null || event.driverKey() == null || Objects.equals(driverKey, event.driverKey()))
|
|
||||||
.filter(event -> requestedFrom == null || !event.occurredAt().isBefore(requestedFrom))
|
|
||||||
.filter(event -> requestedTo == null || !event.occurredAt().isAfter(requestedTo))
|
|
||||||
.map(event -> new TachographEsperSupportGeoEvent(
|
|
||||||
event.eventId(),
|
|
||||||
event.driverKey(),
|
|
||||||
event.occurredAt(),
|
|
||||||
event.eventDomain(),
|
|
||||||
event.eventType(),
|
|
||||||
event.eventLifecycle(),
|
|
||||||
event.registrationKey(),
|
|
||||||
event.vehicleKey(),
|
|
||||||
event.country(),
|
|
||||||
event.region(),
|
|
||||||
event.countryFrom(),
|
|
||||||
event.countryTo(),
|
|
||||||
event.operation(),
|
|
||||||
event.latitude(),
|
|
||||||
event.longitude(),
|
|
||||||
event.odometerKm(),
|
|
||||||
event.rawRecordPath()
|
|
||||||
))
|
|
||||||
.sorted(Comparator.comparing(TachographEsperSupportGeoEvent::occurredAt)
|
|
||||||
.thenComparing(TachographEsperSupportGeoEvent::eventId, Comparator.nullsLast(String::compareTo)))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> List<T> filterIntersecting(
|
|
||||||
List<T> intervals,
|
|
||||||
OffsetDateTime requestedFrom,
|
|
||||||
OffsetDateTime requestedTo,
|
|
||||||
TimeAccessor<T> startAccessor,
|
|
||||||
TimeAccessor<T> endAccessor
|
|
||||||
) {
|
|
||||||
if (intervals == null || intervals.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
if (requestedFrom == null || requestedTo == null) {
|
|
||||||
return List.copyOf(intervals);
|
|
||||||
}
|
|
||||||
return intervals.stream()
|
|
||||||
.filter(interval -> intersects(startAccessor.get(interval), endAccessor.get(interval), requestedFrom, requestedTo))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean intersects(
|
|
||||||
OffsetDateTime intervalStart,
|
|
||||||
OffsetDateTime intervalEnd,
|
|
||||||
OffsetDateTime requestedFrom,
|
|
||||||
OffsetDateTime requestedTo
|
|
||||||
) {
|
|
||||||
if (intervalStart == null || intervalEnd == null || requestedFrom == null || requestedTo == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return intervalEnd.isAfter(requestedFrom) && intervalStart.isBefore(requestedTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime min(OffsetDateTime left, OffsetDateTime right) {
|
|
||||||
if (left == null) {
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
if (right == null) {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
return left.isBefore(right) ? left : right;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime max(OffsetDateTime left, OffsetDateTime right) {
|
|
||||||
if (left == null) {
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
if (right == null) {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
return left.isAfter(right) ? left : right;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime utc(OffsetDateTime value) {
|
|
||||||
return value == null ? null : value.withOffsetSameInstant(ZoneOffset.UTC);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
private interface TimeAccessor<T> {
|
|
||||||
OffsetDateTime get(T value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -40,15 +40,6 @@ public class UnifiedRuntimeEventAssemblyService {
|
||||||
notes.add(request.eventBackend() == UnifiedRuntimeEventBackend.EVENTHUB_DB
|
notes.add(request.eventBackend() == UnifiedRuntimeEventBackend.EVENTHUB_DB
|
||||||
? "Driver seed events were loaded from the local EventHub event store."
|
? "Driver seed events were loaded from the local EventHub event store."
|
||||||
: "Driver seed events were loaded directly from the selected runtime sources.");
|
: "Driver seed events were loaded directly from the selected runtime sources.");
|
||||||
if (request.sourceFamilies().contains(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION)) {
|
|
||||||
if (request.compositeSessionId() != null) {
|
|
||||||
notes.add("Tachograph file-session events were loaded from composite session " + request.compositeSessionId() + ".");
|
|
||||||
} else if (request.sessionIds().size() > 1) {
|
|
||||||
notes.add("Tachograph file-session events were loaded from " + request.sessionIds().size() + " selected sessions.");
|
|
||||||
} else if (request.sessionId() != null) {
|
|
||||||
notes.add("Tachograph file-session events were loaded from session " + request.sessionId() + ".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (request.expandVehicleEvents()) {
|
if (request.expandVehicleEvents()) {
|
||||||
notes.add("Vehicle expansion loaded additional events for vehicles discovered in the driver seed set.");
|
notes.add("Vehicle expansion loaded additional events for vehicles discovered in the driver seed set.");
|
||||||
notes.add("Vehicle expansion padding minutes: " + request.vehicleExpansionPaddingMinutes() + ".");
|
notes.add("Vehicle expansion padding minutes: " + request.vehicleExpansionPaddingMinutes() + ".");
|
||||||
|
|
@ -142,10 +133,10 @@ public class UnifiedRuntimeEventAssemblyService {
|
||||||
appendDeduplicated(byKey, right);
|
appendDeduplicated(byKey, right);
|
||||||
return byKey.values().stream()
|
return byKey.values().stream()
|
||||||
.sorted(Comparator.comparing(EventHubEventDto::occurredAt, Comparator.nullsLast(Comparator.naturalOrder()))
|
.sorted(Comparator.comparing(EventHubEventDto::occurredAt, Comparator.nullsLast(Comparator.naturalOrder()))
|
||||||
.thenComparing(event -> event.eventDomain() == null ? "" : event.eventDomain().name())
|
.thenComparing(event -> event.eventDomain().name())
|
||||||
.thenComparing(event -> event.eventType() == null ? "" : event.eventType().name())
|
.thenComparing(event -> event.eventType().name())
|
||||||
.thenComparing(event -> event.lifecycle() == null ? "" : event.lifecycle().name())
|
.thenComparing(event -> event.lifecycle().name())
|
||||||
.thenComparing(EventHubEventDto::externalSourceEventId, Comparator.nullsLast(String::compareTo)))
|
.thenComparing(EventHubEventDto::externalSourceEventId))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeDriverWorkingTimeScopeResultDto;
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
|
|
||||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeTachographEsperScopeResultDto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Compatibility adapter. Use {@link RuntimeDriverWorkingTimeScopeProcessingService};
|
|
||||||
* tachograph files/databases are only one possible source for the common driver working-time runtime plan.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = false)
|
|
||||||
public class UnifiedRuntimeTachographEsperScopeProcessingService {
|
|
||||||
|
|
||||||
private final RuntimeDriverWorkingTimeScopeProcessingService delegate;
|
|
||||||
|
|
||||||
public UnifiedRuntimeTachographEsperScopeProcessingService(
|
|
||||||
RuntimeDriverWorkingTimeScopeProcessingService delegate
|
|
||||||
) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeTachographEsperScopeResultDto processScope(UnifiedRuntimeProcessingApiRequest apiRequest) {
|
|
||||||
return toLegacy(delegate.processScope(apiRequest));
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnifiedRuntimeTachographEsperScopeResultDto processScope(
|
|
||||||
UnifiedRuntimeProcessingApiRequest apiRequest,
|
|
||||||
boolean includePartitionDebug
|
|
||||||
) {
|
|
||||||
return toLegacy(delegate.processScope(apiRequest, includePartitionDebug));
|
|
||||||
}
|
|
||||||
|
|
||||||
private UnifiedRuntimeTachographEsperScopeResultDto toLegacy(
|
|
||||||
UnifiedRuntimeDriverWorkingTimeScopeResultDto result
|
|
||||||
) {
|
|
||||||
return new UnifiedRuntimeTachographEsperScopeResultDto(
|
|
||||||
result.request(),
|
|
||||||
result.inputEventCount(),
|
|
||||||
result.selectedDriverCount(),
|
|
||||||
result.discoveredVehicleCount(),
|
|
||||||
result.discoveredVehicles(),
|
|
||||||
result.driverResults(),
|
|
||||||
result.partitionDebugByDriver(),
|
|
||||||
result.notes(),
|
|
||||||
result.warnings()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -34,7 +34,7 @@ public final class TachographNationRegistry {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String[] parts = line.split(";", -1);
|
String[] parts = line.split(";", -1);
|
||||||
if (parts.length < 4) {
|
if (parts.length < 5) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Integer numericCode = parseInteger(parts[3]);
|
Integer numericCode = parseInteger(parts[3]);
|
||||||
|
|
@ -43,7 +43,7 @@ public final class TachographNationRegistry {
|
||||||
}
|
}
|
||||||
String name = normalizeNullable(parts[1]);
|
String name = normalizeNullable(parts[1]);
|
||||||
String alphaCode = normalizeAlpha(parts[2]);
|
String alphaCode = normalizeAlpha(parts[2]);
|
||||||
String defaultLanguageCode = parts.length > 4 ? normalizeNullable(parts[4]) : null;
|
String defaultLanguageCode = normalizeNullable(parts[4]);
|
||||||
NationRecord record = new NationRecord(numericCode, alphaCode, name, defaultLanguageCode, true);
|
NationRecord record = new NationRecord(numericCode, alphaCode, name, defaultLanguageCode, true);
|
||||||
byNumeric.put(numericCode, record);
|
byNumeric.put(numericCode, record);
|
||||||
if (alphaCode != null) {
|
if (alphaCode != null) {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import java.sql.Timestamp;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -43,8 +44,6 @@ abstract class AbstractTachographActivityRowMapper implements ExtractionRowMappe
|
||||||
CardSlot cardSlot = cardSlot(rs);
|
CardSlot cardSlot = cardSlot(rs);
|
||||||
CardStatus cardStatus = cardStatus(rs);
|
CardStatus cardStatus = cardStatus(rs);
|
||||||
DrivingStatus drivingStatus = drivingStatus(rs);
|
DrivingStatus drivingStatus = drivingStatus(rs);
|
||||||
EventType eventType = eventType(rs);
|
|
||||||
EventLifecycle lifecycle = lifecycle(rs);
|
|
||||||
|
|
||||||
String externalSourceEventId = string(rs, "external_source_event_id");
|
String externalSourceEventId = string(rs, "external_source_event_id");
|
||||||
if (externalSourceEventId == null) {
|
if (externalSourceEventId == null) {
|
||||||
|
|
@ -60,13 +59,13 @@ abstract class AbstractTachographActivityRowMapper implements ExtractionRowMappe
|
||||||
offsetDateTime(rs, "received_partner_at"),
|
offsetDateTime(rs, "received_partner_at"),
|
||||||
OffsetDateTime.now(),
|
OffsetDateTime.now(),
|
||||||
EventDomain.DRIVER_ACTIVITY,
|
EventDomain.DRIVER_ACTIVITY,
|
||||||
eventType,
|
eventType(rs),
|
||||||
lifecycle,
|
lifecycle(rs),
|
||||||
longValue(rs, "odometer_m"),
|
longValue(rs, "odometer_m"),
|
||||||
null,
|
null,
|
||||||
detailsFactory.driverActivity(cardSlot, cardStatus, drivingStatus),
|
detailsFactory.driverActivity(cardSlot, cardStatus, drivingStatus),
|
||||||
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
||||||
detailsFactory.payloadFromMap(payload(rs, context, driverRef, vehicleRef, eventType, lifecycle, cardSlot, cardStatus, drivingStatus)),
|
detailsFactory.payloadFromMap(payload(rs, context)),
|
||||||
isManualEntry(cardStatus, drivingStatus),
|
isManualEntry(cardStatus, drivingStatus),
|
||||||
context.packageInfo()
|
context.packageInfo()
|
||||||
);
|
);
|
||||||
|
|
@ -116,24 +115,9 @@ abstract class AbstractTachographActivityRowMapper implements ExtractionRowMappe
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> payload(
|
private Map<String, Object> payload(ResultSet rs, ExtractionContext<TachographImportRequest> context) throws SQLException {
|
||||||
ResultSet rs,
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
ExtractionContext<TachographImportRequest> context,
|
put(raw, "sourceRowId", string(rs, "source_row_id"));
|
||||||
DriverRefDto driverRef,
|
|
||||||
VehicleRefDto vehicleRef,
|
|
||||||
EventType eventType,
|
|
||||||
EventLifecycle lifecycle,
|
|
||||||
CardSlot cardSlot,
|
|
||||||
CardStatus cardStatus,
|
|
||||||
DrivingStatus drivingStatus
|
|
||||||
) throws SQLException {
|
|
||||||
Map<String, Object> raw = TachographRawPayloadSupport.baseRawPayload(
|
|
||||||
rs, context, driverRef, vehicleRef, eventType, lifecycle
|
|
||||||
);
|
|
||||||
put(raw, "activityType", eventType == null ? null : eventType.name());
|
|
||||||
put(raw, "slot", cardSlot == null ? null : cardSlot.name());
|
|
||||||
put(raw, "cardStatus", cardStatus == null ? null : cardStatus.name());
|
|
||||||
put(raw, "drivingStatus", drivingStatus == null ? null : drivingStatus.name());
|
|
||||||
raw.putAll(sourceSpecificPayload(rs));
|
raw.putAll(sourceSpecificPayload(rs));
|
||||||
return Map.of("raw", raw);
|
return Map.of("raw", raw);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.sql.Timestamp;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
@ -59,7 +60,7 @@ abstract class AbstractTachographBorderCrossingRowMapper implements ExtractionRo
|
||||||
position(rs),
|
position(rs),
|
||||||
detailsFactory.borderCrossing(string(rs, "country_from"), string(rs, "country_to")),
|
detailsFactory.borderCrossing(string(rs, "country_from"), string(rs, "country_to")),
|
||||||
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
||||||
detailsFactory.payloadFromMap(payload(rs, context, driverRef, vehicleRef)),
|
detailsFactory.payloadFromMap(payload(rs)),
|
||||||
false,
|
false,
|
||||||
context.packageInfo()
|
context.packageInfo()
|
||||||
);
|
);
|
||||||
|
|
@ -111,18 +112,9 @@ abstract class AbstractTachographBorderCrossingRowMapper implements ExtractionRo
|
||||||
return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
|
return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> payload(
|
private Map<String, Object> payload(ResultSet rs) throws SQLException {
|
||||||
ResultSet rs,
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
ExtractionContext<TachographImportRequest> context,
|
put(raw, "sourceRowId", string(rs, "source_row_id"));
|
||||||
DriverRefDto driverRef,
|
|
||||||
VehicleRefDto vehicleRef
|
|
||||||
) throws SQLException {
|
|
||||||
Map<String, Object> raw = TachographRawPayloadSupport.baseRawPayload(
|
|
||||||
rs, context, driverRef, vehicleRef, EventType.BORDER_OUTBOUND, EventLifecycle.OUTBOUND
|
|
||||||
);
|
|
||||||
put(raw, "supportEventType", EventType.BORDER_OUTBOUND.name());
|
|
||||||
put(raw, "countryFrom", string(rs, "country_from"));
|
|
||||||
put(raw, "countryTo", string(rs, "country_to"));
|
|
||||||
raw.putAll(sourceSpecificPayload(rs));
|
raw.putAll(sourceSpecificPayload(rs));
|
||||||
return Map.of("raw", raw);
|
return Map.of("raw", raw);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.sql.Timestamp;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -43,7 +44,6 @@ abstract class AbstractTachographCardEventRowMapper implements ExtractionRowMapp
|
||||||
EventType eventType = eventType(rs);
|
EventType eventType = eventType(rs);
|
||||||
EventLifecycle lifecycle = lifecycle(rs);
|
EventLifecycle lifecycle = lifecycle(rs);
|
||||||
CardStatus cardStatus = cardStatus(lifecycle);
|
CardStatus cardStatus = cardStatus(lifecycle);
|
||||||
CardSlot cardSlot = cardSlot(rs);
|
|
||||||
|
|
||||||
String externalSourceEventId = string(rs, "external_source_event_id");
|
String externalSourceEventId = string(rs, "external_source_event_id");
|
||||||
if (externalSourceEventId == null) {
|
if (externalSourceEventId == null) {
|
||||||
|
|
@ -63,9 +63,9 @@ abstract class AbstractTachographCardEventRowMapper implements ExtractionRowMapp
|
||||||
lifecycle,
|
lifecycle,
|
||||||
longValue(rs, "odometer_m"),
|
longValue(rs, "odometer_m"),
|
||||||
null,
|
null,
|
||||||
detailsFactory.driverCard(cardSlot, cardStatus, driverCard),
|
detailsFactory.driverCard(cardSlot(rs), cardStatus, driverCard),
|
||||||
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
||||||
detailsFactory.payloadFromMap(payload(rs, context, driverRef, vehicleRef, eventType, lifecycle, cardStatus, cardSlot)),
|
detailsFactory.payloadFromMap(payload(rs)),
|
||||||
false,
|
false,
|
||||||
context.packageInfo()
|
context.packageInfo()
|
||||||
);
|
);
|
||||||
|
|
@ -111,22 +111,9 @@ abstract class AbstractTachographCardEventRowMapper implements ExtractionRowMapp
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> payload(
|
private Map<String, Object> payload(ResultSet rs) throws SQLException {
|
||||||
ResultSet rs,
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
ExtractionContext<TachographImportRequest> context,
|
put(raw, "sourceRowId", string(rs, "source_row_id"));
|
||||||
DriverRefDto driverRef,
|
|
||||||
VehicleRefDto vehicleRef,
|
|
||||||
EventType eventType,
|
|
||||||
EventLifecycle lifecycle,
|
|
||||||
CardStatus cardStatus,
|
|
||||||
CardSlot cardSlot
|
|
||||||
) throws SQLException {
|
|
||||||
Map<String, Object> raw = TachographRawPayloadSupport.baseRawPayload(
|
|
||||||
rs, context, driverRef, vehicleRef, eventType, lifecycle
|
|
||||||
);
|
|
||||||
put(raw, "supportEventType", eventType == null ? null : eventType.name());
|
|
||||||
put(raw, "slot", cardSlot == null ? null : cardSlot.name());
|
|
||||||
put(raw, "cardStatus", cardStatus == null ? null : cardStatus.name());
|
|
||||||
raw.putAll(sourceSpecificPayload(rs));
|
raw.putAll(sourceSpecificPayload(rs));
|
||||||
return Map.of("raw", raw);
|
return Map.of("raw", raw);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.sql.Timestamp;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -61,7 +62,7 @@ abstract class AbstractTachographLoadUnloadRowMapper implements ExtractionRowMap
|
||||||
position(rs),
|
position(rs),
|
||||||
detailsFactory.loadUnload(string(rs, "operation")),
|
detailsFactory.loadUnload(string(rs, "operation")),
|
||||||
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
||||||
detailsFactory.payloadFromMap(payload(rs, context, driverRef, vehicleRef, eventType)),
|
detailsFactory.payloadFromMap(payload(rs)),
|
||||||
false,
|
false,
|
||||||
context.packageInfo()
|
context.packageInfo()
|
||||||
);
|
);
|
||||||
|
|
@ -113,18 +114,9 @@ abstract class AbstractTachographLoadUnloadRowMapper implements ExtractionRowMap
|
||||||
return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
|
return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> payload(
|
private Map<String, Object> payload(ResultSet rs) throws SQLException {
|
||||||
ResultSet rs,
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
ExtractionContext<TachographImportRequest> context,
|
put(raw, "sourceRowId", string(rs, "source_row_id"));
|
||||||
DriverRefDto driverRef,
|
|
||||||
VehicleRefDto vehicleRef,
|
|
||||||
EventType eventType
|
|
||||||
) throws SQLException {
|
|
||||||
Map<String, Object> raw = TachographRawPayloadSupport.baseRawPayload(
|
|
||||||
rs, context, driverRef, vehicleRef, eventType, EventLifecycle.SNAPSHOT
|
|
||||||
);
|
|
||||||
put(raw, "supportEventType", eventType == null ? null : eventType.name());
|
|
||||||
put(raw, "operation", string(rs, "operation"));
|
|
||||||
raw.putAll(sourceSpecificPayload(rs));
|
raw.putAll(sourceSpecificPayload(rs));
|
||||||
return Map.of("raw", raw);
|
return Map.of("raw", raw);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.sql.Timestamp;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -62,7 +63,7 @@ abstract class AbstractTachographPlaceRowMapper implements ExtractionRowMapper<T
|
||||||
position(rs),
|
position(rs),
|
||||||
detailsFactory.place(string(rs, "country"), string(rs, "region")),
|
detailsFactory.place(string(rs, "country"), string(rs, "region")),
|
||||||
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
||||||
detailsFactory.payloadFromMap(payload(rs, context, driverRef, vehicleRef, eventType, lifecycle)),
|
detailsFactory.payloadFromMap(payload(rs)),
|
||||||
booleanValue(rs, "manual_entry"),
|
booleanValue(rs, "manual_entry"),
|
||||||
context.packageInfo()
|
context.packageInfo()
|
||||||
);
|
);
|
||||||
|
|
@ -114,20 +115,9 @@ abstract class AbstractTachographPlaceRowMapper implements ExtractionRowMapper<T
|
||||||
return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
|
return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> payload(
|
private Map<String, Object> payload(ResultSet rs) throws SQLException {
|
||||||
ResultSet rs,
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
ExtractionContext<TachographImportRequest> context,
|
put(raw, "sourceRowId", string(rs, "source_row_id"));
|
||||||
DriverRefDto driverRef,
|
|
||||||
VehicleRefDto vehicleRef,
|
|
||||||
EventType eventType,
|
|
||||||
EventLifecycle lifecycle
|
|
||||||
) throws SQLException {
|
|
||||||
Map<String, Object> raw = TachographRawPayloadSupport.baseRawPayload(
|
|
||||||
rs, context, driverRef, vehicleRef, eventType, lifecycle
|
|
||||||
);
|
|
||||||
put(raw, "supportEventType", eventType == null ? null : eventType.name());
|
|
||||||
put(raw, "country", string(rs, "country"));
|
|
||||||
put(raw, "region", string(rs, "region"));
|
|
||||||
raw.putAll(sourceSpecificPayload(rs));
|
raw.putAll(sourceSpecificPayload(rs));
|
||||||
return Map.of("raw", raw);
|
return Map.of("raw", raw);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.sql.Timestamp;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
@ -59,7 +60,7 @@ abstract class AbstractTachographPositionRowMapper implements ExtractionRowMappe
|
||||||
position(rs),
|
position(rs),
|
||||||
detailsFactory.position("GNSS_ACCUMULATED_DRIVING"),
|
detailsFactory.position("GNSS_ACCUMULATED_DRIVING"),
|
||||||
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
||||||
detailsFactory.payloadFromMap(payload(rs, context, driverRef, vehicleRef)),
|
detailsFactory.payloadFromMap(payload(rs)),
|
||||||
false,
|
false,
|
||||||
context.packageInfo()
|
context.packageInfo()
|
||||||
);
|
);
|
||||||
|
|
@ -111,16 +112,9 @@ abstract class AbstractTachographPositionRowMapper implements ExtractionRowMappe
|
||||||
return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
|
return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> payload(
|
private Map<String, Object> payload(ResultSet rs) throws SQLException {
|
||||||
ResultSet rs,
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
ExtractionContext<TachographImportRequest> context,
|
put(raw, "sourceRowId", string(rs, "source_row_id"));
|
||||||
DriverRefDto driverRef,
|
|
||||||
VehicleRefDto vehicleRef
|
|
||||||
) throws SQLException {
|
|
||||||
Map<String, Object> raw = TachographRawPayloadSupport.baseRawPayload(
|
|
||||||
rs, context, driverRef, vehicleRef, EventType.POSITION_RECORDED, EventLifecycle.SNAPSHOT
|
|
||||||
);
|
|
||||||
put(raw, "supportEventType", EventType.POSITION_RECORDED.name());
|
|
||||||
raw.putAll(sourceSpecificPayload(rs));
|
raw.putAll(sourceSpecificPayload(rs));
|
||||||
return Map.of("raw", raw);
|
return Map.of("raw", raw);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import java.sql.Timestamp;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -61,7 +62,7 @@ abstract class AbstractTachographSpecificConditionRowMapper implements Extractio
|
||||||
null,
|
null,
|
||||||
detailsFactory.specificCondition(),
|
detailsFactory.specificCondition(),
|
||||||
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
||||||
detailsFactory.payloadFromMap(payload(rs, context, driverRef, vehicleRef, eventType, lifecycle)),
|
detailsFactory.payloadFromMap(payload(rs)),
|
||||||
false,
|
false,
|
||||||
context.packageInfo()
|
context.packageInfo()
|
||||||
);
|
);
|
||||||
|
|
@ -107,18 +108,9 @@ abstract class AbstractTachographSpecificConditionRowMapper implements Extractio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> payload(
|
private Map<String, Object> payload(ResultSet rs) throws SQLException {
|
||||||
ResultSet rs,
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
ExtractionContext<TachographImportRequest> context,
|
put(raw, "sourceRowId", string(rs, "source_row_id"));
|
||||||
DriverRefDto driverRef,
|
|
||||||
VehicleRefDto vehicleRef,
|
|
||||||
EventType eventType,
|
|
||||||
EventLifecycle lifecycle
|
|
||||||
) throws SQLException {
|
|
||||||
Map<String, Object> raw = TachographRawPayloadSupport.baseRawPayload(
|
|
||||||
rs, context, driverRef, vehicleRef, eventType, lifecycle
|
|
||||||
);
|
|
||||||
put(raw, "supportEventType", eventType == null ? null : eventType.name());
|
|
||||||
raw.putAll(sourceSpecificPayload(rs));
|
raw.putAll(sourceSpecificPayload(rs));
|
||||||
return Map.of("raw", raw);
|
return Map.of("raw", raw);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import java.sql.Timestamp;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -62,7 +63,7 @@ public class SpeedingEventRowMapper implements ExtractionRowMapper<TachographImp
|
||||||
null,
|
null,
|
||||||
detailsFactory.speeding(decimal(rs, "avg_speed_kmh"), decimal(rs, "max_speed_kmh"), null),
|
detailsFactory.speeding(decimal(rs, "avg_speed_kmh"), decimal(rs, "max_speed_kmh"), null),
|
||||||
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
sourcePackageRef != null && sourcePackageRef.hasAnyReference() ? sourcePackageRef : null,
|
||||||
detailsFactory.payloadFromMap(payload(rs, context, driverRef, vehicleRef, lifecycle)),
|
detailsFactory.payloadFromMap(payload(rs)),
|
||||||
false,
|
false,
|
||||||
context.packageInfo()
|
context.packageInfo()
|
||||||
);
|
);
|
||||||
|
|
@ -104,19 +105,9 @@ public class SpeedingEventRowMapper implements ExtractionRowMapper<TachographImp
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> payload(
|
private Map<String, Object> payload(ResultSet rs) throws SQLException {
|
||||||
ResultSet rs,
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
ExtractionContext<TachographImportRequest> context,
|
put(raw, "sourceRowId", string(rs, "source_row_id"));
|
||||||
DriverRefDto driverRef,
|
|
||||||
VehicleRefDto vehicleRef,
|
|
||||||
EventLifecycle lifecycle
|
|
||||||
) throws SQLException {
|
|
||||||
Map<String, Object> raw = TachographRawPayloadSupport.baseRawPayload(
|
|
||||||
rs, context, driverRef, vehicleRef, EventType.SPEEDING, lifecycle
|
|
||||||
);
|
|
||||||
put(raw, "supportEventType", EventType.SPEEDING.name());
|
|
||||||
put(raw, "avgSpeedKmh", decimal(rs, "avg_speed_kmh"));
|
|
||||||
put(raw, "maxSpeedKmh", decimal(rs, "max_speed_kmh"));
|
|
||||||
return Map.of("raw", raw);
|
return Map.of("raw", raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
package at.procon.eventhub.tachograph.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.DriverCardRefDto;
|
|
||||||
import at.procon.eventhub.dto.DriverRefDto;
|
|
||||||
import at.procon.eventhub.dto.EventLifecycle;
|
|
||||||
import at.procon.eventhub.dto.EventType;
|
|
||||||
import at.procon.eventhub.dto.VehicleRefDto;
|
|
||||||
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
|
|
||||||
import at.procon.eventhub.importing.extraction.ExtractionContext;
|
|
||||||
import at.procon.eventhub.tachograph.dto.TachographImportRequest;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
final class TachographRawPayloadSupport {
|
|
||||||
|
|
||||||
private TachographRawPayloadSupport() {
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, Object> baseRawPayload(
|
|
||||||
ResultSet rs,
|
|
||||||
ExtractionContext<TachographImportRequest> context,
|
|
||||||
DriverRefDto driverRef,
|
|
||||||
VehicleRefDto vehicleRef,
|
|
||||||
EventType eventType,
|
|
||||||
EventLifecycle lifecycle
|
|
||||||
) throws SQLException {
|
|
||||||
Map<String, Object> raw = new LinkedHashMap<>();
|
|
||||||
String sourceRowId = string(rs, "source_row_id");
|
|
||||||
put(raw, "sourceRowId", sourceRowId);
|
|
||||||
if (sourceRowId != null) {
|
|
||||||
put(raw, "sourceRowIds", List.of(sourceRowId));
|
|
||||||
put(raw, "intervalId", intervalId(context, sourceRowId));
|
|
||||||
}
|
|
||||||
put(raw, "sourceKind", context == null || context.planItem() == null ? null : context.planItem().sourceKind());
|
|
||||||
put(raw, "extractionCode", context == null || context.planItem() == null ? null : context.planItem().extractionCode());
|
|
||||||
put(raw, "level", "RAW_INTERVAL");
|
|
||||||
put(raw, "eventType", eventType == null ? null : eventType.name());
|
|
||||||
put(raw, "lifecycle", lifecycle == null ? null : lifecycle.name());
|
|
||||||
|
|
||||||
enrichDriver(raw, driverRef);
|
|
||||||
enrichVehicle(raw, vehicleRef);
|
|
||||||
enrichSourcePackage(raw, rs);
|
|
||||||
enrichOdometer(raw, rs);
|
|
||||||
return raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void put(Map<String, Object> target, String key, Object value) {
|
|
||||||
if (value != null) {
|
|
||||||
target.put(key, value instanceof Enum<?> enumValue ? enumValue.name() : value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String intervalId(ExtractionContext<TachographImportRequest> context, String sourceRowId) {
|
|
||||||
String extractionCode = context == null || context.planItem() == null ? null : context.planItem().extractionCode();
|
|
||||||
return "TACHOGRAPH:" + (extractionCode == null || extractionCode.isBlank() ? "UNKNOWN" : extractionCode) + ":" + sourceRowId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void enrichDriver(Map<String, Object> raw, DriverRefDto driverRef) {
|
|
||||||
if (driverRef == null || !driverRef.hasAnyReference()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
put(raw, "driverKey", driverRef.stableKey());
|
|
||||||
put(raw, "driverSourceEntityId", driverRef.sourceEntityId());
|
|
||||||
DriverCardRefDto driverCard = driverRef.driverCard();
|
|
||||||
if (driverCard != null && driverCard.hasValue()) {
|
|
||||||
put(raw, "driverCardKey", driverCard.stableKey());
|
|
||||||
put(raw, "driverCardNation", driverCard.nation());
|
|
||||||
put(raw, "driverCardNationNumericCode", driverCard.nationNumericCode());
|
|
||||||
put(raw, "driverCardNumber", driverCard.number());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void enrichVehicle(Map<String, Object> raw, VehicleRefDto vehicleRef) {
|
|
||||||
if (vehicleRef == null || !vehicleRef.hasAnyReference()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
put(raw, "vehicleKey", vehicleRef.stableKey());
|
|
||||||
put(raw, "vehicleSourceEntityId", vehicleRef.sourceVehicleEntityId());
|
|
||||||
put(raw, "vehicleVin", vehicleRef.vin());
|
|
||||||
put(raw, "vehicleRegistrationSourceEntityId", vehicleRef.sourceRegistrationEntityId());
|
|
||||||
VehicleRegistrationRefDto registration = vehicleRef.vehicleRegistration();
|
|
||||||
if (registration != null && registration.hasValue()) {
|
|
||||||
put(raw, "registrationKey", registration.stableKey());
|
|
||||||
put(raw, "vehicleRegistrationNation", registration.nation());
|
|
||||||
put(raw, "vehicleRegistrationNationNumericCode", registration.nationNumericCode());
|
|
||||||
put(raw, "vehicleRegistrationNumber", registration.number());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void enrichSourcePackage(Map<String, Object> raw, ResultSet rs) throws SQLException {
|
|
||||||
put(raw, "sourcePackageKind", string(rs, "source_package_kind"));
|
|
||||||
put(raw, "sourcePackageId", string(rs, "source_package_id"));
|
|
||||||
put(raw, "sourcePackageEntityId", string(rs, "source_package_entity_id"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void enrichOdometer(Map<String, Object> raw, ResultSet rs) throws SQLException {
|
|
||||||
Long odometerM = longValue(rs, "odometer_m");
|
|
||||||
put(raw, "odometerM", odometerM);
|
|
||||||
if (odometerM != null) {
|
|
||||||
put(raw, "odometerKm", odometerM / 1_000L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String string(ResultSet rs, String column) throws SQLException {
|
|
||||||
try {
|
|
||||||
String value = rs.getString(column);
|
|
||||||
return value == null || value.isBlank() ? null : value.trim();
|
|
||||||
} catch (SQLException ex) {
|
|
||||||
if (missingColumn(ex)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Long longValue(ResultSet rs, String column) throws SQLException {
|
|
||||||
Object value;
|
|
||||||
try {
|
|
||||||
value = rs.getObject(column);
|
|
||||||
} catch (SQLException ex) {
|
|
||||||
if (missingColumn(ex)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (value instanceof Number number) {
|
|
||||||
return number.longValue();
|
|
||||||
}
|
|
||||||
return Long.parseLong(value.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean missingColumn(SQLException ex) {
|
|
||||||
String state = ex.getSQLState();
|
|
||||||
String message = ex.getMessage();
|
|
||||||
return "S0022".equals(state)
|
|
||||||
|| "42703".equals(state)
|
|
||||||
|| (message != null && message.toLowerCase(java.util.Locale.ROOT).contains("column")
|
|
||||||
&& (message.toLowerCase(java.util.Locale.ROOT).contains("not found")
|
|
||||||
|| message.toLowerCase(java.util.Locale.ROOT).contains("not valid")
|
|
||||||
|| message.toLowerCase(java.util.Locale.ROOT).contains("invalid")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package at.procon.eventhub.tachographfilesession.dto;
|
package at.procon.eventhub.tachographfilesession.dto;
|
||||||
|
|
||||||
import at.procon.eventhub.processing.driverworkingtime.dto.DriverWorkingTimeProcessingResultDto;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
|
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent;
|
import at.procon.eventhub.tachographfilesession.model.TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent;
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
||||||
|
|
@ -14,7 +13,6 @@ import java.time.OffsetDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Deprecated(forRemoval = false)
|
|
||||||
public record TachographEsperDriverProcessingResultDto(
|
public record TachographEsperDriverProcessingResultDto(
|
||||||
UUID sessionId,
|
UUID sessionId,
|
||||||
String driverKey,
|
String driverKey,
|
||||||
|
|
@ -51,86 +49,4 @@ public record TachographEsperDriverProcessingResultDto(
|
||||||
List<TachographEsperSupportGeoEvent> supportGeoEvents,
|
List<TachographEsperSupportGeoEvent> supportGeoEvents,
|
||||||
List<String> notes
|
List<String> notes
|
||||||
) {
|
) {
|
||||||
public static TachographEsperDriverProcessingResultDto fromDriverWorkingTime(
|
|
||||||
DriverWorkingTimeProcessingResultDto result
|
|
||||||
) {
|
|
||||||
if (result == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new TachographEsperDriverProcessingResultDto(
|
|
||||||
result.sessionId(),
|
|
||||||
result.driverKey(),
|
|
||||||
result.sourceKind(),
|
|
||||||
result.loadedFrom(),
|
|
||||||
result.loadedTo(),
|
|
||||||
result.requestedFrom(),
|
|
||||||
result.requestedTo(),
|
|
||||||
result.activityIntervalCount(),
|
|
||||||
result.drivingIntervalCount(),
|
|
||||||
result.drivingInterruptionIntervalCount(),
|
|
||||||
result.drivingInterruptionVehicleChangeIntervalCount(),
|
|
||||||
result.dailyWeeklyRestCandidateIntervalCount(),
|
|
||||||
result.dailyWeeklyRestCandidateCoverageIntervalCount(),
|
|
||||||
result.unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount(),
|
|
||||||
result.potentialHomeOvernightStayIntervalCount(),
|
|
||||||
result.potentialInVehicleOvernightStayIntervalCount(),
|
|
||||||
result.potentialInVehicleTripIntervalCount(),
|
|
||||||
result.vehicleUsageIntervalCount(),
|
|
||||||
result.vuCardAbsentIntervalCount(),
|
|
||||||
result.supportGeoEventCount(),
|
|
||||||
result.activityIntervals(),
|
|
||||||
result.drivingIntervals(),
|
|
||||||
result.drivingInterruptionIntervals(),
|
|
||||||
result.drivingInterruptionVehicleChangeIntervals(),
|
|
||||||
result.dailyWeeklyRestCandidateIntervals(),
|
|
||||||
result.dailyWeeklyRestCandidateCoverageIntervals(),
|
|
||||||
result.unclassifiedDailyWeeklyRestCandidateCoverageIntervals(),
|
|
||||||
result.potentialHomeOvernightStayIntervals(),
|
|
||||||
result.potentialInVehicleOvernightStayIntervals(),
|
|
||||||
result.potentialInVehicleTripIntervals(),
|
|
||||||
result.vehicleUsageIntervals(),
|
|
||||||
result.vuCardAbsentIntervals(),
|
|
||||||
result.supportGeoEvents(),
|
|
||||||
result.notes()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DriverWorkingTimeProcessingResultDto toDriverWorkingTime() {
|
|
||||||
return new DriverWorkingTimeProcessingResultDto(
|
|
||||||
sessionId,
|
|
||||||
driverKey,
|
|
||||||
sourceKind,
|
|
||||||
loadedFrom,
|
|
||||||
loadedTo,
|
|
||||||
requestedFrom,
|
|
||||||
requestedTo,
|
|
||||||
activityIntervalCount,
|
|
||||||
drivingIntervalCount,
|
|
||||||
drivingInterruptionIntervalCount,
|
|
||||||
drivingInterruptionVehicleChangeIntervalCount,
|
|
||||||
dailyWeeklyRestCandidateIntervalCount,
|
|
||||||
dailyWeeklyRestCandidateCoverageIntervalCount,
|
|
||||||
unclassifiedDailyWeeklyRestCandidateCoverageIntervalCount,
|
|
||||||
potentialHomeOvernightStayIntervalCount,
|
|
||||||
potentialInVehicleOvernightStayIntervalCount,
|
|
||||||
potentialInVehicleTripIntervalCount,
|
|
||||||
vehicleUsageIntervalCount,
|
|
||||||
vuCardAbsentIntervalCount,
|
|
||||||
supportGeoEventCount,
|
|
||||||
activityIntervals,
|
|
||||||
drivingIntervals,
|
|
||||||
drivingInterruptionIntervals,
|
|
||||||
drivingInterruptionVehicleChangeIntervals,
|
|
||||||
dailyWeeklyRestCandidateIntervals,
|
|
||||||
dailyWeeklyRestCandidateCoverageIntervals,
|
|
||||||
unclassifiedDailyWeeklyRestCandidateCoverageIntervals,
|
|
||||||
potentialHomeOvernightStayIntervals,
|
|
||||||
potentialInVehicleOvernightStayIntervals,
|
|
||||||
potentialInVehicleTripIntervals,
|
|
||||||
vehicleUsageIntervals,
|
|
||||||
vuCardAbsentIntervals,
|
|
||||||
supportGeoEvents,
|
|
||||||
notes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,19 +20,6 @@ public interface DriverTimelineEventBuilder {
|
||||||
ResolvedDriverTimeline timeline
|
ResolvedDriverTimeline timeline
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the rawest point-event representation supported by the implementation.
|
|
||||||
*
|
|
||||||
* <p>The default preserves the existing interval-backed behavior. Implementations that can
|
|
||||||
* emit point events directly from source records should override this method.</p>
|
|
||||||
*/
|
|
||||||
default TachographTimelineEventBundle buildRawEventBundle(
|
|
||||||
TachographFileSession session,
|
|
||||||
DriverExtractionSession driverSession
|
|
||||||
) {
|
|
||||||
return buildEventBundle(session, driverSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
default List<EventHubEventDto> buildEvents(
|
default List<EventHubEventDto> buildEvents(
|
||||||
TachographFileSession session,
|
TachographFileSession session,
|
||||||
DriverExtractionSession driverSession
|
DriverExtractionSession driverSession
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,7 @@ import com.espertech.esper.runtime.client.EPDeployException;
|
||||||
import com.espertech.esper.runtime.client.EPDeployment;
|
import com.espertech.esper.runtime.client.EPDeployment;
|
||||||
import com.espertech.esper.runtime.client.EPRuntime;
|
import com.espertech.esper.runtime.client.EPRuntime;
|
||||||
import com.espertech.esper.runtime.client.EPRuntimeProvider;
|
import com.espertech.esper.runtime.client.EPRuntimeProvider;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import at.procon.eventhub.config.EventHubProperties;
|
import at.procon.eventhub.config.EventHubProperties;
|
||||||
import at.procon.eventhub.dto.EventDomain;
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.dto.EventLifecycle;
|
|
||||||
import at.procon.eventhub.dto.EventType;
|
|
||||||
import at.procon.eventhub.processing.service.UnifiedEventTimelineReconstructor;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
|
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
|
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
|
||||||
|
|
@ -45,7 +39,6 @@ import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StreamUtils;
|
import org.springframework.util.StreamUtils;
|
||||||
|
|
@ -55,42 +48,16 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
|
|
||||||
private static final AtomicLong RUNTIME_COUNTER = new AtomicLong();
|
private static final AtomicLong RUNTIME_COUNTER = new AtomicLong();
|
||||||
private static final String DRIVING_DERIVED_PROJECTION_BUNDLE_EPL_TEMPLATE =
|
private static final String DRIVING_DERIVED_PROJECTION_BUNDLE_EPL_TEMPLATE =
|
||||||
loadResource("esper/driver-working-time-derived-projections.epl");
|
loadResource("esper/tachograph-driving-derived-projection-bundle.epl");
|
||||||
private static final String DRIVING_DERIVED_PROJECTION_EVENTS_PREPROCESSOR_EPL =
|
|
||||||
loadResource("esper/runtime-driver-event-interval-preprocessor.epl");
|
|
||||||
|
|
||||||
private final DriverTimelineBuilder driverTimelineBuilder;
|
private final DriverTimelineBuilder driverTimelineBuilder;
|
||||||
private final RawSourceDriverTimelineEventBuilder rawSourceEventBuilder;
|
|
||||||
private final UnifiedEventTimelineReconstructor timelineReconstructor;
|
|
||||||
private final EventHubProperties properties;
|
private final EventHubProperties properties;
|
||||||
|
|
||||||
public DriverTimelineReusableProjectionBuilder(
|
public DriverTimelineReusableProjectionBuilder(
|
||||||
DriverTimelineBuilder driverTimelineBuilder,
|
DriverTimelineBuilder driverTimelineBuilder,
|
||||||
EventHubProperties properties
|
EventHubProperties properties
|
||||||
) {
|
|
||||||
this(driverTimelineBuilder, null, new UnifiedEventTimelineReconstructor(), properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DriverTimelineReusableProjectionBuilder(
|
|
||||||
DriverTimelineBuilder driverTimelineBuilder,
|
|
||||||
RawSourceDriverTimelineEventBuilder rawSourceEventBuilder,
|
|
||||||
EventHubProperties properties
|
|
||||||
) {
|
|
||||||
this(driverTimelineBuilder, rawSourceEventBuilder, new UnifiedEventTimelineReconstructor(), properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public DriverTimelineReusableProjectionBuilder(
|
|
||||||
DriverTimelineBuilder driverTimelineBuilder,
|
|
||||||
RawSourceDriverTimelineEventBuilder rawSourceEventBuilder,
|
|
||||||
UnifiedEventTimelineReconstructor timelineReconstructor,
|
|
||||||
EventHubProperties properties
|
|
||||||
) {
|
) {
|
||||||
this.driverTimelineBuilder = driverTimelineBuilder;
|
this.driverTimelineBuilder = driverTimelineBuilder;
|
||||||
this.rawSourceEventBuilder = rawSourceEventBuilder;
|
|
||||||
this.timelineReconstructor = timelineReconstructor == null
|
|
||||||
? new UnifiedEventTimelineReconstructor()
|
|
||||||
: timelineReconstructor;
|
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,18 +67,6 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
int significantDrivingMinutes,
|
int significantDrivingMinutes,
|
||||||
int minimumRestPeriodMinutes
|
int minimumRestPeriodMinutes
|
||||||
) {
|
) {
|
||||||
if (session == null || driverSession == null) {
|
|
||||||
return emptyBundle();
|
|
||||||
}
|
|
||||||
if (drivingDerivedProjectionInputMode() == EventHubProperties.DrivingDerivedProjectionInputMode.EVENTS) {
|
|
||||||
return buildEsperDrivingDerivedProjectionBundleFromEvents(
|
|
||||||
session.sessionId(),
|
|
||||||
driverSession.driverKey(),
|
|
||||||
rawSourceEventBuilder().buildRawEventBundle(session, driverSession).allEvents(),
|
|
||||||
significantDrivingMinutes,
|
|
||||||
minimumRestPeriodMinutes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ResolvedDriverTimeline timeline = driverTimelineBuilder.build(session, driverSession);
|
ResolvedDriverTimeline timeline = driverTimelineBuilder.build(session, driverSession);
|
||||||
return buildEsperDrivingDerivedProjectionBundle(
|
return buildEsperDrivingDerivedProjectionBundle(
|
||||||
session.sessionId(),
|
session.sessionId(),
|
||||||
|
|
@ -131,41 +86,6 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
return emptyBundle();
|
return emptyBundle();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drivingDerivedProjectionInputMode() == EventHubProperties.DrivingDerivedProjectionInputMode.EVENTS) {
|
|
||||||
List<Map<String, Object>> activityInputEvents = new ArrayList<>();
|
|
||||||
List<Map<String, Object>> vehicleUsageInputEvents = new ArrayList<>();
|
|
||||||
List<Map<String, Object>> supportGeoInputEvents = new ArrayList<>();
|
|
||||||
|
|
||||||
for (DriverExtractionSession driverSession : session.driversByKey().values()) {
|
|
||||||
if (driverSession == null || driverSession.driverKey() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ResolvedDriverTimeline timeline = reconstructMergedTimelineFromEvents(
|
|
||||||
session.sessionId(),
|
|
||||||
driverSession.driverKey(),
|
|
||||||
rawSourceEventBuilder().buildRawEventBundle(session, driverSession).allEvents()
|
|
||||||
);
|
|
||||||
if (timeline == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
activityInputEvents.addAll(buildActivityIntervalInputEvents(
|
|
||||||
session.sessionId(),
|
|
||||||
driverSession.driverKey(),
|
|
||||||
timeline.activityIntervals()
|
|
||||||
));
|
|
||||||
vehicleUsageInputEvents.addAll(buildVehicleUsageIntervalInputEvents(timeline.vehicleUsageIntervals()));
|
|
||||||
supportGeoInputEvents.addAll(buildSupportGeoInputEvents(session.sessionId(), timeline.supportEvents()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildEsperDrivingDerivedProjectionBundle(
|
|
||||||
activityInputEvents,
|
|
||||||
vehicleUsageInputEvents,
|
|
||||||
supportGeoInputEvents,
|
|
||||||
significantDrivingMinutes,
|
|
||||||
minimumRestPeriodMinutes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Map<String, Object>> activityInputEvents = new ArrayList<>();
|
List<Map<String, Object>> activityInputEvents = new ArrayList<>();
|
||||||
List<Map<String, Object>> vehicleUsageInputEvents = new ArrayList<>();
|
List<Map<String, Object>> vehicleUsageInputEvents = new ArrayList<>();
|
||||||
List<Map<String, Object>> supportGeoInputEvents = new ArrayList<>();
|
List<Map<String, Object>> supportGeoInputEvents = new ArrayList<>();
|
||||||
|
|
@ -314,353 +234,6 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundleFromEvents(
|
|
||||||
List<EventHubEventDto> events,
|
|
||||||
int significantDrivingMinutes,
|
|
||||||
int minimumRestPeriodMinutes
|
|
||||||
) {
|
|
||||||
return buildEsperDrivingDerivedProjectionBundleFromEvents(
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
events,
|
|
||||||
significantDrivingMinutes,
|
|
||||||
minimumRestPeriodMinutes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundleFromEvents(
|
|
||||||
UUID fallbackSessionId,
|
|
||||||
String fallbackDriverKey,
|
|
||||||
List<EventHubEventDto> events,
|
|
||||||
int significantDrivingMinutes,
|
|
||||||
int minimumRestPeriodMinutes
|
|
||||||
) {
|
|
||||||
if (fallbackDriverKey == null) {
|
|
||||||
Map<String, List<EventHubEventDto>> eventsByDriver = groupEventsByDriverKey(events);
|
|
||||||
if (eventsByDriver.size() > 1) {
|
|
||||||
return buildEsperDrivingDerivedProjectionBundleFromGroupedEvents(
|
|
||||||
fallbackSessionId,
|
|
||||||
eventsByDriver,
|
|
||||||
significantDrivingMinutes,
|
|
||||||
minimumRestPeriodMinutes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (eventsByDriver.size() == 1) {
|
|
||||||
Map.Entry<String, List<EventHubEventDto>> onlyDriver = eventsByDriver.entrySet().iterator().next();
|
|
||||||
fallbackDriverKey = onlyDriver.getKey();
|
|
||||||
events = onlyDriver.getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ResolvedDriverTimeline reconstructedTimeline = reconstructMergedTimelineFromEvents(
|
|
||||||
fallbackSessionId,
|
|
||||||
fallbackDriverKey,
|
|
||||||
events
|
|
||||||
);
|
|
||||||
return buildEsperDrivingDerivedProjectionBundle(
|
|
||||||
fallbackSessionId == null ? new UUID(0L, 0L) : fallbackSessionId,
|
|
||||||
fallbackDriverKey,
|
|
||||||
reconstructedTimeline,
|
|
||||||
significantDrivingMinutes,
|
|
||||||
minimumRestPeriodMinutes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundleFromGroupedEvents(
|
|
||||||
UUID fallbackSessionId,
|
|
||||||
Map<String, List<EventHubEventDto>> eventsByDriver,
|
|
||||||
int significantDrivingMinutes,
|
|
||||||
int minimumRestPeriodMinutes
|
|
||||||
) {
|
|
||||||
List<Map<String, Object>> activityInputEvents = new ArrayList<>();
|
|
||||||
List<Map<String, Object>> vehicleUsageInputEvents = new ArrayList<>();
|
|
||||||
List<Map<String, Object>> supportGeoInputEvents = new ArrayList<>();
|
|
||||||
UUID sessionId = fallbackSessionId == null ? new UUID(0L, 0L) : fallbackSessionId;
|
|
||||||
|
|
||||||
for (Map.Entry<String, List<EventHubEventDto>> entry : eventsByDriver.entrySet()) {
|
|
||||||
String driverKey = entry.getKey();
|
|
||||||
if (driverKey == null || driverKey.isBlank()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ResolvedDriverTimeline timeline = reconstructMergedTimelineFromEvents(
|
|
||||||
sessionId,
|
|
||||||
driverKey,
|
|
||||||
entry.getValue()
|
|
||||||
);
|
|
||||||
if (timeline == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
activityInputEvents.addAll(buildActivityIntervalInputEvents(
|
|
||||||
sessionId,
|
|
||||||
driverKey,
|
|
||||||
timeline.activityIntervals()
|
|
||||||
));
|
|
||||||
vehicleUsageInputEvents.addAll(buildVehicleUsageIntervalInputEvents(timeline.vehicleUsageIntervals()));
|
|
||||||
supportGeoInputEvents.addAll(buildSupportGeoInputEvents(sessionId, timeline.supportEvents()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildEsperDrivingDerivedProjectionBundle(
|
|
||||||
activityInputEvents,
|
|
||||||
vehicleUsageInputEvents,
|
|
||||||
supportGeoInputEvents,
|
|
||||||
significantDrivingMinutes,
|
|
||||||
minimumRestPeriodMinutes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, List<EventHubEventDto>> groupEventsByDriverKey(List<EventHubEventDto> events) {
|
|
||||||
Map<String, List<EventHubEventDto>> grouped = new LinkedHashMap<>();
|
|
||||||
for (EventHubEventDto event : safeList(events)) {
|
|
||||||
String driverKey = eventDriverKey(event);
|
|
||||||
if (driverKey == null || driverKey.isBlank()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
grouped.computeIfAbsent(driverKey, ignored -> new ArrayList<>()).add(event);
|
|
||||||
}
|
|
||||||
return grouped;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String eventDriverKey(EventHubEventDto event) {
|
|
||||||
if (event == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = rawPayload(event);
|
|
||||||
return firstNonBlank(text(raw, "driverKey"), driverKey(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResolvedDriverTimeline reconstructMergedTimelineFromEvents(
|
|
||||||
UUID fallbackSessionId,
|
|
||||||
String fallbackDriverKey,
|
|
||||||
List<EventHubEventDto> events
|
|
||||||
) {
|
|
||||||
ResolvedDriverTimeline reconstructed = timelineReconstructor.reconstruct(
|
|
||||||
fallbackSessionId == null ? new UUID(0L, 0L) : fallbackSessionId,
|
|
||||||
fallbackDriverKey,
|
|
||||||
safeList(events)
|
|
||||||
);
|
|
||||||
return new ResolvedDriverTimeline(
|
|
||||||
reconstructed.sourceKind(),
|
|
||||||
reconstructed.loadedFrom(),
|
|
||||||
reconstructed.loadedTo(),
|
|
||||||
mergeVehicleUsageIntervals(reconstructed.vehicleUsageIntervals(), reconstructed.sourceKind()),
|
|
||||||
reconstructed.activityIntervals(),
|
|
||||||
reconstructed.supportEvents(),
|
|
||||||
reconstructed.warnings()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ResolvedVehicleUsageInterval> mergeVehicleUsageIntervals(
|
|
||||||
List<ResolvedVehicleUsageInterval> intervals,
|
|
||||||
String sourceKind
|
|
||||||
) {
|
|
||||||
List<ResolvedVehicleUsageInterval> sorted = safeList(intervals).stream()
|
|
||||||
.sorted(Comparator.comparing(ResolvedVehicleUsageInterval::from)
|
|
||||||
.thenComparing(ResolvedVehicleUsageInterval::to, Comparator.nullsLast(Comparator.naturalOrder())))
|
|
||||||
.toList();
|
|
||||||
if (sorted.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ResolvedVehicleUsageInterval> result = new ArrayList<>();
|
|
||||||
ResolvedVehicleUsageInterval current = sorted.getFirst();
|
|
||||||
List<String> currentSources = new ArrayList<>(current.sourceIntervalIds());
|
|
||||||
for (int i = 1; i < sorted.size(); i++) {
|
|
||||||
ResolvedVehicleUsageInterval next = sorted.get(i);
|
|
||||||
if (canMergeVehicleUsage(current, next)) {
|
|
||||||
currentSources.addAll(next.sourceIntervalIds());
|
|
||||||
current = ResolvedVehicleUsageInterval.resolved(
|
|
||||||
current.sessionId(),
|
|
||||||
current.driverKey(),
|
|
||||||
current.intervalId() + "+" + next.intervalId(),
|
|
||||||
current.from(),
|
|
||||||
mergedTo(current.to(), next.to()),
|
|
||||||
current.odometerBeginKm(),
|
|
||||||
mergedOdometerEnd(current, next),
|
|
||||||
current.registrationKey(),
|
|
||||||
current.vehicleKey(),
|
|
||||||
sourceKind,
|
|
||||||
currentSources
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
result.add(current);
|
|
||||||
current = next;
|
|
||||||
currentSources = new ArrayList<>(current.sourceIntervalIds());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.add(current);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean canMergeVehicleUsage(ResolvedVehicleUsageInterval left, ResolvedVehicleUsageInterval right) {
|
|
||||||
return Objects.equals(left.registrationKey(), right.registrationKey())
|
|
||||||
&& Objects.equals(left.vehicleKey(), right.vehicleKey())
|
|
||||||
&& !right.from().isAfter(mergeBoundary(left.to()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long mergedOdometerEnd(
|
|
||||||
ResolvedVehicleUsageInterval current,
|
|
||||||
ResolvedVehicleUsageInterval next
|
|
||||||
) {
|
|
||||||
if (current == null) {
|
|
||||||
return next == null ? null : next.odometerEndKm();
|
|
||||||
}
|
|
||||||
if (next == null) {
|
|
||||||
return current.odometerEndKm();
|
|
||||||
}
|
|
||||||
if (current.to() == null || next.to() == null) {
|
|
||||||
return next.odometerEndKm() != null ? next.odometerEndKm() : current.odometerEndKm();
|
|
||||||
}
|
|
||||||
if (next.to().isAfter(current.to()) || next.to().isEqual(current.to())) {
|
|
||||||
return next.odometerEndKm() != null ? next.odometerEndKm() : current.odometerEndKm();
|
|
||||||
}
|
|
||||||
return current.odometerEndKm() != null ? current.odometerEndKm() : next.odometerEndKm();
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime mergedTo(OffsetDateTime left, OffsetDateTime right) {
|
|
||||||
if (left == null || right == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return left.isAfter(right) ? left : right;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime mergeBoundary(OffsetDateTime endInclusive) {
|
|
||||||
return endInclusive == null ? OffsetDateTime.MAX : endInclusive.plusSeconds(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundleFromPointInput(
|
|
||||||
List<Map<String, Object>> activityPointInputEvents,
|
|
||||||
List<Map<String, Object>> vehicleUsagePointInputEvents,
|
|
||||||
List<Map<String, Object>> supportGeoInputEvents,
|
|
||||||
int significantDrivingMinutes,
|
|
||||||
int minimumRestPeriodMinutes
|
|
||||||
) {
|
|
||||||
if ((activityPointInputEvents == null || activityPointInputEvents.isEmpty())
|
|
||||||
&& (vehicleUsagePointInputEvents == null || vehicleUsagePointInputEvents.isEmpty())) {
|
|
||||||
return emptyBundle();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals = new ArrayList<>();
|
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals = new ArrayList<>();
|
|
||||||
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> dailyWeeklyRestCandidateCoverageIntervals = new ArrayList<>();
|
|
||||||
List<TachographEsperDailyWeeklyRestCandidateCoverageIntervalEvent> unclassifiedDailyWeeklyRestCandidateCoverageIntervals = new ArrayList<>();
|
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals = new ArrayList<>();
|
|
||||||
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals = new ArrayList<>();
|
|
||||||
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals = new ArrayList<>();
|
|
||||||
List<TachographEsperPotentialInVehicleOvernightStayIntervalEvent> potentialInVehicleOvernightStayIntervals = new ArrayList<>();
|
|
||||||
List<TachographEsperPotentialInVehicleTripIntervalEvent> potentialInVehicleTripIntervals = new ArrayList<>();
|
|
||||||
|
|
||||||
executeWithRuntime(
|
|
||||||
configuration -> {
|
|
||||||
configuration.getCommon().addEventType(
|
|
||||||
"TachographActivityPointInputEvent",
|
|
||||||
activityPointInputDefinition()
|
|
||||||
);
|
|
||||||
configuration.getCommon().addEventType(
|
|
||||||
"TachographVehicleUsagePointInputEvent",
|
|
||||||
vehicleUsagePointInputDefinition()
|
|
||||||
);
|
|
||||||
configuration.getCommon().addEventType(
|
|
||||||
"TachographProjectionFinalizeEvent",
|
|
||||||
projectionFinalizeInputDefinition()
|
|
||||||
);
|
|
||||||
configuration.getCommon().addEventType(
|
|
||||||
"TachographActivityIntervalInputEvent",
|
|
||||||
activityIntervalInputDefinition()
|
|
||||||
);
|
|
||||||
configuration.getCommon().addEventType(
|
|
||||||
"TachographVehicleUsageIntervalInputEvent",
|
|
||||||
vehicleUsageIntervalInputDefinition()
|
|
||||||
);
|
|
||||||
configuration.getCommon().addEventType(
|
|
||||||
"TachographSupportGeoEvidenceInputEvent",
|
|
||||||
supportGeoEvidenceInputDefinition()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
renderDrivingDerivedProjectionEventsEpl(significantDrivingMinutes, minimumRestPeriodMinutes),
|
|
||||||
Map.of(
|
|
||||||
"drivingInterruptionIntervals", newData -> collectDrivingInterruptionIntervalEvents(newData, drivingInterruptionIntervals),
|
|
||||||
"dailyWeeklyRestCandidateIntervals", newData -> collectDrivingInterruptionIntervalEvents(newData, dailyWeeklyRestCandidateIntervals),
|
|
||||||
"dailyWeeklyRestCandidateCoverageIntervals", newData -> collectDailyWeeklyRestCandidateCoverageIntervalEvents(newData, dailyWeeklyRestCandidateCoverageIntervals),
|
|
||||||
"unclassifiedDailyWeeklyRestCandidateCoverageIntervals", newData -> collectDailyWeeklyRestCandidateCoverageIntervalEvents(newData, unclassifiedDailyWeeklyRestCandidateCoverageIntervals),
|
|
||||||
"drivingInterruptionVehicleChangeIntervals", newData -> collectDrivingInterruptionIntervalEvents(newData, drivingInterruptionVehicleChangeIntervals),
|
|
||||||
"vuCardAbsentIntervals", newData -> collectVuCardAbsentIntervalEvents(newData, vuCardAbsentIntervals),
|
|
||||||
"potentialHomeOvernightStayIntervals", newData -> collectPotentialHomeOvernightStayIntervalEvents(newData, potentialHomeOvernightStayIntervals),
|
|
||||||
"potentialInVehicleOvernightStayIntervals", newData -> collectPotentialInVehicleOvernightStayIntervalEvents(newData, potentialInVehicleOvernightStayIntervals),
|
|
||||||
"potentialInVehicleTripIntervals", newData -> collectPotentialInVehicleTripIntervalEvents(newData, potentialInVehicleTripIntervals)
|
|
||||||
),
|
|
||||||
runtime -> {
|
|
||||||
if (supportGeoInputEvents != null) {
|
|
||||||
for (Map<String, Object> supportGeoEvidence : supportGeoInputEvents) {
|
|
||||||
runtime.getEventService().sendEventMap(
|
|
||||||
supportGeoEvidence,
|
|
||||||
"TachographSupportGeoEvidenceInputEvent"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (vehicleUsagePointInputEvents != null) {
|
|
||||||
for (Map<String, Object> point : vehicleUsagePointInputEvents) {
|
|
||||||
runtime.getEventService().sendEventMap(
|
|
||||||
point,
|
|
||||||
"TachographVehicleUsagePointInputEvent"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (Map<String, Object> finalizeEvent : buildProjectionFinalizeEvents(vehicleUsagePointInputEvents)) {
|
|
||||||
runtime.getEventService().sendEventMap(
|
|
||||||
finalizeEvent,
|
|
||||||
"TachographProjectionFinalizeEvent"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (activityPointInputEvents != null) {
|
|
||||||
for (Map<String, Object> point : activityPointInputEvents) {
|
|
||||||
runtime.getEventService().sendEventMap(
|
|
||||||
point,
|
|
||||||
"TachographActivityPointInputEvent"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return new TachographEsperDrivingDerivedProjectionBundle(
|
|
||||||
sortDrivingInterruptionIntervals(drivingInterruptionIntervals),
|
|
||||||
sortDrivingInterruptionIntervals(dailyWeeklyRestCandidateIntervals),
|
|
||||||
sortDailyWeeklyRestCandidateCoverageIntervals(dailyWeeklyRestCandidateCoverageIntervals),
|
|
||||||
sortDailyWeeklyRestCandidateCoverageIntervals(unclassifiedDailyWeeklyRestCandidateCoverageIntervals),
|
|
||||||
sortDrivingInterruptionIntervals(drivingInterruptionVehicleChangeIntervals),
|
|
||||||
sortVuCardAbsentIntervals(vuCardAbsentIntervals),
|
|
||||||
sortPotentialHomeOvernightStayIntervals(potentialHomeOvernightStayIntervals),
|
|
||||||
sortPotentialInVehicleOvernightStayIntervals(potentialInVehicleOvernightStayIntervals),
|
|
||||||
sortPotentialInVehicleTripIntervals(potentialInVehicleTripIntervals)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Map<String, Object>> buildProjectionFinalizeEvents(
|
|
||||||
List<Map<String, Object>> vehicleUsagePointInputEvents
|
|
||||||
) {
|
|
||||||
Map<String, Map<String, Object>> byDriver = new LinkedHashMap<>();
|
|
||||||
for (Map<String, Object> point : safeList(vehicleUsagePointInputEvents)) {
|
|
||||||
String driverKey = Objects.toString(point.get("driverKey"), null);
|
|
||||||
if (driverKey == null || driverKey.isBlank()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Map<String, Object> finalizeEvent = byDriver.computeIfAbsent(driverKey, ignored -> {
|
|
||||||
Map<String, Object> event = new LinkedHashMap<>();
|
|
||||||
event.put("sessionId", point.get("sessionId"));
|
|
||||||
event.put("driverKey", driverKey);
|
|
||||||
event.put("finalizedAtEpochSecond", 0L);
|
|
||||||
return event;
|
|
||||||
});
|
|
||||||
Long occurredAtEpochSecond = (Long) point.get("occurredAtEpochSecond");
|
|
||||||
Long finalizedAtEpochSecond = (Long) finalizeEvent.get("finalizedAtEpochSecond");
|
|
||||||
if (occurredAtEpochSecond != null
|
|
||||||
&& (finalizedAtEpochSecond == null || occurredAtEpochSecond > finalizedAtEpochSecond)) {
|
|
||||||
finalizeEvent.put("finalizedAtEpochSecond", occurredAtEpochSecond);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new ArrayList<>(byDriver.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Map<String, Object>> buildActivityIntervalInputEvents(
|
private List<Map<String, Object>> buildActivityIntervalInputEvents(
|
||||||
UUID sessionId,
|
UUID sessionId,
|
||||||
String driverKey,
|
String driverKey,
|
||||||
|
|
@ -751,56 +324,6 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> activityPointInputDefinition() {
|
|
||||||
Map<String, Object> definition = new LinkedHashMap<>();
|
|
||||||
definition.put("sessionId", UUID.class);
|
|
||||||
definition.put("driverKey", String.class);
|
|
||||||
definition.put("eventId", String.class);
|
|
||||||
definition.put("intervalId", String.class);
|
|
||||||
definition.put("sourceRowId", String.class);
|
|
||||||
definition.put("sourceRowIds", java.util.List.class);
|
|
||||||
definition.put("activityType", String.class);
|
|
||||||
definition.put("lifecycle", String.class);
|
|
||||||
definition.put("occurredAt", OffsetDateTime.class);
|
|
||||||
definition.put("occurredAtEpochSecond", long.class);
|
|
||||||
definition.put("cardSlot", String.class);
|
|
||||||
definition.put("cardStatus", String.class);
|
|
||||||
definition.put("drivingStatus", String.class);
|
|
||||||
definition.put("registrationKey", String.class);
|
|
||||||
definition.put("vehicleKey", String.class);
|
|
||||||
definition.put("sourceKind", String.class);
|
|
||||||
definition.put("synthetic", boolean.class);
|
|
||||||
definition.put("clippedToRequestedPeriod", boolean.class);
|
|
||||||
definition.put("level", String.class);
|
|
||||||
return definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> vehicleUsagePointInputDefinition() {
|
|
||||||
Map<String, Object> definition = new LinkedHashMap<>();
|
|
||||||
definition.put("sessionId", UUID.class);
|
|
||||||
definition.put("driverKey", String.class);
|
|
||||||
definition.put("eventId", String.class);
|
|
||||||
definition.put("intervalId", String.class);
|
|
||||||
definition.put("sourceRowId", String.class);
|
|
||||||
definition.put("sourceRowIds", java.util.List.class);
|
|
||||||
definition.put("lifecycle", String.class);
|
|
||||||
definition.put("occurredAt", OffsetDateTime.class);
|
|
||||||
definition.put("occurredAtEpochSecond", long.class);
|
|
||||||
definition.put("registrationKey", String.class);
|
|
||||||
definition.put("vehicleKey", String.class);
|
|
||||||
definition.put("sourceKind", String.class);
|
|
||||||
definition.put("odometerKm", Long.class);
|
|
||||||
return definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> projectionFinalizeInputDefinition() {
|
|
||||||
Map<String, Object> definition = new LinkedHashMap<>();
|
|
||||||
definition.put("sessionId", UUID.class);
|
|
||||||
definition.put("driverKey", String.class);
|
|
||||||
definition.put("finalizedAtEpochSecond", long.class);
|
|
||||||
return definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> activityIntervalInputDefinition() {
|
private Map<String, Object> activityIntervalInputDefinition() {
|
||||||
Map<String, Object> definition = new LinkedHashMap<>();
|
Map<String, Object> definition = new LinkedHashMap<>();
|
||||||
definition.put("sessionId", UUID.class);
|
definition.put("sessionId", UUID.class);
|
||||||
|
|
@ -947,120 +470,6 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> toActivityPointInputMap(
|
|
||||||
UUID fallbackSessionId,
|
|
||||||
String fallbackDriverKey,
|
|
||||||
EventHubEventDto sourceEvent
|
|
||||||
) {
|
|
||||||
if (sourceEvent == null
|
|
||||||
|| sourceEvent.eventDomain() != EventDomain.DRIVER_ACTIVITY
|
|
||||||
|| (sourceEvent.lifecycle() != EventLifecycle.START && sourceEvent.lifecycle() != EventLifecycle.END)
|
|
||||||
|| sourceEvent.occurredAt() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = rawPayload(sourceEvent);
|
|
||||||
String intervalId = firstNonBlank(text(raw, "intervalId"), text(raw, "sourceRowId"), sourceEvent.externalSourceEventId());
|
|
||||||
String driverKey = firstNonBlank(text(raw, "driverKey"), fallbackDriverKey, driverKey(sourceEvent));
|
|
||||||
if (driverKey == null || intervalId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode attributes = attributes(sourceEvent);
|
|
||||||
Map<String, Object> event = new LinkedHashMap<>();
|
|
||||||
event.put("sessionId", sessionId(fallbackSessionId, raw, sourceEvent));
|
|
||||||
event.put("driverKey", driverKey);
|
|
||||||
event.put("eventId", sourceEvent.externalSourceEventId());
|
|
||||||
event.put("intervalId", intervalId);
|
|
||||||
event.put("sourceRowId", firstNonBlank(text(raw, "sourceRowId"), intervalId));
|
|
||||||
event.put("sourceRowIds", stringList(raw, "sourceRowIds", intervalId));
|
|
||||||
event.put("activityType", firstNonBlank(text(raw, "activityType"), eventTypeAsActivity(sourceEvent.eventType())));
|
|
||||||
event.put("lifecycle", sourceEvent.lifecycle().name());
|
|
||||||
event.put("occurredAt", sourceEvent.occurredAt());
|
|
||||||
event.put("occurredAtEpochSecond", sourceEvent.occurredAt().toEpochSecond());
|
|
||||||
event.put("cardSlot", firstNonBlank(text(raw, "cardSlot"), text(attributes, "cardSlot")));
|
|
||||||
event.put("cardStatus", firstNonBlank(text(raw, "cardStatus"), text(attributes, "cardStatus")));
|
|
||||||
event.put("drivingStatus", firstNonBlank(text(raw, "drivingStatus"), text(attributes, "drivingStatus")));
|
|
||||||
event.put("registrationKey", firstNonBlank(text(raw, "registrationKey"), registrationKey(sourceEvent)));
|
|
||||||
event.put("vehicleKey", firstNonBlank(text(raw, "vehicleKey"), vehicleKey(sourceEvent)));
|
|
||||||
event.put("sourceKind", firstNonBlank(text(raw, "sourceKind"), sourceKind(sourceEvent)));
|
|
||||||
event.put("synthetic", booleanValue(raw, "synthetic", false));
|
|
||||||
event.put("clippedToRequestedPeriod", booleanValue(raw, "clippedToRequestedPeriod", false));
|
|
||||||
event.put("level", firstNonBlank(text(raw, "level"), "RAW_EVENT"));
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> toVehicleUsagePointInputMap(
|
|
||||||
UUID fallbackSessionId,
|
|
||||||
String fallbackDriverKey,
|
|
||||||
EventHubEventDto sourceEvent
|
|
||||||
) {
|
|
||||||
if (sourceEvent == null
|
|
||||||
|| sourceEvent.eventDomain() != EventDomain.DRIVER_CARD
|
|
||||||
|| (sourceEvent.lifecycle() != EventLifecycle.INSERT && sourceEvent.lifecycle() != EventLifecycle.WITHDRAW)
|
|
||||||
|| sourceEvent.occurredAt() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
boolean supportedType = sourceEvent.eventType() == EventType.CARD_INSERTED
|
|
||||||
|| sourceEvent.eventType() == EventType.CARD_WITHDRAWN;
|
|
||||||
if (!supportedType) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = rawPayload(sourceEvent);
|
|
||||||
String intervalId = firstNonBlank(text(raw, "intervalId"), text(raw, "sourceRowId"), sourceEvent.externalSourceEventId());
|
|
||||||
String driverKey = firstNonBlank(text(raw, "driverKey"), fallbackDriverKey, driverKey(sourceEvent));
|
|
||||||
if (driverKey == null || intervalId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Map<String, Object> event = new LinkedHashMap<>();
|
|
||||||
event.put("sessionId", sessionId(fallbackSessionId, raw, sourceEvent));
|
|
||||||
event.put("driverKey", driverKey);
|
|
||||||
event.put("eventId", sourceEvent.externalSourceEventId());
|
|
||||||
event.put("intervalId", intervalId);
|
|
||||||
event.put("sourceRowId", firstNonBlank(text(raw, "sourceRowId"), intervalId));
|
|
||||||
event.put("sourceRowIds", stringList(raw, "sourceRowIds", intervalId));
|
|
||||||
event.put("lifecycle", sourceEvent.lifecycle().name());
|
|
||||||
event.put("occurredAt", sourceEvent.occurredAt());
|
|
||||||
event.put("occurredAtEpochSecond", sourceEvent.occurredAt().toEpochSecond());
|
|
||||||
event.put("registrationKey", firstNonBlank(text(raw, "registrationKey"), registrationKey(sourceEvent)));
|
|
||||||
event.put("vehicleKey", firstNonBlank(text(raw, "vehicleKey"), vehicleKey(sourceEvent)));
|
|
||||||
event.put("sourceKind", firstNonBlank(text(raw, "sourceKind"), sourceKind(sourceEvent)));
|
|
||||||
event.put("odometerKm", odometerKm(sourceEvent, raw));
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> toSupportGeoEvidenceInputMap(
|
|
||||||
UUID fallbackSessionId,
|
|
||||||
String fallbackDriverKey,
|
|
||||||
EventHubEventDto sourceEvent
|
|
||||||
) {
|
|
||||||
if (sourceEvent == null || sourceEvent.occurredAt() == null || sourceEvent.position() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String eventDomain = sourceEvent.eventDomain() == null ? null : sourceEvent.eventDomain().name();
|
|
||||||
int priority = supportGeoPriority(eventDomain);
|
|
||||||
if (priority <= 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = rawPayload(sourceEvent);
|
|
||||||
String driverKey = firstNonBlank(text(raw, "driverKey"), fallbackDriverKey, driverKey(sourceEvent));
|
|
||||||
if (driverKey == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Map<String, Object> event = new LinkedHashMap<>();
|
|
||||||
event.put("sessionId", sessionId(fallbackSessionId, raw, sourceEvent));
|
|
||||||
event.put("driverKey", driverKey);
|
|
||||||
event.put("eventId", firstNonBlank(text(raw, "supportEventId"), text(raw, "sourceRowId"), sourceEvent.externalSourceEventId()));
|
|
||||||
event.put("eventDomain", eventDomain);
|
|
||||||
event.put("occurredAt", sourceEvent.occurredAt());
|
|
||||||
event.put("occurredAtEpochSecond", sourceEvent.occurredAt().toEpochSecond());
|
|
||||||
event.put("registrationKey", firstNonBlank(text(raw, "registrationKey"), registrationKey(sourceEvent)));
|
|
||||||
event.put("vehicleKey", firstNonBlank(text(raw, "vehicleKey"), vehicleKey(sourceEvent)));
|
|
||||||
event.put("latitude", sourceEvent.position().latitude().doubleValue());
|
|
||||||
event.put("longitude", sourceEvent.position().longitude().doubleValue());
|
|
||||||
event.put("odometerKm", odometerKm(sourceEvent, raw));
|
|
||||||
event.put("priority", priority);
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int supportGeoPriority(String eventDomain) {
|
private int supportGeoPriority(String eventDomain) {
|
||||||
if (eventDomain == null || eventDomain.isBlank()) {
|
if (eventDomain == null || eventDomain.isBlank()) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -1074,186 +483,6 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Comparator<Map<String, Object>> pointEventComparator() {
|
|
||||||
return Comparator
|
|
||||||
.comparing((Map<String, Object> event) -> (Long) event.get("occurredAtEpochSecond"))
|
|
||||||
.thenComparing(event -> lifecycleOrder(Objects.toString(event.get("lifecycle"), "")))
|
|
||||||
.thenComparing(event -> Objects.toString(event.get("driverKey"), ""))
|
|
||||||
.thenComparing(event -> Objects.toString(event.get("intervalId"), ""))
|
|
||||||
.thenComparing(event -> Objects.toString(event.get("eventId"), ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
private int lifecycleOrder(String lifecycle) {
|
|
||||||
return switch (lifecycle) {
|
|
||||||
case "INSERT", "START" -> 0;
|
|
||||||
case "WITHDRAW", "END" -> 1;
|
|
||||||
default -> 2;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventHubProperties.DrivingDerivedProjectionInputMode drivingDerivedProjectionInputMode() {
|
|
||||||
return properties.getTachographFileSession().getProcessing().getDrivingDerivedProjectionInputMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
private RawSourceDriverTimelineEventBuilder rawSourceEventBuilder() {
|
|
||||||
if (rawSourceEventBuilder == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Driving-derived projection input mode EVENTS requires RawSourceDriverTimelineEventBuilder"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return rawSourceEventBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonNode rawPayload(EventHubEventDto event) {
|
|
||||||
JsonNode payload = event.payload();
|
|
||||||
if (payload == null || payload.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = payload.get("raw");
|
|
||||||
return raw == null || raw.isNull() ? payload : raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonNode attributes(EventHubEventDto event) {
|
|
||||||
return event.eventDetails() == null ? null : event.eventDetails().attributes();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String text(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String text = value.asText(null);
|
|
||||||
return text == null || text.isBlank() ? null : text;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean booleanValue(JsonNode node, String field, boolean fallback) {
|
|
||||||
if (node == null || field == null || node.get(field) == null || node.get(field).isNull()) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
return node.get(field).asBoolean(fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long longValue(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null || node.get(field) == null || node.get(field).isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value.isNumber()) {
|
|
||||||
return value.asLong();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Long.parseLong(value.asText());
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> stringList(JsonNode node, String field, String fallback) {
|
|
||||||
JsonNode value = node == null || field == null ? null : node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return fallback == null ? List.of() : List.of(fallback);
|
|
||||||
}
|
|
||||||
if (value.isArray()) {
|
|
||||||
List<String> result = new ArrayList<>();
|
|
||||||
value.forEach(item -> {
|
|
||||||
if (item != null && !item.isNull()) {
|
|
||||||
String text = item.asText(null);
|
|
||||||
if (text != null && !text.isBlank()) {
|
|
||||||
result.add(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result.isEmpty() && fallback != null ? List.of(fallback) : List.copyOf(result);
|
|
||||||
}
|
|
||||||
String text = value.asText(null);
|
|
||||||
return text == null || text.isBlank() ? (fallback == null ? List.of() : List.of(fallback)) : List.of(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String firstNonBlank(String... values) {
|
|
||||||
if (values == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (String value : values) {
|
|
||||||
if (value != null && !value.isBlank()) {
|
|
||||||
return value.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String eventTypeAsActivity(EventType eventType) {
|
|
||||||
if (eventType == null) {
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
return switch (eventType) {
|
|
||||||
case DRIVE -> "DRIVE";
|
|
||||||
case WORK -> "WORK";
|
|
||||||
case AVAILABILITY -> "AVAILABILITY";
|
|
||||||
case BREAK_REST -> "BREAK_REST";
|
|
||||||
default -> eventType.name();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private UUID sessionId(UUID fallbackSessionId, JsonNode raw, EventHubEventDto event) {
|
|
||||||
String rawSessionId = firstNonBlank(
|
|
||||||
text(raw, "sessionId"),
|
|
||||||
event.sourcePackageRef() == null ? null : event.sourcePackageRef().sourcePackageId()
|
|
||||||
);
|
|
||||||
if (rawSessionId != null) {
|
|
||||||
try {
|
|
||||||
return UUID.fromString(rawSessionId);
|
|
||||||
} catch (IllegalArgumentException ignored) {
|
|
||||||
// DB-acquired source packages need not be UUID-based file sessions.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fallbackSessionId == null ? new UUID(0L, 0L) : fallbackSessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String driverKey(EventHubEventDto event) {
|
|
||||||
if (event.driverRef() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (event.driverRef().driverCard() != null && event.driverRef().driverCard().hasValue()) {
|
|
||||||
return event.driverRef().driverCard().stableKey();
|
|
||||||
}
|
|
||||||
return event.driverRef().sourceEntityId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String registrationKey(EventHubEventDto event) {
|
|
||||||
if (event.vehicleRef() == null || event.vehicleRef().vehicleRegistration() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return event.vehicleRef().vehicleRegistration().stableKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String vehicleKey(EventHubEventDto event) {
|
|
||||||
if (event.vehicleRef() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return firstNonBlank(event.vehicleRef().vin(), event.vehicleRef().sourceVehicleEntityId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String sourceKind(EventHubEventDto event) {
|
|
||||||
return event.packageInfo() == null || event.packageInfo().eventSource() == null
|
|
||||||
? null
|
|
||||||
: event.packageInfo().eventSource().sourceKind();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long odometerKm(EventHubEventDto event, JsonNode raw) {
|
|
||||||
Long explicit = longValue(raw, event.lifecycle() == EventLifecycle.WITHDRAW ? "odometerEndKm" : "odometerBeginKm");
|
|
||||||
if (explicit != null) {
|
|
||||||
return explicit;
|
|
||||||
}
|
|
||||||
explicit = longValue(raw, "odometerKm");
|
|
||||||
if (explicit != null) {
|
|
||||||
return explicit;
|
|
||||||
}
|
|
||||||
return event.odometerM() == null ? null : event.odometerM() / 1_000L;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String firstSourceIntervalId(ResolvedActivityInterval interval) {
|
private String firstSourceIntervalId(ResolvedActivityInterval interval) {
|
||||||
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().get(0);
|
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().get(0);
|
||||||
}
|
}
|
||||||
|
|
@ -1672,12 +901,6 @@ public class DriverTimelineReusableProjectionBuilder {
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String renderDrivingDerivedProjectionEventsEpl(int significantDrivingMinutes, int minimumRestPeriodMinutes) {
|
|
||||||
return DRIVING_DERIVED_PROJECTION_EVENTS_PREPROCESSOR_EPL
|
|
||||||
+ "\n\n"
|
|
||||||
+ renderDrivingDerivedProjectionBundleEpl(significantDrivingMinutes, minimumRestPeriodMinutes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String renderDrivingDerivedProjectionBundleEpl(int significantDrivingMinutes, int minimumRestPeriodMinutes) {
|
private String renderDrivingDerivedProjectionBundleEpl(int significantDrivingMinutes, int minimumRestPeriodMinutes) {
|
||||||
return DRIVING_DERIVED_PROJECTION_BUNDLE_EPL_TEMPLATE
|
return DRIVING_DERIVED_PROJECTION_BUNDLE_EPL_TEMPLATE
|
||||||
.replace(
|
.replace(
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,6 @@ public class IntervalBackedDriverTimelineEventBuilder implements DriverTimelineE
|
||||||
|
|
||||||
List<EventHubEventDto> activityEvents = buildActivityEvents(
|
List<EventHubEventDto> activityEvents = buildActivityEvents(
|
||||||
session,
|
session,
|
||||||
driverSession.driverKey(),
|
|
||||||
timeline.activityIntervals(),
|
timeline.activityIntervals(),
|
||||||
driverRef,
|
driverRef,
|
||||||
registrationsByKey,
|
registrationsByKey,
|
||||||
|
|
@ -105,7 +104,6 @@ public class IntervalBackedDriverTimelineEventBuilder implements DriverTimelineE
|
||||||
);
|
);
|
||||||
List<EventHubEventDto> vehicleUsageEvents = buildVehicleUsageEvents(
|
List<EventHubEventDto> vehicleUsageEvents = buildVehicleUsageEvents(
|
||||||
session,
|
session,
|
||||||
driverSession.driverKey(),
|
|
||||||
timeline.vehicleUsageIntervals(),
|
timeline.vehicleUsageIntervals(),
|
||||||
driverRef,
|
driverRef,
|
||||||
registrationsByKey,
|
registrationsByKey,
|
||||||
|
|
@ -127,7 +125,6 @@ public class IntervalBackedDriverTimelineEventBuilder implements DriverTimelineE
|
||||||
|
|
||||||
private List<EventHubEventDto> buildActivityEvents(
|
private List<EventHubEventDto> buildActivityEvents(
|
||||||
TachographFileSession session,
|
TachographFileSession session,
|
||||||
String driverKey,
|
|
||||||
List<ResolvedActivityInterval> intervals,
|
List<ResolvedActivityInterval> intervals,
|
||||||
DriverRefDto driverRef,
|
DriverRefDto driverRef,
|
||||||
Map<String, ExtractedVehicleRegistration> registrationsByKey,
|
Map<String, ExtractedVehicleRegistration> registrationsByKey,
|
||||||
|
|
@ -150,8 +147,6 @@ public class IntervalBackedDriverTimelineEventBuilder implements DriverTimelineE
|
||||||
Map<String, Object> raw = new LinkedHashMap<>();
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
raw.put("intervalId", interval.intervalId());
|
raw.put("intervalId", interval.intervalId());
|
||||||
raw.put("sourceRowId", interval.intervalId());
|
raw.put("sourceRowId", interval.intervalId());
|
||||||
raw.put("driverKey", driverKey);
|
|
||||||
raw.put("activityType", interval.activityType());
|
|
||||||
raw.put("sourceRowIds", interval.sourceIntervalIds());
|
raw.put("sourceRowIds", interval.sourceIntervalIds());
|
||||||
raw.put("startedAt", timeText(interval.from()));
|
raw.put("startedAt", timeText(interval.from()));
|
||||||
raw.put("endedAt", timeText(interval.to()));
|
raw.put("endedAt", timeText(interval.to()));
|
||||||
|
|
@ -205,7 +200,6 @@ public class IntervalBackedDriverTimelineEventBuilder implements DriverTimelineE
|
||||||
|
|
||||||
private List<EventHubEventDto> buildVehicleUsageEvents(
|
private List<EventHubEventDto> buildVehicleUsageEvents(
|
||||||
TachographFileSession session,
|
TachographFileSession session,
|
||||||
String driverKey,
|
|
||||||
List<ResolvedVehicleUsageInterval> intervals,
|
List<ResolvedVehicleUsageInterval> intervals,
|
||||||
DriverRefDto driverRef,
|
DriverRefDto driverRef,
|
||||||
Map<String, ExtractedVehicleRegistration> registrationsByKey,
|
Map<String, ExtractedVehicleRegistration> registrationsByKey,
|
||||||
|
|
@ -232,7 +226,6 @@ public class IntervalBackedDriverTimelineEventBuilder implements DriverTimelineE
|
||||||
Map<String, Object> raw = new LinkedHashMap<>();
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
raw.put("intervalId", interval.intervalId());
|
raw.put("intervalId", interval.intervalId());
|
||||||
raw.put("sourceRowId", interval.intervalId());
|
raw.put("sourceRowId", interval.intervalId());
|
||||||
raw.put("driverKey", driverKey);
|
|
||||||
raw.put("sourceRowIds", interval.sourceIntervalIds());
|
raw.put("sourceRowIds", interval.sourceIntervalIds());
|
||||||
raw.put("startedAt", timeText(interval.from()));
|
raw.put("startedAt", timeText(interval.from()));
|
||||||
raw.put("endedAt", timeText(interval.to()));
|
raw.put("endedAt", timeText(interval.to()));
|
||||||
|
|
|
||||||
|
|
@ -1,211 +0,0 @@
|
||||||
package at.procon.eventhub.tachographfilesession.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographTimelineEventBundle;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds EventHub point events directly from the extracted tachograph source records.
|
|
||||||
*
|
|
||||||
* <p>This builder intentionally does not run the normal {@link DriverTimelineBuilder} interval
|
|
||||||
* resolution/merging path. It only wraps each raw source interval as a START/END or
|
|
||||||
* INSERT/WITHDRAW point-event pair. Any subsequent pairing, coalescing, or derived interval logic
|
|
||||||
* belongs to the event-input EPL projection pipeline.</p>
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class RawSourceDriverTimelineEventBuilder {
|
|
||||||
|
|
||||||
private final IntervalBackedDriverTimelineEventBuilder intervalEventBuilder;
|
|
||||||
|
|
||||||
public RawSourceDriverTimelineEventBuilder(IntervalBackedDriverTimelineEventBuilder intervalEventBuilder) {
|
|
||||||
this.intervalEventBuilder = intervalEventBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TachographTimelineEventBundle buildRawEventBundle(
|
|
||||||
TachographFileSession session,
|
|
||||||
DriverExtractionSession driverSession
|
|
||||||
) {
|
|
||||||
if (session == null || driverSession == null) {
|
|
||||||
return new TachographTimelineEventBundle(List.of(), List.of(), List.of());
|
|
||||||
}
|
|
||||||
return intervalEventBuilder.buildEventBundle(session, driverSession, buildRawTimeline(session, driverSession));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResolvedDriverTimeline buildRawTimeline(
|
|
||||||
TachographFileSession session,
|
|
||||||
DriverExtractionSession driverSession
|
|
||||||
) {
|
|
||||||
String sourceKind = session.metadata().driverCardFile() ? "DRIVER_CARD" : "VEHICLE_UNIT";
|
|
||||||
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals = rawVehicleUsageIntervals(
|
|
||||||
session,
|
|
||||||
driverSession,
|
|
||||||
sourceKind
|
|
||||||
);
|
|
||||||
List<ResolvedActivityInterval> activityIntervals = rawActivityIntervals(driverSession, sourceKind);
|
|
||||||
List<ExtractedSupportEvent> supportEvents = sortedSupportEvents(driverSession.supportEvents());
|
|
||||||
return new ResolvedDriverTimeline(
|
|
||||||
sourceKind,
|
|
||||||
minTimestamp(vehicleUsageIntervals, activityIntervals, supportEvents),
|
|
||||||
maxTimestamp(vehicleUsageIntervals, activityIntervals, supportEvents),
|
|
||||||
vehicleUsageIntervals,
|
|
||||||
activityIntervals,
|
|
||||||
supportEvents,
|
|
||||||
mergeWarnings(session.warnings(), driverSession.warnings())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ResolvedVehicleUsageInterval> rawVehicleUsageIntervals(
|
|
||||||
TachographFileSession session,
|
|
||||||
DriverExtractionSession driverSession,
|
|
||||||
String sourceKind
|
|
||||||
) {
|
|
||||||
if (driverSession.cardVehicleUsageIntervals() == null || driverSession.cardVehicleUsageIntervals().isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
return driverSession.cardVehicleUsageIntervals().stream()
|
|
||||||
.filter(interval -> interval.from() != null && (interval.to() == null || interval.to().isAfter(interval.from())))
|
|
||||||
.map(interval -> ResolvedVehicleUsageInterval.resolved(
|
|
||||||
session.sessionId(),
|
|
||||||
driverSession.driverKey(),
|
|
||||||
interval.intervalId(),
|
|
||||||
interval.from(),
|
|
||||||
interval.to(),
|
|
||||||
interval.odometerBeginKm(),
|
|
||||||
interval.odometerEndKm(),
|
|
||||||
interval.registrationKey(),
|
|
||||||
interval.vehicleKey(),
|
|
||||||
sourceKind,
|
|
||||||
List.of(interval.intervalId())
|
|
||||||
))
|
|
||||||
.sorted(Comparator.comparing(ResolvedVehicleUsageInterval::from)
|
|
||||||
.thenComparing(ResolvedVehicleUsageInterval::to, Comparator.nullsLast(Comparator.naturalOrder()))
|
|
||||||
.thenComparing(ResolvedVehicleUsageInterval::intervalId, Comparator.nullsLast(String::compareTo)))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ResolvedActivityInterval> rawActivityIntervals(
|
|
||||||
DriverExtractionSession driverSession,
|
|
||||||
String sourceKind
|
|
||||||
) {
|
|
||||||
if (driverSession.cardActivityIntervals() == null || driverSession.cardActivityIntervals().isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
return driverSession.cardActivityIntervals().stream()
|
|
||||||
.filter(interval -> interval.from() != null && interval.to() != null && interval.to().isAfter(interval.from()))
|
|
||||||
.map(interval -> ResolvedActivityInterval.raw(
|
|
||||||
interval.intervalId(),
|
|
||||||
interval.from(),
|
|
||||||
interval.to(),
|
|
||||||
normalizeActivity(interval.activityType()),
|
|
||||||
interval.slot(),
|
|
||||||
interval.cardStatus(),
|
|
||||||
interval.drivingStatus(),
|
|
||||||
interval.registrationKey(),
|
|
||||||
interval.vehicleKey(),
|
|
||||||
sourceKind,
|
|
||||||
List.of(interval.intervalId())
|
|
||||||
))
|
|
||||||
.sorted(Comparator.comparing(ResolvedActivityInterval::from)
|
|
||||||
.thenComparing(ResolvedActivityInterval::to)
|
|
||||||
.thenComparing(ResolvedActivityInterval::intervalId, Comparator.nullsLast(String::compareTo)))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ExtractedSupportEvent> sortedSupportEvents(List<ExtractedSupportEvent> supportEvents) {
|
|
||||||
if (supportEvents == null || supportEvents.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
return supportEvents.stream()
|
|
||||||
.sorted(Comparator.comparing(ExtractedSupportEvent::occurredAt)
|
|
||||||
.thenComparing(ExtractedSupportEvent::eventDomain, Comparator.nullsLast(String::compareTo))
|
|
||||||
.thenComparing(ExtractedSupportEvent::eventId, Comparator.nullsLast(String::compareTo)))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeActivity(String activityType) {
|
|
||||||
if (activityType == null || activityType.isBlank()) {
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
if ("UNKNOWN_ACTIVITY".equals(activityType)) {
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
return activityType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime minTimestamp(
|
|
||||||
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
|
|
||||||
List<ResolvedActivityInterval> activityIntervals,
|
|
||||||
List<ExtractedSupportEvent> supportEvents
|
|
||||||
) {
|
|
||||||
OffsetDateTime min = null;
|
|
||||||
for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) {
|
|
||||||
min = min(min, interval.from());
|
|
||||||
}
|
|
||||||
for (ResolvedActivityInterval interval : activityIntervals) {
|
|
||||||
min = min(min, interval.from());
|
|
||||||
}
|
|
||||||
for (ExtractedSupportEvent supportEvent : supportEvents) {
|
|
||||||
min = min(min, supportEvent.occurredAt());
|
|
||||||
}
|
|
||||||
return min;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime maxTimestamp(
|
|
||||||
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
|
|
||||||
List<ResolvedActivityInterval> activityIntervals,
|
|
||||||
List<ExtractedSupportEvent> supportEvents
|
|
||||||
) {
|
|
||||||
OffsetDateTime max = null;
|
|
||||||
for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) {
|
|
||||||
max = max(max, interval.to());
|
|
||||||
}
|
|
||||||
for (ResolvedActivityInterval interval : activityIntervals) {
|
|
||||||
max = max(max, interval.to());
|
|
||||||
}
|
|
||||||
for (ExtractedSupportEvent supportEvent : supportEvents) {
|
|
||||||
max = max(max, supportEvent.occurredAt());
|
|
||||||
}
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ExtractionWarning> mergeWarnings(List<ExtractionWarning> sessionWarnings, List<ExtractionWarning> driverWarnings) {
|
|
||||||
LinkedHashSet<ExtractionWarning> merged = new LinkedHashSet<>();
|
|
||||||
if (sessionWarnings != null) {
|
|
||||||
merged.addAll(sessionWarnings);
|
|
||||||
}
|
|
||||||
if (driverWarnings != null) {
|
|
||||||
merged.addAll(driverWarnings);
|
|
||||||
}
|
|
||||||
return List.copyOf(merged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime min(OffsetDateTime left, OffsetDateTime right) {
|
|
||||||
if (left == null) {
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
if (right == null) {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
return left.isBefore(right) ? left : right;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime max(OffsetDateTime left, OffsetDateTime right) {
|
|
||||||
if (left == null) {
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
if (right == null) {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
return left.isAfter(right) ? left : right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue