import gradio as gr import joblib import pandas as pd import numpy as np import re import requests from datetime import datetime from geopy.geocoders import Nominatim from geopy.distance import geodesic # Cargar modelo y dataset model = joblib.load("modelo_docks.pkl") df_stations = pd.read_csv("Informacio_Estacions_Bicing_2025.csv") geolocator = Nominatim(user_agent="bicing-agent") from groq import Groq client = Groq(api_key="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") # LLM: llama-3.3-70b-versatile def preguntar_al_usuario(pregunta): response = client.chat.completions.create( messages=[ {"role": "system", "content": "Eres un asistente de Bicing. Tu tarea es hacer una preguntal usuario y esperar su respuesta. No saludes a menos que te lo pida"}, {"role": "user", "content": f"Pregunta al usuario lo siguiente, puedes modificar el tono para hacerlo mas amigable y añadir ayudas adicionales sobre como introducir los datos: '{pregunta}'"} ], model="llama-3.3-70b-versatile", temperature=0.5, max_completion_tokens=512, top_p=1, stream=False, ) return response.choices[0].message.content.strip() # Estaciones más cercanas def get_nearest_stations(ubicacion, top_n=10): loc = geolocator.geocode(f"{ubicacion}, Barcelona, Spain") if not loc: return pd.DataFrame() user_coord = (loc.latitude, loc.longitude) df_stations["distancia"] = df_stations.apply( lambda row: geodesic(user_coord, (row["lat"], row["lon"])).meters, axis=1 ) return df_stations.nsmallest(top_n, "distancia")[["station_id", "address", "lat", "lon"]] # Tiempo (Open-Meteo) def get_weather_forecast(lat, lon, year, month, day, hour): fecha = f"{year}-{month:02d}-{day:02d}" hora_str = f"{hour:02d}:00" url = ( f"https://api.open-meteo.com/v1/forecast?" f"latitude={lat}&longitude={lon}&hourly=temperature_2m,precipitation&timezone=Europe%2FMadrid" f"&start_date={fecha}&end_date={fecha}" ) r = requests.get(url) if r.status_code != 200: return None, None data = r.json() horas = data["hourly"]["time"] temperaturas = data["hourly"]["temperature_2m"] precipitaciones = data["hourly"]["precipitation"] for i, h in enumerate(horas): if h.endswith(hora_str): return temperaturas[i], precipitaciones[i] return None, None # Predicción con el modelo def predict_disponibilidad(context): estaciones_cercanas = get_nearest_stations(context["ubicacion"]) if estaciones_cercanas.empty: return {"error": "No se encontraron estaciones cercanas."} resultados = [] for _, row in estaciones_cercanas.iterrows(): temp, precip = get_weather_forecast( row["lat"], row["lon"], 2025, context["month"], context["day"], context["hour"] ) if temp is None: continue X = np.array([[ row["station_id"], context["month"], context["day"], context["hour"], context["ctx_value"], context["ctx_value"], context["ctx_value"], context["ctx_value"], temp, precip ]]) pred = model.predict(X)[0] resultados.append({ "station_id": row["station_id"], "address": row["address"], "pred_pct": float(pred), "temperature": round(temp, 1), "precip": round(precip, 1) }) if not resultados: return {"error": "No se pudieron calcular predicciones meteorológicas."} resultados_ordenados = sorted(resultados, key=lambda x: x["pred_pct"], reverse=True) return { "target_pct": context["target_pct"], "candidatas": resultados_ordenados } # Preguntas al usuario preguntas = [ ("ubicacion", "INTRODUCE SALUDO y la pregunta ¿Dónde te gustaría coger la bici? (zona o dirección en Barcelona)"), ("month", "¿En qué mes planeas cogerla? (número 1-12)"), ("day", "¿Qué día del mes?"), ("hour", "¿A qué hora la necesitas? (0-23)?"), ("target_pct", "¿Qué porcentaje mínimo de bicicletas esperas encontrar disponibles? (0 a 100%)") ] # Flujo de conversación def chat(user_input, chat_history, current_step, user_context): key, _ = preguntas[current_step] if key in ["month", "day", "hour", "target_pct"]: match = re.search(r"\d+(\.\d+)?", user_input) if match: value = float(match.group()) user_context[key] = value / 100 if key == "target_pct" else int(value) else: chat_history.append(("user", user_input)) chat_history.append(("assistant", "Introduce un número válido.")) return chat_history, current_step, user_context else: user_context[key] = user_input.strip() chat_history.append(("user", user_input)) current_step += 1 if current_step < len(preguntas): siguiente_pregunta = preguntar_al_usuario(preguntas[current_step][1]) chat_history.append(("assistant", siguiente_pregunta)) else: resultado = predict_disponibilidad(user_context) if "error" in resultado: chat_history.append(("assistant", resultado["error"] + " Reiniciando conversación...")) user_context = { "ubicacion": None, "month": None, "day": None, "hour": None, "target_pct": None, "temperature": None, "lluvia": None } current_step = 0 chat_history.append(("assistant", preguntar_al_usuario(preguntas[0][1]))) return chat_history, current_step, user_context else: clima = resultado["candidatas"][0] # 🧾 Resumen del contexto fecha_str = f"{user_context['day']:02d}/{user_context['month']:02d}/2025" hora_str = f"{user_context['hour']:02d}:00h" resumen_contexto = ( f"📍 *Ubicación*: {user_context['ubicacion']}\n" f"🗓️ *Día*: {fecha_str}\n" f"🕒 *Hora*: {hora_str}\n" f"🎯 *Porcentaje mínimo deseado de bicis*: {int(user_context['target_pct'] * 100)}%" ) # 📈 Predicción meteorológica resumen_meteo = ( f"🌡️ *Temperatura esperada*: {clima['temperature']}°C\n" f"☔ *Precipitación esperada*: {clima['precip']} mm" ) # 🚲 Disponibilidad de estaciones candidatas = resultado["candidatas"] hay_suficientes = any(r["pred_pct"] >= resultado["target_pct"] for r in candidatas) # 🚲 Disponibilidad de estaciones msg_estaciones = "🚲 *Estaciones más cercanas ordenadas por disponibilidad:*\n" for r in candidatas: emoji = "✅" if r["pred_pct"] >= resultado["target_pct"] else "⚠️" msg_estaciones += ( f"{emoji} '{r['address']}' (ID {r['station_id']}): " f"{round(r['pred_pct']*100)}% disponibilidad\n" ) if not hay_suficientes: msg_estaciones += ( "\n⚠️ *Aviso:* ninguna estación cercana alcanza el porcentaje mínimo deseado " f"de {int(resultado['target_pct'] * 100)}%. Puedes intentar con otro horario o ubicación." ) for r in resultado["candidatas"]: emoji = "✅" if r["pred_pct"] >= resultado["target_pct"] else "⚠️" msg_estaciones += ( f"{emoji} '{r['address']}' (ID {r['station_id']}): " f"{round(r['pred_pct']*100)}% disponibilidad\n" ) # Construir mensaje completo mensaje_final = f"{resumen_contexto}\n\n{resumen_meteo}\n\n{msg_estaciones}" chat_history.append(("assistant", mensaje_final.strip())) # 🧠 Resumen generado por LLM resumen_llm = client.chat.completions.create( messages=[ { "role": "system", "content": ( "Eres un asistente experto en movilidad urbana. Resume al usuario de forma clara y amigable " "si podrá encontrar bicis disponibles. se breve." ) }, {"role": "user", "content": mensaje_final.strip()} ], model="llama-3.3-70b-versatile", temperature=0.5, max_completion_tokens=256 ).choices[0].message.content.strip() # Añadir texto fijo al final del resumen resumen_llm += "\n\nSi quieres hacer otra consulta, dime una nueva ubicación o escribe 'reiniciar'." chat_history.append(("assistant", resumen_llm)) return chat_history, current_step, user_context # Interfaz Gradio with gr.Blocks() as demo: chatbot = gr.Chatbot() txt = gr.Textbox(placeholder="Escribe tu respuesta...", label="Tu mensaje") # Nuevo input para ctx histórico ctx_selector = gr.Dropdown( choices=["alto", "medio", "bajo"], value="medio", label="Nivel de ocupación histórica (ctx)" ) state_chat = gr.State([]) state_step = gr.State(0) state_context = gr.State({ "ubicacion": None, "month": None, "day": None, "hour": None, "target_pct": None, "ctx_value": 0.5, # valor por defecto "temperature": None, "lluvia": None }) def user_submit(message, chat_history, current_step, user_context, ctx_selector_value): # Mapear valor textual a número ctx_map = {"alto": 0.9, "medio": 0.5, "bajo": 0.1} user_context["ctx_value"] = ctx_map.get(ctx_selector_value, 0.5) return chat(message, chat_history, current_step, user_context) txt.submit( user_submit, inputs=[txt, state_chat, state_step, state_context, ctx_selector], outputs=[chatbot, state_step, state_context] ) # Primer mensaje primer_pregunta = preguntar_al_usuario(preguntas[0][1]) state_chat.value = [("assistant", primer_pregunta)] chatbot.value = state_chat.value demo.launch()