Pular para o conteúdo

eproc — o sistema que já fala CNJ

O eproc nasceu no TRF4, foi cedido a outros tribunais e é para onde o TJRJ vem migrando o acervo. Se você é magistrado no Rio, é provável que ele já seja o sistema onde o seu trabalho acontece — e isso, por si só, justificaria um capítulo.

Mas não é por isso que ele tem um.

O eproc é o único sistema da tabela desta unidade em que as duas portas estão abertas ao mesmo tempo, e em que a porta do banco fala a mesma língua que você aprendeu na Unidade 2. Essas duas propriedades mudam o que dá para construir, e é disso que trata o capítulo.

Atenção

O aviso de método, de novo, porque ele muda de capítulo para capítulo.

Medido: a estrutura de eventos do eproc de 2º grau, tal como ela aparece num projeto real de conferência de dados contra o acervo do Tribunal, e a comparação com o sistema legado que roda ao lado dele. Os números da seção seguinte vêm dessa conferência.

Não medido: a sua credencial, o seu grau, o seu esquema. Nomes de tabela e de coluna variam entre instalações do eproc e entre versões — o próprio TRF4 evolui o esquema. Trate cada nome deste capítulo como hipótese a confirmar, com o WHERE 1=0 do capítulo do banco, antes de escrever qualquer consulta que valha alguma coisa.

Duas portas para o mesmo processo

MNI Banco (PostgreSQL)
Quem autoriza você, com o seu CPF e senha a TI, com um usuário que ela cria
Alcance exatamente o seu, nem mais nem menos o que a view deixar — e por padrão, demais
Responde um processo que você já sabe qual é quantos, quais, por órgão, evolução
Nas cinco formas só a 5 — Detalhar 1 a 4 — Contar, Listar, Agrupar, Evoluir
Capítulo MNI o banco

Leia a linha "Responde" duas vezes. Ela é a razão de este capítulo não ter código novo de conexão: o eproc não pede uma técnica nova, ele pede que você escolha a porta pela pergunta — que é exatamente o Passo 0 de Escreva a sua, antecipado.

"Quantos processos meus de execução fiscal estão parados?" nunca vai pelo MNI. "O que aconteceu no 0018683-81.2020.8.19.0066?" nunca precisa do banco.

Um agente maduro tem as duas Tools, e o modelo escolhe. Um agente com uma Tool só vai forçar a pergunta na porta errada — e, pior, vai conseguir, devagar e pela metade.

O que faz o eproc diferente

Aqui está a propriedade que vale o capítulo, e ela é sobre códigos de movimento.

Na Unidade 2 você aprendeu a tabela nacional do CNJ: cada andamento tem um código, e esse código significa a mesma coisa em qualquer tribunal do país. 12065 é sobrestamento no TJRJ, no TJSP e no TRF2. É o que permite ao DataJud existir.

O que quase ninguém conta é que o sistema interno normalmente não usa esse código.

Um sistema legado guarda o andamento no vocabulário dele — um código de fase, mais dois ou três complementos —, e o código do CNJ só aparece na hora de enviar para Brasília, através de uma tabela de tradução. Essa tradução não é uma correspondência de um para um: ela desce por níveis, e quando não acha o complemento exato, sobe para o nó pai da árvore.

Ela também não é completa. Na conferência que citei no aviso, sobre as combinações distintas de fase e complementos de um acervo de 2º grau:

combinações distintas encontradas no sistema legado 16.280
resolvidas para um código do CNJ 12.897 — 79,2%
sem tradução 3.383 — 20,8%

Um quinto das combinações não tem para onde ir. A maior parte é dado antigo e inofensivo, mas nem toda: duas famílias vivas — "decisão em petição" e "despacho em petição" — respondem sozinhas por cerca de 59 mil combinações, e caem porque o sistema criou uma variante que a árvore do CNJ não prevê.

No eproc isso não acontece, porque o evento já nasce com o código nacional. A coluna existe no próprio banco do sistema, com o código do CNJ dentro. Não há tabela de tradução no caminho, não há descida por níveis, não há 20,8% caindo em silêncio.

A consequência, para você, é direta e é grande:

Tudo o que você aprendeu na Unidade 2 sobre a tabela do CNJ vale dentro do banco do eproc.

O código que você descobriu na consulta pública de movimentos do CNJ é o mesmo que você põe no WHERE. A árvore de assuntos e o COD_PAI_NACIONAL do capítulo Só os meus processos valem igual. E — o que mais importa numa Tool — o resultado é explicável com documento público: quando o agente disser "42 processos com movimento 12065", você pode conferir o 12065 num site aberto, sem pedir nada a ninguém.

Num sistema legado, a mesma frase exigiria conferir a tabela de tradução, que é interna, e cuja falha é silenciosa.

O driver muda, o padrão não

O eproc é PostgreSQL. Troca o driver e mais nada:

pip install "psycopg[binary]"

O [binary] traz a biblioteca compilada junto, sem exigir compilador nem pg_config — é o mesmo motivo do modo thin do oracledb no capítulo do banco: numa máquina de tribunal, sem administrador, o que precisa de instalador nativo não é instalado.

A conexão segue o padrão da senha em variável de ambiente, sem exceção:

import os
import psycopg

def conectar(cfg: dict) -> psycopg.Connection:
    senha = os.environ.get(cfg["password_env"], "")
    if not senha:
        raise SystemExit(
            f"variável de ambiente {cfg['password_env']} não está definida. "
            "Defina-a antes de rodar; ela não fica em arquivo."
        )
    return psycopg.connect(
        host=cfg["host"],
        port=int(cfg["port"]),
        dbname=cfg["dbname"],
        user=cfg["user"],
        password=senha,
        sslmode="require",       # ver a seção de erros
        connect_timeout=10,
    )

Três diferenças de SQL em relação ao capítulo do Oracle, e são só três:

Oracle (EJUD, DCP) PostgreSQL (eproc)
limitar linhas FETCH FIRST 30 ROWS ONLY LIMIT 30
parâmetro nomeado :assunto %(assunto)s
truncar data para o mês TRUNC(data, 'MM') date_trunc('month', data)

O WHERE 1=0 funciona idêntico, e continua sendo o primeiro comando a rodar. No PostgreSQL você ainda tem uma segunda via, que não lê nem o formato de uma linha:

SELECT column_name, data_type
  FROM information_schema.columns
 WHERE table_name = 'processo_evento'
 ORDER BY ordinal_position;

Grau é outro banco, não outra coluna

Esta é a armadilha que custa uma tarde, e ela não dá erro.

1º e 2º grau costumam ser instâncias separadas do eproc — bancos diferentes, com o mesmo desenho de tabelas. A sua consulta roda perfeitamente na instância errada e devolve zero linhas, que é indistinguível de "não há processos com esse critério".

Duas defesas, e use as duas:

1. O grau entra no nome da Tool. processos_2grau(...), não processos(...). O modelo lê o nome da função; um nome que mente é uma Tool que mente.

2. A Tool devolve de onde veio. Uma chave "fonte": "eproc 2º grau" no dicionário de resposta custa nada e aparece na conversa. Zero linhas com fonte declarada é uma resposta; zero linhas sem fonte é um mistério.

E, antes de tudo isso, uma pergunta ao abrir o chamado: esta credencial enxerga qual grau? É a pergunta que ninguém faz e que decide se o resto do trabalho serve.

Sigilo, aqui, é seu problema

Na Unidade 2 o sigilo não era problema seu: o índice público do DataJud só tem nível 0, e o filtro era cinto de segurança sobre uma porta que já vinha fechada.

Dentro do eproc a porta está aberta. O banco guarda todos os níveis, porque é ele que os aplica.

Valem, sem nenhuma alteração, as três camadas do capítulo Só os meus processos: a view já filtrada pela TI, o WHERE sobre a constante lida do ambiente, e o campo ausente da assinatura da Tool. E vale a frase que governa este curso desde a primeira unidade:

Existem processos, principalmente os criminais, que são e devem ser protegidos, e cujos números nem todos devem ter acesso, para não saberem os dados da parte do processo.

Duas advertências específicas do eproc:

Não presuma o nome da coluna de sigilo. Ele varia entre instalações, e o nome que você viu no JSON do DataJud é o nome do campo de exportação, não necessariamente o da coluna interna. Descubra pelo information_schema e confirme com a TI o que cada valor significa — nível numérico, indicador de segredo de justiça e restrição por parte podem ser três colunas distintas, e filtrar uma delas achando que filtrou as três é o erro que não aparece em teste.

O eproc tem restrição por evento, não só por processo. Um processo público pode conter um evento restrito. Se a sua Tool devolve movimentos, ela precisa filtrar na tabela de eventos também — filtrar só o processo deixa passar exatamente o item que alguém teve o cuidado de restringir.

A Tool

Junta tudo: identidade do ambiente (Só os meus processos), teto de linhas (Unidade 3), código nacional do CNJ como argumento, grau no nome, fonte na resposta.

import os
from mcp.server.fastmcp import FastMCP

# Identidade é configuração, nunca argumento. Lida na importação: se faltar, {: #identidade-é-configuração-nunca-argumento-lida-na-importação-se-faltar }
# o programa não sobe — que é melhor do que subir consultando o Tribunal todo. {: #o-programa-não-sobe-que-é-melhor-do-que-subir-consultando-o-tribunal-todo }
MAGISTRADO = os.environ["EPROC_MAGISTRADO_ID"]
TETO_LINHAS = 30

mcp = FastMCP("eproc")

@mcp.tool(structured_output=True)
def processos_2grau_com_movimento(codigo_movimento: int, dias: int = 90) -> dict:
    """Processos meus que tiveram um determinado andamento no período.

    Args:
        codigo_movimento: código da tabela nacional de movimentos do CNJ
            (ex.: 12065 para sobrestamento). É o mesmo código da Unidade 2.
        dias: janela para trás, em dias. Padrão 90.
    """
    sql = """
        SELECT p.numero_processo,
               p.classe,
               max(e.data_evento) AS ultimo
          FROM vw_meus_processos p
          JOIN processo_evento e ON e.id_processo = p.id_processo
         WHERE e.cod_mni_movimento = %(codigo)s
           AND e.data_evento >= now() - make_interval(days => %(dias)s)
           AND p.cod_magistrado   = %(magistrado)s
           AND p.nivel_sigilo     = 0
         GROUP BY p.numero_processo, p.classe
         ORDER BY ultimo DESC
         LIMIT %(teto)s
    """
    with conectar(CFG) as conn, conn.cursor() as cur:
        cur.execute(sql, {
            "codigo": codigo_movimento,
            "dias": dias,
            "magistrado": MAGISTRADO,   # do ambiente, não da conversa
            "teto": TETO_LINHAS,
        })
        linhas = cur.fetchall()

    return {
        "fonte": "eproc 2º grau",
        "movimento": codigo_movimento,
        "janela_dias": dias,
        "quantidade": len(linhas),
        "truncado": len(linhas) == TETO_LINHAS,
        "processos": [
            {"numero": n, "classe": c, "ultimo_evento": str(u)}
            for n, c, u in linhas
        ],
    }
    # O que NÃO está aqui: nome de parte, CPF, valor da causa, teor de peça.

Repare no "truncado". Sem ele, trinta linhas de um universo de quatrocentas parecem quatrocentas — o modelo não tem como saber, e vai afirmar "são trinta" com a maior tranquilidade. A chave custa uma comparação e evita a única categoria de erro que este curso trata como grave: o número errado dito com confiança.

cod_mni_movimento é o nome que essa coluna tem na instalação que eu conferi. Confirme o seu antes de rodar — é literalmente o primeiro WHERE 1=0 do seu projeto.

Erros que o PostgreSQL dá de um jeito próprio

Quatro, e nenhum deles parece o que é:

relation "processo_evento" does not exist — quase nunca é erro de digitação. É o search_path: a tabela está num esquema que a sua sessão não procura. Qualifique (judicial.processo_evento) ou peça o search_path correto à TI.

A tabela existe e mesmo assim "não existe". O PostgreSQL dobra identificadores para minúsculas quando não estão entre aspas. Uma tabela criada como "Processo" só é encontrada como "Processo", com aspas, para sempre. Se o information_schema mostra maiúsculas, é isto.

As datas andam três horas — e às vezes um dia. timestamp e timestamptz são tipos diferentes, e a conversão usa o fuso da sessão. O sintoma não é um erro: é um evento das 22h do dia 30 que aparece no dia 1º, o que joga o processo para o mês seguinte e estraga silenciosamente qualquer contagem mensal. É primo do problema de datas da Unidade 3, com a mesma assinatura: o resultado é plausível, e por isso ninguém confere. Peça o tipo da coluna à TI e fixe o fuso na sessão.

SSL. Muita instalação exige. sslmode="require" resolve o caso comum; certificado interno exige apontar para a cadeia (sslrootcert), e a saída nunca é sslmode="disable" para fazer funcionar.

Antes de conectar num agente

  1. information_schema.columns responde, e você tem a lista real de colunas — sem nenhum SELECT que leia processo?
  2. A credencial enxerga qual grau? Você confirmou isso com a TI, e não pelo fato de a consulta ter devolvido linhas?
  3. Uma consulta a processos de outro magistrado devolve zero linhas — mesmo você tentando de propósito?
  4. Um processo sigiloso conhecido não aparece; e um processo público com evento restrito devolve o processo sem aquele evento?
  5. O dicionário que a Tool devolve tem fonte e truncado? E não tem nome de parte em canto nenhum — você leu campo por campo, não de memória?
  6. EPROC_SENHA está só no ambiente, e o programa falha ao subir quando EPROC_MAGISTRADO_ID não existe?

O item 3 é o mesmo teste do item 3 do MNI e do item 2 do SEI: é o único que mede o alcance da credencial em vez de supô-lo. O item 4 é específico daqui, e é o que separa quem leu este capítulo de quem filtrou só a tabela de processos.


Próximo: Apêndice — consultas estruturadas no eproc, para quando a pergunta deixar de ser "quais processos" e virar "quantos".