from __future__ import annotations from django.db.models import Q from core.models import ContactAvailabilityEvent, ContactAvailabilitySpan, Person, User from .engine import ensure_fading_state from .inference import now_ms def spans_for_range( *, user: User, person: Person, start_ts: int, end_ts: int, service: str = "", limit: int = 200, ): qs = ContactAvailabilitySpan.objects.filter( user=user, person=person, ).filter( Q(start_ts__lte=end_ts) & Q(end_ts__gte=start_ts) ) if service: qs = qs.filter(service=str(service).strip().lower()) ensure_fading_state( user=user, person=person, person_identifier=None, service=(str(service or "").strip().lower() or "signal"), at_ts=now_ms(), ) return list(qs.order_by("-end_ts")[: max(1, min(int(limit or 200), 500))]) def latest_state_for_people(*, user: User, person_ids: list, service: str = "") -> dict: out = {} if not person_ids: return out qs = ContactAvailabilityEvent.objects.filter(user=user, person_id__in=person_ids) if service: qs = qs.filter(service=str(service).strip().lower()) rows = list(qs.order_by("person_id", "-ts", "-id")) seen = set() for row in rows: person_key = str(row.person_id) if person_key in seen: continue seen.add(person_key) out[person_key] = { "state": str(row.availability_state or "unknown"), "confidence": float(row.confidence or 0.0), "service": str(row.service or ""), "ts": int(row.ts or 0), "source_kind": str(row.source_kind or ""), } return out