Add DTI enrichment evaluation endpoint
This commit is contained in:
parent
94e1227ab3
commit
7be6a08013
|
|
@ -64,7 +64,7 @@
|
|||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/eventhub/esper-poc/tachograph/operating-period-evaluation?tenantKey={{tenantKey}}&driverId={{driverId}}&occurredFrom={{occurredFrom}}&occurredTo={{occurredTo}}&guardHours=24&operatingSplitIdleHours=7&significantDrivingMinutes=3&mergeGapSeconds=0&gapDetectionToleranceSeconds=0&unknownTreatmentMode=AS_BREAK_REST",
|
||||
"raw": "{{baseUrl}}/api/eventhub/esper-poc/tachograph/operating-period-evaluation?tenantKey={{tenantKey}}&driverId={{driverId}}&occurredFrom={{occurredFrom}}&occurredTo={{occurredTo}}&guardHours=24&operatingSplitIdleHours=7&significantDrivingMinutes=3&mergeGapSeconds=0&gapDetectionToleranceSeconds=0&sourceSelectionMode=MIXED&unknownTreatmentMode=AS_BREAK_REST",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
|
|
@ -112,6 +112,10 @@
|
|||
"key": "gapDetectionToleranceSeconds",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"key": "sourceSelectionMode",
|
||||
"value": "MIXED"
|
||||
},
|
||||
{
|
||||
"key": "unknownTreatmentMode",
|
||||
"value": "AS_BREAK_REST"
|
||||
|
|
@ -119,6 +123,84 @@
|
|||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Evaluate tachograph DTI enrichment",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/eventhub/esper-poc/tachograph/dti-enrichment?tenantKey={{tenantKey}}&driverId={{driverId}}&occurredFrom={{occurredFrom}}&occurredTo={{occurredTo}}&guardHours=24&operatingSplitIdleHours=7&significantDrivingMinutes=3&mergeGapSeconds=0&gapDetectionToleranceSeconds=0&sourceSelectionMode=MIXED&unknownTreatmentMode=AS_BREAK_REST&vehicleEvidenceLookbackHours=720&geoSearchWindowMinutes=180&vicinityWindowMinutes=180",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"eventhub",
|
||||
"esper-poc",
|
||||
"tachograph",
|
||||
"dti-enrichment"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "tenantKey",
|
||||
"value": "{{tenantKey}}"
|
||||
},
|
||||
{
|
||||
"key": "driverId",
|
||||
"value": "{{driverId}}"
|
||||
},
|
||||
{
|
||||
"key": "occurredFrom",
|
||||
"value": "{{occurredFrom}}"
|
||||
},
|
||||
{
|
||||
"key": "occurredTo",
|
||||
"value": "{{occurredTo}}"
|
||||
},
|
||||
{
|
||||
"key": "guardHours",
|
||||
"value": "24"
|
||||
},
|
||||
{
|
||||
"key": "operatingSplitIdleHours",
|
||||
"value": "7"
|
||||
},
|
||||
{
|
||||
"key": "significantDrivingMinutes",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"key": "mergeGapSeconds",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"key": "gapDetectionToleranceSeconds",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"key": "sourceSelectionMode",
|
||||
"value": "MIXED"
|
||||
},
|
||||
{
|
||||
"key": "unknownTreatmentMode",
|
||||
"value": "AS_BREAK_REST"
|
||||
},
|
||||
{
|
||||
"key": "vehicleEvidenceLookbackHours",
|
||||
"value": "720"
|
||||
},
|
||||
{
|
||||
"key": "geoSearchWindowMinutes",
|
||||
"value": "180"
|
||||
},
|
||||
{
|
||||
"key": "vicinityWindowMinutes",
|
||||
"value": "180"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"variable": [
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
package at.procon.eventhub.esperpoc.api;
|
||||
|
||||
import at.procon.eventhub.esperpoc.dto.EsperActivityMergeMode;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperDtiEnrichmentRequest;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperDtiEnrichmentResultDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodEngineMode;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodRequest;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodResultDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperPocRequest;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperPocResultDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperShiftResolutionMode;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperSourceSelectionMode;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperUnknownTreatmentMode;
|
||||
import at.procon.eventhub.esperpoc.service.EsperDtiEnrichmentService;
|
||||
import at.procon.eventhub.esperpoc.service.EsperOperatingPeriodEvaluationService;
|
||||
import at.procon.eventhub.esperpoc.service.EsperPocDriverCardActivityService;
|
||||
import java.time.OffsetDateTime;
|
||||
|
|
@ -25,13 +29,16 @@ public class EsperPocController {
|
|||
|
||||
private final EsperPocDriverCardActivityService service;
|
||||
private final EsperOperatingPeriodEvaluationService operatingPeriodEvaluationService;
|
||||
private final EsperDtiEnrichmentService dtiEnrichmentService;
|
||||
|
||||
public EsperPocController(
|
||||
EsperPocDriverCardActivityService service,
|
||||
EsperOperatingPeriodEvaluationService operatingPeriodEvaluationService
|
||||
EsperOperatingPeriodEvaluationService operatingPeriodEvaluationService,
|
||||
EsperDtiEnrichmentService dtiEnrichmentService
|
||||
) {
|
||||
this.service = service;
|
||||
this.operatingPeriodEvaluationService = operatingPeriodEvaluationService;
|
||||
this.dtiEnrichmentService = dtiEnrichmentService;
|
||||
}
|
||||
|
||||
@GetMapping("/tachograph/driver-card-activities")
|
||||
|
|
@ -77,6 +84,7 @@ public class EsperPocController {
|
|||
@RequestParam(required = false) Integer significantDrivingMinutes,
|
||||
@RequestParam(required = false) Integer mergeGapSeconds,
|
||||
@RequestParam(required = false) Integer gapDetectionToleranceSeconds,
|
||||
@RequestParam(required = false) EsperSourceSelectionMode sourceSelectionMode,
|
||||
@RequestParam(required = false) EsperUnknownTreatmentMode unknownTreatmentMode,
|
||||
@RequestParam(required = false) EsperOperatingPeriodEngineMode engineMode
|
||||
) {
|
||||
|
|
@ -90,9 +98,48 @@ public class EsperPocController {
|
|||
significantDrivingMinutes,
|
||||
mergeGapSeconds,
|
||||
gapDetectionToleranceSeconds,
|
||||
sourceSelectionMode,
|
||||
unknownTreatmentMode,
|
||||
engineMode
|
||||
);
|
||||
return ResponseEntity.ok(operatingPeriodEvaluationService.evaluate(request));
|
||||
}
|
||||
|
||||
@GetMapping("/tachograph/dti-enrichment")
|
||||
public ResponseEntity<EsperDtiEnrichmentResultDto> evaluateDtiEnrichment(
|
||||
@RequestParam String tenantKey,
|
||||
@RequestParam UUID driverId,
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime occurredFrom,
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime occurredTo,
|
||||
@RequestParam(defaultValue = "24") Integer guardHours,
|
||||
@RequestParam(required = false) Integer operatingSplitIdleHours,
|
||||
@RequestParam(required = false) Integer significantDrivingMinutes,
|
||||
@RequestParam(required = false) Integer mergeGapSeconds,
|
||||
@RequestParam(required = false) Integer gapDetectionToleranceSeconds,
|
||||
@RequestParam(required = false) EsperSourceSelectionMode sourceSelectionMode,
|
||||
@RequestParam(required = false) EsperUnknownTreatmentMode unknownTreatmentMode,
|
||||
@RequestParam(required = false) EsperOperatingPeriodEngineMode engineMode,
|
||||
@RequestParam(required = false) Integer vehicleEvidenceLookbackHours,
|
||||
@RequestParam(required = false) Integer geoSearchWindowMinutes,
|
||||
@RequestParam(required = false) Integer vicinityWindowMinutes
|
||||
) {
|
||||
EsperDtiEnrichmentRequest request = new EsperDtiEnrichmentRequest(
|
||||
tenantKey,
|
||||
driverId,
|
||||
occurredFrom,
|
||||
occurredTo,
|
||||
guardHours,
|
||||
operatingSplitIdleHours,
|
||||
significantDrivingMinutes,
|
||||
mergeGapSeconds,
|
||||
gapDetectionToleranceSeconds,
|
||||
sourceSelectionMode,
|
||||
unknownTreatmentMode,
|
||||
engineMode,
|
||||
vehicleEvidenceLookbackHours,
|
||||
geoSearchWindowMinutes,
|
||||
vicinityWindowMinutes
|
||||
);
|
||||
return ResponseEntity.ok(dtiEnrichmentService.evaluate(request));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package at.procon.eventhub.esperpoc.dto;
|
||||
|
||||
import at.procon.eventhub.dto.GeoPointDto;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public record DtiBoundaryPositionDto(
|
||||
OffsetDateTime evidenceAt,
|
||||
GeoPointDto position,
|
||||
String eventDomain,
|
||||
String eventType,
|
||||
String sourceKind,
|
||||
String extractionCode,
|
||||
UUID vehicleId,
|
||||
UUID vehicleRegistrationId,
|
||||
String country,
|
||||
String region,
|
||||
String countryFrom,
|
||||
String countryTo,
|
||||
String operation,
|
||||
long deltaSeconds,
|
||||
int confidence,
|
||||
String evidenceSourceRowId
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package at.procon.eventhub.esperpoc.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record DtiBoundaryVehicleDto(
|
||||
UUID vehicleId,
|
||||
UUID vehicleRegistrationId,
|
||||
String vehicleVin,
|
||||
String resolutionSource,
|
||||
int confidence,
|
||||
OffsetDateTime evidenceStartedAt,
|
||||
OffsetDateTime evidenceEndedAt,
|
||||
List<String> evidenceSourceRowIds
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package at.procon.eventhub.esperpoc.dto;
|
||||
|
||||
import at.procon.eventhub.dto.GeoPointDto;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public record DtiBoundaryVicinityEventDto(
|
||||
OffsetDateTime occurredAt,
|
||||
String eventDomain,
|
||||
String eventType,
|
||||
String lifecycle,
|
||||
String sourceKind,
|
||||
String extractionCode,
|
||||
UUID vehicleId,
|
||||
UUID vehicleRegistrationId,
|
||||
GeoPointDto position,
|
||||
String country,
|
||||
String region,
|
||||
String countryFrom,
|
||||
String countryTo,
|
||||
String operation,
|
||||
long deltaSeconds,
|
||||
String sourceRowId
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package at.procon.eventhub.esperpoc.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record EnrichedDtiIntervalDto(
|
||||
String dtiId,
|
||||
UUID driverId,
|
||||
String intervalKind,
|
||||
OffsetDateTime startedAt,
|
||||
OffsetDateTime endedAt,
|
||||
long durationSeconds,
|
||||
long operatingPeriodNo,
|
||||
OffsetDateTime operatingPeriodStartedAt,
|
||||
String previousDrivingSourceRowId,
|
||||
String nextDrivingSourceRowId,
|
||||
DtiBoundaryVehicleDto beginVehicle,
|
||||
DtiBoundaryVehicleDto endVehicle,
|
||||
DtiBoundaryPositionDto beginPosition,
|
||||
DtiBoundaryPositionDto endPosition,
|
||||
List<DtiBoundaryVicinityEventDto> beginVicinityEvents,
|
||||
List<DtiBoundaryVicinityEventDto> endVicinityEvents
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package at.procon.eventhub.esperpoc.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public record EsperDtiEnrichmentRequest(
|
||||
@NotBlank String tenantKey,
|
||||
@NotNull UUID driverId,
|
||||
@NotNull OffsetDateTime occurredFrom,
|
||||
@NotNull OffsetDateTime occurredTo,
|
||||
Integer guardHours,
|
||||
Integer operatingSplitIdleHours,
|
||||
Integer significantDrivingMinutes,
|
||||
Integer mergeGapSeconds,
|
||||
Integer gapDetectionToleranceSeconds,
|
||||
EsperSourceSelectionMode sourceSelectionMode,
|
||||
EsperUnknownTreatmentMode unknownTreatmentMode,
|
||||
EsperOperatingPeriodEngineMode engineMode,
|
||||
Integer vehicleEvidenceLookbackHours,
|
||||
Integer geoSearchWindowMinutes,
|
||||
Integer vicinityWindowMinutes
|
||||
) {
|
||||
public EsperDtiEnrichmentRequest {
|
||||
if (occurredFrom != null && occurredTo != null && !occurredFrom.isBefore(occurredTo)) {
|
||||
throw new IllegalArgumentException("occurredFrom must be before occurredTo");
|
||||
}
|
||||
guardHours = guardHours == null ? 24 : Math.max(0, guardHours);
|
||||
operatingSplitIdleHours = operatingSplitIdleHours == null ? null : Math.max(1, operatingSplitIdleHours);
|
||||
significantDrivingMinutes = significantDrivingMinutes == null ? null : Math.max(1, significantDrivingMinutes);
|
||||
mergeGapSeconds = mergeGapSeconds == null ? null : Math.max(0, mergeGapSeconds);
|
||||
gapDetectionToleranceSeconds = gapDetectionToleranceSeconds == null ? null : Math.max(0, gapDetectionToleranceSeconds);
|
||||
vehicleEvidenceLookbackHours = vehicleEvidenceLookbackHours == null ? 24 * 30 : Math.max(1, vehicleEvidenceLookbackHours);
|
||||
geoSearchWindowMinutes = geoSearchWindowMinutes == null ? 180 : Math.max(1, geoSearchWindowMinutes);
|
||||
vicinityWindowMinutes = vicinityWindowMinutes == null ? 180 : Math.max(1, vicinityWindowMinutes);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package at.procon.eventhub.esperpoc.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record EsperDtiEnrichmentResultDto(
|
||||
String tenantKey,
|
||||
UUID driverId,
|
||||
OffsetDateTime requestedFrom,
|
||||
OffsetDateTime requestedTo,
|
||||
OffsetDateTime loadedFrom,
|
||||
OffsetDateTime loadedTo,
|
||||
OffsetDateTime supportFrom,
|
||||
OffsetDateTime supportTo,
|
||||
int pureDtiCount,
|
||||
int supportEventCount,
|
||||
int vehicleUsageIntervalCount,
|
||||
int geoSearchWindowMinutes,
|
||||
int vicinityWindowMinutes,
|
||||
int vehicleEvidenceLookbackHours,
|
||||
List<EnrichedDtiIntervalDto> dtiIntervals,
|
||||
List<String> notes
|
||||
) {
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ public record EsperOperatingPeriodRequest(
|
|||
Integer significantDrivingMinutes,
|
||||
Integer mergeGapSeconds,
|
||||
Integer gapDetectionToleranceSeconds,
|
||||
EsperSourceSelectionMode sourceSelectionMode,
|
||||
EsperUnknownTreatmentMode unknownTreatmentMode,
|
||||
EsperOperatingPeriodEngineMode engineMode
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public record EsperOperatingPeriodResultDto(
|
|||
int significantDrivingMinutes,
|
||||
int mergeGapSeconds,
|
||||
int gapDetectionToleranceSeconds,
|
||||
EsperSourceSelectionMode sourceSelectionMode,
|
||||
EsperUnknownTreatmentMode unknownTreatmentMode,
|
||||
EsperOperatingPeriodEngineMode engineMode,
|
||||
List<RawActivityEventDto> rawEvents,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package at.procon.eventhub.esperpoc.dto;
|
||||
|
||||
public enum EsperSourceSelectionMode {
|
||||
MIXED,
|
||||
DRIVER_CARD_ONLY
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package at.procon.eventhub.esperpoc.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public record EsperSupportEventDto(
|
||||
UUID eventId,
|
||||
OffsetDateTime occurredAt,
|
||||
String sourceRowId,
|
||||
String externalSourceEventId,
|
||||
String sourceKind,
|
||||
String extractionCode,
|
||||
UUID driverId,
|
||||
UUID driverCardId,
|
||||
UUID vehicleId,
|
||||
UUID vehicleRegistrationId,
|
||||
String eventDomain,
|
||||
String eventType,
|
||||
String lifecycle,
|
||||
String cardSlot,
|
||||
BigDecimal latitude,
|
||||
BigDecimal longitude,
|
||||
String country,
|
||||
String region,
|
||||
String countryFrom,
|
||||
String countryTo,
|
||||
String operation
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package at.procon.eventhub.esperpoc.persistence;
|
||||
|
||||
import at.procon.eventhub.esperpoc.dto.EsperSupportEventDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperSourceSelectionMode;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class EsperPocDtiEnrichmentRepository {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public EsperPocDtiEnrichmentRepository(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
public List<EsperSupportEventDto> findDriverSupportEvents(
|
||||
String tenantKey,
|
||||
UUID driverId,
|
||||
OffsetDateTime supportFrom,
|
||||
OffsetDateTime supportTo,
|
||||
int precedingDriverCardEventCount,
|
||||
EsperSourceSelectionMode sourceSelectionMode
|
||||
) {
|
||||
return jdbcTemplate.query(
|
||||
"""
|
||||
with candidate as (
|
||||
select
|
||||
event.id,
|
||||
event.occurred_at,
|
||||
coalesce(
|
||||
event.payload #>> '{raw,sourceRowId}',
|
||||
regexp_replace(event.external_source_event_id, ':(START|END|INSERT|WITHDRAW)$', '')
|
||||
) as source_row_id,
|
||||
event.external_source_event_id,
|
||||
source.source_kind,
|
||||
coalesce(pkg.extraction_code, '') as extraction_code,
|
||||
event.driver_id,
|
||||
event.driver_card_id,
|
||||
event.vehicle_id,
|
||||
event.vehicle_registration_id,
|
||||
event.event_domain,
|
||||
event.event_type,
|
||||
event.lifecycle,
|
||||
detail.attributes ->> 'cardSlot' as card_slot,
|
||||
st_y(event.position::geometry) as latitude,
|
||||
st_x(event.position::geometry) as longitude,
|
||||
detail.attributes ->> 'country' as country,
|
||||
detail.attributes ->> 'region' as region,
|
||||
detail.attributes ->> 'countryFrom' as country_from,
|
||||
detail.attributes ->> 'countryTo' as country_to,
|
||||
detail.attributes ->> 'operation' as operation
|
||||
from eventhub.event event
|
||||
join eventhub.event_source source on source.id = event.event_source_id
|
||||
join eventhub.data_package pkg on pkg.id = event.data_package_id
|
||||
left join lateral (
|
||||
select detail.attributes
|
||||
from eventhub.event_detail detail
|
||||
where detail.event_occurred_at = event.occurred_at
|
||||
and detail.event_id = event.id
|
||||
order by detail.detail_type
|
||||
limit 1
|
||||
) detail on true
|
||||
where pkg.tenant_key = ?
|
||||
and source.provider_key = 'TACHOGRAPH'
|
||||
and event.driver_id = ?
|
||||
and (? <> 'DRIVER_CARD_ONLY' or source.source_kind = 'DRIVER_CARD')
|
||||
and event.event_domain in ('DRIVER_CARD', 'POSITION', 'PLACE', 'BORDER_CROSSING', 'LOAD_UNLOAD', 'SPECIFIC_CONDITION', 'SPEEDING')
|
||||
and event.occurred_at < ?
|
||||
),
|
||||
in_range as (
|
||||
select * from candidate where occurred_at >= ?
|
||||
),
|
||||
preceding_driver_card as (
|
||||
select *
|
||||
from candidate
|
||||
where occurred_at < ?
|
||||
and event_domain = 'DRIVER_CARD'
|
||||
order by occurred_at desc, id desc
|
||||
limit ?
|
||||
)
|
||||
select *
|
||||
from (
|
||||
select * from in_range
|
||||
union all
|
||||
select * from preceding_driver_card
|
||||
) result
|
||||
order by occurred_at, lifecycle, event_domain, event_type, id
|
||||
""",
|
||||
(rs, rowNum) -> new EsperSupportEventDto(
|
||||
(UUID) rs.getObject("id"),
|
||||
rs.getObject("occurred_at", OffsetDateTime.class),
|
||||
rs.getString("source_row_id"),
|
||||
rs.getString("external_source_event_id"),
|
||||
rs.getString("source_kind"),
|
||||
rs.getString("extraction_code"),
|
||||
(UUID) rs.getObject("driver_id"),
|
||||
(UUID) rs.getObject("driver_card_id"),
|
||||
(UUID) rs.getObject("vehicle_id"),
|
||||
(UUID) rs.getObject("vehicle_registration_id"),
|
||||
rs.getString("event_domain"),
|
||||
rs.getString("event_type"),
|
||||
rs.getString("lifecycle"),
|
||||
rs.getString("card_slot"),
|
||||
rs.getBigDecimal("latitude"),
|
||||
rs.getBigDecimal("longitude"),
|
||||
rs.getString("country"),
|
||||
rs.getString("region"),
|
||||
rs.getString("country_from"),
|
||||
rs.getString("country_to"),
|
||||
rs.getString("operation")
|
||||
),
|
||||
tenantKey,
|
||||
driverId,
|
||||
sourceSelectionMode == null ? EsperSourceSelectionMode.MIXED.name() : sourceSelectionMode.name(),
|
||||
supportTo,
|
||||
supportFrom,
|
||||
supportFrom,
|
||||
precedingDriverCardEventCount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,649 @@
|
|||
package at.procon.eventhub.esperpoc.service;
|
||||
|
||||
import at.procon.eventhub.dto.GeoPointDto;
|
||||
import at.procon.eventhub.esperpoc.dto.DtiBoundaryPositionDto;
|
||||
import at.procon.eventhub.esperpoc.dto.DtiBoundaryVehicleDto;
|
||||
import at.procon.eventhub.esperpoc.dto.DtiBoundaryVicinityEventDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EnrichedDtiIntervalDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperDtiEnrichmentRequest;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperDtiEnrichmentResultDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodRequest;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodResultDto;
|
||||
import at.procon.eventhub.esperpoc.dto.OperatingPeriodDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperSupportEventDto;
|
||||
import at.procon.eventhub.esperpoc.persistence.EsperPocDtiEnrichmentRepository;
|
||||
import java.time.Duration;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class EsperDtiEnrichmentService {
|
||||
|
||||
private static final Set<String> VEHICLE_INTERVAL_EXTRACTION_CODES = Set.of("IW_CYCLE", "CARD_VEHICLES_USED");
|
||||
private static final int PRECEDING_DRIVER_CARD_EVENT_COUNT = 50;
|
||||
|
||||
private final EsperOperatingPeriodEvaluationService operatingPeriodEvaluationService;
|
||||
private final EsperPocDtiEnrichmentRepository enrichmentRepository;
|
||||
|
||||
public EsperDtiEnrichmentService(
|
||||
EsperOperatingPeriodEvaluationService operatingPeriodEvaluationService,
|
||||
EsperPocDtiEnrichmentRepository enrichmentRepository
|
||||
) {
|
||||
this.operatingPeriodEvaluationService = operatingPeriodEvaluationService;
|
||||
this.enrichmentRepository = enrichmentRepository;
|
||||
}
|
||||
|
||||
public EsperDtiEnrichmentResultDto evaluate(EsperDtiEnrichmentRequest request) {
|
||||
EsperOperatingPeriodRequest operatingRequest = new EsperOperatingPeriodRequest(
|
||||
request.tenantKey(),
|
||||
request.driverId(),
|
||||
request.occurredFrom(),
|
||||
request.occurredTo(),
|
||||
request.guardHours(),
|
||||
request.operatingSplitIdleHours(),
|
||||
request.significantDrivingMinutes(),
|
||||
request.mergeGapSeconds(),
|
||||
request.gapDetectionToleranceSeconds(),
|
||||
request.sourceSelectionMode(),
|
||||
request.unknownTreatmentMode(),
|
||||
request.engineMode()
|
||||
);
|
||||
EsperOperatingPeriodResultDto pureDtiResult = operatingPeriodEvaluationService.evaluate(operatingRequest);
|
||||
|
||||
OffsetDateTime supportFrom = request.occurredFrom().minusHours(request.vehicleEvidenceLookbackHours());
|
||||
OffsetDateTime supportTo = request.occurredTo().plusHours(Math.max(request.guardHours(), 24));
|
||||
List<EsperSupportEventDto> rawSupportEvents = enrichmentRepository.findDriverSupportEvents(
|
||||
request.tenantKey(),
|
||||
request.driverId(),
|
||||
supportFrom,
|
||||
supportTo,
|
||||
PRECEDING_DRIVER_CARD_EVENT_COUNT,
|
||||
request.sourceSelectionMode()
|
||||
);
|
||||
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals = mergeVehicleUsageIntervals(buildVehicleUsageIntervals(rawSupportEvents));
|
||||
List<EsperSupportEventDto> supportEvents = condenseSupportEvents(rawSupportEvents);
|
||||
List<PureDtiInterval> pureDtiIntervals = extractPureDtiIntervals(pureDtiResult);
|
||||
List<EnrichedDtiIntervalDto> enrichedIntervals = pureDtiIntervals.stream()
|
||||
.map(interval -> enrichInterval(interval, supportEvents, vehicleUsageIntervals, request))
|
||||
.toList();
|
||||
|
||||
return new EsperDtiEnrichmentResultDto(
|
||||
request.tenantKey(),
|
||||
request.driverId(),
|
||||
pureDtiResult.requestedFrom(),
|
||||
pureDtiResult.requestedTo(),
|
||||
pureDtiResult.loadedFrom(),
|
||||
pureDtiResult.loadedTo(),
|
||||
supportFrom,
|
||||
supportTo,
|
||||
pureDtiIntervals.size(),
|
||||
supportEvents.size(),
|
||||
vehicleUsageIntervals.size(),
|
||||
request.geoSearchWindowMinutes(),
|
||||
request.vicinityWindowMinutes(),
|
||||
request.vehicleEvidenceLookbackHours(),
|
||||
enrichedIntervals,
|
||||
notes(request)
|
||||
);
|
||||
}
|
||||
|
||||
EnrichedDtiIntervalDto enrichInterval(
|
||||
PureDtiInterval interval,
|
||||
List<EsperSupportEventDto> supportEvents,
|
||||
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
|
||||
EsperDtiEnrichmentRequest request
|
||||
) {
|
||||
DtiBoundaryVehicleDto beginVehicle = resolveBoundaryVehicle(interval.startedAt(), vehicleUsageIntervals);
|
||||
DtiBoundaryVehicleDto endVehicle = resolveBoundaryVehicle(interval.endedAt(), vehicleUsageIntervals);
|
||||
DtiBoundaryPositionDto beginPosition = resolveBoundaryPosition(
|
||||
interval.startedAt(),
|
||||
beginVehicle,
|
||||
supportEvents,
|
||||
request.geoSearchWindowMinutes()
|
||||
);
|
||||
DtiBoundaryPositionDto endPosition = resolveBoundaryPosition(
|
||||
interval.endedAt(),
|
||||
endVehicle,
|
||||
supportEvents,
|
||||
request.geoSearchWindowMinutes()
|
||||
);
|
||||
List<DtiBoundaryVicinityEventDto> beginVicinity = resolveBoundaryVicinityEvents(
|
||||
interval.startedAt(),
|
||||
supportEvents,
|
||||
request.vicinityWindowMinutes()
|
||||
);
|
||||
List<DtiBoundaryVicinityEventDto> endVicinity = resolveBoundaryVicinityEvents(
|
||||
interval.endedAt(),
|
||||
supportEvents,
|
||||
request.vicinityWindowMinutes()
|
||||
);
|
||||
return new EnrichedDtiIntervalDto(
|
||||
interval.dtiId(),
|
||||
interval.driverId(),
|
||||
interval.intervalKind(),
|
||||
interval.startedAt(),
|
||||
interval.endedAt(),
|
||||
interval.durationSeconds(),
|
||||
interval.operatingPeriodNo(),
|
||||
interval.operatingPeriodStartedAt(),
|
||||
interval.previousDrivingSourceRowId(),
|
||||
interval.nextDrivingSourceRowId(),
|
||||
beginVehicle,
|
||||
endVehicle,
|
||||
beginPosition,
|
||||
endPosition,
|
||||
beginVicinity,
|
||||
endVicinity
|
||||
);
|
||||
}
|
||||
|
||||
List<PureDtiInterval> extractPureDtiIntervals(EsperOperatingPeriodResultDto result) {
|
||||
if (result == null || result.operatingPeriods() == null || result.operatingPeriods().isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<PureDtiInterval> intervals = new ArrayList<>();
|
||||
for (OperatingPeriodDto operatingPeriod : result.operatingPeriods()) {
|
||||
if (operatingPeriod.drivingTimeInterruptionEvaluation() == null
|
||||
|| operatingPeriod.drivingTimeInterruptionEvaluation().interruptionsBetweenSignificantDrivingPeriods() == null) {
|
||||
continue;
|
||||
}
|
||||
if (operatingPeriod.drivingTimeInterruptionEvaluation().departureAt() != null
|
||||
&& operatingPeriod.startedAt().isBefore(operatingPeriod.drivingTimeInterruptionEvaluation().departureAt())) {
|
||||
intervals.add(new PureDtiInterval(
|
||||
"DTI-PRE-" + operatingPeriod.operatingPeriodNo() + "-" + operatingPeriod.startedAt().toInstant().toEpochMilli(),
|
||||
result.driverId(),
|
||||
"BEFORE_FIRST_SIGNIFICANT_DRIVING",
|
||||
operatingPeriod.startedAt(),
|
||||
operatingPeriod.drivingTimeInterruptionEvaluation().departureAt(),
|
||||
Duration.between(operatingPeriod.startedAt(), operatingPeriod.drivingTimeInterruptionEvaluation().departureAt()).getSeconds(),
|
||||
operatingPeriod.operatingPeriodNo(),
|
||||
operatingPeriod.startedAt(),
|
||||
null,
|
||||
operatingPeriod.drivingTimeInterruptionEvaluation().firstSignificantDrivingPeriod() == null
|
||||
? null
|
||||
: operatingPeriod.drivingTimeInterruptionEvaluation().firstSignificantDrivingPeriod().sourceRowId()
|
||||
));
|
||||
}
|
||||
operatingPeriod.drivingTimeInterruptionEvaluation().interruptionsBetweenSignificantDrivingPeriods()
|
||||
.forEach(interruption -> intervals.add(new PureDtiInterval(
|
||||
"DTI-" + operatingPeriod.operatingPeriodNo() + "-" + interruption.from().toInstant().toEpochMilli(),
|
||||
result.driverId(),
|
||||
"BETWEEN_SIGNIFICANT_DRIVING",
|
||||
interruption.from(),
|
||||
interruption.to(),
|
||||
interruption.durationSeconds(),
|
||||
operatingPeriod.operatingPeriodNo(),
|
||||
operatingPeriod.startedAt(),
|
||||
interruption.previousDrivingSourceRowId(),
|
||||
interruption.nextDrivingSourceRowId()
|
||||
)));
|
||||
if (operatingPeriod.drivingTimeInterruptionEvaluation().arrivalAt() != null
|
||||
&& operatingPeriod.drivingTimeInterruptionEvaluation().arrivalAt().isBefore(operatingPeriod.endedAt())) {
|
||||
intervals.add(new PureDtiInterval(
|
||||
"DTI-POST-" + operatingPeriod.operatingPeriodNo() + "-" + operatingPeriod.drivingTimeInterruptionEvaluation().arrivalAt().toInstant().toEpochMilli(),
|
||||
result.driverId(),
|
||||
"AFTER_LAST_SIGNIFICANT_DRIVING",
|
||||
operatingPeriod.drivingTimeInterruptionEvaluation().arrivalAt(),
|
||||
operatingPeriod.endedAt(),
|
||||
Duration.between(operatingPeriod.drivingTimeInterruptionEvaluation().arrivalAt(), operatingPeriod.endedAt()).getSeconds(),
|
||||
operatingPeriod.operatingPeriodNo(),
|
||||
operatingPeriod.startedAt(),
|
||||
operatingPeriod.drivingTimeInterruptionEvaluation().lastSignificantDrivingPeriod() == null
|
||||
? null
|
||||
: operatingPeriod.drivingTimeInterruptionEvaluation().lastSignificantDrivingPeriod().sourceRowId(),
|
||||
null
|
||||
));
|
||||
}
|
||||
}
|
||||
return intervals;
|
||||
}
|
||||
|
||||
List<ResolvedVehicleUsageInterval> buildVehicleUsageIntervals(List<EsperSupportEventDto> supportEvents) {
|
||||
record VehicleIntervalSeed(
|
||||
EsperSupportEventDto insertEvent,
|
||||
EsperSupportEventDto withdrawEvent
|
||||
) {
|
||||
}
|
||||
java.util.Map<String, VehicleIntervalSeed> bySourceRow = new java.util.LinkedHashMap<>();
|
||||
supportEvents.stream()
|
||||
.filter(event -> "DRIVER_CARD".equals(event.eventDomain()))
|
||||
.filter(event -> VEHICLE_INTERVAL_EXTRACTION_CODES.contains(event.extractionCode()))
|
||||
.filter(event -> "CARD_INSERTED".equals(event.eventType()) || "CARD_WITHDRAWN".equals(event.eventType()))
|
||||
.filter(event -> event.sourceRowId() != null)
|
||||
.forEach(event -> {
|
||||
String key = event.extractionCode() + ":" + event.sourceRowId();
|
||||
VehicleIntervalSeed current = bySourceRow.get(key);
|
||||
EsperSupportEventDto insert = current == null ? null : current.insertEvent();
|
||||
EsperSupportEventDto withdraw = current == null ? null : current.withdrawEvent();
|
||||
if ("CARD_INSERTED".equals(event.eventType())) {
|
||||
if (insert == null || event.occurredAt().isBefore(insert.occurredAt())) {
|
||||
insert = event;
|
||||
}
|
||||
} else if (withdraw == null || event.occurredAt().isAfter(withdraw.occurredAt())) {
|
||||
withdraw = event;
|
||||
}
|
||||
bySourceRow.put(key, new VehicleIntervalSeed(insert, withdraw));
|
||||
});
|
||||
|
||||
List<ResolvedVehicleUsageInterval> result = new ArrayList<>();
|
||||
for (VehicleIntervalSeed seed : bySourceRow.values()) {
|
||||
EsperSupportEventDto anchor = seed.insertEvent() != null ? seed.insertEvent() : seed.withdrawEvent();
|
||||
if (anchor == null || anchor.vehicleId() == null) {
|
||||
continue;
|
||||
}
|
||||
OffsetDateTime startedAt = seed.insertEvent() == null ? null : seed.insertEvent().occurredAt();
|
||||
OffsetDateTime endedAt = seed.withdrawEvent() == null ? null : seed.withdrawEvent().occurredAt();
|
||||
if (startedAt == null && endedAt == null) {
|
||||
continue;
|
||||
}
|
||||
result.add(new ResolvedVehicleUsageInterval(
|
||||
anchor.driverId(),
|
||||
anchor.driverCardId(),
|
||||
anchor.vehicleId(),
|
||||
anchor.vehicleRegistrationId(),
|
||||
anchor.extractionCode(),
|
||||
startedAt,
|
||||
endedAt,
|
||||
sourcePriority(anchor.extractionCode()),
|
||||
sourceConfidence(anchor.extractionCode()),
|
||||
collectSourceRowIds(seed.insertEvent(), seed.withdrawEvent())
|
||||
));
|
||||
}
|
||||
return result.stream()
|
||||
.sorted(Comparator.comparing(ResolvedVehicleUsageInterval::startedAt, Comparator.nullsFirst(Comparator.naturalOrder()))
|
||||
.thenComparing(ResolvedVehicleUsageInterval::endedAt, Comparator.nullsLast(Comparator.naturalOrder())))
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<ResolvedVehicleUsageInterval> mergeVehicleUsageIntervals(List<ResolvedVehicleUsageInterval> intervals) {
|
||||
if (intervals.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<ResolvedVehicleUsageInterval> sorted = intervals.stream()
|
||||
.sorted(Comparator.comparing(ResolvedVehicleUsageInterval::startedAt, Comparator.nullsFirst(Comparator.naturalOrder()))
|
||||
.thenComparing(ResolvedVehicleUsageInterval::endedAt, Comparator.nullsLast(Comparator.naturalOrder())))
|
||||
.toList();
|
||||
List<ResolvedVehicleUsageInterval> merged = new ArrayList<>();
|
||||
ResolvedVehicleUsageInterval current = null;
|
||||
for (ResolvedVehicleUsageInterval next : sorted) {
|
||||
if (current == null) {
|
||||
current = next;
|
||||
continue;
|
||||
}
|
||||
if (canMerge(current, next)) {
|
||||
current = current.merge(next);
|
||||
} else {
|
||||
merged.add(current);
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
if (current != null) {
|
||||
merged.add(current);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
List<EsperSupportEventDto> condenseSupportEvents(List<EsperSupportEventDto> supportEvents) {
|
||||
if (supportEvents == null || supportEvents.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
record VehicleIntervalSeed(
|
||||
EsperSupportEventDto insertEvent,
|
||||
EsperSupportEventDto withdrawEvent
|
||||
) {
|
||||
}
|
||||
java.util.Map<String, VehicleIntervalSeed> seedsByKey = new java.util.LinkedHashMap<>();
|
||||
supportEvents.stream()
|
||||
.filter(event -> "DRIVER_CARD".equals(event.eventDomain()))
|
||||
.filter(event -> "CARD_VEHICLES_USED".equals(event.extractionCode()))
|
||||
.filter(event -> "CARD_INSERTED".equals(event.eventType()) || "CARD_WITHDRAWN".equals(event.eventType()))
|
||||
.filter(event -> event.sourceRowId() != null)
|
||||
.forEach(event -> {
|
||||
String key = event.extractionCode() + ":" + event.sourceRowId();
|
||||
VehicleIntervalSeed current = seedsByKey.get(key);
|
||||
EsperSupportEventDto insert = current == null ? null : current.insertEvent();
|
||||
EsperSupportEventDto withdraw = current == null ? null : current.withdrawEvent();
|
||||
if ("CARD_INSERTED".equals(event.eventType())) {
|
||||
if (insert == null || event.occurredAt().isBefore(insert.occurredAt())) {
|
||||
insert = event;
|
||||
}
|
||||
} else if (withdraw == null || event.occurredAt().isAfter(withdraw.occurredAt())) {
|
||||
withdraw = event;
|
||||
}
|
||||
seedsByKey.put(key, new VehicleIntervalSeed(insert, withdraw));
|
||||
});
|
||||
|
||||
List<ResolvedVehicleUsageInterval> mergedCardVehicleIntervals = mergeVehicleUsageIntervals(
|
||||
buildVehicleUsageIntervals(supportEvents).stream()
|
||||
.filter(interval -> "CARD_VEHICLES_USED".equals(interval.authoritativeSource()))
|
||||
.toList()
|
||||
);
|
||||
Set<UUID> keptDriverCardEventIds = new LinkedHashSet<>();
|
||||
for (ResolvedVehicleUsageInterval interval : mergedCardVehicleIntervals) {
|
||||
EsperSupportEventDto earliestInsert = null;
|
||||
EsperSupportEventDto latestWithdraw = null;
|
||||
for (String sourceRowId : interval.sourceRowIds()) {
|
||||
VehicleIntervalSeed seed = seedsByKey.get("CARD_VEHICLES_USED:" + sourceRowId);
|
||||
if (seed == null) {
|
||||
continue;
|
||||
}
|
||||
if (seed.insertEvent() != null
|
||||
&& (earliestInsert == null || seed.insertEvent().occurredAt().isBefore(earliestInsert.occurredAt()))) {
|
||||
earliestInsert = seed.insertEvent();
|
||||
}
|
||||
if (seed.withdrawEvent() != null
|
||||
&& (latestWithdraw == null || seed.withdrawEvent().occurredAt().isAfter(latestWithdraw.occurredAt()))) {
|
||||
latestWithdraw = seed.withdrawEvent();
|
||||
}
|
||||
}
|
||||
if (earliestInsert != null) {
|
||||
keptDriverCardEventIds.add(earliestInsert.eventId());
|
||||
}
|
||||
if (latestWithdraw != null) {
|
||||
keptDriverCardEventIds.add(latestWithdraw.eventId());
|
||||
}
|
||||
}
|
||||
|
||||
return supportEvents.stream()
|
||||
.filter(event -> !("DRIVER_CARD".equals(event.eventDomain())
|
||||
&& "CARD_VEHICLES_USED".equals(event.extractionCode())
|
||||
&& !keptDriverCardEventIds.contains(event.eventId())))
|
||||
.sorted(Comparator.comparing(EsperSupportEventDto::occurredAt)
|
||||
.thenComparing(EsperSupportEventDto::lifecycle, Comparator.nullsLast(String::compareTo))
|
||||
.thenComparing(EsperSupportEventDto::eventDomain, Comparator.nullsLast(String::compareTo))
|
||||
.thenComparing(EsperSupportEventDto::eventType, Comparator.nullsLast(String::compareTo)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
DtiBoundaryVehicleDto resolveBoundaryVehicle(
|
||||
OffsetDateTime boundary,
|
||||
List<ResolvedVehicleUsageInterval> intervals
|
||||
) {
|
||||
return intervals.stream()
|
||||
.map(interval -> intervalCandidate(boundary, interval))
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(VehicleBoundaryCandidate::score).reversed()
|
||||
.thenComparing(VehicleBoundaryCandidate::deltaSeconds)
|
||||
.thenComparing(candidate -> candidate.interval.startedAt(), Comparator.nullsLast(Comparator.reverseOrder())))
|
||||
.map(candidate -> new DtiBoundaryVehicleDto(
|
||||
candidate.interval.vehicleId(),
|
||||
candidate.interval.vehicleRegistrationId(),
|
||||
null,
|
||||
candidate.interval.authoritativeSource(),
|
||||
candidate.interval.confidence(),
|
||||
candidate.interval.startedAt(),
|
||||
candidate.interval.endedAt(),
|
||||
candidate.interval.sourceRowIds()
|
||||
))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
DtiBoundaryPositionDto resolveBoundaryPosition(
|
||||
OffsetDateTime boundary,
|
||||
DtiBoundaryVehicleDto boundaryVehicle,
|
||||
List<EsperSupportEventDto> supportEvents,
|
||||
int searchWindowMinutes
|
||||
) {
|
||||
long maxDeltaSeconds = Duration.ofMinutes(searchWindowMinutes).getSeconds();
|
||||
return supportEvents.stream()
|
||||
.filter(this::hasPosition)
|
||||
.filter(event -> Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()) <= maxDeltaSeconds)
|
||||
.sorted(Comparator
|
||||
.comparing((EsperSupportEventDto event) -> vehicleMatch(event, boundaryVehicle)).reversed()
|
||||
.thenComparing(event -> Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()))
|
||||
.thenComparing((EsperSupportEventDto event) -> geoDomainPriority(event.eventDomain()), Comparator.reverseOrder())
|
||||
.thenComparing(EsperSupportEventDto::occurredAt, Comparator.reverseOrder()))
|
||||
.map(event -> toBoundaryPosition(boundary, boundaryVehicle, event))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
List<DtiBoundaryVicinityEventDto> resolveBoundaryVicinityEvents(
|
||||
OffsetDateTime boundary,
|
||||
List<EsperSupportEventDto> supportEvents,
|
||||
int vicinityWindowMinutes
|
||||
) {
|
||||
long maxDeltaSeconds = Duration.ofMinutes(vicinityWindowMinutes).getSeconds();
|
||||
return supportEvents.stream()
|
||||
.filter(event -> Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()) <= maxDeltaSeconds)
|
||||
.sorted(Comparator
|
||||
.comparing((EsperSupportEventDto event) -> Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()))
|
||||
.thenComparing((EsperSupportEventDto event) -> supportEventPriority(event.eventDomain()), Comparator.reverseOrder())
|
||||
.thenComparing(EsperSupportEventDto::occurredAt, Comparator.reverseOrder()))
|
||||
.limit(20)
|
||||
.map(event -> new DtiBoundaryVicinityEventDto(
|
||||
event.occurredAt(),
|
||||
event.eventDomain(),
|
||||
event.eventType(),
|
||||
event.lifecycle(),
|
||||
event.sourceKind(),
|
||||
event.extractionCode(),
|
||||
event.vehicleId(),
|
||||
event.vehicleRegistrationId(),
|
||||
toGeoPoint(event),
|
||||
event.country(),
|
||||
event.region(),
|
||||
event.countryFrom(),
|
||||
event.countryTo(),
|
||||
event.operation(),
|
||||
Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()),
|
||||
event.sourceRowId()
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private VehicleBoundaryCandidate intervalCandidate(OffsetDateTime boundary, ResolvedVehicleUsageInterval interval) {
|
||||
if (interval.startedAt() == null) {
|
||||
return null;
|
||||
}
|
||||
boolean covers = !interval.startedAt().isAfter(boundary) && (interval.endedAt() == null || interval.endedAt().isAfter(boundary) || interval.endedAt().isEqual(boundary));
|
||||
long deltaSeconds;
|
||||
int baseScore;
|
||||
if (covers) {
|
||||
deltaSeconds = 0;
|
||||
baseScore = interval.priority() + 200;
|
||||
} else if (interval.endedAt() != null && interval.endedAt().isBefore(boundary)) {
|
||||
deltaSeconds = Duration.between(interval.endedAt(), boundary).getSeconds();
|
||||
baseScore = interval.priority();
|
||||
} else {
|
||||
deltaSeconds = Math.abs(Duration.between(boundary, interval.startedAt()).getSeconds());
|
||||
baseScore = interval.priority() - 25;
|
||||
}
|
||||
if (deltaSeconds > Duration.ofHours(24).getSeconds()) {
|
||||
return null;
|
||||
}
|
||||
return new VehicleBoundaryCandidate(interval, baseScore, deltaSeconds);
|
||||
}
|
||||
|
||||
private boolean canMerge(ResolvedVehicleUsageInterval left, ResolvedVehicleUsageInterval right) {
|
||||
if (!Objects.equals(left.driverId(), right.driverId())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(left.vehicleId(), right.vehicleId())) {
|
||||
return false;
|
||||
}
|
||||
boolean midnightCardVehiclesUsedContinuation =
|
||||
"CARD_VEHICLES_USED".equals(left.authoritativeSource())
|
||||
&& "CARD_VEHICLES_USED".equals(right.authoritativeSource())
|
||||
&& left.endedAt() != null
|
||||
&& right.startedAt() != null
|
||||
&& Duration.between(left.endedAt(), right.startedAt()).getSeconds() == 1;
|
||||
if (!Objects.equals(left.vehicleRegistrationId(), right.vehicleRegistrationId())
|
||||
&& !midnightCardVehiclesUsedContinuation) {
|
||||
return false;
|
||||
}
|
||||
if (left.endedAt() == null || right.startedAt() == null) {
|
||||
return true;
|
||||
}
|
||||
return !right.startedAt().isAfter(left.endedAt().plusSeconds(60));
|
||||
}
|
||||
|
||||
private boolean hasPosition(EsperSupportEventDto event) {
|
||||
return event.latitude() != null && event.longitude() != null;
|
||||
}
|
||||
|
||||
private boolean vehicleMatch(EsperSupportEventDto event, DtiBoundaryVehicleDto boundaryVehicle) {
|
||||
if (boundaryVehicle == null) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(event.vehicleId(), boundaryVehicle.vehicleId())
|
||||
|| (boundaryVehicle.vehicleRegistrationId() != null
|
||||
&& Objects.equals(event.vehicleRegistrationId(), boundaryVehicle.vehicleRegistrationId()));
|
||||
}
|
||||
|
||||
private DtiBoundaryPositionDto toBoundaryPosition(
|
||||
OffsetDateTime boundary,
|
||||
DtiBoundaryVehicleDto boundaryVehicle,
|
||||
EsperSupportEventDto event
|
||||
) {
|
||||
int confidence = geoDomainPriority(event.eventDomain()) + (vehicleMatch(event, boundaryVehicle) ? 100 : 0);
|
||||
return new DtiBoundaryPositionDto(
|
||||
event.occurredAt(),
|
||||
toGeoPoint(event),
|
||||
event.eventDomain(),
|
||||
event.eventType(),
|
||||
event.sourceKind(),
|
||||
event.extractionCode(),
|
||||
event.vehicleId(),
|
||||
event.vehicleRegistrationId(),
|
||||
event.country(),
|
||||
event.region(),
|
||||
event.countryFrom(),
|
||||
event.countryTo(),
|
||||
event.operation(),
|
||||
Math.abs(Duration.between(boundary, event.occurredAt()).getSeconds()),
|
||||
confidence,
|
||||
event.sourceRowId()
|
||||
);
|
||||
}
|
||||
|
||||
private GeoPointDto toGeoPoint(EsperSupportEventDto event) {
|
||||
if (!hasPosition(event)) {
|
||||
return null;
|
||||
}
|
||||
return new GeoPointDto(event.latitude(), event.longitude());
|
||||
}
|
||||
|
||||
private int geoDomainPriority(String eventDomain) {
|
||||
return switch (eventDomain) {
|
||||
case "POSITION" -> 400;
|
||||
case "PLACE" -> 350;
|
||||
case "BORDER_CROSSING" -> 300;
|
||||
case "LOAD_UNLOAD" -> 250;
|
||||
case "SPECIFIC_CONDITION" -> 200;
|
||||
default -> 100;
|
||||
};
|
||||
}
|
||||
|
||||
private int supportEventPriority(String eventDomain) {
|
||||
return switch (eventDomain) {
|
||||
case "DRIVER_CARD" -> 500;
|
||||
case "POSITION" -> 450;
|
||||
case "PLACE" -> 400;
|
||||
case "BORDER_CROSSING" -> 350;
|
||||
case "LOAD_UNLOAD" -> 300;
|
||||
case "SPECIFIC_CONDITION" -> 250;
|
||||
case "SPEEDING" -> 200;
|
||||
default -> 100;
|
||||
};
|
||||
}
|
||||
|
||||
private int sourcePriority(String extractionCode) {
|
||||
return "IW_CYCLE".equals(extractionCode) ? 1000 : "CARD_VEHICLES_USED".equals(extractionCode) ? 900 : 100;
|
||||
}
|
||||
|
||||
private int sourceConfidence(String extractionCode) {
|
||||
return "IW_CYCLE".equals(extractionCode) ? 100 : "CARD_VEHICLES_USED".equals(extractionCode) ? 90 : 50;
|
||||
}
|
||||
|
||||
private List<String> collectSourceRowIds(EsperSupportEventDto first, EsperSupportEventDto second) {
|
||||
Set<String> ids = new LinkedHashSet<>();
|
||||
if (first != null && first.sourceRowId() != null) {
|
||||
ids.add(first.sourceRowId());
|
||||
}
|
||||
if (second != null && second.sourceRowId() != null) {
|
||||
ids.add(second.sourceRowId());
|
||||
}
|
||||
return List.copyOf(ids);
|
||||
}
|
||||
|
||||
private List<String> notes(EsperDtiEnrichmentRequest request) {
|
||||
return List.of(
|
||||
"Pure DTI intervals come from operating-period driving interruption evaluation, including before-first and after-last significant driving intervals.",
|
||||
"Vehicle evidence prefers IW_CYCLE over CARD_VEHICLES_USED when both describe the same time span.",
|
||||
"Boundary geo selection prefers POSITION, then PLACE, then BORDER_CROSSING and LOAD_UNLOAD.",
|
||||
"Vicinity events include non-activity tachograph events around each DTI boundary.",
|
||||
"Vehicle lookback window is " + request.vehicleEvidenceLookbackHours() + " hours."
|
||||
);
|
||||
}
|
||||
|
||||
record ResolvedVehicleUsageInterval(
|
||||
UUID driverId,
|
||||
UUID driverCardId,
|
||||
UUID vehicleId,
|
||||
UUID vehicleRegistrationId,
|
||||
String authoritativeSource,
|
||||
OffsetDateTime startedAt,
|
||||
OffsetDateTime endedAt,
|
||||
int priority,
|
||||
int confidence,
|
||||
List<String> sourceRowIds
|
||||
) {
|
||||
ResolvedVehicleUsageInterval merge(ResolvedVehicleUsageInterval other) {
|
||||
OffsetDateTime mergedStart = startedAt == null ? other.startedAt : other.startedAt == null ? startedAt
|
||||
: startedAt.isBefore(other.startedAt) ? startedAt : other.startedAt;
|
||||
OffsetDateTime mergedEnd;
|
||||
if (endedAt == null || other.endedAt == null) {
|
||||
mergedEnd = null;
|
||||
} else {
|
||||
mergedEnd = endedAt.isAfter(other.endedAt) ? endedAt : other.endedAt;
|
||||
}
|
||||
String source = priority >= other.priority ? authoritativeSource : other.authoritativeSource;
|
||||
int mergedPriority = Math.max(priority, other.priority);
|
||||
int mergedConfidence = Math.max(confidence, other.confidence);
|
||||
LinkedHashSet<String> mergedIds = new LinkedHashSet<>(sourceRowIds);
|
||||
mergedIds.addAll(other.sourceRowIds);
|
||||
return new ResolvedVehicleUsageInterval(
|
||||
driverId,
|
||||
driverCardId != null ? driverCardId : other.driverCardId,
|
||||
vehicleId,
|
||||
vehicleRegistrationId != null ? vehicleRegistrationId : other.vehicleRegistrationId,
|
||||
source,
|
||||
mergedStart,
|
||||
mergedEnd,
|
||||
mergedPriority,
|
||||
mergedConfidence,
|
||||
List.copyOf(mergedIds)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
record VehicleBoundaryCandidate(
|
||||
ResolvedVehicleUsageInterval interval,
|
||||
int score,
|
||||
long deltaSeconds
|
||||
) {
|
||||
}
|
||||
|
||||
record PureDtiInterval(
|
||||
String dtiId,
|
||||
UUID driverId,
|
||||
String intervalKind,
|
||||
OffsetDateTime startedAt,
|
||||
OffsetDateTime endedAt,
|
||||
long durationSeconds,
|
||||
long operatingPeriodNo,
|
||||
OffsetDateTime operatingPeriodStartedAt,
|
||||
String previousDrivingSourceRowId,
|
||||
String nextDrivingSourceRowId
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import at.procon.eventhub.esperpoc.dto.DrivingInterruptionDto;
|
|||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodEngineMode;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodRequest;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodResultDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperSourceSelectionMode;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperUnknownTreatmentMode;
|
||||
import at.procon.eventhub.esperpoc.dto.NonDrivingIntervalDto;
|
||||
import at.procon.eventhub.esperpoc.dto.OperatingPeriodActivityIntervalDto;
|
||||
|
|
@ -68,6 +69,7 @@ public class EsperOperatingPeriodEvaluationService {
|
|||
Duration significantDrivingThreshold = Duration.ofMinutes(resolveSignificantDrivingMinutes(request));
|
||||
Duration mergeGapTolerance = Duration.ofSeconds(resolveMergeGapSeconds(request));
|
||||
Duration gapDetectionTolerance = Duration.ofSeconds(resolveGapDetectionToleranceSeconds(request));
|
||||
EsperSourceSelectionMode sourceSelectionMode = resolveSourceSelectionMode(request);
|
||||
EsperUnknownTreatmentMode unknownTreatmentMode = resolveUnknownTreatmentMode(request);
|
||||
EsperOperatingPeriodEngineMode engineMode = resolveEngineMode(request);
|
||||
|
||||
|
|
@ -82,9 +84,14 @@ public class EsperOperatingPeriodEvaluationService {
|
|||
List<RawActivityEventDto> driverCardRawEvents = rawEvents.stream()
|
||||
.filter(event -> "DRIVER_CARD".equals(event.sourceKind()))
|
||||
.toList();
|
||||
List<RawActivityEventDto> vehicleUnitRawEvents = rawEvents.stream()
|
||||
List<RawActivityEventDto> vehicleUnitRawEvents = sourceSelectionMode == EsperSourceSelectionMode.DRIVER_CARD_ONLY
|
||||
? List.of()
|
||||
: rawEvents.stream()
|
||||
.filter(event -> "VEHICLE_UNIT".equals(event.sourceKind()))
|
||||
.toList();
|
||||
List<RawActivityEventDto> selectedRawEvents = sourceSelectionMode == EsperSourceSelectionMode.DRIVER_CARD_ONLY
|
||||
? driverCardRawEvents
|
||||
: rawEvents;
|
||||
|
||||
long cardIntervalsStartedNanos = System.nanoTime();
|
||||
List<ActivityIntervalDto> driverCardRawIntervals = activityEngine.buildIntervals(driverCardRawEvents);
|
||||
|
|
@ -94,7 +101,9 @@ public class EsperOperatingPeriodEvaluationService {
|
|||
long vuIntervalsElapsedMs = elapsedMillis(vuIntervalsStartedNanos);
|
||||
|
||||
long vuGapFillStartedNanos = System.nanoTime();
|
||||
List<ActivityIntervalDto> resolvedKnownLoadedIntervals = resolveVuFillGaps(driverCardRawIntervals, vehicleUnitRawIntervals);
|
||||
List<ActivityIntervalDto> resolvedKnownLoadedIntervals = sourceSelectionMode == EsperSourceSelectionMode.DRIVER_CARD_ONLY
|
||||
? driverCardRawIntervals
|
||||
: resolveVuFillGaps(driverCardRawIntervals, vehicleUnitRawIntervals);
|
||||
long vuGapFillElapsedMs = elapsedMillis(vuGapFillStartedNanos);
|
||||
|
||||
long unknownGapStartedNanos = System.nanoTime();
|
||||
|
|
@ -153,16 +162,17 @@ public class EsperOperatingPeriodEvaluationService {
|
|||
);
|
||||
long totalElapsedMs = elapsedMillis(startedNanos);
|
||||
|
||||
log.info("Esper operating-period evaluation tenant={} driverId={} requestedFrom={} requestedTo={} loadedFrom={} loadedTo={} unknownMode={} engineMode={} rawEvents={} cardRawEvents={} vuRawEvents={} cardIntervals={} vuIntervals={} resolvedKnownIntervals={} evaluationIntervals={} periodizedIntervals={} mergedIntervals={} nonDrivingIntervals={} operatingPeriods={} timingsMs={{dbRetrieve={}, cardIntervalEsper={}, vuIntervalEsper={}, vuGapFill={}, synthUnknown={}, periodizeEsper={}, merge={}, nonDriving={}, total={}}}",
|
||||
log.info("Esper operating-period evaluation tenant={} driverId={} requestedFrom={} requestedTo={} loadedFrom={} loadedTo={} sourceSelectionMode={} unknownMode={} engineMode={} rawEvents={} cardRawEvents={} vuRawEvents={} cardIntervals={} vuIntervals={} resolvedKnownIntervals={} evaluationIntervals={} periodizedIntervals={} mergedIntervals={} nonDrivingIntervals={} operatingPeriods={} timingsMs={{dbRetrieve={}, cardIntervalEsper={}, vuIntervalEsper={}, vuGapFill={}, synthUnknown={}, periodizeEsper={}, merge={}, nonDriving={}, total={}}}",
|
||||
request.tenantKey(),
|
||||
request.driverId(),
|
||||
requestedFrom,
|
||||
requestedTo,
|
||||
loadedFrom,
|
||||
loadedTo,
|
||||
sourceSelectionMode,
|
||||
unknownTreatmentMode,
|
||||
engineMode,
|
||||
rawEvents.size(),
|
||||
selectedRawEvents.size(),
|
||||
driverCardRawEvents.size(),
|
||||
vehicleUnitRawEvents.size(),
|
||||
driverCardRawIntervals.size(),
|
||||
|
|
@ -190,7 +200,7 @@ public class EsperOperatingPeriodEvaluationService {
|
|||
requestedTo,
|
||||
loadedFrom,
|
||||
loadedTo,
|
||||
rawEvents.size(),
|
||||
selectedRawEvents.size(),
|
||||
driverCardRawEvents.size(),
|
||||
vehicleUnitRawEvents.size(),
|
||||
driverCardRawIntervals.size(),
|
||||
|
|
@ -205,9 +215,10 @@ public class EsperOperatingPeriodEvaluationService {
|
|||
resolveSignificantDrivingMinutes(request),
|
||||
resolveMergeGapSeconds(request),
|
||||
resolveGapDetectionToleranceSeconds(request),
|
||||
sourceSelectionMode,
|
||||
unknownTreatmentMode,
|
||||
engineMode,
|
||||
rawEvents,
|
||||
selectedRawEvents,
|
||||
resolvedKnownLoadedIntervals,
|
||||
evaluationLoadedIntervals,
|
||||
periodizedIntervals,
|
||||
|
|
@ -219,7 +230,8 @@ public class EsperOperatingPeriodEvaluationService {
|
|||
unknownTreatmentMode,
|
||||
resolveOperatingSplitIdleHours(request),
|
||||
resolveSignificantDrivingMinutes(request),
|
||||
resolveGapDetectionToleranceSeconds(request)
|
||||
resolveGapDetectionToleranceSeconds(request),
|
||||
sourceSelectionMode
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -723,15 +735,21 @@ public class EsperOperatingPeriodEvaluationService {
|
|||
: properties.getEsperPoc().getOperatingPeriodEvaluation().getEngineMode();
|
||||
}
|
||||
|
||||
private EsperSourceSelectionMode resolveSourceSelectionMode(EsperOperatingPeriodRequest request) {
|
||||
return request.sourceSelectionMode() == null ? EsperSourceSelectionMode.MIXED : request.sourceSelectionMode();
|
||||
}
|
||||
|
||||
private List<String> notes(
|
||||
EsperOperatingPeriodEngineMode engineMode,
|
||||
EsperUnknownTreatmentMode unknownTreatmentMode,
|
||||
int operatingSplitIdleHours,
|
||||
int significantDrivingMinutes,
|
||||
int gapDetectionToleranceSeconds
|
||||
int gapDetectionToleranceSeconds,
|
||||
EsperSourceSelectionMode sourceSelectionMode
|
||||
) {
|
||||
return List.of(
|
||||
"This endpoint runs in parallel to the existing working-shift PoC and does not change its semantics.",
|
||||
"Source selection mode is " + sourceSelectionMode + ".",
|
||||
"BREAK_REST events are ignored for activity evaluation but still prevent synthetic UNKNOWN intervals from being created over covered rest spans.",
|
||||
"Synthetic UNKNOWN intervals are created only for uncovered gaps between non-rest activities.",
|
||||
"UNKNOWN treatment mode is " + unknownTreatmentMode + ".",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
package at.procon.eventhub.esperpoc.api;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import at.procon.eventhub.esperpoc.dto.EsperDtiEnrichmentResultDto;
|
||||
import at.procon.eventhub.esperpoc.service.EsperDtiEnrichmentService;
|
||||
import at.procon.eventhub.esperpoc.service.EsperOperatingPeriodEvaluationService;
|
||||
import at.procon.eventhub.esperpoc.service.EsperPocDriverCardActivityService;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
class EsperPocControllerTest {
|
||||
|
||||
@Test
|
||||
void exposesDtiEnrichmentEndpoint() throws Exception {
|
||||
EsperPocDriverCardActivityService activityService = org.mockito.Mockito.mock(EsperPocDriverCardActivityService.class);
|
||||
EsperOperatingPeriodEvaluationService operatingService = org.mockito.Mockito.mock(EsperOperatingPeriodEvaluationService.class);
|
||||
EsperDtiEnrichmentService enrichmentService = org.mockito.Mockito.mock(EsperDtiEnrichmentService.class);
|
||||
EsperPocController controller = new EsperPocController(activityService, operatingService, enrichmentService);
|
||||
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
|
||||
|
||||
when(enrichmentService.evaluate(any())).thenReturn(new EsperDtiEnrichmentResultDto(
|
||||
"default",
|
||||
UUID.fromString("00000000-0000-0000-0000-000000000123"),
|
||||
OffsetDateTime.parse("2026-04-01T00:00:00Z"),
|
||||
OffsetDateTime.parse("2026-04-02T00:00:00Z"),
|
||||
OffsetDateTime.parse("2026-03-31T00:00:00Z"),
|
||||
OffsetDateTime.parse("2026-04-03T00:00:00Z"),
|
||||
OffsetDateTime.parse("2026-03-01T00:00:00Z"),
|
||||
OffsetDateTime.parse("2026-04-03T00:00:00Z"),
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
180,
|
||||
180,
|
||||
720,
|
||||
List.of(),
|
||||
List.of("note")
|
||||
));
|
||||
|
||||
mockMvc.perform(get("/api/eventhub/esper-poc/tachograph/dti-enrichment")
|
||||
.param("tenantKey", "default")
|
||||
.param("driverId", "00000000-0000-0000-0000-000000000123")
|
||||
.param("occurredFrom", "2026-04-01T00:00:00Z")
|
||||
.param("occurredTo", "2026-04-02T00:00:00Z"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.tenantKey").value("default"))
|
||||
.andExpect(jsonPath("$.pureDtiCount").value(1))
|
||||
.andExpect(jsonPath("$.vehicleUsageIntervalCount").value(1));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
package at.procon.eventhub.esperpoc.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import at.procon.eventhub.esperpoc.dto.DtiBoundaryPositionDto;
|
||||
import at.procon.eventhub.esperpoc.dto.DtiBoundaryVehicleDto;
|
||||
import at.procon.eventhub.esperpoc.dto.DtiBoundaryVicinityEventDto;
|
||||
import at.procon.eventhub.esperpoc.dto.DrivingInterruptionDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EnrichedDtiIntervalDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperDtiEnrichmentRequest;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodEngineMode;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodResultDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperSourceSelectionMode;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperSupportEventDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperUnknownTreatmentMode;
|
||||
import at.procon.eventhub.esperpoc.dto.OperatingPeriodDto;
|
||||
import at.procon.eventhub.esperpoc.dto.ShiftDrivingEvaluationDto;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class EsperDtiEnrichmentServiceTest {
|
||||
|
||||
private final EsperDtiEnrichmentService service = new EsperDtiEnrichmentService(null, null);
|
||||
|
||||
@Test
|
||||
void mergesVehicleIntervalsAndPrefersIwCycleAsAuthoritativeSource() {
|
||||
UUID driverId = UUID.randomUUID();
|
||||
UUID vehicleId = UUID.randomUUID();
|
||||
UUID registrationId = UUID.randomUUID();
|
||||
|
||||
List<EsperDtiEnrichmentService.ResolvedVehicleUsageInterval> merged = service.mergeVehicleUsageIntervals(
|
||||
service.buildVehicleUsageIntervals(List.of(
|
||||
supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "CARD_VEHICLES_USED", "cv1", "2026-04-01T08:00:00Z", null, null),
|
||||
supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "CARD_VEHICLES_USED", "cv1", "2026-04-01T12:00:00Z", null, null),
|
||||
supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "IW_CYCLE", "iw1", "2026-04-01T08:05:00Z", null, null),
|
||||
supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "IW_CYCLE", "iw1", "2026-04-01T11:55:00Z", null, null)
|
||||
))
|
||||
);
|
||||
|
||||
assertThat(merged).hasSize(1);
|
||||
assertThat(merged.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T08:00:00Z"));
|
||||
assertThat(merged.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T12:00:00Z"));
|
||||
assertThat(merged.get(0).authoritativeSource()).isEqualTo("IW_CYCLE");
|
||||
assertThat(merged.get(0).sourceRowIds()).containsExactly("cv1", "iw1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mergesCardVehiclesUsedAcrossMidnightBoundaryWhenCardRemainsInVehicle() {
|
||||
UUID driverId = UUID.randomUUID();
|
||||
UUID vehicleId = UUID.randomUUID();
|
||||
UUID firstRegistrationId = UUID.randomUUID();
|
||||
UUID secondRegistrationId = UUID.randomUUID();
|
||||
|
||||
List<EsperDtiEnrichmentService.ResolvedVehicleUsageInterval> merged = service.mergeVehicleUsageIntervals(
|
||||
service.buildVehicleUsageIntervals(List.of(
|
||||
supportEvent(driverId, vehicleId, firstRegistrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "CARD_VEHICLES_USED", "cv1", "2026-04-01T08:00:00Z", null, null),
|
||||
supportEvent(driverId, vehicleId, firstRegistrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "CARD_VEHICLES_USED", "cv1", "2026-04-01T23:59:59Z", null, null),
|
||||
supportEvent(driverId, vehicleId, secondRegistrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "CARD_VEHICLES_USED", "cv2", "2026-04-02T00:00:00Z", null, null),
|
||||
supportEvent(driverId, vehicleId, secondRegistrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "CARD_VEHICLES_USED", "cv2", "2026-04-02T12:00:00Z", null, null)
|
||||
))
|
||||
);
|
||||
|
||||
assertThat(merged).hasSize(1);
|
||||
assertThat(merged.get(0).authoritativeSource()).isEqualTo("CARD_VEHICLES_USED");
|
||||
assertThat(merged.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T08:00:00Z"));
|
||||
assertThat(merged.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-02T12:00:00Z"));
|
||||
assertThat(merged.get(0).sourceRowIds()).containsExactly("cv1", "cv2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void condensesConsecutiveCardVehiclesUsedSupportEventsToMergedBoundaryEvents() {
|
||||
UUID driverId = UUID.randomUUID();
|
||||
UUID vehicleId = UUID.randomUUID();
|
||||
UUID firstRegistrationId = UUID.randomUUID();
|
||||
UUID secondRegistrationId = UUID.randomUUID();
|
||||
|
||||
List<EsperSupportEventDto> condensed = service.condenseSupportEvents(List.of(
|
||||
supportEvent(driverId, vehicleId, firstRegistrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "CARD_VEHICLES_USED", "cv1", "2026-04-01T08:00:00Z", null, null),
|
||||
supportEvent(driverId, vehicleId, firstRegistrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "CARD_VEHICLES_USED", "cv1", "2026-04-01T23:59:59Z", null, null),
|
||||
supportEvent(driverId, vehicleId, secondRegistrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "CARD_VEHICLES_USED", "cv2", "2026-04-02T00:00:00Z", null, null),
|
||||
supportEvent(driverId, vehicleId, secondRegistrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "CARD_VEHICLES_USED", "cv2", "2026-04-02T12:00:00Z", null, null),
|
||||
supportEvent(driverId, vehicleId, secondRegistrationId, "POSITION", "POSITION_RECORDED", "SNAPSHOT", "CARD_POSITION", "pos1", "2026-04-02T00:05:00Z", "48.2082", "16.3738")
|
||||
));
|
||||
|
||||
assertThat(condensed).hasSize(3);
|
||||
assertThat(condensed)
|
||||
.extracting(EsperSupportEventDto::extractionCode, EsperSupportEventDto::eventType, EsperSupportEventDto::sourceRowId)
|
||||
.containsExactly(
|
||||
org.assertj.core.groups.Tuple.tuple("CARD_VEHICLES_USED", "CARD_INSERTED", "cv1"),
|
||||
org.assertj.core.groups.Tuple.tuple("CARD_POSITION", "POSITION_RECORDED", "pos1"),
|
||||
org.assertj.core.groups.Tuple.tuple("CARD_VEHICLES_USED", "CARD_WITHDRAWN", "cv2")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvesBoundaryVehicleGeoAndVicinityFromSupportEvents() {
|
||||
UUID driverId = UUID.randomUUID();
|
||||
UUID vehicleId = UUID.randomUUID();
|
||||
UUID registrationId = UUID.randomUUID();
|
||||
OffsetDateTime start = OffsetDateTime.parse("2026-04-01T09:00:00Z");
|
||||
OffsetDateTime end = OffsetDateTime.parse("2026-04-01T10:00:00Z");
|
||||
|
||||
List<EsperSupportEventDto> supportEvents = List.of(
|
||||
supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_INSERTED", "INSERT", "IW_CYCLE", "iw1", "2026-04-01T08:00:00Z", null, null),
|
||||
supportEvent(driverId, vehicleId, registrationId, "DRIVER_CARD", "CARD_WITHDRAWN", "WITHDRAW", "IW_CYCLE", "iw1", "2026-04-01T12:00:00Z", null, null),
|
||||
supportEvent(driverId, vehicleId, registrationId, "POSITION", "POSITION_RECORDED", "SNAPSHOT", "VU_POSITION", "p1", "2026-04-01T09:01:00Z", "48.2082", "16.3738"),
|
||||
supportEvent(driverId, vehicleId, registrationId, "LOAD_UNLOAD", "LOAD", "SNAPSHOT", "VU_LOAD_UNLOAD", "lu1", "2026-04-01T09:03:00Z", "48.2085", "16.3740"),
|
||||
supportEvent(driverId, vehicleId, registrationId, "PLACE", "WORKING_DAY_PLACE_RECORDED", "END", "VU_PLACE", "pl1", "2026-04-01T09:59:00Z", "48.2090", "16.3750")
|
||||
);
|
||||
List<EsperDtiEnrichmentService.ResolvedVehicleUsageInterval> usageIntervals = service.mergeVehicleUsageIntervals(
|
||||
service.buildVehicleUsageIntervals(supportEvents)
|
||||
);
|
||||
|
||||
EnrichedDtiIntervalDto enriched = service.enrichInterval(
|
||||
new EsperDtiEnrichmentService.PureDtiInterval(
|
||||
"DTI-1",
|
||||
driverId,
|
||||
"BETWEEN_SIGNIFICANT_DRIVING",
|
||||
start,
|
||||
end,
|
||||
3600,
|
||||
1,
|
||||
OffsetDateTime.parse("2026-04-01T08:00:00Z"),
|
||||
"d-prev",
|
||||
"d-next"
|
||||
),
|
||||
supportEvents,
|
||||
usageIntervals,
|
||||
new EsperDtiEnrichmentRequest(
|
||||
"default",
|
||||
driverId,
|
||||
OffsetDateTime.parse("2026-04-01T00:00:00Z"),
|
||||
OffsetDateTime.parse("2026-04-02T00:00:00Z"),
|
||||
24,
|
||||
7,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
EsperSourceSelectionMode.MIXED,
|
||||
null,
|
||||
null,
|
||||
24 * 30,
|
||||
180,
|
||||
180
|
||||
)
|
||||
);
|
||||
|
||||
DtiBoundaryVehicleDto beginVehicle = enriched.beginVehicle();
|
||||
assertThat(beginVehicle).isNotNull();
|
||||
assertThat(beginVehicle.vehicleId()).isEqualTo(vehicleId);
|
||||
assertThat(beginVehicle.resolutionSource()).isEqualTo("IW_CYCLE");
|
||||
assertThat(enriched.intervalKind()).isEqualTo("BETWEEN_SIGNIFICANT_DRIVING");
|
||||
|
||||
DtiBoundaryPositionDto beginPosition = enriched.beginPosition();
|
||||
assertThat(beginPosition).isNotNull();
|
||||
assertThat(beginPosition.eventDomain()).isEqualTo("POSITION");
|
||||
assertThat(beginPosition.position().latitude()).isEqualByComparingTo("48.2082");
|
||||
|
||||
DtiBoundaryPositionDto endPosition = enriched.endPosition();
|
||||
assertThat(endPosition).isNotNull();
|
||||
assertThat(endPosition.eventDomain()).isEqualTo("PLACE");
|
||||
|
||||
List<DtiBoundaryVicinityEventDto> beginVicinity = enriched.beginVicinityEvents();
|
||||
assertThat(beginVicinity).extracting(DtiBoundaryVicinityEventDto::eventDomain)
|
||||
.contains("DRIVER_CARD", "POSITION", "LOAD_UNLOAD");
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractsPureDtiFromOperatingPeriodDrivingInterruptions() {
|
||||
UUID driverId = UUID.randomUUID();
|
||||
OffsetDateTime requestedFrom = OffsetDateTime.parse("2026-04-01T00:00:00Z");
|
||||
OffsetDateTime requestedTo = OffsetDateTime.parse("2026-04-02T00:00:00Z");
|
||||
|
||||
EsperOperatingPeriodResultDto result = new EsperOperatingPeriodResultDto(
|
||||
"default",
|
||||
driverId,
|
||||
requestedFrom,
|
||||
requestedTo,
|
||||
requestedFrom.minusHours(24),
|
||||
requestedTo.plusHours(24),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
7,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
EsperSourceSelectionMode.MIXED,
|
||||
EsperUnknownTreatmentMode.AS_BREAK_REST,
|
||||
EsperOperatingPeriodEngineMode.STREAM_COLLECTOR,
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(new OperatingPeriodDto(
|
||||
4,
|
||||
OffsetDateTime.parse("2026-04-01T06:00:00Z"),
|
||||
OffsetDateTime.parse("2026-04-01T18:00:00Z"),
|
||||
12 * 3600L,
|
||||
"FLUSH",
|
||||
List.of(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
new ShiftDrivingEvaluationDto(
|
||||
3,
|
||||
OffsetDateTime.parse("2026-04-01T08:00:00Z"),
|
||||
OffsetDateTime.parse("2026-04-01T16:00:00Z"),
|
||||
activity(driverId, "2026-04-01T08:00:00Z", "2026-04-01T10:00:00Z", "d1"),
|
||||
activity(driverId, "2026-04-01T14:00:00Z", "2026-04-01T16:00:00Z", "d2"),
|
||||
List.of(new DrivingInterruptionDto(
|
||||
OffsetDateTime.parse("2026-04-01T10:00:00Z"),
|
||||
OffsetDateTime.parse("2026-04-01T14:00:00Z"),
|
||||
14400,
|
||||
"d1",
|
||||
"d2"
|
||||
))
|
||||
),
|
||||
false
|
||||
)),
|
||||
List.of("note")
|
||||
);
|
||||
|
||||
List<EsperDtiEnrichmentService.PureDtiInterval> pureDtiIntervals = service.extractPureDtiIntervals(result);
|
||||
|
||||
assertThat(pureDtiIntervals).hasSize(3);
|
||||
assertThat(pureDtiIntervals).extracting(EsperDtiEnrichmentService.PureDtiInterval::intervalKind)
|
||||
.containsExactly("BEFORE_FIRST_SIGNIFICANT_DRIVING", "BETWEEN_SIGNIFICANT_DRIVING", "AFTER_LAST_SIGNIFICANT_DRIVING");
|
||||
assertThat(pureDtiIntervals.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T06:00:00Z"));
|
||||
assertThat(pureDtiIntervals.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T08:00:00Z"));
|
||||
assertThat(pureDtiIntervals.get(0).previousDrivingSourceRowId()).isNull();
|
||||
assertThat(pureDtiIntervals.get(0).nextDrivingSourceRowId()).isEqualTo("d1");
|
||||
|
||||
assertThat(pureDtiIntervals.get(1).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T10:00:00Z"));
|
||||
assertThat(pureDtiIntervals.get(1).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T14:00:00Z"));
|
||||
assertThat(pureDtiIntervals.get(1).operatingPeriodNo()).isEqualTo(4);
|
||||
assertThat(pureDtiIntervals.get(1).previousDrivingSourceRowId()).isEqualTo("d1");
|
||||
assertThat(pureDtiIntervals.get(1).nextDrivingSourceRowId()).isEqualTo("d2");
|
||||
|
||||
assertThat(pureDtiIntervals.get(2).startedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T16:00:00Z"));
|
||||
assertThat(pureDtiIntervals.get(2).endedAt()).isEqualTo(OffsetDateTime.parse("2026-04-01T18:00:00Z"));
|
||||
assertThat(pureDtiIntervals.get(2).previousDrivingSourceRowId()).isEqualTo("d2");
|
||||
assertThat(pureDtiIntervals.get(2).nextDrivingSourceRowId()).isNull();
|
||||
}
|
||||
|
||||
private at.procon.eventhub.esperpoc.dto.ActivityIntervalDto activity(
|
||||
UUID driverId,
|
||||
String from,
|
||||
String to,
|
||||
String sourceRowId
|
||||
) {
|
||||
return at.procon.eventhub.esperpoc.dto.ActivityIntervalDto.raw(
|
||||
driverId,
|
||||
null,
|
||||
null,
|
||||
"DRIVE",
|
||||
"DRIVER",
|
||||
"INSERTED",
|
||||
"KNOWN",
|
||||
"DRIVER_CARD",
|
||||
OffsetDateTime.parse(from),
|
||||
OffsetDateTime.parse(to),
|
||||
sourceRowId
|
||||
);
|
||||
}
|
||||
|
||||
private EsperSupportEventDto supportEvent(
|
||||
UUID driverId,
|
||||
UUID vehicleId,
|
||||
UUID registrationId,
|
||||
String eventDomain,
|
||||
String eventType,
|
||||
String lifecycle,
|
||||
String extractionCode,
|
||||
String sourceRowId,
|
||||
String occurredAt,
|
||||
String latitude,
|
||||
String longitude
|
||||
) {
|
||||
return new EsperSupportEventDto(
|
||||
UUID.randomUUID(),
|
||||
OffsetDateTime.parse(occurredAt),
|
||||
sourceRowId,
|
||||
extractionCode + ":" + sourceRowId + ":" + eventType,
|
||||
extractionCode.startsWith("VU_") || "IW_CYCLE".equals(extractionCode) ? "VEHICLE_UNIT" : "DRIVER_CARD",
|
||||
extractionCode,
|
||||
driverId,
|
||||
UUID.randomUUID(),
|
||||
vehicleId,
|
||||
registrationId,
|
||||
eventDomain,
|
||||
eventType,
|
||||
lifecycle,
|
||||
"DRIVER",
|
||||
latitude == null ? null : new BigDecimal(latitude),
|
||||
longitude == null ? null : new BigDecimal(longitude),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"LOAD".equals(eventType) ? "LOAD" : null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
|
||||
import at.procon.eventhub.esperpoc.dto.ActivityIntervalDto;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodEngineMode;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperOperatingPeriodRequest;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperSourceSelectionMode;
|
||||
import at.procon.eventhub.esperpoc.dto.EsperUnknownTreatmentMode;
|
||||
import at.procon.eventhub.esperpoc.dto.RawActivityEventDto;
|
||||
import at.procon.eventhub.esperpoc.persistence.EsperPocActivityRepository;
|
||||
import at.procon.eventhub.esperpoc.dto.NonDrivingIntervalDto;
|
||||
import at.procon.eventhub.esperpoc.dto.OperatingPeriodActivityIntervalDto;
|
||||
import java.time.Duration;
|
||||
|
|
@ -12,6 +16,10 @@ import java.time.OffsetDateTime;
|
|||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class EsperOperatingPeriodEvaluationServiceTest {
|
||||
|
||||
|
|
@ -132,6 +140,46 @@ class EsperOperatingPeriodEvaluationServiceTest {
|
|||
.isEqualTo(collectorEvaluation.closedPeriods());
|
||||
}
|
||||
|
||||
@Test
|
||||
void driverCardOnlyModeIgnoresVehicleUnitGapFill() {
|
||||
UUID driverId = UUID.randomUUID();
|
||||
EsperPocActivityRepository repository = mock(EsperPocActivityRepository.class);
|
||||
EsperOperatingPeriodEvaluationService evaluationService = new EsperOperatingPeriodEvaluationService(
|
||||
repository,
|
||||
new EsperDriverActivityEngine(),
|
||||
operatingPeriodEngine
|
||||
);
|
||||
when(repository.findDriverActivityEvents(eq("default"), eq(driverId), any(), any())).thenReturn(List.of(
|
||||
raw(driverId, "DRIVER_CARD", "DRIVE", "START", "2026-04-01T08:00:00Z", "card-1"),
|
||||
raw(driverId, "DRIVER_CARD", "DRIVE", "END", "2026-04-01T09:00:00Z", "card-1"),
|
||||
raw(driverId, "VEHICLE_UNIT", "DRIVE", "START", "2026-04-01T09:00:00Z", "vu-1"),
|
||||
raw(driverId, "VEHICLE_UNIT", "DRIVE", "END", "2026-04-01T10:00:00Z", "vu-1")
|
||||
));
|
||||
|
||||
var result = evaluationService.evaluate(new EsperOperatingPeriodRequest(
|
||||
"default",
|
||||
driverId,
|
||||
OffsetDateTime.parse("2026-04-01T00:00:00Z"),
|
||||
OffsetDateTime.parse("2026-04-02T00:00:00Z"),
|
||||
24,
|
||||
7,
|
||||
3,
|
||||
0,
|
||||
0,
|
||||
EsperSourceSelectionMode.DRIVER_CARD_ONLY,
|
||||
EsperUnknownTreatmentMode.AS_BREAK_REST,
|
||||
EsperOperatingPeriodEngineMode.STREAM_COLLECTOR
|
||||
));
|
||||
|
||||
assertThat(result.rawEventCount()).isEqualTo(2);
|
||||
assertThat(result.driverCardRawEventCount()).isEqualTo(2);
|
||||
assertThat(result.vehicleUnitRawEventCount()).isZero();
|
||||
assertThat(result.driverCardIntervalCount()).isEqualTo(1);
|
||||
assertThat(result.vehicleUnitIntervalCount()).isZero();
|
||||
assertThat(result.resolvedKnownIntervalCount()).isEqualTo(1);
|
||||
assertThat(result.rawEvents()).extracting(RawActivityEventDto::sourceKind).containsOnly("DRIVER_CARD");
|
||||
}
|
||||
|
||||
private ActivityIntervalDto activity(
|
||||
UUID driverId,
|
||||
String activity,
|
||||
|
|
@ -184,4 +232,31 @@ class EsperOperatingPeriodEvaluationServiceTest {
|
|||
0L
|
||||
);
|
||||
}
|
||||
|
||||
private RawActivityEventDto raw(
|
||||
UUID driverId,
|
||||
String sourceKind,
|
||||
String eventType,
|
||||
String lifecycle,
|
||||
String occurredAt,
|
||||
String sourceRowId
|
||||
) {
|
||||
return new RawActivityEventDto(
|
||||
UUID.randomUUID(),
|
||||
OffsetDateTime.parse(occurredAt),
|
||||
sourceRowId,
|
||||
sourceKind + ":" + sourceRowId + ":" + lifecycle,
|
||||
sourceKind,
|
||||
"DRIVER_CARD".equals(sourceKind) ? "CARD_ACTIVITY" : "VU_ACTIVITY",
|
||||
driverId,
|
||||
null,
|
||||
null,
|
||||
eventType,
|
||||
lifecycle,
|
||||
"DRIVER",
|
||||
"INSERTED",
|
||||
"KNOWN",
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue