sammy786 commited on
Commit
b13c92f
·
verified ·
1 Parent(s): 809681b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -335
app.py CHANGED
@@ -1,11 +1,32 @@
1
- """
2
- Beautiful Gradio interface for credit card recommendations
3
- """
 
 
 
 
 
4
 
5
- # Add after imports, before client initialization
6
- from typing import Dict, Any
7
- import traceback
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
 
9
 
10
  def safe_get(data: Dict, key: str, default: Any = None) -> Any:
11
  """Safely get value from dictionary with fallback"""
@@ -14,16 +35,13 @@ def safe_get(data: Dict, key: str, default: Any = None) -> Any:
14
  except:
15
  return default
16
 
17
-
18
  def normalize_recommendation_data(data: Dict) -> Dict:
19
  """
20
  Normalize API response to ensure all required fields exist.
21
  Handles both orchestrator format and mock data format.
22
  """
23
 
24
- # Check if this is mock data (simpler format)
25
  if data.get('mock_data'):
26
- # Mock data has simpler structure
27
  return {
28
  'recommended_card': safe_get(data, 'recommended_card', 'Unknown Card'),
29
  'rewards_earned': float(safe_get(data, 'rewards_earned', 0)),
@@ -39,7 +57,6 @@ def normalize_recommendation_data(data: Dict) -> Dict:
39
  'mock_data': True
40
  }
41
 
42
- # Handle orchestrator format (recommended_card is a dict)
43
  recommended_card = safe_get(data, 'recommended_card', {})
44
  if isinstance(recommended_card, dict):
45
  card_name = safe_get(recommended_card, 'card_name', 'Unknown Card')
@@ -48,13 +65,11 @@ def normalize_recommendation_data(data: Dict) -> Dict:
48
  category = safe_get(recommended_card, 'category', 'Unknown')
49
  reasoning = safe_get(recommended_card, 'reasoning', 'Optimal choice')
50
 
51
- # Format reward rate as string
52
  if reward_rate > 0:
53
  rewards_rate_str = f"{reward_rate}x points"
54
  else:
55
  rewards_rate_str = "N/A"
56
  else:
57
- # Handle case where it's already a string
58
  card_name = str(recommended_card) if recommended_card else 'Unknown Card'
59
  reward_amount = float(safe_get(data, 'rewards_earned', 0))
60
  reward_rate = 0
@@ -62,18 +77,14 @@ def normalize_recommendation_data(data: Dict) -> Dict:
62
  category = safe_get(data, 'category', 'Unknown')
63
  reasoning = safe_get(data, 'reasoning', 'Optimal choice')
64
 
65
- # Get merchant and amount from top level
66
  merchant = safe_get(data, 'merchant', 'Unknown Merchant')
67
  amount = float(safe_get(data, 'amount_usd', safe_get(data, 'amount', 0)))
68
-
69
- # Calculate annual potential
70
  annual_potential = reward_amount * 12 if reward_amount > 0 else 0
71
 
72
- # Process alternative cards
73
  alternatives = []
74
  alt_cards = safe_get(data, 'alternative_cards', safe_get(data, 'alternatives', []))
75
 
76
- for alt in alt_cards[:3]: # Limit to top 3
77
  if isinstance(alt, dict):
78
  alt_name = safe_get(alt, 'card_name', safe_get(alt, 'card', 'Unknown'))
79
  alt_reward = float(safe_get(alt, 'reward_amount', safe_get(alt, 'rewards', 0)))
@@ -90,7 +101,6 @@ def normalize_recommendation_data(data: Dict) -> Dict:
90
  'rate': alt_rate_str
91
  })
92
 
93
- # Extract warnings
94
  warnings = safe_get(data, 'warnings', [])
95
  forecast_warning = safe_get(data, 'forecast_warning')
96
  if forecast_warning and isinstance(forecast_warning, dict):
@@ -98,7 +108,6 @@ def normalize_recommendation_data(data: Dict) -> Dict:
98
  if warning_msg:
99
  warnings.append(warning_msg)
100
 
101
- # Build normalized response
102
  normalized = {
103
  'recommended_card': card_name,
104
  'rewards_earned': round(reward_amount, 2),
@@ -120,39 +129,6 @@ def create_loading_state():
120
  """Create loading indicator message"""
121
  return "⏳ **Loading...** Please wait while we fetch your recommendation.", None
122
 
123
-
124
- from datetime import date
125
- from typing import Optional, Tuple, List, Dict, Any
126
- import gradio as gr
127
- from config import (
128
- APP_TITLE, APP_DESCRIPTION, THEME,
129
- MCC_CATEGORIES, SAMPLE_USERS,
130
- MERCHANTS_BY_CATEGORY
131
- )
132
- from utils.api_client import RewardPilotClient
133
- from utils.formatters import (
134
- format_full_recommendation,
135
- format_comparison_table,
136
- format_analytics_metrics,
137
- create_spending_chart,
138
- create_rewards_pie_chart,
139
- create_optimization_gauge,
140
- create_trend_line_chart,
141
- create_card_performance_chart
142
- )
143
- import plotly.graph_objects as go
144
- import gradio as gr
145
- from utils.api_client import RewardPilotClient
146
- from utils.llm_explainer import get_llm_explainer
147
- import config
148
-
149
- # ===================== CARD DATABASE LOADER =====================
150
- import json
151
- import os
152
-
153
- # Path to cards.json
154
- CARDS_FILE = os.path.join(os.path.dirname(__file__), "data", "cards.json")
155
-
156
  def load_card_database() -> dict:
157
  """Load card database from local cards.json"""
158
  try:
@@ -167,7 +143,6 @@ def load_card_database() -> dict:
167
  print(f"❌ Error parsing cards.json: {e}")
168
  return {}
169
 
170
- # Load at startup
171
  CARD_DATABASE = load_card_database()
172
 
173
  def get_card_details(card_id: str, mcc: str = None) -> dict:
@@ -193,17 +168,14 @@ def get_card_details(card_id: str, mcc: str = None) -> dict:
193
  }
194
 
195
  card = CARD_DATABASE[card_id]
196
-
197
- # Get reward rate for specific MCC (if provided)
198
  reward_rate = 1.0
 
199
  if mcc and "reward_structure" in card:
200
  reward_structure = card["reward_structure"]
201
 
202
- # Check exact MCC match
203
  if mcc in reward_structure:
204
  reward_rate = reward_structure[mcc]
205
  else:
206
- # Check MCC ranges (e.g., "3001-3999")
207
  try:
208
  mcc_int = int(mcc)
209
  for key, rate in reward_structure.items():
@@ -215,11 +187,9 @@ def get_card_details(card_id: str, mcc: str = None) -> dict:
215
  except (ValueError, AttributeError):
216
  pass
217
 
218
- # Use default if no match found
219
  if reward_rate == 1.0 and "default" in reward_structure:
220
  reward_rate = reward_structure["default"]
221
 
222
- # Extract spending cap info
223
  spending_caps = card.get("spending_caps", {})
224
  cap_info = {}
225
 
@@ -250,13 +220,8 @@ def get_card_details(card_id: str, mcc: str = None) -> dict:
250
  "spending_caps": cap_info,
251
  "benefits": card.get("benefits", [])
252
  }
253
- # ===================== END CARD DATABASE =====================
254
-
255
 
256
  def get_recommendation_with_agent(user_id, merchant, category, amount):
257
- import httpx
258
- import json
259
-
260
  yield "⏳ **Agent is thinking...** Analyzing your transaction and cards...", None
261
 
262
  try:
@@ -294,7 +259,6 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
294
 
295
  print(f"🔍 KEYS: {list(result.keys())}")
296
 
297
- # ========== EXTRACT BASIC DATA ==========
298
  card_id = result.get('recommended_card', 'Unknown')
299
  rewards_earned = float(result.get('rewards_earned', 0))
300
  rewards_rate = result.get('rewards_rate', 'N/A')
@@ -303,7 +267,6 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
303
  alternatives = result.get('alternative_options', [])
304
  warnings = result.get('warnings', [])
305
 
306
- # Map card_id to card_name
307
  card_name_map = {
308
  'c_citi_custom_cash': 'Citi Custom Cash',
309
  'c_amex_gold': 'American Express Gold',
@@ -316,67 +279,18 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
316
  }
317
  card_name = card_name_map.get(card_id, card_id.replace('c_', '').replace('_', ' ').title())
318
 
319
- # # ========== CARD DATABASE (FALLBACK) ==========
320
- # # If API doesn't provide card_details, use this database
321
- # CARD_DATABASE = {
322
- # 'c_citi_custom_cash': {
323
- # 'reward_rate': 5.0,
324
- # 'monthly_cap': 500,
325
- # 'base_rate': 1.0,
326
- # 'annual_fee': 0,
327
- # 'cap_type': 'monthly'
328
- # },
329
- # 'c_amex_gold': {
330
- # 'reward_rate': 4.0,
331
- # 'annual_cap': 25000,
332
- # 'base_rate': 1.0,
333
- # 'annual_fee': 250,
334
- # 'cap_type': 'annual'
335
- # },
336
- # 'c_chase_sapphire_reserve': {
337
- # 'reward_rate': 3.0,
338
- # 'monthly_cap': None,
339
- # 'base_rate': 3.0,
340
- # 'annual_fee': 550,
341
- # 'cap_type': 'none'
342
- # },
343
- # 'c_chase_freedom_unlimited': {
344
- # 'reward_rate': 1.5,
345
- # 'monthly_cap': None,
346
- # 'base_rate': 1.5,
347
- # 'annual_fee': 0,
348
- # 'cap_type': 'none'
349
- # },
350
- # 'c_chase_sapphire_preferred': {
351
- # 'reward_rate': 2.0,
352
- # 'monthly_cap': None,
353
- # 'base_rate': 2.0,
354
- # 'annual_fee': 95,
355
- # 'cap_type': 'none'
356
- # }
357
- # }
358
-
359
- # ========== GET CARD DETAILS (FROM cards.json) ==========
360
- # Get MCC from transaction
361
  transaction_mcc = result.get('mcc', MCC_CATEGORIES.get(category, "5999"))
362
-
363
- # Load card details from our database
364
  card_details_from_db = get_card_details(card_id, transaction_mcc)
365
-
366
- # Use API card_details if available, otherwise use our database
367
  card_details = result.get('card_details', {})
368
 
369
  if not card_details or not card_details.get('reward_rate'):
370
- # Convert our database format to the format expected by the rest of the code
371
  reward_structure = CARD_DATABASE.get(card_id, {}).get('reward_structure', {})
372
 
373
- # Get reward rate for this MCC
374
  if transaction_mcc in reward_structure:
375
  reward_rate_value = reward_structure[transaction_mcc]
376
  else:
377
  reward_rate_value = reward_structure.get('default', 1.0)
378
 
379
- # Get spending caps
380
  spending_caps_db = CARD_DATABASE.get(card_id, {}).get('spending_caps', {})
381
 
382
  card_details = {
@@ -400,17 +314,15 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
400
 
401
  print(f"✅ CARD DETAILS: {reward_rate_value}%, cap={monthly_cap or annual_cap}, fee=${annual_fee}")
402
 
403
- # ========== CALCULATE ANNUAL PROJECTION ==========
404
  amount_float = float(amount)
405
 
406
- # Frequency assumptions by category
407
  frequency_map = {
408
- 'Groceries': 52, # Weekly
409
  'Restaurants': 52,
410
  'Gas Stations': 52,
411
  'Fast Food': 52,
412
- 'Airlines': 4, # Quarterly
413
- 'Hotels': 12, # Monthly
414
  'Online Shopping': 24,
415
  'Entertainment': 24,
416
  }
@@ -425,9 +337,7 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
425
 
426
  annual_spend = amount_float * frequency
427
 
428
- # ========== TIERED CALCULATION ==========
429
  if monthly_cap:
430
- # Monthly cap logic (e.g., Citi Custom Cash)
431
  monthly_cap_annual = monthly_cap * 12
432
 
433
  if annual_spend <= monthly_cap_annual:
@@ -450,7 +360,6 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
450
  | **Net Rewards** | - | - | **${total_rewards - annual_fee:.2f}** |"""
451
 
452
  elif annual_cap:
453
- # Annual cap logic (e.g., Amex Gold)
454
  if annual_spend <= annual_cap:
455
  high_rate_spend = annual_spend
456
  low_rate_spend = 0
@@ -471,7 +380,6 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
471
  | **Net Rewards** | - | - | **${total_rewards - annual_fee:.2f}** |"""
472
 
473
  else:
474
- # No cap - flat rate
475
  total_rewards = annual_spend * (reward_rate_value / 100)
476
 
477
  calc_table = f"""| Spending Tier | Annual Amount | Rate | Rewards |
@@ -480,7 +388,6 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
480
  | Annual fee | - | - | -${annual_fee:.2f} |
481
  | **Net Rewards** | - | - | **${total_rewards - annual_fee:.2f}** |"""
482
 
483
- # ========== BASELINE COMPARISON ==========
484
  baseline_rewards = annual_spend * 0.01
485
  net_rewards = total_rewards - annual_fee
486
  net_benefit = net_rewards - baseline_rewards
@@ -495,25 +402,21 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
495
 
496
  **Net Benefit: ${net_benefit:+.2f}/year** {"🎉" if net_benefit > 0 else "⚠️"}"""
497
 
498
- # ========== OPTIMIZATION SCORE ==========
499
- # Calculate score based on performance
500
- max_possible_rewards = annual_spend * 0.06 # Theoretical max (6%)
501
 
502
  if max_possible_rewards > 0:
503
  performance_ratio = (net_rewards / max_possible_rewards) * 100
504
 
505
- # Bonus for beating baseline
506
  if net_rewards > baseline_rewards:
507
  improvement = (net_rewards - baseline_rewards) / baseline_rewards
508
  baseline_bonus = min(improvement * 20, 20)
509
  else:
510
- baseline_bonus = -10 # Penalty for underperforming
511
 
512
  optimization_score = int(min(performance_ratio + baseline_bonus, 100))
513
  else:
514
  optimization_score = 0
515
 
516
- # Score breakdown
517
  score_breakdown = {
518
  'reward_rate': min(30, int(optimization_score * 0.30)),
519
  'cap_availability': min(25, int(optimization_score * 0.25)),
@@ -538,7 +441,6 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
538
  - 60-69: Acceptable ⚠️
539
  - <60: Suboptimal ❌"""
540
 
541
- # ========== FORMAT OUTPUT ==========
542
  output = f"""## 🤖 AI Agent Recommendation
543
 
544
  ### 💳 Recommended Card: **{card_name}**
@@ -549,12 +451,10 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
549
  ---
550
 
551
  ### 🧠 Agent's Reasoning:
552
-
553
  {reasoning}
554
 
555
  ---"""
556
 
557
- # Add alternatives
558
  if alternatives:
559
  output += "\n### 🔄 Alternative Options:\n\n"
560
  for alt in alternatives[:3]:
@@ -563,16 +463,13 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
563
  alt_reason = alt.get('reason', '')
564
  output += f"**{alt_card_name}:**\n{alt_reason}\n\n"
565
 
566
- # Add warnings
567
  if warnings:
568
  output += "\n### ⚠️ Important Warnings:\n\n"
569
  for warning in warnings:
570
  output += f"- {warning}\n"
571
 
572
- # Add annual impact with expandable details
573
  output += f"""
574
  ### 💰 Annual Impact
575
-
576
  - **Potential Savings:** ${net_benefit:.2f}/year
577
  - **Optimization Score:** {optimization_score}/100
578
 
@@ -582,24 +479,20 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
582
  #### 💡 Calculation Assumptions:
583
 
584
  **Step 1: Estimate Annual Spending**
585
-
586
  Current transaction: ${amount_float:.2f} at {merchant}
587
  Category: {category}
588
  Frequency assumption: {frequency_label.capitalize()}
589
  Annual estimate: ${amount_float:.2f} × {frequency} = **${annual_spend:.2f}**
590
 
591
  **Step 2: Calculate Rewards with {card_name}**
592
-
593
  {calc_table}
594
 
595
  **Step 3: Compare to Baseline**
596
-
597
  {comparison_text}
598
 
599
  ---
600
 
601
  #### 📈 Optimization Score: {optimization_score}/100
602
-
603
  {score_details}
604
 
605
  ---
@@ -613,10 +506,9 @@ Annual estimate: ${amount_float:.2f} × {frequency} = **${annual_spend:.2f}**
613
 
614
  </details>
615
 
616
- ---"""
617
-
618
- # Add transaction details
619
- output += f"""### 📊 Transaction Details:
620
  - **Amount:** ${amount_float:.2f}
621
  - **Merchant:** {merchant}
622
  - **Category:** {category}
@@ -630,13 +522,11 @@ Annual estimate: ${amount_float:.2f} × {frequency} = **${annual_spend:.2f}**
630
  print("=" * 80)
631
 
632
  except Exception as e:
633
- import traceback
634
  print(f"❌ ERROR: {traceback.format_exc()}")
635
  yield f"❌ **Error:** {str(e)}", None
636
 
637
  def create_agent_recommendation_chart_enhanced(result: Dict) -> go.Figure:
638
  try:
639
- # ✅ FIX: Extract from top level
640
  rec_name_map = {
641
  'c_citi_custom_cash': 'Citi Custom Cash',
642
  'c_amex_gold': 'Amex Gold',
@@ -656,8 +546,7 @@ def create_agent_recommendation_chart_enhanced(result: Dict) -> go.Figure:
656
  for alt in alternatives[:3]:
657
  alt_id = alt.get('card', '')
658
  alt_name = rec_name_map.get(alt_id, alt_id)
659
- # Extract reward from reason text if possible
660
- alt_reward = rec_reward * 0.8 # Estimate
661
  cards.append(alt_name)
662
  rewards.append(alt_reward)
663
  colors.append('#cbd5e0')
@@ -689,13 +578,10 @@ def create_agent_recommendation_chart_enhanced(result: Dict) -> go.Figure:
689
  fig.add_annotation(text="Chart unavailable", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
690
  fig.update_layout(height=400, template='plotly_white')
691
  return fig
692
-
693
- # Initialize clients
694
  client = RewardPilotClient(config.ORCHESTRATOR_URL)
695
  llm = get_llm_explainer()
696
 
697
-
698
- # ===================== Main Recommendation Function =====================
699
  def get_recommendation(
700
  user_id: str,
701
  merchant: str,
@@ -706,7 +592,6 @@ def get_recommendation(
706
  transaction_date: Optional[str]
707
  ) -> tuple:
708
  """Get card recommendation and format response"""
709
- # Validate inputs
710
  if not user_id or not merchant or amount <= 0:
711
  return (
712
  "❌ **Error:** Please fill in all required fields.",
@@ -714,17 +599,14 @@ def get_recommendation(
714
  None,
715
  )
716
 
717
- # Determine MCC code
718
  if use_custom_mcc and custom_mcc:
719
  mcc = custom_mcc
720
  else:
721
  mcc = MCC_CATEGORIES.get(category, "5999")
722
 
723
- # Set default date if not provided
724
  if not transaction_date:
725
  transaction_date = str(date.today())
726
 
727
- # Call API
728
  response: Dict[str, Any] = client.get_recommendation_sync(
729
  user_id=user_id,
730
  merchant=merchant,
@@ -733,10 +615,8 @@ def get_recommendation(
733
  transaction_date=transaction_date,
734
  )
735
 
736
- # Format response
737
  formatted_text = format_full_recommendation(response)
738
 
739
- # Extract card details for comparison
740
  comparison_table: Optional[str]
741
  stats: Optional[str]
742
  if not response.get("error"):
@@ -745,7 +625,6 @@ def get_recommendation(
745
  all_cards = [c for c in ([recommended] + alternatives) if c]
746
  comparison_table = format_comparison_table(all_cards) if all_cards else None
747
 
748
- # Create summary stats
749
  total_analyzed = response.get("total_cards_analyzed", len(all_cards))
750
  best_reward = (recommended.get("reward_amount") or 0.0)
751
  services_used = response.get("services_used", [])
@@ -760,22 +639,18 @@ def get_recommendation(
760
 
761
  return formatted_text, comparison_table, stats
762
 
763
-
764
  def get_recommendation_with_ai(user_id, merchant, category, amount):
765
  """Get card recommendation with LLM-powered explanation"""
766
 
767
- # Validate inputs
768
  if not merchant or not merchant.strip():
769
  return "❌ Please enter a merchant name.", None
770
 
771
  if amount <= 0:
772
  return "❌ Please enter a valid amount greater than $0.", None
773
 
774
- # Show loading state
775
  yield "⏳ **Loading recommendation...** Analyzing your cards and transaction...", None
776
 
777
  try:
778
- # Get base recommendation from orchestrator
779
  result = client.get_recommendation(
780
  user_id=user_id,
781
  merchant=merchant,
@@ -784,16 +659,13 @@ def get_recommendation_with_ai(user_id, merchant, category, amount):
784
  mcc=None
785
  )
786
 
787
- # Check for errors
788
  if not result.get('success'):
789
  error_msg = result.get('error', 'Unknown error')
790
  yield f"❌ Error: {error_msg}", None
791
  return
792
 
793
- # Normalize the data
794
  data = normalize_recommendation_data(result.get('data', {}))
795
 
796
- # Generate LLM explanation if enabled
797
  ai_explanation = ""
798
  if config.LLM_ENABLED:
799
  try:
@@ -812,7 +684,6 @@ def get_recommendation_with_ai(user_id, merchant, category, amount):
812
  print(f"LLM explanation failed: {e}")
813
  ai_explanation = ""
814
 
815
- # Format output
816
  output = f"""
817
  ## 🎯 Recommendation for ${amount:.2f} at {merchant}
818
 
@@ -830,7 +701,6 @@ def get_recommendation_with_ai(user_id, merchant, category, amount):
830
  output += f"""
831
  ### 🤖 AI Insight
832
  {ai_explanation}
833
-
834
  ---
835
  """
836
 
@@ -853,13 +723,11 @@ def get_recommendation_with_ai(user_id, merchant, category, amount):
853
  for alt in data['alternatives']:
854
  output += f"- **{alt['card']}:** ${alt['rewards']:.2f} ({alt['rate']})\n"
855
 
856
- # Create visualization
857
  chart = create_rewards_comparison_chart(data)
858
 
859
  yield output, chart
860
 
861
  except Exception as e:
862
- import traceback
863
  error_details = traceback.format_exc()
864
  print(f"Recommendation error: {error_details}")
865
  yield f"❌ Error: {str(e)}\n\nPlease check your API connection or try again.", None
@@ -868,18 +736,15 @@ def create_rewards_comparison_chart(data: Dict) -> go.Figure:
868
  """Create rewards comparison chart with proper error handling"""
869
 
870
  try:
871
- # Prepare data for chart
872
  cards = [data['recommended_card']]
873
  rewards = [data['rewards_earned']]
874
- colors = ['#667eea'] # Primary color for recommended card
875
 
876
- # Add alternatives
877
  for alt in data.get('alternatives', [])[:3]:
878
  cards.append(alt['card'])
879
  rewards.append(float(alt['rewards']))
880
- colors.append('#a0aec0') # Gray for alternatives
881
 
882
- # Only create chart if we have valid data
883
  if not cards or all(r == 0 for r in rewards):
884
  fig = go.Figure()
885
  fig.add_annotation(
@@ -894,7 +759,6 @@ def create_rewards_comparison_chart(data: Dict) -> go.Figure:
894
  )
895
  return fig
896
 
897
- # Create bar chart
898
  fig = go.Figure(data=[
899
  go.Bar(
900
  x=cards,
@@ -928,10 +792,8 @@ def create_rewards_comparison_chart(data: Dict) -> go.Figure:
928
 
929
  except Exception as e:
930
  print(f"Chart creation error: {e}")
931
- import traceback
932
  print(traceback.format_exc())
933
 
934
- # Return empty chart with error message
935
  fig = go.Figure()
936
  fig.add_annotation(
937
  text=f"Error creating chart",
@@ -941,12 +803,11 @@ def create_rewards_comparison_chart(data: Dict) -> go.Figure:
941
  )
942
  fig.update_layout(height=400, template='plotly_white')
943
  return fig
944
-
945
  def get_analytics_with_insights(user_id):
946
  """Get analytics with LLM-generated insights"""
947
 
948
  try:
949
- # Get analytics data
950
  result = client.get_user_analytics(user_id)
951
 
952
  if not result.get('success'):
@@ -954,7 +815,6 @@ def get_analytics_with_insights(user_id):
954
 
955
  data = result['data']
956
 
957
- # Generate AI insights if enabled
958
  ai_insights = ""
959
  if config.LLM_ENABLED:
960
  try:
@@ -970,29 +830,23 @@ def get_analytics_with_insights(user_id):
970
  print(f"AI insights generation failed: {e}")
971
  ai_insights = ""
972
 
973
- # Format metrics
974
  metrics = f"""
975
  ## 📊 Your Rewards Analytics
976
 
977
  ### Key Metrics
978
-
979
  - **💰 Total Rewards:** ${data['total_rewards']:.2f}
980
  - **📈 Potential Savings:** ${data['potential_savings']:.2f}/year
981
  - **⭐ Optimization Score:** {data['optimization_score']}/100
982
  - **✅ Optimized Transactions:** {data.get('optimized_count', 0)}
983
  """
984
 
985
- # Add AI insights
986
  if ai_insights:
987
  metrics += f"""
988
  ### 🤖 Personalized Insights
989
-
990
  {ai_insights}
991
-
992
  ---
993
  """
994
 
995
- # Create charts (your existing chart code)
996
  spending_chart = create_spending_chart(data)
997
  rewards_chart = create_rewards_distribution_chart(data)
998
  optimization_chart = create_optimization_gauge(data['optimization_score'])
@@ -1002,7 +856,6 @@ def get_analytics_with_insights(user_id):
1002
  except Exception as e:
1003
  return f"❌ Error: {str(e)}", None, None, None
1004
 
1005
- # ===================== Sample Transaction Examples =====================
1006
  EXAMPLES = [
1007
  ["u_alice", "Groceries", "Whole Foods", 125.50, False, "", "2025-01-15"],
1008
  ["u_bob", "Restaurants", "Olive Garden", 65.75, False, "", "2025-01-15"],
@@ -1011,7 +864,6 @@ EXAMPLES = [
1011
  ["u_bob", "Gas Stations", "Shell", 45.00, False, "", ""],
1012
  ]
1013
 
1014
- # ===================== Build Gradio Interface =====================
1015
  def _toggle_custom_mcc(use_custom: bool):
1016
  return gr.update(visible=use_custom, value="")
1017
 
@@ -1026,7 +878,6 @@ with gr.Blocks(
1026
  font-size: 16px;
1027
  line-height: 1.6;
1028
  }
1029
- /* Metric Cards Styling */
1030
  .metric-card {
1031
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1032
  color: white;
@@ -1062,7 +913,6 @@ with gr.Blocks(
1062
  .metric-card-blue {
1063
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
1064
  }
1065
- /* Table styling */
1066
  table {
1067
  width: 100%;
1068
  border-collapse: collapse;
@@ -1091,7 +941,6 @@ with gr.Blocks(
1091
  }
1092
  """,
1093
  ) as app:
1094
- # Header
1095
  gr.Markdown(
1096
  f"""
1097
  # {APP_TITLE}
@@ -1105,7 +954,7 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1105
  ---
1106
  """
1107
  )
1108
- # Agent Status Indicator
1109
  agent_status = """
1110
  🤖 **Autonomous Agent:** ✅ Active (Claude 3.5 Sonnet)
1111
  📊 **Mode:** Dynamic Planning + Reasoning
@@ -1113,10 +962,7 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1113
  """
1114
  gr.Markdown(agent_status)
1115
 
1116
-
1117
- # Ensure all tabs are siblings at the same level
1118
  with gr.Tabs():
1119
- # ========== Tab 1: Get Recommendation ==========
1120
  with gr.Tab("🎯 Get Recommendation"):
1121
  with gr.Row():
1122
  with gr.Column(scale=1):
@@ -1128,7 +974,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1128
  info="Select a user"
1129
  )
1130
 
1131
- # CATEGORY FIRST (moved up)
1132
  category_dropdown = gr.Dropdown(
1133
  choices=list(MCC_CATEGORIES.keys()),
1134
  value="Groceries",
@@ -1136,13 +981,12 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1136
  info="Select the category first"
1137
  )
1138
 
1139
- # MERCHANT DROPDOWN (now dynamic)
1140
  merchant_dropdown = gr.Dropdown(
1141
  choices=MERCHANTS_BY_CATEGORY["Groceries"],
1142
  value="Whole Foods",
1143
  label="🏪 Merchant Name",
1144
  info="Select merchant (changes based on category)",
1145
- allow_custom_value=True # Allows typing custom merchants
1146
  )
1147
 
1148
  amount_input = gr.Number(
@@ -1158,7 +1002,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1158
  value=""
1159
  )
1160
 
1161
- # Advanced options
1162
  with gr.Accordion("⚙️ Advanced Options", open=False):
1163
  use_custom_mcc = gr.Checkbox(
1164
  label="Use Custom MCC Code",
@@ -1184,7 +1027,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1184
  variant="primary",
1185
  size="lg"
1186
  )
1187
-
1188
 
1189
  with gr.Column(scale=2):
1190
  gr.Markdown("### 💡 Recommendation")
@@ -1193,8 +1035,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1193
  elem_classes=["recommendation-output"]
1194
  )
1195
  recommendation_chart = gr.Plot()
1196
-
1197
-
1198
 
1199
  def update_merchant_choices(category):
1200
  """Update merchant dropdown based on selected category"""
@@ -1204,14 +1044,12 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1204
  value=merchants[0] if merchants else ""
1205
  )
1206
 
1207
- # Connect category change to merchant update
1208
  category_dropdown.change(
1209
  fn=update_merchant_choices,
1210
  inputs=[category_dropdown],
1211
  outputs=[merchant_dropdown]
1212
  )
1213
 
1214
- # Stats and comparison below
1215
  with gr.Row():
1216
  with gr.Column():
1217
  gr.Markdown("### 📊 Quick Stats")
@@ -1221,34 +1059,19 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1221
  gr.Markdown("### 🔄 Card Comparison")
1222
  comparison_output = gr.Markdown()
1223
 
1224
-
1225
  recommend_btn.click(
1226
- fn=get_recommendation_with_agent,
1227
- inputs=[user_dropdown, merchant_dropdown, category_dropdown, amount_input],
1228
- outputs=[recommendation_output, recommendation_chart]
1229
- )
1230
- # def get_recommendation_with_loading(user_id, merchant, category, amount):
1231
- # """Wrapper to show loading state"""
1232
- # # Show loading first
1233
- # yield "⏳ **Loading recommendation...** Please wait...", None
1234
-
1235
- # # Get actual result
1236
- # result = get_recommendation_with_ai(user_id, merchant, category, amount)
1237
- # yield result
1238
 
1239
- # recommend_btn.click(
1240
- # fn=get_recommendation_with_loading,
1241
- # inputs=[user_dropdown, merchant_dropdown, category_dropdown, amount_input],
1242
- # outputs=[recommendation_output, recommendation_chart]
1243
- # )
1244
- # Examples
1245
  gr.Markdown("### 📝 Example Transactions")
1246
  gr.Examples(
1247
  examples=EXAMPLES,
1248
  inputs=[
1249
  user_dropdown,
1250
- category_dropdown, # Swapped order
1251
- merchant_dropdown, # Swapped order
1252
  amount_input,
1253
  use_custom_mcc,
1254
  custom_mcc_input,
@@ -1263,11 +1086,9 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1263
  cache_examples=False
1264
  )
1265
 
1266
- # ========== Tab 2: Analytics with Charts (ENHANCED) ==========
1267
  with gr.Tab("📊 Analytics"):
1268
  gr.Markdown("## 🎯 Your Rewards Optimization Dashboard")
1269
 
1270
- # User selector for analytics
1271
  with gr.Row():
1272
  analytics_user = gr.Dropdown(
1273
  choices=SAMPLE_USERS,
@@ -1281,7 +1102,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1281
  scale=1
1282
  )
1283
 
1284
- # Top Metrics Row (Dynamic)
1285
  metrics_display = gr.HTML(
1286
  value="""
1287
  <div style="display: flex; gap: 10px; flex-wrap: wrap;">
@@ -1306,34 +1126,26 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1306
  )
1307
 
1308
  gr.Markdown("---")
1309
-
1310
- # ========== CHARTS SECTION (NEW) ==========
1311
  gr.Markdown("## 📊 Visual Analytics")
1312
 
1313
- # Row 1: Spending Chart + Optimization Gauge
1314
  with gr.Row():
1315
  with gr.Column(scale=2):
1316
  spending_chart = gr.Plot(label="Spending vs Rewards")
1317
  with gr.Column(scale=1):
1318
  optimization_gauge = gr.Plot(label="Your Score")
1319
 
1320
- # Row 2: Pie Chart + Card Performance
1321
  with gr.Row():
1322
  with gr.Column(scale=1):
1323
  rewards_pie_chart = gr.Plot(label="Rewards Distribution")
1324
  with gr.Column(scale=1):
1325
  card_performance_chart = gr.Plot(label="Top Performing Cards")
1326
 
1327
- # Row 3: Trend Line Chart (Full Width)
1328
  with gr.Row():
1329
  trend_chart = gr.Plot(label="12-Month Trends")
1330
 
1331
  gr.Markdown("---")
1332
-
1333
- # ========== DATA TABLES SECTION ==========
1334
  gr.Markdown("## 📋 Detailed Breakdown")
1335
 
1336
- # Detailed Analytics (Dynamic)
1337
  with gr.Row():
1338
  with gr.Column(scale=1):
1339
  gr.Markdown("### 💰 Category Spending Breakdown")
@@ -1377,7 +1189,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1377
 
1378
  gr.Markdown("---")
1379
 
1380
- # Spending Forecast (Dynamic)
1381
  forecast_display = gr.Markdown(
1382
  value="""
1383
  ### 🔮 Next Month Forecast
@@ -1394,21 +1205,17 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1394
  """
1395
  )
1396
 
1397
- # Status indicator
1398
  analytics_status = gr.Markdown(
1399
  value="*Analytics loaded for u_alice*",
1400
  elem_classes=["status-text"]
1401
  )
1402
 
1403
- # ===================== Analytics Update Function (ENHANCED) =====================
1404
  def update_analytics_with_charts(user_id: str):
1405
  """Fetch and format analytics with charts for selected user"""
1406
 
1407
  try:
1408
- # Fetch analytics data from API
1409
  result = client.get_user_analytics(user_id)
1410
 
1411
- # DEBUG: Print what we received
1412
  print("=" * 60)
1413
  print(f"DEBUG: Analytics for {user_id}")
1414
  print(f"Success: {result.get('success')}")
@@ -1418,7 +1225,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1418
  print(f"Total rewards: {result['data'].get('total_rewards')}")
1419
  print("=" * 60)
1420
 
1421
- # Check if request was successful
1422
  if not result.get('success'):
1423
  error_msg = result.get('error', 'Unknown error')
1424
  empty_fig = create_empty_chart(f"Error: {error_msg}")
@@ -1431,10 +1237,8 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1431
  f"*Error: {error_msg}*"
1432
  )
1433
 
1434
- # ✅ Extract the actual data
1435
  analytics_data = result.get('data', {})
1436
 
1437
- # Verify we have data
1438
  if not analytics_data:
1439
  empty_fig = create_empty_chart("No analytics data available")
1440
  return (
@@ -1446,51 +1250,36 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1446
  "*No data available*"
1447
  )
1448
 
1449
- # Import chart functions
1450
- from utils.formatters import (
1451
- format_analytics_metrics,
1452
- create_spending_chart,
1453
- create_rewards_pie_chart,
1454
- create_optimization_gauge,
1455
- create_trend_line_chart,
1456
- create_card_performance_chart
1457
- )
1458
-
1459
- # Format text data (pass unwrapped data)
1460
  metrics_html, table_md, insights_md, forecast_md = format_analytics_metrics(analytics_data)
1461
 
1462
- # Generate charts (pass unwrapped data)
1463
  spending_fig = create_spending_chart(analytics_data)
1464
  pie_fig = create_rewards_pie_chart(analytics_data)
1465
  gauge_fig = create_optimization_gauge(analytics_data)
1466
  trend_fig = create_trend_line_chart(analytics_data)
1467
  performance_fig = create_card_performance_chart(analytics_data)
1468
 
1469
- # Status message
1470
  from datetime import datetime
1471
  status = f"*Analytics updated for {user_id} at {datetime.now().strftime('%I:%M %p')}*"
1472
 
1473
  return (
1474
- metrics_html, # Metric cards
1475
- spending_fig, # Spending bar chart
1476
- gauge_fig, # Optimization gauge
1477
- pie_fig, # Rewards pie chart
1478
- performance_fig, # Card performance chart
1479
- trend_fig, # Trend line chart
1480
- table_md, # Spending table
1481
- insights_md, # Insights text
1482
- forecast_md, # Forecast text
1483
- status # Status message
1484
  )
1485
 
1486
  except Exception as e:
1487
- import traceback
1488
  error_details = traceback.format_exc()
1489
  error_msg = f"❌ Error loading analytics: {str(e)}"
1490
  print(error_msg)
1491
  print(error_details)
1492
 
1493
- # Return empty/error states
1494
  empty_fig = create_empty_chart("Error loading chart")
1495
 
1496
  return (
@@ -1501,7 +1290,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1501
  "Error loading forecast",
1502
  f"*{error_msg}*"
1503
  )
1504
-
1505
 
1506
  def create_empty_chart(message: str) -> go.Figure:
1507
  """Helper to create empty chart with message"""
@@ -1515,7 +1303,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1515
  fig.update_layout(height=400, template='plotly_white')
1516
  return fig
1517
 
1518
- # Connect analytics refresh to button and dropdown
1519
  refresh_analytics_btn.click(
1520
  fn=update_analytics_with_charts,
1521
  inputs=[analytics_user],
@@ -1549,8 +1336,7 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1549
  analytics_status
1550
  ]
1551
  )
1552
- # Tab 3: Chat (NEW!)
1553
- # Tab 3: Chat (NEW!)
1554
  with gr.Tab("💬 Ask AI"):
1555
  gr.Markdown("## Chat with RewardPilot AI")
1556
  gr.Markdown("*Ask questions about credit cards, rewards, and your spending*")
@@ -1576,7 +1362,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1576
  if not message.strip():
1577
  return "", chat_history
1578
 
1579
- # Get user context
1580
  user_context = {}
1581
  try:
1582
  analytics = client.get_user_analytics(user_id)
@@ -1595,7 +1380,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1595
  'top_category': 'Groceries'
1596
  }
1597
 
1598
- # Generate AI response
1599
  try:
1600
  if config.LLM_ENABLED:
1601
  bot_response = llm.chat_response(message, user_context, chat_history)
@@ -1611,7 +1395,6 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1611
  msg.submit(respond, [msg, chatbot, chat_user], [msg, chatbot])
1612
  send_btn.click(respond, [msg, chatbot, chat_user], [msg, chatbot])
1613
 
1614
- # ✅ RESTORED: Example questions
1615
  gr.Markdown("### 💡 Try asking:")
1616
  gr.Examples(
1617
  examples=[
@@ -1623,7 +1406,7 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1623
  ],
1624
  inputs=[msg]
1625
  )
1626
- # ========== Tab: Agent Insights (NEW) ==========
1627
  with gr.Tab("🤖 Agent Insights"):
1628
  gr.Markdown("""
1629
  ## How the Autonomous Agent Works
@@ -1697,8 +1480,7 @@ Get AI-powered credit card recommendations that maximize your rewards based on:
1697
 
1698
  **Try it out in the "Get Recommendation" tab!** 🚀
1699
  """)
1700
-
1701
- # ========== Tab 3: About ==========
1702
  with gr.Tab("ℹ️ About"):
1703
  gr.Markdown(
1704
  """
@@ -1714,7 +1496,6 @@ RewardPilot is an AI-powered credit card recommendation system built using the *
1714
  - 📊 **Interactive visualizations**
1715
 
1716
  ### Features
1717
-
1718
  - Smart card recommendations for every purchase
1719
  - AI-generated personalized insights
1720
  - Visual analytics dashboard
@@ -1722,22 +1503,19 @@ RewardPilot is an AI-powered credit card recommendation system built using the *
1722
  - Real-time cap warnings
1723
  - Multi-card comparison
1724
 
1725
- The system consists of multiple microservices:
1726
-
1727
  1. **Smart Wallet** - Analyzes transaction context and selects optimal cards
1728
  2. **Rewards-RAG** - Retrieves detailed card benefit information using RAG
1729
  3. **Spend-Forecast** - Predicts spending patterns and warns about cap risks
1730
  4. **Orchestrator** - Coordinates all services for comprehensive recommendations
1731
 
1732
  ### 🎯 How It Works
1733
-
1734
  1. **Enter Transaction Details** - Merchant, amount, category
1735
  2. **AI Analysis** - System analyzes your wallet and transaction context
1736
  3. **Get Recommendation** - Receive the best card with detailed reasoning
1737
  4. **Maximize Rewards** - Earn more points/cashback on every purchase
1738
 
1739
  ### 🔧 Technology Stack
1740
-
1741
  - **Backend:** FastAPI, Python
1742
  - **Frontend:** Gradio
1743
  - **AI/ML:** RAG (Retrieval-Augmented Generation)
@@ -1745,7 +1523,6 @@ The system consists of multiple microservices:
1745
  - **Deployment:** Hugging Face Spaces
1746
 
1747
  ### 📚 MCC Categories Supported
1748
-
1749
  - Groceries (5411)
1750
  - Restaurants (5812)
1751
  - Gas Stations (5541)
@@ -1755,11 +1532,9 @@ The system consists of multiple microservices:
1755
  - And many more...
1756
 
1757
  ### 🎓 Built For
1758
-
1759
  **MCP 1st Birthday Hackathon** - Celebrating one year of the Model Context Protocol
1760
 
1761
  ### 👨‍💻 Developer
1762
-
1763
  Built with ❤️ for the MCP community
1764
 
1765
  ---
@@ -1769,18 +1544,15 @@ Built with ❤️ for the MCP community
1769
  """
1770
  )
1771
 
1772
- # ========== Tab 4: API Documentation ==========
1773
  with gr.Tab("📖 API Docs"):
1774
  gr.Markdown(
1775
  """
1776
  ## API Endpoints
1777
 
1778
  ### Orchestrator API
1779
-
1780
  **Base URL:** `https://mcp-1st-birthday-rewardpilot-orchestrator.hf.space`
1781
 
1782
  #### POST `/recommend`
1783
-
1784
  Get comprehensive card recommendation.
1785
 
1786
  **Request:**
@@ -1792,44 +1564,11 @@ Get comprehensive card recommendation.
1792
  "amount_usd": 125.50,
1793
  "transaction_date": "2025-01-15"
1794
  }
1795
- ```
1796
-
1797
- **Response:**
1798
- ```json
1799
- {
1800
- "user_id": "u_alice",
1801
- "merchant": "Whole Foods",
1802
- "amount_usd": 125.5,
1803
- "recommended_card": {
1804
- "card_id": "c_amex_gold",
1805
- "card_name": "American Express Gold Card",
1806
- "reward_rate": 4.0,
1807
- "reward_amount": 502.0,
1808
- "category": "Groceries",
1809
- "reasoning": "Earns 4x points on Groceries"
1810
- },
1811
- "alternative_cards": ["..."],
1812
- "rag_insights": { "...": "..." },
1813
- "forecast_warning": { "...": "..." },
1814
- "services_used": ["smart_wallet", "rewards_rag", "spend_forecast"],
1815
- "final_recommendation": "..."
1816
- }
1817
- ```
1818
-
1819
- ### Other Services
1820
-
1821
- - Smart Wallet: https://mcp-1st-birthday-rewardpilot-smart-wallet.hf.space
1822
- - Rewards-RAG: https://mcp-1st-birthday-rewardpilot-rewards-rag.hf.space
1823
- - Spend-Forecast: https://mcp-1st-birthday-rewardpilot-spend-forecast.hf.space
1824
-
1825
- ### Interactive Docs
1826
-
1827
- Visit `/docs` on any service for interactive Swagger UI documentation.
1828
-
1829
- ### cURL Examples
1830
-
1831
- ```bash
1832
- # Get recommendation
1833
  curl -X POST https://mcp-1st-birthday-rewardpilot-orchestrator.hf.space/recommend \\
1834
  -H "Content-Type: application/json" \\
1835
  -d '{
@@ -1837,10 +1576,8 @@ curl -X POST https://mcp-1st-birthday-rewardpilot-orchestrator.hf.space/recommen
1837
  "merchant": "Whole Foods",
1838
  "mcc": "5411",
1839
  "amount_usd": 125.50
1840
- }'
1841
- ```
1842
- """
1843
- )
1844
 
1845
  # ===================== Launch App =====================
1846
  if __name__ == "__main__":
@@ -1848,4 +1585,4 @@ if __name__ == "__main__":
1848
  server_name="0.0.0.0",
1849
  server_port=7860,
1850
  share=False,
1851
- )
 
1
+ from typing import Dict, Any, Optional, Tuple, List
2
+ import traceback
3
+ import json
4
+ import os
5
+ from datetime import date
6
+ import gradio as gr
7
+ import plotly.graph_objects as go
8
+ import httpx
9
 
10
+ from config import (
11
+ APP_TITLE, APP_DESCRIPTION, THEME,
12
+ MCC_CATEGORIES, SAMPLE_USERS,
13
+ MERCHANTS_BY_CATEGORY
14
+ )
15
+ from utils.api_client import RewardPilotClient
16
+ from utils.formatters import (
17
+ format_full_recommendation,
18
+ format_comparison_table,
19
+ format_analytics_metrics,
20
+ create_spending_chart,
21
+ create_rewards_pie_chart,
22
+ create_optimization_gauge,
23
+ create_trend_line_chart,
24
+ create_card_performance_chart
25
+ )
26
+ from utils.llm_explainer import get_llm_explainer
27
+ import config
28
 
29
+ CARDS_FILE = os.path.join(os.path.dirname(__file__), "data", "cards.json")
30
 
31
  def safe_get(data: Dict, key: str, default: Any = None) -> Any:
32
  """Safely get value from dictionary with fallback"""
 
35
  except:
36
  return default
37
 
 
38
  def normalize_recommendation_data(data: Dict) -> Dict:
39
  """
40
  Normalize API response to ensure all required fields exist.
41
  Handles both orchestrator format and mock data format.
42
  """
43
 
 
44
  if data.get('mock_data'):
 
45
  return {
46
  'recommended_card': safe_get(data, 'recommended_card', 'Unknown Card'),
47
  'rewards_earned': float(safe_get(data, 'rewards_earned', 0)),
 
57
  'mock_data': True
58
  }
59
 
 
60
  recommended_card = safe_get(data, 'recommended_card', {})
61
  if isinstance(recommended_card, dict):
62
  card_name = safe_get(recommended_card, 'card_name', 'Unknown Card')
 
65
  category = safe_get(recommended_card, 'category', 'Unknown')
66
  reasoning = safe_get(recommended_card, 'reasoning', 'Optimal choice')
67
 
 
68
  if reward_rate > 0:
69
  rewards_rate_str = f"{reward_rate}x points"
70
  else:
71
  rewards_rate_str = "N/A"
72
  else:
 
73
  card_name = str(recommended_card) if recommended_card else 'Unknown Card'
74
  reward_amount = float(safe_get(data, 'rewards_earned', 0))
75
  reward_rate = 0
 
77
  category = safe_get(data, 'category', 'Unknown')
78
  reasoning = safe_get(data, 'reasoning', 'Optimal choice')
79
 
 
80
  merchant = safe_get(data, 'merchant', 'Unknown Merchant')
81
  amount = float(safe_get(data, 'amount_usd', safe_get(data, 'amount', 0)))
 
 
82
  annual_potential = reward_amount * 12 if reward_amount > 0 else 0
83
 
 
84
  alternatives = []
85
  alt_cards = safe_get(data, 'alternative_cards', safe_get(data, 'alternatives', []))
86
 
87
+ for alt in alt_cards[:3]:
88
  if isinstance(alt, dict):
89
  alt_name = safe_get(alt, 'card_name', safe_get(alt, 'card', 'Unknown'))
90
  alt_reward = float(safe_get(alt, 'reward_amount', safe_get(alt, 'rewards', 0)))
 
101
  'rate': alt_rate_str
102
  })
103
 
 
104
  warnings = safe_get(data, 'warnings', [])
105
  forecast_warning = safe_get(data, 'forecast_warning')
106
  if forecast_warning and isinstance(forecast_warning, dict):
 
108
  if warning_msg:
109
  warnings.append(warning_msg)
110
 
 
111
  normalized = {
112
  'recommended_card': card_name,
113
  'rewards_earned': round(reward_amount, 2),
 
129
  """Create loading indicator message"""
130
  return "⏳ **Loading...** Please wait while we fetch your recommendation.", None
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  def load_card_database() -> dict:
133
  """Load card database from local cards.json"""
134
  try:
 
143
  print(f"❌ Error parsing cards.json: {e}")
144
  return {}
145
 
 
146
  CARD_DATABASE = load_card_database()
147
 
148
  def get_card_details(card_id: str, mcc: str = None) -> dict:
 
168
  }
169
 
170
  card = CARD_DATABASE[card_id]
 
 
171
  reward_rate = 1.0
172
+
173
  if mcc and "reward_structure" in card:
174
  reward_structure = card["reward_structure"]
175
 
 
176
  if mcc in reward_structure:
177
  reward_rate = reward_structure[mcc]
178
  else:
 
179
  try:
180
  mcc_int = int(mcc)
181
  for key, rate in reward_structure.items():
 
187
  except (ValueError, AttributeError):
188
  pass
189
 
 
190
  if reward_rate == 1.0 and "default" in reward_structure:
191
  reward_rate = reward_structure["default"]
192
 
 
193
  spending_caps = card.get("spending_caps", {})
194
  cap_info = {}
195
 
 
220
  "spending_caps": cap_info,
221
  "benefits": card.get("benefits", [])
222
  }
 
 
223
 
224
  def get_recommendation_with_agent(user_id, merchant, category, amount):
 
 
 
225
  yield "⏳ **Agent is thinking...** Analyzing your transaction and cards...", None
226
 
227
  try:
 
259
 
260
  print(f"🔍 KEYS: {list(result.keys())}")
261
 
 
262
  card_id = result.get('recommended_card', 'Unknown')
263
  rewards_earned = float(result.get('rewards_earned', 0))
264
  rewards_rate = result.get('rewards_rate', 'N/A')
 
267
  alternatives = result.get('alternative_options', [])
268
  warnings = result.get('warnings', [])
269
 
 
270
  card_name_map = {
271
  'c_citi_custom_cash': 'Citi Custom Cash',
272
  'c_amex_gold': 'American Express Gold',
 
279
  }
280
  card_name = card_name_map.get(card_id, card_id.replace('c_', '').replace('_', ' ').title())
281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  transaction_mcc = result.get('mcc', MCC_CATEGORIES.get(category, "5999"))
 
 
283
  card_details_from_db = get_card_details(card_id, transaction_mcc)
 
 
284
  card_details = result.get('card_details', {})
285
 
286
  if not card_details or not card_details.get('reward_rate'):
 
287
  reward_structure = CARD_DATABASE.get(card_id, {}).get('reward_structure', {})
288
 
 
289
  if transaction_mcc in reward_structure:
290
  reward_rate_value = reward_structure[transaction_mcc]
291
  else:
292
  reward_rate_value = reward_structure.get('default', 1.0)
293
 
 
294
  spending_caps_db = CARD_DATABASE.get(card_id, {}).get('spending_caps', {})
295
 
296
  card_details = {
 
314
 
315
  print(f"✅ CARD DETAILS: {reward_rate_value}%, cap={monthly_cap or annual_cap}, fee=${annual_fee}")
316
 
 
317
  amount_float = float(amount)
318
 
 
319
  frequency_map = {
320
+ 'Groceries': 52,
321
  'Restaurants': 52,
322
  'Gas Stations': 52,
323
  'Fast Food': 52,
324
+ 'Airlines': 4,
325
+ 'Hotels': 12,
326
  'Online Shopping': 24,
327
  'Entertainment': 24,
328
  }
 
337
 
338
  annual_spend = amount_float * frequency
339
 
 
340
  if monthly_cap:
 
341
  monthly_cap_annual = monthly_cap * 12
342
 
343
  if annual_spend <= monthly_cap_annual:
 
360
  | **Net Rewards** | - | - | **${total_rewards - annual_fee:.2f}** |"""
361
 
362
  elif annual_cap:
 
363
  if annual_spend <= annual_cap:
364
  high_rate_spend = annual_spend
365
  low_rate_spend = 0
 
380
  | **Net Rewards** | - | - | **${total_rewards - annual_fee:.2f}** |"""
381
 
382
  else:
 
383
  total_rewards = annual_spend * (reward_rate_value / 100)
384
 
385
  calc_table = f"""| Spending Tier | Annual Amount | Rate | Rewards |
 
388
  | Annual fee | - | - | -${annual_fee:.2f} |
389
  | **Net Rewards** | - | - | **${total_rewards - annual_fee:.2f}** |"""
390
 
 
391
  baseline_rewards = annual_spend * 0.01
392
  net_rewards = total_rewards - annual_fee
393
  net_benefit = net_rewards - baseline_rewards
 
402
 
403
  **Net Benefit: ${net_benefit:+.2f}/year** {"🎉" if net_benefit > 0 else "⚠️"}"""
404
 
405
+ max_possible_rewards = annual_spend * 0.06
 
 
406
 
407
  if max_possible_rewards > 0:
408
  performance_ratio = (net_rewards / max_possible_rewards) * 100
409
 
 
410
  if net_rewards > baseline_rewards:
411
  improvement = (net_rewards - baseline_rewards) / baseline_rewards
412
  baseline_bonus = min(improvement * 20, 20)
413
  else:
414
+ baseline_bonus = -10
415
 
416
  optimization_score = int(min(performance_ratio + baseline_bonus, 100))
417
  else:
418
  optimization_score = 0
419
 
 
420
  score_breakdown = {
421
  'reward_rate': min(30, int(optimization_score * 0.30)),
422
  'cap_availability': min(25, int(optimization_score * 0.25)),
 
441
  - 60-69: Acceptable ⚠️
442
  - <60: Suboptimal ❌"""
443
 
 
444
  output = f"""## 🤖 AI Agent Recommendation
445
 
446
  ### 💳 Recommended Card: **{card_name}**
 
451
  ---
452
 
453
  ### 🧠 Agent's Reasoning:
 
454
  {reasoning}
455
 
456
  ---"""
457
 
 
458
  if alternatives:
459
  output += "\n### 🔄 Alternative Options:\n\n"
460
  for alt in alternatives[:3]:
 
463
  alt_reason = alt.get('reason', '')
464
  output += f"**{alt_card_name}:**\n{alt_reason}\n\n"
465
 
 
466
  if warnings:
467
  output += "\n### ⚠️ Important Warnings:\n\n"
468
  for warning in warnings:
469
  output += f"- {warning}\n"
470
 
 
471
  output += f"""
472
  ### 💰 Annual Impact
 
473
  - **Potential Savings:** ${net_benefit:.2f}/year
474
  - **Optimization Score:** {optimization_score}/100
475
 
 
479
  #### 💡 Calculation Assumptions:
480
 
481
  **Step 1: Estimate Annual Spending**
 
482
  Current transaction: ${amount_float:.2f} at {merchant}
483
  Category: {category}
484
  Frequency assumption: {frequency_label.capitalize()}
485
  Annual estimate: ${amount_float:.2f} × {frequency} = **${annual_spend:.2f}**
486
 
487
  **Step 2: Calculate Rewards with {card_name}**
 
488
  {calc_table}
489
 
490
  **Step 3: Compare to Baseline**
 
491
  {comparison_text}
492
 
493
  ---
494
 
495
  #### 📈 Optimization Score: {optimization_score}/100
 
496
  {score_details}
497
 
498
  ---
 
506
 
507
  </details>
508
 
509
+ ---
510
+
511
+ #### 📊 Transaction Details:
 
512
  - **Amount:** ${amount_float:.2f}
513
  - **Merchant:** {merchant}
514
  - **Category:** {category}
 
522
  print("=" * 80)
523
 
524
  except Exception as e:
 
525
  print(f"❌ ERROR: {traceback.format_exc()}")
526
  yield f"❌ **Error:** {str(e)}", None
527
 
528
  def create_agent_recommendation_chart_enhanced(result: Dict) -> go.Figure:
529
  try:
 
530
  rec_name_map = {
531
  'c_citi_custom_cash': 'Citi Custom Cash',
532
  'c_amex_gold': 'Amex Gold',
 
546
  for alt in alternatives[:3]:
547
  alt_id = alt.get('card', '')
548
  alt_name = rec_name_map.get(alt_id, alt_id)
549
+ alt_reward = rec_reward * 0.8
 
550
  cards.append(alt_name)
551
  rewards.append(alt_reward)
552
  colors.append('#cbd5e0')
 
578
  fig.add_annotation(text="Chart unavailable", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
579
  fig.update_layout(height=400, template='plotly_white')
580
  return fig
581
+
 
582
  client = RewardPilotClient(config.ORCHESTRATOR_URL)
583
  llm = get_llm_explainer()
584
 
 
 
585
  def get_recommendation(
586
  user_id: str,
587
  merchant: str,
 
592
  transaction_date: Optional[str]
593
  ) -> tuple:
594
  """Get card recommendation and format response"""
 
595
  if not user_id or not merchant or amount <= 0:
596
  return (
597
  "❌ **Error:** Please fill in all required fields.",
 
599
  None,
600
  )
601
 
 
602
  if use_custom_mcc and custom_mcc:
603
  mcc = custom_mcc
604
  else:
605
  mcc = MCC_CATEGORIES.get(category, "5999")
606
 
 
607
  if not transaction_date:
608
  transaction_date = str(date.today())
609
 
 
610
  response: Dict[str, Any] = client.get_recommendation_sync(
611
  user_id=user_id,
612
  merchant=merchant,
 
615
  transaction_date=transaction_date,
616
  )
617
 
 
618
  formatted_text = format_full_recommendation(response)
619
 
 
620
  comparison_table: Optional[str]
621
  stats: Optional[str]
622
  if not response.get("error"):
 
625
  all_cards = [c for c in ([recommended] + alternatives) if c]
626
  comparison_table = format_comparison_table(all_cards) if all_cards else None
627
 
 
628
  total_analyzed = response.get("total_cards_analyzed", len(all_cards))
629
  best_reward = (recommended.get("reward_amount") or 0.0)
630
  services_used = response.get("services_used", [])
 
639
 
640
  return formatted_text, comparison_table, stats
641
 
 
642
  def get_recommendation_with_ai(user_id, merchant, category, amount):
643
  """Get card recommendation with LLM-powered explanation"""
644
 
 
645
  if not merchant or not merchant.strip():
646
  return "❌ Please enter a merchant name.", None
647
 
648
  if amount <= 0:
649
  return "❌ Please enter a valid amount greater than $0.", None
650
 
 
651
  yield "⏳ **Loading recommendation...** Analyzing your cards and transaction...", None
652
 
653
  try:
 
654
  result = client.get_recommendation(
655
  user_id=user_id,
656
  merchant=merchant,
 
659
  mcc=None
660
  )
661
 
 
662
  if not result.get('success'):
663
  error_msg = result.get('error', 'Unknown error')
664
  yield f"❌ Error: {error_msg}", None
665
  return
666
 
 
667
  data = normalize_recommendation_data(result.get('data', {}))
668
 
 
669
  ai_explanation = ""
670
  if config.LLM_ENABLED:
671
  try:
 
684
  print(f"LLM explanation failed: {e}")
685
  ai_explanation = ""
686
 
 
687
  output = f"""
688
  ## 🎯 Recommendation for ${amount:.2f} at {merchant}
689
 
 
701
  output += f"""
702
  ### 🤖 AI Insight
703
  {ai_explanation}
 
704
  ---
705
  """
706
 
 
723
  for alt in data['alternatives']:
724
  output += f"- **{alt['card']}:** ${alt['rewards']:.2f} ({alt['rate']})\n"
725
 
 
726
  chart = create_rewards_comparison_chart(data)
727
 
728
  yield output, chart
729
 
730
  except Exception as e:
 
731
  error_details = traceback.format_exc()
732
  print(f"Recommendation error: {error_details}")
733
  yield f"❌ Error: {str(e)}\n\nPlease check your API connection or try again.", None
 
736
  """Create rewards comparison chart with proper error handling"""
737
 
738
  try:
 
739
  cards = [data['recommended_card']]
740
  rewards = [data['rewards_earned']]
741
+ colors = ['#667eea']
742
 
 
743
  for alt in data.get('alternatives', [])[:3]:
744
  cards.append(alt['card'])
745
  rewards.append(float(alt['rewards']))
746
+ colors.append('#a0aec0')
747
 
 
748
  if not cards or all(r == 0 for r in rewards):
749
  fig = go.Figure()
750
  fig.add_annotation(
 
759
  )
760
  return fig
761
 
 
762
  fig = go.Figure(data=[
763
  go.Bar(
764
  x=cards,
 
792
 
793
  except Exception as e:
794
  print(f"Chart creation error: {e}")
 
795
  print(traceback.format_exc())
796
 
 
797
  fig = go.Figure()
798
  fig.add_annotation(
799
  text=f"Error creating chart",
 
803
  )
804
  fig.update_layout(height=400, template='plotly_white')
805
  return fig
806
+
807
  def get_analytics_with_insights(user_id):
808
  """Get analytics with LLM-generated insights"""
809
 
810
  try:
 
811
  result = client.get_user_analytics(user_id)
812
 
813
  if not result.get('success'):
 
815
 
816
  data = result['data']
817
 
 
818
  ai_insights = ""
819
  if config.LLM_ENABLED:
820
  try:
 
830
  print(f"AI insights generation failed: {e}")
831
  ai_insights = ""
832
 
 
833
  metrics = f"""
834
  ## 📊 Your Rewards Analytics
835
 
836
  ### Key Metrics
 
837
  - **💰 Total Rewards:** ${data['total_rewards']:.2f}
838
  - **📈 Potential Savings:** ${data['potential_savings']:.2f}/year
839
  - **⭐ Optimization Score:** {data['optimization_score']}/100
840
  - **✅ Optimized Transactions:** {data.get('optimized_count', 0)}
841
  """
842
 
 
843
  if ai_insights:
844
  metrics += f"""
845
  ### 🤖 Personalized Insights
 
846
  {ai_insights}
 
847
  ---
848
  """
849
 
 
850
  spending_chart = create_spending_chart(data)
851
  rewards_chart = create_rewards_distribution_chart(data)
852
  optimization_chart = create_optimization_gauge(data['optimization_score'])
 
856
  except Exception as e:
857
  return f"❌ Error: {str(e)}", None, None, None
858
 
 
859
  EXAMPLES = [
860
  ["u_alice", "Groceries", "Whole Foods", 125.50, False, "", "2025-01-15"],
861
  ["u_bob", "Restaurants", "Olive Garden", 65.75, False, "", "2025-01-15"],
 
864
  ["u_bob", "Gas Stations", "Shell", 45.00, False, "", ""],
865
  ]
866
 
 
867
  def _toggle_custom_mcc(use_custom: bool):
868
  return gr.update(visible=use_custom, value="")
869
 
 
878
  font-size: 16px;
879
  line-height: 1.6;
880
  }
 
881
  .metric-card {
882
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
883
  color: white;
 
913
  .metric-card-blue {
914
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
915
  }
 
916
  table {
917
  width: 100%;
918
  border-collapse: collapse;
 
941
  }
942
  """,
943
  ) as app:
 
944
  gr.Markdown(
945
  f"""
946
  # {APP_TITLE}
 
954
  ---
955
  """
956
  )
957
+
958
  agent_status = """
959
  🤖 **Autonomous Agent:** ✅ Active (Claude 3.5 Sonnet)
960
  📊 **Mode:** Dynamic Planning + Reasoning
 
962
  """
963
  gr.Markdown(agent_status)
964
 
 
 
965
  with gr.Tabs():
 
966
  with gr.Tab("🎯 Get Recommendation"):
967
  with gr.Row():
968
  with gr.Column(scale=1):
 
974
  info="Select a user"
975
  )
976
 
 
977
  category_dropdown = gr.Dropdown(
978
  choices=list(MCC_CATEGORIES.keys()),
979
  value="Groceries",
 
981
  info="Select the category first"
982
  )
983
 
 
984
  merchant_dropdown = gr.Dropdown(
985
  choices=MERCHANTS_BY_CATEGORY["Groceries"],
986
  value="Whole Foods",
987
  label="🏪 Merchant Name",
988
  info="Select merchant (changes based on category)",
989
+ allow_custom_value=True
990
  )
991
 
992
  amount_input = gr.Number(
 
1002
  value=""
1003
  )
1004
 
 
1005
  with gr.Accordion("⚙️ Advanced Options", open=False):
1006
  use_custom_mcc = gr.Checkbox(
1007
  label="Use Custom MCC Code",
 
1027
  variant="primary",
1028
  size="lg"
1029
  )
 
1030
 
1031
  with gr.Column(scale=2):
1032
  gr.Markdown("### 💡 Recommendation")
 
1035
  elem_classes=["recommendation-output"]
1036
  )
1037
  recommendation_chart = gr.Plot()
 
 
1038
 
1039
  def update_merchant_choices(category):
1040
  """Update merchant dropdown based on selected category"""
 
1044
  value=merchants[0] if merchants else ""
1045
  )
1046
 
 
1047
  category_dropdown.change(
1048
  fn=update_merchant_choices,
1049
  inputs=[category_dropdown],
1050
  outputs=[merchant_dropdown]
1051
  )
1052
 
 
1053
  with gr.Row():
1054
  with gr.Column():
1055
  gr.Markdown("### 📊 Quick Stats")
 
1059
  gr.Markdown("### 🔄 Card Comparison")
1060
  comparison_output = gr.Markdown()
1061
 
 
1062
  recommend_btn.click(
1063
+ fn=get_recommendation_with_agent,
1064
+ inputs=[user_dropdown, merchant_dropdown, category_dropdown, amount_input],
1065
+ outputs=[recommendation_output, recommendation_chart]
1066
+ )
 
 
 
 
 
 
 
 
1067
 
 
 
 
 
 
 
1068
  gr.Markdown("### 📝 Example Transactions")
1069
  gr.Examples(
1070
  examples=EXAMPLES,
1071
  inputs=[
1072
  user_dropdown,
1073
+ category_dropdown,
1074
+ merchant_dropdown,
1075
  amount_input,
1076
  use_custom_mcc,
1077
  custom_mcc_input,
 
1086
  cache_examples=False
1087
  )
1088
 
 
1089
  with gr.Tab("📊 Analytics"):
1090
  gr.Markdown("## 🎯 Your Rewards Optimization Dashboard")
1091
 
 
1092
  with gr.Row():
1093
  analytics_user = gr.Dropdown(
1094
  choices=SAMPLE_USERS,
 
1102
  scale=1
1103
  )
1104
 
 
1105
  metrics_display = gr.HTML(
1106
  value="""
1107
  <div style="display: flex; gap: 10px; flex-wrap: wrap;">
 
1126
  )
1127
 
1128
  gr.Markdown("---")
 
 
1129
  gr.Markdown("## 📊 Visual Analytics")
1130
 
 
1131
  with gr.Row():
1132
  with gr.Column(scale=2):
1133
  spending_chart = gr.Plot(label="Spending vs Rewards")
1134
  with gr.Column(scale=1):
1135
  optimization_gauge = gr.Plot(label="Your Score")
1136
 
 
1137
  with gr.Row():
1138
  with gr.Column(scale=1):
1139
  rewards_pie_chart = gr.Plot(label="Rewards Distribution")
1140
  with gr.Column(scale=1):
1141
  card_performance_chart = gr.Plot(label="Top Performing Cards")
1142
 
 
1143
  with gr.Row():
1144
  trend_chart = gr.Plot(label="12-Month Trends")
1145
 
1146
  gr.Markdown("---")
 
 
1147
  gr.Markdown("## 📋 Detailed Breakdown")
1148
 
 
1149
  with gr.Row():
1150
  with gr.Column(scale=1):
1151
  gr.Markdown("### 💰 Category Spending Breakdown")
 
1189
 
1190
  gr.Markdown("---")
1191
 
 
1192
  forecast_display = gr.Markdown(
1193
  value="""
1194
  ### 🔮 Next Month Forecast
 
1205
  """
1206
  )
1207
 
 
1208
  analytics_status = gr.Markdown(
1209
  value="*Analytics loaded for u_alice*",
1210
  elem_classes=["status-text"]
1211
  )
1212
 
 
1213
  def update_analytics_with_charts(user_id: str):
1214
  """Fetch and format analytics with charts for selected user"""
1215
 
1216
  try:
 
1217
  result = client.get_user_analytics(user_id)
1218
 
 
1219
  print("=" * 60)
1220
  print(f"DEBUG: Analytics for {user_id}")
1221
  print(f"Success: {result.get('success')}")
 
1225
  print(f"Total rewards: {result['data'].get('total_rewards')}")
1226
  print("=" * 60)
1227
 
 
1228
  if not result.get('success'):
1229
  error_msg = result.get('error', 'Unknown error')
1230
  empty_fig = create_empty_chart(f"Error: {error_msg}")
 
1237
  f"*Error: {error_msg}*"
1238
  )
1239
 
 
1240
  analytics_data = result.get('data', {})
1241
 
 
1242
  if not analytics_data:
1243
  empty_fig = create_empty_chart("No analytics data available")
1244
  return (
 
1250
  "*No data available*"
1251
  )
1252
 
 
 
 
 
 
 
 
 
 
 
 
1253
  metrics_html, table_md, insights_md, forecast_md = format_analytics_metrics(analytics_data)
1254
 
 
1255
  spending_fig = create_spending_chart(analytics_data)
1256
  pie_fig = create_rewards_pie_chart(analytics_data)
1257
  gauge_fig = create_optimization_gauge(analytics_data)
1258
  trend_fig = create_trend_line_chart(analytics_data)
1259
  performance_fig = create_card_performance_chart(analytics_data)
1260
 
 
1261
  from datetime import datetime
1262
  status = f"*Analytics updated for {user_id} at {datetime.now().strftime('%I:%M %p')}*"
1263
 
1264
  return (
1265
+ metrics_html,
1266
+ spending_fig,
1267
+ gauge_fig,
1268
+ pie_fig,
1269
+ performance_fig,
1270
+ trend_fig,
1271
+ table_md,
1272
+ insights_md,
1273
+ forecast_md,
1274
+ status
1275
  )
1276
 
1277
  except Exception as e:
 
1278
  error_details = traceback.format_exc()
1279
  error_msg = f"❌ Error loading analytics: {str(e)}"
1280
  print(error_msg)
1281
  print(error_details)
1282
 
 
1283
  empty_fig = create_empty_chart("Error loading chart")
1284
 
1285
  return (
 
1290
  "Error loading forecast",
1291
  f"*{error_msg}*"
1292
  )
 
1293
 
1294
  def create_empty_chart(message: str) -> go.Figure:
1295
  """Helper to create empty chart with message"""
 
1303
  fig.update_layout(height=400, template='plotly_white')
1304
  return fig
1305
 
 
1306
  refresh_analytics_btn.click(
1307
  fn=update_analytics_with_charts,
1308
  inputs=[analytics_user],
 
1336
  analytics_status
1337
  ]
1338
  )
1339
+
 
1340
  with gr.Tab("💬 Ask AI"):
1341
  gr.Markdown("## Chat with RewardPilot AI")
1342
  gr.Markdown("*Ask questions about credit cards, rewards, and your spending*")
 
1362
  if not message.strip():
1363
  return "", chat_history
1364
 
 
1365
  user_context = {}
1366
  try:
1367
  analytics = client.get_user_analytics(user_id)
 
1380
  'top_category': 'Groceries'
1381
  }
1382
 
 
1383
  try:
1384
  if config.LLM_ENABLED:
1385
  bot_response = llm.chat_response(message, user_context, chat_history)
 
1395
  msg.submit(respond, [msg, chatbot, chat_user], [msg, chatbot])
1396
  send_btn.click(respond, [msg, chatbot, chat_user], [msg, chatbot])
1397
 
 
1398
  gr.Markdown("### 💡 Try asking:")
1399
  gr.Examples(
1400
  examples=[
 
1406
  ],
1407
  inputs=[msg]
1408
  )
1409
+
1410
  with gr.Tab("🤖 Agent Insights"):
1411
  gr.Markdown("""
1412
  ## How the Autonomous Agent Works
 
1480
 
1481
  **Try it out in the "Get Recommendation" tab!** 🚀
1482
  """)
1483
+
 
1484
  with gr.Tab("ℹ️ About"):
1485
  gr.Markdown(
1486
  """
 
1496
  - 📊 **Interactive visualizations**
1497
 
1498
  ### Features
 
1499
  - Smart card recommendations for every purchase
1500
  - AI-generated personalized insights
1501
  - Visual analytics dashboard
 
1503
  - Real-time cap warnings
1504
  - Multi-card comparison
1505
 
1506
+ The system consists of multiple microservices
 
1507
  1. **Smart Wallet** - Analyzes transaction context and selects optimal cards
1508
  2. **Rewards-RAG** - Retrieves detailed card benefit information using RAG
1509
  3. **Spend-Forecast** - Predicts spending patterns and warns about cap risks
1510
  4. **Orchestrator** - Coordinates all services for comprehensive recommendations
1511
 
1512
  ### 🎯 How It Works
 
1513
  1. **Enter Transaction Details** - Merchant, amount, category
1514
  2. **AI Analysis** - System analyzes your wallet and transaction context
1515
  3. **Get Recommendation** - Receive the best card with detailed reasoning
1516
  4. **Maximize Rewards** - Earn more points/cashback on every purchase
1517
 
1518
  ### 🔧 Technology Stack
 
1519
  - **Backend:** FastAPI, Python
1520
  - **Frontend:** Gradio
1521
  - **AI/ML:** RAG (Retrieval-Augmented Generation)
 
1523
  - **Deployment:** Hugging Face Spaces
1524
 
1525
  ### 📚 MCC Categories Supported
 
1526
  - Groceries (5411)
1527
  - Restaurants (5812)
1528
  - Gas Stations (5541)
 
1532
  - And many more...
1533
 
1534
  ### 🎓 Built For
 
1535
  **MCP 1st Birthday Hackathon** - Celebrating one year of the Model Context Protocol
1536
 
1537
  ### 👨‍💻 Developer
 
1538
  Built with ❤️ for the MCP community
1539
 
1540
  ---
 
1544
  """
1545
  )
1546
 
 
1547
  with gr.Tab("📖 API Docs"):
1548
  gr.Markdown(
1549
  """
1550
  ## API Endpoints
1551
 
1552
  ### Orchestrator API
 
1553
  **Base URL:** `https://mcp-1st-birthday-rewardpilot-orchestrator.hf.space`
1554
 
1555
  #### POST `/recommend`
 
1556
  Get comprehensive card recommendation.
1557
 
1558
  **Request:**
 
1564
  "amount_usd": 125.50,
1565
  "transaction_date": "2025-01-15"
1566
  }
1567
+ Other Services
1568
+ Smart Wallet: https://mcp-1st-birthday-rewardpilot-smart-wallet.hf.space
1569
+ Rewards-RAG: https://mcp-1st-birthday-rewardpilot-rewards-rag.hf.space
1570
+ Spend-Forecast: https://mcp-1st-birthday-rewardpilot-spend-forecast.hf.space
1571
+ Interactive DocsVisit /docs on any service for interactive Swagger UI documentation.cURL ExamplesbashCopy code[data-radix-scroll-area-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-scroll-area-viewport]::-webkit-scrollbar{display:none}# Get recommendation
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1572
  curl -X POST https://mcp-1st-birthday-rewardpilot-orchestrator.hf.space/recommend \\
1573
  -H "Content-Type: application/json" \\
1574
  -d '{
 
1576
  "merchant": "Whole Foods",
1577
  "mcc": "5411",
1578
  "amount_usd": 125.50
1579
+ }'"""
1580
+ )
 
 
1581
 
1582
  # ===================== Launch App =====================
1583
  if __name__ == "__main__":
 
1585
  server_name="0.0.0.0",
1586
  server_port=7860,
1587
  share=False,
1588
+ )