fix: safe status-recommendation flow, MO-016 order independence, brand constant, message codes
- Add a single shared, pure vehicle-status evaluator (app/services/vehicle_status.py)
used identically by the data-quality scanner, a new non-mutating status-recommendation
preview endpoint, and a transactional apply endpoint with optimistic-concurrency token
revalidation -- eliminates the old opaque "calculate and apply" action and the unsafe
"maintenance + active booking -> auto rented" shortcut. Frontend
DataQualityIssueDetail.tsx now shows a review/decide/confirm panel with localized
why/evidence/consequence text in nl-BE/en-GB/fr-BE, with an exact "Change status to
<status>" confirm action per the brief.
- Fix MO-016 issue-order dependency: resolving the booking-overlap issue before vs.
after the status-conflict issue now converges on the same final vehicle status,
proven by test_mo_016_status_conflict_recommendation_is_order_independent.
- Make "Fleet Ops" a non-localizable brand constant (frontend/src/product.ts,
backend PRODUCT_NAME) via {{productName}} interpolation everywhere the brand name
appeared in locale prose; add a permanent test guarding against a translation file
ever defining the brand name or an "appName" key again.
- Convert dynamic backend prose to stable message codes + params: return status
reasons, audit field/actor-type labels, automation last_error, and search
section/vehicle/booking/issue results all now carry codes the frontend localizes,
with raw technical text demoted to a "Technical details" disclosure.
- docs/fleet-ops-correction/: gap audit, i18n inventory, and the vehicle-status
decision table documenting the evaluator's rules and safe-status principles.
148 backend tests + Ruff + mypy green; Alembic migration verified upgrade/downgrade;
frontend tsc/build and the i18n-coverage Playwright suite green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
18344bc8b7
commit
6deb95524d
@@ -40,6 +40,29 @@
|
||||
"was": "{{field}}: was {{value}}",
|
||||
"changed": "{{field}}: {{before}} → {{after}}"
|
||||
},
|
||||
"actorTypes": {
|
||||
"user": "User",
|
||||
"system": "System",
|
||||
"service": "Service"
|
||||
},
|
||||
"fields": {
|
||||
"status": "Status",
|
||||
"operational_status": "Operational status",
|
||||
"registration_number": "Registration number",
|
||||
"make": "Make",
|
||||
"model": "Model",
|
||||
"location": "Location",
|
||||
"first_name": "First name",
|
||||
"last_name": "Last name",
|
||||
"email": "Email",
|
||||
"phone": "Phone",
|
||||
"postal_code": "Postal code",
|
||||
"city": "City",
|
||||
"booking_end_odometer_km": "Booking end odometer",
|
||||
"vehicle_odometer_km": "Vehicle odometer",
|
||||
"survivor": "Retained profile",
|
||||
"loser": "Merged profile"
|
||||
},
|
||||
"actions": {
|
||||
"demo_login": "Logged in",
|
||||
"demo_logout": "Logged out",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"orgLine": "Demo organisation: {{orgName}} (fictional)",
|
||||
"headline1": "Every hand-off.",
|
||||
"headline2": "One clear view.",
|
||||
"defaultDescription": "Fleet Ops brings vehicle, booking and operational data together, supports rental processes, detects data-quality problems and automates controlled follow-up steps.",
|
||||
"defaultDescription": "{{productName}} brings vehicle, booking and operational data together, supports rental processes, detects data-quality problems and automates controlled follow-up steps.",
|
||||
"footnote": "Synthetic demo · no real customer or vehicle data · resettable at any time",
|
||||
"accessEyebrow": "Demo access",
|
||||
"accessHeading": "Choose how to start",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"appName": "Fleet Ops",
|
||||
"orgName": "Northstar Mobility",
|
||||
"brandTagline": "Control Centre",
|
||||
"actions": {
|
||||
@@ -35,7 +34,7 @@
|
||||
"fr-BE": "Français"
|
||||
},
|
||||
"footer": {
|
||||
"productLine": "Fleet Ops Demo",
|
||||
"productLine": "{{productName}} Demo",
|
||||
"locale": "Europe/Brussels · Synthetic demo data"
|
||||
},
|
||||
"demo": {
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"whatYouWillSee": "An overview of what's functionally implemented in this demo, what's synthetic, and which integrations aren't live yet.",
|
||||
"whyItMatters": "A demo is only convincing if visitors can verify for themselves what really works and what's still ahead.",
|
||||
"startAction": "Read the 'About this demo' page.",
|
||||
"expectedOutcome": "You can explain yourself what Fleet Ops is and isn't, with no verbal explanation needed."
|
||||
"expectedOutcome": "You can explain yourself what {{productName}} is and isn't, with no verbal explanation needed."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -146,16 +146,16 @@
|
||||
},
|
||||
"about": {
|
||||
"eyebrow": "About this demo",
|
||||
"title": "What Fleet Ops is and isn't",
|
||||
"title": "What {{productName}} is and isn't",
|
||||
"description": "{{orgName}} is a fictional rental organisation that makes this demo tangible — not a real company.",
|
||||
"loading": "Loading demo information…",
|
||||
"ctaTitle": "Prefer to jump right in?",
|
||||
"ctaBody": "The guided demo walks through all eight steps above in practice.",
|
||||
"ctaButton": "Start guided demo",
|
||||
"problemTitle": "The fictional problem",
|
||||
"problemBody": "{{orgName}} rents around 50 campers and vans from one main location. Bookings, returns, customer records and maintenance used to live in separate spreadsheets and verbal hand-offs, so problems (duplicate customers, incorrect odometer readings, double-booked vehicles) only surfaced late. Fleet Ops shows how one connected system flags these problems early and lets them be resolved under control.",
|
||||
"problemBody": "{{orgName}} rents around 50 campers and vans from one main location. Bookings, returns, customer records and maintenance used to live in separate spreadsheets and verbal hand-offs, so problems (duplicate customers, incorrect odometer readings, double-booked vehicles) only surfaced late. {{productName}} shows how one connected system flags these problems early and lets them be resolved under control.",
|
||||
"scopeTitle": "Who it's for and its scope",
|
||||
"scopeBody": "This demo is for anyone who wants to see how Fleet Ops tackles operational problems at a small rental company: Operations Managers and Rental Employees, and anyone evaluating the approach. The scope is deliberately focused on one connected proof of concept — no accounting, no payments, no public reservations, no full CRM or ERP.",
|
||||
"scopeBody": "This demo is for anyone who wants to see how {{productName}} tackles operational problems at a small rental company: Operations Managers and Rental Employees, and anyone evaluating the approach. The scope is deliberately focused on one connected proof of concept — no accounting, no payments, no public reservations, no full CRM or ERP.",
|
||||
"realTitle": "What really works",
|
||||
"realBody": "Everything below is functional code, not just a mockup: role-based access and sessions, vehicle and booking management, return processing with server-side validation, five data-quality rules each with its own resolution step, a full audit trail, automated delivery to n8n with bounded retries, Docker-based deployment, and an automated test suite (backend and Playwright end-to-end).",
|
||||
"syntheticTitle": "What's synthetic",
|
||||
|
||||
@@ -63,6 +63,14 @@
|
||||
"noQualityIssues": "No quality issues recorded.",
|
||||
"fuel": "Fuel {{percent}}%",
|
||||
"damage": "Damage",
|
||||
"technicalWarning": "Technical warning"
|
||||
"technicalWarning": "Technical warning",
|
||||
"inspectionTypes": {
|
||||
"departure": "Departure inspection",
|
||||
"return": "Return inspection"
|
||||
},
|
||||
"maintenanceCategories": {
|
||||
"periodic_service": "Periodic service",
|
||||
"repair": "Repair"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,13 @@
|
||||
"noAction": "—",
|
||||
"eventTypes": {
|
||||
"vehicle.returned.v1": "Vehicle return processed"
|
||||
},
|
||||
"errorCodes": {
|
||||
"connectionError": "The workflow service was temporarily unreachable. The return was safely saved and can be retried.",
|
||||
"malformedPayload": "The job contained incomplete data and could not be delivered. The underlying data remains safely stored.",
|
||||
"remoteReportedFailure": "The workflow service declined the delivery. The job can be retried.",
|
||||
"staleLeaseRecovered": "This job was recovered after an earlier delivery attempt stalled without a result.",
|
||||
"unknownError": "An unexpected error occurred while delivering this job."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"Which checks are required before checkout?"
|
||||
],
|
||||
"emptyTitle": "Evidence before answers",
|
||||
"emptyDescription": "Ask about returns, damage, inspections or another indexed procedure. Fleet Ops will not invent an answer when evidence is missing.",
|
||||
"emptyDescription": "Ask about returns, damage, inspections or another indexed procedure. {{productName}} will not invent an answer when evidence is missing.",
|
||||
"retrievalFlow": {
|
||||
"question": "Question",
|
||||
"sources": "Sources",
|
||||
|
||||
@@ -27,12 +27,22 @@
|
||||
"resetConfirmYes": "Yes, reset",
|
||||
"resetCancel": "Cancel",
|
||||
"resetFailed": "Could not reset demo data.",
|
||||
"searchLabel": "Search Fleet Ops",
|
||||
"searchLabel": "Search {{productName}}",
|
||||
"searchPlaceholder": "Search fleet, booking or section…",
|
||||
"searchShortcutHint": "Ctrl K",
|
||||
"searchSearching": "Searching…",
|
||||
"searchUnavailable": "Search is unavailable right now.",
|
||||
"searchNoResults": "No matches for \"{{query}}\".",
|
||||
"searchVehicleSummary": "{{make}} {{model}} · {{location}}",
|
||||
"searchSections": {
|
||||
"overview": "Operations dashboard",
|
||||
"fleet": "Vehicle registry",
|
||||
"bookings": "Rental bookings",
|
||||
"quality": "Quality workbench",
|
||||
"knowledge": "Procedure assistant",
|
||||
"integrations": "Automation and integration status",
|
||||
"audit": "Audit history"
|
||||
},
|
||||
"switchRole": "Switch role",
|
||||
"switchRoleTitle": "Switch demo role",
|
||||
"languageSwitcherLabel": "Change language"
|
||||
|
||||
@@ -180,13 +180,48 @@
|
||||
"heading": "Resolve the status conflict",
|
||||
"description": "One authoritative rule recommends a corrected operational status for this vehicle.",
|
||||
"currentStatus": "Current status",
|
||||
"calculateAndApply": "Calculate and apply recommended status",
|
||||
"confirmTitle": "Confirm status change",
|
||||
"confirmBody": "Apply the authoritative recommended status for this vehicle?",
|
||||
"reviewRecommendation": "Review recommendation",
|
||||
"loadingRecommendation": "Loading recommendation…",
|
||||
"recommendationFailed": "Could not load a recommendation.",
|
||||
"recommendedStatus": "Recommended status",
|
||||
"whyHeading": "Why",
|
||||
"evidenceHeading": "Facts",
|
||||
"consequenceHeading": "Consequence",
|
||||
"consequence": {
|
||||
"statusWillChange": "The vehicle status will change to {{status}}.",
|
||||
"issueWillBeRechecked": "This quality issue will be re-checked.",
|
||||
"changeWillBeAudited": "The change will be recorded in the audit trail.",
|
||||
"bookingsNotDeleted": "The bookings themselves are not deleted."
|
||||
},
|
||||
"changeStatusTo": "Change status to {{status}}",
|
||||
"applying": "Applying…",
|
||||
"confirmYes": "Yes, apply",
|
||||
"applied": "Applied {{status}} — {{reason}}",
|
||||
"applyFailed": "Could not apply a recommended status."
|
||||
"applyFailed": "Could not apply a recommended status.",
|
||||
"staleRecommendation": "The situation has changed since this recommendation was shown. Review it again before applying.",
|
||||
"reviewAgain": "Review recommendation again",
|
||||
"manualReview": {
|
||||
"heading": "Manual review required",
|
||||
"body": "The facts for this vehicle contradict each other. An Operations Manager needs to review this in person rather than apply an automatic status change.",
|
||||
"hint": "Use 'Defer' or 'Reject' below, or investigate the vehicle and the related bookings manually."
|
||||
},
|
||||
"noConflict": {
|
||||
"heading": "No change needed",
|
||||
"body": "This vehicle's current status already matches the facts."
|
||||
},
|
||||
"evidence": {
|
||||
"activeBookings": "Active booking(s): {{refs}}",
|
||||
"overlappingBookings": "Overlapping bookings: {{pairs}}",
|
||||
"serviceThresholdReached": "Odometer reading ({{odometer}} km) has reached the service threshold ({{threshold}} km).",
|
||||
"openOverlapIssue": "Open booking-overlap issue: {{ref}}"
|
||||
},
|
||||
"reasonCodes": {
|
||||
"vehicle.active_rental": "This vehicle has an active rental but is not recorded as rented. The status is corrected to reflect the actual situation.",
|
||||
"vehicle.service_threshold_reached": "This vehicle has reached its service threshold. It is set to maintenance so it is not deployed before servicing takes place.",
|
||||
"vehicle.booking_conflict": "This vehicle has two overlapping bookings. Blocking it prevents it from being considered available again before the booking conflict is resolved.",
|
||||
"vehicle.rental_ended": "The rental for this vehicle has ended and there is no active booking left. It is set back to available.",
|
||||
"vehicle.manual_review_required": "The facts for this vehicle contradict each other (for example, an active booking while the vehicle is in maintenance, or a service threshold reached alongside an active rental). This requires a human review rather than an automatic status change.",
|
||||
"vehicle.no_conflict": "This vehicle's current status already matches the facts. No change is needed."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,13 @@
|
||||
"odometerRegressionWarning": "Submitted odometer ({{submitted}} km) is below the canonical reading ({{canonical}} km). The canonical odometer will not change, and a data-quality issue will be opened.",
|
||||
"nextBookingRisk": "Next booking {{ref}} starts {{when}} — may be affected by this return.",
|
||||
"nextBookingLowRisk": "Next booking {{ref}} starts {{when}} — low risk.",
|
||||
"reasonCodes": {
|
||||
"returnBlockedDamageAndTechnical": "Both damage and a technical warning were reported on this return.",
|
||||
"returnBlockedDamage": "Damage was reported on this return.",
|
||||
"returnBlockedTechnicalWarning": "A technical warning was reported on this return.",
|
||||
"returnServiceThresholdReached": "The odometer reading has reached the {{threshold_km}} km service threshold.",
|
||||
"returnRoutedToCleaning": "No damage, technical warning or service threshold reached; the vehicle goes to cleaning."
|
||||
},
|
||||
"commitList": {
|
||||
"inspection": "Create a return inspection",
|
||||
"updateAtomic": "Update the booking and vehicle atomically",
|
||||
@@ -66,8 +73,9 @@
|
||||
"continueDemo": "Continue the demo"
|
||||
},
|
||||
"scenario": {
|
||||
"ariaLabel": "Demo scenario",
|
||||
"title": "Demo scenario: odometer anomaly",
|
||||
"body": "This vehicle currently reads {{odometer}} km. The form below is pre-filled with a return reading below that — a sign of a data-entry mistake or a mixed-up vehicle. Confirm the return to see how Fleet Ops detects and handles this.",
|
||||
"body": "This vehicle currently reads {{odometer}} km. The form below is pre-filled with a return reading below that — a sign of a data-entry mistake or a mixed-up vehicle. Confirm the return to see how {{productName}} detects and handles this.",
|
||||
"preparing": "Preparing scenario…"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user