feat(platform): harden telemetry pipeline and unify Semi UI workspaces

This commit is contained in:
lingniu
2026-07-18 00:26:36 +08:00
parent 65b4e4f055
commit 159c80b0ae
136 changed files with 21616 additions and 1785 deletions

View File

@@ -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)