78 lines
2.8 KiB
Python
78 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.data_quality import DataQualityIssue
|
|
from app.models.outbox import OutboxEvent
|
|
from app.models.vehicle import Vehicle
|
|
from app.schemas import DashboardMetrics
|
|
|
|
SEVERITY_ORDER = {"high": 0, "medium": 1, "low": 2}
|
|
|
|
|
|
def compute_metrics(db: Session) -> DashboardMetrics:
|
|
"""Shared operations-summary computation used by both the dashboard and the MCP
|
|
provider API, so the two never drift out of sync with two copies of the same query."""
|
|
status_counts: dict[str, int] = dict(
|
|
db.execute(
|
|
select(Vehicle.operational_status, func.count()).group_by(Vehicle.operational_status)
|
|
).all() # type: ignore[arg-type]
|
|
)
|
|
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"]))
|
|
)
|
|
return 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,
|
|
)
|
|
|
|
|
|
def list_attention_vehicles(
|
|
db: Session, minimum_severity: str = "medium", on_or_before: date | None = None, limit: int = 20
|
|
) -> list[dict]:
|
|
max_rank = SEVERITY_ORDER.get(minimum_severity, 1)
|
|
stmt = (
|
|
select(DataQualityIssue)
|
|
.where(DataQualityIssue.entity_type == "vehicle", DataQualityIssue.status == "open")
|
|
.order_by(DataQualityIssue.detected_at.asc())
|
|
)
|
|
if on_or_before is not None:
|
|
stmt = stmt.where(func.date(DataQualityIssue.detected_at) <= on_or_before)
|
|
issues = db.scalars(stmt).all()
|
|
filtered = [i for i in issues if SEVERITY_ORDER.get(i.severity, 3) <= max_rank]
|
|
filtered.sort(key=lambda i: SEVERITY_ORDER.get(i.severity, 3))
|
|
|
|
vehicle_ids = {i.entity_id for i in filtered}
|
|
vehicles_by_id = {
|
|
v.id: v for v in db.scalars(select(Vehicle).where(Vehicle.id.in_(vehicle_ids))).all()
|
|
}
|
|
|
|
results = []
|
|
for issue in filtered[:limit]:
|
|
vehicle = vehicles_by_id.get(issue.entity_id)
|
|
if vehicle is None:
|
|
continue
|
|
results.append(
|
|
{
|
|
"vehicle_ref": vehicle.public_ref,
|
|
"severity": issue.severity,
|
|
"rule_type": issue.rule_type,
|
|
"summary": issue.evidence_json.get("summary", ""),
|
|
"detected_at": issue.detected_at,
|
|
}
|
|
)
|
|
return results
|