Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import pandas as pd
|
| 2 |
import csv
|
| 3 |
import plotly.express as px
|
|
@@ -5,10 +6,7 @@ import gradio as gr
|
|
| 5 |
import folium
|
| 6 |
from folium.plugins import MarkerCluster
|
| 7 |
|
| 8 |
-
#
|
| 9 |
-
df = pd.read_excel("Base de Datos Prueba.xlsx", parse_dates=["FECHA_APERTURA"])
|
| 10 |
-
|
| 11 |
-
# Carga de metadatos de oficinas
|
| 12 |
def load_offices_csv(path):
|
| 13 |
with open(path, encoding='latin-1') as f:
|
| 14 |
sample = ''.join([next(f) for _ in range(10)])
|
|
@@ -19,10 +17,23 @@ def load_offices_csv(path):
|
|
| 19 |
df_off.columns = [c.strip() for c in df_off.columns]
|
| 20 |
return df_off
|
| 21 |
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
# Listas
|
| 25 |
-
zonas
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
# Coordenadas hard-coded
|
| 28 |
office_coords = {
|
|
@@ -255,44 +266,75 @@ office_coords = {
|
|
| 255 |
"Zipaquira": (5.025810, -73.991283),
|
| 256 |
}
|
| 257 |
|
| 258 |
-
# Callback para filtrar oficinas según zona
|
| 259 |
def update_oficinas(zona):
|
| 260 |
if zona:
|
| 261 |
-
return sorted(off_meta[off_meta['ZONA']==zona]['NOMBRE OFICINA']
|
| 262 |
-
|
|
|
|
| 263 |
|
| 264 |
-
#
|
| 265 |
-
def dashboard(ofis):
|
| 266 |
d = df.copy()
|
|
|
|
|
|
|
| 267 |
if ofis:
|
| 268 |
d = d[d['OFICINA'].isin(ofis)]
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
m = folium.Map(location=[4.6, -74.1], zoom_start=6)
|
| 273 |
mc = MarkerCluster().add_to(m)
|
| 274 |
-
for ofi in ofis or
|
| 275 |
coord = office_coords.get(ofi)
|
| 276 |
if coord:
|
| 277 |
total = d[d['OFICINA']==ofi]['MONTO_I'].sum()
|
| 278 |
-
folium.CircleMarker(location=coord, radius=
|
| 279 |
popup=f"{ofi}<br>Total: {total:,.0f} COP").add_to(mc)
|
| 280 |
map_html = m._repr_html_()
|
| 281 |
-
return fig1, map_html
|
| 282 |
|
| 283 |
# Interfaz Gradio
|
| 284 |
with gr.Blocks() as demo:
|
| 285 |
-
gr.Markdown('## Dashboard
|
| 286 |
with gr.Row():
|
| 287 |
with gr.Column(scale=1):
|
| 288 |
-
zona
|
| 289 |
-
ofis
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
with gr.Column(scale=3):
|
| 292 |
-
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
zona.change(update_oficinas, inputs=[zona], outputs=[ofis])
|
| 295 |
-
btn.click(dashboard, inputs=[ofis
|
|
|
|
| 296 |
|
| 297 |
if __name__ == '__main__':
|
| 298 |
demo.launch()
|
|
|
|
| 1 |
+
import os
|
| 2 |
import pandas as pd
|
| 3 |
import csv
|
| 4 |
import plotly.express as px
|
|
|
|
| 6 |
import folium
|
| 7 |
from folium.plugins import MarkerCluster
|
| 8 |
|
| 9 |
+
# Función para cargar CSV de oficinas
|
|
|
|
|
|
|
|
|
|
| 10 |
def load_offices_csv(path):
|
| 11 |
with open(path, encoding='latin-1') as f:
|
| 12 |
sample = ''.join([next(f) for _ in range(10)])
|
|
|
|
| 17 |
df_off.columns = [c.strip() for c in df_off.columns]
|
| 18 |
return df_off
|
| 19 |
|
| 20 |
+
# 1. Cargar y preparar datos
|
| 21 |
+
DATA_XLSX = "Base de Datos Prueba.xlsx"
|
| 22 |
+
OFFICES_CSV = "oficinas_completas_deduccion_avanzada.csv"
|
| 23 |
+
|
| 24 |
+
df = pd.read_excel(DATA_XLSX, parse_dates=["FECHA_APERTURA"])
|
| 25 |
+
df["MES"] = df["FECHA_APERTURA"].dt.to_period("M").dt.to_timestamp()
|
| 26 |
+
|
| 27 |
+
off_meta = load_offices_csv(OFFICES_CSV)
|
| 28 |
|
| 29 |
+
# Listas de filtros (sin departamento ni municipio)
|
| 30 |
+
zonas = sorted(off_meta['ZONA'].dropna().astype(str).unique().tolist())
|
| 31 |
+
oficinas = sorted(off_meta['NOMBRE OFICINA'].dropna().astype(str).unique().tolist())
|
| 32 |
+
productos = sorted(df['TIPO PRODUCTO'].dropna().astype(str).unique().tolist())
|
| 33 |
+
colaboradores = sorted(df['SK_COLABORADOR'].dropna().astype(str).unique().tolist())
|
| 34 |
+
segmentos = sorted(df['SEGMENTO_CLIENTE'].dropna().astype(str).unique().tolist())
|
| 35 |
+
min_amt, max_amt = int(df['MONTO_I'].min()), int(df['MONTO_I'].max())
|
| 36 |
+
min_plazo, max_plazo = int(df['PLAZO'].min()), int(df['PLAZO'].max())
|
| 37 |
|
| 38 |
# Coordenadas hard-coded
|
| 39 |
office_coords = {
|
|
|
|
| 266 |
"Zipaquira": (5.025810, -73.991283),
|
| 267 |
}
|
| 268 |
|
| 269 |
+
# Callback para filtrar oficinas según zona directamente (ya no hay dept/muni)
|
| 270 |
def update_oficinas(zona):
|
| 271 |
if zona:
|
| 272 |
+
return sorted(off_meta[off_meta['ZONA']==zona]['NOMBRE OFICINA']
|
| 273 |
+
.dropna().astype(str).unique().tolist())
|
| 274 |
+
return oficinas
|
| 275 |
|
| 276 |
+
# Dashboard
|
| 277 |
+
def dashboard(zona, ofis, tipos_sel, colabor_sel, plazo_rango, segmento_sel, monto_rango):
|
| 278 |
d = df.copy()
|
| 279 |
+
if zona:
|
| 280 |
+
d = d[d['ZONA']==zona]
|
| 281 |
if ofis:
|
| 282 |
d = d[d['OFICINA'].isin(ofis)]
|
| 283 |
+
if tipos_sel:
|
| 284 |
+
d = d[d['TIPO PRODUCTO'].isin(tipos_sel)]
|
| 285 |
+
if colabor_sel:
|
| 286 |
+
d = d[d['SK_COLABORADOR'].isin(colabor_sel)]
|
| 287 |
+
if segmento_sel:
|
| 288 |
+
d = d[d['SEGMENTO_CLIENTE'].isin(segmento_sel)]
|
| 289 |
+
d = d[(d['PLAZO'] >= plazo_rango[0]) & (d['PLAZO'] <= plazo_rango[1])]
|
| 290 |
+
d = d[(d['MONTO_I'] >= monto_rango[0]) & (d['MONTO_I'] <= monto_rango[1])]
|
| 291 |
+
|
| 292 |
+
fig1 = px.bar(d.groupby('MES')['MONTO_I'].sum().reset_index(), x='MES', y='MONTO_I',
|
| 293 |
+
labels={'MES':'Mes','MONTO_I':'Monto (COP)'}, title='Monto por mes')
|
| 294 |
+
fig2 = px.pie(d['TIPO PRODUCTO'].value_counts().reset_index().rename(columns={'index':'TIPO PRODUCTO',0:'CANT'}),
|
| 295 |
+
names='TIPO PRODUCTO', values='CANT', title='Distribución por producto')
|
| 296 |
+
fig3 = px.box(d, x='TIPO PRODUCTO', y='TASA', title='Distribución de tasas')
|
| 297 |
+
df_col = d['SK_COLABORADOR'].value_counts().reset_index().rename(columns={'index':'Colaborador',0:'CANT'})
|
| 298 |
+
fig4 = px.bar(df_col.head(10), x='Colaborador', y='CANT', title='Top 10 colaboradores')
|
| 299 |
+
fig5 = px.histogram(d, x='PLAZO', nbins=20, title='Distribución de plazo (días)')
|
| 300 |
+
df_seg = d['SEGMENTO_CLIENTE'].value_counts().reset_index().rename(columns={'index':'Segmento',0:'CANT'})
|
| 301 |
+
fig6 = px.bar(df_seg, x='Segmento', y='CANT', title='Distribución por segmento')
|
| 302 |
+
|
| 303 |
m = folium.Map(location=[4.6, -74.1], zoom_start=6)
|
| 304 |
mc = MarkerCluster().add_to(m)
|
| 305 |
+
for ofi in ofis or oficinas:
|
| 306 |
coord = office_coords.get(ofi)
|
| 307 |
if coord:
|
| 308 |
total = d[d['OFICINA']==ofi]['MONTO_I'].sum()
|
| 309 |
+
folium.CircleMarker(location=coord, radius=6, fill=True,
|
| 310 |
popup=f"{ofi}<br>Total: {total:,.0f} COP").add_to(mc)
|
| 311 |
map_html = m._repr_html_()
|
| 312 |
+
return fig1, fig2, fig3, fig4, fig5, fig6, map_html
|
| 313 |
|
| 314 |
# Interfaz Gradio
|
| 315 |
with gr.Blocks() as demo:
|
| 316 |
+
gr.Markdown('## Dashboard Bancamía – Filtros de Zona y Oficina')
|
| 317 |
with gr.Row():
|
| 318 |
with gr.Column(scale=1):
|
| 319 |
+
zona = gr.Dropdown(zonas, label='Zona')
|
| 320 |
+
ofis = gr.Dropdown(oficinas, label='Oficina', multiselect=True)
|
| 321 |
+
tipos_sel = gr.CheckboxGroup(productos, label='Tipo de producto')
|
| 322 |
+
colabor_sel = gr.Dropdown(colaboradores, label='Colaborador', multiselect=True)
|
| 323 |
+
plazo_rango = gr.Slider(min_plazo, max_plazo, value=[min_plazo, max_plazo], label='Plazo (días)')
|
| 324 |
+
segmento_sel = gr.Dropdown(segmentos, label='Segmento', multiselect=True)
|
| 325 |
+
monto_rango = gr.Slider(min_amt, max_amt, value=[min_amt, max_amt], step=1000000, label='Monto (COP)')
|
| 326 |
+
btn = gr.Button('Actualizar')
|
| 327 |
with gr.Column(scale=3):
|
| 328 |
+
out1 = gr.Plot()
|
| 329 |
+
out2 = gr.Plot()
|
| 330 |
+
out3 = gr.Plot()
|
| 331 |
+
out4 = gr.Plot()
|
| 332 |
+
out5 = gr.Plot()
|
| 333 |
+
out6 = gr.Plot()
|
| 334 |
+
out7 = gr.HTML()
|
| 335 |
zona.change(update_oficinas, inputs=[zona], outputs=[ofis])
|
| 336 |
+
btn.click(dashboard, inputs=[zona, ofis, tipos_sel, colabor_sel, plazo_rango, segmento_sel, monto_rango],
|
| 337 |
+
outputs=[out1, out2, out3, out4, out5, out6, out7])
|
| 338 |
|
| 339 |
if __name__ == '__main__':
|
| 340 |
demo.launch()
|