Spaces:
Sleeping
Sleeping
| """ | |
| Main Streamlit Dashboard Application | |
| MySpace Ooty Data Analytics - Enhanced Interactive Dashboard | |
| """ | |
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| from datetime import datetime, timedelta | |
| import sys | |
| from pathlib import Path | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| # Add project root to path | |
| project_root = Path(__file__).parent.parent | |
| sys.path.insert(0, str(project_root)) | |
| LOGO_B64 = """ | |
| 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 | |
| """ | |
| from config.settings import * | |
| from src.generate_powerpoint_report import PowerPointReportGenerator | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # PAGE CONFIGURATION & STYLING | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Load logo for page icon | |
| logo_file = project_root / "mypace-logo.png" | |
| st.set_page_config( | |
| page_title="π¨ MySpace Ooty Analytics", | |
| page_icon=str(logo_file) if logo_file.exists() else "π¨", | |
| layout="wide", | |
| initial_sidebar_state="expanded", | |
| ) | |
| # Custom CSS for enhanced UI and responsive design | |
| st.markdown(""" | |
| <style> | |
| /* Base styling */ | |
| .main { padding: 0px; } | |
| .reportview-container { padding-top: 0px; } | |
| /* KPI Cards */ | |
| .kpi-card { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 25px; | |
| border-radius: 12px; | |
| margin: 10px; | |
| box-shadow: 0 4px 15px rgba(0,0,0,0.1); | |
| text-align: center; | |
| } | |
| .kpi-value { | |
| font-size: 32px; | |
| font-weight: bold; | |
| margin: 10px 0; | |
| } | |
| .kpi-label { | |
| font-size: 14px; | |
| opacity: 0.9; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| /* Section Headers */ | |
| .section-header { | |
| border-left: 5px solid #667eea; | |
| padding: 15px; | |
| margin: 20px 0; | |
| background-color: #f5f5f5; | |
| border-radius: 5px; | |
| } | |
| /* Filter Box */ | |
| .filter-box { | |
| background-color: #f0f2f6; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin: 10px 0; | |
| } | |
| /* Responsive Design for Mobile and Tablets */ | |
| @media only screen and (max-width: 768px) { | |
| /* Mobile: Stack elements vertically */ | |
| .kpi-card { | |
| margin: 5px 0; | |
| padding: 15px; | |
| } | |
| .kpi-value { | |
| font-size: 24px; | |
| } | |
| .kpi-label { | |
| font-size: 12px; | |
| } | |
| /* Adjust sidebar for mobile */ | |
| [data-testid="stSidebar"] { | |
| width: 100% !important; | |
| } | |
| /* Make charts responsive */ | |
| .js-plotly-plot { | |
| width: 100% !important; | |
| height: auto !important; | |
| } | |
| /* Adjust table font size for mobile */ | |
| .dataframe { | |
| font-size: 12px !important; | |
| } | |
| /* Stack columns on mobile */ | |
| .row-widget.stHorizontal { | |
| flex-direction: column !important; | |
| } | |
| /* Footer text size */ | |
| .caption { | |
| font-size: 10px !important; | |
| } | |
| } | |
| /* Tablet Design */ | |
| @media only screen and (min-width: 769px) and (max-width: 1024px) { | |
| .kpi-card { | |
| padding: 20px; | |
| } | |
| .kpi-value { | |
| font-size: 28px; | |
| } | |
| /* Adjust sidebar width for tablets */ | |
| [data-testid="stSidebar"] { | |
| width: 280px !important; | |
| } | |
| } | |
| /* Desktop Large Screens */ | |
| @media only screen and (min-width: 1025px) { | |
| [data-testid="stSidebar"] { | |
| width: 320px !important; | |
| } | |
| } | |
| /* Cross-browser compatibility */ | |
| /* Firefox */ | |
| @-moz-document url-prefix() { | |
| .kpi-card { | |
| -moz-border-radius: 12px; | |
| } | |
| } | |
| /* Safari and Chrome */ | |
| @supports (-webkit-appearance: none) { | |
| .kpi-card { | |
| -webkit-border-radius: 12px; | |
| } | |
| } | |
| /* Improve touch targets for mobile */ | |
| @media (hover: none) and (pointer: coarse) { | |
| button, [role="button"], select, input { | |
| min-height: 44px !important; | |
| min-width: 44px !important; | |
| } | |
| } | |
| /* Ensure images are responsive */ | |
| img { | |
| max-width: 100%; | |
| height: auto; | |
| } | |
| /* Make plots responsive */ | |
| .plot-container { | |
| width: 100% !important; | |
| height: auto !important; | |
| } | |
| /* Streamlit specific responsive fixes */ | |
| .stPlotlyChart { | |
| width: 100% !important; | |
| } | |
| /* Improve readability on small screens */ | |
| @media only screen and (max-width: 480px) { | |
| h1 { font-size: 24px !important; } | |
| h2 { font-size: 20px !important; } | |
| h3 { font-size: 18px !important; } | |
| p, li { font-size: 14px !important; } | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # DATA LOADING FUNCTION | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_data(): | |
| """Load processed data from CSV""" | |
| try: | |
| file_path = Path(project_root) / "data" / "processed" / "data_cleaned_with_kpi.csv" | |
| if file_path.exists(): | |
| df = pd.read_csv(file_path) | |
| # Convert date columns | |
| date_cols = ['Year', 'Month', 'Quarter', 'Week', 'Day'] | |
| for col in date_cols: | |
| if col in df.columns: | |
| if col == 'Month_Name': | |
| df[col] = df[col].astype(str) | |
| return df | |
| else: | |
| return None | |
| except Exception as e: | |
| st.error(f"Error loading data: {e}") | |
| return None | |
| def load_kpi_summary(): | |
| """Load KPI summary""" | |
| try: | |
| file_path = Path(project_root) / "data" / "processed" / "kpi_summary.csv" | |
| if file_path.exists(): | |
| return pd.read_csv(file_path) | |
| return None | |
| except: | |
| return None | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SIDEBAR - FILTERS & NAVIGATION | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with st.sidebar: | |
| # Display logo | |
| try: | |
| from PIL import Image | |
| # Try to load logo from streamlit_app directory first (most direct path) | |
| logo_direct = Path(__file__).parent / "mypace-logo.png" | |
| if logo_direct.exists(): | |
| # Load directly from streamlit_app folder | |
| logo_img = Image.open(logo_direct) | |
| st.image(logo_img, use_container_width=True) | |
| else: | |
| # Fallback: Try other paths | |
| logo_candidates = [ | |
| Path(project_root) / "mypace-logo.png", | |
| Path.cwd() / "mypace-logo.png", | |
| ] | |
| logo_path = None | |
| for candidate in logo_candidates: | |
| if candidate.exists(): | |
| logo_path = candidate | |
| break | |
| if logo_path: | |
| logo_img = Image.open(logo_path) | |
| st.image(logo_img, use_container_width=True) | |
| else: | |
| # Try HF URL | |
| hf_logo_url = "https://huggingface.co/spaces/abrahamcbe/myspace-ooty-analytics/resolve/main/mypace-logo.png" | |
| try: | |
| st.image(hf_logo_url, use_container_width=True) | |
| except Exception: | |
| # Use base64 | |
| try: | |
| logo_b64 = "".join(LOGO_B64.split()) | |
| st.image(f"data:image/png;base64,{logo_b64}", use_container_width=True) | |
| except Exception: | |
| st.markdown("<h1 style='text-align: center; font-size: 80px;'>π¨</h1>", unsafe_allow_html=True) | |
| except Exception as e: | |
| st.markdown("<h1 style='text-align: center; font-size: 80px;'>π¨</h1>", unsafe_allow_html=True) | |
| st.title("π¨ MySpace Ooty Holiday Inn") | |
| st.markdown("π Data Analytics Dashboard") | |
| st.markdown("---") | |
| # Navigation | |
| page = st.radio( | |
| "π Navigation", | |
| ["π Overview", "π KPIs & Metrics", "π Data Exploration", | |
| "π Trends & Analysis", "π― Custom Reports"] | |
| ) | |
| st.markdown("---") | |
| # Load data | |
| df = load_data() | |
| if df is not None: | |
| st.success("β Data Loaded Successfully!") | |
| st.metric("Records", f"{len(df):,}") | |
| st.markdown("---") | |
| st.subheader("π§ Filters") | |
| # Date range filter | |
| if 'Year' in df.columns and 'Month' in df.columns: | |
| year_options = sorted([y for y in df['Year'].unique() if pd.notna(y)]) | |
| selected_year = st.multiselect( | |
| "π Select Year(s)", | |
| year_options, | |
| default=year_options if year_options else [], | |
| help="Filter by booking year" | |
| ) | |
| month_options = sorted([m for m in df['Month'].unique() if pd.notna(m)]) | |
| selected_month = st.multiselect( | |
| "π Select Month(s)", | |
| month_options, | |
| default=month_options if month_options else [], | |
| help="Filter by booking month (1-12)" | |
| ) | |
| else: | |
| selected_year = [] | |
| selected_month = [] | |
| # Filter by booking status | |
| if 'Booking_Status' in df.columns: | |
| status_options = [s for s in df['Booking_Status'].unique() if pd.notna(s)] | |
| selected_status = st.multiselect( | |
| "β Select Booking Status", | |
| status_options, | |
| default=status_options if status_options else [], | |
| help="Filter by booking status" | |
| ) | |
| else: | |
| selected_status = [None] | |
| # Apply filters | |
| df_filtered = df.copy() | |
| if 'Year' in df.columns and selected_year: | |
| df_filtered = df_filtered[df_filtered['Year'].isin(selected_year)] | |
| if 'Month' in df.columns and selected_month: | |
| df_filtered = df_filtered[df_filtered['Month'].isin(selected_month)] | |
| if 'Booking_Status' in df_filtered.columns and selected_status and len(selected_status) > 0: | |
| df_filtered = df_filtered[df_filtered['Booking_Status'].isin(selected_status)] | |
| st.metric("Filtered Records", f"{len(df_filtered):,}") | |
| else: | |
| st.warning("β No data found. Please run the EDA notebook first.") | |
| df_filtered = None | |
| st.markdown("---") | |
| st.info("π‘ Tip: Use filters to customize your analysis") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # MAIN CONTENT - PAGE ROUTING | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if df_filtered is None or len(df_filtered) == 0: | |
| st.error("π¨ No data available. Please ensure the data file exists at `data/processed/data_cleaned_with_kpi.csv`") | |
| st.stop() | |
| # PAGE 1: OVERVIEW | |
| if page == "π Overview": | |
| st.title("π Dashboard Overview") | |
| st.markdown("Get a quick summary of your business metrics") | |
| # Expandable filters section | |
| with st.expander("π§ Active Filters", expanded=True): | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("Years Selected", len(selected_year)) | |
| with col2: | |
| st.metric("Months Selected", len(selected_month)) | |
| with col3: | |
| st.metric("Status Selected", len(selected_status)) | |
| st.info(f"π Showing {len(df_filtered):,} records out of {len(df):,} total") | |
| # Load KPI summary | |
| kpi_summary = load_kpi_summary() | |
| if kpi_summary is not None: | |
| kpis_dict = dict(zip(kpi_summary['Metric'], kpi_summary['Value'])) | |
| else: | |
| kpis_dict = {} | |
| # KPI Cards with Custom Styling | |
| st.markdown("### π Key Performance Indicators") | |
| # Calculate KPI values | |
| total_bookings = len(df_filtered) | |
| total_revenue = df_filtered[[col for col in df_filtered.columns if 'amount' in col.lower() or 'revenue' in col.lower()]].sum().sum() | |
| # Calculate average length of stay | |
| if 'No. Nights' in df_filtered.columns: | |
| avg_los = df_filtered['No. Nights'].fillna(0).astype(float).mean() | |
| elif 'Room_Nights' in df_filtered.columns: | |
| avg_los = df_filtered['Room_Nights'].fillna(0).astype(float).mean() | |
| else: | |
| nights_col = [col for col in df_filtered.columns if 'night' in col.lower()] | |
| avg_los = df_filtered[nights_col[0]].fillna(0).astype(float).mean() if nights_col else 0 | |
| avg_revenue = total_revenue / total_bookings if total_bookings > 0 else 0 | |
| # Display KPI cards using columns | |
| kpi_col1, kpi_col2, kpi_col3, kpi_col4 = st.columns([0.8, 1.1, 1.1, 1.1], gap="small") | |
| with kpi_col1: | |
| st.markdown(f""" | |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 25px; border-radius: 12px; text-align: center; color: white; box-shadow: 0 4px 15px rgba(0,0,0,0.1);"> | |
| <div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">TOTAL RECORDS</div> | |
| <div style="font-size: 32px; font-weight: bold;">{total_bookings:,}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with kpi_col2: | |
| st.markdown(f""" | |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 25px; border-radius: 12px; text-align: center; color: white; box-shadow: 0 4px 15px rgba(0,0,0,0.1);"> | |
| <div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">TOTAL REVENUE</div> | |
| <div style="font-size: 32px; font-weight: bold;">βΉ{total_revenue:,.0f}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with kpi_col3: | |
| st.markdown(f""" | |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 25px; border-radius: 12px; text-align: center; color: white; box-shadow: 0 4px 15px rgba(0,0,0,0.1);"> | |
| <div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">AVG. LENGTH OF STAY</div> | |
| <div style="font-size: 32px; font-weight: bold;">{avg_los:.1f} <span style="font-size: 14px; font-weight: 500;">nights</span></div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with kpi_col4: | |
| st.markdown(f""" | |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 25px; border-radius: 12px; text-align: center; color: white; box-shadow: 0 4px 15px rgba(0,0,0,0.1);"> | |
| <div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">REVENUE PER BOOKING</div> | |
| <div style="font-size: 32px; font-weight: bold;">βΉ{avg_revenue:,.0f}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Charts Row 1 | |
| st.markdown("---") | |
| st.markdown("### π Analytics") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.subheader("Bookings by Month") | |
| month_series = None | |
| if 'Month' in df_filtered.columns: | |
| month_series = df_filtered['Month'] | |
| else: | |
| date_col = next((c for c in df_filtered.columns if 'date' in c.lower()), None) | |
| if date_col: | |
| month_series = pd.to_datetime(df_filtered[date_col], errors='coerce').dt.month | |
| if month_series is not None: | |
| month_series = pd.to_numeric(month_series, errors='coerce').dropna().astype(int) | |
| if not month_series.empty: | |
| month_order = list(range(1, 13)) | |
| monthly_data = month_series.value_counts().reindex(month_order, fill_value=0).reset_index() | |
| monthly_data.columns = ['MonthNum', 'Bookings'] | |
| monthly_data['Month'] = pd.to_datetime(monthly_data['MonthNum'], format='%m').dt.strftime('%b') | |
| fig = px.bar( | |
| monthly_data, | |
| x='Month', | |
| y='Bookings', | |
| title="Monthly Booking Distribution", | |
| color='Bookings', | |
| color_continuous_scale='Blues' | |
| ) | |
| fig.update_layout(height=400, showlegend=False) | |
| st.plotly_chart(fig, use_container_width=True) | |
| else: | |
| st.info("No month data available for the current filters.") | |
| else: | |
| st.info("Month data not found. Add a Month or Date column to plot this chart.") | |
| with col2: | |
| st.subheader("Revenue Distribution") | |
| # Prioritize certain columns for revenue | |
| priority_cols = ['Total Amount', 'Booking Amount', 'Received Amount'] | |
| revenue_candidates = [ | |
| col for col in df_filtered.columns | |
| if any(k in col.lower() for k in ['amount', 'revenue', 'total', 'rate', 'price', 'cost']) | |
| ] | |
| # Sort by priority | |
| revenue_candidates = sorted( | |
| revenue_candidates, | |
| key=lambda x: (priority_cols.index(x) if x in priority_cols else len(priority_cols)) | |
| ) | |
| best_col = None | |
| best_score = 0 | |
| for col in revenue_candidates: | |
| cleaned = ( | |
| df_filtered[col] | |
| .astype(str) | |
| .str.replace(r'[^0-9.-]', '', regex=True) | |
| ) | |
| vals = pd.to_numeric(cleaned, errors='coerce') | |
| # Filter to positive values only (revenue should be > 0) | |
| positive_vals = vals[vals > 0] | |
| score = len(positive_vals) | |
| if score > best_score and positive_vals.sum() > 0: | |
| best_score = score | |
| best_col = col | |
| if best_col: | |
| cleaned = ( | |
| df_filtered[best_col] | |
| .astype(str) | |
| .str.replace(r'[^0-9.-]', '', regex=True) | |
| ) | |
| revenue_values = pd.to_numeric(cleaned, errors='coerce') | |
| # Filter to positive values only | |
| revenue_values = revenue_values[revenue_values > 0].dropna() | |
| if revenue_values.empty: | |
| st.info(f"No positive revenue values found in {best_col}") | |
| elif len(revenue_values) <= 5: | |
| st.info("Insufficient data for distribution chart") | |
| else: | |
| try: | |
| # Create histogram using DataFrame format for better Streamlit compatibility | |
| nbins = min(30, max(5, len(revenue_values) // 10)) | |
| df_hist = pd.DataFrame({'Revenue': revenue_values}) | |
| fig = px.histogram( | |
| df_hist, | |
| x='Revenue', | |
| nbins=nbins, | |
| title=f"Revenue Distribution - {best_col}", | |
| color_discrete_sequence=['#636EFA'] | |
| ) | |
| fig.update_layout( | |
| height=400, | |
| xaxis_title=best_col, | |
| yaxis_title="Count", | |
| hovermode='x unified', | |
| showlegend=False | |
| ) | |
| fig.update_traces(marker=dict(line=dict(width=0))) | |
| st.plotly_chart(fig, use_container_width=True) | |
| except Exception as e: | |
| st.error(f"Error creating histogram: {str(e)}") | |
| # Fallback: show summary stats in columns | |
| col_stats1, col_stats2, col_stats3 = st.columns(3) | |
| with col_stats1: | |
| st.metric("Average", f"βΉ{revenue_values.mean():,.0f}") | |
| with col_stats2: | |
| st.metric("Median", f"βΉ{revenue_values.median():,.0f}") | |
| with col_stats3: | |
| st.metric("Max", f"βΉ{revenue_values.max():,.0f}") | |
| else: | |
| st.info("Revenue column not found. Add a revenue/amount column to plot this chart.") | |
| # Charts Row 2 | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.subheader("Bookings by Day of Week") | |
| if 'Day_of_Week' in df_filtered.columns: | |
| dow_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] | |
| dow_data = df_filtered['Day_of_Week'].value_counts().reindex( | |
| [d for d in dow_order if d in df_filtered['Day_of_Week'].values] | |
| ).reset_index(name='Bookings') | |
| dow_data.columns = ['Day', 'Bookings'] | |
| fig = px.bar(dow_data, x='Day', y='Bookings', | |
| title="Bookings by Day of Week", | |
| color='Bookings', color_continuous_scale='Greens') | |
| fig.update_layout(height=400) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with col2: | |
| st.subheader("Holiday vs Regular Season") | |
| if 'Is_Holiday_Season' in df_filtered.columns: | |
| season_data = df_filtered['Is_Holiday_Season'].map({1: 'Holiday Season', 0: 'Regular Season'}).value_counts() | |
| fig = px.pie(values=season_data.values, names=season_data.index, | |
| title="Booking Distribution by Season", | |
| color_discrete_sequence=['#FF6B6B', '#4ECDC4']) | |
| fig.update_layout(height=400) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # PAGE 2: KPIs & METRICS | |
| elif page == "π KPIs & Metrics": | |
| st.title("π Key Performance Indicators") | |
| st.markdown("Detailed business metrics and performance indicators") | |
| kpi_summary = load_kpi_summary() | |
| if kpi_summary is not None: | |
| # Display KPI table | |
| st.subheader("Summary Metrics") | |
| st.dataframe( | |
| kpi_summary.head(15), | |
| use_container_width=True, | |
| height=400 | |
| ) | |
| # Calculate additional metrics | |
| st.markdown("---") | |
| st.subheader("Performance Analysis") | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| total_bookings = len(df_filtered) | |
| st.info(f"π Total Bookings\n\n**{total_bookings:,}** bookings") | |
| with col2: | |
| if 'Is_Weekend' in df_filtered.columns: | |
| weekend_bookings = (df_filtered['Is_Weekend'] == 1).sum() | |
| pct = (weekend_bookings / len(df_filtered) * 100) if len(df_filtered) > 0 else 0 | |
| st.info(f"π Weekend Bookings\n\n**{pct:.1f}%** of total") | |
| with col3: | |
| if 'Is_Holiday_Season' in df_filtered.columns: | |
| holiday_bookings = (df_filtered['Is_Holiday_Season'] == 1).sum() | |
| pct = (holiday_bookings / len(df_filtered) * 100) if len(df_filtered) > 0 else 0 | |
| st.info(f"π Holiday Season\n\n**{pct:.1f}%** of bookings") | |
| # PAGE 3: DATA EXPLORATION | |
| elif page == "π Data Exploration": | |
| st.title("π Exploratory Data Analysis") | |
| # Data Overview | |
| st.subheader("Dataset Overview") | |
| col1, col2, col3, col4 = st.columns(4) | |
| with col1: | |
| st.metric("Rows", f"{len(df_filtered):,}") | |
| with col2: | |
| st.metric("Columns", df_filtered.shape[1]) | |
| with col3: | |
| st.metric("Missing Values", f"{df_filtered.isnull().sum().sum():,}") | |
| with col4: | |
| st.metric("Duplicates", "0") | |
| # Display data | |
| st.subheader("Data Sample") | |
| st.dataframe(df_filtered.head(10), use_container_width=True) | |
| # Column statistics | |
| st.subheader("Column Statistics") | |
| numeric_cols = df_filtered.select_dtypes(include=[np.number]).columns.tolist() | |
| if numeric_cols: | |
| selected_cols = st.multiselect("Select columns to analyze", numeric_cols, default=numeric_cols[:5]) | |
| st.dataframe( | |
| df_filtered[selected_cols].describe().round(2), | |
| use_container_width=True | |
| ) | |
| # PAGE 4: TRENDS & ANALYSIS | |
| elif page == "π Trends & Analysis": | |
| st.title("π Trends & Statistical Analysis") | |
| # Monthly Trend | |
| st.subheader("Monthly Trends") | |
| if 'Month' in df_filtered.columns: | |
| monthly_bookings = df_filtered.groupby('Month').size() | |
| fig = px.line(x=monthly_bookings.index, y=monthly_bookings.values, | |
| labels={'x': 'Month', 'y': 'Bookings'}, | |
| title="Booking Trend Over Months", | |
| markers=True) | |
| fig.update_traces(line=dict(color='#FF6B6B', width=3), marker=dict(size=10)) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Revenue Trends | |
| st.markdown("---") | |
| st.subheader("Revenue Trends") | |
| revenue_cols = [col for col in df_filtered.columns if any(kw in col.lower() for kw in ['amount', 'revenue', 'total'])] | |
| if revenue_cols and 'Month' in df_filtered.columns: | |
| revenue_col = revenue_cols[0] | |
| monthly_revenue = df_filtered.groupby('Month')[revenue_col].sum() | |
| fig = px.line(x=monthly_revenue.index, y=monthly_revenue.values, | |
| labels={'x': 'Month', 'y': 'Revenue (βΉ)'}, | |
| title="Revenue Trend Over Months", | |
| markers=True) | |
| fig.update_traces(line=dict(color='#00CC96', width=3), marker=dict(size=10)) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # PAGE 5: CUSTOM REPORTS | |
| elif page == "π― Custom Reports": | |
| st.title("π― Custom Reports & Export") | |
| st.markdown("Generate personalized reports with selected filters") | |
| # Report options | |
| st.subheader("Report Configuration") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| report_type = st.selectbox( | |
| "Report Type", | |
| ["Summary Report", "Detailed Analysis", "Executive Summary", "Seasonal Analysis"] | |
| ) | |
| with col2: | |
| export_format = st.selectbox( | |
| "Export Format", | |
| ["CSV", "Excel", "PDF (Coming Soon)"] | |
| ) | |
| # Report preview | |
| st.subheader("Report Preview") | |
| st.info(f"π {report_type} - {len(df_filtered):,} records") | |
| st.dataframe(df_filtered.head(20), use_container_width=True) | |
| # Export button | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| if st.button("π₯ Download CSV", key="csv"): | |
| csv = df_filtered.to_csv(index=False) | |
| st.download_button( | |
| label="CSV Report", | |
| data=csv, | |
| file_name=f"myspace_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", | |
| mime="text/csv" | |
| ) | |
| with col2: | |
| if st.button("π Download Excel", key="excel"): | |
| excel_buffer = pd.ExcelWriter('/tmp/report.xlsx', engine='openpyxl') | |
| df_filtered.to_excel(excel_buffer, index=False) | |
| excel_buffer.close() | |
| with open('/tmp/report.xlsx', 'rb') as f: | |
| st.download_button( | |
| label="Excel Report", | |
| data=f.read(), | |
| file_name=f"myspace_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx", | |
| mime="application/vnd.ms-excel" | |
| ) | |
| with col3: | |
| if st.button("π Generate PowerPoint", key="ppt"): | |
| with st.spinner("π Generating PowerPoint presentation..."): | |
| try: | |
| # Generate PowerPoint report | |
| generator = PowerPointReportGenerator() | |
| ppt_path = generator.generate_report() | |
| # Read the file and provide download | |
| with open(ppt_path, 'rb') as f: | |
| st.download_button( | |
| label="π₯ Download PowerPoint", | |
| data=f.read(), | |
| file_name=f"MySpace_Ooty_Report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pptx", | |
| mime="application/vnd.openxmlformats-officedocument.presentationml.presentation" | |
| ) | |
| st.success("β PowerPoint report generated successfully!") | |
| except Exception as e: | |
| st.error(f"β Error generating PowerPoint: {str(e)}") | |
| # Footer | |
| st.markdown("---") | |
| st.markdown("") | |
| with st.container(): | |
| st.markdown("<h4 style='text-align: center; color: #193264;'>π¨ MySpace Holiday Inn - Ooty</h4>", unsafe_allow_html=True) | |
| col1, col2, col3 = st.columns([1, 2, 1]) | |
| with col2: | |
| st.markdown(""" | |
| <div style='text-align: center; font-size: 12px;'> | |
| <p><b>π HEAD OFFICE</b><br/>Kotagiri β 643217</p> | |
| <p><b>π CONTACT US</b><br/> | |
| +91 82206 62206 | +91-6369052954 | +91-6369973006<br/> | |
| π§ myspaceholidayinn@gmail.com<br/> | |
| π± +916381911228</p> | |
| <p><b>π TIMINGS</b><br/> | |
| Check-In: 12:00 PM | Check-Out: 10:00 AM</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.caption(f"π Data Analytics Dashboard | Last Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |