add dashboard

This commit is contained in:
2026-04-09 17:24:07 +05:00
parent ed6de0a4cb
commit 4013701441
10 changed files with 1202 additions and 24 deletions

134
route/stats_crud.py Normal file
View File

@@ -0,0 +1,134 @@
from fastapi import APIRouter, Query
from typing import Optional
from sqlalchemy import func
from bd import make_engine
from bd.tables.response import Response
from bd.tables.poll import Poll
from bd.tables.group import Group
from bd.tables.organization import Organization
router = APIRouter(tags=["stats"], prefix="/stats")
_MONTH_NAMES = ["Янв", "Фев", "Мар", "Апр", "Май", "Июн",
"Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"]
def get_session():
from sqlalchemy.orm import sessionmaker
return sessionmaker(bind=make_engine(), future=True)
@router.get("/summary")
def get_summary():
"""Общая сводка: кол-во ответов, тестов, активных тестов, организаций с активностью."""
Session = get_session()
with Session() as session:
total_responses = session.query(func.count(Response.id)).scalar() or 0
total_polls = session.query(func.count(Poll.id)).scalar() or 0
active_polls = session.query(func.count(Poll.id)).filter(Poll.is_active.is_(True)).scalar() or 0
orgs_active = session.query(func.count(func.distinct(Response.organization_id))).scalar() or 0
return {
"total_responses": total_responses,
"total_polls": total_polls,
"active_polls": active_polls,
"orgs_active": orgs_active,
}
@router.get("/years")
def get_years():
"""Список лет, в которых есть хотя бы один ответ."""
Session = get_session()
with Session() as session:
rows = session.query(
func.extract("year", Response.submitted_at).label("yr")
).distinct().order_by("yr").all()
return [int(r.yr) for r in rows if r.yr is not None]
@router.get("/responses")
def get_responses_grouped(
group_by: str = Query("poll", pattern="^(poll|organization|group|month)$"),
poll_id: Optional[str] = None,
org_id: Optional[str] = None,
group_id: Optional[str] = None,
year: Optional[int] = None,
):
"""
Возвращает агрегированные данные для графиков.
- group_by=poll → количество ответов по каждому тесту
- group_by=organization → количество ответов по каждой организации
- group_by=group → количество ответов по каждой группе
- group_by=month → количество ответов по месяцам (хронологически)
Все фильтры опциональны и комбинируются.
"""
Session = get_session()
with Session() as session:
if group_by == "poll":
q = session.query(Poll.title, func.count(Response.id).label("cnt"))
q = q.join(Response, Poll.id == Response.poll_id)
if org_id:
q = q.filter(Response.organization_id == org_id)
if group_id:
q = q.filter(Response.group_id == group_id)
if year:
q = q.filter(func.extract("year", Response.submitted_at) == year)
if poll_id:
q = q.filter(Response.poll_id == poll_id)
rows = q.group_by(Poll.title).order_by(func.count(Response.id).desc()).limit(20).all()
return [{"label": r[0], "count": r[1]} for r in rows]
if group_by == "organization":
q = session.query(Organization.name_organization, func.count(Response.id).label("cnt"))
q = q.outerjoin(Response, Organization.id == Response.organization_id)
if poll_id:
q = q.filter(Response.poll_id == poll_id)
if group_id:
q = q.filter(Response.group_id == group_id)
if year:
q = q.filter(func.extract("year", Response.submitted_at) == year)
rows = q.group_by(Organization.name_organization).order_by(
func.count(Response.id).desc()
).limit(20).all()
return [{"label": r[0], "count": r[1] or 0} for r in rows]
if group_by == "group":
q = session.query(Group.name_group, func.count(Response.id).label("cnt"))
q = q.outerjoin(Response, Group.id == Response.group_id)
if poll_id:
q = q.filter(Response.poll_id == poll_id)
if org_id:
q = q.filter(Response.organization_id == org_id)
if year:
q = q.filter(func.extract("year", Response.submitted_at) == year)
rows = q.group_by(Group.name_group).order_by(
func.count(Response.id).desc()
).limit(20).all()
return [{"label": r[0], "count": r[1] or 0} for r in rows]
if group_by == "month":
yr_col = func.extract("year", Response.submitted_at).label("yr")
mo_col = func.extract("month", Response.submitted_at).label("mo")
q = session.query(yr_col, mo_col, func.count(Response.id).label("cnt"))
if poll_id:
q = q.filter(Response.poll_id == poll_id)
if org_id:
q = q.filter(Response.organization_id == org_id)
if group_id:
q = q.filter(Response.group_id == group_id)
if year:
q = q.filter(func.extract("year", Response.submitted_at) == year)
rows = q.group_by("yr", "mo").order_by("yr", "mo").all()
return [
{
"label": f"{_MONTH_NAMES[int(r.mo) - 1]} {int(r.yr)}",
"count": r.cnt,
}
for r in rows
]
return []