import dash
from dash import dcc, html, Input, Output, State, callback
import dash_bootstrap_components as dbc
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
# Load the dataset
def load_expedition_data():
# Load the complete dataset here
return pd.read_csv('mountains_with_10_plus_visits.csv', low_memory=False)
df = load_expedition_data()
# Train the machine learning model
def train_prediction_model(dataframe):
# Select relevant features for predicting success
features = ['year', 'season', 'heightm', 'camps', 'totmembers',
'o2used', 'tothired', 'rope']
# Prepare X and y
X = dataframe[features]
y = dataframe['success1'] # Predict success on route 1
# Split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
# Preprocessing
numeric_features = ['year', 'heightm', 'camps', 'totmembers', 'tothired', 'rope']
categorical_features = ['season', 'o2used']
numeric_transformer = Pipeline(steps=[
('scaler', StandardScaler())
])
categorical_transformer = Pipeline(steps=[
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)
])
# Create and train the complete pipeline
model = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', RandomForestClassifier(n_estimators=100, random_state=42))
])
model.fit(X_train, y_train)
return model
# Train the model
prediction_model = train_prediction_model(df)
# Enhanced nature-inspired theme colors with gradients
COLORS = {
'primary': '#1e3a8a', # Deep Mountain Blue
'secondary': '#059669', # Evergreen
'accent': '#f59e0b', # Golden Sunrise
'dark': '#0f172a', # Night Sky
'light': '#f8fafc', # Snow White
'danger': '#dc2626', # Alert Red
'success': '#10b981', # Summit Green
'ice': '#0ea5e9', # Glacier Blue
'rock': '#78716c', # Stone Grey
'cloud': '#e2e8f0', # Cloud Grey
'text': '#1e293b', # Deep Text
'text_light': '#f8fafc', # NUEVO: Texto claro para fondos oscuros
'text_muted': '#64748b', # NUEVO: Texto secundario
'gradient_primary': 'linear-gradient(135deg, #1e3a8a, #3b82f6)',
'gradient_success': 'linear-gradient(135deg, #059669, #10b981)',
'gradient_warning': 'linear-gradient(135deg, #f59e0b, #fbbf24)'
}
# Custom CSS for the enhanced nature theme
external_stylesheets = [
dbc.themes.MINTY,
dbc.icons.FONT_AWESOME,
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap', 'style1.css'
]
# Configure the app with bootstrap
app = dash.Dash(
__name__,
external_stylesheets=external_stylesheets,
suppress_callback_exceptions=True,
meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}]
)
server = app.server
app.title = "๐๏ธ Himalaya Explorer - Your Summit Journey Begins Here"
# Enhanced app layout with compelling narrative
app.layout = dbc.Container([
# Hero Section with Compelling Narrative
html.Div([
html.H1([
"๐๏ธ HIMALAYA EXPLORER",
html.Br(),
html.Small("Your Journey to the World's Highest Peaks Starts Here",
style={"fontSize": "1.2rem", "opacity": "0.9"})
], className="display-3 text-center mb-3 fw-bold"),
html.P([
"๐ Discover the secrets of successful expeditions โข ๐ Analyze decades of climbing data โข ",
"๐ค Predict your summit chances with AI โข โท๏ธ Plan safer adventures"
], className="lead text-center mb-0", style={"fontSize": "1.1rem"})
], className="hero-section"),
# Smart Expedition Filters
html.Div(id='global-filters-container', children=[
dbc.Card([
dbc.CardHeader([
html.I(className="fas fa-filter section-icon"),
"๐ฏ Smart Expedition Filters - Customize Your Exploration"
]),
dbc.CardBody([
html.P("โจ Select your parameters to explore targeted insights from over 10,000+ expedition records",
className="text-muted mb-3"),
dbc.Row([
dbc.Col([
html.Label([html.I(className="fas fa-mountain me-2"), "๐๏ธ Choose Your Peaks (max 3):"]),
dcc.Dropdown(
id='global-peak-dropdown',
options=[{'label': f"๐๏ธ {peak}", 'value': peak} for peak in sorted(df['pkname'].unique())],
value=[sorted(df['pkname'].unique())[0]],
multi=True, placeholder="๐ Search and select peaks...",
className="mb-3", style={"borderRadius": "10px"}
),
], md=4),
dbc.Col([
html.Label([html.I(className="fas fa-snowflake me-2"), "๐จ๏ธ Season:"]),
dcc.Dropdown(
id='global-season-dropdown',
options=[{'label': '๐ All Seasons', 'value': 'all'}] +
[{'label': f"{'๐ธ' if season=='Spring' else 'โ๏ธ' if season=='Summer' else '๐' if season=='Autumn' else 'โ๏ธ'} {season}", 'value': season}
for season in sorted(df['season'].unique())],
value='all', className="mb-3"
),
], md=3),
dbc.Col([
html.Label([html.I(className="fas fa-calendar me-2"), "๐
Time Period:"]),
dcc.RangeSlider(
id='global-year-slider',
min=df['year'].min(), max=df['year'].max(), step=1,
marks={i: str(i) for i in range(df['year'].min(), df['year'].max() + 1, 10)},
value=[df['year'].min(), df['year'].max()], className="mb-3"
),
], md=5),
])
])
], className="filter-card mb-4", style={'display': 'block'})
]),
# Navigation with Adventure Story
dbc.Row([
dbc.Col([
html.H3("๐ Choose Your Adventure Story", className="text-center mb-3"),
dbc.Nav([
dbc.NavItem(dbc.NavLink([
html.I(className="fas fa-mountain me-2"), "๐๏ธ Peak Legends"
], id="btn-peaks", active=True, className="mx-1")),
dbc.NavItem(dbc.NavLink([
html.I(className="fas fa-shield-alt me-2"), "โ ๏ธ Safety Intel"
], id="btn-safety", className="mx-1")),
dbc.NavItem(dbc.NavLink([
html.I(className="fas fa-brain me-2"), "๐ค AI Summit Predictor"
], id="btn-ml", className="mx-1")),
], pills=True, fill=True, className="mb-4")
])
]),
# Dynamic Content Area
html.Div(id='page-content')
], fluid=True, className="px-4")
# Navigation callback with enhanced storytelling
@app.callback(
[Output('page-content', 'children'),
Output('btn-peaks', 'active'),
Output('btn-safety', 'active'),
Output('btn-ml', 'active'),
Output('global-filters-container', 'style')],
[Input('btn-peaks', 'n_clicks'),
Input('btn-safety', 'n_clicks'),
Input('btn-ml', 'n_clicks')]
)
def render_content(peaks_clicks, safety_clicks, ml_clicks):
ctx = dash.callback_context
button_id = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else ''
button_states = [False, False, False]
global_filters_style = {'display': 'block'} # Estilo por defecto: visible
if button_id == 'btn-peaks' or not button_id:
button_states[0] = True
return render_peaks_tab(), *button_states, global_filters_style
elif button_id == 'btn-safety':
button_states[1] = True
return render_safety_tab(), *button_states, global_filters_style
elif button_id == 'btn-ml':
button_states[2] = True
global_filters_style = {'display': 'none'} # OCULTAR FILTROS GLOBALES
return render_ml_tab(), *button_states, global_filters_style
# Enhanced Peak Profiles with storytelling
def render_peaks_tab():
return dbc.Card([
dbc.CardHeader([
html.I(className="fas fa-mountain section-icon"),
"๐๏ธ Legendary Peaks - Where Dreams Meet Reality"
]),
dbc.CardBody([
dbc.Alert([
html.I(className="fas fa-info-circle me-2"),
"๐ Dive into the rich tapestry of Himalayan climbing history. Each peak tells a story of human courage, determination, and the eternal quest to touch the sky."
], color="info", className="mb-4"),
html.Div(id='peak-info-container')
])
])
# Enhanced callback for peak information with better storytelling
@app.callback(
Output('peak-info-container', 'children'),
[Input('global-peak-dropdown', 'value'),
Input('global-season-dropdown', 'value'),
Input('global-year-slider', 'value')]
)
def update_peak_info(selected_peaks, selected_season, year_range):
if not selected_peaks or len(selected_peaks) == 0:
return dbc.Alert("๐ Please select at least one peak to begin your exploration", color="warning")
selected_peaks = selected_peaks[:3]
filtered_df = df.copy()
filtered_df = filtered_df[(filtered_df['year'] >= year_range[0]) & (filtered_df['year'] <= year_range[1])]
if selected_season != 'all':
filtered_df = filtered_df[filtered_df['season'] == selected_season]
peak_expeditions = filtered_df[filtered_df['pkname'].isin(selected_peaks)]
if len(peak_expeditions) == 0:
return dbc.Alert("๐ No expedition data found for your selected criteria. Try adjusting your filters!", color="warning")
# Enhanced expedition timeline
expeditions_by_year = peak_expeditions.groupby(['year', 'pkname']).size().reset_index(name='count')
fig_timeline = px.area(expeditions_by_year, x='year', y='count', color='pkname',
title='๐ The Epic Journey Through Time - Expeditions per Year',
markers=True, template='plotly_white')
fig_timeline.update_layout(
plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',
font={'color': COLORS['text'], 'family': 'Inter'}, height=450,
title_font_size=16, legend_title="๐๏ธ Peak Name"
)
# Success rate visualization with better narrative
success_by_season = peak_expeditions.groupby(['season', 'pkname'])['success1'].mean().reset_index()
fig_success = px.bar(success_by_season, x='season', y='success1', color='pkname',
title='๐ฏ Success Stories by Season - When Dreams Come True',
labels={'success1': '๐ Success Rate', 'season': '๐ฆ๏ธ Season'},
barmode='group', template='plotly_white')
fig_success.update_layout(
plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',
font={'color': COLORS['text'], 'family': 'Inter'}, height=450,
title_font_size=16, legend_title="๐๏ธ Peak Name"
)
# Enhanced peaks information table with expandable routes
peak_info_rows = []
for idx, selected_peak in enumerate(selected_peaks):
current_peak_data = filtered_df[filtered_df['pkname'] == selected_peak]
if len(current_peak_data) == 0:
continue
peak_data = current_peak_data.iloc[0]
first_ascent_year = peak_data.get('pyear', 'Unknown')
first_ascent_team = peak_data.get('pcountry', 'Unknown')
first_summiters = peak_data.get('psummiters', 'Unknown')
height = peak_data.get('heightm', 'Unknown')
location = peak_data.get('location', 'Unknown')
# Get routes for this specific peak
peak_routes = current_peak_data['route1'].dropna().unique()
routes_formatted = [route for route in peak_routes if route and str(route).strip()]
# Create routes dropdown content
routes_content = html.Div([
dbc.Button([
html.I(className="fas fa-route me-2"),
f"View Routes ({len(routes_formatted)})",
html.I(className="fas fa-caret-down ms-2")
],
id=f"routes-btn-{idx}",
color="outline-primary",
size="sm",
style={"width": "100%", "fontSize": "0.8rem"}),
dbc.Collapse([
html.Div([
dbc.ListGroup([
dbc.ListGroupItem([
html.I(className="fas fa-route me-2", style={"color": "#059669"}),
route
], style={"fontSize": "0.85rem", "padding": "0.4rem 0.8rem", "border": "none"})
for route in routes_formatted
], flush=True) if routes_formatted else html.P("No routes available",
className="text-muted mb-0", style={"fontSize": "0.8rem", "padding": "0.5rem"})
], style={"maxHeight": "150px", "overflowY": "auto", "backgroundColor": "#f8fafc",
"border": "1px solid #e2e8f0", "borderRadius": "0.375rem", "marginTop": "0.5rem"})
], id=f"routes-collapse-{idx}", is_open=False)
])
peak_info_rows.append(html.Tr([
html.Td([html.I(className="fas fa-mountain me-2"), selected_peak]),
html.Td([html.I(className="fas fa-ruler-vertical me-2"), f"{height} m"]),
html.Td([html.I(className="fas fa-map-marker-alt me-2"), location]),
html.Td([html.I(className="fas fa-calendar me-2"), f"{first_ascent_year}"]),
html.Td([html.I(className="fas fa-flag me-2"), f"{first_summiters} ({first_ascent_team})"]),
html.Td(routes_content, style={"minWidth": "200px"})
]))
peaks_table = dbc.Table([
html.Thead(html.Tr([
html.Th("๐๏ธ Peak Name"),
html.Th("๐ Height"),
html.Th("๐ Location"),
html.Th("๐ฏ First Ascent"),
html.Th("๐ Pioneers"),
html.Th("๐ค๏ธ Epic Routes", style={"minWidth": "200px"})
])),
html.Tbody(peak_info_rows)
], bordered=True, hover=True, striped=True, className="mb-4")
return dbc.Card([
dbc.CardHeader([
html.I(className="fas fa-chart-line me-2"),
"๐ Peak Performance Analytics"
]),
dbc.CardBody([
html.H5("๐๏ธ Selected Peaks Hall of Fame", className="mb-3"),
peaks_table,
dbc.Row([
dbc.Col([dcc.Graph(figure=fig_timeline, className="mb-4")], md=12)
]),
dbc.Row([
dbc.Col([dcc.Graph(figure=fig_success, className="mb-4")], md=12)
])
])
])
# Peak selection limiter
@app.callback(
Output('global-peak-dropdown', 'value'),
Input('global-peak-dropdown', 'value')
)
def limit_peaks_selection(selected_peaks):
if selected_peaks and len(selected_peaks) > 5:
return selected_peaks[:5]
return selected_peaks
# Enhanced Safety Analysis with dramatic storytelling
def render_safety_tab():
return dbc.Card([
dbc.CardHeader([
html.I(className="fas fa-shield-alt section-icon"),
"โ ๏ธ Mountain Safety Intelligence - Knowledge That Saves Lives"
]),
dbc.CardBody([
dbc.Alert([
html.I(className="fas fa-exclamation-triangle me-2"),
"๐จ Understanding risk is the first step to managing it. These insights are drawn from decades of expedition data to help you make informed decisions in the world's most challenging environment."
], color="warning", className="mb-4"),
html.Div(id='safety-analysis', className='mt-4')
])
])
# Enhanced safety analysis callback
@app.callback(
Output('safety-analysis', 'children'),
[Input('global-year-slider', 'value'),
Input('global-peak-dropdown', 'value'),
Input('global-season-dropdown', 'value')]
)
def update_safety_analysis(years, peaks, season):
filtered_df = df.copy()
filtered_df = filtered_df[(filtered_df['year'] >= years[0]) & (filtered_df['year'] <= years[1])]
if peaks and len(peaks) > 0:
filtered_df = filtered_df[filtered_df['pkname'].isin(peaks)]
if season != 'all':
filtered_df = filtered_df[filtered_df['season'] == season]
total_expeditions = len(filtered_df)
if total_expeditions == 0:
return dbc.Alert("๐ No safety data available for selected parameters", color="warning")
total_members = filtered_df['totmembers'].sum()
total_deaths = filtered_df['mdeaths'].sum()
death_rate = (total_deaths / total_members * 100) if total_members > 0 else 0
yearly_stats = filtered_df.groupby('year').agg({
'mdeaths': 'sum', 'totmembers': 'sum'
}).reset_index()
yearly_stats['mortality_rate'] = yearly_stats['mdeaths'] / yearly_stats['totmembers'] * 100
recent_mortality = yearly_stats[yearly_stats['year'] >= 2010]['mortality_rate'].mean() if not yearly_stats.empty else 0
older_mortality = yearly_stats[yearly_stats['year'] < 2010]['mortality_rate'].mean() if not yearly_stats.empty else 0
trend_emoji = "๐" if recent_mortality > older_mortality else "๐"
trend_text = "increased" if recent_mortality > older_mortality else "decreased"
# Enhanced mortality trend visualization
if not yearly_stats.empty:
fig_mortality_trend = px.line(yearly_stats, x='year', y='mortality_rate',
title='โ ๏ธ Risk Evolution Over Time - Mortality Rate Trends',
labels={'mortality_rate': '๐ Mortality Rate (%)', 'year': '๐
Year'},
markers=True, template='plotly_white')
fig_mortality_trend.update_traces(
line=dict(color=COLORS['danger'], width=3),
marker=dict(color=COLORS['danger'], size=8)
)
fig_mortality_trend.update_layout(
plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',
font={'color': COLORS['text'], 'family': 'Inter'}, height=400
)
else:
fig_mortality_trend = go.Figure()
fig_mortality_trend.update_layout(title='๐ Insufficient data for trend analysis')
# Peak comparison if multiple peaks selected
peak_comparison = None
if peaks and len(peaks) > 1:
peak_mortality = filtered_df.groupby('pkname').agg({
'mdeaths': 'sum', 'totmembers': 'sum'
}).reset_index()
peak_mortality['mortality_rate'] = peak_mortality['mdeaths'] / peak_mortality['totmembers'] * 100
fig_peak_comparison = px.bar(peak_mortality, x='pkname', y='mortality_rate',
title='โ๏ธ Risk Comparison Across Peaks',
labels={'mortality_rate': '๐ Mortality Rate (%)', 'pkname': '๐๏ธ Peak'},
template='plotly_white')
fig_peak_comparison.update_traces(marker_color=COLORS['danger'])
fig_peak_comparison.update_layout(
plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',
font={'color': COLORS['text'], 'family': 'Inter'}, height=400
)
peak_comparison = dbc.Row([
dbc.Col([dcc.Graph(figure=fig_peak_comparison)], md=12)
], className="mb-4")
return dbc.Row([
dbc.Col([
# Critical stats overview
dbc.Row([
dbc.Col([
html.Div([
html.Div("๐", className="metric-icon"),
html.H2(f"{death_rate:.2f}%", className="display-4 mb-0"),
html.P("Overall Risk Rate", className="mb-0", #style={"color": "#f8fafc"}
)
], className="danger-card stats-card")
], md=4),
dbc.Col([
html.Div([
html.Div("๐ฅ", className="metric-icon"),
html.H2(f"{total_members:,}", className="display-4 mb-0"),
html.P("Brave Climbers", className="mb-0")
], className="stats-card")
], md=4),
dbc.Col([
html.Div([
html.Div(trend_emoji, className="metric-icon"),
html.H2(f"{abs(recent_mortality - older_mortality):.1f}%", className="display-4 mb-0"),
html.P(f"Safety {trend_text.title()}", className="mb-0")
], className="success-card stats-card" if trend_text == "decreased" else "danger-card stats-card")
], md=4)
], className="mb-4"),
# Detailed analysis
dbc.Card([
dbc.CardHeader("๐ Comprehensive Risk Analysis"),
dbc.CardBody([
html.P([
f"๐ Safety trends show that mountaineering risks have {trend_text} over time. ",
f"Recent expeditions (2010+) show a {recent_mortality:.2f}% risk rate compared to ",
f"{older_mortality:.2f}% in earlier periods - a testament to improved equipment, ",
"training, and safety protocols."
], className="mb-4"),
peak_comparison if peak_comparison is not None else html.Div(),
dcc.Graph(figure=fig_mortality_trend, className="mb-4"),
dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardHeader("โ ๏ธ Primary Risk Factors"),
dbc.CardBody([
dbc.ListGroup([
dbc.ListGroupItem([
html.I(className="fas fa-thermometer-empty me-2"),
"๐ก๏ธ Extreme weather and exposure"
]),
dbc.ListGroupItem([
html.I(className="fas fa-mountain me-2"),
"๐ชจ Avalanches and rockfall"
]),
dbc.ListGroupItem([
html.I(className="fas fa-lungs me-2"),
"๐ซ Altitude sickness complications"
]),
dbc.ListGroupItem([
html.I(className="fas fa-battery-empty me-2"),
"๐ช Physical exhaustion"
])
])
])
])
], md=6),
dbc.Col([
dbc.Alert([
html.I(className="fas fa-lightbulb me-2"),
html.Strong("๐ก Safety Wisdom: "),
"The mountains don't care about your summit dreams - they only respect your preparation, ",
"judgment, and willingness to turn back when conditions demand it. Every successful ",
"climber is one who chose to climb another day."
], color="info")
], md=6)
])
])
])
])
])
# Enhanced ML Prediction Tab with interactive features
def render_ml_tab():
return dbc.Card([
dbc.CardHeader([
html.I(className="fas fa-brain section-icon"),
"๐ค AI Summit Predictor - Will You Reach the Top?"
]),
dbc.CardBody([
dbc.Alert([
html.I(className="fas fa-robot me-2"),
"๐ง Our AI has analyzed thousands of expeditions to predict your chances of success. Input your expedition parameters below and discover what the data reveals about your summit prospects!"
], color="success", className="mb-4"),
dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardHeader("๐ฏ Configure Your Expedition"),
dbc.CardBody([
html.Div([
html.Label([html.I(className="fas fa-mountain me-2"), "๐๏ธ Target Peak:"]),
dcc.Dropdown(
id='ml-peak-dropdown',
options=[{'label': f"๐๏ธ {peak}", 'value': peak} for peak in sorted(df['pkname'].unique())],
value=sorted(df['pkname'].unique())[0],
className="mb-3"
),
html.Label([html.I(className="fas fa-calendar me-2"), "๐
Expedition Year:"]),
dbc.Input(
id='ml-year-input',
type='number',
min=2025, max=2034, step=1,
value=2025,
className="mb-3"
),
html.Label([html.I(className="fas fa-snowflake me-2"), "๐จ๏ธ Season:"]),
dcc.Dropdown(
id='ml-season-dropdown',
options=[
{'label': '๐ธ Spring', 'value': 'Spring'},
{'label': 'โ๏ธ Summer', 'value': 'Summer'},
{'label': '๐ Autumn', 'value': 'Autumn'},
{'label': 'โ๏ธ Winter', 'value': 'Winter'}
],
value='Spring', className="mb-3"
),
html.Label([html.I(className="fas fa-users me-2"), "๐ฅ Team Size:"]),
dbc.Input(
id='ml-members-input',
type='number',
min=1, max=20, step=1,
value=6,
className="mb-3"
),
html.Label([html.I(className="fas fa-campground me-2"), "๐๏ธ Number of Camps:"]),
dbc.Input(
id='ml-camps-input',
type='number',
min=0, max=6, step=1,
value=3,
className="mb-3"
),
html.Label([html.I(className="fas fa-user-friends me-2"), "๐ Hired Support:"]),
dbc.Input(
id='ml-hired-input',
type='number',
min=0, max=50, step=1,
value=10,
className="mb-3"
),
html.Label([html.I(className="fas fa-wind me-2"), "๐ Oxygen Usage:"]),
dcc.RadioItems(
id='ml-oxygen-radio',
options=[
{'label': 'โ
Yes - Supplemental O2', 'value': True},
{'label': '๐ซ No - Pure Alpine Style', 'value': False}
],
value=True, className="mb-3"
),
html.Label([html.I(className="fas fa-link me-2"), "๐ชข Fixed Ropes:"]),
dcc.RadioItems(
id='ml-rope-radio',
options=[
{'label': 'โ
Yes - Safety First', 'value': True},
{'label': '๐ซ No - Traditional Style', 'value': False}
],
value=True, className="mb-4"
),
dbc.Button([
html.I(className="fas fa-magic me-2"),
"๐ฎ Predict My Success!"
], id='predict-button', color='primary', size='lg',
className="w-100 mb-3")
])
])
])
], md=4),
dbc.Col([
html.Div(id='prediction-results')
], md=8)
])
])
])
# Callbacks para las rutas colapsables
for i in range(5): # Mรกximo 5 picos
@app.callback(
Output(f'routes-collapse-{i}', 'is_open'),
Input(f'routes-btn-{i}', 'n_clicks'),
prevent_initial_call=True
)
def toggle_routes_collapse(n_clicks):
return n_clicks and n_clicks % 2 == 1
# ML Prediction callback
@app.callback(
Output('prediction-results', 'children'),
[Input('predict-button', 'n_clicks')],
[State('ml-peak-dropdown', 'value'),
State('ml-year-input', 'value'),
State('ml-season-dropdown', 'value'),
State('ml-members-input', 'value'),
State('ml-camps-input', 'value'),
State('ml-hired-input', 'value'),
State('ml-oxygen-radio', 'value'),
State('ml-rope-radio', 'value')]
)
def predict_expedition_success(n_clicks, peak, year, season, members, camps, hired, oxygen, rope):
if not n_clicks:
return dbc.Card([
dbc.CardBody([
html.Div([
html.I(className="fas fa-rocket", style={"fontSize": "4rem", "color": COLORS['primary']}),
html.H3("๐ Ready for Launch?", className="mt-3"),
html.P("Configure your expedition parameters and click 'Predict My Success' to discover your summit chances!",
className="text-muted")
], className="text-center py-5")
])
])
try:
# Get peak data for height
peak_data = df[df['pkname'] == peak].iloc[0]
height = peak_data['heightm']
# Prepare prediction data
prediction_data = pd.DataFrame({
'year': [year],
'season': [season],
'heightm': [height],
'camps': [camps],
'totmembers': [members],
'o2used': [oxygen],
'tothired': [hired],
'rope': [rope]
})
# Make prediction
success_probability = prediction_model.predict_proba(prediction_data)[0][1]
success_percentage = success_probability * 100
# Determine risk level and messaging
if success_percentage >= 70:
risk_level = "๐ข HIGH SUCCESS"
risk_color = "success"
advice = "๐ Excellent conditions for success! Your expedition parameters align with historically successful climbs."
emoji = "๐"
elif success_percentage >= 50:
risk_level = "๐ก MODERATE SUCCESS"
risk_color = "warning"
advice = "โ๏ธ Balanced odds. Consider optimizing team size, timing, or support to improve your chances."
emoji = "๐ฏ"
else:
risk_level = "๐ด CHALLENGING CONDITIONS"
risk_color = "danger"
advice = "โ ๏ธ Difficult conditions predicted. Review your parameters and consider additional preparation or support."
emoji = "๐งโโ๏ธ"
# Historical comparison
similar_expeditions = df[
(df['pkname'] == peak) &
(df['season'] == season) &
(df['totmembers'].between(members-2, members+2))
]
historical_success = similar_expeditions['success1'].mean() * 100 if len(similar_expeditions) > 0 else 0
historical_count = len(similar_expeditions)
# Generate insights
insights = []
if oxygen:
insights.append("๐ Oxygen use significantly improves success rates at extreme altitude")
else:
insights.append("๐ซ Alpine style climbing increases difficulty but offers pure achievement")
if members <= 4:
insights.append("๐ฅ Small team allows for faster movement and better coordination")
elif members >= 8:
insights.append("๐ฅ Large team provides safety backup but requires more coordination")
if season == 'Spring':
insights.append("๐ธ Spring offers the best weather windows for most peaks")
elif season == 'Winter':
insights.append("โ๏ธ Winter climbing presents extreme challenges but fewer crowds")
return dbc.Card([
dbc.CardHeader([
html.I(className="fas fa-crystal-ball me-2"),
f"๐ฎ AI Prediction Results for {peak}"
]),
dbc.CardBody([
# Main prediction result
dbc.Alert([
html.Div([
html.H1([emoji, f" {success_percentage:.1f}%"],
className="display-2 mb-0 text-center"),
html.H4(f"{risk_level} PROBABILITY",
className="text-center mb-0 fw-bold")
])
], color=risk_color, className="text-center mb-4"),
# Detailed breakdown
dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardHeader("๐ Prediction Breakdown"),
dbc.CardBody([
html.P(advice, className="mb-3"),
# Progress bars for key factors
html.Div([
html.Label("๐ฏ Success Probability"),
dbc.Progress(value=success_percentage, color=risk_color.replace('danger', 'warning'),
className="mb-3", style={"height": "25px"}),
html.Label("๐ Historical Comparison"),
dbc.Progress(value=historical_success, color="info",
className="mb-2", style={"height": "20px"}),
html.Small(f"Based on {historical_count} similar expeditions",
className="text-muted")
])
])
])
], md=6),
dbc.Col([
dbc.Card([
dbc.CardHeader("๐ก AI Insights"),
dbc.CardBody([
dbc.ListGroup([
dbc.ListGroupItem([
html.I(className="fas fa-lightbulb me-2"),
insight
]) for insight in insights
], flush=True)
])
])
], md=6)
], className="mb-4"),
# Expedition summary
dbc.Card([
dbc.CardHeader("๐ Your Expedition Summary"),
dbc.CardBody([
dbc.Row([
dbc.Col([
html.Strong("๐๏ธ Peak: "), f"{peak} ({height}m)"
], md=6),
dbc.Col([
html.Strong("๐
Year: "), f"{year}"
], md=6)
]),
dbc.Row([
dbc.Col([
html.Strong("๐จ๏ธ Season: "), f"{season}"
], md=6),
dbc.Col([
html.Strong("๐ฅ Team Size: "), f"{members} members"
], md=6)
]),
dbc.Row([
dbc.Col([
html.Strong("๐๏ธ Camps: "), f"{camps}"
], md=6),
dbc.Col([
html.Strong("๐ Support: "), f"{hired} hired"
], md=6)
]),
dbc.Row([
dbc.Col([
html.Strong("๐ Oxygen: "), "Yes" if oxygen else "No"
], md=6),
dbc.Col([
html.Strong("๐ชข Fixed Ropes: "), "Yes" if rope else "No"
], md=6)
])
])
], className="mt-3")
])
])
except Exception as e:
return dbc.Alert([
html.I(className="fas fa-exclamation-triangle me-2"),
f"๐ซ Prediction error: {str(e)}. Please check your parameters and try again."
], color="danger")