from __future__ import annotations from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import render from django.views import View 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): try: return int(value) except Exception: return int(default) def _to_bool(value, default=False): if value is None: return bool(default) text = str(value).strip().lower() if text in {"1", "true", "yes", "on", "y"}: return True if text in {"0", "false", "no", "off", "n"}: return False return bool(default) class AvailabilitySettingsPage(LoginRequiredMixin, View): template_name = "pages/availability-settings.html" def _settings(self, request): row, _ = ContactAvailabilitySettings.objects.get_or_create(user=request.user) return row def post(self, request): row = self._settings(request) row.enabled = _to_bool(request.POST.get("enabled"), row.enabled) row.show_in_chat = _to_bool(request.POST.get("show_in_chat"), row.show_in_chat) row.show_in_groups = _to_bool( request.POST.get("show_in_groups"), row.show_in_groups ) row.inference_enabled = _to_bool( request.POST.get("inference_enabled"), row.inference_enabled ) row.retention_days = max(1, _to_int(request.POST.get("retention_days"), 90)) row.fade_threshold_seconds = max( 30, _to_int(request.POST.get("fade_threshold_seconds"), 900) ) row.save( update_fields=[ "enabled", "show_in_chat", "show_in_groups", "inference_enabled", "retention_days", "fade_threshold_seconds", "updated_at", ] ) return self.get(request) def get(self, request): behavioral_stats, stats_source = self._behavioral_stats(request.user) transport_stats = self._transport_stats(behavioral_stats) totals = self._totals(behavioral_stats) context = { "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