|
|
"""Response formatting utilities""" |
|
|
|
|
|
from typing import Any, Dict, List, Optional, Tuple |
|
|
import plotly.graph_objects as go |
|
|
import plotly.express as px |
|
|
|
|
|
|
|
|
def format_card_display(card_data: Dict) -> str: |
|
|
"""Format card recommendation for display""" |
|
|
card_name = card_data.get("card_name", "Unknown Card") |
|
|
reward_rate = card_data.get("reward_rate", 0) |
|
|
reward_amount = card_data.get("reward_amount", 0) |
|
|
category = card_data.get("category", "General") |
|
|
reasoning = card_data.get("reasoning", "") |
|
|
|
|
|
return f""" |
|
|
### 💳 {card_name} |
|
|
|
|
|
**Reward Rate:** {reward_rate}x points |
|
|
**Reward Amount:** ${reward_amount:.2f} |
|
|
**Category:** {category} |
|
|
**Why:** {reasoning} |
|
|
""" |
|
|
|
|
|
def format_full_recommendation(response: Dict) -> str: |
|
|
"""Format complete recommendation response""" |
|
|
if response.get("error"): |
|
|
return f"❌ **Error:** {response.get('message', 'Unknown error')}" |
|
|
|
|
|
|
|
|
output = f""" |
|
|
# 🎯 Recommendation for {response.get('merchant', 'Unknown')} |
|
|
|
|
|
**Amount:** ${response.get('amount_usd', 0):.2f} |
|
|
**Date:** {response.get('transaction_date', 'N/A')} |
|
|
**User:** {response.get('user_id', 'N/A')} |
|
|
|
|
|
--- |
|
|
|
|
|
## 🏆 Best Card to Use |
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
recommended = response.get("recommended_card", {}) |
|
|
output += format_card_display(recommended) |
|
|
|
|
|
|
|
|
rag_insights = response.get("rag_insights") |
|
|
if rag_insights: |
|
|
output += f""" |
|
|
--- |
|
|
|
|
|
## 📚 Card Benefits |
|
|
|
|
|
{rag_insights.get('benefits', 'No additional information available.')} |
|
|
""" |
|
|
if rag_insights.get('tips'): |
|
|
output += f""" |
|
|
💡 **Pro Tip:** {rag_insights.get('tips')} |
|
|
""" |
|
|
|
|
|
|
|
|
forecast = response.get("forecast_warning") |
|
|
if forecast: |
|
|
risk_level = forecast.get("risk_level", "low") |
|
|
message = forecast.get("message", "") |
|
|
|
|
|
if risk_level == "high": |
|
|
emoji = "🚨" |
|
|
elif risk_level == "medium": |
|
|
emoji = "⚠️" |
|
|
else: |
|
|
emoji = "✅" |
|
|
|
|
|
output += f""" |
|
|
--- |
|
|
|
|
|
## {emoji} Spending Status |
|
|
|
|
|
{message} |
|
|
|
|
|
**Current Spend:** ${forecast.get('current_spend', 0):.2f} |
|
|
**Spending Cap:** ${forecast.get('cap', 0):.2f} |
|
|
**Projected Spend:** ${forecast.get('projected_spend', 0):.2f} |
|
|
""" |
|
|
|
|
|
|
|
|
alternatives = response.get("alternative_cards", []) |
|
|
if alternatives: |
|
|
output += "\n---\n\n## 🔄 Alternative Cards\n\n" |
|
|
for i, alt in enumerate(alternatives[:2], 1): |
|
|
output += f"### Option {i}\n" |
|
|
output += format_card_display(alt) |
|
|
|
|
|
|
|
|
services = response.get("services_used", []) |
|
|
time_ms = response.get("orchestration_time_ms", 0) |
|
|
|
|
|
output += f""" |
|
|
--- |
|
|
|
|
|
**Services Used:** {', '.join(services)} |
|
|
**Response Time:** {time_ms:.0f}ms |
|
|
""" |
|
|
|
|
|
return output |
|
|
|
|
|
def format_comparison_table(cards: list) -> str: |
|
|
"""Format card comparison as markdown table""" |
|
|
if not cards: |
|
|
return "No cards to compare." |
|
|
|
|
|
table = """ |
|
|
| Card | Reward Rate | Reward Amount | Category | |
|
|
|------|-------------|---------------|----------| |
|
|
""" |
|
|
|
|
|
for card in cards: |
|
|
name = card.get("card_name", "Unknown") |
|
|
rate = card.get("reward_rate", 0) |
|
|
amount = card.get("reward_amount", 0) |
|
|
category = card.get("category", "N/A") |
|
|
table += f"| {name} | {rate}x | ${amount:.2f} | {category} |\n" |
|
|
|
|
|
return table |
|
|
|
|
|
|
|
|
|
|
|
def format_analytics_metrics(analytics: Dict[str, Any]) -> tuple: |
|
|
""" |
|
|
Format analytics data for display |
|
|
|
|
|
Returns: |
|
|
Tuple of (metrics_html, table_md, insights_md, forecast_md) |
|
|
""" |
|
|
|
|
|
metrics_html = f""" |
|
|
<div style="display: flex; gap: 10px; flex-wrap: wrap;"> |
|
|
<div class="metric-card" style="flex: 1; min-width: 200px;"> |
|
|
<h2>${analytics['annual_savings']}</h2> |
|
|
<p>💰 Potential Annual Savings</p> |
|
|
</div> |
|
|
<div class="metric-card metric-card-green" style="flex: 1; min-width: 200px;"> |
|
|
<h2>{analytics['rate_increase']}%</h2> |
|
|
<p>📈 Rewards Rate Increase</p> |
|
|
</div> |
|
|
<div class="metric-card metric-card-orange" style="flex: 1; min-width: 200px;"> |
|
|
<h2>{analytics['optimized_transactions']}</h2> |
|
|
<p>✅ Optimized Transactions</p> |
|
|
</div> |
|
|
<div class="metric-card metric-card-blue" style="flex: 1; min-width: 200px;"> |
|
|
<h2>{analytics['optimization_score']}/100</h2> |
|
|
<p>⭐ Optimization Score</p> |
|
|
</div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
table_rows = [] |
|
|
for cat in analytics['category_breakdown']: |
|
|
table_rows.append( |
|
|
f"| {cat['category']} | ${cat['monthly_spend']:.2f} | {cat['best_card']} | " |
|
|
f"${cat['rewards']:.2f} | {cat['rate']} |" |
|
|
) |
|
|
|
|
|
table_md = f""" |
|
|
| Category | Monthly Spend | Best Card | Rewards | Rate | |
|
|
|----------|---------------|-----------|---------|------| |
|
|
{chr(10).join(table_rows)} |
|
|
| **Total** | **${analytics['total_monthly_spend']:.2f}** | - | **${analytics['total_monthly_rewards']:.2f}** | **{analytics['average_rate']}%** | |
|
|
""" |
|
|
|
|
|
|
|
|
top_cats = "\n".join([ |
|
|
f"{i+1}. {cat['name']}: ${cat['amount']:.2f} ({cat['change']})" |
|
|
for i, cat in enumerate(analytics['top_categories']) |
|
|
]) |
|
|
|
|
|
insights_md = f""" |
|
|
**🔥 Top Spending Categories:** |
|
|
{top_cats} |
|
|
|
|
|
**💡 Optimization Opportunities:** |
|
|
- ✅ You're using optimal cards {analytics['optimization_score']}% of the time |
|
|
- 🎯 Switch to Chase Freedom for Q4 5% grocery bonus |
|
|
- ⚠️ Amex Gold dining cap approaching ($2,000 limit) |
|
|
- 💳 Consider applying for Citi Custom Cash |
|
|
|
|
|
**🏆 Best Performing Card:** |
|
|
{analytics['category_breakdown'][0]['best_card']} - ${analytics['category_breakdown'][0]['rewards']:.2f} rewards earned |
|
|
|
|
|
**📊 Year-to-Date:** |
|
|
- Total Rewards: ${analytics['ytd_rewards']:.2f} |
|
|
- Potential if optimized: ${analytics['ytd_potential']:.2f} |
|
|
- **Money left on table: ${analytics['money_left']:.2f}** |
|
|
""" |
|
|
|
|
|
|
|
|
forecast = analytics['forecast'] |
|
|
recommendations = "\n".join([f"{i+1}. {rec}" for i, rec in enumerate(analytics['recommendations'])]) |
|
|
|
|
|
forecast_md = f""" |
|
|
### 🔮 Next Month Forecast |
|
|
|
|
|
Based on your spending patterns: |
|
|
- **Predicted Spend:** ${forecast['next_month_spend']:.2f} |
|
|
- **Predicted Rewards:** ${forecast['next_month_rewards']:.2f} |
|
|
- **Cards to Watch:** {', '.join(forecast['cards_to_watch'])} |
|
|
|
|
|
**Recommendations:** |
|
|
{recommendations} |
|
|
""" |
|
|
|
|
|
return metrics_html, table_md, insights_md, forecast_md |
|
|
|
|
|
def create_spending_chart(analytics: Dict[str, Any]) -> go.Figure: |
|
|
""" |
|
|
Create a bar chart showing spending by category |
|
|
|
|
|
Args: |
|
|
analytics: Analytics data dictionary |
|
|
|
|
|
Returns: |
|
|
Plotly figure object |
|
|
""" |
|
|
categories = [cat['category'] for cat in analytics['category_breakdown']] |
|
|
spending = [cat['monthly_spend'] for cat in analytics['category_breakdown']] |
|
|
rewards = [cat['rewards'] for cat in analytics['category_breakdown']] |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
|
|
|
fig.add_trace(go.Bar( |
|
|
name='Monthly Spend', |
|
|
x=categories, |
|
|
y=spending, |
|
|
marker_color='rgb(102, 126, 234)', |
|
|
text=[f'${s:.0f}' for s in spending], |
|
|
textposition='outside', |
|
|
)) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Bar( |
|
|
name='Rewards Earned', |
|
|
x=categories, |
|
|
y=rewards, |
|
|
marker_color='rgb(56, 239, 125)', |
|
|
text=[f'${r:.2f}' for r in rewards], |
|
|
textposition='outside', |
|
|
)) |
|
|
|
|
|
fig.update_layout( |
|
|
title='💰 Spending vs Rewards by Category', |
|
|
xaxis_title='Category', |
|
|
yaxis_title='Amount ($)', |
|
|
barmode='group', |
|
|
height=400, |
|
|
template='plotly_white', |
|
|
showlegend=True, |
|
|
legend=dict( |
|
|
orientation="h", |
|
|
yanchor="bottom", |
|
|
y=1.02, |
|
|
xanchor="right", |
|
|
x=1 |
|
|
) |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_rewards_pie_chart(analytics: Dict[str, Any]) -> go.Figure: |
|
|
""" |
|
|
Create a pie chart showing rewards distribution by category |
|
|
|
|
|
Args: |
|
|
analytics: Analytics data dictionary |
|
|
|
|
|
Returns: |
|
|
Plotly figure object |
|
|
""" |
|
|
categories = [cat['category'] for cat in analytics['category_breakdown']] |
|
|
rewards = [cat['rewards'] for cat in analytics['category_breakdown']] |
|
|
|
|
|
fig = go.Figure(data=[go.Pie( |
|
|
labels=categories, |
|
|
values=rewards, |
|
|
hole=0.4, |
|
|
marker=dict( |
|
|
colors=['#667eea', '#764ba2', '#f093fb', '#f5576c', '#4facfe', '#00f2fe'] |
|
|
), |
|
|
textinfo='label+percent', |
|
|
textposition='outside', |
|
|
hovertemplate='<b>%{label}</b><br>Rewards: $%{value:.2f}<br>%{percent}<extra></extra>' |
|
|
)]) |
|
|
|
|
|
fig.update_layout( |
|
|
title='🎯 Rewards Distribution by Category', |
|
|
height=400, |
|
|
template='plotly_white', |
|
|
showlegend=True, |
|
|
legend=dict( |
|
|
orientation="v", |
|
|
yanchor="middle", |
|
|
y=0.5, |
|
|
xanchor="left", |
|
|
x=1.05 |
|
|
) |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_optimization_gauge(analytics: Dict[str, Any]) -> go.Figure: |
|
|
""" |
|
|
Create a gauge chart showing optimization score |
|
|
|
|
|
Args: |
|
|
analytics: Analytics data dictionary |
|
|
|
|
|
Returns: |
|
|
Plotly figure object |
|
|
""" |
|
|
score = analytics['optimization_score'] |
|
|
|
|
|
fig = go.Figure(go.Indicator( |
|
|
mode="gauge+number+delta", |
|
|
value=score, |
|
|
domain={'x': [0, 1], 'y': [0, 1]}, |
|
|
title={'text': "⭐ Optimization Score", 'font': {'size': 24}}, |
|
|
delta={'reference': 80, 'increasing': {'color': "green"}}, |
|
|
gauge={ |
|
|
'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"}, |
|
|
'bar': {'color': "darkblue"}, |
|
|
'bgcolor': "white", |
|
|
'borderwidth': 2, |
|
|
'bordercolor': "gray", |
|
|
'steps': [ |
|
|
{'range': [0, 50], 'color': '#ffcccb'}, |
|
|
{'range': [50, 75], 'color': '#ffffcc'}, |
|
|
{'range': [75, 100], 'color': '#ccffcc'} |
|
|
], |
|
|
'threshold': { |
|
|
'line': {'color': "red", 'width': 4}, |
|
|
'thickness': 0.75, |
|
|
'value': 90 |
|
|
} |
|
|
} |
|
|
)) |
|
|
|
|
|
fig.update_layout( |
|
|
height=300, |
|
|
template='plotly_white', |
|
|
margin=dict(l=20, r=20, t=60, b=20) |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_trend_line_chart(analytics: Dict[str, Any]) -> go.Figure: |
|
|
""" |
|
|
Create a line chart showing spending trends (mock data for demo) |
|
|
|
|
|
Args: |
|
|
analytics: Analytics data dictionary |
|
|
|
|
|
Returns: |
|
|
Plotly figure object |
|
|
""" |
|
|
import random |
|
|
|
|
|
|
|
|
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] |
|
|
|
|
|
|
|
|
base_spend = analytics['total_monthly_spend'] |
|
|
spending_data = [base_spend * (0.8 + random.random() * 0.4) for _ in months] |
|
|
|
|
|
|
|
|
avg_rate = analytics['average_rate'] / 100 |
|
|
rewards_data = [spend * avg_rate for spend in spending_data] |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=months, |
|
|
y=spending_data, |
|
|
mode='lines+markers', |
|
|
name='Monthly Spending', |
|
|
line=dict(color='rgb(102, 126, 234)', width=3), |
|
|
marker=dict(size=8), |
|
|
hovertemplate='<b>%{x}</b><br>Spending: $%{y:.2f}<extra></extra>' |
|
|
)) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter( |
|
|
x=months, |
|
|
y=rewards_data, |
|
|
mode='lines+markers', |
|
|
name='Rewards Earned', |
|
|
line=dict(color='rgb(56, 239, 125)', width=3), |
|
|
marker=dict(size=8), |
|
|
hovertemplate='<b>%{x}</b><br>Rewards: $%{y:.2f}<extra></extra>' |
|
|
)) |
|
|
|
|
|
fig.update_layout( |
|
|
title='📈 12-Month Spending & Rewards Trend', |
|
|
xaxis_title='Month', |
|
|
yaxis_title='Amount ($)', |
|
|
height=400, |
|
|
template='plotly_white', |
|
|
hovermode='x unified', |
|
|
showlegend=True, |
|
|
legend=dict( |
|
|
orientation="h", |
|
|
yanchor="bottom", |
|
|
y=1.02, |
|
|
xanchor="right", |
|
|
x=1 |
|
|
) |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_card_performance_chart(analytics: Dict[str, Any]) -> go.Figure: |
|
|
""" |
|
|
Create a horizontal bar chart showing card performance |
|
|
|
|
|
Args: |
|
|
analytics: Analytics data dictionary |
|
|
|
|
|
Returns: |
|
|
Plotly figure object |
|
|
""" |
|
|
|
|
|
card_rewards = {} |
|
|
for cat in analytics['category_breakdown']: |
|
|
card = cat['best_card'] |
|
|
reward = cat['rewards'] |
|
|
if card in card_rewards: |
|
|
card_rewards[card] += reward |
|
|
else: |
|
|
card_rewards[card] = reward |
|
|
|
|
|
|
|
|
sorted_cards = sorted(card_rewards.items(), key=lambda x: x[0], reverse=True) |
|
|
cards = [card for card, _ in sorted_cards] |
|
|
rewards = [reward for _, reward in sorted_cards] |
|
|
|
|
|
fig = go.Figure(go.Bar( |
|
|
x=rewards, |
|
|
y=cards, |
|
|
orientation='h', |
|
|
marker=dict( |
|
|
color=rewards, |
|
|
colorscale='Viridis', |
|
|
showscale=True, |
|
|
colorbar=dict(title="Rewards ($)") |
|
|
), |
|
|
text=[f'${r:.2f}' for r in rewards], |
|
|
textposition='outside', |
|
|
hovertemplate='<b>%{y}</b><br>Total Rewards: $%{x:.2f}<extra></extra>' |
|
|
)) |
|
|
|
|
|
fig.update_layout( |
|
|
title='🏆 Card Performance Ranking', |
|
|
xaxis_title='Total Rewards Earned ($)', |
|
|
yaxis_title='Credit Card', |
|
|
height=400, |
|
|
template='plotly_white', |
|
|
margin=dict(l=150) |
|
|
) |
|
|
|
|
|
return fig |