feat: add decoded fact model

This commit is contained in:
lingniu
2026-06-29 12:55:20 +08:00
parent b5453ff888
commit 969a245454
2 changed files with 254 additions and 0 deletions

View File

@@ -0,0 +1,215 @@
package com.lingniu.ingest.facts;
import com.lingniu.ingest.api.ProtocolId;
import java.time.Instant;
import java.util.Map;
public sealed interface DecodedFact
permits DecodedFact.Location,
DecodedFact.Realtime,
DecodedFact.Alarm,
DecodedFact.Session,
DecodedFact.Register,
DecodedFact.Extension {
String factId();
String frameId();
ProtocolId protocol();
String vehicleKey();
String vin();
String phone();
Instant eventTime();
Instant receivedAt();
String rawUri();
Map<String, String> metadata();
record Location(
String factId,
String frameId,
ProtocolId protocol,
String vehicleKey,
String vin,
String phone,
Instant eventTime,
Instant receivedAt,
String rawUri,
Map<String, String> metadata,
Double longitude,
Double latitude,
Double altitudeM,
Double speedKmh,
Double directionDeg,
Long alarmFlag,
Long statusFlag,
Double totalMileageKm
) implements DecodedFact {
public Location {
BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
vin = BaseFields.clean(vin);
phone = BaseFields.clean(phone);
metadata = BaseFields.metadata(metadata);
}
}
record Realtime(
String factId,
String frameId,
ProtocolId protocol,
String vehicleKey,
String vin,
String phone,
Instant eventTime,
Instant receivedAt,
String rawUri,
Map<String, String> metadata,
Map<String, String> fields
) implements DecodedFact {
public Realtime {
BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
vin = BaseFields.clean(vin);
phone = BaseFields.clean(phone);
metadata = BaseFields.metadata(metadata);
fields = fields == null ? Map.of() : Map.copyOf(fields);
}
}
record Alarm(
String factId,
String frameId,
ProtocolId protocol,
String vehicleKey,
String vin,
String phone,
Instant eventTime,
Instant receivedAt,
String rawUri,
Map<String, String> metadata,
String alarmLevel,
Long alarmCode,
String alarmName,
Map<String, String> fields
) implements DecodedFact {
public Alarm {
BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
vin = BaseFields.clean(vin);
phone = BaseFields.clean(phone);
alarmLevel = BaseFields.clean(alarmLevel);
alarmName = BaseFields.clean(alarmName);
metadata = BaseFields.metadata(metadata);
fields = fields == null ? Map.of() : Map.copyOf(fields);
}
}
record Session(
String factId,
String frameId,
ProtocolId protocol,
String vehicleKey,
String vin,
String phone,
Instant eventTime,
Instant receivedAt,
String rawUri,
Map<String, String> metadata,
String sessionType,
Integer resultCode,
Map<String, String> fields
) implements DecodedFact {
public Session {
BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
vin = BaseFields.clean(vin);
phone = BaseFields.clean(phone);
sessionType = BaseFields.clean(sessionType);
metadata = BaseFields.metadata(metadata);
fields = fields == null ? Map.of() : Map.copyOf(fields);
}
}
record Register(
String factId,
String frameId,
ProtocolId protocol,
String vehicleKey,
String vin,
String phone,
Instant eventTime,
Instant receivedAt,
String rawUri,
Map<String, String> metadata,
Map<String, String> registrationFields
) implements DecodedFact {
public Register {
BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
vin = BaseFields.clean(vin);
phone = BaseFields.clean(phone);
metadata = BaseFields.metadata(metadata);
registrationFields = registrationFields == null ? Map.of() : Map.copyOf(registrationFields);
}
}
record Extension(
String factId,
String frameId,
ProtocolId protocol,
String vehicleKey,
String vin,
String phone,
Instant eventTime,
Instant receivedAt,
String rawUri,
Map<String, String> metadata,
String extensionType,
Map<String, String> fields
) implements DecodedFact {
public Extension {
BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
vin = BaseFields.clean(vin);
phone = BaseFields.clean(phone);
extensionType = BaseFields.clean(extensionType);
metadata = BaseFields.metadata(metadata);
fields = fields == null ? Map.of() : Map.copyOf(fields);
}
}
final class BaseFields {
private BaseFields() {
}
static void validate(String factId,
String frameId,
ProtocolId protocol,
String vehicleKey,
Instant eventTime,
Instant receivedAt,
String rawUri) {
required(factId, "factId");
required(frameId, "frameId");
if (protocol == null) {
throw new IllegalArgumentException("protocol must not be null");
}
required(vehicleKey, "vehicleKey");
if (eventTime == null) {
throw new IllegalArgumentException("eventTime must not be null");
}
if (receivedAt == null) {
throw new IllegalArgumentException("receivedAt must not be null");
}
required(rawUri, "rawUri");
}
static String clean(String value) {
return value == null ? "" : value.trim();
}
static Map<String, String> metadata(Map<String, String> metadata) {
return metadata == null ? Map.of() : Map.copyOf(metadata);
}
private static void required(String value, String field) {
if (clean(value).isBlank()) {
throw new IllegalArgumentException(field + " must not be blank");
}
}
}
}

View File

@@ -0,0 +1,39 @@
package com.lingniu.ingest.facts;
import com.lingniu.ingest.api.ProtocolId;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class DecodedFactTest {
@Test
void locationFactCarriesRawReferenceAndMetadata() {
DecodedFact.Location location = new DecodedFact.Location(
"fact-1",
"frame-1",
ProtocolId.JT808,
"jt808:013912345678",
"",
"013912345678",
Instant.parse("2026-06-29T00:00:00Z"),
Instant.parse("2026-06-29T00:00:01Z"),
"archive://jt808/frame-1.bin",
Map.of("messageId", "0x0200"),
120.123456,
30.123456,
10.0,
42.0,
180.0,
0L,
2L,
1000.5);
assertThat(location.factId()).isEqualTo("fact-1");
assertThat(location.rawUri()).isEqualTo("archive://jt808/frame-1.bin");
assertThat(location.metadata()).containsEntry("messageId", "0x0200");
}
}