diff --git a/frontend/src/pages/SqlExplorer.tsx b/frontend/src/pages/SqlExplorer.tsx
index e2861f1..162cceb 100644
--- a/frontend/src/pages/SqlExplorer.tsx
+++ b/frontend/src/pages/SqlExplorer.tsx
@@ -282,8 +282,8 @@ export function SqlExplorerPage() {
{t.columns.map((c) => (
- {c.primary_key && }
- {c.references && }
+ {c.primary_key && }
+ {c.references && }
{c.name}
{c.type}
diff --git a/services/recommendation/worker.py b/services/recommendation/worker.py
index b514f2b..070707a 100644
--- a/services/recommendation/worker.py
+++ b/services/recommendation/worker.py
@@ -10,6 +10,7 @@ from __future__ import annotations
import json
import logging
+import uuid as _uuid
from datetime import datetime, timezone
import asyncpg
@@ -560,17 +561,36 @@ async def persist_recommendation(
)
rec_id = str(row["id"])
- # Insert evidence citations with position-based weighting
+ # Insert evidence citations with position-based weighting.
+ # Filter out non-UUID document IDs (e.g. synthetic "pattern:..." IDs from
+ # competitive signal propagation) — the recommendation_evidence table
+ # requires a valid UUID foreign key to the documents table.
evidence_rows: list[tuple[str, str, str, float]] = []
for idx, doc_id in enumerate(supporting_ids):
+ try:
+ _uuid.UUID(doc_id)
+ except (ValueError, AttributeError):
+ continue
weight = round(1.0 / (1.0 + idx * 0.1), 4) # rank decay
evidence_rows.append((rec_id, doc_id, "supporting", weight))
for idx, doc_id in enumerate(opposing_ids):
+ try:
+ _uuid.UUID(doc_id)
+ except (ValueError, AttributeError):
+ continue
weight = round(1.0 / (1.0 + idx * 0.1), 4)
evidence_rows.append((rec_id, doc_id, "opposing", weight))
if evidence_rows:
- await pool.executemany(_INSERT_REC_EVIDENCE, evidence_rows)
+ try:
+ await pool.executemany(_INSERT_REC_EVIDENCE, evidence_rows)
+ except Exception:
+ logger.warning(
+ "Failed to insert %d evidence rows for recommendation %s",
+ len(evidence_rows),
+ rec_id,
+ exc_info=True,
+ )
# Persist the eligibility/risk evaluation for audit trail
if eligibility_result is not None: