sammy786's picture
Update app.py
d21cf89 verified
raw
history blame
21.5 kB
"""
Beautiful Gradio interface for credit card recommendations
"""
from datetime import date
from typing import Optional, Tuple, List, Dict, Any
import gradio as gr
from config import (
APP_TITLE, APP_DESCRIPTION, THEME,
MCC_CATEGORIES, SAMPLE_USERS,
MERCHANTS_BY_CATEGORY
)
from utils.api_client import RewardPilotClient
from utils.formatters import (
format_full_recommendation,
format_comparison_table,
)
# ===================== Initialize API client =====================
client = RewardPilotClient()
# ===================== Main Recommendation Function =====================
def get_recommendation(
user_id: str,
merchant: str,
category: str,
amount: float,
use_custom_mcc: bool,
custom_mcc: str,
transaction_date: Optional[str]
) -> tuple:
"""Get card recommendation and format response"""
# Validate inputs
if not user_id or not merchant or amount <= 0:
return (
"❌ **Error:** Please fill in all required fields.",
None,
None,
)
# Determine MCC code
if use_custom_mcc and custom_mcc:
mcc = custom_mcc
else:
mcc = MCC_CATEGORIES.get(category, "5999")
# Set default date if not provided
if not transaction_date:
transaction_date = str(date.today())
# Call API
response: Dict[str, Any] = client.get_recommendation_sync(
user_id=user_id,
merchant=merchant,
mcc=mcc,
amount_usd=amount,
transaction_date=transaction_date,
)
# Format response
formatted_text = format_full_recommendation(response)
# Extract card details for comparison
comparison_table: Optional[str]
stats: Optional[str]
if not response.get("error"):
recommended = response.get("recommended_card", {}) or {}
alternatives: List[Dict[str, Any]] = response.get("alternative_cards", []) or []
all_cards = [c for c in ([recommended] + alternatives) if c]
comparison_table = format_comparison_table(all_cards) if all_cards else None
# Create summary stats
total_analyzed = response.get("total_cards_analyzed", len(all_cards))
best_reward = (recommended.get("reward_amount") or 0.0)
services_used = response.get("services_used", [])
stats = f"""
**Cards Analyzed:** {total_analyzed}
**Best Reward:** ${best_reward:.2f}
**Services Used:** {', '.join(services_used)}
""".strip()
else:
comparison_table = None
stats = None
return formatted_text, comparison_table, stats
# ===================== Sample Transaction Examples =====================
EXAMPLES = [
["u_alice", "Groceries", "Whole Foods", 125.50, False, "", "2025-01-15"],
["u_bob", "Restaurants", "Olive Garden", 65.75, False, "", "2025-01-15"],
["u_charlie", "Airlines", "United Airlines", 450.00, False, "", "2025-01-15"],
["u_alice", "Fast Food", "Starbucks", 15.75, False, "", ""],
["u_bob", "Gas Stations", "Shell", 45.00, False, "", ""],
]
# ===================== Build Gradio Interface =====================
def _toggle_custom_mcc(use_custom: bool):
return gr.update(visible=use_custom, value="")
with gr.Blocks(
theme=THEME if isinstance(THEME, gr.themes.ThemeClass) else gr.themes.Soft(),
title=APP_TITLE,
css="""
.gradio-container {
max-width: 1200px !important;
}
.recommendation-output {
font-size: 16px;
line-height: 1.6;
}
/* Metric Cards Styling */
.metric-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px 20px;
border-radius: 16px;
text-align: center;
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
transition: transform 0.3s ease, box-shadow 0.3s ease;
margin: 10px;
}
.metric-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 32px rgba(102, 126, 234, 0.4);
}
.metric-card h2 {
font-size: 48px;
font-weight: 700;
margin: 0 0 10px 0;
color: white;
}
.metric-card p {
font-size: 16px;
margin: 0;
opacity: 0.9;
color: white;
}
.metric-card-green {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
}
.metric-card-orange {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.metric-card-blue {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
/* Table styling */
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
table th {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 12px;
text-align: left;
font-weight: 600;
}
table td {
padding: 12px;
border-bottom: 1px solid #f0f0f0;
}
table tr:last-child td {
border-bottom: none;
}
table tr:hover {
background: #f8f9fa;
}
""",
) as app:
# Header
gr.Markdown(
f"""
# {APP_TITLE}
{APP_DESCRIPTION}
Get AI-powered credit card recommendations that maximize your rewards based on:
- 💰 **Reward Rates** - Optimal card selection for each purchase
- 📚 **Card Benefits** - Detailed information from our knowledge base
- ⚠️ **Spending Caps** - Risk warnings to avoid missing out on bonuses
---
"""
)
# Ensure all tabs are siblings at the same level
with gr.Tabs():
# ========== Tab 1: Get Recommendation ==========
with gr.Tab("🎯 Get Recommendation"):
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Transaction Details")
user_dropdown = gr.Dropdown(
choices=SAMPLE_USERS,
value=SAMPLE_USERS[0],
label="User ID",
info="Select a user"
)
# CATEGORY FIRST (moved up)
category_dropdown = gr.Dropdown(
choices=list(MCC_CATEGORIES.keys()),
value="Groceries",
label="🏷️ Type of Purchase",
info="Select the category first"
)
# MERCHANT DROPDOWN (now dynamic)
merchant_dropdown = gr.Dropdown(
choices=MERCHANTS_BY_CATEGORY["Groceries"],
value="Whole Foods",
label="🏪 Merchant Name",
info="Select merchant (changes based on category)",
allow_custom_value=True # Allows typing custom merchants
)
amount_input = gr.Number(
label="💵 Amount (USD)",
value=125.50,
minimum=0.01,
step=0.01
)
date_input = gr.Textbox(
label="📅 Transaction Date (Optional)",
placeholder="YYYY-MM-DD or leave blank for today",
value=""
)
# Advanced options
with gr.Accordion("⚙️ Advanced Options", open=False):
use_custom_mcc = gr.Checkbox(
label="Use Custom MCC Code",
value=False
)
custom_mcc_input = gr.Textbox(
label="Custom MCC Code",
placeholder="e.g., 5411",
visible=False
)
def toggle_custom_mcc(use_custom):
return gr.update(visible=use_custom)
use_custom_mcc.change(
fn=toggle_custom_mcc,
inputs=[use_custom_mcc],
outputs=[custom_mcc_input]
)
recommend_btn = gr.Button(
"🚀 Get Recommendation",
variant="primary",
size="lg"
)
with gr.Column(scale=2):
gr.Markdown("### 💡 Recommendation")
recommendation_output = gr.Markdown(
value="✨ Select a category and merchant, then click 'Get Recommendation'",
elem_classes=["recommendation-output"]
)
def update_merchant_choices(category):
"""Update merchant dropdown based on selected category"""
merchants = MERCHANTS_BY_CATEGORY.get(category, ["Custom Merchant"])
return gr.update(
choices=merchants,
value=merchants[0] if merchants else ""
)
# Connect category change to merchant update
category_dropdown.change(
fn=update_merchant_choices,
inputs=[category_dropdown],
outputs=[merchant_dropdown]
)
# Stats and comparison below
with gr.Row():
with gr.Column():
gr.Markdown("### 📊 Quick Stats")
stats_output = gr.Markdown()
with gr.Column():
gr.Markdown("### 🔄 Card Comparison")
comparison_output = gr.Markdown()
# Connect button to function
recommend_btn.click(
fn=get_recommendation,
inputs=[
user_dropdown,
merchant_dropdown,
category_dropdown,
amount_input,
use_custom_mcc,
custom_mcc_input,
date_input
],
outputs=[
recommendation_output,
comparison_output,
stats_output
]
)
# Examples
gr.Markdown("### 📝 Example Transactions")
gr.Examples(
examples=EXAMPLES,
inputs=[
user_dropdown,
category_dropdown, # Swapped order
merchant_dropdown, # Swapped order
amount_input,
use_custom_mcc,
custom_mcc_input,
date_input
],
outputs=[
recommendation_output,
comparison_output,
stats_output
],
fn=get_recommendation,
cache_examples=False
)
# ========== Tab 2: Analytics (DYNAMIC) ==========
with gr.Tab("📊 Analytics"):
gr.Markdown("## 🎯 Your Rewards Optimization Dashboard")
# User selector for analytics
with gr.Row():
analytics_user = gr.Dropdown(
choices=SAMPLE_USERS,
value=SAMPLE_USERS[0],
label="👤 View Analytics For User",
scale=3
)
refresh_analytics_btn = gr.Button(
"🔄 Refresh Analytics",
variant="secondary",
scale=1
)
# Top Metrics Row (Dynamic)
metrics_display = gr.HTML(
value="""
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<div class="metric-card" style="flex: 1; min-width: 200px;">
<h2>$342</h2>
<p>💰 Potential Annual Savings</p>
</div>
<div class="metric-card metric-card-green" style="flex: 1; min-width: 200px;">
<h2>23%</h2>
<p>📈 Rewards Rate Increase</p>
</div>
<div class="metric-card metric-card-orange" style="flex: 1; min-width: 200px;">
<h2>156</h2>
<p>✅ Optimized Transactions</p>
</div>
<div class="metric-card metric-card-blue" style="flex: 1; min-width: 200px;">
<h2>87/100</h2>
<p>⭐ Optimization Score</p>
</div>
</div>
"""
)
gr.Markdown("---")
# Detailed Analytics (Dynamic)
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 💰 Category Spending Breakdown")
spending_table = gr.Markdown(
value="""
| Category | Monthly Spend | Best Card | Rewards | Rate |
|----------|---------------|-----------|---------|------|
| 🛒 Groceries | $450.00 | Amex Gold | $27.00 | 6% |
| 🍽️ Restaurants | $320.00 | Amex Gold | $12.80 | 4% |
| ⛽ Gas | $180.00 | Costco Visa | $7.20 | 4% |
| ✈️ Travel | $850.00 | Sapphire Reserve | $42.50 | 5% |
| 🎬 Entertainment | $125.00 | Freedom Unlimited | $1.88 | 1.5% |
| 🏪 Online Shopping | $280.00 | Amazon Prime | $16.80 | 6% |
| **Total** | **$2,205.00** | - | **$108.18** | **4.9%** |
"""
)
with gr.Column(scale=1):
gr.Markdown("### 📈 Monthly Trends & Insights")
insights_display = gr.Markdown(
value="""
**🔥 Top Spending Categories:**
1. ✈️ Travel: $850 (↑ 45% from last month)
2. 🛒 Groceries: $450 (↑ 12%)
3. 🍽️ Restaurants: $320 (↓ 5%)
**💡 Optimization Opportunities:**
- ✅ You're using optimal cards 87% of the time
- 🎯 Switch to Chase Freedom for Q4 5% grocery bonus
- ⚠️ Amex Gold dining cap approaching ($2,000 limit)
**🏆 Best Performing Card:**
Chase Sapphire Reserve - $42.50 rewards earned
**📊 Year-to-Date:**
- Total Rewards: $1,298.16
- Potential if optimized: $1,640.00
- **Money left on table: $341.84**
"""
)
gr.Markdown("---")
# Spending Forecast (Dynamic)
forecast_display = gr.Markdown(
value="""
### 🔮 Next Month Forecast
Based on your spending patterns:
- **Predicted Spend:** $2,350
- **Predicted Rewards:** $115.25
- **Cards to Watch:** Amex Gold (dining cap), Freedom (quarterly bonus)
**Recommendations:**
1. 💳 Use Chase Freedom for groceries in Q4 (5% back)
2. ⚠️ Monitor Amex Gold dining spend (cap at $2,000)
3. 🎯 Book holiday travel with Sapphire Reserve for 5x points
"""
)
# Status indicator
analytics_status = gr.Markdown(
value="*Analytics loaded for u_alice*",
elem_classes=["status-text"]
)
# ===================== Analytics Update Function =====================
def update_analytics(user_id: str) -> tuple:
"""Fetch and format analytics for selected user"""
try:
# Fetch analytics data
analytics_data = client.get_user_analytics(user_id)
# Format for display
from utils.formatters import format_analytics_metrics
metrics_html, table_md, insights_md, forecast_md = format_analytics_metrics(analytics_data)
# Status message
from datetime import datetime
status = f"*Analytics updated for {user_id} at {datetime.now().strftime('%I:%M %p')}*"
return metrics_html, table_md, insights_md, forecast_md, status
except Exception as e:
error_msg = f"❌ Error loading analytics: {str(e)}"
return (
"<p>Error loading metrics</p>",
"Error loading table",
"Error loading insights",
"Error loading forecast",
error_msg
)
# Connect analytics refresh to button and dropdown
refresh_analytics_btn.click(
fn=update_analytics,
inputs=[analytics_user],
outputs=[
metrics_display,
spending_table,
insights_display,
forecast_display,
analytics_status
]
)
analytics_user.change(
fn=update_analytics,
inputs=[analytics_user],
outputs=[
metrics_display,
spending_table,
insights_display,
forecast_display,
analytics_status
]
)
# ========== Tab 3: About ==========
with gr.Tab("ℹ️ About"):
gr.Markdown(
"""
## About RewardPilot
RewardPilot is an AI-powered credit card recommendation system built using the **Model Context Protocol (MCP)** architecture.
### 🏗️ Architecture
The system consists of multiple microservices:
1. **Smart Wallet** - Analyzes transaction context and selects optimal cards
2. **Rewards-RAG** - Retrieves detailed card benefit information using RAG
3. **Spend-Forecast** - Predicts spending patterns and warns about cap risks
4. **Orchestrator** - Coordinates all services for comprehensive recommendations
### 🎯 How It Works
1. **Enter Transaction Details** - Merchant, amount, category
2. **AI Analysis** - System analyzes your wallet and transaction context
3. **Get Recommendation** - Receive the best card with detailed reasoning
4. **Maximize Rewards** - Earn more points/cashback on every purchase
### 🔧 Technology Stack
- **Backend:** FastAPI, Python
- **Frontend:** Gradio
- **AI/ML:** RAG (Retrieval-Augmented Generation)
- **Architecture:** MCP (Model Context Protocol)
- **Deployment:** Hugging Face Spaces
### 📚 MCC Categories Supported
- Groceries (5411)
- Restaurants (5812)
- Gas Stations (5541)
- Airlines (3000-3999)
- Hotels (7011)
- Entertainment (7832, 7841)
- And many more...
### 🎓 Built For
**MCP 1st Birthday Hackathon** - Celebrating one year of the Model Context Protocol
### 👨‍💻 Developer
Built with ❤️ for the MCP community
---
**Version:** 1.0.0
**Last Updated:** November 2025
"""
)
# ========== Tab 4: API Documentation ==========
with gr.Tab("📖 API Docs"):
gr.Markdown(
"""
## API Endpoints
### Orchestrator API
**Base URL:** `https://mcp-1st-birthday-rewardpilot-orchestrator.hf.space`
#### POST `/recommend`
Get comprehensive card recommendation.
**Request:**
```json
{
"user_id": "u_alice",
"merchant": "Whole Foods",
"mcc": "5411",
"amount_usd": 125.50,
"transaction_date": "2025-01-15"
}
```
**Response:**
```json
{
"user_id": "u_alice",
"merchant": "Whole Foods",
"amount_usd": 125.5,
"recommended_card": {
"card_id": "c_amex_gold",
"card_name": "American Express Gold Card",
"reward_rate": 4.0,
"reward_amount": 502.0,
"category": "Groceries",
"reasoning": "Earns 4x points on Groceries"
},
"alternative_cards": ["..."],
"rag_insights": { "...": "..." },
"forecast_warning": { "...": "..." },
"services_used": ["smart_wallet", "rewards_rag", "spend_forecast"],
"final_recommendation": "..."
}
```
### Other Services
- Smart Wallet: https://mcp-1st-birthday-rewardpilot-smart-wallet.hf.space
- Rewards-RAG: https://mcp-1st-birthday-rewardpilot-rewards-rag.hf.space
- Spend-Forecast: https://mcp-1st-birthday-rewardpilot-spend-forecast.hf.space
### Interactive Docs
Visit `/docs` on any service for interactive Swagger UI documentation.
### cURL Examples
```bash
# Get recommendation
curl -X POST https://mcp-1st-birthday-rewardpilot-orchestrator.hf.space/recommend \\
-H "Content-Type: application/json" \\
-d '{
"user_id": "u_alice",
"merchant": "Whole Foods",
"mcc": "5411",
"amount_usd": 125.50
}'
```
"""
)
# ===================== Launch App =====================
if __name__ == "__main__":
app.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
)