GUI: dashboard Attention Queue presents a curated severity mix instead of pure severity-sort (grouped Now/Today/Later headers); Today's Movements seed data curated so a fresh reset shows a credible day (2+ departures, 2+ returns), with a new seed-integrity test; About Demo restructured into a compact grid with progressive disclosure for technical sections; Duplicate Merge shows match/conflict counts, hides matching fields by default, and previews the final merged record before confirmation. Repo hygiene: removed a stray empty `backend;C` directory and an untracked 31MB zip export; `.gitignore` now excludes future archive exports. n8n: fixed invalid JSON (a missing `},` between two node objects) in the committed `fleet-ops-vehicle-return.json` -- the file could not be parsed. Live-validated workflow 3 (RAGcore Procedure Sync): found and fixed a real defect (three body parameters had a stray trailing `}}`) and a missing Error Workflow wiring, both via the safe `n8n import:workflow` CLI path; exported the corrected, still- inactive workflow as the new source of truth and updated MANIFEST.md/check_drift.py. Publishing it (starts real daily unattended runs) remains a separate decision. RAGcore: root-caused and fixed (live, approved) the "zero retrieval candidates" bug -- a filesystem permission bug (`embedding_profiles.json` unreadable by the app's own runtime user) that broke every retrieval call before it reached Qdrant. Every other suspect (grants, scope resolution, Qdrant filters, embeddings) was verified healthy first. Found a second, deeper gap: the reranker adapter calls an Ollama HTTP route that does not exist on the deployed Ollama version, so `/v1/answers` still returns `not_answerable`. `KNOWLEDGE_PROVIDER` stays `demo` until that is resolved on the RAGcore side. Evidence-based MCP Hub integration status (real tool-call audit history, not just a boolean flag) replaces the old `configured`/`not_configured` guess. Full findings in `docs/final-integrations/current-state-audit.md`. Backend: 172 tests passing, ruff clean, mypy clean (50 files). Frontend: tsc clean, production build clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
422 lines
10 KiB
Python
422 lines
10 KiB
Python
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 WorkflowErrorReportIn(BaseModel):
|
|
workflow_id: str = Field(max_length=120)
|
|
workflow_name: str = Field(max_length=200)
|
|
execution_id: str = Field(max_length=120)
|
|
failed_at: datetime
|
|
error_category: Literal[
|
|
"timeout", "authError", "connectionError", "httpError", "validationError", "unknown"
|
|
]
|
|
error_summary: str = Field(max_length=500)
|
|
trigger_context: str | None = Field(default=None, max_length=200)
|
|
correlation_id: str | None = None
|
|
attempt: int = Field(default=1, ge=1, le=1000)
|
|
retry_action: str | None = Field(default=None, max_length=200)
|
|
|
|
|
|
class WorkflowErrorReportResult(BaseModel):
|
|
status: Literal["registered", "already_registered"]
|
|
execution_id: str
|
|
occurred_at: datetime
|
|
|
|
|
|
class ProcedureDocumentOut(BaseModel):
|
|
id: str
|
|
language: str
|
|
document_id: str
|
|
title: str
|
|
version: str
|
|
content: str
|
|
content_hash: str
|
|
|
|
|
|
class ProcedureListOut(BaseModel):
|
|
documents: list[ProcedureDocumentOut]
|
|
|
|
|
|
class ProcedureSyncResultIn(BaseModel):
|
|
execution_id: str = Field(max_length=120)
|
|
synced: int = Field(ge=0)
|
|
failed: int = Field(default=0, ge=0)
|
|
|
|
|
|
class ProcedureSyncResultResult(BaseModel):
|
|
status: Literal["registered", "already_registered"]
|
|
execution_id: str
|
|
occurred_at: datetime
|
|
|
|
|
|
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 N8nWorkflowEvidence(BaseModel):
|
|
name: str
|
|
built: bool
|
|
last_seen_at: datetime | None
|
|
|
|
|
|
class N8nErrorHandlerStatus(BaseModel):
|
|
total_failures_registered: int
|
|
latest_failure_at: datetime | None
|
|
latest_failure_workflow: str | None
|
|
|
|
|
|
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
|
|
expected_workflow_count: int
|
|
known_workflow_count: int
|
|
workflows: list[N8nWorkflowEvidence]
|
|
error_handler: N8nErrorHandlerStatus
|
|
|
|
|
|
class McpHubIntegrationStatus(BaseModel):
|
|
registration_enabled: bool
|
|
state: Literal["not_configured", "no_evidence", "operational"]
|
|
total_calls: int
|
|
last_tool: str | None = None
|
|
last_client: str | None = None
|
|
last_called_at: datetime | None = None
|
|
|
|
|
|
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
|