"""Response formatting utilities""" from typing import Any, Dict, List, Optional, Tuple import plotly.graph_objects as go import plotly.express as px def safe_get(data: Dict, key: str, default: Any = None) -> Any: """Safely get value from dictionary""" try: return data.get(key, default) except: return default 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')}" # Header 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 card recommended = response.get("recommended_card", {}) output += format_card_display(recommended) # RAG Insights 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 Warning 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} """ # Alternative cards 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) # Metadata 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 # utils/formatters.py def format_analytics_metrics(analytics_data: Dict) -> tuple: """Format analytics data into HTML metrics, tables, and insights""" try: # Extract key metrics total_spending = float(safe_get(analytics_data, 'total_spending', 0)) total_rewards = float(safe_get(analytics_data, 'total_rewards', 0)) potential_savings = float(safe_get(analytics_data, 'potential_savings', 0)) optimization_score = int(safe_get(analytics_data, 'optimization_score', 0)) optimized_count = int(safe_get(analytics_data, 'optimized_count', 0)) total_transactions = int(safe_get(analytics_data, 'total_transactions', 0)) # Calculate optimization percentage opt_percentage = int((optimized_count / total_transactions * 100)) if total_transactions > 0 else 0 # Calculate rewards rate rewards_rate = (total_rewards / total_spending * 100) if total_spending > 0 else 0 # Create metrics HTML metrics_html = f"""

${potential_savings:.0f}

💰 Potential Annual Savings

{rewards_rate:.1f}%

📈 Rewards Rate

{optimized_count}

✅ Optimized Transactions

{optimization_score}/100

⭐ Optimization Score

""" # Create spending table category_breakdown = safe_get(analytics_data, 'category_breakdown', []) table_rows = [] for item in category_breakdown: category = item.get('category', 'Unknown') spending = float(item.get('spending', 0)) rewards = float(item.get('rewards', 0)) transactions = int(item.get('transactions', 0)) rate = (rewards / spending * 100) if spending > 0 else 0 # Category emoji mapping emoji_map = { 'Groceries': '🛒', 'Dining': '🍽️', 'Travel': '✈️', 'Gas': '⛽', 'Online Shopping': '🛍️', 'Entertainment': '🎬' } emoji = emoji_map.get(category, '📦') table_rows.append( f"| {emoji} {category} | ${spending:.2f} | ${rewards:.2f} | {rate:.1f}% | {transactions} |" ) total_spending_sum = sum(float(item.get('spending', 0)) for item in category_breakdown) total_rewards_sum = sum(float(item.get('rewards', 0)) for item in category_breakdown) total_rate = (total_rewards_sum / total_spending_sum * 100) if total_spending_sum > 0 else 0 total_txn = sum(int(item.get('transactions', 0)) for item in category_breakdown) table_md = f""" | Category | Monthly Spend | Rewards | Rate | Transactions | |----------|---------------|---------|------|--------------| {chr(10).join(table_rows)} | **Total** | **${total_spending_sum:.2f}** | **${total_rewards_sum:.2f}** | **{total_rate:.1f}%** | **{total_txn}** | """ # Create insights top_category = safe_get(analytics_data, 'top_category', 'Unknown') cards = safe_get(analytics_data, 'cards', []) insights_md = f""" **🔥 Top Spending Category:** - {top_category}: ${category_breakdown[0].get('spending', 0):.2f} ({opt_percentage}% optimized) **💡 Optimization Opportunities:** - ✅ You're using optimal cards {opt_percentage}% of the time - 🎯 Potential to earn ${potential_savings:.2f} more per year - ⚠️ {total_transactions - optimized_count} transactions could be optimized **🏆 Your Active Cards:** {chr(10).join([f'- {card}' for card in cards[:5]])} **📊 Year-to-Date:** - Total Rewards: ${total_rewards:.2f} - Average per transaction: ${total_rewards/total_transactions:.2f} - **Optimization Score: {optimization_score}/100** """ # Create forecast forecast_md = f""" ### 🔮 Next Month Forecast Based on your spending patterns: - **Predicted Spend:** ${total_spending * 1.05:.2f} - **Predicted Rewards:** ${total_rewards * 1.05:.2f} - **Optimization Target:** {min(optimization_score + 5, 100)}/100 **Recommendations:** 1. 💳 Continue using your top performing cards 2. 🎯 Focus on optimizing {total_transactions - optimized_count} remaining transactions 3. 📈 Potential monthly savings: ${potential_savings/12:.2f} """ return metrics_html, table_md, insights_md, forecast_md except Exception as e: print(f"Error formatting analytics: {e}") import traceback print(traceback.format_exc()) error_msg = "Error loading analytics data" return error_msg, error_msg, error_msg, error_msg def create_spending_chart(analytics_data: Dict) -> go.Figure: """Create spending vs rewards bar chart""" try: # DON'T unwrap - data is already unwrapped in app.py # Just use it directly category_breakdown = safe_get(analytics_data, 'category_breakdown', []) # DEBUG print(f"DEBUG create_spending_chart: Found {len(category_breakdown)} categories") if category_breakdown: print(f"First category: {category_breakdown[0]}") if not category_breakdown: fig = go.Figure() fig.add_annotation( text="No spending data available", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=14, color="#666") ) fig.update_layout(height=400, template='plotly_white') return fig categories = [item.get('category', 'Unknown') for item in category_breakdown] spending = [float(item.get('spending', 0)) for item in category_breakdown] rewards = [float(item.get('rewards', 0)) for item in category_breakdown] fig = go.Figure() # Add spending bars fig.add_trace(go.Bar( name='Spending', x=categories, y=spending, marker_color='#667eea', text=[f'${s:.0f}' for s in spending], textposition='outside', hovertemplate='%{x}
Spending: $%{y:.2f}' )) # Add rewards bars fig.add_trace(go.Bar( name='Rewards', x=categories, y=rewards, marker_color='#38ef7d', text=[f'${r:.2f}' for r in rewards], textposition='outside', hovertemplate='%{x}
Rewards: $%{y:.2f}' )) fig.update_layout( title={ 'text': 'Spending vs Rewards by Category', 'x': 0.5, 'xanchor': 'center' }, xaxis_title='Category', yaxis_title='Amount ($)', barmode='group', template='plotly_white', height=400, legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 ), margin=dict(t=80, b=50, l=50, r=50) ) return fig except Exception as e: print(f"Error creating spending chart: {e}") import traceback print(traceback.format_exc()) fig = go.Figure() fig.add_annotation( text="Error loading spending chart", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=14, color="red") ) fig.update_layout(height=400, template='plotly_white') return fig def create_rewards_pie_chart(analytics_data: Dict) -> go.Figure: """Create rewards distribution pie chart""" try: category_breakdown = safe_get(analytics_data, 'category_breakdown', []) if not category_breakdown: fig = go.Figure() fig.add_annotation( text="No rewards data available", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=14, color="#666") ) fig.update_layout(height=400, template='plotly_white') return fig categories = [item.get('category', 'Unknown') for item in category_breakdown] rewards = [float(item.get('rewards', 0)) for item in category_breakdown] colors = ['#667eea', '#38ef7d', '#f093fb', '#4facfe', '#feca57', '#ff6b6b'] fig = go.Figure(data=[go.Pie( labels=categories, values=rewards, hole=0.4, marker=dict(colors=colors[:len(categories)]), textinfo='label+percent', textposition='outside', hovertemplate='%{label}
Rewards: $%{value:.2f}
%{percent}' )]) fig.update_layout( title={ 'text': 'Rewards Distribution', 'x': 0.5, 'xanchor': 'center' }, template='plotly_white', height=400, showlegend=True, legend=dict( orientation="v", yanchor="middle", y=0.5, xanchor="left", x=1.1 ), margin=dict(t=60, b=30, l=30, r=150) ) return fig except Exception as e: print(f"Error creating pie chart: {e}") import traceback print(traceback.format_exc()) fig = go.Figure() fig.add_annotation( text="Error loading rewards chart", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=14, color="red") ) fig.update_layout(height=400, template='plotly_white') return fig def create_optimization_gauge(analytics_data: Dict) -> go.Figure: """Create optimization score gauge""" try: # Handle both dict with 'optimization_score' key and direct score value if isinstance(analytics_data, dict): score = int(safe_get(analytics_data, 'optimization_score', 75)) else: score = int(analytics_data) if analytics_data else 75 # Determine color based on score if score >= 80: color = "#38ef7d" # Green elif score >= 60: color = "#feca57" # Yellow else: color = "#ff6b6b" # Red fig = go.Figure(go.Indicator( mode="gauge+number+delta", value=score, domain={'x': [0, 1], 'y': [0, 1]}, title={'text': "Optimization Score", 'font': {'size': 20}}, delta={'reference': 75, 'increasing': {'color': "#38ef7d"}}, gauge={ 'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"}, 'bar': {'color': color}, 'bgcolor': "white", 'borderwidth': 2, 'bordercolor': "gray", 'steps': [ {'range': [0, 60], 'color': '#ffebee'}, {'range': [60, 80], 'color': '#fff9e6'}, {'range': [80, 100], 'color': '#e8f5e9'} ], 'threshold': { 'line': {'color': "red", 'width': 4}, 'thickness': 0.75, 'value': 90 } } )) fig.update_layout( template='plotly_white', height=400, margin=dict(t=50, b=30, l=30, r=30) ) return fig except Exception as e: print(f"Error creating gauge: {e}") import traceback print(traceback.format_exc()) fig = go.Figure() fig.add_annotation( text="Error loading optimization score", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=14, color="red") ) fig.update_layout(height=400, template='plotly_white') return fig def create_trend_line_chart(analytics_data: Dict) -> go.Figure: """Create monthly trend line chart""" try: monthly_trends = safe_get(analytics_data, 'monthly_trends', []) if not monthly_trends: fig = go.Figure() fig.add_annotation( text="No trend data available", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=14, color="#666") ) fig.update_layout(height=400, template='plotly_white') return fig months = [item.get('month', '') for item in monthly_trends] spending = [float(item.get('spending', 0)) for item in monthly_trends] rewards = [float(item.get('rewards', 0)) for item in monthly_trends] fig = go.Figure() # Add spending line fig.add_trace(go.Scatter( x=months, y=spending, name='Spending', mode='lines+markers', line=dict(color='#667eea', width=3), marker=dict(size=8), hovertemplate='%{x}
Spending: $%{y:.0f}' )) # Add rewards line fig.add_trace(go.Scatter( x=months, y=rewards, name='Rewards', mode='lines+markers', line=dict(color='#38ef7d', width=3), marker=dict(size=8), yaxis='y2', hovertemplate='%{x}
Rewards: $%{y:.2f}' )) fig.update_layout( title={ 'text': '12-Month Spending & Rewards Trends', 'x': 0.5, 'xanchor': 'center' }, xaxis_title='Month', yaxis=dict( title='Spending ($)', titlefont=dict(color='#667eea'), tickfont=dict(color='#667eea') ), yaxis2=dict( title='Rewards ($)', titlefont=dict(color='#38ef7d'), tickfont=dict(color='#38ef7d'), overlaying='y', side='right' ), template='plotly_white', height=400, legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 ), margin=dict(t=80, b=50, l=60, r=60), hovermode='x unified' ) return fig except Exception as e: print(f"Error creating trend chart: {e}") import traceback print(traceback.format_exc()) fig = go.Figure() fig.add_annotation( text="Error loading trend chart", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=14, color="red") ) fig.update_layout(height=400, template='plotly_white') return fig def create_card_performance_chart(analytics_data: Dict) -> go.Figure: """Create card performance horizontal bar chart""" try: card_performance = safe_get(analytics_data, 'card_performance', []) if not card_performance: fig = go.Figure() fig.add_annotation( text="No card performance data available", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=14, color="#666") ) fig.update_layout(height=400, template='plotly_white') return fig cards = [item.get('card', 'Unknown') for item in card_performance] rewards = [float(item.get('rewards', 0)) for item in card_performance] usage_pct = [float(item.get('usage_pct', 0)) for item in card_performance] colors = ['#667eea', '#38ef7d', '#f093fb'] fig = go.Figure() fig.add_trace(go.Bar( y=cards, x=rewards, orientation='h', marker=dict( color=colors[:len(cards)], line=dict(color='white', width=2) ), text=[f'${r:.2f} ({u}% usage)' for r, u in zip(rewards, usage_pct)], textposition='outside', hovertemplate='%{y}
Rewards: $%{x:.2f}' )) fig.update_layout( title={ 'text': 'Card Performance', 'x': 0.5, 'xanchor': 'center' }, xaxis_title='Rewards Earned ($)', yaxis_title='', template='plotly_white', height=400, showlegend=False, margin=dict(t=60, b=50, l=150, r=100) ) return fig except Exception as e: print(f"Error creating card performance chart: {e}") import traceback print(traceback.format_exc()) fig = go.Figure() fig.add_annotation( text="Error loading card performance", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False, font=dict(size=14, color="red") ) fig.update_layout(height=400, template='plotly_white') return fig