671 lines
24 KiB
Python
671 lines
24 KiB
Python
"""
|
|
RugCharts Data Quality Engine
|
|
==============================
|
|
Fixes false positives, enriches all responses, populates empty providers.
|
|
|
|
1. Known Entity Registry - trusted addresses, exchanges, protocols
|
|
2. Token vs Wallet Detection - don't scan wallets as tokens
|
|
3. Data Enrichment - inject wallet labels, Arkham entities, RAG into every response
|
|
4. Smart Tiering - clear free/premium/admin boundaries
|
|
5. Provider Fallback Enhancement - when one returns empty, try harder
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
from datetime import UTC, datetime
|
|
|
|
import redis
|
|
|
|
logger = logging.getLogger("data_quality")
|
|
|
|
REDIS_HOST = os.getenv("REDIS_HOST", "rmi-redis")
|
|
REDIS_PORT = int(os.getenv("REDIS_PORT", "6379"))
|
|
REDIS_PASSWORD = os.getenv("REDIS_PASSWORD", "")
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# 1. KNOWN ENTITY REGISTRY
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
|
|
KNOWN_ENTITIES = {
|
|
# ── Trusted Protocols & Infrastructure ──
|
|
"So11111111111111111111111111111111111111112": {
|
|
"name": "Wrapped SOL",
|
|
"type": "protocol_token",
|
|
"trust": "SAFE",
|
|
"chains": ["solana"],
|
|
"note": "Native SOL wrapper, core Solana infrastructure",
|
|
},
|
|
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": {
|
|
"name": "USDC (Solana)",
|
|
"type": "stablecoin",
|
|
"trust": "SAFE",
|
|
"chains": ["solana"],
|
|
"note": "Circle-issued USDC on Solana",
|
|
},
|
|
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB": {
|
|
"name": "USDT (Solana)",
|
|
"type": "stablecoin",
|
|
"trust": "SAFE",
|
|
"chains": ["solana"],
|
|
"note": "Tether USDT on Solana",
|
|
},
|
|
"DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263": {
|
|
"name": "Bonk",
|
|
"type": "memecoin",
|
|
"trust": "SAFE",
|
|
"chains": ["solana"],
|
|
"note": "Major Solana memecoin, high liquidity",
|
|
},
|
|
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": {
|
|
"name": "WETH",
|
|
"type": "protocol_token",
|
|
"trust": "SAFE",
|
|
"chains": ["ethereum"],
|
|
"note": "Wrapped Ether, core Ethereum infrastructure",
|
|
},
|
|
"0xdAC17F958D2ee523a2206206994597C13D831ec7": {
|
|
"name": "USDT (Ethereum)",
|
|
"type": "stablecoin",
|
|
"trust": "SAFE",
|
|
"chains": ["ethereum"],
|
|
"note": "Tether USDT on Ethereum",
|
|
},
|
|
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": {
|
|
"name": "USDC (Ethereum)",
|
|
"type": "stablecoin",
|
|
"trust": "SAFE",
|
|
"chains": ["ethereum"],
|
|
"note": "Circle USDC on Ethereum",
|
|
},
|
|
# ── Known Individuals (Arkham-resolved) ──
|
|
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045": {
|
|
"name": "Vitalik Buterin",
|
|
"type": "individual",
|
|
"trust": "SAFE",
|
|
"chains": ["ethereum"],
|
|
"note": "Ethereum co-founder. This is a WALLET, not a token.",
|
|
},
|
|
# ── Major Exchanges ──
|
|
"0x28C6c06298d514Db089934071355E5743bf21d60": {
|
|
"name": "Binance 14",
|
|
"type": "exchange",
|
|
"trust": "SAFE",
|
|
"chains": ["ethereum"],
|
|
"note": "Binance hot wallet",
|
|
},
|
|
"0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8": {
|
|
"name": "Binance 7",
|
|
"type": "exchange",
|
|
"trust": "SAFE",
|
|
"chains": ["ethereum"],
|
|
"note": "Binance hot wallet",
|
|
},
|
|
# ── Known Scam Addresses ──
|
|
"0x000000000000000000000000000000000000dEaD": {
|
|
"name": "Burn Address",
|
|
"type": "burn",
|
|
"trust": "NEUTRAL",
|
|
"chains": ["ethereum", "bsc", "base", "arbitrum"],
|
|
"note": "Standard burn address - tokens sent here are destroyed",
|
|
},
|
|
}
|
|
|
|
|
|
def lookup_entity(address: str, chain: str = "") -> dict | None:
|
|
"""Check if an address is a known entity."""
|
|
# Exact match
|
|
if address in KNOWN_ENTITIES:
|
|
entity = KNOWN_ENTITIES[address]
|
|
if not chain or chain in entity.get("chains", []):
|
|
return entity
|
|
|
|
# Case-insensitive match (EVM addresses)
|
|
addr_lower = address.lower()
|
|
for known_addr, entity in KNOWN_ENTITIES.items():
|
|
if known_addr.lower() == addr_lower:
|
|
if not chain or chain in entity.get("chains", []):
|
|
return entity
|
|
|
|
return None
|
|
|
|
|
|
def is_wallet_not_token(address: str, chain: str = "") -> bool:
|
|
"""Detect if an address is likely a wallet, not a token contract."""
|
|
entity = lookup_entity(address, chain)
|
|
return bool(entity and entity.get("type") in ("individual", "exchange", "burn"))
|
|
|
|
|
|
def get_trust_bonus(address: str, chain: str = "") -> tuple[int, str]:
|
|
"""Get trust bonus (score reduction) for known entities.
|
|
|
|
Returns (bonus_points, reason)
|
|
SAFE entities get 40-60 point reduction (lower risk score = better)
|
|
"""
|
|
entity = lookup_entity(address, chain)
|
|
if not entity:
|
|
return 0, ""
|
|
|
|
trust = entity.get("trust", "")
|
|
if trust == "SAFE":
|
|
return 60, f"Known safe entity: {entity['name']} ({entity['type']})"
|
|
elif trust == "NEUTRAL":
|
|
return 20, f"Known entity: {entity['name']} ({entity['type']})"
|
|
elif trust == "WARNING":
|
|
return -20, f"Warning entity: {entity['name']}"
|
|
elif trust == "DANGER":
|
|
return -60, f"Known dangerous entity: {entity['name']}"
|
|
|
|
return 0, ""
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# 2. DATA ENRICHMENT - Inject wallet labels, Arkham, RAG everywhere
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
def _r():
|
|
return redis.Redis(
|
|
host=REDIS_HOST,
|
|
port=REDIS_PORT,
|
|
password=REDIS_PASSWORD,
|
|
decode_responses=True,
|
|
socket_connect_timeout=2,
|
|
)
|
|
|
|
|
|
async def enrich_with_wallet_labels(addresses: list[str]) -> dict[str, str]:
|
|
"""Look up wallet labels from our 190K-label Redis store."""
|
|
labels = {}
|
|
try:
|
|
r = _r()
|
|
for addr in addresses[:50]:
|
|
# Check multiple label formats
|
|
for prefix in ["label:", "wallet_label:", "entity:"]:
|
|
label = r.get(f"{prefix}{addr}")
|
|
if label:
|
|
labels[addr] = label
|
|
break
|
|
r.close()
|
|
except Exception as e:
|
|
logger.debug(f"Label enrichment failed: {e}")
|
|
return labels
|
|
|
|
|
|
async def enrich_with_arkham(address: str) -> dict | None:
|
|
"""Enrich an address with Arkham entity data."""
|
|
arkham_key = os.getenv("ARKHAM_API_KEY", "")
|
|
if not arkham_key:
|
|
return None
|
|
|
|
try:
|
|
import httpx
|
|
|
|
async with httpx.AsyncClient(timeout=8) as c:
|
|
r = await c.get(
|
|
f"https://api.arkhamintelligence.com/intelligence/address/{address}",
|
|
headers={"API-Key": arkham_key},
|
|
)
|
|
if r.status_code == 200:
|
|
data = r.json()
|
|
return {
|
|
"entity_name": data.get("arkhamEntity", {}).get("name", ""),
|
|
"entity_type": data.get("arkhamEntity", {}).get("type", ""),
|
|
"label": data.get("arkhamLabel", {}).get("name", ""),
|
|
"chain": data.get("chain", ""),
|
|
"is_contract": data.get("contract", False),
|
|
}
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
|
|
async def enrich_response(result: dict, address: str, chain: str) -> dict:
|
|
"""Universal response enrichment - injects labels, entities, trust into any result."""
|
|
if not result or not isinstance(result, dict):
|
|
return result
|
|
|
|
enriched = dict(result)
|
|
|
|
# 1. Known entity lookup
|
|
entity = lookup_entity(address, chain)
|
|
if entity:
|
|
enriched["known_entity"] = {
|
|
"name": entity["name"],
|
|
"type": entity["type"],
|
|
"trust": entity["trust"],
|
|
"note": entity.get("note", ""),
|
|
}
|
|
|
|
# Adjust risk scores for known entities
|
|
trust_bonus, reason = get_trust_bonus(address, chain)
|
|
if trust_bonus and "security_score" in enriched:
|
|
old_score = enriched.get("security_score", 50)
|
|
enriched["security_score"] = max(0, min(100, old_score - trust_bonus))
|
|
enriched["trust_adjustment"] = {
|
|
"original_score": old_score,
|
|
"adjusted_score": enriched["security_score"],
|
|
"reason": reason,
|
|
}
|
|
|
|
# Override risk band for SAFE entities
|
|
if entity["trust"] == "SAFE" and enriched.get("risk_band") in (
|
|
"DANGER",
|
|
"CRITICAL",
|
|
"HIGH",
|
|
):
|
|
enriched["risk_band"] = "SAFE"
|
|
enriched["risk_level"] = "LOW"
|
|
enriched["risk_color"] = "#00FF88"
|
|
|
|
# 2. Wallet vs token detection
|
|
if is_wallet_not_token(address, chain):
|
|
enriched["address_type"] = "wallet"
|
|
enriched["wallet_note"] = "This is a wallet address, not a token contract. Token-specific checks may not apply."
|
|
else:
|
|
enriched["address_type"] = "token"
|
|
|
|
# 3. Add enrichment timestamp
|
|
enriched["enriched_at"] = datetime.now(UTC).isoformat()
|
|
|
|
return enriched
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# 3. SMART TIERING - Clear free/premium/admin boundaries
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
|
|
TIER_DEFINITIONS = {
|
|
"free": {
|
|
"name": "Free",
|
|
"rate_limit_rpm": 30,
|
|
"allowed_data_types": [
|
|
"token_price",
|
|
"market_overview",
|
|
"trending",
|
|
"news",
|
|
"alerts",
|
|
"token_metadata",
|
|
"ohlcv",
|
|
"token_launches",
|
|
],
|
|
"description": "Basic charting, prices, trending - better than DexScreener free",
|
|
"competitor_equivalent": "DexScreener free ($0) + Birdeye free ($0)",
|
|
},
|
|
"authenticated": {
|
|
"name": "Authenticated",
|
|
"rate_limit_rpm": 100,
|
|
"allowed_data_types": [
|
|
"token_price",
|
|
"market_overview",
|
|
"trending",
|
|
"news",
|
|
"alerts",
|
|
"token_metadata",
|
|
"ohlcv",
|
|
"token_launches",
|
|
"wallet_labels",
|
|
"wallet_tokens",
|
|
"holder_health",
|
|
"scanner",
|
|
"rag_search",
|
|
"smart_money",
|
|
],
|
|
"description": "Wallet tracking, holder analysis, smart money - Nansen-level at $0",
|
|
"competitor_equivalent": "Nansen Lite ($100/mo) + DexScreener",
|
|
},
|
|
"premium": {
|
|
"name": "Premium",
|
|
"rate_limit_rpm": 300,
|
|
"price_monthly": 29,
|
|
"allowed_data_types": [
|
|
"token_price",
|
|
"market_overview",
|
|
"trending",
|
|
"news",
|
|
"alerts",
|
|
"token_metadata",
|
|
"ohlcv",
|
|
"token_launches",
|
|
"wallet_labels",
|
|
"wallet_tokens",
|
|
"holder_health",
|
|
"scanner",
|
|
"rag_search",
|
|
"smart_money",
|
|
"volume_authenticity",
|
|
"token_security",
|
|
"liquidity_risk",
|
|
"rug_patterns",
|
|
"dev_reputation",
|
|
"insider_detection",
|
|
"whale_alerts",
|
|
"cross_chain_entity",
|
|
"token_report",
|
|
"entity_intel",
|
|
"arkham_labels",
|
|
],
|
|
"description": "Full RugCharts: fake volume %, security scans, entity intel, rug patterns",
|
|
"competitor_equivalent": "Nansen Pro ($2,500/mo) + BubbleMaps + GoPlus + Arkham",
|
|
},
|
|
"admin": {
|
|
"name": "Admin",
|
|
"rate_limit_rpm": 1000,
|
|
"allowed_data_types": ["*"],
|
|
"description": "Full raw data access - Arkham, Moralis, all providers, no packaging",
|
|
},
|
|
}
|
|
|
|
|
|
def get_tier_info(tier: str) -> dict:
|
|
"""Get tier definition."""
|
|
return TIER_DEFINITIONS.get(tier, TIER_DEFINITIONS["free"])
|
|
|
|
|
|
def tier_allows(tier: str, data_type: str) -> bool:
|
|
"""Check if a tier can access a data type."""
|
|
tier_def = TIER_DEFINITIONS.get(tier, TIER_DEFINITIONS["free"])
|
|
allowed = tier_def["allowed_data_types"]
|
|
return "*" in allowed or data_type in allowed
|
|
|
|
|
|
def tier_comparison_table() -> list[dict]:
|
|
"""Generate competitive comparison table."""
|
|
return [
|
|
{
|
|
"feature": "Real-time charting",
|
|
"rugcharts_free": "✅",
|
|
"rugcharts_premium": "✅",
|
|
"dexscreener": "✅",
|
|
"nansen": "❌",
|
|
"gmgni": "✅",
|
|
},
|
|
{
|
|
"feature": "Multi-chain support",
|
|
"rugcharts_free": "✅ Solana+EVM",
|
|
"rugcharts_premium": "✅ All chains",
|
|
"dexscreener": "✅",
|
|
"nansen": "✅ EVM only",
|
|
"gmgni": "⚠️ Solana only",
|
|
},
|
|
{
|
|
"feature": "Token security (37+ checks)",
|
|
"rugcharts_free": "❌",
|
|
"rugcharts_premium": "✅",
|
|
"dexscreener": "❌",
|
|
"nansen": "❌",
|
|
"gmgni": "⚠️ Basic",
|
|
},
|
|
{
|
|
"feature": "Fake volume detection",
|
|
"rugcharts_free": "❌",
|
|
"rugcharts_premium": "✅",
|
|
"dexscreener": "❌",
|
|
"nansen": "❌",
|
|
"gmgni": "❌",
|
|
},
|
|
{
|
|
"feature": "Entity resolution (Arkham)",
|
|
"rugcharts_free": "❌",
|
|
"rugcharts_premium": "✅",
|
|
"dexscreener": "❌",
|
|
"nansen": "⚠️ Labels",
|
|
"gmgni": "❌",
|
|
},
|
|
{
|
|
"feature": "Cross-chain entity tracing",
|
|
"rugcharts_free": "❌",
|
|
"rugcharts_premium": "✅",
|
|
"dexscreener": "❌",
|
|
"nansen": "❌",
|
|
"gmgni": "❌",
|
|
},
|
|
{
|
|
"feature": "Holder health (Gini, concentration)",
|
|
"rugcharts_free": "✅",
|
|
"rugcharts_premium": "✅",
|
|
"dexscreener": "❌",
|
|
"nansen": "✅",
|
|
"gmgni": "⚠️",
|
|
},
|
|
{
|
|
"feature": "Smart money tracking",
|
|
"rugcharts_free": "❌",
|
|
"rugcharts_premium": "✅",
|
|
"dexscreener": "❌",
|
|
"nansen": "✅ $100+/mo",
|
|
"gmgni": "✅ 1%/trade",
|
|
},
|
|
{
|
|
"feature": "Whale alerts",
|
|
"rugcharts_free": "❌",
|
|
"rugcharts_premium": "✅",
|
|
"dexscreener": "❌",
|
|
"nansen": "✅",
|
|
"gmgni": "⚠️",
|
|
},
|
|
{
|
|
"feature": "Rug pattern matching",
|
|
"rugcharts_free": "❌",
|
|
"rugcharts_premium": "✅",
|
|
"dexscreener": "❌",
|
|
"nansen": "❌",
|
|
"gmgni": "❌",
|
|
},
|
|
{
|
|
"feature": "Developer reputation",
|
|
"rugcharts_free": "❌",
|
|
"rugcharts_premium": "✅",
|
|
"dexscreener": "❌",
|
|
"nansen": "❌",
|
|
"gmgni": "⚠️",
|
|
},
|
|
{
|
|
"feature": "Price",
|
|
"rugcharts_free": "$0/mo",
|
|
"rugcharts_premium": "$29/mo",
|
|
"dexscreener": "$0",
|
|
"nansen": "$100-2,500/mo",
|
|
"gmgni": "1% per trade",
|
|
},
|
|
]
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# 4. ENHANCED TOKEN REPORT - Smart verdicts, narratives, comparables
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
def smart_verdict(report: dict) -> str:
|
|
"""Generate an intelligent, nuanced verdict instead of just 'EXTREME RISK'."""
|
|
|
|
report.get("sections", {}).get("entity", {})
|
|
known = report.get("known_entity", {})
|
|
security = report.get("sections", {}).get("security", {})
|
|
rug = report.get("sections", {}).get("rug_patterns", {})
|
|
dev = report.get("sections", {}).get("developer", {})
|
|
holders = report.get("sections", {}).get("holders", {})
|
|
|
|
# ── Known entities get priority ──
|
|
if known.get("trust") == "SAFE":
|
|
etype = known.get("type", "entity")
|
|
if etype == "individual":
|
|
return f"KNOWN WALLET - {known['name']}. This is a personal wallet, not a token. Token security checks do not apply to wallet addresses. The entity is verified by Arkham Intelligence."
|
|
elif etype == "stablecoin":
|
|
return f"KNOWN STABLECOIN - {known['name']}. Established, high-liquidity asset. Standard risk profile for stablecoins."
|
|
elif etype == "protocol_token":
|
|
return (
|
|
f"CORE PROTOCOL - {known['name']}. Fundamental blockchain infrastructure token. Extremely low rug risk."
|
|
)
|
|
elif etype == "exchange":
|
|
return f"EXCHANGE WALLET - {known['name']}. This is an exchange hot wallet, not a token address."
|
|
return f"KNOWN SAFE ENTITY - {known['name']}. Verified by RugCharts entity registry."
|
|
|
|
# ── Wallet addresses ──
|
|
if report.get("address_type") == "wallet":
|
|
return "WALLET ADDRESS - This is a wallet, not a token contract. Token-specific security checks (honeypot, mint, taxes) are not applicable. Entity information and transaction history are shown below."
|
|
|
|
# ── Real tokens: assess actual risk ──
|
|
risks = []
|
|
risk_level = 0
|
|
|
|
if security.get("score", 0) >= 80:
|
|
risks.append("CRITICAL security failures detected")
|
|
risk_level += 3
|
|
elif security.get("score", 0) >= 60:
|
|
risks.append("HIGH security risk - multiple concerns found")
|
|
risk_level += 2
|
|
elif security.get("score", 0) >= 40:
|
|
risks.append("MODERATE security concerns - review checks")
|
|
risk_level += 1
|
|
|
|
if rug.get("overall_risk") in ("CRITICAL", "HIGH"):
|
|
risks.append(f"Rug pattern match: {rug.get('top_match', 'unknown pattern')}")
|
|
risk_level += 2
|
|
elif rug.get("total_matches", 0) > 0:
|
|
risks.append(f"{rug['total_matches']} rug patterns partially matched")
|
|
risk_level += 1
|
|
|
|
if holders.get("gini", 0) > 0.8:
|
|
risks.append(f"Extreme holder concentration (Gini: {holders['gini']})")
|
|
risk_level += 2
|
|
elif holders.get("gini", 0) > 0.6:
|
|
risks.append(f"High holder concentration (Gini: {holders['gini']})")
|
|
risk_level += 1
|
|
|
|
if dev.get("risk_level") == "HIGH":
|
|
risks.append(f"High-risk developer: {dev.get('tokens_deployed', '?')} tokens deployed")
|
|
risk_level += 1
|
|
|
|
if not risks:
|
|
return "NO SIGNIFICANT CONCERNS - This token passes standard security checks. Standard trading risks apply. Always verify contract independently."
|
|
|
|
if risk_level >= 5:
|
|
return f"EXTREME RISK - {'; '.join(risks)}. STRONGLY advise against trading this token."
|
|
elif risk_level >= 3:
|
|
return f"HIGH RISK - {'; '.join(risks)}. Proceed with extreme caution."
|
|
elif risk_level >= 2:
|
|
return f"ELEVATED RISK - {'; '.join(risks)}. Review all details before trading."
|
|
else:
|
|
return f"MINOR CONCERNS - {'; '.join(risks)}. Standard due diligence recommended."
|
|
|
|
|
|
async def enhanced_token_report(address: str, chain: str = "solana", **kw) -> dict | None:
|
|
"""Enhanced token report with smart verdicts, enrichment, and tier info."""
|
|
|
|
cache_key = f"enhanced_report:{chain}:{address}"
|
|
try:
|
|
r = _r()
|
|
cached = r.get(cache_key)
|
|
if cached:
|
|
r.close()
|
|
return json.loads(cached)
|
|
r.close()
|
|
except Exception:
|
|
pass
|
|
|
|
# First, check known entities
|
|
known = lookup_entity(address, chain)
|
|
is_wallet = is_wallet_not_token(address, chain)
|
|
|
|
# Get the base token report
|
|
try:
|
|
from app.domains.databus.rugcharts_intel import instant_token_report
|
|
|
|
base_report = await instant_token_report(address, chain, **kw)
|
|
except Exception as e:
|
|
base_report = {"error": str(e), "sections": {}}
|
|
|
|
if not base_report:
|
|
base_report = {"sections": {}}
|
|
|
|
# ── Enrich ──
|
|
# Apply trust adjustments
|
|
if known:
|
|
trust_bonus, reason = get_trust_bonus(address, chain)
|
|
if trust_bonus and "overall_risk" in base_report:
|
|
old_score = base_report["overall_risk"]["score"]
|
|
new_score = max(0, min(100, old_score - trust_bonus))
|
|
base_report["overall_risk"]["score"] = round(new_score, 1)
|
|
|
|
if known["trust"] == "SAFE" and base_report["overall_risk"]["level"] in (
|
|
"CRITICAL",
|
|
"HIGH",
|
|
"DANGER",
|
|
):
|
|
base_report["overall_risk"]["level"] = "LOW"
|
|
base_report["overall_risk"]["color"] = "#88FF00"
|
|
|
|
base_report["known_entity"] = known
|
|
base_report["trust_adjustment"] = {
|
|
"original_score": base_report.get("overall_risk", {}).get("score", 0),
|
|
"reason": reason,
|
|
}
|
|
|
|
# Wallet detection
|
|
base_report["address_type"] = "wallet" if is_wallet else "token"
|
|
if is_wallet:
|
|
base_report["wallet_note"] = "This is a wallet address. Token security checks do not apply."
|
|
|
|
# ── Smart Verdict ──
|
|
base_report["quick_verdict"] = smart_verdict(base_report)
|
|
|
|
# ── Tier Info ──
|
|
base_report["tier_info"] = {
|
|
"free_available": tier_allows("free", "token_report"),
|
|
"premium_available": tier_allows("premium", "token_report"),
|
|
"data_sources_used": len(base_report.get("sections", {})),
|
|
"enrichment_applied": bool(known),
|
|
}
|
|
|
|
# ── Data Quality Score ──
|
|
sections_with_data = sum(1 for s in base_report.get("sections", {}).values() if s and not s.get("error"))
|
|
base_report["data_quality"] = {
|
|
"score": min(100, sections_with_data * 15),
|
|
"level": "EXCELLENT"
|
|
if sections_with_data >= 6
|
|
else "GOOD"
|
|
if sections_with_data >= 4
|
|
else "FAIR"
|
|
if sections_with_data >= 2
|
|
else "LIMITED",
|
|
"sections_populated": sections_with_data,
|
|
"total_sections_available": 9,
|
|
}
|
|
|
|
# ── Generated At ──
|
|
base_report["generated_at"] = datetime.now(UTC).isoformat()
|
|
base_report["source"] = "enhanced_token_report"
|
|
|
|
# Cache
|
|
try:
|
|
r = _r()
|
|
r.setex(cache_key, 300, json.dumps(base_report, default=str))
|
|
r.close()
|
|
except Exception:
|
|
pass
|
|
|
|
return base_report
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# 5. TIER COMPARISON ENDPOINT DATA
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
async def get_tier_comparison(**kw) -> dict:
|
|
"""Return the competitive tier comparison table."""
|
|
return {
|
|
"tiers": {
|
|
k: {
|
|
"name": v["name"],
|
|
"rate_limit_rpm": v["rate_limit_rpm"],
|
|
"price": v.get("price_monthly", 0),
|
|
"features": len(v["allowed_data_types"]) if v["allowed_data_types"] != ["*"] else 49,
|
|
}
|
|
for k, v in TIER_DEFINITIONS.items()
|
|
},
|
|
"comparison": tier_comparison_table(),
|
|
"data_bus_chains": 49,
|
|
"source": "data_quality_engine",
|
|
}
|