Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -12,12 +12,12 @@ def load_offices_csv(path):
|
|
| 12 |
sample = ''.join([next(f) for _ in range(10)])
|
| 13 |
dialect = csv.Sniffer().sniff(sample, delimiters=[',',';','\t','|'])
|
| 14 |
sep = dialect.delimiter
|
| 15 |
-
print(f"Delimitador detectado: '{sep}'")
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
return
|
| 19 |
|
| 20 |
-
#
|
| 21 |
DATA_XLSX = "Base de Datos Prueba.xlsx"
|
| 22 |
OFFICES_CSV = "oficinas_completas_deduccion_avanzada.csv"
|
| 23 |
|
|
@@ -26,315 +26,324 @@ df["MES"] = df["FECHA_APERTURA"].dt.to_period("M").dt.to_timestamp()
|
|
| 26 |
|
| 27 |
off_meta = load_offices_csv(OFFICES_CSV)
|
| 28 |
|
| 29 |
-
# Listas
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
| 37 |
|
| 38 |
-
# Coordenadas
|
| 39 |
office_coords = {
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
}
|
| 268 |
|
| 269 |
-
#
|
| 270 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
if zona:
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
return oficinas
|
| 275 |
|
| 276 |
# Dashboard
|
| 277 |
-
def dashboard(
|
|
|
|
| 278 |
d = df.copy()
|
| 279 |
-
if
|
| 280 |
-
d = d[d['
|
|
|
|
|
|
|
| 281 |
if ofis:
|
| 282 |
-
d = d[d['OFICINA'].isin(ofis)]
|
| 283 |
-
if
|
| 284 |
-
d = d[d['TIPO PRODUCTO'].isin(
|
| 285 |
-
if
|
| 286 |
-
d = d[d['SK_COLABORADOR'].isin(
|
| 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 |
-
|
| 295 |
-
|
|
|
|
| 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()
|
|
|
|
| 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()
|
|
|
|
| 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
|
| 306 |
-
|
| 307 |
-
if
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
return fig1, fig2, fig3, fig4, fig5, fig6,
|
| 313 |
|
| 314 |
# Interfaz Gradio
|
| 315 |
with gr.Blocks() as demo:
|
| 316 |
-
gr.Markdown(
|
| 317 |
with gr.Row():
|
| 318 |
with gr.Column(scale=1):
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
|
|
|
| 327 |
with gr.Column(scale=3):
|
| 328 |
-
out1 = gr.Plot()
|
| 329 |
-
|
| 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,
|
| 336 |
-
btn.click(dashboard,
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
demo.launch()
|
|
|
|
| 12 |
sample = ''.join([next(f) for _ in range(10)])
|
| 13 |
dialect = csv.Sniffer().sniff(sample, delimiters=[',',';','\t','|'])
|
| 14 |
sep = dialect.delimiter
|
| 15 |
+
print(f"Delimitador detectado: '{sep}'".replace('{sep}', sep))
|
| 16 |
+
df = pd.read_csv(path, sep=sep, encoding='latin-1', engine='python', on_bad_lines='skip')
|
| 17 |
+
df.columns = [c.strip() for c in df.columns]
|
| 18 |
+
return df
|
| 19 |
|
| 20 |
+
# Carga y preparación de datos
|
| 21 |
DATA_XLSX = "Base de Datos Prueba.xlsx"
|
| 22 |
OFFICES_CSV = "oficinas_completas_deduccion_avanzada.csv"
|
| 23 |
|
|
|
|
| 26 |
|
| 27 |
off_meta = load_offices_csv(OFFICES_CSV)
|
| 28 |
|
| 29 |
+
# Listas estáticas iniciales (convertir a str para evitar mezclas int/str)
|
| 30 |
+
departamentos = sorted(off_meta['DEPARTAMENTO'].dropna().astype(str).unique().tolist())
|
| 31 |
+
municipios = sorted(off_meta['CIUDAD'].dropna().astype(str).unique().tolist())
|
| 32 |
+
zonas = sorted(off_meta['ZONA'].dropna().astype(str).unique().tolist())
|
| 33 |
+
productos = sorted(df['TIPO PRODUCTO'].dropna().astype(str).unique().tolist())
|
| 34 |
+
colaboradores = sorted(df['SK_COLABORADOR'].dropna().astype(str).unique().tolist())
|
| 35 |
+
segmentos = sorted(df['SEGMENTO_CLIENTE'].dropna().astype(str).unique().tolist())
|
| 36 |
+
min_amt, max_amt = int(df['MONTO_I'].min()), int(df['MONTO_I'].max())
|
| 37 |
+
min_plazo, max_plazo= int(df['PLAZO'].min()), int(df['PLAZO'].max())
|
| 38 |
|
| 39 |
+
# Coordenadas de oficinas
|
| 40 |
office_coords = {
|
| 41 |
+
"Abrego": (8.080040, -73.219050),
|
| 42 |
+
"Aguachica": (8.310229, -73.599837),
|
| 43 |
+
"Aguazul": (5.171710, -72.547300),
|
| 44 |
+
"Agustin Codazzi": (10.033471, -73.291284),
|
| 45 |
+
"Andes": (5.655900, -75.879512),
|
| 46 |
+
"Apartado": (7.885610, -76.634790),
|
| 47 |
+
"Arauca": (7.086173, -70.757347),
|
| 48 |
+
"Arjona": (10.297838, -75.308821),
|
| 49 |
+
"Armenia": (6.167542, -75.764031),
|
| 50 |
+
"Av. Caracas": (4.582934, -74.092877),
|
| 51 |
+
"Ayapel": (0.000000, 0.000000),
|
| 52 |
+
"Barrancabermeja": (7.059381, -73.862874),
|
| 53 |
+
"Barrancas": (10.956670, -72.788870),
|
| 54 |
+
"Barranquilla": (10.961041, -74.800959),
|
| 55 |
+
"Belen": (5.989720, -72.913860),
|
| 56 |
+
"Bello": (6.334930, -75.558280),
|
| 57 |
+
"Bienestar Social Empleados": (0.000000, 0.000000),
|
| 58 |
+
"Bosa": (5.930960, -73.616320),
|
| 59 |
+
"Bosconia": (9.972841, -73.885721),
|
| 60 |
+
"Bucaramanga Centro": (7.092868, -73.126498),
|
| 61 |
+
"Buenaventura": (3.877616, -77.007365),
|
| 62 |
+
"Buenos Aires": (3.013970, -76.646120),
|
| 63 |
+
"Buga": (6.254484, -75.563634),
|
| 64 |
+
"Cachingos": (0.000000, 0.000000),
|
| 65 |
+
"Caldas": (6.089999, -75.636627),
|
| 66 |
+
"Cali El Poblado (NO VIGENTE)": (3.418618, -76.497226),
|
| 67 |
+
"Cali Norte": (6.300640, -70.205430),
|
| 68 |
+
"Cali Sur": (0.000000, 0.000000),
|
| 69 |
+
"Canal C": (0.000000, 0.000000),
|
| 70 |
+
"Candelaria": (3.408325, -76.349086),
|
| 71 |
+
"Carepa": (7.757548, -76.656274),
|
| 72 |
+
"Carmen de Viboral": (6.082360, -75.335090),
|
| 73 |
+
"Cartagena": (0.000000, 0.000000),
|
| 74 |
+
"Cartagena Sur (NO VIGENTE)": (0.000000, 0.000000),
|
| 75 |
+
"Cartago": (4.746743, -75.913598),
|
| 76 |
+
"Castilla": (3.827220, -73.688310),
|
| 77 |
+
"Caucasia": (7.987347, -75.196855),
|
| 78 |
+
"Centro Bogota (NO VIGENTE)": (0.000000, 0.000000),
|
| 79 |
+
"Centro MedelliAn": (6.254484, -75.563634),
|
| 80 |
+
"Centro Suba": (5.828915, -73.035056),
|
| 81 |
+
"Cerrito": (6.842993, -72.694730),
|
| 82 |
+
"Chaparral": (3.750151, -75.340480),
|
| 83 |
+
"Chia": (4.859712, -74.059663),
|
| 84 |
+
"Chinacota": (7.607990, -72.600380),
|
| 85 |
+
"Chinchina": (4.984375, -75.604801),
|
| 86 |
+
"Chiquinquira": (5.618910, -73.819970),
|
| 87 |
+
"Cienaga de Oro": (8.874380, -75.621750),
|
| 88 |
+
"Cimitarra": (6.316110, -73.950540),
|
| 89 |
+
"Copacabana": (6.348513, -75.507062),
|
| 90 |
+
"Corozal": (9.317780, -75.295830),
|
| 91 |
+
"Cucuta Atalaya": (7.907352, -72.524705),
|
| 92 |
+
"Cucuta Centro": (7.884556, -72.504855),
|
| 93 |
+
"Dabeiba": (7.033533, -76.167771),
|
| 94 |
+
"Direccion General": (0.000000, 0.000000),
|
| 95 |
+
"Duitama": (5.807690, -73.070165),
|
| 96 |
+
"El Bagre": (7.596620, -74.804880),
|
| 97 |
+
"El Banco": (8.998900, -73.970580),
|
| 98 |
+
"El Cable (NO VIGENTE)": (0.000000, 0.000000),
|
| 99 |
+
"El Tejar (NO VIGENTE)": (0.000000, 0.000000),
|
| 100 |
+
"Engativa": (6.254484, -75.563634),
|
| 101 |
+
"Envigado": (6.173196, -75.592097),
|
| 102 |
+
"Espinal (NO VIGENTE)": (0.000000, 0.000000),
|
| 103 |
+
"Facatativa": (4.811561, -74.384369),
|
| 104 |
+
"Florencia": (1.682220, -77.072610),
|
| 105 |
+
"Florida": (3.324420, -76.235460),
|
| 106 |
+
"Floridablanca": (7.079171, -73.108311),
|
| 107 |
+
"Fonseca": (10.888700, -72.851530),
|
| 108 |
+
"Fontibon": (6.777790, -76.128580),
|
| 109 |
+
"Fundacion": (10.521380, -74.186640),
|
| 110 |
+
"Fundadores (NO VIGENTE)": (0.000000, 0.000000),
|
| 111 |
+
"Funza": (4.714865, -74.212997),
|
| 112 |
+
"Fusagasuga": (4.311530, -74.355406),
|
| 113 |
+
"Galan": (6.637810, -73.288780),
|
| 114 |
+
"Garagoa": (5.083373, -73.363727),
|
| 115 |
+
"Garzon": (2.195783, -75.629006),
|
| 116 |
+
"Gerencia Territorial - Sur": (0.000000, 0.000000),
|
| 117 |
+
"Girardot": (4.303965, -74.804788),
|
| 118 |
+
"Giron": (7.074196, -73.167534),
|
| 119 |
+
"Granada": (4.519657, -74.353677),
|
| 120 |
+
"Guaduas": (5.067815, -74.598816),
|
| 121 |
+
"Ibague": (4.325569, -75.072920),
|
| 122 |
+
"Ibague Centro (NO VIGENTE)": (0.000000, 0.000000),
|
| 123 |
+
"Ipiales": (0.836103, -77.679298),
|
| 124 |
+
"Itagui": (6.170260, -75.616540),
|
| 125 |
+
"Jamundi": (3.111418, -76.606186),
|
| 126 |
+
"Kennedy": (4.622480, -74.150010),
|
| 127 |
+
"La America": (5.400098, -75.546666),
|
| 128 |
+
"La Calera": (4.721216, -73.968126),
|
| 129 |
+
"La Ceja": (6.032140, -75.431942),
|
| 130 |
+
"La Dorada": (5.464481, -74.704063),
|
| 131 |
+
"La Libertad": (2.445261, -76.632240),
|
| 132 |
+
"La Plata": (2.391670, -75.891670),
|
| 133 |
+
"La Union": (8.857282, -75.277048),
|
| 134 |
+
"La Union - Valle": (4.537120, -76.104421),
|
| 135 |
+
"La Victoria": (-0.111490, -71.110860),
|
| 136 |
+
"Leticia": (-4.215596, -69.939065),
|
| 137 |
+
"Libano": (4.922540, -75.063700),
|
| 138 |
+
"Lorica (NO VIGENTE)": (0.000000, 0.000000),
|
| 139 |
+
"Magangue": (9.186535, -74.788838),
|
| 140 |
+
"Maicao": (11.350383, -72.352333),
|
| 141 |
+
"Malaga": (6.702446, -72.731766),
|
| 142 |
+
"Manizales": (5.050927, -75.519500),
|
| 143 |
+
"Manrique": (6.484975, -75.019537),
|
| 144 |
+
"Marinilla": (6.173840, -75.334800),
|
| 145 |
+
"Mariquita": (5.198660, -74.896950),
|
| 146 |
+
"Mesitas": (3.383561, -74.044270),
|
| 147 |
+
"Minorista": (0.000000, 0.000000),
|
| 148 |
+
"Mitu": (1.255250, -70.233390),
|
| 149 |
+
"MoAitos": (0.000000, 0.000000),
|
| 150 |
+
"Mocoa": (1.148930, -76.647750),
|
| 151 |
+
"Mod Empoderados Costa Norte": (0.000000, 0.000000),
|
| 152 |
+
"Mod Empoderados Plus-Bta": (0.000000, 0.000000),
|
| 153 |
+
"Mod Empoderados Plus-M/llin": (0.000000, 0.000000),
|
| 154 |
+
"Mod Empoderados Plus-Sur": (0.000000, 0.000000),
|
| 155 |
+
"Molinos": (10.701780, -74.716750),
|
| 156 |
+
"Mompox": (0.000000, 0.000000),
|
| 157 |
+
"Moniquira": (5.877280, -73.570440),
|
| 158 |
+
"MonteliAbano": (7.983010, -75.417260),
|
| 159 |
+
"MonteriAa Centro": (8.754728, -75.881810),
|
| 160 |
+
"Monteria": (8.773391, -75.817808),
|
| 161 |
+
"Neiva (NO VIGENTE)": (0.000000, 0.000000),
|
| 162 |
+
"Niquia": (2.649380, -75.636650),
|
| 163 |
+
"OcaAa": (8.233420, -73.353310),
|
| 164 |
+
"Orito": (0.665371, -76.872392),
|
| 165 |
+
"PE Soledad Malambo": (10.861604, -74.773950),
|
| 166 |
+
"PE Acacias": (3.987212, -73.765837),
|
| 167 |
+
"PE Anserma": (5.230240, -75.787920),
|
| 168 |
+
"PE Baranoa": (10.796676, -74.914419),
|
| 169 |
+
"PE Belen": (5.989720, -72.913860),
|
| 170 |
+
"PE Cali La Casona": (0.000000, 0.000000),
|
| 171 |
+
"PE Cartagena del ChairA": (1.334860, -74.843460),
|
| 172 |
+
"PE Dosquebradas": (4.839160, -75.667270),
|
| 173 |
+
"PE Istmina": (0.000000, 0.000000),
|
| 174 |
+
"PE La Pintada 2": (5.749960, -75.616299),
|
| 175 |
+
"PE Majagual": (8.534730, -74.657180),
|
| 176 |
+
"PE Pasto (NO VIGENTE)": (0.000000, 0.000000),
|
| 177 |
+
"PE Puerto Wilches": (7.348801, -73.898273),
|
| 178 |
+
"PE PurificaciA3n": (3.856779, -74.932103),
|
| 179 |
+
"PE Santa Rosa del Sur": (7.946502, -74.026033),
|
| 180 |
+
"PE TAoquerres": (0.000000, 0.000000),
|
| 181 |
+
"PE Tame": (6.470290, -71.716970),
|
| 182 |
+
"PE Valle del Guamuez": (0.452500, -76.919170),
|
| 183 |
+
"PE Zarzal": (4.395820, -76.069830),
|
| 184 |
+
"Pailitas": (8.957360, -73.623460),
|
| 185 |
+
"Palmira": (3.538908, -76.298466),
|
| 186 |
+
"Pamplona": (7.377155, -72.648957),
|
| 187 |
+
"Parque de la CaAa (NO VIGENTE)": (0.000000, 0.000000),
|
| 188 |
+
"Parque de las Luces (NO VIGENTE)": (0.000000, 0.000000),
|
| 189 |
+
"Pasto": (0.000000, 0.000000),
|
| 190 |
+
"Patio Bonito": (6.205447, -75.575411),
|
| 191 |
+
"Paz de Ariporo": (5.881428, -71.891972),
|
| 192 |
+
"Pedregal": (6.254484, -75.563634),
|
| 193 |
+
"Perdomo": (2.888170, -75.433810),
|
| 194 |
+
"Pereira": (4.812216, -75.692047),
|
| 195 |
+
"Piedecuesta": (6.988034, -73.050030),
|
| 196 |
+
"PiendamA3 (NO VIGENTE)": (0.000000, 0.000000),
|
| 197 |
+
"Pitalito": (1.790464, -76.055636),
|
| 198 |
+
"Pivijay": (10.447166, -74.408568),
|
| 199 |
+
"Planeta Rica": (8.408920, -75.586800),
|
| 200 |
+
"Plato": (9.791910, -74.782970),
|
| 201 |
+
"Popayan Norte": (2.462388, -76.535919),
|
| 202 |
+
"Popayan Sur": (2.462388, -76.535919),
|
| 203 |
+
"Primero de Mayo": (5.821771, -73.043034),
|
| 204 |
+
"Principal": (13.375420, -81.369090),
|
| 205 |
+
"Puerto Asis": (0.497650, -76.497680),
|
| 206 |
+
"Puerto Berrio": (6.490949, -74.402668),
|
| 207 |
+
"Puerto Boyaca": (5.971557, -74.571408),
|
| 208 |
+
"Puerto CarreAo": (6.190854, -67.484779),
|
| 209 |
+
"Puerto IniArida": (-2.148770, -71.754990),
|
| 210 |
+
"Puerto Libertador": (7.889560, -75.672370),
|
| 211 |
+
"Punto Express Neiva 2": (0.000000, 0.000000),
|
| 212 |
+
"Quibdo": (0.000000, 0.000000),
|
| 213 |
+
"Quirigua": (2.649380, -75.636650),
|
| 214 |
+
"Restrepo": (3.826566, -76.521106),
|
| 215 |
+
"Riohacha": (11.381478, -72.905309),
|
| 216 |
+
"Rionegro": (7.264871, -73.147840),
|
| 217 |
+
"Riosucio": (5.421350, -75.703230),
|
| 218 |
+
"Robledo (NO VIGENTE)": (0.000000, 0.000000),
|
| 219 |
+
"Sahagun": (8.941133, -75.495272),
|
| 220 |
+
"San AndrAs Isla": (6.809803, -72.849736),
|
| 221 |
+
"San Andres de Sotavento": (9.144750, -75.508770),
|
| 222 |
+
"San Bernardo": (4.178771, -74.421565),
|
| 223 |
+
"San Fernando": (9.218060, -74.330294),
|
| 224 |
+
"San Francisco (NO VIGENTE)": (0.000000, 0.000000),
|
| 225 |
+
"San Gil": (6.557700, -73.133180),
|
| 226 |
+
"San Jose del Guaviare": (2.562393, -72.640344),
|
| 227 |
+
"San Juan": (4.461030, -73.680480),
|
| 228 |
+
"San Marcos": (8.658430, -75.131200),
|
| 229 |
+
"San Onofre": (9.737530, -75.525580),
|
| 230 |
+
"San Pelayo (NO VIGENTE)": (0.000000, 0.000000),
|
| 231 |
+
"San Vicente del Caguan": (2.113170, -74.769180),
|
| 232 |
+
"San Vicente del Chucuri": (6.881050, -73.411570),
|
| 233 |
+
"Santa Helenita": (10.325410, -74.961830),
|
| 234 |
+
"Santa MariAa (NO VIGENTE)": (0.000000, 0.000000),
|
| 235 |
+
"Santa Marta Av. Libertador": (11.231008, -74.175841),
|
| 236 |
+
"Santa Rosa de Cabal": (4.890245, -75.626971),
|
| 237 |
+
"Santafe de Antioquia": (6.556870, -75.828060),
|
| 238 |
+
"Santander de Quilichao": (3.008790, -76.485900),
|
| 239 |
+
"Santo Domingo": (6.472363, -75.164506),
|
| 240 |
+
"Sincelejo": (9.300213, -75.395603),
|
| 241 |
+
"Sincelejo Centro (NO VIGENTE)": (9.303609, -75.392834),
|
| 242 |
+
"Soacha": (4.582123, -74.211534),
|
| 243 |
+
"Sogamoso": (5.718314, -72.930984),
|
| 244 |
+
"Soledad (NO VIGENTE)": (0.000000, 0.000000),
|
| 245 |
+
"Suba": (5.451280, -73.814140),
|
| 246 |
+
"Suba Rincon": (4.728370, -74.088350),
|
| 247 |
+
"Tesoreria": (0.000000, 0.000000),
|
| 248 |
+
"Tierralta": (8.173570, -76.059210),
|
| 249 |
+
"ToberiAn": (0.000000, 0.000000),
|
| 250 |
+
"Tulua": (4.084320, -76.196650),
|
| 251 |
+
"Tumaco": (0.000000, 0.000000),
|
| 252 |
+
"Tunja": (5.538590, -73.366380),
|
| 253 |
+
"Turbo": (8.098040, -76.731690),
|
| 254 |
+
"Ubate": (4.482420, -73.934840),
|
| 255 |
+
"Urrao": (6.340116, -76.097593),
|
| 256 |
+
"Valledupar": (10.469026, -73.257035),
|
| 257 |
+
"Valledupar Centro": (10.476217, -73.245945),
|
| 258 |
+
"Velez": (6.012930, -73.673140),
|
| 259 |
+
"Venecia": (4.088080, -74.477460),
|
| 260 |
+
"Villanueva": (4.609834, -72.927380),
|
| 261 |
+
"Villavicencio": (4.144229, -73.634525),
|
| 262 |
+
"Villeta": (5.011370, -74.471560),
|
| 263 |
+
"Yarumal": (6.962765, -75.416779),
|
| 264 |
+
"Yomasa": (4.519873, -74.092186),
|
| 265 |
+
"Yopal": (5.340170, -72.394240),
|
| 266 |
+
"Yumbo": (3.581378, -76.494648),
|
| 267 |
+
"Zipaquira": (5.025810, -73.991283),
|
| 268 |
}
|
| 269 |
|
| 270 |
+
# Callbacks
|
| 271 |
+
def update_municipios(dept):
|
| 272 |
+
return sorted(off_meta.dropna().astype(str).unique().tolist())
|
| 273 |
+
|
| 274 |
+
def update_zonas(dept, muni):
|
| 275 |
+
df2 = off_meta
|
| 276 |
+
return sorted(df2['ZONA'].dropna().astype(str).unique().tolist())
|
| 277 |
+
|
| 278 |
+
def update_oficinas(dept, muni, zona):
|
| 279 |
if zona:
|
| 280 |
+
df2 = df2[df2['ZONA']==zona]
|
| 281 |
+
return sorted(df2['NOMBRE OFICINA'].dropna().astype(str).unique().tolist())
|
|
|
|
| 282 |
|
| 283 |
# Dashboard
|
| 284 |
+
def dashboard(f_inicio, f_fin, zona, tipos, ofis,
|
| 285 |
+
monto_rango, colaborador_sel, plazo_rango, segmento_sel):
|
| 286 |
d = df.copy()
|
| 287 |
+
if f_inicio:
|
| 288 |
+
d = d[d['FECHA_APERTURA'] >= pd.to_datetime(f_inicio)]
|
| 289 |
+
if f_fin:
|
| 290 |
+
d = d[d['FECHA_APERTURA'] <= pd.to_datetime(f_fin)]
|
| 291 |
if ofis:
|
| 292 |
+
d = d[d['OFICINA'].astype(str).isin(ofis)]
|
| 293 |
+
if tipos:
|
| 294 |
+
d = d[d['TIPO PRODUCTO'].astype(str).isin(tipos)]
|
| 295 |
+
if colaborador_sel:
|
| 296 |
+
d = d[d['SK_COLABORADOR'].astype(str).isin(colaborador_sel)]
|
| 297 |
if segmento_sel:
|
| 298 |
+
d = d[d['SEGMENTO_CLIENTE'].astype(str).isin(segmento_sel)]
|
|
|
|
| 299 |
d = d[(d['MONTO_I'] >= monto_rango[0]) & (d['MONTO_I'] <= monto_rango[1])]
|
| 300 |
+
d = d[(d['PLAZO'] >= plazo_rango[0]) & (d['PLAZO'] <= plazo_rango[1])]
|
| 301 |
|
| 302 |
fig1 = px.bar(d.groupby('MES')['MONTO_I'].sum().reset_index(), x='MES', y='MONTO_I',
|
| 303 |
+
labels={'MES':'Mes','MONTO_I':'Monto (COP)'}, title='Monto desembolsado por mes')
|
| 304 |
+
df2 = d['TIPO PRODUCTO'].value_counts().reset_index()
|
| 305 |
+
df2.columns = ['TIPO PRODUCTO','CANT']
|
| 306 |
+
fig2 = px.pie(df2, names='TIPO PRODUCTO', values='CANT', title='Distribución por producto')
|
| 307 |
fig3 = px.box(d, x='TIPO PRODUCTO', y='TASA', title='Distribución de tasas')
|
| 308 |
+
df_col = d['SK_COLABORADOR'].value_counts().reset_index()
|
| 309 |
+
df_col.columns = ['Colaborador','CANT']
|
| 310 |
fig4 = px.bar(df_col.head(10), x='Colaborador', y='CANT', title='Top 10 colaboradores')
|
| 311 |
fig5 = px.histogram(d, x='PLAZO', nbins=20, title='Distribución de plazo (días)')
|
| 312 |
+
df_seg = d['SEGMENTO_CLIENTE'].value_counts().reset_index()
|
| 313 |
+
df_seg.columns = ['Segmento','CANT']
|
| 314 |
fig6 = px.bar(df_seg, x='Segmento', y='CANT', title='Distribución por segmento')
|
| 315 |
|
| 316 |
m = folium.Map(location=[4.6, -74.1], zoom_start=6)
|
| 317 |
mc = MarkerCluster().add_to(m)
|
| 318 |
+
for ofi, coord in office_coords.items():
|
| 319 |
+
sub = d[d['OFICINA'].astype(str)==ofi]
|
| 320 |
+
if sub.empty:
|
| 321 |
+
continue
|
| 322 |
+
total = sub['MONTO_I'].sum()
|
| 323 |
+
folium.CircleMarker(location=coord, radius=6, fill=True,
|
| 324 |
+
popup=f"{ofi}<br>Total: {total:,.0f} COP").add_to(mc)
|
| 325 |
+
return fig1, fig2, fig3, fig4, fig5, fig6, m._repr_html_()
|
| 326 |
|
| 327 |
# Interfaz Gradio
|
| 328 |
with gr.Blocks() as demo:
|
| 329 |
+
gr.Markdown("## Dashboard Bancamía – Análisis Avanzado")
|
| 330 |
with gr.Row():
|
| 331 |
with gr.Column(scale=1):
|
| 332 |
+
f_inicio = gr.Textbox(label="Fecha inicio (YYYY-MM-DD)", value="2025-01-01")
|
| 333 |
+
f_fin = gr.Textbox(label="Fecha fin (YYYY-MM-DD)", value="2025-03-31")
|
| 334 |
+
zona = gr.Dropdown(zonas, label="Zona")
|
| 335 |
+
tipos = gr.CheckboxGroup(choices=productos, label="Tipo de producto")
|
| 336 |
+
colabor = gr.Dropdown(colaboradores, label="Colaborador", multiselect=True)
|
| 337 |
+
plazo = gr.Slider(min_plazo, max_plazo, value=[min_plazo, max_plazo], label="Plazo (días)")
|
| 338 |
+
segmento = gr.Dropdown(segmentos, label="Segmento", multiselect=True)
|
| 339 |
+
monto = gr.Slider(min_amt, max_amt, value=[min_amt, max_amt], step=1000000, label="Monto (COP)")
|
| 340 |
+
btn = gr.Button("Actualizar")
|
| 341 |
with gr.Column(scale=3):
|
| 342 |
+
out1 = gr.Plot(); out2 = gr.Plot(); out3 = gr.Plot()
|
| 343 |
+
out4 = gr.Plot(); out5 = gr.Plot(); out6 = gr.Plot()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
out7 = gr.HTML()
|
| 345 |
+
zona.change(update_oficinas, [zona], [ofis])
|
| 346 |
+
btn.click(dashboard, [f_inicio, f_fin, zona, tipos, ofis, monto, colabor, plazo, segmento],
|
| 347 |
+
[out1, out2, out3, out4, out5, out6, out7])
|
| 348 |
+
if __name__ == "__main__":
|
| 349 |
+
demo.launch()
|
|
|