فهرست منبع

能耗趋势修复

liuq 2 ماه پیش
والد
کامیت
179d9ef1e8

+ 4 - 0
backend/controllers/dashboard_controller.go

@@ -3,6 +3,7 @@ package controllers
 import (
 	"ems-backend/db"
 	"ems-backend/models"
+	"fmt"
 	"net/http"
 	"time"
 
@@ -117,6 +118,9 @@ func GetEnergyTrend(c *gin.Context) {
 	// Get 7-day trend from TDengine
 	trend, days, err := db.GetDailyEnergyTrend(deviceIDs, 7)
 	if err != nil {
+		// Log the error for debugging
+		fmt.Printf("GetEnergyTrend Error: %v\n", err)
+		
 		// Fallback to Mock Data if DB fails
 		c.JSON(http.StatusOK, gin.H{
 			"trend": []float64{120, 132, 101, 134, 90, 230, 210},

+ 39 - 50
backend/db/tdengine.go

@@ -384,7 +384,8 @@ func GetTotalRealtimePower(deviceIDs []string) (float64, error) {
 			total += val.Float64
 		}
 	}
-	return total, nil
+	// Convert W to kW
+	return total / 1000.0, nil
 }
 
 // GetActiveDeviceCount returns the number of unique devices that reported data in the last duration
@@ -393,10 +394,10 @@ func GetActiveDeviceCount(duration time.Duration) (int64, error) {
 		return 0, fmt.Errorf("TDengine not initialized")
 	}
 
-	startTime := time.Now().Add(-duration).Format("2006-01-02 15:04:05")
+	startTime := time.Now().Add(-duration).UnixMilli()
 	
 	// Query distinct device_id from readings table in the last duration
-	query := fmt.Sprintf(`SELECT COUNT(DISTINCT device_id) FROM readings WHERE ts > '%s'`, startTime)
+	query := fmt.Sprintf(`SELECT COUNT(DISTINCT device_id) FROM readings WHERE ts > %d`, startTime)
 	
 	rows, err := TD.Query(query)
 	if err != nil {
@@ -415,6 +416,8 @@ func GetActiveDeviceCount(duration time.Duration) (int64, error) {
 }
 
 // GetDailyEnergyTrend returns daily energy consumption for the last N days
+// Implementation Note: Due to TDengine 3.0 limitation (GROUP BY and INTERVAL conflict),
+// we iterate through each day and query sum of spreads individually.
 func GetDailyEnergyTrend(deviceIDs []string, days int) ([]float64, []string, error) {
 	if TD == nil {
 		return nil, nil, fmt.Errorf("TDengine not initialized")
@@ -425,10 +428,8 @@ func GetDailyEnergyTrend(deviceIDs []string, days int) ([]float64, []string, err
 
 	// Calculate time range
 	now := time.Now()
-	// Start from N days ago at 00:00:00
+	// Start from N days ago
 	startDate := now.AddDate(0, 0, -days+1)
-	startStr := startDate.Format("2006-01-02") + " 00:00:00"
-	endStr := now.Format("2006-01-02 15:04:05")
 
 	var ids []string
 	for _, id := range deviceIDs {
@@ -436,57 +437,45 @@ func GetDailyEnergyTrend(deviceIDs []string, days int) ([]float64, []string, err
 	}
 	inClause := strings.Join(ids, ", ")
 
-	// Query daily spread (consumption) for all devices
-	// SPREAD(val) gives the diff between max and min in the interval
-	// SUM(SPREAD(val)) is not directly supported in one go if we group by interval first?
-	// TDengine: SELECT SUM(val) ... GROUP BY ... won't work on spread directly if spread is agg.
-	// Correct approach: Calculate spread per device per day, then sum up in app?
-	// OR: If we treat 'energy' as cumulative, we can just query MAX(val) - MIN(val) for the whole group? No, that mixes devices.
-	
-	// Best approach with TDengine:
-	// Select time-bucketed spread for EACH device, then sum them up in Go.
-	// Query: SELECT SPREAD(val) FROM readings WHERE metric='energy' AND ... INTERVAL(1d) GROUP BY device_id
-	
-	query := fmt.Sprintf(`SELECT _wstart, SPREAD(val) FROM readings 
-		WHERE metric='energy' AND device_id IN (%s) AND ts >= '%s' AND ts <= '%s' 
-		INTERVAL(1d) GROUP BY device_id`, 
-		inClause, startStr, endStr)
-
-	rows, err := TD.Query(query)
-	if err != nil {
-		return nil, nil, err
-	}
-	defer rows.Close()
-
-	// Map: DateString -> Total Energy
-	dailyMap := make(map[string]float64)
-
-	for rows.Next() {
-		var ts time.Time
-		var val sql.NullFloat64
-		if err := rows.Scan(&ts, &val); err == nil && val.Valid {
-			dateKey := ts.Format("2006-01-02")
-			dailyMap[dateKey] += val.Float64
-		}
-	}
-
-	// Prepare result arrays (ensure continuous days)
 	trend := make([]float64, days)
 	dates := make([]string, days)
-	
+
+	// Iterate each day
 	for i := 0; i < days; i++ {
 		d := startDate.AddDate(0, 0, i)
-		dateKey := d.Format("2006-01-02")
+		dates[i] = d.Format("01-02")
 		
-		// For X-Axis labels (e.g., "Mon", "01-02")
-		// Using MM-DD for clarity
-		dates[i] = d.Format("01-02") 
+		dayStart := d.Format("2006-01-02") + " 00:00:00"
+		// End of the day (or strictly next day 00:00:00)
+		dayEnd := d.AddDate(0, 0, 1).Format("2006-01-02") + " 00:00:00"
 		
-		if val, ok := dailyMap[dateKey]; ok {
-			trend[i] = val
-		} else {
-			trend[i] = 0
+		// Query: Sum of spreads for all devices in this single day
+		// We use a subquery approach if possible, or just sum in app
+		// Query: SELECT SPREAD(val) FROM readings WHERE ... AND ts >= start AND ts < end GROUP BY device_id
+		
+		query := fmt.Sprintf(`SELECT SPREAD(val) FROM readings 
+			WHERE metric='energy' AND device_id IN (%s) AND ts >= '%s' AND ts < '%s' 
+			GROUP BY device_id`, 
+			inClause, dayStart, dayEnd)
+
+		// log.Printf("DEBUG: Querying Day %s: %s", dates[i], query)
+
+		rows, err := TD.Query(query)
+		if err != nil {
+			log.Printf("Error querying day %s: %v", dates[i], err)
+			trend[i] = 0 // Treat as 0 on error
+			continue
+		}
+		
+		var dayTotal float64
+		for rows.Next() {
+			var val sql.NullFloat64
+			if err := rows.Scan(&val); err == nil && val.Valid {
+				dayTotal += val.Float64
+			}
 		}
+		rows.Close()
+		trend[i] = dayTotal
 	}
 
 	return trend, dates, nil

+ 1 - 1
backend/models/schema.go

@@ -24,7 +24,7 @@ type Device struct {
 	SourceID         uuid.UUID      `gorm:"type:uuid"`
 	ExternalID       string         `gorm:"type:varchar(100)"`
 	Name             string         `gorm:"type:varchar(100)"`
-	DeviceType       string         `gorm:"type:varchar(50)"` // ELECTRIC, WATER, GAS
+	DeviceType       string         `gorm:"type:varchar(50)"` // ELECTRIC, WATER, GAS, INDUSTRIAL_ELECTRIC
 	LocationID       *uuid.UUID     `gorm:"type:uuid"`        // 关联空间
 	AttributeMapping datatypes.JSON `gorm:"type:jsonb"`       // 属性映射
 	Status           string         `gorm:"type:varchar(20)"` // NORMAL, INACTIVE

+ 4 - 2
frontend/src/constants/device.ts

@@ -1,11 +1,13 @@
 export const DEVICE_TYPES = [
   { value: 'ELEC', label: '电力设备 (ELEC)' },
   { value: 'WATER', label: '水务设备 (WATER)' },
-  { value: 'GAS', label: '燃气设备 (GAS)' }
+  { value: 'GAS', label: '燃气设备 (GAS)' },
+  { value: 'INDUSTRIAL_ELECTRIC', label: '工业电力设备 (INDUSTRIAL_ELECTRIC)' }
 ];
 
 export const DEVICE_TYPE_MAP: Record<string, string> = {
   ELEC: '电力设备',
   WATER: '水务设备',
-  GAS: '燃气设备'
+  GAS: '燃气设备',
+  INDUSTRIAL_ELECTRIC: '工业电力设备'
 };

+ 1 - 0
frontend/src/views/DeviceList.vue

@@ -301,6 +301,7 @@ const deviceTypeOptions = [
     { label: '电力设备', value: 'ELECTRIC' },
     { label: '水务设备', value: 'WATER' },
     { label: '燃气设备', value: 'GAS' },
+    { label: '工业电力设备', value: 'INDUSTRIAL_ELECTRIC' },
 ];
 
 const getDeviceTypeName = (type: string) => {