Compare commits
No commits in common. "04d7bf513e0a1ba766457b1c88d07c2cbfdbe14a" and "317983eba82e95e59a7165f81a21f4f2d1ba9a9b" have entirely different histories.
04d7bf513e
...
317983eba8
|
|
@ -356,23 +356,12 @@ public class EventHubProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Processing {
|
public static class Processing {
|
||||||
private TimelineInputMode timelineInputMode = TimelineInputMode.INTERVALS;
|
|
||||||
private int operatingSplitIdleHours = 7;
|
private int operatingSplitIdleHours = 7;
|
||||||
private int significantDrivingMinutes = 3;
|
private int significantDrivingMinutes = 3;
|
||||||
private int minimumRestPeriodMinutes = 720;
|
private int minimumRestPeriodMinutes = 720;
|
||||||
private int mergeGapSeconds = 0;
|
private int mergeGapSeconds = 0;
|
||||||
private int gapDetectionToleranceSeconds = 0;
|
private int gapDetectionToleranceSeconds = 0;
|
||||||
|
|
||||||
public TimelineInputMode getTimelineInputMode() {
|
|
||||||
return timelineInputMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimelineInputMode(TimelineInputMode timelineInputMode) {
|
|
||||||
if (timelineInputMode != null) {
|
|
||||||
this.timelineInputMode = timelineInputMode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getOperatingSplitIdleHours() {
|
public int getOperatingSplitIdleHours() {
|
||||||
return operatingSplitIdleHours;
|
return operatingSplitIdleHours;
|
||||||
}
|
}
|
||||||
|
|
@ -414,11 +403,6 @@ public class EventHubProperties {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TimelineInputMode {
|
|
||||||
INTERVALS,
|
|
||||||
EVENTS
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LegalRequirements {
|
public static class LegalRequirements {
|
||||||
private static final String DEFAULT_BASE_URL = "https://legalrequirements.services.bytebar.eu/ODataV4/LR";
|
private static final String DEFAULT_BASE_URL = "https://legalrequirements.services.bytebar.eu/ODataV4/LR";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,389 +0,0 @@
|
||||||
package at.procon.eventhub.persistence;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.DriverCardRefDto;
|
|
||||||
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.GeoPointDto;
|
|
||||||
import at.procon.eventhub.dto.ImportScopeDto;
|
|
||||||
import at.procon.eventhub.dto.ImportScopeType;
|
|
||||||
import at.procon.eventhub.dto.SourceGroupRefDto;
|
|
||||||
import at.procon.eventhub.dto.SourceGroupType;
|
|
||||||
import at.procon.eventhub.dto.SourcePackageRefDto;
|
|
||||||
import at.procon.eventhub.dto.VehicleRefDto;
|
|
||||||
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
public class EventHubEventReadRepository {
|
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
|
||||||
private final ObjectMapper objectMapper;
|
|
||||||
|
|
||||||
public EventHubEventReadRepository(JdbcTemplate jdbcTemplate, ObjectMapper objectMapper) {
|
|
||||||
this.jdbcTemplate = jdbcTemplate;
|
|
||||||
this.objectMapper = objectMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<EventHubEventDto> findEvents(
|
|
||||||
UnifiedDriverEventsRequest request,
|
|
||||||
String providerKey,
|
|
||||||
List<String> sourceKinds
|
|
||||||
) {
|
|
||||||
StringBuilder sql = new StringBuilder(
|
|
||||||
"""
|
|
||||||
select
|
|
||||||
event.id,
|
|
||||||
event.external_source_event_id,
|
|
||||||
event.occurred_at,
|
|
||||||
event.received_partner_at,
|
|
||||||
event.received_hub_at,
|
|
||||||
event.event_domain,
|
|
||||||
event.event_type,
|
|
||||||
event.lifecycle,
|
|
||||||
event.odometer_m,
|
|
||||||
ST_Y(event.position::geometry) as latitude,
|
|
||||||
ST_X(event.position::geometry) as longitude,
|
|
||||||
event.payload,
|
|
||||||
event.manual_entry,
|
|
||||||
source.provider_key,
|
|
||||||
source.source_kind,
|
|
||||||
source.source_key,
|
|
||||||
source.source_instance_key,
|
|
||||||
source.tenant_provider_setting_key,
|
|
||||||
source.external_fleet_key,
|
|
||||||
package.id as data_package_id,
|
|
||||||
package.tenant_key,
|
|
||||||
package.event_family,
|
|
||||||
package.business_date,
|
|
||||||
package.external_package_id,
|
|
||||||
package.source_group_type,
|
|
||||||
package.source_group_entity_id,
|
|
||||||
package.source_group_code,
|
|
||||||
package.source_group_name,
|
|
||||||
package.import_scope_type,
|
|
||||||
package.root_source_org_entity_id,
|
|
||||||
package.root_source_org_code,
|
|
||||||
package.root_source_org_name,
|
|
||||||
package.include_children,
|
|
||||||
package.occurred_from as package_occurred_from,
|
|
||||||
package.occurred_to as package_occurred_to,
|
|
||||||
package.source_package_kind as package_source_package_kind,
|
|
||||||
package.source_package_period_from,
|
|
||||||
package.source_package_period_to,
|
|
||||||
package.source_package_imported_at,
|
|
||||||
event.source_package_id,
|
|
||||||
event.source_package_entity_id,
|
|
||||||
detail.detail_type,
|
|
||||||
detail.attributes,
|
|
||||||
driver.source_driver_entity_id,
|
|
||||||
driver_card.nation as driver_card_nation,
|
|
||||||
driver_card.card_number as driver_card_number,
|
|
||||||
vehicle.source_vehicle_entity_id,
|
|
||||||
vehicle.vin,
|
|
||||||
registration.source_registration_entity_id,
|
|
||||||
registration.nation as vehicle_registration_nation,
|
|
||||||
registration.registration_number as vehicle_registration_number,
|
|
||||||
event.driver_id,
|
|
||||||
event.vehicle_id,
|
|
||||||
event.vehicle_registration_id
|
|
||||||
from eventhub.event event
|
|
||||||
join eventhub.event_source source on source.id = event.event_source_id
|
|
||||||
join eventhub.data_package package on package.id = event.data_package_id
|
|
||||||
left join lateral (
|
|
||||||
select detail_type, attributes
|
|
||||||
from eventhub.event_detail detail
|
|
||||||
where detail.event_occurred_at = event.occurred_at
|
|
||||||
and detail.event_id = event.id
|
|
||||||
order by detail_type
|
|
||||||
limit 1
|
|
||||||
) detail on true
|
|
||||||
left join eventhub.driver driver on driver.id = event.driver_id
|
|
||||||
left join eventhub.driver_card driver_card on driver_card.id = event.driver_card_id
|
|
||||||
left join eventhub.vehicle vehicle on vehicle.id = event.vehicle_id
|
|
||||||
left join eventhub.vehicle_registration registration on registration.id = event.vehicle_registration_id
|
|
||||||
where package.tenant_key = ?
|
|
||||||
and source.provider_key = ?
|
|
||||||
"""
|
|
||||||
);
|
|
||||||
|
|
||||||
List<Object> params = new ArrayList<>();
|
|
||||||
params.add(request.tenantKey());
|
|
||||||
params.add(providerKey);
|
|
||||||
|
|
||||||
if (sourceKinds != null && !sourceKinds.isEmpty()) {
|
|
||||||
sql.append(" and source.source_kind in (");
|
|
||||||
for (int i = 0; i < sourceKinds.size(); i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
sql.append(", ");
|
|
||||||
}
|
|
||||||
sql.append("?");
|
|
||||||
params.add(sourceKinds.get(i));
|
|
||||||
}
|
|
||||||
sql.append(")");
|
|
||||||
}
|
|
||||||
if (request.occurredFrom() != null) {
|
|
||||||
sql.append(" and event.occurred_at >= ?");
|
|
||||||
params.add(request.occurredFrom());
|
|
||||||
}
|
|
||||||
if (request.occurredTo() != null) {
|
|
||||||
sql.append(" and event.occurred_at <= ?");
|
|
||||||
params.add(request.occurredTo());
|
|
||||||
}
|
|
||||||
if (request.driverSourceEntityId() != null) {
|
|
||||||
sql.append(" and driver.source_driver_entity_id = ?");
|
|
||||||
params.add(request.driverSourceEntityId());
|
|
||||||
}
|
|
||||||
if (request.driverCardNumber() != null) {
|
|
||||||
sql.append(" and driver_card.card_number = ?");
|
|
||||||
params.add(request.driverCardNumber());
|
|
||||||
if (request.driverCardNation() != null) {
|
|
||||||
sql.append(" and driver_card.nation = ?");
|
|
||||||
params.add(request.driverCardNation());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (request.vehicleSourceEntityId() != null) {
|
|
||||||
sql.append(" and vehicle.source_vehicle_entity_id = ?");
|
|
||||||
params.add(request.vehicleSourceEntityId());
|
|
||||||
}
|
|
||||||
if (request.vin() != null) {
|
|
||||||
sql.append(" and vehicle.vin = ?");
|
|
||||||
params.add(request.vin());
|
|
||||||
}
|
|
||||||
if (request.registrationNumber() != null) {
|
|
||||||
sql.append(" and registration.registration_number = ?");
|
|
||||||
params.add(request.registrationNumber());
|
|
||||||
if (request.registrationNation() != null) {
|
|
||||||
sql.append(" and registration.nation = ?");
|
|
||||||
params.add(request.registrationNation());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.append(" order by event.occurred_at, event.event_domain, event.event_type, event.lifecycle, event.id");
|
|
||||||
|
|
||||||
return jdbcTemplate.query(
|
|
||||||
sql.toString(),
|
|
||||||
(rs, rowNum) -> mapEvent(rs),
|
|
||||||
params.toArray()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventHubEventDto mapEvent(ResultSet rs) throws SQLException {
|
|
||||||
DriverRefDto driverRef = driverRef(rs);
|
|
||||||
VehicleRefDto vehicleRef = vehicleRef(rs);
|
|
||||||
if ((driverRef == null || !driverRef.hasAnyReference()) && (vehicleRef == null || !vehicleRef.hasAnyReference())) {
|
|
||||||
throw new IllegalStateException("Loaded event does not have any driver or vehicle reference.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new EventHubEventDto(
|
|
||||||
uuid(rs, "id"),
|
|
||||||
rs.getString("external_source_event_id"),
|
|
||||||
driverRef,
|
|
||||||
vehicleRef,
|
|
||||||
rs.getObject("occurred_at", OffsetDateTime.class),
|
|
||||||
rs.getObject("received_partner_at", OffsetDateTime.class),
|
|
||||||
rs.getObject("received_hub_at", OffsetDateTime.class),
|
|
||||||
enumValue(EventDomain.class, rs.getString("event_domain"), EventDomain.TELEMATICS_DATA),
|
|
||||||
enumValue(EventType.class, rs.getString("event_type"), EventType.UNKNOWN_EVENT),
|
|
||||||
enumValue(EventLifecycle.class, rs.getString("lifecycle"), EventLifecycle.SNAPSHOT),
|
|
||||||
longValue(rs, "odometer_m"),
|
|
||||||
point(rs),
|
|
||||||
eventDetails(rs),
|
|
||||||
sourcePackageRef(rs),
|
|
||||||
json(rs.getString("payload")),
|
|
||||||
rs.getBoolean("manual_entry"),
|
|
||||||
packageInfo(rs)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DriverRefDto driverRef(ResultSet rs) throws SQLException {
|
|
||||||
String sourceEntityId = firstNonBlank(
|
|
||||||
rs.getString("source_driver_entity_id"),
|
|
||||||
syntheticId("EVENTHUB_DRIVER", uuid(rs, "driver_id"))
|
|
||||||
);
|
|
||||||
String cardNumber = rs.getString("driver_card_number");
|
|
||||||
DriverCardRefDto driverCard = cardNumber == null
|
|
||||||
? null
|
|
||||||
: new DriverCardRefDto(rs.getString("driver_card_nation"), cardNumber);
|
|
||||||
DriverRefDto driverRef = new DriverRefDto(sourceEntityId, driverCard);
|
|
||||||
return driverRef.hasAnyReference() ? driverRef : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private VehicleRefDto vehicleRef(ResultSet rs) throws SQLException {
|
|
||||||
VehicleRegistrationRefDto registration = null;
|
|
||||||
String registrationNumber = rs.getString("vehicle_registration_number");
|
|
||||||
if (registrationNumber != null) {
|
|
||||||
registration = new VehicleRegistrationRefDto(rs.getString("vehicle_registration_nation"), registrationNumber);
|
|
||||||
}
|
|
||||||
VehicleRefDto vehicleRef = new VehicleRefDto(
|
|
||||||
firstNonBlank(
|
|
||||||
rs.getString("source_vehicle_entity_id"),
|
|
||||||
syntheticId("EVENTHUB_VEHICLE", uuid(rs, "vehicle_id"))
|
|
||||||
),
|
|
||||||
rs.getString("vin"),
|
|
||||||
firstNonBlank(
|
|
||||||
rs.getString("source_registration_entity_id"),
|
|
||||||
syntheticId("EVENTHUB_VEHICLE_REGISTRATION", uuid(rs, "vehicle_registration_id"))
|
|
||||||
),
|
|
||||||
registration
|
|
||||||
);
|
|
||||||
return vehicleRef.hasAnyReference() ? vehicleRef : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventDetailsDto eventDetails(ResultSet rs) throws SQLException {
|
|
||||||
String detailType = rs.getString("detail_type");
|
|
||||||
if (detailType == null || detailType.isBlank()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new EventDetailsDto(detailType, json(rs.getString("attributes")));
|
|
||||||
}
|
|
||||||
|
|
||||||
private SourcePackageRefDto sourcePackageRef(ResultSet rs) throws SQLException {
|
|
||||||
String packageKind = rs.getString("package_source_package_kind");
|
|
||||||
String sourcePackageId = firstNonBlank(rs.getString("source_package_id"), null);
|
|
||||||
String sourceEntityId = firstNonBlank(rs.getString("source_package_entity_id"), null);
|
|
||||||
OffsetDateTime periodFrom = rs.getObject("source_package_period_from", OffsetDateTime.class);
|
|
||||||
OffsetDateTime periodTo = rs.getObject("source_package_period_to", OffsetDateTime.class);
|
|
||||||
OffsetDateTime importedAt = rs.getObject("source_package_imported_at", OffsetDateTime.class);
|
|
||||||
SourcePackageRefDto ref = new SourcePackageRefDto(
|
|
||||||
packageKind,
|
|
||||||
sourcePackageId,
|
|
||||||
sourceEntityId,
|
|
||||||
periodFrom,
|
|
||||||
periodTo,
|
|
||||||
importedAt
|
|
||||||
);
|
|
||||||
return ref.hasAnyReference() ? ref : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventHubPackageRequest packageInfo(ResultSet rs) throws SQLException {
|
|
||||||
EventSourceDto eventSource = new EventSourceDto(
|
|
||||||
rs.getString("provider_key"),
|
|
||||||
rs.getString("source_kind"),
|
|
||||||
rs.getString("source_key"),
|
|
||||||
rs.getString("source_instance_key"),
|
|
||||||
rs.getString("tenant_provider_setting_key"),
|
|
||||||
rs.getString("external_fleet_key")
|
|
||||||
);
|
|
||||||
SourceGroupRefDto sourceGroup = sourceGroup(rs);
|
|
||||||
ImportScopeDto importScope = importScope(rs);
|
|
||||||
String eventFamily = firstNonBlank(rs.getString("event_family"), rs.getString("event_domain"));
|
|
||||||
LocalDate businessDate = rs.getObject("business_date", LocalDate.class);
|
|
||||||
String externalPackageId = firstNonBlank(rs.getString("external_package_id"), rs.getString("data_package_id"));
|
|
||||||
return new EventHubPackageRequest(
|
|
||||||
rs.getString("tenant_key"),
|
|
||||||
eventSource,
|
|
||||||
sourceGroup,
|
|
||||||
importScope,
|
|
||||||
eventFamily,
|
|
||||||
businessDate,
|
|
||||||
externalPackageId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SourceGroupRefDto sourceGroup(ResultSet rs) throws SQLException {
|
|
||||||
String groupType = rs.getString("source_group_type");
|
|
||||||
String sourceEntityId = rs.getString("source_group_entity_id");
|
|
||||||
String code = rs.getString("source_group_code");
|
|
||||||
String name = rs.getString("source_group_name");
|
|
||||||
if (groupType == null && sourceEntityId == null && code == null && name == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new SourceGroupRefDto(
|
|
||||||
enumValue(SourceGroupType.class, groupType, null),
|
|
||||||
sourceEntityId,
|
|
||||||
code,
|
|
||||||
name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ImportScopeDto importScope(ResultSet rs) throws SQLException {
|
|
||||||
SourceGroupRefDto rootOrganisation = null;
|
|
||||||
String rootEntityId = rs.getString("root_source_org_entity_id");
|
|
||||||
String rootCode = rs.getString("root_source_org_code");
|
|
||||||
String rootName = rs.getString("root_source_org_name");
|
|
||||||
if (rootEntityId != null || rootCode != null || rootName != null) {
|
|
||||||
rootOrganisation = new SourceGroupRefDto(SourceGroupType.ORGANISATION, rootEntityId, rootCode, rootName);
|
|
||||||
}
|
|
||||||
return new ImportScopeDto(
|
|
||||||
enumValue(ImportScopeType.class, rs.getString("import_scope_type"), ImportScopeType.TENANT_ALL),
|
|
||||||
rootOrganisation,
|
|
||||||
rs.getBoolean("include_children"),
|
|
||||||
rs.getObject("package_occurred_from", OffsetDateTime.class),
|
|
||||||
rs.getObject("package_occurred_to", OffsetDateTime.class)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private GeoPointDto point(ResultSet rs) throws SQLException {
|
|
||||||
BigDecimal latitude = rs.getBigDecimal("latitude");
|
|
||||||
BigDecimal longitude = rs.getBigDecimal("longitude");
|
|
||||||
return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UUID uuid(ResultSet rs, String column) throws SQLException {
|
|
||||||
return (UUID) rs.getObject(column);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long longValue(ResultSet rs, String column) throws SQLException {
|
|
||||||
Object value = rs.getObject(column);
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (value instanceof Number number) {
|
|
||||||
return number.longValue();
|
|
||||||
}
|
|
||||||
return Long.parseLong(value.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonNode json(String value) {
|
|
||||||
try {
|
|
||||||
return value == null || value.isBlank()
|
|
||||||
? objectMapper.createObjectNode()
|
|
||||||
: objectMapper.readTree(value);
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
throw new IllegalArgumentException("Failed to parse JSON column.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String syntheticId(String prefix, UUID id) {
|
|
||||||
return id == null ? null : prefix + ":" + id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String firstNonBlank(String first, String second) {
|
|
||||||
if (first != null && !first.isBlank()) {
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
return second != null && !second.isBlank() ? second : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T extends Enum<T>> T enumValue(Class<T> type, String value, T fallback) {
|
|
||||||
if (value == null || value.isBlank()) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
String normalized = value.trim().toUpperCase(Locale.ROOT).replace('-', '_').replace(' ', '_');
|
|
||||||
try {
|
|
||||||
return Enum.valueOf(type, normalized);
|
|
||||||
} catch (IllegalArgumentException ignored) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
package at.procon.eventhub.processing.model;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public record UnifiedDriverEventsRequest(
|
|
||||||
UnifiedEventSourceFamily sourceFamily,
|
|
||||||
UUID sessionId,
|
|
||||||
String driverKey,
|
|
||||||
String tenantKey,
|
|
||||||
String driverSourceEntityId,
|
|
||||||
String driverCardNation,
|
|
||||||
String driverCardNumber,
|
|
||||||
String vehicleSourceEntityId,
|
|
||||||
String vin,
|
|
||||||
String registrationNation,
|
|
||||||
String registrationNumber,
|
|
||||||
OffsetDateTime occurredFrom,
|
|
||||||
OffsetDateTime occurredTo
|
|
||||||
) {
|
|
||||||
public UnifiedDriverEventsRequest {
|
|
||||||
Objects.requireNonNull(sourceFamily, "sourceFamily must not be null");
|
|
||||||
driverKey = normalize(driverKey);
|
|
||||||
tenantKey = normalize(tenantKey);
|
|
||||||
driverSourceEntityId = normalize(driverSourceEntityId);
|
|
||||||
driverCardNation = normalizeUpper(driverCardNation);
|
|
||||||
driverCardNumber = normalize(driverCardNumber);
|
|
||||||
vehicleSourceEntityId = normalize(vehicleSourceEntityId);
|
|
||||||
vin = normalizeUpper(vin);
|
|
||||||
registrationNation = normalizeUpper(registrationNation);
|
|
||||||
registrationNumber = normalize(registrationNumber);
|
|
||||||
if (occurredFrom != null && occurredTo != null && occurredTo.isBefore(occurredFrom)) {
|
|
||||||
throw new IllegalArgumentException("occurredTo must not be before occurredFrom");
|
|
||||||
}
|
|
||||||
if (sourceFamily == UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION) {
|
|
||||||
Objects.requireNonNull(sessionId, "sessionId must not be null");
|
|
||||||
if (driverKey == null) {
|
|
||||||
throw new IllegalArgumentException("driverKey must not be blank");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (tenantKey == null) {
|
|
||||||
throw new IllegalArgumentException("tenantKey must not be blank");
|
|
||||||
}
|
|
||||||
if (!hasDriverSelector(driverSourceEntityId, driverCardNumber)
|
|
||||||
&& !hasVehicleSelector(vehicleSourceEntityId, vin, registrationNumber)) {
|
|
||||||
throw new IllegalArgumentException("At least one driver or vehicle selector must be provided.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UnifiedDriverEventsRequest forTachographFileSession(
|
|
||||||
UUID sessionId,
|
|
||||||
String driverKey,
|
|
||||||
OffsetDateTime occurredFrom,
|
|
||||||
OffsetDateTime occurredTo
|
|
||||||
) {
|
|
||||||
return new UnifiedDriverEventsRequest(
|
|
||||||
UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION,
|
|
||||||
sessionId,
|
|
||||||
driverKey,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
occurredFrom,
|
|
||||||
occurredTo
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UnifiedDriverEventsRequest forTachographDbDriver(
|
|
||||||
String tenantKey,
|
|
||||||
String driverSourceEntityId,
|
|
||||||
String driverCardNation,
|
|
||||||
String driverCardNumber,
|
|
||||||
OffsetDateTime occurredFrom,
|
|
||||||
OffsetDateTime occurredTo
|
|
||||||
) {
|
|
||||||
return new UnifiedDriverEventsRequest(
|
|
||||||
UnifiedEventSourceFamily.TACHOGRAPH_DB,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
tenantKey,
|
|
||||||
driverSourceEntityId,
|
|
||||||
driverCardNation,
|
|
||||||
driverCardNumber,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
occurredFrom,
|
|
||||||
occurredTo
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UnifiedDriverEventsRequest forYellowFoxDbVehicle(
|
|
||||||
String tenantKey,
|
|
||||||
String vehicleSourceEntityId,
|
|
||||||
String vin,
|
|
||||||
String registrationNation,
|
|
||||||
String registrationNumber,
|
|
||||||
OffsetDateTime occurredFrom,
|
|
||||||
OffsetDateTime occurredTo
|
|
||||||
) {
|
|
||||||
return new UnifiedDriverEventsRequest(
|
|
||||||
UnifiedEventSourceFamily.YELLOWFOX_DB,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
tenantKey,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
vehicleSourceEntityId,
|
|
||||||
vin,
|
|
||||||
registrationNation,
|
|
||||||
registrationNumber,
|
|
||||||
occurredFrom,
|
|
||||||
occurredTo
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasDriverSelector() {
|
|
||||||
return hasDriverSelector(driverSourceEntityId, driverCardNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasVehicleSelector() {
|
|
||||||
return hasVehicleSelector(vehicleSourceEntityId, vin, registrationNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean hasDriverSelector(String driverSourceEntityId, String driverCardNumber) {
|
|
||||||
return driverSourceEntityId != null || driverCardNumber != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean hasVehicleSelector(String vehicleSourceEntityId, String vin, String registrationNumber) {
|
|
||||||
return vehicleSourceEntityId != null || vin != null || registrationNumber != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String normalize(String value) {
|
|
||||||
return value == null || value.isBlank() ? null : value.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String normalizeUpper(String value) {
|
|
||||||
return value == null || value.isBlank() ? null : value.trim().toUpperCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package at.procon.eventhub.processing.model;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public record UnifiedDriverTimelineRequest(
|
|
||||||
UnifiedEventSourceFamily sourceFamily,
|
|
||||||
UUID sessionId,
|
|
||||||
String driverKey
|
|
||||||
) {
|
|
||||||
public UnifiedDriverTimelineRequest {
|
|
||||||
Objects.requireNonNull(sourceFamily, "sourceFamily must not be null");
|
|
||||||
Objects.requireNonNull(sessionId, "sessionId must not be null");
|
|
||||||
if (driverKey == null || driverKey.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("driverKey must not be blank");
|
|
||||||
}
|
|
||||||
driverKey = driverKey.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UnifiedDriverTimelineRequest forTachographFileSession(
|
|
||||||
UUID sessionId,
|
|
||||||
String driverKey
|
|
||||||
) {
|
|
||||||
return new UnifiedDriverTimelineRequest(
|
|
||||||
UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION,
|
|
||||||
sessionId,
|
|
||||||
driverKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package at.procon.eventhub.processing.model;
|
|
||||||
|
|
||||||
public enum UnifiedEventSourceFamily {
|
|
||||||
TACHOGRAPH_FILE_SESSION,
|
|
||||||
TACHOGRAPH_DB,
|
|
||||||
YELLOWFOX_DB
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.persistence.EventHubEventReadRepository;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
|
||||||
import java.util.List;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class TachographDbUnifiedDriverEventSource implements UnifiedDriverEventSource {
|
|
||||||
|
|
||||||
private static final List<String> SOURCE_KINDS = List.of("DRIVER_CARD", "VEHICLE_UNIT");
|
|
||||||
|
|
||||||
private final EventHubEventReadRepository repository;
|
|
||||||
|
|
||||||
public TachographDbUnifiedDriverEventSource(EventHubEventReadRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(UnifiedDriverEventsRequest request) {
|
|
||||||
return request.sourceFamily() == UnifiedEventSourceFamily.TACHOGRAPH_DB;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) {
|
|
||||||
return repository.findEvents(request, "TACHOGRAPH", SOURCE_KINDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.DriverNotFoundInSessionException;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.DriverTimelineEventBuilder;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionNotFoundException;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionRepository;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class TachographFileSessionUnifiedDriverEventSource implements UnifiedDriverEventSource {
|
|
||||||
|
|
||||||
private final TachographFileSessionRepository repository;
|
|
||||||
private final DriverTimelineEventBuilder eventBuilder;
|
|
||||||
|
|
||||||
public TachographFileSessionUnifiedDriverEventSource(
|
|
||||||
TachographFileSessionRepository repository,
|
|
||||||
DriverTimelineEventBuilder eventBuilder
|
|
||||||
) {
|
|
||||||
this.repository = repository;
|
|
||||||
this.eventBuilder = eventBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(UnifiedDriverEventsRequest request) {
|
|
||||||
return request.sourceFamily() == UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) {
|
|
||||||
TachographFileSession session = repository.find(request.sessionId())
|
|
||||||
.orElseThrow(() -> new TachographFileSessionNotFoundException(request.sessionId()));
|
|
||||||
DriverExtractionSession driver = session.driversByKey().get(request.driverKey());
|
|
||||||
if (driver == null) {
|
|
||||||
throw new DriverNotFoundInSessionException(request.sessionId(), request.driverKey());
|
|
||||||
}
|
|
||||||
return eventBuilder.buildEvents(session, driver).stream()
|
|
||||||
.filter(event -> withinWindow(event.occurredAt(), request.occurredFrom(), request.occurredTo()))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean withinWindow(
|
|
||||||
OffsetDateTime occurredAt,
|
|
||||||
OffsetDateTime occurredFrom,
|
|
||||||
OffsetDateTime occurredTo
|
|
||||||
) {
|
|
||||||
if (occurredAt == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (occurredFrom != null && occurredAt.isBefore(occurredFrom)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return occurredTo == null || !occurredAt.isAfter(occurredTo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDriverTimelineRequest;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
|
||||||
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.service.DriverNotFoundInSessionException;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.EventBackedDriverTimelineBuilder;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionNotFoundException;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionRepository;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class TachographFileSessionUnifiedDriverTimelineSource implements UnifiedDriverTimelineSource {
|
|
||||||
|
|
||||||
private final TachographFileSessionRepository repository;
|
|
||||||
private final EventBackedDriverTimelineBuilder eventBackedDriverTimelineBuilder;
|
|
||||||
|
|
||||||
public TachographFileSessionUnifiedDriverTimelineSource(
|
|
||||||
TachographFileSessionRepository repository,
|
|
||||||
EventBackedDriverTimelineBuilder eventBackedDriverTimelineBuilder
|
|
||||||
) {
|
|
||||||
this.repository = repository;
|
|
||||||
this.eventBackedDriverTimelineBuilder = eventBackedDriverTimelineBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(UnifiedDriverTimelineRequest request) {
|
|
||||||
return request.sourceFamily() == UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ResolvedDriverTimeline loadDriverTimeline(UnifiedDriverTimelineRequest request) {
|
|
||||||
TachographFileSession session = repository.find(request.sessionId())
|
|
||||||
.orElseThrow(() -> new TachographFileSessionNotFoundException(request.sessionId()));
|
|
||||||
DriverExtractionSession driver = session.driversByKey().get(request.driverKey());
|
|
||||||
if (driver == null) {
|
|
||||||
throw new DriverNotFoundInSessionException(request.sessionId(), request.driverKey());
|
|
||||||
}
|
|
||||||
return eventBackedDriverTimelineBuilder.build(session, driver);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface UnifiedDriverEventSource {
|
|
||||||
|
|
||||||
boolean supports(UnifiedDriverEventsRequest request);
|
|
||||||
|
|
||||||
List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request);
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
|
||||||
import java.util.List;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class UnifiedDriverEventSourceService {
|
|
||||||
|
|
||||||
private final List<UnifiedDriverEventSource> eventSources;
|
|
||||||
|
|
||||||
public UnifiedDriverEventSourceService(List<UnifiedDriverEventSource> eventSources) {
|
|
||||||
this.eventSources = List.copyOf(eventSources);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) {
|
|
||||||
return eventSources.stream()
|
|
||||||
.filter(source -> source.supports(request))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> unsupportedSource(request.sourceFamily().name()))
|
|
||||||
.loadDriverEvents(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IllegalArgumentException unsupportedSource(String sourceFamily) {
|
|
||||||
return new IllegalArgumentException("No unified driver event source is registered for source family " + sourceFamily + ".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDriverTimelineRequest;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
|
||||||
import java.util.List;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class UnifiedDriverTimelineService {
|
|
||||||
|
|
||||||
private final List<UnifiedDriverTimelineSource> timelineSources;
|
|
||||||
|
|
||||||
public UnifiedDriverTimelineService(List<UnifiedDriverTimelineSource> timelineSources) {
|
|
||||||
this.timelineSources = List.copyOf(timelineSources);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResolvedDriverTimeline loadDriverTimeline(UnifiedDriverTimelineRequest request) {
|
|
||||||
return timelineSources.stream()
|
|
||||||
.filter(source -> source.supports(request))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> unsupportedSource(request.sourceFamily().name()))
|
|
||||||
.loadDriverTimeline(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IllegalArgumentException unsupportedSource(String sourceFamily) {
|
|
||||||
return new IllegalArgumentException("No unified driver timeline source is registered for source family " + sourceFamily + ".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDriverTimelineRequest;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
|
||||||
|
|
||||||
public interface UnifiedDriverTimelineSource {
|
|
||||||
|
|
||||||
boolean supports(UnifiedDriverTimelineRequest request);
|
|
||||||
|
|
||||||
ResolvedDriverTimeline loadDriverTimeline(UnifiedDriverTimelineRequest request);
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import at.procon.eventhub.dto.EventHubEventDto;
|
|
||||||
import at.procon.eventhub.persistence.EventHubEventReadRepository;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
|
||||||
import java.util.List;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class YellowFoxDbUnifiedDriverEventSource implements UnifiedDriverEventSource {
|
|
||||||
|
|
||||||
private final EventHubEventReadRepository repository;
|
|
||||||
|
|
||||||
public YellowFoxDbUnifiedDriverEventSource(EventHubEventReadRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(UnifiedDriverEventsRequest request) {
|
|
||||||
return request.sourceFamily() == UnifiedEventSourceFamily.YELLOWFOX_DB;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) {
|
|
||||||
return repository.findEvents(request, "YELLOWFOX", List.of("TELEMATICS_PLATFORM"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,22 +8,16 @@ public record ExtractedSupportEvent(
|
||||||
OffsetDateTime occurredAt,
|
OffsetDateTime occurredAt,
|
||||||
String eventDomain,
|
String eventDomain,
|
||||||
String eventType,
|
String eventType,
|
||||||
String eventLifecycle,
|
|
||||||
String slot,
|
String slot,
|
||||||
String registrationKey,
|
String registrationKey,
|
||||||
String vehicleKey,
|
String vehicleKey,
|
||||||
String country,
|
String country,
|
||||||
String region,
|
String region,
|
||||||
String countryFrom,
|
|
||||||
String countryTo,
|
|
||||||
String operation,
|
|
||||||
BigDecimal latitude,
|
BigDecimal latitude,
|
||||||
BigDecimal longitude,
|
BigDecimal longitude,
|
||||||
String authenticationStatus,
|
String authenticationStatus,
|
||||||
Long odometerKm,
|
Long odometerKm,
|
||||||
String code,
|
String code,
|
||||||
BigDecimal avgSpeedKmh,
|
|
||||||
BigDecimal maxSpeedKmh,
|
|
||||||
String rawRecordPath
|
String rawRecordPath
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
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
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,14 +5,12 @@ import at.procon.eventhub.tachographfilesession.model.ExtractedCardActivityInter
|
||||||
import at.procon.eventhub.tachographfilesession.model.ExtractedCardVehicleUsageInterval;
|
import at.procon.eventhub.tachographfilesession.model.ExtractedCardVehicleUsageInterval;
|
||||||
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
|
||||||
import at.procon.eventhub.tachographfilesession.model.ExtractedDriverCard;
|
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.ExtractedVehicle;
|
||||||
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicleRegistration;
|
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicleRegistration;
|
||||||
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
|
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
|
||||||
import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
|
import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
|
|
@ -73,7 +71,6 @@ public class DriverCardXmlExtractionService {
|
||||||
extractVehicleUsageIntervals(document, registrationsByKey, vehiclesByKey, warnings);
|
extractVehicleUsageIntervals(document, registrationsByKey, vehiclesByKey, warnings);
|
||||||
List<ExtractedCardActivityInterval> activityIntervals =
|
List<ExtractedCardActivityInterval> activityIntervals =
|
||||||
assignVehicleCoverage(extractActivityIntervals(document, warnings), vehicleUsageIntervals);
|
assignVehicleCoverage(extractActivityIntervals(document, warnings), vehicleUsageIntervals);
|
||||||
List<ExtractedSupportEvent> supportEvents = extractSupportEvents(document, vehicleUsageIntervals, warnings);
|
|
||||||
|
|
||||||
DriverExtractionSession driverSession = new DriverExtractionSession(
|
DriverExtractionSession driverSession = new DriverExtractionSession(
|
||||||
driverKey,
|
driverKey,
|
||||||
|
|
@ -83,7 +80,7 @@ public class DriverCardXmlExtractionService {
|
||||||
List.copyOf(vehiclesByKey.values()),
|
List.copyOf(vehiclesByKey.values()),
|
||||||
List.copyOf(vehicleUsageIntervals),
|
List.copyOf(vehicleUsageIntervals),
|
||||||
List.copyOf(activityIntervals),
|
List.copyOf(activityIntervals),
|
||||||
List.copyOf(supportEvents),
|
List.of(),
|
||||||
List.copyOf(warnings)
|
List.copyOf(warnings)
|
||||||
);
|
);
|
||||||
Map<String, DriverExtractionSession> driversByKey = Map.of(driverKey, driverSession);
|
Map<String, DriverExtractionSession> driversByKey = Map.of(driverKey, driverSession);
|
||||||
|
|
@ -305,284 +302,6 @@ public class DriverCardXmlExtractionService {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ExtractedSupportEvent> extractSupportEvents(
|
|
||||||
Document document,
|
|
||||||
List<ExtractedCardVehicleUsageInterval> vehicleUsageIntervals,
|
|
||||||
List<ExtractionWarning> warnings
|
|
||||||
) {
|
|
||||||
VehicleUsageLookup vehicleUsageLookup = new VehicleUsageLookup(vehicleUsageIntervals);
|
|
||||||
List<ExtractedSupportEvent> supportEvents = new ArrayList<>();
|
|
||||||
Element root = document.getDocumentElement();
|
|
||||||
extractCardPlaceSupportEvents(root, vehicleUsageLookup, supportEvents, warnings);
|
|
||||||
extractCardGnssSupportEvents(root, vehicleUsageLookup, supportEvents, warnings);
|
|
||||||
extractCardSpecificConditionSupportEvents(root, vehicleUsageLookup, supportEvents, warnings);
|
|
||||||
extractCardBorderCrossingSupportEvents(root, vehicleUsageLookup, supportEvents, warnings);
|
|
||||||
extractCardLoadUnloadSupportEvents(root, vehicleUsageLookup, supportEvents, warnings);
|
|
||||||
supportEvents.sort(Comparator.comparing(ExtractedSupportEvent::occurredAt)
|
|
||||||
.thenComparing(ExtractedSupportEvent::eventDomain, Comparator.nullsLast(String::compareTo))
|
|
||||||
.thenComparing(ExtractedSupportEvent::eventId, Comparator.nullsLast(String::compareTo)));
|
|
||||||
return List.copyOf(supportEvents);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void extractCardPlaceSupportEvents(
|
|
||||||
Element root,
|
|
||||||
VehicleUsageLookup vehicleUsageLookup,
|
|
||||||
List<ExtractedSupportEvent> supportEvents,
|
|
||||||
List<ExtractionWarning> warnings
|
|
||||||
) {
|
|
||||||
List<Element> sections = children(root, "Places");
|
|
||||||
for (int sectionIndex = 0; sectionIndex < sections.size(); sectionIndex++) {
|
|
||||||
Element cardPlaceDailyWorkPeriod = child(sections.get(sectionIndex), "cardPlaceDailyWorkPeriod");
|
|
||||||
List<Element> records = children(cardPlaceDailyWorkPeriod, "placeRecords");
|
|
||||||
for (int i = 0; i < records.size(); i++) {
|
|
||||||
Element record = records.get(i);
|
|
||||||
String path = "/DriverCard/Places[" + (sectionIndex + 1) + "]/cardPlaceDailyWorkPeriod/placeRecords[" + (i + 1) + "]";
|
|
||||||
OffsetDateTime occurredAt = offsetDateTime(childText(record, "entryTime"));
|
|
||||||
if (occurredAt == null) {
|
|
||||||
warnings.add(new ExtractionWarning("CARD_PLACE_MISSING_TIME", "Driver-card place record is missing entryTime.", path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExtractedCardVehicleUsageInterval usage = vehicleUsageLookup.resolve(occurredAt);
|
|
||||||
Element gnss = child(record, "entryGnssPlaceRecord");
|
|
||||||
Element geoCoordinates = child(gnss, "geoCoordinates");
|
|
||||||
BigDecimal latitude = geoCoordinate(geoCoordinates, "latitude", true);
|
|
||||||
BigDecimal longitude = geoCoordinate(geoCoordinates, "longitude", false);
|
|
||||||
String authenticationStatus = normalizeToken(childText(gnss, "authenticationStatus"));
|
|
||||||
String entryType = childText(record, "entryTypeDailyWorkPeriod");
|
|
||||||
supportEvents.add(new ExtractedSupportEvent(
|
|
||||||
"CARDPLACE-" + (i + 1),
|
|
||||||
occurredAt,
|
|
||||||
"PLACE",
|
|
||||||
mapPlaceEntryType(entryType),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
usage == null ? null : usage.registrationKey(),
|
|
||||||
usage == null ? null : usage.vehicleKey(),
|
|
||||||
childText(record, "dailyWorkPeriodCountry"),
|
|
||||||
childText(record, "dailyWorkPeriodRegion"),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
authenticationStatus,
|
|
||||||
longValue(childText(record, "vehicleOdometerValue")),
|
|
||||||
normalizeToken(entryType),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
path
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void extractCardGnssSupportEvents(
|
|
||||||
Element root,
|
|
||||||
VehicleUsageLookup vehicleUsageLookup,
|
|
||||||
List<ExtractedSupportEvent> supportEvents,
|
|
||||||
List<ExtractionWarning> warnings
|
|
||||||
) {
|
|
||||||
List<Element> sections = children(root, "GnssPlaces");
|
|
||||||
for (int sectionIndex = 0; sectionIndex < sections.size(); sectionIndex++) {
|
|
||||||
Element gnssAccumulatedDriving = child(sections.get(sectionIndex), "gnssAccumulatedDriving");
|
|
||||||
List<Element> records = children(gnssAccumulatedDriving, "gnssAccumulatedDrivingRecord");
|
|
||||||
for (int i = 0; i < records.size(); i++) {
|
|
||||||
Element record = records.get(i);
|
|
||||||
String path = "/DriverCard/GnssPlaces[" + (sectionIndex + 1) + "]/gnssAccumulatedDriving/gnssAccumulatedDrivingRecord[" + (i + 1) + "]";
|
|
||||||
OffsetDateTime occurredAt = offsetDateTime(childText(record, "timeStamp"));
|
|
||||||
if (occurredAt == null) {
|
|
||||||
warnings.add(new ExtractionWarning("CARD_GNSS_MISSING_TIME", "Driver-card GNSS record is missing timeStamp.", path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExtractedCardVehicleUsageInterval usage = vehicleUsageLookup.resolve(occurredAt);
|
|
||||||
Element gnss = child(record, "gnssPlaceRecord");
|
|
||||||
Element geoCoordinates = child(gnss, "geoCoordinates");
|
|
||||||
BigDecimal latitude = geoCoordinate(geoCoordinates, "latitude", true);
|
|
||||||
BigDecimal longitude = geoCoordinate(geoCoordinates, "longitude", false);
|
|
||||||
String authenticationStatus = normalizeToken(childText(gnss, "authenticationStatus"));
|
|
||||||
supportEvents.add(new ExtractedSupportEvent(
|
|
||||||
"CARDGNSS-" + (i + 1),
|
|
||||||
occurredAt,
|
|
||||||
"POSITION",
|
|
||||||
"POSITION_RECORDED",
|
|
||||||
"SNAPSHOT",
|
|
||||||
null,
|
|
||||||
usage == null ? null : usage.registrationKey(),
|
|
||||||
usage == null ? null : usage.vehicleKey(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
authenticationStatus,
|
|
||||||
longValue(childText(record, "vehicleOdometerValue")),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
path
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void extractCardSpecificConditionSupportEvents(
|
|
||||||
Element root,
|
|
||||||
VehicleUsageLookup vehicleUsageLookup,
|
|
||||||
List<ExtractedSupportEvent> supportEvents,
|
|
||||||
List<ExtractionWarning> warnings
|
|
||||||
) {
|
|
||||||
List<Element> sections = children(root, "SpecificConditions");
|
|
||||||
for (int sectionIndex = 0; sectionIndex < sections.size(); sectionIndex++) {
|
|
||||||
Element specificConditionData = child(sections.get(sectionIndex), "specificConditionData");
|
|
||||||
List<Element> records = children(specificConditionData, "specificConditionRecord");
|
|
||||||
for (int i = 0; i < records.size(); i++) {
|
|
||||||
Element record = records.get(i);
|
|
||||||
String path = "/DriverCard/SpecificConditions[" + (sectionIndex + 1) + "]/specificConditionData/specificConditionRecord[" + (i + 1) + "]";
|
|
||||||
OffsetDateTime occurredAt = offsetDateTime(childText(record, "entryTime"));
|
|
||||||
if (occurredAt == null) {
|
|
||||||
warnings.add(new ExtractionWarning("CARD_SPECIFIC_CONDITION_MISSING_TIME", "Driver-card specific-condition record is missing entryTime.", path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExtractedCardVehicleUsageInterval usage = vehicleUsageLookup.resolve(occurredAt);
|
|
||||||
String conditionCode = normalizeToken(childText(record, "specificConditionType"));
|
|
||||||
String[] specificCondition = mapSpecificCondition(conditionCode);
|
|
||||||
supportEvents.add(new ExtractedSupportEvent(
|
|
||||||
"CARDSC-" + (i + 1),
|
|
||||||
occurredAt,
|
|
||||||
"SPECIFIC_CONDITION",
|
|
||||||
specificCondition[0],
|
|
||||||
specificCondition[1],
|
|
||||||
null,
|
|
||||||
usage == null ? null : usage.registrationKey(),
|
|
||||||
usage == null ? null : usage.vehicleKey(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
conditionCode,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
path
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void extractCardBorderCrossingSupportEvents(
|
|
||||||
Element root,
|
|
||||||
VehicleUsageLookup vehicleUsageLookup,
|
|
||||||
List<ExtractedSupportEvent> supportEvents,
|
|
||||||
List<ExtractionWarning> warnings
|
|
||||||
) {
|
|
||||||
List<Element> sections = children(root, "BorderCrossings");
|
|
||||||
for (int sectionIndex = 0; sectionIndex < sections.size(); sectionIndex++) {
|
|
||||||
Element cardBorderCrossings = child(sections.get(sectionIndex), "cardBorderCrossings");
|
|
||||||
List<Element> records = children(cardBorderCrossings, "cardBorderCrossingRecord");
|
|
||||||
for (int i = 0; i < records.size(); i++) {
|
|
||||||
Element record = records.get(i);
|
|
||||||
String path = "/DriverCard/BorderCrossings[" + (sectionIndex + 1) + "]/cardBorderCrossings/cardBorderCrossingRecord[" + (i + 1) + "]";
|
|
||||||
Element gnss = child(record, "gnssPlaceAuthRecord");
|
|
||||||
OffsetDateTime occurredAt = offsetDateTime(childText(gnss, "timeStamp"));
|
|
||||||
if (occurredAt == null) {
|
|
||||||
warnings.add(new ExtractionWarning("CARD_BORDER_CROSSING_MISSING_TIME", "Driver-card border-crossing record is missing gnssPlaceAuthRecord/timeStamp.", path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExtractedCardVehicleUsageInterval usage = vehicleUsageLookup.resolve(occurredAt);
|
|
||||||
Element geoCoordinates = child(gnss, "geoCoordinates");
|
|
||||||
BigDecimal latitude = geoCoordinate(geoCoordinates, "latitude", true);
|
|
||||||
BigDecimal longitude = geoCoordinate(geoCoordinates, "longitude", false);
|
|
||||||
String authenticationStatus = normalizeToken(childText(gnss, "authenticationStatus"));
|
|
||||||
supportEvents.add(new ExtractedSupportEvent(
|
|
||||||
"CARDBORDER-" + (i + 1),
|
|
||||||
occurredAt,
|
|
||||||
"BORDER_CROSSING",
|
|
||||||
"BORDER_OUTBOUND",
|
|
||||||
"OUTBOUND",
|
|
||||||
null,
|
|
||||||
usage == null ? null : usage.registrationKey(),
|
|
||||||
usage == null ? null : usage.vehicleKey(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
childText(record, "countryLeft"),
|
|
||||||
childText(record, "countryEntered"),
|
|
||||||
null,
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
authenticationStatus,
|
|
||||||
longValue(childText(record, "vehicleOdometerValue")),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
path
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void extractCardLoadUnloadSupportEvents(
|
|
||||||
Element root,
|
|
||||||
VehicleUsageLookup vehicleUsageLookup,
|
|
||||||
List<ExtractedSupportEvent> supportEvents,
|
|
||||||
List<ExtractionWarning> warnings
|
|
||||||
) {
|
|
||||||
List<Element> sections = children(root, "LoadUnloadOperations");
|
|
||||||
for (int sectionIndex = 0; sectionIndex < sections.size(); sectionIndex++) {
|
|
||||||
Element cardLoadUnloadOperations = child(sections.get(sectionIndex), "cardLoadUnloadOperations");
|
|
||||||
List<Element> records = children(cardLoadUnloadOperations, "cardLoadUnloadRecord");
|
|
||||||
for (int i = 0; i < records.size(); i++) {
|
|
||||||
Element record = records.get(i);
|
|
||||||
String path = "/DriverCard/LoadUnloadOperations[" + (sectionIndex + 1) + "]/cardLoadUnloadOperations/cardLoadUnloadRecord[" + (i + 1) + "]";
|
|
||||||
OffsetDateTime occurredAt = offsetDateTime(childText(record, "timeStamp"));
|
|
||||||
if (occurredAt == null) {
|
|
||||||
warnings.add(new ExtractionWarning("CARD_LOAD_UNLOAD_MISSING_TIME", "Driver-card load/unload record is missing timeStamp.", path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExtractedCardVehicleUsageInterval usage = vehicleUsageLookup.resolve(occurredAt);
|
|
||||||
Element gnss = child(record, "gnssPlaceAuthRecord");
|
|
||||||
Element geoCoordinates = child(gnss, "geoCoordinates");
|
|
||||||
BigDecimal latitude = geoCoordinate(geoCoordinates, "latitude", true);
|
|
||||||
BigDecimal longitude = geoCoordinate(geoCoordinates, "longitude", false);
|
|
||||||
String authenticationStatus = normalizeToken(childText(gnss, "authenticationStatus"));
|
|
||||||
String operation = mapOperation(childText(record, "operationType"));
|
|
||||||
supportEvents.add(new ExtractedSupportEvent(
|
|
||||||
"CARDLOAD-" + (i + 1),
|
|
||||||
occurredAt,
|
|
||||||
"LOAD_UNLOAD",
|
|
||||||
operation,
|
|
||||||
"SNAPSHOT",
|
|
||||||
null,
|
|
||||||
usage == null ? null : usage.registrationKey(),
|
|
||||||
usage == null ? null : usage.vehicleKey(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
operation,
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
authenticationStatus,
|
|
||||||
longValue(childText(record, "vehicleOdometerValue")),
|
|
||||||
normalizeToken(childText(record, "operationType")),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
path
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ExtractedCardActivityInterval> splitByVehicleCoverage(
|
private List<ExtractedCardActivityInterval> splitByVehicleCoverage(
|
||||||
ExtractedCardActivityInterval interval,
|
ExtractedCardActivityInterval interval,
|
||||||
List<ExtractedCardVehicleUsageInterval> overlappingUsages
|
List<ExtractedCardVehicleUsageInterval> overlappingUsages
|
||||||
|
|
@ -647,26 +366,6 @@ public class DriverCardXmlExtractionService {
|
||||||
return usage.to().plusSeconds(1);
|
return usage.to().plusSeconds(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal geoCoordinate(Element geoCoordinates, String component, boolean latitude) {
|
|
||||||
String value = childText(geoCoordinates, component);
|
|
||||||
if (value == null || value.isBlank()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
BigDecimal raw = new BigDecimal(value);
|
|
||||||
BigDecimal abs = raw.abs();
|
|
||||||
BigDecimal degreeThreshold = BigDecimal.valueOf(latitude ? 90D : 180D);
|
|
||||||
if (abs.compareTo(degreeThreshold) <= 0) {
|
|
||||||
return raw;
|
|
||||||
}
|
|
||||||
BigDecimal sign = raw.signum() < 0 ? BigDecimal.valueOf(-1L) : BigDecimal.ONE;
|
|
||||||
BigDecimal absolute = raw.abs();
|
|
||||||
BigDecimal[] degreeAndRemainder = absolute.divideAndRemainder(BigDecimal.valueOf(1000L));
|
|
||||||
BigDecimal degrees = degreeAndRemainder[0];
|
|
||||||
BigDecimal minutes = degreeAndRemainder[1].divide(BigDecimal.TEN);
|
|
||||||
BigDecimal decimalDegrees = degrees.add(minutes.divide(BigDecimal.valueOf(60L), 8, java.math.RoundingMode.HALF_UP));
|
|
||||||
return decimalDegrees.multiply(sign);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Element child(Element parent, String name) {
|
private Element child(Element parent, String name) {
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -757,50 +456,6 @@ public class DriverCardXmlExtractionService {
|
||||||
return value.trim().toUpperCase().replace('-', '_').replace(' ', '_');
|
return value.trim().toUpperCase().replace('-', '_').replace(' ', '_');
|
||||||
}
|
}
|
||||||
|
|
||||||
private String mapPlaceEntryType(String entryType) {
|
|
||||||
String normalized = normalizeToken(entryType);
|
|
||||||
if (normalized == null) {
|
|
||||||
return "DAILY_WORK_PERIOD_PLACE";
|
|
||||||
}
|
|
||||||
return switch (normalized) {
|
|
||||||
case "0" -> "BEGIN_DAILY_WORK_PERIOD";
|
|
||||||
case "1" -> "END_DAILY_WORK_PERIOD";
|
|
||||||
case "2" -> "BEGIN_MANUAL_DAILY_WORK_PERIOD";
|
|
||||||
case "3" -> "END_MANUAL_DAILY_WORK_PERIOD";
|
|
||||||
case "4" -> "BEGIN_ASSUMED_DAILY_WORK_PERIOD";
|
|
||||||
case "5" -> "END_ASSUMED_DAILY_WORK_PERIOD";
|
|
||||||
case "6" -> "BEGIN_GNSS_DAILY_WORK_PERIOD";
|
|
||||||
case "7" -> "END_GNSS_DAILY_WORK_PERIOD";
|
|
||||||
default -> "DAILY_WORK_PERIOD_PLACE";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] mapSpecificCondition(String conditionCode) {
|
|
||||||
String normalized = normalizeToken(conditionCode);
|
|
||||||
if (normalized == null) {
|
|
||||||
return new String[]{"UNKNOWN_EVENT", "SNAPSHOT"};
|
|
||||||
}
|
|
||||||
return switch (normalized) {
|
|
||||||
case "1" -> new String[]{"OUT", "BEGIN"};
|
|
||||||
case "2" -> new String[]{"OUT", "END"};
|
|
||||||
case "3" -> new String[]{"FERRY_TRAIN", "BEGIN"};
|
|
||||||
case "4" -> new String[]{"FERRY_TRAIN", "END"};
|
|
||||||
default -> new String[]{"UNKNOWN_EVENT", "SNAPSHOT"};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String mapOperation(String operationType) {
|
|
||||||
String normalized = normalizeToken(operationType);
|
|
||||||
if (normalized == null) {
|
|
||||||
return "LOAD_UNLOAD";
|
|
||||||
}
|
|
||||||
return switch (normalized) {
|
|
||||||
case "1", "LOAD" -> "LOAD";
|
|
||||||
case "2", "UNLOAD" -> "UNLOAD";
|
|
||||||
default -> "LOAD_UNLOAD";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private record ActivityChange(
|
private record ActivityChange(
|
||||||
OffsetDateTime from,
|
OffsetDateTime from,
|
||||||
String activityType,
|
String activityType,
|
||||||
|
|
@ -829,42 +484,4 @@ public class DriverCardXmlExtractionService {
|
||||||
}
|
}
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class VehicleUsageLookup {
|
|
||||||
private final List<ExtractedCardVehicleUsageInterval> intervals;
|
|
||||||
|
|
||||||
private VehicleUsageLookup(List<ExtractedCardVehicleUsageInterval> intervals) {
|
|
||||||
this.intervals = intervals == null ? List.of() : intervals;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExtractedCardVehicleUsageInterval resolve(OffsetDateTime occurredAt) {
|
|
||||||
if (occurredAt == null || intervals.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int low = 0;
|
|
||||||
int high = intervals.size() - 1;
|
|
||||||
while (low <= high) {
|
|
||||||
int mid = (low + high) >>> 1;
|
|
||||||
ExtractedCardVehicleUsageInterval interval = intervals.get(mid);
|
|
||||||
if (interval.from().isAfter(occurredAt)) {
|
|
||||||
high = mid - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (covers(interval, occurredAt)) {
|
|
||||||
return interval;
|
|
||||||
}
|
|
||||||
low = mid + 1;
|
|
||||||
}
|
|
||||||
for (int i = Math.min(high, intervals.size() - 1); i >= 0; i--) {
|
|
||||||
ExtractedCardVehicleUsageInterval interval = intervals.get(i);
|
|
||||||
if (covers(interval, occurredAt)) {
|
|
||||||
return interval;
|
|
||||||
}
|
|
||||||
if (interval.from().isBefore(occurredAt) && usageEndExclusive(interval, null).isBefore(occurredAt)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,442 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,448 +0,0 @@
|
||||||
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.ExtractedSupportEvent;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ExtractionWarning;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographTimelineEventBundle;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class EventBackedDriverTimelineBuilder {
|
|
||||||
|
|
||||||
private final DriverTimelineEventBuilder eventBuilder;
|
|
||||||
|
|
||||||
public EventBackedDriverTimelineBuilder(DriverTimelineEventBuilder eventBuilder) {
|
|
||||||
this.eventBuilder = eventBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResolvedDriverTimeline build(
|
|
||||||
TachographFileSession session,
|
|
||||||
DriverExtractionSession driverSession
|
|
||||||
) {
|
|
||||||
TachographTimelineEventBundle bundle = eventBuilder.buildEventBundle(session, driverSession);
|
|
||||||
List<ResolvedActivityInterval> activityIntervals =
|
|
||||||
reconstructActivityIntervals(bundle.activityEvents());
|
|
||||||
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals =
|
|
||||||
reconstructVehicleUsageIntervals(session.sessionId(), driverSession.driverKey(), bundle.vehicleUsageEvents());
|
|
||||||
List<ExtractedSupportEvent> supportEvents =
|
|
||||||
reconstructSupportEvents(bundle.supportEvents());
|
|
||||||
List<ExtractionWarning> warnings = mergeWarnings(session.warnings(), driverSession.warnings());
|
|
||||||
|
|
||||||
OffsetDateTime loadedFrom = minTimestamp(activityIntervals, vehicleUsageIntervals, supportEvents);
|
|
||||||
OffsetDateTime loadedTo = maxTimestamp(activityIntervals, vehicleUsageIntervals, supportEvents);
|
|
||||||
return new ResolvedDriverTimeline(
|
|
||||||
resolveSourceKind(session, bundle),
|
|
||||||
loadedFrom,
|
|
||||||
loadedTo,
|
|
||||||
vehicleUsageIntervals,
|
|
||||||
activityIntervals,
|
|
||||||
supportEvents,
|
|
||||||
warnings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ResolvedActivityInterval> reconstructActivityIntervals(List<EventHubEventDto> activityEvents) {
|
|
||||||
if (activityEvents == null || activityEvents.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
Map<String, ActivityAccumulator> byIntervalId = new LinkedHashMap<>();
|
|
||||||
for (EventHubEventDto event : activityEvents) {
|
|
||||||
if (!"DRIVER_ACTIVITY".equals(event.eventDomain().name())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
JsonNode raw = raw(event);
|
|
||||||
String intervalId = text(raw, "intervalId");
|
|
||||||
if (intervalId == null) {
|
|
||||||
intervalId = text(raw, "sourceRowId");
|
|
||||||
}
|
|
||||||
if (intervalId == null) {
|
|
||||||
intervalId = event.externalSourceEventId();
|
|
||||||
}
|
|
||||||
String resolvedIntervalId = intervalId;
|
|
||||||
ActivityAccumulator accumulator = byIntervalId.computeIfAbsent(
|
|
||||||
resolvedIntervalId,
|
|
||||||
ignored -> new ActivityAccumulator(resolvedIntervalId)
|
|
||||||
);
|
|
||||||
accumulator.accept(event, raw);
|
|
||||||
}
|
|
||||||
List<ResolvedActivityInterval> result = new ArrayList<>(byIntervalId.size());
|
|
||||||
for (ActivityAccumulator accumulator : byIntervalId.values()) {
|
|
||||||
ResolvedActivityInterval interval = accumulator.finish();
|
|
||||||
if (interval != null) {
|
|
||||||
result.add(interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.sort(Comparator.comparing(ResolvedActivityInterval::from)
|
|
||||||
.thenComparing(ResolvedActivityInterval::to)
|
|
||||||
.thenComparing(ResolvedActivityInterval::activityType, Comparator.nullsLast(String::compareTo)));
|
|
||||||
return List.copyOf(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ResolvedVehicleUsageInterval> reconstructVehicleUsageIntervals(
|
|
||||||
UUID sessionId,
|
|
||||||
String driverKey,
|
|
||||||
List<EventHubEventDto> vehicleUsageEvents
|
|
||||||
) {
|
|
||||||
if (vehicleUsageEvents == null || vehicleUsageEvents.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
Map<String, VehicleUsageAccumulator> byIntervalId = new LinkedHashMap<>();
|
|
||||||
for (EventHubEventDto event : vehicleUsageEvents) {
|
|
||||||
if (!"DRIVER_CARD".equals(event.eventDomain().name())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
JsonNode raw = raw(event);
|
|
||||||
String intervalId = text(raw, "intervalId");
|
|
||||||
if (intervalId == null) {
|
|
||||||
intervalId = text(raw, "sourceRowId");
|
|
||||||
}
|
|
||||||
if (intervalId == null) {
|
|
||||||
intervalId = event.externalSourceEventId();
|
|
||||||
}
|
|
||||||
String resolvedIntervalId = intervalId;
|
|
||||||
VehicleUsageAccumulator accumulator = byIntervalId.computeIfAbsent(
|
|
||||||
resolvedIntervalId,
|
|
||||||
ignored -> new VehicleUsageAccumulator(sessionId, driverKey, resolvedIntervalId)
|
|
||||||
);
|
|
||||||
accumulator.accept(event, raw);
|
|
||||||
}
|
|
||||||
List<ResolvedVehicleUsageInterval> result = new ArrayList<>(byIntervalId.size());
|
|
||||||
for (VehicleUsageAccumulator accumulator : byIntervalId.values()) {
|
|
||||||
ResolvedVehicleUsageInterval interval = accumulator.finish();
|
|
||||||
if (interval != null) {
|
|
||||||
result.add(interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.sort(Comparator.comparing(ResolvedVehicleUsageInterval::from)
|
|
||||||
.thenComparing(ResolvedVehicleUsageInterval::to, Comparator.nullsLast(Comparator.naturalOrder())));
|
|
||||||
return List.copyOf(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ExtractedSupportEvent> reconstructSupportEvents(List<EventHubEventDto> supportEvents) {
|
|
||||||
if (supportEvents == null || supportEvents.isEmpty()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
List<ExtractedSupportEvent> result = new ArrayList<>(supportEvents.size());
|
|
||||||
for (EventHubEventDto event : supportEvents) {
|
|
||||||
JsonNode raw = raw(event);
|
|
||||||
String eventId = text(raw, "supportEventId");
|
|
||||||
if (eventId == null) {
|
|
||||||
eventId = event.externalSourceEventId();
|
|
||||||
}
|
|
||||||
BigDecimal latitude = event.position() == null ? null : event.position().latitude();
|
|
||||||
BigDecimal longitude = event.position() == null ? null : event.position().longitude();
|
|
||||||
result.add(new ExtractedSupportEvent(
|
|
||||||
eventId,
|
|
||||||
event.occurredAt(),
|
|
||||||
event.eventDomain().name(),
|
|
||||||
text(raw, "supportEventType") == null ? event.eventType().name() : text(raw, "supportEventType"),
|
|
||||||
event.lifecycle().name(),
|
|
||||||
text(raw, "slot"),
|
|
||||||
text(raw, "registrationKey"),
|
|
||||||
text(raw, "vehicleKey"),
|
|
||||||
text(raw, "country"),
|
|
||||||
text(raw, "region"),
|
|
||||||
text(raw, "countryFrom"),
|
|
||||||
text(raw, "countryTo"),
|
|
||||||
text(raw, "operation"),
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
text(raw, "authenticationStatus"),
|
|
||||||
longValue(raw, "odometerKm"),
|
|
||||||
text(raw, "code"),
|
|
||||||
decimal(raw, "avgSpeedKmh"),
|
|
||||||
decimal(raw, "maxSpeedKmh"),
|
|
||||||
text(raw, "rawRecordPath")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
result.sort(Comparator.comparing(ExtractedSupportEvent::occurredAt)
|
|
||||||
.thenComparing(ExtractedSupportEvent::eventDomain, Comparator.nullsLast(String::compareTo))
|
|
||||||
.thenComparing(ExtractedSupportEvent::eventId, Comparator.nullsLast(String::compareTo)));
|
|
||||||
return List.copyOf(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ExtractionWarning> mergeWarnings(List<ExtractionWarning> sessionWarnings, List<ExtractionWarning> driverWarnings) {
|
|
||||||
LinkedHashSet<ExtractionWarning> merged = new LinkedHashSet<>();
|
|
||||||
if (sessionWarnings != null) {
|
|
||||||
merged.addAll(sessionWarnings);
|
|
||||||
}
|
|
||||||
if (driverWarnings != null) {
|
|
||||||
merged.addAll(driverWarnings);
|
|
||||||
}
|
|
||||||
return List.copyOf(merged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime minTimestamp(
|
|
||||||
List<ResolvedActivityInterval> activityIntervals,
|
|
||||||
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
|
|
||||||
List<ExtractedSupportEvent> supportEvents
|
|
||||||
) {
|
|
||||||
OffsetDateTime min = null;
|
|
||||||
for (ResolvedActivityInterval interval : activityIntervals) {
|
|
||||||
min = min(min, interval.from());
|
|
||||||
}
|
|
||||||
for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) {
|
|
||||||
min = min(min, interval.from());
|
|
||||||
}
|
|
||||||
for (ExtractedSupportEvent supportEvent : supportEvents) {
|
|
||||||
min = min(min, supportEvent.occurredAt());
|
|
||||||
}
|
|
||||||
return min;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime maxTimestamp(
|
|
||||||
List<ResolvedActivityInterval> activityIntervals,
|
|
||||||
List<ResolvedVehicleUsageInterval> vehicleUsageIntervals,
|
|
||||||
List<ExtractedSupportEvent> supportEvents
|
|
||||||
) {
|
|
||||||
OffsetDateTime max = null;
|
|
||||||
for (ResolvedActivityInterval interval : activityIntervals) {
|
|
||||||
max = max(max, interval.to());
|
|
||||||
}
|
|
||||||
for (ResolvedVehicleUsageInterval interval : vehicleUsageIntervals) {
|
|
||||||
max = max(max, interval.to());
|
|
||||||
}
|
|
||||||
for (ExtractedSupportEvent supportEvent : supportEvents) {
|
|
||||||
max = max(max, supportEvent.occurredAt());
|
|
||||||
}
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String resolveSourceKind(TachographFileSession session, TachographTimelineEventBundle bundle) {
|
|
||||||
for (EventHubEventDto event : bundle.allEvents()) {
|
|
||||||
JsonNode raw = raw(event);
|
|
||||||
String sourceKind = text(raw, "sourceKind");
|
|
||||||
if (sourceKind != null) {
|
|
||||||
return sourceKind;
|
|
||||||
}
|
|
||||||
if (event.packageInfo() != null && event.packageInfo().eventSource() != null
|
|
||||||
&& event.packageInfo().eventSource().sourceKind() != null) {
|
|
||||||
return event.packageInfo().eventSource().sourceKind();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return session.metadata().driverCardFile() ? "DRIVER_CARD" : "VEHICLE_UNIT";
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime min(OffsetDateTime left, OffsetDateTime right) {
|
|
||||||
if (left == null) {
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
if (right == null) {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
return left.isBefore(right) ? left : right;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OffsetDateTime max(OffsetDateTime left, OffsetDateTime right) {
|
|
||||||
if (left == null) {
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
if (right == null) {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
return left.isAfter(right) ? left : right;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JsonNode raw(EventHubEventDto event) {
|
|
||||||
JsonNode payload = event.payload();
|
|
||||||
if (payload == null || payload.isMissingNode()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode raw = payload.get("raw");
|
|
||||||
return raw == null || raw.isNull() ? payload : raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String text(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
return value == null || value.isNull() ? null : value.asText(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean booleanValue(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
return value != null && !value.isNull() && value.asBoolean(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long longValue(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return value.isNumber() ? value.asLong() : Long.parseLong(value.asText());
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal decimal(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value == null || value.isNull()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (value.isNumber()) {
|
|
||||||
return value.decimalValue();
|
|
||||||
}
|
|
||||||
String text = value.asText(null);
|
|
||||||
return text == null || text.isBlank() ? null : new BigDecimal(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> stringList(JsonNode node, String field) {
|
|
||||||
if (node == null || field == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
JsonNode value = node.get(field);
|
|
||||||
if (value == null || value.isNull() || !value.isArray()) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
List<String> result = new ArrayList<>();
|
|
||||||
value.forEach(item -> {
|
|
||||||
String text = item == null || item.isNull() ? null : item.asText(null);
|
|
||||||
if (text != null && !text.isBlank()) {
|
|
||||||
result.add(text);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return List.copyOf(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String detailText(EventHubEventDto event, String field) {
|
|
||||||
if (event.eventDetails() == null || event.eventDetails().attributes() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JsonNode value = event.eventDetails().attributes().get(field);
|
|
||||||
return value == null || value.isNull() ? null : value.asText(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String activityType(EventHubEventDto event) {
|
|
||||||
return switch (event.eventType()) {
|
|
||||||
case DRIVE -> "DRIVE";
|
|
||||||
case WORK -> "WORK";
|
|
||||||
case AVAILABILITY -> "AVAILABILITY";
|
|
||||||
case BREAK_REST -> "BREAK_REST";
|
|
||||||
default -> "UNKNOWN";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long toKilometers(Long meters) {
|
|
||||||
return meters == null ? null : meters / 1_000L;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class ActivityAccumulator {
|
|
||||||
private final String intervalId;
|
|
||||||
private OffsetDateTime startedAt;
|
|
||||||
private OffsetDateTime endedAt;
|
|
||||||
private EventHubEventDto sample;
|
|
||||||
private JsonNode raw;
|
|
||||||
|
|
||||||
private ActivityAccumulator(String intervalId) {
|
|
||||||
this.intervalId = intervalId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void accept(EventHubEventDto event, JsonNode raw) {
|
|
||||||
if (sample == null) {
|
|
||||||
sample = event;
|
|
||||||
this.raw = raw;
|
|
||||||
}
|
|
||||||
if (event.lifecycle() == at.procon.eventhub.dto.EventLifecycle.START) {
|
|
||||||
startedAt = event.occurredAt();
|
|
||||||
} else if (event.lifecycle() == at.procon.eventhub.dto.EventLifecycle.END) {
|
|
||||||
endedAt = event.occurredAt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResolvedActivityInterval finish() {
|
|
||||||
if (sample == null || startedAt == null || endedAt == null || !endedAt.isAfter(startedAt)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new ResolvedActivityInterval(
|
|
||||||
intervalId,
|
|
||||||
startedAt,
|
|
||||||
endedAt,
|
|
||||||
java.time.Duration.between(startedAt, endedAt).getSeconds(),
|
|
||||||
activityType(sample),
|
|
||||||
detailText(sample, "cardSlot"),
|
|
||||||
detailText(sample, "cardStatus"),
|
|
||||||
detailText(sample, "drivingStatus"),
|
|
||||||
text(raw, "registrationKey"),
|
|
||||||
text(raw, "vehicleKey"),
|
|
||||||
text(raw, "sourceKind"),
|
|
||||||
stringList(raw, "sourceRowIds"),
|
|
||||||
booleanValue(raw, "synthetic"),
|
|
||||||
booleanValue(raw, "clippedToRequestedPeriod"),
|
|
||||||
text(raw, "level") == null ? "RAW_INTERVAL" : text(raw, "level")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class VehicleUsageAccumulator {
|
|
||||||
private final UUID sessionId;
|
|
||||||
private final String driverKey;
|
|
||||||
private final String intervalId;
|
|
||||||
private OffsetDateTime startedAt;
|
|
||||||
private OffsetDateTime endedAt;
|
|
||||||
private Long odometerBeginKm;
|
|
||||||
private Long odometerEndKm;
|
|
||||||
private JsonNode raw;
|
|
||||||
|
|
||||||
private VehicleUsageAccumulator(UUID sessionId, String driverKey, String intervalId) {
|
|
||||||
this.sessionId = sessionId;
|
|
||||||
this.driverKey = driverKey;
|
|
||||||
this.intervalId = intervalId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void accept(EventHubEventDto event, JsonNode raw) {
|
|
||||||
if (this.raw == null) {
|
|
||||||
this.raw = raw;
|
|
||||||
}
|
|
||||||
if (event.eventType() == at.procon.eventhub.dto.EventType.CARD_INSERTED) {
|
|
||||||
startedAt = event.occurredAt();
|
|
||||||
odometerBeginKm = toKilometers(event.odometerM());
|
|
||||||
} else if (event.eventType() == at.procon.eventhub.dto.EventType.CARD_WITHDRAWN) {
|
|
||||||
endedAt = event.occurredAt();
|
|
||||||
odometerEndKm = toKilometers(event.odometerM());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResolvedVehicleUsageInterval finish() {
|
|
||||||
if (startedAt == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ResolvedVehicleUsageInterval.resolved(
|
|
||||||
sessionId,
|
|
||||||
driverKey,
|
|
||||||
intervalId,
|
|
||||||
startedAt,
|
|
||||||
endedAt,
|
|
||||||
odometerBeginKm,
|
|
||||||
odometerEndKm,
|
|
||||||
text(raw, "registrationKey"),
|
|
||||||
text(raw, "vehicleKey"),
|
|
||||||
text(raw, "sourceKind"),
|
|
||||||
stringList(raw, "sourceRowIds")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,637 +0,0 @@
|
||||||
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(), supportEvent.eventLifecycle());
|
|
||||||
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("countryFrom", supportEvent.countryFrom());
|
|
||||||
raw.put("countryTo", supportEvent.countryTo());
|
|
||||||
raw.put("operation", supportEvent.operation());
|
|
||||||
raw.put("authenticationStatus", supportEvent.authenticationStatus());
|
|
||||||
raw.put("odometerKm", supportEvent.odometerKm());
|
|
||||||
raw.put("code", supportEvent.code());
|
|
||||||
raw.put("avgSpeedKmh", supportEvent.avgSpeedKmh());
|
|
||||||
raw.put("maxSpeedKmh", supportEvent.maxSpeedKmh());
|
|
||||||
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 "BORDER_CROSSING" -> EventDomain.BORDER_CROSSING;
|
|
||||||
case "LOAD_UNLOAD" -> EventDomain.LOAD_UNLOAD;
|
|
||||||
case "SPECIFIC_CONDITION" -> EventDomain.SPECIFIC_CONDITION;
|
|
||||||
case "SPEEDING" -> EventDomain.SPEEDING;
|
|
||||||
default -> EventDomain.TELEMATICS_DATA;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventType supportEventType(EventDomain eventDomain, String eventType, String code) {
|
|
||||||
EventType explicit = parseEnum(EventType.class, eventType, null);
|
|
||||||
if (explicit != null) {
|
|
||||||
return explicit;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
case BORDER_CROSSING -> EventType.BORDER_OUTBOUND;
|
|
||||||
case LOAD_UNLOAD -> EventType.LOAD_UNLOAD;
|
|
||||||
case SPEEDING -> EventType.SPEEDING;
|
|
||||||
default -> parseEnum(EventType.class, eventType, EventType.UNKNOWN_EVENT);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventLifecycle supportEventLifecycle(EventDomain eventDomain, String eventType, String lifecycle) {
|
|
||||||
EventLifecycle explicit = parseEnum(EventLifecycle.class, lifecycle, null);
|
|
||||||
if (explicit != null) {
|
|
||||||
return explicit;
|
|
||||||
}
|
|
||||||
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("GNSS_ACCUMULATED_DRIVING");
|
|
||||||
case BORDER_CROSSING -> detailsFactory.borderCrossing(supportEvent.countryFrom(), supportEvent.countryTo());
|
|
||||||
case LOAD_UNLOAD -> detailsFactory.loadUnload(supportEvent.operation());
|
|
||||||
case SPECIFIC_CONDITION -> detailsFactory.specificCondition();
|
|
||||||
case SPEEDING -> detailsFactory.speeding(supportEvent.avgSpeedKmh(), supportEvent.maxSpeedKmh(), null);
|
|
||||||
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,7 +13,6 @@ 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;
|
||||||
|
|
@ -34,21 +33,15 @@ 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 EventBackedDriverTimelineBuilder eventBackedDriverTimelineBuilder;
|
|
||||||
private final EventHubProperties properties;
|
private final EventHubProperties properties;
|
||||||
|
|
||||||
public TachographFileSessionProcessingService(
|
public TachographFileSessionProcessingService(
|
||||||
TachographFileSessionRepository repository,
|
TachographFileSessionRepository repository,
|
||||||
DriverTimelineBuilder driverTimelineBuilder,
|
DriverTimelineBuilder driverTimelineBuilder,
|
||||||
DriverTimelineReusableProjectionBuilder reusableProjectionBuilder,
|
|
||||||
EventBackedDriverTimelineBuilder eventBackedDriverTimelineBuilder,
|
|
||||||
EventHubProperties properties
|
EventHubProperties properties
|
||||||
) {
|
) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.driverTimelineBuilder = driverTimelineBuilder;
|
this.driverTimelineBuilder = driverTimelineBuilder;
|
||||||
this.reusableProjectionBuilder = reusableProjectionBuilder;
|
|
||||||
this.eventBackedDriverTimelineBuilder = eventBackedDriverTimelineBuilder;
|
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,7 +60,7 @@ public class TachographFileSessionProcessingService {
|
||||||
throw new DriverNotFoundInSessionException(sessionId, driverKey);
|
throw new DriverNotFoundInSessionException(sessionId, driverKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResolvedDriverTimeline timeline = resolveTimeline(session, driver);
|
ResolvedDriverTimeline timeline = driverTimelineBuilder.build(session, driver);
|
||||||
OffsetDateTime loadedFrom = timeline.loadedFrom();
|
OffsetDateTime loadedFrom = timeline.loadedFrom();
|
||||||
OffsetDateTime loadedTo = timeline.loadedTo();
|
OffsetDateTime loadedTo = timeline.loadedTo();
|
||||||
OffsetDateTime requestedFrom = effectiveRequest.occurredFrom() == null ? loadedFrom : utc(effectiveRequest.occurredFrom());
|
OffsetDateTime requestedFrom = effectiveRequest.occurredFrom() == null ? loadedFrom : utc(effectiveRequest.occurredFrom());
|
||||||
|
|
@ -149,7 +142,7 @@ public class TachographFileSessionProcessingService {
|
||||||
throw new DriverNotFoundInSessionException(sessionId, driverKey);
|
throw new DriverNotFoundInSessionException(sessionId, driverKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResolvedDriverTimeline timeline = resolveTimeline(session, driver);
|
ResolvedDriverTimeline timeline = driverTimelineBuilder.build(session, driver);
|
||||||
OffsetDateTime requestedFrom = effectiveRequest.occurredFrom() == null ? timeline.loadedFrom() : utc(effectiveRequest.occurredFrom());
|
OffsetDateTime requestedFrom = effectiveRequest.occurredFrom() == null ? timeline.loadedFrom() : utc(effectiveRequest.occurredFrom());
|
||||||
OffsetDateTime requestedTo = effectiveRequest.occurredTo() == null ? timeline.loadedTo() : utc(effectiveRequest.occurredTo());
|
OffsetDateTime requestedTo = effectiveRequest.occurredTo() == null ? timeline.loadedTo() : utc(effectiveRequest.occurredTo());
|
||||||
if (requestedFrom != null && requestedTo != null && requestedTo.isBefore(requestedFrom)) {
|
if (requestedFrom != null && requestedTo != null && requestedTo.isBefore(requestedFrom)) {
|
||||||
|
|
@ -168,16 +161,13 @@ public class TachographFileSessionProcessingService {
|
||||||
requestedFrom,
|
requestedFrom,
|
||||||
requestedTo
|
requestedTo
|
||||||
);
|
);
|
||||||
TachographEsperDrivingDerivedProjectionBundle derivedProjectionBundle =
|
List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionIntervals =
|
||||||
reusableProjectionBuilder.buildEsperDrivingDerivedProjectionBundle(
|
driverTimelineBuilder.buildEsperDrivingInterruptionIntervalEvents(
|
||||||
sessionId,
|
sessionId,
|
||||||
driverKey,
|
driverKey,
|
||||||
timeline,
|
timeline,
|
||||||
significantDrivingMinutes,
|
significantDrivingMinutes
|
||||||
minimumRestPeriodMinutes
|
|
||||||
);
|
);
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionIntervals =
|
|
||||||
derivedProjectionBundle.drivingInterruptionIntervals();
|
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals =
|
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionIntervals =
|
||||||
clipEsperDrivingInterruptionIntervalEvents(
|
clipEsperDrivingInterruptionIntervalEvents(
|
||||||
rawDrivingInterruptionIntervals,
|
rawDrivingInterruptionIntervals,
|
||||||
|
|
@ -185,7 +175,10 @@ public class TachographFileSessionProcessingService {
|
||||||
requestedTo
|
requestedTo
|
||||||
);
|
);
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> rawDailyWeeklyRestCandidateIntervals =
|
List<TachographEsperDrivingInterruptionIntervalEvent> rawDailyWeeklyRestCandidateIntervals =
|
||||||
derivedProjectionBundle.dailyWeeklyRestCandidateIntervals();
|
driverTimelineBuilder.buildEsperDailyWeeklyRestCandidateIntervalEvents(
|
||||||
|
rawDrivingInterruptionIntervals,
|
||||||
|
minimumRestPeriodMinutes
|
||||||
|
);
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals =
|
List<TachographEsperDrivingInterruptionIntervalEvent> dailyWeeklyRestCandidateIntervals =
|
||||||
clipEsperDrivingInterruptionIntervalEvents(
|
clipEsperDrivingInterruptionIntervalEvents(
|
||||||
rawDailyWeeklyRestCandidateIntervals,
|
rawDailyWeeklyRestCandidateIntervals,
|
||||||
|
|
@ -193,7 +186,9 @@ public class TachographFileSessionProcessingService {
|
||||||
requestedTo
|
requestedTo
|
||||||
);
|
);
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionVehicleChangeIntervals =
|
List<TachographEsperDrivingInterruptionIntervalEvent> rawDrivingInterruptionVehicleChangeIntervals =
|
||||||
derivedProjectionBundle.drivingInterruptionVehicleChangeIntervals();
|
driverTimelineBuilder.buildEsperDrivingInterruptionVehicleChangeIntervalEvents(
|
||||||
|
rawDailyWeeklyRestCandidateIntervals
|
||||||
|
);
|
||||||
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals =
|
List<TachographEsperDrivingInterruptionIntervalEvent> drivingInterruptionVehicleChangeIntervals =
|
||||||
clipEsperDrivingInterruptionIntervalEvents(
|
clipEsperDrivingInterruptionIntervalEvents(
|
||||||
rawDrivingInterruptionVehicleChangeIntervals,
|
rawDrivingInterruptionVehicleChangeIntervals,
|
||||||
|
|
@ -201,10 +196,13 @@ public class TachographFileSessionProcessingService {
|
||||||
requestedTo
|
requestedTo
|
||||||
);
|
);
|
||||||
List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals =
|
List<TachographEsperVuCardAbsentIntervalEvent> rawVuCardAbsentIntervals =
|
||||||
derivedProjectionBundle.vuCardAbsentIntervals();
|
driverTimelineBuilder.buildEsperVuCardAbsentIntervalEvents(timeline);
|
||||||
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals =
|
List<TachographEsperPotentialHomeOvernightStayIntervalEvent> potentialHomeOvernightStayIntervals =
|
||||||
clipEsperPotentialHomeOvernightStayIntervalEvents(
|
clipEsperPotentialHomeOvernightStayIntervalEvents(
|
||||||
derivedProjectionBundle.potentialHomeOvernightStayIntervals(),
|
driverTimelineBuilder.buildEsperPotentialHomeOvernightStayIntervalEvents(
|
||||||
|
rawDrivingInterruptionVehicleChangeIntervals,
|
||||||
|
rawVuCardAbsentIntervals
|
||||||
|
),
|
||||||
rawVuCardAbsentIntervals,
|
rawVuCardAbsentIntervals,
|
||||||
requestedFrom,
|
requestedFrom,
|
||||||
requestedTo
|
requestedTo
|
||||||
|
|
@ -248,17 +246,6 @@ public class TachographFileSessionProcessingService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResolvedDriverTimeline resolveTimeline(
|
|
||||||
TachographFileSession session,
|
|
||||||
DriverExtractionSession driver
|
|
||||||
) {
|
|
||||||
if (properties.getTachographFileSession().getProcessing().getTimelineInputMode()
|
|
||||||
== EventHubProperties.TimelineInputMode.EVENTS) {
|
|
||||||
return eventBackedDriverTimelineBuilder.build(session, driver);
|
|
||||||
}
|
|
||||||
return driverTimelineBuilder.build(session, driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TachographEsperActivityIntervalEvent> clipEsperActivityIntervalEvents(
|
private List<TachographEsperActivityIntervalEvent> clipEsperActivityIntervalEvents(
|
||||||
List<TachographEsperActivityIntervalEvent> intervals,
|
List<TachographEsperActivityIntervalEvent> intervals,
|
||||||
OffsetDateTime requestedFrom,
|
OffsetDateTime requestedFrom,
|
||||||
|
|
|
||||||
|
|
@ -369,9 +369,6 @@ public class VehicleUnitXmlExtractionService {
|
||||||
extractVuPlaceSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
|
extractVuPlaceSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
|
||||||
extractVuGnssSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
|
extractVuGnssSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
|
||||||
extractVuSpecificConditionSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
|
extractVuSpecificConditionSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
|
||||||
extractVuBorderCrossingSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
|
|
||||||
extractVuLoadUnloadSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
|
|
||||||
extractVuSpeedingSupportEvents(document, vehicleContext, vuCardIwIntervals, driversByKey, warnings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void extractVuPlaceSupportEvents(
|
private void extractVuPlaceSupportEvents(
|
||||||
|
|
@ -418,22 +415,16 @@ public class VehicleUnitXmlExtractionService {
|
||||||
occurredAt,
|
occurredAt,
|
||||||
"PLACE",
|
"PLACE",
|
||||||
mapPlaceEntryType(entryType),
|
mapPlaceEntryType(entryType),
|
||||||
null,
|
|
||||||
assignment.slot(),
|
assignment.slot(),
|
||||||
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
|
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
|
||||||
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
||||||
text(record, "placeRecord/dailyWorkPeriodCountry"),
|
text(record, "placeRecord/dailyWorkPeriodCountry"),
|
||||||
text(record, "placeRecord/dailyWorkPeriodRegion"),
|
text(record, "placeRecord/dailyWorkPeriodRegion"),
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
authenticationStatus,
|
authenticationStatus,
|
||||||
longValue(text(record, "placeRecord/vehicleOdometerValue")),
|
longValue(text(record, "placeRecord/vehicleOdometerValue")),
|
||||||
normalizeToken(entryType),
|
normalizeToken(entryType),
|
||||||
null,
|
|
||||||
null,
|
|
||||||
path
|
path
|
||||||
),
|
),
|
||||||
warnings
|
warnings
|
||||||
|
|
@ -489,23 +480,17 @@ public class VehicleUnitXmlExtractionService {
|
||||||
"VUGNSS-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
|
"VUGNSS-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
|
||||||
occurredAt,
|
occurredAt,
|
||||||
"POSITION",
|
"POSITION",
|
||||||
"POSITION_RECORDED",
|
"GNSS_ACCUMULATED_DRIVING",
|
||||||
"SNAPSHOT",
|
|
||||||
assignment.slot(),
|
assignment.slot(),
|
||||||
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
|
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
|
||||||
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
authenticationStatus,
|
authenticationStatus,
|
||||||
longValue(text(record, "vehicleOdometerValue")),
|
longValue(text(record, "vehicleOdometerValue")),
|
||||||
null,
|
null,
|
||||||
null,
|
|
||||||
null,
|
|
||||||
path
|
path
|
||||||
),
|
),
|
||||||
warnings
|
warnings
|
||||||
|
|
@ -546,7 +531,6 @@ public class VehicleUnitXmlExtractionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
String conditionCode = normalizeToken(text(record, "specificConditionType"));
|
String conditionCode = normalizeToken(text(record, "specificConditionType"));
|
||||||
String[] specificCondition = mapSpecificCondition(conditionCode);
|
|
||||||
for (DriverAssignment assignment : assignments) {
|
for (DriverAssignment assignment : assignments) {
|
||||||
addSupportEvent(
|
addSupportEvent(
|
||||||
driversByKey,
|
driversByKey,
|
||||||
|
|
@ -555,8 +539,7 @@ public class VehicleUnitXmlExtractionService {
|
||||||
"VUSC-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
|
"VUSC-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
|
||||||
occurredAt,
|
occurredAt,
|
||||||
"SPECIFIC_CONDITION",
|
"SPECIFIC_CONDITION",
|
||||||
specificCondition[0],
|
"SPECIFIC_CONDITION",
|
||||||
specificCondition[1],
|
|
||||||
assignment.slot(),
|
assignment.slot(),
|
||||||
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
|
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
|
||||||
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
||||||
|
|
@ -566,12 +549,7 @@ public class VehicleUnitXmlExtractionService {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
conditionCode,
|
conditionCode,
|
||||||
null,
|
|
||||||
null,
|
|
||||||
path
|
path
|
||||||
),
|
),
|
||||||
warnings
|
warnings
|
||||||
|
|
@ -580,247 +558,6 @@ public class VehicleUnitXmlExtractionService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void extractVuBorderCrossingSupportEvents(
|
|
||||||
Document document,
|
|
||||||
VehicleContext vehicleContext,
|
|
||||||
List<VuCardIwInterval> vuCardIwIntervals,
|
|
||||||
Map<String, DriverExtractionBuilder> driversByKey,
|
|
||||||
List<ExtractionWarning> warnings
|
|
||||||
) {
|
|
||||||
NodeList records = nodes(document, "/VehicleUnit/Activities/vuBorderCrossingData/vuBorderCrossingRecord");
|
|
||||||
for (int i = 0; i < records.getLength(); i++) {
|
|
||||||
Element record = (Element) records.item(i);
|
|
||||||
String path = "/VehicleUnit/Activities/vuBorderCrossingData/vuBorderCrossingRecord[" + (i + 1) + "]";
|
|
||||||
OffsetDateTime occurredAt = offsetDateTime(text(record, "gnssPlaceAuthRecord/timeStamp"));
|
|
||||||
if (occurredAt == null) {
|
|
||||||
warnings.add(new ExtractionWarning("VU_BORDER_CROSSING_MISSING_TIME", "Vehicle-unit border-crossing record is missing gnssPlaceAuthRecord/timeStamp.", path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DriverAssignment> assignments = new ArrayList<>();
|
|
||||||
assignments.addAll(resolveDriverAssignments(
|
|
||||||
occurredAt,
|
|
||||||
driverKeyFromCardNode(record, "cardNumberDriverSlot"),
|
|
||||||
"DRIVER",
|
|
||||||
vuCardIwIntervals
|
|
||||||
));
|
|
||||||
assignments.addAll(resolveDriverAssignments(
|
|
||||||
occurredAt,
|
|
||||||
driverKeyFromCardNode(record, "cardNumberCodriverSlot"),
|
|
||||||
"CO_DRIVER",
|
|
||||||
vuCardIwIntervals
|
|
||||||
));
|
|
||||||
assignments = distinctAssignments(assignments);
|
|
||||||
if (assignments.isEmpty()) {
|
|
||||||
warnings.add(new ExtractionWarning("VU_BORDER_CROSSING_UNASSIGNED", "Vehicle-unit border-crossing record could not be assigned to an active driver session.", path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigDecimal latitude = geoCoordinate(record, "gnssPlaceAuthRecord/geoCoordinates/latitude", true);
|
|
||||||
BigDecimal longitude = geoCoordinate(record, "gnssPlaceAuthRecord/geoCoordinates/longitude", false);
|
|
||||||
String authenticationStatus = normalizeToken(text(record, "gnssPlaceAuthRecord/authenticationStatus"));
|
|
||||||
for (DriverAssignment assignment : assignments) {
|
|
||||||
addSupportEvent(
|
|
||||||
driversByKey,
|
|
||||||
assignment,
|
|
||||||
new ExtractedSupportEvent(
|
|
||||||
"VUBORDER-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
|
|
||||||
occurredAt,
|
|
||||||
"BORDER_CROSSING",
|
|
||||||
"BORDER_OUTBOUND",
|
|
||||||
"OUTBOUND",
|
|
||||||
assignment.slot(),
|
|
||||||
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
|
|
||||||
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
text(record, "countryLeft"),
|
|
||||||
text(record, "countryEntered"),
|
|
||||||
null,
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
authenticationStatus,
|
|
||||||
longValue(text(record, "vehicleOdometerValue")),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
path
|
|
||||||
),
|
|
||||||
warnings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void extractVuLoadUnloadSupportEvents(
|
|
||||||
Document document,
|
|
||||||
VehicleContext vehicleContext,
|
|
||||||
List<VuCardIwInterval> vuCardIwIntervals,
|
|
||||||
Map<String, DriverExtractionBuilder> driversByKey,
|
|
||||||
List<ExtractionWarning> warnings
|
|
||||||
) {
|
|
||||||
NodeList records = nodes(document, "/VehicleUnit/Activities/vuLoadUnloadData/vuLoadUnloadRecord");
|
|
||||||
for (int i = 0; i < records.getLength(); i++) {
|
|
||||||
Element record = (Element) records.item(i);
|
|
||||||
String path = "/VehicleUnit/Activities/vuLoadUnloadData/vuLoadUnloadRecord[" + (i + 1) + "]";
|
|
||||||
OffsetDateTime occurredAt = offsetDateTime(text(record, "timeStamp"));
|
|
||||||
if (occurredAt == null) {
|
|
||||||
warnings.add(new ExtractionWarning("VU_LOAD_UNLOAD_MISSING_TIME", "Vehicle-unit load/unload record is missing timeStamp.", path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DriverAssignment> assignments = new ArrayList<>();
|
|
||||||
assignments.addAll(resolveDriverAssignments(
|
|
||||||
occurredAt,
|
|
||||||
driverKeyFromCardNode(record, "cardNumberDriverSlot"),
|
|
||||||
"DRIVER",
|
|
||||||
vuCardIwIntervals
|
|
||||||
));
|
|
||||||
assignments.addAll(resolveDriverAssignments(
|
|
||||||
occurredAt,
|
|
||||||
driverKeyFromCardNode(record, "cardNumberCodriverSlot"),
|
|
||||||
"CO_DRIVER",
|
|
||||||
vuCardIwIntervals
|
|
||||||
));
|
|
||||||
assignments = distinctAssignments(assignments);
|
|
||||||
if (assignments.isEmpty()) {
|
|
||||||
warnings.add(new ExtractionWarning("VU_LOAD_UNLOAD_UNASSIGNED", "Vehicle-unit load/unload record could not be assigned to an active driver session.", path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigDecimal latitude = geoCoordinate(record, "gnssPlaceAuthRecord/geoCoordinates/latitude", true);
|
|
||||||
BigDecimal longitude = geoCoordinate(record, "gnssPlaceAuthRecord/geoCoordinates/longitude", false);
|
|
||||||
String authenticationStatus = normalizeToken(text(record, "gnssPlaceAuthRecord/authenticationStatus"));
|
|
||||||
String operation = mapOperation(text(record, "operationType"));
|
|
||||||
for (DriverAssignment assignment : assignments) {
|
|
||||||
addSupportEvent(
|
|
||||||
driversByKey,
|
|
||||||
assignment,
|
|
||||||
new ExtractedSupportEvent(
|
|
||||||
"VULOAD-" + (i + 1) + "-" + assignment.driverKey() + "-" + assignment.slot(),
|
|
||||||
occurredAt,
|
|
||||||
"LOAD_UNLOAD",
|
|
||||||
operation,
|
|
||||||
"SNAPSHOT",
|
|
||||||
assignment.slot(),
|
|
||||||
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
|
|
||||||
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
operation,
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
authenticationStatus,
|
|
||||||
longValue(text(record, "vehicleOdometerValue")),
|
|
||||||
normalizeToken(text(record, "operationType")),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
path
|
|
||||||
),
|
|
||||||
warnings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void extractVuSpeedingSupportEvents(
|
|
||||||
Document document,
|
|
||||||
VehicleContext vehicleContext,
|
|
||||||
List<VuCardIwInterval> vuCardIwIntervals,
|
|
||||||
Map<String, DriverExtractionBuilder> driversByKey,
|
|
||||||
List<ExtractionWarning> warnings
|
|
||||||
) {
|
|
||||||
NodeList records = nodes(document, "/VehicleUnit/EventsFaults/vuOverSpeedingEventData/vuOverSpeedingEventRecords");
|
|
||||||
for (int i = 0; i < records.getLength(); i++) {
|
|
||||||
Element record = (Element) records.item(i);
|
|
||||||
String path = "/VehicleUnit/EventsFaults/vuOverSpeedingEventData/vuOverSpeedingEventRecords[" + (i + 1) + "]";
|
|
||||||
OffsetDateTime beginAt = offsetDateTime(text(record, "eventBeginTime"));
|
|
||||||
OffsetDateTime endAt = offsetDateTime(text(record, "eventEndTime"));
|
|
||||||
if (beginAt == null && endAt == null) {
|
|
||||||
warnings.add(new ExtractionWarning("VU_SPEEDING_MISSING_TIME", "Vehicle-unit speeding record is missing both eventBeginTime and eventEndTime.", path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DriverAssignment> assignments = distinctAssignments(resolveDriverAssignments(
|
|
||||||
beginAt != null ? beginAt : endAt,
|
|
||||||
driverKeyFromCardNode(record, "cardNumberDriverSlotBegin"),
|
|
||||||
"DRIVER",
|
|
||||||
vuCardIwIntervals
|
|
||||||
));
|
|
||||||
if (assignments.isEmpty()) {
|
|
||||||
warnings.add(new ExtractionWarning("VU_SPEEDING_UNASSIGNED", "Vehicle-unit speeding record could not be assigned to an active driver session.", path));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigDecimal avgSpeedKmh = decimalValue(text(record, "averageSpeedValue"));
|
|
||||||
BigDecimal maxSpeedKmh = decimalValue(text(record, "maxSpeedValue"));
|
|
||||||
for (DriverAssignment assignment : assignments) {
|
|
||||||
if (beginAt != null) {
|
|
||||||
addSupportEvent(
|
|
||||||
driversByKey,
|
|
||||||
assignment,
|
|
||||||
new ExtractedSupportEvent(
|
|
||||||
"VUSPEED-" + (i + 1) + "-" + assignment.driverKey() + "-BEGIN",
|
|
||||||
beginAt,
|
|
||||||
"SPEEDING",
|
|
||||||
"SPEEDING",
|
|
||||||
"BEGIN",
|
|
||||||
assignment.slot(),
|
|
||||||
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
|
|
||||||
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
normalizeToken(text(record, "eventType")),
|
|
||||||
avgSpeedKmh,
|
|
||||||
maxSpeedKmh,
|
|
||||||
path
|
|
||||||
),
|
|
||||||
warnings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (endAt != null) {
|
|
||||||
addSupportEvent(
|
|
||||||
driversByKey,
|
|
||||||
assignment,
|
|
||||||
new ExtractedSupportEvent(
|
|
||||||
"VUSPEED-" + (i + 1) + "-" + assignment.driverKey() + "-END",
|
|
||||||
endAt,
|
|
||||||
"SPEEDING",
|
|
||||||
"SPEEDING",
|
|
||||||
"END",
|
|
||||||
assignment.slot(),
|
|
||||||
vehicleContext.registration() == null ? null : vehicleContext.registration().registrationKey(),
|
|
||||||
vehicleContext.vehicle() == null ? null : vehicleContext.vehicle().vehicleKey(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
normalizeToken(text(record, "eventType")),
|
|
||||||
avgSpeedKmh,
|
|
||||||
maxSpeedKmh,
|
|
||||||
path
|
|
||||||
),
|
|
||||||
warnings
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPartiallyCovered(ExtractedCardActivityInterval interval, List<ActivitySegment> segments) {
|
private boolean isPartiallyCovered(ExtractedCardActivityInterval interval, List<ActivitySegment> segments) {
|
||||||
if (segments.isEmpty()) {
|
if (segments.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -959,13 +696,6 @@ public class VehicleUnitXmlExtractionService {
|
||||||
return Long.parseLong(value.trim());
|
return Long.parseLong(value.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal decimalValue(String value) {
|
|
||||||
if (value == null || value.isBlank()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new BigDecimal(value.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal geoCoordinate(Object node, String expression, boolean latitude) {
|
private BigDecimal geoCoordinate(Object node, String expression, boolean latitude) {
|
||||||
String value = text(node, expression);
|
String value = text(node, expression);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
|
@ -1029,32 +759,6 @@ public class VehicleUnitXmlExtractionService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] mapSpecificCondition(String conditionCode) {
|
|
||||||
String normalized = normalizeToken(conditionCode);
|
|
||||||
if (normalized == null) {
|
|
||||||
return new String[]{"UNKNOWN_EVENT", "SNAPSHOT"};
|
|
||||||
}
|
|
||||||
return switch (normalized) {
|
|
||||||
case "1" -> new String[]{"OUT", "BEGIN"};
|
|
||||||
case "2" -> new String[]{"OUT", "END"};
|
|
||||||
case "3" -> new String[]{"FERRY_TRAIN", "BEGIN"};
|
|
||||||
case "4" -> new String[]{"FERRY_TRAIN", "END"};
|
|
||||||
default -> new String[]{"UNKNOWN_EVENT", "SNAPSHOT"};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String mapOperation(String operationType) {
|
|
||||||
String normalized = normalizeToken(operationType);
|
|
||||||
if (normalized == null) {
|
|
||||||
return "LOAD_UNLOAD";
|
|
||||||
}
|
|
||||||
return switch (normalized) {
|
|
||||||
case "1", "LOAD" -> "LOAD";
|
|
||||||
case "2", "UNLOAD" -> "UNLOAD";
|
|
||||||
default -> "LOAD_UNLOAD";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String joinCardNumber(Element node, String basePath) {
|
private String joinCardNumber(Element node, String basePath) {
|
||||||
String driverIdentification = text(node, basePath + "/driverIdentification");
|
String driverIdentification = text(node, basePath + "/driverIdentification");
|
||||||
if (driverIdentification == null) {
|
if (driverIdentification == null) {
|
||||||
|
|
|
||||||
|
|
@ -1,253 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
package at.procon.eventhub.processing.model;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
class UnifiedDriverEventsRequestTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void buildsFileSessionRequest() {
|
|
||||||
UUID sessionId = UUID.randomUUID();
|
|
||||||
|
|
||||||
UnifiedDriverEventsRequest request = UnifiedDriverEventsRequest.forTachographFileSession(
|
|
||||||
sessionId,
|
|
||||||
" 12:123 ",
|
|
||||||
OffsetDateTime.parse("2026-05-01T00:00:00Z"),
|
|
||||||
OffsetDateTime.parse("2026-05-02T00:00:00Z")
|
|
||||||
);
|
|
||||||
|
|
||||||
assertThat(request.sourceFamily()).isEqualTo(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION);
|
|
||||||
assertThat(request.sessionId()).isEqualTo(sessionId);
|
|
||||||
assertThat(request.driverKey()).isEqualTo("12:123");
|
|
||||||
assertThat(request.tenantKey()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void buildsTachographDbDriverRequest() {
|
|
||||||
UnifiedDriverEventsRequest request = UnifiedDriverEventsRequest.forTachographDbDriver(
|
|
||||||
" default ",
|
|
||||||
" DRIVER:42 ",
|
|
||||||
"at",
|
|
||||||
" 123 ",
|
|
||||||
OffsetDateTime.parse("2026-05-01T00:00:00Z"),
|
|
||||||
OffsetDateTime.parse("2026-05-02T00:00:00Z")
|
|
||||||
);
|
|
||||||
|
|
||||||
assertThat(request.sourceFamily()).isEqualTo(UnifiedEventSourceFamily.TACHOGRAPH_DB);
|
|
||||||
assertThat(request.tenantKey()).isEqualTo("default");
|
|
||||||
assertThat(request.driverSourceEntityId()).isEqualTo("DRIVER:42");
|
|
||||||
assertThat(request.driverCardNation()).isEqualTo("AT");
|
|
||||||
assertThat(request.driverCardNumber()).isEqualTo("123");
|
|
||||||
assertThat(request.hasDriverSelector()).isTrue();
|
|
||||||
assertThat(request.hasVehicleSelector()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void buildsYellowFoxVehicleRequest() {
|
|
||||||
UnifiedDriverEventsRequest request = UnifiedDriverEventsRequest.forYellowFoxDbVehicle(
|
|
||||||
"default",
|
|
||||||
"VEHICLE:99",
|
|
||||||
"wdb123",
|
|
||||||
"de",
|
|
||||||
"W-123AB",
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
assertThat(request.sourceFamily()).isEqualTo(UnifiedEventSourceFamily.YELLOWFOX_DB);
|
|
||||||
assertThat(request.vehicleSourceEntityId()).isEqualTo("VEHICLE:99");
|
|
||||||
assertThat(request.vin()).isEqualTo("WDB123");
|
|
||||||
assertThat(request.registrationNation()).isEqualTo("DE");
|
|
||||||
assertThat(request.registrationNumber()).isEqualTo("W-123AB");
|
|
||||||
assertThat(request.hasVehicleSelector()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void rejectsDbRequestWithoutSubjectSelector() {
|
|
||||||
assertThatThrownBy(() -> new UnifiedDriverEventsRequest(
|
|
||||||
UnifiedEventSourceFamily.TACHOGRAPH_DB,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"default",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)).isInstanceOf(IllegalArgumentException.class)
|
|
||||||
.hasMessageContaining("At least one driver or vehicle selector");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
import at.procon.eventhub.config.EventHubProperties;
|
|
||||||
import at.procon.eventhub.dto.EventDomain;
|
|
||||||
import at.procon.eventhub.dto.EventLifecycle;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
|
||||||
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.service.DriverKeyFactory;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.DriverTimelineBuilder;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.InMemoryTachographFileSessionRepository;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.IntervalBackedDriverTimelineEventBuilder;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionRepository;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.VehicleKeyFactory;
|
|
||||||
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 UnifiedDriverEventSourceServiceTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void loadsNormalizedFileSessionEventsThroughUnifiedService() {
|
|
||||||
EventHubProperties properties = new EventHubProperties();
|
|
||||||
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
|
||||||
DriverTimelineBuilder timelineBuilder = new DriverTimelineBuilder();
|
|
||||||
UnifiedDriverEventSourceService service = new UnifiedDriverEventSourceService(List.of(
|
|
||||||
new TachographFileSessionUnifiedDriverEventSource(
|
|
||||||
repository,
|
|
||||||
new IntervalBackedDriverTimelineEventBuilder(
|
|
||||||
timelineBuilder,
|
|
||||||
new DriverKeyFactory(),
|
|
||||||
new VehicleKeyFactory(),
|
|
||||||
new EventDetailsFactory(new ObjectMapper())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
));
|
|
||||||
|
|
||||||
DriverExtractionSession driver = driver();
|
|
||||||
TachographFileSession session = session(driver);
|
|
||||||
repository.save(session);
|
|
||||||
|
|
||||||
List<at.procon.eventhub.dto.EventHubEventDto> events = service.loadDriverEvents(
|
|
||||||
UnifiedDriverEventsRequest.forTachographFileSession(
|
|
||||||
session.sessionId(),
|
|
||||||
driver.driverKey(),
|
|
||||||
OffsetDateTime.parse("2026-05-01T08:30:00Z"),
|
|
||||||
OffsetDateTime.parse("2026-05-01T09:00:00Z")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
assertThat(events).hasSize(3);
|
|
||||||
assertThat(events).extracting(event -> event.occurredAt())
|
|
||||||
.containsExactly(
|
|
||||||
OffsetDateTime.parse("2026-05-01T08:30:00Z"),
|
|
||||||
OffsetDateTime.parse("2026-05-01T08:45:00Z"),
|
|
||||||
OffsetDateTime.parse("2026-05-01T09:00:00Z")
|
|
||||||
);
|
|
||||||
assertThat(events).extracting(event -> event.eventDomain())
|
|
||||||
.containsExactly(
|
|
||||||
EventDomain.DRIVER_ACTIVITY,
|
|
||||||
EventDomain.POSITION,
|
|
||||||
EventDomain.DRIVER_ACTIVITY
|
|
||||||
);
|
|
||||||
assertThat(events.get(0).lifecycle()).isEqualTo(EventLifecycle.START);
|
|
||||||
assertThat(events.get(2).lifecycle()).isEqualTo(EventLifecycle.END);
|
|
||||||
assertThat(events.get(1).eventDetails().type()).isEqualTo("POSITION");
|
|
||||||
}
|
|
||||||
|
|
||||||
private DriverExtractionSession driver() {
|
|
||||||
return 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(new ExtractedSupportEvent(
|
|
||||||
"SUP-1",
|
|
||||||
OffsetDateTime.parse("2026-05-01T08:45:00Z"),
|
|
||||||
"POSITION",
|
|
||||||
"POSITION_RECORDED",
|
|
||||||
"SNAPSHOT",
|
|
||||||
"DRIVER",
|
|
||||||
"12:REG-1",
|
|
||||||
"VIN-1",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
BigDecimal.valueOf(48.2082),
|
|
||||||
BigDecimal.valueOf(16.3738),
|
|
||||||
"AUTHENTIC",
|
|
||||||
150L,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"raw-path"
|
|
||||||
)),
|
|
||||||
List.of()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TachographFileSession session(DriverExtractionSession driver) {
|
|
||||||
return 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, 1, 1, 1, 1, 0),
|
|
||||||
List.of(),
|
|
||||||
Instant.now(),
|
|
||||||
Instant.now().plus(4, ChronoUnit.HOURS)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
package at.procon.eventhub.processing.service;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
import at.procon.eventhub.config.EventHubProperties;
|
|
||||||
import at.procon.eventhub.processing.model.UnifiedDriverTimelineRequest;
|
|
||||||
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.ResolvedDriverTimeline;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.DriverKeyFactory;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.EventBackedDriverTimelineBuilder;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.InMemoryTachographFileSessionRepository;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.IntervalBackedDriverTimelineEventBuilder;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionRepository;
|
|
||||||
import at.procon.eventhub.tachographfilesession.service.VehicleKeyFactory;
|
|
||||||
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 UnifiedDriverTimelineServiceTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void reconstructsTimelineThroughUnifiedService() {
|
|
||||||
EventHubProperties properties = new EventHubProperties();
|
|
||||||
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
|
||||||
EventBackedDriverTimelineBuilder eventBackedBuilder = new EventBackedDriverTimelineBuilder(
|
|
||||||
new IntervalBackedDriverTimelineEventBuilder(
|
|
||||||
new at.procon.eventhub.tachographfilesession.service.DriverTimelineBuilder(),
|
|
||||||
new DriverKeyFactory(),
|
|
||||||
new VehicleKeyFactory(),
|
|
||||||
new EventDetailsFactory(new ObjectMapper())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
UnifiedDriverTimelineService service = new UnifiedDriverTimelineService(List.of(
|
|
||||||
new TachographFileSessionUnifiedDriverTimelineSource(repository, eventBackedBuilder)
|
|
||||||
));
|
|
||||||
|
|
||||||
DriverExtractionSession driver = driver();
|
|
||||||
TachographFileSession session = session(driver);
|
|
||||||
repository.save(session);
|
|
||||||
|
|
||||||
ResolvedDriverTimeline timeline = service.loadDriverTimeline(
|
|
||||||
UnifiedDriverTimelineRequest.forTachographFileSession(session.sessionId(), driver.driverKey())
|
|
||||||
);
|
|
||||||
|
|
||||||
assertThat(timeline.sourceKind()).isEqualTo("DRIVER_CARD");
|
|
||||||
assertThat(timeline.loadedFrom()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:00:00Z"));
|
|
||||||
assertThat(timeline.loadedTo()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
|
||||||
assertThat(timeline.activityIntervals()).hasSize(1);
|
|
||||||
assertThat(timeline.vehicleUsageIntervals()).hasSize(1);
|
|
||||||
assertThat(timeline.supportEvents()).hasSize(1);
|
|
||||||
assertThat(timeline.activityIntervals().get(0).activityType()).isEqualTo("DRIVE");
|
|
||||||
assertThat(timeline.vehicleUsageIntervals().get(0).registrationKey()).isEqualTo("12:REG-1");
|
|
||||||
}
|
|
||||||
|
|
||||||
private DriverExtractionSession driver() {
|
|
||||||
return 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(new ExtractedSupportEvent(
|
|
||||||
"SUP-1",
|
|
||||||
OffsetDateTime.parse("2026-05-01T08:45:00Z"),
|
|
||||||
"POSITION",
|
|
||||||
"POSITION_RECORDED",
|
|
||||||
"SNAPSHOT",
|
|
||||||
"DRIVER",
|
|
||||||
"12:REG-1",
|
|
||||||
"VIN-1",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
BigDecimal.valueOf(48.2082),
|
|
||||||
BigDecimal.valueOf(16.3738),
|
|
||||||
"AUTHENTIC",
|
|
||||||
150L,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"raw-path"
|
|
||||||
)),
|
|
||||||
List.of()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TachographFileSession session(DriverExtractionSession driver) {
|
|
||||||
return 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, 1, 1, 1, 1, 0),
|
|
||||||
List.of(),
|
|
||||||
Instant.now(),
|
|
||||||
Instant.now().plus(4, ChronoUnit.HOURS)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -49,13 +49,6 @@ class DriverCardXmlExtractionServiceTest {
|
||||||
assertThat(driver.cardActivityIntervals().get(0).registrationKey()).isEqualTo("12:W-12345A");
|
assertThat(driver.cardActivityIntervals().get(0).registrationKey()).isEqualTo("12:W-12345A");
|
||||||
assertThat(driver.cardActivityIntervals().get(1).to()).isEqualTo(driver.cardVehicleUsageIntervals().get(0).to().plusSeconds(1));
|
assertThat(driver.cardActivityIntervals().get(1).to()).isEqualTo(driver.cardVehicleUsageIntervals().get(0).to().plusSeconds(1));
|
||||||
assertThat(driver.cardActivityIntervals().get(2).registrationKey()).isEqualTo("12:W-54321B");
|
assertThat(driver.cardActivityIntervals().get(2).registrationKey()).isEqualTo("12:W-54321B");
|
||||||
assertThat(driver.supportEvents()).hasSize(5);
|
|
||||||
assertThat(driver.supportEvents()).extracting("eventDomain")
|
|
||||||
.containsExactly("PLACE", "POSITION", "SPECIFIC_CONDITION", "BORDER_CROSSING", "LOAD_UNLOAD");
|
|
||||||
assertThat(driver.supportEvents().get(2).eventType()).isEqualTo("FERRY_TRAIN");
|
|
||||||
assertThat(driver.supportEvents().get(2).eventLifecycle()).isEqualTo("BEGIN");
|
|
||||||
assertThat(driver.supportEvents().get(3).countryFrom()).isEqualTo("12");
|
|
||||||
assertThat(driver.supportEvents().get(4).operation()).isEqualTo("UNLOAD");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -109,92 +109,6 @@ final class DriverCardXmlSamples {
|
||||||
</cardDriverActivity>
|
</cardDriverActivity>
|
||||||
<signature></signature>
|
<signature></signature>
|
||||||
</DriverActivityData>
|
</DriverActivityData>
|
||||||
<Places>
|
|
||||||
<cardPlaceDailyWorkPeriod>
|
|
||||||
<placeRecords>
|
|
||||||
<entryTime>2026-04-01T08:00:00Z</entryTime>
|
|
||||||
<entryTypeDailyWorkPeriod>0</entryTypeDailyWorkPeriod>
|
|
||||||
<dailyWorkPeriodCountry>12</dailyWorkPeriodCountry>
|
|
||||||
<dailyWorkPeriodRegion>9</dailyWorkPeriodRegion>
|
|
||||||
<vehicleOdometerValue>1000</vehicleOdometerValue>
|
|
||||||
<entryGnssPlaceRecord>
|
|
||||||
<timeStamp>2026-04-01T08:00:00Z</timeStamp>
|
|
||||||
<gnssAccuracy>5</gnssAccuracy>
|
|
||||||
<geoCoordinates>
|
|
||||||
<latitude>48.2082</latitude>
|
|
||||||
<longitude>16.3738</longitude>
|
|
||||||
</geoCoordinates>
|
|
||||||
<authenticationStatus>1</authenticationStatus>
|
|
||||||
</entryGnssPlaceRecord>
|
|
||||||
</placeRecords>
|
|
||||||
</cardPlaceDailyWorkPeriod>
|
|
||||||
<signature></signature>
|
|
||||||
</Places>
|
|
||||||
<SpecificConditions>
|
|
||||||
<specificConditionData>
|
|
||||||
<specificConditionRecord>
|
|
||||||
<entryTime>2026-04-01T10:30:00Z</entryTime>
|
|
||||||
<specificConditionType>3</specificConditionType>
|
|
||||||
</specificConditionRecord>
|
|
||||||
</specificConditionData>
|
|
||||||
<signature></signature>
|
|
||||||
</SpecificConditions>
|
|
||||||
<GnssPlaces>
|
|
||||||
<gnssAccumulatedDriving>
|
|
||||||
<gnssAccumulatedDrivingRecord>
|
|
||||||
<timeStamp>2026-04-01T09:30:00Z</timeStamp>
|
|
||||||
<gnssPlaceRecord>
|
|
||||||
<timeStamp>2026-04-01T09:30:00Z</timeStamp>
|
|
||||||
<gnssAccuracy>4</gnssAccuracy>
|
|
||||||
<geoCoordinates>
|
|
||||||
<latitude>48.2000</latitude>
|
|
||||||
<longitude>16.3600</longitude>
|
|
||||||
</geoCoordinates>
|
|
||||||
<authenticationStatus>1</authenticationStatus>
|
|
||||||
</gnssPlaceRecord>
|
|
||||||
<vehicleOdometerValue>1050</vehicleOdometerValue>
|
|
||||||
</gnssAccumulatedDrivingRecord>
|
|
||||||
</gnssAccumulatedDriving>
|
|
||||||
<signature></signature>
|
|
||||||
</GnssPlaces>
|
|
||||||
<BorderCrossings>
|
|
||||||
<cardBorderCrossings>
|
|
||||||
<cardBorderCrossingRecord>
|
|
||||||
<countryLeft>12</countryLeft>
|
|
||||||
<countryEntered>56</countryEntered>
|
|
||||||
<gnssPlaceAuthRecord>
|
|
||||||
<timeStamp>2026-04-01T12:15:00Z</timeStamp>
|
|
||||||
<gnssAccuracy>4</gnssAccuracy>
|
|
||||||
<geoCoordinates>
|
|
||||||
<latitude>48.3000</latitude>
|
|
||||||
<longitude>16.5000</longitude>
|
|
||||||
</geoCoordinates>
|
|
||||||
<authenticationStatus>0</authenticationStatus>
|
|
||||||
</gnssPlaceAuthRecord>
|
|
||||||
<vehicleOdometerValue>1120</vehicleOdometerValue>
|
|
||||||
</cardBorderCrossingRecord>
|
|
||||||
</cardBorderCrossings>
|
|
||||||
<signature></signature>
|
|
||||||
</BorderCrossings>
|
|
||||||
<LoadUnloadOperations>
|
|
||||||
<cardLoadUnloadOperations>
|
|
||||||
<cardLoadUnloadRecord>
|
|
||||||
<timeStamp>2026-04-01T12:45:00Z</timeStamp>
|
|
||||||
<operationType>2</operationType>
|
|
||||||
<gnssPlaceAuthRecord>
|
|
||||||
<timeStamp>2026-04-01T12:45:00Z</timeStamp>
|
|
||||||
<gnssAccuracy>3</gnssAccuracy>
|
|
||||||
<geoCoordinates>
|
|
||||||
<latitude>48.3100</latitude>
|
|
||||||
<longitude>16.5100</longitude>
|
|
||||||
</geoCoordinates>
|
|
||||||
<authenticationStatus>1</authenticationStatus>
|
|
||||||
</gnssPlaceAuthRecord>
|
|
||||||
<vehicleOdometerValue>1130</vehicleOdometerValue>
|
|
||||||
</cardLoadUnloadRecord>
|
|
||||||
</cardLoadUnloadOperations>
|
|
||||||
<signature></signature>
|
|
||||||
</LoadUnloadOperations>
|
|
||||||
</DriverCard>
|
</DriverCard>
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,6 @@ class DriverTimelineBuilderTest {
|
||||||
OffsetDateTime.parse("2026-05-01T08:30:00Z"),
|
OffsetDateTime.parse("2026-05-01T08:30:00Z"),
|
||||||
"PLACE",
|
"PLACE",
|
||||||
"BEGIN_DAILY_WORK_PERIOD",
|
"BEGIN_DAILY_WORK_PERIOD",
|
||||||
null,
|
|
||||||
"DRIVER",
|
"DRIVER",
|
||||||
"12:REG-1",
|
"12:REG-1",
|
||||||
"VIN-1",
|
"VIN-1",
|
||||||
|
|
@ -88,11 +87,6 @@ class DriverTimelineBuilderTest {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"d"
|
"d"
|
||||||
)),
|
)),
|
||||||
List.of(new ExtractionWarning("W1", "warning", "/x"))
|
List.of(new ExtractionWarning("W1", "warning", "/x"))
|
||||||
|
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
package at.procon.eventhub.tachographfilesession.service;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
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.ResolvedDriverTimeline;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
|
||||||
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
|
|
||||||
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 EventBackedDriverTimelineBuilderTest {
|
|
||||||
|
|
||||||
private final DriverTimelineBuilder directBuilder = new DriverTimelineBuilder();
|
|
||||||
private final EventBackedDriverTimelineBuilder eventBackedBuilder =
|
|
||||||
new EventBackedDriverTimelineBuilder(
|
|
||||||
new IntervalBackedDriverTimelineEventBuilder(
|
|
||||||
directBuilder,
|
|
||||||
new DriverKeyFactory(),
|
|
||||||
new VehicleKeyFactory(),
|
|
||||||
new EventDetailsFactory(new ObjectMapper())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void reconstructsTimelineFromEvents() {
|
|
||||||
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(new ExtractedSupportEvent(
|
|
||||||
"SUP-1",
|
|
||||||
OffsetDateTime.parse("2026-05-01T08:45:00Z"),
|
|
||||||
"POSITION",
|
|
||||||
"POSITION_RECORDED",
|
|
||||||
"SNAPSHOT",
|
|
||||||
"DRIVER",
|
|
||||||
"12:REG-1",
|
|
||||||
"VIN-1",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
BigDecimal.valueOf(48.2082),
|
|
||||||
BigDecimal.valueOf(16.3738),
|
|
||||||
"AUTHENTIC",
|
|
||||||
150L,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"raw-path"
|
|
||||||
)),
|
|
||||||
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, 1, 1, 1, 1, 0),
|
|
||||||
List.of(),
|
|
||||||
Instant.now(),
|
|
||||||
Instant.now().plus(4, ChronoUnit.HOURS)
|
|
||||||
);
|
|
||||||
|
|
||||||
ResolvedDriverTimeline direct = directBuilder.build(session, driver);
|
|
||||||
ResolvedDriverTimeline reconstructed = eventBackedBuilder.build(session, driver);
|
|
||||||
|
|
||||||
assertThat(reconstructed.sourceKind()).isEqualTo(direct.sourceKind());
|
|
||||||
assertThat(reconstructed.loadedFrom()).isEqualTo(direct.loadedFrom());
|
|
||||||
assertThat(reconstructed.loadedTo()).isEqualTo(direct.loadedTo());
|
|
||||||
assertThat(reconstructed.activityIntervals()).containsExactlyElementsOf(direct.activityIntervals());
|
|
||||||
assertThat(reconstructed.vehicleUsageIntervals()).containsExactlyElementsOf(direct.vehicleUsageIntervals());
|
|
||||||
assertThat(reconstructed.supportEvents()).containsExactlyElementsOf(direct.supportEvents());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
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",
|
|
||||||
"POSITION_RECORDED",
|
|
||||||
"SNAPSHOT",
|
|
||||||
"DRIVER",
|
|
||||||
"12:REG-1",
|
|
||||||
"VIN-1",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
BigDecimal.valueOf(48.2082),
|
|
||||||
BigDecimal.valueOf(16.3738),
|
|
||||||
"AUTHENTIC",
|
|
||||||
150L,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,6 @@ package at.procon.eventhub.tachographfilesession.service;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
import at.procon.eventhub.config.EventHubProperties;
|
import at.procon.eventhub.config.EventHubProperties;
|
||||||
import at.procon.eventhub.service.EventDetailsFactory;
|
|
||||||
import at.procon.eventhub.tachographfilesession.dto.TachographEsperEventsProcessingRequest;
|
import at.procon.eventhub.tachographfilesession.dto.TachographEsperEventsProcessingRequest;
|
||||||
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
|
import at.procon.eventhub.tachographfilesession.dto.TachographEsperDriverProcessingResultDto;
|
||||||
import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingRequest;
|
import at.procon.eventhub.tachographfilesession.dto.TachographOperatingPeriodsProcessingRequest;
|
||||||
|
|
@ -20,7 +19,6 @@ import java.time.temporal.ChronoUnit;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class TachographFileSessionProcessingServiceTest {
|
class TachographFileSessionProcessingServiceTest {
|
||||||
|
|
@ -29,19 +27,9 @@ 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,
|
||||||
driverTimelineBuilder,
|
new DriverTimelineBuilder(),
|
||||||
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder),
|
|
||||||
new EventBackedDriverTimelineBuilder(
|
|
||||||
new IntervalBackedDriverTimelineEventBuilder(
|
|
||||||
driverTimelineBuilder,
|
|
||||||
new DriverKeyFactory(),
|
|
||||||
new VehicleKeyFactory(),
|
|
||||||
new EventDetailsFactory(new ObjectMapper())
|
|
||||||
)
|
|
||||||
),
|
|
||||||
properties
|
properties
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -111,19 +99,9 @@ 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,
|
||||||
driverTimelineBuilder,
|
new DriverTimelineBuilder(),
|
||||||
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder),
|
|
||||||
new EventBackedDriverTimelineBuilder(
|
|
||||||
new IntervalBackedDriverTimelineEventBuilder(
|
|
||||||
driverTimelineBuilder,
|
|
||||||
new DriverKeyFactory(),
|
|
||||||
new VehicleKeyFactory(),
|
|
||||||
new EventDetailsFactory(new ObjectMapper())
|
|
||||||
)
|
|
||||||
),
|
|
||||||
properties
|
properties
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -209,109 +187,13 @@ class TachographFileSessionProcessingServiceTest {
|
||||||
assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
|
assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void canUseEventBackedTimelineModeForEsperProcessing() {
|
|
||||||
EventHubProperties properties = new EventHubProperties();
|
|
||||||
properties.getTachographFileSession().getProcessing().setTimelineInputMode(EventHubProperties.TimelineInputMode.EVENTS);
|
|
||||||
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
|
||||||
DriverTimelineBuilder driverTimelineBuilder = new DriverTimelineBuilder();
|
|
||||||
TachographFileSessionProcessingService service = new TachographFileSessionProcessingService(
|
|
||||||
repository,
|
|
||||||
driverTimelineBuilder,
|
|
||||||
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder),
|
|
||||||
new EventBackedDriverTimelineBuilder(
|
|
||||||
new IntervalBackedDriverTimelineEventBuilder(
|
|
||||||
driverTimelineBuilder,
|
|
||||||
new DriverKeyFactory(),
|
|
||||||
new VehicleKeyFactory(),
|
|
||||||
new EventDetailsFactory(new ObjectMapper())
|
|
||||||
)
|
|
||||||
),
|
|
||||||
properties
|
|
||||||
);
|
|
||||||
|
|
||||||
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-01T11:00:00Z"),
|
|
||||||
100L,
|
|
||||||
200L,
|
|
||||||
"12:REG-1",
|
|
||||||
"VIN-1",
|
|
||||||
"vu-1"
|
|
||||||
),
|
|
||||||
new ExtractedCardVehicleUsageInterval(
|
|
||||||
"CVU-2",
|
|
||||||
OffsetDateTime.parse("2026-05-01T12:00:00Z"),
|
|
||||||
OffsetDateTime.parse("2026-05-01T13:00:00Z"),
|
|
||||||
201L,
|
|
||||||
260L,
|
|
||||||
"12:REG-2",
|
|
||||||
"VIN-2",
|
|
||||||
"vu-2"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
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"),
|
|
||||||
new ExtractedCardActivityInterval("ACT-2", OffsetDateTime.parse("2026-05-01T09:00:00Z"), OffsetDateTime.parse("2026-05-01T10:00:00Z"), "WORK", "DRIVER", "INSERTED", "SINGLE", "12:REG-1", "VIN-1", "b"),
|
|
||||||
new ExtractedCardActivityInterval("ACT-3", OffsetDateTime.parse("2026-05-01T10:00:00Z"), OffsetDateTime.parse("2026-05-01T10:05:00Z"), "DRIVE", "DRIVER", "INSERTED", "SINGLE", "12:REG-2", "VIN-2", "c")
|
|
||||||
),
|
|
||||||
List.of(),
|
|
||||||
List.of()
|
|
||||||
);
|
|
||||||
TachographFileSession session = new TachographFileSession(
|
|
||||||
UUID.randomUUID(),
|
|
||||||
new TachographFileSessionMetadata("default", "legalrequirements-drivercard", "sample", "sample.ddd", "a", 3, "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)
|
|
||||||
);
|
|
||||||
repository.save(session);
|
|
||||||
|
|
||||||
TachographEsperDriverProcessingResultDto result = service.getEsperDriverProcessingResults(
|
|
||||||
session.sessionId(),
|
|
||||||
driver.driverKey(),
|
|
||||||
new TachographEsperEventsProcessingRequest(
|
|
||||||
OffsetDateTime.parse("2026-05-01T08:45:00Z"),
|
|
||||||
OffsetDateTime.parse("2026-05-01T12:30:00Z"),
|
|
||||||
3,
|
|
||||||
720
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
assertThat(result.activityIntervalCount()).isEqualTo(3);
|
|
||||||
assertThat(result.drivingIntervalCount()).isEqualTo(2);
|
|
||||||
assertThat(result.drivingInterruptionIntervalCount()).isEqualTo(1);
|
|
||||||
assertThat(result.vehicleUsageIntervalCount()).isEqualTo(2);
|
|
||||||
assertThat(result.vuCardAbsentIntervalCount()).isEqualTo(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
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,
|
||||||
driverTimelineBuilder,
|
new DriverTimelineBuilder(),
|
||||||
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder),
|
|
||||||
new EventBackedDriverTimelineBuilder(
|
|
||||||
new IntervalBackedDriverTimelineEventBuilder(
|
|
||||||
driverTimelineBuilder,
|
|
||||||
new DriverKeyFactory(),
|
|
||||||
new VehicleKeyFactory(),
|
|
||||||
new EventDetailsFactory(new ObjectMapper())
|
|
||||||
)
|
|
||||||
),
|
|
||||||
properties
|
properties
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -390,19 +272,9 @@ 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,
|
||||||
driverTimelineBuilder,
|
new DriverTimelineBuilder(),
|
||||||
new DriverTimelineReusableProjectionBuilder(driverTimelineBuilder),
|
|
||||||
new EventBackedDriverTimelineBuilder(
|
|
||||||
new IntervalBackedDriverTimelineEventBuilder(
|
|
||||||
driverTimelineBuilder,
|
|
||||||
new DriverKeyFactory(),
|
|
||||||
new VehicleKeyFactory(),
|
|
||||||
new EventDetailsFactory(new ObjectMapper())
|
|
||||||
)
|
|
||||||
),
|
|
||||||
properties
|
properties
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,17 +65,10 @@ class VehicleUnitXmlExtractionServiceTest {
|
||||||
assertThat(secondDriver.cardVehicleUsageIntervals().get(0).to()).isNull();
|
assertThat(secondDriver.cardVehicleUsageIntervals().get(0).to()).isNull();
|
||||||
assertThat(secondDriver.cardActivityIntervals()).hasSize(1);
|
assertThat(secondDriver.cardActivityIntervals()).hasSize(1);
|
||||||
assertThat(secondDriver.cardActivityIntervals().get(0).from().toString()).isEqualTo("2026-04-02T07:30Z");
|
assertThat(secondDriver.cardActivityIntervals().get(0).from().toString()).isEqualTo("2026-04-02T07:30Z");
|
||||||
assertThat(secondDriver.supportEvents()).hasSize(6);
|
assertThat(secondDriver.supportEvents()).hasSize(2);
|
||||||
assertThat(secondDriver.supportEvents()).extracting("eventDomain")
|
assertThat(secondDriver.supportEvents()).extracting("eventDomain").containsExactly("POSITION", "SPECIFIC_CONDITION");
|
||||||
.containsExactly("POSITION", "SPECIFIC_CONDITION", "BORDER_CROSSING", "LOAD_UNLOAD", "SPEEDING", "SPEEDING");
|
|
||||||
assertThat(secondDriver.supportEvents().get(0).latitude()).isNotNull();
|
assertThat(secondDriver.supportEvents().get(0).latitude()).isNotNull();
|
||||||
assertThat(secondDriver.supportEvents().get(1).eventType()).isEqualTo("OUT");
|
assertThat(secondDriver.supportEvents().get(1).code()).isEqualTo("1");
|
||||||
assertThat(secondDriver.supportEvents().get(1).eventLifecycle()).isEqualTo("BEGIN");
|
|
||||||
assertThat(secondDriver.supportEvents().get(2).countryFrom()).isEqualTo("12");
|
|
||||||
assertThat(secondDriver.supportEvents().get(2).countryTo()).isEqualTo("56");
|
|
||||||
assertThat(secondDriver.supportEvents().get(3).operation()).isEqualTo("LOAD");
|
|
||||||
assertThat(secondDriver.supportEvents().get(4).eventLifecycle()).isEqualTo("BEGIN");
|
|
||||||
assertThat(secondDriver.supportEvents().get(5).eventLifecycle()).isEqualTo("END");
|
|
||||||
|
|
||||||
assertThat(session.warnings()).isEmpty();
|
assertThat(session.warnings()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,80 +169,7 @@ final class VehicleUnitXmlSamples {
|
||||||
<specificConditionType>1</specificConditionType>
|
<specificConditionType>1</specificConditionType>
|
||||||
</specificConditionRecords>
|
</specificConditionRecords>
|
||||||
</vuSpecificConditionData>
|
</vuSpecificConditionData>
|
||||||
<vuBorderCrossingData>
|
|
||||||
<vuBorderCrossingRecord>
|
|
||||||
<cardNumberDriverSlot>
|
|
||||||
<cardType>DRIVER_CARD</cardType>
|
|
||||||
<cardIssuingMemberState>12</cardIssuingMemberState>
|
|
||||||
<cardNumber>
|
|
||||||
<driverIdentification>999999999999</driverIdentification>
|
|
||||||
<cardReplacementIndex>1</cardReplacementIndex>
|
|
||||||
<cardRenewalIndex>1</cardRenewalIndex>
|
|
||||||
</cardNumber>
|
|
||||||
<generation>2</generation>
|
|
||||||
</cardNumberDriverSlot>
|
|
||||||
<countryLeft>12</countryLeft>
|
|
||||||
<countryEntered>56</countryEntered>
|
|
||||||
<gnssPlaceAuthRecord>
|
|
||||||
<timeStamp>2026-04-02T08:30:00Z</timeStamp>
|
|
||||||
<geoCoordinates>
|
|
||||||
<latitude>48020</latitude>
|
|
||||||
<longitude>16380</longitude>
|
|
||||||
</geoCoordinates>
|
|
||||||
<authenticationStatus>AUTHENTICATED</authenticationStatus>
|
|
||||||
</gnssPlaceAuthRecord>
|
|
||||||
<vehicleOdometerValue>1220</vehicleOdometerValue>
|
|
||||||
</vuBorderCrossingRecord>
|
|
||||||
</vuBorderCrossingData>
|
|
||||||
<vuLoadUnloadData>
|
|
||||||
<vuLoadUnloadRecord>
|
|
||||||
<timeStamp>2026-04-02T08:45:00Z</timeStamp>
|
|
||||||
<operationType>1</operationType>
|
|
||||||
<cardNumberDriverSlot>
|
|
||||||
<cardType>DRIVER_CARD</cardType>
|
|
||||||
<cardIssuingMemberState>12</cardIssuingMemberState>
|
|
||||||
<cardNumber>
|
|
||||||
<driverIdentification>999999999999</driverIdentification>
|
|
||||||
<cardReplacementIndex>1</cardReplacementIndex>
|
|
||||||
<cardRenewalIndex>1</cardRenewalIndex>
|
|
||||||
</cardNumber>
|
|
||||||
<generation>2</generation>
|
|
||||||
</cardNumberDriverSlot>
|
|
||||||
<gnssPlaceAuthRecord>
|
|
||||||
<timeStamp>2026-04-02T08:45:00Z</timeStamp>
|
|
||||||
<geoCoordinates>
|
|
||||||
<latitude>48025</latitude>
|
|
||||||
<longitude>16385</longitude>
|
|
||||||
</geoCoordinates>
|
|
||||||
<authenticationStatus>AUTHENTICATED</authenticationStatus>
|
|
||||||
</gnssPlaceAuthRecord>
|
|
||||||
<vehicleOdometerValue>1225</vehicleOdometerValue>
|
|
||||||
</vuLoadUnloadRecord>
|
|
||||||
</vuLoadUnloadData>
|
|
||||||
</Activities>
|
</Activities>
|
||||||
<EventsFaults>
|
|
||||||
<vuOverSpeedingEventData>
|
|
||||||
<vuOverSpeedingEventRecords>
|
|
||||||
<eventType>1</eventType>
|
|
||||||
<eventRecordPurpose>1</eventRecordPurpose>
|
|
||||||
<eventBeginTime>2026-04-02T08:10:00Z</eventBeginTime>
|
|
||||||
<eventEndTime>2026-04-02T08:12:00Z</eventEndTime>
|
|
||||||
<maxSpeedValue>96</maxSpeedValue>
|
|
||||||
<averageSpeedValue>91</averageSpeedValue>
|
|
||||||
<cardNumberDriverSlotBegin>
|
|
||||||
<cardType>DRIVER_CARD</cardType>
|
|
||||||
<cardIssuingMemberState>12</cardIssuingMemberState>
|
|
||||||
<cardNumber>
|
|
||||||
<driverIdentification>999999999999</driverIdentification>
|
|
||||||
<cardReplacementIndex>1</cardReplacementIndex>
|
|
||||||
<cardRenewalIndex>1</cardRenewalIndex>
|
|
||||||
</cardNumber>
|
|
||||||
<generation>2</generation>
|
|
||||||
</cardNumberDriverSlotBegin>
|
|
||||||
<similarEventsNumber>1</similarEventsNumber>
|
|
||||||
</vuOverSpeedingEventRecords>
|
|
||||||
</vuOverSpeedingEventData>
|
|
||||||
</EventsFaults>
|
|
||||||
</VehicleUnit>
|
</VehicleUnit>
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue