Files
MapMil/centers/analytics/api/app/services/analytics.py
T
gitrusprusandCursor 2c16bc0b56 Add CA admin UI and extend admin API for the unified platform.
Deliver parsers, events, analytics, and PI management in Vue; fix Telegram session mount and map navigation to events by eventId.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 14:40:59 +03:00

93 lines
2.8 KiB
Python

from datetime import datetime, timedelta, timezone
from sqlalchemy import func
from sqlalchemy.orm import Session
from ..models import Consumer, Event, ParseJob
from ..schemas import AnalyticsSummary, TimelinePoint, TopItem
def get_analytics_summary(db: Session) -> AnalyticsSummary:
now = datetime.now(timezone.utc)
day_ago = now - timedelta(days=1)
total_events = db.query(func.count(Event.id)).scalar() or 0
events_with_coords = (
db.query(func.count(Event.id))
.filter(Event.latitude.isnot(None), Event.longitude.isnot(None))
.scalar()
or 0
)
events_last_24h = (
db.query(func.count(Event.id))
.filter(Event.ingested_at >= day_ago)
.scalar()
or 0
)
total_consumers = db.query(func.count(Consumer.id)).scalar() or 0
active_consumers = (
db.query(func.count(Consumer.id))
.filter(Consumer.is_active.is_(True))
.scalar()
or 0
)
total_jobs = db.query(func.count(ParseJob.id)).scalar() or 0
pending_jobs = (
db.query(func.count(ParseJob.id))
.filter(ParseJob.status.in_(["queued", "running", "pending"]))
.scalar()
or 0
)
return AnalyticsSummary(
total_events=total_events,
events_with_coords=events_with_coords,
events_last_24h=events_last_24h,
total_consumers=total_consumers,
active_consumers=active_consumers,
total_jobs=total_jobs,
pending_jobs=pending_jobs,
)
def get_timeline(db: Session, days: int = 30) -> list[TimelinePoint]:
since = datetime.now(timezone.utc) - timedelta(days=days)
day_col = func.date_trunc("day", Event.ingested_at).label("day")
rows = (
db.query(day_col, func.count(Event.id).label("count"))
.filter(Event.ingested_at >= since)
.group_by(day_col)
.order_by(day_col)
.all()
)
return [
TimelinePoint(date=row.day.date().isoformat(), count=row.count)
for row in rows
]
def get_top_localities(db: Session, limit: int = 10) -> list[TopItem]:
rows = (
db.query(Event.locality, func.count(Event.id).label("count"))
.filter(Event.locality != "", Event.locality.isnot(None))
.group_by(Event.locality)
.order_by(func.count(Event.id).desc())
.limit(limit)
.all()
)
return [TopItem(name=row.locality, count=row.count) for row in rows]
def get_top_regions(db: Session, limit: int = 10) -> list[TopItem]:
rows = (
db.query(Event.region, func.count(Event.id).label("count"))
.filter(Event.region.isnot(None), Event.region != "")
.group_by(Event.region)
.order_by(func.count(Event.id).desc())
.limit(limit)
.all()
)
return [TopItem(name=row.region, count=row.count) for row in rows]