This release is a pre-release and may not be stable for production use.
JurisGrow
Expansão contínua de classes para classificadores de documentos jurídicos.
O JurisGrow envolve um XGBoost já treinado e permite acrescentar classes novas ao longo do tempo — sem retreinar o modelo original e medindo, a cada passo, quanto as classes antigas sofreram.
pip install jurisgrow
O JurisGrow não realiza nenhuma comunicação de rede por padrão: sem telemetria, sem analytics, sem chamadas a serviços externos, sem dependência de LLM.
O que é
Um classificador em produção é um ativo: foi validado, tem métricas conhecidas e alguém confia nele. Quando aparece um tipo de documento novo, a saída óbvia — retreinar tudo — custa caro, exige o dataset histórico completo e coloca em risco tudo o que já funcionava.
O JurisGrow é a alternativa. O modelo base vira conhecimento histórico congelado; cada classe nova ganha um residual pequeno que compete com ele na inferência. Replay dá memória, mineração de negativos duros dá precisão de fronteira, e a política de aceitação recusa qualquer residual que danifique as classes antigas.
BASE (congelado) estabilidade
+
RESIDUAIS plasticidade
+
REPLAY memória
+
NEGATIVOS DUROS fronteira
+
DETECÇÃO DE UNKNOWN mundo aberto
Por que não simplesmente retreinar o XGBoost?
| Retreino completo | JurisGrow | |
|---|---|---|
| Dataset histórico | necessário por inteiro | só o replay compacto |
| Custo por classe nova | treino completo | um residual pequeno |
| Risco às classes antigas | não medido por construção | medido e vetado — leia o ≥ |
| Modelo validado | substituído | preservado |
Retreinar continua sendo uma opção legítima — e às vezes a melhor. O JurisGrow existe para quando ela é cara, arriscada ou impossível.
Como envolvo um classificador existente
from jurisgrow import JurisGrowClassifier
from jurisgrow.core.features import TfidfFeaturePipeline
clf = JurisGrowClassifier.from_xgboost(
model=modelo_existente, # XGBClassifier já treinado
feature_pipeline=pipeline_existente,
class_names=["peticao_alpha", "certidao_beta", "decisao_gamma"],
)
clf.predict("documento fictício requerendo operação alfa")
# 'peticao_alpha'
Sem residuais, o JurisGrow é indistinguível do modelo base — mesmo rótulo, mesma probabilidade. Isso é verificado por teste.
O modelo base nunca é reajustado. Não existe caminho de código que chame
base.fit() depois do from_xgboost.
Documentos estruturados, não só texto
Se o seu classificador consome um documento estruturado através de um
ColumnTransformer, o JurisGrow o reaproveita como está:
from jurisgrow.core.features import SklearnColumnPipeline
pipeline = SklearnColumnPipeline(
column_transformer=transformador_ja_ajustado,
row_builder=minha_funcao_documento_para_tabela,
)
Como adiciono uma classe
# 1. memória das classes antigas, a partir de dados de TREINO
clf.fit_replay(textos_de_treino, rotulos_de_treino)
# 2. a classe nova
relatorio = clf.add_class("termo_delta", texts=documentos_novos)
print(relatorio.summary())
# [ACEITO] termo_delta: F1=0.9231 (n=80) | esquecimento=+0.0111 (proxy_fpr) |
# ativação falsa=≥0.0111 (limite inferior)
A operação é transacional. Se o treino falhar, ou se a política recusar, o classificador continua exatamente como estava — mesmas predições, bit a bit.
Como o esquecimento é medido
Toda add_class() responde duas perguntas, não uma:
relatorio.new_class_f1 # aprendemos o novo?
relatorio.forgetting # o antigo sofreu?
relatorio.forgetting_method # COMO esse número foi obtido
relatorio.false_residual_activation_rate # quanto o residual invadiu?
relatorio.false_activation_is_lower_bound # é estimativa ou piso?
Leia o ≥ antes de confiar no número
Se o replay foi construído com os documentos em que o modelo base treinou — que é o que o passo 1 acima faz, e o padrão da biblioteca — a taxa de ativação falsa é um limite inferior, não uma estimativa. O base acerta quase 100% nesses documentos, as meta-features do residual derivam dessas probabilidades, e o residual quase não ativa falso ali. Medido em corpus com classes sobrepostas: 0,03 relatado contra 0,44 real.
Particionar o replay não corrige — as duas metades estão igualmente contaminadas. Para uma medida honesta, reserve documentos das classes antigas que o base não viu:
clf.fit_replay(textos_holdout, rotulos_holdout, seen_by_base=False)
O summary() imprime ≥ sempre que o número for piso, e
forgetting_method diz se o esquecimento foi medido, aproximado, garantido por
construção, ou não avaliado.
A AcceptancePolicy recusa por padrão se o dano passar do orçamento — F1 alto
na classe nova não compra aceitação:
from jurisgrow import AcceptancePolicy
politica = AcceptancePolicy(
min_new_class_f1=0.85,
max_old_false_positive_rate=0.02, # guarda principal
max_old_class_f1_drop=0.01, # guarda secundária
)
relatorio = clf.add_class("termo_delta", texts=docs, policy=politica)
if not relatorio.accepted:
for razao in relatorio.reasons: # todas as razões, não a primeira
print(razao)
A guarda principal é a taxa de ativação falsa, não o macro-F1. Com n
classes, uma única delas colapsando move o macro-F1 em 1/n — com 31 classes
isso é 0,032, mais que o triplo de um orçamento de 0,01. A ativação falsa mede o
dano diretamente e não muda de escala com o número de classes.
Documentos de tipo desconhecido
Um classificador closed-set diante de um tipo novo devolve, em silêncio, o rótulo conhecido mais parecido — com confiança alta e acerto nulo.
clf.fit_unknown_detector(proba_conhecidos, proba_novos)
p = clf.predict_detailed(documento_estranho)
print(p.label, p.reason) # UNKNOWN low_margin
Os limiares são calibrados, nunca constantes arbitrárias. Sem calibração o detector funciona, mas emite aviso.
Como salvo e carrego
clf.save("meu_modelo")
recarregado = JurisGrowClassifier.load("meu_modelo")
XGBoost em UBJ nativo, limiares e manifesto em JSON legível, replay em matriz esparsa. O round-trip preserva rótulo e confiança.
O manifesto traz contains_raw_text explícito, para um revisor humano decidir
num relance se o artefato pode sair da instituição.
Segurança: arquivos
.joblibsão desserializados com pickle e podem executar código arbitrário. Carregue apenas modelos de origem confiável.
O JurisGrow envia meus dados para algum lugar?
Não. Sem telemetria, sem analytics, sem relatório remoto de erros, sem requisição HTTP, sem chamada a LLM, sem serviço de nuvem.
Por padrão o ReplayBuffer guarda vetores esparsos, não texto, e o
save() recusa gravar texto cru sem allow_raw_replay=True. Predições não
retêm a entrada. O relatório de treino registra contagens e nomes de classe,
nunca conteúdo de documento.
Tutoriais
Sete notebooks executáveis em notebooks/ — do primeiro modelo à
adição de classes e à escolha da ordem de decisão, com dados fictícios:
pip install "jurisgrow[notebooks]"
jupyter lab notebooks/
Desenvolvimento
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest # testes (dados sintéticos apenas)
ruff check . # lint
mypy jurisgrow # tipos
python scripts/privacy_check.py . # varredura antes de publicar
A especificação completa do projeto está em
docs/especificacao-original.md; o plano de
implementação por etapas, em docs/plano/.
Licença
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jurisgrow-0.1.0.dev0.tar.gz.
File metadata
- Download URL: jurisgrow-0.1.0.dev0.tar.gz
- Upload date:
- Size: 328.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ee10beffef5d5b7676a2d068b256df6fa798e65a8f92bf73fb6e0443c7ebc8c4
|
|
| MD5 |
98703b8a2e2accebc9a4693a26fb3c05
|
|
| BLAKE2b-256 |
738619f43dd3ea706b44b0e700c0ceb8019d4c5e664d5bc78a125b180812bdd9
|
File details
Details for the file jurisgrow-0.1.0.dev0-py3-none-any.whl.
File metadata
- Download URL: jurisgrow-0.1.0.dev0-py3-none-any.whl
- Upload date:
- Size: 172.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d3dfd03e7ff87646ec2c6ad134e92c767b38b81e8a3783d6d3f16cf13f5bab41
|
|
| MD5 |
6f41aad98d70cee64e16bc47c2877bfc
|
|
| BLAKE2b-256 |
79b7a4d45f102227d4c0f1dec9338ace345fbde62dd19175ef022faca2bc684e
|