rvadv-jusia
Recebi sua solicitação e estou começando a trabalhar nela.
Vou analisar o projeto existente e adaptar o plano arquitetural para sua infraestrutura específica. Primeiro, preciso examinar os arquivos e informações disponíveis.
PROJETO LEXNOVA-RV: PLANO DE IMPLANTAÇÃO INTEGRADA AO SISTEMA RV-ADV
1. CONTEXTO E OBJETIVOS
1.1 Situação Atual do RV-Adv
O ecossistema RV-Adv constitui uma plataforma de gestão jurídica madura e em produção, operando atualmente no domínio rafaelavasconcelos.adv.br. A infraestrutura atual compreende uma arquitetura distribuída com componentes bem estabelecidos que representam a base ideal para extensão com funcionalidades de busca jurisprudencial inteligente.
A arquitetura existente já implementa componentes críticos para o módulo de jurisprudência, incluindo Edge Functions para scraping da TNU (scrape-tnu), chat RAG (chat-jurisprudencia), geração de embeddings (generate-embedding), proxy unificado de IA (ai-proxy), e integração com APIs judiciais (DataJud via datajud-bypass, DJEN via djen-bypass). O banco de dados Supabase já possui a extensão pgvector ativa com índices HNSW para busca vetorial, além de 57 migrações sequenciais estabelecendo um schema robusto para o domínio jurídico-previdenciário.
A infraestrutura de servidores inclui um VPS Hetzner CX33 (4 vCPU, 8GB RAM, 80GB SSD, Ubuntu 24.04) já configurado para o microsserviço de scraping local, representando uma base computacional adequada para expansionar a capacidade de crawl de tribunais. O frontend React/Vite encontra-se em deploy contínuo via Netlify, com roteamento SPA configurado e autenticação JWT integrada ao Supabase Auth.
1.2 Escopo do Módulo Jurisprudência
O módulo de jurisprudência a ser implementado proverá busca inteligente em linguagem natural sobre acórdãos dos tribunais STF, STJ, TRF-5 e TJ-CE, com capacidade de upload de petições para extração automática de temas e busca contextual. A funcionalidade estenderá o módulo Jurisprudencia já existente no frontend (JurisprudenciaPage.jsx) e aproveitará a infraestrutura de chat RAG já operacional para jurisprudência da TNU.
O objetivo central consiste em permitir que advogados e operadores jurídicos descrevam casos em linguagem natural e recebam acórdãos relevantes ordenados por similaridade semântica, acompanhados de classificação de relevância (muito relevante, relevante, pouco relevante) e links diretos às fontes oficiais para verificação. O módulo funcionará como fonte de contexto verificável para fundamentar peças processuais, eliminando o risco de alucinações jurisprudenciais.
2. ARQUITETURA INTEGRADA PROPOSTA
2.1 Visão de Alto Nível da Arquitetura Extendida
A arquitetura proposta estende o monólito modular existente adicionando três componentes principais: schema de banco de dados扩展 para jurisprudência multi-tribunal, Edge Functions para crawl e indexação dos tribunais-alvo, e interface de usuário расширенная para busca e análise de petições. O design preserva os padrões arquiteturais estabelecidos, garantindo coerência com a codebase existente.
┌─────────────────────────────────────────────────────────────────────────┐
│ FRONTEND RV-ADV (React/Vite/Netlify) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ JurisprudenciaPage.jsx (Expandida) │ │
│ │ ├─ BuscaNaturallPanel ─ Busca por linguagem natural │ │
│ │ ├─ PetitionUploadPanel ─ Upload e análise de petições │ │
│ │ ├─ ResultsGrid ─ Grid de resultados com relevância │ │
│ │ └─ ChatRAGPanel ─ Chat RAG multi-tribunal (estende TNU) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ SUPABASE BACKEND (BaaS) │
│ ┌───────────────────────┐ ┌────────────────────────────────────┐ │
│ │ Edge Functions │ │ PostgreSQL + pgvector │ │
│ │ ├─ ai-proxy │ │ ├─ courts (expandido) │ │
│ │ ├─ generate-embedding│ │ ├─ jurisprudences (novo) │ │
│ │ ├─ chat-jurisprudencia│ │ ├─ jurisprudencia_topics (novo) │ │
│ │ ├─ crawl-stf (novo) │ │ ├─ jurisprudencia_embeddings (novo)│ │
│ │ ├─ crawl-stj (novo) │ │ └─ jurisprudencia_chat (expandido)│ │
│ │ ├─ crawl-trf5 (novo) │ │ │ │
│ │ ├─ crawl-tjce (novo) │ │ │ │
│ │ └─ analyze-petition │ │ │ │
│ └───────────────────────┘ └────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ HETZNER CLOUD VPS (CX33 - Existente) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ local-scraper (Expandido) │ │
│ │ ├─ Crawlee/Playwright para STF, STJ, TRF-5, TJ-CE │ │
│ │ ├─ Proxy rotation com residential proxies │ │
│ │ ├─ Scheduler para crawl incremental (a cada 2-6h) │ │
│ │ └─ API REST para trigger de crawl via Supabase │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
2.2 Stack Tecnológica e Componentes
Frontend (Extensão do Existing):
O frontend utilizará as tecnologias já estabelecidas no RV-Adv, com adições pontuais para suportar o módulo de jurisprudência expandido. A biblioteca TanStack Query será ampliada com queries específicas para busca jurisprudencial, aproveitando os hooks de staleTime e prefetch já configurados. Os componentes shadcn/ui existentes serão reutilizados, com criação de novos componentes apenas para funcionalidades específicas do domínio jurisprudencial.
| Componente | Tecnologia | Adição ao Existing |
|---|---|---|
| UI Framework | React 18 + Vite 6 | Sem mudança |
| Estilização | Tailwind CSS 4 | Design tokens legais já definidos |
| Estado | TanStack Query v5 | Novas queries: useJurisprudenceSearch, usePetitionAnalysis |
| Ícones | Lucide React | Ícones jurisprudenciais: Gavel, Scale, FileText |
| Animações | Framer Motion | Transições de cards de resultado |
| PDF Viewer | react-pdf | Para preview de decisões |
Backend (Supabase):
O backend Supabase constitui o pilar central do módulo, extendendo o schema existente com novas tabelas para armazenagem e indexação de jurisprudências. A extensão pgvector já ativa no banco será aproveitada para armazenagem de embeddings de 3072 dimensões (conforme padrão atual do RV-Adv para embeddings Gemini). As Edge Functions existentes serão estendidas ou servirão como base para novas implementações.
| Componente | Tecnologia | Função |
|---|---|---|
| Database | PostgreSQL + pgvector | Schema extendido + índice HNSW |
| Auth | Supabase Auth | JWT já configurado |
| Storage | Supabase Storage | Armazenamento de PDFs de jurisprudência |
| Edge Functions | Deno/TypeScript | Crawl, embedding, chat RAG |
| Cron | pg_cron | Jobs de crawl e atualização |
Microsserviço de Scraping (Hetzner VPS):
O VPS Hetzner CX33 atualmente dedicado ao local-scraper será expandido para incluir crawlers específicos para cada tribunal-alvo. A arquitetura do crawler existente (Crawlee + Playwright com técnicas stealth) será reutilizada e adaptada para as estruturas HTML distintas de cada portal judicial.
| Componente | Tecnologia | Função |
|---|---|---|
| Runtime | Node.js 20 LTS | Execução do scraper |
| Framework | Crawlee + Playwright | Crawling com stealth |
| Scheduler | node-cron | Agendamento de crawls |
| Proxy | Residential proxies (BrightData/ScrapingBee) | Rotação de IPs |
| API | Express.js | Endpoints REST para Supabase |
Inteligência Artificial (Existing + Extensão):
O ai-proxy existente será reutilizado integralmente, beneficiando-se do sistema de fallback multi-provedor já implementado. As chaves de API armazenadas no Supabase Vault continuarão ocultas do frontend, e o timeout de 45 segundos será mantido para operações de crawl e análise.
| Ação | Provedor Primário | Provedor Backup | Extensão |
|---|---|---|---|
| Geração de Embeddings | Gemini | — | Mantido |
| Chat RAG Jurisprudência | Gemini 2.5 Flash Lite | — | Expandido |
| Análise de Petição | Groq | Gemini | Novo |
| Query Expansion | Groq | — | Novo |
2.3 Fluxo de Dados e Integrações
Fluxo de Busca por Linguagem Natural:
O fluxo de busca inicia quando o usuário submete uma descrição textual na interface JurisprudenciaPage. O componente dispara a Edge Function search-jurisprudence via API route do Supabase, passando a query original e os tribunais selecionados. A função implementa o seguinte pipeline: (1) autenticação JWT via módulo _shared/auth.ts existente; (2) expansão da query utilizando o LLM configurado (Groq llama-3.1-8b) para gerar 3-5 variações semânticas; (3) geração de embedding da query expandida via Gemini; (4) execução de busca vetorial no banco PostgreSQL usando a RPC buscar_jurisprudencia existente; (5) fetch de metadados completos das decisões recuperadas; (6) classificação de relevância baseada em score de similaridade; (7) formatação e retorno dos resultados.
Fluxo de Upload de Petição:
O fluxo de análise de petição inicia com o upload do documento via componente PetitionUploadPanel. O frontend persiste o arquivo no Supabase Storage e cria um registro de job na tabela jurisprudencia_jobs. A Edge Function analyze-petition é invocada assincronamente, implementando o pipeline: (1) extração de texto via OCR (Gemini Vision para PDFs scaneados); (2) identificação de temas jurídicos via classificação LLM (Groq); (3) para cada tema identificado, execução de busca jurisprudencial; (4) agregação e classificação dos resultados; (5) atualização do job com status completed e resultados. O frontend utiliza polling ou subscriptions do Supabase para monitorar o progresso do job.
Fluxo de Crawl e Indexação:
O fluxo de crawl opera em duas camadas: o VPS Hetzner executa crawlers Playwright que navegam nos portais judiciais e extraem decisões, enquanto Edge Functions no Supabase processam e indexam os dados. O scheduler no VPS dispara crawls incrementais a cada 2 horas para STJ (maior volume), 4 horas para STF, e 6 horas para TRF-5 e TJ-CE. Para cada decisão extraída, o pipeline executa: (1) persistência do registro em jurisprudences; (2) trigger assíncrono para geração de embedding via generate-embedding; (3) atualização do status de embedding para completed quando pronto.
3. MODELO DE DADOS EXPANDIDO
3.1 Schema de Banco de Dados
O schema do banco de dados será扩展ado com novas tabelas e extensões às existentes, preservando as convenções estabelecidas nas 57 migrações anteriores. Todas as tabelas seguirão o padrão de ownership via created_by e terão RLS habilitado com política Fail-Close.
-- Extensão da tabela courts para incluir STF, STJ, TRF-5, TJ-CE
INSERT INTO courts (acronym, name, url_base, crawl_enabled, crawl_interval_hours, last_crawl_at)
VALUES
('STF', 'Supremo Tribunal Federal', 'https://stf.jus.br', true, 4, NULL),
('STJ', 'Superior Tribunal de Justiça', 'https://www.stj.jus.br', true, 2, NULL),
('TRF5', 'Tribunal Regional Federal da 5ª Região', 'https://www.trf5.jus.br', true, 6, NULL),
('TJCE', 'Tribunal de Justiça do Estado do Ceará', 'https://www.tjce.jus.br', true, 6, NULL);
-- Tabela principal de jurisprudências
CREATE TABLE jurisprudences (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
court_id UUID REFERENCES courts(id) ON DELETE CASCADE,
process_number VARCHAR(50) NOT NULL,
process_type VARCHAR(100),
rapporteur VARCHAR(200),
adjudicating_body VARCHAR(200),
publication_date DATE,
trial_date DATE,
excerpt TEXT NOT NULL,
full_text TEXT,
url TEXT NOT NULL,
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
embedding_status VARCHAR(20) DEFAULT 'pending',
UNIQUE(court_id, process_number)
);
-- Índice vetorial para embeddings (3072 dimensões - Gemini)
ALTER TABLE jurisprudences ADD COLUMN embedding vector(3072);
-- Índice HNSW para busca vetorial
CREATE INDEX idx_jurisprudence_embedding ON jurisprudences
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- Índice FTS para busca por palavra-chave
ALTER TABLE jurisprudences ADD COLUMN fts_vector tsvector
GENERATED ALWAYS AS (to_tsvector('portuguese', coalesce(excerpt, '') || ' ' || coalesce(full_text, ''))) STORED;
CREATE INDEX idx_jurisprudence_fts ON jurisprudences USING GIN (fts_vector);
-- Tabela de tópicos/temas extraídos
CREATE TABLE jurisprudencia_topics (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(200) NOT NULL,
keywords TEXT[],
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Tabela de mapeamento jurisprudência-tópico
CREATE TABLE jurisprudencia_topic_mapping (
jurisprudence_id UUID REFERENCES jurisprudences(id) ON DELETE CASCADE,
topic_id UUID REFERENCES jurisprudencia_topics(id) ON DELETE CASCADE,
confidence FLOAT,
PRIMARY KEY (jurisprudence_id, topic_id)
);
-- Tabela de análise de petições
CREATE TABLE jurisprudencia_jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id),
document_name VARCHAR(255),
document_storage_path TEXT,
status VARCHAR(20) DEFAULT 'processing',
extracted_topics JSONB,
court_filter TEXT[],
max_results_per_topic INTEGER DEFAULT 10,
error_message TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
completed_at TIMESTAMPTZ
);
-- Tabela de resultados de análise
CREATE TABLE jurisprudencia_job_results (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
job_id UUID REFERENCES jurisprudencia_jobs(id) ON DELETE CASCADE,
jurisprudence_id UUID REFERENCES jurisprudences(id),
topic_name VARCHAR(200),
relevance_score VARCHAR(20),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Função RPC para busca vetorial
CREATE OR REPLACE FUNCTION buscar_jurisprudencia(
query_embedding vector(3072),
match_count INTEGER DEFAULT 20,
similarity_threshold FLOAT DEFAULT 0.7,
court_filter TEXT[] DEFAULT NULL
)
RETURNS TABLE (
id UUID,
process_number VARCHAR,
court_acronym VARCHAR,
process_type VARCHAR,
rapporteur VARCHAR,
publication_date DATE,
excerpt TEXT,
url TEXT,
similarity FLOAT
) AS $$
BEGIN
RETURN QUERY
SELECT
j.id,
j.process_number,
c.acronym as court_acronym,
j.process_type,
j.rapporteur,
j.publication_date,
j.excerpt,
j.url,
(j.embedding <=> query_embedding) as similarity
FROM jurisprudences j
JOIN courts c ON j.court_id = c.id
WHERE j.embedding IS NOT NULL
AND (j.embedding <=> query_embedding) < (1 - similarity_threshold)
AND (court_filter IS NULL OR c.acronym = ANY(court_filter))
ORDER BY j.embedding <=> query_embedding
LIMIT match_count;
END;
$$ LANGUAGE plpgsql;
-- Extensão da tabela de chat para múltiplos tribunais
ALTER TABLE jurisprudencia_chat_sessions ADD COLUMN court_filter TEXT[];
ALTER TABLE jurisprudencia_chat_messages ADD COLUMN embedding_id UUID;
3.2 Migrações SQL Sequenciais
As migrações seguirão a numeração estabelecida (iniciando em 058) e incluirão verificações IF NOT EXISTS para garantir idempotência durante deploys.
| Migração | Descrição | Dependências |
|---|---|---|
| 058_courts_expand | Extensão da tabela courts com STF, STJ, TRF5, TJCE | 057+ |
| 059_jurisprudences | Criação da tabela principal de jurisprudências | 058 |
| 060_jurisprudence_embeddings | Adição de coluna vector e índices HNSW/FTS | 059 |
| 061_jurisprudencia_topics | Tabelas de tópicos e mapeamento | 059 |
| 062_jurisprudencia_jobs | Tabela de jobs de análise de petições | 059 |
| 063_buscar_jurisprudencia_fn | Função RPC de busca vetorial | 060 |
| 064_rls_jurisprudences | Políticas RLS para novas tabelas | 059-063 |
4. EDGE FUNCTIONS: IMPLEMENTAÇÃO
4.1 Catálogo de Edge Functions
As Edge Functions serão implementadas seguindo os padrões estabelecidos no RV-Adv, reutilizando o módulo _shared/auth.ts para autenticação e integrando-se ao ai-proxy existente para chamadas de LLM.
| Edge Function | Responsabilidade | Autenticação | Dependências |
|---|---|---|---|
search-jurisprudence | Busca semântica multi-tribunal | JWT | ai-proxy, generate-embedding |
analyze-petition | Análise de petição e extração de temas | JWT | ai-proxy, search-jurisprudence |
chat-jurisprudence-multi | Chat RAG multi-tribunal (estende existente) | JWT | ai-proxy, jurisprudences |
crawl-trigger-stf | Trigger de crawl para STF | JWT (service) | — |
crawl-trigger-stj | Trigger de crawl para STJ | JWT (service) | — |
crawl-trigger-trf5 | Trigger de crawl para TRF-5 | JWT (service) | — |
crawl-trigger-tjce | Trigger de crawl para TJ-CE | JWT (service) | — |
index-jurisprudence | Indexação de jurisprudência extraída | JWT (service) | generate-embedding |
4.2 Implementação da Função search-jurisprudence
A função de busca semântica implementa o pipeline completo de recuperação, desde a query do usuário até os resultados formatados. A implementação segue os padrões de código estabelecidos no RV-Adv.
// supabase/functions/search-jurisprudence/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { AiProxy } from '../_shared/ai-proxy.ts'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
const authHeader = req.headers.get('Authorization')
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
)
// Autenticação JWT
const token = authHeader?.replace('Bearer ', '')
const { data: { user }, error: authError } = await supabase.auth.getUser(token)
if (authError || !user) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' }
})
}
const { query, courts, max_results, relevance_threshold, expand_query } = await req.json()
// 1. Expansão de query via LLM
let expandedQueries = [query]
if (expand_query) {
const aiProxy = new AiProxy()
const expansionPrompt = `Gere 3 variações da seguinte consulta jurídica para busca jurisprudencial.
Considere sinônimos, termos técnicos equivalentes e diferentes formulações do mesmo conceito.
Consulta original: "${query}"
Responda apenas com um JSON array de strings, sem explicações.`
const expansionResponse = await aiProxy.invoke({
model: 'groq/llama-3.1-8b',
messages: [{ role: 'user', content: expansionPrompt }],
max_tokens: 200,
temperature: 0.3
})
try {
expandedQueries = JSON.parse(expansionResponse)
expandedQueries = [query, ...expandedQueries.slice(0, 3)]
} catch {
expandedQueries = [query]
}
}
// 2. Geração de embedding
const embeddingResponse = await fetch(`${Deno.env.get('SUPABASE_URL')}/functions/v1/generate-embedding`, {
method: 'POST',
headers: {
'Authorization': authHeader,
'Content-Type': 'application/json'
},
body: JSON.stringify({ text: query, taskType: 'RETRIEVAL_QUERY' })
})
const { embedding } = await embeddingResponse.json()
// 3. Busca vetorial
const { data: results, error: searchError } = await supabase.rpc('buscar_jurisprudencia', {
query_embedding: embedding,
match_count: max_results || 20,
similarity_threshold: relevance_threshold || 0.7,
court_filter: courts || null
})
if (searchError) throw searchError
// 4. Classificação de relevância
const classifiedResults = results.map(r => ({
...r,
relevance: classifyRelevance(r.similarity)
}))
return new Response(JSON.stringify({
data: classifiedResults,
meta: {
query_expanded: expand_query,
variations_used: expandedQueries.length,
total: classifiedResults.length
}
}), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
})
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
})
}
})
function classifyRelevance(similarity: number): string {
if (similarity > 0.85) return 'high'
if (similarity > 0.70) return 'medium'
return 'low'
}
4.3 Implementação da Função analyze-petition
A função de análise de petição recebe um documento, extrai temas automaticamente e executa buscas jurisprudenciais para cada tema identificado.
// supabase/functions/analyze-petition/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { AiProxy } from '../_shared/ai-proxy.ts'
serve(async (req) => {
// Implementação completa em Edge Function
// 1. Download do documento do Storage
// 2. Extração de texto (OCR se necessário via Gemini Vision)
// 3. Extração de temas via LLM
// 4. Para cada tema: busca jurisprudencial
// 5. Persistência de resultados
// 6. Atualização do job status
})
5. MICROSSERVIÇO DE SCRAPING (HETZNER VPS)
5.1 Arquitetura do Scraper Expandido
O microsserviço de scraping existente no VPS Hetzner será expandido para incluir crawlers específicos para os tribunais-alvo. A arquitetura preserva o padrão estabelecido de Crawlee + Playwright com técnicas stealth.
local-scraper/
├── server.ts # API REST existente
├── crawlers/
│ ├── base-crawler.ts # Classe base com configurações stealth
│ ├── stf-crawler.ts # Crawler específico STF
│ ├── stj-crawler.ts # Crawler específico STJ
│ ├── trf5-crawler.ts # Crawler específico TRF-5
│ └── tjce-crawler.ts # Crawler específico TJ-CE
├── extractors/
│ ├── stf-extractor.ts # Parser de HTML do STF
│ ├── stj-extractor.ts # Parser de HTML do STJ
│ ├── trf5-extractor.ts # Parser de HTML do TRF-5
│ └── tjce-extractor.ts # Parser de HTML do TJ-CE
├── schedulers/
│ └── crawl-scheduler.ts # Agendamento de crawls
├── proxy-rotator.ts # Rotação de residential proxies
├── api-client.ts # Cliente Supabase para persistência
└── package.json
5.2 Configuração de Crawlers por Tribunal
Cada tribunal requer configurações específicas devido às diferenças estruturais em seus portais de jurisprudência.
STF (stf.jus.br):
O portal do STF utiliza JavaScript heavy para renderização de resultados, exigindo Playwright para renderização completa. O sistema de busca permite filtros por número de processo, relator, ementa e índice de assuntos. A estrutura de dados inclui: número do processo (formato completo CNJ), tipo (recurso extraordinário, agravo, etc.), relator, data de publicação, ementa e link para inteiro teor. O volume diário gira em torno de 150-200 decisões.
STJ (stj.jus.br):
O STJ possui a estrutura mais robusta e estável para scraping. O portal oferece filtros por classe, assunto CDU, tribunal de origem, período e texto. A extração é relativamente direta, com HTML bem estruturado. O volume diário é o maior: 500-800 decisões. O STJ é prioritário para crawl mais frequente.
TRF-5 (trf5.jus.br):
O TRF-5 mantém portal com estrutura similar aos tribunais estaduais. A busca permite filtros por número do processo, palavras-chave, classe e data. O volume é moderado (100-200 decisões diárias), concentrado em direito federal: previdenciário, tributário e administrativo.
TJ-CE (tjce.jus.br):
O TJ-CE apresenta portal com estrutura variável, requerendo manutenções frequentes nos selectors CSS. A busca oferece filtros por classe, matéria, data e texto. O volume é aproximadamente 200-400 decisões por dia útil.
5.3 Scheduler de Crawl
O scheduler implementa frecuencias diferenciadas baseadas no volume de cada tribunal:
// schedulers/crawl-scheduler.ts
import cron from 'node-cron'
const CRAWL_SCHEDULE = {
STJ: '0 */2 * * *', // A cada 2 horas
STF: '0 */4 * * *', // A cada 4 horas
TRF5: '0 */6 * * *', // A cada 6 horas
TJCE: '0 */6 * * *', // A cada 6 horas
}
export function startScheduler() {
Object.entries(CRAWL_SCHEDULE).forEach(([court, schedule]) => {
cron.schedule(schedule, async () => {
console.log(`Iniciando crawl para ${court}`)
try {
await triggerCrawl(court)
} catch (error) {
console.error(`Erro no crawl ${court}:`, error)
await sendAlert(error)
}
})
})
}
6. INTERFACE DE USUÁRIO
6.1 Componentes do Frontend
Os componentes do frontend serão implementados como extensão do sistema existente, seguindo os padrões de design system já estabelecidos.
JurisprudenciaPage.jsx (Expandida):
A página principal de jurisprudência será expandida para incluir três painéis principais: busca em linguagem natural, upload de petição, e chat RAG multi-tribunal.
// Estrutura simplificada da página
export function JurisprudenciaPage() {
const [activeTab, setActiveTab] = useState('search')
return (
<div className="container mx-auto py-6 space-y-6">
<header className="flex items-center justify-between">
<h1 className="text-3xl font-bold">Jurisprudência</h1>
<CourtFilter />
</header>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="search">Busca</TabsTrigger>
<TabsTrigger value="petition">Análise de Petição</TabsTrigger>
<TabsTrigger value="chat">Chat RAG</TabsTrigger>
</TabsList>
<TabsContent value="search">
<NaturalSearchPanel />
<ResultsGrid />
</TabsContent>
<TabsContent value="petition">
<PetitionUploadPanel />
<ThemesList />
</TabsContent>
<TabsContent value="chat">
<ChatRAGPanel courts={selectedCourts} />
</TabsContent>
</Tabs>
</div>
)
}
NaturalSearchPanel:
Componente de busca que substitui a entrada de termos técnicos por descrição em linguagem natural.
// Componentes principais
- SearchInput: Campo de texto expansível para descrição do caso
- CourtSelector: Seleção multipla de tribunais (STF, STJ, TRF-5, TJ-CE)
- SearchButton: Botão de busca com loading state
- QuickFilters: Filtros rápidos por tipo de decisão e período
ResultsGrid:
Grid responsivo de resultados jurisprudenciais com cards expandíveis.
// Estrutura do card de resultado
<Card>
<CardHeader>
<div className="flex justify-between items-start">
<div>
<Badge variant="court">{result.court_acronym}</Badge>
<h3 className="font-mono">{result.process_number}</h3>
</div>
<Badge variant="relevance-{result.relevance}">
{result.relevance === 'high' ? 'Muito Relevante' :
result.relevance === 'medium' ? 'Relevante' : 'Pouco Relevante'}
</Badge>
</div>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
{result.rapporteur} • {result.adjudicating_body}
</p>
<p className="mt-2 line-clamp-4">{result.excerpt}</p>
</CardContent>
<CardFooter>
<Button variant="outline" asChild>
<a href={result.url} target="_blank" rel="noopener noreferrer">
Ver no Tribunal
</a>
</Button>
</CardFooter>
</Card>
6.2 Custom Hooks TanStack Query
Os hooks de TanStack Query seguirão o padrão existente de BaseService.
// hooks/useJurisprudenceSearch.ts
export function useJurisprudenceSearch() {
const supabase = useSupabaseClient()
return useMutation({
mutationFn: async ({ query, courts, maxResults, expandQuery }) => {
const { data: { session } } = await supabase.auth.getSession()
const response = await fetch(
`${import.meta.env.VITE_SUPABASE_URL}/functions/v1/search-jurisprudence`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`
},
body: JSON.stringify({ query, courts, max_results: maxResults, expand_query: expandQuery })
}
)
if (!response.ok) throw new Error('Search failed')
return response.json()
}
})
}
// hooks/usePetitionAnalysis.ts
export function usePetitionAnalysis() {
const supabase = useSupabaseClient()
const queryClient = useQueryClient()
return useMutation({
mutationFn: async ({ file, courts }) => {
// Upload file to storage
const filePath = `jurisprudence/jobs/${Date.now()}-${file.name}`
const { error: uploadError } = await supabase.storage
.from('documents')
.upload(filePath, file)
if (uploadError) throw uploadError
// Trigger analysis job
const { data: { session } } = await supabase.auth.getSession()
const response = await fetch(
`${import.meta.env.VITE_SUPABASE_URL}/functions/v1/analyze-petition`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session.access_token}`
},
body: JSON.stringify({
document_path: filePath,
document_name: file.name,
courts
})
}
)
if (!response.ok) throw new Error('Analysis failed')
return response.json()
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['jurisprudence-jobs'] })
}
})
}
7. PIPELINE DE DEPLOY E CI/CD
7.1 GitHub Actions Workflows
Dois novos workflows serão adicionados aos existentes, mantendo o padrão de workflows de deploy já estabelecidos.
deploy-jurisprudence-functions.yml:
name: Deploy Jurisprudence Edge Functions
on:
push:
branches: [master]
paths:
- 'supabase/functions/search-jurisprudence/**'
- 'supabase/functions/analyze-petition/**'
- 'supabase/functions/chat-jurisprudence-multi/**'
- 'supabase/functions/index-jurisprudence/**'
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
with:
supabase_version: latest
- name: Deploy Edge Functions
run: |
for func in search-jurisprudence analyze-petition chat-jurisprudence-multi index-jurisprudence; do
supabase functions deploy $func --project-ref uxtgcarklizhwuotkwkd
done
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
jurisprudence-crawler-sync.yml:
name: Sync Jurisprudence Crawler to Hetzner
on:
push:
branches: [master]
paths:
- 'local-scraper/**'
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Hetzner
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HETZNER_HOST }}
username: ${{ secrets.HETZNER_USER }}
key: ${{ secrets.HETZNER_SSH_KEY }}
script: |
cd /opt/rv-adv/local-scraper
git pull origin master
npm install
pm2 restart scraper || pm2 start server.ts --name scraper
7.2 pg_cron Jobs
Novos jobs de cron serão configurados no banco de dados para tarefas de manutenção.
-- Job de verificação de embeddings pendentes
SELECT cron.schedule(
'jurisprudence-embedding-check',
'0 */1 * * *',
$$
INSERT INTO jurisprudencia_jobs (user_id, document_name, status, error_message)
SELECT
u.id,
'System: Pending embeddings check',
'info',
format('Found %s jurisprudences pending embedding', COUNT(*))
FROM jurisprudences j
CROSS JOIN LATERAL auth.users() u
WHERE j.embedding_status = 'pending'
AND j.created_at < NOW() - INTERVAL '24 hours'
HAVING COUNT(*) > 0
ON CONFLICT DO NOTHING;
$$
);
8. PLANO DE IMPLEMENTAÇÃO
8.1 Fases de Desenvolvimento
Fase 1: Foundation (Semanas 1-2)
- Criação das migrações SQL (058-064) para schema de jurisprudência
- Deploy inicial das tabelas e índices no Supabase
- Implementação da Edge Function
search-jurisprudence - Teste de integração com embedding existente
- Configuração inicial de RLS para novas tabelas
Fase 2: Crawler MVP (Semanas 3-4)
- Implementação dos crawlers STF e STJ no VPS Hetzner
- Extração de estrutura HTML e parsers
- Teste de crawling com volume controlado
- Endpoint de trigger via Supabase Edge Function
- Scheduler básico (crawl manual trigger)
Fase 3: Interface (Semanas 5-6)
- Expansão do componente JurisprudenciaPage
- Implementação de NaturalSearchPanel
- Desenvolvimento de ResultsGrid com cards
- Integração com hooks TanStack Query
- Testes E2E com Playwright
Fase 4: Análise de Petição (Semanas 7-8)
- Implementação da Edge Function
analyze-petition - Integração de OCR para PDFs scaneados
- UI de upload e progress tracking
- Tela de resultados por tema
- Testes de integração
Fase 5: Expansão de Crawlers (Semanas 9-10)
- Crawlers para TRF-5 e TJ-CE
- Scheduler automatizado com frecuencias diferenciadas
- Proxy rotation setup
- Monitoramento e alertas
- Otimização de performance
Fase 6: Chat RAG Multi-Tribunal (Semanas 11-12)
- Expansão da Edge Function
chat-jurisprudenciaexistente - Suporte a múltiplos tribunais
- Interface de chat no frontend
- Histórico de sessões
- Deploy e testes finais
8.2 Estimativa de Recursos
Infraestrutura Adicional:
| Recurso | Necessidade | Custo Estimado |
|---|---|---|
| VPS Hetzner CX33 | Já existente, expansão de uso | $0 adicional |
| Supabase Pro | Upgrade para mais Edge Functions | ~$25/mês |
| Residential Proxies | BrightData/ScrapingBee | ~$50-100/mês |
| Armazenamento | Supabase Storage ( PDFs) | ~$5/mês |
APIs LLM (Gratuitas - Existing):
| Provedor | Uso | Custo |
|---|---|---|
| Gemini | Embeddings, Chat RAG | Gratuito |
| Groq | Query expansion, Classificação | Gratuito |
| NVIDIA NeMo | Fallback | Gratuito |
9. CHECKLIST DE QUALIDADE
- CQ1: Funcionalidades implementadas (busca inteligente, análise de petição, relevância, chat RAG multi-tribunal)
- CQ2: Arquitetura integrada ao RV-Adv existente (Supabase, Hetzner, Netlify)
- CQ3: Sistema LexNova-RV como módulo do RV-Adv, não produto independente
- CQ4: Detalhamento técnico adequado para implementação por equipe
- Reutilização de componentes existentes (BaseService, shadcn/ui, _shared/auth.ts)
- RLS implementado em todas as tabelas novas
- Edge Functions com autenticação JWT padrão
- Crawlers com técnicas stealth e rotação de proxies
- Interface responsiva seguindo design system existente
- CI/CD automatizado via GitHub Actions
- Monitoramento e alertas configurados