|
|
"""API client for RewardPilot Orchestrator""" |
|
|
|
|
|
import httpx |
|
|
from typing import Dict, Optional |
|
|
from config import ORCHESTRATOR_URL |
|
|
from typing import Any, Dict, List, Optional, Tuple |
|
|
import logging |
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
class RewardPilotClient: |
|
|
"""Client for interacting with RewardPilot Orchestrator""" |
|
|
|
|
|
def __init__(self, base_url: str = ORCHESTRATOR_URL): |
|
|
self.base_url = base_url.rstrip('/') |
|
|
self.timeout = 30.0 |
|
|
|
|
|
async def get_recommendation( |
|
|
self, |
|
|
user_id: str, |
|
|
merchant: str, |
|
|
mcc: str, |
|
|
amount_usd: float, |
|
|
transaction_date: Optional[str] = None |
|
|
) -> Dict: |
|
|
"""Get card recommendation from orchestrator""" |
|
|
async with httpx.AsyncClient(timeout=self.timeout) as client: |
|
|
try: |
|
|
payload = { |
|
|
"user_id": user_id, |
|
|
"merchant": merchant, |
|
|
"mcc": mcc, |
|
|
"amount_usd": amount_usd |
|
|
} |
|
|
if transaction_date: |
|
|
payload["transaction_date"] = transaction_date |
|
|
|
|
|
response = await client.post( |
|
|
f"{self.base_url}/recommend", |
|
|
json=payload |
|
|
) |
|
|
response.raise_for_status() |
|
|
return response.json() |
|
|
except httpx.HTTPError as e: |
|
|
return { |
|
|
"error": True, |
|
|
"message": f"API Error: {str(e)}" |
|
|
} |
|
|
except Exception as e: |
|
|
return { |
|
|
"error": True, |
|
|
"message": f"Unexpected error: {str(e)}" |
|
|
} |
|
|
|
|
|
def get_recommendation_sync( |
|
|
self, |
|
|
user_id: str, |
|
|
merchant: str, |
|
|
mcc: str, |
|
|
amount_usd: float, |
|
|
transaction_date: Optional[str] = None |
|
|
) -> Dict: |
|
|
"""Synchronous version for Gradio""" |
|
|
import asyncio |
|
|
try: |
|
|
loop = asyncio.get_event_loop() |
|
|
except RuntimeError: |
|
|
loop = asyncio.new_event_loop() |
|
|
asyncio.set_event_loop(loop) |
|
|
|
|
|
return loop.run_until_complete( |
|
|
self.get_recommendation( |
|
|
user_id, merchant, mcc, amount_usd, transaction_date |
|
|
) |
|
|
) |
|
|
|
|
|
def get_user_analytics(self, user_id: str) -> Dict[str, Any]: |
|
|
""" |
|
|
Fetch user analytics including spending patterns and optimization metrics |
|
|
|
|
|
Args: |
|
|
user_id: User identifier |
|
|
|
|
|
Returns: |
|
|
Dictionary containing analytics data |
|
|
""" |
|
|
try: |
|
|
|
|
|
response = httpx.get( |
|
|
f"{self.orchestrator_url}/analytics/{user_id}", |
|
|
timeout=30.0 |
|
|
) |
|
|
response.raise_for_status() |
|
|
return response.json() |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Analytics fetch failed: {e}") |
|
|
|
|
|
return self._get_mock_analytics(user_id) |
|
|
|
|
|
def _get_mock_analytics(self, user_id: str) -> Dict[str, Any]: |
|
|
"""Generate mock analytics data for demo purposes""" |
|
|
import random |
|
|
|
|
|
|
|
|
user_multipliers = { |
|
|
"u_alice": 1.2, |
|
|
"u_bob": 0.9, |
|
|
"u_charlie": 1.5, |
|
|
} |
|
|
multiplier = user_multipliers.get(user_id, 1.0) |
|
|
|
|
|
return { |
|
|
"user_id": user_id, |
|
|
"annual_savings": round(342 * multiplier, 2), |
|
|
"rate_increase": round(23 * multiplier, 1), |
|
|
"optimized_transactions": int(156 * multiplier), |
|
|
"optimization_score": min(100, int(87 * multiplier)), |
|
|
"category_breakdown": [ |
|
|
{ |
|
|
"category": "π Groceries", |
|
|
"monthly_spend": round(450 * multiplier, 2), |
|
|
"best_card": "Amex Gold", |
|
|
"rewards": round(27 * multiplier, 2), |
|
|
"rate": "6%" |
|
|
}, |
|
|
{ |
|
|
"category": "π½οΈ Restaurants", |
|
|
"monthly_spend": round(320 * multiplier, 2), |
|
|
"best_card": "Amex Gold", |
|
|
"rewards": round(12.8 * multiplier, 2), |
|
|
"rate": "4%" |
|
|
}, |
|
|
{ |
|
|
"category": "β½ Gas", |
|
|
"monthly_spend": round(180 * multiplier, 2), |
|
|
"best_card": "Costco Visa", |
|
|
"rewards": round(7.2 * multiplier, 2), |
|
|
"rate": "4%" |
|
|
}, |
|
|
{ |
|
|
"category": "βοΈ Travel", |
|
|
"monthly_spend": round(850 * multiplier, 2), |
|
|
"best_card": "Sapphire Reserve", |
|
|
"rewards": round(42.5 * multiplier, 2), |
|
|
"rate": "5%" |
|
|
}, |
|
|
{ |
|
|
"category": "π¬ Entertainment", |
|
|
"monthly_spend": round(125 * multiplier, 2), |
|
|
"best_card": "Freedom Unlimited", |
|
|
"rewards": round(1.88 * multiplier, 2), |
|
|
"rate": "1.5%" |
|
|
}, |
|
|
{ |
|
|
"category": "πͺ Online Shopping", |
|
|
"monthly_spend": round(280 * multiplier, 2), |
|
|
"best_card": "Amazon Prime", |
|
|
"rewards": round(16.8 * multiplier, 2), |
|
|
"rate": "6%" |
|
|
} |
|
|
], |
|
|
"total_monthly_spend": round(2205 * multiplier, 2), |
|
|
"total_monthly_rewards": round(108.18 * multiplier, 2), |
|
|
"average_rate": round(4.9, 1), |
|
|
"top_categories": [ |
|
|
{"name": "βοΈ Travel", "amount": round(850 * multiplier, 2), "change": "+45%"}, |
|
|
{"name": "π Groceries", "amount": round(450 * multiplier, 2), "change": "+12%"}, |
|
|
{"name": "π½οΈ Restaurants", "amount": round(320 * multiplier, 2), "change": "-5%"} |
|
|
], |
|
|
"ytd_rewards": round(1298.16 * multiplier, 2), |
|
|
"ytd_potential": round(1640 * multiplier, 2), |
|
|
"money_left": round(341.84 * multiplier, 2), |
|
|
"forecast": { |
|
|
"next_month_spend": round(2350 * multiplier, 2), |
|
|
"next_month_rewards": round(115.25 * multiplier, 2), |
|
|
"cards_to_watch": ["Amex Gold (dining cap)", "Freedom (quarterly bonus)"] |
|
|
}, |
|
|
"recommendations": [ |
|
|
"π³ Use Chase Freedom for groceries in Q4 (5% back)", |
|
|
"β οΈ Monitor Amex Gold dining spend (cap at $2,000)", |
|
|
"π― Book holiday travel with Sapphire Reserve for 5x points" |
|
|
] |
|
|
} |