O Pipeline Real: Da Documentação à Implementação
A Arquitetura Completa: 5 Etapas Fundamentais
1. Upload do Edital (PDF/Imagem)
- O usuário faz upload do edital via dashboard web ou extensão Chrome
- O arquivo é enviado para a API Python via endpoint
/api/documents/upload
- Todo acesso é autenticado via Supabase JWT para garantir segurança
# Frontend: React Dropzone
const uploadEdital = async (file) => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/documents/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${supabaseToken}`
},
body: formData
});
return response.json();
};
# Backend: FastAPI endpoint
@app.post("/api/documents/upload")
async def upload_document(
file: UploadFile = File(...),
user_id: str = Depends(verify_jwt_token)
):
"""Recebe edital para análise"""
# Validações básicas
if not file.filename.endswith('.pdf'):
raise HTTPException(400, "Apenas PDFs aceitos")
# Salva arquivo temporariamente
file_path = f"temp/{user_id}_{file.filename}"
with open(file_path, "wb") as buffer:
buffer.write(await file.read())
# Envia para processamento assíncrono
task_id = await queue_processing(file_path, user_id)
return {"task_id": task_id, "status": "processing"}
2. Pré-processamento, Chunking e Embeddings
- O documento é convertido em texto (OCR se necessário)
- O texto é dividido em "chunks" (partes menores) para facilitar a análise sem perder contexto
- Cada chunk recebe um embedding vetorial semântico
- Os embeddings são armazenados para busca vetorial (RAG)
import cv2
import easyocr
from pdf2image import convert_from_path
import spacy
import openai
async def preprocess_document(file_path):
"""Pipeline completo de pré-processamento"""
# 1. Conversão e OCR (se necessário)
if file_path.endswith('.pdf'):
# Tentar extrair texto diretamente
text = extract_text_from_pdf(file_path)
# Se pouco texto, usar OCR
if len(text.strip()) < 100:
images = convert_from_path(file_path)
text = ""
for image in images:
# Melhorar qualidade da imagem
img_array = np.array(image)
img_gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
# CLAHE para melhorar contraste
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
img_enhanced = clahe.apply(img_gray)
# OCR com EasyOCR
reader = easyocr.Reader(['pt'])
results = reader.readtext(img_enhanced)
page_text = " ".join([item[1] for item in results])
text += page_text + "\n"
# 2. Chunking inteligente com spaCy
nlp = spacy.load("pt_core_news_sm")
doc = nlp(text)
chunks = []
current_chunk = ""
max_chunk_size = 1000 # tokens
for sent in doc.sents:
if len(current_chunk) + len(sent.text) > max_chunk_size:
if current_chunk:
chunks.append(current_chunk.strip())
current_chunk = sent.text
else:
current_chunk += " " + sent.text
if current_chunk:
chunks.append(current_chunk.strip())
# 3. Geração de embeddings
embeddings = []
for i, chunk in enumerate(chunks):
response = await openai.Embedding.acreate(
model="text-embedding-3-small",
input=chunk
)
embeddings.append({
'chunk_id': i,
'text': chunk,
'embedding': response['data'][0]['embedding']
})
return embeddings
3. Geração de Prompt + Contexto (RAG)
- Para cada questão do checklist, o sistema busca os chunks mais relevantes via similaridade de cosseno
- O prompt é montado com contexto extraído do edital + a pergunta específica
- Usa templates customizados para análise imobiliária
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
async def rag_retrieval(question, embeddings, top_k=5):
"""Sistema RAG - busca contextual inteligente"""
# 1. Gerar embedding da pergunta
question_response = await openai.Embedding.acreate(
model="text-embedding-3-small",
input=question
)
question_embedding = question_response['data'][0]['embedding']
# 2. Calcular similaridade com todos os chunks
chunk_embeddings = [item['embedding'] for item in embeddings]
similarities = cosine_similarity(
[question_embedding],
chunk_embeddings
)[0]
# 3. Pegar os top_k chunks mais relevantes
top_indices = np.argsort(similarities)[-top_k:][::-1]
relevant_chunks = [embeddings[i] for i in top_indices]
# 4. Montar contexto consolidado
context = "\n\n".join([
f"TRECHO {i+1}:\n{chunk['text']}"
for i, chunk in enumerate(relevant_chunks)
])
return context
async def generate_prompt(question, context):
"""Template de prompt otimizado para editais"""
prompt_template = f"""
Você é um especialista em análise de editais imobiliários da Caixa Econômica Federal.
CONTEXTO RELEVANTE DO EDITAL:
{context}
PERGUNTA ESPECÍFICA:
{question}
INSTRUÇÕES:
1. Analise APENAS as informações do contexto fornecido
2. Responda de forma objetiva e precisa
3. Se a informação não estiver clara no contexto, mencione isso
4. Cite trechos específicos quando possível
RESPOSTA:
"""
return prompt_template
4. Processamento com LLM (OpenAI GPT)
- O prompt é enviado para o LLM via API
- O modelo responde com a resposta estruturada, grau de confiança e, se possível, fonte textual
- O processo é repetido para cada item do checklist
import openai
import json
from typing import Dict, Any
async def process_with_llm(prompt: str, question: str) -> Dict[str, Any]:
"""Processamento com GPT-4.1-nano"""
try:
response = await openai.ChatCompletion.acreate(
model="gpt-4.1-nano", # Modelo configurado
messages=[
{"role": "system", "content": "Você é um especialista em editais da Caixa Econômica Federal."},
{"role": "user", "content": prompt}
],
temperature=0.1, # Baixa temperatura para respostas mais consistentes
max_tokens=500
)
raw_response = response.choices[0].message.content
# Estruturar resposta
analysis_result = {
"question": question,
"answer": raw_response,
"confidence": extract_confidence_score(raw_response),
"source_quotes": extract_quotes(raw_response),
"timestamp": datetime.now().isoformat(),
"model_used": "gpt-4.1-nano"
}
return analysis_result
except Exception as e:
return {
"question": question,
"answer": "Erro no processamento",
"confidence": 0,
"error": str(e),
"timestamp": datetime.now().isoformat()
}
async def process_full_checklist(embeddings, checklist_questions):
"""Processa checklist completo"""
results = []
for question in checklist_questions:
# 1. RAG - buscar contexto relevante
context = await rag_retrieval(question, embeddings)
# 2. Gerar prompt estruturado
prompt = await generate_prompt(question, context)
# 3. Processar com LLM
result = await process_with_llm(prompt, question)
results.append(result)
# Delay para evitar rate limits
await asyncio.sleep(0.5)
return results
5. Resultado Estruturado para o Usuário
- O backend agrega todas as respostas, calcula métricas (confiança média, tempo de processamento, etc.)
- O resultado é enviado para o frontend, que exibe:
- Checklist respondido
- Confiança de cada resposta
- Principais descobertas
- Relatório exportável
import asyncio
from datetime import datetime
from supabase import create_client
async def aggregate_and_deliver_results(analysis_results, user_id, document_id):
"""Agrega resultados e entrega para o usuário"""
# 1. Calcular métricas consolidadas
total_questions = len(analysis_results)
high_confidence_answers = len([r for r in analysis_results if r.get('confidence', 0) > 80])
avg_confidence = sum([r.get('confidence', 0) for r in analysis_results]) / total_questions
# 2. Identificar principais descobertas
key_findings = []
risk_flags = []
for result in analysis_results:
if any(keyword in result['answer'].lower() for keyword in ['débito', 'pendência', 'ação judicial']):
risk_flags.append(result)
if result.get('confidence', 0) > 90:
key_findings.append(result)
# 3. Estruturar resultado final
final_result = {
"document_id": document_id,
"user_id": user_id,
"analysis_completed_at": datetime.now().isoformat(),
"summary": {
"total_questions": total_questions,
"high_confidence_answers": high_confidence_answers,
"average_confidence": round(avg_confidence, 1),
"processing_time": "32 segundos" # calculado dinamicamente
},
"checklist": analysis_results,
"key_findings": key_findings[:5], # Top 5
"risk_flags": risk_flags,
"export_ready": True
}
# 4. Salvar no Supabase para histórico
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
await supabase.table('analysis_results').insert(final_result).execute()
# 5. Enviar para frontend via WebSocket ou polling
await notify_frontend(user_id, final_result)
return final_result
async def generate_exportable_report(final_result):
"""Gera relatório exportável (PDF/Excel)"""
report_data = {
"title": f"Análise de Edital - {final_result['document_id'][:8]}",
"summary": final_result['summary'],
"detailed_analysis": final_result['checklist'],
"recommendations": generate_recommendations(final_result['risk_flags']),
"confidence_chart": generate_confidence_visualization(final_result['checklist'])
}
# Gerar PDF e Excel para download
pdf_path = await generate_pdf_report(report_data)
excel_path = await generate_excel_report(report_data)
return {
"pdf_download_url": pdf_path,
"excel_download_url": excel_path
}
Exemplo Real: "O arrematante assume débitos condominiais?"
Pergunta: "O arrematante assume débitos condominiais?"
ETAPA 1 - Upload: PDF de 23 páginas recebido
ETAPA 2 - Pré-processamento:
- OCR não necessário (PDF digital)
- Texto extraído: 15.847 palavras
- Chunks gerados: 28 chunks semânticos
- Embeddings: 28 vetores de 1536 dimensões
ETAPA 3 - RAG Retrieval:
Top 5 chunks mais relevantes (similaridade > 0.85):
1. "Seção 8.3 - O arrematante assumirá os débitos condominiais..."
2. "São de responsabilidade do arrematante todas as taxas..."
3. "O valor dos débitos condominiais poderá ser consultado..."
4. "Não há limitação do valor dos débitos condominiais..."
5. "O arrematante deverá quitar os débitos antes da escritura..."
ETAPA 4 - LLM Processing:
Prompt enviado para GPT-4.1-nano com contexto consolidado
ETAPA 5 - Resultado:
{
"question": "O arrematante assume débitos condominiais?",
"answer": "SIM. Conforme cláusula 8.3 do edital, o arrematante assumirá os débitos condominiais vencidos e vincendos. O valor pode ser consultado na administradora do condomínio e não há limitação de valor.",
"confidence": 94,
"source_quotes": [
"O arrematante assumirá os débitos condominiais vencidos e vincendos",
"São de responsabilidade do arrematante todas as taxas condominiais"
],
"risk_level": "ALTO",
"recommendations": [
"Consultar valor exato na administradora antes do leilão",
"Considerar débito no cálculo do lance máximo"
]
}
Stack Tecnológica Completa
📁 ETAPA 1 - Upload do Edital (PDF)
├── React Dropzone (interface de upload)
├── FastAPI (backend Python)
└── Supabase Auth (autenticação JWT)
🔧 ETAPA 2 - Pré-processamento, Chunking e Embeddings
├── EasyOCR (reconhecimento óptico)
├── OpenCV (processamento de imagem)
├── PyPDF2 + pdf2image (manipulação PDF)
├── spaCy (processamento de linguagem)
├── NLTK (toolkit de NLP)
└── OpenAI API (text-embedding-3-small)
🔍 ETAPA 3 - Geração de Prompt + Contexto (RAG)
├── Faiss (busca vetorial eficiente)
├── Numpy (computação numérica)
├── Similaridade de Cosseno (algoritmo de busca)
└── Templates customizados (estrutura de prompts)
🤖 ETAPA 4 - Processamento com LLM (OpenAI GPT)
├── OpenAI GPT-4.1-nano (modelo de IA)
└── FastAPI (gerenciamento de requisições)
📊 ETAPA 5 - Resultado Estruturado para o Usuário
├── FastAPI (consolidação de dados)
├── Supabase (persistência e histórico)
└── Next.js/React (interface do usuário)
Observações Importantes da Arquitetura
- 🔄 Processamento é assíncrono: O backend processa em background e o frontend faz polling do status
- 📈 Pipeline é escalável: Chunking e embeddings permitem paralelismo e análise de documentos grandes
- 🎯 RAG aumenta precisão: Busca vetorial garante que o LLM sempre recebe contexto relevante
- 🔐 Segurança: Todo acesso é autenticado via Supabase JWT
- 📊 Logs e histórico: Todas as análises são salvas para auditoria e reuso
flowchart LR
A[Upload do Edital] --> B[Pré-processamento]
B --> C[Geração de Prompt + Contexto]
C --> D[LLM OpenAI GPT]
D --> E[Resultado Estruturado]
A Reflexão Final
- React + FastAPI: Interface e backend confiáveis
- EasyOCR + OpenCV: Extração precisa de texto, mesmo de PDFs ruins
- spaCy + NLTK: Processamento inteligente de linguagem natural
- OpenAI Embeddings: Vetorização semântica de alta qualidade
- RAG + Similaridade de Cosseno: Busca contextual inteligente
- GPT-4.1-nano: Análise especializada e estruturada
- Supabase: Persistência, autenticação e histórico
Próximo capítulo: como transformar toda essa potência tecnológica em uma interface que os usuários realmente amam usar! Stay tuned! 🎨
Dúvidas sobre a implementação ou quer saber mais detalhes técnicos? Vamos conversar!