rmi-backend/app/domains/databus/response_schema.py

94 lines
3.3 KiB
Python

"""DataBus Response Schema Validation"""
import logging
from typing import Any
logger = logging.getLogger("databus.response_schema")
class SchemaValidator:
"""Lightweight schema validation for DataBus responses.
Each data type has an expected schema. If a provider returns data
that doesn't match, the DataBus falls back to the next provider.
"""
SCHEMAS = { # noqa: RUF012
"token_price": {
"required": ["price_usd"],
"optional": ["change_24h", "volume_24h", "market_cap"],
},
"wallet_labels": {"required": ["label"], "optional": ["source", "confidence", "category"]},
"risk_scan": {
"required": ["risk_score"],
"optional": ["is_honeypot", "threats", "risk_factors"],
},
"entity_intel": {
"required": ["entity_name"],
"optional": ["category", "addresses", "links"],
},
"arkham_entity": {
"required": ["entity_name"],
"optional": ["category", "description", "website"],
},
"arkham_portfolio": {
"required": ["total_value_usd"],
"optional": ["token_count", "tokens", "chain_exposures"],
},
"market_overview": {
"required": ["total_mcap"],
"optional": ["btc_dom", "eth_dom", "fgi", "volume_24h"],
},
"trending": {
"required": ["name"],
"optional": ["symbol", "price_usd", "change_24h", "volume_24h"],
},
"funding_source": {
"required": ["funders"],
"optional": ["first_funder", "funding_tx_count", "source_type"],
},
"alerts": {"required": ["alerts"], "optional": ["count", "severity"]},
"dex_data": {
"required": ["pair_address"],
"optional": ["liquidity", "volume_24h", "price_usd"],
},
"news": {
"required": ["title"],
"optional": ["source_name", "published_at", "url", "sentiment"],
},
"threat_check": {
"required": ["threat_score"],
"optional": ["threat_detected", "threats", "recommendation"],
},
}
def validate(self, data_type: str, data: Any) -> tuple:
"""Validate response data against expected schema.
Returns (is_valid, missing_fields).
"""
if not isinstance(data, dict):
return False, ["data must be dict"]
schema = self.SCHEMAS.get(data_type)
if not schema:
return True, [] # No schema = pass through
required = schema.get("required", [])
missing = [f for f in required if f not in data]
if missing:
return False, missing
return True, []
def check_response(self, data_type: str, result: dict) -> dict:
"""Check a full DataBus response dict. Returns annotated result."""
if not result or "data" not in result:
return result
data = result["data"]
is_valid, missing = self.validate(data_type, data)
result["schema_valid"] = is_valid
if not is_valid:
result["schema_missing"] = missing
logger.warning(f"Schema validation failed for {data_type}: missing {missing}")
return result
# Module-level singleton instance
schema_validator = SchemaValidator()