diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetryMetadataValues.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetryMetadataValues.java new file mode 100644 index 00000000..48f85bba --- /dev/null +++ b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetryMetadataValues.java @@ -0,0 +1,22 @@ +package com.lingniu.ingest.api.event; + +import java.util.Locale; + +/** + * Stable string formatting for metadata values shared across protocol mappers. + */ +public final class TelemetryMetadataValues { + + private TelemetryMetadataValues() { + } + + public static String doubleValue(double value) { + if (!Double.isFinite(value)) { + return ""; + } + String formatted = String.format(Locale.ROOT, "%.6f", value); + return formatted.indexOf('.') < 0 + ? formatted + : formatted.replaceAll("0+$", "").replaceAll("\\.$", ""); + } +} diff --git a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetryMetadataValuesTest.java b/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetryMetadataValuesTest.java new file mode 100644 index 00000000..d19abfc6 --- /dev/null +++ b/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetryMetadataValuesTest.java @@ -0,0 +1,23 @@ +package com.lingniu.ingest.api.event; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class TelemetryMetadataValuesTest { + + @Test + void formatsDoubleWithoutFloatingPointNoise() { + assertThat(TelemetryMetadataValues.doubleValue(123456.70000000001)).isEqualTo("123456.7"); + assertThat(TelemetryMetadataValues.doubleValue(1234.5)).isEqualTo("1234.5"); + assertThat(TelemetryMetadataValues.doubleValue(1234.0)).isEqualTo("1234"); + assertThat(TelemetryMetadataValues.doubleValue(1.23456789)).isEqualTo("1.234568"); + } + + @Test + void rejectsNonFiniteDoubleValuesAsBlankMetadata() { + assertThat(TelemetryMetadataValues.doubleValue(Double.NaN)).isEmpty(); + assertThat(TelemetryMetadataValues.doubleValue(Double.POSITIVE_INFINITY)).isEmpty(); + assertThat(TelemetryMetadataValues.doubleValue(Double.NEGATIVE_INFINITY)).isEmpty(); + } +} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/mapper/YutongEventMapper.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/mapper/YutongEventMapper.java index 93d001a3..b8537222 100644 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/mapper/YutongEventMapper.java +++ b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/mapper/YutongEventMapper.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.lingniu.ingest.api.ProtocolId; import com.lingniu.ingest.api.event.LocationPayload; import com.lingniu.ingest.api.event.RealtimePayload; +import com.lingniu.ingest.api.event.TelemetryMetadataValues; import com.lingniu.ingest.api.event.VehicleEvent; import com.lingniu.ingest.api.spi.EventMapper; import com.lingniu.ingest.identity.VehicleIdentity; @@ -16,7 +17,6 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Base64; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.UUID; @@ -99,7 +99,7 @@ public final class YutongEventMapper implements EventMapper { 0, 0); Map locationMeta = mileage == null ? meta - : withMeta(meta, "total_mileage_km", formatDouble(mileage)); + : withMeta(meta, "total_mileage_km", TelemetryMetadataValues.doubleValue(mileage)); out.add(new VehicleEvent.Location( UUID.randomUUID().toString(), vin, ProtocolId.MQTT_YUTONG, payload.deviceTime(), ingestTime, @@ -237,13 +237,6 @@ public final class YutongEventMapper implements EventMapper { } } - private static String formatDouble(double value) { - String formatted = String.format(Locale.ROOT, "%.6f", value); - return formatted.indexOf('.') < 0 - ? formatted - : formatted.replaceAll("0+$", "").replaceAll("\\.$", ""); - } - private record IdentityResolution(VehicleIdentity identity, String errorMessage) { private static IdentityResolution ok(VehicleIdentity identity) { return new IdentityResolution(identity, null); diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapper.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapper.java index b2f5a864..f0ea087f 100644 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapper.java +++ b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapper.java @@ -4,6 +4,7 @@ import com.lingniu.ingest.api.ProtocolId; import com.lingniu.ingest.api.event.AlarmPayload; import com.lingniu.ingest.api.event.LocationPayload; import com.lingniu.ingest.api.event.RealtimePayload; +import com.lingniu.ingest.api.event.TelemetryMetadataValues; import com.lingniu.ingest.api.event.VehicleEvent; import com.lingniu.ingest.api.spi.EventMapper; import com.lingniu.ingest.protocol.gb32960.model.CommandBody; @@ -16,7 +17,6 @@ import java.time.Instant; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -166,7 +166,7 @@ public final class Gb32960EventMapper implements EventMapper { v != null && v.speedKmh != null ? v.speedKmh : 0.0, 0.0, 0, p.statusFlag); Map locationMeta = v != null && v.totalMileageKm != null - ? withMeta(meta, "total_mileage_km", formatDouble(v.totalMileageKm)) + ? withMeta(meta, "total_mileage_km", TelemetryMetadataValues.doubleValue(v.totalMileageKm)) : meta; out.add(new VehicleEvent.Location( UUID.randomUUID().toString(), @@ -341,10 +341,4 @@ public final class Gb32960EventMapper implements EventMapper { return Map.copyOf(copy); } - private static String formatDouble(double value) { - String formatted = String.format(Locale.ROOT, "%.6f", value); - return formatted.indexOf('.') < 0 - ? formatted - : formatted.replaceAll("0+$", "").replaceAll("\\.$", ""); - } } diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java index 0fba341d..d336762b 100644 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java +++ b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java @@ -2,6 +2,7 @@ package com.lingniu.ingest.protocol.jt808.mapper; import com.lingniu.ingest.api.ProtocolId; import com.lingniu.ingest.api.event.LocationPayload; +import com.lingniu.ingest.api.event.TelemetryMetadataValues; import com.lingniu.ingest.api.event.VehicleEvent; import com.lingniu.ingest.api.spi.EventMapper; import com.lingniu.ingest.identity.VehicleIdentity; @@ -287,7 +288,7 @@ public final class Jt808EventMapper implements EventMapper { copy.putAll(Jt808LocationAdditionalInfo.decode(loc.extensionItems())); Double totalMileageKm = totalMileageKm(loc); if (totalMileageKm != null) { - copy.put("total_mileage_km", Double.toString(totalMileageKm)); + copy.put("total_mileage_km", TelemetryMetadataValues.doubleValue(totalMileageKm)); } } return Map.copyOf(copy);