Fix driver card usage interval pairing
This commit is contained in:
parent
6dd1fb7447
commit
7457872d51
|
|
@ -0,0 +1,226 @@
|
|||
package at.procon.eventhub.processing.support;
|
||||
|
||||
import at.procon.eventhub.dto.EventDetailsDto;
|
||||
import at.procon.eventhub.dto.EventDomain;
|
||||
import at.procon.eventhub.dto.EventHubEventDto;
|
||||
import at.procon.eventhub.dto.EventLifecycle;
|
||||
import at.procon.eventhub.dto.EventType;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class RuntimeEventIdentityResolver {
|
||||
|
||||
private RuntimeEventIdentityResolver() {
|
||||
}
|
||||
|
||||
public static String canonicalEventKey(EventHubEventDto event) {
|
||||
if (event == null) {
|
||||
return "NULL_EVENT";
|
||||
}
|
||||
JsonNode raw = rawPayload(event);
|
||||
JsonNode details = detailAttributes(event);
|
||||
List<String> parts = new ArrayList<>();
|
||||
parts.add("EVENT");
|
||||
parts.add(nullToEmpty(event.packageInfo() == null ? null : event.packageInfo().tenantKey()));
|
||||
parts.add(nullToEmpty(event.eventDomain() == null ? null : event.eventDomain().name()));
|
||||
parts.add(nullToEmpty(event.eventType() == null ? null : event.eventType().name()));
|
||||
parts.add(nullToEmpty(event.lifecycle() == null ? null : event.lifecycle().name()));
|
||||
parts.add(normalizeTime(event.occurredAt()));
|
||||
parts.add(nullToEmpty(TachographRuntimeIdentityResolver.driverKey(event)));
|
||||
parts.add(nullToEmpty(TachographRuntimeIdentityResolver.registrationKey(event)));
|
||||
parts.add(nullToEmpty(TachographRuntimeIdentityResolver.vehicleKey(event)));
|
||||
parts.add(isIntervalPointEvent(event) ? nullToEmpty(runtimeIntervalKey(event)) : "");
|
||||
parts.add(nullToEmpty(firstNonBlank(text(raw, "activityType"), event.eventType() == null ? null : event.eventType().name())));
|
||||
parts.add(nullToEmpty(firstNonBlank(text(raw, "slot"), text(raw, "cardSlot"), text(details, "cardSlot"))));
|
||||
parts.add(nullToEmpty(firstNonBlank(text(raw, "cardStatus"), text(details, "cardStatus"))));
|
||||
parts.add(nullToEmpty(firstNonBlank(text(raw, "drivingStatus"), text(details, "drivingStatus"))));
|
||||
parts.add(nullToEmpty(firstNonBlank(text(raw, "supportEventType"), event.eventType() == null ? null : event.eventType().name())));
|
||||
parts.add(nullToEmpty(firstNonBlank(text(raw, "country"), text(details, "country"))));
|
||||
parts.add(nullToEmpty(text(raw, "region")));
|
||||
parts.add(nullToEmpty(firstNonBlank(text(raw, "countryFrom"), text(details, "countryFrom"))));
|
||||
parts.add(nullToEmpty(firstNonBlank(text(raw, "countryTo"), text(details, "countryTo"))));
|
||||
parts.add(nullToEmpty(firstNonBlank(text(raw, "operation"), text(details, "operation"))));
|
||||
parts.add(nullToEmpty(text(raw, "authenticationStatus")));
|
||||
parts.add(nullToEmpty(text(raw, "code")));
|
||||
parts.add(nullToEmpty(firstNonBlank(
|
||||
numberText(firstNonBlankNode(raw, "odometerKm")),
|
||||
numberText(firstNonBlankNode(raw, "odometerM"))
|
||||
)));
|
||||
parts.add(nullToEmpty(positionKey(event)));
|
||||
return String.join("|", parts);
|
||||
}
|
||||
|
||||
public static String runtimeIntervalKey(EventHubEventDto event) {
|
||||
if (event == null) {
|
||||
return null;
|
||||
}
|
||||
String originalIntervalId = presentationIntervalId(event);
|
||||
if (!isIntervalPointEvent(event)) {
|
||||
return originalIntervalId;
|
||||
}
|
||||
JsonNode raw = rawPayload(event);
|
||||
OffsetDateTime startedAt = intervalStartedAt(raw);
|
||||
OffsetDateTime endedAt = intervalEndedAt(raw);
|
||||
if (startedAt == null && endedAt == null) {
|
||||
return originalIntervalId;
|
||||
}
|
||||
JsonNode details = detailAttributes(event);
|
||||
List<String> parts = new ArrayList<>();
|
||||
parts.add("INTERVAL");
|
||||
parts.add(nullToEmpty(event.eventDomain() == null ? null : event.eventDomain().name()));
|
||||
parts.add(nullToEmpty(intervalSemanticType(event, raw)));
|
||||
parts.add(nullToEmpty(TachographRuntimeIdentityResolver.driverKey(event)));
|
||||
parts.add(nullToEmpty(TachographRuntimeIdentityResolver.registrationKey(event)));
|
||||
parts.add(nullToEmpty(TachographRuntimeIdentityResolver.vehicleKey(event)));
|
||||
parts.add(nullToEmpty(intervalCardSlot(event, raw, details)));
|
||||
parts.add(nullToEmpty(intervalCardStatus(event, raw, details)));
|
||||
parts.add(nullToEmpty(intervalDrivingStatus(event, raw, details)));
|
||||
parts.add(normalizeTime(startedAt));
|
||||
parts.add(normalizeTime(endedAt));
|
||||
return String.join("|", parts);
|
||||
}
|
||||
|
||||
public static String presentationIntervalId(EventHubEventDto event) {
|
||||
JsonNode raw = rawPayload(event);
|
||||
return firstNonBlank(
|
||||
text(raw, "intervalId"),
|
||||
text(raw, "sourceRowId"),
|
||||
event == null ? null : event.externalSourceEventId()
|
||||
);
|
||||
}
|
||||
|
||||
private static boolean isIntervalPointEvent(EventHubEventDto event) {
|
||||
if (event == null || event.eventDomain() == null || event.lifecycle() == null) {
|
||||
return false;
|
||||
}
|
||||
if (event.eventDomain() == EventDomain.DRIVER_ACTIVITY) {
|
||||
return event.lifecycle() == EventLifecycle.START || event.lifecycle() == EventLifecycle.END;
|
||||
}
|
||||
if (event.eventDomain() != EventDomain.DRIVER_CARD) {
|
||||
return false;
|
||||
}
|
||||
return event.eventType() == EventType.CARD_INSERTED || event.eventType() == EventType.CARD_WITHDRAWN;
|
||||
}
|
||||
|
||||
private static OffsetDateTime intervalStartedAt(JsonNode raw) {
|
||||
return time(firstNonBlank(text(raw, "startedAt"), text(raw, "intervalStartedAt")));
|
||||
}
|
||||
|
||||
private static OffsetDateTime intervalEndedAt(JsonNode raw) {
|
||||
return time(firstNonBlank(text(raw, "endedAt"), text(raw, "intervalEndedAt")));
|
||||
}
|
||||
|
||||
private static String intervalSemanticType(EventHubEventDto event, JsonNode raw) {
|
||||
if (event == null) {
|
||||
return null;
|
||||
}
|
||||
if (event.eventDomain() == EventDomain.DRIVER_ACTIVITY) {
|
||||
return firstNonBlank(text(raw, "activityType"), event.eventType() == null ? null : event.eventType().name());
|
||||
}
|
||||
if (event.eventDomain() == EventDomain.DRIVER_CARD) {
|
||||
return "CARD_USAGE";
|
||||
}
|
||||
return event.eventType() == null ? null : event.eventType().name();
|
||||
}
|
||||
|
||||
private static String intervalCardSlot(EventHubEventDto event, JsonNode raw, JsonNode details) {
|
||||
return firstNonBlank(text(raw, "slot"), text(raw, "cardSlot"), text(details, "cardSlot"));
|
||||
}
|
||||
|
||||
private static String intervalCardStatus(EventHubEventDto event, JsonNode raw, JsonNode details) {
|
||||
if (event != null && event.eventDomain() == EventDomain.DRIVER_CARD) {
|
||||
return null;
|
||||
}
|
||||
return firstNonBlank(text(raw, "cardStatus"), text(details, "cardStatus"));
|
||||
}
|
||||
|
||||
private static String intervalDrivingStatus(EventHubEventDto event, JsonNode raw, JsonNode details) {
|
||||
if (event != null && event.eventDomain() == EventDomain.DRIVER_CARD) {
|
||||
return null;
|
||||
}
|
||||
return firstNonBlank(text(raw, "drivingStatus"), text(details, "drivingStatus"));
|
||||
}
|
||||
|
||||
private static String positionKey(EventHubEventDto event) {
|
||||
if (event == null || event.position() == null) {
|
||||
return null;
|
||||
}
|
||||
return nullToEmpty(event.position().latitude()) + ":" + nullToEmpty(event.position().longitude());
|
||||
}
|
||||
|
||||
private static JsonNode rawPayload(EventHubEventDto event) {
|
||||
return TachographRuntimeIdentityResolver.rawPayload(event);
|
||||
}
|
||||
|
||||
private static JsonNode detailAttributes(EventHubEventDto event) {
|
||||
EventDetailsDto details = event == null ? null : event.eventDetails();
|
||||
return details == null ? null : details.attributes();
|
||||
}
|
||||
|
||||
private static JsonNode firstNonBlankNode(JsonNode node, String field) {
|
||||
if (node == null || field == null) {
|
||||
return null;
|
||||
}
|
||||
JsonNode value = node.get(field);
|
||||
if (value == null || value.isNull()) {
|
||||
return null;
|
||||
}
|
||||
if (value.isTextual() && value.asText().isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String numberText(JsonNode value) {
|
||||
if (value == null || value.isNull()) {
|
||||
return null;
|
||||
}
|
||||
return value.isNumber() ? value.numberValue().toString() : text(value, null);
|
||||
}
|
||||
|
||||
private static OffsetDateTime time(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return OffsetDateTime.parse(value.trim());
|
||||
} catch (DateTimeParseException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalizeTime(OffsetDateTime value) {
|
||||
return value == null ? "" : value.toInstant().toString();
|
||||
}
|
||||
|
||||
private static String text(JsonNode node, String field) {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
JsonNode value = field == null ? node : node.get(field);
|
||||
if (value == null || value.isNull()) {
|
||||
return null;
|
||||
}
|
||||
String text = value.asText(null);
|
||||
return text == null || text.isBlank() ? null : text.trim();
|
||||
}
|
||||
|
||||
private static String firstNonBlank(String... values) {
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
for (String value : values) {
|
||||
if (value != null && !value.isBlank()) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String nullToEmpty(Object value) {
|
||||
return value == null ? "" : String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
package at.procon.eventhub.processing.eventprocessing.module;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import at.procon.eventhub.dto.DriverRefDto;
|
||||
import at.procon.eventhub.dto.EventDetailsDto;
|
||||
import at.procon.eventhub.dto.EventDomain;
|
||||
import at.procon.eventhub.dto.EventHubEventDto;
|
||||
import at.procon.eventhub.dto.EventHubPackageRequest;
|
||||
import at.procon.eventhub.dto.EventLifecycle;
|
||||
import at.procon.eventhub.dto.EventSourceDto;
|
||||
import at.procon.eventhub.dto.EventType;
|
||||
import at.procon.eventhub.dto.ImportScopeDto;
|
||||
import at.procon.eventhub.dto.VehicleRefDto;
|
||||
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
|
||||
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeVehicleUsageInterval;
|
||||
import at.procon.eventhub.processing.dto.UnifiedRuntimeProcessingApiRequest;
|
||||
import at.procon.eventhub.processing.eventprocessing.plan.RuntimeProcessingExecutionApiRequest;
|
||||
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class DriverVehicleUsageIntervalsModuleTest {
|
||||
|
||||
@Test
|
||||
void pairsVehicleUsagePointsWhenCardStatusDiffersAcrossLifecycleEvents() {
|
||||
DriverVehicleUsageIntervalsModule module = new DriverVehicleUsageIntervalsModule();
|
||||
RuntimeProcessingModuleContext context = new RuntimeProcessingModuleContext(
|
||||
new RuntimeProcessingExecutionApiRequest(
|
||||
"driver-working-time-v1",
|
||||
runtimeScope(),
|
||||
null,
|
||||
List.of(),
|
||||
Map.of()
|
||||
),
|
||||
List.of(
|
||||
vehicleUsageEvent(
|
||||
"CVU-1",
|
||||
EventType.CARD_INSERTED,
|
||||
EventLifecycle.INSERT,
|
||||
"2026-05-01T07:50:00Z",
|
||||
"2026-05-01T07:50:00Z",
|
||||
"2026-05-01T10:10:00Z",
|
||||
100_000L,
|
||||
"INSERTED"
|
||||
),
|
||||
vehicleUsageEvent(
|
||||
"CVU-99",
|
||||
EventType.CARD_WITHDRAWN,
|
||||
EventLifecycle.WITHDRAW,
|
||||
"2026-05-01T10:10:00Z",
|
||||
"2026-05-01T07:50:00Z",
|
||||
"2026-05-01T10:10:00Z",
|
||||
140_000L,
|
||||
"NOT_INSERTED"
|
||||
)
|
||||
),
|
||||
Map.of(),
|
||||
Map.of()
|
||||
);
|
||||
|
||||
RuntimeProcessingModuleResult result = module.execute(context);
|
||||
|
||||
assertThat(result.status()).isEqualTo(RuntimeProcessingModuleStatus.SUCCESS);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<DriverWorkingTimeVehicleUsageInterval> intervals =
|
||||
(List<DriverWorkingTimeVehicleUsageInterval>) result.output();
|
||||
assertThat(intervals).hasSize(1);
|
||||
assertThat(intervals.get(0).intervalId()).isEqualTo("CVU-1");
|
||||
assertThat(intervals.get(0).startedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T07:50:00Z"));
|
||||
assertThat(intervals.get(0).endedAt()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:10:00Z"));
|
||||
}
|
||||
|
||||
private UnifiedRuntimeProcessingApiRequest runtimeScope() {
|
||||
return new UnifiedRuntimeProcessingApiRequest(
|
||||
UUID.randomUUID(),
|
||||
List.of(),
|
||||
null,
|
||||
"default",
|
||||
Set.of(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION),
|
||||
null,
|
||||
null,
|
||||
"DRIVER:42",
|
||||
Set.of(),
|
||||
false,
|
||||
Set.of(),
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
OffsetDateTime.parse("2026-05-01T00:00:00Z"),
|
||||
OffsetDateTime.parse("2026-05-02T00:00:00Z"),
|
||||
true,
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private EventHubEventDto vehicleUsageEvent(
|
||||
String intervalId,
|
||||
EventType eventType,
|
||||
EventLifecycle lifecycle,
|
||||
String occurredAt,
|
||||
String startedAt,
|
||||
String endedAt,
|
||||
long odometerM,
|
||||
String cardStatus
|
||||
) {
|
||||
ObjectNode raw = JsonNodeFactory.instance.objectNode();
|
||||
raw.put("intervalId", intervalId);
|
||||
raw.put("driverKey", "DRIVER:42");
|
||||
raw.put("startedAt", startedAt);
|
||||
raw.put("endedAt", endedAt);
|
||||
raw.put("registrationKey", "12:REG-1");
|
||||
raw.put("vehicleKey", "VIN-1");
|
||||
raw.put("sourceKind", "DRIVER_CARD");
|
||||
raw.putArray("sourceRowIds").add("row-" + intervalId);
|
||||
ObjectNode payload = JsonNodeFactory.instance.objectNode();
|
||||
payload.set("raw", raw);
|
||||
ObjectNode attributes = JsonNodeFactory.instance.objectNode();
|
||||
attributes.put("cardStatus", cardStatus);
|
||||
OffsetDateTime occurred = OffsetDateTime.parse(occurredAt);
|
||||
return new EventHubEventDto(
|
||||
UUID.randomUUID(),
|
||||
intervalId + ":" + eventType.name(),
|
||||
new DriverRefDto("DRIVER:42", null),
|
||||
new VehicleRefDto("VEH-1", "VIN-1", "VR-1", new VehicleRegistrationRefDto("12", 12, "REG-1")),
|
||||
occurred,
|
||||
null,
|
||||
occurred,
|
||||
EventDomain.DRIVER_CARD,
|
||||
eventType,
|
||||
lifecycle,
|
||||
odometerM,
|
||||
null,
|
||||
new EventDetailsDto("DRIVER_CARD", attributes),
|
||||
null,
|
||||
payload,
|
||||
false,
|
||||
packageInfo()
|
||||
);
|
||||
}
|
||||
|
||||
private EventHubPackageRequest packageInfo() {
|
||||
EventSourceDto source = new EventSourceDto("TACHOGRAPH", "DRIVER_CARD", "SOURCE", null, null, null);
|
||||
return new EventHubPackageRequest(
|
||||
"default",
|
||||
source,
|
||||
null,
|
||||
ImportScopeDto.tenantAll(
|
||||
OffsetDateTime.parse("2026-05-01T00:00:00Z"),
|
||||
OffsetDateTime.parse("2026-05-02T00:00:00Z")
|
||||
),
|
||||
EventDomain.DRIVER_CARD.name(),
|
||||
LocalDate.parse("2026-05-01"),
|
||||
source.stableKey() + ":" + EventDomain.DRIVER_CARD.name() + ":2026-05-01"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
|
||||
import at.procon.eventhub.dto.DriverRefDto;
|
||||
import at.procon.eventhub.dto.EventDomain;
|
||||
import at.procon.eventhub.dto.EventDetailsDto;
|
||||
import at.procon.eventhub.dto.EventHubEventDto;
|
||||
import at.procon.eventhub.dto.EventLifecycle;
|
||||
import at.procon.eventhub.dto.EventSourceDto;
|
||||
|
|
@ -30,10 +31,10 @@ class UnifiedEventTimelineReconstructorTest {
|
|||
@Test
|
||||
void reconstructsTimelineFromUnifiedEvents() {
|
||||
List<EventHubEventDto> events = List.of(
|
||||
activityEvent("ACT-1", EventLifecycle.START, "2026-05-01T08:00:00Z"),
|
||||
activityEvent("ACT-1", EventLifecycle.END, "2026-05-01T09:00:00Z"),
|
||||
vehicleUsageEvent("CVU-1", EventType.CARD_INSERTED, EventLifecycle.INSERT, "2026-05-01T07:50:00Z", 100_000L),
|
||||
vehicleUsageEvent("CVU-1", EventType.CARD_WITHDRAWN, EventLifecycle.WITHDRAW, "2026-05-01T10:10:00Z", 140_000L),
|
||||
activityEvent("ACT-1", EventLifecycle.START, "2026-05-01T08:00:00Z", "2026-05-01T08:00:00Z", "2026-05-01T09:00:00Z"),
|
||||
activityEvent("ACT-1", EventLifecycle.END, "2026-05-01T09:00:00Z", "2026-05-01T08:00:00Z", "2026-05-01T09:00:00Z"),
|
||||
vehicleUsageEvent("CVU-1", EventType.CARD_INSERTED, EventLifecycle.INSERT, "2026-05-01T07:50:00Z", "2026-05-01T07:50:00Z", "2026-05-01T10:10:00Z", 100_000L),
|
||||
vehicleUsageEvent("CVU-1", EventType.CARD_WITHDRAWN, EventLifecycle.WITHDRAW, "2026-05-01T10:10:00Z", "2026-05-01T07:50:00Z", "2026-05-01T10:10:00Z", 140_000L),
|
||||
supportEvent("SUP-1", "2026-05-01T08:30:00Z")
|
||||
);
|
||||
|
||||
|
|
@ -60,9 +61,73 @@ class UnifiedEventTimelineReconstructorTest {
|
|||
assertThat(timeline.supportEvents().get(0).latitude()).isEqualByComparingTo(BigDecimal.valueOf(48.2082));
|
||||
}
|
||||
|
||||
private EventHubEventDto activityEvent(String intervalId, EventLifecycle lifecycle, String occurredAt) {
|
||||
@Test
|
||||
void reconstructsCrossSourceActivityIntervalUsingRuntimeIntervalKey() {
|
||||
List<EventHubEventDto> events = List.of(
|
||||
activityEvent("CARD-ACT-1", EventLifecycle.START, "2026-05-01T08:00:00Z", "2026-05-01T08:00:00Z", "2026-05-01T09:00:00Z"),
|
||||
activityEvent("VU-ACT-99", EventLifecycle.END, "2026-05-01T09:00:00Z", "2026-05-01T08:00:00Z", "2026-05-01T09:00:00Z")
|
||||
);
|
||||
|
||||
ResolvedDriverTimeline timeline = reconstructor.reconstruct(
|
||||
UUID.randomUUID(),
|
||||
"DRIVER:42",
|
||||
events
|
||||
);
|
||||
|
||||
assertThat(timeline.activityIntervals()).hasSize(1);
|
||||
assertThat(timeline.activityIntervals().get(0).intervalId()).isEqualTo("CARD-ACT-1");
|
||||
assertThat(timeline.activityIntervals().get(0).from()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:00:00Z"));
|
||||
assertThat(timeline.activityIntervals().get(0).to()).isEqualTo(OffsetDateTime.parse("2026-05-01T09:00:00Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reconstructsVehicleUsageIntervalWhenCardStatusDiffersAcrossLifecycleEvents() {
|
||||
List<EventHubEventDto> events = List.of(
|
||||
vehicleUsageEvent(
|
||||
"CVU-1",
|
||||
EventType.CARD_INSERTED,
|
||||
EventLifecycle.INSERT,
|
||||
"2026-05-01T07:50:00Z",
|
||||
"2026-05-01T07:50:00Z",
|
||||
"2026-05-01T10:10:00Z",
|
||||
100_000L,
|
||||
"INSERTED"
|
||||
),
|
||||
vehicleUsageEvent(
|
||||
"CVU-99",
|
||||
EventType.CARD_WITHDRAWN,
|
||||
EventLifecycle.WITHDRAW,
|
||||
"2026-05-01T10:10:00Z",
|
||||
"2026-05-01T07:50:00Z",
|
||||
"2026-05-01T10:10:00Z",
|
||||
140_000L,
|
||||
"NOT_INSERTED"
|
||||
)
|
||||
);
|
||||
|
||||
ResolvedDriverTimeline timeline = reconstructor.reconstruct(
|
||||
UUID.randomUUID(),
|
||||
"DRIVER:42",
|
||||
events
|
||||
);
|
||||
|
||||
assertThat(timeline.vehicleUsageIntervals()).hasSize(1);
|
||||
assertThat(timeline.vehicleUsageIntervals().get(0).intervalId()).isEqualTo("CVU-1");
|
||||
assertThat(timeline.vehicleUsageIntervals().get(0).from()).isEqualTo(OffsetDateTime.parse("2026-05-01T07:50:00Z"));
|
||||
assertThat(timeline.vehicleUsageIntervals().get(0).to()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:10:00Z"));
|
||||
}
|
||||
|
||||
private EventHubEventDto activityEvent(
|
||||
String intervalId,
|
||||
EventLifecycle lifecycle,
|
||||
String occurredAt,
|
||||
String startedAt,
|
||||
String endedAt
|
||||
) {
|
||||
ObjectNode raw = JsonNodeFactory.instance.objectNode();
|
||||
raw.put("intervalId", intervalId);
|
||||
raw.put("startedAt", startedAt);
|
||||
raw.put("endedAt", endedAt);
|
||||
raw.put("registrationKey", "12:REG-1");
|
||||
raw.put("vehicleKey", "VIN-1");
|
||||
raw.put("sourceKind", "DRIVER_CARD");
|
||||
|
|
@ -95,16 +160,37 @@ class UnifiedEventTimelineReconstructorTest {
|
|||
EventType eventType,
|
||||
EventLifecycle lifecycle,
|
||||
String occurredAt,
|
||||
String startedAt,
|
||||
String endedAt,
|
||||
long odometerM
|
||||
) {
|
||||
return vehicleUsageEvent(intervalId, eventType, lifecycle, occurredAt, startedAt, endedAt, odometerM, null);
|
||||
}
|
||||
|
||||
private EventHubEventDto vehicleUsageEvent(
|
||||
String intervalId,
|
||||
EventType eventType,
|
||||
EventLifecycle lifecycle,
|
||||
String occurredAt,
|
||||
String startedAt,
|
||||
String endedAt,
|
||||
long odometerM,
|
||||
String cardStatus
|
||||
) {
|
||||
ObjectNode raw = JsonNodeFactory.instance.objectNode();
|
||||
raw.put("intervalId", intervalId);
|
||||
raw.put("startedAt", startedAt);
|
||||
raw.put("endedAt", endedAt);
|
||||
raw.put("registrationKey", "12:REG-1");
|
||||
raw.put("vehicleKey", "VIN-1");
|
||||
raw.put("sourceKind", "DRIVER_CARD");
|
||||
raw.putArray("sourceRowIds").add("row-" + intervalId);
|
||||
ObjectNode payload = JsonNodeFactory.instance.objectNode();
|
||||
payload.set("raw", raw);
|
||||
ObjectNode attributes = JsonNodeFactory.instance.objectNode();
|
||||
if (cardStatus != null) {
|
||||
attributes.put("cardStatus", cardStatus);
|
||||
}
|
||||
return new EventHubEventDto(
|
||||
UUID.randomUUID(),
|
||||
intervalId + ":" + eventType.name(),
|
||||
|
|
@ -118,7 +204,7 @@ class UnifiedEventTimelineReconstructorTest {
|
|||
lifecycle,
|
||||
odometerM,
|
||||
null,
|
||||
null,
|
||||
cardStatus == null ? null : new EventDetailsDto("DRIVER_CARD", attributes),
|
||||
null,
|
||||
payload,
|
||||
false,
|
||||
|
|
|
|||
Loading…
Reference in New Issue