chore: remove generated deployment and audit evidence
This commit is contained in:
@@ -1,284 +0,0 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user