fix: localize the primary data-quality evidence summary (live-caught on Unraid)
Live validation on the deployed fix branch caught a real bug: every data-quality issue's top-of-page "Evidence summary" line rendered the raw, always-English legacy evidence.summary string unconditionally -- in all three languages -- even though the backend has been emitting structured, localizable evidence.signals for a while (app/services/data_quality.py already documented this exact intent). The frontend side of that conversion was never finished. - DataQualityIssueDetail.tsx now renders evidence.signals through the operator's locale as the primary summary; the raw evidence.summary string is only visible inside "Technical details" (via the existing EvidenceDisclosure JSON dump). - The four DQ-DEMO-* seed rows that anchor the guided demo's scripted scenarios now carry real, accurate signals computed at seed time (duplicate-customer's similarity score is the actual SequenceMatcher ratio on the seeded names, not invented) instead of only a legacy English sentence. - Rows with no structured signals (generic filler seed data) fall back to the raw text rather than showing a blank summary; the one known placeholder string gets its own localized rendering so it never displays as English filler either. - New regression test: the vehicle_status_conflict evidence summary must show localized text and must never contain the specific raw English sentence that was live-visible before this fix, in all 3 languages. 151 backend tests, Ruff, mypy green; full local Playwright suite green (a couple of sequential-run-only flakes, both confirmed to pass in isolation and unrelated to this change). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
cda2c32bd0
commit
2e4fb43f09
+47
-13
@@ -4,6 +4,7 @@ import csv
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, date, datetime, timedelta
|
||||
from difflib import SequenceMatcher
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import delete, insert, update
|
||||
@@ -108,21 +109,22 @@ def load_seed(db: Session) -> SeedResult:
|
||||
|
||||
customer_id_by_ref: dict[str, uuid.UUID] = {}
|
||||
customer_rows = []
|
||||
customer_row_by_ref: dict[str, dict] = {}
|
||||
for row in _read_csv("customers.csv"):
|
||||
cid = uuid.uuid4()
|
||||
customer_id_by_ref[row["public_ref"]] = cid
|
||||
customer_rows.append(
|
||||
{
|
||||
"id": cid,
|
||||
"public_ref": row["public_ref"],
|
||||
"first_name": row["first_name"],
|
||||
"last_name": row["last_name"],
|
||||
"email": row["email"] or None,
|
||||
"phone": row["phone"] or None,
|
||||
"postal_code": row["postal_code"] or None,
|
||||
"city": row["city"] or None,
|
||||
}
|
||||
)
|
||||
customer_row = {
|
||||
"id": cid,
|
||||
"public_ref": row["public_ref"],
|
||||
"first_name": row["first_name"],
|
||||
"last_name": row["last_name"],
|
||||
"email": row["email"] or None,
|
||||
"phone": row["phone"] or None,
|
||||
"postal_code": row["postal_code"] or None,
|
||||
"city": row["city"] or None,
|
||||
}
|
||||
customer_rows.append(customer_row)
|
||||
customer_row_by_ref[row["public_ref"]] = customer_row
|
||||
db.execute(insert(Customer), customer_rows)
|
||||
counts["customers"] = len(customer_rows)
|
||||
# Second pass for merged_into (self-referencing FK) since target must exist first.
|
||||
@@ -223,11 +225,42 @@ def load_seed(db: Session) -> SeedResult:
|
||||
return "customer", customer_id_by_ref[entity_ref]
|
||||
return "vehicle", vehicle_id_by_ref[entity_ref]
|
||||
|
||||
def _seed_signals(public_ref: str, entity_ref: str, related_refs: list[str]) -> list[dict]:
|
||||
# The four named DQ-DEMO-* rows anchor the guided demo's scripted scenarios, so
|
||||
# they carry real, accurate structured signals (not just a legacy English
|
||||
# sentence) -- the frontend renders these as the primary, localized evidence;
|
||||
# see docs/fleet-ops-correction/current-gap-audit.md §6.
|
||||
if public_ref == "DQ-DEMO-DUPLICATE":
|
||||
a = customer_row_by_ref[entity_ref]
|
||||
b = customer_row_by_ref[related_refs[0]]
|
||||
name_a = f"{a['first_name']} {a['last_name']}".strip().lower()
|
||||
name_b = f"{b['first_name']} {b['last_name']}".strip().lower()
|
||||
ratio = SequenceMatcher(None, name_a, name_b).ratio()
|
||||
return [
|
||||
{"code": "duplicate.exact_email"},
|
||||
{"code": "duplicate.exact_phone"},
|
||||
{"code": "duplicate.same_postal_code"},
|
||||
{"code": "duplicate.similar_name", "params": {"score": round(ratio, 2)}},
|
||||
]
|
||||
if public_ref == "DQ-DEMO-OVERLAP":
|
||||
return [{"code": "overlap.reserved_bookings", "params": {"refs": related_refs}}]
|
||||
if public_ref == "DQ-DEMO-STATUS":
|
||||
return [{"code": "vehicle.booking_conflict"}]
|
||||
if public_ref == "DQ-DEMO-ATTENTION":
|
||||
return [
|
||||
{
|
||||
"code": "attention.upcoming_booking_missing_inspection",
|
||||
"params": {"booking_ref": related_refs[0] if related_refs else ""},
|
||||
}
|
||||
]
|
||||
return []
|
||||
|
||||
dq_rows = []
|
||||
now = datetime.now(UTC)
|
||||
for row in _read_csv("data_quality_issues.csv"):
|
||||
entity_type, entity_id = resolve_entity(row["entity_ref"])
|
||||
related_ref = row.get("related_ref") or ""
|
||||
related_refs = related_ref.split("|") if related_ref else []
|
||||
dq_rows.append(
|
||||
{
|
||||
"id": uuid.uuid4(),
|
||||
@@ -240,7 +273,8 @@ def load_seed(db: Session) -> SeedResult:
|
||||
"evidence_json": {
|
||||
"summary": row["evidence"],
|
||||
"entity_ref": row["entity_ref"],
|
||||
"related_refs": related_ref.split("|") if related_ref else [],
|
||||
"related_refs": related_refs,
|
||||
"signals": _seed_signals(row["public_ref"], row["entity_ref"], related_refs),
|
||||
},
|
||||
"proposed_action_json": {},
|
||||
"detected_at": now,
|
||||
|
||||
Reference in New Issue
Block a user