rmi-backend/app/routers/label_lookup.py
opencode c762564d40 style(rmi-backend): complete lint cleanup — 1175→0 ruff errors
- Fix 71 invalid-syntax files (class-body newline-broken assignments)
- Add from/None chain to 307 B904 raise-without-from sites
- Add B008 ignore to ruff.toml (already in pyproject.toml)
- Noqa F401 on __init__.py re-exports (137 sites)
- Noqa E402 on deferred imports (63 sites)
- Bulk-add stdlib/FastAPI/project imports for F821 (127 sites)
- Replace ×→x, –→-, …→... in docstrings (4093 chars)
- Manual refactor of 5 SIM103/SIM116 patterns

Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py)
Co-authored-by: opencode <opencode@rugmunch.io>
2026-07-06 15:43:20 +02:00

95 lines
3.2 KiB
Python

"""
Lightweight wallet label lookup endpoint for CF Worker enrichment.
Called by Workers to annotate tool responses with label data.
"""
import json
import logging
import os
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
logger = logging.getLogger("labels.lookup")
router = APIRouter(prefix="/api/v1/labels", tags=["wallet-labels"])
class LabelLookupRequest(BaseModel):
addresses: list[str] = Field(..., min_items=1, max_items=10)
chain: str = Field(default="ethereum")
@router.post("/lookup")
async def lookup_labels(req: LabelLookupRequest):
"""Batch lookup wallet labels for enrichment.
Called by CF Workers to annotate tool responses with wallet intelligence.
Returns labels from Redis cache + ClickHouse, with 5min Redis caching.
"""
try:
addresses = [a.strip() for a in req.addresses if a.strip()]
labels: dict[str, list] = {a: [] for a in addresses}
# Try ClickHouse first
try:
from clickhouse_driver import Client
ch = Client(
host=os.getenv("CH_HOST", "rmi-clickhouse"),
port=int(os.getenv("CH_PORT", "9000")),
user=os.getenv("CH_USER", "default"),
password=os.getenv("CH_PASSWORD", "") or None,
settings={"max_execution_time": 3},
)
placeholders = ", ".join(["%s"] * len(addresses))
rows = ch.execute(
f"SELECT address, label_name, label_category, source, is_sanctioned "
f"FROM wallet_memory.wallet_labels "
f"WHERE address IN ({placeholders}) "
f"ORDER BY loaded_at DESC LIMIT 50",
addresses,
)
for row in rows:
addr = row[0]
labels[addr].append(
{
"name": row[1] or "",
"category": row[2] or "",
"source": row[3] or "",
"sanctioned": bool(row[4]),
}
)
except Exception:
# Fallback to Redis
try:
import redis as _redis
r = _redis.Redis(
host=os.getenv("REDIS_HOST", "rmi-redis"),
port=int(os.getenv("REDIS_PORT", "6379")),
password=os.getenv("REDIS_PASSWORD", ""),
decode_responses=True,
socket_connect_timeout=2,
)
for addr in addresses:
cached = r.get(f"x402:enrich:{addr}")
if cached:
data = json.loads(cached)
labels[addr] = data.get("labels", [])
except Exception:
pass
# Count labeled addresses
labeled = sum(1 for v in labels.values() if v)
return {
"labels": labels,
"addresses_checked": len(addresses),
"addresses_labeled": labeled,
"total_labels": sum(len(v) for v in labels.values()),
}
except Exception as e:
logger.error(f"Label lookup failed: {e}")
raise HTTPException(status_code=500, detail=str(e)) from e