Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -153,7 +153,7 @@ def calculate_metrics(df, threshold_minutes, scope="all"):
|
|
| 153 |
"current_problems": current_problems,
|
| 154 |
"problems_solved": problems_solved,
|
| 155 |
"solve_efficiency": solve_efficiency,
|
| 156 |
-
"
|
| 157 |
}
|
| 158 |
|
| 159 |
def create_threshold_analysis(df, thresholds, scope="all"):
|
|
@@ -172,7 +172,8 @@ st.markdown('<h1 class="main-header">Getaround Delay Analysis</h1>', unsafe_allo
|
|
| 172 |
|
| 173 |
# Sidebar with controls
|
| 174 |
with st.sidebar:
|
| 175 |
-
|
|
|
|
| 176 |
|
| 177 |
st.markdown("## Analysis Controls")
|
| 178 |
|
|
@@ -186,7 +187,7 @@ with st.sidebar:
|
|
| 186 |
|
| 187 |
if selected == "Threshold & Scope":
|
| 188 |
st.markdown("### Settings")
|
| 189 |
-
threshold = st.slider("Threshold (minutes)", 0, 300,
|
| 190 |
help="Minimum delay between consecutive rentals")
|
| 191 |
scope = st.selectbox("Implementation Scope", ["all", "connect"],
|
| 192 |
format_func=lambda x: "All Cars" if x == "all" else "Connect Cars Only")
|
|
@@ -378,9 +379,9 @@ elif selected == "Threshold & Scope":
|
|
| 378 |
|
| 379 |
st.markdown("""
|
| 380 |
<div class="insight-box">
|
| 381 |
-
<strong>
|
| 382 |
-
(prevented from booking) due to insufficient gap between rentals.
|
| 383 |
-
|
| 384 |
</div>
|
| 385 |
""", unsafe_allow_html=True)
|
| 386 |
|
|
@@ -423,9 +424,9 @@ elif selected == "Threshold & Scope":
|
|
| 423 |
with col4:
|
| 424 |
st.markdown(f"""
|
| 425 |
<div class="metric-card">
|
| 426 |
-
<h3 style="color: #9b59b6;">{current_metrics['
|
| 427 |
-
<p>
|
| 428 |
-
<small>% of
|
| 429 |
</div>
|
| 430 |
""", unsafe_allow_html=True)
|
| 431 |
st.caption("Formula: blocked_rentals / total_rentals * 100")
|
|
@@ -439,7 +440,7 @@ elif selected == "Threshold & Scope":
|
|
| 439 |
# Create dual-axis chart
|
| 440 |
fig = make_subplots(
|
| 441 |
rows=1, cols=2,
|
| 442 |
-
subplot_titles=("Problems Solved vs Blocked Rentals", "Efficiency vs
|
| 443 |
specs=[[{"secondary_y": True}, {"secondary_y": True}]]
|
| 444 |
)
|
| 445 |
|
|
@@ -455,15 +456,15 @@ elif selected == "Threshold & Scope":
|
|
| 455 |
row=1, col=1, secondary_y=True
|
| 456 |
)
|
| 457 |
|
| 458 |
-
# Right chart: Efficiency vs
|
| 459 |
fig.add_trace(
|
| 460 |
go.Scatter(x=threshold_data["threshold"], y=threshold_data["solve_efficiency"],
|
| 461 |
mode="lines+markers", name="Efficiency (%)", line=dict(color="blue")),
|
| 462 |
row=1, col=2, secondary_y=False
|
| 463 |
)
|
| 464 |
fig.add_trace(
|
| 465 |
-
go.Scatter(x=threshold_data["threshold"], y=threshold_data["
|
| 466 |
-
mode="lines+markers", name="
|
| 467 |
row=1, col=2, secondary_y=True
|
| 468 |
)
|
| 469 |
|
|
@@ -496,7 +497,7 @@ elif selected == "Threshold & Scope":
|
|
| 496 |
**Problems Solved:** {all_metrics['problems_solved']:,}
|
| 497 |
**Blocked Rentals:** {all_metrics['blocked_rentals']:,} ({all_metrics['blocked_percentage']:.1f}%)
|
| 498 |
**Efficiency:** {all_metrics['solve_efficiency']:.1f}%
|
| 499 |
-
**
|
| 500 |
""")
|
| 501 |
|
| 502 |
with col2:
|
|
@@ -505,7 +506,7 @@ elif selected == "Threshold & Scope":
|
|
| 505 |
**Problems Solved:** {connect_metrics['problems_solved']:,}
|
| 506 |
**Blocked Rentals:** {connect_metrics['blocked_rentals']:,} ({connect_metrics['blocked_percentage']:.1f}%)
|
| 507 |
**Efficiency:** {connect_metrics['solve_efficiency']:.1f}%
|
| 508 |
-
**
|
| 509 |
""")
|
| 510 |
|
| 511 |
# Scope comparison chart
|
|
@@ -530,90 +531,73 @@ elif selected == "Threshold & Scope":
|
|
| 530 |
st.plotly_chart(fig_scope, use_container_width=True)
|
| 531 |
|
| 532 |
# Recommendations
|
| 533 |
-
st.markdown('<div class="section-header">
|
| 534 |
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
# Normalize both metrics to 0-100 scale for comparison
|
| 548 |
-
viable_thresholds["efficiency_norm"] = viable_thresholds["solve_efficiency"]
|
| 549 |
-
viable_thresholds["revenue_loss_norm"] = viable_thresholds["revenue_loss_percent"]
|
| 550 |
-
|
| 551 |
-
# Find point where efficiency is still high but revenue loss hasn't grown too much
|
| 552 |
-
# Look for threshold where efficiency > 20% and revenue loss < 8%
|
| 553 |
-
good_balance = viable_thresholds[
|
| 554 |
-
(viable_thresholds["efficiency_norm"] >= 20) &
|
| 555 |
-
(viable_thresholds["revenue_loss_norm"] <= 8.0)
|
| 556 |
-
]
|
| 557 |
-
|
| 558 |
-
if len(good_balance) > 0:
|
| 559 |
-
# Among balanced options, pick the one that solves most problems
|
| 560 |
-
optimal = good_balance.sort_values("problems_solved", ascending=False).iloc[0]
|
| 561 |
-
else:
|
| 562 |
-
# Fallback: find best efficiency among viable options
|
| 563 |
-
optimal = viable_thresholds.sort_values("solve_efficiency", ascending=False).iloc[0]
|
| 564 |
-
|
| 565 |
-
optimal_threshold = int(optimal["threshold"])
|
| 566 |
-
else:
|
| 567 |
-
optimal_threshold = 90 # Sensible fallback
|
| 568 |
|
| 569 |
-
#
|
| 570 |
-
|
| 571 |
-
recommended_scope = "Connect Only"
|
| 572 |
-
scope_reason = "Higher efficiency with most problems solved"
|
| 573 |
-
else:
|
| 574 |
-
recommended_scope = "All Cars"
|
| 575 |
-
scope_reason = "Solves more problems overall"
|
| 576 |
|
| 577 |
col1, col2 = st.columns(2)
|
| 578 |
-
|
| 579 |
with col1:
|
| 580 |
-
st.markdown(
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
<small>Logic: Find crossover point where efficiency ≥20% and revenue loss ≤8%</small>
|
| 586 |
-
</div>
|
| 587 |
-
""", unsafe_allow_html=True)
|
| 588 |
|
| 589 |
with col2:
|
| 590 |
-
st.markdown(
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
#
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
summary_data = []
|
| 603 |
-
|
| 604 |
-
for t in summary_thresholds:
|
| 605 |
if t in threshold_data["threshold"].values:
|
| 606 |
row = threshold_data[threshold_data["threshold"] == t].iloc[0]
|
| 607 |
-
|
| 608 |
"Threshold": f"{int(t)} min",
|
|
|
|
| 609 |
"Problems Solved": int(row["problems_solved"]),
|
| 610 |
-
"
|
| 611 |
"Efficiency": f"{row['solve_efficiency']:.1f}%",
|
| 612 |
-
"Revenue
|
| 613 |
})
|
| 614 |
|
| 615 |
-
|
| 616 |
-
st.dataframe(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
|
| 618 |
# ========== FOOTER ==========
|
| 619 |
st.markdown("---")
|
|
|
|
| 153 |
"current_problems": current_problems,
|
| 154 |
"problems_solved": problems_solved,
|
| 155 |
"solve_efficiency": solve_efficiency,
|
| 156 |
+
"availability_impact": revenue_impact # More accurate naming
|
| 157 |
}
|
| 158 |
|
| 159 |
def create_threshold_analysis(df, thresholds, scope="all"):
|
|
|
|
| 172 |
|
| 173 |
# Sidebar with controls
|
| 174 |
with st.sidebar:
|
| 175 |
+
# Logo placeholder - you can add your logo here
|
| 176 |
+
# st.image("logo.png", width=200) # Uncomment and add your logo file
|
| 177 |
|
| 178 |
st.markdown("## Analysis Controls")
|
| 179 |
|
|
|
|
| 187 |
|
| 188 |
if selected == "Threshold & Scope":
|
| 189 |
st.markdown("### Settings")
|
| 190 |
+
threshold = st.slider("Threshold (minutes)", 0, 300, 120, step=30,
|
| 191 |
help="Minimum delay between consecutive rentals")
|
| 192 |
scope = st.selectbox("Implementation Scope", ["all", "connect"],
|
| 193 |
format_func=lambda x: "All Cars" if x == "all" else "Connect Cars Only")
|
|
|
|
| 379 |
|
| 380 |
st.markdown("""
|
| 381 |
<div class="insight-box">
|
| 382 |
+
<strong>Availability Impact Explanation:</strong> This represents the percentage of total rental slots that would be blocked
|
| 383 |
+
(prevented from booking) due to insufficient gap between rentals. This affects inventory availability but doesn't
|
| 384 |
+
directly translate to revenue loss, as demand patterns and pricing vary significantly.
|
| 385 |
</div>
|
| 386 |
""", unsafe_allow_html=True)
|
| 387 |
|
|
|
|
| 424 |
with col4:
|
| 425 |
st.markdown(f"""
|
| 426 |
<div class="metric-card">
|
| 427 |
+
<h3 style="color: #9b59b6;">{current_metrics['availability_impact']:.1f}%</h3>
|
| 428 |
+
<p>Availability Impact</p>
|
| 429 |
+
<small>% of rental slots blocked</small>
|
| 430 |
</div>
|
| 431 |
""", unsafe_allow_html=True)
|
| 432 |
st.caption("Formula: blocked_rentals / total_rentals * 100")
|
|
|
|
| 440 |
# Create dual-axis chart
|
| 441 |
fig = make_subplots(
|
| 442 |
rows=1, cols=2,
|
| 443 |
+
subplot_titles=("Problems Solved vs Blocked Rentals", "Efficiency vs Availability Impact"),
|
| 444 |
specs=[[{"secondary_y": True}, {"secondary_y": True}]]
|
| 445 |
)
|
| 446 |
|
|
|
|
| 456 |
row=1, col=1, secondary_y=True
|
| 457 |
)
|
| 458 |
|
| 459 |
+
# Right chart: Efficiency vs Availability Impact
|
| 460 |
fig.add_trace(
|
| 461 |
go.Scatter(x=threshold_data["threshold"], y=threshold_data["solve_efficiency"],
|
| 462 |
mode="lines+markers", name="Efficiency (%)", line=dict(color="blue")),
|
| 463 |
row=1, col=2, secondary_y=False
|
| 464 |
)
|
| 465 |
fig.add_trace(
|
| 466 |
+
go.Scatter(x=threshold_data["threshold"], y=threshold_data["availability_impact"],
|
| 467 |
+
mode="lines+markers", name="Availability Impact (%)", line=dict(color="orange")),
|
| 468 |
row=1, col=2, secondary_y=True
|
| 469 |
)
|
| 470 |
|
|
|
|
| 497 |
**Problems Solved:** {all_metrics['problems_solved']:,}
|
| 498 |
**Blocked Rentals:** {all_metrics['blocked_rentals']:,} ({all_metrics['blocked_percentage']:.1f}%)
|
| 499 |
**Efficiency:** {all_metrics['solve_efficiency']:.1f}%
|
| 500 |
+
**Availability Impact:** {all_metrics['availability_impact']:.1f}%
|
| 501 |
""")
|
| 502 |
|
| 503 |
with col2:
|
|
|
|
| 506 |
**Problems Solved:** {connect_metrics['problems_solved']:,}
|
| 507 |
**Blocked Rentals:** {connect_metrics['blocked_rentals']:,} ({connect_metrics['blocked_percentage']:.1f}%)
|
| 508 |
**Efficiency:** {connect_metrics['solve_efficiency']:.1f}%
|
| 509 |
+
**Availability Impact:** {connect_metrics['availability_impact']:.1f}%
|
| 510 |
""")
|
| 511 |
|
| 512 |
# Scope comparison chart
|
|
|
|
| 531 |
st.plotly_chart(fig_scope, use_container_width=True)
|
| 532 |
|
| 533 |
# Recommendations
|
| 534 |
+
st.markdown('<div class="section-header">Business Decision Framework</div>', unsafe_allow_html=True)
|
| 535 |
|
| 536 |
+
st.markdown("""
|
| 537 |
+
<div class="insight-box">
|
| 538 |
+
<strong>Key Business Question:</strong> How much availability reduction are you willing to accept to improve customer experience and reduce cancellations?
|
| 539 |
+
<br><br>
|
| 540 |
+
The decision depends on your strategic priorities:
|
| 541 |
+
<ul>
|
| 542 |
+
<li><strong>Customer Experience Focus:</strong> Higher thresholds solve more problems but reduce available booking slots</li>
|
| 543 |
+
<li><strong>Revenue Maximization:</strong> Lower thresholds maintain availability but allow more customer wait times</li>
|
| 544 |
+
<li><strong>Operational Efficiency:</strong> Consider implementation complexity (all cars vs Connect only)</li>
|
| 545 |
+
</ul>
|
| 546 |
+
</div>
|
| 547 |
+
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
|
| 549 |
+
# Show current trade-offs
|
| 550 |
+
st.markdown("### Current Settings Trade-offs")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
|
| 552 |
col1, col2 = st.columns(2)
|
|
|
|
| 553 |
with col1:
|
| 554 |
+
st.markdown("#### Benefits")
|
| 555 |
+
st.write(f"• Eliminates {current_metrics['problems_solved']:,} customer wait situations")
|
| 556 |
+
st.write(f"• Prevents potential cancellations and complaints")
|
| 557 |
+
st.write(f"• Improves customer satisfaction and retention")
|
| 558 |
+
st.write(f"• Achieves {current_metrics['solve_efficiency']:.1f}% efficiency (problems solved per blocked slot)")
|
|
|
|
|
|
|
|
|
|
| 559 |
|
| 560 |
with col2:
|
| 561 |
+
st.markdown("#### Costs")
|
| 562 |
+
st.write(f"• Blocks {current_metrics['blocked_rentals']:,} potential booking opportunities")
|
| 563 |
+
st.write(f"• Reduces available inventory by {current_metrics['availability_impact']:.1f}%")
|
| 564 |
+
st.write(f"• May impact short-term revenue growth")
|
| 565 |
+
st.write(f"• Requires operational changes and monitoring")
|
| 566 |
+
|
| 567 |
+
# Strategic recommendations based on different priorities
|
| 568 |
+
st.markdown("### Strategic Options")
|
| 569 |
+
|
| 570 |
+
# Calculate options for different thresholds
|
| 571 |
+
options_data = []
|
| 572 |
+
for t in [60, 90, 120, 150]:
|
|
|
|
|
|
|
|
|
|
| 573 |
if t in threshold_data["threshold"].values:
|
| 574 |
row = threshold_data[threshold_data["threshold"] == t].iloc[0]
|
| 575 |
+
options_data.append({
|
| 576 |
"Threshold": f"{int(t)} min",
|
| 577 |
+
"Strategy": "Conservative" if t <= 90 else "Balanced" if t <= 120 else "Aggressive",
|
| 578 |
"Problems Solved": int(row["problems_solved"]),
|
| 579 |
+
"Availability Impact": f"{row['availability_impact']:.1f}%",
|
| 580 |
"Efficiency": f"{row['solve_efficiency']:.1f}%",
|
| 581 |
+
"Recommendation": "Revenue-focused" if t <= 90 else "Balanced approach" if t <= 120 else "Customer experience-focused"
|
| 582 |
})
|
| 583 |
|
| 584 |
+
options_df = pd.DataFrame(options_data)
|
| 585 |
+
st.dataframe(options_df, use_container_width=True, hide_index=True)
|
| 586 |
+
|
| 587 |
+
# Final business recommendation
|
| 588 |
+
current_option = options_df[options_df["Threshold"] == f"{threshold} min"]
|
| 589 |
+
if len(current_option) > 0:
|
| 590 |
+
current_strategy = current_option.iloc[0]["Strategy"]
|
| 591 |
+
current_rec = current_option.iloc[0]["Recommendation"]
|
| 592 |
+
|
| 593 |
+
st.markdown(f"""
|
| 594 |
+
<div class="recommendation-box">
|
| 595 |
+
<h3>Current Selection: {threshold} minutes ({current_strategy} Strategy)</h3>
|
| 596 |
+
<p><strong>Profile:</strong> {current_rec}</p>
|
| 597 |
+
<p><strong>Business Impact:</strong> This threshold solves {current_metrics['problems_solved']:,} customer problems while blocking {current_metrics['availability_impact']:.1f}% of potential bookings.</p>
|
| 598 |
+
<p><strong>Decision Rationale:</strong> Choose this if you prioritize {"customer experience over short-term availability" if threshold >= 120 else "availability over customer experience improvements" if threshold <= 90 else "a balanced approach between customer experience and availability"}.</p>
|
| 599 |
+
</div>
|
| 600 |
+
""", unsafe_allow_html=True)
|
| 601 |
|
| 602 |
# ========== FOOTER ==========
|
| 603 |
st.markdown("---")
|