Update app.py
Browse files
app.py
CHANGED
|
@@ -3177,6 +3177,7 @@ with gr.Blocks(
|
|
| 3177 |
# Define fallback alternatives by category
|
| 3178 |
category_alternatives = {
|
| 3179 |
"Wholesale Club": [
|
|
|
|
| 3180 |
{"card": "Citi Double Cash", "rewards": amount * 0.02, "rate": "2% cashback", "note": "Works everywhere"},
|
| 3181 |
{"card": "Chase Freedom Unlimited", "rewards": amount * 0.015, "rate": "1.5% cashback", "note": "Good backup option"},
|
| 3182 |
{"card": "Capital One Quicksilver", "rewards": amount * 0.015, "rate": "1.5% cashback", "note": "No annual fee"}
|
|
@@ -3184,17 +3185,20 @@ with gr.Blocks(
|
|
| 3184 |
"Grocery Store": [
|
| 3185 |
{"card": "Amex Gold", "rewards": amount * 0.04, "rate": "4x points", "note": "Best for U.S. supermarkets"},
|
| 3186 |
{"card": "Citi Custom Cash", "rewards": amount * 0.05, "rate": "5% cashback", "note": "Up to $500/month"},
|
| 3187 |
-
{"card": "Chase Freedom Flex", "rewards": amount * 0.05, "rate": "5% rotating", "note": "When groceries are bonus category"}
|
|
|
|
| 3188 |
],
|
| 3189 |
"Restaurant": [
|
| 3190 |
{"card": "Amex Gold", "rewards": amount * 0.04, "rate": "4x points", "note": "Worldwide dining"},
|
| 3191 |
{"card": "Chase Sapphire Reserve", "rewards": amount * 0.03, "rate": "3x points", "note": "Premium travel card"},
|
| 3192 |
-
{"card": "Capital One Savor", "rewards": amount * 0.04, "rate": "4% cashback", "note": "Dining specialist"}
|
|
|
|
| 3193 |
],
|
| 3194 |
"Gas Station": [
|
| 3195 |
{"card": "Costco Anywhere Visa", "rewards": amount * 0.04, "rate": "4% cashback", "note": "Best for gas"},
|
| 3196 |
{"card": "Citi Custom Cash", "rewards": amount * 0.05, "rate": "5% cashback", "note": "Up to $500/month"},
|
| 3197 |
-
{"card": "Chase Freedom Unlimited", "rewards": amount * 0.015, "rate": "1.5% cashback", "note": "Baseline option"}
|
|
|
|
| 3198 |
],
|
| 3199 |
"Department Store": [
|
| 3200 |
{"card": "Target RedCard", "rewards": amount * 0.05, "rate": "5% off", "note": "At Target only"},
|
|
@@ -3220,11 +3224,19 @@ with gr.Blocks(
|
|
| 3220 |
{"card": "Capital One Quicksilver", "rewards": amount * 0.015, "rate": "1.5% cashback", "note": "Simple cashback"}
|
| 3221 |
])
|
| 3222 |
|
| 3223 |
-
#
|
| 3224 |
-
|
| 3225 |
-
alternatives = [alt for alt in alternatives if primary_lower not in alt['card'].lower()]
|
| 3226 |
|
| 3227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3228 |
|
| 3229 |
def analyze_receipt_with_vision(image_path, user_id):
|
| 3230 |
"""Extract transaction data from receipt using GPT-4 Vision"""
|
|
@@ -3308,29 +3320,29 @@ with gr.Blocks(
|
|
| 3308 |
}
|
| 3309 |
|
| 3310 |
mcc = category_to_mcc.get(category, "5999")
|
|
|
|
| 3311 |
|
| 3312 |
-
print(f"π Receipt: {receipt_data['merchant']} | {category} | MCC {mcc} | ${
|
| 3313 |
|
| 3314 |
# Get primary recommendation
|
| 3315 |
rec_result = client.get_recommendation(
|
| 3316 |
user_id=user_id,
|
| 3317 |
merchant=receipt_data['merchant'],
|
| 3318 |
category=category,
|
| 3319 |
-
amount=
|
| 3320 |
mcc=mcc
|
| 3321 |
)
|
| 3322 |
|
| 3323 |
if rec_result.get('success'):
|
| 3324 |
data = normalize_recommendation_data(rec_result.get('data', {}))
|
| 3325 |
|
| 3326 |
-
#
|
| 3327 |
alternatives = data.get('alternatives', [])
|
| 3328 |
|
| 3329 |
if not alternatives or len(alternatives) < 2:
|
| 3330 |
-
# Manually create alternatives based on category
|
| 3331 |
alternatives = generate_card_alternatives(
|
| 3332 |
category=category,
|
| 3333 |
-
amount=
|
| 3334 |
primary_card=data['recommended_card']
|
| 3335 |
)
|
| 3336 |
|
|
@@ -3341,7 +3353,9 @@ with gr.Blocks(
|
|
| 3341 |
if "costco" in merchant_lower:
|
| 3342 |
context_note = """
|
| 3343 |
---
|
|
|
|
| 3344 |
### π‘ Costco Shopping Tip
|
|
|
|
| 3345 |
**Accepted Payment:** Costco only accepts **Visa cards** at warehouse locations. Amex and Mastercard are not accepted.
|
| 3346 |
|
| 3347 |
**Best Card:** Costco Anywhere Visa Card offers **2% cashback** at Costco and Costco.com.
|
|
@@ -3351,7 +3365,9 @@ with gr.Blocks(
|
|
| 3351 |
elif "whole foods" in merchant_lower or "amazon" in merchant_lower:
|
| 3352 |
context_note = """
|
| 3353 |
---
|
|
|
|
| 3354 |
### π‘ Whole Foods Tip
|
|
|
|
| 3355 |
**Amazon Prime Members:** Get an extra **10% off** sale items at Whole Foods with Prime membership.
|
| 3356 |
|
| 3357 |
**Best Card:** Amazon Prime Visa offers **5% cashback** at Whole Foods for Prime members.
|
|
@@ -3359,12 +3375,28 @@ with gr.Blocks(
|
|
| 3359 |
---
|
| 3360 |
"""
|
| 3361 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3362 |
# Build output
|
| 3363 |
output = f"""## πΈ Receipt Analysis
|
| 3364 |
|
| 3365 |
### π§Ύ Extracted Information
|
|
|
|
| 3366 |
- **Merchant:** {receipt_data['merchant']}
|
| 3367 |
-
- **Amount:** ${
|
| 3368 |
- **Date:** {receipt_data['date']}
|
| 3369 |
- **Category:** {receipt_data['category']}
|
| 3370 |
|
|
@@ -3373,35 +3405,38 @@ with gr.Blocks(
|
|
| 3373 |
for item in receipt_data.get('items', []):
|
| 3374 |
output += f"- {item}\n"
|
| 3375 |
|
| 3376 |
-
output +=
|
| 3377 |
|
| 3378 |
output += f"""
|
|
|
|
| 3379 |
### π³ Best Card for This Purchase
|
| 3380 |
|
| 3381 |
**π {data['recommended_card']}**
|
| 3382 |
|
| 3383 |
- **Rewards Earned:** ${data['rewards_earned']:.2f}
|
| 3384 |
- **Rewards Rate:** {data['rewards_rate']}
|
| 3385 |
-
- **
|
| 3386 |
-
|
|
|
|
|
|
|
| 3387 |
|
| 3388 |
"""
|
| 3389 |
|
| 3390 |
-
#
|
| 3391 |
if alternatives and len(alternatives) > 0:
|
| 3392 |
-
output += """
|
| 3393 |
-
|
| 3394 |
-
|
| 3395 |
-
|
| 3396 |
-
for i, alt in enumerate(alternatives[:3], 1):
|
| 3397 |
card_name = alt.get('card', 'Unknown Card')
|
| 3398 |
rewards = alt.get('rewards', 0)
|
| 3399 |
rate = alt.get('rate', '0%')
|
|
|
|
| 3400 |
|
| 3401 |
-
output += f"{i}.
|
| 3402 |
output += f" - Rewards: ${rewards:.2f} ({rate})\n"
|
| 3403 |
-
if
|
| 3404 |
-
output += f" -
|
| 3405 |
output += "\n"
|
| 3406 |
|
| 3407 |
# Add warnings
|
|
|
|
| 3177 |
# Define fallback alternatives by category
|
| 3178 |
category_alternatives = {
|
| 3179 |
"Wholesale Club": [
|
| 3180 |
+
{"card": "Costco Anywhere Visa", "rewards": amount * 0.02, "rate": "2% cashback", "note": "Best for Costco purchases"},
|
| 3181 |
{"card": "Citi Double Cash", "rewards": amount * 0.02, "rate": "2% cashback", "note": "Works everywhere"},
|
| 3182 |
{"card": "Chase Freedom Unlimited", "rewards": amount * 0.015, "rate": "1.5% cashback", "note": "Good backup option"},
|
| 3183 |
{"card": "Capital One Quicksilver", "rewards": amount * 0.015, "rate": "1.5% cashback", "note": "No annual fee"}
|
|
|
|
| 3185 |
"Grocery Store": [
|
| 3186 |
{"card": "Amex Gold", "rewards": amount * 0.04, "rate": "4x points", "note": "Best for U.S. supermarkets"},
|
| 3187 |
{"card": "Citi Custom Cash", "rewards": amount * 0.05, "rate": "5% cashback", "note": "Up to $500/month"},
|
| 3188 |
+
{"card": "Chase Freedom Flex", "rewards": amount * 0.05, "rate": "5% rotating", "note": "When groceries are bonus category"},
|
| 3189 |
+
{"card": "Citi Double Cash", "rewards": amount * 0.02, "rate": "2% cashback", "note": "Universal fallback"}
|
| 3190 |
],
|
| 3191 |
"Restaurant": [
|
| 3192 |
{"card": "Amex Gold", "rewards": amount * 0.04, "rate": "4x points", "note": "Worldwide dining"},
|
| 3193 |
{"card": "Chase Sapphire Reserve", "rewards": amount * 0.03, "rate": "3x points", "note": "Premium travel card"},
|
| 3194 |
+
{"card": "Capital One Savor", "rewards": amount * 0.04, "rate": "4% cashback", "note": "Dining specialist"},
|
| 3195 |
+
{"card": "Citi Double Cash", "rewards": amount * 0.02, "rate": "2% cashback", "note": "Universal fallback"}
|
| 3196 |
],
|
| 3197 |
"Gas Station": [
|
| 3198 |
{"card": "Costco Anywhere Visa", "rewards": amount * 0.04, "rate": "4% cashback", "note": "Best for gas"},
|
| 3199 |
{"card": "Citi Custom Cash", "rewards": amount * 0.05, "rate": "5% cashback", "note": "Up to $500/month"},
|
| 3200 |
+
{"card": "Chase Freedom Unlimited", "rewards": amount * 0.015, "rate": "1.5% cashback", "note": "Baseline option"},
|
| 3201 |
+
{"card": "Citi Double Cash", "rewards": amount * 0.02, "rate": "2% cashback", "note": "Universal fallback"}
|
| 3202 |
],
|
| 3203 |
"Department Store": [
|
| 3204 |
{"card": "Target RedCard", "rewards": amount * 0.05, "rate": "5% off", "note": "At Target only"},
|
|
|
|
| 3224 |
{"card": "Capital One Quicksilver", "rewards": amount * 0.015, "rate": "1.5% cashback", "note": "Simple cashback"}
|
| 3225 |
])
|
| 3226 |
|
| 3227 |
+
# β
IMPROVED FILTER: Normalize card names for comparison
|
| 3228 |
+
primary_normalized = primary_card.lower().replace('_', ' ').replace('-', ' ').strip()
|
|
|
|
| 3229 |
|
| 3230 |
+
filtered_alternatives = []
|
| 3231 |
+
for alt in alternatives:
|
| 3232 |
+
alt_normalized = alt['card'].lower().replace('_', ' ').replace('-', ' ').strip()
|
| 3233 |
+
|
| 3234 |
+
# Check if this alternative matches the primary card
|
| 3235 |
+
if primary_normalized not in alt_normalized and alt_normalized not in primary_normalized:
|
| 3236 |
+
filtered_alternatives.append(alt)
|
| 3237 |
+
|
| 3238 |
+
# Return top 3 unique alternatives
|
| 3239 |
+
return filtered_alternatives[:3]
|
| 3240 |
|
| 3241 |
def analyze_receipt_with_vision(image_path, user_id):
|
| 3242 |
"""Extract transaction data from receipt using GPT-4 Vision"""
|
|
|
|
| 3320 |
}
|
| 3321 |
|
| 3322 |
mcc = category_to_mcc.get(category, "5999")
|
| 3323 |
+
amount = float(receipt_data['amount'])
|
| 3324 |
|
| 3325 |
+
print(f"π Receipt: {receipt_data['merchant']} | {category} | MCC {mcc} | ${amount:.2f}")
|
| 3326 |
|
| 3327 |
# Get primary recommendation
|
| 3328 |
rec_result = client.get_recommendation(
|
| 3329 |
user_id=user_id,
|
| 3330 |
merchant=receipt_data['merchant'],
|
| 3331 |
category=category,
|
| 3332 |
+
amount=amount,
|
| 3333 |
mcc=mcc
|
| 3334 |
)
|
| 3335 |
|
| 3336 |
if rec_result.get('success'):
|
| 3337 |
data = normalize_recommendation_data(rec_result.get('data', {}))
|
| 3338 |
|
| 3339 |
+
# Generate alternatives
|
| 3340 |
alternatives = data.get('alternatives', [])
|
| 3341 |
|
| 3342 |
if not alternatives or len(alternatives) < 2:
|
|
|
|
| 3343 |
alternatives = generate_card_alternatives(
|
| 3344 |
category=category,
|
| 3345 |
+
amount=amount,
|
| 3346 |
primary_card=data['recommended_card']
|
| 3347 |
)
|
| 3348 |
|
|
|
|
| 3353 |
if "costco" in merchant_lower:
|
| 3354 |
context_note = """
|
| 3355 |
---
|
| 3356 |
+
|
| 3357 |
### π‘ Costco Shopping Tip
|
| 3358 |
+
|
| 3359 |
**Accepted Payment:** Costco only accepts **Visa cards** at warehouse locations. Amex and Mastercard are not accepted.
|
| 3360 |
|
| 3361 |
**Best Card:** Costco Anywhere Visa Card offers **2% cashback** at Costco and Costco.com.
|
|
|
|
| 3365 |
elif "whole foods" in merchant_lower or "amazon" in merchant_lower:
|
| 3366 |
context_note = """
|
| 3367 |
---
|
| 3368 |
+
|
| 3369 |
### π‘ Whole Foods Tip
|
| 3370 |
+
|
| 3371 |
**Amazon Prime Members:** Get an extra **10% off** sale items at Whole Foods with Prime membership.
|
| 3372 |
|
| 3373 |
**Best Card:** Amazon Prime Visa offers **5% cashback** at Whole Foods for Prime members.
|
|
|
|
| 3375 |
---
|
| 3376 |
"""
|
| 3377 |
|
| 3378 |
+
# β
FORMAT REASONING INTO BULLET POINTS
|
| 3379 |
+
reasoning = data.get('reasoning', '')
|
| 3380 |
+
|
| 3381 |
+
# Split reasoning into sentences and format as bullets
|
| 3382 |
+
reasoning_bullets = []
|
| 3383 |
+
if reasoning:
|
| 3384 |
+
# Split by periods, but keep sentences together
|
| 3385 |
+
sentences = re.split(r'(?<=[.!?])\s+', reasoning)
|
| 3386 |
+
for sentence in sentences:
|
| 3387 |
+
sentence = sentence.strip()
|
| 3388 |
+
if sentence and len(sentence) > 10: # Ignore very short fragments
|
| 3389 |
+
reasoning_bullets.append(f" - {sentence}")
|
| 3390 |
+
|
| 3391 |
+
reasoning_formatted = "\n".join(reasoning_bullets) if reasoning_bullets else f" - {reasoning}"
|
| 3392 |
+
|
| 3393 |
# Build output
|
| 3394 |
output = f"""## πΈ Receipt Analysis
|
| 3395 |
|
| 3396 |
### π§Ύ Extracted Information
|
| 3397 |
+
|
| 3398 |
- **Merchant:** {receipt_data['merchant']}
|
| 3399 |
+
- **Amount:** ${amount:.2f}
|
| 3400 |
- **Date:** {receipt_data['date']}
|
| 3401 |
- **Category:** {receipt_data['category']}
|
| 3402 |
|
|
|
|
| 3405 |
for item in receipt_data.get('items', []):
|
| 3406 |
output += f"- {item}\n"
|
| 3407 |
|
| 3408 |
+
output += context_note
|
| 3409 |
|
| 3410 |
output += f"""
|
| 3411 |
+
|
| 3412 |
### π³ Best Card for This Purchase
|
| 3413 |
|
| 3414 |
**π {data['recommended_card']}**
|
| 3415 |
|
| 3416 |
- **Rewards Earned:** ${data['rewards_earned']:.2f}
|
| 3417 |
- **Rewards Rate:** {data['rewards_rate']}
|
| 3418 |
+
- **Annual Potential:** ${data['annual_potential']:.2f}/year
|
| 3419 |
+
|
| 3420 |
+
**Why This Card:**
|
| 3421 |
+
{reasoning_formatted}
|
| 3422 |
|
| 3423 |
"""
|
| 3424 |
|
| 3425 |
+
# Show alternatives
|
| 3426 |
if alternatives and len(alternatives) > 0:
|
| 3427 |
+
output += """### π Other Card Options
|
| 3428 |
+
|
| 3429 |
+
"""
|
| 3430 |
+
for i, alt in enumerate(alternatives, 1):
|
|
|
|
| 3431 |
card_name = alt.get('card', 'Unknown Card')
|
| 3432 |
rewards = alt.get('rewards', 0)
|
| 3433 |
rate = alt.get('rate', '0%')
|
| 3434 |
+
note = alt.get('note', '')
|
| 3435 |
|
| 3436 |
+
output += f"**{i}. {card_name}**\n"
|
| 3437 |
output += f" - Rewards: ${rewards:.2f} ({rate})\n"
|
| 3438 |
+
if note:
|
| 3439 |
+
output += f" - {note}\n"
|
| 3440 |
output += "\n"
|
| 3441 |
|
| 3442 |
# Add warnings
|