Extend runtime event mixing rules and descriptors
This commit is contained in:
parent
b4289abea5
commit
33a2f52d33
|
|
@ -0,0 +1,48 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.mixing;
|
||||||
|
|
||||||
|
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 java.time.OffsetDateTime;
|
||||||
|
|
||||||
|
public record RuntimeEventDescriptor(
|
||||||
|
EventHubEventDto event,
|
||||||
|
String eventIdentityKey,
|
||||||
|
String eventKey,
|
||||||
|
RuntimeEventSourceProfile sourceProfile,
|
||||||
|
String compatibleActivityKey,
|
||||||
|
String compatibleSupportEvidenceKey,
|
||||||
|
boolean driverActivityPoint,
|
||||||
|
boolean driverCardUsagePoint,
|
||||||
|
boolean supportEvidenceCandidate
|
||||||
|
) {
|
||||||
|
public EventDomain eventDomain() {
|
||||||
|
return event == null ? null : event.eventDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventType eventType() {
|
||||||
|
return event == null ? null : event.eventType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventLifecycle lifecycle() {
|
||||||
|
return event == null ? null : event.lifecycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime occurredAt() {
|
||||||
|
return event == null ? null : event.occurredAt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String extractionCode() {
|
||||||
|
return sourceProfile == null ? null : sourceProfile.extractionCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String keyFor(String equivalenceType) {
|
||||||
|
return switch (equivalenceType) {
|
||||||
|
case RuntimeEventMixingRule.EQUIVALENCE_EXACT_EVENT_KEY -> eventKey;
|
||||||
|
case RuntimeEventMixingRule.EQUIVALENCE_COMPATIBLE_ACTIVITY_KEY -> compatibleActivityKey;
|
||||||
|
case RuntimeEventMixingRule.EQUIVALENCE_COMPATIBLE_SUPPORT_KEY -> compatibleSupportEvidenceKey;
|
||||||
|
default -> eventKey;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,301 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.mixing;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventDomain;
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.dto.EventLifecycle;
|
||||||
|
import at.procon.eventhub.dto.GeoPointDto;
|
||||||
|
import at.procon.eventhub.processing.support.RuntimeEntityReferenceResolver;
|
||||||
|
import at.procon.eventhub.processing.support.RuntimeEventIdentityResolver;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class RuntimeEventDescriptorFactory {
|
||||||
|
|
||||||
|
public List<RuntimeEventDescriptor> describeSorted(List<EventHubEventDto> events) {
|
||||||
|
return sort(events).stream()
|
||||||
|
.map(this::describe)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RuntimeEventDescriptor describe(EventHubEventDto event) {
|
||||||
|
RuntimeEventSourceProfile profile = sourceProfile(event);
|
||||||
|
return new RuntimeEventDescriptor(
|
||||||
|
event,
|
||||||
|
eventIdentityKey(event),
|
||||||
|
RuntimeEventIdentityResolver.canonicalEventKey(event),
|
||||||
|
profile,
|
||||||
|
compatibleActivityKey(event),
|
||||||
|
compatibleSupportEvidenceKey(event),
|
||||||
|
isDriverActivityPoint(event),
|
||||||
|
isDriverCardUsagePoint(event),
|
||||||
|
isSupportEvidenceCandidate(event)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EventHubEventDto> sort(List<EventHubEventDto> events) {
|
||||||
|
return (events == null ? List.<EventHubEventDto>of() : events).stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.sorted(eventComparator())
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDriverActivityPoint(EventHubEventDto event) {
|
||||||
|
return event != null
|
||||||
|
&& event.eventDomain() == EventDomain.DRIVER_ACTIVITY
|
||||||
|
&& (event.lifecycle() == EventLifecycle.START || event.lifecycle() == EventLifecycle.END)
|
||||||
|
&& event.occurredAt() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDriverCardUsagePoint(EventHubEventDto event) {
|
||||||
|
return event != null
|
||||||
|
&& event.eventDomain() == EventDomain.DRIVER_CARD
|
||||||
|
&& (event.lifecycle() == EventLifecycle.INSERT || event.lifecycle() == EventLifecycle.WITHDRAW)
|
||||||
|
&& event.occurredAt() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSupportEvidenceCandidate(EventHubEventDto event) {
|
||||||
|
return event != null && !isDriverActivityPoint(event) && !isDriverCardUsagePoint(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RuntimeEventSourceProfile sourceProfile(EventHubEventDto event) {
|
||||||
|
JsonNode raw = rawPayload(event);
|
||||||
|
String sourceKind = firstNonBlank(text(raw, "sourceKind"), sourceKind(event));
|
||||||
|
String extractionCode = firstNonBlank(
|
||||||
|
text(raw, "extractionCode"),
|
||||||
|
fileSessionExtractionCode(event, sourceKind),
|
||||||
|
extractionCodeFromExternalSourceEventId(event)
|
||||||
|
);
|
||||||
|
String sourceSystem = firstNonBlank(
|
||||||
|
text(raw, "sourceSystem"),
|
||||||
|
sourceProvider(event),
|
||||||
|
sourceSystemFromExternalSourceEventId(event)
|
||||||
|
);
|
||||||
|
if (sourceSystem == null && (extractionCode != null || isTachographFileSessionEvent(event))) {
|
||||||
|
sourceSystem = "TACHOGRAPH";
|
||||||
|
}
|
||||||
|
return new RuntimeEventSourceProfile(
|
||||||
|
normalizeUpper(sourceSystem),
|
||||||
|
normalizeUpper(sourceKind),
|
||||||
|
normalizeUpper(extractionCode)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String eventIdentityKey(EventHubEventDto event) {
|
||||||
|
if (event == null) {
|
||||||
|
return "<null>";
|
||||||
|
}
|
||||||
|
return firstNonBlank(
|
||||||
|
event.externalSourceEventId(),
|
||||||
|
event.eventId() == null ? null : event.eventId().toString(),
|
||||||
|
RuntimeEventIdentityResolver.canonicalEventKey(event)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comparator<EventHubEventDto> eventComparator() {
|
||||||
|
return Comparator.comparing(EventHubEventDto::occurredAt, Comparator.nullsLast(Comparator.naturalOrder()))
|
||||||
|
.thenComparing(event -> event.eventDomain() == null ? "" : event.eventDomain().name())
|
||||||
|
.thenComparing(event -> event.eventType() == null ? "" : event.eventType().name())
|
||||||
|
.thenComparing(event -> event.lifecycle() == null ? "" : event.lifecycle().name())
|
||||||
|
.thenComparing(event -> sourceProfile(event).extractionCode(), Comparator.nullsLast(String::compareTo))
|
||||||
|
.thenComparing(EventHubEventDto::externalSourceEventId, Comparator.nullsLast(String::compareTo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String fileSessionExtractionCode(EventHubEventDto event, String sourceKind) {
|
||||||
|
if (!isTachographFileSessionEvent(event)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String normalizedSourceKind = normalizeUpper(sourceKind);
|
||||||
|
if (normalizedSourceKind == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (event != null && event.eventDomain() == EventDomain.DRIVER_ACTIVITY) {
|
||||||
|
return switch (normalizedSourceKind) {
|
||||||
|
case "DRIVER_CARD" -> "CARD_ACTIVITY";
|
||||||
|
case "VEHICLE_UNIT" -> "VU_ACTIVITY";
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (event != null && event.eventDomain() == EventDomain.DRIVER_CARD) {
|
||||||
|
return switch (normalizedSourceKind) {
|
||||||
|
case "DRIVER_CARD" -> "CARD_VEHICLES_USED";
|
||||||
|
case "VEHICLE_UNIT" -> "IW_CYCLE";
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (event == null || event.eventDomain() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String prefix = switch (normalizedSourceKind) {
|
||||||
|
case "DRIVER_CARD" -> "CARD";
|
||||||
|
case "VEHICLE_UNIT" -> "VU";
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
if (prefix == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return switch (event.eventDomain()) {
|
||||||
|
case POSITION -> prefix + "_POSITION";
|
||||||
|
case PLACE -> prefix + "_PLACE";
|
||||||
|
case BORDER_CROSSING -> prefix + "_BORDER_CROSSING";
|
||||||
|
case SPEEDING -> Objects.equals("VU", prefix) ? "SPEEDING_EVENTS" : null;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTachographFileSessionEvent(EventHubEventDto event) {
|
||||||
|
if (event == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String packageKind = event.sourcePackageRef() == null ? null : normalizeUpper(event.sourcePackageRef().packageKind());
|
||||||
|
if (Objects.equals("TACHOGRAPH_FILE_SESSION", packageKind)
|
||||||
|
|| Objects.equals("COMPOSITE_TACHOGRAPH_FILE_SESSION", packageKind)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String provider = sourceProvider(event);
|
||||||
|
if (Objects.equals("TACHOGRAPH_FILE_SESSION", normalizeUpper(provider))
|
||||||
|
|| Objects.equals("COMPOSITE_TACHOGRAPH_FILE_SESSION", normalizeUpper(provider))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String sourceKey = event.packageInfo() == null || event.packageInfo().eventSource() == null
|
||||||
|
? null
|
||||||
|
: normalizeUpper(event.packageInfo().eventSource().sourceKey());
|
||||||
|
if (Objects.equals("TACHOGRAPH_FILE_SESSION", sourceKey)
|
||||||
|
|| Objects.equals("COMPOSITE_TACHOGRAPH_FILE_SESSION", sourceKey)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String externalId = event.externalSourceEventId();
|
||||||
|
return externalId != null
|
||||||
|
&& (externalId.startsWith("TACHOGRAPH_FILE_SESSION:")
|
||||||
|
|| externalId.startsWith("COMPOSITE_TACHOGRAPH_FILE_SESSION:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String compatibleActivityKey(EventHubEventDto event) {
|
||||||
|
JsonNode raw = rawPayload(event);
|
||||||
|
return String.join("|",
|
||||||
|
"ACTIVITY_COMPATIBLE",
|
||||||
|
nullToEmpty(event == null || event.packageInfo() == null ? null : event.packageInfo().tenantKey()),
|
||||||
|
nullToEmpty(RuntimeEntityReferenceResolver.driverKey(event)),
|
||||||
|
nullToEmpty(event == null || event.eventDomain() == null ? null : event.eventDomain().name()),
|
||||||
|
nullToEmpty(event == null || event.eventType() == null ? null : event.eventType().name()),
|
||||||
|
nullToEmpty(event == null || event.lifecycle() == null ? null : event.lifecycle().name()),
|
||||||
|
normalizeTime(event == null ? null : event.occurredAt()),
|
||||||
|
nullToEmpty(RuntimeEntityReferenceResolver.registrationKey(event)),
|
||||||
|
nullToEmpty(firstNonBlank(text(raw, "startedAt"), text(raw, "intervalStartedAt"))),
|
||||||
|
nullToEmpty(firstNonBlank(text(raw, "endedAt"), text(raw, "intervalEndedAt"))),
|
||||||
|
nullToEmpty(firstNonBlank(text(raw, "slot"), text(raw, "cardSlot"))),
|
||||||
|
nullToEmpty(text(raw, "cardStatus")),
|
||||||
|
nullToEmpty(text(raw, "drivingStatus"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String compatibleSupportEvidenceKey(EventHubEventDto event) {
|
||||||
|
JsonNode raw = rawPayload(event);
|
||||||
|
GeoPointDto position = event == null ? null : event.position();
|
||||||
|
return String.join("|",
|
||||||
|
"SUPPORT_COMPATIBLE",
|
||||||
|
nullToEmpty(event == null || event.packageInfo() == null ? null : event.packageInfo().tenantKey()),
|
||||||
|
nullToEmpty(RuntimeEntityReferenceResolver.driverKey(event)),
|
||||||
|
nullToEmpty(event == null || event.eventDomain() == null ? null : event.eventDomain().name()),
|
||||||
|
nullToEmpty(event == null || event.eventType() == null ? null : event.eventType().name()),
|
||||||
|
nullToEmpty(event == null || event.lifecycle() == null ? null : event.lifecycle().name()),
|
||||||
|
normalizeTime(event == null ? null : event.occurredAt()),
|
||||||
|
nullToEmpty(RuntimeEntityReferenceResolver.registrationKey(event)),
|
||||||
|
nullToEmpty(firstNonBlank(text(raw, "latitude"), position == null || position.latitude() == null ? null : position.latitude().toPlainString())),
|
||||||
|
nullToEmpty(firstNonBlank(text(raw, "longitude"), position == null || position.longitude() == null ? null : position.longitude().toPlainString())),
|
||||||
|
nullToEmpty(firstNonBlank(text(raw, "odometerM"), event == null || event.odometerM() == null ? null : String.valueOf(event.odometerM()))),
|
||||||
|
nullToEmpty(firstNonBlank(text(raw, "country"), detailText(event, "country"))),
|
||||||
|
nullToEmpty(firstNonBlank(text(raw, "region"), detailText(event, "region"))),
|
||||||
|
nullToEmpty(firstNonBlank(text(raw, "countryFrom"), detailText(event, "countryFrom"))),
|
||||||
|
nullToEmpty(firstNonBlank(text(raw, "countryTo"), detailText(event, "countryTo"))),
|
||||||
|
nullToEmpty(firstNonBlank(text(raw, "operation"), detailText(event, "operation")))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode rawPayload(EventHubEventDto event) {
|
||||||
|
return RuntimeEntityReferenceResolver.rawPayload(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sourceKind(EventHubEventDto event) {
|
||||||
|
return event == null || event.packageInfo() == null || event.packageInfo().eventSource() == null
|
||||||
|
? null
|
||||||
|
: event.packageInfo().eventSource().sourceKind();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sourceProvider(EventHubEventDto event) {
|
||||||
|
return event == null || event.packageInfo() == null || event.packageInfo().eventSource() == null
|
||||||
|
? null
|
||||||
|
: event.packageInfo().eventSource().providerKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sourceSystemFromExternalSourceEventId(EventHubEventDto event) {
|
||||||
|
String externalId = event == null ? null : event.externalSourceEventId();
|
||||||
|
if (externalId == null || externalId.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String[] parts = externalId.split(":");
|
||||||
|
return parts.length >= 1 ? parts[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractionCodeFromExternalSourceEventId(EventHubEventDto event) {
|
||||||
|
String externalId = event == null ? null : event.externalSourceEventId();
|
||||||
|
if (externalId == null || externalId.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String[] parts = externalId.split(":");
|
||||||
|
return parts.length >= 2 ? parts[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String detailText(EventHubEventDto event, String field) {
|
||||||
|
if (event == null || event.eventDetails() == null || event.eventDetails().attributes() == null || field == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JsonNode value = event.eventDetails().attributes().get(field);
|
||||||
|
if (value == null || value.isNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String text = value.asText(null);
|
||||||
|
return text == null || text.isBlank() ? null : text.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String text(JsonNode node, String field) {
|
||||||
|
if (node == null || field == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JsonNode value = node.get(field);
|
||||||
|
if (value == null || value.isNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String text = value.asText(null);
|
||||||
|
return text == null || text.isBlank() ? null : text.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonBlank(String... values) {
|
||||||
|
if (values == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (String value : values) {
|
||||||
|
if (value != null && !value.isBlank()) {
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeUpper(String value) {
|
||||||
|
return value == null || value.isBlank() ? null : value.trim().toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String nullToEmpty(Object value) {
|
||||||
|
return value == null ? "" : String.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeTime(OffsetDateTime value) {
|
||||||
|
return value == null ? "" : value.toInstant().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.mixing;
|
||||||
|
|
||||||
|
public enum RuntimeEventMixingChannel {
|
||||||
|
ACTIVITY_TIMELINE,
|
||||||
|
VEHICLE_USAGE,
|
||||||
|
SUPPORT_EVIDENCE,
|
||||||
|
VALIDATION,
|
||||||
|
AUDIT
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.mixing;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventDomain;
|
||||||
|
import at.procon.eventhub.dto.EventLifecycle;
|
||||||
|
import at.procon.eventhub.dto.EventType;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public record RuntimeEventMixingRule(
|
||||||
|
String ruleId,
|
||||||
|
RuntimeEventMixingChannel channel,
|
||||||
|
String equivalenceType,
|
||||||
|
Set<EventDomain> eventDomains,
|
||||||
|
Set<EventType> eventTypes,
|
||||||
|
Set<EventLifecycle> lifecycles,
|
||||||
|
Set<String> primaryExtractionCodes,
|
||||||
|
Set<String> secondaryExtractionCodes,
|
||||||
|
RuntimeResolvedEventRole primaryRole,
|
||||||
|
RuntimeResolvedEventRole secondaryRole,
|
||||||
|
String decision,
|
||||||
|
String reason
|
||||||
|
) {
|
||||||
|
public static final String EQUIVALENCE_EXACT_EVENT_KEY = "EXACT_EVENT_KEY";
|
||||||
|
public static final String EQUIVALENCE_COMPATIBLE_ACTIVITY_KEY = "COMPATIBLE_ACTIVITY_KEY";
|
||||||
|
public static final String EQUIVALENCE_COMPATIBLE_SUPPORT_KEY = "COMPATIBLE_SUPPORT_KEY";
|
||||||
|
|
||||||
|
public RuntimeEventMixingRule {
|
||||||
|
eventDomains = eventDomains == null ? Set.of() : Set.copyOf(eventDomains);
|
||||||
|
eventTypes = eventTypes == null ? Set.of() : Set.copyOf(eventTypes);
|
||||||
|
lifecycles = lifecycles == null ? Set.of() : Set.copyOf(lifecycles);
|
||||||
|
primaryExtractionCodes = normalize(primaryExtractionCodes);
|
||||||
|
secondaryExtractionCodes = normalize(secondaryExtractionCodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(RuntimeEventDescriptor descriptor) {
|
||||||
|
if (descriptor == null || descriptor.event() == null || descriptor.sourceProfile() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!descriptor.sourceProfile().isTachographRuntimeSource()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!eventDomains.isEmpty() && !eventDomains.contains(descriptor.eventDomain())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!eventTypes.isEmpty() && !eventTypes.contains(descriptor.eventType())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!lifecycles.isEmpty() && !lifecycles.contains(descriptor.lifecycle())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (channel == RuntimeEventMixingChannel.ACTIVITY_TIMELINE && !descriptor.driverActivityPoint()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (channel == RuntimeEventMixingChannel.SUPPORT_EVIDENCE && !descriptor.supportEvidenceCandidate()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String extractionCode = descriptor.extractionCode();
|
||||||
|
return primaryExtractionCodes.contains(extractionCode) || secondaryExtractionCodes.contains(extractionCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrimary(RuntimeEventDescriptor descriptor) {
|
||||||
|
return descriptor != null && primaryExtractionCodes.contains(descriptor.extractionCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSecondary(RuntimeEventDescriptor descriptor) {
|
||||||
|
return descriptor != null && secondaryExtractionCodes.contains(descriptor.extractionCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> normalize(Set<String> values) {
|
||||||
|
if (values == null || values.isEmpty()) {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
return values.stream()
|
||||||
|
.filter(value -> value != null && !value.isBlank())
|
||||||
|
.map(value -> value.trim().toUpperCase(java.util.Locale.ROOT))
|
||||||
|
.collect(java.util.stream.Collectors.toUnmodifiableSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.mixing;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventDomain;
|
||||||
|
import at.procon.eventhub.dto.EventLifecycle;
|
||||||
|
import at.procon.eventhub.dto.EventType;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class RuntimeEventMixingRuleRegistry {
|
||||||
|
|
||||||
|
public List<RuntimeEventMixingRule> rulesForMode(String mode) {
|
||||||
|
if (RuntimeEventMixingService.MODE_OFF.equals(mode)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return List.of(
|
||||||
|
tachographCardVuActivityExactEventKey(),
|
||||||
|
tachographCardVuSupportExactEventKey(),
|
||||||
|
tachographCardVuActivityCompatibleKey(),
|
||||||
|
tachographCardVuSupportCompatibleKey()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimeEventMixingRule tachographCardVuActivityExactEventKey() {
|
||||||
|
return new RuntimeEventMixingRule(
|
||||||
|
RuntimeEventMixingService.RULE_TACHOGRAPH_CARD_VU_ACTIVITY_SAME_EVENT_KEY,
|
||||||
|
RuntimeEventMixingChannel.ACTIVITY_TIMELINE,
|
||||||
|
RuntimeEventMixingRule.EQUIVALENCE_EXACT_EVENT_KEY,
|
||||||
|
Set.of(EventDomain.DRIVER_ACTIVITY),
|
||||||
|
Set.of(EventType.DRIVE, EventType.BREAK_REST, EventType.AVAILABILITY, EventType.WORK, EventType.UNKNOWN_ACTIVITY),
|
||||||
|
Set.of(EventLifecycle.START, EventLifecycle.END),
|
||||||
|
Set.of("CARD_ACTIVITY"),
|
||||||
|
Set.of("VU_ACTIVITY"),
|
||||||
|
RuntimeResolvedEventRole.FUSED_PRIMARY,
|
||||||
|
RuntimeResolvedEventRole.SUPPRESSED_DUPLICATE,
|
||||||
|
"FUSED_PRIMARY_SELECTED",
|
||||||
|
"CARD_ACTIVITY and VU_ACTIVITY describe the same driver activity point. CARD_ACTIVITY is kept as primary for the activity timeline; VU_ACTIVITY is suppressed from activity intervalization."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimeEventMixingRule tachographCardVuActivityCompatibleKey() {
|
||||||
|
return new RuntimeEventMixingRule(
|
||||||
|
RuntimeEventMixingService.RULE_TACHOGRAPH_CARD_VU_ACTIVITY_COMPATIBLE_KEY,
|
||||||
|
RuntimeEventMixingChannel.ACTIVITY_TIMELINE,
|
||||||
|
RuntimeEventMixingRule.EQUIVALENCE_COMPATIBLE_ACTIVITY_KEY,
|
||||||
|
Set.of(EventDomain.DRIVER_ACTIVITY),
|
||||||
|
Set.of(EventType.DRIVE, EventType.BREAK_REST, EventType.AVAILABILITY, EventType.WORK, EventType.UNKNOWN_ACTIVITY),
|
||||||
|
Set.of(EventLifecycle.START, EventLifecycle.END),
|
||||||
|
Set.of("CARD_ACTIVITY"),
|
||||||
|
Set.of("VU_ACTIVITY"),
|
||||||
|
RuntimeResolvedEventRole.FUSED_PRIMARY,
|
||||||
|
RuntimeResolvedEventRole.SUPPRESSED_DUPLICATE,
|
||||||
|
"FUSED_PRIMARY_SELECTED",
|
||||||
|
"CARD_ACTIVITY and VU_ACTIVITY describe a compatible driver activity point. CARD_ACTIVITY is kept as primary for the activity timeline; VU_ACTIVITY is suppressed from activity intervalization."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimeEventMixingRule tachographCardVuSupportExactEventKey() {
|
||||||
|
return new RuntimeEventMixingRule(
|
||||||
|
RuntimeEventMixingService.RULE_TACHOGRAPH_CARD_VU_SUPPORT_SAME_EVENT_KEY,
|
||||||
|
RuntimeEventMixingChannel.SUPPORT_EVIDENCE,
|
||||||
|
RuntimeEventMixingRule.EQUIVALENCE_EXACT_EVENT_KEY,
|
||||||
|
Set.of(EventDomain.POSITION, EventDomain.PLACE, EventDomain.BORDER_CROSSING),
|
||||||
|
Set.of(EventType.POSITION_RECORDED, EventType.WORKING_DAY_PLACE_RECORDED,
|
||||||
|
EventType.BORDER_INBOUND, EventType.BORDER_OUTBOUND, EventType.BORDER_OUT_EU),
|
||||||
|
Set.of(EventLifecycle.SNAPSHOT, EventLifecycle.INBOUND, EventLifecycle.OUTBOUND, EventLifecycle.OUT_EU),
|
||||||
|
Set.of("CARD_POSITION", "CARD_PLACE", "CARD_BORDER_CROSSING"),
|
||||||
|
Set.of("VU_POSITION", "VU_PLACE", "VU_BORDER_CROSSING"),
|
||||||
|
RuntimeResolvedEventRole.FUSED_PRIMARY,
|
||||||
|
RuntimeResolvedEventRole.SUPPRESSED_DUPLICATE,
|
||||||
|
"FUSED_PRIMARY_SELECTED",
|
||||||
|
"CARD and VU support evidence describe the same semantic event. CARD evidence is kept as primary support evidence; VU evidence is suppressed from support-evidence normalization but retained as audit/corroborating evidence."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimeEventMixingRule tachographCardVuSupportCompatibleKey() {
|
||||||
|
return new RuntimeEventMixingRule(
|
||||||
|
RuntimeEventMixingService.RULE_TACHOGRAPH_CARD_VU_SUPPORT_COMPATIBLE_KEY,
|
||||||
|
RuntimeEventMixingChannel.SUPPORT_EVIDENCE,
|
||||||
|
RuntimeEventMixingRule.EQUIVALENCE_COMPATIBLE_SUPPORT_KEY,
|
||||||
|
Set.of(EventDomain.POSITION, EventDomain.PLACE, EventDomain.BORDER_CROSSING),
|
||||||
|
Set.of(EventType.POSITION_RECORDED, EventType.WORKING_DAY_PLACE_RECORDED,
|
||||||
|
EventType.BORDER_INBOUND, EventType.BORDER_OUTBOUND, EventType.BORDER_OUT_EU),
|
||||||
|
Set.of(EventLifecycle.SNAPSHOT, EventLifecycle.INBOUND, EventLifecycle.OUTBOUND, EventLifecycle.OUT_EU),
|
||||||
|
Set.of("CARD_POSITION", "CARD_PLACE", "CARD_BORDER_CROSSING"),
|
||||||
|
Set.of("VU_POSITION", "VU_PLACE", "VU_BORDER_CROSSING"),
|
||||||
|
RuntimeResolvedEventRole.FUSED_PRIMARY,
|
||||||
|
RuntimeResolvedEventRole.SUPPRESSED_DUPLICATE,
|
||||||
|
"FUSED_PRIMARY_SELECTED",
|
||||||
|
"CARD and VU support evidence describe a compatible semantic event. CARD evidence is kept as primary support evidence; VU evidence is suppressed from support-evidence normalization. Vehicle/VIN identity from the VU event is copied to the primary event when the card event has weaker vehicle identity."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.mixing;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record RuntimeResolvedEvent(
|
||||||
|
EventHubEventDto event,
|
||||||
|
RuntimeEventMixingChannel channel,
|
||||||
|
RuntimeResolvedEventRole role,
|
||||||
|
String ruleId,
|
||||||
|
String equivalenceType,
|
||||||
|
String effectiveEventKey,
|
||||||
|
String primaryExternalSourceEventId,
|
||||||
|
List<String> relatedExternalSourceEventIds,
|
||||||
|
String reason
|
||||||
|
) {
|
||||||
|
public RuntimeResolvedEvent {
|
||||||
|
relatedExternalSourceEventIds = relatedExternalSourceEventIds == null
|
||||||
|
? List.of()
|
||||||
|
: List.copyOf(relatedExternalSourceEventIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package at.procon.eventhub.processing.eventprocessing.mixing;
|
||||||
|
|
||||||
|
public enum RuntimeResolvedEventRole {
|
||||||
|
PRIMARY,
|
||||||
|
FUSED_PRIMARY,
|
||||||
|
SECONDARY_CORROBORATING,
|
||||||
|
FALLBACK_PRIMARY,
|
||||||
|
SUPPRESSED_DUPLICATE,
|
||||||
|
SUPPORT_ONLY,
|
||||||
|
CONFLICTING_EVIDENCE,
|
||||||
|
VEHICLE_USAGE_INPUT
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue