from __future__ import annotations from datetime import date, datetime from fastapi import APIRouter, Depends from sqlalchemy import func, select from sqlalchemy.orm import Session from app.api.deps import get_current_user, get_db from app.core.config import get_settings from app.models.booking import Booking from app.models.customer import Customer from app.models.data_quality import DataQualityIssue from app.models.outbox import OutboxEvent from app.models.vehicle import Vehicle from app.schemas import ( AttentionItem, AutomationRunOut, CurrentUser, DashboardMetrics, DashboardOut, TodayItem, ) router = APIRouter(prefix="/api/v1/dashboard", tags=["dashboard"]) settings = get_settings() _SEVERITY_ORDER = {"high": 0, "medium": 1, "low": 2} def _today() -> date: return datetime.fromisoformat(settings.demo_today).date() @router.get("", response_model=DashboardOut) def get_dashboard( db: Session = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ) -> DashboardOut: status_counts = dict( db.execute( select(Vehicle.operational_status, func.count()).group_by(Vehicle.operational_status) ).all() ) open_issues = db.scalar( select(func.count()).select_from(DataQualityIssue).where(DataQualityIssue.status == "open") ) pending_or_failed = db.scalar( select(func.count()) .select_from(OutboxEvent) .where(OutboxEvent.delivery_status.in_(["pending", "failed"])) ) metrics = DashboardMetrics( available=status_counts.get("available", 0), rented=status_counts.get("rented", 0), cleaning=status_counts.get("cleaning", 0), maintenance=status_counts.get("maintenance", 0), blocked=status_counts.get("blocked", 0), open_quality_issues=open_issues or 0, pending_or_failed_workflows=pending_or_failed or 0, ) vehicles_by_id = {v.id: v for v in db.scalars(select(Vehicle)).all()} customers_by_id = {c.id: c for c in db.scalars(select(Customer)).all()} issues = db.scalars( select(DataQualityIssue) .where(DataQualityIssue.status == "open") .order_by(DataQualityIssue.detected_at.asc()) ).all() attention_items = [] for issue in issues: if issue.entity_type == "vehicle": entity = vehicles_by_id.get(issue.entity_id) link_type = "vehicle" else: entity = customers_by_id.get(issue.entity_id) link_type = "customer" link_ref = entity.public_ref if entity else "" title = f"{issue.rule_type.replace('_', ' ').title()} — {link_ref}" attention_items.append( AttentionItem( kind="quality_issue", severity=issue.severity, title=title, detail=issue.evidence_json.get("summary", ""), link_type=link_type, link_ref=link_ref, issue_ref=issue.public_ref, ) ) attention_items.sort(key=lambda item: _SEVERITY_ORDER.get(item.severity, 3)) attention_items = attention_items[:8] today = _today() bookings = db.scalars(select(Booking)).all() today_items: list[TodayItem] = [] for b in bookings: vehicle = vehicles_by_id.get(b.vehicle_id) vehicle_ref = vehicle.public_ref if vehicle else "" if b.starts_at.date() == today and b.status in ("reserved", "active"): today_items.append( TodayItem( kind="departure", booking_ref=b.public_ref, vehicle_ref=vehicle_ref, scheduled_at=b.starts_at, ) ) if b.ends_at.date() == today and b.status in ("active", "returned"): today_items.append( TodayItem( kind="return", booking_ref=b.public_ref, vehicle_ref=vehicle_ref, scheduled_at=b.ends_at, ) ) today_items.sort(key=lambda item: item.scheduled_at) recent = db.scalars( select(OutboxEvent).order_by(OutboxEvent.occurred_at.desc()).limit(5) ).all() recent_automation = [ AutomationRunOut( event_id=str(r.event_id), event_type=r.event_type, aggregate_ref=r.payload_json.get("aggregate_ref", ""), status=r.delivery_status, attempts=r.attempts, last_error=r.last_error, occurred_at=r.occurred_at, ) for r in recent ] return DashboardOut( metrics=metrics, attention_items=attention_items, today=today_items, recent_automation=recent_automation, )