from __future__ import annotations from datetime import datetime from typing import Annotated, Any, Literal from pydantic import BaseModel, Field 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: Annotated[int, Field(ge=0)] fuel_level_percent: Annotated[int, Field(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 ReturnPreviewResult(BaseModel): booking_ref: str vehicle_ref: str canonical_odometer_km: int submitted_odometer_km: int odometer_regression: bool resulting_odometer_km: int resulting_vehicle_status: str status_reason: str status_reason_code: str status_reason_params: dict[str, str | int] = {} would_create_quality_issue: bool attention_reasons: list[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 ProvideFieldsRequest(BaseModel): fields: dict[str, str] class ResolveOdometerRegressionRequest(BaseModel): decision: Literal["retain_canonical", "correct_reading"] booking_ref: str | None = None corrected_odometer_km: Annotated[int, Field(ge=0)] | None = None note: str | None = Field(default=None, max_length=500) class ResolveOverlapRequest(BaseModel): booking_ref: str note: str | None = Field(default=None, max_length=500) class VehicleStatusFactsOut(BaseModel): active_booking_refs: list[str] overlapping_booking_pairs: list[list[str]] service_threshold_reached: bool odometer_km: int next_service_km: int open_booking_overlap_issue_ref: str | None = None class StatusRecommendationOut(BaseModel): current_status: str recommended_status: str | None recommendation_code: str safe_to_apply: bool manual_review_required: bool facts: VehicleStatusFactsOut blocking_reasons: list[str] recommendation_token: str class ApplyRecommendedStatusRequest(BaseModel): recommendation_token: str class ApplyRecommendedStatusResult(BaseModel): issue: DataQualityIssueOut applied_status: str reason_code: str class SearchResultItem(BaseModel): type: Literal["vehicle", "booking", "data_quality_issue", "section"] label: str detail_code: str detail_params: dict[str, str] = {} link: str class SearchResponse(BaseModel): query: str results: list[SearchResultItem] class N8nIntegrationStatus(BaseModel): configured: bool dispatch_enabled: bool state: Literal["disabled", "unavailable", "degraded", "operational", "no_evidence"] pending: int delivering: int failed: int succeeded: int latest_success_at: datetime | None latest_failure_at: datetime | None class McpHubIntegrationStatus(BaseModel): registration_enabled: bool state: Literal["not_configured", "configured"] class IntegrationStatusOut(BaseModel): n8n: N8nIntegrationStatus mcp_hub: McpHubIntegrationStatus class DemoScenarioOut(BaseModel): id: str estimated_minutes: int required_roles: list[Role] start_path: str ready: bool blocked_reason_code: str | None = None blocked_reason_params: dict[str, str] = {} class DemoIntegrationSummaryOut(BaseModel): key: Literal["n8n", "ragcore", "mcp_hub"] status_code: str detail_code: str detail_params: dict[str, str | int] = {} class DemoManifestOut(BaseModel): demo_mode: bool organization_name: str timezone: str synthetic_data: bool allow_reset: bool last_reset_at: datetime | None anchor_date: str | None guide_available: bool required_roles: list[Role] scenarios: list[DemoScenarioOut] integrations: list[DemoIntegrationSummaryOut] 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 rule_type: 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 last_error_code: 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 entity_ref: str | None = None entity_link: str | None = None correlation_id: str occurred_at: datetime before: dict[str, Any] | None = None after: dict[str, Any] | None = None metadata: dict[str, Any] | None = None