Pular para o conteúdo

O campo que não cabe

A Unidade 2 terminou dizendo que havia um campo que ela não tinha tocado.

Este é o capítulo dele.

movimentos[] é a lista de andamentos de um processo: cada despacho, cada conclusão, cada mandado expedido, com o código da Tabela Processual Unificada, o nome do movimento e a data e hora em que aconteceu. É o campo mais útil da API do DataJud para quem julga — é onde está a resposta para "o que andou nesse processo" — e é o único campo do índice capaz de quebrar o seu agente sozinho.

Este capítulo não escreve Tool nenhuma. Ele mede.

Um movimento, de perto

Antes de contar quantos são, vale ver como é um. Este veio do processo 0012105-73.2003.8.19.0042, exatamente como a API o devolve:

{
  "complementosTabelados": [
    {
      "codigo": 2,
      "valor": 2,
      "nome": "sorteio",
      "descricao": "tipo_de_distribuicao_redistribuicao"
    }
  ],
  "codigo": 26,
  "nome": "Distribuição",
  "dataHora": "0995-09-12T00:00:00.000Z"
}

Três coisas aqui merecem nota, e as três voltam depois.

O código 26 é nacional. 26 = Distribuição na TPU, e vale para o TJRJ, para o TJSP e para o STJ. Se você um dia for comparar acervos, é por esse campo — não pelo nome, que é a versão legível e pode variar.

complementosTabelados é um array dentro do array. Um movimento pode carregar complementos — o tipo de distribuição, o motivo da remessa, a classe nova numa mudança de classe. É informação boa, e é a maior parte do peso do campo.

A data diz 0995. Ano novecentos e noventa e cinco. Um processo de 2003, distribuído mil anos antes. Não é erro de leitura: é o que está no índice nacional. Guarde isso — vamos precisar dele no capítulo seguinte, quando a Tool tiver que ordenar por data.

Nota

Dado público real vem sujo. Não é defeito do DataJud especificamente; é o que acontece quando 90 tribunais alimentam uma base com décadas de migração de sistema atrás.

O ponto prático: uma Tool que só funciona com dado limpo não é uma Tool, é uma demonstração. A sua vai receber ano 995 uma hora, e o que ela faz nesse dia é decisão sua, tomada hoje.

A pergunta que a API se recusa a responder

A primeira tentativa de medir foi a óbvia: perguntar ao próprio Elasticsearch quantos movimentos os processos têm, em média, usando uma agregação com script.

{
  "size": 0,
  "query": { "match_all": {} },
  "aggs": {
    "qtd_movimentos": {
      "stats": {
        "script": {
          "source": "params._source.movimentos == null ? 0 : params._source.movimentos.size()"
        }
      }
    }
  }
}

Resposta:

cnj.ErroDataJud: HTTP 504: <html>
<head><title>504 Gateway Time-out</title></head>
<body>
<center><h1>504 Gateway Time-out</h1></center>
<hr><center>NSX LB</center>
</body>
</html>

Faz sentido. A consulta manda o cluster do CNJ abrir o _source de 23 milhões de documentos e contar um array em cada um. O balanceador corta antes de o cluster terminar, e você recebe um HTML de gateway em vez de um erro do Elasticsearch — a resposta nem chegou a ser do índice.

Não há truque para contornar isso, e nem deveria haver: uma API pública que deixasse qualquer um rodar script sobre a base inteira não duraria uma semana.

Então medimos como se mede fora de laboratório: por amostra.

resposta = consultar({
    "size": 200,
    "query": {"bool": {"filter": [{"range": {"nivelSigilo": {"lte": 0}}}]}},
})
qtds = [len(p["_source"].get("movimentos") or []) for p in resposta["hits"]["hits"]]

Duzentos processos públicos, contados na sua máquina.

Quanto pesa, de fato

movimentos por processo
mínimo 3
mediana 74
média 99,4
percentil 90 222
percentil 95 289
percentil 99 509
máximo 613

A mediana é 74. Metade dos processos passa disso.

O campeão da amostra é o 0018683-81.2020.8.19.0066 — Procedimento Especial da Lei Antitóxicos, 2ª Vara de Barra do Piraí — com 613 movimentos, 105.123 bytes só no array de andamentos. Cento e cinco mil caracteres, de um processo só.

Atenção

Uma intuição que este curso teve e a medição desmentiu.

A Unidade 2 supôs que os processos antigos seriam os pesados. É o palpite natural: mais anos, mais andamentos.

Os dez processos mais antigos do índice — incluindo um de 1880 e um de 1901 — têm média de 4,4 movimentos, máximo de 10. Chegaram ao DataJud por migração, com histórico praticamente vazio.

Já os dez processos recentes de Violência Doméstica Contra a Mulher têm média de 23,9. E o campeão, com 613, é de 2020.

O que pesa não é a idade: é a tramitação. Processo criminal em curso, com mandados, intimações e certidões, acumula andamento rápido.

Vale a regra geral: neste curso, quando a intuição e a medição divergirem, quem fica no texto é a medição.

O número que importa não é o do processo

613 movimentos é um número interessante. Não é o número que decide nada.

O que decide é: quanto disso entra numa Observation.

Lembre do ciclo da Unidade 1. A Tool devolve, o retorno vira Observation, a Observation entra no contexto do modelo, e o modelo escreve o passo seguinte lendo aquilo. Tudo o que a sua Tool devolve ocupa janela — e ocupa de novo a cada passo, porque o histórico vai junto.

Agora imagine a pergunta mais natural do mundo para um magistrado:

"Me mostre os últimos andamentos dos processos de violência doméstica dessa vara."

Cinco processos. Se a Tool devolver os cinco inteiros, com movimentos[] completo, isto é o que chega ao modelo:

bytes tokens (estimados)
5 processos inteiros 434.717 ~108.700
os mesmos 5, com 5 movimentos cada 1.446 ~360

Trezentas vezes menor.

Nota

Os tokens acima são estimativa por regra de bolso — cerca de 4 bytes por token em português. Não é contagem de tokenizador, e o número exato muda com o modelo.

Não muda a conclusão. Mesmo errando por um fator de dois, 108 mil tokens não cabem: o qwen2:7b que você rodou na Unidade 2 trabalha com janela de alguns milhares. A Observation não seria truncada de forma elegante — o passo simplesmente falha, ou o modelo recebe metade de um JSON e raciocina em cima do pedaço.

E repare no detalhe cruel: isso acontece num único passo. Não é degradação gradual que você percebe e corrige. É o primeiro for sobre cinco processos.

O mesmo número, dois documentos

Uma última descoberta da medição, e ela muda o desenho da Tool.

Procurando o campeão pelo número:

consultar({
    "size": 5,
    "query": {"term": {"numeroProcesso": "00186838120208190066"}},
    "_source": ["numeroProcesso", "grau", "classe.nome"],
})

Duas respostas:

{"numeroProcesso": "00186838120208190066", "grau": "G1",
 "classe": {"nome": "Procedimento Especial da Lei Antitóxicos"}}
{"numeroProcesso": "00186838120208190066", "grau": "G2",
 "classe": {"nome": "Apelação Criminal"}}

O mesmo processo, dois documentos no índice, classes diferentes. Em primeiro grau é ação penal; em segundo, apelação. Cada um com a sua própria lista de movimentos.

Isso é correto e é assim que o DataJud modela: um documento por processo por grau. Mas significa que movimentos[] não é uma coisa só, e que uma Tool que pegue o primeiro hit e ignore o resto vai devolver metade da história sem avisar ninguém.

É o mesmo tipo de erro da Unidade 2 — não dá exceção, dá resultado.

O que este capítulo estabeleceu

Quatro fatos, todos medidos, nenhum suposto:

  1. A mediana é de 74 movimentos por processo; o máximo encontrado, 613.
  2. Devolver movimentos[] inteiro de 5 processos põe ~108 mil tokens numa única Observation.
  3. O peso vem da tramitação, não da idade do processo.
  4. Um número de processo corresponde a um documento por grau, cada um com a sua lista.

O capítulo seguinte escreve a Tool que lida com isso. E a pergunta que ela responde não é "como trazer movimentos[]" — a API já traz, é fácil.

É: o que não devolver.