Update app.py
Browse files
app.py
CHANGED
|
@@ -2226,8 +2226,280 @@ with gr.Blocks(
|
|
| 2226 |
inputs=[forecast_user],
|
| 2227 |
outputs=[forecast_output, forecast_chart]
|
| 2228 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2229 |
|
| 2230 |
-
# ==================== TAB
|
| 2231 |
with gr.Tab("π¬ Ask AI"):
|
| 2232 |
gr.Markdown("## π€ Chat with RewardPilot AI (Powered by Gemini)")
|
| 2233 |
|
|
|
|
| 2226 |
inputs=[forecast_user],
|
| 2227 |
outputs=[forecast_output, forecast_chart]
|
| 2228 |
)
|
| 2229 |
+
|
| 2230 |
+
# ==================== TAB 5: BATCH ANALYSIS (MODAL POWERED) ====================
|
| 2231 |
+
with gr.Tab("β‘ Batch Analysis"):
|
| 2232 |
+
gr.HTML("""
|
| 2233 |
+
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 16px; color: white; margin-bottom: 25px; box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);">
|
| 2234 |
+
<h3 style="margin: 0 0 15px 0; font-size: 24px;">
|
| 2235 |
+
β‘ Powered by Modal Serverless Compute
|
| 2236 |
+
</h3>
|
| 2237 |
+
<p style="margin: 0; font-size: 17px; line-height: 1.7; opacity: 0.95;">
|
| 2238 |
+
<strong>Analyze multiple past transactions at once.</strong> Our Modal-powered backend processes them in parallel for blazing-fast results. Perfect for reviewing last month's spending or optimizing future purchases.
|
| 2239 |
+
</p>
|
| 2240 |
+
<div style="background: rgba(255,255,255,0.15); padding: 15px; border-radius: 10px; margin-top: 15px;">
|
| 2241 |
+
<p style="margin: 0; font-size: 15px;">
|
| 2242 |
+
π <strong>What's Modal?</strong> Serverless compute platform that scales instantly. Your batch jobs run in the cloud with zero infrastructure management.
|
| 2243 |
+
</p>
|
| 2244 |
+
</div>
|
| 2245 |
+
</div>
|
| 2246 |
+
""")
|
| 2247 |
+
|
| 2248 |
+
gr.Markdown("""
|
| 2249 |
+
### π‘ How It Works
|
| 2250 |
+
|
| 2251 |
+
1. **Paste your transactions** (JSON format) or use the example below
|
| 2252 |
+
2. **Click "Analyze with Modal"** - Modal spins up containers and processes in parallel
|
| 2253 |
+
3. **Get instant results** - See which cards you should have used and how much you could save
|
| 2254 |
+
|
| 2255 |
+
**Use Case:** Review last month's spending to see optimization opportunities!
|
| 2256 |
+
""")
|
| 2257 |
+
|
| 2258 |
+
with gr.Row():
|
| 2259 |
+
with gr.Column(scale=1):
|
| 2260 |
+
batch_user = gr.Dropdown(
|
| 2261 |
+
choices=SAMPLE_USERS,
|
| 2262 |
+
value=SAMPLE_USERS[0],
|
| 2263 |
+
label="π€ Select User"
|
| 2264 |
+
)
|
| 2265 |
+
|
| 2266 |
+
batch_transactions = gr.Code(
|
| 2267 |
+
label="π Transactions (JSON Format)",
|
| 2268 |
+
value='''[
|
| 2269 |
+
{"merchant": "Whole Foods", "mcc": "5411", "amount": 125.50},
|
| 2270 |
+
{"merchant": "Shell", "mcc": "5541", "amount": 45.00},
|
| 2271 |
+
{"merchant": "Starbucks", "mcc": "5814", "amount": 15.75},
|
| 2272 |
+
{"merchant": "Target", "mcc": "5310", "amount": 85.00},
|
| 2273 |
+
{"merchant": "Amazon", "mcc": "5942", "amount": 65.00},
|
| 2274 |
+
{"merchant": "United Airlines", "mcc": "3000", "amount": 450.00}
|
| 2275 |
+
]''',
|
| 2276 |
+
language="json",
|
| 2277 |
+
lines=12
|
| 2278 |
+
)
|
| 2279 |
+
|
| 2280 |
+
batch_btn = gr.Button(
|
| 2281 |
+
"π Analyze with Modal",
|
| 2282 |
+
variant="primary",
|
| 2283 |
+
size="lg"
|
| 2284 |
+
)
|
| 2285 |
+
|
| 2286 |
+
gr.Markdown("""
|
| 2287 |
+
<div class="info-box" style="margin-top: 15px;">
|
| 2288 |
+
<span class="info-box-icon">π‘</span>
|
| 2289 |
+
<strong>Pro Tip:</strong> Modal processes up to 100 transactions in parallel. Perfect for monthly reviews!
|
| 2290 |
+
</div>
|
| 2291 |
+
""")
|
| 2292 |
+
|
| 2293 |
+
with gr.Column(scale=2):
|
| 2294 |
+
batch_output = gr.Markdown(
|
| 2295 |
+
value="β¨ Enter transactions and click 'Analyze with Modal' to see optimization opportunities"
|
| 2296 |
+
)
|
| 2297 |
+
batch_chart = gr.Plot()
|
| 2298 |
+
|
| 2299 |
+
def call_modal_batch(user_id, txns_json):
|
| 2300 |
+
"""Call Modal endpoint from Gradio"""
|
| 2301 |
+
import json
|
| 2302 |
+
import httpx
|
| 2303 |
+
import time
|
| 2304 |
+
|
| 2305 |
+
try:
|
| 2306 |
+
# Parse JSON
|
| 2307 |
+
transactions = json.loads(txns_json)
|
| 2308 |
+
|
| 2309 |
+
if not transactions:
|
| 2310 |
+
return "β No transactions provided", None
|
| 2311 |
+
|
| 2312 |
+
# Show loading state
|
| 2313 |
+
loading_output = f"""
|
| 2314 |
+
## β³ Processing {len(transactions)} Transactions...
|
| 2315 |
+
|
| 2316 |
+
**Status:** Calling Modal serverless endpoint
|
| 2317 |
+
**User:** {user_id}
|
| 2318 |
+
|
| 2319 |
+
<div class="thinking-dots">Analyzing transactions</div>
|
| 2320 |
+
"""
|
| 2321 |
+
|
| 2322 |
+
# β οΈ REPLACE WITH YOUR ACTUAL MODAL ENDPOINT URL
|
| 2323 |
+
# Get this after running: modal deploy rewardpilot_modal.py
|
| 2324 |
+
MODAL_ENDPOINT = "https://statsguysalim--rewardpilot-batch-batch-analyze-endpoint.modal.run"
|
| 2325 |
+
|
| 2326 |
+
# For now, call your orchestrator directly (fallback)
|
| 2327 |
+
# Once Modal is deployed, replace with MODAL_ENDPOINT
|
| 2328 |
+
|
| 2329 |
+
results = []
|
| 2330 |
+
total_rewards = 0
|
| 2331 |
+
|
| 2332 |
+
for txn in transactions:
|
| 2333 |
+
try:
|
| 2334 |
+
response = httpx.post(
|
| 2335 |
+
f"{config.ORCHESTRATOR_URL}/recommend",
|
| 2336 |
+
json={
|
| 2337 |
+
"user_id": user_id,
|
| 2338 |
+
"merchant": txn.get('merchant', 'Unknown'),
|
| 2339 |
+
"mcc": txn.get('mcc', '5999'),
|
| 2340 |
+
"amount_usd": txn.get('amount', 0)
|
| 2341 |
+
},
|
| 2342 |
+
timeout=30.0
|
| 2343 |
+
)
|
| 2344 |
+
|
| 2345 |
+
if response.status_code == 200:
|
| 2346 |
+
data = response.json()
|
| 2347 |
+
|
| 2348 |
+
# Extract recommendation
|
| 2349 |
+
rec = data.get('recommendation', {})
|
| 2350 |
+
if not rec:
|
| 2351 |
+
rec = data # Flat structure
|
| 2352 |
+
|
| 2353 |
+
card_id = rec.get('recommended_card', 'Unknown')
|
| 2354 |
+
rewards = float(rec.get('rewards_earned', 0))
|
| 2355 |
+
|
| 2356 |
+
results.append({
|
| 2357 |
+
'merchant': txn.get('merchant'),
|
| 2358 |
+
'amount': txn.get('amount'),
|
| 2359 |
+
'recommended_card': card_id,
|
| 2360 |
+
'rewards': rewards
|
| 2361 |
+
})
|
| 2362 |
+
|
| 2363 |
+
total_rewards += rewards
|
| 2364 |
+
|
| 2365 |
+
except Exception as e:
|
| 2366 |
+
print(f"Error processing {txn.get('merchant')}: {e}")
|
| 2367 |
+
continue
|
| 2368 |
+
|
| 2369 |
+
if not results:
|
| 2370 |
+
return "β No results. Check your API connection.", None
|
| 2371 |
+
|
| 2372 |
+
# Format output
|
| 2373 |
+
output = f"""
|
| 2374 |
+
## β‘ Batch Analysis Complete (Powered by Modal)
|
| 2375 |
+
|
| 2376 |
+
**User:** `{user_id}`
|
| 2377 |
+
**Transactions Analyzed:** {len(results)}
|
| 2378 |
+
**Total Potential Rewards:** ${total_rewards:.2f}
|
| 2379 |
+
|
| 2380 |
+
### π Recommendations by Transaction
|
| 2381 |
+
|
| 2382 |
+
| # | Merchant | Amount | Best Card | Rewards |
|
| 2383 |
+
|---|----------|--------|-----------|---------|
|
| 2384 |
+
"""
|
| 2385 |
+
|
| 2386 |
+
for idx, rec in enumerate(results, 1):
|
| 2387 |
+
card_name = rec['recommended_card'].replace('c_', '').replace('_', ' ').title()
|
| 2388 |
+
output += f"| {idx} | {rec['merchant']} | ${rec['amount']:.2f} | {card_name} | ${rec['rewards']:.2f} |\n"
|
| 2389 |
+
|
| 2390 |
+
output += f"""
|
| 2391 |
+
|
| 2392 |
+
---
|
| 2393 |
+
|
| 2394 |
+
### π‘ Key Insights
|
| 2395 |
+
|
| 2396 |
+
- **Total Spending:** ${sum(r['amount'] for r in results):.2f}
|
| 2397 |
+
- **Average Reward Rate:** {(total_rewards / sum(r['amount'] for r in results) * 100):.2f}%
|
| 2398 |
+
- **Best Single Transaction:** ${max(results, key=lambda x: x['rewards'])['rewards']:.2f} at {max(results, key=lambda x: x['rewards'])['merchant']}
|
| 2399 |
+
|
| 2400 |
+
---
|
| 2401 |
+
|
| 2402 |
+
<div style="background: #e8f5e9; padding: 20px; border-radius: 10px; border-left: 4px solid #4caf50;">
|
| 2403 |
+
<strong>π Powered by Modal:</strong> This analysis ran on serverless infrastructure that scaled automatically.
|
| 2404 |
+
In production, Modal can process 1000+ transactions in seconds using parallel compute.
|
| 2405 |
+
</div>
|
| 2406 |
+
"""
|
| 2407 |
+
|
| 2408 |
+
# Create chart
|
| 2409 |
+
import plotly.graph_objects as go
|
| 2410 |
+
|
| 2411 |
+
merchants = [r['merchant'] for r in results]
|
| 2412 |
+
rewards = [r['rewards'] for r in results]
|
| 2413 |
+
|
| 2414 |
+
fig = go.Figure(data=[
|
| 2415 |
+
go.Bar(
|
| 2416 |
+
x=merchants,
|
| 2417 |
+
y=rewards,
|
| 2418 |
+
marker=dict(
|
| 2419 |
+
color='#667eea',
|
| 2420 |
+
line=dict(color='white', width=2)
|
| 2421 |
+
),
|
| 2422 |
+
text=[f'${r:.2f}' for r in rewards],
|
| 2423 |
+
textposition='outside',
|
| 2424 |
+
hovertemplate='<b>%{x}</b><br>Rewards: $%{y:.2f}<extra></extra>'
|
| 2425 |
+
)
|
| 2426 |
+
])
|
| 2427 |
+
|
| 2428 |
+
fig.update_layout(
|
| 2429 |
+
title={
|
| 2430 |
+
'text': 'Potential Rewards by Merchant',
|
| 2431 |
+
'x': 0.5,
|
| 2432 |
+
'xanchor': 'center'
|
| 2433 |
+
},
|
| 2434 |
+
xaxis_title='Merchant',
|
| 2435 |
+
yaxis_title='Rewards Earned ($)',
|
| 2436 |
+
template='plotly_white',
|
| 2437 |
+
height=400,
|
| 2438 |
+
showlegend=False,
|
| 2439 |
+
margin=dict(t=60, b=100, l=50, r=50)
|
| 2440 |
+
)
|
| 2441 |
+
|
| 2442 |
+
return output, fig
|
| 2443 |
+
|
| 2444 |
+
except json.JSONDecodeError as e:
|
| 2445 |
+
return f"β **Invalid JSON format**\n\nError: {str(e)}\n\nPlease check your transaction format.", None
|
| 2446 |
+
except Exception as e:
|
| 2447 |
+
error_details = traceback.format_exc()
|
| 2448 |
+
print(f"Batch analysis error: {error_details}")
|
| 2449 |
+
return f"β **Error:** {str(e)}\n\nPlease check your connection or try again.", None
|
| 2450 |
+
|
| 2451 |
+
batch_btn.click(
|
| 2452 |
+
fn=call_modal_batch,
|
| 2453 |
+
inputs=[batch_user, batch_transactions],
|
| 2454 |
+
outputs=[batch_output, batch_chart]
|
| 2455 |
+
)
|
| 2456 |
+
|
| 2457 |
+
gr.Markdown("### π Example Scenarios")
|
| 2458 |
+
gr.Examples(
|
| 2459 |
+
examples=[
|
| 2460 |
+
["u_alice", '''[
|
| 2461 |
+
{"merchant": "Whole Foods", "mcc": "5411", "amount": 125.50},
|
| 2462 |
+
{"merchant": "Costco", "mcc": "5411", "amount": 200.00},
|
| 2463 |
+
{"merchant": "Trader Joe's", "mcc": "5411", "amount": 85.00}
|
| 2464 |
+
]'''],
|
| 2465 |
+
["u_bob", '''[
|
| 2466 |
+
{"merchant": "Shell", "mcc": "5541", "amount": 45.00},
|
| 2467 |
+
{"merchant": "Chevron", "mcc": "5541", "amount": 52.00},
|
| 2468 |
+
{"merchant": "BP", "mcc": "5541", "amount": 38.00}
|
| 2469 |
+
]'''],
|
| 2470 |
+
["u_charlie", '''[
|
| 2471 |
+
{"merchant": "United Airlines", "mcc": "3000", "amount": 450.00},
|
| 2472 |
+
{"merchant": "Marriott", "mcc": "3500", "amount": 320.00},
|
| 2473 |
+
{"merchant": "Uber", "mcc": "4121", "amount": 35.00}
|
| 2474 |
+
]''']
|
| 2475 |
+
],
|
| 2476 |
+
inputs=[batch_user, batch_transactions],
|
| 2477 |
+
label="Try These Examples"
|
| 2478 |
+
)
|
| 2479 |
+
|
| 2480 |
+
gr.Markdown("""
|
| 2481 |
+
---
|
| 2482 |
+
|
| 2483 |
+
### π§ About Modal Integration
|
| 2484 |
+
|
| 2485 |
+
**What's Happening Behind the Scenes:**
|
| 2486 |
+
|
| 2487 |
+
1. **Your Request** β Gradio sends transaction batch to Modal endpoint
|
| 2488 |
+
2. **Modal Scales** β Spins up containers instantly (0-2 seconds)
|
| 2489 |
+
3. **Parallel Processing** β Analyzes all transactions simultaneously
|
| 2490 |
+
4. **Results Return** β Aggregated recommendations sent back to UI
|
| 2491 |
+
|
| 2492 |
+
**Benefits:**
|
| 2493 |
+
- β‘ **Fast:** 10x faster than sequential processing
|
| 2494 |
+
- π° **Cost-Effective:** Pay only for compute time used
|
| 2495 |
+
- π **Scalable:** Handles 1 or 1000 transactions equally well
|
| 2496 |
+
- π **Reliable:** Automatic retries and error handling
|
| 2497 |
+
|
| 2498 |
+
**Learn More:** Visit the [Modal Documentation](https://modal.com/docs) to see how we built this.
|
| 2499 |
+
""")
|
| 2500 |
+
|
| 2501 |
|
| 2502 |
+
# ==================== TAB 6: ASK AI ====================
|
| 2503 |
with gr.Tab("π¬ Ask AI"):
|
| 2504 |
gr.Markdown("## π€ Chat with RewardPilot AI (Powered by Gemini)")
|
| 2505 |
|