from __future__ import annotations from datetime import timedelta from typing import Any from django.conf import settings from django.db.models import Count, Q, Sum from django.utils import timezone from apps.jobs.models import JobPosting, ScoreRun from apps.jobs.services.relevance import it_relevance_query from apps.notifications.models import DigestOutbox, ReminderOutbox from apps.profiles.models import SearchProfile from apps.sources.models import Source, SourceRun from ..health import readiness def build_automation_cockpit() -> dict[str, Any]: """Build an honest operational read model without probing unsafe hidden endpoints.""" since = timezone.now() - timedelta(hours=24) recent_runs = SourceRun.objects.filter(started_at__gte=since) run_metrics = recent_runs.aggregate( discovered=Sum("discovered_count"), extracted=Sum("extracted_count"), created=Sum("created_count"), updated=Sum("updated_count"), duplicates=Sum("duplicate_count"), failures=Count("id", filter=Q(status=SourceRun.Status.FAILED)), ) notification_counts = { "pending": DigestOutbox.objects.filter(status=DigestOutbox.Status.PENDING).count() + ReminderOutbox.objects.filter(status=ReminderOutbox.Status.PENDING).count(), "failed": DigestOutbox.objects.filter(status=DigestOutbox.Status.FAILED).count() + ReminderOutbox.objects.filter(status=ReminderOutbox.Status.FAILED).count(), "sent_24h": DigestOutbox.objects.filter( status=DigestOutbox.Status.SENT, sent_at__gte=since ).count() + ReminderOutbox.objects.filter( status=ReminderOutbox.Status.SENT, sent_at__gte=since ).count(), } source_total = Source.objects.count() source_active = Source.objects.filter(status=Source.Status.ACTIVE).count() active_jobs = JobPosting.objects.filter(status=JobPosting.Status.ACTIVE) phase_rows = [ {"key": "discover", "label": "Ontdekken", "value": source_total, "unit": "bronnen"}, {"key": "fetch", "label": "Ophalen", "value": recent_runs.count(), "unit": "runs / 24u"}, { "key": "extract", "label": "Extraheren", "value": run_metrics["extracted"] or 0, "unit": "items", }, { "key": "normalize", "label": "Normaliseren", "value": (run_metrics["created"] or 0) + (run_metrics["updated"] or 0), "unit": "records", }, { "key": "dedupe", "label": "Dedupliceren", "value": run_metrics["duplicates"] or 0, "unit": "herkend", }, { "key": "filter", "label": "IT-filter", "value": active_jobs.exclude(it_relevance_query()).count(), "unit": "uit beeld", }, { "key": "analyze", "label": "Analyseren", "value": ScoreRun.objects.filter(created_at__gte=since).count(), "unit": "scores / 24u", }, { "key": "notify", "label": "Notificeren", "value": notification_counts["sent_24h"], "unit": "verzonden / 24u", }, ] return { "health": readiness(), "source_total": source_total, "source_active": source_active, "run_metrics": run_metrics, "run_count": recent_runs.count(), "active_jobs": active_jobs.filter(it_relevance_query()).count(), "review_jobs": ScoreRun.objects.filter( recommendation__in=[ScoreRun.Recommendation.STRONG, ScoreRun.Recommendation.POSSIBLE], job__status=JobPosting.Status.ACTIVE, ).count(), "notification_counts": notification_counts, "phase_rows": phase_rows, "recent_runs": list( SourceRun.objects.select_related("source").order_by("-started_at")[:10] ), "source_rows": Source.objects.annotate(run_count=Count("runs")).order_by("name")[:12], "queue_state": "Eager/lokaal" if settings.CELERY_TASK_ALWAYS_EAGER else "Geconfigureerd, worker niet gemeten", "ai_state": "Geconfigureerd" if settings.OLLAMA_ENABLED and settings.OLLAMA_MODEL else "Niet geconfigureerd", "ai_profiles": SearchProfile.objects.filter(ai_scoring_enabled=True).count(), "browser_state": "Geen aparte browserextractor geconfigureerd", }