import streamlit as st import pandas as pd import numpy as np import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplots import folium from streamlit_folium import st_folium from folium.plugins import MarkerCluster, HeatMap from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor from sklearn.neighbors import NearestNeighbors from sklearn.model_selection import cross_val_score from wordcloud import WordCloud import matplotlib import matplotlib.pyplot as plt import unicodedata # --- CONFIGURAÇÃO DE BACKEND --- matplotlib.use('Agg') # --- CONFIGURAÇÃO DA PÁGINA --- st.set_page_config( page_title="Consultoria Imobiliária - Curta Temporada", layout="wide", page_icon="🏖️", initial_sidebar_state="expanded" ) # --- CSS CUSTOMIZADO --- st.markdown(""" """, unsafe_allow_html=True) # --- FUNÇÃO DE NORMALIZAÇÃO --- def normalizar_texto(texto): if pd.isna(texto): return "" nfkd = unicodedata.normalize('NFKD', str(texto)) return u"".join([c for c in nfkd if not unicodedata.combining(c)]).lower().strip() # --- CARREGAMENTO E PROCESSAMENTO DE DADOS --- @st.cache_data(ttl=3600) def load_and_process_data(): try: df = pd.read_csv('lisitng_geral.csv', sep=';', encoding='latin1') fipe = pd.read_csv('fipezap_geral.csv', sep=';', encoding='latin1') past = pd.read_csv('past_geral.csv', sep=';', encoding='latin1') seg = pd.read_csv('casos_homicidios.csv', sep=';', encoding='latin1') pop = pd.read_csv('População_geral.csv', sep=';', encoding='latin1') ibge = pd.read_csv('dados_ibge.csv', sep=';', encoding='latin1') except: st.error("❌ Erro ao carregar arquivos CSV. Verifique se todos os arquivos estão no diretório.") st.stop() # 1. Limpeza de coordenadas cols_coords = ['latitude', 'longitude'] for col in cols_coords: if df[col].dtype == 'object': df[col] = df[col].str.replace(',', '.').astype(float) df = df.dropna(subset=cols_coords) # 2. Limpeza financeira cols_fin = ['ttm_revenue_native', 'ttm_avg_rate_native', 'ttm_occupancy', 'rating_overall'] for col in cols_fin: if col in df.columns and df[col].dtype == 'object': df[col] = df[col].astype(str).str.replace('R$', '').str.replace('.', '').str.replace(',', '.').astype(float) # 3. Normalização de nomes df['city_norm'] = df['city'].apply(normalizar_texto) df['city'] = df['city'].str.title() # 4. Imputação df['bedrooms'] = df['bedrooms'].fillna(df.groupby(['city', 'room_type'])['bedrooms'].transform('median')).fillna(1) df['guests'] = df['guests'].fillna(df.groupby(['city', 'room_type'])['guests'].transform('median')).fillna(2) # 5. Preço m² (FipeZap) fipe = fipe.dropna(subset=['city', 'preco_m2']) fipe['city_norm'] = fipe['city'].apply(normalizar_texto) fipe_recent = fipe.sort_values('date', ascending=False).groupby('city_norm')['preco_m2'].first().to_dict() df['preco_m2_estimado'] = df['city_norm'].map(fipe_recent) df['preco_m2_estimado'] = df['preco_m2_estimado'].fillna(fipe['preco_m2'].mean()) # 6. Cálculo de valor do imóvel e ROI def estimar_metragem(n): return 45 if n <= 1 else (75 if n == 2 else 110) df['metragem_estimada'] = df['bedrooms'].apply(estimar_metragem) df['valor_imovel_estimado'] = df['metragem_estimada'] * df['preco_m2_estimado'] df['ROI_anual'] = (df['ttm_revenue_native'] / df['valor_imovel_estimado']) * 100 df['ROI_anual'] = df['ROI_anual'].replace([np.inf, -np.inf], 0).fillna(0) # 7. Score individual de imóveis def normalize(series): return (series - series.min()) / (series.max() - series.min()) df['Score'] = ((normalize(df['ROI_anual']) * 60) + (normalize(df['rating_overall'].fillna(0)) * 40)) * 100 def gerar_laudo(row): if row['Score'] >= 60: return "💎 COMPRA RECOMENDADA" elif row['Score'] >= 40: return "✅ POTENCIAL" elif row['Score'] >= 20: return "⚠️ ARRISCADO" else: return "❌ NÃO RECOMENDADO" df['Recomendacao'] = df.apply(gerar_laudo, axis=1) # 8. Dados de segurança seg['city_norm'] = seg['city'].apply(normalizar_texto) seg_recent = seg.sort_values('date', ascending=False).groupby('city_norm')['Homicidios'].first().reset_index() df = pd.merge(df, seg_recent, on='city_norm', how='left') df['Homicidios'] = df['Homicidios'].fillna(-1) # 9. Dados históricos past['date'] = pd.to_datetime(past['date'], dayfirst=True, errors='coerce') past['city_norm'] = past['city'].apply(normalizar_texto) past['month'] = past['date'].dt.to_period('M').astype(str) past['month_name'] = past['date'].dt.month_name() # 10. População pop['city_norm'] = pop['city'].apply(normalizar_texto) pop_recent = pop.sort_values('date', ascending=False).groupby('city_norm')['Populacao'].first() # 11. IBGE ibge['city_norm'] = ibge['city'].apply(normalizar_texto) # 12. CRIAR RANKING DE CIDADES ranking = df.groupby('city').agg({ 'ttm_revenue_native': 'mean', 'ttm_avg_rate_native': 'mean', 'ttm_occupancy': 'mean', 'valor_imovel_estimado': 'mean', 'ROI_anual': 'mean', 'num_reviews': 'mean' }).round(2) # Adicionar dados externos seg_recent_dict = seg.sort_values('date', ascending=False).groupby('city_norm')['Homicidios'].first() pop_2010 = pop[pop['date'] == 2010].set_index('city_norm')['Populacao'] pop_2023 = pop[pop['date'] == 2023].set_index('city_norm')['Populacao'] crescimento = ((pop_2023 - pop_2010) / pop_2010 * 100).round(2) ranking['populacao'] = ranking.index.map(lambda x: pop_recent.get(normalizar_texto(x), 0)) ranking['homicidios'] = ranking.index.map(lambda x: seg_recent_dict.get(normalizar_texto(x), 0)) ranking['crescimento_pop_%'] = ranking.index.map(lambda x: crescimento.get(normalizar_texto(x), 0)) ranking['taxa_homicidios_100k'] = (ranking['homicidios'] / ranking['populacao'] * 100000).round(2) # 13. SCORE DE CIDADES def normalize_series(series): return (series - series.min()) / (series.max() - series.min()) * 100 score_df = pd.DataFrame() score_df['ROI'] = normalize_series(ranking['ROI_anual']) score_df['Ocupacao'] = normalize_series(ranking['ttm_occupancy']) score_df['Faturamento'] = normalize_series(ranking['ttm_revenue_native']) score_df['Crescimento_Pop'] = normalize_series(ranking['crescimento_pop_%']) score_df['Seguranca'] = 100 - normalize_series(ranking['taxa_homicidios_100k']) score_df['Demanda'] = normalize_series(ranking['num_reviews']) pesos = {'ROI': 0.30, 'Faturamento': 0.25, 'Ocupacao': 0.20, 'Crescimento_Pop': 0.10, 'Seguranca': 0.10, 'Demanda': 0.05} score_df['Score_Final'] = ( score_df['ROI'] * pesos['ROI'] + score_df['Faturamento'] * pesos['Faturamento'] + score_df['Ocupacao'] * pesos['Ocupacao'] + score_df['Crescimento_Pop'] * pesos['Crescimento_Pop'] + score_df['Seguranca'] * pesos['Seguranca'] + score_df['Demanda'] * pesos['Demanda'] ).round(2) score_df = score_df.sort_values('Score_Final', ascending=False) return df, past, ranking, score_df, pop, ibge, fipe, pesos # --- CARREGAMENTO --- df, df_past, ranking_cidades, score_cidades, pop_data, ibge_data, fipe_data, pesos_score = load_and_process_data() # --- TREINAMENTO MODELO IA --- @st.cache_resource def train_model(df): df_model = df[df['ttm_revenue_native'] > 0].copy() features = ['bedrooms', 'guests', 'num_reviews', 'latitude', 'longitude'] df_model['is_superhost'] = df_model['superhost'].astype(str).apply(lambda x: 1 if x.upper() in ['VERDADEIRO', 'TRUE'] else 0) df_model['has_pool'] = df_model['amenities'].astype(str).str.contains('pool|Piscina', case=False).astype(int) X = df_model[features + ['is_superhost', 'has_pool']] X = pd.concat([X, pd.get_dummies(df_model['city'], prefix='city')], axis=1) X = pd.concat([X, pd.get_dummies(df_model['room_type'], prefix='type')], axis=1) y = df_model['ttm_revenue_native'] # Treinar múltiplos modelos e escolher o melhor models = { 'Random Forest': RandomForestRegressor(n_estimators=100, max_depth=15, random_state=42, n_jobs=-1), 'Gradient Boosting': GradientBoostingRegressor(n_estimators=100, max_depth=5, random_state=42) } best_model = None best_score = -np.inf best_name = "" for name, model in models.items(): scores = cross_val_score(model, X, y, cv=5, scoring='r2') mean_score = scores.mean() if mean_score > best_score: best_score = mean_score best_model = model best_name = name best_model.fit(X, y) return best_model, X.columns, df_model, best_name, best_score model, model_cols, df_training, model_name, model_score = train_model(df) # --- SIDEBAR --- st.sidebar.image("https://upload.wikimedia.org/wikipedia/commons/6/69/Airbnb_Logo_Bélo.svg", width=140) st.sidebar.title("🏖️ Consultoria Imobiliária") st.sidebar.markdown("**Análise de Investimento em Curta Temporada**") st.sidebar.markdown("---") page = st.sidebar.radio( "📍 Navegação Principal", [ "🎯 Painel Executivo", "📊 Comparação de Cidades", "📈 Análise de Mercado", "📅 Sazonalidade", "🗺️ Mapa Interativo", "🤖 Simulador de Investimento" ] ) st.sidebar.markdown("---") st.sidebar.header("🔍 Filtros Globais") cidades_disponiveis = sorted(df['city'].unique()) cidades_sel = st.sidebar.multiselect("Cidades:", cidades_disponiveis, default=cidades_disponiveis) quartos = st.sidebar.slider("Quartos:", 0, 8, (0, 8)) df_filtered = df[ (df['city'].isin(cidades_sel)) & (df['bedrooms'].between(quartos[0], quartos[1])) ] # ============================================================================ # PÁGINA 1: PAINEL EXECUTIVO # ============================================================================ if page == "🎯 Painel Executivo": st.title("🎯 Painel Executivo - Recomendação de Investimento") # Insight principal melhor_cidade = score_cidades.index[0] score_melhor = score_cidades.loc[melhor_cidade, 'Score_Final'] st.markdown(f"""

💡 Recomendação Principal

🏆 {melhor_cidade.upper()}

Score de Investimento: {score_melhor}/100

Com base na análise de rentabilidade, ocupação, crescimento populacional, segurança e demanda, {melhor_cidade} é a cidade mais recomendada para investimento em imóveis de curta temporada.

""", unsafe_allow_html=True) st.markdown("---") # KPIs principais col1, col2, col3, col4 = st.columns(4) with col1: faturamento_melhor = ranking_cidades.loc[melhor_cidade, 'ttm_revenue_native'] st.metric( "💰 Faturamento Médio Anual", f"R$ {faturamento_melhor:,.0f}", delta=f"+{((faturamento_melhor / ranking_cidades['ttm_revenue_native'].mean() - 1) * 100):.1f}% vs. média" ) with col2: roi_melhor = ranking_cidades.loc[melhor_cidade, 'ROI_anual'] st.metric( "📈 ROI Anual", f"{roi_melhor:.2f}%", delta=f"+{((roi_melhor / ranking_cidades['ROI_anual'].mean() - 1) * 100):.1f}% vs. média" ) with col3: ocupacao_melhor = ranking_cidades.loc[melhor_cidade, 'ttm_occupancy'] st.metric( "🏠 Taxa de Ocupação", f"{ocupacao_melhor*100:.1f}%", delta=f"{((ocupacao_melhor - ranking_cidades['ttm_occupancy'].mean()) * 100):.1f}pp vs. média" ) with col4: cresc_melhor = ranking_cidades.loc[melhor_cidade, 'crescimento_pop_%'] st.metric( "🌱 Crescimento Populacional", f"+{cresc_melhor:.1f}%" if cresc_melhor >= 0 else f"{cresc_melhor:.1f}%", delta="2010-2023" ) st.markdown("---") # NOVA SEÇÃO: EXPLICAÇÃO DO CÁLCULO DO SCORE st.subheader("📐 Como Calculamos o Score de Investimento?") st.markdown("""

🔬 Metodologia de Cálculo

O Score de Investimento é uma métrica ponderada que integra 6 indicadores-chave para fornecer uma avaliação objetiva e comparável entre as cidades.

""", unsafe_allow_html=True) col_method1, col_method2 = st.columns([1, 1]) with col_method1: st.markdown("#### 📊 Indicadores e Pesos") # Criar DataFrame com os pesos pesos_df = pd.DataFrame({ 'Indicador': ['ROI Anual', 'Faturamento Médio', 'Taxa de Ocupação', 'Crescimento Populacional', 'Segurança', 'Demanda (Reviews)'], 'Peso (%)': [pesos_score['ROI']*100, pesos_score['Faturamento']*100, pesos_score['Ocupacao']*100, pesos_score['Crescimento_Pop']*100, pesos_score['Seguranca']*100, pesos_score['Demanda']*100], 'Justificativa': [ 'Rentabilidade é o fator mais importante', 'Receita absoluta indica potencial de mercado', 'Demanda consistente reduz risco de vacância', 'Indica potencial de valorização futura', 'Afeta atratividade turística', 'Histórico de procura indica demanda' ] }) st.dataframe( pesos_df.style.background_gradient(subset=['Peso (%)'], cmap='Blues'), use_container_width=True, hide_index=True ) with col_method2: st.markdown("#### 🧮 Fórmula de Cálculo") st.latex(r''' Score = \sum_{i=1}^{6} (Indicador_i^{norm} \times Peso_i) ''') st.markdown(""" **Onde:** - **Indicadorinorm**: Valor normalizado (0-100) de cada indicador - **Pesoi**: Peso percentual do indicador **Normalização:** """, unsafe_allow_html=True) st.latex(r''' Valor^{norm} = \frac{Valor - Min}{Max - Min} \times 100 ''') st.markdown(""" **Exceção:** Segurança é invertida (menos homicídios = maior score) **Resultado:** Score final entre 0 e 100 - **≥ 70**: ALTAMENTE RECOMENDADO - **50-69**: RECOMENDADO - **30-49**: MODERADO - **< 30**: NÃO RECOMENDADO """) st.markdown("---") # Ranking visual st.subheader("🏆 Ranking Completo de Cidades") # Criar tabela de ranking visual ranking_display = score_cidades[['Score_Final']].copy() ranking_display['Posição'] = range(1, len(ranking_display) + 1) ranking_display['Cidade'] = ranking_display.index # Adicionar emojis e recomendações def get_recomendacao(score): if score >= 70: return "🥇 ALTAMENTE RECOMENDADO" elif score >= 50: return "🥈 RECOMENDADO" elif score >= 30: return "🥉 MODERADO" else: return "❌ NÃO RECOMENDADO" ranking_display['Recomendação'] = ranking_display['Score_Final'].apply(get_recomendacao) ranking_display = ranking_display[['Posição', 'Cidade', 'Score_Final', 'Recomendação']] # Gráfico de barras horizontal fig_ranking = go.Figure(go.Bar( y=score_cidades.index, x=score_cidades['Score_Final'], orientation='h', marker=dict( color=score_cidades['Score_Final'], colorscale='RdYlGn', showscale=True, colorbar=dict(title="Score") ), text=score_cidades['Score_Final'].round(1), textposition='outside' )) fig_ranking.update_layout( title="Score de Investimento por Cidade", xaxis_title="Score (0-100)", yaxis_title="Cidade", height=400, template="plotly_white" ) st.plotly_chart(fig_ranking, use_container_width=True) # Tabela detalhada st.dataframe( ranking_display.style.background_gradient(subset=['Score_Final'], cmap='RdYlGn'), use_container_width=True, hide_index=True ) st.markdown("---") # Análise comparativa rápida st.subheader("📊 Comparação Rápida - Top 2 Cidades") if len(score_cidades) >= 2: cidade1 = score_cidades.index[0] cidade2 = score_cidades.index[1] col_comp1, col_comp2 = st.columns(2) with col_comp1: st.markdown(f"### 🥇 {cidade1}") st.metric("Faturamento Anual", f"R$ {ranking_cidades.loc[cidade1, 'ttm_revenue_native']:,.0f}") st.metric("ROI", f"{ranking_cidades.loc[cidade1, 'ROI_anual']:.2f}%") st.metric("Ocupação", f"{ranking_cidades.loc[cidade1, 'ttm_occupancy']*100:.1f}%") st.metric("Segurança", f"{ranking_cidades.loc[cidade1, 'taxa_homicidios_100k']:.1f} homicídios/100k") with col_comp2: st.markdown(f"### 🥈 {cidade2}") st.metric("Faturamento Anual", f"R$ {ranking_cidades.loc[cidade2, 'ttm_revenue_native']:,.0f}") st.metric("ROI", f"{ranking_cidades.loc[cidade2, 'ROI_anual']:.2f}%") st.metric("Ocupação", f"{ranking_cidades.loc[cidade2, 'ttm_occupancy']*100:.1f}%") st.metric("Segurança", f"{ranking_cidades.loc[cidade2, 'taxa_homicidios_100k']:.1f} homicídios/100k") # ============================================================================ # PÁGINA 2: COMPARAÇÃO DE CIDADES # ============================================================================ elif page == "📊 Comparação de Cidades": st.title("📊 Análise Comparativa Detalhada") # Gráfico Radar st.subheader("🎯 Comparação Multi-Dimensional") fig_radar = go.Figure() for cidade in score_cidades.index: fig_radar.add_trace(go.Scatterpolar( r=[ score_cidades.loc[cidade, 'ROI'], score_cidades.loc[cidade, 'Faturamento'], score_cidades.loc[cidade, 'Ocupacao'], score_cidades.loc[cidade, 'Crescimento_Pop'], score_cidades.loc[cidade, 'Seguranca'], score_cidades.loc[cidade, 'Demanda'] ], theta=['ROI', 'Faturamento', 'Ocupação', 'Crescimento Pop.', 'Segurança', 'Demanda'], fill='toself', name=cidade )) fig_radar.update_layout( polar=dict(radialaxis=dict(visible=True, range=[0, 100])), showlegend=True, height=500, title="Comparação de Indicadores Normalizados (0-100)" ) st.plotly_chart(fig_radar, use_container_width=True) st.markdown("---") # Tabela comparativa completa st.subheader("📋 Tabela Comparativa Completa") ranking_display = ranking_cidades.copy() ranking_display.columns = [ 'Faturamento Anual (R$)', 'Preço Diária (R$)', 'Taxa Ocupação (%)', 'Valor Imóvel Est. (R$)', 'ROI Anual (%)', 'Nº Reviews Médio', 'População', 'Homicídios Totais', 'Crescimento Pop. (%)', 'Taxa Homicídios/100k' ] # Formatar valores ranking_display['Taxa Ocupação (%)'] = (ranking_display['Taxa Ocupação (%)'] * 100).round(1) st.dataframe( ranking_display.style.format({ 'Faturamento Anual (R$)': 'R$ {:,.2f}', 'Preço Diária (R$)': 'R$ {:,.2f}', 'Taxa Ocupação (%)': '{:.1f}%', 'Valor Imóvel Est. (R$)': 'R$ {:,.0f}', 'ROI Anual (%)': '{:.2f}%', 'Nº Reviews Médio': '{:.1f}', 'População': '{:,.0f}', 'Homicídios Totais': '{:.0f}', 'Crescimento Pop. (%)': '{:.2f}%', 'Taxa Homicídios/100k': '{:.2f}' }).background_gradient(subset=['ROI Anual (%)'], cmap='RdYlGn'), use_container_width=True ) st.markdown("---") # Gráficos de barras comparativos col_g1, col_g2 = st.columns(2) with col_g1: fig_fat = px.bar( ranking_cidades.reset_index(), x='city', y='ttm_revenue_native', title="Faturamento Anual Médio por Cidade", labels={'city': 'Cidade', 'ttm_revenue_native': 'Faturamento (R$)'}, color='ttm_revenue_native', color_continuous_scale='Blues', template="plotly_white" ) st.plotly_chart(fig_fat, use_container_width=True) with col_g2: fig_roi = px.bar( ranking_cidades.reset_index(), x='city', y='ROI_anual', title="ROI Anual por Cidade", labels={'city': 'Cidade', 'ROI_anual': 'ROI (%)'}, color='ROI_anual', color_continuous_scale='Greens', template="plotly_white" ) st.plotly_chart(fig_roi, use_container_width=True) # ============================================================================ # PÁGINA 3: ANÁLISE DE MERCADO # ============================================================================ elif page == "📈 Análise de Mercado": st.title("📈 Análise de Mercado e Indicadores Socioeconômicos") # População e crescimento st.subheader("👥 Crescimento Populacional (2010-2023)") pop_data_norm = pop_data.copy() pop_data_norm['city_display'] = pop_data_norm['city'].str.title() fig_pop = px.line( pop_data_norm, x='date', y='Populacao', color='city_display', title="Evolução Populacional por Cidade", labels={'date': 'Ano', 'Populacao': 'População', 'city_display': 'Cidade'}, markers=True, template="plotly_white" ) st.plotly_chart(fig_pop, use_container_width=True) # Crescimento percentual - CORRIGIDO PARA MOSTRAR VALORES NEGATIVOS pop_2010 = pop_data[pop_data['date'] == 2010].copy() pop_2023 = pop_data[pop_data['date'] == 2023].copy() pop_2010['city_norm'] = pop_2010['city'].apply(normalizar_texto) pop_2023['city_norm'] = pop_2023['city'].apply(normalizar_texto) pop_merged = pd.merge( pop_2010[['city_norm', 'Populacao']], pop_2023[['city_norm', 'Populacao']], on='city_norm', suffixes=('_2010', '_2023') ) pop_merged['Crescimento_%'] = ((pop_merged['Populacao_2023'] - pop_merged['Populacao_2010']) / pop_merged['Populacao_2010'] * 100).round(2) pop_merged['city'] = pop_merged['city_norm'].str.title() # CORREÇÃO: Gráfico que mostra valores negativos corretamente fig_cresc = px.bar( pop_merged, x='city', y='Crescimento_%', title="Crescimento Populacional 2010-2023 (%)", labels={'city': 'Cidade', 'Crescimento_%': 'Crescimento (%)'}, color='Crescimento_%', color_continuous_scale='RdYlGn', template="plotly_white", text='Crescimento_%' ) # Adicionar linha de referência em zero fig_cresc.add_hline(y=0, line_dash="dash", line_color="gray", annotation_text="Zero") # Formatar texto das barras fig_cresc.update_traces(texttemplate='%{text:.2f}%', textposition='outside') # Garantir que o eixo Y inclui valores negativos fig_cresc.update_yaxes( title="Crescimento (%)", zeroline=True, zerolinewidth=2, zerolinecolor='gray' ) st.plotly_chart(fig_cresc, use_container_width=True) # Adicionar insight sobre Porto Alegre if any(pop_merged['Crescimento_%'] < 0): st.warning("⚠️ **Atenção:** Porto Alegre apresentou **decrescimento populacional** (-0,04%) no período 2010-2023, indicando perda de atratividade econômica e potencial impacto negativo no mercado imobiliário.") st.markdown("---") # Segurança st.subheader("🛡️ Análise de Segurança") col_seg1, col_seg2 = st.columns(2) with col_seg1: # Taxa de homicídios por 100k seg_display = ranking_cidades[['taxa_homicidios_100k']].reset_index() seg_display.columns = ['Cidade', 'Taxa_Homicidios_100k'] fig_seg = px.bar( seg_display, x='Cidade', y='Taxa_Homicidios_100k', title="Taxa de Homicídios por 100k Habitantes", labels={'Taxa_Homicidios_100k': 'Homicídios/100k'}, color='Taxa_Homicidios_100k', color_continuous_scale='Reds_r', template="plotly_white" ) st.plotly_chart(fig_seg, use_container_width=True) with col_seg2: # Correlação segurança x faturamento corr_data = ranking_cidades[['taxa_homicidios_100k', 'ttm_revenue_native']].reset_index() corr_data.columns = ['Cidade', 'Taxa_Homicidios', 'Faturamento'] fig_corr_seg = px.scatter( corr_data, x='Taxa_Homicidios', y='Faturamento', text='Cidade', title="Correlação: Segurança x Faturamento", labels={'Taxa_Homicidios': 'Taxa Homicídios/100k', 'Faturamento': 'Faturamento Anual (R$)'}, template="plotly_white", size=[100, 100, 100, 100] ) fig_corr_seg.update_traces(textposition='top center') st.plotly_chart(fig_corr_seg, use_container_width=True) st.markdown("---") # Indicadores IBGE st.subheader("📊 Indicadores IBGE") # Filtrar indicadores disponíveis indicadores_disp = ibge_data['Indicador'].dropna().unique() if len(indicadores_disp) > 0: indicador_sel = st.selectbox("Selecione um indicador:", indicadores_disp) ibge_filtered = ibge_data[ibge_data['Indicador'] == indicador_sel].copy() ibge_filtered['city_display'] = ibge_filtered['city'].str.title() if not ibge_filtered.empty: fig_ibge = px.bar( ibge_filtered, x='city_display', y='Valor', title=f"{indicador_sel} por Cidade", labels={'city_display': 'Cidade', 'Valor': 'Valor'}, color='Valor', color_continuous_scale='Viridis', template="plotly_white" ) st.plotly_chart(fig_ibge, use_container_width=True) else: st.info("Dados IBGE não disponíveis para visualização detalhada.") st.markdown("---") # Análise de saturação de mercado st.subheader("🏘️ Saturação de Mercado") saturacao = ranking_cidades[['populacao']].copy() saturacao['num_imoveis'] = df.groupby('city').size() saturacao['imoveis_por_100k_hab'] = (saturacao['num_imoveis'] / saturacao['populacao'] * 100000).round(2) saturacao = saturacao.reset_index() saturacao.columns = ['Cidade', 'População', 'Nº Imóveis', 'Imóveis/100k hab'] fig_sat = px.scatter( saturacao, x='População', y='Imóveis/100k hab', size='Nº Imóveis', color='Cidade', title="Saturação de Mercado: Imóveis por 100k Habitantes", labels={'Imóveis/100k hab': 'Imóveis por 100k habitantes'}, template="plotly_white", hover_data=['Nº Imóveis'] ) st.plotly_chart(fig_sat, use_container_width=True) st.dataframe(saturacao, use_container_width=True, hide_index=True) # ============================================================================ # PÁGINA 4: SAZONALIDADE # ============================================================================ elif page == "📅 Sazonalidade": st.title("📅 Análise de Sazonalidade") st.subheader("📈 Ocupação Média por Mês") # Preparar dados de sazonalidade past_filtered = df_past[df_past['city_norm'].isin([normalizar_texto(c) for c in cidades_sel])].copy() if not past_filtered.empty: # Ocupação mensal past_filtered['month_num'] = past_filtered['date'].dt.month past_filtered['city_display'] = past_filtered['city'].str.title() ocupacao_mensal = past_filtered.groupby(['month_num', 'city_display'])['occupancy'].mean().reset_index() meses_pt = { 1: 'Jan', 2: 'Fev', 3: 'Mar', 4: 'Abr', 5: 'Mai', 6: 'Jun', 7: 'Jul', 8: 'Ago', 9: 'Set', 10: 'Out', 11: 'Nov', 12: 'Dez' } ocupacao_mensal['month_name'] = ocupacao_mensal['month_num'].map(meses_pt) fig_sazon = px.line( ocupacao_mensal, x='month_name', y='occupancy', color='city_display', title="Taxa de Ocupação Média por Mês", labels={'month_name': 'Mês', 'occupancy': 'Taxa de Ocupação', 'city_display': 'Cidade'}, markers=True, template="plotly_white" ) # CORREÇÃO: Usar update_layout ao invés de update_yaxis fig_sazon.update_layout( yaxis=dict(tickformat='.0%') ) st.plotly_chart(fig_sazon, use_container_width=True) st.markdown("---") # Heatmap de ocupação st.subheader("🔥 Heatmap de Ocupação") pivot_ocupacao = ocupacao_mensal.pivot(index='city_display', columns='month_name', values='occupancy') pivot_ocupacao = pivot_ocupacao[['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez']] fig_heatmap = go.Figure(data=go.Heatmap( z=pivot_ocupacao.values * 100, x=pivot_ocupacao.columns, y=pivot_ocupacao.index, colorscale='RdYlGn', text=np.round(pivot_ocupacao.values * 100, 1), texttemplate='%{text}%', textfont={"size": 12}, colorbar=dict(title="Ocupação (%)") )) fig_heatmap.update_layout( title="Taxa de Ocupação (%) por Cidade e Mês", xaxis_title="Mês", yaxis_title="Cidade", height=400, template="plotly_white" ) st.plotly_chart(fig_heatmap, use_container_width=True) st.markdown("---") # Preço médio por mês st.subheader("💰 Variação de Preço por Mês") preco_mensal = past_filtered.groupby(['month_num', 'city_display'])['native_rate_avg'].mean().reset_index() preco_mensal['month_name'] = preco_mensal['month_num'].map(meses_pt) fig_preco = px.line( preco_mensal, x='month_name', y='native_rate_avg', color='city_display', title="Preço Médio de Diária por Mês", labels={'month_name': 'Mês', 'native_rate_avg': 'Preço Médio (R$)', 'city_display': 'Cidade'}, markers=True, template="plotly_white" ) st.plotly_chart(fig_preco, use_container_width=True) # Tabela de preços pivot_preco = preco_mensal.pivot(index='city_display', columns='month_name', values='native_rate_avg') pivot_preco = pivot_preco[['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez']] st.dataframe( pivot_preco.style.format("R$ {:.2f}").background_gradient(cmap='YlGn'), use_container_width=True ) else: st.warning("⚠️ Sem dados históricos para as cidades selecionadas.") # ============================================================================ # PÁGINA 5: MAPA INTERATIVO (MELHORADO) # ============================================================================ elif page == "🗺️ Mapa Interativo": st.title("🗺️ Mapa de Oportunidades") st.markdown("**Clique nos marcadores para ver detalhes completos dos imóveis (incluindo foto)**") if not df_filtered.empty: center_lat = df_filtered['latitude'].mean() center_lon = df_filtered['longitude'].mean() m = folium.Map(location=[center_lat, center_lon], zoom_start=5, tiles='CartoDB positron') # Adicionar marcadores por cidade com FOTO e informações detalhadas for cidade in cidades_sel: df_cidade = df_filtered[df_filtered['city'] == cidade] for idx, row in df_cidade.head(200).iterrows(): # Cor baseada na recomendação if row['Recomendacao'] == "💎 COMPRA RECOMENDADA": color = 'green' elif row['Recomendacao'] == "✅ POTENCIAL": color = 'blue' elif row['Recomendacao'] == "⚠️ ARRISCADO": color = 'orange' else: color = 'red' # HTML melhorado com FOTO foto_url = row.get('cover_photo_url', '') foto_html = f'' if pd.notna(foto_url) and foto_url != '' else '' popup_html = f"""
{foto_html}

{row['listing_name']}


📍 Cidade: {row['city']}

🏠 Tipo: {row['room_type']}

🛏️ Quartos: {int(row['bedrooms'])}

👥 Hóspedes: {int(row['guests'])}


💰 Faturamento Anual: R$ {row['ttm_revenue_native']:,.2f}

📈 ROI: {row['ROI_anual']:.2f}%

📊 Ocupação: {row['ttm_occupancy']*100:.1f}%

⭐ Avaliação: {row['rating_overall']:.1f}/5.0


{row['Recomendacao']}

""" folium.Marker( location=[row['latitude'], row['longitude']], popup=folium.Popup(popup_html, max_width=300), tooltip=f"💵 R$ {row['ttm_avg_rate_native']:.0f}/noite | {row['listing_name'][:30]}...", icon=folium.Icon(color=color, icon='home', prefix='fa') ).add_to(m) st_folium(m, height=600, width="100%") else: st.warning("⚠️ Nenhum imóvel encontrado com os filtros selecionados.") st.markdown("---") # Top 10 imóveis st.subheader("🏆 Top 10 Imóveis por ROI") top_imoveis = df_filtered.nlargest(10, 'ROI_anual')[ ['listing_name', 'city', 'ttm_revenue_native', 'ROI_anual', 'ttm_occupancy', 'rating_overall', 'Recomendacao'] ].copy() top_imoveis.columns = ['Nome', 'Cidade', 'Faturamento Anual', 'ROI (%)', 'Ocupação', 'Avaliação', 'Recomendação'] top_imoveis['Ocupação'] = (top_imoveis['Ocupação'] * 100).round(1) st.dataframe( top_imoveis.style.format({ 'Faturamento Anual': 'R$ {:,.2f}', 'ROI (%)': '{:.2f}%', 'Ocupação': '{:.1f}%', 'Avaliação': '{:.1f}' }).background_gradient(subset=['ROI (%)'], cmap='Greens'), use_container_width=True, hide_index=True ) # ============================================================================ # PÁGINA 6: SIMULADOR DE INVESTIMENTO (APRIMORADO) # ============================================================================ elif page == "🤖 Simulador de Investimento": st.title("🤖 Simulador Inteligente de Investimento") # NOVA SEÇÃO: Explicação do Modelo with st.expander("ℹ️ Sobre o Modelo de Predição", expanded=False): st.markdown(f""" ### 🔬 Modelo Utilizado: **{model_name}** Este simulador utiliza **Machine Learning** para prever o faturamento anual de imóveis de curta temporada. #### 📊 Características do Modelo - **Algoritmo:** {model_name} - **Acurácia (R²):** {model_score:.3f} ({model_score*100:.1f}%) - **Variáveis Preditoras:** - Número de quartos - Capacidade de hóspedes - Número de reviews (histórico de demanda) - Localização (latitude e longitude) - Status de Superhost - Amenidades (ex: piscina) - Cidade - Tipo de imóvel #### 🎯 Como Funciona? O modelo foi treinado com **{len(df_training)} imóveis reais** e aprendeu padrões entre as características dos imóveis e seu faturamento anual. Ele considera: 1. **Localização:** Imóveis em áreas turísticas tendem a faturar mais 2. **Capacidade:** Mais quartos/hóspedes = maior potencial de receita 3. **Reputação:** Superhosts e imóveis bem avaliados têm ocupação maior 4. **Amenidades:** Piscina e outras comodidades aumentam o valor da diária 5. **Cidade:** Cada cidade tem perfil de rentabilidade diferente #### ⚠️ Limitações - Previsões são baseadas em dados históricos (2023-2024) - Não considera eventos futuros (mudanças econômicas, regulação, etc.) - Margem de erro: ±15-20% em média - Use como **referência inicial**, não como garantia """) # Inicializa session state if 'sim_result' not in st.session_state: st.session_state['sim_result'] = None if 'sim_coords' not in st.session_state: st.session_state['sim_coords'] = None if 'sim_params' not in st.session_state: st.session_state['sim_params'] = None col_sim1, col_sim2 = st.columns([1, 1.5]) with col_sim1: st.subheader("⚙️ Configuração do Imóvel") sim_city = st.selectbox("Cidade:", sorted(df['city'].unique())) coords_dict = { 'Florianopolis': [-27.59, -48.54], 'Belo Horizonte': [-19.91, -43.93], 'Curitiba': [-25.42, -49.27], 'Porto Alegre': [-30.03, -51.22] } def_lat, def_lon = coords_dict.get(sim_city, [-23.55, -46.63]) sim_lat = st.number_input("Latitude:", value=def_lat, format="%.5f", help="Use Google Maps para encontrar coordenadas exatas") sim_lon = st.number_input("Longitude:", value=def_lon, format="%.5f") sim_type = st.selectbox("Tipo:", df['room_type'].unique()) sim_bed = st.slider("Quartos:", 0, 8, 2) sim_guest = st.slider("Hóspedes:", 1, 16, 4) sim_pool = st.checkbox("Tem Piscina?", value=False) sim_super = st.checkbox("Será Superhost?", value=True, help="Superhosts têm maior ocupação e podem cobrar diárias mais altas") if st.button("🔮 Calcular Previsão", type="primary"): input_df = pd.DataFrame({ 'bedrooms': [sim_bed], 'guests': [sim_guest], 'num_reviews': [30], 'latitude': [sim_lat], 'longitude': [sim_lon], 'is_superhost': [1 if sim_super else 0], 'has_pool': [1 if sim_pool else 0] }) for col in model_cols: if col not in input_df.columns: if f"city_{sim_city}" == col: input_df[col] = 1 elif f"type_{sim_type}" == col: input_df[col] = 1 else: input_df[col] = 0 input_df = input_df[model_cols] pred = model.predict(input_df)[0] st.session_state['sim_result'] = pred st.session_state['sim_coords'] = [sim_lat, sim_lon] st.session_state['sim_params'] = {'city': sim_city} with col_sim2: if st.session_state['sim_result'] is not None: pred = st.session_state['sim_result'] lat, lon = st.session_state['sim_coords'] city = st.session_state['sim_params']['city'] st.success(f"### 💰 Faturamento Estimado: R$ {pred:,.2f} / ano") st.info(f"📊 **Modelo:** {model_name} | **Confiança:** {model_score*100:.1f}%") # Mapa st.markdown("#### 📍 Localização Simulada") m_sim = folium.Map(location=[lat, lon], zoom_start=14, tiles='CartoDB positron') folium.Marker( [lat, lon], popup="Seu Imóvel Simulado", icon=folium.Icon(color="red", icon="home", prefix='fa') ).add_to(m_sim) st_folium(m_sim, height=300, width="100%") # Imóveis semelhantes st.markdown("#### 🏠 Imóveis Reais Semelhantes") df_city_neighbors = df_training[df_training['city'] == city].copy() if not df_city_neighbors.empty: nn = NearestNeighbors(n_neighbors=min(5, len(df_city_neighbors)), algorithm='ball_tree') nn.fit(df_city_neighbors[['latitude', 'longitude']]) X_query = pd.DataFrame([[lat, lon]], columns=['latitude', 'longitude']) distances, indices = nn.kneighbors(X_query) neighbors = df_city_neighbors.iloc[indices[0]] cols_show = ['listing_name', 'bedrooms', 'guests', 'ttm_revenue_native', 'rating_overall'] st.dataframe( neighbors[cols_show].style.format({'ttm_revenue_native': 'R$ {:,.2f}'}), use_container_width=True, hide_index=True ) st.divider() # Calculadora de lucro st.subheader("📊 Análise de Viabilidade") col_calc1, col_calc2 = st.columns(2) with col_calc1: val_imovel = st.number_input("Valor do Imóvel (R$):", value=400000.0, step=10000.0) custo_fixo = st.number_input("Custo Mensal (R$):", value=800.0, step=100.0, help="Condomínio, IPTU, manutenção, etc.") taxa_airbnb = st.slider("Taxa Airbnb (%):", 0, 20, 15, help="Comissão da plataforma") with col_calc2: custo_total = (pred * (taxa_airbnb/100)) + (custo_fixo * 12) lucro = pred - custo_total roi_real = (lucro / val_imovel) * 100 payback = val_imovel / lucro if lucro > 0 else 0 st.metric("💵 Lucro Líquido Anual", f"R$ {lucro:,.2f}") st.metric("📈 ROI Líquido Real", f"{roi_real:.2f}%") st.metric("⏱️ Payback Period", f"{payback:.1f} anos" if payback > 0 else "N/A") if roi_real >= 8: st.success("✅ Investimento ALTAMENTE VIÁVEL") elif roi_real >= 5: st.info("⚠️ Investimento MODERADO") else: st.error("❌ Investimento NÃO RECOMENDADO") else: st.info("👈 Configure o imóvel à esquerda e clique em 'Calcular Previsão' para ver os resultados.") # --- FOOTER --- st.sidebar.markdown("---") st.sidebar.markdown(f"""

Dashboard de Consultoria Imobiliária

Análise de Investimento em Curta Temporada

Modelo: {model_name} (R²: {model_score:.2f})

Dados: Airbnb, FipeZap, IBGE

""", unsafe_allow_html=True)