# ============================================================
# 12 — Tracer-GAP: reconciling a financial gap with proof
#
# 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(12)

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. lançamentos contábeis de um período com divergência
tipos = [
    "lançamento de provisão para devedores duvidosos sem baixa correspondente",
    "estorno de receita registrado em competência distinta do pagamento",
    "transferência entre contas sem documento de suporte anexado",
    "apropriação de despesa antecipada em período incorreto",
    "ajuste de câmbio não refletido no resultado do exercício",
    "baixa de ativo imobilizado sem laudo de desmobilização",
    "reclassificação de despesa operacional para não operacional",
    "provisão trabalhista sem correspondência na folha",
]
lancamentos = [f"LAN-{i+1:05d} {random.choice(tipos)}" for i in range(800)]
corpus = np.stack([embed(l) for l in lancamentos]).astype(np.float32)

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

# 2. a pergunta da auditoria interna

consulta = "provisão sem baixa ou documento de suporte"
r = engine.search_with_commitment(embed(consulta), k=5)

print(f"entries in period      : {len(lancamentos)}")
print(f"suspicious entries     : {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("\nentries for review:")
for idx in r["indices"][:3]:
    print(f"  {lancamentos[idx][:72]}")

assert r["bound_violations"] == 0

# ── 3. o problema do "ou" ───────────────────────────────────
# Uma consulta com "ou" pede DUAS coisas distintas. O embedding soma
# os dois lados num vetor só e o resultado é a média — não a união.
# Aqui isso significa que as provisões sem baixa (a primeira condição)
# praticamente desaparecem do ranking, e só as transferências aparecem.
cat_a = "provisão sem baixa"
cat_b = "documento de suporte"

def _top(pergunta, k=3):
    rr = engine.search_with_commitment(embed(pergunta), k=k)
    return [lancamentos[i] for i in rr["indices"]]

print(f"\nwhat the 'or' hides:")
print(f"  condition A — {cat_a}      : "
      f"{sum(1 for l in lancamentos if 'sem baixa' in l)} lançamentos")
print(f"  condition B — {cat_b} : "
      f"{sum(1 for l in lancamentos if 'documento de suporte' in l)} lançamentos")
print(f"\n  combined query returns:")
for l in _top(consulta):
    print(f"    {l[:66]}")
print(f"\n  query A alone returns:")
for l in _top(cat_a):
    print(f"    {l[:66]}")

print("\nOK — exact top-5, proven. But 'or' needs TWO searches:")
print("one per condition. Summing both into one vector returns the mean,")
print("not the union — and the weaker condition disappears from the result.")
