feat: 推广 V3.5 实时氢耗并完善导出
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
// mileage-correction writes a confirmed daily terminal odometer correction.
|
||||
// It is intentionally explicit: callers must provide the VIN, natural day and
|
||||
// confirmed cumulative mileage; it never derives values from a guess.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
||||
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/stats"
|
||||
)
|
||||
|
||||
func main() {
|
||||
vin := flag.String("vin", "", "VIN")
|
||||
date := flag.String("date", "", "natural day, yyyy-MM-dd")
|
||||
total := flag.Float64("total-km", 0, "confirmed cumulative mileage, km")
|
||||
flag.Parse()
|
||||
if strings.TrimSpace(*vin) == "" || strings.TrimSpace(*date) == "" || *total <= 0 {
|
||||
log.Fatal("vin, date and positive total-km are required")
|
||||
}
|
||||
dsn := strings.TrimSpace(os.Getenv("MYSQL_DSN"))
|
||||
if dsn == "" {
|
||||
log.Fatal("MYSQL_DSN is required")
|
||||
}
|
||||
loc, err := time.LoadLocation("Asia/Shanghai")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
eventTime, err := time.ParseInLocation("2006-01-02 15:04:05", strings.TrimSpace(*date)+" 23:59:59", loc)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
db, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
ctx := context.Background()
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
candidate := stats.SourceMileageSample{
|
||||
VIN: strings.TrimSpace(*vin),
|
||||
StatDate: strings.TrimSpace(*date),
|
||||
Protocol: envelope.ProtocolGB32960,
|
||||
SourceKey: "GB32960:manual-correction@manual",
|
||||
SourceIP: "manual",
|
||||
SourceEndpoint: "manual-correction",
|
||||
FirstTotalKM: *total,
|
||||
LatestTotalKM: *total,
|
||||
DailyKM: 0,
|
||||
SampleCount: 1,
|
||||
FirstEventTime: eventTime,
|
||||
LatestEventTime: eventTime,
|
||||
QualityStatus: stats.QualityOK,
|
||||
QualityReason: "manual_correction_confirmed_total",
|
||||
}
|
||||
if err := stats.UpsertSourceMileage(ctx, db, candidate); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := stats.ProjectDailyMileage(ctx, db, candidate.VIN, candidate.StatDate, candidate.Protocol); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Keep both cumulative fields aligned. day_end_total_mileage_km is the API
|
||||
// field of record, while latest_total_mileage_km remains useful to internal
|
||||
// readers and must not retain the rejected reset value.
|
||||
if _, err := db.ExecContext(ctx, `
|
||||
UPDATE vehicle_daily_mileage
|
||||
SET latest_total_mileage_km=?, day_end_total_mileage_km=?
|
||||
WHERE vin=? AND stat_date=? AND protocol=?`,
|
||||
*total, *total, candidate.VIN, candidate.StatDate, string(candidate.Protocol)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var daily, latest, dayEnd float64
|
||||
if err := db.QueryRowContext(ctx, `SELECT daily_mileage_km,latest_total_mileage_km,day_end_total_mileage_km FROM vehicle_daily_mileage WHERE vin=? AND stat_date=? AND protocol=?`, candidate.VIN, candidate.StatDate, string(candidate.Protocol)).Scan(&daily, &latest, &dayEnd); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("corrected vin=%s date=%s protocol=%s daily_km=%.3f latest_total_km=%.3f day_end_total_km=%.3f\n", candidate.VIN, candidate.StatDate, candidate.Protocol, daily, latest, dayEnd)
|
||||
}
|
||||
Reference in New Issue
Block a user