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>
This commit is contained in:
78
backend/app/api/v1/monitoring.py
Normal file
78
backend/app/api/v1/monitoring.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, and_
|
||||
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
|
||||
from app.models.user import User
|
||||
|
||||
router = APIRouter(prefix="/monitoring", tags=["实时监控"])
|
||||
|
||||
|
||||
@router.get("/devices/{device_id}/realtime")
|
||||
async def device_realtime(device_id: int, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
|
||||
"""获取单台设备的最新实时数据"""
|
||||
now = datetime.now(timezone.utc)
|
||||
five_min_ago = now - timedelta(minutes=5)
|
||||
|
||||
result = await db.execute(
|
||||
select(EnergyData).where(
|
||||
and_(EnergyData.device_id == device_id, EnergyData.timestamp >= five_min_ago)
|
||||
).order_by(EnergyData.timestamp.desc()).limit(20)
|
||||
)
|
||||
data_points = result.scalars().all()
|
||||
latest = {}
|
||||
for d in data_points:
|
||||
if d.data_type not in latest:
|
||||
latest[d.data_type] = {"value": d.value, "unit": d.unit, "timestamp": str(d.timestamp)}
|
||||
|
||||
device_q = await db.execute(select(Device).where(Device.id == device_id))
|
||||
device = device_q.scalar_one_or_none()
|
||||
|
||||
return {
|
||||
"device": {
|
||||
"id": device.id, "name": device.name, "code": device.code,
|
||||
"device_type": device.device_type, "status": device.status,
|
||||
"model": device.model, "manufacturer": device.manufacturer,
|
||||
} if device else None,
|
||||
"data": latest,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/energy-flow")
|
||||
async def energy_flow(db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user)):
|
||||
"""能流图数据 - 展示能量流向"""
|
||||
now = datetime.now(timezone.utc)
|
||||
five_min_ago = now - timedelta(minutes=5)
|
||||
|
||||
# 获取各类设备最新功率
|
||||
result = await db.execute(
|
||||
select(Device.device_type, func.sum(EnergyData.value))
|
||||
.join(EnergyData, EnergyData.device_id == Device.id)
|
||||
.where(and_(EnergyData.timestamp >= five_min_ago, EnergyData.data_type == "power"))
|
||||
.group_by(Device.device_type)
|
||||
)
|
||||
power_by_type = {row[0]: round(row[1], 2) for row in result.all()}
|
||||
|
||||
pv_power = power_by_type.get("pv_inverter", 0)
|
||||
hp_power = power_by_type.get("heat_pump", 0)
|
||||
total_load = hp_power + power_by_type.get("meter", 0)
|
||||
grid_import = max(0, total_load - pv_power)
|
||||
grid_export = max(0, pv_power - total_load)
|
||||
|
||||
return {
|
||||
"nodes": [
|
||||
{"id": "pv", "name": "光伏发电", "power": pv_power, "unit": "kW"},
|
||||
{"id": "grid", "name": "电网", "power": grid_import - grid_export, "unit": "kW"},
|
||||
{"id": "heatpump", "name": "热泵系统", "power": hp_power, "unit": "kW"},
|
||||
{"id": "building", "name": "建筑负荷", "power": total_load, "unit": "kW"},
|
||||
],
|
||||
"links": [
|
||||
{"source": "pv", "target": "building", "value": min(pv_power, total_load)},
|
||||
{"source": "pv", "target": "grid", "value": grid_export},
|
||||
{"source": "grid", "target": "building", "value": grid_import},
|
||||
{"source": "grid", "target": "heatpump", "value": hp_power},
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user