rwayz commited on
Commit
2ab9338
·
verified ·
1 Parent(s): 4c06429

Delete graphs

Browse files
Files changed (1) hide show
  1. graphs/main_graph.py +0 -775
graphs/main_graph.py DELETED
@@ -1,775 +0,0 @@
1
- """
2
- Grafo principal do LangGraph para o AgentGraph
3
- """
4
- import logging
5
- import pandas as pd
6
- import re
7
- from typing import Dict, Any, Optional
8
- from langgraph.graph import StateGraph, END
9
- from langgraph.checkpoint.memory import MemorySaver
10
- from sqlalchemy import Integer, Float, DateTime
11
-
12
- from nodes.agent_node import AgentState, should_refine_response, should_generate_graph
13
- from nodes.csv_processing_node import csv_processing_node
14
- from nodes.database_node import (
15
- create_database_from_dataframe_node,
16
- load_existing_database_node,
17
- get_database_sample_node
18
- )
19
- from nodes.query_node import (
20
- validate_query_input_node,
21
- prepare_query_context_node,
22
- process_user_query_node
23
- )
24
- from nodes.refinement_node import (
25
- refine_response_node,
26
- format_final_response_node
27
- )
28
- from nodes.cache_node import (
29
- check_cache_node,
30
- cache_response_node,
31
- update_history_node
32
- )
33
- from nodes.graph_selection_node import graph_selection_node
34
- from nodes.graph_generation_node import graph_generation_node
35
- from nodes.custom_nodes import CustomNodeManager
36
- from agents.sql_agent import SQLAgentManager
37
- from agents.tools import CacheManager
38
- from utils.database import create_sql_database
39
- from utils.config import get_active_csv_path, SQL_DB_PATH
40
- from utils.object_manager import get_object_manager
41
-
42
- class AgentGraphManager:
43
- """
44
- Gerenciador principal do grafo LangGraph
45
- """
46
-
47
- def __init__(self):
48
- self.graph = None
49
- self.app = None
50
- self.cache_manager = CacheManager()
51
- self.custom_node_manager = CustomNodeManager()
52
- self.object_manager = get_object_manager()
53
- self.engine = None
54
- self.sql_agent = None
55
- self.db = None
56
- # IDs para objetos não-serializáveis
57
- self.agent_id = None
58
- self.engine_id = None
59
- self.db_id = None
60
- self.cache_id = None
61
- self._initialize_system()
62
- self._build_graph()
63
-
64
- def _initialize_system(self):
65
- """Inicializa o sistema com banco e agente padrão"""
66
- try:
67
- # Para inicialização síncrona, vamos usar load_existing_database_node de forma síncrona
68
- # ou criar uma versão síncrona temporária
69
- import os
70
- from sqlalchemy import create_engine
71
-
72
- # Verifica se banco existe
73
- if os.path.exists(SQL_DB_PATH):
74
- # Carrega banco existente
75
- self.engine = create_engine(f"sqlite:///{SQL_DB_PATH}")
76
- db = create_sql_database(self.engine)
77
- logging.info("Banco existente carregado")
78
- else:
79
- # Cria novo banco usando função síncrona temporária
80
- csv_path = get_active_csv_path()
81
- self.engine = self._create_engine_sync(csv_path)
82
- db = create_sql_database(self.engine)
83
- logging.info("Novo banco criado")
84
-
85
- # Armazena banco de dados
86
- self.db = db
87
- self.db_id = self.object_manager.store_database(db)
88
-
89
- # Cria agente SQL
90
- self.sql_agent = SQLAgentManager(db)
91
-
92
- # Armazena objetos no gerenciador
93
- self.agent_id = self.object_manager.store_sql_agent(self.sql_agent, self.db_id)
94
- self.engine_id = self.object_manager.store_engine(self.engine)
95
- self.cache_id = self.object_manager.store_cache_manager(self.cache_manager)
96
-
97
- logging.info("Sistema inicializado com sucesso")
98
-
99
- except Exception as e:
100
- logging.error(f"Erro ao inicializar sistema: {e}")
101
- raise
102
-
103
- def _create_engine_sync(self, csv_path: str):
104
- """Cria engine de forma síncrona para inicialização"""
105
- import pandas as pd
106
- from sqlalchemy import create_engine
107
- from sqlalchemy.types import DateTime, Integer, Float
108
-
109
- # Lê CSV
110
- df = pd.read_csv(csv_path, sep=';')
111
-
112
- # Processamento inteligente de tipos
113
- sql_types = {}
114
- df = self._smart_type_conversion(df, sql_types)
115
-
116
- # Cria engine e salva dados
117
- engine = create_engine(f"sqlite:///{SQL_DB_PATH}")
118
- df.to_sql("tabela", engine, index=False, if_exists="replace", dtype=sql_types)
119
-
120
- logging.info(f"Banco criado com {len(df)} registros")
121
- return engine
122
-
123
- def _build_graph(self):
124
- """Constrói o grafo LangGraph com nova arquitetura"""
125
- try:
126
- # Cria o StateGraph
127
- workflow = StateGraph(AgentState)
128
-
129
- # Adiciona nós de validação e preparação
130
- workflow.add_node("validate_input", validate_query_input_node)
131
- workflow.add_node("check_cache", check_cache_node)
132
- workflow.add_node("prepare_context", prepare_query_context_node)
133
- workflow.add_node("get_db_sample", get_database_sample_node)
134
-
135
- # Adiciona nós de processamento
136
- workflow.add_node("process_query", process_user_query_node)
137
-
138
- # Adiciona nós de gráficos
139
- workflow.add_node("graph_selection", graph_selection_node)
140
- workflow.add_node("graph_generation", graph_generation_node)
141
-
142
- # Adiciona nós de refinamento
143
- workflow.add_node("refine_response", refine_response_node)
144
- workflow.add_node("format_response", format_final_response_node)
145
-
146
- # Adiciona nós de cache e histórico
147
- workflow.add_node("cache_response", cache_response_node)
148
- workflow.add_node("update_history", update_history_node)
149
-
150
- # Define ponto de entrada
151
- workflow.set_entry_point("validate_input")
152
-
153
- # Fluxo principal
154
- workflow.add_edge("validate_input", "check_cache")
155
-
156
- # Condicional para cache hit
157
- workflow.add_conditional_edges(
158
- "check_cache",
159
- lambda state: "update_history" if state.get("cache_hit") else "prepare_context"
160
- )
161
-
162
- workflow.add_edge("prepare_context", "get_db_sample")
163
- workflow.add_edge("get_db_sample", "process_query")
164
-
165
- # Condicional para gráficos (após AgentSQL)
166
- workflow.add_conditional_edges(
167
- "process_query",
168
- should_generate_graph,
169
- {
170
- "graph_selection": "graph_selection",
171
- "refine_response": "refine_response",
172
- "cache_response": "cache_response"
173
- }
174
- )
175
-
176
- # Fluxo dos gráficos
177
- workflow.add_edge("graph_selection", "graph_generation")
178
-
179
- # Após geração de gráfico, vai para refinamento ou cache
180
- workflow.add_conditional_edges(
181
- "graph_generation",
182
- should_refine_response,
183
- {
184
- "refine_response": "refine_response",
185
- "cache_response": "cache_response"
186
- }
187
- )
188
-
189
- workflow.add_edge("refine_response", "format_response")
190
- workflow.add_edge("format_response", "cache_response")
191
- workflow.add_edge("cache_response", "update_history")
192
- workflow.add_edge("update_history", END)
193
-
194
- # Compila o grafo
195
- memory = MemorySaver()
196
- self.app = workflow.compile(checkpointer=memory)
197
-
198
- logging.info("Grafo LangGraph construído com sucesso")
199
-
200
- except Exception as e:
201
- logging.error(f"Erro ao construir grafo: {e}")
202
- raise
203
-
204
- async def process_query(
205
- self,
206
- user_input: str,
207
- selected_model: str = "GPT-4o-mini",
208
- advanced_mode: bool = False,
209
- thread_id: str = "default"
210
- ) -> Dict[str, Any]:
211
- """
212
- Processa uma query do usuário através do grafo
213
-
214
- Args:
215
- user_input: Entrada do usuário
216
- selected_model: Modelo LLM selecionado
217
- advanced_mode: Se deve usar refinamento avançado
218
- thread_id: ID da thread para checkpoint
219
-
220
- Returns:
221
- Resultado do processamento
222
- """
223
- try:
224
- # Verifica se precisa recriar agente SQL com modelo diferente
225
- current_sql_agent = self.object_manager.get_sql_agent(self.agent_id)
226
- if current_sql_agent and current_sql_agent.model_name != selected_model:
227
- logging.info(f"Recriando agente SQL com modelo {selected_model}")
228
-
229
- # Recupera banco de dados associado ao agente
230
- db_id = self.object_manager.get_db_id_for_agent(self.agent_id)
231
- if db_id:
232
- db = self.object_manager.get_database(db_id)
233
- if db:
234
- new_sql_agent = SQLAgentManager(db, selected_model)
235
- self.agent_id = self.object_manager.store_sql_agent(new_sql_agent, db_id)
236
- logging.info(f"Agente SQL recriado com sucesso para modelo {selected_model}")
237
- else:
238
- logging.error("Banco de dados não encontrado para recriar agente")
239
- else:
240
- logging.error("ID do banco de dados não encontrado para o agente")
241
-
242
- # Prepara estado inicial com IDs serializáveis
243
- initial_state = {
244
- "user_input": user_input,
245
- "selected_model": selected_model,
246
- "response": "",
247
- "advanced_mode": advanced_mode,
248
- "execution_time": 0.0,
249
- "error": None,
250
- "intermediate_steps": [],
251
- "db_sample_dict": {},
252
- # IDs para recuperar objetos não-serializáveis
253
- "agent_id": self.agent_id,
254
- "engine_id": self.engine_id,
255
- "db_id": self.db_id,
256
- "cache_id": self.cache_id,
257
- # Campos relacionados a gráficos
258
- "query_type": "sql_query", # Será atualizado pela detecção
259
- "sql_query_extracted": None,
260
- "graph_type": None,
261
- "graph_data": None,
262
- "graph_image_id": None,
263
- "graph_generated": False,
264
- "graph_error": None
265
- }
266
-
267
- # Executa o grafo
268
- config = {"configurable": {"thread_id": thread_id}}
269
- result = await self.app.ainvoke(initial_state, config=config)
270
-
271
- logging.info(f"Query processada com sucesso: {user_input[:50]}...")
272
- return result
273
-
274
- except Exception as e:
275
- error_msg = f"Erro ao processar query: {e}"
276
- logging.error(error_msg)
277
- return {
278
- "user_input": user_input,
279
- "response": error_msg,
280
- "error": error_msg,
281
- "execution_time": 0.0
282
- }
283
-
284
- async def handle_csv_upload(self, file_path: str) -> Dict[str, Any]:
285
- """
286
- Processa upload de CSV usando nova arquitetura de nós
287
-
288
- Args:
289
- file_path: Caminho do arquivo CSV
290
-
291
- Returns:
292
- Resultado do upload
293
- """
294
- try:
295
- # Etapa 1: Processa CSV
296
- csv_state = {
297
- "file_path": file_path,
298
- "success": False,
299
- "message": "",
300
- "csv_data_sample": {},
301
- "column_info": {},
302
- "processing_stats": {}
303
- }
304
-
305
- csv_result = await csv_processing_node(csv_state)
306
-
307
- if not csv_result["success"]:
308
- return csv_result
309
-
310
- # Etapa 2: Cria banco de dados
311
- db_state = csv_result.copy()
312
- db_result = await create_database_from_dataframe_node(db_state)
313
-
314
- if not db_result["success"]:
315
- return db_result
316
-
317
- # Etapa 3: Atualiza sistema
318
- if db_result["success"]:
319
- # Atualiza IDs dos objetos
320
- self.engine_id = db_result["engine_id"]
321
- self.db_id = db_result["db_id"]
322
-
323
- # Cria novo agente SQL
324
- new_engine = self.object_manager.get_engine(self.engine_id)
325
- new_db = self.object_manager.get_database(self.db_id)
326
- new_sql_agent = SQLAgentManager(new_db)
327
-
328
- # Atualiza agente
329
- self.agent_id = self.object_manager.store_sql_agent(new_sql_agent, self.db_id)
330
-
331
- # Limpa cache
332
- cache_manager = self.object_manager.get_cache_manager(self.cache_id)
333
- if cache_manager:
334
- cache_manager.clear_cache()
335
-
336
- logging.info("[UPLOAD] Sistema atualizado com novo CSV")
337
-
338
- return db_result
339
-
340
- except Exception as e:
341
- error_msg = f"❌ Erro no upload de CSV: {e}"
342
- logging.error(error_msg)
343
- return {
344
- "success": False,
345
- "message": error_msg
346
- }
347
-
348
- async def reset_system(self) -> Dict[str, Any]:
349
- """
350
- Reseta o sistema ao estado inicial
351
-
352
- Returns:
353
- Resultado do reset
354
- """
355
- try:
356
- # Usa nó de reset customizado
357
- state = {
358
- "success": False,
359
- "message": "",
360
- "engine_id": self.engine_id,
361
- "agent_id": self.agent_id,
362
- "cache_id": self.cache_id
363
- }
364
-
365
- result = await self.custom_node_manager.execute_node("system_reset", state)
366
-
367
- # Se reset foi bem-sucedido, atualiza IDs
368
- if result.get("success"):
369
- self.engine_id = result.get("engine_id", self.engine_id)
370
- self.agent_id = result.get("agent_id", self.agent_id)
371
- # Cache ID permanece o mesmo, apenas é limpo
372
-
373
- logging.info("[RESET] Sistema resetado com sucesso")
374
-
375
- return result
376
-
377
- except Exception as e:
378
- error_msg = f"❌ Erro ao resetar sistema: {e}"
379
- logging.error(error_msg)
380
- return {
381
- "success": False,
382
- "message": error_msg
383
- }
384
-
385
- def toggle_advanced_mode(self, enabled: bool) -> str:
386
- """
387
- Alterna modo avançado
388
-
389
- Args:
390
- enabled: Se deve habilitar modo avançado
391
-
392
- Returns:
393
- Mensagem de status
394
- """
395
- message = "Modo avançado ativado." if enabled else "Modo avançado desativado."
396
- logging.info(f"[MODO AVANÇADO] {'Ativado' if enabled else 'Desativado'}")
397
- return message
398
-
399
- def get_history(self) -> list:
400
- """
401
- Retorna histórico de conversas
402
-
403
- Returns:
404
- Lista com histórico
405
- """
406
- return self.cache_manager.get_history()
407
-
408
- def clear_cache(self):
409
- """Limpa cache do sistema"""
410
- self.cache_manager.clear_cache()
411
- logging.info("Cache limpo")
412
-
413
- async def get_system_info(self) -> Dict[str, Any]:
414
- """
415
- Obtém informações do sistema
416
-
417
- Returns:
418
- Informações do sistema
419
- """
420
- state = {
421
- "engine": self.engine,
422
- "sql_agent": self.sql_agent,
423
- "cache_manager": self.cache_manager
424
- }
425
-
426
- result = await self.custom_node_manager.execute_node("system_info", state)
427
- return result.get("system_info", {})
428
-
429
- async def validate_system(self) -> Dict[str, Any]:
430
- """
431
- Valida o estado do sistema
432
-
433
- Returns:
434
- Resultado da validação
435
- """
436
- state = {
437
- "engine": self.engine,
438
- "sql_agent": self.sql_agent,
439
- "cache_manager": self.cache_manager
440
- }
441
-
442
- result = await self.custom_node_manager.execute_node("system_validation", state)
443
- return result.get("validation", {})
444
-
445
- def _smart_type_conversion(self, df, sql_types):
446
- """
447
- Conversão inteligente de tipos de dados com suporte a formatos brasileiros
448
- """
449
- import re
450
-
451
- logging.info("[TYPE_CONVERSION] 🔧 Iniciando conversão inteligente de tipos")
452
-
453
- for col in df.columns:
454
- col_data = df[col].dropna() # Remove NaN para análise
455
-
456
- if len(col_data) == 0:
457
- continue
458
-
459
- # Amostra para análise (primeiros 100 valores não-nulos)
460
- sample = col_data.head(100).astype(str)
461
-
462
- logging.debug(f"[TYPE_CONVERSION] 📊 Analisando coluna: {col}")
463
-
464
- # 1. DETECTAR DATAS
465
- if self._is_date_column(sample):
466
- try:
467
- df[col] = self._convert_to_date(df[col])
468
- sql_types[col] = DateTime
469
- logging.debug(f"[TYPE_CONVERSION] ✅ {col} → DATETIME")
470
- continue
471
- except Exception as e:
472
- logging.warning(f"[TYPE_CONVERSION] ⚠️ Falha ao converter {col} para data: {e}")
473
-
474
- # 2. DETECTAR NÚMEROS INTEIROS (PRIORIDADE ALTA)
475
- if self._is_integer_column(sample):
476
- try:
477
- # Converter removendo caracteres não numéricos, mas mantendo negativos
478
- def clean_integer(value):
479
- if pd.isna(value):
480
- return None
481
- value_str = str(value).strip()
482
- # Manter apenas dígitos e sinal negativo
483
- clean_value = ''.join(c for c in value_str if c.isdigit() or c == '-')
484
- if clean_value and clean_value != '-':
485
- return int(clean_value)
486
- return None
487
-
488
- df[col] = df[col].apply(clean_integer).astype('Int64')
489
- sql_types[col] = Integer
490
- logging.debug(f"[TYPE_CONVERSION] ✅ {col} → INTEGER")
491
- continue
492
- except Exception as e:
493
- logging.warning(f"[TYPE_CONVERSION] ⚠️ Falha ao converter {col} para inteiro: {e}")
494
-
495
- # 3. DETECTAR VALORES MONETÁRIOS
496
- if self._is_monetary_column(sample):
497
- try:
498
- df[col] = self._convert_to_monetary(df[col])
499
- sql_types[col] = Float
500
- logging.debug(f"[TYPE_CONVERSION] ✅ {col} → FLOAT (monetário)")
501
- continue
502
- except Exception as e:
503
- logging.warning(f"[TYPE_CONVERSION] ⚠️ Falha ao converter {col} para monetário: {e}")
504
-
505
- # 4. DETECTAR NÚMEROS DECIMAIS
506
- if self._is_float_column(sample):
507
- try:
508
- df[col] = self._convert_to_float(df[col])
509
- sql_types[col] = Float
510
- logging.debug(f"[TYPE_CONVERSION] ✅ {col} → FLOAT")
511
- continue
512
- except Exception as e:
513
- logging.warning(f"[TYPE_CONVERSION] ⚠️ Falha ao converter {col} para float: {e}")
514
-
515
- # 5. MANTER COMO TEXTO (padrão)
516
- logging.debug(f"[TYPE_CONVERSION] 📝 {col} → TEXT (padrão)")
517
-
518
- # Resumo da conversão
519
- type_summary = {}
520
- for col, sql_type in sql_types.items():
521
- type_name = sql_type.__name__ if hasattr(sql_type, '__name__') else str(sql_type).split('.')[-1].replace('>', '')
522
- if type_name not in type_summary:
523
- type_summary[type_name] = 0
524
- type_summary[type_name] += 1
525
-
526
- summary_text = ", ".join([f"{count} {type_name}" for type_name, count in type_summary.items()])
527
- logging.info(f"[TYPE_CONVERSION] ✅ Conversão concluída: {summary_text}")
528
- return df
529
-
530
- def _is_date_column(self, sample):
531
- """Detecta se uma coluna contém datas BASEADO APENAS NOS VALORES"""
532
- import re
533
-
534
- # Padrões de data brasileiros e internacionais
535
- date_patterns = [
536
- r'^\d{1,2}[\/\-\.]\d{1,2}[\/\-\.]\d{4}$', # DD/MM/YYYY ou DD-MM-YYYY
537
- r'^\d{4}[\/\-\.]\d{1,2}[\/\-\.]\d{1,2}$', # YYYY/MM/DD ou YYYY-MM-DD
538
- r'^\d{1,2}[\/\-\.]\d{1,2}[\/\-\.]\d{2}$', # DD/MM/YY
539
- ]
540
-
541
- # Verificar se pelo menos 70% dos valores seguem padrão de data
542
- date_count = 0
543
- for value in sample:
544
- if pd.isna(value) or value == '':
545
- continue
546
- for pattern in date_patterns:
547
- if re.match(pattern, str(value).strip()):
548
- date_count += 1
549
- break
550
-
551
- return date_count / len(sample) >= 0.7
552
-
553
- def _is_monetary_column(self, sample):
554
- """Detecta se uma coluna contém valores monetários BASEADO APENAS NOS VALORES"""
555
- import re
556
-
557
- # Padrões monetários brasileiros e internacionais
558
- money_patterns = [
559
- r'^R\$\s*\d+[,\.]\d{2}$', # R$ 10,50 ou R$ 10.50
560
- r'^\d+[,\.]\d{2}$', # 10,50 ou 10.50
561
- r'^R\$\s*\d+$', # R$ 10
562
- r'^\$\s*\d+[,\.]\d{2}$', # $ 10.50
563
- r'^\$\s*\d+$', # $ 10
564
- ]
565
-
566
- # Verificar se pelo menos 60% dos valores seguem padrão monetário
567
- money_count = 0
568
- for value in sample:
569
- if pd.isna(value) or value == '':
570
- continue
571
- value_str = str(value).strip()
572
- for pattern in money_patterns:
573
- if re.match(pattern, value_str):
574
- money_count += 1
575
- break
576
-
577
- return money_count / len(sample) >= 0.6
578
-
579
- def _is_integer_column(self, sample):
580
- """Detecta se uma coluna contém números inteiros"""
581
- try:
582
- # Primeiro, verificar se há vírgulas ou pontos decimais nos valores
583
- has_decimal_separators = False
584
- valid_numeric_count = 0
585
- integer_count = 0
586
-
587
- for value in sample:
588
- if pd.isna(value) or value == '':
589
- continue
590
-
591
- value_str = str(value).strip()
592
-
593
- # Se contém vírgula ou ponto seguido de dígitos, é decimal
594
- if (',' in value_str and any(c.isdigit() for c in value_str.split(',')[-1])) or \
595
- ('.' in value_str and any(c.isdigit() for c in value_str.split('.')[-1])):
596
- has_decimal_separators = True
597
- break
598
-
599
- # Tentar converter para número
600
- try:
601
- # Remover espaços e caracteres não numéricos (exceto - para negativos)
602
- clean_value = ''.join(c for c in value_str if c.isdigit() or c == '-')
603
- if clean_value and clean_value != '-':
604
- num_value = int(clean_value)
605
- valid_numeric_count += 1
606
- integer_count += 1
607
- except:
608
- # Se não conseguir converter para int, tentar float
609
- try:
610
- float_value = float(value_str)
611
- valid_numeric_count += 1
612
- # Se o float é igual ao int, conta como inteiro
613
- if float_value == int(float_value):
614
- integer_count += 1
615
- except:
616
- continue
617
-
618
- # Se encontrou separadores decimais, não é coluna de inteiros
619
- if has_decimal_separators:
620
- return False
621
-
622
- # Verificar se pelo menos 80% são números válidos
623
- if valid_numeric_count == 0 or valid_numeric_count / len(sample) < 0.8:
624
- return False
625
-
626
- # Verificar se pelo menos 95% dos números válidos são inteiros
627
- return integer_count / valid_numeric_count >= 0.95
628
-
629
- except Exception as e:
630
- logging.debug(f"Erro na detecção de inteiros: {e}")
631
- return False
632
-
633
- def _is_float_column(self, sample):
634
- """Detecta se uma coluna contém números decimais (com vírgula ou ponto)"""
635
- try:
636
- has_decimal_values = False
637
- valid_numeric_count = 0
638
-
639
- for value in sample:
640
- if pd.isna(value) or value == '':
641
- continue
642
-
643
- value_str = str(value).strip()
644
-
645
- # Verificar se contém separadores decimais com dígitos após
646
- if (',' in value_str and any(c.isdigit() for c in value_str.split(',')[-1])) or \
647
- ('.' in value_str and any(c.isdigit() for c in value_str.split('.')[-1])):
648
- has_decimal_values = True
649
-
650
- # Tentar converter para numérico (substituindo vírgula por ponto)
651
- try:
652
- clean_value = value_str.replace(',', '.')
653
- float(clean_value)
654
- valid_numeric_count += 1
655
- except:
656
- continue
657
-
658
- # Só é float se tem separadores decimais E pelo menos 80% são números válidos
659
- if not has_decimal_values:
660
- return False
661
-
662
- return valid_numeric_count / len(sample) >= 0.8
663
-
664
- except Exception as e:
665
- logging.debug(f"Erro na detecção de floats: {e}")
666
- return False
667
-
668
- def _convert_to_date(self, series):
669
- """Converte série para datetime com formatos brasileiros"""
670
- # Tentar diferentes formatos de data
671
- date_formats = [
672
- '%d/%m/%Y', # 31/12/2023
673
- '%d-%m-%Y', # 31-12-2023
674
- '%d.%m.%Y', # 31.12.2023
675
- '%Y-%m-%d', # 2023-12-31
676
- '%Y/%m/%d', # 2023/12/31
677
- '%d/%m/%y', # 31/12/23
678
- ]
679
-
680
- for fmt in date_formats:
681
- try:
682
- return pd.to_datetime(series, format=fmt, errors='raise')
683
- except:
684
- continue
685
-
686
- # Se nenhum formato específico funcionou, usar inferência automática
687
- try:
688
- return pd.to_datetime(series, dayfirst=True, errors='coerce')
689
- except:
690
- raise ValueError("Não foi possível converter para data")
691
-
692
- def _convert_to_monetary(self, series):
693
- """Converte série para valores monetários (float)"""
694
- def clean_monetary(value):
695
- if pd.isna(value):
696
- return None
697
-
698
- # Converter para string e limpar
699
- value_str = str(value).strip()
700
-
701
- # Remover símbolos monetários
702
- value_str = value_str.replace('R$', '').replace('$', '').strip()
703
-
704
- # Tratar formato brasileiro (vírgula como decimal)
705
- if ',' in value_str and '.' in value_str:
706
- # Formato: 1.234,56 → 1234.56
707
- value_str = value_str.replace('.', '').replace(',', '.')
708
- elif ',' in value_str:
709
- # Formato: 1234,56 → 1234.56
710
- value_str = value_str.replace(',', '.')
711
-
712
- try:
713
- return float(value_str)
714
- except:
715
- return None
716
-
717
- return series.apply(clean_monetary)
718
-
719
- def _convert_to_float(self, series):
720
- """Converte série para float com formato brasileiro"""
721
- def clean_float(value):
722
- if pd.isna(value):
723
- return None
724
-
725
- value_str = str(value).strip()
726
-
727
- # Tratar formato brasileiro
728
- if ',' in value_str:
729
- value_str = value_str.replace(',', '.')
730
-
731
- try:
732
- return float(value_str)
733
- except:
734
- return None
735
-
736
- return series.apply(clean_float)
737
-
738
- # Instância global do gerenciador
739
- _graph_manager: Optional[AgentGraphManager] = None
740
-
741
- def get_graph_manager() -> AgentGraphManager:
742
- """
743
- Retorna instância singleton do gerenciador de grafo
744
-
745
- Returns:
746
- AgentGraphManager
747
- """
748
- global _graph_manager
749
- if _graph_manager is None:
750
- _graph_manager = AgentGraphManager()
751
- return _graph_manager
752
-
753
- async def initialize_graph() -> AgentGraphManager:
754
- """
755
- Inicializa o grafo principal
756
-
757
- Returns:
758
- AgentGraphManager inicializado
759
- """
760
- try:
761
- manager = get_graph_manager()
762
-
763
- # Valida sistema
764
- validation = await manager.validate_system()
765
- if not validation.get("overall_valid", False):
766
- logging.warning("Sistema não passou na validação completa")
767
-
768
- logging.info("Grafo principal inicializado e validado")
769
- return manager
770
-
771
- except Exception as e:
772
- logging.error(f"Erro ao inicializar grafo: {e}")
773
- raise
774
-
775
- # Classe GraphManager removida - funcionalidade movida para AgentGraphManager