CatherineYefi's picture
Update app.py
af87319 verified
# VisaTier 4.0 - Optimized Immigration ROI Calculator
# Streamlined for maximum conversion and user experience
import gradio as gr
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
import numpy as np
from dataclasses import dataclass
from typing import Dict, List, Tuple
# =========================
# STREAMLINED STYLING
# =========================
OPTIMIZED_CSS = """
:root {
--primary: #2563eb;
--primary-dark: #1d4ed8;
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
--surface: #ffffff;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2e8f0;
--radius: 12px;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--gradient: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
}
.gradio-container {
max-width: 1200px !important;
margin: 0 auto !important;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
}
.hero-section {
background: var(--gradient);
color: white;
padding: 3rem 2rem;
border-radius: var(--radius);
margin-bottom: 2rem;
text-align: center;
}
.hero-title {
font-size: 2.5rem;
font-weight: 800;
margin-bottom: 1rem;
}
.hero-subtitle {
font-size: 1.2rem;
opacity: 0.9;
margin-bottom: 2rem;
}
.social-proof {
background: rgba(255,255,255,0.1);
padding: 1rem;
border-radius: 8px;
display: inline-block;
}
.profile-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1rem;
margin: 2rem 0;
}
.profile-card {
background: var(--surface);
border: 2px solid var(--border);
border-radius: var(--radius);
padding: 1.5rem;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.profile-card:hover, .profile-card.selected {
border-color: var(--primary);
transform: translateY(-2px);
box-shadow: var(--shadow);
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
}
.profile-icon {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.profile-name {
font-weight: 600;
font-size: 1rem;
margin-bottom: 0.5rem;
}
.kpi-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin: 2rem 0;
}
.kpi-card {
background: var(--surface);
border-radius: var(--radius);
padding: 2rem;
text-align: center;
box-shadow: var(--shadow);
border-top: 4px solid var(--primary);
}
.kpi-label {
font-size: 0.9rem;
color: var(--text-muted);
margin-bottom: 0.5rem;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 0.5px;
}
.kpi-value {
font-size: 2.5rem;
font-weight: 800;
color: var(--primary);
margin-bottom: 0.5rem;
}
.kpi-note {
font-size: 0.85rem;
color: var(--text-muted);
}
.kpi-card.success { border-top-color: var(--success); }
.kpi-card.success .kpi-value { color: var(--success); }
.kpi-card.warning { border-top-color: var(--warning); }
.kpi-card.warning .kpi-value { color: var(--warning); }
.kpi-card.error { border-top-color: var(--error); }
.kpi-card.error .kpi-value { color: var(--error); }
.insight-card {
background: var(--surface);
border-left: 4px solid var(--primary);
border-radius: var(--radius);
padding: 1.5rem;
margin: 1rem 0;
box-shadow: var(--shadow);
}
.insight-title {
font-weight: 600;
color: var(--primary);
margin-bottom: 0.5rem;
}
.offer-modal {
background: var(--surface);
border: 2px solid var(--primary);
border-radius: var(--radius);
padding: 2rem;
margin: 2rem 0;
text-align: center;
position: relative;
overflow: hidden;
}
.offer-modal::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: conic-gradient(from 0deg, transparent, var(--primary), transparent);
animation: rotate 4s linear infinite;
z-index: -1;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.value-badge {
background: var(--success);
color: white;
padding: 0.5rem 1rem;
border-radius: 50px;
display: inline-block;
font-weight: 600;
margin-bottom: 1rem;
font-size: 0.9rem;
}
.cta-button {
background: var(--gradient) !important;
border: none !important;
border-radius: var(--radius) !important;
padding: 1rem 3rem !important;
font-weight: 700 !important;
font-size: 1.1rem !important;
color: white !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
text-transform: uppercase !important;
letter-spacing: 1px !important;
box-shadow: 0 4px 15px rgba(37, 99, 235, 0.4) !important;
}
.cta-button:hover {
transform: translateY(-3px) !important;
box-shadow: 0 8px 25px rgba(37, 99, 235, 0.6) !important;
}
.comparison-table {
background: var(--surface);
border-radius: var(--radius);
overflow: hidden;
box-shadow: var(--shadow);
margin: 2rem 0;
}
.country-highlight {
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border-left: 4px solid var(--primary);
padding: 1rem;
margin: 1rem 0;
border-radius: var(--radius);
}
@media (max-width: 768px) {
.hero-title { font-size: 1.8rem; }
.hero-subtitle { font-size: 1rem; }
.profile-grid { grid-template-columns: 1fr 1fr; }
.kpi-grid { grid-template-columns: 1fr; }
}
"""
# =========================
# SIMPLIFIED DATA MODELS
# =========================
@dataclass
class ProfileData:
id: str
name: str
icon: str
revenue: int
margin: int
risk_level: str
growth_potential: float
@dataclass
class CountryData:
name: str
corp_tax: float
pers_tax: float
living_cost: int
setup_cost: int
growth_multiplier: float
ease_score: float
key_benefit: str
# Streamlined profiles - reduced from 6 to 4 core archetypes
PROFILES = {
"startup": ProfileData("startup", "Tech Startup", "🚀", 50000, 20, "High", 2.8),
"crypto": ProfileData("crypto", "Crypto/Web3", "₿", 80000, 35, "Very High", 3.5),
"consulting": ProfileData("consulting", "Consultant", "💼", 30000, 60, "Low", 1.8),
"ecommerce": ProfileData("ecommerce", "E-commerce", "🛒", 45000, 15, "Medium", 2.2)
}
# Top 5 destinations based on real migration data
COUNTRIES = {
"UAE": CountryData("UAE (Dubai)", 0.09, 0.00, 8500, 45000, 2.4, 9.4, "0% personal tax"),
"Singapore": CountryData("Singapore", 0.17, 0.22, 7200, 38000, 2.1, 9.6, "Asian gateway"),
"Estonia": CountryData("Estonia", 0.20, 0.20, 2800, 8000, 1.8, 9.0, "Digital nomad visa"),
"Portugal": CountryData("Portugal", 0.21, 0.48, 2200, 12000, 1.6, 7.8, "NHR tax regime"),
"USA": CountryData("USA", 0.21, 0.37, 8800, 65000, 2.0, 8.4, "Largest market")
}
# =========================
# SIMPLIFIED CALCULATION ENGINE
# =========================
class SimpleROICalculator:
@staticmethod
def calculate_roi(profile: ProfileData, country: CountryData,
custom_revenue: float = None, years: int = 5) -> Dict:
"""Simplified ROI calculation focused on key metrics"""
# Use custom revenue or profile default
monthly_revenue = custom_revenue or profile.revenue
# Current situation (assuming 25% corp tax, 15% personal tax, 4500 living costs)
current_profit = monthly_revenue * (profile.margin / 100)
current_after_tax = current_profit * 0.75 * 0.85 # Standard tax rates
current_net = current_after_tax - 4500 # Standard living costs
# New situation with relocation
new_revenue = monthly_revenue * country.growth_multiplier * profile.growth_potential
new_profit = new_revenue * (min(profile.margin + 10, 70) / 100) # Margin improvement
new_after_tax = new_profit * (1 - country.corp_tax) * (1 - country.pers_tax)
new_net = new_after_tax - country.living_cost
# Calculate key metrics
monthly_improvement = new_net - current_net
annual_improvement = monthly_improvement * 12
total_benefit = annual_improvement * years
setup_cost = country.setup_cost
# ROI calculation
roi = ((total_benefit - setup_cost) / setup_cost) * 100 if setup_cost > 0 else 0
payback_months = setup_cost / monthly_improvement if monthly_improvement > 0 else float('inf')
# Risk-adjusted ROI based on profile risk tolerance
risk_factors = {"Low": 0.9, "Medium": 0.8, "High": 0.7, "Very High": 0.6}
risk_multiplier = risk_factors.get(profile.risk_level, 0.8)
adjusted_roi = roi * risk_multiplier
return {
"roi": roi,
"adjusted_roi": adjusted_roi,
"annual_savings": annual_improvement,
"monthly_improvement": monthly_improvement,
"payback_months": payback_months,
"total_benefit": total_benefit,
"setup_cost": setup_cost,
"success_probability": min(90, country.ease_score * 10),
"risk_level": profile.risk_level
}
# =========================
# STREAMLINED VISUALIZATION
# =========================
class ChartBuilder:
@staticmethod
def create_comparison_chart(results: Dict, countries: List[str]) -> go.Figure:
"""Simple comparison chart"""
fig = make_subplots(
rows=1, cols=2,
subplot_titles=("ROI Comparison", "Payback Period"),
specs=[[{"type": "bar"}, {"type": "bar"}]]
)
rois = [results[c]["adjusted_roi"] for c in countries]
paybacks = [min(results[c]["payback_months"], 60) for c in countries] # Cap at 5 years
# ROI bars
fig.add_trace(
go.Bar(x=countries, y=rois, name="ROI (%)",
marker_color=['#10b981' if r > 100 else '#f59e0b' if r > 50 else '#ef4444' for r in rois]),
row=1, col=1
)
# Payback bars
fig.add_trace(
go.Bar(x=countries, y=paybacks, name="Payback (months)",
marker_color='#2563eb'),
row=1, col=2
)
fig.update_layout(height=400, showlegend=False, template="plotly_white")
return fig
@staticmethod
def create_timeline_chart(result: Dict) -> go.Figure:
"""Cash flow timeline"""
months = list(range(1, 61)) # 5 years
monthly_cf = result["monthly_improvement"]
cumulative = [-result["setup_cost"]]
for month in months:
cumulative.append(cumulative[-1] + monthly_cf)
fig = go.Figure()
# Add break-even line
fig.add_hline(y=0, line_dash="dash", line_color="red",
annotation_text="Break-even point")
# Cumulative cash flow
fig.add_trace(go.Scatter(
x=months, y=cumulative[1:],
mode='lines+markers',
name='Cumulative Cash Flow',
line=dict(color='#2563eb', width=3),
fill='tonexty' if any(cf > 0 for cf in cumulative[1:]) else None
))
fig.update_layout(
title="Cash Flow Projection Over 5 Years",
xaxis_title="Months",
yaxis_title="Cumulative Cash Flow (€)",
template="plotly_white",
height=400
)
return fig
# =========================
# OPTIMIZED APPLICATION
# =========================
def create_optimized_app():
with gr.Blocks(css=OPTIMIZED_CSS, title="VisaTier 4.0", theme=gr.themes.Soft()) as app:
# State
selected_profile = gr.State("startup")
calculation_results = gr.State({})
# Hero Section
gr.HTML("""
<div class="hero-section">
<h1 class="hero-title">Immigration ROI Calculator</h1>
<p class="hero-subtitle">Make data-driven decisions about business relocation</p>
<div class="social-proof">
<strong>2,100+ entrepreneurs</strong> used our calculator to optimize their relocations
</div>
</div>
""")
# Single-step profile selection
gr.Markdown("### Select Your Business Profile")
profile_html = '<div class="profile-grid">'
for pid, profile in PROFILES.items():
profile_html += f"""
<div class="profile-card" onclick="selectProfile('{pid}', this)" id="card-{pid}">
<div class="profile-icon">{profile.icon}</div>
<div class="profile-name">{profile.name}</div>
<div style="font-size: 0.85rem; color: var(--text-muted);">
{profile.revenue:,}/mo • {profile.margin}% margin
</div>
</div>"""
profile_html += """
</div>
<script>
function selectProfile(profileId, element) {
document.querySelectorAll('.profile-card').forEach(card =>
card.classList.remove('selected'));
element.classList.add('selected');
// Update hidden dropdown
const dropdown = document.querySelector('select[data-testid="dropdown"]');
if (dropdown) {
dropdown.value = profileId;
dropdown.dispatchEvent(new Event('change'));
}
}
// Auto-select first profile
document.addEventListener('DOMContentLoaded', () => {
const firstCard = document.querySelector('#card-startup');
if (firstCard) selectProfile('startup', firstCard);
});
</script>"""
gr.HTML(profile_html)
profile_dropdown = gr.Dropdown(
choices=list(PROFILES.keys()),
value="startup",
visible=False
)
# Simplified inputs in single row
with gr.Row():
custom_revenue = gr.Number(
label="Monthly Revenue (€)",
value=50000,
info="Leave empty to use profile default"
)
target_countries = gr.CheckboxGroup(
choices=list(COUNTRIES.keys()),
value=["UAE", "Singapore", "Estonia"],
label="Countries to Compare"
)
# Single calculate button
calculate_btn = gr.Button(
"Calculate ROI for All Countries",
variant="primary",
elem_classes=["cta-button"],
size="lg"
)
# Results section
results_display = gr.HTML(visible=False)
comparison_chart = gr.Plot(visible=False)
timeline_chart = gr.Plot(visible=False)
recommendations = gr.HTML(visible=False)
def calculate_all_rois(profile_id, revenue, countries):
"""Calculate ROI for all selected countries"""
if not countries or profile_id not in PROFILES:
return [gr.update()] * 4
profile = PROFILES[profile_id]
calculator = SimpleROICalculator()
results = {}
# Calculate for each country
for country_id in countries:
if country_id in COUNTRIES:
country = COUNTRIES[country_id]
results[country_id] = calculator.calculate_roi(profile, country, revenue)
if not results:
return [gr.update()] * 4
# Generate results HTML
best_country = max(results.keys(), key=lambda c: results[c]["adjusted_roi"])
best_roi = results[best_country]["adjusted_roi"]
# KPI Cards
kpi_html = f"""
<div class="kpi-grid">
<div class="kpi-card {'success' if best_roi > 150 else 'warning' if best_roi > 75 else 'error'}">
<div class="kpi-label">Best ROI</div>
<div class="kpi-value">{best_roi:.0f}%</div>
<div class="kpi-note">{COUNTRIES[best_country].name}</div>
</div>
<div class="kpi-card">
<div class="kpi-label">Annual Savings</div>
<div class="kpi-value">€{results[best_country]['annual_savings']:,.0f}</div>
<div class="kpi-note">Per year after setup</div>
</div>
<div class="kpi-card">
<div class="kpi-label">Payback Time</div>
<div class="kpi-value">{results[best_country]['payback_months']:.1f}</div>
<div class="kpi-note">Months to break even</div>
</div>
<div class="kpi-card">
<div class="kpi-label">Setup Investment</div>
<div class="kpi-value">€{results[best_country]['setup_cost']:,}</div>
<div class="kpi-note">Initial investment required</div>
</div>
</div>
"""
# Create charts
comparison = ChartBuilder.create_comparison_chart(results, countries)
timeline = ChartBuilder.create_timeline_chart(results[best_country])
# Recommendations
country_data = COUNTRIES[best_country]
rec_html = f"""
<div class="country-highlight">
<h3>🏆 Recommended: {country_data.name}</h3>
<p><strong>Key advantage:</strong> {country_data.key_benefit}</p>
<p><strong>Why it works for {profile.name}s:</strong>
{best_roi:.0f}% ROI with {results[best_country]['payback_months']:.1f} month payback period.</p>
</div>
<div class="offer-modal">
<div class="value-badge">Limited Time: 50% Off</div>
<h3>Get Your Complete {country_data.name} Migration Plan</h3>
<p style="font-size: 1.1rem; margin: 1rem 0;">
Detailed roadmap, legal requirements, tax optimization strategies
</p>
<div style="font-size: 1.2rem; margin: 1rem 0;">
<span style="text-decoration: line-through; color: #64748b;">€997</span>
<strong style="color: #10b981; font-size: 1.5rem;"> €497</strong>
</div>
<button class="cta-button">Get Migration Plan Now</button>
<p style="font-size: 0.8rem; color: #64748b; margin-top: 1rem;">
30-day money-back guarantee • Secure payment
</p>
</div>
"""
return (
gr.update(value=kpi_html, visible=True),
gr.update(value=comparison, visible=True),
gr.update(value=timeline, visible=True),
gr.update(value=rec_html, visible=True)
)
# Connect calculation
calculate_btn.click(
calculate_all_rois,
inputs=[profile_dropdown, custom_revenue, target_countries],
outputs=[results_display, comparison_chart, timeline_chart, recommendations]
)
# Auto-update revenue when profile changes
def update_revenue(profile_id):
if profile_id in PROFILES:
return PROFILES[profile_id].revenue
return 50000
profile_dropdown.change(
update_revenue,
inputs=[profile_dropdown],
outputs=[custom_revenue]
)
# Footer
gr.HTML("""
<div style="text-align: center; margin-top: 3rem; padding: 2rem;
border-top: 1px solid var(--border); color: var(--text-muted);">
<p><strong>Disclaimer:</strong> Estimates for planning purposes only.
Consult qualified professionals for legal and tax advice.</p>
<p>© 2025 VisaTier • <a href="#" style="color: var(--primary);">Privacy</a> •
<a href="#" style="color: var(--primary);">Terms</a></p>
</div>
""")
return app
# =========================
# LAUNCH
# =========================
if __name__ == "__main__":
app = create_optimized_app()
app.launch(server_name="0.0.0.0", server_port=7860, share=False)