Add reusable tachograph projections and event adapter
This commit is contained in:
parent
317983eba8
commit
9ef8bfc412
|
|
@ -0,0 +1,12 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record TachographEsperDrivingDerivedProjectionBundle(
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals,
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals,
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals,
|
||||||
|
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals,
|
||||||
|
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.model;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record TachographTimelineEventBundle(
|
||||||
|
List<EventHubEventDto> activityEvents,
|
||||||
|
List<EventHubEventDto> vehicleUsageEvents,
|
||||||
|
List<EventHubEventDto> supportEvents
|
||||||
|
) {
|
||||||
|
public TachographTimelineEventBundle {
|
||||||
|
activityEvents = copy(activityEvents);
|
||||||
|
vehicleUsageEvents = copy(vehicleUsageEvents);
|
||||||
|
supportEvents = copy(supportEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EventHubEventDto> allEvents() {
|
||||||
|
List<EventHubEventDto> result = new ArrayList<>(activityEvents.size() + vehicleUsageEvents.size() + supportEvents.size());
|
||||||
|
result.addAll(activityEvents);
|
||||||
|
result.addAll(vehicleUsageEvents);
|
||||||
|
result.addAll(supportEvents);
|
||||||
|
result.sort(Comparator.comparing(EventHubEventDto::occurredAt)
|
||||||
|
.thenComparing(event -> event.eventDomain().name())
|
||||||
|
.thenComparing(event -> event.eventType().name())
|
||||||
|
.thenComparing(event -> event.lifecycle().name())
|
||||||
|
.thenComparing(EventHubEventDto::externalSourceEventId));
|
||||||
|
return List.copyOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<EventHubEventDto> copy(List<EventHubEventDto> events) {
|
||||||
|
return events == null ? List.of() : List.copyOf(events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographTimelineEventBundle;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface DriverTimelineEventBuilder {
|
||||||
|
|
||||||
|
TachographTimelineEventBundle buildEventBundle(
|
||||||
|
TachographFileSession session,
|
||||||
|
DriverExtractionSession driverSession
|
||||||
|
);
|
||||||
|
|
||||||
|
TachographTimelineEventBundle buildEventBundle(
|
||||||
|
TachographFileSession session,
|
||||||
|
DriverExtractionSession driverSession,
|
||||||
|
ResolvedDriverTimeline timeline
|
||||||
|
);
|
||||||
|
|
||||||
|
default List<EventHubEventDto> buildEvents(
|
||||||
|
TachographFileSession session,
|
||||||
|
DriverExtractionSession driverSession
|
||||||
|
) {
|
||||||
|
return buildEventBundle(session, driverSession).allEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<EventHubEventDto> buildEvents(
|
||||||
|
TachographFileSession session,
|
||||||
|
DriverExtractionSession driverSession,
|
||||||
|
ResolvedDriverTimeline timeline
|
||||||
|
) {
|
||||||
|
return buildEventBundle(session, driverSession, timeline).allEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,442 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.service;
|
||||||
|
|
||||||
|
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 at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
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.TachographEsperDrivingDerivedProjectionBundle;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographEsperVuCardAbsentIntervalEvent;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StreamUtils;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DriverTimelineReusableProjectionBuilder {
|
||||||
|
|
||||||
|
private static final AtomicLong RUNTIME_COUNTER = new AtomicLong();
|
||||||
|
private static final String DRIVING_DERIVED_PROJECTION_BUNDLE_EPL_TEMPLATE =
|
||||||
|
loadResource("esper/tachograph-driving-derived-projection-bundle.epl");
|
||||||
|
|
||||||
|
private final DriverTimelineBuilder driverTimelineBuilder;
|
||||||
|
|
||||||
|
public DriverTimelineReusableProjectionBuilder(DriverTimelineBuilder driverTimelineBuilder) {
|
||||||
|
this.driverTimelineBuilder = driverTimelineBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
|
||||||
|
TachographFileSession session,
|
||||||
|
DriverExtractionSession driverSession,
|
||||||
|
int significantDrivingMinutes,
|
||||||
|
int minimumRestPeriodMinutes
|
||||||
|
) {
|
||||||
|
ResolvedDriverTimeline timeline = driverTimelineBuilder.build(session, driverSession);
|
||||||
|
return buildEsperDrivingDerivedProjectionBundle(
|
||||||
|
session.sessionId(),
|
||||||
|
driverSession.driverKey(),
|
||||||
|
timeline,
|
||||||
|
significantDrivingMinutes,
|
||||||
|
minimumRestPeriodMinutes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
|
||||||
|
UUID sessionId,
|
||||||
|
String driverKey,
|
||||||
|
ResolvedDriverTimeline timeline,
|
||||||
|
int significantDrivingMinutes,
|
||||||
|
int minimumRestPeriodMinutes
|
||||||
|
) {
|
||||||
|
if (timeline == null) {
|
||||||
|
return emptyBundle();
|
||||||
|
}
|
||||||
|
return buildEsperDrivingDerivedProjectionBundle(
|
||||||
|
sessionId,
|
||||||
|
driverKey,
|
||||||
|
timeline.activityIntervals(),
|
||||||
|
timeline.vehicleUsageIntervals(),
|
||||||
|
significantDrivingMinutes,
|
||||||
|
minimumRestPeriodMinutes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
|
||||||
|
UUID sessionId,
|
||||||
|
String driverKey,
|
||||||
|
List<ResolvedActivityInterval> activityIntervals,
|
||||||
|
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
|
||||||
|
int significantDrivingMinutes,
|
||||||
|
int minimumRestPeriodMinutes
|
||||||
|
) {
|
||||||
|
if ((activityIntervals == null || activityIntervals.isEmpty())
|
||||||
|
&& (vehicleUsageIntervals == null || vehicleUsageIntervals.isEmpty())) {
|
||||||
|
return emptyBundle();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals = new ArrayList<>();
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals = new ArrayList<>();
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals = new ArrayList<>();
|
||||||
|
List<TachographEsperVuCardAbsentIntervalEvent> vuCardAbsentIntervals = new ArrayList<>();
|
||||||
|
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals = new ArrayList<>();
|
||||||
|
|
||||||
|
executeWithRuntime(
|
||||||
|
configuration -> {
|
||||||
|
configuration.getCommon().addEventType(
|
||||||
|
"TachographActivityIntervalInputEvent",
|
||||||
|
activityIntervalInputDefinition()
|
||||||
|
);
|
||||||
|
configuration.getCommon().addEventType(
|
||||||
|
"TachographVehicleUsageIntervalInputEvent",
|
||||||
|
vehicleUsageIntervalInputDefinition()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
renderDrivingDerivedProjectionBundleEpl(significantDrivingMinutes, minimumRestPeriodMinutes),
|
||||||
|
Map.of(
|
||||||
|
"drivingInterruptionIntervals", newData -> collectDrivingInterruptionIntervalEvents(newData, drivingInterruptionIntervals),
|
||||||
|
"dailyWeeklyRestCandidateIntervals", newData -> collectDrivingInterruptionIntervalEvents(newData, dailyWeeklyRestCandidateIntervals),
|
||||||
|
"drivingInterruptionVehicleChangeIntervals", newData -> collectDrivingInterruptionIntervalEvents(newData, drivingInterruptionVehicleChangeIntervals),
|
||||||
|
"vuCardAbsentIntervals", newData -> collectVuCardAbsentIntervalEvents(newData, vuCardAbsentIntervals),
|
||||||
|
"potentialHomeOvernightStayIntervals", newData -> collectPotentialHomeOvernightStayIntervalEvents(newData, potentialHomeOvernightStayIntervals)
|
||||||
|
),
|
||||||
|
runtime -> {
|
||||||
|
if (vehicleUsageIntervals != null) {
|
||||||
|
for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) {
|
||||||
|
runtime.getEventService().sendEventMap(
|
||||||
|
toVehicleUsageIntervalInputMap(interval),
|
||||||
|
"TachographVehicleUsageIntervalInputEvent"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (activityIntervals != null) {
|
||||||
|
for (ResolvedActivityInterval interval : activityIntervals) {
|
||||||
|
runtime.getEventService().sendEventMap(
|
||||||
|
toActivityIntervalInputMap(sessionId, driverKey, interval),
|
||||||
|
"TachographActivityIntervalInputEvent"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return new TachographEsperDrivingDerivedProjectionBundle(
|
||||||
|
sortDrivingInterruptionIntervals(drivingInterruptionIntervals),
|
||||||
|
sortDrivingInterruptionIntervals(dailyWeeklyRestCandidateIntervals),
|
||||||
|
sortDrivingInterruptionIntervals(drivingInterruptionVehicleChangeIntervals),
|
||||||
|
sortVuCardAbsentIntervals(vuCardAbsentIntervals),
|
||||||
|
sortPotentialHomeOvernightStayIntervals(potentialHomeOvernightStayIntervals)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TachographEsperDrivingDerivedProjectionBundle emptyBundle() {
|
||||||
|
return new TachographEsperDrivingDerivedProjectionBundle(
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeWithRuntime(
|
||||||
|
Consumer<Configuration> configurationSetup,
|
||||||
|
String epl,
|
||||||
|
Map<String, Consumer<EventBean[]>> listeners,
|
||||||
|
Consumer<EPRuntime> sender
|
||||||
|
) {
|
||||||
|
EPRuntime runtime = null;
|
||||||
|
try {
|
||||||
|
Configuration configuration = new Configuration();
|
||||||
|
configurationSetup.accept(configuration);
|
||||||
|
String runtimeUri = "eventhub-tachograph-reusable-projection-" + RUNTIME_COUNTER.incrementAndGet();
|
||||||
|
runtime = EPRuntimeProvider.getRuntime(runtimeUri, configuration);
|
||||||
|
|
||||||
|
CompilerArguments arguments = new CompilerArguments(configuration);
|
||||||
|
EPCompiled compiled = EPCompilerProvider.getCompiler().compile(epl, arguments);
|
||||||
|
EPDeployment deployment = runtime.getDeploymentService().deploy(compiled);
|
||||||
|
for (Map.Entry<String, Consumer<EventBean[]>> entry : listeners.entrySet()) {
|
||||||
|
runtime.getDeploymentService()
|
||||||
|
.getStatement(deployment.getDeploymentId(), entry.getKey())
|
||||||
|
.addListener((newData, oldData, statement, rt) -> entry.getValue().accept(newData));
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.accept(runtime);
|
||||||
|
} catch (EPCompileException | EPDeployException e) {
|
||||||
|
throw new IllegalStateException("Cannot compile/deploy reusable tachograph projection EPL bundle", e);
|
||||||
|
} finally {
|
||||||
|
if (runtime != null) {
|
||||||
|
runtime.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> activityIntervalInputDefinition() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> vehicleUsageIntervalInputDefinition() {
|
||||||
|
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 Map<String, Object> toActivityIntervalInputMap(
|
||||||
|
UUID sessionId,
|
||||||
|
String driverKey,
|
||||||
|
ResolvedActivityInterval interval
|
||||||
|
) {
|
||||||
|
Map<String, Object> event = new LinkedHashMap<>();
|
||||||
|
event.put("sessionId", sessionId);
|
||||||
|
event.put("driverKey", driverKey);
|
||||||
|
event.put("intervalId", interval.intervalId());
|
||||||
|
event.put("activityType", interval.activityType());
|
||||||
|
event.put("cardSlot", interval.slot());
|
||||||
|
event.put("cardStatus", interval.cardStatus());
|
||||||
|
event.put("drivingStatus", interval.drivingStatus());
|
||||||
|
event.put("registrationKey", interval.registrationKey());
|
||||||
|
event.put("vehicleKey", interval.vehicleKey());
|
||||||
|
event.put("sourceKind", interval.sourceKind());
|
||||||
|
event.put("firstSourceIntervalId", firstSourceIntervalId(interval));
|
||||||
|
event.put("lastSourceIntervalId", lastSourceIntervalId(interval));
|
||||||
|
event.put("startedAt", interval.from());
|
||||||
|
event.put("endedAt", interval.to());
|
||||||
|
event.put("startedAtEpochSecond", interval.from().toEpochSecond());
|
||||||
|
event.put("endedAtEpochSecond", interval.to().toEpochSecond());
|
||||||
|
event.put("durationSeconds", interval.durationSeconds());
|
||||||
|
event.put("sourceIntervalIds", interval.sourceIntervalIds());
|
||||||
|
event.put("synthetic", interval.synthetic());
|
||||||
|
event.put("clippedToRequestedPeriod", interval.clippedToRequestedPeriod());
|
||||||
|
event.put("level", interval.level());
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> toVehicleUsageIntervalInputMap(ResolvedVehicleUsageInterval interval) {
|
||||||
|
Map<String, Object> event = new LinkedHashMap<>();
|
||||||
|
event.put("sessionId", interval.sessionId());
|
||||||
|
event.put("driverKey", interval.driverKey());
|
||||||
|
event.put("intervalId", interval.intervalId());
|
||||||
|
event.put("firstSourceIntervalId", firstSourceIntervalId(interval));
|
||||||
|
event.put("lastSourceIntervalId", lastSourceIntervalId(interval));
|
||||||
|
event.put("startedAt", interval.from());
|
||||||
|
event.put("endedAt", interval.to());
|
||||||
|
event.put("startedAtEpochSecond", interval.from().toEpochSecond());
|
||||||
|
event.put("endedAtEpochSecond", interval.to() == null ? null : interval.to().toEpochSecond());
|
||||||
|
event.put("durationSeconds", interval.durationSeconds());
|
||||||
|
event.put("odometerBeginKm", interval.odometerBeginKm());
|
||||||
|
event.put("odometerEndKm", interval.odometerEndKm());
|
||||||
|
event.put("registrationKey", interval.registrationKey());
|
||||||
|
event.put("vehicleKey", interval.vehicleKey());
|
||||||
|
event.put("sourceKind", interval.sourceKind());
|
||||||
|
event.put("sourceIntervalIds", interval.sourceIntervalIds());
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstSourceIntervalId(ResolvedActivityInterval interval) {
|
||||||
|
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String lastSourceIntervalId(ResolvedActivityInterval interval) {
|
||||||
|
return interval.sourceIntervalIds().isEmpty()
|
||||||
|
? interval.intervalId()
|
||||||
|
: interval.sourceIntervalIds().get(interval.sourceIntervalIds().size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstSourceIntervalId(ResolvedVehicleUsageInterval interval) {
|
||||||
|
return interval.sourceIntervalIds().isEmpty() ? interval.intervalId() : interval.sourceIntervalIds().get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String lastSourceIntervalId(ResolvedVehicleUsageInterval interval) {
|
||||||
|
return interval.sourceIntervalIds().isEmpty()
|
||||||
|
? interval.intervalId()
|
||||||
|
: interval.sourceIntervalIds().get(interval.sourceIntervalIds().size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectDrivingInterruptionIntervalEvents(
|
||||||
|
EventBean[] newData,
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> target
|
||||||
|
) {
|
||||||
|
if (newData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (EventBean event : newData) {
|
||||||
|
long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond");
|
||||||
|
long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond");
|
||||||
|
target.add(new TachographEsperDrivingInterruptionIntervalEvent(
|
||||||
|
(UUID) event.get("sessionId"),
|
||||||
|
(String) event.get("driverKey"),
|
||||||
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
||||||
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
||||||
|
(Long) event.get("durationSeconds"),
|
||||||
|
(String) event.get("previousDrivingSourceIntervalId"),
|
||||||
|
(String) event.get("nextDrivingSourceIntervalId"),
|
||||||
|
(String) event.get("previousRegistrationKey"),
|
||||||
|
(String) event.get("nextRegistrationKey"),
|
||||||
|
(String) event.get("previousVehicleKey"),
|
||||||
|
(String) event.get("nextVehicleKey")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectVuCardAbsentIntervalEvents(
|
||||||
|
EventBean[] newData,
|
||||||
|
List<TachographEsperVuCardAbsentIntervalEvent> target
|
||||||
|
) {
|
||||||
|
if (newData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (EventBean event : newData) {
|
||||||
|
long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond");
|
||||||
|
long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond");
|
||||||
|
target.add(new TachographEsperVuCardAbsentIntervalEvent(
|
||||||
|
(UUID) event.get("sessionId"),
|
||||||
|
(String) event.get("driverKey"),
|
||||||
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
||||||
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
||||||
|
(Long) event.get("durationSeconds"),
|
||||||
|
(String) event.get("previousUsageIntervalId"),
|
||||||
|
(String) event.get("nextUsageIntervalId"),
|
||||||
|
(String) event.get("previousRegistrationKey"),
|
||||||
|
(String) event.get("nextRegistrationKey"),
|
||||||
|
(String) event.get("previousVehicleKey"),
|
||||||
|
(String) event.get("nextVehicleKey")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectPotentialHomeOvernightStayIntervalEvents(
|
||||||
|
EventBean[] newData,
|
||||||
|
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> target
|
||||||
|
) {
|
||||||
|
if (newData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (EventBean event : newData) {
|
||||||
|
long startedAtEpochSecond = (Long) event.get("startedAtEpochSecond");
|
||||||
|
long endedAtEpochSecond = (Long) event.get("endedAtEpochSecond");
|
||||||
|
target.add(new TachographEsperPotentialHomeOvernightStayIntervalEvent(
|
||||||
|
(UUID) event.get("sessionId"),
|
||||||
|
(String) event.get("driverKey"),
|
||||||
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(startedAtEpochSecond), ZoneOffset.UTC),
|
||||||
|
OffsetDateTime.ofInstant(Instant.ofEpochSecond(endedAtEpochSecond), ZoneOffset.UTC),
|
||||||
|
(Long) event.get("durationSeconds"),
|
||||||
|
(Long) event.get("unknownDurationSeconds"),
|
||||||
|
(Double) event.get("unknownCoveragePercent"),
|
||||||
|
(String) event.get("previousDrivingSourceIntervalId"),
|
||||||
|
(String) event.get("nextDrivingSourceIntervalId"),
|
||||||
|
(String) event.get("previousRegistrationKey"),
|
||||||
|
(String) event.get("nextRegistrationKey"),
|
||||||
|
(String) event.get("previousVehicleKey"),
|
||||||
|
(String) event.get("nextVehicleKey")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TachographEsperDrivingInterruptionIntervalEvent> sortDrivingInterruptionIntervals(
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> intervals
|
||||||
|
) {
|
||||||
|
return intervals.stream()
|
||||||
|
.sorted(Comparator.comparing(TachographEsperDrivingInterruptionIntervalEvent::startedAt)
|
||||||
|
.thenComparing(TachographEsperDrivingInterruptionIntervalEvent::endedAt))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TachographEsperVuCardAbsentIntervalEvent> sortVuCardAbsentIntervals(
|
||||||
|
List<TachographEsperVuCardAbsentIntervalEvent> intervals
|
||||||
|
) {
|
||||||
|
return intervals.stream()
|
||||||
|
.sorted(Comparator.comparing(TachographEsperVuCardAbsentIntervalEvent::startedAt)
|
||||||
|
.thenComparing(TachographEsperVuCardAbsentIntervalEvent::endedAt))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TachographEsperPotentialHomeOvernightStayIntervalEvent> sortPotentialHomeOvernightStayIntervals(
|
||||||
|
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> intervals
|
||||||
|
) {
|
||||||
|
return intervals.stream()
|
||||||
|
.sorted(Comparator.comparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::startedAt)
|
||||||
|
.thenComparing(TachographEsperPotentialHomeOvernightStayIntervalEvent::endedAt))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String renderDrivingDerivedProjectionBundleEpl(int significantDrivingMinutes, int minimumRestPeriodMinutes) {
|
||||||
|
return DRIVING_DERIVED_PROJECTION_BUNDLE_EPL_TEMPLATE
|
||||||
|
.replace(
|
||||||
|
"${SIGNIFICANT_DRIVING_THRESHOLD_SECONDS}",
|
||||||
|
Long.toString(Math.max(1, significantDrivingMinutes) * 60L)
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"${MINIMUM_REST_PERIOD_THRESHOLD_SECONDS}",
|
||||||
|
Long.toString(Math.max(1, minimumRestPeriodMinutes) * 60L)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,615 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.CardSlot;
|
||||||
|
import at.procon.eventhub.dto.CardStatus;
|
||||||
|
import at.procon.eventhub.dto.DriverCardRefDto;
|
||||||
|
import at.procon.eventhub.dto.DriverRefDto;
|
||||||
|
import at.procon.eventhub.dto.DrivingStatus;
|
||||||
|
import at.procon.eventhub.dto.EventDetailsDto;
|
||||||
|
import at.procon.eventhub.dto.EventDomain;
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.dto.EventHubPackageRequest;
|
||||||
|
import at.procon.eventhub.dto.EventLifecycle;
|
||||||
|
import at.procon.eventhub.dto.EventSourceDto;
|
||||||
|
import at.procon.eventhub.dto.EventType;
|
||||||
|
import at.procon.eventhub.dto.GeoPointDto;
|
||||||
|
import at.procon.eventhub.dto.ImportScopeDto;
|
||||||
|
import at.procon.eventhub.dto.SourcePackageRefDto;
|
||||||
|
import at.procon.eventhub.dto.VehicleRefDto;
|
||||||
|
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
|
||||||
|
import at.procon.eventhub.service.EventDetailsFactory;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriverCard;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicle;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicleRegistration;
|
||||||
|
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.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class IntervalBackedDriverTimelineEventBuilder implements DriverTimelineEventBuilder {
|
||||||
|
|
||||||
|
private final DriverTimelineBuilder driverTimelineBuilder;
|
||||||
|
private final DriverKeyFactory driverKeyFactory;
|
||||||
|
private final VehicleKeyFactory vehicleKeyFactory;
|
||||||
|
private final EventDetailsFactory detailsFactory;
|
||||||
|
|
||||||
|
public IntervalBackedDriverTimelineEventBuilder(
|
||||||
|
DriverTimelineBuilder driverTimelineBuilder,
|
||||||
|
DriverKeyFactory driverKeyFactory,
|
||||||
|
VehicleKeyFactory vehicleKeyFactory,
|
||||||
|
EventDetailsFactory detailsFactory
|
||||||
|
) {
|
||||||
|
this.driverTimelineBuilder = driverTimelineBuilder;
|
||||||
|
this.driverKeyFactory = driverKeyFactory;
|
||||||
|
this.vehicleKeyFactory = vehicleKeyFactory;
|
||||||
|
this.detailsFactory = detailsFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TachographTimelineEventBundle buildEventBundle(
|
||||||
|
TachographFileSession session,
|
||||||
|
DriverExtractionSession driverSession
|
||||||
|
) {
|
||||||
|
ResolvedDriverTimeline timeline = driverTimelineBuilder.build(session, driverSession);
|
||||||
|
return buildEventBundle(session, driverSession, timeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TachographTimelineEventBundle buildEventBundle(
|
||||||
|
TachographFileSession session,
|
||||||
|
DriverExtractionSession driverSession,
|
||||||
|
ResolvedDriverTimeline timeline
|
||||||
|
) {
|
||||||
|
if (session == null || driverSession == null || timeline == null) {
|
||||||
|
return new TachographTimelineEventBundle(List.of(), List.of(), List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, ExtractedVehicleRegistration> registrationsByKey = new LinkedHashMap<>();
|
||||||
|
for (ExtractedVehicleRegistration registration : driverSession.vehicleRegistrations()) {
|
||||||
|
registrationsByKey.put(registration.registrationKey(), registration);
|
||||||
|
}
|
||||||
|
Map<String, ExtractedVehicle> vehiclesByKey = new LinkedHashMap<>();
|
||||||
|
for (ExtractedVehicle vehicle : driverSession.vehicles()) {
|
||||||
|
vehiclesByKey.put(vehicle.vehicleKey(), vehicle);
|
||||||
|
}
|
||||||
|
|
||||||
|
DriverRefDto driverRef = driverRef(driverSession);
|
||||||
|
EventSourceDto eventSource = eventSource(session, timeline);
|
||||||
|
SourcePackageRefDto sourcePackageRef = sourcePackageRef(session, timeline);
|
||||||
|
|
||||||
|
List<EventHubEventDto> activityEvents = buildActivityEvents(
|
||||||
|
session,
|
||||||
|
timeline.activityIntervals(),
|
||||||
|
driverRef,
|
||||||
|
registrationsByKey,
|
||||||
|
vehiclesByKey,
|
||||||
|
eventSource,
|
||||||
|
sourcePackageRef
|
||||||
|
);
|
||||||
|
List<EventHubEventDto> vehicleUsageEvents = buildVehicleUsageEvents(
|
||||||
|
session,
|
||||||
|
timeline.vehicleUsageIntervals(),
|
||||||
|
driverRef,
|
||||||
|
registrationsByKey,
|
||||||
|
vehiclesByKey,
|
||||||
|
eventSource,
|
||||||
|
sourcePackageRef
|
||||||
|
);
|
||||||
|
List<EventHubEventDto> supportEvents = buildSupportEvents(
|
||||||
|
session,
|
||||||
|
timeline.supportEvents(),
|
||||||
|
driverRef,
|
||||||
|
registrationsByKey,
|
||||||
|
vehiclesByKey,
|
||||||
|
eventSource,
|
||||||
|
sourcePackageRef
|
||||||
|
);
|
||||||
|
return new TachographTimelineEventBundle(activityEvents, vehicleUsageEvents, supportEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EventHubEventDto> buildActivityEvents(
|
||||||
|
TachographFileSession session,
|
||||||
|
List<ResolvedActivityInterval> intervals,
|
||||||
|
DriverRefDto driverRef,
|
||||||
|
Map<String, ExtractedVehicleRegistration> registrationsByKey,
|
||||||
|
Map<String, ExtractedVehicle> vehiclesByKey,
|
||||||
|
EventSourceDto eventSource,
|
||||||
|
SourcePackageRefDto sourcePackageRef
|
||||||
|
) {
|
||||||
|
if (intervals == null || intervals.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
List<EventHubEventDto> events = new ArrayList<>(intervals.size() * 2);
|
||||||
|
for (ResolvedActivityInterval interval : intervals) {
|
||||||
|
VehicleRefDto vehicleRef = vehicleRef(interval.registrationKey(), interval.vehicleKey(), registrationsByKey, vehiclesByKey);
|
||||||
|
EventType eventType = activityEventType(interval.activityType());
|
||||||
|
EventDetailsDto details = detailsFactory.driverActivity(
|
||||||
|
cardSlot(interval.slot()),
|
||||||
|
cardStatus(interval.cardStatus()),
|
||||||
|
drivingStatus(interval.drivingStatus())
|
||||||
|
);
|
||||||
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
|
raw.put("intervalId", interval.intervalId());
|
||||||
|
raw.put("sourceRowId", interval.intervalId());
|
||||||
|
raw.put("sourceRowIds", interval.sourceIntervalIds());
|
||||||
|
raw.put("startedAt", timeText(interval.from()));
|
||||||
|
raw.put("endedAt", timeText(interval.to()));
|
||||||
|
raw.put("durationSeconds", interval.durationSeconds());
|
||||||
|
raw.put("sourceKind", interval.sourceKind());
|
||||||
|
raw.put("registrationKey", interval.registrationKey());
|
||||||
|
raw.put("vehicleKey", interval.vehicleKey());
|
||||||
|
raw.put("synthetic", interval.synthetic());
|
||||||
|
raw.put("clippedToRequestedPeriod", interval.clippedToRequestedPeriod());
|
||||||
|
raw.put("level", interval.level());
|
||||||
|
|
||||||
|
events.add(event(
|
||||||
|
session,
|
||||||
|
interval.from(),
|
||||||
|
EventDomain.DRIVER_ACTIVITY,
|
||||||
|
eventType,
|
||||||
|
EventLifecycle.START,
|
||||||
|
eventSource,
|
||||||
|
driverRef,
|
||||||
|
vehicleRef,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
details,
|
||||||
|
sourcePackageRef,
|
||||||
|
raw,
|
||||||
|
isManualEntry(interval.cardStatus(), interval.drivingStatus()),
|
||||||
|
"ACTIVITY",
|
||||||
|
interval.intervalId()
|
||||||
|
));
|
||||||
|
events.add(event(
|
||||||
|
session,
|
||||||
|
interval.to(),
|
||||||
|
EventDomain.DRIVER_ACTIVITY,
|
||||||
|
eventType,
|
||||||
|
EventLifecycle.END,
|
||||||
|
eventSource,
|
||||||
|
driverRef,
|
||||||
|
vehicleRef,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
details,
|
||||||
|
sourcePackageRef,
|
||||||
|
raw,
|
||||||
|
isManualEntry(interval.cardStatus(), interval.drivingStatus()),
|
||||||
|
"ACTIVITY",
|
||||||
|
interval.intervalId()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return List.copyOf(events);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EventHubEventDto> buildVehicleUsageEvents(
|
||||||
|
TachographFileSession session,
|
||||||
|
List<ResolvedVehicleUsageInterval> intervals,
|
||||||
|
DriverRefDto driverRef,
|
||||||
|
Map<String, ExtractedVehicleRegistration> registrationsByKey,
|
||||||
|
Map<String, ExtractedVehicle> vehiclesByKey,
|
||||||
|
EventSourceDto eventSource,
|
||||||
|
SourcePackageRefDto sourcePackageRef
|
||||||
|
) {
|
||||||
|
if (intervals == null || intervals.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
List<EventHubEventDto> events = new ArrayList<>(intervals.size() * 2);
|
||||||
|
for (ResolvedVehicleUsageInterval interval : intervals) {
|
||||||
|
VehicleRefDto vehicleRef = vehicleRef(interval.registrationKey(), interval.vehicleKey(), registrationsByKey, vehiclesByKey);
|
||||||
|
EventDetailsDto insertDetails = detailsFactory.driverCard(
|
||||||
|
null,
|
||||||
|
CardStatus.INSERTED,
|
||||||
|
driverRef == null ? null : driverRef.driverCard()
|
||||||
|
);
|
||||||
|
EventDetailsDto withdrawDetails = detailsFactory.driverCard(
|
||||||
|
null,
|
||||||
|
CardStatus.NOT_INSERTED,
|
||||||
|
driverRef == null ? null : driverRef.driverCard()
|
||||||
|
);
|
||||||
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
|
raw.put("intervalId", interval.intervalId());
|
||||||
|
raw.put("sourceRowId", interval.intervalId());
|
||||||
|
raw.put("sourceRowIds", interval.sourceIntervalIds());
|
||||||
|
raw.put("startedAt", timeText(interval.from()));
|
||||||
|
raw.put("endedAt", timeText(interval.to()));
|
||||||
|
raw.put("durationSeconds", interval.durationSeconds());
|
||||||
|
raw.put("registrationKey", interval.registrationKey());
|
||||||
|
raw.put("vehicleKey", interval.vehicleKey());
|
||||||
|
raw.put("sourceKind", interval.sourceKind());
|
||||||
|
raw.put("odometerBeginKm", interval.odometerBeginKm());
|
||||||
|
raw.put("odometerEndKm", interval.odometerEndKm());
|
||||||
|
|
||||||
|
events.add(event(
|
||||||
|
session,
|
||||||
|
interval.from(),
|
||||||
|
EventDomain.DRIVER_CARD,
|
||||||
|
EventType.CARD_INSERTED,
|
||||||
|
EventLifecycle.INSERT,
|
||||||
|
eventSource,
|
||||||
|
driverRef,
|
||||||
|
vehicleRef,
|
||||||
|
odometerMeters(interval.odometerBeginKm()),
|
||||||
|
null,
|
||||||
|
insertDetails,
|
||||||
|
sourcePackageRef,
|
||||||
|
raw,
|
||||||
|
false,
|
||||||
|
"VEHICLE_USAGE",
|
||||||
|
interval.intervalId()
|
||||||
|
));
|
||||||
|
if (interval.to() != null) {
|
||||||
|
events.add(event(
|
||||||
|
session,
|
||||||
|
interval.to(),
|
||||||
|
EventDomain.DRIVER_CARD,
|
||||||
|
EventType.CARD_WITHDRAWN,
|
||||||
|
EventLifecycle.WITHDRAW,
|
||||||
|
eventSource,
|
||||||
|
driverRef,
|
||||||
|
vehicleRef,
|
||||||
|
odometerMeters(interval.odometerEndKm()),
|
||||||
|
null,
|
||||||
|
withdrawDetails,
|
||||||
|
sourcePackageRef,
|
||||||
|
raw,
|
||||||
|
false,
|
||||||
|
"VEHICLE_USAGE",
|
||||||
|
interval.intervalId()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.copyOf(events);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EventHubEventDto> buildSupportEvents(
|
||||||
|
TachographFileSession session,
|
||||||
|
List<ExtractedSupportEvent> supportEvents,
|
||||||
|
DriverRefDto driverRef,
|
||||||
|
Map<String, ExtractedVehicleRegistration> registrationsByKey,
|
||||||
|
Map<String, ExtractedVehicle> vehiclesByKey,
|
||||||
|
EventSourceDto eventSource,
|
||||||
|
SourcePackageRefDto sourcePackageRef
|
||||||
|
) {
|
||||||
|
if (supportEvents == null || supportEvents.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
List<EventHubEventDto> events = new ArrayList<>(supportEvents.size());
|
||||||
|
for (ExtractedSupportEvent supportEvent : supportEvents) {
|
||||||
|
EventDomain eventDomain = supportEventDomain(supportEvent.eventDomain());
|
||||||
|
EventType eventType = supportEventType(eventDomain, supportEvent.eventType(), supportEvent.code());
|
||||||
|
EventLifecycle lifecycle = supportEventLifecycle(eventDomain, supportEvent.eventType());
|
||||||
|
boolean manualEntry = isManualPlaceEvent(supportEvent.eventType());
|
||||||
|
VehicleRefDto vehicleRef = vehicleRef(
|
||||||
|
supportEvent.registrationKey(),
|
||||||
|
supportEvent.vehicleKey(),
|
||||||
|
registrationsByKey,
|
||||||
|
vehiclesByKey
|
||||||
|
);
|
||||||
|
EventDetailsDto details = supportDetails(eventDomain, supportEvent);
|
||||||
|
Map<String, Object> raw = new LinkedHashMap<>();
|
||||||
|
raw.put("sourceRowId", supportEvent.eventId());
|
||||||
|
raw.put("supportEventId", supportEvent.eventId());
|
||||||
|
raw.put("supportEventType", supportEvent.eventType());
|
||||||
|
raw.put("slot", supportEvent.slot());
|
||||||
|
raw.put("registrationKey", supportEvent.registrationKey());
|
||||||
|
raw.put("vehicleKey", supportEvent.vehicleKey());
|
||||||
|
raw.put("country", supportEvent.country());
|
||||||
|
raw.put("region", supportEvent.region());
|
||||||
|
raw.put("authenticationStatus", supportEvent.authenticationStatus());
|
||||||
|
raw.put("odometerKm", supportEvent.odometerKm());
|
||||||
|
raw.put("code", supportEvent.code());
|
||||||
|
raw.put("rawRecordPath", supportEvent.rawRecordPath());
|
||||||
|
|
||||||
|
events.add(event(
|
||||||
|
session,
|
||||||
|
supportEvent.occurredAt(),
|
||||||
|
eventDomain,
|
||||||
|
eventType,
|
||||||
|
lifecycle,
|
||||||
|
eventSource,
|
||||||
|
driverRef,
|
||||||
|
vehicleRef,
|
||||||
|
odometerMeters(supportEvent.odometerKm()),
|
||||||
|
position(supportEvent.latitude(), supportEvent.longitude()),
|
||||||
|
details,
|
||||||
|
sourcePackageRef,
|
||||||
|
raw,
|
||||||
|
manualEntry,
|
||||||
|
"SUPPORT",
|
||||||
|
supportEvent.eventId()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return List.copyOf(events);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventHubEventDto event(
|
||||||
|
TachographFileSession session,
|
||||||
|
OffsetDateTime occurredAt,
|
||||||
|
EventDomain eventDomain,
|
||||||
|
EventType eventType,
|
||||||
|
EventLifecycle lifecycle,
|
||||||
|
EventSourceDto eventSource,
|
||||||
|
DriverRefDto driverRef,
|
||||||
|
VehicleRefDto vehicleRef,
|
||||||
|
Long odometerM,
|
||||||
|
GeoPointDto position,
|
||||||
|
EventDetailsDto details,
|
||||||
|
SourcePackageRefDto sourcePackageRef,
|
||||||
|
Map<String, Object> rawPayload,
|
||||||
|
boolean manualEntry,
|
||||||
|
String group,
|
||||||
|
String sourceId
|
||||||
|
) {
|
||||||
|
return new EventHubEventDto(
|
||||||
|
UUID.randomUUID(),
|
||||||
|
externalSourceEventId(session.sessionId(), group, sourceId, lifecycle, occurredAt),
|
||||||
|
driverRef,
|
||||||
|
vehicleRef,
|
||||||
|
occurredAt,
|
||||||
|
null,
|
||||||
|
OffsetDateTime.now(),
|
||||||
|
eventDomain,
|
||||||
|
eventType,
|
||||||
|
lifecycle,
|
||||||
|
odometerM,
|
||||||
|
position,
|
||||||
|
details,
|
||||||
|
sourcePackageRef,
|
||||||
|
detailsFactory.payloadFromMap(Map.of("raw", rawPayload)),
|
||||||
|
manualEntry,
|
||||||
|
packageInfo(session, eventSource, eventDomain, occurredAt)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DriverRefDto driverRef(DriverExtractionSession driverSession) {
|
||||||
|
ExtractedDriver driver = driverSession.driver();
|
||||||
|
ExtractedDriverCard card = driverSession.driverCard();
|
||||||
|
DriverCardRefDto driverCardRef = card == null || card.cardNumber() == null
|
||||||
|
? driverCardFromKey(driverSession.driverKey())
|
||||||
|
: new DriverCardRefDto(card.cardNation(), card.cardNumber());
|
||||||
|
String sourceDriverId = driver == null || driver.sourceDriverId() == null
|
||||||
|
? driverKeyFactory.createSourceDriverId(driverSession.driverKey())
|
||||||
|
: driver.sourceDriverId();
|
||||||
|
return new DriverRefDto(sourceDriverId, driverCardRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DriverCardRefDto driverCardFromKey(String driverKey) {
|
||||||
|
if (driverKey == null || driverKey.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int separator = driverKey.indexOf(':');
|
||||||
|
if (separator < 0) {
|
||||||
|
return new DriverCardRefDto(null, driverKey);
|
||||||
|
}
|
||||||
|
return new DriverCardRefDto(driverKey.substring(0, separator), driverKey.substring(separator + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private VehicleRefDto vehicleRef(
|
||||||
|
String registrationKey,
|
||||||
|
String vehicleKey,
|
||||||
|
Map<String, ExtractedVehicleRegistration> registrationsByKey,
|
||||||
|
Map<String, ExtractedVehicle> vehiclesByKey
|
||||||
|
) {
|
||||||
|
ExtractedVehicleRegistration registration = registrationKey == null ? null : registrationsByKey.get(registrationKey);
|
||||||
|
ExtractedVehicle vehicle = vehicleKey == null ? null : vehiclesByKey.get(vehicleKey);
|
||||||
|
VehicleRegistrationRefDto registrationRef = registration != null
|
||||||
|
? new VehicleRegistrationRefDto(registration.registrationNation(), registration.registrationNumber())
|
||||||
|
: registrationFromKey(registrationKey);
|
||||||
|
VehicleRefDto vehicleRef = new VehicleRefDto(
|
||||||
|
vehicle != null ? vehicle.sourceVehicleId() : vehicleKeyFactory.createSourceVehicleId(vehicleKey),
|
||||||
|
vehicle != null ? vehicle.vin() : vehicleKey,
|
||||||
|
registration != null ? registration.sourceVehicleRegistrationId() : vehicleKeyFactory.createSourceVehicleRegistrationId(registrationKey),
|
||||||
|
registrationRef
|
||||||
|
);
|
||||||
|
return vehicleRef.hasAnyReference() ? vehicleRef : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VehicleRegistrationRefDto registrationFromKey(String registrationKey) {
|
||||||
|
if (registrationKey == null || registrationKey.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int separator = registrationKey.indexOf(':');
|
||||||
|
if (separator < 0) {
|
||||||
|
return new VehicleRegistrationRefDto(null, registrationKey);
|
||||||
|
}
|
||||||
|
return new VehicleRegistrationRefDto(registrationKey.substring(0, separator), registrationKey.substring(separator + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventSourceDto eventSource(TachographFileSession session, ResolvedDriverTimeline timeline) {
|
||||||
|
String sourceKind = timeline.sourceKind() == null || timeline.sourceKind().isBlank()
|
||||||
|
? (session.metadata().driverCardFile() ? "DRIVER_CARD" : "VEHICLE_UNIT")
|
||||||
|
: timeline.sourceKind();
|
||||||
|
return new EventSourceDto(
|
||||||
|
"TACHOGRAPH",
|
||||||
|
sourceKind,
|
||||||
|
"TACHOGRAPH_" + sourceKind,
|
||||||
|
session.metadata().sourceInstanceKey(),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SourcePackageRefDto sourcePackageRef(TachographFileSession session, ResolvedDriverTimeline timeline) {
|
||||||
|
return new SourcePackageRefDto(
|
||||||
|
"TACHOGRAPH_FILE_SESSION",
|
||||||
|
session.sessionId().toString(),
|
||||||
|
session.metadata().uploadedFileSha256(),
|
||||||
|
timeline.loadedFrom(),
|
||||||
|
timeline.loadedTo(),
|
||||||
|
session.createdAt().atOffset(ZoneOffset.UTC)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventHubPackageRequest packageInfo(
|
||||||
|
TachographFileSession session,
|
||||||
|
EventSourceDto eventSource,
|
||||||
|
EventDomain eventDomain,
|
||||||
|
OffsetDateTime occurredAt
|
||||||
|
) {
|
||||||
|
LocalDate businessDate = occurredAt == null ? null : occurredAt.toLocalDate();
|
||||||
|
return new EventHubPackageRequest(
|
||||||
|
tenantOrDefault(session.metadata().tenantKey()),
|
||||||
|
eventSource,
|
||||||
|
null,
|
||||||
|
ImportScopeDto.tenantAll(null, null),
|
||||||
|
eventDomain.name(),
|
||||||
|
businessDate,
|
||||||
|
"TACHOGRAPH_FILE_SESSION:" + session.sessionId() + ":" + eventDomain.name() + ":" + businessDate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventType activityEventType(String activityType) {
|
||||||
|
if (activityType == null) {
|
||||||
|
return EventType.UNKNOWN_ACTIVITY;
|
||||||
|
}
|
||||||
|
return switch (activityType) {
|
||||||
|
case "DRIVE" -> EventType.DRIVE;
|
||||||
|
case "WORK" -> EventType.WORK;
|
||||||
|
case "AVAILABILITY" -> EventType.AVAILABILITY;
|
||||||
|
case "BREAK_REST" -> EventType.BREAK_REST;
|
||||||
|
default -> EventType.UNKNOWN_ACTIVITY;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private CardSlot cardSlot(String value) {
|
||||||
|
return parseEnum(CardSlot.class, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CardStatus cardStatus(String value) {
|
||||||
|
return parseEnum(CardStatus.class, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrivingStatus drivingStatus(String value) {
|
||||||
|
return parseEnum(DrivingStatus.class, value, DrivingStatus.UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isManualEntry(String cardStatus, String drivingStatus) {
|
||||||
|
return Objects.equals("NOT_INSERTED", normalizeToken(cardStatus))
|
||||||
|
&& Objects.equals("KNOWN", normalizeToken(drivingStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventDomain supportEventDomain(String value) {
|
||||||
|
return switch (normalizeToken(value)) {
|
||||||
|
case "PLACE" -> EventDomain.PLACE;
|
||||||
|
case "POSITION" -> EventDomain.POSITION;
|
||||||
|
case "SPECIFIC_CONDITION" -> EventDomain.SPECIFIC_CONDITION;
|
||||||
|
default -> EventDomain.TELEMATICS_DATA;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventType supportEventType(EventDomain eventDomain, String eventType, String code) {
|
||||||
|
return switch (eventDomain) {
|
||||||
|
case PLACE -> EventType.WORKING_DAY_PLACE_RECORDED;
|
||||||
|
case POSITION -> EventType.POSITION_RECORDED;
|
||||||
|
case SPECIFIC_CONDITION -> {
|
||||||
|
String normalizedCode = normalizeToken(code);
|
||||||
|
if (Objects.equals("FERRY_TRAIN", normalizedCode) || Objects.equals("FERRY_OR_TRAIN", normalizedCode)) {
|
||||||
|
yield EventType.FERRY_TRAIN;
|
||||||
|
}
|
||||||
|
if (Objects.equals("OUT_OF_SCOPE", normalizedCode) || Objects.equals("OUT", normalizedCode)) {
|
||||||
|
yield EventType.OUT;
|
||||||
|
}
|
||||||
|
yield EventType.UNKNOWN_EVENT;
|
||||||
|
}
|
||||||
|
default -> parseEnum(EventType.class, eventType, EventType.UNKNOWN_EVENT);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventLifecycle supportEventLifecycle(EventDomain eventDomain, String eventType) {
|
||||||
|
if (eventDomain == EventDomain.PLACE) {
|
||||||
|
String normalized = normalizeToken(eventType);
|
||||||
|
if (normalized != null && normalized.startsWith("BEGIN")) {
|
||||||
|
return EventLifecycle.BEGIN;
|
||||||
|
}
|
||||||
|
if (normalized != null && normalized.startsWith("END")) {
|
||||||
|
return EventLifecycle.END;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EventLifecycle.SNAPSHOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isManualPlaceEvent(String eventType) {
|
||||||
|
String normalized = normalizeToken(eventType);
|
||||||
|
return normalized != null && normalized.contains("MANUAL");
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventDetailsDto supportDetails(EventDomain eventDomain, ExtractedSupportEvent supportEvent) {
|
||||||
|
return switch (eventDomain) {
|
||||||
|
case PLACE -> detailsFactory.place(supportEvent.country(), supportEvent.region());
|
||||||
|
case POSITION -> detailsFactory.position(supportEvent.eventType());
|
||||||
|
case SPECIFIC_CONDITION -> detailsFactory.specificCondition();
|
||||||
|
default -> new EventDetailsDto("TACHOGRAPH_SUPPORT", detailsFactory.payloadFromMap(Map.of()));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private GeoPointDto position(BigDecimal latitude, BigDecimal longitude) {
|
||||||
|
return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long odometerMeters(Long kilometers) {
|
||||||
|
return kilometers == null ? null : kilometers * 1_000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String timeText(OffsetDateTime value) {
|
||||||
|
return value == null ? null : value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String externalSourceEventId(
|
||||||
|
UUID sessionId,
|
||||||
|
String group,
|
||||||
|
String sourceId,
|
||||||
|
EventLifecycle lifecycle,
|
||||||
|
OffsetDateTime occurredAt
|
||||||
|
) {
|
||||||
|
return "TACHOGRAPH_FILE_SESSION:"
|
||||||
|
+ sessionId
|
||||||
|
+ ":"
|
||||||
|
+ group
|
||||||
|
+ ":"
|
||||||
|
+ sourceId
|
||||||
|
+ ":"
|
||||||
|
+ lifecycle.name()
|
||||||
|
+ ":"
|
||||||
|
+ occurredAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String tenantOrDefault(String value) {
|
||||||
|
return value == null || value.isBlank() ? "default" : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeToken(String value) {
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value.trim().toUpperCase().replace('-', '_').replace(' ', '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends Enum<T>> T parseEnum(Class<T> type, String value, T fallback) {
|
||||||
|
String normalized = normalizeToken(value);
|
||||||
|
if (normalized == null) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Enum.valueOf(type, normalized);
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ import at.procon.eventhub.tachographfilesession.model.ProcessedShiftDrivingEvalu
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
|
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
|
import at.procon.eventhub.tachographfilesession.model.TachographEsperActivityIntervalEvent;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingDerivedProjectionBundle;
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingInterruptionIntervalEvent;
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
|
import at.procon.eventhub.tachographfilesession.model.TachographEsperPotentialHomeOvernightStayIntervalEvent;
|
||||||
|
|
@ -33,15 +34,18 @@ public class TachographFileSessionProcessingService {
|
||||||
|
|
||||||
private final TachographFileSessionRepository repository;
|
private final TachographFileSessionRepository repository;
|
||||||
private final DriverTimelineBuilder driverTimelineBuilder;
|
private final DriverTimelineBuilder driverTimelineBuilder;
|
||||||
|
private final DriverTimelineReusableProjectionBuilder reusableProjectionBuilder;
|
||||||
private final EventHubProperties properties;
|
private final EventHubProperties properties;
|
||||||
|
|
||||||
public TachographFileSessionProcessingService(
|
public TachographFileSessionProcessingService(
|
||||||
TachographFileSessionRepository repository,
|
TachographFileSessionRepository repository,
|
||||||
DriverTimelineBuilder driverTimelineBuilder,
|
DriverTimelineBuilder driverTimelineBuilder,
|
||||||
|
DriverTimelineReusableProjectionBuilder reusableProjectionBuilder,
|
||||||
EventHubProperties properties
|
EventHubProperties properties
|
||||||
) {
|
) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.driverTimelineBuilder = driverTimelineBuilder;
|
this.driverTimelineBuilder = driverTimelineBuilder;
|
||||||
|
this.reusableProjectionBuilder = reusableProjectionBuilder;
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,13 +165,16 @@ public class TachographFileSessionProcessingService {
|
||||||
requestedFrom,
|
requestedFrom,
|
||||||
requestedTo
|
requestedTo
|
||||||
);
|
);
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionIntervals =
|
TachographEsperDrivingDerivedProjectionBundle derivedProjectionBundle =
|
||||||
driverTimelineBuilder.buildEsperDrivingInterruptionIntervalEvents(
|
reusableProjectionBuilder.buildEsperDrivingDerivedProjectionBundle(
|
||||||
sessionId,
|
sessionId,
|
||||||
driverKey,
|
driverKey,
|
||||||
timeline,
|
timeline,
|
||||||
significantDrivingMinutes
|
significantDrivingMinutes,
|
||||||
|
minimumRestPeriodMinutes
|
||||||
);
|
);
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionIntervals =
|
||||||
|
derivedProjectionBundle.drivingInterruptionIntervals();
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals =
|
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals =
|
||||||
clipEsperDrivingInterruptionIntervalEvents(
|
clipEsperDrivingInterruptionIntervalEvents(
|
||||||
rawDrivingInterruptionIntervals,
|
rawDrivingInterruptionIntervals,
|
||||||
|
|
@ -175,10 +182,7 @@ public class TachographFileSessionProcessingService {
|
||||||
requestedTo
|
requestedTo
|
||||||
);
|
);
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> rawDailyWeeklyRestCandidateIntervals =
|
List<TachographEsperDrivingInterruptionIntervalEvent> rawDailyWeeklyRestCandidateIntervals =
|
||||||
driverTimelineBuilder.buildEsperDailyWeeklyRestCandidateIntervalEvents(
|
derivedProjectionBundle.dailyWeeklyRestCandidateIntervals();
|
||||||
rawDrivingInterruptionIntervals,
|
|
||||||
minimumRestPeriodMinutes
|
|
||||||
);
|
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals =
|
List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals =
|
||||||
clipEsperDrivingInterruptionIntervalEvents(
|
clipEsperDrivingInterruptionIntervalEvents(
|
||||||
rawDailyWeeklyRestCandidateIntervals,
|
rawDailyWeeklyRestCandidateIntervals,
|
||||||
|
|
@ -186,9 +190,7 @@ public class TachographFileSessionProcessingService {
|
||||||
requestedTo
|
requestedTo
|
||||||
);
|
);
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionVehicleChangeIntervals =
|
List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionVehicleChangeIntervals =
|
||||||
driverTimelineBuilder.buildEsperDrivingInterruptionVehicleChangeIntervalEvents(
|
derivedProjectionBundle.drivingInterruptionVehicleChangeIntervals();
|
||||||
rawDailyWeeklyRestCandidateIntervals
|
|
||||||
);
|
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals =
|
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals =
|
||||||
clipEsperDrivingInterruptionIntervalEvents(
|
clipEsperDrivingInterruptionIntervalEvents(
|
||||||
rawDrivingInterruptionVehicleChangeIntervals,
|
rawDrivingInterruptionVehicleChangeIntervals,
|
||||||
|
|
@ -196,13 +198,10 @@ public class TachographFileSessionProcessingService {
|
||||||
requestedTo
|
requestedTo
|
||||||
);
|
);
|
||||||
List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals =
|
List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals =
|
||||||
driverTimelineBuilder.buildEsperVuCardAbsentIntervalEvents(timeline);
|
derivedProjectionBundle.vuCardAbsentIntervals();
|
||||||
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals =
|
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals =
|
||||||
clipEsperPotentialHomeOvernightStayIntervalEvents(
|
clipEsperPotentialHomeOvernightStayIntervalEvents(
|
||||||
driverTimelineBuilder.buildEsperPotentialHomeOvernightStayIntervalEvents(
|
derivedProjectionBundle.potentialHomeOvernightStayIntervals(),
|
||||||
rawDrivingInterruptionVehicleChangeIntervals,
|
|
||||||
rawVuCardAbsentIntervals
|
|
||||||
),
|
|
||||||
rawVuCardAbsentIntervals,
|
rawVuCardAbsentIntervals,
|
||||||
requestedFrom,
|
requestedFrom,
|
||||||
requestedTo
|
requestedTo
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,253 @@
|
||||||
|
create schema SignificantDrivingInterval(
|
||||||
|
sessionId java.util.UUID,
|
||||||
|
driverKey string,
|
||||||
|
firstSourceIntervalId string,
|
||||||
|
lastSourceIntervalId string,
|
||||||
|
startedAtEpochSecond long,
|
||||||
|
endedAtEpochSecond long,
|
||||||
|
durationSeconds long,
|
||||||
|
registrationKey string,
|
||||||
|
vehicleKey string
|
||||||
|
);
|
||||||
|
|
||||||
|
create schema DrivingInterruptionInterval(
|
||||||
|
sessionId java.util.UUID,
|
||||||
|
driverKey string,
|
||||||
|
startedAtEpochSecond long,
|
||||||
|
endedAtEpochSecond long,
|
||||||
|
durationSeconds long,
|
||||||
|
previousDrivingSourceIntervalId string,
|
||||||
|
nextDrivingSourceIntervalId string,
|
||||||
|
previousRegistrationKey string,
|
||||||
|
nextRegistrationKey string,
|
||||||
|
previousVehicleKey string,
|
||||||
|
nextVehicleKey string
|
||||||
|
);
|
||||||
|
|
||||||
|
create schema DailyWeeklyRestCandidateInterval(
|
||||||
|
sessionId java.util.UUID,
|
||||||
|
driverKey string,
|
||||||
|
startedAtEpochSecond long,
|
||||||
|
endedAtEpochSecond long,
|
||||||
|
durationSeconds long,
|
||||||
|
previousDrivingSourceIntervalId string,
|
||||||
|
nextDrivingSourceIntervalId string,
|
||||||
|
previousRegistrationKey string,
|
||||||
|
nextRegistrationKey string,
|
||||||
|
previousVehicleKey string,
|
||||||
|
nextVehicleKey string
|
||||||
|
);
|
||||||
|
|
||||||
|
create schema DrivingInterruptionVehicleChangeInterval(
|
||||||
|
sessionId java.util.UUID,
|
||||||
|
driverKey string,
|
||||||
|
startedAtEpochSecond long,
|
||||||
|
endedAtEpochSecond long,
|
||||||
|
durationSeconds long,
|
||||||
|
previousDrivingSourceIntervalId string,
|
||||||
|
nextDrivingSourceIntervalId string,
|
||||||
|
previousRegistrationKey string,
|
||||||
|
nextRegistrationKey string,
|
||||||
|
previousVehicleKey string,
|
||||||
|
nextVehicleKey string
|
||||||
|
);
|
||||||
|
|
||||||
|
create context PerDriver partition by driverKey from TachographVehicleUsageIntervalInputEvent;
|
||||||
|
|
||||||
|
create schema VuCardAbsentInterval(
|
||||||
|
sessionId java.util.UUID,
|
||||||
|
driverKey string,
|
||||||
|
startedAtEpochSecond long,
|
||||||
|
endedAtEpochSecond long,
|
||||||
|
durationSeconds long,
|
||||||
|
previousUsageIntervalId string,
|
||||||
|
nextUsageIntervalId string,
|
||||||
|
previousRegistrationKey string,
|
||||||
|
nextRegistrationKey string,
|
||||||
|
previousVehicleKey string,
|
||||||
|
nextVehicleKey string
|
||||||
|
);
|
||||||
|
|
||||||
|
create schema PotentialHomeOvernightStayInterval(
|
||||||
|
sessionId java.util.UUID,
|
||||||
|
driverKey string,
|
||||||
|
startedAtEpochSecond long,
|
||||||
|
endedAtEpochSecond long,
|
||||||
|
durationSeconds long,
|
||||||
|
unknownDurationSeconds long,
|
||||||
|
unknownCoveragePercent double,
|
||||||
|
previousDrivingSourceIntervalId string,
|
||||||
|
nextDrivingSourceIntervalId string,
|
||||||
|
previousRegistrationKey string,
|
||||||
|
nextRegistrationKey string,
|
||||||
|
previousVehicleKey string,
|
||||||
|
nextVehicleKey string
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into SignificantDrivingInterval
|
||||||
|
select
|
||||||
|
sessionId,
|
||||||
|
driverKey,
|
||||||
|
firstSourceIntervalId,
|
||||||
|
lastSourceIntervalId,
|
||||||
|
startedAtEpochSecond,
|
||||||
|
endedAtEpochSecond,
|
||||||
|
durationSeconds,
|
||||||
|
registrationKey,
|
||||||
|
vehicleKey
|
||||||
|
from TachographActivityIntervalInputEvent(activityType = 'DRIVE', durationSeconds > ${SIGNIFICANT_DRIVING_THRESHOLD_SECONDS});
|
||||||
|
|
||||||
|
create window PreviousSignificantDrivingInterval#unique(driverKey) as SignificantDrivingInterval;
|
||||||
|
|
||||||
|
on SignificantDrivingInterval as next
|
||||||
|
insert into DrivingInterruptionInterval
|
||||||
|
select
|
||||||
|
priorInterval.sessionId as sessionId,
|
||||||
|
priorInterval.driverKey as driverKey,
|
||||||
|
priorInterval.endedAtEpochSecond as startedAtEpochSecond,
|
||||||
|
next.startedAtEpochSecond as endedAtEpochSecond,
|
||||||
|
next.startedAtEpochSecond - priorInterval.endedAtEpochSecond as durationSeconds,
|
||||||
|
priorInterval.lastSourceIntervalId as previousDrivingSourceIntervalId,
|
||||||
|
next.firstSourceIntervalId as nextDrivingSourceIntervalId,
|
||||||
|
priorInterval.registrationKey as previousRegistrationKey,
|
||||||
|
next.registrationKey as nextRegistrationKey,
|
||||||
|
priorInterval.vehicleKey as previousVehicleKey,
|
||||||
|
next.vehicleKey as nextVehicleKey
|
||||||
|
from PreviousSignificantDrivingInterval as priorInterval
|
||||||
|
where priorInterval.driverKey = next.driverKey
|
||||||
|
and next.startedAtEpochSecond > priorInterval.endedAtEpochSecond;
|
||||||
|
|
||||||
|
@Priority(20)
|
||||||
|
on SignificantDrivingInterval
|
||||||
|
delete from PreviousSignificantDrivingInterval;
|
||||||
|
|
||||||
|
@Priority(10)
|
||||||
|
on SignificantDrivingInterval as current
|
||||||
|
insert into PreviousSignificantDrivingInterval
|
||||||
|
select *;
|
||||||
|
|
||||||
|
insert into DailyWeeklyRestCandidateInterval
|
||||||
|
select *
|
||||||
|
from DrivingInterruptionInterval(durationSeconds > ${MINIMUM_REST_PERIOD_THRESHOLD_SECONDS});
|
||||||
|
|
||||||
|
insert into DrivingInterruptionVehicleChangeInterval
|
||||||
|
select *
|
||||||
|
from DailyWeeklyRestCandidateInterval(
|
||||||
|
previousRegistrationKey is not null,
|
||||||
|
nextRegistrationKey is not null,
|
||||||
|
previousRegistrationKey != nextRegistrationKey
|
||||||
|
);
|
||||||
|
|
||||||
|
context PerDriver
|
||||||
|
create window PreviousVehicleUsageInterval#lastevent as TachographVehicleUsageIntervalInputEvent;
|
||||||
|
|
||||||
|
@Priority(30)
|
||||||
|
context PerDriver
|
||||||
|
on TachographVehicleUsageIntervalInputEvent as next
|
||||||
|
insert into VuCardAbsentInterval
|
||||||
|
select
|
||||||
|
priorInterval.sessionId as sessionId,
|
||||||
|
priorInterval.driverKey as driverKey,
|
||||||
|
priorInterval.endedAtEpochSecond + 1L as startedAtEpochSecond,
|
||||||
|
next.startedAtEpochSecond as endedAtEpochSecond,
|
||||||
|
next.startedAtEpochSecond - (priorInterval.endedAtEpochSecond + 1L) as durationSeconds,
|
||||||
|
priorInterval.lastSourceIntervalId as previousUsageIntervalId,
|
||||||
|
next.firstSourceIntervalId as nextUsageIntervalId,
|
||||||
|
priorInterval.registrationKey as previousRegistrationKey,
|
||||||
|
next.registrationKey as nextRegistrationKey,
|
||||||
|
priorInterval.vehicleKey as previousVehicleKey,
|
||||||
|
next.vehicleKey as nextVehicleKey
|
||||||
|
from PreviousVehicleUsageInterval as priorInterval
|
||||||
|
where priorInterval.endedAt is not null
|
||||||
|
and next.startedAt is not null
|
||||||
|
and next.startedAtEpochSecond > priorInterval.endedAtEpochSecond + 1L;
|
||||||
|
|
||||||
|
@Priority(20)
|
||||||
|
context PerDriver
|
||||||
|
on TachographVehicleUsageIntervalInputEvent
|
||||||
|
delete from PreviousVehicleUsageInterval;
|
||||||
|
|
||||||
|
@Priority(10)
|
||||||
|
context PerDriver
|
||||||
|
on TachographVehicleUsageIntervalInputEvent as current
|
||||||
|
insert into PreviousVehicleUsageInterval
|
||||||
|
select *;
|
||||||
|
|
||||||
|
insert into PotentialHomeOvernightStayInterval
|
||||||
|
select
|
||||||
|
c.sessionId as sessionId,
|
||||||
|
c.driverKey as driverKey,
|
||||||
|
c.startedAtEpochSecond as startedAtEpochSecond,
|
||||||
|
c.endedAtEpochSecond as endedAtEpochSecond,
|
||||||
|
c.durationSeconds as durationSeconds,
|
||||||
|
sum(
|
||||||
|
case
|
||||||
|
when u.startedAtEpochSecond <= c.startedAtEpochSecond and u.endedAtEpochSecond >= c.endedAtEpochSecond
|
||||||
|
then c.durationSeconds
|
||||||
|
when u.startedAtEpochSecond <= c.startedAtEpochSecond
|
||||||
|
then u.endedAtEpochSecond - c.startedAtEpochSecond
|
||||||
|
when u.endedAtEpochSecond >= c.endedAtEpochSecond
|
||||||
|
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
|
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
|
end
|
||||||
|
) as unknownDurationSeconds,
|
||||||
|
(sum(
|
||||||
|
case
|
||||||
|
when u.startedAtEpochSecond <= c.startedAtEpochSecond and u.endedAtEpochSecond >= c.endedAtEpochSecond
|
||||||
|
then c.durationSeconds
|
||||||
|
when u.startedAtEpochSecond <= c.startedAtEpochSecond
|
||||||
|
then u.endedAtEpochSecond - c.startedAtEpochSecond
|
||||||
|
when u.endedAtEpochSecond >= c.endedAtEpochSecond
|
||||||
|
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
|
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
|
end
|
||||||
|
) * 100.0d) / c.durationSeconds as unknownCoveragePercent,
|
||||||
|
c.previousDrivingSourceIntervalId as previousDrivingSourceIntervalId,
|
||||||
|
c.nextDrivingSourceIntervalId as nextDrivingSourceIntervalId,
|
||||||
|
c.previousRegistrationKey as previousRegistrationKey,
|
||||||
|
c.nextRegistrationKey as nextRegistrationKey,
|
||||||
|
c.previousVehicleKey as previousVehicleKey,
|
||||||
|
c.nextVehicleKey as nextVehicleKey
|
||||||
|
from DrivingInterruptionVehicleChangeInterval as c unidirectional,
|
||||||
|
VuCardAbsentInterval#keepall as u
|
||||||
|
where u.driverKey = c.driverKey
|
||||||
|
and u.startedAtEpochSecond < c.endedAtEpochSecond
|
||||||
|
and u.endedAtEpochSecond > c.startedAtEpochSecond
|
||||||
|
group by
|
||||||
|
c.sessionId,
|
||||||
|
c.driverKey,
|
||||||
|
c.startedAtEpochSecond,
|
||||||
|
c.endedAtEpochSecond,
|
||||||
|
c.durationSeconds,
|
||||||
|
c.previousDrivingSourceIntervalId,
|
||||||
|
c.nextDrivingSourceIntervalId,
|
||||||
|
c.previousRegistrationKey,
|
||||||
|
c.nextRegistrationKey,
|
||||||
|
c.previousVehicleKey,
|
||||||
|
c.nextVehicleKey
|
||||||
|
having sum(
|
||||||
|
case
|
||||||
|
when u.startedAtEpochSecond <= c.startedAtEpochSecond and u.endedAtEpochSecond >= c.endedAtEpochSecond
|
||||||
|
then c.durationSeconds
|
||||||
|
when u.startedAtEpochSecond <= c.startedAtEpochSecond
|
||||||
|
then u.endedAtEpochSecond - c.startedAtEpochSecond
|
||||||
|
when u.endedAtEpochSecond >= c.endedAtEpochSecond
|
||||||
|
then c.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
|
else u.endedAtEpochSecond - u.startedAtEpochSecond
|
||||||
|
end
|
||||||
|
) * 100L >= c.durationSeconds * 95L;
|
||||||
|
|
||||||
|
@name('drivingInterruptionIntervals')
|
||||||
|
select * from DrivingInterruptionInterval;
|
||||||
|
|
||||||
|
@name('dailyWeeklyRestCandidateIntervals')
|
||||||
|
select * from DailyWeeklyRestCandidateInterval;
|
||||||
|
|
||||||
|
@name('drivingInterruptionVehicleChangeIntervals')
|
||||||
|
select * from DrivingInterruptionVehicleChangeInterval;
|
||||||
|
|
||||||
|
@name('vuCardAbsentIntervals')
|
||||||
|
select * from VuCardAbsentInterval;
|
||||||
|
|
||||||
|
@name('potentialHomeOvernightStayIntervals')
|
||||||
|
select * from PotentialHomeOvernightStayInterval;
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.service;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedCardActivityInterval;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedCardVehicleUsageInterval;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
|
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.TachographEsperVuCardAbsentIntervalEvent;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class DriverTimelineReusableProjectionBuilderTest {
|
||||||
|
|
||||||
|
private final DriverTimelineBuilder legacyBuilder = new DriverTimelineBuilder();
|
||||||
|
private final DriverTimelineReusableProjectionBuilder reusableBuilder =
|
||||||
|
new DriverTimelineReusableProjectionBuilder(legacyBuilder);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void matchesLegacyDrivingDerivedProjectionChain() {
|
||||||
|
DriverExtractionSession driver = new DriverExtractionSession(
|
||||||
|
"12:123",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
List.of(
|
||||||
|
new ExtractedCardVehicleUsageInterval(
|
||||||
|
"CVU-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T10:00:00Z"),
|
||||||
|
100L,
|
||||||
|
200L,
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"vu-1"
|
||||||
|
),
|
||||||
|
new ExtractedCardVehicleUsageInterval(
|
||||||
|
"CVU-2",
|
||||||
|
OffsetDateTime.parse("2026-05-02T00:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-02T02:00:00Z"),
|
||||||
|
201L,
|
||||||
|
260L,
|
||||||
|
"12:REG-2",
|
||||||
|
"VIN-2",
|
||||||
|
"vu-2"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
List.of(
|
||||||
|
new ExtractedCardActivityInterval(
|
||||||
|
"ACT-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T10:00:00Z"),
|
||||||
|
"DRIVE",
|
||||||
|
"DRIVER",
|
||||||
|
"INSERTED",
|
||||||
|
"SINGLE",
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"a"
|
||||||
|
),
|
||||||
|
new ExtractedCardActivityInterval(
|
||||||
|
"ACT-2",
|
||||||
|
OffsetDateTime.parse("2026-05-02T00:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-02T00:30:00Z"),
|
||||||
|
"DRIVE",
|
||||||
|
"DRIVER",
|
||||||
|
"INSERTED",
|
||||||
|
"SINGLE",
|
||||||
|
"12:REG-2",
|
||||||
|
"VIN-2",
|
||||||
|
"b"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
List.of(),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
TachographFileSession session = new TachographFileSession(
|
||||||
|
UUID.randomUUID(),
|
||||||
|
new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 2, "42", "b", true, null),
|
||||||
|
Map.of(driver.driverKey(), driver),
|
||||||
|
new ExtractionStats(1, 2, 2, 1, 1, 0),
|
||||||
|
List.of(),
|
||||||
|
Instant.now(),
|
||||||
|
Instant.now().plus(4, ChronoUnit.HOURS)
|
||||||
|
);
|
||||||
|
|
||||||
|
ResolvedDriverTimeline timeline = legacyBuilder.build(session, driver);
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> legacyDrivingInterruptions =
|
||||||
|
legacyBuilder.buildEsperDrivingInterruptionIntervalEvents(session.sessionId(), driver.driverKey(), timeline, 3);
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> legacyDailyWeeklyRestCandidates =
|
||||||
|
legacyBuilder.buildEsperDailyWeeklyRestCandidateIntervalEvents(legacyDrivingInterruptions, 720);
|
||||||
|
List<TachographEsperDrivingInterruptionIntervalEvent> legacyDrivingInterruptionVehicleChanges =
|
||||||
|
legacyBuilder.buildEsperDrivingInterruptionVehicleChangeIntervalEvents(legacyDailyWeeklyRestCandidates);
|
||||||
|
List<TachographEsperVuCardAbsentIntervalEvent> legacyVuCardAbsentIntervals =
|
||||||
|
legacyBuilder.buildEsperVuCardAbsentIntervalEvents(timeline);
|
||||||
|
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> legacyPotentialHomeOvernightStays =
|
||||||
|
legacyBuilder.buildEsperPotentialHomeOvernightStayIntervalEvents(
|
||||||
|
legacyDrivingInterruptionVehicleChanges,
|
||||||
|
legacyVuCardAbsentIntervals
|
||||||
|
);
|
||||||
|
|
||||||
|
TachographEsperDrivingDerivedProjectionBundle reusableBundle =
|
||||||
|
reusableBuilder.buildEsperDrivingDerivedProjectionBundle(
|
||||||
|
session.sessionId(),
|
||||||
|
driver.driverKey(),
|
||||||
|
timeline,
|
||||||
|
3,
|
||||||
|
720
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(reusableBundle.drivingInterruptionIntervals()).containsExactlyElementsOf(legacyDrivingInterruptions);
|
||||||
|
assertThat(reusableBundle.dailyWeeklyRestCandidateIntervals()).containsExactlyElementsOf(legacyDailyWeeklyRestCandidates);
|
||||||
|
assertThat(reusableBundle.drivingInterruptionVehicleChangeIntervals()).containsExactlyElementsOf(legacyDrivingInterruptionVehicleChanges);
|
||||||
|
assertThat(reusableBundle.vuCardAbsentIntervals()).containsExactlyElementsOf(legacyVuCardAbsentIntervals);
|
||||||
|
assertThat(reusableBundle.potentialHomeOvernightStayIntervals()).containsExactlyElementsOf(legacyPotentialHomeOvernightStays);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
package at.procon.eventhub.tachographfilesession.service;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventDomain;
|
||||||
|
import at.procon.eventhub.dto.EventLifecycle;
|
||||||
|
import at.procon.eventhub.dto.EventType;
|
||||||
|
import at.procon.eventhub.service.EventDetailsFactory;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedCardActivityInterval;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedCardVehicleUsageInterval;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriverCard;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicle;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicleRegistration;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographTimelineEventBundle;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class IntervalBackedDriverTimelineEventBuilderTest {
|
||||||
|
|
||||||
|
private final IntervalBackedDriverTimelineEventBuilder builder = new IntervalBackedDriverTimelineEventBuilder(
|
||||||
|
new DriverTimelineBuilder(),
|
||||||
|
new DriverKeyFactory(),
|
||||||
|
new VehicleKeyFactory(),
|
||||||
|
new EventDetailsFactory(new ObjectMapper())
|
||||||
|
);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildsLifecycleActivityEventsFromIntervals() {
|
||||||
|
DriverExtractionSession driver = new DriverExtractionSession(
|
||||||
|
"12:123",
|
||||||
|
new ExtractedDriver("12:123", "DRV:12:123", "Doe", "Jane", null, null, null, null, null),
|
||||||
|
new ExtractedDriverCard("CARD:12:123", "12", "123", null, null, null, null),
|
||||||
|
List.of(new ExtractedVehicleRegistration("12:REG-1", "VR:12:REG-1", "12", "REG-1")),
|
||||||
|
List.of(new ExtractedVehicle("VIN-1", "VIN:VIN-1", "VIN-1")),
|
||||||
|
List.of(new ExtractedCardVehicleUsageInterval(
|
||||||
|
"CVU-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T10:00:00Z"),
|
||||||
|
100L,
|
||||||
|
200L,
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"vu-1"
|
||||||
|
)),
|
||||||
|
List.of(new ExtractedCardActivityInterval(
|
||||||
|
"ACT-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:30:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T09:00:00Z"),
|
||||||
|
"DRIVE",
|
||||||
|
"DRIVER",
|
||||||
|
"INSERTED",
|
||||||
|
"SINGLE",
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"a"
|
||||||
|
)),
|
||||||
|
List.of(),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
TachographFileSession session = session(driver, true);
|
||||||
|
|
||||||
|
TachographTimelineEventBundle bundle = builder.buildEventBundle(session, driver);
|
||||||
|
|
||||||
|
assertThat(bundle.activityEvents()).hasSize(2);
|
||||||
|
assertThat(bundle.activityEvents().get(0).eventDomain()).isEqualTo(EventDomain.DRIVER_ACTIVITY);
|
||||||
|
assertThat(bundle.activityEvents().get(0).eventType()).isEqualTo(EventType.DRIVE);
|
||||||
|
assertThat(bundle.activityEvents().get(0).lifecycle()).isEqualTo(EventLifecycle.START);
|
||||||
|
assertThat(bundle.activityEvents().get(0).occurredAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:30:00Z"));
|
||||||
|
assertThat(bundle.activityEvents().get(0).driverRef().sourceEntityId()).isEqualTo("DRV:12:123");
|
||||||
|
assertThat(bundle.activityEvents().get(0).vehicleRef().vin()).isEqualTo("VIN-1");
|
||||||
|
assertThat(bundle.activityEvents().get(0).eventDetails().type()).isEqualTo("DRIVER_ACTIVITY");
|
||||||
|
assertThat(bundle.activityEvents().get(0).payload().get("raw").get("intervalId").asText()).isEqualTo("ACT-1");
|
||||||
|
assertThat(bundle.activityEvents().get(1).lifecycle()).isEqualTo(EventLifecycle.END);
|
||||||
|
assertThat(bundle.activityEvents().get(1).occurredAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T09:00:00Z"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildsVehicleUsageAndSupportEvents() {
|
||||||
|
DriverExtractionSession driver = new DriverExtractionSession(
|
||||||
|
"12:123",
|
||||||
|
new ExtractedDriver("12:123", "DRV:12:123", "Doe", "Jane", null, null, null, null, null),
|
||||||
|
new ExtractedDriverCard("CARD:12:123", "12", "123", null, null, null, null),
|
||||||
|
List.of(new ExtractedVehicleRegistration("12:REG-1", "VR:12:REG-1", "12", "REG-1")),
|
||||||
|
List.of(new ExtractedVehicle("VIN-1", "VIN:VIN-1", "VIN-1")),
|
||||||
|
List.of(new ExtractedCardVehicleUsageInterval(
|
||||||
|
"CVU-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T10:00:00Z"),
|
||||||
|
100L,
|
||||||
|
200L,
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"vu-1"
|
||||||
|
)),
|
||||||
|
List.of(),
|
||||||
|
List.of(new ExtractedSupportEvent(
|
||||||
|
"VUGNSS-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:45:00Z"),
|
||||||
|
"POSITION",
|
||||||
|
"GNSS_ACCUMULATED_DRIVING",
|
||||||
|
"DRIVER",
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
BigDecimal.valueOf(48.2082),
|
||||||
|
BigDecimal.valueOf(16.3738),
|
||||||
|
"AUTHENTIC",
|
||||||
|
150L,
|
||||||
|
null,
|
||||||
|
"raw-path"
|
||||||
|
)),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
TachographFileSession session = session(driver, false);
|
||||||
|
|
||||||
|
TachographTimelineEventBundle bundle = builder.buildEventBundle(session, driver);
|
||||||
|
|
||||||
|
assertThat(bundle.vehicleUsageEvents()).hasSize(2);
|
||||||
|
assertThat(bundle.vehicleUsageEvents().get(0).eventDomain()).isEqualTo(EventDomain.DRIVER_CARD);
|
||||||
|
assertThat(bundle.vehicleUsageEvents().get(0).eventType()).isEqualTo(EventType.CARD_INSERTED);
|
||||||
|
assertThat(bundle.vehicleUsageEvents().get(0).lifecycle()).isEqualTo(EventLifecycle.INSERT);
|
||||||
|
assertThat(bundle.vehicleUsageEvents().get(0).odometerM()).isEqualTo(100_000L);
|
||||||
|
assertThat(bundle.vehicleUsageEvents().get(1).eventType()).isEqualTo(EventType.CARD_WITHDRAWN);
|
||||||
|
assertThat(bundle.vehicleUsageEvents().get(1).lifecycle()).isEqualTo(EventLifecycle.WITHDRAW);
|
||||||
|
assertThat(bundle.vehicleUsageEvents().get(1).odometerM()).isEqualTo(200_000L);
|
||||||
|
|
||||||
|
assertThat(bundle.supportEvents()).hasSize(1);
|
||||||
|
assertThat(bundle.supportEvents().get(0).eventDomain()).isEqualTo(EventDomain.POSITION);
|
||||||
|
assertThat(bundle.supportEvents().get(0).eventType()).isEqualTo(EventType.POSITION_RECORDED);
|
||||||
|
assertThat(bundle.supportEvents().get(0).position().latitude()).isEqualTo(BigDecimal.valueOf(48.2082));
|
||||||
|
assertThat(bundle.supportEvents().get(0).eventDetails().type()).isEqualTo("POSITION");
|
||||||
|
|
||||||
|
assertThat(bundle.allEvents()).extracting(event -> event.occurredAt()).isSorted();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TachographFileSession session(DriverExtractionSession driver, boolean driverCardFile) {
|
||||||
|
return new TachographFileSession(
|
||||||
|
UUID.randomUUID(),
|
||||||
|
new TachographFileSessionMetadata(
|
||||||
|
"default",
|
||||||
|
"legalrequirements-drivercard",
|
||||||
|
"sample",
|
||||||
|
"sample.ddd",
|
||||||
|
"a",
|
||||||
|
2,
|
||||||
|
"42",
|
||||||
|
"b",
|
||||||
|
driverCardFile,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
Map.of(driver.driverKey(), driver),
|
||||||
|
new ExtractionStats(
|
||||||
|
1,
|
||||||
|
driver.cardActivityIntervals().size(),
|
||||||
|
driver.cardVehicleUsageIntervals().size(),
|
||||||
|
driver.vehicleRegistrations().size(),
|
||||||
|
driver.vehicles().size(),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
List.of(),
|
||||||
|
Instant.now(),
|
||||||
|
Instant.now().plus(4, ChronoUnit.HOURS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,9 +27,11 @@ class TachographFileSessionProcessingServiceTest {
|
||||||
void returnsEsperDriverProcessingResultsFromSessionTimeline() {
|
void returnsEsperDriverProcessingResultsFromSessionTimeline() {
|
||||||
EventHubProperties properties = new EventHubProperties();
|
EventHubProperties properties = new EventHubProperties();
|
||||||
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
||||||
|
DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
|
||||||
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
|
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
|
||||||
repository,
|
repository,
|
||||||
new DriverTimelineBuilder(),
|
driverTimelineBuilder,
|
||||||
|
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder),
|
||||||
properties
|
properties
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -99,9 +101,11 @@ class TachographFileSessionProcessingServiceTest {
|
||||||
void appliesOccurredWindowToEsperDriverProcessingResults() {
|
void appliesOccurredWindowToEsperDriverProcessingResults() {
|
||||||
EventHubProperties properties = new EventHubProperties();
|
EventHubProperties properties = new EventHubProperties();
|
||||||
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
||||||
|
DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
|
||||||
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
|
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
|
||||||
repository,
|
repository,
|
||||||
new DriverTimelineBuilder(),
|
driverTimelineBuilder,
|
||||||
|
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder),
|
||||||
properties
|
properties
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -191,9 +195,11 @@ class TachographFileSessionProcessingServiceTest {
|
||||||
void returnsPotentialHomeOvernightStayIntervalsWhenVuCardAbsentCoversLongDti() {
|
void returnsPotentialHomeOvernightStayIntervalsWhenVuCardAbsentCoversLongDti() {
|
||||||
EventHubProperties properties = new EventHubProperties();
|
EventHubProperties properties = new EventHubProperties();
|
||||||
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
||||||
|
DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
|
||||||
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
|
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
|
||||||
repository,
|
repository,
|
||||||
new DriverTimelineBuilder(),
|
driverTimelineBuilder,
|
||||||
|
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder),
|
||||||
properties
|
properties
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -272,9 +278,11 @@ class TachographFileSessionProcessingServiceTest {
|
||||||
void evaluatesOperatingPeriodsFromSessionTimeline() {
|
void evaluatesOperatingPeriodsFromSessionTimeline() {
|
||||||
EventHubProperties properties = new EventHubProperties();
|
EventHubProperties properties = new EventHubProperties();
|
||||||
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
||||||
|
DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
|
||||||
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
|
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
|
||||||
repository,
|
repository,
|
||||||
new DriverTimelineBuilder(),
|
driverTimelineBuilder,
|
||||||
|
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder),
|
||||||
properties
|
properties
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue