diff --git a/backend/app/api/v1/dashboard.py b/backend/app/api/v1/dashboard.py index 350a686..4e10ddc 100644 --- a/backend/app/api/v1/dashboard.py +++ b/backend/app/api/v1/dashboard.py @@ -105,21 +105,58 @@ async def get_overview(db: AsyncSession = Depends(get_db), user: User = Depends( @router.get("/realtime") async def get_realtime_data(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)): - """实时功率数据 - 获取最近的采集数据""" + """实时功率数据 - 获取最近的采集数据,按站去重防止重复计数""" now = datetime.now(timezone.utc) - five_min_ago = now - timedelta(minutes=20) - - latest_q = await db.execute( - select(EnergyData).where( - and_(EnergyData.timestamp >= five_min_ago, EnergyData.data_type == "power") - ).order_by(EnergyData.timestamp.desc()).limit(50) - ) - data_points = latest_q.scalars().all() + window_start = now - timedelta(minutes=20) + # Get latest power per station (dedup by device name prefix) + # Sungrow collectors report station-level power, so multiple devices + # sharing the same station (AP1xx = Phase 1, AP2xx = Phase 2) report + # identical values. GROUP BY station prefix and take MAX to avoid + # double-counting. + from sqlalchemy import text as sa_text pv_ids = await _get_pv_device_ids(db) hp_ids = await _get_hp_device_ids(db) - pv_power = sum(d.value for d in data_points if d.device_id in pv_ids) - heatpump_power = sum(d.value for d in data_points if d.device_id in hp_ids) + + # PV power: dedup by station prefix + if pv_ids: + pv_q = await db.execute( + select( + func.substring(Device.name, 1, 3).label("station"), + func.max(EnergyData.value).label("power"), + ).select_from(EnergyData).join( + Device, EnergyData.device_id == Device.id + ).where( + and_( + EnergyData.timestamp >= window_start, + EnergyData.data_type == "power", + EnergyData.device_id.in_(pv_ids), + ) + ).group_by(sa_text("1")) + ) + pv_power = sum(row[1] or 0 for row in pv_q.all()) + else: + pv_power = 0 + + # Heat pump power: dedup by station prefix + if hp_ids: + hp_q = await db.execute( + select( + func.substring(Device.name, 1, 3).label("station"), + func.max(EnergyData.value).label("power"), + ).select_from(EnergyData).join( + Device, EnergyData.device_id == Device.id + ).where( + and_( + EnergyData.timestamp >= window_start, + EnergyData.data_type == "power", + EnergyData.device_id.in_(hp_ids), + ) + ).group_by(sa_text("1")) + ) + heatpump_power = sum(row[1] or 0 for row in hp_q.all()) + else: + heatpump_power = 0 return { "timestamp": str(now), diff --git a/backend/app/api/v1/kpi.py b/backend/app/api/v1/kpi.py index 1bd4522..b2c7b09 100644 --- a/backend/app/api/v1/kpi.py +++ b/backend/app/api/v1/kpi.py @@ -35,31 +35,32 @@ async def get_solar_kpis(db: AsyncSession = Depends(get_db), user: User = Depend "total_rated_kw": 0, "daily_generation_kwh": 0, } - # Get latest daily_energy per PV device for today + # Get latest daily_energy per station (dedup by device name prefix) + # Sungrow collectors report station-level data per device, so multiple + # devices sharing the same station report identical values. + # Group by station prefix (first 3 chars of name, e.g. "AP1" vs "AP2") + # and take MAX per station to avoid double-counting. + from sqlalchemy import text as sa_text daily_gen_q = await db.execute( select( - EnergyData.device_id, + func.substring(Device.name, 1, 3).label("station"), func.max(EnergyData.value).label("max_energy"), + ).select_from(EnergyData).join( + Device, EnergyData.device_id == Device.id ).where( and_( EnergyData.timestamp >= today_start, EnergyData.data_type == "daily_energy", EnergyData.device_id.in_(pv_ids), ) - ).group_by(EnergyData.device_id) + ).group_by(sa_text("1")) ) - # Check if values are station-level (all identical) or device-level daily_values = daily_gen_q.all() if not daily_values: daily_generation_kwh = 0 else: - values = [row[1] or 0 for row in daily_values] - # If all values are identical, it's station-level data — use max (not sum) - if len(set(values)) == 1 and len(values) > 1: - daily_generation_kwh = values[0] - else: - daily_generation_kwh = sum(values) + daily_generation_kwh = sum(row[1] or 0 for row in daily_values) # Performance Ratio (PR) = actual output / (rated capacity * peak sun hours) # Approximate peak sun hours from time of day (simplified)