功能:完善里程边界与氢耗流式统计

This commit is contained in:
lingniu
2026-09-02 14:05:04 +08:00
parent 4201878c35
commit 8759668d39
23 changed files with 1638 additions and 159 deletions
@@ -302,7 +302,9 @@ func (w *Writer) EnsureSchema(ctx context.Context) error {
DailyMileageSourceTableSQL,
DailyMileageTableSQL,
HydrogenTankCapacityTableSQL,
HydrogenEnergyParameterTableSQL,
HydrogenStreamStateTableSQL,
HydrogenSegmentStreamStateTableSQL,
} {
if _, err := w.exec.ExecContext(ctx, statement); err != nil {
return err
@@ -433,6 +435,15 @@ func (w *Writer) AppendWithResult(ctx context.Context, env envelope.FrameEnvelop
if err := w.applyRealtimeBaseline(ctx, &candidate); err != nil {
return result, err
}
// A new natural-day/source row may start from the previous day's
// odometer baseline. Attribute that initial delta to the current
// running mode so pure-hydrogen mileage remains the complement of
// pure-electric mileage from the first accepted sample onward.
if candidate.QualityStatus == QualityOK &&
candidate.LatestPureHydrogenModeKnown &&
candidate.LatestPureHydrogenActive {
candidate.PureHydrogenMileageKM = candidate.DailyKM
}
}
projectDaily := w.shouldProjectDailyMileage(sample)
if projectDaily {
@@ -868,6 +879,16 @@ func (w *Writer) applyRealtimeBaseline(ctx context.Context, candidate *SourceMil
return err
}
if !found {
protocolBaseline, protocolFound, protocolErr := lookupPreviousProtocolBaseline(ctx, w.query, candidate.VIN, candidate.StatDate, candidate.Protocol)
if protocolErr != nil {
return protocolErr
}
if protocolFound && candidate.LatestTotalKM+maxNegativeMileageJitterKM < protocolBaseline.LatestTotalKM {
candidate.DailyKM = 0
candidate.QualityStatus = QualityInvalidDelta
candidate.QualityReason = QualityReasonTotalRollback
return nil
}
// If no earlier odometer exists at all, use the first current-day sample.
// Later samples retain that boundary through the in-memory baseline cache.
candidate.DailyKM = 0
@@ -875,9 +896,25 @@ func (w *Writer) applyRealtimeBaseline(ctx context.Context, candidate *SourceMil
candidate.QualityReason = QualityReasonCurrentDayFirst
return nil
}
if !IsUsableDailyMileageBoundary(candidate.StatDate, baseline.LatestEventTime, w.loc) {
// A vehicle recovering after one or more empty natural days must start a
// fresh day boundary. Otherwise the complete offline-period odometer
// increase is incorrectly assigned to the recovery day.
candidate.FirstTotalKM = candidate.LatestTotalKM
candidate.FirstEventTime = candidate.LatestEventTime
candidate.DailyKM = 0
candidate.QualityStatus = QualityOK
candidate.QualityReason = QualityReasonCurrentDayFirst
return nil
}
candidate.FirstTotalKM = baseline.LatestTotalKM
candidate.FirstEventTime = baseline.LatestEventTime
candidate.DailyKM = DailyMileageFromDayBoundary(baseline.LatestTotalKM, candidate.LatestTotalKM)
if candidate.LatestTotalKM+maxNegativeMileageJitterKM < baseline.LatestTotalKM {
candidate.QualityStatus = QualityInvalidDelta
candidate.QualityReason = QualityReasonTotalRollback
return nil
}
candidate.QualityStatus = QualityOK
candidate.QualityReason = baseline.QualityReason
if candidate.QualityReason == "" {