119 lines
4.5 KiB
Python
119 lines
4.5 KiB
Python
"""统计 API"""
|
|
from fastapi import APIRouter, Depends
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import func, case
|
|
from app.core.database import get_db
|
|
from app.models.device import ONUDevice, DeviceStatusHistory
|
|
from datetime import datetime, timedelta
|
|
|
|
router = APIRouter(prefix="/api/stats", tags=["统计"])
|
|
|
|
|
|
@router.get("/dashboard")
|
|
def get_dashboard(db: Session = Depends(get_db)):
|
|
"""仪表板统计:总体、城区、城郊、乡镇在线率"""
|
|
# 每台设备最新状态子查询
|
|
latest_subq = (
|
|
db.query(
|
|
DeviceStatusHistory.onu_device_id,
|
|
func.max(DeviceStatusHistory.checked_at).label("max_checked_at")
|
|
)
|
|
.group_by(DeviceStatusHistory.onu_device_id)
|
|
.subquery()
|
|
)
|
|
latest_status_subq = (
|
|
db.query(
|
|
DeviceStatusHistory.onu_device_id,
|
|
DeviceStatusHistory.status
|
|
)
|
|
.join(
|
|
latest_subq,
|
|
(DeviceStatusHistory.onu_device_id == latest_subq.c.onu_device_id) &
|
|
(DeviceStatusHistory.checked_at == latest_subq.c.max_checked_at)
|
|
)
|
|
.subquery()
|
|
)
|
|
|
|
rows = (
|
|
db.query(
|
|
ONUDevice.region,
|
|
ONUDevice.school_name,
|
|
func.count().label("total"),
|
|
func.sum(case((latest_status_subq.c.status == 'online', 1), else_=0)).label("online"),
|
|
func.sum(case((latest_status_subq.c.status == 'offline', 1), else_=0)).label("offline"),
|
|
)
|
|
.outerjoin(latest_status_subq, ONUDevice.id == latest_status_subq.c.onu_device_id)
|
|
.group_by(ONUDevice.region, ONUDevice.school_name)
|
|
.order_by(ONUDevice.region, ONUDevice.school_name)
|
|
.all()
|
|
)
|
|
|
|
overall = {"total": 0, "online": 0, "offline": 0}
|
|
urban_schools, suburban_schools = [], []
|
|
rural_towns = {}
|
|
|
|
for row in rows:
|
|
region = row.region or ""
|
|
total = int(row.total or 0)
|
|
online = int(row.online or 0)
|
|
offline = int(row.offline or 0)
|
|
overall["total"] += total
|
|
overall["online"] += online
|
|
overall["offline"] += offline
|
|
|
|
school_stat = {"name": row.school_name or "未知", "total": total, "online": online, "offline": offline}
|
|
|
|
if region == "城区":
|
|
urban_schools.append(school_stat)
|
|
elif region == "城郊":
|
|
suburban_schools.append(school_stat)
|
|
else:
|
|
if region not in rural_towns:
|
|
rural_towns[region] = {"region": region, "total": 0, "online": 0, "offline": 0, "schools": []}
|
|
rural_towns[region]["total"] += total
|
|
rural_towns[region]["online"] += online
|
|
rural_towns[region]["offline"] += offline
|
|
rural_towns[region]["schools"].append(school_stat)
|
|
|
|
def sort_by_rate(items):
|
|
return sorted(items, key=lambda x: x["online"] / x["total"] if x["total"] > 0 else 0)
|
|
|
|
def agg(items):
|
|
return {"total": sum(s["total"] for s in items), "online": sum(s["online"] for s in items), "offline": sum(s["offline"] for s in items)}
|
|
|
|
rural_list = sort_by_rate(list(rural_towns.values()))
|
|
|
|
return {
|
|
"overall": overall,
|
|
"urban": {**agg(urban_schools), "schools": sort_by_rate(urban_schools)},
|
|
"suburban": {**agg(suburban_schools), "schools": sort_by_rate(suburban_schools)},
|
|
"rural": {**agg(list(rural_towns.values())), "towns": rural_list},
|
|
}
|
|
|
|
|
|
@router.get("/summary")
|
|
def get_summary(db: Session = Depends(get_db)):
|
|
"""获取统计摘要"""
|
|
total = db.query(ONUDevice).count()
|
|
latest_status = db.query(
|
|
DeviceStatusHistory.status,
|
|
func.count(DeviceStatusHistory.id)
|
|
).group_by(DeviceStatusHistory.status).all()
|
|
status_dict = dict(latest_status)
|
|
return {"total": total, "online": status_dict.get('online', 0), "offline": status_dict.get('offline', 0)}
|
|
|
|
|
|
@router.get("/trend")
|
|
def get_trend(days: int = 7, db: Session = Depends(get_db)):
|
|
"""获取状态趋势数据"""
|
|
start_date = datetime.utcnow() - timedelta(days=days)
|
|
history = db.query(
|
|
func.date(DeviceStatusHistory.checked_at).label('date'),
|
|
func.sum(func.case((DeviceStatusHistory.status == 'online', 1), else_=0)).label('online'),
|
|
func.sum(func.case((DeviceStatusHistory.status == 'offline', 1), else_=0)).label('offline')
|
|
).filter(DeviceStatusHistory.checked_at >= start_date).group_by(
|
|
func.date(DeviceStatusHistory.checked_at)
|
|
).all()
|
|
return [{"date": str(h.date), "online": h.online, "offline": h.offline} for h in history]
|
|
|