Files
lingniu-vehicle-ingest/go/vehicle-gateway/internal/stats/hydrogen_capacity.go
2026-07-27 16:46:15 +08:00

150 lines
5.1 KiB
Go

package stats
import (
"context"
"database/sql"
"fmt"
"regexp"
"strings"
)
const HydrogenTankCapacityTableSQL = `CREATE TABLE IF NOT EXISTS vehicle_hydrogen_tank_capacity (
vin VARCHAR(64) NOT NULL,
source_vehicle_id BIGINT NOT NULL,
source_model_id BIGINT NULL,
plate_number VARCHAR(50) NOT NULL DEFAULT '',
brand_name VARCHAR(500) NOT NULL DEFAULT '',
model_name VARCHAR(500) NOT NULL DEFAULT '',
tank_capacity_l DECIMAL(12,2) NOT NULL,
active TINYINT(1) NOT NULL DEFAULT 1,
source_updated_at DATETIME NULL,
synced_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (vin),
KEY idx_hydrogen_capacity_model (source_model_id),
KEY idx_hydrogen_capacity_active (active,vin)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
type HydrogenCapacitySyncResult struct {
Read int
Written int
Deactivated int64
}
type hydrogenCapacityRow struct {
VIN string
SourceVehicleID int64
SourceModelID sql.NullInt64
Plate string
Brand string
Model string
CapacityLiters float64
SourceUpdatedAt sql.NullTime
}
var mysqlIdentifier = regexp.MustCompile(`^[A-Za-z0-9_]+$`)
func SyncHydrogenTankCapacities(ctx context.Context, db *sql.DB, sourceSchema string) (HydrogenCapacitySyncResult, error) {
result := HydrogenCapacitySyncResult{}
if db == nil {
return result, fmt.Errorf("mysql database is required")
}
sourceSchema = strings.TrimSpace(sourceSchema)
if !mysqlIdentifier.MatchString(sourceSchema) {
return result, fmt.Errorf("invalid asset schema %q", sourceSchema)
}
query := fmt.Sprintf(`SELECT vi.id,vi.vehicle_model_id,UPPER(TRIM(vi.vin)),COALESCE(vi.plate_number,''),
COALESCE(vm.brand,''),COALESCE(vm.model,''),vm.tank_capacity,
CASE
WHEN vi.update_time IS NULL THEN vm.update_time
WHEN vm.update_time IS NULL THEN vi.update_time
ELSE GREATEST(vi.update_time,vm.update_time)
END
FROM %s.vehicle_info vi
JOIN %s.vehicle_model vm ON vm.id=vi.vehicle_model_id
WHERE COALESCE(vi.del_flag,'0')='0' AND COALESCE(vm.del_flag,'0')='0'
AND LENGTH(TRIM(vi.vin))=17 AND vm.tank_capacity>0`, sourceSchema, sourceSchema)
rows, err := db.QueryContext(ctx, query)
if err != nil {
return result, fmt.Errorf("read asset hydrogen capacities: %w", err)
}
byVIN := map[string]hydrogenCapacityRow{}
for rows.Next() {
var row hydrogenCapacityRow
if err := rows.Scan(&row.SourceVehicleID, &row.SourceModelID, &row.VIN, &row.Plate, &row.Brand, &row.Model, &row.CapacityLiters, &row.SourceUpdatedAt); err != nil {
rows.Close()
return result, err
}
result.Read++
if row.CapacityLiters <= 0 || row.CapacityLiters > 10000 {
continue
}
if previous, exists := byVIN[row.VIN]; !exists || newerCapacityRow(row, previous) {
byVIN[row.VIN] = row
}
}
if err := rows.Close(); err != nil {
return result, err
}
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return result, err
}
defer tx.Rollback()
_, err = tx.ExecContext(ctx, `UPDATE vehicle_hydrogen_tank_capacity SET active=0 WHERE active=1`)
if err != nil {
return result, err
}
const upsert = `INSERT INTO vehicle_hydrogen_tank_capacity(
vin,source_vehicle_id,source_model_id,plate_number,brand_name,model_name,tank_capacity_l,active,source_updated_at,synced_at
) VALUES(?,?,?,?,?,?,?,1,?,NOW(3))
ON DUPLICATE KEY UPDATE source_vehicle_id=VALUES(source_vehicle_id),source_model_id=VALUES(source_model_id),
plate_number=VALUES(plate_number),brand_name=VALUES(brand_name),model_name=VALUES(model_name),
tank_capacity_l=VALUES(tank_capacity_l),active=1,source_updated_at=VALUES(source_updated_at),synced_at=NOW(3)`
for _, row := range byVIN {
if _, err := tx.ExecContext(ctx, upsert, row.VIN, row.SourceVehicleID, row.SourceModelID, row.Plate, row.Brand, row.Model, row.CapacityLiters, row.SourceUpdatedAt); err != nil {
return result, err
}
result.Written++
}
if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM vehicle_hydrogen_tank_capacity WHERE active=0`).Scan(&result.Deactivated); err != nil {
return result, err
}
if err := tx.Commit(); err != nil {
return result, err
}
return result, nil
}
func newerCapacityRow(left, right hydrogenCapacityRow) bool {
if left.SourceUpdatedAt.Valid != right.SourceUpdatedAt.Valid {
return left.SourceUpdatedAt.Valid
}
if left.SourceUpdatedAt.Valid && !left.SourceUpdatedAt.Time.Equal(right.SourceUpdatedAt.Time) {
return left.SourceUpdatedAt.Time.After(right.SourceUpdatedAt.Time)
}
return left.SourceVehicleID > right.SourceVehicleID
}
func LoadHydrogenTankCapacities(ctx context.Context, query Queryer) (map[string]float64, error) {
if query == nil {
return nil, fmt.Errorf("mysql queryer is required")
}
rows, err := query.QueryContext(ctx, `SELECT UPPER(TRIM(vin)),tank_capacity_l FROM vehicle_hydrogen_tank_capacity WHERE active=1 AND tank_capacity_l>0`)
if err != nil {
return nil, err
}
defer rows.Close()
capacities := map[string]float64{}
for rows.Next() {
var vin string
var capacity float64
if err := rows.Scan(&vin, &capacity); err != nil {
return nil, err
}
if len(vin) == 17 && capacity > 0 && capacity <= 10000 {
capacities[vin] = capacity
}
}
return capacities, rows.Err()
}