Implement Manticore fully and re-theme

This commit is contained in:
2026-03-11 02:19:08 +00:00
parent da044be68c
commit cbedcd67f6
46 changed files with 3444 additions and 944 deletions

View File

@@ -1,14 +1,12 @@
from __future__ import annotations
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Count, Max, Q
from django.shortcuts import render
from django.views import View
from core.models import (
ContactAvailabilityEvent,
ContactAvailabilitySettings,
)
from core.events.manticore import get_behavioral_availability_stats
from core.events.shadow import get_shadow_behavioral_availability_stats
from core.models import ContactAvailabilitySettings, Person
def _to_int(value, default=0):
@@ -64,42 +62,105 @@ class AvailabilitySettingsPage(LoginRequiredMixin, View):
return self.get(request)
def get(self, request):
settings_row = self._settings(request)
contact_stats = list(
ContactAvailabilityEvent.objects.filter(
user=request.user, person__isnull=False
)
.values("person_id", "person__name", "service")
.annotate(
total_events=Count("id"),
available_events=Count("id", filter=Q(availability_state="available")),
fading_events=Count("id", filter=Q(availability_state="fading")),
unavailable_events=Count(
"id", filter=Q(availability_state="unavailable")
),
unknown_events=Count("id", filter=Q(availability_state="unknown")),
native_presence_events=Count(
"id", filter=Q(source_kind="native_presence")
),
read_receipt_events=Count("id", filter=Q(source_kind="read_receipt")),
typing_events=Count(
"id",
filter=Q(source_kind="typing_start") | Q(source_kind="typing_stop"),
),
message_activity_events=Count(
"id",
filter=Q(source_kind="message_in") | Q(source_kind="message_out"),
),
inferred_timeout_events=Count(
"id", filter=Q(source_kind="inferred_timeout")
),
last_event_ts=Max("ts"),
)
.order_by("-total_events", "person__name", "service")
)
behavioral_stats, stats_source = self._behavioral_stats(request.user)
transport_stats = self._transport_stats(behavioral_stats)
totals = self._totals(behavioral_stats)
context = {
"settings_row": settings_row,
"contact_stats": contact_stats,
"settings_row": self._settings(request),
"behavioral_stats": behavioral_stats,
"behavioral_stats_source": stats_source,
"transport_stats": transport_stats,
"behavioral_totals": totals,
}
return render(request, self.template_name, context)
def _behavioral_stats(self, user):
try:
person_map = {
str(row["id"]): str(row["name"] or "")
for row in Person.objects.filter(user=user).values("id", "name")
}
rows = []
for row in list(
get_behavioral_availability_stats(user_id=int(user.id)) or []
):
person_id = str(row.get("person_id") or "").strip()
rows.append(
{
"person_id": person_id,
"person_name": person_map.get(person_id, person_id or "-"),
"service": str(row.get("transport") or "").strip().lower(),
"total_events": _to_int(row.get("total_events"), 0),
"presence_events": _to_int(row.get("presence_events"), 0),
"read_events": _to_int(row.get("read_events"), 0),
"typing_events": _to_int(row.get("typing_events"), 0),
"message_events": _to_int(row.get("message_events"), 0),
"abandoned_events": _to_int(row.get("abandoned_events"), 0),
"last_event_ts": _to_int(row.get("last_event_ts"), 0),
}
)
if rows:
return rows, "manticore"
except Exception:
pass
return list(get_shadow_behavioral_availability_stats(user=user)), "conversation_event_shadow"
def _transport_stats(self, behavioral_stats: list[dict]) -> list[dict]:
by_transport = {}
for row in list(behavioral_stats or []):
service = str(row.get("service") or "").strip().lower() or "-"
state = by_transport.setdefault(
service,
{
"service": service,
"contacts": 0,
"total_events": 0,
"presence_events": 0,
"read_events": 0,
"typing_events": 0,
"message_events": 0,
"abandoned_events": 0,
"last_event_ts": 0,
},
)
state["contacts"] += 1
for key in (
"total_events",
"presence_events",
"read_events",
"typing_events",
"message_events",
"abandoned_events",
):
state[key] += _to_int(row.get(key), 0)
state["last_event_ts"] = max(
int(state.get("last_event_ts") or 0),
_to_int(row.get("last_event_ts"), 0),
)
return sorted(
by_transport.values(),
key=lambda row: (-int(row.get("total_events") or 0), str(row.get("service") or "")),
)
def _totals(self, behavioral_stats: list[dict]) -> dict:
totals = {
"contacts": 0,
"total_events": 0,
"presence_events": 0,
"read_events": 0,
"typing_events": 0,
"message_events": 0,
"abandoned_events": 0,
}
for row in list(behavioral_stats or []):
totals["contacts"] += 1
for key in (
"total_events",
"presence_events",
"read_events",
"typing_events",
"message_events",
"abandoned_events",
):
totals[key] += _to_int(row.get(key), 0)
return totals