package capacity

import (
	"bufio"
	"fmt"
	"math"
	"sort"
	"strconv"
	"strings"
	"time"

	"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
	"lingniu-vehicle-ingest/go/vehicle-gateway/internal/topics"
)

type Status string

const (
	StatusOK       Status = "ok"
	StatusDegraded Status = "degraded"
)

type Report struct {
	Status                      Status                                  `json:"status"`
	CheckedAt                   time.Time                               `json:"checked_at"`
	Totals                      Totals                                  `json:"totals"`
	Services                    []ServiceCheck                          `json:"services"`
	GatewayFields               *GatewayFieldsDiagnostics               `json:"gateway_fields,omitempty"`
	ProtocolFlow                *ProtocolFlowDiagnostics                `json:"protocol_flow,omitempty"`
	IdentityResolution          *IdentityResolutionDiagnostics          `json:"identity_resolution,omitempty"`
	PipelineLatency             *PipelineLatencyDiagnostics             `json:"pipeline_latency,omitempty"`
	DailyMileageDiagnostics     *DailyMileageDiagnostics                `json:"daily_mileage_diagnostics,omitempty"`
	DailyMileageSourceQuality   *DailyMileageSourceQualityDiagnostics   `json:"daily_mileage_source_quality,omitempty"`
	DailyMileageSourceSelection *DailyMileageSourceSelectionDiagnostics `json:"daily_mileage_source_selection,omitempty"`
	DataSourceDiagnostics       *DataSourceDiagnostics                  `json:"data_source_diagnostics,omitempty"`
	DataSourceKindDiagnostics   *DataSourceDiagnostics                  `json:"data_source_kind_diagnostics,omitempty"`
	JT808IdentityGaps           *JT808IdentityGapDiagnostics            `json:"jt808_identity_gaps,omitempty"`
	Findings                    []string                                `json:"findings"`
}

type Totals struct {
	ActiveConnections                  int64   `json:"active_connections"`
	KafkaLag                           float64 `json:"kafka_lag"`
	BridgeConsumerPending              float64 `json:"bridge_consumer_pending"`
	BridgeAckPending                   float64 `json:"bridge_ack_pending"`
	BridgeBatchPendingMessages         float64 `json:"bridge_batch_pending_messages"`
	FieldsProjectorKafkaLag            float64 `json:"fields_projector_kafka_lag"`
	FieldsProjectorBatchPending        float64 `json:"fields_projector_batch_pending_messages"`
	FieldsProjectorRetryPending        float64 `json:"fields_projector_retry_pending_messages"`
	FastWriterConsumerPending          float64 `json:"fast_writer_consumer_pending"`
	FastWriterAckPending               float64 `json:"fast_writer_ack_pending"`
	FastWriterBatchPending             float64 `json:"fast_writer_batch_pending_messages"`
	HistoryBatchPending                float64 `json:"history_batch_pending_messages"`
	HistoryRowsPending                 float64 `json:"history_rows_pending"`
	HistoryRetryPending                float64 `json:"history_retry_pending_messages"`
	RealtimeBatchPending               float64 `json:"realtime_batch_pending_messages"`
	RealtimeAsyncQueuePending          float64 `json:"realtime_async_queue_pending"`
	RealtimeRetryPending               float64 `json:"realtime_retry_pending_messages"`
	StatRetryPending                   float64 `json:"stat_retry_pending_messages"`
	IdentityBatchPending               float64 `json:"identity_batch_pending_messages"`
	IdentityFactsPending               float64 `json:"identity_batch_pending_facts"`
	IdentityRetryPending               float64 `json:"identity_retry_pending_messages"`
	JT808RegistrationWriteQueuePending float64 `json:"jt808_registration_write_queue_pending"`
	JT808RegistrationWriteErrors       float64 `json:"jt808_registration_write_errors"`
}

type ServiceCheck struct {
	Name    string             `json:"name"`
	Metrics map[string]float64 `json:"metrics"`
}

type GatewayFieldsDiagnostics struct {
	Protocols []GatewayProtocolFields `json:"protocols,omitempty"`
}

type GatewayProtocolFields struct {
	Protocol                 string   `json:"protocol"`
	RawOK                    float64  `json:"raw_ok"`
	RealtimeCandidates       float64  `json:"realtime_candidates"`
	FieldsOK                 float64  `json:"fields_ok"`
	FieldsPublished          float64  `json:"fields_published"`
	FieldsDelegated          float64  `json:"fields_delegated"`
	FieldsSkippedNonRealtime float64  `json:"fields_skipped_non_realtime"`
	FieldsSkippedMissing     float64  `json:"fields_skipped_missing"`
	FieldsPublishError       float64  `json:"fields_publish_error"`
	MissingRatio             float64  `json:"missing_ratio"`
	LatestFieldCount         *float64 `json:"latest_field_count,omitempty"`
	Status                   string   `json:"status"`
	Reason                   string   `json:"reason,omitempty"`
}

type ProtocolFlowDiagnostics struct {
	Protocols []ProtocolFlowProtocol `json:"protocols,omitempty"`
}

type ProtocolFlowProtocol struct {
	Protocol                      string   `json:"protocol"`
	RawOK                         float64  `json:"raw_ok"`
	RealtimeCandidates            float64  `json:"realtime_candidates"`
	FieldsPublished               float64  `json:"fields_published"`
	LatestFieldCount              *float64 `json:"latest_field_count,omitempty"`
	IdentityTotal                 int64    `json:"identity_total"`
	IdentitySkipped               int64    `json:"identity_skipped"`
	IdentityResolved              int64    `json:"identity_resolved"`
	IdentityNonResolved           int64    `json:"identity_non_resolved"`
	IdentityNonResolvedRatio      float64  `json:"identity_non_resolved_ratio"`
	StatWritesOK                  float64  `json:"stat_writes_ok"`
	StatMileageCandidateWrites    float64  `json:"stat_mileage_candidate_writes"`
	StatSamplesFound              float64  `json:"stat_samples_found"`
	StatSamplesWritten            float64  `json:"stat_samples_written"`
	StatSamplesActionableSkipped  float64  `json:"stat_samples_actionable_skipped"`
	StatSkippedMissingFields      float64  `json:"stat_skipped_missing_fields"`
	StatSkippedMissingVIN         float64  `json:"stat_skipped_missing_vin"`
	StatSkippedMissingMileage     float64  `json:"stat_skipped_missing_mileage"`
	StatSkippedNonMileageFrame    float64  `json:"stat_skipped_non_mileage_frame"`
	StatSkippedNonPositiveMileage float64  `json:"stat_skipped_non_positive_mileage"`
	StatSkippedMissingTime        float64  `json:"stat_skipped_missing_time"`
	StatEventTimeFutureAdjusted   float64  `json:"stat_event_time_future_adjusted"`
	StatSkippedMissingSource      float64  `json:"stat_skipped_missing_source"`
	StatSkippedSameMileage        float64  `json:"stat_skipped_same_mileage"`
	StatSourceWritten             float64  `json:"stat_source_written"`
	StatSourceThrottled           float64  `json:"stat_source_throttled"`
	StatSourceMissingEndpoint     float64  `json:"stat_source_missing_endpoint"`
	StatSourceSkippedUnmanaged    float64  `json:"stat_source_skipped_unmanaged"`
	StatProjectionAttempted       float64  `json:"stat_projection_attempted"`
	StatProjectionWritten         float64  `json:"stat_projection_written"`
	StatProjectionThrottled       float64  `json:"stat_projection_throttled"`
	Status                        string   `json:"status"`
	Reason                        string   `json:"reason,omitempty"`
}

type IdentityResolutionDiagnostics struct {
	Total                 int64                        `json:"total"`
	SkippedCount          int64                        `json:"skipped_count"`
	ResolvedCount         int64                        `json:"resolved_count"`
	NonResolvedCount      int64                        `json:"non_resolved_count"`
	UnresolvedCount       int64                        `json:"unresolved_count"`
	TimeoutCount          int64                        `json:"timeout_count"`
	ErrorCount            int64                        `json:"error_count"`
	OtherNonResolvedCount int64                        `json:"other_non_resolved_count"`
	NonResolvedRatio      float64                      `json:"non_resolved_ratio"`
	StaleCacheCount       int64                        `json:"stale_cache_count"`
	StaleCacheRatio       float64                      `json:"stale_cache_ratio"`
	CacheEntries          map[string]int64             `json:"cache_entries,omitempty"`
	Issues                []IdentityIssueCount         `json:"issues,omitempty"`
	Protocols             []IdentityProtocolResolution `json:"protocols,omitempty"`
}

type IdentityIssueCount struct {
	Protocol  string `json:"protocol"`
	Status    string `json:"status"`
	MessageID string `json:"message_id"`
	Reason    string `json:"reason"`
	Count     int64  `json:"count"`
}

type IdentityProtocolResolution struct {
	Protocol              string           `json:"protocol"`
	Total                 int64            `json:"total"`
	SkippedCount          int64            `json:"skipped_count"`
	ResolvedCount         int64            `json:"resolved_count"`
	NonResolvedCount      int64            `json:"non_resolved_count"`
	UnresolvedCount       int64            `json:"unresolved_count"`
	TimeoutCount          int64            `json:"timeout_count"`
	ErrorCount            int64            `json:"error_count"`
	OtherNonResolvedCount int64            `json:"other_non_resolved_count"`
	NonResolvedRatio      float64          `json:"non_resolved_ratio"`
	StaleCacheCount       int64            `json:"stale_cache_count"`
	StaleCacheRatio       float64          `json:"stale_cache_ratio"`
	CacheStatusCounts     map[string]int64 `json:"cache_status_counts,omitempty"`
}

type PipelineLatencyDiagnostics struct {
	TotalSamples int64                 `json:"total_samples"`
	MaxP99MS     float64               `json:"max_p99_ms"`
	Items        []PipelineLatencyItem `json:"items,omitempty"`
}

type PipelineLatencyItem struct {
	Stage       string  `json:"stage"`
	Service     string  `json:"service"`
	Topic       string  `json:"topic,omitempty"`
	Subject     string  `json:"subject,omitempty"`
	P99MS       float64 `json:"p99_ms"`
	Samples     int64   `json:"samples"`
	ThresholdMS float64 `json:"threshold_ms,omitempty"`
	Status      string  `json:"status"`
}

type DailyMileageDiagnostics struct {
	URL                  string                              `json:"url,omitempty"`
	VehicleTotal         int64                               `json:"vehicle_total"`
	ActionableIssueTotal int64                               `json:"actionable_issue_total"`
	PipelineIssueTotal   int64                               `json:"pipeline_issue_total"`
	SourceDataIssueTotal int64                               `json:"source_data_issue_total"`
	Items                []DailyMileageDiagnosisReasonItem   `json:"items,omitempty"`
	Samples              []DailyMileageDiagnosisSample       `json:"samples,omitempty"`
	FieldStatus          *DailyMileageFieldStatusDiagnostics `json:"field_status,omitempty"`
}

type DailyMileageDiagnosisReasonItem struct {
	StatDate                  string `json:"stat_date"`
	Protocol                  string `json:"protocol"`
	Diagnosis                 string `json:"diagnosis"`
	Reason                    string `json:"reason"`
	Count                     int64  `json:"count"`
	Severity                  string `json:"severity,omitempty"`
	RecommendedOperatorAction string `json:"recommended_operator_action,omitempty"`
}

type DailyMileageFieldStatusDiagnostics struct {
	URL                  string                               `json:"url,omitempty"`
	SummaryTotal         int64                                `json:"summary_total"`
	VehicleTotal         int64                                `json:"vehicle_total"`
	ActionableIssueTotal int64                                `json:"actionable_issue_total"`
	PipelineIssueTotal   int64                                `json:"pipeline_issue_total"`
	SourceDataIssueTotal int64                                `json:"source_data_issue_total"`
	Items                []DailyMileageFieldStatusSummaryItem `json:"items,omitempty"`
}

type DailyMileageFieldStatusSummaryItem struct {
	StatDate                   string `json:"stat_date"`
	Protocol                   string `json:"protocol"`
	Diagnosis                  string `json:"diagnosis"`
	Reason                     string `json:"reason"`
	RealtimeMileageFieldStatus string `json:"realtime_mileage_field_status"`
	Count                      int64  `json:"count"`
	Severity                   string `json:"severity,omitempty"`
	FieldStatusSeverity        string `json:"field_status_severity,omitempty"`
	RecommendedOperatorAction  string `json:"recommended_operator_action,omitempty"`
	FieldStatusAction          string `json:"field_status_action,omitempty"`
}

type DailyMileageDiagnosisSample struct {
	VIN                        string   `json:"vin"`
	StatDate                   string   `json:"stat_date"`
	Protocol                   string   `json:"protocol"`
	Plate                      string   `json:"plate,omitempty"`
	PlatformName               string   `json:"platform_name,omitempty"`
	Peer                       string   `json:"peer,omitempty"`
	SnapshotEventTime          string   `json:"snapshot_event_time,omitempty"`
	LocationEventTime          string   `json:"location_event_time,omitempty"`
	SnapshotUpdatedAt          string   `json:"snapshot_updated_at,omitempty"`
	LocationUpdatedAt          string   `json:"location_updated_at,omitempty"`
	RealtimeTotalMileageKM     *float64 `json:"realtime_total_mileage_km,omitempty"`
	RealtimeTotalMileageAt     string   `json:"realtime_total_mileage_event_time,omitempty"`
	DailyMileageKM             *float64 `json:"daily_mileage_km,omitempty"`
	DailyLatestTotalMileageKM  *float64 `json:"daily_latest_total_mileage_km,omitempty"`
	SourceSampleCount          int64    `json:"source_sample_count"`
	OKSourceCount              int64    `json:"ok_source_count"`
	SelectableSourceCount      int64    `json:"selectable_source_count"`
	RealtimeFieldCount         int64    `json:"realtime_field_count,omitempty"`
	RealtimeSampleFields       []string `json:"realtime_sample_fields,omitempty"`
	RealtimeMileageFieldStatus string   `json:"realtime_mileage_field_status,omitempty"`
	RealtimeMileageEvidence    string   `json:"realtime_mileage_evidence,omitempty"`
	LatestStatEventTime        string   `json:"latest_stat_event_time,omitempty"`
	QualityStatuses            []string `json:"quality_statuses,omitempty"`
	RawFrameQueryPath          string   `json:"raw_frame_query_path,omitempty"`
	SourceSampleQueryPath      string   `json:"source_sample_query_path,omitempty"`
	Diagnosis                  string   `json:"diagnosis"`
	Reason                     string   `json:"reason"`
	Severity                   string   `json:"severity,omitempty"`
	RecommendedOperatorAction  string   `json:"recommended_operator_action,omitempty"`
}

type DailyMileageSourceQualityDiagnostics struct {
	URL                                     string                          `json:"url,omitempty"`
	SummaryTotal                            int64                           `json:"summary_total"`
	SourceCount                             int64                           `json:"source_count"`
	VehicleCount                            int64                           `json:"vehicle_count"`
	SampleCount                             int64                           `json:"sample_count"`
	OKSourceCount                           int64                           `json:"ok_source_count"`
	InvalidDeltaSourceCount                 int64                           `json:"invalid_delta_source_count"`
	NoBaselineSourceCount                   int64                           `json:"no_baseline_source_count"`
	FallbackAfterInvalidBaselineSourceCount int64                           `json:"fallback_after_invalid_baseline_source_count"`
	RealtimeLocationFallbackSourceCount     int64                           `json:"realtime_location_fallback_source_count"`
	Items                                   []DailyMileageSourceQualityItem `json:"items,omitempty"`
	ItemsTruncated                          bool                            `json:"items_truncated,omitempty"`
}

type DailyMileageSourceQualityItem struct {
	StatDate       string  `json:"stat_date"`
	Protocol       string  `json:"protocol"`
	QualityStatus  string  `json:"quality_status"`
	QualityReason  string  `json:"quality_reason,omitempty"`
	SourceCount    int64   `json:"source_count"`
	VehicleCount   int64   `json:"vehicle_count"`
	SelectedCount  int64   `json:"selected_count"`
	SampleCount    int64   `json:"sample_count"`
	DailyMileageKM float64 `json:"daily_mileage_km"`
}

type DailyMileageSourceSelectionDiagnostics struct {
	URL                    string                                `json:"url,omitempty"`
	SummaryTotal           int64                                 `json:"summary_total"`
	SourceCount            int64                                 `json:"source_count"`
	VehicleCount           int64                                 `json:"vehicle_count"`
	SampleCount            int64                                 `json:"sample_count"`
	SelectedSourceCount    int64                                 `json:"selected_source_count"`
	NotSelectedSourceCount int64                                 `json:"not_selected_source_count"`
	NotSelectedMileageKM   float64                               `json:"not_selected_mileage_km"`
	ExcludedSourceCount    int64                                 `json:"excluded_source_count"`
	UnmanagedSourceCount   int64                                 `json:"unmanaged_source_count"`
	UnknownKindSourceCount int64                                 `json:"unknown_kind_source_count"`
	Protocols              []DailyMileageSourceSelectionProtocol `json:"protocols,omitempty"`
	Items                  []DailyMileageSourceSelectionItem     `json:"items,omitempty"`
	Samples                []DailyMileageSourceSelectionSample   `json:"samples,omitempty"`
	ItemsTruncated         bool                                  `json:"items_truncated,omitempty"`
}

type DailyMileageSourceSelectionProtocol struct {
	Protocol               string  `json:"protocol"`
	SourceCount            int64   `json:"source_count"`
	VehicleCount           int64   `json:"vehicle_count"`
	SampleCount            int64   `json:"sample_count"`
	SelectedSourceCount    int64   `json:"selected_source_count"`
	NotSelectedSourceCount int64   `json:"not_selected_source_count"`
	NotSelectedMileageKM   float64 `json:"not_selected_mileage_km"`
	ExcludedSourceCount    int64   `json:"excluded_source_count"`
	UnmanagedSourceCount   int64   `json:"unmanaged_source_count"`
	UnknownKindSourceCount int64   `json:"unknown_kind_source_count"`
}

type DailyMileageSourceSelectionItem struct {
	StatDate        string  `json:"stat_date"`
	Protocol        string  `json:"protocol"`
	SelectionStatus string  `json:"selection_status"`
	SelectionReason string  `json:"selection_reason,omitempty"`
	SelectionAction string  `json:"selection_action,omitempty"`
	SourceCount     int64   `json:"source_count"`
	VehicleCount    int64   `json:"vehicle_count"`
	SelectedCount   int64   `json:"selected_count"`
	SampleCount     int64   `json:"sample_count"`
	DailyMileageKM  float64 `json:"daily_mileage_km"`
}

type DailyMileageSourceSelectionSample struct {
	VIN                     string                             `json:"vin"`
	StatDate                string                             `json:"stat_date"`
	Protocol                string                             `json:"protocol"`
	SourceKey               string                             `json:"source_key"`
	SourceIP                string                             `json:"source_ip,omitempty"`
	SourceEndpoint          string                             `json:"source_endpoint,omitempty"`
	Phone                   string                             `json:"phone,omitempty"`
	DeviceID                string                             `json:"device_id,omitempty"`
	PlatformName            string                             `json:"platform_name,omitempty"`
	SourceID                *int64                             `json:"source_id,omitempty"`
	SourceCode              string                             `json:"source_code,omitempty"`
	SourceKind              string                             `json:"source_kind,omitempty"`
	SourceEnabled           *bool                              `json:"source_enabled,omitempty"`
	TrustPriority           *int                               `json:"trust_priority,omitempty"`
	LatestTotalMileageKM    *float64                           `json:"latest_total_mileage_km,omitempty"`
	DailyMileageKM          float64                            `json:"daily_mileage_km"`
	SampleCount             int64                              `json:"sample_count"`
	QualityStatus           string                             `json:"quality_status,omitempty"`
	QualityReason           string                             `json:"quality_reason,omitempty"`
	SelectionStatus         string                             `json:"selection_status,omitempty"`
	SelectionReason         string                             `json:"selection_reason,omitempty"`
	SelectionAction         string                             `json:"selection_action,omitempty"`
	SelectedSource          *DailyMileageSourceSelectionSample `json:"selected_source,omitempty"`
	SelectedSourceQueryPath string                             `json:"selected_source_query_path,omitempty"`
	UpdatedAt               string                             `json:"updated_at,omitempty"`
}

type DataSourceDiagnostics struct {
	URL                               string                       `json:"url,omitempty"`
	MissingSourceCodeTotal            int64                        `json:"missing_source_code_total"`
	RecentMissingSourceCodeTotal      int64                        `json:"recent_missing_source_code_total"`
	UnknownSourceKindTotal            int64                        `json:"unknown_source_kind_total,omitempty"`
	RecentUnknownSourceKindTotal      int64                        `json:"recent_unknown_source_kind_total,omitempty"`
	PlatformKindCandidateTotal        int64                        `json:"platform_kind_candidate_total,omitempty"`
	RecentPlatformKindCandidateTotal  int64                        `json:"recent_platform_kind_candidate_total,omitempty"`
	RecentAgeThresholdSeconds         int64                        `json:"recent_age_threshold_seconds,omitempty"`
	ActionableCandidateTotal          int64                        `json:"actionable_candidate_total"`
	MappingIssueTotal                 int64                        `json:"mapping_issue_total,omitempty"`
	RecentMappingIssueTotal           int64                        `json:"recent_mapping_issue_total,omitempty"`
	ReasonCounts                      map[string]int64             `json:"reason_counts,omitempty"`
	MappingIssueReasonCounts          map[string]int64             `json:"mapping_issue_reason_counts,omitempty"`
	ReasonCountsTruncated             bool                         `json:"reason_counts_truncated,omitempty"`
	MappingIssueReasonCountsTruncated bool                         `json:"mapping_issue_reason_counts_truncated,omitempty"`
	Samples                           []DataSourceDiagnosticSample `json:"samples,omitempty"`
	MappingIssueSamples               []DataSourceDiagnosticSample `json:"mapping_issue_samples,omitempty"`
}

type DataSourceDiagnosticSample struct {
	ID                                int64    `json:"id"`
	Protocol                          string   `json:"protocol"`
	SourceIP                          string   `json:"source_ip"`
	LatestSourceEndpoint              string   `json:"latest_source_endpoint,omitempty"`
	PlatformName                      string   `json:"platform_name,omitempty"`
	SourceCode                        string   `json:"source_code,omitempty"`
	SourceKind                        string   `json:"source_kind"`
	FirstSeenAt                       string   `json:"first_seen_at,omitempty"`
	LatestSeenAt                      string   `json:"latest_seen_at,omitempty"`
	ActiveSpanSeconds                 int64    `json:"active_span_seconds"`
	LatestSeenAgeSeconds              int64    `json:"latest_seen_age_seconds"`
	RegistrationRows                  int64    `json:"registration_rows"`
	PhoneCount                        int64    `json:"phone_count"`
	UnknownVINRows                    int64    `json:"unknown_vin_rows"`
	IdentifierMatchedPhones           int64    `json:"identifier_matched_phones"`
	UnmappedPhoneCount                int64    `json:"unmapped_phone_count"`
	IdentifierMatchRatio              float64  `json:"identifier_match_ratio"`
	ConfiguredSourceCodeMatchedPhones int64    `json:"configured_source_code_matched_phones"`
	ConfiguredSourceCodePlatformName  string   `json:"configured_source_code_platform_name,omitempty"`
	ConfiguredSourceCodeConflict      bool     `json:"configured_source_code_conflict"`
	SourcePlatformNameMismatch        bool     `json:"source_platform_name_mismatch"`
	MatchedSourceCodeCount            int64    `json:"matched_source_code_count"`
	CandidateSourceCode               string   `json:"candidate_source_code,omitempty"`
	CandidatePlatformName             string   `json:"candidate_platform_name,omitempty"`
	MatchedSourceCodes                []string `json:"matched_source_codes,omitempty"`
	MatchedPlatformNames              []string `json:"matched_platform_names,omitempty"`
	SamplePhones                      []string `json:"sample_phones,omitempty"`
	Reason                            string   `json:"reason"`
	RecommendedOperatorAction         string   `json:"recommended_operator_action"`
	SuggestedSourceKind               string   `json:"suggested_source_kind"`
	SuggestionConfidence              string   `json:"suggestion_confidence"`
	SuggestionReason                  string   `json:"suggestion_reason"`
}

type JT808IdentityGapDiagnostics struct {
	URL           string                   `json:"url,omitempty"`
	Total         int64                    `json:"total"`
	RecentSeconds int64                    `json:"recent_seconds"`
	Samples       []JT808IdentityGapSample `json:"samples,omitempty"`
}

type JT808IdentityGapSample struct {
	Phone                     string `json:"phone"`
	DeviceID                  string `json:"device_id,omitempty"`
	Plate                     string `json:"plate,omitempty"`
	VIN                       string `json:"vin,omitempty"`
	SourceIP                  string `json:"source_ip,omitempty"`
	SourceEndpoint            string `json:"source_endpoint,omitempty"`
	SourceCode                string `json:"source_code,omitempty"`
	PlatformName              string `json:"platform_name,omitempty"`
	SourceKind                string `json:"source_kind,omitempty"`
	FirstRegisteredAt         string `json:"first_registered_at,omitempty"`
	LatestRegisteredAt        string `json:"latest_registered_at,omitempty"`
	LatestAuthenticatedAt     string `json:"latest_authenticated_at,omitempty"`
	LatestSeenAt              string `json:"latest_seen_at,omitempty"`
	LatestSeenAgeSeconds      int64  `json:"latest_seen_age_seconds"`
	Reason                    string `json:"reason"`
	RecommendedOperatorAction string `json:"recommended_operator_action"`
	RawFrameQueryPath         string `json:"raw_frame_query_path,omitempty"`
	DataSourceQueryPath       string `json:"data_source_query_path,omitempty"`
}

const (
	DailyMileageSeverityOK         = "ok"
	DailyMileageSeverityPipeline   = "pipeline"
	DailyMileageSeveritySourceData = "source_data"
)

func AnnotateDailyMileageDiagnostics(items []DailyMileageDiagnosisReasonItem) (annotated []DailyMileageDiagnosisReasonItem, pipelineIssues int64, sourceDataIssues int64) {
	annotated = make([]DailyMileageDiagnosisReasonItem, 0, len(items))
	for _, item := range items {
		severity := strings.ToLower(strings.TrimSpace(item.Severity))
		if severity != DailyMileageSeverityOK && severity != DailyMileageSeverityPipeline && severity != DailyMileageSeveritySourceData {
			severity = DailyMileageIssueSeverity(item)
		}
		item.Severity = severity
		if strings.TrimSpace(item.RecommendedOperatorAction) == "" {
			item.RecommendedOperatorAction = DailyMileageRecommendedOperatorAction(item.Diagnosis, item.Reason)
		}
		switch item.Severity {
		case DailyMileageSeverityPipeline:
			pipelineIssues += item.Count
		case DailyMileageSeveritySourceData:
			sourceDataIssues += item.Count
		}
		annotated = append(annotated, item)
	}
	return annotated, pipelineIssues, sourceDataIssues
}

func DailyMileageIssueSeverity(item DailyMileageDiagnosisReasonItem) string {
	diagnosis := strings.ToUpper(strings.TrimSpace(item.Diagnosis))
	reason := strings.ToLower(strings.TrimSpace(item.Reason))
	switch diagnosis {
	case "", "OK":
		return DailyMileageSeverityOK
	case "MISSING_DAILY":
		if reason == "source_samples_all_invalid" {
			return DailyMileageSeveritySourceData
		}
		return DailyMileageSeverityPipeline
	case "NO_SOURCE_SAMPLE":
		return DailyMileageSeverityPipeline
	case "NO_TOTAL_MILEAGE":
		switch reason {
		case "realtime_total_mileage_time_missing":
			return DailyMileageSeverityPipeline
		default:
			return DailyMileageSeveritySourceData
		}
	default:
		return DailyMileageSeverityPipeline
	}
}

func DailyMileageRecommendedOperatorAction(diagnosis, reason string) string {
	diagnosis = strings.ToUpper(strings.TrimSpace(diagnosis))
	reason = strings.ToLower(strings.TrimSpace(reason))
	switch diagnosis {
	case "", "OK":
		return "无需处理，车辆当日里程已生成"
	case "MISSING_DAILY":
		return "统计源样本已存在但最终日表缺失，优先排查 stat-writer 投影任务、MySQL 写入和 source 选举"
	case "NO_SOURCE_SAMPLE":
		return "实时位置已有有效总里程但统计字段样本缺失，优先排查字段流 topic、stat-writer 消费和协议字段映射"
	case "NO_TOTAL_MILEAGE":
		switch reason {
		case "realtime_total_mileage_time_missing":
			return "总里程值存在但事件时间缺失，优先排查解析器和 history/realtime 写入的时间字段"
		case "realtime_total_mileage_missing":
			return "当日实时数据缺少总里程字段，核对平台是否上报该字段以及协议字段映射是否覆盖"
		case "realtime_total_mileage_non_positive":
			return "终端上报总里程小于等于 0，不参与差值统计，需核对设备或平台里程字段来源"
		case "realtime_total_mileage_not_reported_on_stat_date":
			return "最新总里程不是统计日期内上报，等待当日含总里程数据或检查平台是否只上报部分字段"
		default:
			return "车辆当日在线但没有可用于里程统计的总里程，优先核对源平台报文字段和解析映射"
		}
	default:
		return "诊断状态未知，检查统计诊断 SQL 和上游字段流"
	}
}

type Thresholds struct {
	GatewayFrameP99MS                                  float64
	GatewayIdentityP99MS                               float64
	GatewayResponseRecentP99MS                         float64
	AsyncSinkP99MS                                     float64
	AsyncSinkQueueWaitRecentP99MS                      float64
	BridgeBatchP99MS                                   float64
	BridgeKafkaWriteP99MS                              float64
	BridgeKafkaE2EP99MS                                float64
	BridgeKafkaRecentE2EP99MS                          float64
	BridgeFetchWaitMaxMS                               float64
	BridgeWorkersMin                                   float64
	BridgeRouteErrorMax                                float64
	BridgeKafkaWriteErrorMax                           float64
	BridgeMessageMinReceived                           float64
	BridgeMessageMaxUnackedRatio                       float64
	FieldsProjectorWorkersPerProtocolMin               float64
	FieldsProjectorInvalidJSONMax                      float64
	FieldsProjectorWriteErrorMax                       float64
	FieldsProjectorBatchPendingMax                     float64
	FastWriterStageP99MS                               float64
	FastWriterRedisE2EP99MS                            float64
	FastWriterRedisRecentE2EP99MS                      float64
	FastWriterFetchWaitMaxMS                           float64
	FastWriterWorkersMin                               float64
	FastWriterInvalidJSONMax                           float64
	FastWriterFallbackErrorMax                         float64
	FastWriterDecoupledUpdateErrorMax                  float64
	FastWriterTDengineEnabledMax                       float64
	FastWriterMessageMinReceived                       float64
	FastWriterMessageMaxUnackedRatio                   float64
	HistoryFlushP99MS                                  float64
	HistoryWriteE2EP99MS                               float64
	HistoryWriteRecentE2EP99MS                         float64
	HistoryWorkersMin                                  float64
	HistoryInvalidJSONMax                              float64
	HistoryLocationErrorMax                            float64
	HistoryLocationErrorRecentSec                      float64
	HistoryLocationAccountingMaxRatio                  float64
	HistoryFallbackErrorMax                            float64
	HistoryRetryMax                                    float64
	HistoryRetryExhaustedMax                           float64
	RealtimeBatchP99MS                                 float64
	RealtimeStoreP99MS                                 float64
	RealtimeWorkersMin                                 float64
	RealtimeStoreErrorMax                              float64
	RealtimeInvalidJSONMax                             float64
	StatWriteP99MS                                     float64
	StatWriteE2EP99MS                                  float64
	StatWriteRecentE2EP99MS                            float64
	StatWorkersMin                                     float64
	IdentityWorkersMin                                 float64
	StatInvalidJSONMax                                 float64
	StatProtocolTopicMismatchMax                       float64
	MessageContractMismatchMax                         float64
	StatRetryMax                                       float64
	StatRetryExhaustedMax                              float64
	ProcessUptimeMinSec                                float64
	ProcessHeapAllocMaxBytes                           float64
	ProcessHeapSysMaxBytes                             float64
	ProcessGoroutinesMax                               float64
	KafkaLagMax                                        float64
	KafkaConsumerCommitErrorMax                        float64
	KafkaConsumerMessageMinReceived                    float64
	KafkaConsumerMaxUncommittedRatio                   float64
	DurableSpoolBacklogMax                             float64
	DurableSpoolOldestAgeMaxSec                        float64
	DurableSpoolErrorMax                               float64
	DurableSpoolReplayErrorMax                         float64
	DurableOutboxInflightMax                           float64
	DurableOutboxErrorMax                              float64
	AsyncSinkEnqueueErrorMax                           float64
	AsyncSinkPublishErrorMax                           float64
	AsyncSinkQueueDepthMax                             float64
	AsyncSinkQueueMaxUsageRatio                        float64
	FastWriterBatchPendingMax                          float64
	HistoryBatchPendingMax                             float64
	HistoryRowsPendingMax                              float64
	RealtimeBatchPendingMax                            float64
	RealtimeAsyncQueueDepthMax                         float64
	RealtimeAsyncQueueMaxUsageRatio                    float64
	RealtimeAsyncQueueDroppedMax                       float64
	HistoryParsedFieldMinFrames                        float64
	HistoryParsedFieldMaxMissingRatio                  float64
	MinHistogramSamples                                float64
	StatSampleMinWrites                                float64
	StatSampleMaxActionableSkipRatio                   float64
	StatSampleFutureAdjustmentMax                      float64
	StatSourceMinSamples                               float64
	StatSourceMaxMissingRatio                          float64
	StatProjectionMinSampleWrites                      float64
	StatBatchPendingMax                                float64
	ConsumerRetryPendingMax                            float64
	StatCacheEntriesMax                                float64
	RealtimeThrottleMinSamples                         float64
	RealtimeThrottleCacheEntriesMax                    float64
	RealtimePlateCacheEntriesMax                       float64
	FastWriterRedisEnvelopeMinSeen                     float64
	FastWriterRedisEnvelopeMaxBadRatio                 float64
	FastWriterRedisFieldMinSeen                        float64
	FastWriterRedisFieldMaxStaleRatio                  float64
	GatewayFieldMinRaw                                 float64
	GatewayFieldMaxMissingRatio                        float64
	GatewayFieldMinCount                               float64
	GatewayBridgeMinPublish                            float64
	GatewayFastWriterMinRawPublish                     float64
	GatewayParseMinFrames                              float64
	GatewayParseMaxBadRatio                            float64
	GatewayResponseMinSamples                          float64
	GatewayResponseMaxErrorRatio                       float64
	GatewayIdentityMinSamples                          float64
	GatewayIdentityMaxUnresolvedRatio                  float64
	GatewayIdentityCacheEntriesMax                     float64
	GatewayIdentitySnapshotRequired                    float64
	GatewayIdentitySnapshotMaxAgeSec                   float64
	GatewayIdentityLocationTouchFailureMax             float64
	GatewayIdentityRegistrationWriteQueueDepthMax      float64
	GatewayIdentityRegistrationWriteQueueMaxUsageRatio float64
	GatewayIdentityRegistrationWriteErrorMax           float64
	GatewayIdentityStaleMinSamples                     float64
	GatewayIdentityMaxStaleCacheRatio                  float64
	StatTopicMinBridge                                 float64
	RawFanoutMinBridge                                 float64
	LastActivityStaleSec                               float64
	RequiredConsumerTopics                             map[string][]string
}

func DefaultThresholds() Thresholds {
	return Thresholds{
		GatewayFrameP99MS:                                  250,
		GatewayIdentityP99MS:                               100,
		GatewayResponseRecentP99MS:                         100,
		AsyncSinkP99MS:                                     100,
		AsyncSinkQueueWaitRecentP99MS:                      100,
		BridgeBatchP99MS:                                   5000,
		BridgeKafkaWriteP99MS:                              500,
		BridgeKafkaE2EP99MS:                                0,
		BridgeKafkaRecentE2EP99MS:                          300,
		BridgeFetchWaitMaxMS:                               50,
		BridgeWorkersMin:                                   2,
		BridgeRouteErrorMax:                                0,
		BridgeKafkaWriteErrorMax:                           0,
		BridgeMessageMinReceived:                           5000,
		BridgeMessageMaxUnackedRatio:                       0.20,
		FieldsProjectorWorkersPerProtocolMin:               1,
		FieldsProjectorInvalidJSONMax:                      0,
		FieldsProjectorWriteErrorMax:                       0,
		FieldsProjectorBatchPendingMax:                     1000,
		FastWriterStageP99MS:                               100,
		FastWriterRedisE2EP99MS:                            0,
		FastWriterRedisRecentE2EP99MS:                      100,
		FastWriterFetchWaitMaxMS:                           50,
		FastWriterWorkersMin:                               4,
		FastWriterInvalidJSONMax:                           0,
		FastWriterFallbackErrorMax:                         0,
		FastWriterDecoupledUpdateErrorMax:                  0,
		FastWriterTDengineEnabledMax:                       0,
		FastWriterMessageMinReceived:                       5000,
		FastWriterMessageMaxUnackedRatio:                   0.20,
		HistoryFlushP99MS:                                  1000,
		HistoryWriteE2EP99MS:                               0,
		HistoryWriteRecentE2EP99MS:                         2000,
		HistoryWorkersMin:                                  3,
		HistoryInvalidJSONMax:                              0,
		HistoryLocationErrorMax:                            0,
		HistoryLocationErrorRecentSec:                      300,
		HistoryLocationAccountingMaxRatio:                  0.02,
		HistoryFallbackErrorMax:                            0,
		HistoryRetryMax:                                    100,
		HistoryRetryExhaustedMax:                           0,
		RealtimeBatchP99MS:                                 500,
		RealtimeStoreP99MS:                                 100,
		RealtimeWorkersMin:                                 3,
		RealtimeStoreErrorMax:                              0,
		RealtimeInvalidJSONMax:                             0,
		StatWriteP99MS:                                     100,
		StatWriteE2EP99MS:                                  0,
		StatWriteRecentE2EP99MS:                            1000,
		StatWorkersMin:                                     3,
		IdentityWorkersMin:                                 3,
		StatInvalidJSONMax:                                 0,
		StatProtocolTopicMismatchMax:                       0,
		MessageContractMismatchMax:                         0,
		StatRetryMax:                                       100,
		StatRetryExhaustedMax:                              0,
		ProcessUptimeMinSec:                                0,
		ProcessHeapAllocMaxBytes:                           0,
		ProcessHeapSysMaxBytes:                             0,
		ProcessGoroutinesMax:                               0,
		KafkaLagMax:                                        1000,
		KafkaConsumerCommitErrorMax:                        0,
		KafkaConsumerMessageMinReceived:                    100,
		KafkaConsumerMaxUncommittedRatio:                   0.20,
		DurableSpoolBacklogMax:                             10000,
		DurableSpoolOldestAgeMaxSec:                        300,
		DurableSpoolErrorMax:                               0,
		DurableSpoolReplayErrorMax:                         0,
		DurableOutboxInflightMax:                           10000,
		DurableOutboxErrorMax:                              0,
		AsyncSinkEnqueueErrorMax:                           0,
		AsyncSinkPublishErrorMax:                           0,
		AsyncSinkQueueDepthMax:                             10000,
		AsyncSinkQueueMaxUsageRatio:                        0.80,
		FastWriterBatchPendingMax:                          1000,
		HistoryBatchPendingMax:                             1000,
		HistoryRowsPendingMax:                              5000,
		RealtimeBatchPendingMax:                            1000,
		RealtimeAsyncQueueDepthMax:                         10000,
		RealtimeAsyncQueueMaxUsageRatio:                    0.80,
		RealtimeAsyncQueueDroppedMax:                       0,
		HistoryParsedFieldMinFrames:                        100,
		HistoryParsedFieldMaxMissingRatio:                  0.05,
		MinHistogramSamples:                                100,
		StatSampleMinWrites:                                100,
		StatSampleMaxActionableSkipRatio:                   0.60,
		StatSampleFutureAdjustmentMax:                      0,
		StatSourceMinSamples:                               100,
		StatSourceMaxMissingRatio:                          0.60,
		StatProjectionMinSampleWrites:                      100,
		StatBatchPendingMax:                                1000,
		ConsumerRetryPendingMax:                            0,
		StatCacheEntriesMax:                                1000000,
		RealtimeThrottleMinSamples:                         100,
		RealtimeThrottleCacheEntriesMax:                    1000000,
		RealtimePlateCacheEntriesMax:                       200000,
		FastWriterRedisEnvelopeMinSeen:                     100,
		FastWriterRedisEnvelopeMaxBadRatio:                 0.50,
		FastWriterRedisFieldMinSeen:                        1000,
		FastWriterRedisFieldMaxStaleRatio:                  0.20,
		GatewayFieldMinRaw:                                 100,
		GatewayFieldMaxMissingRatio:                        0.20,
		GatewayFieldMinCount:                               1,
		GatewayBridgeMinPublish:                            1000,
		GatewayFastWriterMinRawPublish:                     1000,
		GatewayParseMinFrames:                              100,
		GatewayParseMaxBadRatio:                            0.05,
		GatewayResponseMinSamples:                          100,
		GatewayResponseMaxErrorRatio:                       0.05,
		GatewayIdentityMinSamples:                          100,
		GatewayIdentityMaxUnresolvedRatio:                  0.20,
		GatewayIdentityCacheEntriesMax:                     300000,
		GatewayIdentitySnapshotRequired:                    1,
		GatewayIdentitySnapshotMaxAgeSec:                   180,
		GatewayIdentityLocationTouchFailureMax:             0,
		GatewayIdentityRegistrationWriteQueueDepthMax:      10000,
		GatewayIdentityRegistrationWriteQueueMaxUsageRatio: 0.80,
		GatewayIdentityRegistrationWriteErrorMax:           0,
		GatewayIdentityStaleMinSamples:                     100,
		GatewayIdentityMaxStaleCacheRatio:                  0.05,
		StatTopicMinBridge:                                 1000,
		RawFanoutMinBridge:                                 1000,
		LastActivityStaleSec:                               300,
	}
}

func DefaultRequiredConsumerTopics() map[string][]string {
	return map[string][]string{
		"fields-projector": {
			topics.RawGB32960,
			topics.RawJT808,
			topics.RawYutongMQTT,
		},
		"history": {
			topics.RawGB32960,
			topics.RawJT808,
			topics.RawYutongMQTT,
		},
		"realtime": {
			topics.RawGB32960,
			topics.RawJT808,
			topics.RawYutongMQTT,
		},
		"stat": {
			topics.FieldsGB32960,
			topics.FieldsJT808,
			topics.FieldsYutongMQTT,
		},
		"identity": {
			topics.RawJT808,
		},
	}
}

func Evaluate(metricsByService map[string]string) Report {
	return EvaluateWithThresholds(metricsByService, DefaultThresholds())
}

func EvaluateWithThresholds(metricsByService map[string]string, thresholds Thresholds) Report {
	report := Report{
		Status:    StatusOK,
		CheckedAt: time.Now().UTC(),
		Services:  make([]ServiceCheck, 0, len(metricsByService)),
	}
	parsedByService := make(map[string]parsedMetricSet, len(metricsByService))
	var names []string
	for name := range metricsByService {
		names = append(names, name)
	}
	sort.Strings(names)
	for _, name := range names {
		parsed := parseMetricSet(metricsByService[name])
		parsedByService[name] = parsed
		metrics := parsed.totals
		addLastActivityAges(metrics, report.CheckedAt)
		report.Services = append(report.Services, ServiceCheck{Name: name, Metrics: metrics})
		report.Totals.ActiveConnections += int64(metrics["vehicle_gateway_active_connections"])
		report.Totals.KafkaLag += metrics["vehicle_history_kafka_lag"]
		report.Totals.KafkaLag += metrics["vehicle_stat_kafka_lag"]
		report.Totals.KafkaLag += metrics["vehicle_realtime_kafka_lag"]
		report.Totals.KafkaLag += metrics["vehicle_identity_writer_kafka_lag"]
		report.Totals.BridgeConsumerPending += metrics["vehicle_bridge_nats_consumer_pending"]
		report.Totals.BridgeAckPending += metrics["vehicle_bridge_nats_consumer_ack_pending"]
		report.Totals.BridgeBatchPendingMessages += metrics["vehicle_bridge_batch_pending_messages"]
		report.Totals.FieldsProjectorKafkaLag += metrics["vehicle_fields_projector_kafka_lag"]
		report.Totals.FieldsProjectorBatchPending += metrics["vehicle_fields_projector_batch_pending_messages"]
		report.Totals.FieldsProjectorRetryPending += metrics["vehicle_fields_projector_retry_pending_messages"]
		report.Totals.FastWriterConsumerPending += metrics["vehicle_fast_writer_nats_consumer_pending"]
		report.Totals.FastWriterAckPending += metrics["vehicle_fast_writer_nats_consumer_ack_pending"]
		report.Totals.FastWriterBatchPending += metrics["vehicle_fast_writer_batch_pending_messages"]
		report.Totals.HistoryBatchPending += metrics["vehicle_history_batch_pending_messages"]
		report.Totals.HistoryRowsPending += metrics["vehicle_history_batch_pending_rows"]
		report.Totals.HistoryRetryPending += metrics["vehicle_history_retry_pending_messages"]
		report.Totals.RealtimeBatchPending += metrics["vehicle_realtime_batch_pending_messages"]
		report.Totals.RealtimeAsyncQueuePending += metrics["vehicle_realtime_async_queue_depth"]
		report.Totals.RealtimeRetryPending += metrics["vehicle_realtime_retry_pending_messages"]
		report.Totals.StatRetryPending += metrics["vehicle_stat_retry_pending_messages"]
		report.Totals.IdentityBatchPending += metrics["vehicle_identity_writer_batch_pending_messages"]
		report.Totals.IdentityFactsPending += metrics["vehicle_identity_writer_batch_pending_facts"]
		report.Totals.IdentityRetryPending += metrics["vehicle_identity_writer_retry_pending_messages"]
		report.Totals.JT808RegistrationWriteQueuePending += gatewayIdentityCacheMetric(parsed, "registration_write_queue", "vehicle_gateway_identity_cache_entries")
		report.Totals.JT808RegistrationWriteErrors += jt808RegistrationWriteErrors(parsed)
		report.Findings = append(report.Findings, findingsForService(name, metrics)...)
		report.Findings = append(report.Findings, findingsForMetrics(name, metrics, thresholds)...)
		report.Findings = append(report.Findings, findingsForRuntimeConfig(name, parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForAsyncSinkQueueUsage(name, parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForAsyncSinkErrors(name, parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForDurableSpool(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForDurableOutbox(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForHistograms(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForKafkaConsumerCompletion(name, parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForMessageContractMismatches(name, parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForBridgeRouteErrors(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForBridgeKafkaWriteErrors(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForBridgeMessageCompletion(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForFieldsProjector(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForHistoryInvalidJSON(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForHistoryLocationErrors(parsed, thresholds, report.CheckedAt)...)
		report.Findings = append(report.Findings, findingsForHistoryLocationAccounting(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForHistoryFallbackErrors(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForWriteRetries(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForHistoryParsedFields(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForStatInvalidJSON(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForStatProtocolTopicMismatches(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForStatSamples(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForStatSources(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForStatProjections(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForStatCache(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForFastWriterRedisEnvelopes(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForFastWriterRedisFields(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForFastWriterInvalidJSON(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForFastWriterFallbackErrors(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForFastWriterDecoupledUpdateErrors(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForFastWriterMessageCompletion(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForRealtimeInvalidJSON(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForRealtimeStoreErrors(parsed, thresholds)...)
		report.Findings = append(report.Findings, findingsForRealtimeAsyncQueue(parsed, thresholds)...)
	}
	report.Findings = append(report.Findings, findingsForGatewayIdentityQuality(parsedByService["gateway"], thresholds)...)
	report.Findings = append(report.Findings, findingsForGatewayIdentityCache(parsedByService["gateway"], thresholds)...)
	report.Findings = append(report.Findings, findingsForGatewayIdentitySnapshot(parsedByService["gateway"], thresholds, report.CheckedAt)...)
	report.Findings = append(report.Findings, findingsForGatewayIdentityLocationTouchFailures(parsedByService["gateway"], thresholds)...)
	report.Findings = append(report.Findings, findingsForGatewayRegistrationWriteQueue(parsedByService["gateway"], thresholds)...)
	report.Findings = append(report.Findings, findingsForGatewayRegistrationWriteErrors(parsedByService["gateway"], thresholds)...)
	report.Findings = append(report.Findings, findingsForGatewayIdentityStaleCache(parsedByService["gateway"], thresholds)...)
	report.Findings = append(report.Findings, findingsForIdentityWriterOwnership(parsedByService["gateway"], parsedByService["identity"])...)
	report.GatewayFields = buildGatewayFieldsDiagnostics(parsedByService["gateway"], thresholds)
	report.IdentityResolution = buildIdentityResolutionDiagnostics(parsedByService["gateway"])
	report.ProtocolFlow = buildProtocolFlowDiagnostics(parsedByService, thresholds)
	report.PipelineLatency = buildPipelineLatencyDiagnostics(parsedByService, thresholds)
	report.Findings = append(report.Findings, findingsForGatewayParseQuality(parsedByService["gateway"], thresholds)...)
	report.Findings = append(report.Findings, findingsForGatewayResponseQuality(parsedByService["gateway"], thresholds)...)
	report.Findings = append(report.Findings, findingsForRealtimeThrottle(parsedByService["realtime"], thresholds)...)
	report.Findings = append(report.Findings, findingsForRealtimeThrottleCache(parsedByService["realtime"], thresholds)...)
	report.Findings = append(report.Findings, findingsForRealtimePlateCache(parsedByService["realtime"], thresholds)...)
	report.Findings = append(report.Findings, findingsForRequiredConsumerTopics(parsedByService, thresholds)...)
	report.Findings = append(report.Findings, findingsForTopicFlow(parsedByService, thresholds)...)
	report.Findings = append(report.Findings, findingsForLastActivity(parsedByService, thresholds, report.CheckedAt)...)
	if len(report.Findings) > 0 {
		report.Status = StatusDegraded
	}
	return report
}

func findingsForService(name string, metrics map[string]float64) []string {
	if metrics["vehicle_service_info"] <= 0 {
		return []string{fmt.Sprintf("%s service info metric missing", name)}
	}
	if requiresKafkaConsumerInfo(name) && metrics["vehicle_kafka_consumer_info"] <= 0 {
		return []string{fmt.Sprintf("%s kafka consumer info metric missing", name)}
	}
	return nil
}

func requiresKafkaConsumerInfo(name string) bool {
	switch name {
	case "history", "stat", "realtime", "fields-projector":
		return true
	default:
		return false
	}
}

func findingsForMetrics(service string, metrics map[string]float64, thresholds Thresholds) []string {
	var findings []string
	if thresholds.ProcessUptimeMinSec > 0 {
		value, ok := metrics["vehicle_process_uptime_seconds"]
		if !ok {
			findings = append(findings, fmt.Sprintf("%s process uptime metric missing", service))
		} else if value < thresholds.ProcessUptimeMinSec {
			findings = append(findings, fmt.Sprintf("%s process uptime %.0fs below %.0fs", service, value, thresholds.ProcessUptimeMinSec))
		}
	}
	if thresholds.ProcessHeapAllocMaxBytes > 0 {
		value, ok := metrics["vehicle_process_heap_alloc_bytes"]
		if !ok {
			findings = append(findings, fmt.Sprintf("%s process heap alloc metric missing", service))
		} else if value > thresholds.ProcessHeapAllocMaxBytes {
			findings = append(findings, fmt.Sprintf("%s process heap alloc %.0f bytes exceeds %.0f", service, value, thresholds.ProcessHeapAllocMaxBytes))
		}
	}
	if thresholds.ProcessHeapSysMaxBytes > 0 {
		value, ok := metrics["vehicle_process_heap_sys_bytes"]
		if !ok {
			findings = append(findings, fmt.Sprintf("%s process heap sys metric missing", service))
		} else if value > thresholds.ProcessHeapSysMaxBytes {
			findings = append(findings, fmt.Sprintf("%s process heap sys %.0f bytes exceeds %.0f", service, value, thresholds.ProcessHeapSysMaxBytes))
		}
	}
	if thresholds.ProcessGoroutinesMax > 0 {
		value, ok := metrics["vehicle_process_goroutines"]
		if !ok {
			findings = append(findings, fmt.Sprintf("%s process goroutines metric missing", service))
		} else if value > thresholds.ProcessGoroutinesMax {
			findings = append(findings, fmt.Sprintf("%s process goroutines %.0f exceeds %.0f", service, value, thresholds.ProcessGoroutinesMax))
		}
	}
	if value := metrics["vehicle_gateway_connection_rejections_total"]; value > 0 {
		findings = append(findings, fmt.Sprintf("gateway connection rejections %.0f", value))
	}
	if thresholds.AsyncSinkQueueDepthMax > 0 {
		if value := metrics["vehicle_async_sink_queue_depth"]; value > thresholds.AsyncSinkQueueDepthMax {
			findings = append(findings, fmt.Sprintf("async sink queue depth %.0f exceeds %.0f", value, thresholds.AsyncSinkQueueDepthMax))
		}
	}
	if value := metrics["vehicle_bridge_nats_consumer_ack_pending"]; value > 100 {
		findings = append(findings, fmt.Sprintf("bridge ack pending %.0f exceeds 100", value))
	}
	if value := metrics["vehicle_bridge_nats_consumer_pending"]; value > 10_000 {
		findings = append(findings, fmt.Sprintf("bridge consumer pending %.0f exceeds 10000", value))
	}
	if value := metrics["vehicle_bridge_batch_pending_messages"]; value > 1000 {
		findings = append(findings, fmt.Sprintf("bridge batch pending %.0f exceeds 1000", value))
	}
	if value := metrics["vehicle_fast_writer_nats_consumer_ack_pending"]; value > 100 {
		findings = append(findings, fmt.Sprintf("fast writer ack pending %.0f exceeds 100", value))
	}
	if value := metrics["vehicle_fast_writer_nats_consumer_pending"]; value > 10_000 {
		findings = append(findings, fmt.Sprintf("fast writer consumer pending %.0f exceeds 10000", value))
	}
	if value := metrics["vehicle_fast_writer_batch_pending_messages"]; thresholds.FastWriterBatchPendingMax > 0 && value > thresholds.FastWriterBatchPendingMax {
		findings = append(findings, fmt.Sprintf("fast writer batch pending %.0f exceeds %.0f", value, thresholds.FastWriterBatchPendingMax))
	}
	if value := metrics["vehicle_fast_writer_tdengine_enabled"]; thresholds.FastWriterTDengineEnabledMax >= 0 && value > thresholds.FastWriterTDengineEnabledMax {
		findings = append(findings, fmt.Sprintf("fast writer tdengine enabled %.0f exceeds %.0f", value, thresholds.FastWriterTDengineEnabledMax))
	}
	if value := metrics["vehicle_realtime_redis_projector_enabled"]; value > 0 {
		findings = append(findings, fmt.Sprintf("realtime redis projector enabled %.0f", value))
	}
	if value := metrics["vehicle_history_batch_pending_messages"]; thresholds.HistoryBatchPendingMax > 0 && value > thresholds.HistoryBatchPendingMax {
		findings = append(findings, fmt.Sprintf("history batch pending %.0f exceeds %.0f", value, thresholds.HistoryBatchPendingMax))
	}
	if value := metrics["vehicle_history_batch_pending_rows"]; thresholds.HistoryRowsPendingMax > 0 && value > thresholds.HistoryRowsPendingMax {
		findings = append(findings, fmt.Sprintf("history rows pending %.0f exceeds %.0f", value, thresholds.HistoryRowsPendingMax))
	}
	if value := metrics["vehicle_stat_batch_pending_messages"]; thresholds.StatBatchPendingMax > 0 && value > thresholds.StatBatchPendingMax {
		findings = append(findings, fmt.Sprintf("stat batch pending %.0f exceeds %.0f", value, thresholds.StatBatchPendingMax))
	}
	if value := metrics["vehicle_realtime_batch_pending_messages"]; thresholds.RealtimeBatchPendingMax > 0 && value > thresholds.RealtimeBatchPendingMax {
		findings = append(findings, fmt.Sprintf("realtime batch pending %.0f exceeds %.0f", value, thresholds.RealtimeBatchPendingMax))
	}
	if value := metrics["vehicle_realtime_async_queue_depth"]; thresholds.RealtimeAsyncQueueDepthMax > 0 && value > thresholds.RealtimeAsyncQueueDepthMax {
		findings = append(findings, fmt.Sprintf("realtime async queue depth %.0f exceeds %.0f", value, thresholds.RealtimeAsyncQueueDepthMax))
	}
	for _, retry := range []struct {
		name   string
		metric string
	}{
		{name: "history", metric: "vehicle_history_retry_pending_messages"},
		{name: "realtime", metric: "vehicle_realtime_retry_pending_messages"},
		{name: "stat", metric: "vehicle_stat_retry_pending_messages"},
		{name: "identity", metric: "vehicle_identity_writer_retry_pending_messages"},
		{name: "fields projector", metric: "vehicle_fields_projector_retry_pending_messages"},
	} {
		if value := metrics[retry.metric]; thresholds.ConsumerRetryPendingMax >= 0 && value > thresholds.ConsumerRetryPendingMax {
			findings = append(findings, fmt.Sprintf("%s retry pending %.0f exceeds %.0f", retry.name, value, thresholds.ConsumerRetryPendingMax))
		}
	}
	if value := metrics["vehicle_history_kafka_lag"] + metrics["vehicle_stat_kafka_lag"] + metrics["vehicle_realtime_kafka_lag"] + metrics["vehicle_identity_writer_kafka_lag"] + metrics["vehicle_fields_projector_kafka_lag"]; thresholds.KafkaLagMax > 0 && value > thresholds.KafkaLagMax {
		findings = append(findings, fmt.Sprintf("kafka lag %.0f exceeds %.0f", value, thresholds.KafkaLagMax))
	}
	if value := metrics["vehicle_identity_writer_batch_pending_messages"]; thresholds.RealtimeBatchPendingMax > 0 && value > thresholds.RealtimeBatchPendingMax {
		findings = append(findings, fmt.Sprintf("identity batch pending %.0f exceeds %.0f", value, thresholds.RealtimeBatchPendingMax))
	}
	if value := metrics["vehicle_fields_projector_batch_pending_messages"]; thresholds.FieldsProjectorBatchPendingMax > 0 && value > thresholds.FieldsProjectorBatchPendingMax {
		findings = append(findings, fmt.Sprintf("fields projector batch pending %.0f exceeds %.0f", value, thresholds.FieldsProjectorBatchPendingMax))
	}
	return findings
}

func findingsForFieldsProjector(parsed parsedMetricSet, thresholds Thresholds) []string {
	var invalidJSON float64
	var writeErrors float64
	for _, sample := range parsed.samples {
		switch {
		case sample.name == "vehicle_fields_projector_kafka_messages_total" && sample.labels["status"] == "invalid_json":
			invalidJSON += sample.value
		case sample.name == "vehicle_fields_projector_kafka_writes_total" && sample.labels["status"] == "error":
			writeErrors += sample.value
		}
	}
	var findings []string
	if thresholds.FieldsProjectorInvalidJSONMax >= 0 && invalidJSON > thresholds.FieldsProjectorInvalidJSONMax {
		findings = append(findings, fmt.Sprintf("fields projector invalid json %.0f exceeds %.0f", invalidJSON, thresholds.FieldsProjectorInvalidJSONMax))
	}
	if thresholds.FieldsProjectorWriteErrorMax >= 0 && writeErrors > thresholds.FieldsProjectorWriteErrorMax {
		findings = append(findings, fmt.Sprintf("fields projector kafka write errors %.0f exceeds %.0f", writeErrors, thresholds.FieldsProjectorWriteErrorMax))
	}
	return findings
}

func findingsForRuntimeConfig(service string, parsed parsedMetricSet, thresholds Thresholds) []string {
	var findings []string
	if service == "bridge" || hasServiceInfo(parsed, "nats-kafka-bridge") {
		findings = append(findings, configMaxFinding(parsed, "vehicle_bridge_config", "fetch_wait_ms", "bridge fetch wait", "ms", thresholds.BridgeFetchWaitMaxMS)...)
		findings = append(findings, configMinFinding(parsed, "vehicle_bridge_config", "workers", "bridge workers", "", thresholds.BridgeWorkersMin)...)
	}
	if service == "fields-projector" || hasServiceInfo(parsed, "vehicle-fields-projector") {
		findings = append(findings, configMinFinding(parsed, "vehicle_fields_projector_config", "workers_per_protocol", "fields projector workers per protocol", "", thresholds.FieldsProjectorWorkersPerProtocolMin)...)
	}
	if service == "fast-writer" || hasServiceInfo(parsed, "nats-fast-writer") {
		findings = append(findings, configMaxFinding(parsed, "vehicle_fast_writer_config", "fetch_wait_ms", "fast writer fetch wait", "ms", thresholds.FastWriterFetchWaitMaxMS)...)
		findings = append(findings, configMinFinding(parsed, "vehicle_fast_writer_config", "workers", "fast writer workers", "", thresholds.FastWriterWorkersMin)...)
	}
	if service == "history" || hasServiceInfo(parsed, "vehicle-history-writer") {
		findings = append(findings, configMinFinding(parsed, "vehicle_history_config", "workers", "history workers", "", thresholds.HistoryWorkersMin)...)
	}
	if service == "realtime" || hasServiceInfo(parsed, "vehicle-realtime-writer") {
		findings = append(findings, configMinFinding(parsed, "vehicle_realtime_config", "workers", "realtime workers", "", thresholds.RealtimeWorkersMin)...)
	}
	if service == "stat" || hasServiceInfo(parsed, "vehicle-stat-writer") {
		findings = append(findings, configMinFinding(parsed, "vehicle_stat_config", "workers", "stat workers", "", thresholds.StatWorkersMin)...)
	}
	if service == "identity" || hasServiceInfo(parsed, "vehicle-identity-writer") {
		findings = append(findings, configMinFinding(parsed, "vehicle_identity_writer_config", "workers", "identity workers", "", thresholds.IdentityWorkersMin)...)
	}
	sort.Strings(findings)
	return findings
}

func configMaxFinding(parsed parsedMetricSet, metricName string, setting string, label string, unit string, threshold float64) []string {
	if threshold <= 0 {
		return nil
	}
	value, ok := maxConfigGaugeValue(parsed, metricName, setting)
	if !ok {
		return []string{fmt.Sprintf("%s config metric missing (%s setting=%s)", label, metricName, setting)}
	}
	if value <= threshold {
		return nil
	}
	return []string{fmt.Sprintf("%s %.0f%s exceeds %.0f%s", label, value, unit, threshold, unit)}
}

func configMinFinding(parsed parsedMetricSet, metricName string, setting string, label string, unit string, threshold float64) []string {
	if threshold <= 0 {
		return nil
	}
	value, ok := minConfigGaugeValue(parsed, metricName, setting)
	if !ok {
		return []string{fmt.Sprintf("%s config metric missing (%s setting=%s)", label, metricName, setting)}
	}
	if value >= threshold {
		return nil
	}
	return []string{fmt.Sprintf("%s %.0f%s below %.0f%s", label, value, unit, threshold, unit)}
}

func hasServiceInfo(parsed parsedMetricSet, service string) bool {
	service = strings.TrimSpace(service)
	if service == "" {
		return false
	}
	for _, sample := range parsed.samples {
		if sample.name == "vehicle_service_info" && sample.labels["service"] == service && sample.value > 0 {
			return true
		}
	}
	return false
}

func maxConfigGaugeValue(parsed parsedMetricSet, metricName string, setting string) (float64, bool) {
	var out float64
	found := false
	for _, sample := range parsed.samples {
		if sample.name != metricName || sample.labels["setting"] != setting {
			continue
		}
		if !found || sample.value > out {
			out = sample.value
			found = true
		}
	}
	return out, found
}

func minConfigGaugeValue(parsed parsedMetricSet, metricName string, setting string) (float64, bool) {
	var out float64
	found := false
	for _, sample := range parsed.samples {
		if sample.name != metricName || sample.labels["setting"] != setting {
			continue
		}
		if !found || sample.value < out {
			out = sample.value
			found = true
		}
	}
	return out, found
}

func findingsForAsyncSinkQueueUsage(service string, parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.AsyncSinkQueueMaxUsageRatio <= 0 {
		return nil
	}
	type queueSample struct {
		labels   map[string]string
		depth    float64
		capacity float64
	}
	queues := map[string]*queueSample{}
	for _, sample := range parsed.samples {
		switch sample.name {
		case "vehicle_async_sink_queue_depth":
			key := metricLabelsKey(sample.labels)
			item := queues[key]
			if item == nil {
				item = &queueSample{labels: sample.labels}
				queues[key] = item
			}
			item.depth += sample.value
		case "vehicle_async_sink_queue_capacity":
			key := metricLabelsKey(sample.labels)
			item := queues[key]
			if item == nil {
				item = &queueSample{labels: sample.labels}
				queues[key] = item
			}
			item.capacity += sample.value
		}
	}
	var keys []string
	for key := range queues {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	var findings []string
	for _, key := range keys {
		item := queues[key]
		if item.capacity <= 0 || item.depth <= 0 {
			continue
		}
		ratio := item.depth / item.capacity
		if ratio <= thresholds.AsyncSinkQueueMaxUsageRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"%s async sink queue usage %.2f exceeds %.2f (%s, depth %.0f, capacity %.0f)",
			service,
			ratio,
			thresholds.AsyncSinkQueueMaxUsageRatio,
			key,
			item.depth,
			item.capacity,
		))
	}
	return findings
}

func findingsForAsyncSinkErrors(service string, parsed parsedMetricSet, thresholds Thresholds) []string {
	var findings []string
	if thresholds.AsyncSinkEnqueueErrorMax >= 0 {
		for _, sample := range parsed.samples {
			if sample.name != "vehicle_async_sink_enqueue_total" {
				continue
			}
			status := strings.TrimSpace(sample.labels["status"])
			if status == "" || status == "queued" {
				continue
			}
			if sample.value <= thresholds.AsyncSinkEnqueueErrorMax {
				continue
			}
			sink := strings.TrimSpace(sample.labels["sink"])
			if sink == "" {
				sink = "unknown"
			}
			kind := strings.TrimSpace(sample.labels["kind"])
			if kind == "" {
				kind = "unknown"
			}
			findings = append(findings, fmt.Sprintf(
				"%s async sink enqueue %s %.0f exceeds %.0f for sink %s kind %s",
				service,
				status,
				sample.value,
				thresholds.AsyncSinkEnqueueErrorMax,
				sink,
				kind,
			))
		}
	}
	if thresholds.AsyncSinkPublishErrorMax >= 0 {
		for _, sample := range parsed.samples {
			if sample.name != "vehicle_async_sink_publish_total" || sample.labels["status"] != "error" {
				continue
			}
			if sample.value <= thresholds.AsyncSinkPublishErrorMax {
				continue
			}
			sink := strings.TrimSpace(sample.labels["sink"])
			if sink == "" {
				sink = "unknown"
			}
			kind := strings.TrimSpace(sample.labels["kind"])
			if kind == "" {
				kind = "unknown"
			}
			findings = append(findings, fmt.Sprintf(
				"%s async sink publish errors %.0f exceeds %.0f for sink %s kind %s",
				service,
				sample.value,
				thresholds.AsyncSinkPublishErrorMax,
				sink,
				kind,
			))
		}
	}
	sort.Strings(findings)
	return findings
}

func findingsForDurableSpool(parsed parsedMetricSet, thresholds Thresholds) []string {
	var findings []string
	if thresholds.DurableSpoolBacklogMax > 0 {
		for _, sample := range parsed.samples {
			if sample.name != "vehicle_durable_spool_backlog_files" || sample.value <= thresholds.DurableSpoolBacklogMax {
				continue
			}
			name := strings.TrimSpace(sample.labels["name"])
			if name == "" {
				name = "unknown"
			}
			findings = append(findings, fmt.Sprintf(
				"durable spool backlog %.0f exceeds %.0f for name %s",
				sample.value,
				thresholds.DurableSpoolBacklogMax,
				name,
			))
		}
	}
	if thresholds.DurableSpoolOldestAgeMaxSec > 0 {
		for _, sample := range parsed.samples {
			if sample.name != "vehicle_durable_spool_oldest_age_seconds" || sample.value <= thresholds.DurableSpoolOldestAgeMaxSec {
				continue
			}
			name := strings.TrimSpace(sample.labels["name"])
			if name == "" {
				name = "unknown"
			}
			findings = append(findings, fmt.Sprintf(
				"durable spool oldest age %.0fs exceeds %.0fs for name %s",
				sample.value,
				thresholds.DurableSpoolOldestAgeMaxSec,
				name,
			))
		}
	}
	if thresholds.DurableSpoolErrorMax >= 0 {
		for _, sample := range parsed.samples {
			if sample.name != "vehicle_durable_spool_records_total" || sample.labels["status"] != "error" || sample.value <= thresholds.DurableSpoolErrorMax {
				continue
			}
			name := strings.TrimSpace(sample.labels["name"])
			if name == "" {
				name = "unknown"
			}
			kind := strings.TrimSpace(sample.labels["kind"])
			if kind == "" {
				kind = "unknown"
			}
			findings = append(findings, fmt.Sprintf(
				"durable spool record errors %.0f exceeds %.0f for name %s kind %s",
				sample.value,
				thresholds.DurableSpoolErrorMax,
				name,
				kind,
			))
		}
	}
	if thresholds.DurableSpoolReplayErrorMax >= 0 {
		for _, sample := range parsed.samples {
			if sample.name != "vehicle_durable_spool_replay_total" || !strings.HasSuffix(sample.labels["status"], "_error") || sample.value <= thresholds.DurableSpoolReplayErrorMax {
				continue
			}
			name := strings.TrimSpace(sample.labels["name"])
			if name == "" {
				name = "unknown"
			}
			status := strings.TrimSpace(sample.labels["status"])
			if status == "" {
				status = "unknown"
			}
			findings = append(findings, fmt.Sprintf(
				"durable spool replay %s %.0f exceeds %.0f for name %s",
				status,
				sample.value,
				thresholds.DurableSpoolReplayErrorMax,
				name,
			))
		}
	}
	sort.Strings(findings)
	return findings
}

func findingsForDurableOutbox(parsed parsedMetricSet, thresholds Thresholds) []string {
	var findings []string
	if thresholds.DurableOutboxInflightMax > 0 {
		for _, sample := range parsed.samples {
			if sample.name != "vehicle_durable_outbox_inflight" || sample.value <= thresholds.DurableOutboxInflightMax {
				continue
			}
			name := strings.TrimSpace(sample.labels["name"])
			if name == "" {
				name = "unknown"
			}
			findings = append(findings, fmt.Sprintf(
				"durable outbox inflight %.0f exceeds %.0f for name %s",
				sample.value,
				thresholds.DurableOutboxInflightMax,
				name,
			))
		}
	}
	if thresholds.DurableOutboxErrorMax >= 0 {
		for _, sample := range parsed.samples {
			if sample.name != "vehicle_durable_outbox_publish_total" || !durableOutboxErrorStatus(sample.labels["status"]) || sample.value <= thresholds.DurableOutboxErrorMax {
				continue
			}
			name := strings.TrimSpace(sample.labels["name"])
			if name == "" {
				name = "unknown"
			}
			kind := strings.TrimSpace(sample.labels["kind"])
			if kind == "" {
				kind = "unknown"
			}
			status := strings.TrimSpace(sample.labels["status"])
			findings = append(findings, fmt.Sprintf(
				"durable outbox %s %.0f exceeds %.0f for name %s kind %s",
				status,
				sample.value,
				thresholds.DurableOutboxErrorMax,
				name,
				kind,
			))
		}
	}
	sort.Strings(findings)
	return findings
}

func durableOutboxErrorStatus(status string) bool {
	switch strings.TrimSpace(status) {
	case "validation_error", "submit_error", "ack_error", "remove_error", "close_timeout", "quarantined":
		return true
	default:
		return false
	}
}

func findingsForHistograms(parsed parsedMetricSet, thresholds Thresholds) []string {
	var findings []string
	findings = append(findings, histogramP99Findings(parsed, "vehicle_gateway_frame_duration_ms_histogram", "gateway frame", thresholds.GatewayFrameP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_gateway_identity_duration_ms_histogram", "gateway identity", thresholds.GatewayIdentityP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, recentP99GaugeFindings(parsed, "vehicle_gateway_response_e2e_recent_p99_ms", "vehicle_gateway_response_e2e_recent_samples", "gateway response e2e", thresholds.GatewayResponseRecentP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_async_sink_publish_duration_ms_histogram", "async sink publish", thresholds.AsyncSinkP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, recentP99GaugeFindings(parsed, "vehicle_async_sink_queue_wait_recent_p99_ms", "vehicle_async_sink_queue_wait_recent_samples", "async sink queue wait", thresholds.AsyncSinkQueueWaitRecentP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_bridge_batch_duration_ms_histogram", "bridge batch", thresholds.BridgeBatchP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_bridge_kafka_write_duration_ms_histogram", "bridge kafka write", thresholds.BridgeKafkaWriteP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_bridge_kafka_e2e_duration_ms_histogram", "bridge kafka e2e", thresholds.BridgeKafkaE2EP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, recentP99GaugeFindings(parsed, "vehicle_bridge_kafka_e2e_recent_p99_ms", "vehicle_bridge_kafka_e2e_recent_samples", "bridge kafka e2e", thresholds.BridgeKafkaRecentE2EP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_fast_writer_stage_duration_ms_histogram", "fast writer stage", thresholds.FastWriterStageP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_fast_writer_redis_e2e_duration_ms_histogram", "fast writer redis e2e", thresholds.FastWriterRedisE2EP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, recentP99GaugeFindings(parsed, "vehicle_fast_writer_redis_e2e_recent_p99_ms", "vehicle_fast_writer_redis_e2e_recent_samples", "fast writer redis e2e", thresholds.FastWriterRedisRecentE2EP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_history_batch_flush_duration_ms_histogram", "history flush", thresholds.HistoryFlushP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_history_write_e2e_duration_ms_histogram", "history write e2e", thresholds.HistoryWriteE2EP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, recentP99GaugeFindings(parsed, "vehicle_history_write_e2e_recent_p99_ms", "vehicle_history_write_e2e_recent_samples", "history write e2e", thresholds.HistoryWriteRecentE2EP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_realtime_batch_flush_duration_ms_histogram", "realtime batch flush", thresholds.RealtimeBatchP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_realtime_store_update_duration_ms_histogram", "realtime store update", thresholds.RealtimeStoreP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_stat_write_duration_ms_histogram", "stat write", thresholds.StatWriteP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, histogramP99Findings(parsed, "vehicle_stat_write_e2e_duration_ms_histogram", "stat write e2e", thresholds.StatWriteE2EP99MS, thresholds.MinHistogramSamples)...)
	findings = append(findings, recentP99GaugeFindings(parsed, "vehicle_stat_write_e2e_recent_p99_ms", "vehicle_stat_write_e2e_recent_samples", "stat write e2e", thresholds.StatWriteRecentE2EP99MS, thresholds.MinHistogramSamples)...)
	return findings
}

type gatewayPublishCounters struct {
	rawOK                    float64
	fieldsOK                 float64
	fieldsPublished          float64
	fieldsDelegated          float64
	fieldsSkippedNonRealtime float64
	fieldsSkippedMissing     float64
	fieldsPublishError       float64
}

type gatewayFieldFlowGroup struct {
	gatewayPublishCounters
	latestCount    float64
	hasLatestCount bool
}

type gatewayIdentityCounters struct {
	total      float64
	resolved   float64
	unresolved float64
}

type identityResolutionGroup struct {
	protocol          string
	total             int64
	skipped           int64
	resolved          int64
	unresolved        int64
	timeout           int64
	errorCount        int64
	otherNonResolved  int64
	staleCache        int64
	cacheStatusCounts map[string]int64
}

type pipelineLatencySpec struct {
	service           string
	stage             string
	metricName        string
	samplesMetricName string
	keyLabel          string
	thresholdMS       float64
}

type gatewayParseCounters struct {
	total    float64
	ok       float64
	partial  float64
	badFrame float64
	other    float64
}

type gatewayResponseCounters struct {
	total      float64
	ok         float64
	buildError float64
	writeError float64
}

type historyParsedFieldCounters struct {
	present float64
	missing float64
}

type kafkaConsumerCompletionCounters struct {
	received    float64
	committedOK float64
	commitError float64
}

type kafkaConsumerCompletionSpec struct {
	serviceName   string
	messageMetric string
	commitMetric  string
}

func findingsForKafkaConsumerCompletion(service string, parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.KafkaConsumerMessageMinReceived <= 0 || thresholds.KafkaConsumerMaxUncommittedRatio <= 0 {
		return nil
	}
	spec, ok := kafkaConsumerCompletionSpecForService(service)
	if !ok {
		return nil
	}
	groups := map[string]*kafkaConsumerCompletionCounters{}
	for _, sample := range parsed.samples {
		switch sample.name {
		case spec.messageMetric, spec.commitMetric:
		default:
			continue
		}
		topic := strings.TrimSpace(sample.labels["topic"])
		if topic == "" || topic == "mixed" || topic == "unknown" {
			continue
		}
		group := groups[topic]
		if group == nil {
			group = &kafkaConsumerCompletionCounters{}
			groups[topic] = group
		}
		switch {
		case sample.name == spec.messageMetric && sample.labels["status"] == "received":
			group.received += sample.value
		case sample.name == spec.commitMetric && sample.labels["status"] == "ok":
			group.committedOK += sample.value
		case sample.name == spec.commitMetric && sample.labels["status"] == "error":
			group.commitError += sample.value
		}
	}
	var topics []string
	for topic := range groups {
		topics = append(topics, topic)
	}
	sort.Strings(topics)
	var findings []string
	for _, topic := range topics {
		group := groups[topic]
		if thresholds.KafkaConsumerCommitErrorMax >= 0 && group.commitError > thresholds.KafkaConsumerCommitErrorMax {
			findings = append(findings, fmt.Sprintf(
				"%s kafka consumer commit errors %.0f exceeds %.0f for topic %s",
				spec.serviceName,
				group.commitError,
				thresholds.KafkaConsumerCommitErrorMax,
				topic,
			))
		}
		if group.received < thresholds.KafkaConsumerMessageMinReceived {
			continue
		}
		uncommitted := group.received - group.committedOK
		if uncommitted < 0 {
			uncommitted = 0
		}
		ratio := uncommitted / group.received
		if ratio <= thresholds.KafkaConsumerMaxUncommittedRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"%s kafka consumer uncommitted message ratio %.2f exceeds %.2f for topic %s: received %.0f, commit ok %.0f, commit errors %.0f, uncommitted %.0f",
			spec.serviceName,
			ratio,
			thresholds.KafkaConsumerMaxUncommittedRatio,
			topic,
			group.received,
			group.committedOK,
			group.commitError,
			uncommitted,
		))
	}
	return findings
}

func kafkaConsumerCompletionSpecForService(service string) (kafkaConsumerCompletionSpec, bool) {
	switch service {
	case "history":
		return kafkaConsumerCompletionSpec{
			serviceName:   "history",
			messageMetric: "vehicle_history_kafka_messages_total",
			commitMetric:  "vehicle_history_kafka_commits_total",
		}, true
	case "realtime":
		return kafkaConsumerCompletionSpec{
			serviceName:   "realtime",
			messageMetric: "vehicle_realtime_kafka_messages_total",
			commitMetric:  "vehicle_realtime_kafka_commits_total",
		}, true
	case "stat":
		return kafkaConsumerCompletionSpec{
			serviceName:   "stat",
			messageMetric: "vehicle_stat_kafka_messages_total",
			commitMetric:  "vehicle_stat_kafka_commits_total",
		}, true
	case "identity":
		return kafkaConsumerCompletionSpec{
			serviceName:   "identity",
			messageMetric: "vehicle_identity_writer_kafka_messages_total",
			commitMetric:  "vehicle_identity_writer_kafka_commits_total",
		}, true
	default:
		return kafkaConsumerCompletionSpec{}, false
	}
}

func findingsForMessageContractMismatches(service string, parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.MessageContractMismatchMax < 0 {
		return nil
	}
	specs := messageContractMismatchSpecs(service)
	if len(specs) == 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		status := strings.TrimSpace(sample.labels["status"])
		if status == "" {
			continue
		}
		for _, spec := range specs {
			if sample.name != spec.metric || status != spec.status {
				continue
			}
			if sample.value <= thresholds.MessageContractMismatchMax {
				continue
			}
			key := strings.TrimSpace(sample.labels[spec.keyLabel])
			if key == "" {
				key = "unknown"
			}
			findings = append(findings, fmt.Sprintf(
				"%s message contract %s %.0f exceeds %.0f for %s %s",
				spec.serviceLabel,
				status,
				sample.value,
				thresholds.MessageContractMismatchMax,
				spec.keyLabel,
				key,
			))
		}
	}
	sort.Strings(findings)
	return findings
}

type messageContractMismatchSpec struct {
	serviceLabel string
	metric       string
	status       string
	keyLabel     string
}

func messageContractMismatchSpecs(service string) []messageContractMismatchSpec {
	switch service {
	case "history":
		return []messageContractMismatchSpec{
			{serviceLabel: "history", metric: "vehicle_history_kafka_messages_total", status: "event_kind_mismatch", keyLabel: "topic"},
			{serviceLabel: "history", metric: "vehicle_history_kafka_messages_total", status: "protocol_topic_mismatch", keyLabel: "topic"},
		}
	case "realtime":
		return []messageContractMismatchSpec{
			{serviceLabel: "realtime", metric: "vehicle_realtime_kafka_messages_total", status: "event_kind_mismatch", keyLabel: "topic"},
			{serviceLabel: "realtime", metric: "vehicle_realtime_kafka_messages_total", status: "protocol_topic_mismatch", keyLabel: "topic"},
		}
	case "stat":
		return []messageContractMismatchSpec{
			{serviceLabel: "stat", metric: "vehicle_stat_kafka_messages_total", status: "event_kind_mismatch", keyLabel: "topic"},
		}
	case "identity":
		return []messageContractMismatchSpec{
			{serviceLabel: "identity", metric: "vehicle_identity_writer_kafka_messages_total", status: "event_kind_mismatch", keyLabel: "topic"},
			{serviceLabel: "identity", metric: "vehicle_identity_writer_kafka_messages_total", status: "protocol_topic_mismatch", keyLabel: "topic"},
		}
	case "fast-writer":
		return []messageContractMismatchSpec{
			{serviceLabel: "fast writer", metric: "vehicle_fast_writer_messages_total", status: "event_kind_mismatch", keyLabel: "subject"},
			{serviceLabel: "fast writer", metric: "vehicle_fast_writer_messages_total", status: "protocol_topic_mismatch", keyLabel: "subject"},
		}
	default:
		return nil
	}
}

func findingsForIdentityWriterOwnership(gateway parsedMetricSet, identityWriter parsedMetricSet) []string {
	if len(identityWriter.samples) == 0 {
		return nil
	}
	for _, sample := range gateway.samples {
		if sample.name != "vehicle_gateway_jt808_registration_gateway_writes_enabled" {
			continue
		}
		if sample.value > 0 {
			return []string{"jt808 registration has duplicate writers: gateway writes remain enabled while identity writer is active"}
		}
		return nil
	}
	return []string{"gateway jt808 registration ownership metric missing while identity writer is active"}
}

func findingsForHistoryParsedFields(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.HistoryParsedFieldMinFrames <= 0 || thresholds.HistoryParsedFieldMaxMissingRatio <= 0 {
		return nil
	}
	groups := map[string]*historyParsedFieldCounters{}
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_history_parsed_fields_total" {
			continue
		}
		topic := strings.TrimSpace(sample.labels["topic"])
		if topic == "" {
			topic = "unknown"
		}
		protocol := strings.TrimSpace(sample.labels["protocol"])
		if protocol == "" {
			protocol = "UNKNOWN"
		}
		key := topic + "\x00" + protocol
		group := groups[key]
		if group == nil {
			group = &historyParsedFieldCounters{}
			groups[key] = group
		}
		switch sample.labels["status"] {
		case "present":
			group.present += sample.value
		case "missing":
			group.missing += sample.value
		}
	}
	var keys []string
	for key := range groups {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	var findings []string
	for _, key := range keys {
		group := groups[key]
		total := group.present + group.missing
		if total < thresholds.HistoryParsedFieldMinFrames || total <= 0 {
			continue
		}
		ratio := group.missing / total
		if ratio <= thresholds.HistoryParsedFieldMaxMissingRatio {
			continue
		}
		topic, protocol, _ := strings.Cut(key, "\x00")
		findings = append(findings, fmt.Sprintf(
			"history parsed fields missing ratio %.2f exceeds %.2f for topic %s protocol %s (total %.0f, present %.0f, missing %.0f)",
			ratio,
			thresholds.HistoryParsedFieldMaxMissingRatio,
			topic,
			protocol,
			total,
			group.present,
			group.missing,
		))
	}
	return findings
}

func findingsForHistoryInvalidJSON(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.HistoryInvalidJSONMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_history_kafka_messages_total" || sample.labels["status"] != "invalid_json" {
			continue
		}
		if sample.value <= thresholds.HistoryInvalidJSONMax {
			continue
		}
		topic := strings.TrimSpace(sample.labels["topic"])
		if topic == "" {
			topic = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"history invalid json %.0f exceeds %.0f for topic %s",
			sample.value,
			thresholds.HistoryInvalidJSONMax,
			topic,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForRealtimeInvalidJSON(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.RealtimeInvalidJSONMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_realtime_kafka_messages_total" || sample.labels["status"] != "invalid_json" {
			continue
		}
		if sample.value <= thresholds.RealtimeInvalidJSONMax {
			continue
		}
		topic := strings.TrimSpace(sample.labels["topic"])
		if topic == "" {
			topic = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"realtime invalid json %.0f exceeds %.0f for topic %s",
			sample.value,
			thresholds.RealtimeInvalidJSONMax,
			topic,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForRealtimeStoreErrors(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.RealtimeStoreErrorMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_realtime_store_updates_total" || sample.labels["status"] != "error" {
			continue
		}
		if sample.value <= thresholds.RealtimeStoreErrorMax {
			continue
		}
		store := strings.TrimSpace(sample.labels["store"])
		if store == "" {
			store = "unknown"
		}
		protocol := strings.TrimSpace(sample.labels["protocol"])
		if protocol == "" {
			protocol = "UNKNOWN"
		}
		findings = append(findings, fmt.Sprintf(
			"realtime store update errors %.0f exceeds %.0f for store %s protocol %s",
			sample.value,
			thresholds.RealtimeStoreErrorMax,
			store,
			protocol,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForRealtimeAsyncQueue(parsed parsedMetricSet, thresholds Thresholds) []string {
	var findings []string
	if thresholds.RealtimeAsyncQueueDroppedMax >= 0 {
		for _, sample := range parsed.samples {
			if sample.name != "vehicle_realtime_async_queue_total" {
				continue
			}
			status := sample.labels["status"]
			if status != "dropped" && status != "closed" {
				continue
			}
			if sample.value <= thresholds.RealtimeAsyncQueueDroppedMax {
				continue
			}
			store := strings.TrimSpace(sample.labels["store"])
			if store == "" {
				store = "unknown"
			}
			protocol := strings.TrimSpace(sample.labels["protocol"])
			if protocol == "" {
				protocol = "UNKNOWN"
			}
			findings = append(findings, fmt.Sprintf(
				"realtime async queue %s %.0f exceeds %.0f for store %s protocol %s",
				status,
				sample.value,
				thresholds.RealtimeAsyncQueueDroppedMax,
				store,
				protocol,
			))
		}
	}
	if thresholds.RealtimeAsyncQueueMaxUsageRatio <= 0 {
		sort.Strings(findings)
		return findings
	}
	type asyncQueueSample struct {
		labels map[string]string
		depth  float64
	}
	queues := map[string]*asyncQueueSample{}
	capacityByKey := map[string]float64{}
	capacityByStore := map[string]float64{}
	for _, sample := range parsed.samples {
		switch sample.name {
		case "vehicle_realtime_async_queue_depth":
			key := metricLabelsKey(sample.labels)
			item := queues[key]
			if item == nil {
				item = &asyncQueueSample{labels: sample.labels}
				queues[key] = item
			}
			item.depth += sample.value
		case "vehicle_realtime_async_queue_capacity":
			key := metricLabelsKey(sample.labels)
			capacityByKey[key] += sample.value
			store := strings.TrimSpace(sample.labels["store"])
			if store != "" {
				capacityByStore[store] += sample.value
			}
		}
	}
	var keys []string
	for key := range queues {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		item := queues[key]
		capacity := capacityByKey[key]
		if capacity <= 0 {
			capacity = capacityByStore[strings.TrimSpace(item.labels["store"])]
		}
		if capacity <= 0 || item.depth <= 0 {
			continue
		}
		ratio := item.depth / capacity
		if ratio <= thresholds.RealtimeAsyncQueueMaxUsageRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"realtime async queue usage %.2f exceeds %.2f (%s, depth %.0f, capacity %.0f)",
			ratio,
			thresholds.RealtimeAsyncQueueMaxUsageRatio,
			key,
			item.depth,
			capacity,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForGatewayResponseQuality(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.GatewayResponseMinSamples <= 0 || thresholds.GatewayResponseMaxErrorRatio <= 0 {
		return nil
	}
	groups := map[string]*gatewayResponseCounters{}
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_gateway_response_total" {
			continue
		}
		protocol := strings.TrimSpace(sample.labels["protocol"])
		if protocol == "" {
			protocol = "unknown"
		}
		group := groups[protocol]
		if group == nil {
			group = &gatewayResponseCounters{}
			groups[protocol] = group
		}
		switch sample.labels["status"] {
		case "ok":
			group.ok += sample.value
			group.total += sample.value
		case "build_error":
			group.buildError += sample.value
			group.total += sample.value
		case "write_error":
			group.writeError += sample.value
			group.total += sample.value
		}
	}
	var protocols []string
	for protocol := range groups {
		protocols = append(protocols, protocol)
	}
	sort.Strings(protocols)
	var findings []string
	for _, protocol := range protocols {
		group := groups[protocol]
		if group.total < thresholds.GatewayResponseMinSamples || group.total <= 0 {
			continue
		}
		errors := group.buildError + group.writeError
		ratio := errors / group.total
		if ratio <= thresholds.GatewayResponseMaxErrorRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"gateway response error ratio %.2f exceeds %.2f for protocol %s (total %.0f, ok %.0f, errors %.0f, build_error %.0f, write_error %.0f)",
			ratio,
			thresholds.GatewayResponseMaxErrorRatio,
			protocol,
			group.total,
			group.ok,
			errors,
			group.buildError,
			group.writeError,
		))
	}
	return findings
}

func findingsForGatewayParseQuality(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.GatewayParseMinFrames <= 0 || thresholds.GatewayParseMaxBadRatio <= 0 {
		return nil
	}
	groups := map[string]*gatewayParseCounters{}
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_gateway_frames_total" {
			continue
		}
		protocol := strings.TrimSpace(sample.labels["protocol"])
		if protocol == "" {
			protocol = "unknown"
		}
		group := groups[protocol]
		if group == nil {
			group = &gatewayParseCounters{}
			groups[protocol] = group
		}
		group.total += sample.value
		switch envelope.ParseStatus(sample.labels["status"]) {
		case envelope.ParseOK:
			group.ok += sample.value
		case envelope.ParsePartial:
			group.partial += sample.value
		case envelope.ParseBadFrame:
			group.badFrame += sample.value
		default:
			group.other += sample.value
		}
	}
	var protocols []string
	for protocol := range groups {
		protocols = append(protocols, protocol)
	}
	sort.Strings(protocols)
	var findings []string
	for _, protocol := range protocols {
		group := groups[protocol]
		if group.total < thresholds.GatewayParseMinFrames || group.total <= 0 {
			continue
		}
		bad := group.partial + group.badFrame + group.other
		ratio := bad / group.total
		if ratio <= thresholds.GatewayParseMaxBadRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"gateway parse bad ratio %.2f exceeds %.2f for protocol %s (total %.0f, ok %.0f, bad %.0f, partial %.0f, bad_frame %.0f, other %.0f)",
			ratio,
			thresholds.GatewayParseMaxBadRatio,
			protocol,
			group.total,
			group.ok,
			bad,
			group.partial,
			group.badFrame,
			group.other,
		))
	}
	return findings
}

func findingsForGatewayIdentityQuality(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.GatewayIdentityMinSamples <= 0 || thresholds.GatewayIdentityMaxUnresolvedRatio <= 0 {
		return nil
	}
	groups := map[string]*gatewayIdentityCounters{}
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_gateway_identity_total" {
			continue
		}
		protocol := strings.TrimSpace(sample.labels["protocol"])
		if protocol == "" {
			protocol = "unknown"
		}
		group := groups[protocol]
		if group == nil {
			group = &gatewayIdentityCounters{}
			groups[protocol] = group
		}
		group.total += sample.value
		if sample.labels["status"] == "resolved" {
			group.resolved += sample.value
		} else {
			group.unresolved += sample.value
		}
	}
	var protocols []string
	for protocol := range groups {
		protocols = append(protocols, protocol)
	}
	sort.Strings(protocols)
	var findings []string
	for _, protocol := range protocols {
		group := groups[protocol]
		if group.total < thresholds.GatewayIdentityMinSamples || group.total <= 0 {
			continue
		}
		ratio := group.unresolved / group.total
		if ratio <= thresholds.GatewayIdentityMaxUnresolvedRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"gateway identity unresolved ratio %.2f exceeds %.2f for protocol %s (total %.0f, unresolved %.0f, resolved %.0f)",
			ratio,
			thresholds.GatewayIdentityMaxUnresolvedRatio,
			protocol,
			group.total,
			group.unresolved,
			group.resolved,
		))
	}
	return findings
}

func buildIdentityResolutionDiagnostics(parsed parsedMetricSet) *IdentityResolutionDiagnostics {
	groups := map[string]*identityResolutionGroup{}
	cacheEntries := map[string]int64{}
	issues := map[string]IdentityIssueCount{}
	for _, sample := range parsed.samples {
		switch sample.name {
		case "vehicle_gateway_identity_total":
			protocol := normalizedMetricLabel(sample.labels, "protocol")
			status := normalizedMetricLabel(sample.labels, "status")
			group := identityResolutionGroupForProtocol(groups, protocol)
			value := metricCount(sample.value)
			group.total += value
			switch status {
			case "resolved":
				group.resolved += value
			case "unresolved":
				group.unresolved += value
			case "timeout":
				group.timeout += value
			case "error":
				group.errorCount += value
			default:
				group.otherNonResolved += value
			}
		case "vehicle_gateway_identity_skips_total":
			protocol := normalizedMetricLabel(sample.labels, "protocol")
			group := identityResolutionGroupForProtocol(groups, protocol)
			group.skipped += metricCount(sample.value)
		case "vehicle_gateway_identity_issues_total":
			issue := IdentityIssueCount{
				Protocol:  normalizedMetricLabel(sample.labels, "protocol"),
				Status:    normalizedMetricLabel(sample.labels, "status"),
				MessageID: normalizedMetricLabel(sample.labels, "message_id"),
				Reason:    normalizedMetricLabel(sample.labels, "reason"),
				Count:     metricCount(sample.value),
			}
			if issue.Count <= 0 {
				continue
			}
			key := strings.Join([]string{issue.Protocol, issue.Status, issue.MessageID, issue.Reason}, "\x00")
			existing := issues[key]
			if existing.Count == 0 {
				existing = issue
			} else {
				existing.Count += issue.Count
			}
			issues[key] = existing
		case "vehicle_gateway_identity_cache_total":
			protocol := normalizedMetricLabel(sample.labels, "protocol")
			cacheStatus := normalizedMetricLabel(sample.labels, "cache_status")
			group := identityResolutionGroupForProtocol(groups, protocol)
			value := metricCount(sample.value)
			if group.cacheStatusCounts == nil {
				group.cacheStatusCounts = map[string]int64{}
			}
			group.cacheStatusCounts[cacheStatus] += value
			if cacheStatus == "stale" {
				group.staleCache += value
			}
		case "vehicle_gateway_identity_cache_entries":
			cacheName := normalizedMetricLabel(sample.labels, "cache")
			cacheEntries[cacheName] += metricCount(sample.value)
		}
	}
	if len(groups) == 0 && len(cacheEntries) == 0 && len(issues) == 0 {
		return nil
	}
	diagnostics := &IdentityResolutionDiagnostics{}
	if len(cacheEntries) > 0 {
		diagnostics.CacheEntries = cacheEntries
	}
	if len(issues) > 0 {
		diagnostics.Issues = sortedIdentityIssues(issues)
	}
	var protocols []string
	for protocol := range groups {
		protocols = append(protocols, protocol)
	}
	sort.Strings(protocols)
	for _, protocol := range protocols {
		group := groups[protocol]
		row := identityProtocolResolution(group)
		diagnostics.Total += row.Total
		diagnostics.SkippedCount += row.SkippedCount
		diagnostics.ResolvedCount += row.ResolvedCount
		diagnostics.NonResolvedCount += row.NonResolvedCount
		diagnostics.UnresolvedCount += row.UnresolvedCount
		diagnostics.TimeoutCount += row.TimeoutCount
		diagnostics.ErrorCount += row.ErrorCount
		diagnostics.OtherNonResolvedCount += row.OtherNonResolvedCount
		diagnostics.StaleCacheCount += row.StaleCacheCount
		diagnostics.Protocols = append(diagnostics.Protocols, row)
	}
	diagnostics.NonResolvedRatio = ratioInt64(diagnostics.NonResolvedCount, diagnostics.Total)
	diagnostics.StaleCacheRatio = ratioInt64(diagnostics.StaleCacheCount, diagnostics.Total)
	return diagnostics
}

func sortedIdentityIssues(issues map[string]IdentityIssueCount) []IdentityIssueCount {
	rows := make([]IdentityIssueCount, 0, len(issues))
	for _, row := range issues {
		rows = append(rows, row)
	}
	sort.Slice(rows, func(i, j int) bool {
		if rows[i].Protocol != rows[j].Protocol {
			return rows[i].Protocol < rows[j].Protocol
		}
		if rows[i].Status != rows[j].Status {
			return rows[i].Status < rows[j].Status
		}
		if rows[i].MessageID != rows[j].MessageID {
			return rows[i].MessageID < rows[j].MessageID
		}
		return rows[i].Reason < rows[j].Reason
	})
	return rows
}

func identityProtocolResolution(group *identityResolutionGroup) IdentityProtocolResolution {
	if group == nil {
		return IdentityProtocolResolution{Protocol: "unknown"}
	}
	nonResolved := group.unresolved + group.timeout + group.errorCount + group.otherNonResolved
	row := IdentityProtocolResolution{
		Protocol:              group.protocol,
		Total:                 group.total,
		SkippedCount:          group.skipped,
		ResolvedCount:         group.resolved,
		NonResolvedCount:      nonResolved,
		UnresolvedCount:       group.unresolved,
		TimeoutCount:          group.timeout,
		ErrorCount:            group.errorCount,
		OtherNonResolvedCount: group.otherNonResolved,
		NonResolvedRatio:      ratioInt64(nonResolved, group.total),
		StaleCacheCount:       group.staleCache,
		StaleCacheRatio:       ratioInt64(group.staleCache, group.total),
		CacheStatusCounts:     group.cacheStatusCounts,
	}
	return row
}

func identityResolutionGroupForProtocol(groups map[string]*identityResolutionGroup, protocol string) *identityResolutionGroup {
	if protocol == "" {
		protocol = "unknown"
	}
	group := groups[protocol]
	if group == nil {
		group = &identityResolutionGroup{protocol: protocol}
		groups[protocol] = group
	}
	return group
}

func normalizedMetricLabel(labels map[string]string, key string) string {
	value := strings.TrimSpace(labels[key])
	if value == "" {
		return "unknown"
	}
	return value
}

func metricCount(value float64) int64 {
	if value <= 0 {
		return 0
	}
	return int64(math.Round(value))
}

func ratioInt64(numerator int64, denominator int64) float64 {
	if denominator <= 0 {
		return 0
	}
	return float64(numerator) / float64(denominator)
}

func buildPipelineLatencyDiagnostics(parsedByService map[string]parsedMetricSet, thresholds Thresholds) *PipelineLatencyDiagnostics {
	specs := []pipelineLatencySpec{
		{
			service:           "gateway",
			stage:             "gateway_protocol_response_e2e",
			metricName:        "vehicle_gateway_response_e2e_recent_p99_ms",
			samplesMetricName: "vehicle_gateway_response_e2e_recent_samples",
			keyLabel:          "protocol",
			thresholdMS:       thresholds.GatewayResponseRecentP99MS,
		},
		{
			service:           "bridge",
			stage:             "bridge_kafka_e2e",
			metricName:        "vehicle_bridge_kafka_e2e_recent_p99_ms",
			samplesMetricName: "vehicle_bridge_kafka_e2e_recent_samples",
			keyLabel:          "topic",
			thresholdMS:       thresholds.BridgeKafkaRecentE2EP99MS,
		},
		{
			service:           "fast-writer",
			stage:             "fast_writer_redis_e2e",
			metricName:        "vehicle_fast_writer_redis_e2e_recent_p99_ms",
			samplesMetricName: "vehicle_fast_writer_redis_e2e_recent_samples",
			keyLabel:          "subject",
			thresholdMS:       thresholds.FastWriterRedisRecentE2EP99MS,
		},
		{
			service:           "history",
			stage:             "history_tdengine_write_e2e",
			metricName:        "vehicle_history_write_e2e_recent_p99_ms",
			samplesMetricName: "vehicle_history_write_e2e_recent_samples",
			keyLabel:          "topic",
			thresholdMS:       thresholds.HistoryWriteRecentE2EP99MS,
		},
		{
			service:           "stat",
			stage:             "stat_mysql_write_e2e",
			metricName:        "vehicle_stat_write_e2e_recent_p99_ms",
			samplesMetricName: "vehicle_stat_write_e2e_recent_samples",
			keyLabel:          "topic",
			thresholdMS:       thresholds.StatWriteRecentE2EP99MS,
		},
	}
	var items []PipelineLatencyItem
	for _, spec := range specs {
		items = append(items, pipelineLatencyItemsForSpec(parsedByService[spec.service], spec, thresholds.MinHistogramSamples)...)
	}
	if len(items) == 0 {
		return nil
	}
	sort.Slice(items, func(i, j int) bool {
		if items[i].Stage != items[j].Stage {
			return items[i].Stage < items[j].Stage
		}
		if items[i].Service != items[j].Service {
			return items[i].Service < items[j].Service
		}
		if items[i].Topic != items[j].Topic {
			return items[i].Topic < items[j].Topic
		}
		return items[i].Subject < items[j].Subject
	})
	diagnostics := &PipelineLatencyDiagnostics{Items: items}
	for _, item := range items {
		diagnostics.TotalSamples += item.Samples
		if item.P99MS > diagnostics.MaxP99MS {
			diagnostics.MaxP99MS = item.P99MS
		}
	}
	return diagnostics
}

func pipelineLatencyItemsForSpec(parsed parsedMetricSet, spec pipelineLatencySpec, minSamples float64) []PipelineLatencyItem {
	samplesByKey := map[string]float64{}
	for _, sample := range parsed.samples {
		if sample.name != spec.samplesMetricName {
			continue
		}
		samplesByKey[metricLabelsKey(sample.labels)] = sample.value
	}
	var items []PipelineLatencyItem
	for _, sample := range parsed.samples {
		if sample.name != spec.metricName {
			continue
		}
		key := metricLabelsKey(sample.labels)
		row := PipelineLatencyItem{
			Stage:       spec.stage,
			Service:     spec.service,
			P99MS:       sample.value,
			Samples:     metricCount(samplesByKey[key]),
			ThresholdMS: spec.thresholdMS,
		}
		switch spec.keyLabel {
		case "subject":
			row.Subject = normalizedMetricLabel(sample.labels, spec.keyLabel)
		default:
			row.Topic = normalizedMetricLabel(sample.labels, spec.keyLabel)
		}
		row.Status = pipelineLatencyStatus(row.P99MS, float64(row.Samples), spec.thresholdMS, minSamples)
		items = append(items, row)
	}
	return items
}

func pipelineLatencyStatus(p99MS float64, samples float64, thresholdMS float64, minSamples float64) string {
	if minSamples > 0 && samples < minSamples {
		return "insufficient_samples"
	}
	if thresholdMS > 0 && p99MS > thresholdMS {
		return "degraded"
	}
	return "ok"
}

func findingsForGatewayIdentityCache(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.GatewayIdentityCacheEntriesMax <= 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_gateway_identity_cache_entries" || sample.value <= thresholds.GatewayIdentityCacheEntriesMax {
			continue
		}
		cacheName := strings.TrimSpace(sample.labels["cache"])
		if cacheName == "" {
			cacheName = "unknown"
		}
		if cacheName == "registration_write_queue" {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"gateway identity cache entries %.0f exceeds %.0f for cache %s",
			sample.value,
			thresholds.GatewayIdentityCacheEntriesMax,
			cacheName,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForGatewayIdentitySnapshot(parsed parsedMetricSet, thresholds Thresholds, checkedAt time.Time) []string {
	if thresholds.GatewayIdentitySnapshotRequired <= 0 && thresholds.GatewayIdentitySnapshotMaxAgeSec <= 0 {
		return nil
	}
	if !hasServiceInfo(parsed, "vehicle-gateway") {
		return nil
	}
	ready, readyFound := parsed.totals["vehicle_gateway_identity_snapshot_ready"]
	var findings []string
	if thresholds.GatewayIdentitySnapshotRequired > 0 {
		switch {
		case !readyFound:
			findings = append(findings, "gateway identity snapshot readiness metric missing")
		case ready < thresholds.GatewayIdentitySnapshotRequired:
			findings = append(findings, fmt.Sprintf("gateway identity snapshot ready %.0f below %.0f", ready, thresholds.GatewayIdentitySnapshotRequired))
		}
	}
	if thresholds.GatewayIdentitySnapshotMaxAgeSec <= 0 || !readyFound || ready <= 0 {
		return findings
	}
	lastSuccess, ok := parsed.totals["vehicle_gateway_identity_snapshot_last_success_unix_seconds"]
	if !ok || lastSuccess <= 0 {
		return append(findings, "gateway identity snapshot last-success metric missing")
	}
	age := checkedAt.Unix() - int64(lastSuccess)
	if age < 0 {
		age = 0
	}
	if float64(age) > thresholds.GatewayIdentitySnapshotMaxAgeSec {
		findings = append(findings, fmt.Sprintf(
			"gateway identity snapshot age %ds exceeds %.0fs",
			age,
			thresholds.GatewayIdentitySnapshotMaxAgeSec,
		))
	}
	return findings
}

func findingsForGatewayRegistrationWriteQueue(parsed parsedMetricSet, thresholds Thresholds) []string {
	depth := gatewayIdentityCacheMetric(parsed, "registration_write_queue", "vehicle_gateway_identity_cache_entries")
	capacity := gatewayIdentityCacheMetric(parsed, "registration_write_queue", "vehicle_gateway_identity_cache_max_entries")
	var findings []string
	if thresholds.GatewayIdentityRegistrationWriteQueueDepthMax > 0 && depth > thresholds.GatewayIdentityRegistrationWriteQueueDepthMax {
		findings = append(findings, fmt.Sprintf(
			"gateway jt808 registration write queue depth %.0f exceeds %.0f",
			depth,
			thresholds.GatewayIdentityRegistrationWriteQueueDepthMax,
		))
	}
	if thresholds.GatewayIdentityRegistrationWriteQueueMaxUsageRatio > 0 && depth > 0 && capacity > 0 {
		ratio := depth / capacity
		if ratio > thresholds.GatewayIdentityRegistrationWriteQueueMaxUsageRatio {
			findings = append(findings, fmt.Sprintf(
				"gateway jt808 registration write queue usage %.2f exceeds %.2f (depth %.0f, capacity %.0f)",
				ratio,
				thresholds.GatewayIdentityRegistrationWriteQueueMaxUsageRatio,
				depth,
				capacity,
			))
		}
	}
	sort.Strings(findings)
	return findings
}

func gatewayIdentityCacheMetric(parsed parsedMetricSet, cacheName string, metricName string) float64 {
	cacheName = strings.TrimSpace(cacheName)
	if cacheName == "" || metricName == "" {
		return 0
	}
	var total float64
	for _, sample := range parsed.samples {
		if sample.name != metricName || strings.TrimSpace(sample.labels["cache"]) != cacheName {
			continue
		}
		total += sample.value
	}
	return total
}

func findingsForGatewayRegistrationWriteErrors(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.GatewayIdentityRegistrationWriteErrorMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_gateway_jt808_registration_write_total" {
			continue
		}
		status := strings.TrimSpace(sample.labels["status"])
		if status == "" || status == "ok" {
			continue
		}
		if sample.value <= thresholds.GatewayIdentityRegistrationWriteErrorMax {
			continue
		}
		mode := strings.TrimSpace(sample.labels["mode"])
		if mode == "" {
			mode = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"gateway jt808 registration write %s count %.0f exceeds %.0f for mode %s",
			status,
			sample.value,
			thresholds.GatewayIdentityRegistrationWriteErrorMax,
			mode,
		))
	}
	sort.Strings(findings)
	return findings
}

func jt808RegistrationWriteErrors(parsed parsedMetricSet) float64 {
	var total float64
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_gateway_jt808_registration_write_total" {
			continue
		}
		status := strings.TrimSpace(sample.labels["status"])
		if status == "" || status == "ok" {
			continue
		}
		total += sample.value
	}
	return total
}

func findingsForGatewayIdentityLocationTouchFailures(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.GatewayIdentityLocationTouchFailureMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_gateway_identity_cache_entries" || strings.TrimSpace(sample.labels["cache"]) != "location_touch_failure" {
			continue
		}
		if sample.value <= thresholds.GatewayIdentityLocationTouchFailureMax {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"gateway identity location touch failure cache %.0f exceeds %.0f; jt808 registration writes are failing or in protective backoff",
			sample.value,
			thresholds.GatewayIdentityLocationTouchFailureMax,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForGatewayIdentityStaleCache(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.GatewayIdentityStaleMinSamples <= 0 || thresholds.GatewayIdentityMaxStaleCacheRatio <= 0 {
		return nil
	}
	totalsByProtocol := map[string]float64{}
	staleByProtocol := map[string]float64{}
	for _, sample := range parsed.samples {
		protocol := strings.TrimSpace(sample.labels["protocol"])
		if protocol == "" {
			protocol = "unknown"
		}
		switch sample.name {
		case "vehicle_gateway_identity_total":
			totalsByProtocol[protocol] += sample.value
		case "vehicle_gateway_identity_cache_total":
			if sample.labels["cache_status"] == "stale" {
				staleByProtocol[protocol] += sample.value
			}
		}
	}
	var protocols []string
	for protocol := range totalsByProtocol {
		protocols = append(protocols, protocol)
	}
	sort.Strings(protocols)
	var findings []string
	for _, protocol := range protocols {
		total := totalsByProtocol[protocol]
		if total < thresholds.GatewayIdentityStaleMinSamples || total <= 0 {
			continue
		}
		stale := staleByProtocol[protocol]
		ratio := stale / total
		if ratio <= thresholds.GatewayIdentityMaxStaleCacheRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"gateway identity stale cache ratio %.2f exceeds %.2f for protocol %s (total %.0f, stale %.0f)",
			ratio,
			thresholds.GatewayIdentityMaxStaleCacheRatio,
			protocol,
			total,
			stale,
		))
	}
	return findings
}

func findingsForBridgeRouteErrors(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.BridgeRouteErrorMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_bridge_messages_total" || sample.labels["status"] != "route_error" {
			continue
		}
		if sample.value <= thresholds.BridgeRouteErrorMax {
			continue
		}
		subject := strings.TrimSpace(sample.labels["subject"])
		if subject == "" {
			subject = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"bridge route errors %.0f exceeds %.0f for subject %s",
			sample.value,
			thresholds.BridgeRouteErrorMax,
			subject,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForBridgeKafkaWriteErrors(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.BridgeKafkaWriteErrorMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_bridge_kafka_writes_total" || sample.labels["status"] != "error" {
			continue
		}
		if sample.value <= thresholds.BridgeKafkaWriteErrorMax {
			continue
		}
		topic := strings.TrimSpace(sample.labels["topic"])
		if topic == "" {
			topic = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"bridge kafka write errors %.0f exceeds %.0f for topic %s",
			sample.value,
			thresholds.BridgeKafkaWriteErrorMax,
			topic,
		))
	}
	sort.Strings(findings)
	return findings
}

type bridgeMessageCounters struct {
	received          float64
	ackOK             float64
	droppedRouteError float64
}

func findingsForBridgeMessageCompletion(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.BridgeMessageMinReceived <= 0 || thresholds.BridgeMessageMaxUnackedRatio <= 0 {
		return nil
	}
	groups := map[string]*bridgeMessageCounters{}
	for _, sample := range parsed.samples {
		switch sample.name {
		case "vehicle_bridge_messages_total", "vehicle_bridge_nats_acks_total":
		default:
			continue
		}
		subject := strings.TrimSpace(sample.labels["subject"])
		if subject == "" || subject == "mixed" || subject == "unknown" {
			continue
		}
		group := groups[subject]
		if group == nil {
			group = &bridgeMessageCounters{}
			groups[subject] = group
		}
		switch {
		case sample.name == "vehicle_bridge_messages_total" && sample.labels["status"] == "received":
			group.received += sample.value
		case sample.name == "vehicle_bridge_nats_acks_total" && sample.labels["status"] == "ok":
			group.ackOK += sample.value
		case sample.name == "vehicle_bridge_nats_acks_total" && sample.labels["status"] == "dropped_route_error":
			group.droppedRouteError += sample.value
		}
	}
	var subjects []string
	for subject := range groups {
		subjects = append(subjects, subject)
	}
	sort.Strings(subjects)
	var findings []string
	for _, subject := range subjects {
		group := groups[subject]
		if group.received < thresholds.BridgeMessageMinReceived {
			continue
		}
		completed := group.ackOK + group.droppedRouteError
		unacked := group.received - completed
		if unacked < 0 {
			unacked = 0
		}
		ratio := unacked / group.received
		if ratio <= thresholds.BridgeMessageMaxUnackedRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"bridge unacked message ratio %.2f exceeds %.2f for subject %s: received %.0f, ack ok %.0f, dropped route error %.0f, unacked %.0f",
			ratio,
			thresholds.BridgeMessageMaxUnackedRatio,
			subject,
			group.received,
			group.ackOK,
			group.droppedRouteError,
			unacked,
		))
	}
	return findings
}

func findingsForTopicFlow(parsedByService map[string]parsedMetricSet, thresholds Thresholds) []string {
	var findings []string
	findings = append(findings, findingsForGatewayFieldFlow(parsedByService["gateway"], thresholds)...)
	findings = append(findings, findingsForGatewayFieldCounts(parsedByService["gateway"], thresholds)...)
	findings = append(findings, findingsForGatewayToBridgeFlow(parsedByService["gateway"], parsedByService["bridge"], thresholds)...)
	findings = append(findings, findingsForGatewayToFastWriterFlow(parsedByService["gateway"], parsedByService["fast-writer"], thresholds)...)
	findings = append(findings, findingsForBridgeRawFanout(parsedByService["bridge"], parsedByService["history"], parsedByService["realtime"], thresholds)...)
	findings = append(findings, findingsForBridgeToStatFieldFlow(parsedByService["bridge"], parsedByService["stat"], thresholds)...)
	return findings
}

func findingsForGatewayFieldFlow(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.GatewayFieldMinRaw <= 0 {
		return nil
	}
	groups := gatewayFieldFlowGroups(parsed)
	protocols := gatewayFieldFlowProtocols(groups)
	var findings []string
	for _, protocol := range protocols {
		group := groups[protocol]
		realtimeCandidates := gatewayRealtimeCandidates(group.gatewayPublishCounters)
		if realtimeCandidates < thresholds.GatewayFieldMinRaw {
			continue
		}
		if group.fieldsOK <= 0 {
			findings = append(findings, fmt.Sprintf(
				"gateway fields missing for protocol %s: raw ok %.0f, realtime candidates %.0f, fields ok %.0f, skipped non-realtime %.0f, skipped missing fields %.0f, publish errors %.0f",
				protocol,
				group.rawOK,
				realtimeCandidates,
				group.fieldsOK,
				group.fieldsSkippedNonRealtime,
				group.fieldsSkippedMissing,
				group.fieldsPublishError,
			))
			continue
		}
		missingRatio := (group.fieldsSkippedMissing + group.fieldsPublishError) / realtimeCandidates
		if thresholds.GatewayFieldMaxMissingRatio > 0 && missingRatio > thresholds.GatewayFieldMaxMissingRatio {
			findings = append(findings, fmt.Sprintf(
				"gateway fields missing ratio %.2f exceeds %.2f for protocol %s: realtime candidates %.0f, fields ok %.0f, skipped missing fields %.0f, publish errors %.0f",
				missingRatio,
				thresholds.GatewayFieldMaxMissingRatio,
				protocol,
				realtimeCandidates,
				group.fieldsOK,
				group.fieldsSkippedMissing,
				group.fieldsPublishError,
			))
		}
	}
	return findings
}

func findingsForGatewayFieldCounts(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.GatewayFieldMinCount <= 0 {
		return nil
	}
	minPublished := thresholds.GatewayFieldMinRaw
	if minPublished <= 0 {
		minPublished = 1
	}
	groups := gatewayFieldFlowGroups(parsed)
	protocols := gatewayFieldFlowProtocols(groups)
	var findings []string
	for _, protocol := range protocols {
		group := groups[protocol]
		fieldsAccepted := group.fieldsPublished + group.fieldsDelegated
		if fieldsAccepted < minPublished {
			continue
		}
		if !group.hasLatestCount {
			findings = append(findings, fmt.Sprintf(
				"gateway fields count metric missing for protocol %s after %.0f published fields events",
				protocol,
				fieldsAccepted,
			))
			continue
		}
		if group.latestCount < thresholds.GatewayFieldMinCount {
			findings = append(findings, fmt.Sprintf(
				"gateway fields count %.0f below %.0f for protocol %s after %.0f published fields events",
				group.latestCount,
				thresholds.GatewayFieldMinCount,
				protocol,
				fieldsAccepted,
			))
		}
	}
	return findings
}

func buildGatewayFieldsDiagnostics(parsed parsedMetricSet, thresholds Thresholds) *GatewayFieldsDiagnostics {
	groups := gatewayFieldFlowGroups(parsed)
	protocols := gatewayFieldFlowProtocols(groups)
	if len(protocols) == 0 {
		return nil
	}
	out := &GatewayFieldsDiagnostics{Protocols: make([]GatewayProtocolFields, 0, len(protocols))}
	for _, protocol := range protocols {
		group := groups[protocol]
		realtimeCandidates := gatewayRealtimeCandidates(group.gatewayPublishCounters)
		missingRatio := 0.0
		if realtimeCandidates > 0 {
			missingRatio = (group.fieldsSkippedMissing + group.fieldsPublishError) / realtimeCandidates
		}
		status, reason := gatewayFieldDiagnosticsStatus(group, realtimeCandidates, thresholds)
		var latestCount *float64
		if group.hasLatestCount {
			value := group.latestCount
			latestCount = &value
		}
		out.Protocols = append(out.Protocols, GatewayProtocolFields{
			Protocol:                 protocol,
			RawOK:                    group.rawOK,
			RealtimeCandidates:       realtimeCandidates,
			FieldsOK:                 group.fieldsOK,
			FieldsPublished:          group.fieldsPublished,
			FieldsDelegated:          group.fieldsDelegated,
			FieldsSkippedNonRealtime: group.fieldsSkippedNonRealtime,
			FieldsSkippedMissing:     group.fieldsSkippedMissing,
			FieldsPublishError:       group.fieldsPublishError,
			MissingRatio:             missingRatio,
			LatestFieldCount:         latestCount,
			Status:                   status,
			Reason:                   reason,
		})
	}
	return out
}

func gatewayFieldDiagnosticsStatus(group *gatewayFieldFlowGroup, realtimeCandidates float64, thresholds Thresholds) (string, string) {
	if thresholds.GatewayFieldMinRaw > 0 && realtimeCandidates >= thresholds.GatewayFieldMinRaw {
		if group.fieldsOK <= 0 {
			return "degraded", "missing_fields_publish"
		}
		missingRatio := 0.0
		if realtimeCandidates > 0 {
			missingRatio = (group.fieldsSkippedMissing + group.fieldsPublishError) / realtimeCandidates
		}
		if thresholds.GatewayFieldMaxMissingRatio > 0 && missingRatio > thresholds.GatewayFieldMaxMissingRatio {
			return "degraded", "high_missing_ratio"
		}
	}
	minPublished := thresholds.GatewayFieldMinRaw
	if minPublished <= 0 {
		minPublished = 1
	}
	if thresholds.GatewayFieldMinCount > 0 && group.fieldsPublished+group.fieldsDelegated >= minPublished {
		if !group.hasLatestCount {
			return "degraded", "missing_field_count_metric"
		}
		if group.latestCount < thresholds.GatewayFieldMinCount {
			return "degraded", "low_field_count"
		}
	}
	return "ok", ""
}

func gatewayFieldFlowGroups(parsed parsedMetricSet) map[string]*gatewayFieldFlowGroup {
	groups := map[string]*gatewayFieldFlowGroup{}
	groupForProtocol := func(protocol string) *gatewayFieldFlowGroup {
		protocol = strings.TrimSpace(protocol)
		if protocol == "" {
			protocol = "unknown"
		}
		group := groups[protocol]
		if group == nil {
			group = &gatewayFieldFlowGroup{}
			groups[protocol] = group
		}
		return group
	}
	for _, sample := range parsed.samples {
		switch sample.name {
		case "vehicle_gateway_publish_total":
			if sample.labels["status"] != "ok" && sample.labels["status"] != "delegated" {
				continue
			}
			group := groupForProtocol(sample.labels["protocol"])
			switch sample.labels["kind"] {
			case "raw":
				group.rawOK += sample.value
			case "fields":
				group.fieldsOK += sample.value
			}
		case "vehicle_gateway_fields_total":
			group := groupForProtocol(sample.labels["protocol"])
			switch sample.labels["status"] {
			case "published":
				group.fieldsPublished += sample.value
			case "delegated_to_bridge":
				group.fieldsDelegated += sample.value
			case "skipped_non_realtime":
				group.fieldsSkippedNonRealtime += sample.value
			case "skipped_missing_fields":
				group.fieldsSkippedMissing += sample.value
			case "publish_error":
				group.fieldsPublishError += sample.value
			}
		case "vehicle_gateway_fields_count":
			if sample.labels["status"] != "published" && sample.labels["status"] != "delegated_to_bridge" {
				continue
			}
			group := groupForProtocol(sample.labels["protocol"])
			group.latestCount = sample.value
			group.hasLatestCount = true
		}
	}
	return groups
}

func gatewayFieldFlowProtocols(groups map[string]*gatewayFieldFlowGroup) []string {
	var protocols []string
	for protocol := range groups {
		protocols = append(protocols, protocol)
	}
	sort.Strings(protocols)
	return protocols
}

func gatewayRealtimeCandidates(group gatewayPublishCounters) float64 {
	realtimeCandidates := group.rawOK - group.fieldsSkippedNonRealtime
	if realtimeCandidates < 0 {
		return 0
	}
	return realtimeCandidates
}

func findingsForGatewayToFastWriterFlow(gateway parsedMetricSet, fastWriter parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.GatewayFastWriterMinRawPublish <= 0 {
		return nil
	}
	gatewayPublishes := gatewayPublishGroups(gateway.samples)
	fastWriterMessages := metricValuesBySubject(fastWriter.samples, "vehicle_fast_writer_messages_total", "received", "vehicle.raw.go.")
	var findings []string
	for _, group := range gatewayPublishes {
		if group.kind != "raw" || group.count < thresholds.GatewayFastWriterMinRawPublish {
			continue
		}
		subject, ok := expectedKafkaTopicForGatewayPublish(group.protocol, group.kind)
		if !ok {
			continue
		}
		if fastWriterMessages[subject] <= 0 {
			findings = append(findings, fmt.Sprintf(
				"fast writer received messages missing for gateway raw publish protocol %s: gateway ok %.0f, expected subject %s, fast writer received %.0f",
				group.protocol,
				group.count,
				subject,
				fastWriterMessages[subject],
			))
		}
	}
	return findings
}

type gatewayPublishGroup struct {
	protocol string
	kind     string
	count    float64
}

func findingsForGatewayToBridgeFlow(gateway parsedMetricSet, bridge parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.GatewayBridgeMinPublish <= 0 {
		return nil
	}
	gatewayPublishes := gatewayPublishGroups(gateway.samples)
	bridgeWrites := metricValuesByTopic(bridge.samples, "vehicle_bridge_kafka_writes_total", "ok", "vehicle.")
	var findings []string
	for _, group := range gatewayPublishes {
		if group.count < thresholds.GatewayBridgeMinPublish {
			continue
		}
		topic, ok := expectedKafkaTopicForGatewayPublish(group.protocol, group.kind)
		if !ok {
			findings = append(findings, fmt.Sprintf(
				"gateway %s publish topic mapping missing for protocol %s: gateway ok %.0f",
				group.kind,
				group.protocol,
				group.count,
			))
			continue
		}
		if bridgeWrites[topic] <= 0 {
			findings = append(findings, fmt.Sprintf(
				"bridge writes missing for gateway %s publish protocol %s: gateway ok %.0f, expected topic %s, bridge writes ok %.0f",
				group.kind,
				group.protocol,
				group.count,
				topic,
				bridgeWrites[topic],
			))
		}
	}
	return findings
}

func gatewayPublishGroups(samples []metricSample) []gatewayPublishGroup {
	groups := map[string]*gatewayPublishGroup{}
	for _, sample := range samples {
		if sample.name != "vehicle_gateway_publish_total" || (sample.labels["status"] != "ok" && sample.labels["status"] != "delegated") {
			continue
		}
		protocol := strings.TrimSpace(sample.labels["protocol"])
		if protocol == "" {
			protocol = "unknown"
		}
		kind := strings.TrimSpace(sample.labels["kind"])
		if kind == "" {
			kind = "unknown"
		}
		key := protocol + "\x00" + kind
		group := groups[key]
		if group == nil {
			group = &gatewayPublishGroup{protocol: protocol, kind: kind}
			groups[key] = group
		}
		group.count += sample.value
	}
	var keys []string
	for key := range groups {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	out := make([]gatewayPublishGroup, 0, len(keys))
	for _, key := range keys {
		out = append(out, *groups[key])
	}
	return out
}

func expectedKafkaTopicForGatewayPublish(protocol string, kind string) (string, bool) {
	switch kind {
	case "raw":
		switch envelope.Protocol(protocol) {
		case envelope.ProtocolGB32960:
			return topics.RawGB32960, true
		case envelope.ProtocolJT808:
			return topics.RawJT808, true
		case envelope.ProtocolYutongMQTT:
			return topics.RawYutongMQTT, true
		default:
			return "", false
		}
	case "fields":
		switch envelope.Protocol(protocol) {
		case envelope.ProtocolGB32960:
			return topics.FieldsGB32960, true
		case envelope.ProtocolJT808:
			return topics.FieldsJT808, true
		case envelope.ProtocolYutongMQTT:
			return topics.FieldsYutongMQTT, true
		default:
			return "", false
		}
	default:
		return "", false
	}
}

func findingsForBridgeRawFanout(bridge parsedMetricSet, history parsedMetricSet, realtime parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.RawFanoutMinBridge <= 0 {
		return nil
	}
	bridgeWrites := metricValuesByTopic(bridge.samples, "vehicle_bridge_kafka_writes_total", "ok", "vehicle.raw.go.")
	historyConsumers := metricValuesByTopic(history.samples, "vehicle_kafka_consumer_info", "", "vehicle.raw.go.")
	historyReceived := metricValuesByTopic(history.samples, "vehicle_history_kafka_messages_total", "received", "vehicle.raw.go.")
	historyWrites := metricValuesByTopic(history.samples, "vehicle_history_writes_total", "ok", "vehicle.raw.go.")
	realtimeConsumers := metricValuesByTopic(realtime.samples, "vehicle_kafka_consumer_info", "", "vehicle.raw.go.")
	realtimeReceived := metricValuesByTopic(realtime.samples, "vehicle_realtime_kafka_messages_total", "received", "vehicle.raw.go.")
	realtimeUpdates := metricValuesByTopic(realtime.samples, "vehicle_realtime_updates_total", "ok", "vehicle.raw.go.")
	var topics []string
	for topic := range bridgeWrites {
		topics = append(topics, topic)
	}
	sort.Strings(topics)
	var findings []string
	for _, topic := range topics {
		writes := bridgeWrites[topic]
		if writes < thresholds.RawFanoutMinBridge {
			continue
		}
		if historyConsumers[topic] <= 0 {
			findings = append(findings, fmt.Sprintf("history consumer topic missing for %s: bridge raw writes ok %.0f", topic, writes))
		} else if historyReceived[topic] <= 0 {
			findings = append(findings, fmt.Sprintf("history messages missing for %s: bridge raw writes ok %.0f, history received %.0f", topic, writes, historyReceived[topic]))
		} else if historyWrites[topic] <= 0 {
			findings = append(findings, fmt.Sprintf("history writes missing for %s: bridge raw writes ok %.0f, history received %.0f, history writes ok %.0f", topic, writes, historyReceived[topic], historyWrites[topic]))
		}
		if realtimeConsumers[topic] <= 0 {
			findings = append(findings, fmt.Sprintf("realtime consumer topic missing for %s: bridge raw writes ok %.0f", topic, writes))
		} else if realtimeReceived[topic] <= 0 {
			findings = append(findings, fmt.Sprintf("realtime messages missing for %s: bridge raw writes ok %.0f, realtime received %.0f", topic, writes, realtimeReceived[topic]))
		} else if realtimeUpdates[topic] <= 0 {
			findings = append(findings, fmt.Sprintf("realtime updates missing for %s: bridge raw writes ok %.0f, realtime received %.0f, realtime updates ok %.0f", topic, writes, realtimeReceived[topic], realtimeUpdates[topic]))
		}
	}
	return findings
}

func findingsForBridgeToStatFieldFlow(bridge parsedMetricSet, stat parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.StatTopicMinBridge <= 0 {
		return nil
	}
	bridgeWrites := metricValuesByTopic(bridge.samples, "vehicle_bridge_kafka_writes_total", "ok", "vehicle.fields.go.")
	statReceived := metricValuesByTopic(stat.samples, "vehicle_stat_kafka_messages_total", "received", "vehicle.fields.go.")
	statConsumers := metricValuesByTopic(stat.samples, "vehicle_kafka_consumer_info", "", "vehicle.fields.go.")
	var topics []string
	for topic := range bridgeWrites {
		topics = append(topics, topic)
	}
	sort.Strings(topics)
	var findings []string
	for _, topic := range topics {
		writes := bridgeWrites[topic]
		if writes < thresholds.StatTopicMinBridge {
			continue
		}
		if statConsumers[topic] <= 0 {
			findings = append(findings, fmt.Sprintf("stat consumer topic missing for %s: bridge writes ok %.0f", topic, writes))
			continue
		}
		if statReceived[topic] <= 0 {
			findings = append(findings, fmt.Sprintf("stat messages missing for %s: bridge writes ok %.0f, stat received %.0f", topic, writes, statReceived[topic]))
		}
	}
	return findings
}

func findingsForRequiredConsumerTopics(parsedByService map[string]parsedMetricSet, thresholds Thresholds) []string {
	if len(thresholds.RequiredConsumerTopics) == 0 {
		return nil
	}
	var services []string
	for service := range thresholds.RequiredConsumerTopics {
		services = append(services, service)
	}
	sort.Strings(services)
	var findings []string
	for _, service := range services {
		required := normalizeTopicSet(thresholds.RequiredConsumerTopics[service])
		if len(required) == 0 {
			continue
		}
		seen := kafkaConsumerTopicSet(parsedByService[service])
		var topics []string
		for topic := range required {
			topics = append(topics, topic)
		}
		sort.Strings(topics)
		for _, topic := range topics {
			if seen[topic] > 0 {
				continue
			}
			findings = append(findings, fmt.Sprintf("%s required kafka consumer topic missing: %s", service, topic))
		}
	}
	return findings
}

func normalizeTopicSet(values []string) map[string]struct{} {
	out := map[string]struct{}{}
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		out[value] = struct{}{}
	}
	return out
}

func kafkaConsumerTopicSet(parsed parsedMetricSet) map[string]float64 {
	out := map[string]float64{}
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_kafka_consumer_info" || sample.value <= 0 {
			continue
		}
		topic := strings.TrimSpace(sample.labels["topic"])
		if topic == "" {
			continue
		}
		out[topic] += sample.value
	}
	return out
}

func metricValuesByTopic(samples []metricSample, metricName string, status string, topicPrefix string) map[string]float64 {
	out := map[string]float64{}
	for _, sample := range samples {
		if sample.name != metricName {
			continue
		}
		if status != "" && sample.labels["status"] != status {
			continue
		}
		topic := strings.TrimSpace(sample.labels["topic"])
		if topic == "" || (topicPrefix != "" && !strings.HasPrefix(topic, topicPrefix)) {
			continue
		}
		out[topic] += sample.value
	}
	return out
}

func metricValuesBySubject(samples []metricSample, metricName string, status string, subjectPrefix string) map[string]float64 {
	out := map[string]float64{}
	for _, sample := range samples {
		if sample.name != metricName {
			continue
		}
		if status != "" && sample.labels["status"] != status {
			continue
		}
		subject := strings.TrimSpace(sample.labels["subject"])
		if subject == "" || (subjectPrefix != "" && !strings.HasPrefix(subject, subjectPrefix)) {
			continue
		}
		out[subject] += sample.value
	}
	return out
}

func findingsForLastActivity(parsedByService map[string]parsedMetricSet, thresholds Thresholds, checkedAt time.Time) []string {
	if thresholds.LastActivityStaleSec <= 0 {
		return nil
	}
	specs := []lastActivitySpec{
		{service: "gateway", metric: "vehicle_gateway_last_frame_unix_seconds", label: "gateway last frame", status: "OK"},
		{service: "gateway", metric: "vehicle_gateway_last_publish_unix_seconds", label: "gateway last publish", status: "ok"},
		{service: "bridge", metric: "vehicle_bridge_last_message_unix_seconds", label: "bridge last message", status: "received"},
		{service: "bridge", metric: "vehicle_bridge_last_kafka_write_unix_seconds", label: "bridge last kafka write", status: "ok"},
		{service: "bridge", metric: "vehicle_bridge_last_ack_unix_seconds", label: "bridge last ack", status: "ok"},
		{service: "fast-writer", metric: "vehicle_fast_writer_last_message_unix_seconds", label: "fast writer last message", status: "received"},
		{service: "fast-writer", metric: "vehicle_fast_writer_last_stage_unix_seconds", label: "fast writer last stage", status: "ok"},
		{service: "history", metric: "vehicle_history_last_message_unix_seconds", label: "history last message", status: "received"},
		{service: "history", metric: "vehicle_history_last_write_unix_seconds", label: "history last write", status: "ok"},
		{service: "history", metric: "vehicle_history_last_commit_unix_seconds", label: "history last commit", status: "ok"},
		{service: "stat", metric: "vehicle_stat_last_message_unix_seconds", label: "stat last message", status: "received"},
		{service: "stat", metric: "vehicle_stat_last_write_unix_seconds", label: "stat last write", status: "ok"},
		{service: "stat", metric: "vehicle_stat_last_commit_unix_seconds", label: "stat last commit", status: "ok"},
		{service: "realtime", metric: "vehicle_realtime_last_message_unix_seconds", label: "realtime last message", status: "received"},
		{service: "realtime", metric: "vehicle_realtime_last_update_unix_seconds", label: "realtime last update", status: "ok"},
		{service: "realtime", metric: "vehicle_realtime_last_store_update_unix_seconds", label: "realtime last store update", status: "ok"},
		{service: "realtime", metric: "vehicle_realtime_last_commit_unix_seconds", label: "realtime last commit", status: "ok"},
		{service: "identity", metric: "vehicle_identity_writer_last_message_unix_seconds", label: "identity last message", status: "received"},
		{service: "identity", metric: "vehicle_identity_writer_last_write_unix_seconds", label: "identity last write", status: "ok"},
		{service: "identity", metric: "vehicle_identity_writer_last_commit_unix_seconds", label: "identity last commit", status: "ok"},
	}
	var findings []string
	for _, spec := range specs {
		findings = append(findings, staleActivityFindings(parsedByService[spec.service], spec, thresholds.LastActivityStaleSec, checkedAt)...)
	}
	sort.Strings(findings)
	return findings
}

type lastActivitySpec struct {
	service string
	metric  string
	label   string
	status  string
}

func staleActivityFindings(parsed parsedMetricSet, spec lastActivitySpec, thresholdSec float64, checkedAt time.Time) []string {
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != spec.metric {
			continue
		}
		if spec.status != "" && sample.labels["status"] != spec.status {
			continue
		}
		if sample.value <= 0 {
			continue
		}
		if isRollupLastActivity(sample) {
			continue
		}
		ageSec := checkedAt.Sub(time.Unix(int64(sample.value), 0).UTC()).Seconds()
		if ageSec <= thresholdSec {
			continue
		}
		findings = append(findings, fmt.Sprintf("%s stale %.0fs exceeds %.0fs (%s)", spec.label, ageSec, thresholdSec, metricLabelsKey(sample.labels)))
	}
	return findings
}

func isRollupLastActivity(sample metricSample) bool {
	for _, label := range []string{"subject", "topic"} {
		switch strings.TrimSpace(sample.labels[label]) {
		case "mixed", "unknown":
			return true
		}
	}
	return false
}

type statSampleCounters struct {
	writesOK                  float64
	found                     float64
	written                   float64
	skippedMissingFields      float64
	skippedMissingVIN         float64
	skippedMissingMileage     float64
	skippedNonMileageFrame    float64
	skippedNonPositiveMileage float64
	skippedMissingTime        float64
	futureAdjusted            float64
	skippedSameMileage        float64
	skippedMissingSource      float64
}

type statSourceCounters struct {
	found                  float64
	attempted              float64
	written                float64
	skippedThrottled       float64
	skippedMissingEndpoint float64
	skippedUnmanaged       float64
}

type statProjectionCounters struct {
	samplesWritten   float64
	attempted        float64
	written          float64
	skippedThrottled float64
}

type protocolFlowStatCounters struct {
	samples     statSampleCounters
	sources     statSourceCounters
	projections statProjectionCounters
}

func buildProtocolFlowDiagnostics(parsedByService map[string]parsedMetricSet, thresholds Thresholds) *ProtocolFlowDiagnostics {
	gatewayGroups := gatewayFieldFlowGroups(parsedByService["gateway"])
	identityDiagnostics := buildIdentityResolutionDiagnostics(parsedByService["gateway"])
	identityByProtocol := map[string]IdentityProtocolResolution{}
	if identityDiagnostics != nil {
		for _, row := range identityDiagnostics.Protocols {
			identityByProtocol[row.Protocol] = row
		}
	}
	statGroups := protocolFlowStatGroups(parsedByService["stat"])

	protocolSet := map[string]struct{}{}
	for protocol := range gatewayGroups {
		protocolSet[protocol] = struct{}{}
	}
	for protocol := range identityByProtocol {
		protocolSet[protocol] = struct{}{}
	}
	for protocol := range statGroups {
		protocolSet[protocol] = struct{}{}
	}
	if len(protocolSet) == 0 {
		return nil
	}
	protocols := make([]string, 0, len(protocolSet))
	for protocol := range protocolSet {
		protocols = append(protocols, protocol)
	}
	sort.Strings(protocols)

	out := &ProtocolFlowDiagnostics{Protocols: make([]ProtocolFlowProtocol, 0, len(protocols))}
	for _, protocol := range protocols {
		gatewayGroup := gatewayGroups[protocol]
		statGroup := statGroups[protocol]
		identityRow := identityByProtocol[protocol]
		row := ProtocolFlowProtocol{Protocol: protocol}
		if gatewayGroup != nil {
			row.RawOK = gatewayGroup.rawOK
			row.RealtimeCandidates = gatewayRealtimeCandidates(gatewayGroup.gatewayPublishCounters)
			row.FieldsPublished = gatewayGroup.fieldsPublished
			if gatewayGroup.hasLatestCount {
				value := gatewayGroup.latestCount
				row.LatestFieldCount = &value
			}
		}
		row.IdentityTotal = identityRow.Total
		row.IdentitySkipped = identityRow.SkippedCount
		row.IdentityResolved = identityRow.ResolvedCount
		row.IdentityNonResolved = identityRow.NonResolvedCount
		row.IdentityNonResolvedRatio = identityRow.NonResolvedRatio
		if statGroup != nil {
			row.StatWritesOK = statGroup.samples.writesOK
			row.StatMileageCandidateWrites = statMileageCandidateWrites(statGroup.samples)
			row.StatSamplesFound = statGroup.samples.found
			row.StatSamplesWritten = statGroup.samples.written
			row.StatSamplesActionableSkipped = statActionableSkips(statGroup.samples)
			row.StatSkippedMissingFields = statGroup.samples.skippedMissingFields
			row.StatSkippedMissingVIN = statGroup.samples.skippedMissingVIN
			row.StatSkippedMissingMileage = statGroup.samples.skippedMissingMileage
			row.StatSkippedNonMileageFrame = statGroup.samples.skippedNonMileageFrame
			row.StatSkippedNonPositiveMileage = statGroup.samples.skippedNonPositiveMileage
			row.StatSkippedMissingTime = statGroup.samples.skippedMissingTime
			row.StatEventTimeFutureAdjusted = statGroup.samples.futureAdjusted
			row.StatSkippedMissingSource = statGroup.samples.skippedMissingSource
			row.StatSkippedSameMileage = statGroup.samples.skippedSameMileage
			row.StatSourceWritten = statGroup.sources.written
			row.StatSourceThrottled = statGroup.sources.skippedThrottled
			row.StatSourceMissingEndpoint = statGroup.sources.skippedMissingEndpoint
			row.StatSourceSkippedUnmanaged = statGroup.sources.skippedUnmanaged
			row.StatProjectionAttempted = statGroup.projections.attempted
			row.StatProjectionWritten = statGroup.projections.written
			row.StatProjectionThrottled = statGroup.projections.skippedThrottled
		}
		row.Status, row.Reason = protocolFlowStatus(gatewayGroup, identityRow, statGroup, thresholds)
		out.Protocols = append(out.Protocols, row)
	}
	return out
}

func protocolFlowStatGroups(parsed parsedMetricSet) map[string]*protocolFlowStatCounters {
	groups := map[string]*protocolFlowStatCounters{}
	groupForSample := func(sample metricSample) *protocolFlowStatCounters {
		protocol := protocolForFieldsMetricSample(sample)
		group := groups[protocol]
		if group == nil {
			group = &protocolFlowStatCounters{}
			groups[protocol] = group
		}
		return group
	}
	for _, sample := range parsed.samples {
		switch sample.name {
		case "vehicle_stat_writes_total":
			if sample.labels["status"] == "ok" {
				groupForSample(sample).samples.writesOK += sample.value
			}
		case "vehicle_stat_samples_total":
			group := groupForSample(sample)
			switch sample.labels["status"] {
			case "found":
				group.samples.found += sample.value
				group.sources.found += sample.value
			case "written":
				group.samples.written += sample.value
				group.projections.samplesWritten += sample.value
			case "skipped_missing_fields":
				group.samples.skippedMissingFields += sample.value
			case "skipped_missing_vin":
				group.samples.skippedMissingVIN += sample.value
			case "skipped_missing_mileage":
				group.samples.skippedMissingMileage += sample.value
			case "skipped_non_mileage_frame":
				group.samples.skippedNonMileageFrame += sample.value
			case "skipped_non_positive_mileage":
				group.samples.skippedNonPositiveMileage += sample.value
			case "skipped_missing_time":
				group.samples.skippedMissingTime += sample.value
			case "event_time_future_adjusted":
				group.samples.futureAdjusted += sample.value
			case "skipped_same_mileage":
				group.samples.skippedSameMileage += sample.value
			case "skipped_missing_source":
				group.samples.skippedMissingSource += sample.value
			}
		case "vehicle_stat_sources_total":
			group := groupForSample(sample)
			switch sample.labels["status"] {
			case "attempted":
				group.sources.attempted += sample.value
			case "written":
				group.sources.written += sample.value
			case "skipped_throttled":
				group.sources.skippedThrottled += sample.value
			case "skipped_missing_endpoint":
				group.sources.skippedMissingEndpoint += sample.value
			case "skipped_unmanaged":
				group.sources.skippedUnmanaged += sample.value
			}
		case "vehicle_stat_projections_total":
			group := groupForSample(sample)
			switch sample.labels["status"] {
			case "attempted":
				group.projections.attempted += sample.value
			case "written":
				group.projections.written += sample.value
			case "skipped_throttled":
				group.projections.skippedThrottled += sample.value
			}
		}
	}
	return groups
}

func protocolForFieldsMetricSample(sample metricSample) string {
	if protocol := strings.TrimSpace(sample.labels["protocol"]); protocol != "" {
		return protocol
	}
	if protocol, ok := topics.ProtocolForKnownFieldsTopic(sample.labels["topic"]); ok {
		return protocol
	}
	return "unknown"
}

func statActionableSkips(group statSampleCounters) float64 {
	return group.skippedMissingFields +
		group.skippedMissingVIN +
		group.skippedMissingMileage +
		group.skippedNonPositiveMileage +
		group.skippedMissingTime +
		group.skippedMissingSource
}

func statOnlyMissingMileageSkips(group statSampleCounters) bool {
	return group.skippedMissingMileage > 0 &&
		group.skippedMissingFields == 0 &&
		group.skippedMissingVIN == 0 &&
		group.skippedNonPositiveMileage == 0 &&
		group.skippedMissingTime == 0 &&
		group.skippedMissingSource == 0
}

func statMileageCandidateWrites(group statSampleCounters) float64 {
	candidates := group.writesOK - group.skippedNonMileageFrame
	if candidates < 0 {
		return 0
	}
	return candidates
}

func protocolFlowStatus(gatewayGroup *gatewayFieldFlowGroup, identityRow IdentityProtocolResolution, statGroup *protocolFlowStatCounters, thresholds Thresholds) (string, string) {
	if gatewayGroup != nil {
		realtimeCandidates := gatewayRealtimeCandidates(gatewayGroup.gatewayPublishCounters)
		status, reason := gatewayFieldDiagnosticsStatus(gatewayGroup, realtimeCandidates, thresholds)
		if status != "ok" {
			return status, "gateway_fields_" + reason
		}
	}
	if thresholds.GatewayIdentityMinSamples > 0 &&
		thresholds.GatewayIdentityMaxUnresolvedRatio > 0 &&
		float64(identityRow.Total) >= thresholds.GatewayIdentityMinSamples &&
		identityRow.NonResolvedRatio > thresholds.GatewayIdentityMaxUnresolvedRatio {
		return "degraded", "identity_unresolved_ratio"
	}
	if statGroup == nil {
		return "ok", ""
	}
	if thresholds.StatSampleMinWrites > 0 && statMileageCandidateWrites(statGroup.samples) >= thresholds.StatSampleMinWrites {
		if thresholds.StatSampleFutureAdjustmentMax >= 0 &&
			statGroup.samples.futureAdjusted > thresholds.StatSampleFutureAdjustmentMax {
			return "degraded", "stat_future_event_time_adjusted"
		}
		if statGroup.samples.found <= 0 {
			if statActionableSkips(statGroup.samples) <= 0 && statGroup.samples.skippedNonMileageFrame > 0 {
				return "ok", ""
			}
			if statOnlyMissingMileageSkips(statGroup.samples) {
				return "ok", ""
			}
			return "degraded", "stat_samples_missing"
		}
		if thresholds.StatSampleMaxActionableSkipRatio > 0 {
			ratio := statActionableSkips(statGroup.samples) / statMileageCandidateWrites(statGroup.samples)
			if ratio > thresholds.StatSampleMaxActionableSkipRatio {
				return "degraded", "stat_actionable_skip_ratio"
			}
		}
		if statGroup.samples.written <= 0 && statGroup.samples.skippedMissingSource > 0 {
			return "degraded", "stat_samples_missing_source"
		}
	}
	if thresholds.StatSourceMinSamples > 0 && statGroup.sources.found >= thresholds.StatSourceMinSamples {
		activeSourceTouches := statGroup.sources.written + statGroup.sources.skippedThrottled + statGroup.sources.skippedUnmanaged
		if activeSourceTouches <= 0 && statGroup.sources.skippedMissingEndpoint <= 0 {
			return "degraded", "stat_source_tracking_missing"
		}
		if thresholds.StatSourceMaxMissingRatio > 0 {
			ratio := statGroup.sources.skippedMissingEndpoint / statGroup.sources.found
			if ratio > thresholds.StatSourceMaxMissingRatio {
				return "degraded", "stat_source_missing_endpoint_ratio"
			}
		}
	}
	if thresholds.StatProjectionMinSampleWrites > 0 &&
		statGroup.projections.samplesWritten >= thresholds.StatProjectionMinSampleWrites &&
		statGroup.projections.written <= 0 {
		return "degraded", "stat_projection_missing"
	}
	return "ok", ""
}

func findingsForStatProjections(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.StatProjectionMinSampleWrites <= 0 {
		return nil
	}
	groups := map[string]*statProjectionCounters{}
	for _, sample := range parsed.samples {
		switch sample.name {
		case "vehicle_stat_samples_total":
			if sample.labels["status"] == "written" {
				statProjectionGroup(groups, sample.labels["topic"]).samplesWritten += sample.value
			}
		case "vehicle_stat_projections_total":
			group := statProjectionGroup(groups, sample.labels["topic"])
			switch sample.labels["status"] {
			case "attempted":
				group.attempted += sample.value
			case "written":
				group.written += sample.value
			case "skipped_throttled":
				group.skippedThrottled += sample.value
			}
		}
	}
	var topics []string
	for topic := range groups {
		topics = append(topics, topic)
	}
	sort.Strings(topics)
	var findings []string
	for _, topic := range topics {
		group := groups[topic]
		if group.samplesWritten < thresholds.StatProjectionMinSampleWrites {
			continue
		}
		if group.written > 0 {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"stat final projection missing for %s: samples written %.0f, projections attempted %.0f, written %.0f, skipped throttled %.0f",
			topic,
			group.samplesWritten,
			group.attempted,
			group.written,
			group.skippedThrottled,
		))
	}
	return findings
}

func statProjectionGroup(groups map[string]*statProjectionCounters, topic string) *statProjectionCounters {
	topic = strings.TrimSpace(topic)
	if topic == "" {
		topic = "unknown"
	}
	group := groups[topic]
	if group == nil {
		group = &statProjectionCounters{}
		groups[topic] = group
	}
	return group
}

func findingsForStatCache(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.StatCacheEntriesMax <= 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_stat_cache_entries" || sample.value <= thresholds.StatCacheEntriesMax {
			continue
		}
		cache := strings.TrimSpace(sample.labels["cache"])
		if cache == "" {
			cache = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"stat cache entries %.0f exceeds %.0f for cache %s",
			sample.value,
			thresholds.StatCacheEntriesMax,
			cache,
		))
	}
	sort.Strings(findings)
	return findings
}

type realtimeThrottleCounters struct {
	passed             float64
	skippedInterval    float64
	skippedNonRealtime float64
	skippedMissingVIN  float64
}

func findingsForRealtimeThrottle(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.RealtimeThrottleMinSamples <= 0 {
		return nil
	}
	groups := map[string]*realtimeThrottleCounters{}
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_realtime_throttle_total" {
			continue
		}
		group := realtimeThrottleGroup(groups, sample.labels["store"], sample.labels["protocol"])
		switch sample.labels["status"] {
		case "passed":
			group.passed += sample.value
		case "skipped_interval":
			group.skippedInterval += sample.value
		case "skipped_non_realtime":
			group.skippedNonRealtime += sample.value
		case "skipped_missing_vin":
			group.skippedMissingVIN += sample.value
		}
	}
	var keys []string
	for key := range groups {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	var findings []string
	for _, key := range keys {
		group := groups[key]
		total := group.passed + group.skippedInterval + group.skippedNonRealtime + group.skippedMissingVIN
		if total < thresholds.RealtimeThrottleMinSamples || group.skippedInterval <= 0 || group.passed > 0 {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"realtime throttle passing updates missing for %s: total %.0f, passed %.0f, skipped interval %.0f, skipped non-realtime %.0f, skipped missing vin %.0f",
			key,
			total,
			group.passed,
			group.skippedInterval,
			group.skippedNonRealtime,
			group.skippedMissingVIN,
		))
	}
	return findings
}

func findingsForRealtimeThrottleCache(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.RealtimeThrottleCacheEntriesMax <= 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_realtime_throttle_cache_entries" || sample.value <= thresholds.RealtimeThrottleCacheEntriesMax {
			continue
		}
		store := strings.TrimSpace(sample.labels["store"])
		if store == "" {
			store = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"realtime throttle cache entries %.0f exceeds %.0f for store %s",
			sample.value,
			thresholds.RealtimeThrottleCacheEntriesMax,
			store,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForRealtimePlateCache(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.RealtimePlateCacheEntriesMax <= 0 {
		return nil
	}
	value := parsed.totals["vehicle_realtime_plate_cache_entries"]
	if value <= thresholds.RealtimePlateCacheEntriesMax {
		return nil
	}
	return []string{fmt.Sprintf(
		"realtime plate cache entries %.0f exceeds %.0f",
		value,
		thresholds.RealtimePlateCacheEntriesMax,
	)}
}

func realtimeThrottleGroup(groups map[string]*realtimeThrottleCounters, store string, protocol string) *realtimeThrottleCounters {
	store = strings.TrimSpace(store)
	if store == "" {
		store = "unknown"
	}
	protocol = strings.TrimSpace(protocol)
	if protocol == "" {
		protocol = "unknown"
	}
	key := "store=" + store + ",protocol=" + protocol
	group := groups[key]
	if group == nil {
		group = &realtimeThrottleCounters{}
		groups[key] = group
	}
	return group
}

type fastWriterRedisFieldCounters struct {
	seen         float64
	written      float64
	skippedStale float64
}

type fastWriterRedisEnvelopeCounters struct {
	seen                     float64
	updated                  float64
	skippedNonRealtime       float64
	skippedMissingVIN        float64
	skippedMissingVehicleKey float64
	skippedMissingFields     float64
}

type fastWriterMessageCounters struct {
	received    float64
	ok          float64
	invalidJSON float64
}

func findingsForFastWriterMessageCompletion(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.FastWriterMessageMinReceived <= 0 || thresholds.FastWriterMessageMaxUnackedRatio <= 0 {
		return nil
	}
	groups := map[string]*fastWriterMessageCounters{}
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_fast_writer_messages_total" {
			continue
		}
		subject := strings.TrimSpace(sample.labels["subject"])
		if subject == "" || subject == "mixed" || subject == "unknown" {
			continue
		}
		group := groups[subject]
		if group == nil {
			group = &fastWriterMessageCounters{}
			groups[subject] = group
		}
		switch sample.labels["status"] {
		case "received":
			group.received += sample.value
		case "ok":
			group.ok += sample.value
		case "invalid_json":
			group.invalidJSON += sample.value
		}
	}
	var subjects []string
	for subject := range groups {
		subjects = append(subjects, subject)
	}
	sort.Strings(subjects)
	var findings []string
	for _, subject := range subjects {
		group := groups[subject]
		if group.received < thresholds.FastWriterMessageMinReceived {
			continue
		}
		completed := group.ok + group.invalidJSON
		unacked := group.received - completed
		if unacked < 0 {
			unacked = 0
		}
		ratio := unacked / group.received
		if ratio <= thresholds.FastWriterMessageMaxUnackedRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"fast writer unacked message ratio %.2f exceeds %.2f for subject %s: received %.0f, ok %.0f, invalid_json %.0f, unacked %.0f",
			ratio,
			thresholds.FastWriterMessageMaxUnackedRatio,
			subject,
			group.received,
			group.ok,
			group.invalidJSON,
			unacked,
		))
	}
	return findings
}

func findingsForFastWriterRedisEnvelopes(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.FastWriterRedisEnvelopeMinSeen <= 0 {
		return nil
	}
	groups := map[string]*fastWriterRedisEnvelopeCounters{}
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_fast_writer_redis_envelopes_total" {
			continue
		}
		subject := strings.TrimSpace(sample.labels["subject"])
		if subject == "" || subject == "mixed" || subject == "unknown" {
			continue
		}
		group := groups[subject]
		if group == nil {
			group = &fastWriterRedisEnvelopeCounters{}
			groups[subject] = group
		}
		switch sample.labels["status"] {
		case "seen":
			group.seen += sample.value
		case "updated":
			group.updated += sample.value
		case "skipped_non_realtime":
			group.skippedNonRealtime += sample.value
		case "skipped_missing_vin":
			group.skippedMissingVIN += sample.value
		case "skipped_missing_vehicle_key":
			group.skippedMissingVehicleKey += sample.value
		case "skipped_missing_fields":
			group.skippedMissingFields += sample.value
		}
	}
	var subjects []string
	for subject := range groups {
		subjects = append(subjects, subject)
	}
	sort.Strings(subjects)
	var findings []string
	for _, subject := range subjects {
		group := groups[subject]
		if group.seen < thresholds.FastWriterRedisEnvelopeMinSeen {
			continue
		}
		if group.updated <= 0 {
			findings = append(findings, fmt.Sprintf(
				"fast writer redis realtime updates missing for subject %s: envelopes seen %.0f, updated %.0f, skipped non-realtime %.0f, missing vin %.0f, missing vehicle key %.0f, missing parsed fields %.0f",
				subject,
				group.seen,
				group.updated,
				group.skippedNonRealtime,
				group.skippedMissingVIN,
				group.skippedMissingVehicleKey,
				group.skippedMissingFields,
			))
			continue
		}
		if thresholds.FastWriterRedisEnvelopeMaxBadRatio <= 0 {
			continue
		}
		actionableSkips := group.skippedMissingVIN + group.skippedMissingVehicleKey + group.skippedMissingFields
		ratio := actionableSkips / group.seen
		if ratio <= thresholds.FastWriterRedisEnvelopeMaxBadRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"fast writer redis envelope skip ratio %.2f exceeds %.2f for subject %s: envelopes seen %.0f, updated %.0f, missing vin %.0f, missing vehicle key %.0f, missing parsed fields %.0f",
			ratio,
			thresholds.FastWriterRedisEnvelopeMaxBadRatio,
			subject,
			group.seen,
			group.updated,
			group.skippedMissingVIN,
			group.skippedMissingVehicleKey,
			group.skippedMissingFields,
		))
	}
	return findings
}

func findingsForFastWriterRedisFields(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.FastWriterRedisFieldMinSeen <= 0 || thresholds.FastWriterRedisFieldMaxStaleRatio <= 0 {
		return nil
	}
	groups := map[string]*fastWriterRedisFieldCounters{}
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_fast_writer_redis_fields_total" {
			continue
		}
		subject := strings.TrimSpace(sample.labels["subject"])
		if subject == "" || subject == "mixed" || subject == "unknown" {
			continue
		}
		group := groups[subject]
		if group == nil {
			group = &fastWriterRedisFieldCounters{}
			groups[subject] = group
		}
		switch sample.labels["status"] {
		case "seen":
			group.seen += sample.value
		case "written":
			group.written += sample.value
		case "skipped_stale":
			group.skippedStale += sample.value
		}
	}
	var subjects []string
	for subject := range groups {
		subjects = append(subjects, subject)
	}
	sort.Strings(subjects)
	var findings []string
	for _, subject := range subjects {
		group := groups[subject]
		if group.seen < thresholds.FastWriterRedisFieldMinSeen || group.seen <= 0 {
			continue
		}
		ratio := group.skippedStale / group.seen
		if ratio <= thresholds.FastWriterRedisFieldMaxStaleRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"fast writer redis stale field ratio %.2f exceeds %.2f for subject %s (seen %.0f, written %.0f, skipped_stale %.0f)",
			ratio,
			thresholds.FastWriterRedisFieldMaxStaleRatio,
			subject,
			group.seen,
			group.written,
			group.skippedStale,
		))
	}
	return findings
}

func findingsForHistoryLocationErrors(parsed parsedMetricSet, thresholds Thresholds, checkedAt time.Time) []string {
	if thresholds.HistoryLocationErrorMax < 0 {
		return nil
	}
	lastErrorByTopic := map[string]float64{}
	if thresholds.HistoryLocationErrorRecentSec > 0 {
		for _, sample := range parsed.samples {
			if sample.name != "vehicle_history_last_location_write_unix_seconds" || sample.labels["status"] != "error" || sample.value <= 0 {
				continue
			}
			topic := strings.TrimSpace(sample.labels["topic"])
			if sample.value > lastErrorByTopic[topic] {
				lastErrorByTopic[topic] = sample.value
			}
		}
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_history_location_writes_total" || sample.labels["status"] != "error" {
			continue
		}
		if sample.value <= thresholds.HistoryLocationErrorMax {
			continue
		}
		topic := strings.TrimSpace(sample.labels["topic"])
		if topic == "" {
			topic = "unknown"
		}
		recency := ""
		if thresholds.HistoryLocationErrorRecentSec > 0 {
			if lastError, ok := lastErrorByTopic[topic]; ok {
				ageSec := checkedAt.Sub(time.Unix(int64(lastError), 0).UTC()).Seconds()
				if ageSec < 0 {
					ageSec = 0
				}
				if ageSec > thresholds.HistoryLocationErrorRecentSec {
					continue
				}
				recency = fmt.Sprintf(", last error %.0fs ago", ageSec)
			} else {
				recency = ", last error activity unavailable"
			}
		}
		findings = append(findings, fmt.Sprintf(
			"history location writes error %.0f exceeds %.0f for topic %s%s",
			sample.value,
			thresholds.HistoryLocationErrorMax,
			topic,
			recency,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForHistoryLocationAccounting(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.HistoryLocationAccountingMaxRatio <= 0 {
		return nil
	}
	writesOKByTopic := map[string]float64{}
	locationStatusByTopic := map[string]float64{}
	for _, sample := range parsed.samples {
		topic := strings.TrimSpace(sample.labels["topic"])
		if topic == "" || topic == "mixed" || topic == "unknown" {
			continue
		}
		switch {
		case sample.name == "vehicle_history_writes_total" && sample.labels["status"] == "ok":
			writesOKByTopic[topic] += sample.value
		case sample.name == "vehicle_history_location_writes_total":
			locationStatusByTopic[topic] += sample.value
		}
	}
	var topics []string
	for topic := range writesOKByTopic {
		topics = append(topics, topic)
	}
	sort.Strings(topics)
	var findings []string
	for _, topic := range topics {
		writesOK := writesOKByTopic[topic]
		if writesOK < thresholds.KafkaConsumerMessageMinReceived {
			continue
		}
		accounted := locationStatusByTopic[topic]
		diff := math.Abs(writesOK - accounted)
		ratio := diff / writesOK
		if ratio <= thresholds.HistoryLocationAccountingMaxRatio {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"history location accounting mismatch ratio %.2f exceeds %.2f for topic %s: history writes ok %.0f, location statuses %.0f, diff %.0f",
			ratio,
			thresholds.HistoryLocationAccountingMaxRatio,
			topic,
			writesOK,
			accounted,
			diff,
		))
	}
	return findings
}

func findingsForHistoryFallbackErrors(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.HistoryFallbackErrorMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_history_batch_fallback_total" || sample.labels["status"] != "error" {
			continue
		}
		if sample.value <= thresholds.HistoryFallbackErrorMax {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"history batch fallback errors %.0f exceeds %.0f",
			sample.value,
			thresholds.HistoryFallbackErrorMax,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForFastWriterFallbackErrors(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.FastWriterFallbackErrorMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_fast_writer_batch_fallback_total" || sample.labels["status"] != "error" {
			continue
		}
		if sample.value <= thresholds.FastWriterFallbackErrorMax {
			continue
		}
		stage := strings.TrimSpace(sample.labels["stage"])
		if stage == "" {
			stage = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"fast writer batch fallback errors %.0f exceeds %.0f for stage %s",
			sample.value,
			thresholds.FastWriterFallbackErrorMax,
			stage,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForFastWriterDecoupledUpdateErrors(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.FastWriterDecoupledUpdateErrorMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_fast_writer_decoupled_updates_total" || sample.labels["status"] != "error" {
			continue
		}
		if sample.value <= thresholds.FastWriterDecoupledUpdateErrorMax {
			continue
		}
		reason := strings.TrimSpace(sample.labels["reason"])
		if reason == "" {
			reason = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"fast writer decoupled update errors %.0f exceeds %.0f for reason %s",
			sample.value,
			thresholds.FastWriterDecoupledUpdateErrorMax,
			reason,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForWriteRetries(parsed parsedMetricSet, thresholds Thresholds) []string {
	var findings []string
	findings = append(findings, writeRetryFindings(parsed, "vehicle_history_write_retries_total", "history", thresholds.HistoryRetryMax, thresholds.HistoryRetryExhaustedMax)...)
	findings = append(findings, writeRetryFindings(parsed, "vehicle_stat_write_retries_total", "stat", thresholds.StatRetryMax, thresholds.StatRetryExhaustedMax)...)
	sort.Strings(findings)
	return findings
}

func writeRetryFindings(parsed parsedMetricSet, metricName string, serviceLabel string, retryMax float64, exhaustedMax float64) []string {
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != metricName {
			continue
		}
		status := strings.TrimSpace(sample.labels["status"])
		operation := strings.TrimSpace(sample.labels["operation"])
		if operation == "" {
			operation = "unknown"
		}
		switch status {
		case "retry":
			if retryMax < 0 || sample.value <= retryMax {
				continue
			}
			findings = append(findings, fmt.Sprintf(
				"%s write retries %.0f exceeds %.0f for operation %s",
				serviceLabel,
				sample.value,
				retryMax,
				operation,
			))
		case "exhausted":
			if exhaustedMax < 0 || sample.value <= exhaustedMax {
				continue
			}
			findings = append(findings, fmt.Sprintf(
				"%s write retry exhausted %.0f exceeds %.0f for operation %s",
				serviceLabel,
				sample.value,
				exhaustedMax,
				operation,
			))
		}
	}
	return findings
}

func findingsForStatInvalidJSON(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.StatInvalidJSONMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_stat_kafka_messages_total" || sample.labels["status"] != "invalid_json" {
			continue
		}
		if sample.value <= thresholds.StatInvalidJSONMax {
			continue
		}
		topic := strings.TrimSpace(sample.labels["topic"])
		if topic == "" {
			topic = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"stat invalid json %.0f exceeds %.0f for topic %s",
			sample.value,
			thresholds.StatInvalidJSONMax,
			topic,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForStatProtocolTopicMismatches(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.StatProtocolTopicMismatchMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_stat_kafka_messages_total" || sample.labels["status"] != "protocol_topic_mismatch" {
			continue
		}
		if sample.value <= thresholds.StatProtocolTopicMismatchMax {
			continue
		}
		topic := strings.TrimSpace(sample.labels["topic"])
		if topic == "" {
			topic = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"stat protocol topic mismatches %.0f exceeds %.0f for topic %s",
			sample.value,
			thresholds.StatProtocolTopicMismatchMax,
			topic,
		))
	}
	findings = append(findings, findingsForStatMetricProtocolTopicMismatches(parsed, thresholds)...)
	sort.Strings(findings)
	return findings
}

func findingsForStatMetricProtocolTopicMismatches(parsed parsedMetricSet, thresholds Thresholds) []string {
	var findings []string
	for _, sample := range parsed.samples {
		switch sample.name {
		case "vehicle_stat_samples_total", "vehicle_stat_sources_total", "vehicle_stat_projections_total":
		default:
			continue
		}
		topic := strings.TrimSpace(sample.labels["topic"])
		expectedProtocol, ok := topics.ProtocolForKnownFieldsTopic(topic)
		if !ok {
			continue
		}
		protocol := strings.TrimSpace(sample.labels["protocol"])
		if sample.value <= thresholds.StatProtocolTopicMismatchMax {
			continue
		}
		status := strings.TrimSpace(sample.labels["status"])
		if status == "" {
			status = "unknown"
		}
		if protocol == "" {
			findings = append(findings, fmt.Sprintf(
				"stat metric protocol label missing %.0f exceeds %.0f for %s topic %s (status %s)",
				sample.value,
				thresholds.StatProtocolTopicMismatchMax,
				sample.name,
				topic,
				status,
			))
			continue
		}
		if protocol == expectedProtocol {
			continue
		}
		findings = append(findings, fmt.Sprintf(
			"stat metric protocol topic mismatches %.0f exceeds %.0f for %s topic %s: expected %s, got %s (status %s)",
			sample.value,
			thresholds.StatProtocolTopicMismatchMax,
			sample.name,
			topic,
			expectedProtocol,
			protocol,
			status,
		))
	}
	return findings
}

func findingsForFastWriterInvalidJSON(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.FastWriterInvalidJSONMax < 0 {
		return nil
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != "vehicle_fast_writer_messages_total" || sample.labels["status"] != "invalid_json" {
			continue
		}
		if sample.value <= thresholds.FastWriterInvalidJSONMax {
			continue
		}
		subject := strings.TrimSpace(sample.labels["subject"])
		if subject == "" {
			subject = "unknown"
		}
		findings = append(findings, fmt.Sprintf(
			"fast writer invalid json %.0f exceeds %.0f for subject %s",
			sample.value,
			thresholds.FastWriterInvalidJSONMax,
			subject,
		))
	}
	sort.Strings(findings)
	return findings
}

func findingsForStatSamples(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.StatSampleMinWrites <= 0 {
		return nil
	}
	groups := map[string]*statSampleCounters{}
	for _, sample := range parsed.samples {
		switch sample.name {
		case "vehicle_stat_writes_total":
			if sample.labels["status"] != "ok" {
				continue
			}
			statSampleGroup(groups, sample.labels["topic"]).writesOK += sample.value
		case "vehicle_stat_samples_total":
			group := statSampleGroup(groups, sample.labels["topic"])
			switch sample.labels["status"] {
			case "found":
				group.found += sample.value
			case "written":
				group.written += sample.value
			case "skipped_missing_fields":
				group.skippedMissingFields += sample.value
			case "skipped_missing_vin":
				group.skippedMissingVIN += sample.value
			case "skipped_missing_mileage":
				group.skippedMissingMileage += sample.value
			case "skipped_non_mileage_frame":
				group.skippedNonMileageFrame += sample.value
			case "skipped_non_positive_mileage":
				group.skippedNonPositiveMileage += sample.value
			case "skipped_missing_time":
				group.skippedMissingTime += sample.value
			case "event_time_future_adjusted":
				group.futureAdjusted += sample.value
			case "skipped_same_mileage":
				group.skippedSameMileage += sample.value
			case "skipped_missing_source":
				group.skippedMissingSource += sample.value
			}
		}
	}
	var topics []string
	for topic := range groups {
		topics = append(topics, topic)
	}
	sort.Strings(topics)
	var findings []string
	for _, topic := range topics {
		group := groups[topic]
		mileageCandidates := statMileageCandidateWrites(*group)
		if mileageCandidates < thresholds.StatSampleMinWrites {
			continue
		}
		if thresholds.StatSampleFutureAdjustmentMax >= 0 &&
			group.futureAdjusted > thresholds.StatSampleFutureAdjustmentMax {
			findings = append(findings, fmt.Sprintf(
				"stat future event time adjustments %.0f exceeds %.0f for %s: writes ok %.0f, mileage candidates %.0f, found %.0f, written %.0f",
				group.futureAdjusted,
				thresholds.StatSampleFutureAdjustmentMax,
				topic,
				group.writesOK,
				mileageCandidates,
				group.found,
				group.written,
			))
		}
		actionableSkips := statActionableSkips(*group)
		if group.found <= 0 {
			if actionableSkips <= 0 && group.skippedNonMileageFrame > 0 {
				continue
			}
			if statOnlyMissingMileageSkips(*group) {
				continue
			}
			findings = append(findings, fmt.Sprintf(
				"stat samples missing for %s: writes ok %.0f, mileage candidates %.0f, missing fields %.0f, skipped missing vin %.0f, missing mileage %.0f, non-mileage frames %.0f, non-positive mileage %.0f, missing time %.0f, missing source %.0f",
				topic,
				group.writesOK,
				mileageCandidates,
				group.skippedMissingFields,
				group.skippedMissingVIN,
				group.skippedMissingMileage,
				group.skippedNonMileageFrame,
				group.skippedNonPositiveMileage,
				group.skippedMissingTime,
				group.skippedMissingSource,
			))
			continue
		}
		if thresholds.StatSampleMaxActionableSkipRatio > 0 {
			ratio := actionableSkips / mileageCandidates
			if ratio > thresholds.StatSampleMaxActionableSkipRatio {
				findings = append(findings, fmt.Sprintf(
					"stat actionable skip ratio %.2f exceeds %.2f for %s: writes ok %.0f, mileage candidates %.0f, found %.0f, written %.0f, missing fields %.0f, skipped missing vin %.0f, missing mileage %.0f, non-mileage frames %.0f, non-positive mileage %.0f, missing time %.0f, missing source %.0f, same mileage %.0f",
					ratio,
					thresholds.StatSampleMaxActionableSkipRatio,
					topic,
					group.writesOK,
					mileageCandidates,
					group.found,
					group.written,
					group.skippedMissingFields,
					group.skippedMissingVIN,
					group.skippedMissingMileage,
					group.skippedNonMileageFrame,
					group.skippedNonPositiveMileage,
					group.skippedMissingTime,
					group.skippedMissingSource,
					group.skippedSameMileage,
				))
			}
		}
		if group.written <= 0 && group.skippedMissingSource > 0 {
			findings = append(findings, fmt.Sprintf(
				"stat samples not written for %s: found %.0f, skipped missing source %.0f, same mileage %.0f",
				topic,
				group.found,
				group.skippedMissingSource,
				group.skippedSameMileage,
			))
		}
	}
	return findings
}

func findingsForStatSources(parsed parsedMetricSet, thresholds Thresholds) []string {
	if thresholds.StatSourceMinSamples <= 0 {
		return nil
	}
	groups := map[string]*statSourceCounters{}
	for _, sample := range parsed.samples {
		switch sample.name {
		case "vehicle_stat_samples_total":
			if sample.labels["status"] == "found" {
				statSourceGroup(groups, sample.labels["topic"]).found += sample.value
			}
		case "vehicle_stat_sources_total":
			group := statSourceGroup(groups, sample.labels["topic"])
			switch sample.labels["status"] {
			case "attempted":
				group.attempted += sample.value
			case "written":
				group.written += sample.value
			case "skipped_throttled":
				group.skippedThrottled += sample.value
			case "skipped_missing_endpoint":
				group.skippedMissingEndpoint += sample.value
			case "skipped_unmanaged":
				group.skippedUnmanaged += sample.value
			}
		}
	}
	var topics []string
	for topic := range groups {
		topics = append(topics, topic)
	}
	sort.Strings(topics)
	var findings []string
	for _, topic := range topics {
		group := groups[topic]
		if group.found < thresholds.StatSourceMinSamples || group.found <= 0 {
			continue
		}
		activeSourceTouches := group.written + group.skippedThrottled + group.skippedUnmanaged
		if activeSourceTouches <= 0 && group.skippedMissingEndpoint <= 0 {
			findings = append(findings, fmt.Sprintf(
				"stat source tracking missing for %s: samples found %.0f, source written %.0f, source throttled %.0f, source unmanaged %.0f, missing endpoint %.0f",
				topic,
				group.found,
				group.written,
				group.skippedThrottled,
				group.skippedUnmanaged,
				group.skippedMissingEndpoint,
			))
			continue
		}
		if thresholds.StatSourceMaxMissingRatio <= 0 {
			continue
		}
		ratio := group.skippedMissingEndpoint / group.found
		if ratio > thresholds.StatSourceMaxMissingRatio {
			findings = append(findings, fmt.Sprintf(
				"stat source missing endpoint ratio %.2f exceeds %.2f for %s: samples found %.0f, source written %.0f, source throttled %.0f, source unmanaged %.0f, missing endpoint %.0f",
				ratio,
				thresholds.StatSourceMaxMissingRatio,
				topic,
				group.found,
				group.written,
				group.skippedThrottled,
				group.skippedUnmanaged,
				group.skippedMissingEndpoint,
			))
		}
	}
	return findings
}

func statSampleGroup(groups map[string]*statSampleCounters, topic string) *statSampleCounters {
	topic = strings.TrimSpace(topic)
	if topic == "" {
		topic = "unknown"
	}
	group := groups[topic]
	if group == nil {
		group = &statSampleCounters{}
		groups[topic] = group
	}
	return group
}

func statSourceGroup(groups map[string]*statSourceCounters, topic string) *statSourceCounters {
	topic = strings.TrimSpace(topic)
	if topic == "" {
		topic = "unknown"
	}
	group := groups[topic]
	if group == nil {
		group = &statSourceCounters{}
		groups[topic] = group
	}
	return group
}

func histogramP99Findings(parsed parsedMetricSet, histogramName string, label string, thresholdMS float64, minSamples float64) []string {
	if thresholdMS <= 0 {
		return nil
	}
	groups := histogramGroups(parsed.samples, histogramName)
	var findings []string
	var keys []string
	for key := range groups {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		estimate, count, ok := histogramQuantile(groups[key], 0.99)
		if !ok || count < minSamples || estimate <= thresholdMS {
			continue
		}
		findings = append(findings, fmt.Sprintf("%s p99 %.0fms exceeds %.0fms (%s, samples %.0f)", label, estimate, thresholdMS, key, count))
	}
	return findings
}

func recentP99GaugeFindings(parsed parsedMetricSet, metricName string, samplesMetricName string, label string, thresholdMS float64, minSamples float64) []string {
	if thresholdMS <= 0 {
		return nil
	}
	samplesByKey := map[string]float64{}
	for _, sample := range parsed.samples {
		if sample.name != samplesMetricName {
			continue
		}
		samplesByKey[metricLabelsKey(sample.labels)] = sample.value
	}
	var findings []string
	for _, sample := range parsed.samples {
		if sample.name != metricName {
			continue
		}
		key := metricLabelsKey(sample.labels)
		samples := samplesByKey[key]
		if samples < minSamples || sample.value <= thresholdMS {
			continue
		}
		findings = append(findings, fmt.Sprintf("%s recent p99 %.0fms exceeds %.0fms (%s, samples %.0f)", label, sample.value, thresholdMS, key, samples))
	}
	sort.Strings(findings)
	return findings
}

func parseMetrics(text string) map[string]float64 {
	return parseMetricSet(text).totals
}

type parsedMetricSet struct {
	totals  map[string]float64
	samples []metricSample
}

type metricSample struct {
	name   string
	labels map[string]string
	value  float64
}

func parseMetricSet(text string) parsedMetricSet {
	out := map[string]float64{}
	var samples []metricSample
	scanner := bufio.NewScanner(strings.NewReader(text))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		sample, ok := parseMetricSample(line)
		if !ok {
			continue
		}
		if aggregateMetricByMax(sample.name) {
			if current, ok := out[sample.name]; !ok || sample.value > current {
				out[sample.name] = sample.value
			}
		} else {
			out[sample.name] += sample.value
		}
		samples = append(samples, sample)
	}
	return parsedMetricSet{totals: out, samples: samples}
}

func aggregateMetricByMax(name string) bool {
	return isLastActivityUnixMetric(name) ||
		strings.HasSuffix(name, "_p99_ms")
}

func isLastActivityUnixMetric(name string) bool {
	return strings.Contains(name, "_last_") && strings.HasSuffix(name, "_unix_seconds")
}

func addLastActivityAges(metrics map[string]float64, checkedAt time.Time) {
	if len(metrics) == 0 {
		return
	}
	for name, value := range metrics {
		if !isLastActivityUnixMetric(name) || value <= 0 {
			continue
		}
		age := checkedAt.Sub(time.Unix(int64(value), 0).UTC()).Seconds()
		if age < 0 {
			age = 0
		}
		metrics[strings.TrimSuffix(name, "_unix_seconds")+"_age_seconds"] = age
	}
}

func parseMetricLine(line string) (string, float64, bool) {
	sample, ok := parseMetricSample(line)
	return sample.name, sample.value, ok
}

func parseMetricSample(line string) (metricSample, bool) {
	parts := strings.Fields(line)
	if len(parts) < 2 {
		return metricSample{}, false
	}
	name, labels := parseMetricNameAndLabels(parts[0])
	value, err := strconv.ParseFloat(parts[1], 64)
	if err != nil {
		return metricSample{}, false
	}
	return metricSample{name: name, labels: labels, value: value}, true
}

func parseMetricNameAndLabels(value string) (string, map[string]string) {
	index := strings.IndexByte(value, '{')
	if index < 0 {
		return value, nil
	}
	end := strings.LastIndexByte(value, '}')
	if end < index {
		return value[:index], nil
	}
	return value[:index], parseMetricLabels(value[index+1 : end])
}

func parseMetricLabels(value string) map[string]string {
	labels := map[string]string{}
	for len(value) > 0 {
		value = strings.TrimLeft(value, " \t,")
		if value == "" {
			break
		}
		index := strings.IndexByte(value, '=')
		if index <= 0 {
			break
		}
		key := strings.TrimSpace(value[:index])
		value = strings.TrimLeft(value[index+1:], " \t")
		if key == "" || value == "" {
			break
		}
		if value[0] != '"' {
			next := strings.IndexByte(value, ',')
			if next < 0 {
				labels[key] = strings.TrimSpace(value)
				break
			}
			labels[key] = strings.TrimSpace(value[:next])
			value = value[next+1:]
			continue
		}
		labelValue, rest, ok := readQuotedLabelValue(value)
		if !ok {
			break
		}
		labels[key] = labelValue
		value = rest
	}
	if len(labels) == 0 {
		return nil
	}
	return labels
}

func readQuotedLabelValue(value string) (string, string, bool) {
	if value == "" || value[0] != '"' {
		return "", value, false
	}
	var b strings.Builder
	escaped := false
	for index := 1; index < len(value); index++ {
		ch := value[index]
		if escaped {
			switch ch {
			case 'n':
				b.WriteByte('\n')
			default:
				b.WriteByte(ch)
			}
			escaped = false
			continue
		}
		if ch == '\\' {
			escaped = true
			continue
		}
		if ch == '"' {
			return b.String(), value[index+1:], true
		}
		b.WriteByte(ch)
	}
	return "", value, false
}

type histogramBucket struct {
	upperBound float64
	count      float64
}

func histogramGroups(samples []metricSample, histogramName string) map[string][]histogramBucket {
	groups := map[string][]histogramBucket{}
	for _, sample := range samples {
		if sample.name != histogramName+"_bucket" {
			continue
		}
		upperBound, ok := parseBucketUpperBound(sample.labels["le"])
		if !ok {
			continue
		}
		key := metricLabelsKey(sample.labels, "le")
		groups[key] = append(groups[key], histogramBucket{upperBound: upperBound, count: sample.value})
	}
	return groups
}

func parseBucketUpperBound(value string) (float64, bool) {
	if value == "+Inf" {
		return math.Inf(1), true
	}
	parsed, err := strconv.ParseFloat(value, 64)
	return parsed, err == nil
}

func histogramQuantile(buckets []histogramBucket, quantile float64) (float64, float64, bool) {
	if len(buckets) == 0 || quantile <= 0 || quantile > 1 {
		return 0, 0, false
	}
	sort.Slice(buckets, func(i, j int) bool {
		return buckets[i].upperBound < buckets[j].upperBound
	})
	total := buckets[len(buckets)-1].count
	if total <= 0 {
		return 0, 0, false
	}
	target := total * quantile
	for _, bucket := range buckets {
		if bucket.count >= target {
			return bucket.upperBound, total, true
		}
	}
	return buckets[len(buckets)-1].upperBound, total, true
}

func metricLabelsKey(labels map[string]string, exclude ...string) string {
	if len(labels) == 0 {
		return "all"
	}
	excluded := map[string]struct{}{}
	for _, key := range exclude {
		excluded[key] = struct{}{}
	}
	keys := make([]string, 0, len(labels))
	for key := range labels {
		if _, skip := excluded[key]; skip {
			continue
		}
		keys = append(keys, key)
	}
	if len(keys) == 0 {
		return "all"
	}
	sort.Strings(keys)
	parts := make([]string, 0, len(keys))
	for _, key := range keys {
		parts = append(parts, key+"="+labels[key])
	}
	return strings.Join(parts, ",")
}
