fix: localize dashboard evidence, explain blocked vehicles, clarify pending odometers
Three content defects found by a live reviewer:
- Dashboard attention subtext was raw, untranslated evidence.summary text, and for
11 of 15 seeded issues that text was literally "Synthetic deterministic seed
issue". AttentionItem now exposes evidence_signals (stable code + params, same
shape as the issue detail page) instead of a detail string; the frontend renders
them through a shared describeEvidenceSignal() used by both the dashboard and the
issue detail page. Every previously-placeholder seed row now cites a real,
per-rule-type fact (a genuinely crossed service threshold, a genuinely blank
field, or a real pair of booking odometer readings) instead of invented prose.
- 5 of 7 blocked vehicles had no quality issue at all and one had only a resolved
one, so "needs attention" led nowhere. Each now has a real open
missing_required_field issue backed by a genuinely blank field (no schema change,
no migration -- reuses the existing data-quality pipeline).
- Booking odometer fields showing a bare "-" for 25 reserved + 1 active booking now
show a localized explanation ("trip hasn't started yet" / "not yet closed").
MO-024's rented-but-service-overdue contradiction was already caught by the
vehicle-status evaluator (DQ-SCAN, vehicle.manual_review_required) -- added a
regression test rather than new logic.
Also fixed a related bug the above exposed: the vehicle entity_snapshot omitted
registration_number entirely, so the "provide missing fields" form always showed
it blank regardless of the real value.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3808bbe132
commit
4faac24b5a
@@ -24,6 +24,22 @@ def test_dashboard_attention_items_link_to_records(ops_client):
|
||||
assert item["severity"] in ("low", "medium", "high")
|
||||
|
||||
|
||||
def test_dashboard_attention_items_expose_localizable_signals_not_raw_text(ops_client):
|
||||
"""The dashboard subtext used to be raw, untranslated evidence text (and for most
|
||||
seeded issues, the meaningless placeholder 'Synthetic deterministic seed issue').
|
||||
The API must never emit prose here -- only stable signal codes + params, exactly
|
||||
like the data-quality issue detail page, for the frontend to localize."""
|
||||
response = ops_client.get("/api/v1/dashboard")
|
||||
body = response.json()
|
||||
assert len(body["attention_items"]) > 0
|
||||
for item in body["attention_items"]:
|
||||
assert "detail" not in item
|
||||
assert len(item["evidence_signals"]) > 0
|
||||
for signal in item["evidence_signals"]:
|
||||
assert signal["code"]
|
||||
assert signal["code"] != "Synthetic deterministic seed issue"
|
||||
|
||||
|
||||
def test_dashboard_recent_automation_capped_at_five(ops_client):
|
||||
response = ops_client.get("/api/v1/dashboard")
|
||||
body = response.json()
|
||||
|
||||
@@ -220,6 +220,21 @@ def test_provide_fields_resolves_a_vehicle_missing_field_issue(ops_client):
|
||||
assert vehicle["registration_number"] == "TST-999"
|
||||
|
||||
|
||||
def test_vehicle_entity_snapshot_includes_registration_number(ops_client):
|
||||
"""The snapshot used to omit registration_number entirely, so the 'provide missing
|
||||
fields' form always showed it blank -- even for a vehicle whose plate was actually
|
||||
on file, and even when a *different* field was the genuinely missing one."""
|
||||
issues = ops_client.get(
|
||||
"/api/v1/data-quality/issues",
|
||||
params={"rule_type": "missing_required_field", "status": "open"},
|
||||
).json()
|
||||
target = next(i for i in issues if i["entity_type"] == "vehicle")
|
||||
vehicle = ops_client.get(f"/api/v1/vehicles/{target['entity_ref']}").json()
|
||||
|
||||
detail = ops_client.get(f"/api/v1/data-quality/issues/{target['public_ref']}").json()
|
||||
assert detail["entity_snapshot"]["registration_number"] == vehicle["registration_number"]
|
||||
|
||||
|
||||
def test_resolve_overlap_requires_operations_manager(employee_client):
|
||||
response = employee_client.post(
|
||||
"/api/v1/data-quality/issues/DQ-DEMO-OVERLAP/resolve-overlap",
|
||||
|
||||
@@ -23,12 +23,13 @@ def test_seed_counts_match_deterministic_dataset():
|
||||
assert db.scalar(select(func.count()).select_from(Vehicle)) == 50
|
||||
assert db.scalar(select(func.count()).select_from(Customer)) == 180
|
||||
assert db.scalar(select(func.count()).select_from(Booking)) == 246
|
||||
# 15 from the CSV plus a deterministic set discovered by the post-seed scan. The
|
||||
# shared vehicle-status evaluator (app.services.vehicle_status) now also catches
|
||||
# 21 from the CSV (15 original + 6 giving every unexplained blocked vehicle a
|
||||
# real open issue) plus a deterministic set discovered by the post-seed scan. The
|
||||
# shared vehicle-status evaluator (app.services.vehicle_status) also catches
|
||||
# MO-024: an active/return-pending booking (BK-DEMO-RETURN) on a vehicle that has
|
||||
# already crossed its service-due odometer threshold -- a genuine conflict the
|
||||
# previous hand-rolled scanner never checked for.
|
||||
assert db.scalar(select(func.count()).select_from(DataQualityIssue)) == 27
|
||||
assert db.scalar(select(func.count()).select_from(DataQualityIssue)) == 33
|
||||
assert db.scalar(select(func.count()).select_from(OutboxEvent)) == 20
|
||||
assert db.scalar(select(func.count()).select_from(User)) == 2
|
||||
finally:
|
||||
@@ -200,3 +201,76 @@ def test_seed_today_movements_are_a_credible_mix():
|
||||
assert len(returns) >= 2
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_every_blocked_vehicle_has_a_real_open_issue():
|
||||
"""A live reviewer found blocked vehicles with no explanation anywhere in the UI --
|
||||
5 with zero quality issues at all, one (MO-049) with only a resolved one. Every
|
||||
vehicle seeded as 'blocked' must now have at least one real, currently open
|
||||
DataQualityIssue an operator can click through to."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
blocked = db.scalars(select(Vehicle).where(Vehicle.operational_status == "blocked")).all()
|
||||
assert len(blocked) > 0
|
||||
for vehicle in blocked:
|
||||
open_issue = db.scalar(
|
||||
select(DataQualityIssue).where(
|
||||
DataQualityIssue.entity_type == "vehicle",
|
||||
DataQualityIssue.entity_id == vehicle.id,
|
||||
DataQualityIssue.status == "open",
|
||||
)
|
||||
)
|
||||
assert open_issue is not None, f"{vehicle.public_ref} is blocked with no open issue"
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seed_scenario_mo024_service_conflict_is_flagged():
|
||||
"""Regression lock-in for the live-reported MO-024 defect: 'rented' with a real
|
||||
active booking (BK-DEMO-RETURN) yet already 14,820 km past its service threshold,
|
||||
with nothing surfacing the contradiction. The shared vehicle-status evaluator
|
||||
already catches this (a real conflict, not a fabricated third status) -- this test
|
||||
exists so a future change can't silently regress it back to unexplained."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
vehicle = _by_ref(db, Vehicle, "MO-024")
|
||||
assert vehicle is not None
|
||||
assert vehicle.operational_status == "rented"
|
||||
assert vehicle.odometer_km >= vehicle.next_service_km
|
||||
|
||||
issue = db.scalar(
|
||||
select(DataQualityIssue).where(
|
||||
DataQualityIssue.entity_type == "vehicle",
|
||||
DataQualityIssue.entity_id == vehicle.id,
|
||||
DataQualityIssue.status == "open",
|
||||
DataQualityIssue.rule_type == "vehicle_status_conflict",
|
||||
)
|
||||
)
|
||||
assert issue is not None
|
||||
signals = issue.evidence_json.get("signals", [])
|
||||
assert any(s["code"] == "vehicle.manual_review_required" for s in signals)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seed_evidence_has_no_placeholder_summary():
|
||||
"""Every seed-only data-quality issue used to carry the vacuous evidence
|
||||
'Synthetic deterministic seed issue' with no structured signal at all -- a visitor
|
||||
had no way to understand why it needed attention. Every issue must now carry a real
|
||||
summary and at least one localizable signal (code + params)."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
issues = db.scalars(select(DataQualityIssue)).all()
|
||||
assert len(issues) > 0
|
||||
for issue in issues:
|
||||
summary = issue.evidence_json.get("summary", "")
|
||||
assert summary != "Synthetic deterministic seed issue", (
|
||||
f"{issue.public_ref} still has the meaningless placeholder summary"
|
||||
)
|
||||
signals = issue.evidence_json.get("signals", [])
|
||||
assert len(signals) > 0, f"{issue.public_ref} has no structured evidence signal"
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
Reference in New Issue
Block a user