Four read-only, service-token-protected MCP provider endpoints (operations summary, attention vehicles, vehicle details, knowledge search facade). Shared-secret auth reusing the M4 callback pattern. Service-request audit trail for every call. Extracted shared operations-summary logic out of the dashboard router to avoid duplicating retrieval logic. 66 backend tests passing, ruff clean. Verified all four endpoints and audit trail directly via curl against the live stack (no live MCP Hub instance available in this environment).
222 lines
4.7 KiB
Python
222 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, Field, conint
|
|
|
|
Role = Literal["operations_manager", "rental_employee"]
|
|
|
|
|
|
class DemoLoginRequest(BaseModel):
|
|
role: Role
|
|
|
|
|
|
class CurrentUser(BaseModel):
|
|
public_ref: str
|
|
display_name: str
|
|
role: Role
|
|
|
|
|
|
class VehicleOut(BaseModel):
|
|
public_ref: str
|
|
make: str
|
|
model: str
|
|
model_year: int
|
|
registration_number: str
|
|
location: str
|
|
operational_status: str
|
|
odometer_km: int
|
|
next_service_km: int
|
|
active: bool
|
|
attention: bool = False
|
|
|
|
|
|
class BookingSummaryOut(BaseModel):
|
|
public_ref: str
|
|
customer_ref: str
|
|
vehicle_ref: str
|
|
starts_at: datetime
|
|
ends_at: datetime
|
|
status: str
|
|
|
|
|
|
class BookingOut(BookingSummaryOut):
|
|
start_odometer_km: int | None
|
|
end_odometer_km: int | None
|
|
requirements_complete: bool
|
|
customer_name: str
|
|
|
|
|
|
class RegisterReturnRequest(BaseModel):
|
|
end_odometer_km: conint(ge=0)
|
|
fuel_level_percent: conint(ge=0, le=100)
|
|
cleanliness_ok: bool
|
|
damage_reported: bool
|
|
technical_warning: bool
|
|
notes: str | None = Field(default=None, max_length=2000)
|
|
|
|
|
|
class NextBookingRisk(BaseModel):
|
|
booking_ref: str
|
|
starts_at: datetime
|
|
at_risk: bool
|
|
|
|
|
|
class RegisterReturnResult(BaseModel):
|
|
booking_ref: str
|
|
vehicle_ref: str
|
|
inspection_ref: str
|
|
resulting_vehicle_status: str
|
|
odometer_regression: bool
|
|
quality_issue_ref: str | None
|
|
workflow_event_id: str
|
|
next_booking_risk: NextBookingRisk | None
|
|
|
|
|
|
class InspectionOut(BaseModel):
|
|
public_ref: str
|
|
booking_ref: str
|
|
type: str
|
|
fuel_level_percent: int
|
|
cleanliness_ok: bool
|
|
damage_reported: bool
|
|
technical_warning: bool
|
|
odometer_km: int
|
|
completed_at: datetime
|
|
|
|
|
|
class MaintenanceOut(BaseModel):
|
|
public_ref: str
|
|
occurred_at: datetime
|
|
odometer_km: int
|
|
category: str
|
|
summary: str
|
|
|
|
|
|
class DataQualityIssueOut(BaseModel):
|
|
public_ref: str
|
|
rule_type: str
|
|
entity_type: str
|
|
entity_ref: str
|
|
severity: str
|
|
status: str
|
|
evidence: dict[str, Any]
|
|
detected_at: datetime
|
|
resolved_at: datetime | None = None
|
|
|
|
|
|
class DataQualityIssueDetailOut(DataQualityIssueOut):
|
|
entity_snapshot: dict[str, Any] | None = None
|
|
related_snapshots: list[dict[str, Any]] = Field(default_factory=list)
|
|
|
|
|
|
class MergeCustomersRequest(BaseModel):
|
|
survivor_ref: str
|
|
field_overrides: dict[str, str] | None = None
|
|
|
|
|
|
class MergeCustomersResult(BaseModel):
|
|
issue_ref: str
|
|
survivor_ref: str
|
|
loser_ref: str
|
|
rewired_bookings: int
|
|
|
|
|
|
class ScanResultOut(BaseModel):
|
|
created: dict[str, int]
|
|
|
|
|
|
class VehicleDetailOut(VehicleOut):
|
|
bookings: list[BookingSummaryOut] = Field(default_factory=list)
|
|
inspections: list[InspectionOut] = Field(default_factory=list)
|
|
maintenance: list[MaintenanceOut] = Field(default_factory=list)
|
|
quality_issues: list[DataQualityIssueOut] = Field(default_factory=list)
|
|
|
|
|
|
class DashboardMetrics(BaseModel):
|
|
available: int
|
|
rented: int
|
|
cleaning: int
|
|
maintenance: int
|
|
blocked: int
|
|
open_quality_issues: int
|
|
pending_or_failed_workflows: int
|
|
|
|
|
|
class AttentionItem(BaseModel):
|
|
kind: Literal["quality_issue", "vehicle"]
|
|
severity: str
|
|
title: str
|
|
detail: str
|
|
link_type: Literal["vehicle", "booking", "customer"]
|
|
link_ref: str
|
|
issue_ref: str | None = None
|
|
|
|
|
|
class TodayItem(BaseModel):
|
|
kind: Literal["departure", "return"]
|
|
booking_ref: str
|
|
vehicle_ref: str
|
|
scheduled_at: datetime
|
|
|
|
|
|
class AutomationRunOut(BaseModel):
|
|
event_id: str
|
|
event_type: str
|
|
aggregate_ref: str
|
|
status: str
|
|
attempts: int
|
|
last_error: str | None
|
|
occurred_at: datetime
|
|
|
|
|
|
class DashboardOut(BaseModel):
|
|
metrics: DashboardMetrics
|
|
attention_items: list[AttentionItem]
|
|
today: list[TodayItem]
|
|
recent_automation: list[AutomationRunOut]
|
|
|
|
|
|
class OperationsSummaryOut(BaseModel):
|
|
tenant: str
|
|
metrics: DashboardMetrics
|
|
|
|
|
|
class AttentionVehicleOut(BaseModel):
|
|
vehicle_ref: str
|
|
severity: Literal["low", "medium", "high"]
|
|
rule_type: str
|
|
summary: str
|
|
detected_at: datetime
|
|
|
|
|
|
class McpVehicleDetailOut(BaseModel):
|
|
public_ref: str
|
|
make: str
|
|
model: str
|
|
model_year: int
|
|
location: str
|
|
operational_status: str
|
|
odometer_km: int
|
|
next_service_km: int
|
|
open_quality_issue_count: int
|
|
current_booking_ref: str | None
|
|
|
|
|
|
class McpKnowledgeSearchRequest(BaseModel):
|
|
question: str = Field(min_length=3, max_length=1000)
|
|
max_sources: int = Field(default=4, ge=1, le=8)
|
|
|
|
|
|
class AuditEventOut(BaseModel):
|
|
id: str
|
|
actor_type: str
|
|
actor_label: str
|
|
action: str
|
|
entity_type: str
|
|
entity_id: str | None
|
|
correlation_id: str
|
|
occurred_at: datetime
|
|
metadata: dict[str, Any] | None = None
|