# ============================================================
# 11 — Tracer-JUS: contract due diligence with proof of exclusion
#
# Colab:  !pip install winnex-madhava winnex-tracer
# ============================================================

# instalação automática — Colab, Jupyter e script local.
# No Colab, "!pip" dentro de comentário não executa: a instalação
# precisa acontecer de dentro da célula, antes dos imports.
import importlib
import subprocess
import sys

for _pkg in ["winnex-madhava", "winnex-tracer"]:
    _mod = _pkg.replace("-", "_")
    try:
        __import__(_mod)
        continue
    except ImportError:
        pass
    _r = subprocess.run(
        [sys.executable, "-m", "pip", "install", "-q", "--upgrade", _pkg],
        capture_output=True, text=True
    )
    # O interpretador do Colab já está em execução quando o pip termina.
    # Sem invalidar o cache, o import abaixo pode não enxergar o pacote.
    importlib.invalidate_caches()
    try:
        __import__(_mod)
    except ImportError:
        sys.stderr.write(_r.stdout or "")
        sys.stderr.write(_r.stderr or "")
        raise SystemExit(
            "\n\nNão foi possível importar " + _pkg + " após instalar.\n"
            "No Colab: menu 'Ambiente de execução' -> 'Reiniciar sessão',\n"
            "e execute esta célula novamente.\n"
        )

import random
import numpy as np
import winnex_madhava as mad
from winnex_tracer.core import InstrumentedEngine

DIM = 128
random.seed(11)

def embed(t):
    v = np.zeros(DIM, dtype=np.float32)
    for tok in t.lower().split():
        for i, ch in enumerate(tok):
            v[(ord(ch) * 31 + i * 7) % DIM] += 1.0
    n = np.linalg.norm(v)
    return v / n if n else v

# 1. cláusulas de contratos em uma due diligence
tipos_clausula = [
    "cláusula de rescisão antecipada com multa proporcional ao prazo restante",
    "cláusula de confidencialidade com vigência de cinco anos após o término",
    "cláusula de reajuste anual pelo IGP-M ou índice equivalente",
    "cláusula de garantia mediante fiança bancária ou seguro garantia",
    "cláusula arbitral com sede em câmara arbitral de São Paulo",
    "cláusula de limitação de responsabilidade ao valor do contrato",
    "cláusula de não concorrência por dois anos na mesma região",
    "cláusula de foro de eleição na comarca da sede da contratante",
    "cláusula de cessão de direitos mediante anuência escrita",
    "cláusula de força maior excluindo eventos previsíveis",
]
clausulas = [f"CLA-{i+1:04d} {random.choice(tipos_clausula)}" for i in range(500)]
corpus = np.stack([embed(c) for c in clausulas]).astype(np.float32)

# 2. instrumenta

base = mad.build_engine(corpus, dim=DIM, k=4, k1_fraction=1.0,
                        postfilter=True, audit_exhaustive=True)
engine = InstrumentedEngine(base, mode="commitment")

# 3. a pergunta que a due diligence faz
consulta = "risco de rescisão antecipada e multa aplicável"
r = engine.search_with_commitment(embed(consulta), k=4)

print(f"clauses in repository : {len(clausulas)}")
print(f"clauses at risk       : {len(r['indices'])}")
print(f"excluded with proof   : {r['total_excluded_count']}")
print(f"guarantee             : {r['recall_guarantee']}")
print(f"bound violations      : {r['bound_violations']}")

print("\nclauses requiring attention:")
for idx in r["indices"][:3]:
    print(f"  {clausulas[idx][:72]}")

assert r["bound_violations"] == 0

# ── 4. o limite honesto do k ────────────────────────────────
# A garantia prova que o top-k está certo. Ela NÃO prova que todo
# documento relevante está no top-k — isso é decidido por k, não pelo
# motor. Rodar de novo com k maior mostra a diferença, e é o número
# que a due diligence precisa antes de assinar.
risco_no_corpus = sum(1 for c in clausulas if "rescisão antecipada" in c)

print(f"\nwarning — what k=4 means here:")
print(f"  termination clauses in repository    : {risco_no_corpus}")
print(f"  returned with k=4                    : {len(r['indices'])}")
print(f"  NOT examined                         : "
      f"{risco_no_corpus - len(r['indices'])}")

# ARMADILHA: `k` é fixado no build_engine e funciona como TETO.
# Pedir k maior no retrieve NÃO aumenta o resultado — ele é truncado
# silenciosamente no k do build. Verificado: build k=4 + retrieve k=54
# devolve 4, sem erro e sem aviso.
base_amplo = mad.build_engine(corpus, dim=DIM, k=risco_no_corpus,
                              k1_fraction=1.0, postfilter=True,
                              audit_exhaustive=True)
engine_amplo = InstrumentedEngine(base_amplo, mode="commitment")
r_amplo = engine_amplo.search_with_commitment(embed(consulta), k=risco_no_corpus)
print(f"  returned with engine k={risco_no_corpus:<3d}          : "
      f"{len(r_amplo['indices'])}")

print("\nOK — exact top-4, proven. Coverage is the analyst's decision.")
print("For 'nothing omitted': rebuild the engine with k >= the number of")
print("risk clauses — raising k in retrieve alone has no effect.")
