from __future__ import annotations import json import time from django.core.management.base import BaseCommand, CommandError from core.events.manticore import get_recent_event_rows from core.models import ConversationEvent class Command(BaseCommand): help = "Quick non-mutating sanity check for recent canonical event writes." def _recent_rows(self, *, minutes: int, service: str, user_id: str, limit: int): cutoff_ts = int(time.time() * 1000) - (minutes * 60 * 1000) queryset = ConversationEvent.objects.filter(ts__gte=cutoff_ts).order_by("-ts") if service: queryset = queryset.filter(origin_transport=service) if user_id: queryset = queryset.filter(user_id=user_id) rows = list( queryset.values( "id", "user_id", "session_id", "ts", "event_type", "direction", "origin_transport", "trace_id", )[:limit] ) if rows: return rows, "django" try: manticore_rows = get_recent_event_rows( minutes=minutes, service=service, user_id=user_id, limit=limit, ) except Exception: manticore_rows = [] return manticore_rows, "manticore" if manticore_rows else "django" def add_arguments(self, parser): parser.add_argument("--minutes", type=int, default=120) parser.add_argument("--service", default="") parser.add_argument("--user-id", default="") parser.add_argument("--limit", type=int, default=200) parser.add_argument("--require-types", default="") parser.add_argument("--fail-if-empty", action="store_true", default=False) parser.add_argument("--json", action="store_true", default=False) def handle(self, *args, **options): minutes = max(1, int(options.get("minutes") or 120)) service = str(options.get("service") or "").strip().lower() user_id = str(options.get("user_id") or "").strip() limit = max(1, int(options.get("limit") or 200)) require_types_raw = str(options.get("require_types") or "").strip() fail_if_empty = bool(options.get("fail_if_empty")) as_json = bool(options.get("json")) required_types = [ item.strip().lower() for item in require_types_raw.split(",") if item.strip() ] rows, data_source = self._recent_rows( minutes=minutes, service=service, user_id=user_id, limit=limit, ) event_type_counts = {} for row in rows: key = str(row.get("event_type") or "") event_type_counts[key] = int(event_type_counts.get(key) or 0) + 1 missing_required_types = [ event_type for event_type in required_types if int(event_type_counts.get(event_type) or 0) <= 0 ] payload = { "minutes": minutes, "service": service, "user_id": user_id, "data_source": data_source, "count": len(rows), "event_type_counts": event_type_counts, "required_types": required_types, "missing_required_types": missing_required_types, "sample": rows[:25], } if as_json: self.stdout.write(json.dumps(payload, indent=2, sort_keys=True)) return self.stdout.write( f"event-ledger-smoke minutes={minutes} service={service or '-'} user={user_id or '-'} source={data_source} count={len(rows)}" ) self.stdout.write(f"event_type_counts={event_type_counts}") if required_types: self.stdout.write( f"required_types={required_types} missing_required_types={missing_required_types}" ) if fail_if_empty and len(rows) == 0: raise CommandError("No recent canonical event rows found.") if missing_required_types: raise CommandError( "Missing required event types: " + ", ".join(missing_required_types) )