-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
350 lines (288 loc) · 12.8 KB
/
Copy pathapp.py
File metadata and controls
350 lines (288 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
from flask import Flask, request, jsonify, send_from_directory
import os
from werkzeug.utils import secure_filename
from models.Grafo import Grafo
from threading import Thread
app = Flask(__name__, static_folder='static')
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['ALLOWED_EXTENSIONS'] = {'csv'}
# Crear directorio de uploads si no existe
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
# Grafo global que se utilizará en la aplicación
grafo = None
# Variable global para almacenar el estado del cálculo de métricas
metricas_estado = {
'en_progreso': False,
'progreso': 0,
'metricas_calculadas': set(),
'error': None
}
def allowed_file(filename):
"""Verifica que el archivo tenga una extensión permitida"""
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
@app.route('/')
def index():
"""Página principal de la aplicación"""
return send_from_directory(app.static_folder, 'index.html')
@app.route('/cargar-grafo', methods=['POST'])
def cargar_grafo():
"""Carga un grafo a partir de archivos CSV de nodos y aristas"""
# Verificar que los archivos están en la solicitud
if 'nodes' not in request.files or 'edges' not in request.files:
return jsonify({'error': 'Se requieren archivos de nodos y aristas'}), 400
nodes_file = request.files['nodes']
edges_file = request.files['edges']
# Verificar que los archivos tienen nombre y son del tipo permitido
if nodes_file.filename == '' or edges_file.filename == '':
return jsonify({'error': 'No se seleccionaron archivos'}), 400
if not (allowed_file(nodes_file.filename) and allowed_file(edges_file.filename)):
return jsonify({'error': 'Formato de archivo no permitido'}), 400
# Guardar los archivos
nodes_path = os.path.join(app.config['UPLOAD_FOLDER'], 'nodes.csv')
edges_path = os.path.join(app.config['UPLOAD_FOLDER'], 'edges.csv')
nodes_file.save(nodes_path)
edges_file.save(edges_path)
# Crear el grafo
global grafo
try:
grafo = Grafo(nodes_path, edges_path)
num_nodos = grafo.get_num_nodos()
num_aristas = sum(nodo.get_num_adyacentes() for nodo in grafo.get_nodos().values()) // 2
return jsonify({
'mensaje': f'¡Grafo cargado exitosamente! Contiene {num_nodos} nodos y {num_aristas} aristas.',
'num_nodos': num_nodos,
'num_aristas': num_aristas
}), 200
except Exception as e:
return jsonify({'error': f'Error al cargar el grafo: {str(e)}'}), 500
@app.route('/info-grafo', methods=['GET'])
def info_grafo():
"""Retorna información básica del grafo"""
global grafo
if grafo is None:
return jsonify({'error': 'No hay grafo cargado. Por favor, cargue un grafo primero.'}), 400
# Obtener información básica
num_nodos = grafo.get_num_nodos()
# Contar aristas (cada arista está contada dos veces, una por cada nodo)
total_aristas = sum(nodo.get_num_adyacentes() for nodo in grafo.get_nodos().values()) // 2
return jsonify({
'mensaje': f'Información del grafo obtenida: {num_nodos} nodos y {total_aristas} aristas',
'num_nodos': num_nodos,
'num_aristas': total_aristas
}), 200
@app.route('/nodos', methods=['GET'])
def listar_nodos():
"""Retorna la lista de nodos en el grafo"""
global grafo
if grafo is None:
return jsonify({'error': 'No hay grafo cargado. Por favor, cargue un grafo primero.'}), 400
nodos = []
for nodo in grafo.get_nodos().values():
nodos.append({
'id': nodo.get_id(),
'nombre': nodo.get_nombre(),
'num_adyacentes': nodo.get_num_adyacentes()
})
return jsonify({
'mensaje': f'Se encontraron {len(nodos)} nodos en el grafo',
'nodos': nodos
}), 200
@app.route('/nodo/<id_nodo>', methods=['GET'])
def info_nodo(id_nodo):
"""Retorna información detallada de un nodo específico"""
global grafo
if grafo is None:
return jsonify({'error': 'No hay grafo cargado. Por favor, cargue un grafo primero.'}), 400
try:
# Registrar el ID del nodo solicitado para depuración
print(f"Solicitud de información para nodo con ID: '{id_nodo}'")
nodo = grafo.get_nodo(id_nodo)
if nodo is None:
# Registrar IDs disponibles para depuración
ids_disponibles = list(grafo.get_nodos().keys())
print(f"El nodo con ID '{id_nodo}' no existe. IDs disponibles: {ids_disponibles[:10]}")
return jsonify({'error': f"No existe el nodo con ID '{id_nodo}'"}), 404
# Obtener IDs de nodos adyacentes
adyacentes = [ady.get_id() for ady in nodo.get_adyacentes()]
# Construir respuesta con información del nodo
respuesta = {
'mensaje': f'Información detallada del nodo {nodo.get_nombre()} (ID: {id_nodo})',
'id': nodo.get_id(),
'nombre': nodo.get_nombre(),
'num_adyacentes': nodo.get_num_adyacentes(),
'adyacentes': adyacentes,
'centralidad': nodo.get_centralidad(),
'cercania': nodo.get_cercania(),
'intermediacion': nodo.get_intermediacion(),
'pagerank': nodo.get_pagerank(),
'authority': nodo.get_auth(),
'hub': nodo.get_hub()
}
return jsonify(respuesta), 200
except Exception as e:
print(f"Error al procesar información del nodo: {str(e)}")
return jsonify({'error': f'Error al procesar información del nodo: {str(e)}'}), 500
@app.route('/calcular-metricas', methods=['POST'])
def calcular_metricas():
"""Inicia el cálculo de métricas de manera asíncrona"""
global grafo, metricas_estado
if grafo is None:
return jsonify({'error': 'No hay grafo cargado. Por favor, cargue un grafo primero.'}), 400
if metricas_estado['en_progreso']:
return jsonify({'error': 'Ya hay un cálculo de métricas en progreso.'}), 400
# Reiniciar estado
metricas_estado['en_progreso'] = True
metricas_estado['progreso'] = 0
metricas_estado['metricas_calculadas'] = set()
metricas_estado['error'] = None
# Iniciar el cálculo en un hilo separado
thread = Thread(target=calcular_metricas_async)
thread.daemon = True
thread.start()
return jsonify({
'mensaje': 'Cálculo de métricas iniciado',
'estado': 'iniciado'
})
@app.route('/estado-metricas', methods=['GET'])
def obtener_estado_metricas():
"""Obtiene el estado actual del cálculo de métricas"""
global metricas_estado
return jsonify({
'en_progreso': metricas_estado['en_progreso'],
'progreso': metricas_estado['progreso'],
'metricas_calculadas': list(metricas_estado['metricas_calculadas']),
'error': metricas_estado['error']
})
def calcular_metricas_async():
"""Función que realiza el cálculo de métricas de manera asíncrona"""
global grafo, metricas_estado
try:
# Lista de métricas a calcular
metricas = [
('Centralidad', grafo.calcular_centralidad),
('Cercanía', grafo.calcular_cercania),
('Intermediación', grafo.calcular_intermediacion),
('PageRank', grafo.calcular_pagerank),
('HITS', grafo.calcular_hits)
]
total_metricas = len(metricas)
for i, (nombre, funcion) in enumerate(metricas):
try:
funcion()
metricas_estado['metricas_calculadas'].add(nombre)
except Exception as e:
print(f"Error al calcular {nombre}: {str(e)}")
# Actualizar progreso
metricas_estado['progreso'] = ((i + 1) / total_metricas) * 100
except Exception as e:
metricas_estado['error'] = str(e)
finally:
metricas_estado['en_progreso'] = False
@app.route('/aristas', methods=['GET'])
def listar_aristas():
"""Retorna la lista de aristas en el grafo"""
global grafo
if grafo is None:
return jsonify({'error': 'No hay grafo cargado. Por favor, cargue un grafo primero.'}), 400
aristas = []
visitadas = set() # Para evitar duplicados en grafos no dirigidos
for id_nodo, nodo in grafo.get_nodos().items():
for ady in nodo.get_adyacentes():
# Crear un identificador único para cada arista
arista_id = tuple(sorted([id_nodo, ady.get_id()]))
if arista_id not in visitadas:
aristas.append({
'source': id_nodo,
'target': ady.get_id()
})
visitadas.add(arista_id)
return jsonify({
'mensaje': f'Se encontraron {len(aristas)} aristas en el grafo',
'aristas': aristas
}), 200
@app.route('/distancia/<id_nodo1>/<id_nodo2>', methods=['GET'])
def calcular_distancia(id_nodo1, id_nodo2):
"""Calcula la distancia geodésica entre dos nodos"""
global grafo
if grafo is None:
return jsonify({'error': 'No hay grafo cargado. Por favor, cargue un grafo primero.'}), 400
if not grafo.existe_nodo(id_nodo1) or not grafo.existe_nodo(id_nodo2):
return jsonify({'error': 'Uno o ambos nodos no existen'}), 404
distancia = grafo.distancia_geodesica_BFS(id_nodo1, id_nodo2)
nodo1 = grafo.get_nodo(id_nodo1).get_nombre()
nodo2 = grafo.get_nodo(id_nodo2).get_nombre()
if distancia == float('inf'):
return jsonify({
'mensaje': f'No existe camino entre los nodos "{nodo1}" y "{nodo2}"',
'distancia': 'infinito',
'nodo1': {'id': id_nodo1, 'nombre': nodo1},
'nodo2': {'id': id_nodo2, 'nombre': nodo2}
}), 200
return jsonify({
'mensaje': f'La distancia entre "{nodo1}" y "{nodo2}" es {distancia}',
'distancia': distancia,
'nodo1': {'id': id_nodo1, 'nombre': nodo1},
'nodo2': {'id': id_nodo2, 'nombre': nodo2}
}), 200
@app.route('/buscar-nodo/<nombre>', methods=['GET'])
def buscar_nodo(nombre):
"""Busca un nodo por su nombre"""
global grafo
if grafo is None:
return jsonify({'error': 'No hay grafo cargado. Por favor, cargue un grafo primero.'}), 400
nodo = grafo.buscar_nodo_por_nombre(nombre)
if nodo is None:
return jsonify({'error': f'No se encontró ningún nodo con nombre "{nombre}"'}), 404
# Obtener IDs de nodos adyacentes para mostrar información más completa
adyacentes = [ady.get_id() for ady in nodo.get_adyacentes()]
return jsonify({
'mensaje': f'Nodo "{nombre}" encontrado con ID: {nodo.get_id()}',
'id': nodo.get_id(),
'nombre': nodo.get_nombre(),
'num_adyacentes': nodo.get_num_adyacentes(),
'adyacentes': adyacentes
}), 200
@app.route('/grafo-visualizacion', methods=['GET'])
def grafo_visualizacion():
"""Obtiene los datos del grafo en formato para visualización"""
global grafo
if grafo is None:
return jsonify({'error': 'No hay grafo cargado. Por favor, cargue un grafo primero.'}), 400
try:
nodos = []
for nodo in grafo.get_nodos().values():
# Asegurarse de que los IDs siempre sean strings
id_nodo = str(nodo.get_id())
nodos.append({
'id': id_nodo,
'label': nodo.get_nombre(),
'value': nodo.get_centralidad() or 1 # Tamaño basado en centralidad
})
enlaces = []
visitadas = set() # Para evitar duplicados en grafos no dirigidos
for id_nodo, nodo in grafo.get_nodos().items():
# Asegurarse de que los IDs siempre sean strings
id_nodo = str(id_nodo)
for ady in nodo.get_adyacentes():
id_ady = str(ady.get_id())
# Crear un identificador único para cada arista
arista_id = tuple(sorted([id_nodo, id_ady]))
if arista_id not in visitadas:
enlaces.append({
'source': id_nodo,
'target': id_ady
})
visitadas.add(arista_id)
# Imprimir algunos IDs para depuración
print(f"Nodos generados para visualización: {[n['id'] for n in nodos[:5]]}")
return jsonify({
'mensaje': f'Visualización del grafo generada con {len(nodos)} nodos y {len(enlaces)} aristas',
'nodes': nodos,
'links': enlaces
}), 200
except Exception as e:
print(f"Error al generar visualización del grafo: {str(e)}")
return jsonify({'error': f'Error al generar visualización del grafo: {str(e)}'}), 500
if __name__ == '__main__':
app.run(debug=True)