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>
93 lines
2.8 KiB
Python
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]
|