Update app.py
Browse files
app.py
CHANGED
|
@@ -1723,9 +1723,9 @@ with gr.Blocks(
|
|
| 1723 |
<p class="hero-subtitle">
|
| 1724 |
<strong>You have 5 credit cards. You're at checkout. Which one do you use?</strong><br>
|
| 1725 |
Most people pick wrong and lose <strong>$400+ per year</strong>.<br>
|
| 1726 |
-
Our AI agent makes the optimal choice in <strong>2 seconds</strong
|
| 1727 |
-
|
| 1728 |
-
|
| 1729 |
</p>
|
| 1730 |
|
| 1731 |
<div class="impact-stats">
|
|
@@ -3145,7 +3145,6 @@ with gr.Blocks(
|
|
| 3145 |
with open(image_path, "rb") as image_file:
|
| 3146 |
base64_image = base64.b64encode(image_file.read()).decode('utf-8')
|
| 3147 |
|
| 3148 |
-
# β
IMPROVED PROMPT with better category detection
|
| 3149 |
response = openai_client.chat.completions.create(
|
| 3150 |
model="gpt-4o",
|
| 3151 |
messages=[{
|
|
@@ -3156,13 +3155,11 @@ with gr.Blocks(
|
|
| 3156 |
"text": """Extract the following from this receipt and classify accurately:
|
| 3157 |
|
| 3158 |
1. **Merchant name** (exact as shown on receipt)
|
| 3159 |
-
2. **Total amount** (final total only
|
| 3160 |
3. **Date** (format: YYYY-MM-DD, or "Unknown" if not visible)
|
| 3161 |
-
4. **Category** - Choose the MOST SPECIFIC
|
| 3162 |
- "Wholesale Club" (Costco, Sam's Club, BJ's)
|
| 3163 |
- "Grocery Store" (Whole Foods, Safeway, Kroger, Trader Joe's)
|
| 3164 |
-
- "Supermarket" (traditional grocery stores)
|
| 3165 |
-
- "Convenience Store" (7-Eleven, Circle K)
|
| 3166 |
- "Restaurant" (sit-down dining)
|
| 3167 |
- "Fast Food" (quick service)
|
| 3168 |
- "Gas Station"
|
|
@@ -3172,11 +3169,9 @@ with gr.Blocks(
|
|
| 3172 |
|
| 3173 |
5. **Top 3 items** purchased (if visible)
|
| 3174 |
|
| 3175 |
-
**IMPORTANT
|
| 3176 |
- Costco, Sam's Club, BJ's = "Wholesale Club" (NOT "Grocery Store")
|
| 3177 |
-
- Walmart
|
| 3178 |
-
- Target = "Department Store" (NOT "Grocery Store")
|
| 3179 |
-
- Whole Foods, Safeway, Kroger = "Grocery Store"
|
| 3180 |
|
| 3181 |
Return as JSON:
|
| 3182 |
{
|
|
@@ -3198,7 +3193,6 @@ with gr.Blocks(
|
|
| 3198 |
max_tokens=500
|
| 3199 |
)
|
| 3200 |
|
| 3201 |
-
# Parse response
|
| 3202 |
receipt_data_str = response.choices[0].message.content
|
| 3203 |
|
| 3204 |
import re
|
|
@@ -3210,12 +3204,10 @@ with gr.Blocks(
|
|
| 3210 |
|
| 3211 |
category = receipt_data['category']
|
| 3212 |
|
| 3213 |
-
# Map category to
|
| 3214 |
category_to_mcc = {
|
| 3215 |
"Wholesale Club": "5300",
|
| 3216 |
"Grocery Store": "5411",
|
| 3217 |
-
"Supermarket": "5411",
|
| 3218 |
-
"Convenience Store": "5499",
|
| 3219 |
"Restaurant": "5812",
|
| 3220 |
"Fast Food": "5814",
|
| 3221 |
"Gas Station": "5541",
|
|
@@ -3223,80 +3215,116 @@ with gr.Blocks(
|
|
| 3223 |
"Online Shopping": "5942"
|
| 3224 |
}
|
| 3225 |
|
| 3226 |
-
mcc = category_to_mcc.get(category,
|
| 3227 |
|
| 3228 |
-
print(f"π Receipt
|
| 3229 |
-
print(f" Merchant: {receipt_data['merchant']}")
|
| 3230 |
-
print(f" Category: {category}")
|
| 3231 |
-
print(f" MCC: {mcc}")
|
| 3232 |
-
print(f" Amount: ${receipt_data['amount']:.2f}")
|
| 3233 |
|
| 3234 |
-
# Get
|
| 3235 |
rec_result = client.get_recommendation(
|
| 3236 |
user_id=user_id,
|
| 3237 |
merchant=receipt_data['merchant'],
|
| 3238 |
-
category=category,
|
| 3239 |
amount=float(receipt_data['amount']),
|
| 3240 |
-
mcc=mcc
|
| 3241 |
)
|
| 3242 |
|
| 3243 |
if rec_result.get('success'):
|
| 3244 |
data = normalize_recommendation_data(rec_result.get('data', {}))
|
| 3245 |
|
| 3246 |
-
# β
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3247 |
context_note = ""
|
| 3248 |
-
|
|
|
|
|
|
|
| 3249 |
context_note = """
|
| 3250 |
-
|
| 3251 |
-
|
| 3252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3253 |
|
|
|
|
| 3254 |
output = f"""## πΈ Receipt Analysis
|
| 3255 |
|
| 3256 |
### π§Ύ Extracted Information
|
| 3257 |
- **Merchant:** {receipt_data['merchant']}
|
| 3258 |
- **Amount:** ${receipt_data['amount']:.2f}
|
| 3259 |
- **Date:** {receipt_data['date']}
|
| 3260 |
-
- **Category:** {receipt_data['category']}
|
| 3261 |
|
| 3262 |
**Items Purchased:**
|
| 3263 |
"""
|
| 3264 |
for item in receipt_data.get('items', []):
|
| 3265 |
output += f"- {item}\n"
|
| 3266 |
|
|
|
|
|
|
|
| 3267 |
output += f"""
|
| 3268 |
-
|
| 3269 |
-
{context_note}
|
| 3270 |
-
|
| 3271 |
-
---
|
| 3272 |
-
|
| 3273 |
-
### π³ Optimal Card Recommendation
|
| 3274 |
|
| 3275 |
-
|
| 3276 |
|
| 3277 |
-
- **Rewards Earned:** ${data['rewards_earned']:.2f}
|
|
|
|
| 3278 |
- **Reasoning:** {data['reasoning']}
|
| 3279 |
-
- **Annual Potential:** ${data['annual_potential']:.2f}/year
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3280 |
|
| 3281 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3282 |
|
| 3283 |
-
|
| 3284 |
-
|
|
|
|
| 3285 |
for warning in data['warnings']:
|
| 3286 |
output += f"- {warning}\n"
|
| 3287 |
|
| 3288 |
-
|
| 3289 |
-
output += "\n### π Alternative Options\n\n"
|
| 3290 |
-
for alt in data['alternatives'][:3]:
|
| 3291 |
-
output += f"- **{alt['card']}:** ${alt['rewards']:.2f} ({alt['rate']})\n"
|
| 3292 |
-
|
| 3293 |
-
# Create chart
|
| 3294 |
chart = create_rewards_comparison_chart(data)
|
| 3295 |
|
| 3296 |
return output, chart
|
| 3297 |
|
| 3298 |
else:
|
| 3299 |
-
return f"β
Receipt scanned
|
| 3300 |
|
| 3301 |
except Exception as e:
|
| 3302 |
error_details = traceback.format_exc()
|
|
|
|
| 1723 |
<p class="hero-subtitle">
|
| 1724 |
<strong>You have 5 credit cards. You're at checkout. Which one do you use?</strong><br>
|
| 1725 |
Most people pick wrong and lose <strong>$400+ per year</strong>.<br>
|
| 1726 |
+
Our AI agent makes the optimal choice in <strong>2 seconds</strong>.<br>
|
| 1727 |
+
Powered by OpenAI <strong>GPT-4 + Gemini + Modal</strong><br>
|
| 1728 |
+
Your AI-powered rewards optimization assistant
|
| 1729 |
</p>
|
| 1730 |
|
| 1731 |
<div class="impact-stats">
|
|
|
|
| 3145 |
with open(image_path, "rb") as image_file:
|
| 3146 |
base64_image = base64.b64encode(image_file.read()).decode('utf-8')
|
| 3147 |
|
|
|
|
| 3148 |
response = openai_client.chat.completions.create(
|
| 3149 |
model="gpt-4o",
|
| 3150 |
messages=[{
|
|
|
|
| 3155 |
"text": """Extract the following from this receipt and classify accurately:
|
| 3156 |
|
| 3157 |
1. **Merchant name** (exact as shown on receipt)
|
| 3158 |
+
2. **Total amount** (final total only)
|
| 3159 |
3. **Date** (format: YYYY-MM-DD, or "Unknown" if not visible)
|
| 3160 |
+
4. **Category** - Choose the MOST SPECIFIC:
|
| 3161 |
- "Wholesale Club" (Costco, Sam's Club, BJ's)
|
| 3162 |
- "Grocery Store" (Whole Foods, Safeway, Kroger, Trader Joe's)
|
|
|
|
|
|
|
| 3163 |
- "Restaurant" (sit-down dining)
|
| 3164 |
- "Fast Food" (quick service)
|
| 3165 |
- "Gas Station"
|
|
|
|
| 3169 |
|
| 3170 |
5. **Top 3 items** purchased (if visible)
|
| 3171 |
|
| 3172 |
+
**IMPORTANT:**
|
| 3173 |
- Costco, Sam's Club, BJ's = "Wholesale Club" (NOT "Grocery Store")
|
| 3174 |
+
- Walmart, Target = "Department Store" (NOT "Grocery Store")
|
|
|
|
|
|
|
| 3175 |
|
| 3176 |
Return as JSON:
|
| 3177 |
{
|
|
|
|
| 3193 |
max_tokens=500
|
| 3194 |
)
|
| 3195 |
|
|
|
|
| 3196 |
receipt_data_str = response.choices[0].message.content
|
| 3197 |
|
| 3198 |
import re
|
|
|
|
| 3204 |
|
| 3205 |
category = receipt_data['category']
|
| 3206 |
|
| 3207 |
+
# Map category to MCC
|
| 3208 |
category_to_mcc = {
|
| 3209 |
"Wholesale Club": "5300",
|
| 3210 |
"Grocery Store": "5411",
|
|
|
|
|
|
|
| 3211 |
"Restaurant": "5812",
|
| 3212 |
"Fast Food": "5814",
|
| 3213 |
"Gas Station": "5541",
|
|
|
|
| 3215 |
"Online Shopping": "5942"
|
| 3216 |
}
|
| 3217 |
|
| 3218 |
+
mcc = category_to_mcc.get(category, "5999")
|
| 3219 |
|
| 3220 |
+
print(f"π Receipt: {receipt_data['merchant']} | {category} | MCC {mcc} | ${receipt_data['amount']:.2f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3221 |
|
| 3222 |
+
# Get primary recommendation
|
| 3223 |
rec_result = client.get_recommendation(
|
| 3224 |
user_id=user_id,
|
| 3225 |
merchant=receipt_data['merchant'],
|
| 3226 |
+
category=category,
|
| 3227 |
amount=float(receipt_data['amount']),
|
| 3228 |
+
mcc=mcc
|
| 3229 |
)
|
| 3230 |
|
| 3231 |
if rec_result.get('success'):
|
| 3232 |
data = normalize_recommendation_data(rec_result.get('data', {}))
|
| 3233 |
|
| 3234 |
+
# β
FIX: Generate alternatives if not provided
|
| 3235 |
+
alternatives = data.get('alternatives', [])
|
| 3236 |
+
|
| 3237 |
+
if not alternatives or len(alternatives) < 2:
|
| 3238 |
+
# Manually create alternatives based on category
|
| 3239 |
+
alternatives = generate_card_alternatives(
|
| 3240 |
+
category=category,
|
| 3241 |
+
amount=float(receipt_data['amount']),
|
| 3242 |
+
primary_card=data['recommended_card']
|
| 3243 |
+
)
|
| 3244 |
+
|
| 3245 |
+
# Add context for special merchants
|
| 3246 |
context_note = ""
|
| 3247 |
+
merchant_lower = receipt_data['merchant'].lower()
|
| 3248 |
+
|
| 3249 |
+
if "costco" in merchant_lower:
|
| 3250 |
context_note = """
|
| 3251 |
+
---
|
| 3252 |
+
### π‘ Costco Shopping Tip
|
| 3253 |
+
**Accepted Payment:** Costco only accepts **Visa cards** at warehouse locations. Amex and Mastercard are not accepted.
|
| 3254 |
+
|
| 3255 |
+
**Best Card:** Costco Anywhere Visa Card offers **2% cashback** at Costco and Costco.com.
|
| 3256 |
+
|
| 3257 |
+
---
|
| 3258 |
+
"""
|
| 3259 |
+
elif "whole foods" in merchant_lower or "amazon" in merchant_lower:
|
| 3260 |
+
context_note = """
|
| 3261 |
+
---
|
| 3262 |
+
### π‘ Whole Foods Tip
|
| 3263 |
+
**Amazon Prime Members:** Get an extra **10% off** sale items at Whole Foods with Prime membership.
|
| 3264 |
+
|
| 3265 |
+
**Best Card:** Amazon Prime Visa offers **5% cashback** at Whole Foods for Prime members.
|
| 3266 |
+
|
| 3267 |
+
---
|
| 3268 |
+
"""
|
| 3269 |
|
| 3270 |
+
# Build output
|
| 3271 |
output = f"""## πΈ Receipt Analysis
|
| 3272 |
|
| 3273 |
### π§Ύ Extracted Information
|
| 3274 |
- **Merchant:** {receipt_data['merchant']}
|
| 3275 |
- **Amount:** ${receipt_data['amount']:.2f}
|
| 3276 |
- **Date:** {receipt_data['date']}
|
| 3277 |
+
- **Category:** {receipt_data['category']}
|
| 3278 |
|
| 3279 |
**Items Purchased:**
|
| 3280 |
"""
|
| 3281 |
for item in receipt_data.get('items', []):
|
| 3282 |
output += f"- {item}\n"
|
| 3283 |
|
| 3284 |
+
output += f"\n{context_note}\n"
|
| 3285 |
+
|
| 3286 |
output += f"""
|
| 3287 |
+
### π³ Best Card for This Purchase
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3288 |
|
| 3289 |
+
**π {data['recommended_card']}**
|
| 3290 |
|
| 3291 |
+
- **Rewards Earned:** ${data['rewards_earned']:.2f}
|
| 3292 |
+
- **Rewards Rate:** {data['rewards_rate']}
|
| 3293 |
- **Reasoning:** {data['reasoning']}
|
| 3294 |
+
- **Annual Potential:** ${data['annual_potential']:.2f}/year (based on similar spending)
|
| 3295 |
+
|
| 3296 |
+
"""
|
| 3297 |
+
|
| 3298 |
+
# β
Always show alternatives
|
| 3299 |
+
if alternatives and len(alternatives) > 0:
|
| 3300 |
+
output += """
|
| 3301 |
+
### π Other Card Options
|
| 3302 |
|
| 3303 |
"""
|
| 3304 |
+
for i, alt in enumerate(alternatives[:3], 1):
|
| 3305 |
+
card_name = alt.get('card', 'Unknown Card')
|
| 3306 |
+
rewards = alt.get('rewards', 0)
|
| 3307 |
+
rate = alt.get('rate', '0%')
|
| 3308 |
+
|
| 3309 |
+
output += f"{i}. **{card_name}**\n"
|
| 3310 |
+
output += f" - Rewards: ${rewards:.2f} ({rate})\n"
|
| 3311 |
+
if 'note' in alt:
|
| 3312 |
+
output += f" - Note: {alt['note']}\n"
|
| 3313 |
+
output += "\n"
|
| 3314 |
|
| 3315 |
+
# Add warnings
|
| 3316 |
+
if data.get('warnings'):
|
| 3317 |
+
output += "\n### β οΈ Important Notices\n\n"
|
| 3318 |
for warning in data['warnings']:
|
| 3319 |
output += f"- {warning}\n"
|
| 3320 |
|
| 3321 |
+
# Create comparison chart
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3322 |
chart = create_rewards_comparison_chart(data)
|
| 3323 |
|
| 3324 |
return output, chart
|
| 3325 |
|
| 3326 |
else:
|
| 3327 |
+
return f"β
Receipt scanned!\n\n```json\n{json.dumps(receipt_data, indent=2)}\n```\n\nβ Could not get card recommendation.", None
|
| 3328 |
|
| 3329 |
except Exception as e:
|
| 3330 |
error_details = traceback.format_exc()
|