import streamlit as st import time import json import pandas as pd import plotly.express as px import plotly.graph_objects as go import matplotlib.pyplot as plt import numpy as np import lightgbm as lgb from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity from sklearn.metrics import mean_absolute_error, mean_squared_error from joblib import dump, load from utils import recomienda_tf from utils import retroalimentacion from streamlit_lottie import st_lottie import requests # Page configuration st.set_page_config(page_title="DeepInsightz", page_icon=":bar_chart:", layout="wide") # Custom CSS for dynamic theme styling # Custom CSS for dynamic theme styling if st.get_option("theme.base") == "dark": background_color = "#282828" text_color = "white" metric_box_color = "#4f4f4f" sidebar_color = "#282828" plot_bgcolor = "rgba(0, 0, 0, 0)" primary_color = '#00FF00' # for positive delta negative_color = '#FF0000' # for negative delta else: background_color = "#f4f4f4" text_color = "#black" metric_box_color = "#dee2e8" sidebar_color = "#dee2e8" plot_bgcolor = "#f4f4f4" primary_color = '#228B22' # for positive delta in light mode negative_color = '#8B0000' # for negative delta in light mode # Load custom CSS for dynamic theme and Lottie animation st.markdown(f""" """, unsafe_allow_html=True) # Function to show Lottie animation as an overlay def display_lottie_animation(show_animation): if show_animation: st.markdown('
Introduce el código del cliente para explorar información detallada del mismo, incluyendo ventas anteriores, predicciones para el año actual e información específica por fabricante.
""", unsafe_allow_html=True) # Combine text input and dropdown into a single searchable selectbox customer_code = st.selectbox( "Escribe o selecciona el código de tu cliente", df['CLIENTE'].unique(), # All customer codes format_func=lambda x: str(x), # Ensures the values are displayed as strings help="Start typing to search for a specific customer code" ) # Fabricante dropdown (with 'Todos' option) fabricantes = ["Todos"] + list(nombres_proveedores['nombre'].unique()) # Agregar la opción 'Todos' fabricante_seleccionado = st.selectbox( "Selecciona el fabricante (o Todos)", fabricantes, help="Selecciona un fabricante específico o 'Todos' para ver todos los fabricantes" ) if st.button("Calcular"): if customer_code: with st.spinner("Estamos identificando el grupo del cliente..."): # Find Customer's Cluster customer_match = customer_clusters[customer_clusters['cliente_id'] == customer_code] time.sleep(1) if not customer_match.empty: cluster = customer_match['cluster_id'].values[0] if fabricante_seleccionado == "Todos": # Actuar como el comportamiento actual with st.spinner(f"Seleccionando el modelo predictivo..."): # Load the Corresponding Model model_path = f'models/modelo_cluster_{cluster}.txt' gbm = lgb.Booster(model_file=model_path) with st.spinner("Preparando los datos..."): # Load predict data for that cluster predict_data = pd.read_csv(f'predicts/predict_cluster_{cluster}.csv') # Convert cliente_id to string predict_data['cliente_id'] = predict_data['cliente_id'].astype(str) with st.spinner("Filtrando data..."): # Filter for the specific customer customer_code_str = str(customer_code) customer_data = predict_data[predict_data['cliente_id'] == customer_code_str] with st.spinner("Geneerando predicciones de venta..."): if not customer_data.empty: # Define features consistently with the training process lag_features = [f'precio_total_lag_{lag}' for lag in range(1, 25)] features = lag_features + ['mes', 'marca_id_encoded', 'año', 'cluster_id'] # Prepare data for prediction X_predict = customer_data[features] # Convert categorical features to 'category' dtype categorical_features = ['mes', 'marca_id_encoded', 'cluster_id'] for feature in categorical_features: X_predict[feature] = X_predict[feature].astype('category') # Make Prediction for the selected customer y_pred = gbm.predict(X_predict, num_iteration=gbm.best_iteration) # Reassemble the results results = customer_data[['cliente_id', 'marca_id_encoded', 'fecha_mes']].copy() results['ventas_predichas'] = y_pred # Load actual data from df_agg_2024 actual_sales = df_agg_2024[df_agg_2024['cliente_id'] == customer_code_str] if not actual_sales.empty: # Merge predictions with actual sales results = results.merge(actual_sales[['cliente_id', 'marca_id_encoded', 'fecha_mes', 'precio_total']], on=['cliente_id', 'marca_id_encoded', 'fecha_mes'], how='left') results.rename(columns={'precio_total': 'ventas_reales'}, inplace=True) else: # If no actual sales data for 2024, fill 'ventas_reales' with 0 results['ventas_reales'] = 0 # Ensure any missing sales data is filled with 0 results['ventas_reales'].fillna(0, inplace=True) # Define the cutoff date for the last 12 months fecha_inicio = pd.to_datetime("2023-01-01") fecha_corte = pd.to_datetime("2024-09-01") # Convertir fecha_mes a datetime en el DataFrame historical_data historical_data['fecha_mes'] = pd.to_datetime(historical_data['fecha_mes'], errors='coerce') # Ensure cliente_id is of type string and strip any leading/trailing whitespace historical_data['cliente_id'] = historical_data['cliente_id'].astype(str).str.strip() customer_code_str = str(customer_code).strip() # Ensure the customer code is also properly formatted filtered_historical_data = historical_data[historical_data['cliente_id'] == customer_code_str] # Filtrar los datos históricos por cliente y por el rango de fechas (2023) fecha_inicio_2023 = pd.to_datetime("2022-01-01") fecha_fin_2023 = pd.to_datetime("2023-12-31") datos_historicos = historical_data[ (historical_data['cliente_id'] == customer_code_str) & (historical_data['fecha_mes'] >= fecha_inicio_2023) & (historical_data['fecha_mes'] <= fecha_fin_2023) ].groupby('fecha_mes')['precio_total'].sum().reset_index() # Renombrar la columna 'precio_total' a 'ventas_historicas' si no está vacía if not datos_historicos.empty: datos_historicos.rename(columns={'precio_total': 'ventas_historicas'}, inplace=True) else: # Si los datos históricos están vacíos, generar fechas de 2023 con ventas_historicas = 0 fechas_2023 = pd.date_range(start='2022-01-01', end='2023-12-31', freq='M') datos_historicos = pd.DataFrame({'fecha_mes': fechas_2023, 'ventas_historicas': [0] * len(fechas_2023)}) # Filtrar los datos de predicciones y ventas reales para 2024 datos_cliente_total = results.groupby('fecha_mes').agg({ 'ventas_reales': 'sum', 'ventas_predichas': 'sum' }).reset_index() # Asegurarnos de que fecha_mes en datos_cliente_total es datetime datos_cliente_total['fecha_mes'] = pd.to_datetime(datos_cliente_total['fecha_mes'], errors='coerce') # Generar un rango de fechas para 2024 si no hay predicciones fechas_2024 = pd.date_range(start='2024-01-01', end='2024-12-31', freq='M') fechas_df_2024 = pd.DataFrame({'fecha_mes': fechas_2024}) # Asegurarnos de que fecha_mes en fechas_df_2024 es datetime fechas_df_2024['fecha_mes'] = pd.to_datetime(fechas_df_2024['fecha_mes'], errors='coerce') # Combinar datos históricos con predicciones y ventas reales usando un merge # Usamos how='outer' para asegurarnos de incluir todas las fechas de 2023 y 2024 datos_combinados = pd.merge(datos_historicos, datos_cliente_total, on='fecha_mes', how='outer').sort_values('fecha_mes') # Rellenar los NaN: 0 en ventas_historicas donde faltan predicciones, y viceversa datos_combinados['ventas_historicas'].fillna(0, inplace=True) datos_combinados['ventas_predichas'].fillna(0, inplace=True) datos_combinados['ventas_reales'].fillna(0, inplace=True) # Crear la gráfica con Plotly fig = go.Figure() # Graficar ventas históricas fig.add_trace(go.Scatter( x=datos_combinados['fecha_mes'], y=datos_combinados['ventas_historicas'], mode='lines+markers', name='Ventas Históricas', line=dict(color='blue') )) # Graficar ventas predichas fig.add_trace(go.Scatter( x=datos_combinados['fecha_mes'], y=datos_combinados['ventas_predichas'], mode='lines+markers', name='Ventas Predichas', line=dict(color='orange') )) # Graficar ventas reales fig.add_trace(go.Scatter( x=datos_combinados['fecha_mes'], y=datos_combinados['ventas_reales'], mode='lines+markers', name='Ventas Reales', line=dict(color='green') )) # Personalizar el layout para enfocarse en 2023 y 2024 fig.update_layout( title=f"Ventas Históricas, Predichas y Reales para Cliente {customer_code}", xaxis_title="Fecha", yaxis_title="Ventas (€)", height=600, xaxis_range=[fecha_inicio_2023, pd.to_datetime("2024-09-30")], # Ajustar el rango del eje x a 2023-2024 legend_title="Tipo de Ventas", hovermode="x unified" ) # Mostrar la gráfica en Streamlit st.plotly_chart(fig) # Calculate metrics for 2024 data datos_2024 = datos_combinados[datos_combinados['fecha_mes'].dt.year == 2024] actual = datos_2024['ventas_reales'] predicted = datos_2024['ventas_predichas'] def calculate_mape(y_true, y_pred): mask = y_true != 0 return np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100 mae = mean_absolute_error(actual, predicted) mse = mean_squared_error(actual, predicted) rmse = np.sqrt(mse) mape = calculate_mape(actual, predicted) smape = np.mean(2 * np.abs(actual - predicted) / (np.abs(actual) + np.abs(predicted))) * 100 # Display metrics st.subheader("Métricas de Predicción (2024)") col1, col2, col3, col4 = st.columns(4) col1.metric("MAE", f"{mae:.2f} €",help="Promedio de la diferencia absoluta entre las predicciones y los valores reales.") col2.metric("MAPE", f"{mape:.2f}%",help="Porcentaje promedio de error en las predicciones.") col3.metric("RMSE", f"{rmse:.2f} €",help="Medida de la desviación estándar de los residuos de predicción.") col4.metric("SMAPE", f"{smape:.2f}%",help="Alternativa al MAPE que maneja mejor los valores cercanos a cero.") # Split space into two columns col1, col2 = st.columns(2) # Column 1: Radar chart for top manufacturers with col1: st.subheader("¡Esto tiene buena pinta!") st.info("Su cliente ha superado las ventas predichas de las siguientes marcas:") # Group results by manufacturer to calculate the total predicted and actual sales grouped_results = results.groupby('marca_id_encoded').agg({ 'ventas_reales': 'sum', 'ventas_predichas': 'sum' }).reset_index() # Identify manufacturers that exceeded predicted sales overperforming_manufacturers = grouped_results[grouped_results['ventas_reales'] > grouped_results['ventas_predichas']].copy() if not overperforming_manufacturers.empty: # Calculate the extra amount (difference between actual and predicted sales) overperforming_manufacturers['extra_amount'] = overperforming_manufacturers['ventas_reales'] - overperforming_manufacturers['ventas_predichas'] # Sort by the highest extra amount overperforming_manufacturers = overperforming_manufacturers.sort_values(by='extra_amount', ascending=False) # Limit to top 10 overperforming manufacturers top_overperformers = overperforming_manufacturers.head(10) # Display two cards per row for i in range(0, len(top_overperformers), 2): cols = st.columns(2) # Create two columns for two cards in a row for j, col in enumerate(cols): if i + j < len(top_overperformers): row = top_overperformers.iloc[i + j] manufacturer_name = get_supplier_name_encoded(row['marca_id_encoded']) predicted = row['ventas_predichas'] actual = row['ventas_reales'] extra = row['extra_amount'] # Use st.metric for compact display in each column with col: st.metric( label=f"{manufacturer_name}", value=f"{actual:.2f}€", delta=f"Exceeded by {extra:.2f}€", delta_color="normal" ) # Radar chart logic remains the same customer_df = df[df["CLIENTE"] == str(customer_code)] all_manufacturers = customer_df.iloc[:, 1:].T all_manufacturers.index = all_manufacturers.index.astype(str) customer_euros = euros_proveedor[euros_proveedor["CLIENTE"] == str(customer_code)] sales_data = customer_euros.iloc[:, 1:].T sales_data.index = sales_data.index.astype(str) sales_data_filtered = sales_data.drop(index='CLIENTE', errors='ignore') sales_data_filtered = sales_data_filtered.apply(pd.to_numeric, errors='coerce') all_manufacturers = all_manufacturers.apply(pd.to_numeric, errors='coerce') top_units = all_manufacturers.sort_values(by=all_manufacturers.columns[0], ascending=False).head(10) top_sales = sales_data_filtered.sort_values(by=sales_data_filtered.columns[0], ascending=False).head(10) combined_top = pd.concat([top_units, top_sales]).index.unique()[:20] combined_top = [m for m in combined_top if m in all_manufacturers.index and m in sales_data_filtered.index] if combined_top: combined_data = pd.DataFrame({ 'units': all_manufacturers.loc[combined_top, all_manufacturers.columns[0]], 'sales': sales_data_filtered.loc[combined_top, sales_data_filtered.columns[0]] }).fillna(0) combined_data_sorted = combined_data.sort_values(by=['units', 'sales'], ascending=False) non_zero_manufacturers = combined_data_sorted[combined_data_sorted['units'] > 0] if len(non_zero_manufacturers) < 3: zero_manufacturers = combined_data_sorted[combined_data_sorted['units'] == 0].head(3 - len(non_zero_manufacturers)) manufacturers_to_show = pd.concat([non_zero_manufacturers, zero_manufacturers]) else: manufacturers_to_show = non_zero_manufacturers values = manufacturers_to_show['units'].tolist() amounts = manufacturers_to_show['sales'].tolist() manufacturers = [get_supplier_name(m) for m in manufacturers_to_show.index] if manufacturers: fig = radar_chart(manufacturers, values, amounts, f'Gráfico de radar para los {len(manufacturers)} principales fabricantes del cliente {customer_code}') st.pyplot(fig) # Column 2: Alerts and additional analysis with col2: st.subheader("¡Puede que tengas que revisar esto!") st.warning("Se esperaba que tu cliente comprara más productos de las siguientes marcas:") # Group results by manufacturer to calculate the total predicted and actual sales grouped_results = results.groupby('marca_id_encoded').agg({ 'ventas_reales': 'sum', 'ventas_predichas': 'sum' }).reset_index() # Identify manufacturers that didn't meet predicted sales underperforming_manufacturers = grouped_results[grouped_results['ventas_reales'] < grouped_results['ventas_predichas']].copy() if not underperforming_manufacturers.empty: # Calculate the missed amount underperforming_manufacturers['missed_amount'] = underperforming_manufacturers['ventas_predichas'] - underperforming_manufacturers['ventas_reales'] # Sort by the highest missed amount underperforming_manufacturers = underperforming_manufacturers.sort_values(by='missed_amount', ascending=False) # Limit to top 10 missed amounts top_misses = underperforming_manufacturers.head(10) # Display two cards per row for i in range(0, len(top_misses), 2): cols = st.columns(2) # Create two columns for two cards in a row for j, col in enumerate(cols): if i + j < len(top_misses): row = top_misses.iloc[i + j] manufacturer_name = get_supplier_name_encoded(row['marca_id_encoded']) predicted = row['ventas_predichas'] actual = row['ventas_reales'] missed = row['missed_amount'] # Use st.metric for compact display in each column with col: st.metric( label=f"{manufacturer_name}", value=f"{actual:.2f}€", delta=f"Missed by {missed:.2f}€", delta_color="inverse" ) else: st.success("All manufacturers have met or exceeded predicted sales.") # Gráfico de ventas anuales ventas_clientes['codigo_cliente'] = ventas_clientes['codigo_cliente'].astype(str).str.strip() sales_columns = ['VENTA_2021', 'VENTA_2022', 'VENTA_2023'] if all(col in ventas_clientes.columns for col in sales_columns): customer_sales_data = ventas_clientes[ventas_clientes['codigo_cliente'] == customer_code] if not customer_sales_data.empty: customer_sales = customer_sales_data[sales_columns].values[0] years = ['2021', '2022', '2023'] # Convert 'fecha_mes' to datetime format if it's not already if not pd.api.types.is_datetime64_any_dtype(results['fecha_mes']): results['fecha_mes'] = pd.to_datetime(results['fecha_mes'], errors='coerce') # Add the 2024 actual and predicted data if 'ventas_predichas' in results.columns and 'ventas_reales' in results.columns: actual_sales_2024 = results[results['fecha_mes'].dt.year == 2024]['ventas_reales'].sum() predicted_sales_2024 = results[results['fecha_mes'].dt.year == 2024]['ventas_predichas'].sum() # Assuming only 9 months of actual data are available, annualize the sales months_available = 9 actual_sales_2024_annual = (actual_sales_2024 / months_available) * 12 # Prepare data for the bar chart sales_values = list(customer_sales) + [actual_sales_2024_annual] predicted_values = list(customer_sales) + [predicted_sales_2024] years.append('2024') # Create the bar chart for historical and 2024 data fig_sales_bar = go.Figure() fig_sales_bar.add_trace(go.Bar( x=years[:3], y=sales_values[:3], name="Historical Sales", marker_color='blue' )) fig_sales_bar.add_trace(go.Bar( x=[years[3]], y=[sales_values[3]], name="2024 Actual Sales (Annualized)", marker_color='green' )) fig_sales_bar.add_trace(go.Bar( x=[years[3]], y=[predicted_values[3]], name="2024 Predicted Sales", marker_color='orange' )) # Customize layout fig_sales_bar.update_layout( title=f"Ventas anuales de tu cliente", xaxis_title="Year", yaxis_title="Sales (€)", barmode='group', height=600, legend_title_text="Sales Type", hovermode="x unified" ) # Display the chart st.plotly_chart(fig_sales_bar, use_container_width=True) else: st.warning(f"No predicted or actual data found for customer {customer_code} for 2024.") else: with st.spinner(f"Seleccionando el modelo predictivo..."): # Load the Corresponding Model model_path = f'models/modelo_cluster_{cluster}.txt' gbm = lgb.Booster(model_file=model_path) with st.spinner(f"Mostrando datos para el fabricante {fabricante_seleccionado}..."): # Mostrar el cliente y el fabricante seleccionados st.write(f"**Cliente seleccionado:** {customer_code}") st.write(f"**Fabricante seleccionado:** {fabricante_seleccionado}") # Obtener el código del fabricante seleccionado codigo_fabricante_seleccionado = np.int64(nombres_proveedores[nombres_proveedores['nombre'] == fabricante_seleccionado]['codigo'].values[0]) st.write(f"**Código fabricante seleccionado:** {codigo_fabricante_seleccionado}") # Verificar si el código está presente en el LabelEncoder y obtener su encoded if codigo_fabricante_seleccionado in marca_id_mapping.classes_: codigo_fabricante_encoded = marca_id_mapping.transform([codigo_fabricante_seleccionado])[0] st.write(f"**Código fabricante encoded (marca_id_encoded):** {codigo_fabricante_encoded}") # Filtrar datos solo para este fabricante with st.spinner("Preparando los datos..."): predict_data = pd.read_csv(f'predicts/predict_cluster_{cluster}.csv') predict_data['cliente_id'] = predict_data['cliente_id'].astype(str) customer_code_str = str(customer_code) customer_data = predict_data[(predict_data['cliente_id'] == customer_code_str) & (predict_data['marca_id_encoded'] == codigo_fabricante_encoded)] with st.spinner("Generando predicciones de venta..."): if not customer_data.empty: # Preparar las características lag_features = [f'precio_total_lag_{lag}' for lag in range(1, 25)] features = lag_features + ['mes', 'marca_id_encoded', 'año', 'cluster_id'] X_predict = customer_data[features] # Convertir las características categóricas a su dtype correspondiente categorical_features = ['mes', 'marca_id_encoded', 'cluster_id'] for feature in categorical_features: X_predict[feature] = X_predict[feature].astype('category') # Realizar la predicción y_pred = gbm.predict(X_predict, num_iteration=gbm.best_iteration) results = customer_data[['cliente_id', 'marca_id_encoded', 'fecha_mes']].copy() results['ventas_predichas'] = y_pred # Cargar datos reales para 2024 actual_sales = df_agg_2024[(df_agg_2024['cliente_id'] == customer_code_str) & (df_agg_2024['marca_id_encoded'] == codigo_fabricante_encoded)] if not actual_sales.empty: results = results.merge(actual_sales[['cliente_id', 'marca_id_encoded', 'fecha_mes', 'precio_total']], on=['cliente_id', 'marca_id_encoded', 'fecha_mes'], how='left') results.rename(columns={'precio_total': 'ventas_reales'}, inplace=True) else: results['ventas_reales'] = 0 results['ventas_reales'].fillna(0, inplace=True) # Generar gráfica y métricas results['fecha_mes'] = pd.to_datetime(results['fecha_mes'], errors='coerce') if not pd.api.types.is_datetime64_any_dtype(df_agg_2024['fecha_mes']): df_agg_2024['fecha_mes'] = pd.to_datetime(df_agg_2024['fecha_mes'], errors='coerce') fecha_inicio_2023 = pd.to_datetime("2023-01-01") fecha_fin_2023 = pd.to_datetime("2023-12-31") datos_cliente_total = results.groupby('fecha_mes').agg({'ventas_reales': 'sum', 'ventas_predichas': 'sum'}).reset_index() # Crear la gráfica fig = go.Figure() fig.add_trace(go.Scatter(x=datos_cliente_total['fecha_mes'], y=datos_cliente_total['ventas_predichas'], mode='lines+markers', name='Ventas Predichas', line=dict(color='orange'))) fig.add_trace(go.Scatter(x=datos_cliente_total['fecha_mes'], y=datos_cliente_total['ventas_reales'], mode='lines+markers', name='Ventas Reales', line=dict(color='green'))) fig.update_layout(title=f"Ventas Predichas y Reales para Cliente {customer_code} y Fabricante {fabricante_seleccionado}", xaxis_title="Fecha", yaxis_title="Ventas (€)", height=600) st.plotly_chart(fig) # Cálculo de métricas datos_2024 = datos_cliente_total[datos_cliente_total['fecha_mes'].dt.year == 2024] actual = datos_2024['ventas_reales'] predicted = datos_2024['ventas_predichas'] mae = mean_absolute_error(actual, predicted) mse = mean_squared_error(actual, predicted) rmse = np.sqrt(mse) mape = np.mean(np.abs((actual - predicted) / actual)) * 100 if not actual.empty else 0 smape = np.mean(2 * np.abs(actual - predicted) / (np.abs(actual) + np.abs(predicted))) * 100 if not actual.empty else 0 # Mostrar métricas st.subheader("Métricas de Predicción (2024)") col1, col2, col3, col4 = st.columns(4) col1.metric("MAE", f"{mae:.2f} €") col2.metric("MAPE", f"{mape:.2f}%") col3.metric("RMSE", f"{rmse:.2f} €") col4.metric("SMAPE", f"{smape:.2f}%") else: st.warning(f"No se encontraron datos para el cliente {customer_code} y el fabricante {fabricante_seleccionado}.") else: st.warning(f"El código de fabricante {codigo_fabricante_seleccionado} no se encuentra en el LabelEncoder.") # else: # with st.spinner(f"Mostrando datos para el fabricante {fabricante_seleccionado}..."): # # Mostrar el cliente y el fabricante seleccionados # st.write(f"**Cliente seleccionado:** {customer_code}") # st.write(f"**Fabricante seleccionado:** {fabricante_seleccionado}") # codigo_fabricante_seleccionado = np.int64(nombres_proveedores[nombres_proveedores['nombre'] == fabricante_seleccionado]['codigo'].values[0]) # st.write(f"**Código fabricante seleccionado:** {codigo_fabricante_seleccionado}") # if codigo_fabricante_seleccionado in marca_id_mapping.classes_: # # Si el código está en el LabelEncoder, hacer la transformación # codigo_fabricante_encoded = marca_id_mapping.transform([codigo_fabricante_seleccionado])[0] # st.write(f"**Código fabricante encoded (marca_id_encoded):** {codigo_fabricante_encoded}") # else: # # Si el código no se encuentra en el LabelEncoder, mostrar advertencia y los códigos disponibles # st.warning(f"El código de fabricante {codigo_fabricante_seleccionado} no se encuentra en el LabelEncoder.") # st.write("Lista de códigos de fabricantes disponibles en el LabelEncoder:") # # Imprimir los códigos disponibles y su tipo # available_codes = marca_id_mapping.classes_ # st.write(f"**Códigos disponibles:** {available_codes}") # st.write(f"**Tipo de los códigos disponibles:** {type(available_codes[0])}") # Customer Recommendations Page elif page == "💡 Recomendación de Artículos": # Carga de CSV necesarios cestas y productos # cestas = pd.read_csv('cestas_final.csv') # Cambio a cestas_final (sin duplicados) # productos = pd.read_csv('productos.csv') # Inicializar session state para la cesta si no existe if 'new_basket' not in st.session_state: st.session_state['new_basket'] = [] # Inicializar como una lista vacía if 'recommendations_df' not in st.session_state: st.session_state['recommendations_df'] = None if 'selected_descriptions' not in st.session_state: st.session_state['selected_descriptions'] = [] def add_to_basket(rec_code): if rec_code not in st.session_state['new_basket']: st.session_state['new_basket'].append(rec_code) # Estilo principal de la página st.markdown( "Obtén recomendaciones personalizadas para tus clientes basadas en su cesta de compra.
""", unsafe_allow_html=True) st.write("### Selecciona los artículos para la cesta:") # Añadir separador para mejorar la segmentación visual st.divider() # Mostrar lista de artículos disponibles (ahora se usa el código asociado a cada descripción) available_articles = productos[['ARTICULO', 'DESCRIPCION']].drop_duplicates() # Crear diccionario para asignar las descripciones a los códigos article_dict = dict(zip(available_articles['DESCRIPCION'], available_articles['ARTICULO'])) # Permitir seleccionar las descripciones, pero trabajar con los códigos selected_descriptions = st.multiselect("Selecciona los artículos",available_articles['DESCRIPCION'].unique(),default=st.session_state['selected_descriptions']) st.session_state['selected_descriptions'] = selected_descriptions if selected_descriptions: st.write("### Visualiza la selección de artículos:") for description in selected_descriptions: code = article_dict[description] # Usar el código del artículo col1, col2 = st.columns([1, 2]) # Ajustar proporciones para que las imágenes y textos se alineen with col1: # Mostrar la imagen del artículo img_url = f"https://www.saneamiento-martinez.com/imagenes/articulos/{code}_1.JPG" st.image(img_url, width=100) with col2: # Mostrar la descripción del artículo st.write(f"**{description}**(Código: {code})") # Añadir el artículo seleccionado a la cesta if code not in st.session_state['new_basket']: st.session_state['new_basket'].append(code) st_lottie(lottie_animation, height=200, width=200) st.rerun() # Añadir un botón estilizado "Calcular" con icono if st.button("🛒 Obtener Recomendaciones"): # Crear una lista de artículos basada en los códigos y cantidades new_basket = [article_dict[description] for description in selected_descriptions] # Usar los códigos en lugar de las descripciones st.write(f"Artículos seleccionados (códigos): {new_basket}") if new_basket: st.session_state['recommendations_df'] = recomienda_tf(new_basket, cestas, productos) # Display recommendations from session state if st.session_state['recommendations_df'] is not None and not st.session_state['recommendations_df'].empty: st.success("### Según tu cesta, te recomendamos que consideres añadir estos artículos:") for idx, row in st.session_state['recommendations_df'].iterrows(): rec_code = row['ARTICULO'] rec_desc = row['DESCRIPCION'] rec_relevance = row['RELEVANCIA'] rec_img_url = f"https://www.saneamiento-martinez.com/imagenes/articulos/{rec_code}_1.JPG" if image_exists(rec_img_url): rec_col1, rec_col2, rec_col3, rec_col4 = st.columns([1, 3, 1, 1]) with rec_col1: st.image(rec_img_url, width=100) with rec_col2: st.write(f"**{rec_desc}** (Código: {rec_code})") with rec_col3: st.metric(label="Relevancia", value=f"{rec_relevance * 100:.2f}") with rec_col4: # Disable button if item is already in basket button_disabled = rec_code in st.session_state['new_basket'] if not button_disabled: st.button("➕", key=f"add_{rec_code}", on_click=add_to_basket, args=(rec_code,), disabled=button_disabled) else: st.write("✓") # Crear columnas para el input manual y el botón en una fila más compacta col1, col2 = st.columns([4, 1]) # Input manual del código de producto con el botón en la misma fila with col1: manual_code = st.text_input("Añadir código manualmente:", value="", key="manual_code", max_chars=15) # Botón para añadir el código manualmente ingresado, junto al input with col2: if st.button("➕", key="add_manual_code"): if manual_code and manual_code not in st.session_state['new_basket']: st.session_state['new_basket'].append(manual_code) st.success(f"✓ El código {manual_code} ha sido añadido manualmente a la cesta.") elif manual_code in st.session_state['new_basket']: st.warning(f"⚠️ El código {manual_code} ya está en la cesta.") else: st.warning("⚠️ No se ha introducido un código válido.") # Preview section for code in st.session_state['new_basket']: # Crear columnas para el texto del artículo y el botón de eliminar col1, col2 = st.columns([6, 1]) # Mostrar el nombre y código del artículo with col1: if code in productos['ARTICULO'].values: article_desc = productos[productos['ARTICULO'] == code]['DESCRIPCION'].values[0] st.write(f"- {article_desc} (Código: {code})") else: st.write(f"- (Código: {code}) [Artículo no encontrado en la base de datos]") # Añadir un botón para eliminar el artículo with col2: if st.button("❌", key=f"remove_{code}"): st.session_state['new_basket'].remove(code) st.success(f"El artículo con código {code} ha sido eliminado de la cesta.") st.rerun() # Botón para añadir cesta al histórico if st.button("📦 Añadir cesta al histórico"): if st.session_state['new_basket']: retroalimentacion(cestas, st.session_state['new_basket']) st.success("✓ La cesta ha sido añadida al histórico.") # Reset all session states st.session_state['new_basket'] = [] st.session_state['recommendations_df'] = None st.session_state['selected_descriptions'] = [] else: st.warning("⚠️ No hay artículos en la cesta para añadir.") elif st.session_state['recommendations_df'] is not None: st.warning("⚠️ No se encontraron recomendaciones para la cesta proporcionada.")