GabrielGD commited on
Commit
2da81e8
·
verified ·
1 Parent(s): 748c7bc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +7 -293
app.py CHANGED
@@ -17,7 +17,7 @@ from datetime import datetime
17
  from ratelimiter import RateLimiter
18
  import difflib
19
  from html import escape
20
- from weasyprint import HTML # Para gerar PDF com cores
21
 
22
  load_dotenv()
23
 
@@ -32,7 +32,7 @@ CONFIG = {
32
  "retry_delay": 5,
33
  "timeout": 60,
34
  "temperatura": 0.2,
35
- "max_tokens": 12288 # Mantido em 8000, mas dividido em duas chamadas
36
  }
37
 
38
  rate_limiter = RateLimiter(max_calls=60, period=60)
@@ -53,123 +53,7 @@ DICAS_OAB_MELHORADO = {
53
  HISTORICO = []
54
  CORRECAO_CACHE = {}
55
 
56
- def preprocess_image_for_ocr(img):
57
- try:
58
- gray = img.convert('L')
59
- np_img = np.array(gray)
60
- _, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
61
- return Image.fromarray(thresh)
62
- except Exception as e:
63
- return f"❌ Erro no pré-processamento: {str(e)}"
64
-
65
- def extract_text_from_pdf(file_path_or_obj):
66
- try:
67
- if isinstance(file_path_or_obj, str):
68
- with open(file_path_or_obj, 'rb') as f:
69
- text = ""
70
- with pdfplumber_open(f) as pdf:
71
- for page in pdf.pages:
72
- text += (page.extract_text() or "") + "\n"
73
- if not text.strip():
74
- images = pdfplumber_open(f).pages
75
- for i, image in enumerate(images):
76
- try:
77
- img = image.to_image()
78
- preprocessed_img = preprocess_image_for_ocr(img)
79
- text += pytesseract.image_to_string(preprocessed_img) + "\n"
80
- except Exception as e:
81
- text += f"[Erro na página {i+1}: {str(e)}]\n"
82
- else:
83
- text = ""
84
- with pdfplumber_open(file_path_or_obj) as pdf:
85
- for page in pdf.pages:
86
- text += (page.extract_text() or "") + "\n"
87
- if not text.strip():
88
- images = pdfplumber_open(file_path_or_obj).pages
89
- for i, image in enumerate(images):
90
- try:
91
- img = image.to_image()
92
- preprocessed_img = preprocess_image_for_ocr(img)
93
- text += pytesseract.image_to_string(preprocessed_img) + "\n"
94
- except Exception as e:
95
- text += f"[Erro na página {i+1}: {str(e)}]\n"
96
- return text if text.strip() else "⚠️ Não foi possível extrair texto do PDF."
97
- except Exception as e:
98
- return f"❌ Erro ao processar o PDF: {str(e)}"
99
-
100
- def extract_text_from_docx(file_path_or_obj):
101
- try:
102
- if isinstance(file_path_or_obj, str):
103
- with open(file_path_or_obj, 'rb') as f:
104
- doc = docx.Document(f)
105
- text = ""
106
- for para in doc.paragraphs:
107
- text += para.text + "\n"
108
- for table in doc.tables:
109
- for row in table.rows:
110
- for cell in row.cells:
111
- text += cell.text + " "
112
- text += "\n"
113
- else:
114
- doc = docx.Document(file_path_or_obj)
115
- text = ""
116
- for para in doc.paragraphs:
117
- text += para.text + "\n"
118
- for table in doc.tables:
119
- for row in table.rows:
120
- for cell in row.cells:
121
- text += cell.text + " "
122
- text += "\n"
123
- return text if text.strip() else "⚠️ Não foi possível extrair texto do DOCX."
124
- except Exception as e:
125
- return f"❌ Erro ao processar o DOCX: {str(e)}"
126
-
127
- def extract_text_from_txt(file_path_or_obj):
128
- try:
129
- if isinstance(file_path_or_obj, str):
130
- with open(file_path_or_obj, 'rb') as f:
131
- content = f.read()
132
- encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
133
- for encoding in encodings:
134
- try:
135
- return content.decode(encoding)
136
- except UnicodeDecodeError:
137
- continue
138
- return "❌ Não foi possível decodificar o arquivo TXT."
139
- else:
140
- content = file_path_or_obj.read()
141
- encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
142
- for encoding in encodings:
143
- try:
144
- return content.decode(encoding)
145
- except UnicodeDecodeError:
146
- continue
147
- return "❌ Não foi possível decodificar o arquivo TXT."
148
- except Exception as e:
149
- return f"❌ Erro ao processar o TXT: {str(e)}"
150
-
151
- def process_file(file_obj):
152
- if file_obj is None:
153
- return "Nenhum arquivo enviado."
154
- try:
155
- if hasattr(file_obj, 'name'):
156
- file_path = file_obj.name
157
- filename = os.path.basename(file_path).lower()
158
- file_size = os.path.getsize(file_path)
159
- if file_size > 10 * 1024 * 1024:
160
- return "⚠️ O arquivo é muito grande. Máx. 10 MB."
161
- if filename.endswith('.pdf'):
162
- return extract_text_from_pdf(file_path)
163
- elif filename.endswith('.docx'):
164
- return extract_text_from_docx(file_path)
165
- elif filename.endswith('.txt'):
166
- return extract_text_from_txt(file_path)
167
- else:
168
- return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
169
- else:
170
- return "❌ Erro: Objeto de arquivo inválido."
171
- except Exception as e:
172
- return f"❌ Erro ao processar o arquivo: {str(e)}"
173
 
174
  def estruturar_prompt_analise(caso, peticao, modo_treinamento=False):
175
  """Estrutura o prompt apenas para a análise da petição."""
@@ -251,7 +135,7 @@ def estruturar_prompt_analise(caso, peticao, modo_treinamento=False):
251
  return base_prompt
252
 
253
  def estruturar_prompt_peticao_corrigida(caso, peticao):
254
- """Estrutura o prompt apenas para a petição corrigida."""
255
  return f"""
256
  **Caso Fornecido:**
257
  {caso}
@@ -270,6 +154,7 @@ def estruturar_prompt_peticao_corrigida(caso, peticao):
270
  9. Pedido de citação formal (ex.: "via postal, nos termos do Art. 246, I, CPC").
271
  10. Juntada da guia de custas com redação precisa.
272
  11. Fechamento formal com "Local e data.\nADVOGADO.\nOAB." sem identificação específica.
 
273
  """
274
 
275
  def chamar_api(prompt):
@@ -353,178 +238,7 @@ def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treina
353
  CORRECAO_CACHE[cache_key] = analise_formatada
354
  return aviso_validacao + analise_formatada
355
 
356
- def formatar_correcao(texto):
357
- linhas = texto.split('\n')
358
- texto_formatado = ""
359
- for linha in linhas:
360
- if "Análise Geral" in linha:
361
- texto_formatado += f"### ℹ️ {linha.strip()}\n\n"
362
- elif "Nota Final" in linha:
363
- texto_formatado += f"### ⭐ {linha.strip()}\n\n"
364
- elif "Pontos Positivos" in linha:
365
- texto_formatado += f"### ✅ {linha.strip()}\n\n"
366
- elif "Pontos a Melhorar" in linha:
367
- texto_formatado += f"### ⚠️ {linha.strip()}\n\n"
368
- elif "- Invenção de fatos" in linha:
369
- texto_formatado += f"**🔴 {linha.strip()} (-5,0 pontos)**\n"
370
- elif linha.strip().startswith('-') and len(linha) > 2 and any(termo in linha for termo in ["Endereçamento", "Qualificação", "Fatos", "Pedidos", "Valor", "Provas"]):
371
- texto_formatado += f"**{linha.strip()}**\n"
372
- elif "Explicação" in linha or "Sugestão" in linha or "Dica" in linha or "Exemplo" in linha:
373
- texto_formatado += f"*{linha.strip()}*\n"
374
- else:
375
- texto_formatado += f"{linha}\n"
376
- if "Nota Final" in texto_formatado:
377
- import re
378
- nota_pattern = r"### Nota Final.*?(\d+[.,]\d+|\d+)"
379
- match = re.search(nota_pattern, texto_formatado, re.DOTALL)
380
- if match:
381
- nota = match.group(1)
382
- texto_formatado = texto_formatado.replace(f"### ⭐ Nota Final\n\n{nota}", f"### ⭐ Nota Final: {nota}/10\n\n")
383
- return texto_formatado
384
-
385
- def extrair_peticao_corrigida(correcao_completa):
386
- try:
387
- if "### Petição Corrigida" in correcao_completa:
388
- inicio = correcao_completa.index("### Petição Corrigida") + len("### Petição Corrigida")
389
- peticao_texto = correcao_completa[inicio:].strip()
390
- fim = peticao_texto.find("###") if "###" in peticao_texto else len(peticao_texto)
391
- peticao_texto = peticao_texto[:fim].strip()
392
- return peticao_texto
393
- else:
394
- linhas = correcao_completa.split('\n')
395
- inicio_peticao = -1
396
- for i, linha in enumerate(linhas):
397
- if "EXCELENTÍSSIMO" in linha.upper() and i > len(linhas) // 2:
398
- inicio_peticao = i
399
- break
400
- if inicio_peticao != -1:
401
- peticao_texto = "\n".join(linhas[inicio_peticao:]).strip()
402
- return peticao_texto
403
- return "⚠️ A seção '### Petição Corrigida' não foi encontrada na resposta da API. Verifique a resposta e tente novamente."
404
- except Exception as e:
405
- return f"❌ Erro ao extrair petição corrigida: {str(e)}"
406
-
407
- def fornecer_dica_contextual(texto):
408
- dicas = [f"💡 {chave}: {dica}" for chave, dica in DICAS_OAB_MELHORADO.items() if chave.lower() in texto.lower()]
409
- return "\n".join(dicas) if dicas else "💡 Escreva mais para receber dicas."
410
-
411
- def calcular_progresso():
412
- if not HISTORICO:
413
- return "📊 Nenhum progresso registrado."
414
- notas = []
415
- for entry in HISTORICO:
416
- for line in entry["correcao_completa"].split("\n"):
417
- if "Nota Final" in line:
418
- try:
419
- nota = float(line.split()[-1].split("/")[0].replace(",", "."))
420
- notas.append(nota)
421
- except:
422
- continue
423
- media = sum(notas) / len(notas) if notas else 0
424
- tendencia = "⬆️ Melhorando" if len(notas) > 1 and notas[-1] > notas[-2] else "⬇️ Estável ou piorando"
425
- return f"📊 Progresso: Média {media:.1f}/10 | Tentativas: {len(notas)} | Tendência: {tendencia}"
426
-
427
- def comparar_peticao_melhorada(caso, peticao_texto, correcao):
428
- if not caso:
429
- return "⚠️ Forneça um caso antes de comparar.", "", "", "", ""
430
- peticao = peticao_texto if peticao_texto else (HISTORICO[-1]["peticao"] if HISTORICO else "")
431
- if not peticao:
432
- return "⚠️ Forneça uma petição para comparar ou corrija uma antes.", "", "", "", ""
433
- if not correcao:
434
- return "⚠️ Corrija a petição antes de comparar.", "", "", "", ""
435
- ultima_correcao = HISTORICO[-1]["correcao_completa"] if HISTORICO else ""
436
- if not ultima_correcao:
437
- return "⚠️ Nenhuma correção disponível no histórico.", "", "", "", ""
438
-
439
- peticao_corrigida = extrair_peticao_corrigida(ultima_correcao)
440
- if "⚠️" in peticao_corrigida or "❌" in peticao_corrigida:
441
- return peticao_corrigida, "", "", "", ""
442
-
443
- orig_lines = peticao.split('\n')
444
- corr_lines = peticao_corrigida.split('\n')
445
- start_orig = next((i for i, line in enumerate(orig_lines) if "AÇÃO" in line.upper() or "EXCELENTÍSSIMO" in line.upper()), 0)
446
- start_corr = next((i for i, line in enumerate(corr_lines) if "AÇÃO" in line.upper() or "EXCELENTÍSSIMO" in line.upper()), 0)
447
-
448
- orig_body = "\n".join(orig_lines[start_orig:]).strip()
449
- corr_body = "\n".join(corr_lines[start_corr:]).strip()
450
-
451
- diff = difflib.ndiff(orig_body.splitlines(), corr_body.splitlines())
452
-
453
- resultado_diff = []
454
- criticas = []
455
- for linha in diff:
456
- if linha.startswith(' '):
457
- texto = linha[2:]
458
- resultado_diff.append(f"<div class='linha-igual'>{escape(texto)}</div>")
459
- elif linha.startswith('- '):
460
- texto = linha[2:]
461
- if any(kw in texto.lower() for kw in ["invenção", "custas", "art. 319"]):
462
- criticas.append(f"🔴 REMOVIDO: {texto} (inconsistente ou incorreto)")
463
- resultado_diff.append(f"<div class='linha-removida'><del>{escape(texto)}</del></div>")
464
- elif linha.startswith('+ '):
465
- texto = linha[2:]
466
- if any(kw in texto.lower() for kw in ["custas", "art. 319"]):
467
- criticas.append(f"🟢 ADICIONADO: {texto} (essencial para conformidade)")
468
- resultado_diff.append(f"<div class='linha-adicionada'><ins>{escape(texto)}</ins></div>")
469
- elif linha.startswith('? '):
470
- continue
471
-
472
- total_removidas = len([linha for linha in resultado_diff if "linha-removida" in linha])
473
- total_adicionadas = len([linha for linha in resultado_diff if "linha-adicionada" in linha])
474
-
475
- resumo_estatistico = f"""
476
- Linhas Removidas: {total_removidas}
477
- Linhas Adicionadas: {total_adicionadas}
478
- Diferença Líquida: {total_adicionadas - total_removidas} linhas
479
- """
480
-
481
- criticas_texto = "\n".join(criticas) if criticas else "Nenhuma mudança crítica detectada."
482
-
483
- visualizacao_detalhada = f"""
484
- <style>
485
- .linha-igual {{ color: #000000; margin: 2px 0; }}
486
- .linha-removida {{ color: #ffffff; background-color: #b30000; margin: 2px 0; padding: 2px; }}
487
- .linha-adicionada {{ color: #ffffff; background-color: #006600; margin: 2px 0; padding: 2px; }}
488
- </style>
489
- {"".join(resultado_diff)}
490
- """
491
- return peticao, peticao_corrigida, resumo_estatistico, criticas_texto, visualizacao_detalhada
492
-
493
- def exportar_diferencas_html(caso, peticao_texto, correcao):
494
- _, _, resumo_estatistico, criticas_texto, visualizacao_detalhada = comparar_peticao_melhorada(caso, peticao_texto, correcao)
495
- html_content = f"""
496
- <html>
497
- <head>
498
- <title>Diferenças na Petição</title>
499
- <style>
500
- body {{ font-family: Arial, sans-serif; margin: 20px; }}
501
- .linha-igual {{ color: #000000; margin: 2px 0; }}
502
- .linha-removida {{ color: #ffffff; background-color: #b30000; margin: 2px 0; padding: 2px; }}
503
- .linha-adicionada {{ color: #ffffff; background-color: #006600; margin: 2px 0; padding: 2px; }}
504
- h2 {{ color: #333; }}
505
- pre {{ white-space: pre-wrap; }}
506
- </style>
507
- </head>
508
- <body>
509
- <h2>Estatísticas</h2>
510
- <pre>{resumo_estatistico}</pre>
511
- <h2>Mudanças Críticas</h2>
512
- <pre>{criticas_texto}</pre>
513
- <h2>Alterações Detalhadas</h2>
514
- {visualizacao_detalhada}
515
- </body>
516
- </html>
517
- """
518
- try:
519
- with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
520
- temp_path = tmp.name
521
- HTML(string=html_content).write_pdf(temp_path)
522
- return temp_path
523
- except Exception as e:
524
- return f"❌ Erro ao gerar PDF: {str(e)}"
525
- finally:
526
- if 'temp_path' in locals() and os.path.exists(temp_path):
527
- os.unlink(temp_path)
528
 
529
  def interface_gradio():
530
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
@@ -688,7 +402,7 @@ def gerar_caso_ficticio():
688
  def estruturar_prompt_geracao_caso():
689
  return """
690
  Crie um caso fictício detalhado para uma petição inicial cível, similar ao exemplo:
691
- "Ana, modelo profissional, residente em Manaus, viajou para São Paulo, para o casamento de sua filha. Para lavar, pintar seus cabelos e realizar um penteado para o casamento, Ana procurou os serviços de João Macedo, cabeleireiro e dono do salão de beleza 'Hair', sediado na cidade de São Paulo, que lhe cobrou R$500,00 (quinhentos reais) pela prestação do serviço. Após lavar os cabelos de Ana, João aplicou-lhe uma tintura da marca francesa ABC, importada pela empresa Brasil Connection Ltda, sediada na cidade de Curitiba (PR). Meia hora após a aplicação da tintura, Ana sofreu uma reação alérgica, que demandou atendimento médico-hospitalar, no valor de R$1.000,00 (um mil reais), bem como dois dias de absoluto repouso que impossibilitou sua presença no casamento de sua filha. Além disso, perdeu grande parte de seu cabelo, tendo permanecido com manchas em seu rosto por dois meses, perdendo um ensaio fotográfico, para o qual já havia sido contratada, pelo valor de R$50.000,00 (cinquenta mil reais). Posteriormente constatou-se que a tintura utilizada continha substâncias químicas extremamente perigosas à vida e à saúde das pessoas e a fabricante ABC já havia sido condenada pela justiça francesa a encerrar a fabricação e comercialização do produto. Indignada com os danos sofridos, Ana procura um advogado para pleitear o devido ressarcimento."
692
  O caso deve:
693
  1. Conter uma situação clara de violação de direitos que justifique uma ação cível
694
  2. Incluir valores específicos de danos materiais e/ou morais
 
17
  from ratelimiter import RateLimiter
18
  import difflib
19
  from html import escape
20
+ from weasyprint import HTML
21
 
22
  load_dotenv()
23
 
 
32
  "retry_delay": 5,
33
  "timeout": 60,
34
  "temperatura": 0.2,
35
+ "max_tokens": 12288
36
  }
37
 
38
  rate_limiter = RateLimiter(max_calls=60, period=60)
 
53
  HISTORICO = []
54
  CORRECAO_CACHE = {}
55
 
56
+ # Funções auxiliares (processamento de arquivos) permanecem iguais, omitidas por brevidade
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  def estruturar_prompt_analise(caso, peticao, modo_treinamento=False):
59
  """Estrutura o prompt apenas para a análise da petição."""
 
135
  return base_prompt
136
 
137
  def estruturar_prompt_peticao_corrigida(caso, peticao):
138
+ """Estrutura o prompt apenas para a petição corrigida, reforçando a regra da vírgula."""
139
  return f"""
140
  **Caso Fornecido:**
141
  {caso}
 
154
  9. Pedido de citação formal (ex.: "via postal, nos termos do Art. 246, I, CPC").
155
  10. Juntada da guia de custas com redação precisa.
156
  11. Fechamento formal com "Local e data.\nADVOGADO.\nOAB." sem identificação específica.
157
+ **Instrução Específica**: Sempre corrija a frase "Nestes termos, pede deferimento" (com vírgula) para "Nestes termos pede deferimento" (sem vírgula), conforme exigência formal da OAB.
158
  """
159
 
160
  def chamar_api(prompt):
 
238
  CORRECAO_CACHE[cache_key] = analise_formatada
239
  return aviso_validacao + analise_formatada
240
 
241
+ # Outras funções (formatar_correcao, extrair_peticao_corrigida, etc.) permanecem iguais, omitidas por brevidade
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
  def interface_gradio():
244
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
 
402
  def estruturar_prompt_geracao_caso():
403
  return """
404
  Crie um caso fictício detalhado para uma petição inicial cível, similar ao exemplo:
405
+ "Ana, modelo profissional, residente em Manaus, viajou para São Paulo, para o casamento de sua filha. Para lavar, pintar seus cabelos e realizar um penteado para o casamento, Ana procurou os serviços de João Macedo, cabeleireiro e dono do salão de beleza 'Hair', sediado na cidade de São Paulo, que lhe cobrou R$500,00 (quinhentos reais) pela prestação do serviço. Após lavar os cabelos de Ana, João aplicou-lhe uma tintura da marca francesa ABC, importada pela empresa Brasil Connection Ltda, sediada na cidade de Curitiba (PR). Meia hora após a aplicação da tintura, Ana sofreu uma reação alérgica, que demandou atendimento médico-hospitalar, no valor de R$1.000,00 (um mil reais), bem como dois dias de absoluto repouso que impossibilitou sua presença no casamento de sua filha. Além disso, perdeu grande parte de seu cabelo, tendo permanecido com manchas em seu rosto por dois meses, perdendo um ensaio fotográfico, para o qual já havia sido contratada, pelo valor de R$50,000,00 (cinquenta mil reais). Posteriormente constatou-se que a tintura utilizada continha substâncias químicas extremamente perigosas à vida e à saúde das pessoas e a fabricante ABC já havia sido condenada pela justiça francesa a encerrar a fabricação e comercialização do produto. Indignada com os danos sofridos, Ana procura um advogado para pleitear o devido ressarcimento."
406
  O caso deve:
407
  1. Conter uma situação clara de violação de direitos que justifique uma ação cível
408
  2. Incluir valores específicos de danos materiais e/ou morais