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, }