Implement Manticore fully and re-theme
This commit is contained in:
148
core/events/shadow.py
Normal file
148
core/events/shadow.py
Normal file
@@ -0,0 +1,148 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from django.db.models import Count, Max, Q
|
||||
|
||||
from core.models import ConversationEvent, Person, User
|
||||
|
||||
|
||||
def _kind_from_event_type(event_type: str) -> str:
|
||||
normalized = str(event_type or "").strip().lower()
|
||||
return {
|
||||
"message_created": "message_sent",
|
||||
"delivery_receipt": "message_delivered",
|
||||
"read_receipt": "message_read",
|
||||
"typing_started": "composing_started",
|
||||
"typing_stopped": "composing_stopped",
|
||||
"composing_abandoned": "composing_abandoned",
|
||||
"presence_available": "presence_available",
|
||||
"presence_unavailable": "presence_unavailable",
|
||||
}.get(normalized, normalized)
|
||||
|
||||
|
||||
def get_shadow_behavioral_availability_stats(*, user: User) -> list[dict]:
|
||||
person_map = {
|
||||
str(row["id"]): str(row["name"] or "")
|
||||
for row in Person.objects.filter(user=user).values("id", "name")
|
||||
}
|
||||
rows = (
|
||||
ConversationEvent.objects.filter(
|
||||
user=user,
|
||||
session__identifier__person__isnull=False,
|
||||
)
|
||||
.values("session__identifier__person_id", "origin_transport")
|
||||
.annotate(
|
||||
total_events=Count("id"),
|
||||
presence_events=Count(
|
||||
"id",
|
||||
filter=Q(event_type__in=["presence_available", "presence_unavailable"]),
|
||||
),
|
||||
read_events=Count("id", filter=Q(event_type="read_receipt")),
|
||||
typing_events=Count(
|
||||
"id",
|
||||
filter=Q(
|
||||
event_type__in=["typing_started", "typing_stopped"]
|
||||
),
|
||||
),
|
||||
message_events=Count("id", filter=Q(event_type="message_created")),
|
||||
abandoned_events=Count("id", filter=Q(event_type="composing_abandoned")),
|
||||
last_event_ts=Max("ts"),
|
||||
)
|
||||
.order_by("-total_events", "session__identifier__person_id", "origin_transport")
|
||||
)
|
||||
output = []
|
||||
for row in rows:
|
||||
person_id = str(row.get("session__identifier__person_id") or "").strip()
|
||||
output.append(
|
||||
{
|
||||
"person_id": person_id,
|
||||
"person_name": person_map.get(person_id, person_id or "-"),
|
||||
"service": str(row.get("origin_transport") or "").strip().lower(),
|
||||
"total_events": int(row.get("total_events") or 0),
|
||||
"presence_events": int(row.get("presence_events") or 0),
|
||||
"read_events": int(row.get("read_events") or 0),
|
||||
"typing_events": int(row.get("typing_events") or 0),
|
||||
"message_events": int(row.get("message_events") or 0),
|
||||
"abandoned_events": int(row.get("abandoned_events") or 0),
|
||||
"last_event_ts": int(row.get("last_event_ts") or 0),
|
||||
}
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
def get_shadow_behavioral_latest_states(
|
||||
*, user: User, person_ids: list[str], transport: str = ""
|
||||
) -> list[dict]:
|
||||
queryset = ConversationEvent.objects.filter(
|
||||
user=user,
|
||||
session__identifier__person_id__in=[str(value) for value in person_ids],
|
||||
event_type__in=[
|
||||
"message_created",
|
||||
"delivery_receipt",
|
||||
"read_receipt",
|
||||
"typing_started",
|
||||
"typing_stopped",
|
||||
"composing_abandoned",
|
||||
"presence_available",
|
||||
"presence_unavailable",
|
||||
],
|
||||
).select_related("session__identifier")
|
||||
if transport:
|
||||
queryset = queryset.filter(origin_transport=str(transport).strip().lower())
|
||||
rows = []
|
||||
seen = set()
|
||||
for row in queryset.order_by(
|
||||
"session__identifier__person_id", "-ts", "-created_at"
|
||||
)[:500]:
|
||||
person_id = str(getattr(row.session.identifier, "person_id", "") or "").strip()
|
||||
if not person_id or person_id in seen:
|
||||
continue
|
||||
seen.add(person_id)
|
||||
rows.append(
|
||||
{
|
||||
"person_id": person_id,
|
||||
"transport": str(row.origin_transport or "").strip().lower(),
|
||||
"kind": _kind_from_event_type(row.event_type),
|
||||
"ts": int(row.ts or 0),
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def get_shadow_behavioral_events_for_range(
|
||||
*,
|
||||
user: User,
|
||||
person_id: str,
|
||||
start_ts: int,
|
||||
end_ts: int,
|
||||
transport: str = "",
|
||||
) -> list[dict]:
|
||||
queryset = ConversationEvent.objects.filter(
|
||||
user=user,
|
||||
session__identifier__person_id=str(person_id or "").strip(),
|
||||
ts__gte=int(start_ts),
|
||||
ts__lte=int(end_ts),
|
||||
event_type__in=[
|
||||
"message_created",
|
||||
"delivery_receipt",
|
||||
"read_receipt",
|
||||
"typing_started",
|
||||
"typing_stopped",
|
||||
"composing_abandoned",
|
||||
"presence_available",
|
||||
"presence_unavailable",
|
||||
],
|
||||
).order_by("ts", "created_at")
|
||||
if transport:
|
||||
queryset = queryset.filter(origin_transport=str(transport).strip().lower())
|
||||
return [
|
||||
{
|
||||
"person_id": str(person_id or "").strip(),
|
||||
"session_id": str(row.session_id or ""),
|
||||
"transport": str(row.origin_transport or "").strip().lower(),
|
||||
"kind": _kind_from_event_type(row.event_type),
|
||||
"direction": str(row.direction or "").strip().lower(),
|
||||
"ts": int(row.ts or 0),
|
||||
"payload": dict(row.payload or {}),
|
||||
}
|
||||
for row in queryset[:1000]
|
||||
]
|
||||
Reference in New Issue
Block a user