From 74fbdd2b1ac23e6784d6bce1939265ee4bdfd38c Mon Sep 17 00:00:00 2001 From: lingniu Date: Wed, 1 Jul 2026 09:36:57 +0800 Subject: [PATCH] docs: align jt808 mileage metric design --- .../2026-06-30-kafka-streaming-mileage.md | 21 ++++++++------ .../VehicleStatRepositoryContractTest.java | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/docs/superpowers/plans/2026-06-30-kafka-streaming-mileage.md b/docs/superpowers/plans/2026-06-30-kafka-streaming-mileage.md index 1b1d4f06..7d501471 100644 --- a/docs/superpowers/plans/2026-06-30-kafka-streaming-mileage.md +++ b/docs/superpowers/plans/2026-06-30-kafka-streaming-mileage.md @@ -2,14 +2,14 @@ ## Current Goal -Consume JT808 Kafka location events, keep rolling daily state, and write the derived daily mileage into the common vehicle-stat metric repository. +Consume JT808 Kafka location events and write the derived daily mileage into the common vehicle-stat metric repository. ## Current Design - Source topic: `vehicle.event.jt808.v1` - Runtime app: `vehicle-analytics-app` -- Runtime state: Redis `Jt808MileageStateStore` -- Metric output: `VehicleStatRepository.saveDailyStat(...)` +- Runtime state: none outside `vehicle_stat_metric` +- Metric output: `VehicleStatRepository.recordDailyMileageSample(...)` - Production metric storage: JDBC/MySQL `vehicle_stat_metric` - Date boundary: `Asia/Shanghai` - Calculation method: `JT808_TOTAL_MILEAGE_DIFF` @@ -20,7 +20,14 @@ JT808 daily mileage is calculated only from the GPS total mileage reported by JT daily_mileage_km = last_total_mileage_km - first_total_mileage_km ``` -The stream saves a metric only when at least two ordered location points for the same vehicle and local day contain valid `total_mileage_km`. Negative differences are treated as invalid and are not written. +The first valid local-day sample writes `daily_mileage_km=0.0`. Later samples update the same metric row with: + +```text +metric_value = latest_total_mileage_km - first_total_mileage_km +metric_key = daily_mileage_km +``` + +The first and latest GPS total mileage values stay on that same metric row as calculation source columns so restarts recover from MySQL without Redis or another state table. ## Runtime Properties @@ -32,12 +39,8 @@ VEHICLE_STAT_REPOSITORY_TYPE=jdbc MYSQL_JDBC_URL= MYSQL_USERNAME= MYSQL_PASSWORD= -REDIS_HOST= -REDIS_PORT=6379 -REDIS_DATABASE=50 -REDIS_PASSWORD= ``` ## Notes -Do not create or write a protocol-specific JT808 daily-mileage table. Do not add GPS segment distance, speed integral, or jump-filter state back into this path unless the mileage definition changes again. +Do not create or write a protocol-specific JT808 daily-mileage table. Do not add distance accumulation, integral calculation, Redis state, or memory state back into this path unless the mileage definition changes again. diff --git a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatRepositoryContractTest.java b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatRepositoryContractTest.java index dbffa523..623ce3c2 100644 --- a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatRepositoryContractTest.java +++ b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatRepositoryContractTest.java @@ -2,7 +2,10 @@ package com.lingniu.ingest.vehiclestat; import org.junit.jupiter.api.Test; +import java.io.IOException; import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; @@ -15,4 +18,30 @@ class VehicleStatRepositoryContractTest { .map(Method::getName)) .containsExactlyInAnyOrder("recordDailyMileageSample", "findDailyStat"); } + + @Test + void jt808MileagePlanDocumentsMetricTableDiffOnly() throws IOException { + String plan = Files.readString(repositoryRoot() + .resolve("docs/superpowers/plans/2026-06-30-kafka-streaming-mileage.md")); + + assertThat(plan) + .contains("Runtime state: none outside `vehicle_stat_metric`") + .contains("daily_mileage_km = last_total_mileage_km - first_total_mileage_km") + .contains("Do not create or write a protocol-specific JT808 daily-mileage table.") + .doesNotContain("Redis `Jt808MileageStateStore`") + .doesNotContain("REDIS_HOST") + .doesNotContain("GPS segment distance") + .doesNotContain("speed integral"); + } + + private static Path repositoryRoot() { + Path path = Path.of("").toAbsolutePath(); + while (path != null && !Files.exists(path.resolve("docs/superpowers/plans"))) { + path = path.getParent(); + } + if (path == null) { + throw new IllegalStateException("repository root not found"); + } + return path; + } }