docs: align jt808 mileage metric design
This commit is contained in:
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
## Current Goal
|
## 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
|
## Current Design
|
||||||
|
|
||||||
- Source topic: `vehicle.event.jt808.v1`
|
- Source topic: `vehicle.event.jt808.v1`
|
||||||
- Runtime app: `vehicle-analytics-app`
|
- Runtime app: `vehicle-analytics-app`
|
||||||
- Runtime state: Redis `Jt808MileageStateStore`
|
- Runtime state: none outside `vehicle_stat_metric`
|
||||||
- Metric output: `VehicleStatRepository.saveDailyStat(...)`
|
- Metric output: `VehicleStatRepository.recordDailyMileageSample(...)`
|
||||||
- Production metric storage: JDBC/MySQL `vehicle_stat_metric`
|
- Production metric storage: JDBC/MySQL `vehicle_stat_metric`
|
||||||
- Date boundary: `Asia/Shanghai`
|
- Date boundary: `Asia/Shanghai`
|
||||||
- Calculation method: `JT808_TOTAL_MILEAGE_DIFF`
|
- 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
|
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
|
## Runtime Properties
|
||||||
|
|
||||||
@@ -32,12 +39,8 @@ VEHICLE_STAT_REPOSITORY_TYPE=jdbc
|
|||||||
MYSQL_JDBC_URL=<jdbc-url>
|
MYSQL_JDBC_URL=<jdbc-url>
|
||||||
MYSQL_USERNAME=<user>
|
MYSQL_USERNAME=<user>
|
||||||
MYSQL_PASSWORD=<password>
|
MYSQL_PASSWORD=<password>
|
||||||
REDIS_HOST=<host>
|
|
||||||
REDIS_PORT=6379
|
|
||||||
REDIS_DATABASE=50
|
|
||||||
REDIS_PASSWORD=<password>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes
|
## 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.
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package com.lingniu.ingest.vehiclestat;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@@ -15,4 +18,30 @@ class VehicleStatRepositoryContractTest {
|
|||||||
.map(Method::getName))
|
.map(Method::getName))
|
||||||
.containsExactlyInAnyOrder("recordDailyMileageSample", "findDailyStat");
|
.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user