fix: materialize stationary yutong daily mileage

This commit is contained in:
lingniu
2026-07-19 13:23:33 +08:00
parent dffe559160
commit 4da26ad3ba
7 changed files with 292 additions and 9 deletions

View File

@@ -69,6 +69,7 @@ type AppendResult struct {
SamplesSkippedMissingFields int
SamplesSkippedMissingVIN int
SamplesSkippedMissingMileage int
SamplesRecoveredStationary int
SamplesSkippedNonMileageFrame int
SamplesSkippedNonPositiveMileage int
SamplesSkippedMissingTime int
@@ -282,6 +283,19 @@ func (w *Writer) AppendWithResult(ctx context.Context, env envelope.FrameEnvelop
if err != nil {
return result, err
}
var stationaryCandidate *SourceMileageSample
if len(samples) == 0 && hasSource && result.SamplesSkippedMissingMileage > 0 {
sample, candidate, recovered, recoverErr := w.stationaryCarryForward(ctx, env, identity)
if recoverErr != nil {
return result, recoverErr
}
if recovered {
samples = []MetricSample{sample}
stationaryCandidate = &candidate
result.SamplesFound++
result.SamplesRecoveredStationary++
}
}
for _, sample := range samples {
if !hasSource {
result.SamplesSkippedMissingSource++
@@ -293,8 +307,12 @@ func (w *Writer) AppendWithResult(ctx context.Context, env envelope.FrameEnvelop
continue
}
candidate := SourceMileageSampleFromMetric(sample, identity)
if err := w.applyRealtimeBaseline(ctx, &candidate); err != nil {
return result, err
if stationaryCandidate != nil {
candidate = *stationaryCandidate
} else {
if err := w.applyRealtimeBaseline(ctx, &candidate); err != nil {
return result, err
}
}
projectDaily := w.shouldProjectDailyMileage(sample)
if projectDaily {
@@ -316,6 +334,47 @@ func (w *Writer) AppendWithResult(ctx context.Context, env envelope.FrameEnvelop
return result, nil
}
func (w *Writer) stationaryCarryForward(ctx context.Context, env envelope.FrameEnvelope, identity SourceIdentity) (MetricSample, SourceMileageSample, bool, error) {
if env.Protocol != envelope.ProtocolYutongMQTT || strings.TrimSpace(env.VIN) == "" {
return MetricSample{}, SourceMileageSample{}, false, nil
}
location, ok := telemetry.LocationProjectionForProtocol(env.Protocol, env.Fields)
if !ok || location.SpeedKMH == nil || *location.SpeedKMH != 0 {
return MetricSample{}, SourceMileageSample{}, false, nil
}
eventMS, _, ok := envelope.NormalizedEventTimeMSWithReason(env)
if !ok {
return MetricSample{}, SourceMileageSample{}, false, nil
}
eventTime := time.UnixMilli(eventMS).In(w.loc)
sample := MetricSample{
VIN: strings.TrimSpace(env.VIN),
Protocol: env.Protocol,
StatDate: eventTime.Format("2006-01-02"),
EventTime: eventTime,
SourceKey: SourceKeyForSource(env.Protocol, env.Phone, env.DeviceID, identity.SourceIP, identity.SourceKind, identity.SourceCode),
Phone: strings.TrimSpace(env.Phone),
DeviceID: strings.TrimSpace(env.DeviceID),
SourceEndpoint: strings.TrimSpace(env.SourceEndpoint),
PlatformName: strings.TrimSpace(env.PlatformName),
}
candidate := SourceMileageSampleFromMetric(sample, identity)
baseline, found, err := w.previousBaseline(ctx, candidate)
if err != nil || !found || baseline.LatestTotalKM <= 0 || baseline.LatestEventTime.IsZero() {
return MetricSample{}, SourceMileageSample{}, false, err
}
sample.TotalMileageKM = baseline.LatestTotalKM
candidate = SourceMileageSampleFromMetric(sample, identity)
candidate.FirstTotalKM = baseline.LatestTotalKM
candidate.LatestTotalKM = baseline.LatestTotalKM
candidate.DailyKM = 0
candidate.FirstEventTime = baseline.LatestEventTime
candidate.LatestEventTime = eventTime
candidate.QualityStatus = QualityOK
candidate.QualityReason = QualityReasonStationaryCarry
return sample, candidate, true, nil
}
func (w *Writer) writeMileageSample(ctx context.Context, sample MetricSample, candidate SourceMileageSample, projectDaily bool) error {
if projectDaily {
if beginner, ok := w.exec.(txBeginner); ok {