feat(platform): harden telemetry pipeline and unify Semi UI workspaces
This commit is contained in:
@@ -445,6 +445,31 @@ func TestTrackPrimarySourcePreventsParallelProtocolsFromBecomingOneRoute(t *test
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrackPrimarySourcePrefersUsefulMovementOverHighFrequencyStaticHeartbeat(t *testing.T) {
|
||||
points := make([]HistoryLocationRow, 0, 120)
|
||||
for index := 0; index < 100; index++ {
|
||||
points = append(points, HistoryLocationRow{
|
||||
DeviceTime: time.Date(2026, 7, 16, 8, 0, index, 0, time.UTC).Format(time.RFC3339),
|
||||
Protocol: "YUTONG_MQTT",
|
||||
Longitude: 121.400000,
|
||||
Latitude: 31.200000,
|
||||
})
|
||||
}
|
||||
for index := 0; index < 20; index++ {
|
||||
points = append(points, HistoryLocationRow{
|
||||
DeviceTime: time.Date(2026, 7, 16, 8, index, 0, 0, time.UTC).Format(time.RFC3339),
|
||||
Protocol: "JT808",
|
||||
Longitude: 121.400000 + float64(index)*0.002,
|
||||
Latitude: 31.200000 + float64(index)*0.001,
|
||||
SpeedKmh: 36,
|
||||
})
|
||||
}
|
||||
selected, protocol, alternate := selectTrackPrimarySource(points, "")
|
||||
if protocol != "JT808" || len(selected) != 20 || alternate != 100 {
|
||||
t.Fatalf("moving source must beat static heartbeat volume: protocol=%s selected=%d alternate=%d", protocol, len(selected), alternate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrackSegmentsExposeStopsAndDataGapsWithoutClaimingIgnition(t *testing.T) {
|
||||
points := []HistoryLocationRow{
|
||||
{DeviceTime: "2026-07-03 10:00:00", SpeedKmh: 0, Longitude: 113.1, Latitude: 23.1},
|
||||
@@ -487,6 +512,23 @@ func TestMonitorBoundsAreValidatedAndApplied(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitorMapIgnoresInvalidOptionalBounds(t *testing.T) {
|
||||
vehicles := Page[VehicleRealtimeRow]{
|
||||
Items: []VehicleRealtimeRow{{
|
||||
VIN: "LTEST000000000001", Plate: "粤A00001", Online: true, LocationAvailable: true,
|
||||
Longitude: 113.2, Latitude: 23.1, LastSeen: "2026-07-17T15:41:00+08:00",
|
||||
}},
|
||||
Total: 1,
|
||||
}
|
||||
result, err := buildMonitorMapResponse(vehicles, url.Values{"zoom": {"12"}, "bounds": {"105,31,103,29"}})
|
||||
if err != nil {
|
||||
t.Fatalf("optional invalid bounds must not fail the map: %v", err)
|
||||
}
|
||||
if result.Total != 1 || len(result.Points) != 1 {
|
||||
t.Fatalf("invalid bounds should fall back to the bounded unfiltered map: %+v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitorQueryKeepsServerOwnedKeywordAndMotionStatus(t *testing.T) {
|
||||
query := normalizeMonitorQuery(url.Values{"keyword": {"沪A"}, "status": {"driving"}})
|
||||
if query.Get("vin") != "沪A" || query.Get("limit") != "10000" {
|
||||
@@ -921,13 +963,14 @@ func TestBuildLatestTelemetryResponseUsesCatalogAndNewestSourceEvidence(t *testi
|
||||
definitions := []MetricDefinition{
|
||||
{Key: "speed_kmh", Label: "速度", Description: "车辆最新行驶速度", Unit: "km/h", Category: "driving", ValueType: "numeric", SourceFields: map[string]string{"GB32960": "gb32960.vehicle.speed_kmh"}},
|
||||
{Key: "alarm_active", Label: "协议告警位", Unit: "", Category: "safety", ValueType: "boolean", SourceFields: map[string]string{"GB32960": "gb32960.alarm.general_alarm_flag"}},
|
||||
{Key: "hydrogen_concentration_percent", Label: "最高氢浓度", Unit: "%", Category: "fuel-cell", ValueType: "numeric", SourceFields: map[string]string{"GB32960": "gb32960.fuel_cell.max_hydrogen_concentration_percent"}},
|
||||
}
|
||||
frames := []RawFrameRow{
|
||||
{ID: "new", VIN: "VIN001", Protocol: "GB32960", DeviceTime: "2026-07-14 09:29:58", ServerTime: "2026-07-14 09:29:59", ParseStatus: "ok", SourceEndpoint: "gateway-a", ParsedFields: map[string]any{"gb32960.vehicle.speed_kmh": 42.5, "gb32960.alarm.general_alarm_flag": float64(1), "vendor.custom_temperature_c": 31.2}},
|
||||
{ID: "new", VIN: "VIN001", Protocol: "GB32960", DeviceTime: "2026-07-14 09:29:58", ServerTime: "2026-07-14 09:29:59", ParseStatus: "ok", SourceEndpoint: "gateway-a", ParsedFields: map[string]any{"gb32960.vehicle.speed_kmh": 42.5, "gb32960.alarm.general_alarm_flag": float64(1), "gb32960.fuel_cell.max_hydrogen_concentration_percent": 0.032, "vendor.custom_temperature_c": 31.2}},
|
||||
{ID: "old", VIN: "VIN001", Protocol: "GB32960", DeviceTime: "2026-07-14 09:20:00", ServerTime: "2026-07-14 09:20:01", ParseStatus: "ok", ParsedFields: map[string]any{"gb32960.vehicle.speed_kmh": 10.0}},
|
||||
}
|
||||
response := buildLatestTelemetryResponse("VIN001", frames, definitions, now)
|
||||
if response.VIN != "VIN001" || response.ScannedFrames != 2 || len(response.Values) != 3 || len(response.Categories) != 3 {
|
||||
if response.VIN != "VIN001" || response.ScannedFrames != 2 || len(response.Values) != 4 || len(response.Categories) != 4 {
|
||||
t.Fatalf("unexpected response: %+v", response)
|
||||
}
|
||||
byKey := map[string]LatestTelemetryValue{}
|
||||
@@ -940,11 +983,58 @@ func TestBuildLatestTelemetryResponseUsesCatalogAndNewestSourceEvidence(t *testi
|
||||
if alarm := byKey["alarm_active"]; alarm.Value != true || alarm.Category != "alarm" {
|
||||
t.Fatalf("catalog boolean should be normalized: %+v", alarm)
|
||||
}
|
||||
if hydrogen := byKey["hydrogen_concentration_percent"]; hydrogen.Value != 0.032 || hydrogen.Category != "fuel-cell" || hydrogen.Unit != "%" {
|
||||
t.Fatalf("fuel-cell catalog metric should retain its business category: %+v", hydrogen)
|
||||
}
|
||||
if extension := byKey["vendor.custom_temperature_c"]; extension.Category != "extension" || extension.Unit != "℃" || extension.Protocol != "GB32960" {
|
||||
t.Fatalf("manufacturer extension should retain source semantics: %+v", extension)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGB32960ReferenceFormatsStatusAndHistoryMetadata(t *testing.T) {
|
||||
if display := protocolDisplayValue("gb32960.vehicle.vehicle_status", float64(1)); display != "启动(1)" {
|
||||
t.Fatalf("vehicle status display = %q", display)
|
||||
}
|
||||
if display := protocolDisplayValue("gb32960.position.position_status", 6); display != "有效定位 · 南纬 · 西经(6)" {
|
||||
t.Fatalf("position status display = %q", display)
|
||||
}
|
||||
columns := mergeDiscoveredRawMetrics(historyRawMetrics(), []HistoryDataRow{{Values: map[string]any{
|
||||
"gb32960.drive_motor.motors.motor_1.state": 2,
|
||||
}}})
|
||||
for _, column := range columns {
|
||||
if column.Key == "gb32960.drive_motor.motors.motor_1.state" {
|
||||
if column.Label != "驱动电机状态 · GB32960" || len(column.ValueMappings) == 0 || column.ValueMappings[1].Label != "发电" {
|
||||
t.Fatalf("unexpected protocol reference column: %+v", column)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("GB32960 reference column missing: %+v", columns)
|
||||
}
|
||||
|
||||
func TestBuildLatestTelemetryResponseRetainsSameMetricAcrossProtocols(t *testing.T) {
|
||||
now := time.Date(2026, 7, 14, 9, 30, 0, 0, time.FixedZone("Asia/Shanghai", 8*60*60))
|
||||
definitions := []MetricDefinition{{
|
||||
Key: "total_mileage_km", Label: "总里程", Description: "车辆累计行驶里程", Unit: "km", Category: "driving", ValueType: "numeric",
|
||||
SourceFields: map[string]string{"GB32960": "gb32960.vehicle.total_mileage_km", "JT808": "jt808.location.total_mileage_km"},
|
||||
}}
|
||||
frames := []RawFrameRow{
|
||||
{ID: "gb", VIN: "VIN001", Protocol: "GB32960", ServerTime: "2026-07-14 09:29:59", ParseStatus: "ok", ParsedFields: map[string]any{"gb32960.vehicle.total_mileage_km": 1000.0}},
|
||||
{ID: "jt", VIN: "VIN001", Protocol: "JT808", ServerTime: "2026-07-14 09:29:59", ParseStatus: "ok", ParsedFields: map[string]any{"jt808.location.total_mileage_km": 980.0}},
|
||||
}
|
||||
response := buildLatestTelemetryResponse("VIN001", frames, definitions, now)
|
||||
if len(response.Values) != 2 {
|
||||
t.Fatalf("same unified metric from two protocols must be retained independently: %+v", response.Values)
|
||||
}
|
||||
byProtocol := map[string]LatestTelemetryValue{}
|
||||
for _, value := range response.Values {
|
||||
byProtocol[value.Protocol] = value
|
||||
}
|
||||
if byProtocol["GB32960"].Label != "仪表盘总里程" || byProtocol["JT808"].Label != "GPS 总里程" {
|
||||
t.Fatalf("mileage semantics were not preserved: %+v", byProtocol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLatestTelemetryQualityDistinguishesStaleAndWarnings(t *testing.T) {
|
||||
now := time.Date(2026, 7, 14, 9, 30, 0, 0, time.FixedZone("Asia/Shanghai", 8*60*60))
|
||||
stale, reason, freshness, delay := latestTelemetryQuality(RawFrameRow{DeviceTime: "2026-07-14 09:19:59", ServerTime: "2026-07-14 09:20:00", ParseStatus: "ok"}, now)
|
||||
|
||||
Reference in New Issue
Block a user