rmi-backend/app/routers/intelligence_panel.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

276 lines
9 KiB
Python

"""
Intelligence Panel Router
=========================
Endpoints for the intelligence feed panel on rugmunch.io/intelligence
Data sources:
- News service (200+ crypto news sources, 15 tiers)
- Security alerts (rug detection, whale tracking)
- Prediction market signals (Polymarket, Kalshi, Limitless, Manifold)
- Crypto Fear Index (aggregated risk from prediction markets)
- Bulletin posts (community intelligence)
"""
from datetime import UTC, datetime
from fastapi import APIRouter, Query
from pydantic import BaseModel
from app.core.logging import get_logger
logger = get_logger(__name__)
router = APIRouter(prefix="/api/v1/intelligence", tags=["intelligence-panel"])
# Import prediction market intelligence (LAZY - deferred to first request)
pm_intel = None
_pm_intel_loaded = False
def _get_pm_intel():
global pm_intel, _pm_intel_loaded
if _pm_intel_loaded:
return pm_intel
_pm_intel_loaded = True
try:
from app.services.prediction_market_intel import (
get_prediction_market_intel,
)
pm_intel = get_prediction_market_intel()
except ImportError:
pm_intel = None
return pm_intel
class IntelSignalResponse(BaseModel):
signal_type: str
severity: str
headline: str
insight: str
action: str
confidence: float
generated_at: str
class FearIndexResponse(BaseModel):
overall_risk: float
exploit_risk: float
regulatory_risk: float
stablecoin_risk: float
exchange_risk: float
generated_at: str
class IntelligenceItem(BaseModel):
id: str
title: str
content: str | None = None
url: str | None = None
source: str
published_at: str
category: str
kind: str
highlight: bool | None = False
severity: str | None = None
class IntelligenceFeedResponse(BaseModel):
items: list[IntelligenceItem]
count: int
timestamp: str
# ── Prediction Market Intelligence ─────────────────────────
@router.get("/prediction-signals", response_model=list[IntelSignalResponse])
async def get_prediction_signals(limit: int = Query(10, ge=1, le=20)):
"""Get actionable intelligence signals from prediction markets.
Signal types:
- HIGH_CONSENSUS_THREAT: Markets strongly expect an adverse event
- LOW_CONSENSUS_RISK: Markets dismiss a feared risk
- TOKEN_RISK_SIGNAL: Specific token flagged by prediction markets
- ECOSYSTEM_RISK: Broad ecosystem-level threat indicator
Each signal includes: severity, plain-English insight, recommended action.
"""
_get_pm_intel()
if not pm_intel:
return []
signals = await pm_intel.generate_signals(max_signals=limit)
return [
IntelSignalResponse(
signal_type=s.signal_type,
severity=s.severity,
headline=s.headline,
insight=s.insight,
action=s.action,
confidence=round(s.confidence, 2),
generated_at=s.generated_at,
)
for s in signals
]
@router.get("/crypto-fear-index", response_model=FearIndexResponse)
async def get_crypto_fear_index():
"""Get the Crypto Fear Index - risk aggregated from prediction markets.
Components:
- overall_risk: Weighted aggregate of all categories
- exploit_risk: DeFi hack/exploit/vulnerability probability
- regulatory_risk: SEC/CFTC/enforcement action probability
- stablecoin_risk: Depeg/collapse probability
- exchange_risk: Insolvency/breach probability
Derived from live prediction market data across Polymarket, Kalshi, Limitless, Manifold.
Higher = more risk priced in by traders with real money at stake.
"""
_get_pm_intel()
if not pm_intel:
return FearIndexResponse(
overall_risk=0,
exploit_risk=0,
regulatory_risk=0,
stablecoin_risk=0,
exchange_risk=0,
generated_at=datetime.now(UTC).isoformat(),
)
fear = await pm_intel.generate_crypto_fear_index()
return FearIndexResponse(
overall_risk=fear.overall_risk,
exploit_risk=fear.exploit_risk,
regulatory_risk=fear.regulatory_risk,
stablecoin_risk=fear.stablecoin_risk,
exchange_risk=fear.exchange_risk,
generated_at=fear.generated_at,
)
# ── Aggregated Feed ────────────────────────────────────────
@router.get("/feed", response_model=IntelligenceFeedResponse)
async def get_intelligence_feed(limit: int = Query(20, ge=1, le=50)):
"""Get aggregated intelligence feed combining all sources."""
_get_pm_intel()
items = []
now_ts = datetime.now(UTC).isoformat()
# 1. Prediction market signals (highest priority - forward-looking)
if pm_intel:
try:
signals = await pm_intel.generate_signals(max_signals=5)
for s in signals:
severity = s.severity
items.append(
IntelligenceItem(
id=f"pm-{s.signal_type}-{now_ts}",
title=s.headline,
content=s.insight,
url="/intelligence",
source=f"Prediction Markets ({s.signal_type.replace('_', ' ').title()})",
published_at=now_ts,
category="prediction-market",
kind="signal",
highlight=(severity in ("critical", "high")),
severity=severity,
)
)
except Exception as e:
logger.info(f"Prediction market signals unavailable: {e}")
# 2. Crypto Fear Index summary
if pm_intel:
try:
fear = await pm_intel.generate_crypto_fear_index()
risk_label = (
"EXTREME FEAR"
if fear.overall_risk >= 75
else "FEAR"
if fear.overall_risk >= 50
else "NEUTRAL"
if fear.overall_risk >= 25
else "GREED"
if fear.overall_risk >= 10
else "EXTREME GREED"
)
items.append(
IntelligenceItem(
id=f"fear-index-{now_ts}",
title=f"Crypto Fear Index: {fear.overall_risk:.0f}/100 - {risk_label}",
content=(
f"Exploit risk: {fear.exploit_risk:.0f}/100 | "
f"Regulatory: {fear.regulatory_risk:.0f}/100 | "
f"Stablecoin: {fear.stablecoin_risk:.0f}/100 | "
f"Exchange: {fear.exchange_risk:.0f}/100. "
f"Derived from live Polymarket + Kalshi + Manifold prediction markets."
),
url="/intelligence",
source="Crypto Fear Index",
published_at=now_ts,
category="risk-index",
kind="index",
highlight=(fear.overall_risk >= 50),
severity="high" if fear.overall_risk >= 50 else "medium",
)
)
except Exception as e:
logger.info(f"Crypto Fear Index unavailable: {e}")
# 3. Prediction market data sources status
items.append(
IntelligenceItem(
id=f"sources-{now_ts}",
title="Prediction Market Intelligence Active - 4 Sources Live",
content="RMI now monitors Polymarket, Kalshi, Limitless, and Manifold for crypto security signals. Live risk indicators, token threat detection, and ecosystem monitoring are online.",
url="/intelligence",
source="RMI System",
published_at=now_ts,
category="system",
kind="status",
highlight=False,
)
)
return IntelligenceFeedResponse(
items=items[:limit],
count=min(len(items), limit),
timestamp=now_ts,
)
@router.get("/latest")
async def get_latest_intelligence():
"""Get latest intelligence snapshot for quick updates."""
_get_pm_intel()
latest = {
"timestamp": datetime.now(UTC).isoformat(),
}
if pm_intel:
try:
signals = await pm_intel.generate_signals(max_signals=3)
if signals:
top = signals[0]
latest["top_signal"] = {
"headline": top.headline,
"severity": top.severity,
"insight": top.insight,
"action": top.action,
}
fear = await pm_intel.generate_crypto_fear_index()
latest["fear_index"] = {
"overall": fear.overall_risk,
"exploit": fear.exploit_risk,
"regulatory": fear.regulatory_risk,
"stablecoin": fear.stablecoin_risk,
"exchange": fear.exchange_risk,
}
except Exception as e:
latest["error"] = str(e)
return latest