sammy786 commited on
Commit
317dcab
Β·
verified Β·
1 Parent(s): 75b2861

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -202
app.py CHANGED
@@ -360,202 +360,83 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
360
 
361
  print(f"πŸ” KEYS: {list(result.keys())}")
362
 
363
- card_id = result.get('recommended_card', 'Unknown')
364
- rewards_earned = float(result.get('rewards_earned', 0))
365
- rewards_rate = result.get('rewards_rate', 'N/A')
366
- confidence = float(result.get('confidence', 0))
367
- reasoning = result.get('reasoning', 'No reasoning provided')
368
- alternatives = result.get('alternative_options', [])
369
- warnings = result.get('warnings', [])
370
 
371
- card_name_map = {
372
- 'c_citi_custom_cash': 'Citi Custom Cash',
373
- 'c_amex_gold': 'American Express Gold',
374
- 'c_chase_sapphire_reserve': 'Chase Sapphire Reserve',
375
- 'c_chase_freedom_unlimited': 'Chase Freedom Unlimited',
376
- 'c_chase_sapphire_preferred': 'Chase Sapphire Preferred',
377
- 'c_capital_one_venture': 'Capital One Venture',
378
- 'c_discover_it': 'Discover it',
379
- 'c_wells_fargo_active_cash': 'Wells Fargo Active Cash'
380
- }
381
- card_name = card_name_map.get(card_id, card_id.replace('c_', '').replace('_', ' ').title())
382
 
383
- transaction_mcc = result.get('mcc', MCC_CATEGORIES.get(category, "5999"))
384
- card_details_from_db = get_card_details(card_id, transaction_mcc)
385
- card_details = result.get('card_details', {})
386
-
387
- if not card_details or not card_details.get('reward_rate'):
388
- reward_structure = CARD_DATABASE.get(card_id, {}).get('reward_structure', {})
389
-
390
- if transaction_mcc in reward_structure:
391
- reward_rate_value = reward_structure[transaction_mcc]
392
- else:
393
- reward_rate_value = reward_structure.get('default', 1.0)
394
-
395
- spending_caps_db = CARD_DATABASE.get(card_id, {}).get('spending_caps', {})
396
-
397
- card_details = {
398
- 'reward_rate': reward_rate_value,
399
- 'monthly_cap': spending_caps_db.get('monthly_bonus'),
400
- 'annual_cap': spending_caps_db.get('annual_bonus'),
401
- 'quarterly_cap': spending_caps_db.get('quarterly_bonus'),
402
- 'base_rate': reward_structure.get('default', 1.0),
403
- 'annual_fee': CARD_DATABASE.get(card_id, {}).get('annual_fee', 0),
404
- 'cap_type': 'monthly' if 'monthly_bonus' in spending_caps_db else
405
- 'annual' if 'annual_bonus' in spending_caps_db else
406
- 'quarterly' if 'quarterly_bonus' in spending_caps_db else 'none'
407
- }
408
- print(f"βœ… Using cards.json details for {card_id}")
409
 
410
  reward_rate_value = card_details.get('reward_rate', 1.0)
411
- monthly_cap = card_details.get('monthly_cap', None)
412
- annual_cap = card_details.get('annual_cap', None)
413
- base_rate = card_details.get('base_rate', 1.0)
414
  annual_fee = card_details.get('annual_fee', 0)
 
415
 
416
- print(f"βœ… CARD DETAILS: {reward_rate_value}%, cap={monthly_cap or annual_cap}, fee=${annual_fee}")
417
-
418
- amount_float = float(amount)
 
419
 
420
- frequency_map = {
421
- 'Groceries': 52,
422
- 'Restaurants': 52,
423
- 'Gas Stations': 52,
424
- 'Fast Food': 52,
425
- 'Airlines': 4,
426
- 'Hotels': 12,
427
- 'Online Shopping': 24,
428
- 'Entertainment': 24,
429
- }
430
- frequency = frequency_map.get(category, 26)
431
- frequency_label = {
432
- 52: 'weekly',
433
- 26: 'bi-weekly',
434
- 24: 'bi-weekly',
435
- 12: 'monthly',
436
- 4: 'quarterly'
437
- }.get(frequency, f'{frequency}x per year')
438
-
439
- annual_spend = amount_float * frequency
440
-
441
- if monthly_cap:
442
- monthly_cap_annual = monthly_cap * 12
443
 
444
- if annual_spend <= monthly_cap_annual:
445
- high_rate_spend = annual_spend
446
- low_rate_spend = 0
 
447
  else:
448
- high_rate_spend = monthly_cap_annual
449
- low_rate_spend = annual_spend - monthly_cap_annual
450
 
451
- high_rate_rewards = high_rate_spend * (reward_rate_value / 100)
452
- low_rate_rewards = low_rate_spend * (base_rate / 100)
453
- total_rewards = high_rate_rewards + low_rate_rewards
454
-
455
- calc_table = f"""
456
- | Spending Tier | Annual Amount | Rate | Rewards |
457
- |---------------|---------------|------|---------|
458
- | First ${monthly_cap}/month | ${high_rate_spend:.2f} | {reward_rate_value}% | ${high_rate_rewards:.2f} |
459
- | Remaining spend | ${low_rate_spend:.2f} | {base_rate}% | ${low_rate_rewards:.2f} |
460
- | **Subtotal** | **${annual_spend:.2f}** | - | **${total_rewards:.2f}** |
461
- | Annual fee | - | - | -${annual_fee:.2f} |
462
- | **Net Rewards** | - | - | **${total_rewards - annual_fee:.2f}** |
463
- """
464
-
465
- elif annual_cap:
466
- if annual_spend <= annual_cap:
467
- high_rate_spend = annual_spend
468
  low_rate_spend = 0
469
  else:
470
- high_rate_spend = annual_cap
471
- low_rate_spend = annual_spend - annual_cap
472
 
473
  high_rate_rewards = high_rate_spend * (reward_rate_value / 100)
474
- low_rate_rewards = low_rate_spend * (base_rate / 100)
475
- total_rewards = high_rate_rewards + low_rate_rewards
476
 
477
- calc_table = f"""
478
- | Spending Tier | Annual Amount | Rate | Rewards |
479
  |---------------|---------------|------|---------|
480
- | Up to ${annual_cap:,.0f}/year | ${high_rate_spend:.2f} | {reward_rate_value}% | ${high_rate_rewards:.2f} |
481
- | Above cap | ${low_rate_spend:.2f} | {base_rate}% | ${low_rate_rewards:.2f} |
482
- | **Subtotal** | **${annual_spend:.2f}** | - | **${total_rewards:.2f}** |
483
  | Annual fee | - | - | -${annual_fee:.2f} |
484
- | **Net Rewards** | - | - | **${total_rewards - annual_fee:.2f}** |
485
- """
486
-
487
  else:
488
- total_rewards = annual_spend * (reward_rate_value / 100)
489
-
490
- calc_table = f"""
491
- | Spending Tier | Annual Amount | Rate | Rewards |
492
  |---------------|---------------|------|---------|
493
- | All spending | ${annual_spend:.2f} | {reward_rate_value}% | ${total_rewards:.2f} |
494
  | Annual fee | - | - | -${annual_fee:.2f} |
495
- | **Net Rewards** | - | - | **${total_rewards - annual_fee:.2f}** |
496
- """
497
-
498
- baseline_rewards = annual_spend * 0.01
499
- net_rewards = total_rewards - annual_fee
500
- net_benefit = net_rewards - baseline_rewards
501
-
502
- comparison_text = f"""
503
- **With {card_name}:**
504
- - Earnings: ${total_rewards:.2f}
505
- - Annual fee: -${annual_fee:.2f}
506
- - **Net total: ${net_rewards:.2f}/year**
507
-
508
- **With Baseline 1% Card:**
509
- - All spending at 1%: ${baseline_rewards:.2f}/year
510
-
511
- **Net Benefit: ${net_benefit:+.2f}/year** {"πŸŽ‰" if net_benefit > 0 else "⚠️"}
512
- """
513
-
514
- max_possible_rewards = annual_spend * 0.06
515
 
516
- if max_possible_rewards > 0:
517
- performance_ratio = (net_rewards / max_possible_rewards) * 100
518
-
519
- if net_rewards > baseline_rewards:
520
- improvement = (net_rewards - baseline_rewards) / baseline_rewards
521
- baseline_bonus = min(improvement * 20, 20)
522
- else:
523
- baseline_bonus = -10
524
-
525
- optimization_score = int(min(performance_ratio + baseline_bonus, 100))
526
- else:
527
- optimization_score = 0
528
-
529
- score_breakdown = {
530
- 'reward_rate': min(30, int(optimization_score * 0.30)),
531
- 'cap_availability': min(25, int(optimization_score * 0.25)),
532
- 'annual_fee': min(20, int(optimization_score * 0.20)),
533
- 'category_match': min(20, int(optimization_score * 0.20)),
534
- 'penalties': max(-5, int((optimization_score - 100) * 0.05))
535
- }
536
-
537
- score_details = f"""
538
- **Score Components:**
539
- - {"βœ…" if score_breakdown['reward_rate'] > 20 else "⚠️"} Reward rate: **+{score_breakdown['reward_rate']} points**
540
- - {"βœ…" if score_breakdown['cap_availability'] > 15 else "⚠️"} Cap availability: **+{score_breakdown['cap_availability']} points**
541
- - {"βœ…" if score_breakdown['annual_fee'] > 15 else "⚠️"} Annual fee value: **+{score_breakdown['annual_fee']} points**
542
- - {"βœ…" if score_breakdown['category_match'] > 15 else "⚠️"} Category match: **+{score_breakdown['category_match']} points**
543
- - {"⚠️" if score_breakdown['penalties'] < 0 else "βœ…"} Limitations: **{score_breakdown['penalties']} points**
544
-
545
- **Total: {optimization_score}/100**
546
-
547
- **Score Ranges:**
548
- - 90-100: Optimal choice βœ…
549
- - 80-89: Great choice πŸ‘
550
- - 70-79: Good choice πŸ‘Œ
551
- - 60-69: Acceptable ⚠️
552
- - <60: Suboptimal ❌
553
- """
554
  def format_reasoning(text):
555
- """Format reasoning text into clean bullet points"""
556
  if text.strip().startswith(('-', 'β€’', '*', '1.', '2.')):
557
  return text
558
-
559
  sentences = text.replace('\n', ' ').split('. ')
560
  bullets = []
561
  for sentence in sentences[:4]:
@@ -564,19 +445,18 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
564
  if not sentence.endswith('.'):
565
  sentence += '.'
566
  bullets.append(f"- {sentence}")
567
-
568
  return '\n'.join(bullets) if bullets else f"- {text}"
569
 
570
  reasoning_bullets = format_reasoning(reasoning)
571
 
572
- # Format the output - CRITICAL: No indentation on f-string!
573
  output = f"""## 🎯 Recommended: **{card_name}**
574
-
575
  | Metric | Value |
576
  |--------|-------|
577
  | πŸ’° **Rewards Earned** | ${rewards_earned:.2f} ({rewards_rate}) |
578
  | πŸ“Š **Confidence** | {confidence*100:.0f}% |
579
- | πŸ“ˆ **Annual Potential** | ${net_benefit:.2f}/year |
580
  | ⭐ **Optimization Score** | {optimization_score}/100 |
581
 
582
  ---
@@ -588,42 +468,38 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
588
  ---
589
  """
590
 
591
- # Alternatives
592
  if alternatives:
593
  output += "\n### πŸ”„ Alternative Options\n\n"
594
- output += "| Card | Rewards | Why? |\n"
595
- output += "|------|---------|------|\n"
596
  for alt in alternatives[:3]:
597
- alt_card_id = alt.get('card', '')
598
- alt_card_name = card_name_map.get(alt_card_id, alt_card_id.replace('c_', '').replace('_', ' ').title())
 
599
  alt_reason = alt.get('reason', 'Good alternative')
600
- alt_reward = alt.get('reward_amount', rewards_earned * 0.8)
601
-
602
  alt_reason_short = alt_reason.split('.')[0].strip()
603
  if not alt_reason_short.endswith('.'):
604
  alt_reason_short += '.'
605
-
606
- output += f"| {alt_card_name} | ${alt_reward:.2f} | {alt_reason_short} |\n"
607
  output += "\n---\n"
608
 
609
- # Warnings
610
  if warnings:
611
  output += "\n### ⚠️ Alerts\n\n"
612
  for warning in warnings:
613
  output += f"- {warning}\n"
614
  output += "\n---\n"
615
 
616
- # Calculation details (collapsible)
617
- output += f"""
618
- <details>
619
  <summary>πŸ“Š <b>Annual Impact Calculation</b> (Click to expand)</summary>
620
 
621
  <br>
622
 
623
  **Assumptions:**
624
-
625
- - Transaction: ${amount_float:.2f} at {merchant} ({category})
626
- - Frequency: {frequency_label} β†’ ${annual_spend:.2f}/year
627
 
628
  **Rewards Breakdown:**
629
 
@@ -633,26 +509,20 @@ def get_recommendation_with_agent(user_id, merchant, category, amount):
633
 
634
  **Net Benefit:** ${net_benefit:+.2f}/year {"πŸŽ‰" if net_benefit > 0 else "⚠️"}
635
 
636
- **Card Details:** {reward_rate_value}% on {category} | Cap: {"$" + str(monthly_cap or annual_cap) if (monthly_cap or annual_cap) else "None"} | Fee: ${annual_fee}
637
 
638
  <br>
639
-
640
  </details>
641
  """
642
-
643
 
644
- chart = create_agent_recommendation_chart_enhanced(result)
 
 
645
  yield output, chart
646
 
647
- print("=" * 80)
648
- print("πŸ“€ FINAL OUTPUT (first 500 chars):")
649
- print(output[:500])
650
- print("=" * 80)
651
-
652
  except Exception as e:
653
  print(f"❌ ERROR: {traceback.format_exc()}")
654
  yield f"❌ **Error:** {str(e)}", None
655
-
656
  def create_agent_recommendation_chart_enhanced(result: Dict) -> go.Figure:
657
  try:
658
  rec_name_map = {
 
360
 
361
  print(f"πŸ” KEYS: {list(result.keys())}")
362
 
363
+ recommendation = result.get('recommendation', {})
 
 
 
 
 
 
364
 
365
+ if not recommendation:
366
+ yield f"❌ Invalid response: No recommendation found", None
367
+ return
 
 
 
 
 
 
 
 
368
 
369
+ # βœ… Extract values correctly
370
+ card_id = recommendation.get('recommended_card', 'Unknown')
371
+ card_name = recommendation.get('card_name', card_id)
372
+ rewards_earned = float(recommendation.get('rewards_earned', 0))
373
+ rewards_rate = recommendation.get('rewards_rate', 'N/A')
374
+ confidence = float(recommendation.get('confidence', 0))
375
+ reasoning = recommendation.get('reasoning', 'No reasoning provided')
376
+ alternatives = recommendation.get('alternative_options', [])
377
+ warnings = recommendation.get('warnings', [])
378
+
379
+ # βœ… CRITICAL FIX: Access nested annual_impact object
380
+ annual_impact = recommendation.get('annual_impact', {})
381
+ annual_potential = annual_impact.get('potential_savings', 0)
382
+ optimization_score = annual_impact.get('optimization_score', 0)
383
+ frequency = annual_impact.get('transaction_frequency', 12)
384
+ annual_spending = annual_impact.get('annual_spending', 0)
385
+ frequency_label = annual_impact.get('frequency_assumption', f'{frequency}x per year')
386
+
387
+ # βœ… Get card details from database
388
+ transaction_mcc = transaction.get('mcc', MCC_CATEGORIES.get(category, "5999"))
389
+ card_details = get_card_details(card_id, transaction_mcc)
 
 
 
 
 
390
 
391
  reward_rate_value = card_details.get('reward_rate', 1.0)
 
 
 
392
  annual_fee = card_details.get('annual_fee', 0)
393
+ spending_caps = card_details.get('spending_caps', {})
394
 
395
+ # βœ… Calculate baseline comparison
396
+ baseline_rewards = annual_spending * 0.01
397
+ net_rewards = annual_potential - annual_fee
398
+ net_benefit = net_rewards - baseline_rewards
399
 
400
+ # βœ… Build calculation table
401
+ if spending_caps and spending_caps.get('limit'):
402
+ cap_limit = spending_caps['limit']
403
+ cap_type = spending_caps.get('type', 'monthly')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
 
405
+ if cap_type == 'monthly':
406
+ cap_annual = cap_limit * 12
407
+ elif cap_type == 'quarterly':
408
+ cap_annual = cap_limit * 4
409
  else:
410
+ cap_annual = cap_limit
 
411
 
412
+ if annual_spending <= cap_annual:
413
+ high_rate_spend = annual_spending
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
  low_rate_spend = 0
415
  else:
416
+ high_rate_spend = cap_annual
417
+ low_rate_spend = annual_spending - cap_annual
418
 
419
  high_rate_rewards = high_rate_spend * (reward_rate_value / 100)
420
+ low_rate_rewards = low_rate_spend * (card_details.get('base_rate', 1.0) / 100)
 
421
 
422
+ calc_table = f"""| Spending Tier | Annual Amount | Rate | Rewards |
 
423
  |---------------|---------------|------|---------|
424
+ | First ${spending_caps['display']} | ${high_rate_spend:.2f} | {reward_rate_value}% | ${high_rate_rewards:.2f} |
425
+ | Remaining spend | ${low_rate_spend:.2f} | {card_details.get('base_rate', 1.0)}% | ${low_rate_rewards:.2f} |
426
+ | **Subtotal** | **${annual_spending:.2f}** | - | **${annual_potential:.2f}** |
427
  | Annual fee | - | - | -${annual_fee:.2f} |
428
+ | **Net Rewards** | - | - | **${net_rewards:.2f}** |"""
 
 
429
  else:
430
+ calc_table = f"""| Spending Tier | Annual Amount | Rate | Rewards |
 
 
 
431
  |---------------|---------------|------|---------|
432
+ | All spending | ${annual_spending:.2f} | {reward_rate_value}% | ${annual_potential:.2f} |
433
  | Annual fee | - | - | -${annual_fee:.2f} |
434
+ | **Net Rewards** | - | - | **${net_rewards:.2f}** |"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
 
436
+ # βœ… Format reasoning
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  def format_reasoning(text):
 
438
  if text.strip().startswith(('-', 'β€’', '*', '1.', '2.')):
439
  return text
 
440
  sentences = text.replace('\n', ' ').split('. ')
441
  bullets = []
442
  for sentence in sentences[:4]:
 
445
  if not sentence.endswith('.'):
446
  sentence += '.'
447
  bullets.append(f"- {sentence}")
 
448
  return '\n'.join(bullets) if bullets else f"- {text}"
449
 
450
  reasoning_bullets = format_reasoning(reasoning)
451
 
452
+ # βœ… Build output with CORRECT values from annual_impact
453
  output = f"""## 🎯 Recommended: **{card_name}**
454
+
455
  | Metric | Value |
456
  |--------|-------|
457
  | πŸ’° **Rewards Earned** | ${rewards_earned:.2f} ({rewards_rate}) |
458
  | πŸ“Š **Confidence** | {confidence*100:.0f}% |
459
+ | πŸ“ˆ **Annual Potential** | ${annual_potential:.2f}/year |
460
  | ⭐ **Optimization Score** | {optimization_score}/100 |
461
 
462
  ---
 
468
  ---
469
  """
470
 
471
+ # βœ… Alternatives
472
  if alternatives:
473
  output += "\n### πŸ”„ Alternative Options\n\n"
474
+ output += "| Card | Rewards | Rate | Why? |\n"
475
+ output += "|------|---------|------|------|\n"
476
  for alt in alternatives[:3]:
477
+ alt_card_name = alt.get('card_name', alt.get('card', 'Unknown'))
478
+ alt_rewards = alt.get('rewards_earned', 0)
479
+ alt_rate = alt.get('rewards_rate', 'N/A')
480
  alt_reason = alt.get('reason', 'Good alternative')
 
 
481
  alt_reason_short = alt_reason.split('.')[0].strip()
482
  if not alt_reason_short.endswith('.'):
483
  alt_reason_short += '.'
484
+ output += f"| {alt_card_name} | ${alt_rewards:.2f} | {alt_rate} | {alt_reason_short} |\n"
 
485
  output += "\n---\n"
486
 
487
+ # βœ… Warnings
488
  if warnings:
489
  output += "\n### ⚠️ Alerts\n\n"
490
  for warning in warnings:
491
  output += f"- {warning}\n"
492
  output += "\n---\n"
493
 
494
+ # βœ… Calculation details
495
+ output += f"""<details>
 
496
  <summary>πŸ“Š <b>Annual Impact Calculation</b> (Click to expand)</summary>
497
 
498
  <br>
499
 
500
  **Assumptions:**
501
+ - Transaction: ${float(amount):.2f} at {merchant} ({category})
502
+ - Frequency: {frequency_label} β†’ ${annual_spending:.2f}/year
 
503
 
504
  **Rewards Breakdown:**
505
 
 
509
 
510
  **Net Benefit:** ${net_benefit:+.2f}/year {"πŸŽ‰" if net_benefit > 0 else "⚠️"}
511
 
512
+ **Card Details:** {reward_rate_value}% on {category} | Cap: {"$" + str(spending_caps.get('limit', 'None')) if spending_caps else "None"} | Fee: ${annual_fee}
513
 
514
  <br>
 
515
  </details>
516
  """
 
517
 
518
+ # βœ… Create chart
519
+ chart = create_agent_recommendation_chart_enhanced(recommendation)
520
+
521
  yield output, chart
522
 
 
 
 
 
 
523
  except Exception as e:
524
  print(f"❌ ERROR: {traceback.format_exc()}")
525
  yield f"❌ **Error:** {str(e)}", None
 
526
  def create_agent_recommendation_chart_enhanced(result: Dict) -> go.Figure:
527
  try:
528
  rec_name_map = {