feat: 推广 V3.5 实时氢耗并完善导出

This commit is contained in:
lingniu
2026-09-04 15:42:02 +08:00
parent a26179c8fe
commit e402eb5e60
53 changed files with 2855 additions and 320 deletions
@@ -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)
}
@@ -82,6 +82,11 @@ func main() {
} else {
registry.SetGauge("vehicle_stat_hydrogen_capacity_cache_entries", nil, float64(loaded))
}
if loaded, err := writer.ReloadHydrogenRealtimeParameters(ctx); err != nil {
logger.Warn("hydrogen V3.5 parameter cache load failed", "error", err)
} else {
registry.SetGauge("vehicle_stat_hydrogen_v35_parameter_cache_entries", nil, float64(loaded))
}
if cfg.NormalizePlatformSourcesOnStart {
statDate := time.Now().In(cfg.Location).Format("2006-01-02")
normalizeCtx, cancel := context.WithTimeout(ctx, cfg.NormalizePlatformSourcesTimeout)
@@ -191,6 +196,13 @@ func syncHydrogenCapacities(ctx context.Context, logger interface {
}
registry.IncCounter("vehicle_stat_hydrogen_capacity_sync_total", metrics.Labels{"status": "ok"})
registry.SetGauge("vehicle_stat_hydrogen_capacity_cache_entries", nil, float64(loaded))
parameterCount, parameterErr := writer.ReloadHydrogenRealtimeParameters(syncCtx)
if parameterErr != nil {
registry.IncCounter("vehicle_stat_hydrogen_energy_parameter_sync_total", metrics.Labels{"status": "cache_error"})
logger.Warn("hydrogen V3.5 parameter cache reload failed", "error", parameterErr)
} else {
registry.SetGauge("vehicle_stat_hydrogen_v35_parameter_cache_entries", nil, float64(parameterCount))
}
metrics.RecordLastActivity(registry, "vehicle_stat_hydrogen_capacity_last_sync_unix_seconds", nil)
logger.Info("hydrogen tank capacities synchronized", "source_schema", cfg.HydrogenCapacitySourceSchema, "read", result.Read, "written", result.Written, "deactivated", result.Deactivated, "cache_entries", loaded)
}