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:
NuklearRabbit
2026-08-03 21:37:34 +02:00
co-authored by Claude Sonnet 5
parent 18344bc8b7
commit 6deb95524d
68 changed files with 1734 additions and 225 deletions
+4 -3
View File
@@ -5,6 +5,7 @@ import { useDemoManifest } from "../context/DemoManifestContext";
import { useLocaleFormat } from "../i18n/format";
import { Icon } from "../components/Icons";
import { IntegrationMark, LoadingState, PageHeader, SectionHeading } from "../components/PageChrome";
import { PRODUCT_NAME } from "../product";
const INTEGRATION_ICON: Record<string, "n8n" | "rag" | "mcp"> = {
n8n: "n8n",
@@ -36,7 +37,7 @@ export function AboutDemo() {
<div className="page">
<PageHeader
eyebrow={t("about.eyebrow")}
title={t("about.title")}
title={t("about.title", { productName: PRODUCT_NAME })}
description={manifest ? t("about.description", { orgName: manifest.organization_name }) : undefined}
/>
@@ -58,12 +59,12 @@ export function AboutDemo() {
<section className="record-surface about-card">
<h2>{t("about.problemTitle")}</h2>
<p>{t("about.problemBody", { orgName: manifest.organization_name })}</p>
<p>{t("about.problemBody", { orgName: manifest.organization_name, productName: PRODUCT_NAME })}</p>
</section>
<section className="record-surface about-card">
<h2>{t("about.scopeTitle")}</h2>
<p>{t("about.scopeBody")}</p>
<p>{t("about.scopeBody", { productName: PRODUCT_NAME })}</p>
</section>
<section className="record-surface about-card">
+2 -2
View File
@@ -36,7 +36,7 @@ function ChangeDiff({
const b = before?.[key];
const a = after?.[key];
if (JSON.stringify(b) === JSON.stringify(a)) continue;
const field = humanizeField(key);
const field = t(`fields.${key}`, { defaultValue: humanizeField(key) });
if (b === undefined) lines.push({ field, text: t("diff.setTo", { field, value: JSON.stringify(a) }) });
else if (a === undefined) lines.push({ field, text: t("diff.was", { field, value: JSON.stringify(b) }) });
else lines.push({ field, text: t("diff.changed", { field, before: JSON.stringify(b), after: JSON.stringify(a) }) });
@@ -163,7 +163,7 @@ export function Audit() {
<span className="table-subtext">{formatDateTime(e.occurred_at)}</span>
</div>
<div className="audit-group-meta">
<span><strong>{e.actor_label}</strong> <small className="table-subtext">{e.actor_type}</small></span>
<span><strong>{e.actor_label}</strong> <small className="table-subtext">{t(`actorTypes.${e.actor_type}`, { defaultValue: e.actor_type })}</small></span>
{e.entity_link ? (
<Link to={e.entity_link}>{e.entity_ref ?? e.entity_type}</Link>
) : (
+15 -1
View File
@@ -118,7 +118,21 @@ export function Automation() {
<StatusBadge status={r.status} label={t(`ledger.status${r.status.charAt(0).toUpperCase()}${r.status.slice(1)}`)} />
</td>
<td data-label={t("ledger.columns.attempts")}>{r.attempts}</td>
<td data-label={t("ledger.columns.lastError")}>{r.last_error ?? "—"}</td>
<td data-label={t("ledger.columns.lastError")}>
{r.last_error_code ? (
<>
<span>{t(`ledger.errorCodes.${r.last_error_code}`, { defaultValue: r.last_error ?? r.last_error_code })}</span>
{r.last_error && (
<details className="evidence-disclosure">
<summary>{t("common:actions.technicalDetails")}</summary>
<pre className="evidence-block">{r.last_error}</pre>
</details>
)}
</>
) : (
"—"
)}
</td>
<td data-label={t("ledger.columns.when")}>
<time dateTime={r.occurred_at}>{formatDateTime(r.occurred_at)}</time>
</td>
+3 -2
View File
@@ -9,6 +9,7 @@ import { StatusBadge } from "../components/Badge";
import { ReturnForm, ReturnResultPanel } from "../components/ReturnForm";
import { Icon } from "../components/Icons";
import { ErrorState, LoadingState, PageHeader } from "../components/PageChrome";
import { PRODUCT_NAME } from "../product";
export function BookingDetail() {
const { t } = useTranslation(["bookings", "returns"]);
@@ -73,11 +74,11 @@ export function BookingDetail() {
</dl></section>
{isReturnAnomalyScenario && !returnResult && canonicalOdometerKm !== null && (
<section className="record-surface scenario-callout" aria-label="Demo scenario">
<section className="record-surface scenario-callout" aria-label={t("returns:scenario.ariaLabel")}>
<Icon name="spark" />
<div>
<strong>{t("returns:scenario.title")}</strong>
<p>{t("returns:scenario.body", { odometer: formatNumber(canonicalOdometerKm) })}</p>
<p>{t("returns:scenario.body", { odometer: formatNumber(canonicalOdometerKm), productName: PRODUCT_NAME })}</p>
</div>
</section>
)}
+139 -15
View File
@@ -6,6 +6,7 @@ import type {
ApplyRecommendedStatusResult,
DataQualityIssueDetail as IssueDetail,
EntitySnapshot,
StatusRecommendation,
} from "../api/types";
import { SeverityBadge, StatusBadge } from "../components/Badge";
import { useAuth } from "../context/AuthContext";
@@ -443,15 +444,62 @@ function BookingOverlapPanel({ issue, onResolved }: { issue: IssueDetail; onReso
);
}
function statusLabel(t: (key: string, opts?: Record<string, unknown>) => string, status: string): string {
const raw = t(`fleet:statuses.${status}`, { defaultValue: status });
return raw.charAt(0).toUpperCase() + raw.slice(1);
}
function StatusRecommendationEvidence({ recommendation }: { recommendation: StatusRecommendation }) {
const { t } = useTranslation("quality");
const { formatNumber } = useLocaleFormat();
const facts = recommendation.facts;
const lines: string[] = [];
if (facts.active_booking_refs.length > 0) {
lines.push(
t("detail.vehicleStatusConflict.evidence.activeBookings", { refs: facts.active_booking_refs.join(", ") }),
);
}
if (facts.overlapping_booking_pairs.length > 0) {
lines.push(
t("detail.vehicleStatusConflict.evidence.overlappingBookings", {
pairs: facts.overlapping_booking_pairs.map((pair) => pair.join(" ↔ ")).join(", "),
}),
);
}
if (facts.service_threshold_reached) {
lines.push(
t("detail.vehicleStatusConflict.evidence.serviceThresholdReached", {
odometer: formatNumber(facts.odometer_km),
threshold: formatNumber(facts.next_service_km),
}),
);
}
if (facts.open_booking_overlap_issue_ref) {
lines.push(
t("detail.vehicleStatusConflict.evidence.openOverlapIssue", { ref: facts.open_booking_overlap_issue_ref }),
);
}
if (lines.length === 0) return null;
return (
<ul className="evidence-list">
{lines.map((line) => (
<li key={line}>{line}</li>
))}
</ul>
);
}
function VehicleStatusConflictPanel({ issue, onResolved }: { issue: IssueDetail; onResolved: () => void }) {
const { t } = useTranslation("quality");
const navigate = useNavigate();
const { manifest } = useDemoManifest();
const { open: guideOpen, currentIndex, completeAndAdvance } = useDemoGuide();
const [recommendation, setRecommendation] = useState<StatusRecommendation | null>(null);
const [loadingRecommendation, setLoadingRecommendation] = useState(false);
const [stale, setStale] = useState(false);
const [error, setError] = useState<string | null>(null);
const [result, setResult] = useState<ApplyRecommendedStatusResult | null>(null);
const [submitting, setSubmitting] = useState(false);
const [confirming, setConfirming] = useState(false);
function continueDemo() {
completeAndAdvance();
@@ -465,23 +513,52 @@ function VehicleStatusConflictPanel({ issue, onResolved }: { issue: IssueDetail;
// visibility via `result` rather than disappearing the instant the reload lands.
if (issue.status !== "open" && !result) return null;
async function loadRecommendation() {
setError(null);
setStale(false);
setLoadingRecommendation(true);
try {
const preview = await api.post<StatusRecommendation>(
`/api/v1/data-quality/issues/${issue.public_ref}/status-recommendation`,
);
setRecommendation(preview);
} catch (err) {
setError(err instanceof ApiError ? err.message : t("detail.vehicleStatusConflict.recommendationFailed"));
} finally {
setLoadingRecommendation(false);
}
}
async function handleApply() {
if (!recommendation) return;
setError(null);
setSubmitting(true);
try {
const applied = await api.post<ApplyRecommendedStatusResult>(
`/api/v1/data-quality/issues/${issue.public_ref}/apply-recommended-status`,
{ recommendation_token: recommendation.recommendation_token },
);
setResult(applied);
onResolved();
} catch (err) {
setError(err instanceof ApiError ? err.message : t("detail.vehicleStatusConflict.applyFailed"));
setConfirming(false);
if (err instanceof ApiError && err.code === "RECOMMENDATION_STALE") {
setError(t("detail.vehicleStatusConflict.staleRecommendation"));
setRecommendation(null);
setStale(true);
} else {
setError(err instanceof ApiError ? err.message : t("detail.vehicleStatusConflict.applyFailed"));
}
} finally {
setSubmitting(false);
}
}
const needsManualReview =
recommendation !== null && (recommendation.manual_review_required || !recommendation.safe_to_apply);
const noChangeNeeded = recommendation !== null && !needsManualReview && recommendation.recommended_status === null;
const hasSafeRecommendation =
recommendation !== null && !needsManualReview && recommendation.recommended_status !== null;
return (
<section className="panel" aria-labelledby="status-conflict-heading">
<SectionHeading
@@ -496,7 +573,12 @@ function VehicleStatusConflictPanel({ issue, onResolved }: { issue: IssueDetail;
{result ? (
<>
<p className="quiet-empty">
<Icon name="check" /> {t("detail.vehicleStatusConflict.applied", { status: result.applied_status, reason: result.reason })}
<Icon name="check" /> {t("detail.vehicleStatusConflict.applied", {
status: statusLabel(t, result.applied_status),
reason: t(`detail.vehicleStatusConflict.reasonCodes.${result.reason_code}`, {
defaultValue: result.reason_code,
}),
})}
</p>
<div className="result-links">
<Link className="button button-secondary" to="/audit">{t("detail.resolved.viewAudit")}<Icon name="chevron" /></Link>
@@ -508,21 +590,63 @@ function VehicleStatusConflictPanel({ issue, onResolved }: { issue: IssueDetail;
)}
</div>
</>
) : !confirming ? (
<button type="button" className="button button-primary" onClick={() => setConfirming(true)}>
{t("detail.vehicleStatusConflict.calculateAndApply")}
) : !recommendation ? (
<button type="button" className="button button-primary" onClick={loadRecommendation} disabled={loadingRecommendation}>
{loadingRecommendation
? t("detail.vehicleStatusConflict.loadingRecommendation")
: t(stale ? "detail.vehicleStatusConflict.reviewAgain" : "detail.vehicleStatusConflict.reviewRecommendation")}
</button>
) : (
<div className="confirm-bar" role="alertdialog" aria-label={t("detail.vehicleStatusConflict.confirmTitle")}>
<p>{t("detail.vehicleStatusConflict.confirmBody")}</p>
) : needsManualReview ? (
<div className="status-decision" role="status">
<h3>{t("detail.vehicleStatusConflict.manualReview.heading")}</h3>
<p>{t("detail.vehicleStatusConflict.manualReview.body")}</p>
<StatusRecommendationEvidence recommendation={recommendation} />
<p className="table-subtext">{t("detail.vehicleStatusConflict.manualReview.hint")}</p>
</div>
) : noChangeNeeded ? (
<div className="status-decision" role="status">
<h3>{t("detail.vehicleStatusConflict.noConflict.heading")}</h3>
<p>{t("detail.vehicleStatusConflict.noConflict.body")}</p>
</div>
) : hasSafeRecommendation && recommendation.recommended_status ? (
<div className="status-decision" role="group" aria-label={t("detail.vehicleStatusConflict.recommendedStatus")}>
<dl className="detail-grid">
<div>
<dt>{t("detail.vehicleStatusConflict.recommendedStatus")}</dt>
<dd>
<StatusBadge
status={recommendation.recommended_status}
label={t(`fleet:statuses.${recommendation.recommended_status}`, {
defaultValue: recommendation.recommended_status,
})}
/>
</dd>
</div>
</dl>
<h3>{t("detail.vehicleStatusConflict.whyHeading")}</h3>
<p>{t(`detail.vehicleStatusConflict.reasonCodes.${recommendation.recommendation_code}`, {
defaultValue: recommendation.recommendation_code,
})}</p>
<h3>{t("detail.vehicleStatusConflict.evidenceHeading")}</h3>
<StatusRecommendationEvidence recommendation={recommendation} />
<h3>{t("detail.vehicleStatusConflict.consequenceHeading")}</h3>
<ul className="evidence-list">
<li>{t("detail.vehicleStatusConflict.consequence.statusWillChange", {
status: statusLabel(t, recommendation.recommended_status),
})}</li>
<li>{t("detail.vehicleStatusConflict.consequence.issueWillBeRechecked")}</li>
<li>{t("detail.vehicleStatusConflict.consequence.changeWillBeAudited")}</li>
<li>{t("detail.vehicleStatusConflict.consequence.bookingsNotDeleted")}</li>
</ul>
<button type="button" className="button button-primary" onClick={handleApply} disabled={submitting}>
{submitting ? t("detail.vehicleStatusConflict.applying") : t("detail.vehicleStatusConflict.confirmYes")}
</button>
<button type="button" className="button button-secondary" onClick={() => setConfirming(false)} disabled={submitting}>
{t("list.cancel")}
{submitting
? t("detail.vehicleStatusConflict.applying")
: t("detail.vehicleStatusConflict.changeStatusTo", {
status: statusLabel(t, recommendation.recommended_status),
})}
</button>
</div>
)}
) : null}
</section>
);
}
+2 -1
View File
@@ -4,6 +4,7 @@ import { api, ApiError } from "../api/client";
import type { GroundedAnswer, KnowledgeHealth } from "../api/types";
import { Icon } from "../components/Icons";
import { PageHeader } from "../components/PageChrome";
import { PRODUCT_NAME } from "../product";
interface Exchange {
question: string;
@@ -121,7 +122,7 @@ export function Knowledge() {
<div className="knowledge-empty">
<span><Icon name="knowledge" /></span>
<h2>{t("emptyTitle")}</h2>
<p>{t("emptyDescription")}</p>
<p>{t("emptyDescription", { productName: PRODUCT_NAME })}</p>
<div className="retrieval-flow" aria-hidden="true">
<span>{t("retrievalFlow.question")}</span><i />
<span>{providerLabel}</span><i />
+3 -2
View File
@@ -6,6 +6,7 @@ import { useDemoManifest } from "../context/DemoManifestContext";
import { LanguageSwitcher } from "../components/LanguageSwitcher";
import type { Role } from "../api/types";
import { BrandMark, Icon } from "../components/Icons";
import { PRODUCT_NAME } from "../product";
export function Login() {
const { t } = useTranslation(["auth", "common"]);
@@ -25,14 +26,14 @@ export function Login() {
}
const orgName = manifest?.organization_name ?? t("common:orgName");
const description = t("defaultDescription");
const description = t("defaultDescription", { productName: PRODUCT_NAME });
return (
<main className="login-shell">
<section className="login-story" aria-labelledby="product-name">
<div className="brand-lockup login-brand">
<BrandMark className="brand-mark" />
<div><strong>{t("common:appName")}</strong><span>{t("brandTagline")}</span></div>
<div><strong>{PRODUCT_NAME}</strong><span>{t("brandTagline")}</span></div>
</div>
<div className="login-message">
<p className="eyebrow">{t("orgLine", { orgName })}</p>
+2 -2
View File
@@ -83,7 +83,7 @@ export function VehicleDetail() {
{vehicle.inspections.length === 0 && <li>{t("detail.noInspections")}</li>}
{vehicle.inspections.map((i) => (
<li key={i.public_ref}>
<span>{i.type}</span>
<span>{t(`fleet:detail.inspectionTypes.${i.type}`, { defaultValue: i.type })}</span>
<span>{formatNumber(i.odometer_km)} km</span>
<span>{t("detail.fuel", { percent: i.fuel_level_percent })}</span>
{i.damage_reported && <span className="badge severity-high">{t("detail.damage")}</span>}
@@ -99,7 +99,7 @@ export function VehicleDetail() {
{vehicle.maintenance.length === 0 && <li>{t("detail.noMaintenance")}</li>}
{vehicle.maintenance.map((m) => (
<li key={m.public_ref}>
<span>{m.category}</span>
<span>{t(`fleet:detail.maintenanceCategories.${m.category}`, { defaultValue: m.category })}</span>
<span>{m.summary}</span>
<time dateTime={m.occurred_at}>{formatShortDate(m.occurred_at)}</time>
</li>