# Fleet Ops correction and release — final evidence **Result: PASS** ## Commits - Source branch / commit (verified pre-correction baseline): `master` @ `18344bc8b7a75a2f868bf15bf498fc030ac6c34c` - Fix branch: `fix/fleet-ops-i18n-status-flow` - Final fix-branch commit: `284b3c7` (merged content identical to `2e4fb43`, which carries the evidence-summary localization fix) - Main-before-merge: `18344bc8b7a75a2f868bf15bf498fc030ac6c34c` (confirmed unchanged via `git fetch` + `git rev-parse origin/master` immediately before merging — no unexpected commits landed on master while this branch was in progress) - Merge commit: `de0bdea84fea01b4501deb7099107bc753c2e6d7` (`git merge --no-ff fix/fleet-ops-i18n-status-flow -m "merge: complete Fleet Ops localization and status resolution"`, zero conflicts) - Final main commit: `de0bdea84fea01b4501deb7099107bc753c2e6d7` - Deployed commit: `de0bdea84fea01b4501deb7099107bc753c2e6d7` (`.deploy/source-revision` on Unraid) - Gitea main branch: `master` (confirmed via `git fetch origin && git rev-parse origin/master` matching local `master` after push) - Live URL: `http://192.168.10.150:1236` Fix-branch commit history: `6deb955`, `e6539d1`, `ac4b163`, `1fdd2b3`, `1e40775`, `a7ac5ed`, `7851e80`, `cda2c32`, `2e4fb43`, `284b3c7`. ## What this correction fixed 1. **Status-recommendation flow redesigned** (sections 8A–8F). The old single opaque "calculate and apply recommended status" action is replaced by a single shared, pure evaluator (`backend/app/services/vehicle_status.py::evaluate_vehicle_status`, documented in `docs/fleet-ops-correction/vehicle-status-decision-table.md`) used identically by the scanner, a non-mutating preview endpoint (`POST /api/v1/data-quality/issues/{ref}/status-recommendation`), and a transactional apply endpoint (`POST .../apply-recommended-status`) that locks the row, recomputes facts, rejects a stale `recommendation_token`, refuses unsafe/manual- review recommendations, and re-validates post-write before resolving the issue. - Forbidden shortcuts eliminated: "maintenance + active booking" no longer auto-recommends "rented" (being in maintenance is itself now a blocking fact); "maintenance with nothing else wrong" no longer auto-clears to "available" (no fact proves maintenance is actually finished — release stays a manual decision). - Frontend: "Review recommendation" → a localized decision panel (current/ recommended status, why, evidence, consequences) → an exact "Change status to <status>" confirm action → result, or a distinct "Manual review required" state offering no generic apply button. 2. **MO-016 order independence** (section 9). Order independence does not mean "same final status regardless of order" — resolving the booking overlap first genuinely removes the conflict, correctly leaving nothing to apply. What holds either way: the recommendation always reflects real current facts (never a stale proxy), and nothing unsafe is ever applied (never "rented"). Proven by a backend test explicitly scoped to MO-016/DQ-DEMO-STATUS (the original version wasn't — `_first_open()` returned whichever of ~14 open `vehicle_status_conflict` issues was most recent, not necessarily MO-016's) and a browser-level Playwright test covering both orders. 3. **"Fleet Ops" is a non-localizable brand constant** (`frontend/src/product.ts`, backend `PRODUCT_NAME`), wired via `{{productName}}` interpolation everywhere the brand appeared in locale prose. A permanent test fails the build if any locale file ever defines the brand name or an `appName` key again. 4. **Dynamic backend prose converted to message codes + params** (sections 5/6/10): return status reasons, audit field/actor-type labels, automation `last_error` (new `last_error_code` column, migration `799d8800e241`), search results (sections/ vehicles/bookings/issues), and — found live on Unraid — the data-quality evidence summary. Raw technical text is demoted to a "Technical details" disclosure everywhere. 5. **Knowledge-base fixes**: the demo provider's tokenizer silently dropped accented characters (`[a-z0-9]+` split "véhicule" into "v"+"hicule"), breaking French retrieval broadly — fixed to include the Latin-1 accented range. Reweighted section scoring so a body match (real substance) outranks a heading/title match (a shallow structural hint) — the old weighting misranked the damage procedure behind an unrelated document for the brief's exact validation question in all 3 languages. Removed leftover "MobilityOps"/"PoC" mentions from 9 procedure documents. 6. **Search, audit, automation, maintenance/inspections localized** (section 10): backend returns stable codes + params only; the frontend localizes section labels, vehicle summaries, booking/issue statuses, audit action/field/actor labels, automation error explanations, and maintenance/inspection type labels. 7. **i18n test suite strengthened** (section 11): key parity, brand invariant, translation-quality (cross-locale identical-value detection), a hardcoded-JSX-text static scan (had to anchor on backreferenced closing-tag names — a naive `>text<` regex misread TypeScript generics as JSX), and a 3-language route matrix (every main route, no console errors, correct `html[lang]`, real page headings). ## Live-caught bug (the deployment validation earning its keep) Live validation on the freshly-deployed fix branch directly caught a real defect: every data-quality issue's top-of-page evidence summary was unconditionally showing raw, always-English text (e.g. *"vehicle marked available while reserved bookings conflict"*) in **all three languages**, because the frontend never finished the `evidence.signals` localization the backend had already been emitting (the backend code even had a comment describing the intended design that the frontend didn't implement). Fixed in commit `2e4fb43`: - `DataQualityIssueDetail.tsx` now renders `evidence.signals` through the operator's locale as the primary evidence text. - The four `DQ-DEMO-*` seed rows that anchor the guided demo's scripted scenarios now carry real, accurate signals computed at seed time (the duplicate-customer similarity score is the actual `SequenceMatcher` ratio on the seeded names, not invented). - Rows with no structured signals fall back to raw text rather than showing a blank summary; the one known filler placeholder gets its own localized rendering. - A regression test locks this in: the vehicle-status-conflict evidence summary must show localized text and must never contain the specific raw English sentence that was live-visible before the fix, in all 3 languages. Also found and fixed along the way: a frontend logic bug conflating "no conflict" with "manual review required" (both carry `safe_to_apply: false`), which showed a false "manual review required" panel for MO-016 after its booking overlap was resolved instead of the correct "no change needed" state (fixed in `1fdd2b3`). ## Translation coverage - All three locale files (`nl-BE`, `en-GB`, `fr-BE`) define exactly the same key set for every namespace (`i18n-coverage.spec.ts`, structural guarantee). - No locale file contains an empty string value. - No locale file defines the brand name or an `appName` key (brand-invariant test). - Cross-locale translation-quality check: for every string ≥8 characters of real prose, nl-BE ≠ en-GB, fr-BE ≠ en-GB, fr-BE ≠ nl-BE, with a precise, audited allowlist for genuine proper nouns/cognates (23 entries, each with a documented reason). - Hardcoded-JSX-text static scan: zero findings against the current codebase (verified against both false positives — TypeScript generics — and a deliberately-injected- then-reverted false negative). - 3-language route matrix: every main route (dashboard, vehicles, vehicle detail, bookings, booking detail, data quality, issue detail, automation, knowledge, audit, scenarios, about) opens cleanly in all 3 languages with no console errors, correct `html[lang]`, and a real page heading. - **Remaining visible wrong-language text**: none found. The one gap that existed (the data-quality evidence summary) was found live and fixed before merge. ## Branding - Visible product name: **Fleet Ops**, exactly, in all 3 languages, everywhere (login, topbar, footer "Fleet Ops Demo", document title, About page, Demo Guide, knowledge base). Verified structurally (brand-invariant test) and live (branding test across dashboard/vehicles/data-quality/audit/automation/knowledge pages in all 3 languages; visual screenshots of the login screen in nl-BE and fr-BE). - Technical identifier retained (by design, per the brief): repository name, local directory, package/module names, Compose project, deployment directory, database name, and the `/health` endpoint's `service: "mobilityops-api"` field remain "mobilityops" — none of these are visible UI text. - No visible "MobilityOps" or "PoC" anywhere in the UI or the demo knowledge base (9 procedure documents cleaned up; regression test in `test_knowledge.py` scans every procedure file for both strings). ## Status-preview / apply / manual-review / MO-016 ordering - **Preview**: verified non-mutating — the issue's `status` stays `"open"` after calling the preview endpoint and re-fetching it via a fresh request. - **Apply**: the confirm button names the exact target status ("Change status to Blocked" / "Status wijzigen naar Geblokkeerd" / "Changer le statut vers Bloqué"); applying resolves the issue and updates the vehicle atomically. - **Manual review**: MO-024 (active rental + service-threshold reached, a genuine fact contradiction) shows "Manual review required" with no generic apply button rendered at all. - **Stale token**: simulated by resolving the underlying booking overlap after the preview was fetched but before applying — the apply call is correctly rejected (`RECOMMENDATION_STALE`), the UI shows the "situation has changed" message, and the user must review again before a new apply is possible. - **MO-016 ordering**: both orders tested. Resolving the overlap first correctly leaves nothing to apply (vehicle stays "available", genuinely correct). Resolving the status conflict first safely blocks the vehicle; resolving the now-redundant overlap afterwards does not disturb it. Neither order ever produces "rented". ## Knowledge (per language) The brief's exact validation question, in each language, grounds on the damage procedure as the **primary** (not just top-3) source: - nl-BE: *"Wat moet ik doen wanneer een voertuig beschadigd terugkomt?"* → damage procedure, Dutch source, Dutch excerpt. - en-GB: *"What should I do when a vehicle returns with damage?"* → damage procedure, English source, English excerpt. - fr-BE: *"Que dois-je faire lorsqu'un véhicule revient endommagé ?"* → damage procedure, French source, French excerpt. This required two real fixes: a tokenizer bug that silently dropped accented characters (breaking French retrieval broadly) and a scoring-weight rebalance (body matches now outrank heading/title matches). ## Audit / automation - Audit: action labels localized (`workflow_retry` → "automatisering opnieuw geprobeerd" / "automation retried" / "automatisation relancée", etc.), field names localized (`operational_status` → "Operationele status" / "Operational status" / "Statut opérationnel"), actor types localized, raw technical codes only inside "Technical details". Verified live and via a dedicated Playwright test. - Automation: the seeded synthetic failure shows a localized primary explanation ("De workflowdienst was tijdelijk niet bereikbaar…") with the raw technical message ("Synthetic connection timeout to n8n") only under "Technical details". Verified live and via a dedicated Playwright test. ## Backend tests / lint / types - `pytest`: **151 passed**, 0 failed (clean checkout, local dev, and post-merge master — run four times across this correction, always 151/151). - `ruff check .`: all checks passed, every run. - `mypy app` (strict): no issues found in 49 source files, every run. - Alembic: `alembic upgrade head` from empty database lands on `799d8800e241` (the new `outbox_events.last_error_code` column); `downgrade -1` / `upgrade head` round-trip verified. ## Frontend build / Playwright - `npm ci`, `tsc -b`, `vite build`: clean, every run. - Full Playwright suite: **116 tests**, run repeatedly against the local dev stack, an isolated clean-checkout stack, the live fix-branch deployment, and the live post-merge master deployment — **116/116 passed** on the final master-deployment run and on the final local run. A handful of transient, sequential-run-only flakes occurred at various points across ~10 full-suite runs today (different test each time, e.g. a pre-existing logout-timing race in `AuthContext.logout()` unrelated to this branch); every single one was confirmed to pass cleanly in isolation. - Guided demo covered indirectly via `guided-demo-full.spec.ts`, `demo-guide.spec.ts`, and the route matrix across all 3 languages — no dedicated "run the guided tour end-to-end in French" script exists beyond what those specs plus the branding/route-matrix tests already exercise, since the guided tour's steps route through the same pages already covered per-language. ## Clean-checkout drill Fresh `git clone --branch fix/fleet-ops-i18n-status-flow` of only committed files into an isolated Compose project (`cleancheckfleetops`, ports 8129/1229/5679 to avoid colliding with the working dev stack). From empty volumes: build → up → `alembic upgrade head` → `reset_and_seed` (50 vehicles / 180 customers / 246 bookings / 27 data-quality issues / 20 workflow runs) → 151 backend tests + Ruff + mypy green → frontend build green → full Playwright suite green → final reset → `scenario_integrity.all_ready: true`. Isolated stack, containers, volumes, and images torn down afterward; working dev environment confirmed untouched. ## Unraid deployment Deployed via `git archive` → `scp` → extract into `/mnt/user/appdata/mobilityops` (preserving `.env` and persistent volumes) → `.deploy/source-revision` → rebuild `api`+`web` → `alembic upgrade head` → reset/reseed. Done twice: once for the fix branch (caught the evidence-summary bug), once for the final merged master. Both times: containers healthy, no errors in `api`/`web` container logs, full Playwright suite green against the live server, `scenario_integrity.all_ready: true` after final reset. RAGcore and MCP Hub were not activated (the demo `KnowledgeProvider` — deterministic local retrieval — remains what's live, per the brief's constraint against activating unvalidated live integrations). ## Responsive / accessibility - Breakpoint matrix (1440×1000, 1280×800, 1024×768, 768×1024, 430×932, 390×844, 360×800) × 3 languages: no horizontal overflow, localized headings visible (`responsive-i18n.spec.ts`). - Status-recommendation panel: keyboard-only activation of "Review recommendation" and "Change status to X" verified via focus assertions (not just click); reduced-motion emulated during the flow; status never conveyed by colour alone (the badge always carries its own localized text); `aria-live="polite"` added so the applied confirmation is announced to screen readers. ## Known limitations - A pre-existing, narrow timing race in `AuthContext.logout()` (clears local state and redirects before awaiting the server-side cookie-clearing POST) occasionally flakes one specific Playwright test only under heavy sequential load; not introduced by this branch, not fixed (out of this branch's scope), always passes in isolation. - The 11 generic `DQ-0xxx` filler seed rows (not tied to a named demo scenario) show a localized generic placeholder rather than rich structured evidence, since they carry no real underlying data gap to describe accurately (the CSV's placeholder text doesn't correspond to an actually-missing field on the referenced vehicles). - No dedicated "full guided demo in French, screenshot every step" script exists as a single artifact; coverage is composed from the route matrix, branding, and existing guided-demo specs, each run across all 3 languages. ## Screenshots `artifacts/fleet-ops-correction/screenshots/`, all captured live against `http://192.168.10.150:1236`: - `login-nl-BE.jpg` — login screen, Dutch (default), "Fleet Ops" brand + "Bedieningscentrum" subtitle. - `login-fr-BE.jpg` — login screen switched to French, "Fleet Ops" brand + "Centre de contrôle" subtitle, "Organisation de démo : Northstar Mobility (fictive)". - `dq-demo-status-fr-BE-collapsed.jpg` — DQ-DEMO-STATUS in French: the localized evidence summary ("Ce véhicule a deux réservations qui se chevauchent…") replacing the raw English sentence, in its collapsed pre-review state. - `dq-demo-status-fr-BE-clean-reload.jpg` — the same page after a clean reload, confirming the fix is stable across navigation. One capture attempt mid-session showed the brand rendered as "Vlootoperaties" instead of "Fleet Ops" — investigated immediately via `document.documentElement` inspection and confirmed to be **Chrome's own built-in page-translate feature** auto-triggering on the automation browser profile (`class="translated-ltr"`, `lang` rewritten to bare `"nl"` by Google Translate, not the app), re-triggering specifically on React DOM mutations from clicking through the panel. Not an application defect: a clean reload immediately after showed the correct "Fleet Ops" brand and correctly localized French content again, and none of the 116 Playwright tests (which run in a clean automated browser context without this extension behaviour) ever observed it. ## Rollback procedure 1. `ssh unraid`, `cd /mnt/user/appdata/mobilityops`. 2. `git archive --format=tar 18344bc -o` (from a local clone) → `scp` → extract, or restore from the previous `.deploy/source-revision` (`18344bc8b7a75a2f868bf15bf498fc030ac6c34c`). 3. `echo 18344bc8b7a75a2f868bf15bf498fc030ac6c34c > .deploy/source-revision`. 4. `docker compose -f compose.yaml -f compose.unraid.yaml build api web && ... up -d api web`. 5. `alembic downgrade e7b08389f47f` if the `last_error_code` column must also be rolled back (not required for a same-schema rollback within this correction's own history, only if reverting past the whole correction). 6. Re-seed and re-verify `scenario_integrity.all_ready: true`. The fix branch `fix/fleet-ops-i18n-status-flow` was not deleted.