Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -20,9 +20,8 @@ except ImportError:
|
|
| 20 |
subprocess.run(["pip", "install", "fake-useragent"], check=True)
|
| 21 |
from fake_useragent import UserAgent
|
| 22 |
|
| 23 |
-
# --- 1. GENERADOR DE URLS (
|
| 24 |
-
def construir_urls_final(barrio, ciudad, tipo, hab, ban, park, antiguedad):
|
| 25 |
-
# Mapeo exacto de antigüedad (Con "anos")
|
| 26 |
mapa_ant = {
|
| 27 |
"Menos de 1 año": "de-0-a-1-anos",
|
| 28 |
"1 a 8 años": "de-1-a-8-anos",
|
|
@@ -33,44 +32,52 @@ def construir_urls_final(barrio, ciudad, tipo, hab, ban, park, antiguedad):
|
|
| 33 |
slug_ant = mapa_ant.get(antiguedad, "de-1-a-8-anos")
|
| 34 |
slug_park = f"{int(park)}-parqueadero" if int(park) == 1 else f"{int(park)}-parqueaderos"
|
| 35 |
|
| 36 |
-
# Limpieza de zona
|
| 37 |
b_slug = barrio.lower().strip().replace(" ", "-")
|
| 38 |
c_slug = ciudad.lower().strip().replace(" ", "-")
|
|
|
|
| 39 |
|
| 40 |
-
# Manejo de tipos de inmueble (Agrupación descubierta por el usuario)
|
| 41 |
tipo_slug = tipo.lower().strip()
|
| 42 |
if tipo_slug in ["apartamento", "casa"]:
|
| 43 |
tipo_fr = "casas-y-apartamentos-y-apartaestudios"
|
| 44 |
else:
|
| 45 |
-
tipo_fr = tipo_slug + "s"
|
| 46 |
|
| 47 |
-
# --- URL FINCA RAÍZ
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
| 52 |
|
|
|
|
|
|
|
| 53 |
# --- URL METROCUADRADO ---
|
| 54 |
-
|
|
|
|
| 55 |
|
| 56 |
return url_fr, url_mc
|
| 57 |
|
| 58 |
-
# --- 2. EXTRACTOR DE PRECIO
|
| 59 |
-
def extraer_precio_regex(texto):
|
| 60 |
patron = r'\$\s?(\d{1,3}(?:[.,]\d{3})*)'
|
| 61 |
coincidencias = re.findall(patron, texto)
|
| 62 |
if coincidencias:
|
| 63 |
precios = [int(p.replace('.', '').replace(',', '')) for p in coincidencias]
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
if precios_validos:
|
| 67 |
return precios_validos[0]
|
| 68 |
return 0
|
| 69 |
|
| 70 |
-
# --- 3. MOTOR DE EXTRACCIÓN
|
| 71 |
-
def motor_tramitia_final(barrio, ciudad, area, tipo, hab, ban, park, antiguedad):
|
| 72 |
resultados = []
|
| 73 |
-
url_fr, url_mc = construir_urls_final(barrio, ciudad, tipo, hab, ban, park, antiguedad)
|
| 74 |
|
| 75 |
log_visible = f"✅ URLs GENERADAS (AUDITORÍA):\nFR: {url_fr}\nMC: {url_mc}\n\n"
|
| 76 |
ua = UserAgent()
|
|
@@ -91,7 +98,7 @@ def motor_tramitia_final(barrio, ciudad, area, tipo, hab, ban, park, antiguedad)
|
|
| 91 |
# --- FINCA RAÍZ ---
|
| 92 |
try:
|
| 93 |
page = context.new_page()
|
| 94 |
-
log_visible += "🔄 Escaneando Finca Raíz...\n"
|
| 95 |
page.goto(url_fr, wait_until="domcontentloaded", timeout=60000)
|
| 96 |
|
| 97 |
page.mouse.move(random.randint(100, 500), random.randint(100, 500))
|
|
@@ -107,7 +114,7 @@ def motor_tramitia_final(barrio, ciudad, area, tipo, hab, ban, park, antiguedad)
|
|
| 107 |
txt = el.inner_text()
|
| 108 |
|
| 109 |
if "$" in txt:
|
| 110 |
-
precio = extraer_precio_regex(txt)
|
| 111 |
if precio > 0:
|
| 112 |
href = el.get_attribute("href")
|
| 113 |
full_url = f"https://www.fincaraiz.com.co{href}" if href and href.startswith("/") else href
|
|
@@ -128,7 +135,7 @@ def motor_tramitia_final(barrio, ciudad, area, tipo, hab, ban, park, antiguedad)
|
|
| 128 |
# --- METROCUADRADO ---
|
| 129 |
try:
|
| 130 |
page = context.new_page()
|
| 131 |
-
log_visible += "🔄 Escaneando Metrocuadrado...\n"
|
| 132 |
page.goto(url_mc, wait_until="domcontentloaded", timeout=60000)
|
| 133 |
|
| 134 |
for _ in range(5):
|
|
@@ -143,7 +150,7 @@ def motor_tramitia_final(barrio, ciudad, area, tipo, hab, ban, park, antiguedad)
|
|
| 143 |
txt = card.inner_text()
|
| 144 |
|
| 145 |
if "$" in txt:
|
| 146 |
-
precio = extraer_precio_regex(txt)
|
| 147 |
if precio > 0:
|
| 148 |
enlace = card.query_selector("a")
|
| 149 |
if enlace:
|
|
@@ -184,7 +191,7 @@ def motor_tramitia_final(barrio, ciudad, area, tipo, hab, ban, park, antiguedad)
|
|
| 184 |
pdf = FPDF()
|
| 185 |
pdf.add_page()
|
| 186 |
pdf.set_font("Arial", 'B', 14)
|
| 187 |
-
pdf.cell(0, 10, f"ESTUDIO DE MERCADO: {barrio.upper()}", ln=True)
|
| 188 |
pdf.ln(5)
|
| 189 |
|
| 190 |
for _, r in df_final.iterrows():
|
|
@@ -205,8 +212,8 @@ def motor_tramitia_final(barrio, ciudad, area, tipo, hab, ban, park, antiguedad)
|
|
| 205 |
maximo = df_final['Precio'].max()
|
| 206 |
|
| 207 |
resumen = (
|
| 208 |
-
f"💰 **ESTIMACIÓN DE
|
| 209 |
-
f"🔹 **
|
| 210 |
f"📉 Mínimo Zona: ${minimo:,.0f}\n"
|
| 211 |
f"📈 Máximo Zona: ${maximo:,.0f}"
|
| 212 |
)
|
|
@@ -217,20 +224,28 @@ def motor_tramitia_final(barrio, ciudad, area, tipo, hab, ban, park, antiguedad)
|
|
| 217 |
|
| 218 |
# --- INTERFAZ GRÁFICA ---
|
| 219 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 220 |
-
gr.Markdown("## 🤖 TramitIA Pro:
|
| 221 |
|
| 222 |
with gr.Row():
|
| 223 |
with gr.Column(scale=1):
|
|
|
|
|
|
|
| 224 |
c = gr.Textbox(label="Ciudad", value="Barranquilla")
|
| 225 |
b = gr.Textbox(label="Barrio (Ej: La Concepcion)", value="La Concepcion")
|
| 226 |
-
a = gr.Number(label="Área M2", value=70)
|
| 227 |
-
|
| 228 |
-
t = gr.Dropdown(
|
| 229 |
-
["Apartamento", "Casa", "Bodega", "Lote", "Oficina"],
|
| 230 |
-
label="Tipo",
|
| 231 |
-
value="Apartamento"
|
| 232 |
-
)
|
| 233 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
with gr.Row():
|
| 235 |
h = gr.Number(label="Habitaciones", value=3)
|
| 236 |
ban = gr.Number(label="Baños", value=2)
|
|
@@ -240,7 +255,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 240 |
["Menos de 1 año", "1 a 8 años", "9 a 15 años", "16 a 30 años", "Más de 30 años"],
|
| 241 |
label="Antigüedad", value="1 a 8 años"
|
| 242 |
)
|
| 243 |
-
btn = gr.Button("EJECUTAR ANÁLISIS", variant="primary")
|
| 244 |
|
| 245 |
with gr.Column(scale=2):
|
| 246 |
res_fin = gr.Markdown("### 💰 El resultado aparecerá aquí...")
|
|
@@ -249,6 +264,8 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 249 |
with gr.TabItem("Tabla de Resultados"): out_df = gr.Dataframe()
|
| 250 |
with gr.TabItem("Descargar PDF"): out_pdf = gr.File()
|
| 251 |
|
| 252 |
-
btn.click(motor_tramitia_final,
|
|
|
|
|
|
|
| 253 |
|
| 254 |
demo.launch()
|
|
|
|
| 20 |
subprocess.run(["pip", "install", "fake-useragent"], check=True)
|
| 21 |
from fake_useragent import UserAgent
|
| 22 |
|
| 23 |
+
# --- 1. GENERADOR DE URLS (VENTA/ARRIENDO Y EXTRAS) ---
|
| 24 |
+
def construir_urls_final(operacion, barrio, ciudad, tipo, hab, ban, park, antiguedad, m2_min, m2_max, ascensor, piscina):
|
|
|
|
| 25 |
mapa_ant = {
|
| 26 |
"Menos de 1 año": "de-0-a-1-anos",
|
| 27 |
"1 a 8 años": "de-1-a-8-anos",
|
|
|
|
| 32 |
slug_ant = mapa_ant.get(antiguedad, "de-1-a-8-anos")
|
| 33 |
slug_park = f"{int(park)}-parqueadero" if int(park) == 1 else f"{int(park)}-parqueaderos"
|
| 34 |
|
|
|
|
| 35 |
b_slug = barrio.lower().strip().replace(" ", "-")
|
| 36 |
c_slug = ciudad.lower().strip().replace(" ", "-")
|
| 37 |
+
op_slug = operacion.lower().strip() # 'venta' o 'arriendo'
|
| 38 |
|
|
|
|
| 39 |
tipo_slug = tipo.lower().strip()
|
| 40 |
if tipo_slug in ["apartamento", "casa"]:
|
| 41 |
tipo_fr = "casas-y-apartamentos-y-apartaestudios"
|
| 42 |
else:
|
| 43 |
+
tipo_fr = tipo_slug + "s"
|
| 44 |
|
| 45 |
+
# --- URL FINCA RAÍZ MULTIFILTRO ---
|
| 46 |
+
url_fr_base = f"https://www.fincaraiz.com.co/{op_slug}/{tipo_fr}/{b_slug}/{c_slug}/{int(hab)}-o-mas-habitaciones/{int(ban)}-o-mas-banos/{slug_park}/{slug_ant}/m2-desde-{int(m2_min)}/m2-hasta-{int(m2_max)}"
|
| 47 |
+
|
| 48 |
+
# Extras condicionales
|
| 49 |
+
if ascensor: url_fr_base += "/con-ascensor"
|
| 50 |
+
if piscina: url_fr_base += "/con-piscina"
|
| 51 |
|
| 52 |
+
url_fr = url_fr_base
|
| 53 |
+
|
| 54 |
# --- URL METROCUADRADO ---
|
| 55 |
+
# MC usa un formato más simple, le pasamos la operación dinámica
|
| 56 |
+
url_mc = f"https://www.metrocuadrado.com/{tipo_slug}-casa-oficina/{op_slug}/{c_slug}/{b_slug}/{int(ban)}-banos-{int(hab)}-habitaciones/?search=form"
|
| 57 |
|
| 58 |
return url_fr, url_mc
|
| 59 |
|
| 60 |
+
# --- 2. EXTRACTOR DE PRECIO DINÁMICO ---
|
| 61 |
+
def extraer_precio_regex(texto, operacion):
|
| 62 |
patron = r'\$\s?(\d{1,3}(?:[.,]\d{3})*)'
|
| 63 |
coincidencias = re.findall(patron, texto)
|
| 64 |
if coincidencias:
|
| 65 |
precios = [int(p.replace('.', '').replace(',', '')) for p in coincidencias]
|
| 66 |
+
|
| 67 |
+
# Filtro de cordura según si es Venta o Arriendo
|
| 68 |
+
if operacion == "Arriendo":
|
| 69 |
+
precios_validos = [p for p in precios if 600000 <= p <= 40000000] # Ignora ventas y admón
|
| 70 |
+
else: # Venta
|
| 71 |
+
precios_validos = [p for p in precios if p >= 40000000] # Ignora arriendos cruzados
|
| 72 |
+
|
| 73 |
if precios_validos:
|
| 74 |
return precios_validos[0]
|
| 75 |
return 0
|
| 76 |
|
| 77 |
+
# --- 3. MOTOR DE EXTRACCIÓN ---
|
| 78 |
+
def motor_tramitia_final(operacion, barrio, ciudad, area, m2_min, m2_max, tipo, hab, ban, park, antiguedad, ascensor, piscina):
|
| 79 |
resultados = []
|
| 80 |
+
url_fr, url_mc = construir_urls_final(operacion, barrio, ciudad, tipo, hab, ban, park, antiguedad, m2_min, m2_max, ascensor, piscina)
|
| 81 |
|
| 82 |
log_visible = f"✅ URLs GENERADAS (AUDITORÍA):\nFR: {url_fr}\nMC: {url_mc}\n\n"
|
| 83 |
ua = UserAgent()
|
|
|
|
| 98 |
# --- FINCA RAÍZ ---
|
| 99 |
try:
|
| 100 |
page = context.new_page()
|
| 101 |
+
log_visible += f"🔄 Escaneando Finca Raíz ({operacion})...\n"
|
| 102 |
page.goto(url_fr, wait_until="domcontentloaded", timeout=60000)
|
| 103 |
|
| 104 |
page.mouse.move(random.randint(100, 500), random.randint(100, 500))
|
|
|
|
| 114 |
txt = el.inner_text()
|
| 115 |
|
| 116 |
if "$" in txt:
|
| 117 |
+
precio = extraer_precio_regex(txt, operacion) # Pasamos la operación
|
| 118 |
if precio > 0:
|
| 119 |
href = el.get_attribute("href")
|
| 120 |
full_url = f"https://www.fincaraiz.com.co{href}" if href and href.startswith("/") else href
|
|
|
|
| 135 |
# --- METROCUADRADO ---
|
| 136 |
try:
|
| 137 |
page = context.new_page()
|
| 138 |
+
log_visible += f"🔄 Escaneando Metrocuadrado ({operacion})...\n"
|
| 139 |
page.goto(url_mc, wait_until="domcontentloaded", timeout=60000)
|
| 140 |
|
| 141 |
for _ in range(5):
|
|
|
|
| 150 |
txt = card.inner_text()
|
| 151 |
|
| 152 |
if "$" in txt:
|
| 153 |
+
precio = extraer_precio_regex(txt, operacion)
|
| 154 |
if precio > 0:
|
| 155 |
enlace = card.query_selector("a")
|
| 156 |
if enlace:
|
|
|
|
| 191 |
pdf = FPDF()
|
| 192 |
pdf.add_page()
|
| 193 |
pdf.set_font("Arial", 'B', 14)
|
| 194 |
+
pdf.cell(0, 10, f"ESTUDIO DE MERCADO: {barrio.upper()} ({operacion.upper()})", ln=True)
|
| 195 |
pdf.ln(5)
|
| 196 |
|
| 197 |
for _, r in df_final.iterrows():
|
|
|
|
| 212 |
maximo = df_final['Precio'].max()
|
| 213 |
|
| 214 |
resumen = (
|
| 215 |
+
f"💰 **ESTIMACIÓN DE {operacion.upper()}**\n"
|
| 216 |
+
f"🔹 **Valor Sugerido:** ${promedio:,.0f}\n"
|
| 217 |
f"📉 Mínimo Zona: ${minimo:,.0f}\n"
|
| 218 |
f"📈 Máximo Zona: ${maximo:,.0f}"
|
| 219 |
)
|
|
|
|
| 224 |
|
| 225 |
# --- INTERFAZ GRÁFICA ---
|
| 226 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 227 |
+
gr.Markdown("## 🤖 TramitIA Pro: Tasador Inmobiliario Integral")
|
| 228 |
|
| 229 |
with gr.Row():
|
| 230 |
with gr.Column(scale=1):
|
| 231 |
+
op = gr.Radio(["Arriendo", "Venta"], label="¿Qué deseas hacer?", value="Arriendo")
|
| 232 |
+
|
| 233 |
c = gr.Textbox(label="Ciudad", value="Barranquilla")
|
| 234 |
b = gr.Textbox(label="Barrio (Ej: La Concepcion)", value="La Concepcion")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
|
| 236 |
+
with gr.Row():
|
| 237 |
+
t = gr.Dropdown(["Apartamento", "Casa", "Bodega", "Lote", "Oficina"], label="Tipo", value="Apartamento")
|
| 238 |
+
a = gr.Number(label="Área M2 (Tu Cliente)", value=70)
|
| 239 |
+
|
| 240 |
+
gr.Markdown("### Filtros de Búsqueda")
|
| 241 |
+
with gr.Row():
|
| 242 |
+
m2_min = gr.Number(label="M2 Mínimo", value=10)
|
| 243 |
+
m2_max = gr.Number(label="M2 Máximo", value=200)
|
| 244 |
+
|
| 245 |
+
with gr.Row():
|
| 246 |
+
ascensor = gr.Checkbox(label="Con Ascensor")
|
| 247 |
+
piscina = gr.Checkbox(label="Con Piscina")
|
| 248 |
+
|
| 249 |
with gr.Row():
|
| 250 |
h = gr.Number(label="Habitaciones", value=3)
|
| 251 |
ban = gr.Number(label="Baños", value=2)
|
|
|
|
| 255 |
["Menos de 1 año", "1 a 8 años", "9 a 15 años", "16 a 30 años", "Más de 30 años"],
|
| 256 |
label="Antigüedad", value="1 a 8 años"
|
| 257 |
)
|
| 258 |
+
btn = gr.Button("EJECUTAR ANÁLISIS COMERCIAL", variant="primary")
|
| 259 |
|
| 260 |
with gr.Column(scale=2):
|
| 261 |
res_fin = gr.Markdown("### 💰 El resultado aparecerá aquí...")
|
|
|
|
| 264 |
with gr.TabItem("Tabla de Resultados"): out_df = gr.Dataframe()
|
| 265 |
with gr.TabItem("Descargar PDF"): out_pdf = gr.File()
|
| 266 |
|
| 267 |
+
btn.click(motor_tramitia_final,
|
| 268 |
+
[op, b, c, a, m2_min, m2_max, t, h, ban, p, e, ascensor, piscina],
|
| 269 |
+
[msg, out_df, out_pdf, res_fin])
|
| 270 |
|
| 271 |
demo.launch()
|