Spaces:
Build error
Build error
| import requests | |
| from bs4 import BeautifulSoup | |
| import json | |
| import re | |
| import pandas as pd | |
| from io import BytesIO | |
| from collections import namedtuple | |
| import numpy as np | |
| import streamlit as st | |
| from groq import Groq | |
| def clean_text(text): | |
| return re.sub(r'\s+', ' ', text).strip() | |
| def formatta_numero(stringa): | |
| stringa = stringa.split(',')[0] | |
| stringa = stringa.replace(".", "").replace("da", "").replace("€", "").replace("%","").replace("m²", "").replace("locali", "").strip() | |
| stringa = stringa.split(' ')[0] | |
| return stringa | |
| # Estrae le informazioni dagli ANNUNCI | |
| def extract_info(provincia, comune, prezzo_medio_mq, listing): | |
| info = {} | |
| superficie = "" | |
| locali = "" | |
| info['Provincia'] = provincia | |
| info['Comune'] = comune | |
| price_elem = listing.find('div', class_='in-listingCardPrice') | |
| prezzo = clean_text(price_elem.text) if price_elem else "" | |
| link_elem = listing.find('a', class_='in-listingCardTitle') | |
| if link_elem: | |
| link = link_elem['href'] | |
| titolo = link_elem.text.strip() | |
| feature_list = listing.find('div', class_='in-listingCardFeatureList') | |
| if feature_list: | |
| for item in feature_list.find_all('div', class_='in-listingCardFeatureList__item'): | |
| use_elem = item.find('use', class_='nd-icon__use') | |
| if use_elem: | |
| if use_elem.get('xlink:href') == '#planimetry': | |
| locali = item.find('span').text.strip() | |
| elif use_elem.get('xlink:href') == '#size': | |
| superficie = item.find('span').text.strip() | |
| image_url = "" | |
| img = listing.find('figure', class_='nd-figure nd-ratio in-photo') | |
| if img: | |
| image_url = img.find('img')['src'] | |
| superficie = formatta_numero(superficie) | |
| locali = formatta_numero(locali) | |
| prezzo = formatta_numero(prezzo) | |
| info['Immagine'] = image_url | |
| info['Titolo'] = titolo | |
| info['Prezzo'] = int(prezzo) | |
| info['Superficie'] = int(superficie) | |
| try: | |
| prezzo_numerico = int(prezzo) | |
| superficie_numerica = int(superficie) | |
| info['PrezzoMq'] = prezzo_numerico // superficie_numerica | |
| prezzo_medio_mq = formatta_numero(prezzo_medio_mq) | |
| prezzo_medio_mq_numerico = int(prezzo_medio_mq) | |
| differenza = prezzo_medio_mq_numerico - info['PrezzoMq'] | |
| vantaggio = (differenza / prezzo_medio_mq_numerico) * 100 | |
| vantaggio = max(0, vantaggio) | |
| vantaggio = int(vantaggio) | |
| except (ValueError, ZeroDivisionError): | |
| info['PrezzoMq'] = 0 | |
| vantaggio = 0 | |
| info['Locali'] = int(locali) | |
| info['Link'] = link | |
| info['PrezzoMedioMq'] = int(prezzo_medio_mq) | |
| info['Vantaggio'] = vantaggio | |
| if info['PrezzoMq']< int(prezzo_medio_mq) and info['PrezzoMq']>0: | |
| info['Vantaggioso'] = True | |
| else: | |
| info['Vantaggioso'] = False | |
| return info | |
| # Legge gli ANNUNCI (pagina x pagina) sulla base del COMUNE di appartenenza | |
| def scrape_immobiliare(provincia, comune, prezzo_medio_mq, prezzo_minimo, prezzo_massimo, locali_minimo, locali_massimo): | |
| print(provincia + " " + comune + " " + prezzo_medio_mq) | |
| comune_url = comune.replace(" ", "-") | |
| if tipologia_case == "Asta Immobiliare": | |
| tipologia_url = "aste-immobiliari" | |
| else: | |
| tipologia_url = "vendita-case" | |
| base_url = f"https://www.immobiliare.it/{tipologia_url}/{comune_url}/?prezzoMinimo={prezzo_minimo}&prezzoMassimo={prezzo_massimo}&localiMinimo={locali_minimo}&localiMassimo={locali_massimo}&random=123456" | |
| results = [] | |
| page = 1 | |
| url = base_url | |
| while True: | |
| print(f'Elaborazione pagina {page}') | |
| response = requests.get(url) | |
| soup = BeautifulSoup(response.content, 'html.parser') | |
| listings = soup.find_all('li', class_='nd-list__item in-searchLayoutListItem') | |
| if not listings: | |
| break | |
| for listing in listings: | |
| results.append(extract_info(provincia, comune, prezzo_medio_mq, listing)) | |
| pagination = soup.find('div', class_='in-pagination__list') | |
| if pagination: | |
| next_page = pagination.find('a', class_='in-pagination__item', string=lambda text: text and text.strip().isdigit()) | |
| if not next_page: | |
| break | |
| page += 1 | |
| url = base_url + '&pag=' + str(page) | |
| return json.dumps(results, ensure_ascii=False, indent=2) | |
| # Restituisce l'elenco dei COMUNI di una Provincia e il PREZZO MEDIO | |
| def get_elenco_comuni(provincia): | |
| base_url = f"https://www.immobiliare.it/mercato-immobiliare/lombardia/{provincia}-provincia/" | |
| results = [] | |
| print(f'Lettura Comuni e Prezzo Medio al Mq') | |
| response = requests.get(base_url) | |
| soup = BeautifulSoup(response.content, 'html.parser') | |
| rows = soup.find_all('tr', class_='nd-table__row') | |
| results = [] | |
| for row in rows: | |
| cells = row.find_all('td', class_='nd-table__cell') | |
| if len(cells) >= 2: | |
| comune = cells[0].get_text(strip=True) | |
| prezzo_vendita = cells[1].get_text(strip=True) | |
| results.append({ | |
| 'provincia': provincia, | |
| 'comune': comune, | |
| 'prezzo': prezzo_vendita | |
| }) | |
| return results | |
| st.set_page_config(layout="wide") | |
| st.title('🏠 Immobiliare A.I. ') | |
| st.write("##### Il tuo assistente di intelligenza artificiale per la ricerca di occasioni immobiliari") | |
| with st.expander("Informazioni"): | |
| st.write("Immobiliare A.I. è la webapp che semplifica la ricerca di immobili, grazie a algoritmi avanzati che calcolano il vantaggio di ogni offerta. Trova le migliori occasioni sul mercato con analisi precise e personalizzate. Scopri l’immobile giusto per te con facilità e sicurezza!") | |
| cerca_premuto = False | |
| comuni_provincia = {} | |
| with st.sidebar: | |
| if "numero_immobili_validi" not in st.session_state: | |
| st.numero_immobili_validi = 0 | |
| comuni_provincia_Brescia = get_elenco_comuni('Brescia') | |
| comuni_provincia_Bergamo = get_elenco_comuni('Bergamo') | |
| comuni_provincia = comuni_provincia_Brescia + comuni_provincia_Bergamo | |
| st.title("Filtri") | |
| tipologia_case = st.selectbox("Tipologia", ("Acquisto Immobile", "Asta Immobiliare")) | |
| elenco = [d['comune'] for d in comuni_provincia] | |
| comune_input = st.multiselect( | |
| "Comuni", | |
| elenco | |
| ) | |
| prezzo_minimo = st.sidebar.slider("Prezzo Minimo", min_value=0, max_value=1000, value=200) | |
| prezzo_massimo = st.sidebar.slider("Prezzo Massimo", min_value=0, max_value=1000, value=230) | |
| locali = list(range(1, 21)) # Intervallo da 1 a 10 | |
| locali_range = st.sidebar.select_slider( | |
| "Locali", | |
| options=locali, | |
| value=(locali[2], locali[4]) # Valore iniziale, da 1 a 5 locali | |
| ) | |
| mostra_grafici = st.toggle("Mostra grafici", value = True) | |
| analisi_ai = st.toggle("Analizza i dati tramite l'A.I.", value = True) | |
| locali_minimo, locali_massimo = locali_range | |
| prezzo_minimo = prezzo_minimo*1000 | |
| prezzo_massimo = prezzo_massimo*1000 | |
| cerca_premuto = st.button("Cerca", use_container_width=True, type='primary') | |
| def scrivi_dataframe(output, riepilogo): | |
| if len(output) > 0: | |
| st.numero_immobili_validi = st.numero_immobili_validi + 1 | |
| if not riepilogo: | |
| st.write(f"### {comune_provincia['comune']}") | |
| df = pd.DataFrame(output) | |
| df_originale = df.sort_values(by=["Vantaggio", "PrezzoMq"], ascending=[False, True]) | |
| if not riepilogo: | |
| columns_to_display = ["Vantaggioso", "Vantaggio", "Immagine", "Titolo", "PrezzoMq", "Prezzo", "Superficie", "Locali", "PrezzoMedioMq", "Link"] | |
| else: | |
| columns_to_display = ["Vantaggioso", "Vantaggio", "Immagine", "Comune", "Titolo", "PrezzoMq", "Prezzo", "Superficie", "Locali", "PrezzoMedioMq", "Link"] | |
| df = df_originale[columns_to_display] | |
| df = df.style.format(thousands='.') | |
| vantaggioso_count = df_originale["Vantaggioso"].sum() | |
| total_rows = df_originale.shape[0] | |
| if mostra_grafici: | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| vantaggioso_count = df_originale["Vantaggioso"].sum() | |
| total_rows = df_originale.shape[0] | |
| st.metric("Numero Immobili", int(total_rows), int(vantaggioso_count)) | |
| st.write('Prezzo Totale') | |
| chart_data = df_originale["Prezzo"] | |
| st.line_chart(chart_data, color = "#FF4B4B", height=110) | |
| with col2: | |
| vantaggioso_count = df_originale["Vantaggioso"].sum() | |
| total_rows = df_originale.shape[0] | |
| st.metric("% Immobili Vantaggiosi", int((vantaggioso_count/total_rows)* 100), 100-int((vantaggioso_count/total_rows)* 100)) | |
| st.write('Locali') | |
| immobili_per_locali = df_originale.sort_values(by=["Locali"], ascending=[True]).groupby("Locali").size() | |
| st.bar_chart(immobili_per_locali, color = "#ffb7b7", height=110) | |
| with col3: | |
| superficie_count = df_originale["Superficie"].sum() | |
| total_rows = df_originale.shape[0] | |
| st.metric("Media Superficie", int(superficie_count//total_rows), int(superficie_count)) | |
| st.write('Prezzo Medio al Metro Quadro') | |
| chart_data = df_originale["PrezzoMq"] | |
| st.area_chart(chart_data, color = "#FF4B4B", height=110) | |
| st.dataframe(df, hide_index=True, use_container_width=True, | |
| column_config ={ | |
| "Vantaggioso": st.column_config.CheckboxColumn("Vantaggioso"), | |
| "Vantaggio": st.column_config.ProgressColumn( | |
| "Punteggio", | |
| help="Vantaggio in %", | |
| format='%f', | |
| min_value=0, | |
| max_value=100, | |
| ), | |
| "Immagine": st.column_config.ImageColumn("Anteprima", help="Anteprima", width="small"), | |
| "PrezzoMq": "€/Mq", | |
| "PrezzoMedioMq": "Media €/Mq", | |
| "Prezzo": st.column_config.NumberColumn( | |
| "Prezzo Totale", | |
| help="Il prezzo totale dell'immobile in EURO", | |
| step=1, | |
| format="%d €", | |
| ), | |
| "Superficie": "Superficie", | |
| "Locali": "Locali", | |
| "Link": st.column_config.LinkColumn("App URL") | |
| }) | |
| st.write(f"Riepilogando nel comune {comune_provincia['comune']} sono presenti **{vantaggioso_count} Immobili vantaggiosi** rispetto ai {total_rows} totali") | |
| st.divider() | |
| def analizza_dati_ai(output): | |
| Ak = "XXXXgsk_COQPhsuaXqvuCkKPryiQWGdyb3FYCHBSIcn30wk3ZZay8WYC6tpIXXXX" | |
| client = Groq(api_key=Ak) | |
| prompt_messages = [ | |
| { | |
| "role": "system", | |
| "content": "Sei un'assistente virtuale specializzata nel supporto agli investitori immobiliari. Il tuo compito è analizzare i dati di un file CSV contenente un elenco di immobili e fornire consigli, idee e analisi dettagliate per massimizzare il profitto attraverso la riqualificazione e rivendita degli stessi. Devi considerare vari fattori come la posizione degli immobili, il prezzo di acquisto, il potenziale valore di mercato post-riqualificazione, i costi stimati per la ristrutturazione e altri dati pertinenti. Il tuo obiettivo è offrire suggerimenti strategici basati sui dati, evidenziare le migliori opportunità di investimento, identificare eventuali rischi, e fornire consigli pratici per ottimizzare i profitti. Rispondi in modo chiaro, conciso e professionale." | |
| }, | |
| { | |
| "role": "user", | |
| "content": f"Ho un file CSV con un elenco di immobili che sto considerando per un investimento. Vorrei che tu analizzassi i dati e mi fornissi consigli dettagliati su come posso massimizzare il mio profitto attraverso la riqualificazione e la rivendita di questi immobili. Per ogni immobile, per favore prendi in considerazione i seguenti aspetti:\n\n1. Valore attuale: Quanto vale l'immobile al momento dell'acquisto?\n2. Costi di riqualificazione stimati: Quanto potrebbe costare la ristrutturazione o la riqualificazione?\n3. Valore di mercato potenziale: Quanto potrebbe valere l'immobile una volta riqualificato?\n4. Rendimento potenziale: Qual è il margine di profitto previsto, tenendo conto dei costi totali?\n5. Tempistica di rivendita: In quanto tempo è probabile che l'immobile venga venduto una volta riqualificato?\n6. Analisi del mercato locale: Qual è la situazione del mercato immobiliare nella zona specifica? Ci sono trend emergenti?\n7. Rischi potenziali: Quali sono i possibili rischi o svantaggi legati a ciascun immobile?\nInfine, suggeriscimi le migliori opportunità di investimento tra gli immobili elencati, con una breve spiegazione del motivo per cui questi immobili sono i più promettenti.\n\n{output}" | |
| } | |
| ] | |
| response_area = st.empty() | |
| completion = client.chat.completions.create( | |
| model="llama-3.1-70b-versatile", | |
| messages=prompt_messages, | |
| temperature=1, | |
| max_tokens=1024, | |
| top_p=1, | |
| stream=True, | |
| stop=None, | |
| ) | |
| response_text = "" | |
| for chunk in completion: | |
| response_text += chunk.choices[0].delta.content or "" | |
| response_area.text(response_text) | |
| if cerca_premuto: | |
| if len(comune_input)>0: | |
| comuni_selezionati = comune_input | |
| comuni_selezionati = [comune.upper() for comune in comuni_selezionati] | |
| output = [] | |
| output_singolo = [] | |
| for comune_provincia in comuni_provincia: | |
| if comune_provincia['comune'].upper() in comuni_selezionati: | |
| with st.spinner(f"Ricerca Immobili Comune: {comune_provincia['comune']}"): | |
| output_singolo = json.loads(scrape_immobiliare(comune_provincia['provincia'], | |
| comune_provincia['comune'], | |
| comune_provincia['prezzo'], | |
| prezzo_minimo, | |
| prezzo_massimo, | |
| locali_minimo, | |
| locali_massimo)) | |
| scrivi_dataframe(output_singolo, False) | |
| output += output_singolo | |
| if len(comuni_selezionati)>1 and st.numero_immobili_validi>1: | |
| st.write(f"### Comuni Selezionati") | |
| scrivi_dataframe(output, True) | |
| if st.numero_immobili_validi > 0 and analisi_ai: | |
| analizza_dati_ai(output) | |
| st.success("Elaborazione Completata") | |
| else: | |
| st.error("Per favore, inserisci il nome di un comune.") |