package envelope import "strings" func IsRealtimeTelemetryFrame(env FrameEnvelope) bool { if env.ParseStatus == ParseBadFrame { return false } switch env.Protocol { case ProtocolGB32960: command := strings.TrimSpace(env.MessageID) if command == "" && env.Parsed != nil { if header, ok := env.Parsed["header"].(map[string]any); ok { command = strings.TrimSpace(stringAny(header["command"])) } } if command != "" && !strings.EqualFold(command, "0x02") && !strings.EqualFold(command, "0x03") { return false } _, hasDataUnits := env.Parsed["data_units"] return hasDataUnits || hasGB32960ParsedTelemetryField(env) || hasRealtimeField(env) case ProtocolJT808: messageID := strings.TrimSpace(env.MessageID) if messageID != "" && !strings.EqualFold(messageID, "0x0200") { return false } if _, ok := env.Parsed["location"]; ok { return true } return hasParsedFieldPrefix(env, "jt808.location.") || hasRealtimeField(env) case ProtocolYutongMQTT: data, ok := env.Parsed["data"].(map[string]any) if ok && len(data) > 0 { return true } return hasParsedFieldPrefix(env, "yutong_mqtt.data.") || hasParsedFieldPrefix(env, "yutong_mqtt.root.data.") default: return false } } func hasGB32960ParsedTelemetryField(env FrameEnvelope) bool { for field := range env.ParsedFields { if !strings.HasPrefix(field, "gb32960.") { continue } if strings.HasPrefix(field, "gb32960.header.") || strings.HasPrefix(field, "gb32960.platform.") || strings.HasPrefix(field, "gb32960.identity.") || strings.HasPrefix(field, "gb32960.device_time.") { continue } return true } return false } // RequiresVehicleIdentity keeps platform/control frames out of vehicle identity // quality metrics while preserving JT808's phone-centric registration/auth flow. func RequiresVehicleIdentity(env FrameEnvelope) bool { if env.ParseStatus == ParseBadFrame { return false } switch env.Protocol { case ProtocolGB32960: return IsRealtimeTelemetryFrame(env) case ProtocolJT808: return true case ProtocolYutongMQTT: return IsRealtimeTelemetryFrame(env) default: return false } } func hasRealtimeField(env FrameEnvelope) bool { if env.Fields[FieldLatitude] != nil || env.Fields[FieldLongitude] != nil || env.Fields[FieldTotalMileageKM] != nil || env.Fields[FieldSpeedKMH] != nil || env.Fields[FieldSOCPercent] != nil { return true } for field := range env.ParsedFields { if strings.HasSuffix(field, ".latitude") || strings.HasSuffix(field, ".longitude") || strings.HasSuffix(field, ".total_mileage_km") || strings.HasSuffix(field, ".speed_kmh") || strings.HasSuffix(field, ".soc_percent") { return true } } return false } func hasParsedFieldPrefix(env FrameEnvelope, prefix string) bool { for field := range env.ParsedFields { if strings.HasPrefix(field, prefix) { return true } } return false } func stringAny(value any) string { if text, ok := value.(string); ok { return text } return "" }