feat(stats): store source mileage candidates
This commit is contained in:
151
go/vehicle-gateway/internal/stats/source_mileage.go
Normal file
151
go/vehicle-gateway/internal/stats/source_mileage.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
||||
)
|
||||
|
||||
const (
|
||||
QualityOK = "OK"
|
||||
QualityNoPreviousBaseline = "NO_PREVIOUS_BASELINE"
|
||||
QualityInvalidDelta = "INVALID_DELTA"
|
||||
)
|
||||
|
||||
type SourceMileageSample struct {
|
||||
VIN string
|
||||
StatDate string
|
||||
Protocol envelope.Protocol
|
||||
SourceKey string
|
||||
SourceIP string
|
||||
SourceEndpoint string
|
||||
Phone string
|
||||
DeviceID string
|
||||
PlatformName string
|
||||
FirstTotalKM float64
|
||||
LatestTotalKM float64
|
||||
DailyKM float64
|
||||
SampleCount int64
|
||||
FirstEventTime time.Time
|
||||
LatestEventTime time.Time
|
||||
QualityStatus string
|
||||
QualityReason string
|
||||
}
|
||||
|
||||
func SourceKey(protocol envelope.Protocol, phone string, deviceID string, sourceIP string) string {
|
||||
identity := strings.TrimSpace(phone)
|
||||
if identity == "" {
|
||||
identity = strings.TrimSpace(deviceID)
|
||||
}
|
||||
if identity == "" {
|
||||
identity = "unknown"
|
||||
}
|
||||
return string(protocol) + ":" + identity + "@" + strings.TrimSpace(sourceIP)
|
||||
}
|
||||
|
||||
func SourceMileageSampleFromMetric(sample MetricSample, identity SourceIdentity) SourceMileageSample {
|
||||
eventTime := sample.EventTime
|
||||
return SourceMileageSample{
|
||||
VIN: sample.VIN,
|
||||
StatDate: sample.StatDate,
|
||||
Protocol: sample.Protocol,
|
||||
SourceKey: SourceKey(sample.Protocol, sample.Phone, sample.DeviceID, identity.SourceIP),
|
||||
SourceIP: identity.SourceIP,
|
||||
SourceEndpoint: identity.SourceEndpoint,
|
||||
Phone: sample.Phone,
|
||||
DeviceID: sample.DeviceID,
|
||||
FirstTotalKM: sample.TotalMileageKM,
|
||||
LatestTotalKM: sample.TotalMileageKM,
|
||||
DailyKM: 0,
|
||||
SampleCount: 1,
|
||||
FirstEventTime: eventTime,
|
||||
LatestEventTime: eventTime,
|
||||
QualityStatus: QualityOK,
|
||||
QualityReason: "realtime_sample",
|
||||
}
|
||||
}
|
||||
|
||||
func UpsertSourceMileage(ctx context.Context, exec Execer, sample SourceMileageSample) error {
|
||||
if exec == nil {
|
||||
panic("stats execer must not be nil")
|
||||
}
|
||||
if sample.VIN == "" || sample.SourceKey == "" {
|
||||
return nil
|
||||
}
|
||||
if sample.QualityStatus == "" {
|
||||
sample.QualityStatus = QualityOK
|
||||
}
|
||||
_, err := exec.ExecContext(ctx, upsertSourceMileageSQL,
|
||||
sample.VIN,
|
||||
sample.StatDate,
|
||||
string(sample.Protocol),
|
||||
sample.SourceKey,
|
||||
sample.SourceIP,
|
||||
sample.SourceEndpoint,
|
||||
sample.Phone,
|
||||
sample.DeviceID,
|
||||
sample.PlatformName,
|
||||
sample.FirstTotalKM,
|
||||
sample.LatestTotalKM,
|
||||
sample.DailyKM,
|
||||
sample.SampleCount,
|
||||
sample.FirstEventTime,
|
||||
sample.LatestEventTime,
|
||||
sample.QualityStatus,
|
||||
sample.QualityReason,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertSourceMileageSQL = `
|
||||
INSERT INTO vehicle_daily_mileage_source
|
||||
(vin, stat_date, protocol, source_key, source_ip, source_endpoint, phone, device_id, platform_name,
|
||||
first_total_mileage_km, latest_total_mileage_km, daily_mileage_km, sample_count,
|
||||
first_event_time, latest_event_time, quality_status, quality_reason)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
source_ip = VALUES(source_ip),
|
||||
source_endpoint = VALUES(source_endpoint),
|
||||
phone = VALUES(phone),
|
||||
device_id = VALUES(device_id),
|
||||
platform_name = COALESCE(VALUES(platform_name), platform_name),
|
||||
first_total_mileage_km = CASE
|
||||
WHEN first_total_mileage_km IS NULL OR first_total_mileage_km <= 0
|
||||
THEN VALUES(first_total_mileage_km)
|
||||
ELSE LEAST(first_total_mileage_km, VALUES(first_total_mileage_km))
|
||||
END,
|
||||
latest_total_mileage_km = CASE
|
||||
WHEN latest_total_mileage_km IS NULL OR latest_total_mileage_km <= 0
|
||||
THEN VALUES(latest_total_mileage_km)
|
||||
ELSE GREATEST(latest_total_mileage_km, VALUES(latest_total_mileage_km))
|
||||
END,
|
||||
/* daily_mileage_km = VALUES(daily_mileage_km) */
|
||||
daily_mileage_km = GREATEST(
|
||||
CASE
|
||||
WHEN latest_total_mileage_km IS NULL OR latest_total_mileage_km <= 0
|
||||
THEN VALUES(latest_total_mileage_km)
|
||||
ELSE latest_total_mileage_km
|
||||
END,
|
||||
VALUES(latest_total_mileage_km)
|
||||
) - CASE
|
||||
WHEN first_total_mileage_km IS NULL OR first_total_mileage_km <= 0
|
||||
THEN VALUES(first_total_mileage_km)
|
||||
ELSE LEAST(first_total_mileage_km, VALUES(first_total_mileage_km))
|
||||
END,
|
||||
sample_count = sample_count + VALUES(sample_count),
|
||||
first_event_time = CASE
|
||||
WHEN first_event_time IS NULL OR VALUES(first_event_time) < first_event_time
|
||||
THEN VALUES(first_event_time)
|
||||
ELSE first_event_time
|
||||
END,
|
||||
latest_event_time = CASE
|
||||
WHEN latest_event_time IS NULL OR VALUES(latest_event_time) > latest_event_time
|
||||
THEN VALUES(latest_event_time)
|
||||
ELSE latest_event_time
|
||||
END,
|
||||
quality_status = VALUES(quality_status),
|
||||
quality_reason = VALUES(quality_reason),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
`
|
||||
Reference in New Issue
Block a user