Files
MobilityOps/backend/app/schemas.py
T
NuklearRabbitandClaude Sonnet 5 337f8716bb polish: rebrand to Fleet Ops, add trilingual i18n, adaptive demo guide, and UX overhaul
Rebrands the product from MobilityOps to Fleet Ops across the UI, backend defaults and
knowledge base, and makes nl-BE/en-GB/fr-BE full first-class languages: i18next with
eager-bundled per-namespace resources, a persisted accessible language switcher (topbar
and mobile drawer), locale-aware date/number formatting, and a coverage test that fails
the build on any missing or empty translation key.

Backend dynamic content (demo scenarios, blocked-reason text, integration status) moves
from fixed English/Dutch prose to stable message codes + params so the frontend can
localize it; the demo knowledge base gains a fully translated NL/EN/FR procedure corpus
(11 documents each) with per-language retrieval and localized evidence-state messages.

The Demo Guide becomes breakpoint-adaptive: a docked rail on extra-wide desktop, a
floating panel that auto-collapses to a persistent, closable progress chip on standard
desktop/tablet, and a collapsed/half/full bottom sheet on mobile -- with scroll+focus+
highlight on "go to this step", Escape handling, and reduced-motion support.

The Data Quality Workbench gets accessible choice-card decisions with a clear primary/
secondary/tertiary action hierarchy; the Automation ledger groups repeated successes and
uses meaningful short refs; the Audit trail groups events by correlation id with human
action labels and readable before/after diffs. Attention Queue, Today's movements,
Vehicles, Bookings and Data Quality rows are fully clickable (stretched-link pattern)
with independent secondary links, keyboard support and mobile touch targets.

Fixes a topbar overflow on mobile caused by the new language switcher (moved into the
mobile drawer at <=960px) and two dangling aria-labelledby references introduced this
session. Updates all affected Playwright specs for the new nl-BE default and the new
Audit/DemoGuide DOM structure, and adds new i18n-coverage, demo-guide-adaptive and
clickable-rows specs. 131 backend tests, Ruff and mypy, and 71 Playwright tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 18:33:22 +02:00

327 lines
7.4 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
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 ApplyRecommendedStatusResult(BaseModel):
issue: DataQualityIssueOut
applied_status: str
reason: str
class SearchResultItem(BaseModel):
type: Literal["vehicle", "booking", "data_quality_issue", "section"]
label: str
detail: 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
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