AIdeaText commited on
Commit
7dd86ce
·
verified ·
1 Parent(s): f24e047

Create semantic_analysis_BackUp_18-5-2025.py

Browse files
modules/text_analysis/semantic_analysis_BackUp_18-5-2025.py ADDED
@@ -0,0 +1,475 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/text_analysis/semantic_analysis.py
2
+
3
+ # 1. Importaciones estándar del sistema
4
+ import logging
5
+ import io
6
+ import base64
7
+ from collections import Counter, defaultdict
8
+
9
+ # 2. Importaciones de terceros
10
+ import streamlit as st
11
+ import spacy
12
+ import networkx as nx
13
+ import matplotlib.pyplot as plt
14
+ from sklearn.feature_extraction.text import TfidfVectorizer
15
+ from sklearn.metrics.pairwise import cosine_similarity
16
+
17
+ # Solo configurar si no hay handlers ya configurados
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # 4. Importaciones locales
21
+ from .stopwords import (
22
+ process_text,
23
+ clean_text,
24
+ get_custom_stopwords,
25
+ get_stopwords_for_spacy
26
+ )
27
+
28
+
29
+ # Define colors for grammatical categories
30
+ POS_COLORS = {
31
+ 'ADJ': '#FFA07A', 'ADP': '#98FB98', 'ADV': '#87CEFA', 'AUX': '#DDA0DD',
32
+ 'CCONJ': '#F0E68C', 'DET': '#FFB6C1', 'INTJ': '#FF6347', 'NOUN': '#90EE90',
33
+ 'NUM': '#FAFAD2', 'PART': '#D3D3D3', 'PRON': '#FFA500', 'PROPN': '#20B2AA',
34
+ 'SCONJ': '#DEB887', 'SYM': '#7B68EE', 'VERB': '#FF69B4', 'X': '#A9A9A9',
35
+ }
36
+
37
+ POS_TRANSLATIONS = {
38
+ 'es': {
39
+ 'ADJ': 'Adjetivo', 'ADP': 'Preposición', 'ADV': 'Adverbio', 'AUX': 'Auxiliar',
40
+ 'CCONJ': 'Conjunción Coordinante', 'DET': 'Determinante', 'INTJ': 'Interjección',
41
+ 'NOUN': 'Sustantivo', 'NUM': 'Número', 'PART': 'Partícula', 'PRON': 'Pronombre',
42
+ 'PROPN': 'Nombre Propio', 'SCONJ': 'Conjunción Subordinante', 'SYM': 'Símbolo',
43
+ 'VERB': 'Verbo', 'X': 'Otro',
44
+ },
45
+ 'en': {
46
+ 'ADJ': 'Adjective', 'ADP': 'Preposition', 'ADV': 'Adverb', 'AUX': 'Auxiliary',
47
+ 'CCONJ': 'Coordinating Conjunction', 'DET': 'Determiner', 'INTJ': 'Interjection',
48
+ 'NOUN': 'Noun', 'NUM': 'Number', 'PART': 'Particle', 'PRON': 'Pronoun',
49
+ 'PROPN': 'Proper Noun', 'SCONJ': 'Subordinating Conjunction', 'SYM': 'Symbol',
50
+ 'VERB': 'Verb', 'X': 'Other',
51
+ },
52
+ 'fr': {
53
+ 'ADJ': 'Adjectif', 'ADP': 'Préposition', 'ADV': 'Adverbe', 'AUX': 'Auxiliaire',
54
+ 'CCONJ': 'Conjonction de Coordination', 'DET': 'Déterminant', 'INTJ': 'Interjection',
55
+ 'NOUN': 'Nom', 'NUM': 'Nombre', 'PART': 'Particule', 'PRON': 'Pronom',
56
+ 'PROPN': 'Nom Propre', 'SCONJ': 'Conjonction de Subordination', 'SYM': 'Symbole',
57
+ 'VERB': 'Verbe', 'X': 'Autre',
58
+ }
59
+ }
60
+
61
+ ENTITY_LABELS = {
62
+ 'es': {
63
+ "Personas": "lightblue",
64
+ "Lugares": "lightcoral",
65
+ "Inventos": "lightgreen",
66
+ "Fechas": "lightyellow",
67
+ "Conceptos": "lightpink"
68
+ },
69
+ 'en': {
70
+ "People": "lightblue",
71
+ "Places": "lightcoral",
72
+ "Inventions": "lightgreen",
73
+ "Dates": "lightyellow",
74
+ "Concepts": "lightpink"
75
+ },
76
+ 'fr': {
77
+ "Personnes": "lightblue",
78
+ "Lieux": "lightcoral",
79
+ "Inventions": "lightgreen",
80
+ "Dates": "lightyellow",
81
+ "Concepts": "lightpink"
82
+ }
83
+ }
84
+
85
+ ###########################################################
86
+ def fig_to_bytes(fig):
87
+ """Convierte una figura de matplotlib a bytes."""
88
+ try:
89
+ buf = io.BytesIO()
90
+ fig.savefig(buf, format='png', dpi=300, bbox_inches='tight')
91
+ buf.seek(0)
92
+ return buf.getvalue()
93
+ except Exception as e:
94
+ logger.error(f"Error en fig_to_bytes: {str(e)}")
95
+ return None
96
+
97
+ ###########################################################
98
+ def perform_semantic_analysis(text, nlp, lang_code):
99
+ """
100
+ Realiza el análisis semántico completo del texto.
101
+ """
102
+ if not text or not nlp or not lang_code:
103
+ logger.error("Parámetros inválidos para el análisis semántico")
104
+ return {
105
+ 'success': False,
106
+ 'error': 'Parámetros inválidos'
107
+ }
108
+
109
+ try:
110
+ logger.info(f"Starting semantic analysis for language: {lang_code}")
111
+
112
+ # Procesar texto y remover stopwords
113
+ doc = nlp(text)
114
+ if not doc:
115
+ logger.error("Error al procesar el texto con spaCy")
116
+ return {
117
+ 'success': False,
118
+ 'error': 'Error al procesar el texto'
119
+ }
120
+
121
+ # Identificar conceptos clave
122
+ logger.info("Identificando conceptos clave...")
123
+ stopwords = get_custom_stopwords(lang_code)
124
+ key_concepts = identify_key_concepts(doc, stopwords=stopwords)
125
+
126
+ if not key_concepts:
127
+ logger.warning("No se identificaron conceptos clave")
128
+ return {
129
+ 'success': False,
130
+ 'error': 'No se pudieron identificar conceptos clave'
131
+ }
132
+
133
+ # Crear grafo de conceptos
134
+ logger.info(f"Creando grafo de conceptos con {len(key_concepts)} conceptos...")
135
+ concept_graph = create_concept_graph(doc, key_concepts)
136
+
137
+ if not concept_graph.nodes():
138
+ logger.warning("Se creó un grafo vacío")
139
+ return {
140
+ 'success': False,
141
+ 'error': 'No se pudo crear el grafo de conceptos'
142
+ }
143
+
144
+ # Visualizar grafo
145
+ logger.info("Visualizando grafo...")
146
+ plt.clf() # Limpiar figura actual
147
+ concept_graph_fig = visualize_concept_graph(concept_graph, lang_code)
148
+
149
+ # Convertir a bytes
150
+ logger.info("Convirtiendo grafo a bytes...")
151
+ graph_bytes = fig_to_bytes(concept_graph_fig)
152
+
153
+ if not graph_bytes:
154
+ logger.error("Error al convertir grafo a bytes")
155
+ return {
156
+ 'success': False,
157
+ 'error': 'Error al generar visualización'
158
+ }
159
+
160
+ # Limpiar recursos
161
+ plt.close(concept_graph_fig)
162
+ plt.close('all')
163
+
164
+ result = {
165
+ 'success': True,
166
+ 'key_concepts': key_concepts,
167
+ 'concept_graph': graph_bytes
168
+ }
169
+
170
+ logger.info("Análisis semántico completado exitosamente")
171
+ return result
172
+
173
+ except Exception as e:
174
+ logger.error(f"Error in perform_semantic_analysis: {str(e)}")
175
+ plt.close('all') # Asegurarse de limpiar recursos
176
+ return {
177
+ 'success': False,
178
+ 'error': str(e)
179
+ }
180
+ finally:
181
+ plt.close('all') # Asegurar limpieza incluso si hay error
182
+
183
+ ############################################################
184
+
185
+ def identify_key_concepts(doc, stopwords, min_freq=2, min_length=3):
186
+ """
187
+ Identifica conceptos clave en el texto, excluyendo entidades nombradas.
188
+ Args:
189
+ doc: Documento procesado por spaCy
190
+ stopwords: Lista de stopwords
191
+ min_freq: Frecuencia mínima para considerar un concepto
192
+ min_length: Longitud mínima del concepto
193
+ Returns:
194
+ List[Tuple[str, int]]: Lista de tuplas (concepto, frecuencia)
195
+ """
196
+ try:
197
+ word_freq = Counter()
198
+
199
+ # Crear conjunto de tokens que son parte de entidades
200
+ entity_tokens = set()
201
+ for ent in doc.ents:
202
+ entity_tokens.update(token.i for token in ent)
203
+
204
+ # Procesar tokens
205
+ for token in doc:
206
+ # Verificar si el token no es parte de una entidad nombrada
207
+ if (token.i not in entity_tokens and # No es parte de una entidad
208
+ token.lemma_.lower() not in stopwords and # No es stopword
209
+ len(token.lemma_) >= min_length and # Longitud mínima
210
+ token.is_alpha and # Es alfabético
211
+ not token.is_punct and # No es puntuación
212
+ not token.like_num and # No es número
213
+ not token.is_space and # No es espacio
214
+ not token.is_stop and # No es stopword de spaCy
215
+ not token.pos_ == 'PROPN' and # No es nombre propio
216
+ not token.pos_ == 'SYM' and # No es símbolo
217
+ not token.pos_ == 'NUM' and # No es número
218
+ not token.pos_ == 'X'): # No es otro
219
+
220
+ # Convertir a minúsculas y añadir al contador
221
+ word_freq[token.lemma_.lower()] += 1
222
+
223
+ # Filtrar conceptos por frecuencia mínima y ordenar por frecuencia
224
+ concepts = [(word, freq) for word, freq in word_freq.items()
225
+ if freq >= min_freq]
226
+ concepts.sort(key=lambda x: x[1], reverse=True)
227
+
228
+ logger.info(f"Identified {len(concepts)} key concepts after excluding entities")
229
+ return concepts[:10]
230
+
231
+ except Exception as e:
232
+ logger.error(f"Error en identify_key_concepts: {str(e)}")
233
+ return []
234
+
235
+ ########################################################################
236
+
237
+ def create_concept_graph(doc, key_concepts):
238
+ """
239
+ Crea un grafo de relaciones entre conceptos, ignorando entidades.
240
+ Args:
241
+ doc: Documento procesado por spaCy
242
+ key_concepts: Lista de tuplas (concepto, frecuencia)
243
+ Returns:
244
+ nx.Graph: Grafo de conceptos
245
+ """
246
+ try:
247
+ G = nx.Graph()
248
+
249
+ # Crear un conjunto de conceptos clave para búsqueda rápida
250
+ concept_words = {concept[0].lower() for concept in key_concepts}
251
+
252
+ # Crear conjunto de tokens que son parte de entidades
253
+ entity_tokens = set()
254
+ for ent in doc.ents:
255
+ entity_tokens.update(token.i for token in ent)
256
+
257
+ # Añadir nodos al grafo
258
+ for concept, freq in key_concepts:
259
+ G.add_node(concept.lower(), weight=freq)
260
+
261
+ # Analizar cada oración
262
+ for sent in doc.sents:
263
+ # Obtener conceptos en la oración actual, excluyendo entidades
264
+ current_concepts = []
265
+ for token in sent:
266
+ if (token.i not in entity_tokens and
267
+ token.lemma_.lower() in concept_words):
268
+ current_concepts.append(token.lemma_.lower())
269
+
270
+ # Crear conexiones entre conceptos en la misma oración
271
+ for i, concept1 in enumerate(current_concepts):
272
+ for concept2 in current_concepts[i+1:]:
273
+ if concept1 != concept2:
274
+ if G.has_edge(concept1, concept2):
275
+ G[concept1][concept2]['weight'] += 1
276
+ else:
277
+ G.add_edge(concept1, concept2, weight=1)
278
+
279
+ return G
280
+
281
+ except Exception as e:
282
+ logger.error(f"Error en create_concept_graph: {str(e)}")
283
+ return nx.Graph()
284
+
285
+ ###############################################################################
286
+
287
+ def visualize_concept_graph(G, lang_code):
288
+ try:
289
+ # 1. Diccionario de traducciones
290
+ GRAPH_LABELS = {
291
+ 'es': {
292
+ 'concept_network': 'Relaciones entre conceptos clave',
293
+ 'concept_centrality': 'Centralidad de conceptos clave'
294
+ },
295
+ 'en': {
296
+ 'concept_network': 'Relationships between key concepts',
297
+ 'concept_centrality': 'Concept centrality'
298
+ },
299
+ 'fr': {
300
+ 'concept_network': 'Relations entre concepts clés',
301
+ 'concept_centrality': 'Centralité des concepts'
302
+ },
303
+ 'pt': {
304
+ 'concept_network': 'Relações entre conceitos-chave',
305
+ 'concept_centrality': 'Centralidade dos conceitos'
306
+ }
307
+ }
308
+
309
+ # 2. Obtener traducciones (inglés por defecto)
310
+ translations = GRAPH_LABELS.get(lang_code, GRAPH_LABELS['en'])
311
+
312
+ # Configuración de la figura
313
+ fig, ax = plt.subplots(figsize=(15, 10))
314
+
315
+ if not G.nodes():
316
+ logger.warning("Grafo vacío, retornando figura vacía")
317
+ return fig
318
+
319
+ # Convertir a grafo dirigido para flechas
320
+ DG = nx.DiGraph(G)
321
+ centrality = nx.degree_centrality(G)
322
+
323
+ # Layout consistente
324
+ pos = nx.spring_layout(DG, k=2, iterations=50, seed=42)
325
+
326
+ # Escalado de elementos visuales
327
+ num_nodes = len(DG.nodes())
328
+ scale_factor = 1000 if num_nodes < 10 else 500 if num_nodes < 20 else 200
329
+ node_sizes = [DG.nodes[node].get('weight', 1) * scale_factor for node in DG.nodes()]
330
+ edge_widths = [DG[u][v].get('weight', 1) for u, v in DG.edges()]
331
+ node_colors = [plt.cm.viridis(centrality[node]) for node in DG.nodes()]
332
+
333
+ # Dibujar elementos del grafo
334
+ nx.draw_networkx_nodes(
335
+ DG, pos,
336
+ node_size=node_sizes,
337
+ node_color=node_colors,
338
+ alpha=0.7,
339
+ ax=ax
340
+ )
341
+
342
+ nx.draw_networkx_edges(
343
+ DG, pos,
344
+ width=edge_widths,
345
+ alpha=0.6,
346
+ edge_color='gray',
347
+ arrows=True,
348
+ arrowsize=20,
349
+ arrowstyle='->',
350
+ connectionstyle='arc3,rad=0.2',
351
+ ax=ax
352
+ )
353
+
354
+ # Etiquetas de nodos
355
+ font_size = 12 if num_nodes < 10 else 10 if num_nodes < 20 else 8
356
+ nx.draw_networkx_labels(
357
+ DG, pos,
358
+ font_size=font_size,
359
+ font_weight='bold',
360
+ bbox=dict(facecolor='white', edgecolor='none', alpha=0.7),
361
+ ax=ax
362
+ )
363
+
364
+ # Barra de color (centralidad)
365
+ sm = plt.cm.ScalarMappable(
366
+ cmap=plt.cm.viridis,
367
+ norm=plt.Normalize(vmin=0, vmax=1)
368
+ )
369
+ sm.set_array([])
370
+ plt.colorbar(sm, ax=ax, label=translations['concept_centrality'])
371
+
372
+ # Título del gráfico
373
+ plt.title(translations['concept_network'], pad=20, fontsize=14)
374
+ ax.set_axis_off()
375
+ plt.tight_layout()
376
+
377
+ return fig
378
+
379
+ except Exception as e:
380
+ logger.error(f"Error en visualize_concept_graph: {str(e)}")
381
+ return plt.figure()
382
+
383
+ ########################################################################
384
+ def create_entity_graph(entities):
385
+ G = nx.Graph()
386
+ for entity_type, entity_list in entities.items():
387
+ for entity in entity_list:
388
+ G.add_node(entity, type=entity_type)
389
+ for i, entity1 in enumerate(entity_list):
390
+ for entity2 in entity_list[i+1:]:
391
+ G.add_edge(entity1, entity2)
392
+ return G
393
+
394
+
395
+ #############################################################
396
+ def visualize_entity_graph(G, lang_code):
397
+ fig, ax = plt.subplots(figsize=(12, 8))
398
+ pos = nx.spring_layout(G)
399
+ for entity_type, color in ENTITY_LABELS[lang_code].items():
400
+ node_list = [node for node, data in G.nodes(data=True) if data['type'] == entity_type]
401
+ nx.draw_networkx_nodes(G, pos, nodelist=node_list, node_color=color, node_size=500, alpha=0.8, ax=ax)
402
+ nx.draw_networkx_edges(G, pos, width=1, alpha=0.5, ax=ax)
403
+ nx.draw_networkx_labels(G, pos, font_size=8, font_weight="bold", ax=ax)
404
+ ax.set_title(f"Relaciones entre Entidades ({lang_code})", fontsize=16)
405
+ ax.axis('off')
406
+ plt.tight_layout()
407
+ return fig
408
+
409
+
410
+ #################################################################################
411
+ def create_topic_graph(topics, doc):
412
+ G = nx.Graph()
413
+ for topic in topics:
414
+ G.add_node(topic, weight=doc.text.count(topic))
415
+ for i, topic1 in enumerate(topics):
416
+ for topic2 in topics[i+1:]:
417
+ weight = sum(1 for sent in doc.sents if topic1 in sent.text and topic2 in sent.text)
418
+ if weight > 0:
419
+ G.add_edge(topic1, topic2, weight=weight)
420
+ return G
421
+
422
+ def visualize_topic_graph(G, lang_code):
423
+ fig, ax = plt.subplots(figsize=(12, 8))
424
+ pos = nx.spring_layout(G)
425
+ node_sizes = [G.nodes[node]['weight'] * 100 for node in G.nodes()]
426
+ nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color='lightgreen', alpha=0.8, ax=ax)
427
+ nx.draw_networkx_labels(G, pos, font_size=10, font_weight="bold", ax=ax)
428
+ edge_weights = [G[u][v]['weight'] for u, v in G.edges()]
429
+ nx.draw_networkx_edges(G, pos, width=edge_weights, alpha=0.5, ax=ax)
430
+ ax.set_title(f"Relaciones entre Temas ({lang_code})", fontsize=16)
431
+ ax.axis('off')
432
+ plt.tight_layout()
433
+ return fig
434
+
435
+ ###########################################################################################
436
+ def generate_summary(doc, lang_code):
437
+ sentences = list(doc.sents)
438
+ summary = sentences[:3] # Toma las primeras 3 oraciones como resumen
439
+ return " ".join([sent.text for sent in summary])
440
+
441
+ def extract_entities(doc, lang_code):
442
+ entities = defaultdict(list)
443
+ for ent in doc.ents:
444
+ if ent.label_ in ENTITY_LABELS[lang_code]:
445
+ entities[ent.label_].append(ent.text)
446
+ return dict(entities)
447
+
448
+ def analyze_sentiment(doc, lang_code):
449
+ positive_words = sum(1 for token in doc if token.sentiment > 0)
450
+ negative_words = sum(1 for token in doc if token.sentiment < 0)
451
+ total_words = len(doc)
452
+ if positive_words > negative_words:
453
+ return "Positivo"
454
+ elif negative_words > positive_words:
455
+ return "Negativo"
456
+ else:
457
+ return "Neutral"
458
+
459
+ def extract_topics(doc, lang_code):
460
+ vectorizer = TfidfVectorizer(stop_words='english', max_features=5)
461
+ tfidf_matrix = vectorizer.fit_transform([doc.text])
462
+ feature_names = vectorizer.get_feature_names_out()
463
+ return list(feature_names)
464
+
465
+ # Asegúrate de que todas las funciones necesarias estén exportadas
466
+ __all__ = [
467
+ 'perform_semantic_analysis',
468
+ 'identify_key_concepts',
469
+ 'create_concept_graph',
470
+ 'visualize_concept_graph',
471
+ 'fig_to_bytes', # Faltaba esta coma
472
+ 'ENTITY_LABELS',
473
+ 'POS_COLORS',
474
+ 'POS_TRANSLATIONS'
475
+ ]