diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java index 0fe7e5c3..818e6f6c 100644 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java +++ b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java @@ -63,25 +63,26 @@ public final class JdbcVehicleStatMetricRepository implements VehicleStatReposit } String normalizedVin = clean(vin); Date date = Date.valueOf(statDate); - OptionalDouble existingStart = findMetric(normalizedVin, date, DAILY_MILEAGE_START_TOTAL_KEY); - double startTotalMileage = existingStart.orElse(totalMileageKm); - if (existingStart.isEmpty()) { - upsertMetric(normalizedVin, date, DAILY_MILEAGE_START_TOTAL_KEY, startTotalMileage, - DAILY_MILEAGE_UNIT, DailyMileageStrategy.JT808_TOTAL_MILEAGE_DIFF.name()); - } - - OptionalDouble existingLatest = findMetric(normalizedVin, date, DAILY_MILEAGE_LATEST_TOTAL_KEY); - if (existingLatest.isPresent() && totalMileageKm < existingLatest.getAsDouble()) { - return findDailyStat(normalizedVin, statDate); - } - if (totalMileageKm < startTotalMileage) { - return findDailyStat(normalizedVin, statDate); - } - String strategy = DailyMileageStrategy.JT808_TOTAL_MILEAGE_DIFF.name(); - upsertMetric(normalizedVin, date, DAILY_MILEAGE_LATEST_TOTAL_KEY, totalMileageKm, - DAILY_MILEAGE_UNIT, strategy); - double dailyMileageKm = totalMileageKm - startTotalMileage; + OptionalDouble existingMin = findMetric(normalizedVin, date, DAILY_MILEAGE_START_TOTAL_KEY); + double minTotalMileage = existingMin.isPresent() + ? Math.min(existingMin.getAsDouble(), totalMileageKm) + : totalMileageKm; + if (existingMin.isEmpty() || Double.compare(minTotalMileage, existingMin.getAsDouble()) != 0) { + upsertMetric(normalizedVin, date, DAILY_MILEAGE_START_TOTAL_KEY, minTotalMileage, + DAILY_MILEAGE_UNIT, strategy); + } + + OptionalDouble existingMax = findMetric(normalizedVin, date, DAILY_MILEAGE_LATEST_TOTAL_KEY); + double maxTotalMileage = existingMax.isPresent() + ? Math.max(existingMax.getAsDouble(), totalMileageKm) + : totalMileageKm; + if (existingMax.isEmpty() || Double.compare(maxTotalMileage, existingMax.getAsDouble()) != 0) { + upsertMetric(normalizedVin, date, DAILY_MILEAGE_LATEST_TOTAL_KEY, maxTotalMileage, + DAILY_MILEAGE_UNIT, strategy); + } + + double dailyMileageKm = maxTotalMileage - minTotalMileage; VehicleDailyStatResult result = new VehicleDailyStatResult( normalizedVin, statDate, diff --git a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java index e980ef15..e395b197 100644 --- a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java +++ b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java @@ -78,6 +78,39 @@ class JdbcVehicleStatMetricRepositoryTest { String.class)).isEmpty(); } + @Test + void recalculatesJt808MileageFromDailyMinAndMaxTotalsWhenSamplesArriveOutOfOrder() { + JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource( + "jdbc:h2:mem:vehicle_stat_metric_jt808_out_of_order;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", + "sa", + "")); + JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate); + + repository.recordDailyMileageSample("VIN001", LocalDate.of(2026, 7, 1), 1008.75).orElseThrow(); + VehicleDailyStatResult lowerSample = repository.recordDailyMileageSample( + "VIN001", LocalDate.of(2026, 7, 1), 1000.5).orElseThrow(); + VehicleDailyStatResult higherSample = repository.recordDailyMileageSample( + "VIN001", LocalDate.of(2026, 7, 1), 1010.0).orElseThrow(); + + assertThat(lowerSample.dailyMileageKm()).hasValue(8.25); + assertThat(higherSample.dailyMileageKm()).hasValue(9.5); + assertThat(jdbcTemplate.queryForObject(""" + SELECT metric_value + FROM vehicle_stat_metric + WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_start_total_km' + """, Double.class)).isEqualTo(1000.5); + assertThat(jdbcTemplate.queryForObject(""" + SELECT metric_value + FROM vehicle_stat_metric + WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_latest_total_km' + """, Double.class)).isEqualTo(1010.0); + assertThat(jdbcTemplate.queryForObject(""" + SELECT metric_value + FROM vehicle_stat_metric + WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' + """, Double.class)).isEqualTo(9.5); + } + @Test void readsLegacyCalculationMethodAsTotalMileageDifference() { JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource(