Files
tianpu-ems/backend/app/api/v1/energy.py

145 lines
6.2 KiB
Python
Raw Normal View History

from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_, text
from pydantic import BaseModel
from app.core.database import get_db
from app.core.deps import get_current_user
from app.models.energy import EnergyData, EnergyDailySummary
from app.models.user import User
router = APIRouter(prefix="/energy", tags=["能耗数据"])
@router.get("/history")
async def query_history(
device_id: int | None = None,
data_type: str = "power",
start_time: str | None = None,
end_time: str | None = None,
granularity: str = Query("hour", pattern="^(raw|5min|hour|day)$"),
page: int = Query(1, ge=1),
page_size: int = Query(100, ge=1, le=1000),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""历史数据查询"""
query = select(EnergyData).where(EnergyData.data_type == data_type)
if device_id:
query = query.where(EnergyData.device_id == device_id)
if start_time:
query = query.where(EnergyData.timestamp >= start_time)
if end_time:
query = query.where(EnergyData.timestamp <= end_time)
if granularity == "raw":
query = query.order_by(EnergyData.timestamp.desc()).offset((page - 1) * page_size).limit(page_size)
result = await db.execute(query)
return [{"timestamp": str(d.timestamp), "value": d.value, "unit": d.unit, "device_id": d.device_id}
for d in result.scalars().all()]
else:
if granularity == "5min":
time_bucket = func.to_timestamp(
func.floor(func.extract('epoch', EnergyData.timestamp) / 300) * 300
).label('time_bucket')
elif granularity == "hour":
time_bucket = func.date_trunc('hour', EnergyData.timestamp).label('time_bucket')
else: # day
time_bucket = func.date_trunc('day', EnergyData.timestamp).label('time_bucket')
agg_query = select(
time_bucket,
func.avg(EnergyData.value).label('avg_value'),
func.max(EnergyData.value).label('max_value'),
func.min(EnergyData.value).label('min_value'),
).where(EnergyData.data_type == data_type)
if device_id:
agg_query = agg_query.where(EnergyData.device_id == device_id)
if start_time:
agg_query = agg_query.where(EnergyData.timestamp >= start_time)
if end_time:
agg_query = agg_query.where(EnergyData.timestamp <= end_time)
agg_query = agg_query.group_by(text('time_bucket')).order_by(text('time_bucket'))
result = await db.execute(agg_query)
return [{"time": str(r[0]), "avg": round(r[1], 2), "max": round(r[2], 2), "min": round(r[3], 2)}
for r in result.all()]
@router.get("/daily-summary")
async def daily_summary(
start_date: str | None = None,
end_date: str | None = None,
energy_type: str | None = None,
device_id: int | None = None,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""每日能耗汇总"""
query = select(EnergyDailySummary)
if start_date:
query = query.where(EnergyDailySummary.date >= start_date)
if end_date:
query = query.where(EnergyDailySummary.date <= end_date)
if energy_type:
query = query.where(EnergyDailySummary.energy_type == energy_type)
if device_id:
query = query.where(EnergyDailySummary.device_id == device_id)
query = query.order_by(EnergyDailySummary.date.desc()).limit(365)
result = await db.execute(query)
return [{
"date": str(s.date), "device_id": s.device_id, "energy_type": s.energy_type,
"consumption": s.total_consumption, "generation": s.total_generation,
"peak_power": s.peak_power, "avg_power": s.avg_power,
"operating_hours": s.operating_hours, "cost": s.cost, "carbon_emission": s.carbon_emission,
} for s in result.scalars().all()]
@router.get("/comparison")
async def energy_comparison(
device_id: int | None = None,
energy_type: str = "electricity",
period: str = Query("month", pattern="^(day|week|month|year)$"),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""能耗同比环比分析"""
now = datetime.now(timezone.utc)
if period == "day":
current_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
prev_start = current_start - timedelta(days=1)
yoy_start = current_start.replace(year=current_start.year - 1)
elif period == "week":
current_start = now - timedelta(days=now.weekday())
current_start = current_start.replace(hour=0, minute=0, second=0, microsecond=0)
prev_start = current_start - timedelta(weeks=1)
yoy_start = current_start.replace(year=current_start.year - 1)
elif period == "month":
current_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
prev_start = (current_start - timedelta(days=1)).replace(day=1)
yoy_start = current_start.replace(year=current_start.year - 1)
else:
current_start = now.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
prev_start = current_start.replace(year=current_start.year - 1)
yoy_start = prev_start
async def sum_consumption(start, end):
q = select(func.sum(EnergyDailySummary.total_consumption)).where(
and_(EnergyDailySummary.date >= start, EnergyDailySummary.date < end,
EnergyDailySummary.energy_type == energy_type)
)
if device_id:
q = q.where(EnergyDailySummary.device_id == device_id)
r = await db.execute(q)
return r.scalar() or 0
current = await sum_consumption(current_start, now)
previous = await sum_consumption(prev_start, current_start)
yoy = await sum_consumption(yoy_start, yoy_start.replace(year=yoy_start.year + 1))
return {
"current": round(current, 2),
"previous": round(previous, 2),
"yoy": round(yoy, 2),
"mom_change": round((current - previous) / previous * 100, 1) if previous else 0,
"yoy_change": round((current - yoy) / yoy * 100, 1) if yoy else 0,
}