import streamlit as st import pandas as pd import numpy as np import plotly.express as px import folium from streamlit_folium import st_folium from folium.plugins import MarkerCluster from sklearn.ensemble import RandomForestRegressor from sklearn.neighbors import NearestNeighbors from wordcloud import WordCloud import matplotlib import matplotlib.pyplot as plt import unicodedata # --- CONFIGURAÇÃO DE BACKEND (Evita erros de memória/thread) --- matplotlib.use('Agg') # --- CONFIGURAÇÃO DA PÁGINA --- st.set_page_config(page_title="Airbnb Intelligence Pro", layout="wide", page_icon="🏘️") # --- CSS CUSTOMIZADO --- st.markdown(""" """, unsafe_allow_html=True) # --- FUNÇÃO DE NORMALIZAÇÃO DE TEXTO --- 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 LIMPEZA (CACHEADO) --- @st.cache_data(ttl=3600) def load_data(): try: # Carregamento dos arquivos df = pd.read_csv('lisitng_geral.csv', sep=';', encoding='utf-8') fipe = pd.read_csv('fipezap_geral.csv', sep=';', encoding='utf-8') past = pd.read_csv('past_geral.csv', sep=';', encoding='utf-8') seg = pd.read_csv('casos_homicidios.csv', sep=';', encoding='utf-8') except: # Fallback de encoding e nomes try: df = pd.read_csv('lisitng_geral.csv', sep=';', encoding='latin1') fipe = pd.read_csv('fipezap_geral (1).csv', sep=';', encoding='latin1') past = pd.read_csv('past_geral (1).csv', sep=';', encoding='latin1') seg = pd.read_csv('casos_homicidios (1).csv', sep=';', encoding='latin1') except FileNotFoundError: st.error("ERRO CRÍTICO: Arquivos CSV não encontrados. Verifique o upload.") 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) # Normalização df['city_norm'] = df['city'].apply(normalizar_texto) df['city'] = df['city'].str.title() # 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) # 3. Limpeza FipeZap fipe = fipe.dropna(subset=['city', 'preco_m2']) fipe['city_norm'] = fipe['city'].apply(normalizar_texto) media_cidade = fipe.groupby('city_norm')['preco_m2'].mean().to_dict() df['preco_m2_estimado'] = df['city_norm'].map(media_cidade) df['preco_m2_estimado'] = df['preco_m2_estimado'].fillna(fipe['preco_m2'].mean()) # 4. Cálculos KPI 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) # Score 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) # 5. 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) # 6. Histórico past['date'] = pd.to_datetime(past['date'], dayfirst=True, errors='coerce') past['city_norm'] = past['city'].apply(normalizar_texto) return df, past # --- 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'] model = RandomForestRegressor(n_estimators=50, random_state=42, n_jobs=-1) model.fit(X, y) return model, X.columns, df_model # --- CARREGAMENTO --- df, df_past = load_data() model, model_cols, df_training = train_model(df) # --- SIDEBAR --- st.sidebar.image("https://upload.wikimedia.org/wikipedia/commons/6/69/Airbnb_Logo_Bélo.svg", width=140) st.sidebar.header("Navegação") page = st.sidebar.radio( "Menu Principal", ["📊 Dashboard de Mercado", "📈 Análise Exploratória", "🤖 Simulador IA"], label_visibility="collapsed" ) 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: DASHBOARD --- if page == "📊 Dashboard de Mercado": st.title("📍 Inteligência Imobiliária Airbnb") kpi1, kpi2, kpi3, kpi4 = st.columns(4) kpi1.metric("Imóveis Analisados", len(df_filtered)) kpi2.metric("Preço Médio/Noite", f"R$ {df_filtered['ttm_avg_rate_native'].median():.0f}") kpi3.metric("Faturamento Médio", f"R$ {df_filtered['ttm_revenue_native'].mean():,.0f}") kpi4.metric("ROI Estimado", f"{df_filtered['ROI_anual'].median():.1f}% a.a.") st.markdown("---") st.subheader("Mapa de Oportunidades") st.caption("Clique nos círculos coloridos para ver a Ficha Técnica completa abaixo.") map_data = None selected_listing = None 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=12, tiles='CartoDB positron') marker_cluster = MarkerCluster().add_to(m) for idx, row in df_filtered.head(1000).iterrows(): color = '#2ecc71' if row['Recomendacao'] == "💎 COMPRA RECOMENDADA" else '#3498db' folium.CircleMarker( location=[row['latitude'], row['longitude']], radius=6, color=color, fill=True, fill_color=color, fill_opacity=0.8, popup=row['listing_name'], tooltip=f"R$ {row['ttm_avg_rate_native']:.0f}" ).add_to(marker_cluster) map_data = st_folium(m, height=450, width="100%") else: st.warning("Sem dados.") # --- RESTAURAÇÃO DA FICHA TÉCNICA DETALHADA --- if map_data and map_data.get('last_object_clicked_popup'): name_clicked = map_data['last_object_clicked_popup'] match = df_filtered[df_filtered['listing_name'] == name_clicked] if not match.empty: selected_listing = match.iloc[0] if selected_listing is not None: st.markdown("### 📋 Ficha Técnica do Imóvel") with st.container(): # Cabeçalho estilizado st.markdown(f"""

🏡 {selected_listing['listing_name']}

""", unsafe_allow_html=True) c_img, c_info, c_kpi = st.columns([1, 1, 1]) with c_img: if pd.notna(selected_listing['cover_photo_url']): st.image(selected_listing['cover_photo_url'], use_container_width=True) else: st.info("Sem foto disponível") with c_info: st.markdown(f"**📍 Cidade:** {selected_listing['city']}") st.markdown(f"**🏠 Tipo:** {selected_listing['room_type']}") st.markdown(f"**👥 Acomoda:** {int(selected_listing['guests'])} pessoas") st.markdown(f"**🛏️ Quartos:** {int(selected_listing['bedrooms'])}") st.metric("Nota Geral", f"⭐ {selected_listing['rating_overall']}") with c_kpi: st.metric("Faturamento Anual", f"R$ {selected_listing['ttm_revenue_native']:,.2f}") st.metric("ROI Estimado", f"{selected_listing['ROI_anual']:.2f}%") status = selected_listing['Recomendacao'] if "COMPRA" in status: st.success(f"## {status}") elif "NÃO" in status: st.error(f"## {status}") else: st.warning(f"## {status}") else: st.info("👆 Clique em uma bolinha colorida no mapa acima para ver a análise completa do imóvel aqui.") st.markdown("---") tab_sazonal, tab_seg, tab_word = st.tabs(["📅 Sazonalidade & Preços", "🛡️ Segurança", "☁️ Amenities"]) with tab_sazonal: cidades_norm = [normalizar_texto(c) for c in cidades_sel] past_filt = df_past[df_past['city_norm'].isin(cidades_norm)].copy() col_graf, col_tab = st.columns([2, 1]) if not past_filt.empty: past_filt['month_str'] = past_filt['date'].dt.strftime('%Y-%m') seasonal = past_filt.groupby('month_str')['occupancy'].mean().reset_index() fig_line = px.line(seasonal, x='month_str', y='occupancy', markers=True, title="Taxa de Ocupação Média", template="plotly_white") fig_line.update_traces(line_color='#FF5A5F') col_graf.plotly_chart(fig_line, use_container_width=True) st.subheader("💰 Preço Médio por Mês (R$)") price_table = past_filt.groupby(['month_str', 'city'])['native_rate_avg'].mean().unstack().fillna(0) st.dataframe(price_table.style.format("R$ {:.2f}"), use_container_width=True) else: st.warning("Sem dados históricos.") with tab_seg: st.subheader("Relação Violência Urbana x Receita") if 'Homicidios' in df_filtered.columns: fig_seg = px.scatter(df_filtered[df_filtered['Homicidios'] >= 0], x="Homicidios", y="ttm_revenue_native", color="city", template="plotly_white", hover_name="listing_name") st.plotly_chart(fig_seg, use_container_width=True) with tab_word: st.subheader("O que os imóveis Top Performers oferecem?") top = df_filtered[df_filtered['Recomendacao'] == "💎 COMPRA RECOMENDADA"] text = " ".join(str(a) for a in top['amenities'].dropna()) if text: wc = WordCloud(width=800, height=300, background_color='white').generate(text) fig, ax = plt.subplots() ax.imshow(wc, interpolation='bilinear') ax.axis("off") st.pyplot(fig) plt.close(fig) # --- PÁGINA 2: EXPLORATÓRIA --- elif page == "📈 Análise Exploratória": st.title("📈 Análise Exploratória de Dados") col1, col2 = st.columns(2) with col1: st.subheader("Distribuição de Preços") fig_hist = px.histogram(df_filtered, x="ttm_avg_rate_native", nbins=50, title="Frequência de Preços de Diária", color_discrete_sequence=['#FF5A5F'], template="plotly_white") st.plotly_chart(fig_hist, use_container_width=True) with col2: st.subheader("Preços por Cidade") fig_box = px.box(df_filtered, x="city", y="ttm_avg_rate_native", title="Variação de Preço por Cidade", color="city", template="plotly_white") st.plotly_chart(fig_box, use_container_width=True) st.markdown("---") col3, col4 = st.columns(2) with col3: st.subheader("Correlação: Nota vs Faturamento") fig_corr = px.scatter(df_filtered, x="rating_overall", y="ttm_revenue_native", color="room_type", size="bedrooms", title="Nota impacta no Faturamento?", template="plotly_white", opacity=0.7) st.plotly_chart(fig_corr, use_container_width=True) with col4: st.subheader("Tipos de Imóveis") df_counts = df_filtered['room_type'].value_counts().reset_index() df_counts.columns = ['Tipo_Imovel', 'Contagem'] fig_bar = px.bar(df_counts, x='Tipo_Imovel', y='Contagem', labels={'Tipo_Imovel': 'Tipo', 'Contagem': 'Quantidade'}, title="Contagem por Tipo de Imóvel", template="plotly_white") st.plotly_chart(fig_bar, use_container_width=True) # --- PÁGINA 3: SIMULADOR (COM SESSION STATE PARA NÃO SUMIR) --- elif page == "🤖 Simulador IA": st.title("🤖 Simulador Inteligente") # Inicializa variáveis de estado se não existirem 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 c1, c2 = st.columns([1, 1.5]) with c1: st.subheader("Parâmetros 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") 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) if st.button("🔮 Calcular Previsão"): # Salva parâmetros na sessão 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] # ATUALIZA O ESTADO st.session_state['sim_result'] = pred st.session_state['sim_coords'] = [sim_lat, sim_lon] st.session_state['sim_params'] = {'city': sim_city} with c2: # VERIFICA SE HÁ RESULTADO GRAVADO NA SESSÃO 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.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") ).add_to(m_sim) st_folium(m_sim, height=300, width="100%") st.markdown("#### 🏠 Imóveis Reais Semelhantes (Vizinhança)") df_city_neighbors = df_training[df_training['city'] == city].copy() if not df_city_neighbors.empty: nn = NearestNeighbors(n_neighbors=5, 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 ) else: st.info("Não há imóveis suficientes nesta cidade para comparação.") st.divider() st.subheader("📊 Calculadora de Lucro Líquido") col_calc1, col_calc2 = st.columns(2) with col_calc1: val_imovel = st.number_input("Valor do Imóvel (R$):", value=400000.0) custo_fixo = st.number_input("Custo Mensal (R$):", value=800.0) with col_calc2: custo_total = (pred * 0.15) + (custo_fixo * 12) lucro = pred - custo_total roi_real = (lucro / val_imovel) * 100 st.metric("Lucro Líquido Anual", f"R$ {lucro:,.2f}") st.metric("ROI Líquido Real", f"{roi_real:.2f}%")