from __future__ import annotations import time from django.core.management.base import BaseCommand from core.events.behavior import summarize_metrics from core.events.manticore import get_event_ledger_backend from core.util import logs log = logs.get_logger("gia_analysis") class Command(BaseCommand): help = "Compute behavioral metrics from Manticore event rows into gia_metrics." def add_arguments(self, parser): parser.add_argument("--once", action="store_true", default=False) parser.add_argument("--user-id", type=int) parser.add_argument("--person-id") parser.add_argument("--sleep-seconds", type=float, default=60.0) parser.add_argument("--window-days", nargs="*", type=int, default=[1, 7, 30, 90]) def _run_cycle( self, *, user_id: int | None = None, person_id: str = "", window_days: list[int] | None = None, ) -> int: backend = get_event_ledger_backend() now_ms = int(time.time() * 1000) baseline_since = now_ms - (90 * 86400000) windows = sorted({max(1, int(value)) for value in list(window_days or [1, 7, 30, 90])}) targets = backend.list_event_targets(user_id=user_id) if person_id: targets = [ row for row in targets if str(row.get("person_id") or "").strip() == str(person_id).strip() ] written = 0 for target in targets: target_user_id = int(target.get("user_id") or 0) target_person_id = str(target.get("person_id") or "").strip() if target_user_id <= 0 or not target_person_id: continue baseline_rows = backend.fetch_events( user_id=target_user_id, person_id=target_person_id, since_ts=baseline_since, ) if not baseline_rows: continue for window in windows: since_ts = now_ms - (int(window) * 86400000) window_rows = [ row for row in baseline_rows if int(row.get("ts") or 0) >= since_ts ] metrics = summarize_metrics(window_rows, baseline_rows) for metric, values in metrics.items(): backend.upsert_metric( user_id=target_user_id, person_id=target_person_id, window_days=int(window), metric=metric, value_ms=int(values.get("value_ms") or 0), baseline_ms=int(values.get("baseline_ms") or 0), z_score=float(values.get("z_score") or 0.0), sample_n=int(values.get("sample_n") or 0), computed_at=now_ms, ) written += 1 return written def handle(self, *args, **options): once = bool(options.get("once")) sleep_seconds = max(1.0, float(options.get("sleep_seconds") or 60.0)) user_id = options.get("user_id") person_id = str(options.get("person_id") or "").strip() window_days = list(options.get("window_days") or [1, 7, 30, 90]) while True: written = self._run_cycle( user_id=user_id, person_id=person_id, window_days=window_days, ) self.stdout.write(f"gia-analysis wrote={written}") if once: return time.sleep(sleep_seconds)