feat(platform): add online vehicle statistics detail

This commit is contained in:
lingniu
2026-07-04 17:47:38 +08:00
parent d266396c47
commit 3b817ef874
9 changed files with 253 additions and 2 deletions

View File

@@ -43,6 +43,7 @@ func (h *Handler) routes() {
h.mux.HandleFunc("GET /api/mileage/summary", h.handleMileageSummary)
h.mux.HandleFunc("GET /api/mileage/daily", h.handleDailyMileage)
h.mux.HandleFunc("GET /api/statistics/online-summary", h.handleOnlineStatisticsSummary)
h.mux.HandleFunc("GET /api/statistics/online-vehicles", h.handleOnlineVehicleStatuses)
h.mux.HandleFunc("GET /api/quality/summary", h.handleQualitySummary)
h.mux.HandleFunc("GET /api/quality/issues", h.handleQualityIssues)
h.mux.HandleFunc("GET /api/quality/notification-plan", h.handleQualityNotificationPlan)
@@ -176,6 +177,11 @@ func (h *Handler) handleOnlineStatisticsSummary(w http.ResponseWriter, r *http.R
h.write(w, r, data, err)
}
func (h *Handler) handleOnlineVehicleStatuses(w http.ResponseWriter, r *http.Request) {
data, err := h.service.OnlineVehicleStatuses(r.Context(), r.URL.Query())
h.write(w, r, data, err)
}
func (h *Handler) handleQualityIssues(w http.ResponseWriter, r *http.Request) {
data, err := h.service.QualityIssues(r.Context(), r.URL.Query())
h.write(w, r, data, err)

View File

@@ -746,6 +746,7 @@ func TestHandlerHistoryMileageQualityOps(t *testing.T) {
{"/api/mileage/summary?limit=10", "totalMileageKm"},
{"/api/mileage/daily?limit=10", "dailyMileageKm"},
{"/api/statistics/online-summary?limit=10", "onlineRatePercent"},
{"/api/statistics/online-vehicles?limit=10", "offlineDurationMinutes"},
{"/api/quality/summary?limit=10", "issueVehicleCount"},
{"/api/quality/issues?limit=10", "sourceEndpoint"},
{"/api/ops/health", "linkHealth"},

View File

@@ -275,6 +275,20 @@ type OnlineStatisticsSummary struct {
Evidence string `json:"evidence"`
}
type OnlineVehicleStatusRow struct {
VIN string `json:"vin"`
Plate string `json:"plate"`
Phone string `json:"phone"`
OEM string `json:"oem"`
Protocols []string `json:"protocols"`
Online bool `json:"online"`
LastSeen string `json:"lastSeen"`
OfflineDurationMinutes *int `json:"offlineDurationMinutes"`
SourceCount int `json:"sourceCount"`
OnlineSourceCount int `json:"onlineSourceCount"`
BindingStatus string `json:"bindingStatus"`
}
type QualityIssueRow struct {
VIN string `json:"vin"`
Plate string `json:"plate"`

View File

@@ -909,6 +909,56 @@ func (s *Service) OnlineStatisticsSummary(ctx context.Context, query url.Values)
}, nil
}
func (s *Service) OnlineVehicleStatuses(ctx context.Context, query url.Values) (Page[OnlineVehicleStatusRow], error) {
resolvedQuery, err := s.resolveVehicleQuery(ctx, query)
if err != nil {
return Page[OnlineVehicleStatusRow]{}, err
}
coverage, err := s.store.VehicleCoverage(ctx, resolvedQuery)
if err != nil {
return Page[OnlineVehicleStatusRow]{}, err
}
now := time.Now()
items := make([]OnlineVehicleStatusRow, 0, len(coverage.Items))
for _, row := range coverage.Items {
items = append(items, OnlineVehicleStatusRow{
VIN: row.VIN,
Plate: row.Plate,
Phone: row.Phone,
OEM: row.OEM,
Protocols: append([]string(nil), row.Protocols...),
Online: row.Online,
LastSeen: row.LastSeen,
OfflineDurationMinutes: offlineDurationMinutes(row.Online, row.LastSeen, now),
SourceCount: row.SourceCount,
OnlineSourceCount: row.OnlineSourceCount,
BindingStatus: row.BindingStatus,
})
}
return Page[OnlineVehicleStatusRow]{
Items: items,
Total: coverage.Total,
Limit: coverage.Limit,
Offset: coverage.Offset,
}, nil
}
func offlineDurationMinutes(online bool, lastSeen string, now time.Time) *int {
if online {
value := 0
return &value
}
parsed, ok := parseVehicleServiceTime(lastSeen)
if !ok {
return nil
}
minutes := int(now.Sub(parsed).Minutes())
if minutes < 0 {
minutes = 0
}
return &minutes
}
func (s *Service) QualitySummary(ctx context.Context, query url.Values) (QualitySummary, error) {
return s.store.QualitySummary(ctx, query)
}