Files
MobilityOps-Public/backend/app/services/operations.py
T

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