[Scheduler] Implemented inspection, gps, odometer, safety-event
This commit is contained in:
parent
dc10f8b1d7
commit
e1d17b7af7
|
|
@ -1,7 +1,8 @@
|
|||
package com.goi.integration.samsara.client;
|
||||
|
||||
import com.goi.integration.common.dto.ExtIngestResult;
|
||||
import com.goi.integration.samsara.dto.ExtInspectionIngestCommand;
|
||||
import com.goi.integration.samsara.dto.VehicleInspectionIngestCommand;
|
||||
import com.goi.integration.samsara.dto.VehicleStatOdometerCommand;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -27,11 +28,7 @@ public class OprIngestClient {
|
|||
// @Autowired
|
||||
// private ObjectMapper objectMapper;
|
||||
|
||||
public ExtIngestResult ingestInspection(ExtInspectionIngestCommand command) {
|
||||
// String json = objectMapper
|
||||
// .writerWithDefaultPrettyPrinter()
|
||||
// .writeValueAsString(command);
|
||||
//
|
||||
public ExtIngestResult ingestInspection(VehicleInspectionIngestCommand command) {
|
||||
log.info(
|
||||
"[OPR_INGEST][REQUEST] source={}, type={}, fetchedAt={}",
|
||||
command.getSource(),
|
||||
|
|
@ -39,19 +36,6 @@ public class OprIngestClient {
|
|||
command.getFetchedAt()
|
||||
);
|
||||
|
||||
/*
|
||||
try {
|
||||
log.debug(
|
||||
"[OPR_INGEST][REQUEST_BODY]\n{}",
|
||||
new com.fasterxml.jackson.databind.ObjectMapper()
|
||||
.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(command)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.debug("[OPR_INGEST][REQUEST_BODY] failed to serialize", e);
|
||||
}
|
||||
*/
|
||||
|
||||
try {
|
||||
ExtIngestResult result = oprWebClient.post()
|
||||
.uri("/ext/samsara/inspections/ingest")
|
||||
|
|
@ -97,4 +81,63 @@ public class OprIngestClient {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================
|
||||
* Odometer ingest
|
||||
* ========================= */
|
||||
public ExtIngestResult ingestVehicleOdometer(
|
||||
VehicleStatOdometerCommand command
|
||||
) {
|
||||
|
||||
log.info(
|
||||
"[OPR_INGEST][ODOMETER][REQUEST] source={}, records={}, fetchedAt={}",
|
||||
command.getSource(),
|
||||
command.getRecords() != null ? command.getRecords().size() : 0,
|
||||
command.getFetchedAt()
|
||||
);
|
||||
|
||||
try {
|
||||
ExtIngestResult result = oprWebClient.post()
|
||||
.uri("/ext/samsara/odometer/ingest")
|
||||
.header(INTERNAL_TOKEN_HEADER, token)
|
||||
.bodyValue(command)
|
||||
.retrieve()
|
||||
.onStatus(
|
||||
status -> status.is4xxClientError() || status.is5xxServerError(),
|
||||
resp -> resp.bodyToMono(String.class)
|
||||
.map(body -> {
|
||||
if (resp.statusCode().value() == 403) {
|
||||
return new IllegalStateException(
|
||||
"OPR_AUTH_FAILED: " + body
|
||||
);
|
||||
}
|
||||
return new RuntimeException(
|
||||
"OPR_INGEST_HTTP_ERROR: " +
|
||||
resp.statusCode() + " body=" + body
|
||||
);
|
||||
})
|
||||
)
|
||||
.bodyToMono(ExtIngestResult.class)
|
||||
.block();
|
||||
|
||||
log.info(
|
||||
"[OPR_INGEST][ODOMETER][SUCCESS] received={}, inserted={}, updated={}, skipped={}",
|
||||
result.getReceived(),
|
||||
result.getInserted(),
|
||||
result.getUpdated(),
|
||||
result.getSkipped()
|
||||
);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error(
|
||||
"[OPR_INGEST][ODOMETER][FAILED] source={}, error={}",
|
||||
command.getSource(),
|
||||
e.getMessage(),
|
||||
e
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,32 @@ public class SamsaraClient {
|
|||
.queryParam("vehicleIds", vehicleIdsParam)
|
||||
.queryParam("startTime", startTime.toString())
|
||||
.queryParam("endTime", endTime.toString())
|
||||
.queryParam("types", "obdOdometerMeters")
|
||||
.queryParam("types", "obdOdometerMeters,gpsOdometerMeters")
|
||||
.build())
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vehicle odometer history (OBD odometer meters)
|
||||
* Raw JSON 반환
|
||||
*/
|
||||
public String getVehicleEngineSeconds(
|
||||
List<String> vehicleExternalIds,
|
||||
Instant startTime,
|
||||
Instant endTime
|
||||
) {
|
||||
|
||||
String vehicleIdsParam = String.join(",", vehicleExternalIds);
|
||||
|
||||
return webClient.get()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("/fleet/vehicles/stats/history")
|
||||
.queryParam("vehicleIds", vehicleIdsParam)
|
||||
.queryParam("startTime", startTime.toString())
|
||||
.queryParam("endTime", endTime.toString())
|
||||
.queryParam("types", "obdEngineSeconds")
|
||||
.build())
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
|
|
@ -89,7 +114,7 @@ public class SamsaraClient {
|
|||
* Raw JSON 반환
|
||||
*
|
||||
*/
|
||||
public String getVehicleSafetyEvents(Instant startTime, Instant endTime, List<String> vehicleExternalIds) {
|
||||
public String getVehicleSafetyEvents(List<String> vehicleExternalIds, Instant startTime, Instant endTime) {
|
||||
|
||||
String vehicleIdsParam = String.join(",", vehicleExternalIds);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
|
||||
import com.goi.integration.samsara.dto.ScheduleWorkerRequestDto;
|
||||
import com.goi.integration.samsara.dto.ScheduleWorkerResponseDto;
|
||||
import com.goi.integration.samsara.service.InspectionIngestWorker;
|
||||
import com.goi.integration.samsara.service.VehicleInspectionIngestWorker;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -20,7 +20,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
@Slf4j
|
||||
public class InspectionWorkerController {
|
||||
|
||||
private final InspectionIngestWorker inspectionIngestWorker;
|
||||
private final VehicleInspectionIngestWorker inspectionIngestWorker;
|
||||
|
||||
@PostMapping("/worker")
|
||||
public ScheduleWorkerResponseDto runDvir(
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
package com.goi.integration.samsara.controller;
|
||||
|
||||
import com.goi.integration.samsara.dto.VehicleOdometerHistoryResponseDto;
|
||||
import com.goi.integration.samsara.dto.ScheduleWorkerRequestDto;
|
||||
import com.goi.integration.samsara.dto.VehicleGpsResponseDto;
|
||||
import com.goi.integration.samsara.dto.VehicleOdometerCommand;
|
||||
import com.goi.integration.samsara.service.VehicleOdometerHistoryService;
|
||||
import com.goi.integration.samsara.service.VehicleOdometerService;
|
||||
import com.goi.integration.samsara.dto.VehicleStatEngineSecondsCommand;
|
||||
import com.goi.integration.samsara.dto.VehicleStatOdometerCommand;
|
||||
import com.goi.integration.samsara.dto.VehicleStatSafetyEventCommand;
|
||||
import com.goi.integration.samsara.service.VehicleGpsService;
|
||||
import com.goi.integration.samsara.service.VehicleStatEngineSecondsService;
|
||||
import com.goi.integration.samsara.service.VehicleStatOdometerService;
|
||||
import com.goi.integration.samsara.service.VehicleStatSafetyEventService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
|
@ -20,8 +22,9 @@ import org.springframework.web.bind.annotation.*;
|
|||
public class VehicleController {
|
||||
|
||||
private final VehicleGpsService vehicleStatService;
|
||||
private final VehicleOdometerHistoryService vehicleOdometerHistoryService;
|
||||
private final VehicleOdometerService vehicleOdometerService;
|
||||
private final VehicleStatOdometerService statOdometerService;
|
||||
private final VehicleStatEngineSecondsService statEngineSecondsService;
|
||||
private final VehicleStatSafetyEventService statSafetyEventService;
|
||||
|
||||
@GetMapping("/stat/gps")
|
||||
public List<VehicleGpsResponseDto> getGps(
|
||||
|
|
@ -30,42 +33,34 @@ public class VehicleController {
|
|||
return vehicleStatService.getVehicleGps(vehicleIds);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Vehicle odometer history summary (window-based)
|
||||
* odometer fetch 후 리턴
|
||||
*/
|
||||
@GetMapping("/stat/odometer/history")
|
||||
public List<VehicleOdometerHistoryResponseDto> getOdometerHistory(
|
||||
@RequestParam List<String> vehicleIds,
|
||||
@RequestParam Instant startTime,
|
||||
@RequestParam Instant endTime
|
||||
@PostMapping("/stat/odometer/fetch")
|
||||
public VehicleStatOdometerCommand fetchStatOdometer(
|
||||
@RequestBody ScheduleWorkerRequestDto request
|
||||
) {
|
||||
return vehicleOdometerHistoryService.getOdometerHistory(
|
||||
vehicleIds, startTime, endTime
|
||||
);
|
||||
return statOdometerService.fetchCommand(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vehicle odometer raw fetch (for opr-rest-api ingest)
|
||||
* odometer fetch 후 리턴
|
||||
*/
|
||||
@PostMapping("/odometer/fetch")
|
||||
public VehicleOdometerCommand fetchOdometer(
|
||||
@RequestParam List<String> vehicleIds,
|
||||
@RequestParam Instant startTime,
|
||||
@RequestParam Instant endTime
|
||||
@PostMapping("/stat/engine-seconds/fetch")
|
||||
public VehicleStatEngineSecondsCommand fetchStatEngineSeconds(
|
||||
@RequestBody ScheduleWorkerRequestDto request
|
||||
) {
|
||||
return vehicleOdometerService.fetchOdometerCommand(
|
||||
vehicleIds,
|
||||
startTime,
|
||||
endTime
|
||||
);
|
||||
return statEngineSecondsService.fetchCommand(request);
|
||||
}
|
||||
|
||||
// @GetMapping("/safety/events")
|
||||
// public List<VehicleSafetyEventResponseDto> getSafetyEvents(
|
||||
// @RequestParam Instant startTime,
|
||||
// @RequestParam Instant endTime,
|
||||
// @RequestParam List<String> vehicleIds
|
||||
// ) {
|
||||
// return vehicleSafetyEventService.getSafetyEvents(vehicleIds, startTime, endTime);
|
||||
// }
|
||||
/**
|
||||
* odometer fetch 후 리턴
|
||||
*/
|
||||
@PostMapping("/stat/safety-event/fetch")
|
||||
public VehicleStatSafetyEventCommand fetchStatSafetyEvent(
|
||||
@RequestBody ScheduleWorkerRequestDto request
|
||||
) {
|
||||
return statSafetyEventService.fetchCommand(request);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import lombok.Data;
|
|||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
|
|
@ -19,4 +20,6 @@ public class ScheduleWorkerRequestDto {
|
|||
private LocalDateTime to;
|
||||
private Integer maxRecords;
|
||||
private Map<String, Map<String, String>> config;
|
||||
|
||||
private List<String> vehicleExternalIds;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import java.util.List;
|
|||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class VehicleOdometerCommand {
|
||||
public class VehicleInspectionIngestCommand {
|
||||
private String source;
|
||||
private LocalDateTime fetchedAt;
|
||||
private List<VehicleOdometerRecordDto> records;
|
||||
private List<VehicleInspectionRecordDto> records;
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class ExtInspectionRecordDto {
|
||||
public class VehicleInspectionRecordDto {
|
||||
private String externalId; // inspection id
|
||||
private String vehicleExternalId; // vehicle.id
|
||||
private String driverExternalId; // signatoryUser.id
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.goi.integration.samsara.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class VehicleOdometerHistoryResponseDto {
|
||||
private String vohVehicleExternalId;
|
||||
private Instant vohFirstSampleTime;
|
||||
private Instant vohLastSampleTime;
|
||||
private Long vohFirstOdometerMeters;
|
||||
private Long vohLastOdometerMeters;
|
||||
private Integer vohSampleCount;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.goi.integration.samsara.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class VehicleStatEngineSecondsCommand {
|
||||
private String source;
|
||||
private LocalDateTime fetchedAt;
|
||||
private List<VehicleStatEngineSecondsRecordDto> records;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.goi.integration.samsara.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class VehicleStatEngineSecondsRecordDto {
|
||||
private String vehicleExternalId;
|
||||
private Instant sampleTime;
|
||||
private Long engineSeconds;
|
||||
private String payloadHash;
|
||||
private JsonNode payloadJson;
|
||||
}
|
||||
|
|
@ -12,8 +12,8 @@ import java.util.List;
|
|||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class ExtInspectionIngestCommand {
|
||||
public class VehicleStatOdometerCommand {
|
||||
private String source;
|
||||
private LocalDateTime fetchedAt;
|
||||
private List<ExtInspectionRecordDto> records;
|
||||
private List<VehicleStatOdometerRecordDto> records;
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import lombok.Builder;
|
|||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Instant;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
|
|
@ -13,10 +13,10 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class VehicleOdometerRecordDto {
|
||||
private String source; // SAMSARA
|
||||
public class VehicleStatOdometerRecordDto {
|
||||
private String vehicleExternalId; // samsara vehicle.id
|
||||
private LocalDateTime sampleTime; // snapshot time (UTC)
|
||||
private Instant sampleTime; // snapshot time (UTC)
|
||||
private String odometerType; // "OBD" | "GPS"
|
||||
private Long odometerMeters; // cumulative meters
|
||||
private String payloadHash; // idempotent hash
|
||||
private JsonNode payloadJson; // minimal raw payload
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.goi.integration.samsara.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class VehicleStatSafetyEventCommand {
|
||||
private String source;
|
||||
private LocalDateTime fetchedAt;
|
||||
private List<VehicleStatSafetyEventRecordDto> records;
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.goi.integration.samsara.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class VehicleStatSafetyEventRecordDto {
|
||||
private String eventId;
|
||||
private String vehicleId;
|
||||
private String driverId;
|
||||
private Instant eventTime;
|
||||
private LocalDate eventDate;
|
||||
private String coachingState;
|
||||
private Double maxAccelerationG;
|
||||
private Double latitude;
|
||||
private Double longitude;
|
||||
private String videoForwardUrl;
|
||||
private String videoInwardUrl;
|
||||
private JsonNode behaviorLabels;
|
||||
private JsonNode rawPayload;
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@ import com.goi.integration.common.dto.ExtIngestResult;
|
|||
import com.goi.integration.common.util.DateTimeUtil;
|
||||
import com.goi.integration.common.util.ExtPayloadHashUtil;
|
||||
import com.goi.integration.samsara.client.OprIngestClient;
|
||||
import com.goi.integration.samsara.dto.ExtInspectionIngestCommand;
|
||||
import com.goi.integration.samsara.dto.ExtInspectionRecordDto;
|
||||
import com.goi.integration.samsara.dto.VehicleInspectionIngestCommand;
|
||||
import com.goi.integration.samsara.dto.VehicleInspectionRecordDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -19,7 +19,7 @@ import java.util.List;
|
|||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InspectionIngestService {
|
||||
public class VehicleInspectionIngestService {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
private final OprIngestClient oprIngestClient;
|
||||
|
|
@ -44,7 +44,7 @@ public class InspectionIngestService {
|
|||
}
|
||||
|
||||
//
|
||||
List<ExtInspectionRecordDto> records = new ArrayList<>();
|
||||
List<VehicleInspectionRecordDto> records = new ArrayList<>();
|
||||
log.info("inspection data size={}", data.size());
|
||||
|
||||
// data[] 각 node 가 하나의 inspection (preTrip / postTrip)
|
||||
|
|
@ -67,7 +67,7 @@ public class InspectionIngestService {
|
|||
|
||||
// record DTO
|
||||
records.add(
|
||||
ExtInspectionRecordDto.builder()
|
||||
VehicleInspectionRecordDto.builder()
|
||||
.externalId(externalId)
|
||||
.vehicleExternalId(vehicleExtId)
|
||||
.driverExternalId(driverExtId)
|
||||
|
|
@ -82,8 +82,8 @@ public class InspectionIngestService {
|
|||
}
|
||||
|
||||
// ingest command 생성
|
||||
ExtInspectionIngestCommand command =
|
||||
ExtInspectionIngestCommand.builder()
|
||||
VehicleInspectionIngestCommand command =
|
||||
VehicleInspectionIngestCommand.builder()
|
||||
.source("SAMSARA")
|
||||
.fetchedAt(LocalDateTime.now())
|
||||
.records(records)
|
||||
|
|
@ -15,10 +15,10 @@ import java.time.ZoneOffset;
|
|||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InspectionIngestWorker {
|
||||
public class VehicleInspectionIngestWorker {
|
||||
|
||||
private final SamsaraClient samsaraClient;
|
||||
private final InspectionIngestService ingestService;
|
||||
private final VehicleInspectionIngestService ingestService;
|
||||
|
||||
public ScheduleWorkerResponseDto execute(ScheduleWorkerRequestDto request) {
|
||||
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
package com.goi.integration.samsara.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.goi.integration.samsara.client.SamsaraClient;
|
||||
import com.goi.integration.samsara.dto.VehicleOdometerHistoryResponseDto;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VehicleOdometerHistoryService {
|
||||
|
||||
private final SamsaraClient samsaraClient;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public List<VehicleOdometerHistoryResponseDto> getOdometerHistory(
|
||||
List<String> vehicleExternalIds,
|
||||
Instant startTime,
|
||||
Instant endTime
|
||||
) {
|
||||
// api 호출
|
||||
String rawJson = samsaraClient.getVehicleOdometerHistory(vehicleExternalIds, startTime, endTime);
|
||||
|
||||
return parseOdometerHistory(rawJson);
|
||||
}
|
||||
|
||||
private List<VehicleOdometerHistoryResponseDto> parseOdometerHistory(String rawJson) {
|
||||
|
||||
List<VehicleOdometerHistoryResponseDto> results = new ArrayList<>();
|
||||
|
||||
try {
|
||||
JsonNode root = objectMapper.readTree(rawJson);
|
||||
JsonNode dataArray = root.path("data");
|
||||
|
||||
for (JsonNode vehicleNode : dataArray) {
|
||||
|
||||
String vehicleId = vehicleNode.path("id").asText();
|
||||
JsonNode odoArray = vehicleNode.path("obdOdometerMeters");
|
||||
|
||||
if (!odoArray.isArray() || odoArray.isEmpty()) {
|
||||
// 데이터 없음
|
||||
results.add(
|
||||
VehicleOdometerHistoryResponseDto.builder()
|
||||
.vohVehicleExternalId(vehicleId)
|
||||
.vohSampleCount(0)
|
||||
.build()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
Iterator<JsonNode> it = odoArray.elements();
|
||||
|
||||
JsonNode first = it.next();
|
||||
JsonNode last = first;
|
||||
|
||||
int count = 1;
|
||||
|
||||
while (it.hasNext()) {
|
||||
last = it.next();
|
||||
count++;
|
||||
}
|
||||
|
||||
// 거리 계산은 opr 에서. 여기선 이전 누적치를 모름.
|
||||
long firstValue = first.path("value").asLong();
|
||||
long lastValue = last.path("value").asLong();
|
||||
|
||||
Instant firstTime = Instant.parse(first.path("time").asText());
|
||||
Instant lastTime = Instant.parse(last.path("time").asText());
|
||||
|
||||
results.add(
|
||||
VehicleOdometerHistoryResponseDto.builder()
|
||||
.vohVehicleExternalId(vehicleId)
|
||||
.vohFirstSampleTime(firstTime)
|
||||
.vohLastSampleTime(lastTime)
|
||||
.vohFirstOdometerMeters(firstValue)
|
||||
.vohLastOdometerMeters(lastValue)
|
||||
.vohSampleCount(count)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[ODOMETER][PARSE_FAIL]", e);
|
||||
throw new RuntimeException("Failed to parse vehicle odometer history", e);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
package com.goi.integration.samsara.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.goi.integration.common.util.DateTimeUtil;
|
||||
import com.goi.integration.common.util.ExtPayloadHashUtil;
|
||||
import com.goi.integration.samsara.client.SamsaraClient;
|
||||
import com.goi.integration.samsara.dto.VehicleOdometerCommand;
|
||||
import com.goi.integration.samsara.dto.VehicleOdometerRecordDto;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VehicleOdometerService {
|
||||
|
||||
private final SamsaraClient samsaraClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* Fetch samsara odometer data and build command
|
||||
*/
|
||||
public VehicleOdometerCommand fetchOdometerCommand(
|
||||
List<String> vehicleExternalIds,
|
||||
Instant startTime,
|
||||
Instant endTime
|
||||
) {
|
||||
|
||||
String rawJson =
|
||||
samsaraClient.getVehicleOdometerHistory(
|
||||
vehicleExternalIds,
|
||||
startTime,
|
||||
endTime
|
||||
);
|
||||
|
||||
List<VehicleOdometerRecordDto> records =
|
||||
parseRawOdometer(rawJson);
|
||||
|
||||
return VehicleOdometerCommand.builder()
|
||||
.source("SAMSARA")
|
||||
.fetchedAt(LocalDateTime.now())
|
||||
.records(records)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse samsara raw JSON → odometer snapshot records
|
||||
*/
|
||||
private List<VehicleOdometerRecordDto> parseRawOdometer(String rawJson) {
|
||||
|
||||
List<VehicleOdometerRecordDto> records = new ArrayList<>();
|
||||
|
||||
try {
|
||||
JsonNode root = objectMapper.readTree(rawJson);
|
||||
JsonNode dataArray = root.path("data");
|
||||
|
||||
if (!dataArray.isArray()) {
|
||||
log.warn("[ODOMETER] no data[] in samsara response");
|
||||
return records;
|
||||
}
|
||||
|
||||
for (JsonNode vehicleNode : dataArray) {
|
||||
|
||||
String vehicleExtId =
|
||||
vehicleNode.path("id").asText(null);
|
||||
|
||||
JsonNode odoArray =
|
||||
vehicleNode.path("obdOdometerMeters");
|
||||
|
||||
if (!odoArray.isArray() || odoArray.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (JsonNode sample : odoArray) {
|
||||
|
||||
String timeStr =
|
||||
sample.path("time").asText(null);
|
||||
|
||||
if (timeStr == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDateTime sampleTime =
|
||||
DateTimeUtil.parseToUTC(timeStr);
|
||||
|
||||
long meters =
|
||||
sample.path("value").asLong();
|
||||
|
||||
String hash =
|
||||
ExtPayloadHashUtil.sha256FromStrings(
|
||||
vehicleExtId,
|
||||
timeStr,
|
||||
String.valueOf(meters)
|
||||
);
|
||||
|
||||
records.add(
|
||||
VehicleOdometerRecordDto.builder()
|
||||
.source("SAMSARA")
|
||||
.vehicleExternalId(vehicleExtId)
|
||||
.sampleTime(sampleTime)
|
||||
.odometerMeters(meters)
|
||||
.payloadHash(hash)
|
||||
.payloadJson(sample)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[ODOMETER][PARSE_FAIL]", e);
|
||||
throw new RuntimeException("Failed to parse samsara odometer payload", e);
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
package com.goi.integration.samsara.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.goi.integration.common.util.ExtPayloadHashUtil;
|
||||
import com.goi.integration.samsara.client.SamsaraClient;
|
||||
import com.goi.integration.samsara.dto.ScheduleWorkerRequestDto;
|
||||
import com.goi.integration.samsara.dto.VehicleStatEngineSecondsCommand;
|
||||
import com.goi.integration.samsara.dto.VehicleStatEngineSecondsRecordDto;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* - Samsara API 호출
|
||||
* - raw JSON 파싱
|
||||
* - "사실 데이터"를 record 단위로 변환
|
||||
*
|
||||
* 판단 / 선택 / 병합 없음
|
||||
* obdEngineSeconds 단일 타입
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class VehicleStatEngineSecondsService {
|
||||
|
||||
private final SamsaraClient samsaraClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* Controller 진입점
|
||||
*/
|
||||
public VehicleStatEngineSecondsCommand fetchCommand(ScheduleWorkerRequestDto request) {
|
||||
|
||||
if (request.getVehicleExternalIds() == null || request.getVehicleExternalIds().isEmpty()) {
|
||||
throw new IllegalArgumentException("vehicleExternalIds is required");
|
||||
}
|
||||
if (request.getFrom() == null || request.getTo() == null) {
|
||||
throw new IllegalArgumentException("from/to is required");
|
||||
}
|
||||
|
||||
Instant start = request.getFrom().toInstant(ZoneOffset.UTC);
|
||||
Instant end = request.getTo().toInstant(ZoneOffset.UTC);
|
||||
|
||||
log.info(
|
||||
"[{}][ENGINE_SECONDS][FETCH] start={} end={} vehicles={}",
|
||||
request.getJobCode(), start, end, request.getVehicleExternalIds().size()
|
||||
);
|
||||
|
||||
String rawJson = samsaraClient.getVehicleEngineSeconds(
|
||||
request.getVehicleExternalIds(),
|
||||
start,
|
||||
end
|
||||
);
|
||||
|
||||
VehicleStatEngineSecondsCommand command = buildCommandFromRawJson(rawJson);
|
||||
|
||||
log.info(
|
||||
"[{}][ENGINE_SECONDS][FETCH_OK] records={} fetchedAt={}",
|
||||
request.getJobCode(),
|
||||
command.getRecords().size(),
|
||||
command.getFetchedAt()
|
||||
);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* raw JSON → records → command
|
||||
*
|
||||
* response sample:
|
||||
* {
|
||||
* "data": [
|
||||
* {
|
||||
* "id": "...",
|
||||
* "obdEngineSeconds": [{"time":"...Z","value":123}, ...]
|
||||
* }, ...
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
private VehicleStatEngineSecondsCommand buildCommandFromRawJson(String rawJson) {
|
||||
|
||||
try {
|
||||
JsonNode root = objectMapper.readTree(rawJson);
|
||||
JsonNode dataArray = root.path("data");
|
||||
|
||||
if (!dataArray.isArray()) {
|
||||
log.warn("[ENGINE_SECONDS] samsara response has no data[]");
|
||||
return VehicleStatEngineSecondsCommand.builder()
|
||||
.source("SAMSARA")
|
||||
.fetchedAt(LocalDateTime.now())
|
||||
.records(List.of())
|
||||
.build();
|
||||
}
|
||||
|
||||
List<VehicleStatEngineSecondsRecordDto> records = new ArrayList<>();
|
||||
|
||||
for (JsonNode vehicleNode : dataArray) {
|
||||
|
||||
String vehicleExtId = vehicleNode.path("id").asText(null);
|
||||
if (vehicleExtId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// obdEngineSeconds samples only
|
||||
parseSamples(
|
||||
records,
|
||||
vehicleExtId,
|
||||
vehicleNode.path("obdEngineSeconds")
|
||||
);
|
||||
}
|
||||
|
||||
return VehicleStatEngineSecondsCommand.builder()
|
||||
.source("SAMSARA")
|
||||
.fetchedAt(LocalDateTime.now())
|
||||
.records(records)
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[ENGINE_SECONDS][PARSE_FAIL]", e);
|
||||
throw new RuntimeException("ENGINE_SECONDS_FETCH_PARSE_FAIL", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* samplesArray: [{"time": "...Z", "value": 123}, ...]
|
||||
*/
|
||||
private void parseSamples(
|
||||
List<VehicleStatEngineSecondsRecordDto> records,
|
||||
String vehicleExtId,
|
||||
JsonNode arrayNode
|
||||
) {
|
||||
|
||||
if (arrayNode == null || !arrayNode.isArray() || arrayNode.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (JsonNode sample : arrayNode) {
|
||||
|
||||
String timeStr = sample.path("time").asText(null);
|
||||
if (timeStr == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Instant sampleTime;
|
||||
try {
|
||||
sampleTime = Instant.parse(timeStr);
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long engineSeconds = sample.path("value").asLong();
|
||||
|
||||
// 단일 타입이지만 해시 혹시 모를 확장을 위해 "obdEngineSeconds" 고정 문자열 포함 권장
|
||||
String hash = ExtPayloadHashUtil.sha256FromStrings(
|
||||
vehicleExtId,
|
||||
timeStr,
|
||||
"obdEngineSeconds",
|
||||
String.valueOf(engineSeconds)
|
||||
);
|
||||
|
||||
records.add(
|
||||
VehicleStatEngineSecondsRecordDto.builder()
|
||||
.vehicleExternalId(vehicleExtId)
|
||||
.sampleTime(sampleTime)
|
||||
.engineSeconds(engineSeconds)
|
||||
.payloadHash(hash)
|
||||
.payloadJson(sample) // 원본 sample 그대로
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
package com.goi.integration.samsara.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.goi.integration.common.util.ExtPayloadHashUtil;
|
||||
import com.goi.integration.samsara.client.SamsaraClient;
|
||||
import com.goi.integration.samsara.dto.ScheduleWorkerRequestDto;
|
||||
import com.goi.integration.samsara.dto.VehicleStatOdometerCommand;
|
||||
import com.goi.integration.samsara.dto.VehicleStatOdometerRecordDto;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* integration-service 책임:
|
||||
* - Samsara API 호출
|
||||
* - raw JSON 파싱
|
||||
* - "사실 데이터"를 record 단위로 변환
|
||||
*
|
||||
* 판단 / 선택 / 병합 없음
|
||||
* OBD / GPS 는 type 으로 구분된 독립 record
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class VehicleStatOdometerService {
|
||||
|
||||
private final SamsaraClient samsaraClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* Controller 진입점
|
||||
*/
|
||||
public VehicleStatOdometerCommand fetchCommand(ScheduleWorkerRequestDto request) {
|
||||
|
||||
if (request.getVehicleExternalIds() == null || request.getVehicleExternalIds().isEmpty()) {
|
||||
throw new IllegalArgumentException("vehicleExternalIds is required");
|
||||
}
|
||||
if (request.getFrom() == null || request.getTo() == null) {
|
||||
throw new IllegalArgumentException("from/to is required");
|
||||
}
|
||||
|
||||
Instant start = request.getFrom().toInstant(ZoneOffset.UTC);
|
||||
Instant end = request.getTo().toInstant(ZoneOffset.UTC);
|
||||
|
||||
log.info(
|
||||
"[{}][ODOMETER][FETCH] start={} end={} vehicles={}",
|
||||
request.getJobCode(), start, end, request.getVehicleExternalIds().size()
|
||||
);
|
||||
|
||||
String rawJson = samsaraClient.getVehicleOdometerHistory(
|
||||
request.getVehicleExternalIds(),
|
||||
start,
|
||||
end
|
||||
);
|
||||
|
||||
VehicleStatOdometerCommand command = buildCommandFromRawJson(rawJson);
|
||||
|
||||
log.info(
|
||||
"[{}][ODOMETER][FETCH_OK] records={} fetchedAt={}",
|
||||
request.getJobCode(),
|
||||
command.getRecords().size(),
|
||||
command.getFetchedAt()
|
||||
);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* raw JSON → records → command
|
||||
*
|
||||
* - OBD / GPS 각각 독립 record
|
||||
* - time 이 달라도 정상 처리
|
||||
* - hash 에 type 포함
|
||||
*/
|
||||
private VehicleStatOdometerCommand buildCommandFromRawJson(String rawJson) {
|
||||
|
||||
try {
|
||||
JsonNode root = objectMapper.readTree(rawJson);
|
||||
JsonNode dataArray = root.path("data");
|
||||
|
||||
if (!dataArray.isArray()) {
|
||||
log.warn("[ODOMETER] samsara response has no data[]");
|
||||
return VehicleStatOdometerCommand.builder()
|
||||
.source("SAMSARA")
|
||||
.fetchedAt(LocalDateTime.now())
|
||||
.records(List.of())
|
||||
.build();
|
||||
}
|
||||
|
||||
List<VehicleStatOdometerRecordDto> records = new ArrayList<>();
|
||||
|
||||
for (JsonNode vehicleNode : dataArray) {
|
||||
|
||||
String vehicleExtId = vehicleNode.path("id").asText(null);
|
||||
if (vehicleExtId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// OBD samples
|
||||
parseSamples(
|
||||
records,
|
||||
vehicleExtId,
|
||||
vehicleNode.path("obdOdometerMeters"),
|
||||
"OBD"
|
||||
);
|
||||
|
||||
// GPS samples
|
||||
parseSamples(
|
||||
records,
|
||||
vehicleExtId,
|
||||
vehicleNode.path("gpsOdometerMeters"),
|
||||
"GPS"
|
||||
);
|
||||
}
|
||||
|
||||
return VehicleStatOdometerCommand.builder()
|
||||
.source("SAMSARA")
|
||||
.fetchedAt(LocalDateTime.now())
|
||||
.records(records)
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[ODOMETER][PARSE_FAIL]", e);
|
||||
throw new RuntimeException("ODOMETER_FETCH_PARSE_FAIL", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* samplesArray: [{"time": "...Z", "value": 123}, ...]
|
||||
* type: "OBD" | "GPS"
|
||||
*/
|
||||
private void parseSamples(
|
||||
List<VehicleStatOdometerRecordDto> records,
|
||||
String vehicleExtId,
|
||||
JsonNode arrayNode,
|
||||
String type
|
||||
) {
|
||||
|
||||
if (arrayNode == null || !arrayNode.isArray() || arrayNode.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (JsonNode sample : arrayNode) {
|
||||
|
||||
String timeStr = sample.path("time").asText(null);
|
||||
if (timeStr == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Instant sampleTime;
|
||||
try {
|
||||
sampleTime = Instant.parse(timeStr);
|
||||
} catch (Exception e) {
|
||||
// invalid timestamp → skip
|
||||
continue;
|
||||
}
|
||||
|
||||
long meters = sample.path("value").asLong();
|
||||
|
||||
// ✅ type 포함 hash (idempotent)
|
||||
String hash = ExtPayloadHashUtil.sha256FromStrings(
|
||||
vehicleExtId,
|
||||
timeStr,
|
||||
type,
|
||||
String.valueOf(meters)
|
||||
);
|
||||
|
||||
records.add(
|
||||
VehicleStatOdometerRecordDto.builder()
|
||||
.vehicleExternalId(vehicleExtId)
|
||||
.sampleTime(sampleTime)
|
||||
.odometerType(type) // "OBD" or "GPS"
|
||||
.odometerMeters(meters)
|
||||
.payloadHash(hash)
|
||||
.payloadJson(sample) // 원본 sample 그대로
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
package com.goi.integration.samsara.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.goi.integration.samsara.client.SamsaraClient;
|
||||
import com.goi.integration.samsara.dto.ScheduleWorkerRequestDto;
|
||||
import com.goi.integration.samsara.dto.VehicleStatSafetyEventCommand;
|
||||
import com.goi.integration.samsara.dto.VehicleStatSafetyEventRecordDto;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* - Samsara /fleet/safety-events API 호출
|
||||
* - raw JSON 파싱
|
||||
* - safety event 단위 record 생성
|
||||
*
|
||||
* ❌ 판단 / 병합 / dispatch 매핑 없음
|
||||
* ✅ 사실 데이터 그대로 전달
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class VehicleStatSafetyEventService {
|
||||
|
||||
private final SamsaraClient samsaraClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* Controller 진입점
|
||||
*/
|
||||
public VehicleStatSafetyEventCommand fetchCommand(ScheduleWorkerRequestDto request) {
|
||||
|
||||
if (request.getVehicleExternalIds() == null || request.getVehicleExternalIds().isEmpty()) {
|
||||
throw new IllegalArgumentException("vehicleExternalIds is required");
|
||||
}
|
||||
if (request.getFrom() == null || request.getTo() == null) {
|
||||
throw new IllegalArgumentException("from/to is required");
|
||||
}
|
||||
|
||||
Instant start = request.getFrom().toInstant(ZoneOffset.UTC);
|
||||
Instant end = request.getTo().toInstant(ZoneOffset.UTC);
|
||||
|
||||
log.info(
|
||||
"[{}][SAFETY_EVENT][FETCH] start={} end={} vehicles={}",
|
||||
request.getJobCode(), start, end, request.getVehicleExternalIds().size()
|
||||
);
|
||||
|
||||
String rawJson = samsaraClient.getVehicleSafetyEvents(
|
||||
request.getVehicleExternalIds(),
|
||||
start,
|
||||
end
|
||||
);
|
||||
|
||||
VehicleStatSafetyEventCommand command = buildCommandFromRawJson(rawJson);
|
||||
|
||||
log.info(
|
||||
"[{}][SAFETY_EVENT][FETCH_OK] records={} fetchedAt={}",
|
||||
request.getJobCode(),
|
||||
command.getRecords().size(),
|
||||
command.getFetchedAt()
|
||||
);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* raw JSON → records → command
|
||||
*
|
||||
* response sample:
|
||||
* {
|
||||
* "data": [
|
||||
* {
|
||||
* "id": "...",
|
||||
* "vehicle": { "id": "..." },
|
||||
* "driver": { "id": "..." },
|
||||
* "time": "...Z",
|
||||
* "location": { "latitude": 0, "longitude": 0 },
|
||||
* "coachingState": "...",
|
||||
* "maxAccelerationGForce": 0.58,
|
||||
* "behaviorLabels": [...]
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
private VehicleStatSafetyEventCommand buildCommandFromRawJson(String rawJson) {
|
||||
|
||||
try {
|
||||
JsonNode root = objectMapper.readTree(rawJson);
|
||||
JsonNode dataArray = root.path("data");
|
||||
|
||||
if (!dataArray.isArray()) {
|
||||
log.warn("[SAFETY_EVENT] samsara response has no data[]");
|
||||
return VehicleStatSafetyEventCommand.builder()
|
||||
.source("SAMSARA")
|
||||
.fetchedAt(LocalDateTime.now())
|
||||
.records(List.of())
|
||||
.build();
|
||||
}
|
||||
|
||||
List<VehicleStatSafetyEventRecordDto> records = new ArrayList<>();
|
||||
|
||||
for (JsonNode eventNode : dataArray) {
|
||||
|
||||
String eventId = eventNode.path("id").asText(null);
|
||||
if (eventId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonNode vehicleNode = eventNode.path("vehicle");
|
||||
String vehicleId = vehicleNode.path("id").asText(null);
|
||||
if (vehicleId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonNode driverNode = eventNode.path("driver");
|
||||
String driverId = driverNode.isMissingNode()
|
||||
? null
|
||||
: driverNode.path("id").asText(null);
|
||||
|
||||
String timeStr = eventNode.path("time").asText(null);
|
||||
if (timeStr == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Instant eventTime;
|
||||
try {
|
||||
eventTime = Instant.parse(timeStr);
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonNode locationNode = eventNode.path("location");
|
||||
|
||||
records.add(
|
||||
VehicleStatSafetyEventRecordDto.builder()
|
||||
.eventId(eventId)
|
||||
.vehicleId(vehicleId)
|
||||
.driverId(driverId)
|
||||
.eventTime(eventTime)
|
||||
.eventDate(eventTime.atZone(ZoneOffset.UTC).toLocalDate())
|
||||
.coachingState(eventNode.path("coachingState").asText(null))
|
||||
.maxAccelerationG(
|
||||
eventNode.hasNonNull("maxAccelerationGForce")
|
||||
? eventNode.get("maxAccelerationGForce").asDouble()
|
||||
: null
|
||||
)
|
||||
.latitude(
|
||||
locationNode.hasNonNull("latitude")
|
||||
? locationNode.get("latitude").asDouble()
|
||||
: null
|
||||
)
|
||||
.longitude(
|
||||
locationNode.hasNonNull("longitude")
|
||||
? locationNode.get("longitude").asDouble()
|
||||
: null
|
||||
)
|
||||
.videoForwardUrl(
|
||||
eventNode.path("downloadForwardVideoUrl").asText(null)
|
||||
)
|
||||
.videoInwardUrl(
|
||||
eventNode.path("downloadInwardVideoUrl").asText(null)
|
||||
)
|
||||
.behaviorLabels(eventNode.path("behaviorLabels"))
|
||||
.rawPayload(eventNode)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
return VehicleStatSafetyEventCommand.builder()
|
||||
.source("SAMSARA")
|
||||
.fetchedAt(LocalDateTime.now())
|
||||
.records(records)
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[SAFETY_EVENT][PARSE_FAIL]", e);
|
||||
throw new RuntimeException("SAFETY_EVENT_FETCH_PARSE_FAIL", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue