Files
tianpu-ems/backend/app/api/v1/dashboard.py
Du Wenbo f53a610a19 Initial commit: Tianpu Zero-Carbon EMS Platform
Full-stack energy management system for Tianpu Daxing campus.
- Frontend: React 19 + TypeScript + Ant Design + ECharts
- Backend: FastAPI + SQLAlchemy + PostgreSQL/TimescaleDB
- Features: PV monitoring, heat pump management, carbon tracking, alarms, reports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:36:06 +08:00

140 lines
5.0 KiB
Python

from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_, text
from app.core.database import get_db
from app.core.deps import get_current_user
from app.models.device import Device
from app.models.energy import EnergyData, EnergyDailySummary
from app.models.alarm import AlarmEvent
from app.models.carbon import CarbonEmission
from app.models.user import User
router = APIRouter(prefix="/dashboard", tags=["大屏数据"])
@router.get("/overview")
async def get_overview(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
"""能源总览大屏核心数据"""
now = datetime.now(timezone.utc)
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
# 设备状态统计
device_stats_q = await db.execute(
select(Device.status, func.count(Device.id)).where(Device.is_active == True).group_by(Device.status)
)
device_stats = {row[0]: row[1] for row in device_stats_q.all()}
# 今日能耗汇总
daily_q = await db.execute(
select(
EnergyDailySummary.energy_type,
func.sum(EnergyDailySummary.total_consumption),
func.sum(EnergyDailySummary.total_generation),
).where(EnergyDailySummary.date >= today_start).group_by(EnergyDailySummary.energy_type)
)
energy_summary = {}
for row in daily_q.all():
energy_summary[row[0]] = {"consumption": row[1] or 0, "generation": row[2] or 0}
# 今日碳排放
carbon_q = await db.execute(
select(func.sum(CarbonEmission.emission), func.sum(CarbonEmission.reduction))
.where(CarbonEmission.date >= today_start)
)
carbon_row = carbon_q.first()
# 活跃告警数
alarm_count_q = await db.execute(
select(func.count(AlarmEvent.id)).where(AlarmEvent.status == "active")
)
active_alarms = alarm_count_q.scalar() or 0
# 最近告警
recent_alarms_q = await db.execute(
select(AlarmEvent).where(AlarmEvent.status == "active").order_by(AlarmEvent.triggered_at.desc()).limit(10)
)
recent_alarms = [
{"id": a.id, "title": a.title, "severity": a.severity, "device_id": a.device_id,
"triggered_at": str(a.triggered_at)}
for a in recent_alarms_q.scalars().all()
]
return {
"device_stats": {
"online": device_stats.get("online", 0),
"offline": device_stats.get("offline", 0),
"alarm": device_stats.get("alarm", 0),
"total": sum(device_stats.values()),
},
"energy_today": energy_summary,
"carbon": {
"emission": carbon_row[0] or 0 if carbon_row else 0,
"reduction": carbon_row[1] or 0 if carbon_row else 0,
},
"active_alarms": active_alarms,
"recent_alarms": recent_alarms,
}
@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=5)
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()
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)
return {
"timestamp": str(now),
"pv_power": round(pv_power, 2),
"heatpump_power": round(heatpump_power, 2),
"total_load": round(pv_power + heatpump_power, 2),
"grid_power": round(max(0, heatpump_power - pv_power), 2),
}
@router.get("/load-curve")
async def get_load_curve(
hours: int = 24,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""负荷曲线数据"""
now = datetime.now(timezone.utc)
start = now - timedelta(hours=hours)
result = await db.execute(
select(
func.date_trunc('hour', EnergyData.timestamp).label('hour'),
func.avg(EnergyData.value).label('avg_power'),
).where(
and_(EnergyData.timestamp >= start, EnergyData.data_type == "power")
).group_by(text('hour')).order_by(text('hour'))
)
return [{"time": str(row[0]), "power": round(row[1], 2)} for row in result.all()]
async def _get_pv_device_ids(db: AsyncSession):
result = await db.execute(
select(Device.id).where(Device.device_type == "pv_inverter", Device.is_active == True)
)
return [r[0] for r in result.fetchall()]
async def _get_hp_device_ids(db: AsyncSession):
result = await db.execute(
select(Device.id).where(Device.device_type == "heat_pump", Device.is_active == True)
)
return [r[0] for r in result.fetchall()]