File size: 1,665 Bytes
825b28a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
"""Per-tenant analytics event recording + aggregation."""

from __future__ import annotations

from datetime import UTC, datetime, timedelta
from typing import Any

from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models import Event


def record(
    db: AsyncSession,
    tenant_id: int | None,
    type_: str,
    *,
    session_id: str | None = None,
    meta: dict[str, Any] | None = None,
) -> None:
    """Queue an analytics event (persisted with the turn's commit)."""
    db.add(Event(tenant_id=tenant_id, type=type_, session_id=session_id, meta=meta or {}))


async def summary(db: AsyncSession, tenant_id: int, days: int = 30) -> dict[str, Any]:
    since = datetime.now(UTC) - timedelta(days=days)
    rows = (
        await db.execute(
            select(Event.type, func.count())
            .where(Event.tenant_id == tenant_id, Event.created_at >= since)
            .group_by(Event.type)
        )
    ).all()
    counts = {t: int(n) for t, n in rows}
    sessions = (
        await db.execute(
            select(func.count(func.distinct(Event.session_id))).where(
                Event.tenant_id == tenant_id, Event.created_at >= since
            )
        )
    ).scalar() or 0
    return {
        "days": days,
        "conversations": int(sessions),
        "messages": counts.get("turn", 0),
        "product_views": counts.get("products_shown", 0),
        "cart_links": counts.get("cart_link", 0),
        "order_lookups": counts.get("order_lookup", 0),
        "escalations": counts.get("escalation", 0),
        "stock_watches": counts.get("stock_watch", 0),
        "by_type": counts,
    }