[Schedule]
- Removed schedule (schedules are managed in sys-rest-api) [Odometer] - Created call Samsara to get odometer
This commit is contained in:
parent
ce146cd41a
commit
dc10f8b1d7
|
|
@ -1,29 +0,0 @@
|
||||||
package com.goi.integration.common.config;
|
|
||||||
|
|
||||||
import com.goi.integration.common.config.ScheduleJobConfigDto;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class InMemoryScheduleJobConfigProvider implements ScheduleJobConfigProvider {
|
|
||||||
|
|
||||||
private final Map<String, ScheduleJobConfigDto> configs = Map.of(
|
|
||||||
"SAMSARA_DVIR",
|
|
||||||
ScheduleJobConfigDto.builder()
|
|
||||||
.sjcJobCode("SAMSARA_DVIR")
|
|
||||||
.sjcEnabled(true)
|
|
||||||
.sjcCronExpression("0 */10 * * * *")
|
|
||||||
.sjcLookbackHours(24)
|
|
||||||
.sjcOverlapMinutes(10)
|
|
||||||
.sjcMaxRecords(252)
|
|
||||||
.sjcTimezone("UTC")
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<ScheduleJobConfigDto> getJobConfig(String jobCode) {
|
|
||||||
return Optional.ofNullable(configs.get(jobCode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package com.goi.integration.common.config;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Builder
|
|
||||||
public class ScheduleJobConfigDto {
|
|
||||||
private String sjcJobCode; // SAMSARA_DVIR
|
|
||||||
private String sjcCronExpression; // "0 */10 * * * *"
|
|
||||||
private Integer sjcLookbackHours; // 24
|
|
||||||
private Integer sjcOverlapMinutes;// 10
|
|
||||||
private Integer sjcMaxRecords; // 252
|
|
||||||
private Boolean sjcEnabled; // true
|
|
||||||
private String sjcTimezone; // "UTC"
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package com.goi.integration.common.config;
|
|
||||||
|
|
||||||
import com.goi.integration.common.config.ScheduleJobConfigDto;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface ScheduleJobConfigProvider {
|
|
||||||
Optional<ScheduleJobConfigDto> getJobConfig(String jobCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -11,16 +11,14 @@ import lombok.NoArgsConstructor;
|
||||||
@Builder
|
@Builder
|
||||||
public class ExtIngestResult {
|
public class ExtIngestResult {
|
||||||
private String source;
|
private String source;
|
||||||
private String recordType;
|
|
||||||
private int received;
|
private int received;
|
||||||
private int inserted;
|
private int inserted;
|
||||||
private int updated;
|
private int updated;
|
||||||
private int skipped;
|
private int skipped;
|
||||||
|
|
||||||
public static ExtIngestResult empty(String source, String recordType) {
|
public static ExtIngestResult empty(String source) {
|
||||||
return ExtIngestResult.builder()
|
return ExtIngestResult.builder()
|
||||||
.source(source)
|
.source(source)
|
||||||
.recordType(recordType)
|
|
||||||
.received(0)
|
.received(0)
|
||||||
.inserted(0)
|
.inserted(0)
|
||||||
.updated(0)
|
.updated(0)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,37 @@ public final class DateTimeUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JsonNode → LocalDateTime (UTC)
|
||||||
|
*/
|
||||||
|
public static LocalDateTime parseToUTC(JsonNode node) {
|
||||||
|
if (node == null || node.isMissingNode() || node.isNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parseToUTC(node.asText(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String → LocalDateTime (UTC)
|
||||||
|
* ex) 2025-12-22T13:30:24.365Z
|
||||||
|
* ex) 2025-12-22T13:30:24+00:00
|
||||||
|
* ex) 2025-12-22T08:30:24-05:00
|
||||||
|
*/
|
||||||
|
public static LocalDateTime parseToUTC(String value) {
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return OffsetDateTime
|
||||||
|
.parse(value)
|
||||||
|
.atZoneSameInstant(ZoneId.of("UTC"))
|
||||||
|
.toLocalDateTime();
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
return null; // ingest 안정성 우선
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static LocalDateTime parseToToronto(String value) {
|
public static LocalDateTime parseToToronto(String value) {
|
||||||
if (value == null || value.isBlank()) return null;
|
if (value == null || value.isBlank()) return null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,25 @@ public final class ExtPayloadHashUtil {
|
||||||
throw new RuntimeException("Failed to hash raw json", e);
|
throw new RuntimeException("Failed to hash raw json", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 문자열 조합 기반 SHA-256
|
||||||
|
* (odometer snapshot idempotent / 변경 감지용)
|
||||||
|
*/
|
||||||
|
public static String sha256FromStrings(String... values) {
|
||||||
|
try {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String v : values) {
|
||||||
|
if (v != null) {
|
||||||
|
sb.append(v);
|
||||||
|
}
|
||||||
|
sb.append('|'); // 구분자
|
||||||
|
}
|
||||||
|
return sha256(sb.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to hash from strings", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- internal ---------- */
|
/* ---------- internal ---------- */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,8 @@ public class OprIngestClient {
|
||||||
// .writeValueAsString(command);
|
// .writeValueAsString(command);
|
||||||
//
|
//
|
||||||
log.info(
|
log.info(
|
||||||
"[OPR_INGEST][REQUEST] source={}, type={}, records={}, fetchedAt={}",
|
"[OPR_INGEST][REQUEST] source={}, type={}, fetchedAt={}",
|
||||||
command.getSource(),
|
command.getSource(),
|
||||||
command.getRecordType(),
|
|
||||||
command.getRecords() != null ? command.getRecords().size() : 0,
|
command.getRecords() != null ? command.getRecords().size() : 0,
|
||||||
command.getFetchedAt()
|
command.getFetchedAt()
|
||||||
);
|
);
|
||||||
|
|
@ -90,9 +89,8 @@ public class OprIngestClient {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 실패 로그
|
// 실패 로그
|
||||||
log.error(
|
log.error(
|
||||||
"[OPR_INGEST][FAILED] source={}, type={}, error={}",
|
"[OPR_INGEST][FAILED] source={}, error={}",
|
||||||
command.getSource(),
|
command.getSource(),
|
||||||
command.getRecordType(),
|
|
||||||
e.getMessage(),
|
e.getMessage(),
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.goi.integration.samsara.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
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 lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/samsara/inspection")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class InspectionWorkerController {
|
||||||
|
|
||||||
|
private final InspectionIngestWorker inspectionIngestWorker;
|
||||||
|
|
||||||
|
@PostMapping("/worker")
|
||||||
|
public ScheduleWorkerResponseDto runDvir(
|
||||||
|
@RequestBody ScheduleWorkerRequestDto request
|
||||||
|
) {
|
||||||
|
return inspectionIngestWorker.execute(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package com.goi.integration.samsara.controller;
|
|
||||||
|
|
||||||
import com.goi.integration.samsara.job.DvirIngestJob;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/internal/test")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class SamsaraTestController {
|
|
||||||
|
|
||||||
private final DvirIngestJob dvirJob;
|
|
||||||
|
|
||||||
@PostMapping("/samsara-dvir")
|
|
||||||
public String runDvirNow() {
|
|
||||||
dvirJob.run();
|
|
||||||
return "SAMSARA_DVIR_TRIGGERED";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,9 @@ package com.goi.integration.samsara.controller;
|
||||||
|
|
||||||
import com.goi.integration.samsara.dto.VehicleOdometerHistoryResponseDto;
|
import com.goi.integration.samsara.dto.VehicleOdometerHistoryResponseDto;
|
||||||
import com.goi.integration.samsara.dto.VehicleGpsResponseDto;
|
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.VehicleOdometerHistoryService;
|
||||||
|
import com.goi.integration.samsara.service.VehicleOdometerService;
|
||||||
import com.goi.integration.samsara.service.VehicleGpsService;
|
import com.goi.integration.samsara.service.VehicleGpsService;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
@ -19,6 +21,7 @@ public class VehicleController {
|
||||||
|
|
||||||
private final VehicleGpsService vehicleStatService;
|
private final VehicleGpsService vehicleStatService;
|
||||||
private final VehicleOdometerHistoryService vehicleOdometerHistoryService;
|
private final VehicleOdometerHistoryService vehicleOdometerHistoryService;
|
||||||
|
private final VehicleOdometerService vehicleOdometerService;
|
||||||
|
|
||||||
@GetMapping("/stat/gps")
|
@GetMapping("/stat/gps")
|
||||||
public List<VehicleGpsResponseDto> getGps(
|
public List<VehicleGpsResponseDto> getGps(
|
||||||
|
|
@ -41,6 +44,22 @@ public class VehicleController {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vehicle odometer raw fetch (for opr-rest-api ingest)
|
||||||
|
*/
|
||||||
|
@PostMapping("/odometer/fetch")
|
||||||
|
public VehicleOdometerCommand fetchOdometer(
|
||||||
|
@RequestParam List<String> vehicleIds,
|
||||||
|
@RequestParam Instant startTime,
|
||||||
|
@RequestParam Instant endTime
|
||||||
|
) {
|
||||||
|
return vehicleOdometerService.fetchOdometerCommand(
|
||||||
|
vehicleIds,
|
||||||
|
startTime,
|
||||||
|
endTime
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// @GetMapping("/safety/events")
|
// @GetMapping("/safety/events")
|
||||||
// public List<VehicleSafetyEventResponseDto> getSafetyEvents(
|
// public List<VehicleSafetyEventResponseDto> getSafetyEvents(
|
||||||
// @RequestParam Instant startTime,
|
// @RequestParam Instant startTime,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import java.util.List;
|
||||||
@Builder
|
@Builder
|
||||||
public class ExtInspectionIngestCommand {
|
public class ExtInspectionIngestCommand {
|
||||||
private String source;
|
private String source;
|
||||||
private String recordType;
|
|
||||||
private LocalDateTime fetchedAt;
|
private LocalDateTime fetchedAt;
|
||||||
private List<ExtInspectionRecordDto> records;
|
private List<ExtInspectionRecordDto> 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.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class ScheduleWorkerRequestDto {
|
||||||
|
|
||||||
|
private String jobCode;
|
||||||
|
private LocalDateTime from;
|
||||||
|
private LocalDateTime to;
|
||||||
|
private Integer maxRecords;
|
||||||
|
private Map<String, Map<String, String>> config;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.goi.integration.samsara.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class ScheduleWorkerResponseDto {
|
||||||
|
|
||||||
|
private boolean success;
|
||||||
|
|
||||||
|
private int processedCount;
|
||||||
|
private int successCount;
|
||||||
|
private int failCount;
|
||||||
|
|
||||||
|
private String errorCode;
|
||||||
|
private String errorMessage;
|
||||||
|
}
|
||||||
|
|
@ -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 VehicleOdometerCommand {
|
||||||
|
private String source;
|
||||||
|
private LocalDateTime fetchedAt;
|
||||||
|
private List<VehicleOdometerRecordDto> records;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.goi.integration.samsara.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class VehicleOdometerRecordDto {
|
||||||
|
private String source; // SAMSARA
|
||||||
|
private String vehicleExternalId; // samsara vehicle.id
|
||||||
|
private LocalDateTime sampleTime; // snapshot time (UTC)
|
||||||
|
private Long odometerMeters; // cumulative meters
|
||||||
|
private String payloadHash; // idempotent hash
|
||||||
|
private JsonNode payloadJson; // minimal raw payload
|
||||||
|
}
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
package com.goi.integration.samsara.job;
|
|
||||||
|
|
||||||
import com.goi.integration.common.config.ScheduleJobConfigDto;
|
|
||||||
import com.goi.integration.common.config.ScheduleJobConfigProvider;
|
|
||||||
import com.goi.integration.common.dto.ExtIngestResult;
|
|
||||||
import com.goi.integration.samsara.client.SamsaraClient;
|
|
||||||
import com.goi.integration.samsara.service.InspectionIngestService;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class DvirIngestJob {
|
|
||||||
|
|
||||||
private static final String JOB_CODE = "SAMSARA_DVIR";
|
|
||||||
|
|
||||||
private final ScheduleJobConfigProvider configProvider;
|
|
||||||
private final SamsaraClient samsaraClient;
|
|
||||||
private final InspectionIngestService ingestService;
|
|
||||||
|
|
||||||
@Scheduled(cron = "${ext.samsara.jobs.dvir.cron:0 */10 * * * *}")
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
ScheduleJobConfigDto cfg = configProvider.getJobConfig(JOB_CODE)
|
|
||||||
.filter(ScheduleJobConfigDto::getSjcEnabled)
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
if (cfg == null) {
|
|
||||||
log.info("[{}] disabled or config missing", JOB_CODE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int limit = (cfg.getSjcMaxRecords() != null) ? cfg.getSjcMaxRecords() : 252;
|
|
||||||
|
|
||||||
Instant now = Instant.now();
|
|
||||||
long lookbackSec = Duration.ofHours(cfg.getSjcLookbackHours() != null ? cfg.getSjcLookbackHours() : 24).getSeconds();
|
|
||||||
long overlapSec = Duration.ofMinutes(cfg.getSjcOverlapMinutes() != null ? cfg.getSjcOverlapMinutes() : 0).getSeconds();
|
|
||||||
|
|
||||||
Instant start = now.minusSeconds(lookbackSec + overlapSec);
|
|
||||||
Instant end = now;
|
|
||||||
|
|
||||||
log.info("[{}] calling samsara dvir history start={} end={} limit={}", JOB_CODE, start, end, limit);
|
|
||||||
|
|
||||||
String rawJson = samsaraClient.getDvirHistory(limit, start, end);
|
|
||||||
|
|
||||||
// opr-rest-api ingest 호출
|
|
||||||
ExtIngestResult result = ingestService.ingestFromRawJson(rawJson);
|
|
||||||
|
|
||||||
log.info("[{}] ingest result received={}, inserted={}, updated={}, skipped={}",
|
|
||||||
JOB_CODE, result.getReceived(), result.getInserted(), result.getUpdated(), result.getSkipped());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -21,8 +21,6 @@ import java.util.List;
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class InspectionIngestService {
|
public class InspectionIngestService {
|
||||||
|
|
||||||
private static final String JOB_CODE = "SAMSARA_DVIR";
|
|
||||||
|
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final OprIngestClient oprIngestClient;
|
private final OprIngestClient oprIngestClient;
|
||||||
|
|
||||||
|
|
@ -42,12 +40,12 @@ public class InspectionIngestService {
|
||||||
// data[] 없음
|
// data[] 없음
|
||||||
if (!data.isArray()) {
|
if (!data.isArray()) {
|
||||||
log.warn("Samsara DVIR response has no data[] array");
|
log.warn("Samsara DVIR response has no data[] array");
|
||||||
return ExtIngestResult.empty("SAMSARA", "DVIR");
|
return ExtIngestResult.empty("SAMSARA");
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
List<ExtInspectionRecordDto> records = new ArrayList<>();
|
List<ExtInspectionRecordDto> records = new ArrayList<>();
|
||||||
log.info("[{}] data={}", JOB_CODE, data.size());
|
log.info("inspection data size={}", data.size());
|
||||||
|
|
||||||
// data[] 각 node 가 하나의 inspection (preTrip / postTrip)
|
// data[] 각 node 가 하나의 inspection (preTrip / postTrip)
|
||||||
for (JsonNode node : data) {
|
for (JsonNode node : data) {
|
||||||
|
|
@ -87,7 +85,6 @@ public class InspectionIngestService {
|
||||||
ExtInspectionIngestCommand command =
|
ExtInspectionIngestCommand command =
|
||||||
ExtInspectionIngestCommand.builder()
|
ExtInspectionIngestCommand.builder()
|
||||||
.source("SAMSARA")
|
.source("SAMSARA")
|
||||||
.recordType("DVIR")
|
|
||||||
.fetchedAt(LocalDateTime.now())
|
.fetchedAt(LocalDateTime.now())
|
||||||
.records(records)
|
.records(records)
|
||||||
.build();
|
.build();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.goi.integration.samsara.service;
|
||||||
|
|
||||||
|
import com.goi.integration.common.dto.ExtIngestResult;
|
||||||
|
import com.goi.integration.samsara.client.SamsaraClient;
|
||||||
|
import com.goi.integration.samsara.dto.ScheduleWorkerRequestDto;
|
||||||
|
import com.goi.integration.samsara.dto.ScheduleWorkerResponseDto;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class InspectionIngestWorker {
|
||||||
|
|
||||||
|
private final SamsaraClient samsaraClient;
|
||||||
|
private final InspectionIngestService ingestService;
|
||||||
|
|
||||||
|
public ScheduleWorkerResponseDto execute(ScheduleWorkerRequestDto request) {
|
||||||
|
|
||||||
|
Instant start = request.getFrom().toInstant(ZoneOffset.UTC);
|
||||||
|
Instant end = request.getTo().toInstant(ZoneOffset.UTC);
|
||||||
|
int limit = request.getMaxRecords() != null ? request.getMaxRecords() : 252;
|
||||||
|
|
||||||
|
log.info("[{}] worker start={} end={} limit={}", request.getJobCode(), start, end, limit);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String rawJson = samsaraClient.getDvirHistory(limit, start, end);
|
||||||
|
|
||||||
|
ExtIngestResult result = ingestService.ingestFromRawJson(rawJson);
|
||||||
|
|
||||||
|
log.info("[{}] ingest success received={}, inserted={}, updated={}, skipped={}",
|
||||||
|
request.getJobCode(),
|
||||||
|
result.getReceived(),
|
||||||
|
result.getInserted(),
|
||||||
|
result.getUpdated(),
|
||||||
|
result.getSkipped()
|
||||||
|
);
|
||||||
|
|
||||||
|
return ScheduleWorkerResponseDto.builder()
|
||||||
|
.success(true)
|
||||||
|
.processedCount(result.getReceived())
|
||||||
|
.successCount(result.getInserted() + result.getUpdated())
|
||||||
|
.failCount(result.getSkipped())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[{}] ingest failed", request.getJobCode(), e);
|
||||||
|
|
||||||
|
return ScheduleWorkerResponseDto.builder()
|
||||||
|
.success(false)
|
||||||
|
.errorCode("INSPECTION_INGEST_ERROR")
|
||||||
|
.errorMessage(e.getMessage())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue