Jadjoueidi commited on
Commit
a3ebc4b
ยท
verified ยท
1 Parent(s): ad03a6c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +289 -42
app.py CHANGED
@@ -125,7 +125,11 @@ def run_pipeline(
125
  "Low": -0.08,
126
  "Medium": 0.00,
127
  "High": 0.08,
128
- "Peak": 0.12
 
 
 
 
129
  }.get(str(season), 0.00)
130
 
131
  price_penalty = max(min(price_gap_pct / 100, 0.35), -0.35) * 0.30
@@ -151,26 +155,32 @@ def run_pipeline(
151
 
152
  if demand_score >= 70:
153
  demand_level = "High"
 
154
  elif demand_score >= 45:
155
  demand_level = "Medium"
 
156
  else:
157
  demand_level = "Low"
 
158
 
159
  if price_gap_pct > 15 and demand_level != "High":
160
  pricing_recommendation = "Consider lowering price"
161
  suggested_price = round(competitor_avg_price * 1.05, 2)
162
  insight = "The listing appears overpriced compared with similar properties."
163
  next_step = f"Test a lower price around ${suggested_price} to improve occupancy."
 
164
  elif price_gap_pct < -10 and demand_level in ["Medium", "High"]:
165
  pricing_recommendation = "Consider raising price"
166
  suggested_price = round(min(competitor_avg_price * 0.98, price * 1.12), 2)
167
  insight = "The listing appears underpriced relative to comparable demand."
168
  next_step = f"Consider increasing the price toward ${suggested_price}."
 
169
  else:
170
  pricing_recommendation = "Keep price stable"
171
  suggested_price = round(price, 2)
172
  insight = "The current price is aligned with comparable listings."
173
  next_step = "Keep price stable and focus on visibility, reviews, and conversion."
 
174
 
175
  opportunity_score = round(
176
  demand_score * 0.45 +
@@ -211,38 +221,65 @@ def run_pipeline(
211
  }
212
 
213
  result_text = f"""
214
- # StayWise AI Pipeline Result
215
-
216
- ## Recommendation
217
- **{pricing_recommendation}**
218
-
219
- ## Key Metrics
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
- - Current price: **${price:,.2f}**
222
- - Suggested price: **${suggested_price:,.2f}**
223
- - Competitor average price: **${competitor_avg_price:,.2f}**
224
- - Price vs competitors: **{price_gap_pct:.2f}%**
225
- - Estimated occupancy: **{occupancy * 100:.1f}%**
226
- - Estimated booked nights per month: **{booked_nights}**
227
- - Estimated monthly revenue: **${monthly_revenue:,.2f}**
228
- - Demand score: **{demand_score}/100**
229
- - Demand level: **{demand_level}**
230
- - Opportunity score: **{opportunity_score}/100**
231
 
232
- ## Business Insight
233
- {insight}
234
 
235
- ## Next Step
236
- {next_step}
237
- """
238
 
239
- automation_text = f"""
240
- # n8n Automation Output
 
 
 
241
 
242
- - Status: **{n8n_response.get("status", "unknown")}**
243
- - Insight: {n8n_response.get("insight", "No insight returned.")}
244
- - Next step: {n8n_response.get("next_step", "No next step returned.")}
245
- - Log: {n8n_response.get("log", "No log returned.")}
246
  """
247
 
248
  cols = [
@@ -267,20 +304,230 @@ def run_pipeline(
267
  return result_text, automation_text, comparable_table, json_output
268
 
269
 
270
- with gr.Blocks() as demo:
271
- gr.Markdown(
272
- """
273
- # ๐Ÿ  StayWise AI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
- **AI-powered pricing and performance optimization for short-term rentals.**
276
 
277
- Enter listing details, run the full pipeline, compare with similar listings, and optionally send the result to n8n.
 
 
 
 
 
 
 
 
278
  """
279
  )
280
 
281
  with gr.Row():
282
- with gr.Column():
283
- gr.Markdown("## Property Inputs")
284
 
285
  neighbourhood_group = gr.Dropdown(
286
  choices=neighbourhood_groups,
@@ -351,16 +598,16 @@ with gr.Blocks() as demo:
351
  value=False
352
  )
353
 
354
- run_button = gr.Button("Run Full Pipeline")
355
 
356
- with gr.Column():
357
- result_output = gr.Markdown()
358
- automation_output = gr.Markdown()
359
 
360
- gr.Markdown("## Comparable Listings")
361
  comparable_output = gr.Dataframe()
362
 
363
- gr.Markdown("## JSON Pipeline Output")
364
  json_output = gr.Code(language="json")
365
 
366
  run_button.click(
 
125
  "Low": -0.08,
126
  "Medium": 0.00,
127
  "High": 0.08,
128
+ "Peak": 0.12,
129
+ "Spring": 0.03,
130
+ "Summer": 0.08,
131
+ "Autumn": 0.00,
132
+ "Winter": -0.05
133
  }.get(str(season), 0.00)
134
 
135
  price_penalty = max(min(price_gap_pct / 100, 0.35), -0.35) * 0.30
 
155
 
156
  if demand_score >= 70:
157
  demand_level = "High"
158
+ demand_badge = "๐ŸŸข High"
159
  elif demand_score >= 45:
160
  demand_level = "Medium"
161
+ demand_badge = "๐ŸŸก Medium"
162
  else:
163
  demand_level = "Low"
164
+ demand_badge = "๐Ÿ”ด Low"
165
 
166
  if price_gap_pct > 15 and demand_level != "High":
167
  pricing_recommendation = "Consider lowering price"
168
  suggested_price = round(competitor_avg_price * 1.05, 2)
169
  insight = "The listing appears overpriced compared with similar properties."
170
  next_step = f"Test a lower price around ${suggested_price} to improve occupancy."
171
+ recommendation_badge = "๐Ÿ”ป Price Reduction Suggested"
172
  elif price_gap_pct < -10 and demand_level in ["Medium", "High"]:
173
  pricing_recommendation = "Consider raising price"
174
  suggested_price = round(min(competitor_avg_price * 0.98, price * 1.12), 2)
175
  insight = "The listing appears underpriced relative to comparable demand."
176
  next_step = f"Consider increasing the price toward ${suggested_price}."
177
+ recommendation_badge = "๐Ÿš€ Revenue Opportunity"
178
  else:
179
  pricing_recommendation = "Keep price stable"
180
  suggested_price = round(price, 2)
181
  insight = "The current price is aligned with comparable listings."
182
  next_step = "Keep price stable and focus on visibility, reviews, and conversion."
183
+ recommendation_badge = "โœ… Stable Positioning"
184
 
185
  opportunity_score = round(
186
  demand_score * 0.45 +
 
221
  }
222
 
223
  result_text = f"""
224
+ <div class="result-card">
225
+
226
+ <div class="section-label">Pipeline Result</div>
227
+
228
+ <h2>{recommendation_badge}</h2>
229
+
230
+ <div class="kpi-grid">
231
+ <div class="kpi-card">
232
+ <div class="kpi-title">Current Price</div>
233
+ <div class="kpi-value">${price:,.0f}</div>
234
+ </div>
235
+ <div class="kpi-card">
236
+ <div class="kpi-title">Suggested Price</div>
237
+ <div class="kpi-value">${suggested_price:,.0f}</div>
238
+ </div>
239
+ <div class="kpi-card">
240
+ <div class="kpi-title">Monthly Revenue</div>
241
+ <div class="kpi-value">${monthly_revenue:,.0f}</div>
242
+ </div>
243
+ <div class="kpi-card">
244
+ <div class="kpi-title">Demand</div>
245
+ <div class="kpi-value">{demand_badge}</div>
246
+ </div>
247
+ </div>
248
+
249
+ <h3>Key Metrics</h3>
250
+
251
+ <table class="metric-table">
252
+ <tr><td>Competitor average price</td><td>${competitor_avg_price:,.2f}</td></tr>
253
+ <tr><td>Price vs competitors</td><td>{price_gap_pct:.2f}%</td></tr>
254
+ <tr><td>Estimated occupancy</td><td>{occupancy * 100:.1f}%</td></tr>
255
+ <tr><td>Estimated booked nights / month</td><td>{booked_nights}</td></tr>
256
+ <tr><td>Demand score</td><td>{demand_score}/100</td></tr>
257
+ <tr><td>Opportunity score</td><td>{opportunity_score}/100</td></tr>
258
+ </table>
259
+
260
+ <h3>Business Insight</h3>
261
+ <p>{insight}</p>
262
+
263
+ <h3>Next Step</h3>
264
+ <p>{next_step}</p>
265
+
266
+ </div>
267
+ """
268
 
269
+ automation_text = f"""
270
+ <div class="automation-card">
 
 
 
 
 
 
 
 
271
 
272
+ <div class="section-label">n8n Automation Output</div>
 
273
 
274
+ <h2>Automation Status: {n8n_response.get("status", "unknown")}</h2>
 
 
275
 
276
+ <table class="metric-table">
277
+ <tr><td>Insight</td><td>{n8n_response.get("insight", "No insight returned.")}</td></tr>
278
+ <tr><td>Next step</td><td>{n8n_response.get("next_step", "No next step returned.")}</td></tr>
279
+ <tr><td>Log</td><td>{n8n_response.get("log", "No log returned.")}</td></tr>
280
+ </table>
281
 
282
+ </div>
 
 
 
283
  """
284
 
285
  cols = [
 
304
  return result_text, automation_text, comparable_table, json_output
305
 
306
 
307
+ custom_css = """
308
+ /* Main background */
309
+ body {
310
+ background: radial-gradient(circle at top left, #1e3a8a 0%, #0f172a 35%, #020617 100%) !important;
311
+ color: #f8fafc !important;
312
+ }
313
+
314
+ /* Main container */
315
+ .gradio-container {
316
+ max-width: 1250px !important;
317
+ margin: auto !important;
318
+ font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, sans-serif !important;
319
+ }
320
+
321
+ /* Header */
322
+ #hero {
323
+ background: linear-gradient(135deg, rgba(37, 99, 235, 0.95), rgba(14, 165, 233, 0.85));
324
+ border-radius: 28px;
325
+ padding: 38px 42px;
326
+ margin-bottom: 26px;
327
+ box-shadow: 0 25px 60px rgba(0, 0, 0, 0.35);
328
+ border: 1px solid rgba(255,255,255,0.16);
329
+ }
330
+
331
+ #hero h1 {
332
+ font-size: 48px;
333
+ margin: 0 0 8px 0;
334
+ color: white;
335
+ letter-spacing: -1px;
336
+ }
337
+
338
+ #hero p {
339
+ font-size: 18px;
340
+ margin: 6px 0 0 0;
341
+ color: #e0f2fe;
342
+ }
343
+
344
+ .hero-badge {
345
+ display: inline-block;
346
+ padding: 6px 12px;
347
+ background: rgba(255,255,255,0.16);
348
+ border: 1px solid rgba(255,255,255,0.22);
349
+ border-radius: 999px;
350
+ font-size: 13px;
351
+ margin-bottom: 14px;
352
+ color: white;
353
+ }
354
+
355
+ /* Panels */
356
+ .gr-block, .gr-form, .gr-box, .gr-panel {
357
+ border-radius: 20px !important;
358
+ }
359
+
360
+ .input-panel {
361
+ background: rgba(15, 23, 42, 0.82);
362
+ border: 1px solid rgba(148, 163, 184, 0.25);
363
+ border-radius: 24px;
364
+ padding: 20px;
365
+ box-shadow: 0 15px 45px rgba(0,0,0,0.25);
366
+ }
367
+
368
+ /* Buttons */
369
+ button.primary, .gr-button {
370
+ border-radius: 14px !important;
371
+ font-weight: 700 !important;
372
+ background: linear-gradient(90deg, #2563eb, #06b6d4) !important;
373
+ border: none !important;
374
+ color: white !important;
375
+ box-shadow: 0 10px 25px rgba(37, 99, 235, 0.32) !important;
376
+ }
377
+
378
+ button.primary:hover, .gr-button:hover {
379
+ transform: translateY(-1px);
380
+ filter: brightness(1.08);
381
+ }
382
+
383
+ /* Labels */
384
+ label, .wrap label {
385
+ color: #dbeafe !important;
386
+ font-weight: 600 !important;
387
+ }
388
+
389
+ /* Inputs */
390
+ input, textarea, select {
391
+ border-radius: 12px !important;
392
+ }
393
+
394
+ /* Slider */
395
+ input[type="range"] {
396
+ accent-color: #38bdf8 !important;
397
+ }
398
+
399
+ /* Output cards */
400
+ .result-card, .automation-card {
401
+ background: rgba(15, 23, 42, 0.88);
402
+ border: 1px solid rgba(148, 163, 184, 0.25);
403
+ border-radius: 26px;
404
+ padding: 26px;
405
+ margin-bottom: 22px;
406
+ box-shadow: 0 18px 50px rgba(0,0,0,0.28);
407
+ }
408
+
409
+ .result-card h2, .automation-card h2 {
410
+ margin-top: 8px;
411
+ color: #f8fafc;
412
+ font-size: 26px;
413
+ }
414
+
415
+ .result-card h3, .automation-card h3 {
416
+ margin-top: 24px;
417
+ color: #bae6fd;
418
+ font-size: 20px;
419
+ }
420
+
421
+ .result-card p, .automation-card p {
422
+ color: #e5e7eb;
423
+ line-height: 1.6;
424
+ }
425
+
426
+ .section-label {
427
+ display: inline-block;
428
+ background: rgba(56, 189, 248, 0.13);
429
+ border: 1px solid rgba(56, 189, 248, 0.35);
430
+ color: #7dd3fc;
431
+ padding: 7px 13px;
432
+ border-radius: 999px;
433
+ font-size: 13px;
434
+ font-weight: 700;
435
+ letter-spacing: 0.4px;
436
+ text-transform: uppercase;
437
+ }
438
+
439
+ /* KPI Cards */
440
+ .kpi-grid {
441
+ display: grid;
442
+ grid-template-columns: repeat(4, minmax(120px, 1fr));
443
+ gap: 14px;
444
+ margin: 22px 0;
445
+ }
446
+
447
+ .kpi-card {
448
+ background: linear-gradient(180deg, rgba(30, 41, 59, 0.95), rgba(15, 23, 42, 0.95));
449
+ border: 1px solid rgba(148, 163, 184, 0.25);
450
+ border-radius: 18px;
451
+ padding: 18px;
452
+ }
453
+
454
+ .kpi-title {
455
+ color: #94a3b8;
456
+ font-size: 13px;
457
+ margin-bottom: 8px;
458
+ }
459
+
460
+ .kpi-value {
461
+ color: #f8fafc;
462
+ font-size: 22px;
463
+ font-weight: 800;
464
+ }
465
+
466
+ /* Tables */
467
+ .metric-table {
468
+ width: 100%;
469
+ border-collapse: collapse;
470
+ overflow: hidden;
471
+ border-radius: 14px;
472
+ margin-top: 12px;
473
+ }
474
+
475
+ .metric-table td {
476
+ padding: 12px 14px;
477
+ border-bottom: 1px solid rgba(148, 163, 184, 0.18);
478
+ color: #e5e7eb;
479
+ }
480
+
481
+ .metric-table td:first-child {
482
+ color: #93c5fd;
483
+ font-weight: 650;
484
+ width: 45%;
485
+ }
486
+
487
+ .metric-table tr:last-child td {
488
+ border-bottom: none;
489
+ }
490
+
491
+ /* Dataframe and code areas */
492
+ .dataframe, .wrap, .contain {
493
+ border-radius: 18px !important;
494
+ }
495
+
496
+ /* Footer text */
497
+ .small-note {
498
+ color: #94a3b8;
499
+ font-size: 13px;
500
+ margin-top: -8px;
501
+ margin-bottom: 18px;
502
+ }
503
+
504
+ @media (max-width: 900px) {
505
+ .kpi-grid {
506
+ grid-template-columns: repeat(2, minmax(120px, 1fr));
507
+ }
508
+
509
+ #hero h1 {
510
+ font-size: 36px;
511
+ }
512
+ }
513
+ """
514
 
 
515
 
516
+ with gr.Blocks(css=custom_css) as demo:
517
+ gr.HTML(
518
+ """
519
+ <div id="hero">
520
+ <div class="hero-badge">Short-Term Rental Pricing Assistant</div>
521
+ <h1>๐Ÿ  StayWise AI</h1>
522
+ <p>AI-powered pricing and performance optimization for short-term rentals.</p>
523
+ <p class="small-note">Run the full pipeline, benchmark a listing against comparable properties, and return automation insights from n8n.</p>
524
+ </div>
525
  """
526
  )
527
 
528
  with gr.Row():
529
+ with gr.Column(scale=1):
530
+ gr.Markdown("## ๐Ÿ“Š Property Inputs")
531
 
532
  neighbourhood_group = gr.Dropdown(
533
  choices=neighbourhood_groups,
 
598
  value=False
599
  )
600
 
601
+ run_button = gr.Button("๐Ÿš€ Run Full Pipeline", variant="primary")
602
 
603
+ with gr.Column(scale=2):
604
+ result_output = gr.HTML()
605
+ automation_output = gr.HTML()
606
 
607
+ gr.Markdown("## ๐Ÿ“ˆ Comparable Listings")
608
  comparable_output = gr.Dataframe()
609
 
610
+ gr.Markdown("## ๐Ÿงพ Pipeline Data Output")
611
  json_output = gr.Code(language="json")
612
 
613
  run_button.click(