功能:完善里程边界与氢耗流式统计
This commit is contained in:
@@ -29,6 +29,7 @@ type config struct {
|
||||
DateFrom string
|
||||
DateTo string
|
||||
Protocols []envelope.Protocol
|
||||
VINs []string
|
||||
Method string
|
||||
Limit int
|
||||
DryRun bool
|
||||
@@ -39,6 +40,8 @@ type config struct {
|
||||
GPSFallback bool
|
||||
ProgressEvery int64
|
||||
BaselineLookback int
|
||||
ListHydrogenVINs bool
|
||||
ListActiveVINs bool
|
||||
Location *time.Location
|
||||
}
|
||||
|
||||
@@ -117,6 +120,21 @@ func main() {
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
mysqlDB, err := sql.Open("mysql", cfg.MySQLDSN)
|
||||
if err != nil {
|
||||
fail("open mysql", err)
|
||||
}
|
||||
defer mysqlDB.Close()
|
||||
if err := mysqlDB.PingContext(ctx); err != nil {
|
||||
fail("ping mysql", err)
|
||||
}
|
||||
if cfg.ListHydrogenVINs {
|
||||
if err := printActiveHydrogenVINs(ctx, mysqlDB); err != nil {
|
||||
fail("list active hydrogen vins", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
td, err := sql.Open(cfg.TDengineDriver, cfg.TDengineDSN)
|
||||
if err != nil {
|
||||
fail("open tdengine", err)
|
||||
@@ -125,14 +143,11 @@ func main() {
|
||||
if err := td.PingContext(ctx); err != nil {
|
||||
fail("ping tdengine", err)
|
||||
}
|
||||
|
||||
mysqlDB, err := sql.Open("mysql", cfg.MySQLDSN)
|
||||
if err != nil {
|
||||
fail("open mysql", err)
|
||||
}
|
||||
defer mysqlDB.Close()
|
||||
if err := mysqlDB.PingContext(ctx); err != nil {
|
||||
fail("ping mysql", err)
|
||||
if cfg.ListActiveVINs {
|
||||
if err := printActiveFrameVINs(ctx, td, cfg); err != nil {
|
||||
fail("list active frame vins", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
schemaWriter := stats.NewWriter(mysqlDB, cfg.Location)
|
||||
@@ -516,10 +531,11 @@ func addRealtimeLocationFallbackAggregates(ctx context.Context, mysqlDB *sql.DB,
|
||||
currentRow.TotalKM = previousRow.TotalKM
|
||||
currentSources[i] = currentRow
|
||||
}
|
||||
usesPreviousDayBoundary := hasPrevious && stats.IsUsableDailyMileageBoundary(date, previousRow.TS, currentRow.FirstTS.Location())
|
||||
agg := aggregateFromDailySource(date, protocol, currentRow, previousRow, hasPrevious)
|
||||
if cfg.StationaryCarry {
|
||||
agg.QualityReason = stats.QualityReasonStationaryCarry
|
||||
} else if hasPrevious {
|
||||
} else if usesPreviousDayBoundary {
|
||||
agg.QualityReason = "realtime_location_fallback_historical_baseline"
|
||||
} else {
|
||||
agg.QualityReason = "realtime_location_fallback_current_day_first_baseline"
|
||||
@@ -909,7 +925,7 @@ func aggregateFromDailySource(date string, protocol envelope.Protocol, current d
|
||||
firstEventTime := current.FirstTS
|
||||
qualityStatus := stats.QualityOK
|
||||
qualityReason := stats.QualityReasonCurrentDayFirst
|
||||
if hasPrevious {
|
||||
if hasPrevious && stats.IsUsableDailyMileageBoundary(date, previous.TS, current.FirstTS.Location()) {
|
||||
firstKM = previous.TotalKM
|
||||
firstEventTime = previous.TS
|
||||
qualityStatus = stats.QualityOK
|
||||
@@ -1295,10 +1311,32 @@ func fieldsForStats(protocol envelope.Protocol, vin string, text string) map[str
|
||||
}
|
||||
fields[key] = value
|
||||
}
|
||||
promoteVehicleRunningModeForStats(protocol, fields, text)
|
||||
promoteFuelCellWorkModeForStats(protocol, fields, text)
|
||||
return fields
|
||||
}
|
||||
|
||||
func promoteVehicleRunningModeForStats(protocol envelope.Protocol, fields map[string]any, text string) {
|
||||
if protocol != envelope.ProtocolGB32960 || fields == nil {
|
||||
return
|
||||
}
|
||||
if _, exists := fields[envelope.FieldVehicleRunningMode]; exists {
|
||||
return
|
||||
}
|
||||
for _, key := range []string{"gb32960.vehicle.running_mode", "running_mode"} {
|
||||
if value, exists := fields[key]; exists {
|
||||
fields[envelope.FieldVehicleRunningMode] = value
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, key := range []string{"gb32960.vehicle.running_mode", "running_mode"} {
|
||||
if value, ok := extractJSONStringField(text, key); ok {
|
||||
fields[envelope.FieldVehicleRunningMode] = value
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func promoteFuelCellWorkModeForStats(protocol envelope.Protocol, fields map[string]any, text string) {
|
||||
if fields == nil {
|
||||
return
|
||||
@@ -1383,6 +1421,7 @@ func loadConfig() (config, error) {
|
||||
DateFrom: dateFrom,
|
||||
DateTo: dateTo,
|
||||
Protocols: protocols,
|
||||
VINs: parseVINs(env("BACKFILL_VINS", "")),
|
||||
Method: env("BACKFILL_METHOD", "last_diff"),
|
||||
Limit: envInt("BACKFILL_LIMIT", 0),
|
||||
DryRun: envBool("BACKFILL_DRY_RUN", true),
|
||||
@@ -1393,10 +1432,29 @@ func loadConfig() (config, error) {
|
||||
GPSFallback: envBool("BACKFILL_GPS_FALLBACK", envBool("BACKFILL_JT808_GPS_FALLBACK", true)),
|
||||
ProgressEvery: int64(envInt("BACKFILL_PROGRESS_EVERY", 100000)),
|
||||
BaselineLookback: envInt("BACKFILL_BASELINE_LOOKBACK_DAYS", 7),
|
||||
ListHydrogenVINs: envBool("BACKFILL_LIST_HYDROGEN_VINS", false),
|
||||
ListActiveVINs: envBool("BACKFILL_LIST_ACTIVE_VINS", false),
|
||||
Location: loc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseVINs(value string) []string {
|
||||
seen := make(map[string]struct{})
|
||||
vins := make([]string, 0)
|
||||
for _, raw := range strings.Split(value, ",") {
|
||||
vin := strings.ToUpper(strings.TrimSpace(raw))
|
||||
if vin == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[vin]; ok {
|
||||
continue
|
||||
}
|
||||
seen[vin] = struct{}{}
|
||||
vins = append(vins, vin)
|
||||
}
|
||||
return vins
|
||||
}
|
||||
|
||||
func resolveBackfillDateRange(now time.Time, loc *time.Location) (string, string) {
|
||||
if loc == nil {
|
||||
loc = time.FixedZone("Asia/Shanghai", 8*3600)
|
||||
@@ -1441,6 +1499,13 @@ func queryRawFrames(ctx context.Context, db *sql.DB, cfg config) (*sql.Rows, err
|
||||
}
|
||||
where = append(where, "protocol IN ("+strings.Join(quoted, ",")+")")
|
||||
}
|
||||
if len(cfg.VINs) > 0 {
|
||||
quoted := make([]string, 0, len(cfg.VINs))
|
||||
for _, vin := range cfg.VINs {
|
||||
quoted = append(quoted, "'"+quote(vin)+"'")
|
||||
}
|
||||
where = append(where, "vin IN ("+strings.Join(quoted, ",")+")")
|
||||
}
|
||||
where = append(where, realtimeMileageFramePredicate())
|
||||
sqlText := fmt.Sprintf(`SELECT protocol, vin, phone, device_id, source_endpoint, event_id, message_id, event_time, received_at, parsed_json, raw_text
|
||||
FROM %s.raw_frames
|
||||
@@ -1449,10 +1514,61 @@ WHERE %s
|
||||
if cfg.Limit > 0 {
|
||||
sqlText += fmt.Sprintf(" LIMIT %d", cfg.Limit)
|
||||
}
|
||||
slog.Info("query raw frames", "date_from", cfg.DateFrom, "date_to", cfg.DateTo, "protocols", cfg.Protocols, "limit", cfg.Limit)
|
||||
slog.Info("query raw frames", "date_from", cfg.DateFrom, "date_to", cfg.DateTo, "protocols", cfg.Protocols, "vins", cfg.VINs, "limit", cfg.Limit)
|
||||
return db.QueryContext(ctx, sqlText)
|
||||
}
|
||||
|
||||
func printActiveHydrogenVINs(ctx context.Context, db *sql.DB) error {
|
||||
rows, err := db.QueryContext(ctx, `SELECT DISTINCT UPPER(TRIM(vin))
|
||||
FROM vehicle_hydrogen_tank_capacity
|
||||
WHERE active = 1 AND tank_capacity_l > 0 AND vin IS NOT NULL AND TRIM(vin) <> ''
|
||||
ORDER BY UPPER(TRIM(vin))`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var vin string
|
||||
if err := rows.Scan(&vin); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(vin)
|
||||
}
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
func printActiveFrameVINs(ctx context.Context, db *sql.DB, cfg config) error {
|
||||
where := backfillTimePredicates(cfg, cfg.DateFrom, nextDate(cfg.DateTo))
|
||||
where = append(where,
|
||||
"parse_status = 'OK'",
|
||||
"vin IS NOT NULL",
|
||||
"vin <> ''",
|
||||
)
|
||||
if len(cfg.Protocols) > 0 {
|
||||
quoted := make([]string, 0, len(cfg.Protocols))
|
||||
for _, protocol := range cfg.Protocols {
|
||||
quoted = append(quoted, "'"+quote(string(protocol))+"'")
|
||||
}
|
||||
where = append(where, "protocol IN ("+strings.Join(quoted, ",")+")")
|
||||
}
|
||||
where = append(where, realtimeMileageFramePredicate())
|
||||
rows, err := db.QueryContext(ctx, fmt.Sprintf(`SELECT DISTINCT vin
|
||||
FROM %s.raw_frames
|
||||
WHERE %s`, ident(cfg.TDengineDatabase), strings.Join(where, " AND ")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var vin string
|
||||
if err := rows.Scan(&vin); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(strings.ToUpper(strings.TrimSpace(vin)))
|
||||
}
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
func backfillTimePredicates(cfg config, eventDateFrom string, eventDateToExclusive string) []string {
|
||||
where := make([]string, 0, 4)
|
||||
if !cfg.EventTimeFullScan {
|
||||
|
||||
Reference in New Issue
Block a user