feat: sync vehicle monitoring and accumulated platform updates

Add monitoring metrics, numeric sorting, report-time display, vehicle number labels, and map layout fixes. Include accumulated mileage, hydrogen, OneOS authentication, open platform, ingestion, deployment, and UI updates.

Exclude temporary files, generated reports, and credentials. Validate frontend 632 tests and production build, API packages, vehicle-map tests, and release installer tests. Gateway suite has one pre-existing date-sensitive identity throttling test failure reproduced on the parent commit.
This commit is contained in:
lingniu
2026-10-26 18:05:59 +08:00
parent 8b32ae2988
commit d6b957dfc8
290 changed files with 28238 additions and 1647 deletions
@@ -282,25 +282,10 @@ func NormalizePlatformSourceMileageForDate(ctx context.Context, db interface {
if strings.TrimSpace(statDate) == "" {
return 0, nil
}
rows, err := db.QueryContext(ctx, selectPlatformSourceMileageLegacyVINSQL, statDate, string(protocol))
vins, err := PlatformSourceMileageLegacyVINs(ctx, db, statDate, protocol)
if err != nil {
return 0, err
}
defer rows.Close()
var vins []string
for rows.Next() {
var vin string
if err := rows.Scan(&vin); err != nil {
return 0, err
}
if strings.TrimSpace(vin) != "" {
vins = append(vins, vin)
}
}
if err := rows.Err(); err != nil {
return 0, err
}
normalized := 0
for _, vin := range vins {
@@ -315,6 +300,27 @@ func NormalizePlatformSourceMileageForDate(ctx context.Context, db interface {
return normalized, nil
}
// PlatformSourceMileageLegacyVINs lets batch callers apply their own atomic
// projection policy while sharing the exact registry/terminal alias rules.
func PlatformSourceMileageLegacyVINs(ctx context.Context, db Queryer, statDate string, protocol envelope.Protocol) ([]string, error) {
rows, err := db.QueryContext(ctx, selectPlatformSourceMileageLegacyVINSQL, statDate, string(protocol))
if err != nil {
return nil, err
}
defer rows.Close()
var vins []string
for rows.Next() {
var vin string
if err := rows.Scan(&vin); err != nil {
return nil, err
}
if strings.TrimSpace(vin) != "" {
vins = append(vins, vin)
}
}
return vins, rows.Err()
}
func ShouldNormalizePlatformSourceMileage(sample SourceMileageSample) bool {
return strings.Contains(sample.SourceKey, platformSourceKeyPrefix)
}
@@ -441,7 +447,20 @@ const upsertSourceMergedQualityWindowDaysSQL = `GREATEST(1, DATEDIFF(DATE(` + up
const upsertSourceMergedMaxMileageSQL = `(` + upsertSourceMergedQualityWindowDaysSQL + ` * ` + maxSelectedDailyMileageKMSQL + `)`
const normalizePlatformFirstTotalSQL = `CAST(SUBSTRING_INDEX(GROUP_CONCAT(s.first_total_mileage_km ORDER BY s.first_event_time ASC, s.updated_at ASC SEPARATOR ','), ',', 1) AS DECIMAL(18,3))`
// Prefer the newest boundary before this natural day. If no historical
// boundary exists, retain the earliest sample inside the day. Select the
// odometer and its timestamp from the same row so stale forwarding aliases
// cannot restore an older baseline after a repair or a cache refresh.
const normalizePlatformFirstOrderSQL = `CASE
WHEN s.first_event_time < CAST(CONCAT(s.stat_date, ' 00:00:00') AS DATETIME) THEN 0
WHEN s.first_event_time IS NOT NULL THEN 1
ELSE 2 END,
CASE WHEN s.first_event_time < CAST(CONCAT(s.stat_date, ' 00:00:00') AS DATETIME) THEN s.first_event_time ELSE NULL END DESC,
s.first_event_time ASC, s.updated_at DESC`
const normalizePlatformFirstTotalSQL = `CAST(SUBSTRING_INDEX(GROUP_CONCAT(s.first_total_mileage_km ORDER BY ` + normalizePlatformFirstOrderSQL + ` SEPARATOR ','), ',', 1) AS DECIMAL(18,3))`
const normalizePlatformFirstEventSQL = `CAST(SUBSTRING_INDEX(GROUP_CONCAT(s.first_event_time ORDER BY ` + normalizePlatformFirstOrderSQL + ` SEPARATOR ','), ',', 1) AS DATETIME)`
const normalizePlatformLatestTotalSQL = `CAST(SUBSTRING_INDEX(GROUP_CONCAT(s.latest_total_mileage_km ORDER BY s.latest_event_time DESC, s.updated_at DESC SEPARATOR ','), ',', 1) AS DECIMAL(18,3))`
@@ -454,7 +473,7 @@ const normalizePlatformDailySQL = `CASE
ELSE ` + normalizePlatformDeltaSQL + `
END`
const normalizePlatformQualityWindowDaysSQL = `GREATEST(1, DATEDIFF(DATE(MAX(s.latest_event_time)), DATE(MIN(s.first_event_time))))`
const normalizePlatformQualityWindowDaysSQL = `GREATEST(1, DATEDIFF(DATE(MAX(s.latest_event_time)), DATE(` + normalizePlatformFirstEventSQL + `)))`
const normalizePlatformMaxMileageSQL = `(` + normalizePlatformQualityWindowDaysSQL + ` * ` + maxSelectedDailyMileageKMSQL + `)`
@@ -537,7 +556,7 @@ SELECT
SUM(s.pure_hydrogen_mileage_km) AS pure_hydrogen_mileage_km,
SUM(s.sample_count) AS sample_count,
SUM(s.pure_hydrogen_sample_count) AS pure_hydrogen_sample_count,
MIN(s.first_event_time) AS first_event_time,
` + normalizePlatformFirstEventSQL + ` AS first_event_time,
MAX(s.latest_event_time) AS latest_event_time,
CAST(SUBSTRING_INDEX(GROUP_CONCAT(s.latest_pure_hydrogen_active ORDER BY s.latest_event_time DESC, s.updated_at DESC SEPARATOR ','), ',', 1) AS UNSIGNED) AS latest_pure_hydrogen_active,
CAST(SUBSTRING_INDEX(GROUP_CONCAT(s.latest_pure_hydrogen_mode_known ORDER BY s.latest_event_time DESC, s.updated_at DESC SEPARATOR ','), ',', 1) AS UNSIGNED) AS latest_pure_hydrogen_mode_known,
@@ -548,7 +567,7 @@ SELECT
CASE
WHEN ` + normalizePlatformOutsideDailyRangeSQL + ` THEN 'outside_daily_range'
WHEN ` + normalizePlatformDailySQL + ` = 0 AND ` + normalizePlatformDeltaSQL + ` < 0 THEN 'negative_jitter_clamped'
WHEN MIN(s.first_event_time) < CAST(CONCAT(s.stat_date, ' 00:00:00') AS DATETIME)
WHEN ` + normalizePlatformFirstEventSQL + ` < CAST(CONCAT(s.stat_date, ' 00:00:00') AS DATETIME)
THEN '` + QualityReasonHistorical + `'
ELSE '` + QualityReasonCurrentDayFirst + `'
END AS quality_reason
@@ -611,10 +630,7 @@ ON DUPLICATE KEY UPDATE
latest_total_mileage_km = ` + upsertSourceMergedLatestTotalSQL + `,
daily_mileage_km = ` + upsertSourceMergedDailySQL + `,
sample_count = sample_count + VALUES(sample_count),
first_event_time = CASE
WHEN first_event_time IS NULL OR VALUES(first_event_time) <= first_event_time THEN VALUES(first_event_time)
ELSE first_event_time
END,
first_event_time = ` + upsertSourceMergedFirstEventSQL + `,
latest_event_time = CASE
WHEN latest_event_time IS NULL OR VALUES(latest_event_time) >= latest_event_time THEN VALUES(latest_event_time)
ELSE latest_event_time