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

286 lines
9.1 KiB
Python

"""
RMI Tool Changelog & Version System
=====================================
Tracks tool versions, changes, and deprecations.
Provides transparency for tool quality and evolution.
Endpoints:
GET /api/v1/tools/changelog - Full changelog
GET /api/v1/tools/changelog/{tool} - Changelog for specific tool
GET /api/v1/tools/version/{tool} - Current version for tool
GET /api/v1/tools/deprecated - List of deprecated tools
Author: RMI Development
Date: 2026-06-05
"""
import json
import logging
from datetime import UTC, datetime
from typing import Any
from fastapi import APIRouter
from fastapi.responses import JSONResponse
from app.core.redis import get_redis
logger = logging.getLogger("tool_changelog")
router = APIRouter(prefix="/api/v1/tools", tags=["tool-changelog"])
# ── Redis Helper ─────────────────────────────────────────────────
INITIAL_CHANGELOG = [
{
"date": "2026-06-05",
"version": "3.3.0",
"type": "feature",
"tool": "whale_copy_trade",
"title": "New Tool: Whale Copy Trade Engine",
"description": "Real-time copy trade engine - input a smart money wallet, get their exact last 24h trades with entry/exit prices, PnL, and suggested follow trades.",
"breaking": False,
},
{
"date": "2026-06-05",
"version": "3.3.0",
"type": "feature",
"tool": "rug_predictor_live",
"title": "New Tool: Live Rug Predictor",
"description": "Analyzes tokens in their first 5-30 minutes with 6-signal rug probability scoring. Detects low liquidity, unlocked LP, price crashes, mass selling, and more.",
"breaking": False,
},
{
"date": "2026-06-05",
"version": "3.3.0",
"type": "feature",
"tool": "whale_cluster",
"title": "New Tool: Whale Cluster Detection",
"description": "Identify coordinated whale clusters - wallets that move together via same funding source, timing patterns, and token sets. Detects wash trading and insider networks.",
"breaking": False,
},
{
"date": "2026-06-05",
"version": "3.3.0",
"type": "infrastructure",
"tool": "*",
"title": "Developer Tier System",
"description": "Free developer API keys with 100 calls/day, no payment required. Tier system: FREE (100/day), TRIAL (3-5/tool), PAID (pay-per-use), PRO ($29/mo, 5000/day).",
"breaking": False,
},
{
"date": "2026-06-05",
"version": "3.3.0",
"type": "infrastructure",
"tool": "*",
"title": "Facilitator Health Monitoring",
"description": "60s health check loop for all payment facilitators with latency tracking, success rate monitoring, and auto-failover readiness.",
"breaking": False,
},
{
"date": "2026-06-05",
"version": "3.3.0",
"type": "infrastructure",
"tool": "*",
"title": "Public Status Page",
"description": "Public-facing status page at /api/v1/status showing health of all RMI services - backend, Redis, ClickHouse, MCP, facilitators. 30s monitoring loop.",
"breaking": False,
},
{
"date": "2026-06-05",
"version": "3.3.0",
"type": "infrastructure",
"tool": "*",
"title": "Webhook Notification Pipeline",
"description": "Real-time webhook delivery system with retry, signing, rate limiting, and dead letter queue. Polls every 5 seconds for new alerts.",
"breaking": False,
},
{
"date": "2026-06-05",
"version": "3.3.0",
"type": "infrastructure",
"tool": "*",
"title": "Persistent State Layer",
"description": "User watchlists, portfolios, saved scans, and alert history. Redis-backed with JSON serialization.",
"breaking": False,
},
{
"date": "2026-06-05",
"version": "3.3.0",
"type": "improvement",
"tool": "*",
"title": "SDK v2.0 Released",
"description": "Complete rewrite with async/sync dual API, retry with exponential backoff, batch tool calls, webhook management, proper error classes, and type hints.",
"breaking": False,
},
{
"date": "2026-06-01",
"version": "3.2.0",
"type": "improvement",
"tool": "*",
"title": "Response Enrichment Pipeline",
"description": "Every tool response now enriched with wallet labels, RAG similarity search, scam pattern detection, risk summary, intel briefing, and confidence scores.",
"breaking": False,
},
{
"date": "2026-06-01",
"version": "3.2.0",
"type": "feature",
"tool": "composite_score",
"title": "New Tool: Composite Score",
"description": "One-number buy/sell/avoid score combining reputation, rug probability, market health, narrative sentiment, MEV exposure, and DeFi position.",
"breaking": False,
},
{
"date": "2026-06-01",
"version": "3.2.0",
"type": "feature",
"tool": "smart_money",
"title": "New Tool: Smart Money Finder",
"description": "Find profitable traders via win rate estimation, token count, balance analysis, and wallet label cross-reference.",
"breaking": False,
},
{
"date": "2026-05-28",
"version": "3.1.0",
"type": "infrastructure",
"tool": "*",
"title": "Multi-Facilitator Payment System",
"description": "Smart router auto-picks best facilitator per chain/token. 7 active facilitators across 11 payment chains.",
"breaking": False,
},
]
# ── Changelog Management ─────────────────────────────────────────
def get_changelog(tool: str | None = None, limit: int = 50) -> list[dict[str, Any]]:
"""Get changelog entries."""
r = get_redis()
# Load from Redis if available
entries = []
if r:
stored = r.lrange("rmi:changelog", 0, -1)
entries = [json.loads(e) for e in stored]
# If no stored entries, use initial data
if not entries:
entries = INITIAL_CHANGELOG.copy()
if r:
# Store initial entries
for entry in reversed(entries):
r.lpush("rmi:changelog", json.dumps(entry))
# Filter by tool
if tool:
entries = [e for e in entries if e.get("tool") == tool or e.get("tool") == "*"]
# Limit
return entries[:limit]
def add_changelog_entry(
tool: str,
title: str,
description: str,
version: str = "",
change_type: str = "improvement",
breaking: bool = False,
):
"""Add a changelog entry."""
r = get_redis()
if not r:
return
entry = {
"date": datetime.now(UTC).strftime("%Y-%m-%d"),
"version": version or "current",
"type": change_type,
"tool": tool,
"title": title,
"description": description,
"breaking": breaking,
}
r.lpush("rmi:changelog", json.dumps(entry))
r.ltrim("rmi:changelog", 0, 499) # Keep last 500
def get_tool_version(tool: str) -> dict[str, Any]:
"""Get current version info for a tool."""
changelog = get_changelog(tool, limit=1)
if changelog:
entry = changelog[0]
return {
"tool": tool,
"version": entry.get("version", "unknown"),
"last_updated": entry.get("date", ""),
"last_change": entry.get("title", ""),
}
# Default version
return {
"tool": tool,
"version": "1.0.0",
"last_updated": "unknown",
"last_change": "No changelog entries",
}
def get_deprecated_tools() -> list[dict[str, Any]]:
"""Get list of deprecated tools."""
r = get_redis()
if not r:
return []
deprecated = r.smembers("rmi:tools:deprecated")
return [json.loads(d) for d in deprecated]
# ── Endpoints ────────────────────────────────────────────────────
@router.get("/changelog")
async def tool_changelog(tool: str | None = None, limit: int = 50):
"""Get changelog entries."""
entries = get_changelog(tool, limit)
return JSONResponse(
content={
"changelog": entries,
"total": len(entries),
"filter": tool,
}
)
@router.get("/changelog/{tool}")
async def tool_specific_changelog(tool: str, limit: int = 20):
"""Get changelog for a specific tool."""
entries = get_changelog(tool, limit)
return JSONResponse(
content={
"tool": tool,
"changelog": entries,
"total": len(entries),
}
)
@router.get("/version/{tool}")
async def tool_version(tool: str):
"""Get current version for a tool."""
return JSONResponse(content=get_tool_version(tool))
@router.get("/deprecated")
async def deprecated_tools():
"""Get list of deprecated tools."""
return JSONResponse(
content={
"deprecated": get_deprecated_tools(),
"count": len(get_deprecated_tools()),
}
)