Bancamia / app.py
hedtorresca's picture
Update app.py
756d05e verified
import os
import pandas as pd
import csv
import plotly.express as px
import gradio as gr
import folium
from folium.plugins import MarkerCluster
# Función para cargar CSV de oficinas
def load_offices_csv(path):
with open(path, encoding='latin-1') as f:
sample = ''.join([next(f) for _ in range(10)])
dialect = csv.Sniffer().sniff(sample, delimiters=[',',';','\t','|'])
sep = dialect.delimiter
print(f"Delimitador detectado: '{sep}'".replace('{sep}', sep))
df = pd.read_csv(path, sep=sep, encoding='latin-1', engine='python', on_bad_lines='skip')
df.columns = [c.strip() for c in df.columns]
return df
# Carga y preparación de datos
DATA_XLSX = "Base de Datos Prueba.xlsx"
OFFICES_CSV = "oficinas_completas_deduccion_avanzada.csv"
df = pd.read_excel(DATA_XLSX, parse_dates=["FECHA_APERTURA"])
df["MES"] = df["FECHA_APERTURA"].dt.to_period("M").dt.to_timestamp()
off_meta = load_offices_csv(OFFICES_CSV)
# Listas estáticas iniciales (convertir a str para evitar mezclas int/str)
departamentos = sorted(off_meta['DEPARTAMENTO'].dropna().astype(str).unique().tolist())
municipios = sorted(off_meta['CIUDAD'].dropna().astype(str).unique().tolist())
zonas = sorted(off_meta['ZONA'].dropna().astype(str).unique().tolist())
productos = sorted(df['TIPO PRODUCTO'].dropna().astype(str).unique().tolist())
colaboradores = sorted(df['SK_COLABORADOR'].dropna().astype(str).unique().tolist())
segmentos = sorted(df['SEGMENTO_CLIENTE'].dropna().astype(str).unique().tolist())
min_amt, max_amt = int(df['MONTO_I'].min()), int(df['MONTO_I'].max())
min_plazo, max_plazo= int(df['PLAZO'].min()), int(df['PLAZO'].max())
# Coordenadas de oficinas
office_coords = {
"Abrego": (8.080040, -73.219050),
"Aguachica": (8.310229, -73.599837),
"Aguazul": (5.171710, -72.547300),
"Agustin Codazzi": (10.033471, -73.291284),
"Andes": (5.655900, -75.879512),
"Apartado": (7.885610, -76.634790),
"Arauca": (7.086173, -70.757347),
"Arjona": (10.297838, -75.308821),
"Armenia": (6.167542, -75.764031),
"Av. Caracas": (4.582934, -74.092877),
"Ayapel": (0.000000, 0.000000),
"Barrancabermeja": (7.059381, -73.862874),
"Barrancas": (10.956670, -72.788870),
"Barranquilla": (10.961041, -74.800959),
"Belen": (5.989720, -72.913860),
"Bello": (6.334930, -75.558280),
"Bienestar Social Empleados": (0.000000, 0.000000),
"Bosa": (5.930960, -73.616320),
"Bosconia": (9.972841, -73.885721),
"Bucaramanga Centro": (7.092868, -73.126498),
"Buenaventura": (3.877616, -77.007365),
"Buenos Aires": (3.013970, -76.646120),
"Buga": (6.254484, -75.563634),
"Cachingos": (0.000000, 0.000000),
"Caldas": (6.089999, -75.636627),
"Cali El Poblado (NO VIGENTE)": (3.418618, -76.497226),
"Cali Norte": (6.300640, -70.205430),
"Cali Sur": (0.000000, 0.000000),
"Canal C": (0.000000, 0.000000),
"Candelaria": (3.408325, -76.349086),
"Carepa": (7.757548, -76.656274),
"Carmen de Viboral": (6.082360, -75.335090),
"Cartagena": (0.000000, 0.000000),
"Cartagena Sur (NO VIGENTE)": (0.000000, 0.000000),
"Cartago": (4.746743, -75.913598),
"Castilla": (3.827220, -73.688310),
"Caucasia": (7.987347, -75.196855),
"Centro Bogota (NO VIGENTE)": (0.000000, 0.000000),
"Centro MedelliAn": (6.254484, -75.563634),
"Centro Suba": (5.828915, -73.035056),
"Cerrito": (6.842993, -72.694730),
"Chaparral": (3.750151, -75.340480),
"Chia": (4.859712, -74.059663),
"Chinacota": (7.607990, -72.600380),
"Chinchina": (4.984375, -75.604801),
"Chiquinquira": (5.618910, -73.819970),
"Cienaga de Oro": (8.874380, -75.621750),
"Cimitarra": (6.316110, -73.950540),
"Copacabana": (6.348513, -75.507062),
"Corozal": (9.317780, -75.295830),
"Cucuta Atalaya": (7.907352, -72.524705),
"Cucuta Centro": (7.884556, -72.504855),
"Dabeiba": (7.033533, -76.167771),
"Direccion General": (0.000000, 0.000000),
"Duitama": (5.807690, -73.070165),
"El Bagre": (7.596620, -74.804880),
"El Banco": (8.998900, -73.970580),
"El Cable (NO VIGENTE)": (0.000000, 0.000000),
"El Tejar (NO VIGENTE)": (0.000000, 0.000000),
"Engativa": (6.254484, -75.563634),
"Envigado": (6.173196, -75.592097),
"Espinal (NO VIGENTE)": (0.000000, 0.000000),
"Facatativa": (4.811561, -74.384369),
"Florencia": (1.682220, -77.072610),
"Florida": (3.324420, -76.235460),
"Floridablanca": (7.079171, -73.108311),
"Fonseca": (10.888700, -72.851530),
"Fontibon": (6.777790, -76.128580),
"Fundacion": (10.521380, -74.186640),
"Fundadores (NO VIGENTE)": (0.000000, 0.000000),
"Funza": (4.714865, -74.212997),
"Fusagasuga": (4.311530, -74.355406),
"Galan": (6.637810, -73.288780),
"Garagoa": (5.083373, -73.363727),
"Garzon": (2.195783, -75.629006),
"Gerencia Territorial - Sur": (0.000000, 0.000000),
"Girardot": (4.303965, -74.804788),
"Giron": (7.074196, -73.167534),
"Granada": (4.519657, -74.353677),
"Guaduas": (5.067815, -74.598816),
"Ibague": (4.325569, -75.072920),
"Ibague Centro (NO VIGENTE)": (0.000000, 0.000000),
"Ipiales": (0.836103, -77.679298),
"Itagui": (6.170260, -75.616540),
"Jamundi": (3.111418, -76.606186),
"Kennedy": (4.622480, -74.150010),
"La America": (5.400098, -75.546666),
"La Calera": (4.721216, -73.968126),
"La Ceja": (6.032140, -75.431942),
"La Dorada": (5.464481, -74.704063),
"La Libertad": (2.445261, -76.632240),
"La Plata": (2.391670, -75.891670),
"La Union": (8.857282, -75.277048),
"La Union - Valle": (4.537120, -76.104421),
"La Victoria": (-0.111490, -71.110860),
"Leticia": (-4.215596, -69.939065),
"Libano": (4.922540, -75.063700),
"Lorica (NO VIGENTE)": (0.000000, 0.000000),
"Magangue": (9.186535, -74.788838),
"Maicao": (11.350383, -72.352333),
"Malaga": (6.702446, -72.731766),
"Manizales": (5.050927, -75.519500),
"Manrique": (6.484975, -75.019537),
"Marinilla": (6.173840, -75.334800),
"Mariquita": (5.198660, -74.896950),
"Mesitas": (3.383561, -74.044270),
"Minorista": (0.000000, 0.000000),
"Mitu": (1.255250, -70.233390),
"MoAitos": (0.000000, 0.000000),
"Mocoa": (1.148930, -76.647750),
"Mod Empoderados Costa Norte": (0.000000, 0.000000),
"Mod Empoderados Plus-Bta": (0.000000, 0.000000),
"Mod Empoderados Plus-M/llin": (0.000000, 0.000000),
"Mod Empoderados Plus-Sur": (0.000000, 0.000000),
"Molinos": (10.701780, -74.716750),
"Mompox": (0.000000, 0.000000),
"Moniquira": (5.877280, -73.570440),
"MonteliAbano": (7.983010, -75.417260),
"MonteriAa Centro": (8.754728, -75.881810),
"Monteria": (8.773391, -75.817808),
"Neiva (NO VIGENTE)": (0.000000, 0.000000),
"Niquia": (2.649380, -75.636650),
"OcaAa": (8.233420, -73.353310),
"Orito": (0.665371, -76.872392),
"PE Soledad Malambo": (10.861604, -74.773950),
"PE Acacias": (3.987212, -73.765837),
"PE Anserma": (5.230240, -75.787920),
"PE Baranoa": (10.796676, -74.914419),
"PE Belen": (5.989720, -72.913860),
"PE Cali La Casona": (0.000000, 0.000000),
"PE Cartagena del ChairA": (1.334860, -74.843460),
"PE Dosquebradas": (4.839160, -75.667270),
"PE Istmina": (0.000000, 0.000000),
"PE La Pintada 2": (5.749960, -75.616299),
"PE Majagual": (8.534730, -74.657180),
"PE Pasto (NO VIGENTE)": (0.000000, 0.000000),
"PE Puerto Wilches": (7.348801, -73.898273),
"PE PurificaciA3n": (3.856779, -74.932103),
"PE Santa Rosa del Sur": (7.946502, -74.026033),
"PE TAoquerres": (0.000000, 0.000000),
"PE Tame": (6.470290, -71.716970),
"PE Valle del Guamuez": (0.452500, -76.919170),
"PE Zarzal": (4.395820, -76.069830),
"Pailitas": (8.957360, -73.623460),
"Palmira": (3.538908, -76.298466),
"Pamplona": (7.377155, -72.648957),
"Parque de la CaAa (NO VIGENTE)": (0.000000, 0.000000),
"Parque de las Luces (NO VIGENTE)": (0.000000, 0.000000),
"Pasto": (0.000000, 0.000000),
"Patio Bonito": (6.205447, -75.575411),
"Paz de Ariporo": (5.881428, -71.891972),
"Pedregal": (6.254484, -75.563634),
"Perdomo": (2.888170, -75.433810),
"Pereira": (4.812216, -75.692047),
"Piedecuesta": (6.988034, -73.050030),
"PiendamA3 (NO VIGENTE)": (0.000000, 0.000000),
"Pitalito": (1.790464, -76.055636),
"Pivijay": (10.447166, -74.408568),
"Planeta Rica": (8.408920, -75.586800),
"Plato": (9.791910, -74.782970),
"Popayan Norte": (2.462388, -76.535919),
"Popayan Sur": (2.462388, -76.535919),
"Primero de Mayo": (5.821771, -73.043034),
"Principal": (13.375420, -81.369090),
"Puerto Asis": (0.497650, -76.497680),
"Puerto Berrio": (6.490949, -74.402668),
"Puerto Boyaca": (5.971557, -74.571408),
"Puerto CarreAo": (6.190854, -67.484779),
"Puerto IniArida": (-2.148770, -71.754990),
"Puerto Libertador": (7.889560, -75.672370),
"Punto Express Neiva 2": (0.000000, 0.000000),
"Quibdo": (0.000000, 0.000000),
"Quirigua": (2.649380, -75.636650),
"Restrepo": (3.826566, -76.521106),
"Riohacha": (11.381478, -72.905309),
"Rionegro": (7.264871, -73.147840),
"Riosucio": (5.421350, -75.703230),
"Robledo (NO VIGENTE)": (0.000000, 0.000000),
"Sahagun": (8.941133, -75.495272),
"San AndrAs Isla": (6.809803, -72.849736),
"San Andres de Sotavento": (9.144750, -75.508770),
"San Bernardo": (4.178771, -74.421565),
"San Fernando": (9.218060, -74.330294),
"San Francisco (NO VIGENTE)": (0.000000, 0.000000),
"San Gil": (6.557700, -73.133180),
"San Jose del Guaviare": (2.562393, -72.640344),
"San Juan": (4.461030, -73.680480),
"San Marcos": (8.658430, -75.131200),
"San Onofre": (9.737530, -75.525580),
"San Pelayo (NO VIGENTE)": (0.000000, 0.000000),
"San Vicente del Caguan": (2.113170, -74.769180),
"San Vicente del Chucuri": (6.881050, -73.411570),
"Santa Helenita": (10.325410, -74.961830),
"Santa MariAa (NO VIGENTE)": (0.000000, 0.000000),
"Santa Marta Av. Libertador": (11.231008, -74.175841),
"Santa Rosa de Cabal": (4.890245, -75.626971),
"Santafe de Antioquia": (6.556870, -75.828060),
"Santander de Quilichao": (3.008790, -76.485900),
"Santo Domingo": (6.472363, -75.164506),
"Sincelejo": (9.300213, -75.395603),
"Sincelejo Centro (NO VIGENTE)": (9.303609, -75.392834),
"Soacha": (4.582123, -74.211534),
"Sogamoso": (5.718314, -72.930984),
"Soledad (NO VIGENTE)": (0.000000, 0.000000),
"Suba": (5.451280, -73.814140),
"Suba Rincon": (4.728370, -74.088350),
"Tesoreria": (0.000000, 0.000000),
"Tierralta": (8.173570, -76.059210),
"ToberiAn": (0.000000, 0.000000),
"Tulua": (4.084320, -76.196650),
"Tumaco": (0.000000, 0.000000),
"Tunja": (5.538590, -73.366380),
"Turbo": (8.098040, -76.731690),
"Ubate": (4.482420, -73.934840),
"Urrao": (6.340116, -76.097593),
"Valledupar": (10.469026, -73.257035),
"Valledupar Centro": (10.476217, -73.245945),
"Velez": (6.012930, -73.673140),
"Venecia": (4.088080, -74.477460),
"Villanueva": (4.609834, -72.927380),
"Villavicencio": (4.144229, -73.634525),
"Villeta": (5.011370, -74.471560),
"Yarumal": (6.962765, -75.416779),
"Yomasa": (4.519873, -74.092186),
"Yopal": (5.340170, -72.394240),
"Yumbo": (3.581378, -76.494648),
"Zipaquira": (5.025810, -73.991283),
}
# Callbacks
def update_municipios(dept):
return sorted(off_meta.dropna().astype(str).unique().tolist())
def update_zonas(dept, muni):
df2 = off_meta
return sorted(df2['ZONA'].dropna().astype(str).unique().tolist())
def update_oficinas(dept, muni, zona):
if zona:
df2 = df2[df2['ZONA']==zona]
return sorted(df2['NOMBRE OFICINA'].dropna().astype(str).unique().tolist())
# Dashboard
def dashboard(f_inicio, f_fin, zona, tipos,
colaborador_sel, segmento_sel):
d = df.copy()
if f_inicio:
d = d[d['FECHA_APERTURA'] >= pd.to_datetime(f_inicio)]
if f_fin:
d = d[d['FECHA_APERTURA'] <= pd.to_datetime(f_fin)]
if tipos:
d = d[d['TIPO PRODUCTO'].astype(str).isin(tipos)]
if colaborador_sel:
d = d[d['SK_COLABORADOR'].astype(str).isin(colaborador_sel)]
if segmento_sel:
d = d[d['SEGMENTO_CLIENTE'].astype(str).isin(segmento_sel)]
fig1 = px.bar(d.groupby('MES')['MONTO_I'].sum().reset_index(), x='MES', y='MONTO_I',
labels={'MES':'Mes','MONTO_I':'Monto (COP)'}, title='Monto desembolsado por mes')
df2 = d['TIPO PRODUCTO'].value_counts().reset_index()
df2.columns = ['TIPO PRODUCTO','CANT']
fig2 = px.pie(df2, names='TIPO PRODUCTO', values='CANT', title='Distribución por producto')
fig3 = px.box(d, x='TIPO PRODUCTO', y='TASA', title='Distribución de tasas')
df_col = d['SK_COLABORADOR'].value_counts().reset_index()
df_col.columns = ['Colaborador','CANT']
fig4 = px.bar(df_col.head(10), x='Colaborador', y='CANT', title='Top 10 colaboradores')
fig5 = px.histogram(d, x='PLAZO', nbins=20, title='Distribución de plazo (días)')
df_seg = d['SEGMENTO_CLIENTE'].value_counts().reset_index()
df_seg.columns = ['Segmento','CANT']
fig6 = px.bar(df_seg, x='Segmento', y='CANT', title='Distribución por segmento')
m = folium.Map(location=[4.6, -74.1], zoom_start=6)
mc = MarkerCluster().add_to(m)
for ofi, coord in office_coords.items():
sub = d[d['OFICINA'].astype(str)==ofi]
if sub.empty:
continue
total = sub['MONTO_I'].sum()
folium.CircleMarker(location=coord, radius=6, fill=True,
popup=f"{ofi}<br>Total: {total:,.0f} COP").add_to(mc)
return fig1, fig2, fig3, fig4, fig5, fig6, m._repr_html_()
# Interfaz Gradio
with gr.Blocks() as demo:
gr.Markdown("## Dashboard Bancamía – Análisis Exploratorio")
with gr.Row():
with gr.Column(scale=1):
f_inicio = gr.Textbox(label="Fecha inicio (YYYY-MM-DD)", value="2025-01-01")
f_fin = gr.Textbox(label="Fecha fin (YYYY-MM-DD)", value="2025-03-31")
zona = gr.Dropdown(zonas, label="Zona")
tipos = gr.CheckboxGroup(choices=productos, label="Tipo de producto")
colabor = gr.Dropdown(colaboradores, label="Colaborador", multiselect=True)
segmento = gr.Dropdown(segmentos, label="Segmento", multiselect=True)
btn = gr.Button("Actualizar")
with gr.Column(scale=3):
out1 = gr.Plot(); out2 = gr.Plot(); out3 = gr.Plot()
out4 = gr.Plot(); out5 = gr.Plot(); out6 = gr.Plot()
out7 = gr.HTML()
btn.click(dashboard, [f_inicio, f_fin, zona, tipos, colabor, segmento],
[out1, out2, out3, out4, out5, out6, out7])
if __name__ == "__main__":
demo.launch()