Commit Graph
19 Commits
Author SHA1 Message Date
omp ebf430c535 docs: anotar plano como superado e registrar achados medidos da busca
README e .env.example passam a registrar, com os números medidos no índice
real de 31.098 versículos: o stemming (Mt 5:44 de 434º a 1º no BM25), o BM25
sobre o versículo em vez da janela (12º a 1º), a cota MAX_KEYWORD_ONLY e os dois
limites que nenhum ajuste de nota resolve — MIN_SIMILARITY não bloqueia off-topic
(0/6 em qualquer limiar de 0,30 a 0,50) e a nota BM25 não separa acerto de ruído
(Dt 28:27 a 1,000 do topo contra Lc 7:47 a 0,716).
2026-11-02 13:18:49 -03:00
omp 0e498e2826 fix: livros numerados em citações e remoção completa de citação não sustentada
Dois defeitos na validação de citações, ambos visíveis ao usuário:

1. _WORD_RE excluía dígitos, então o número do livro era descartado. Afeta os 18
   livros numerados (1/2/3 Samuel, Reis, Crônicas, Coríntios, Tessalonicenses,
   Timóteo, Pedro, João):
   - extract_reference('1 João 4:8') devolvia ('João',4,8) — perguntar por
     1 João 4:8 devolvia João 4:8 em silêncio pela busca determinística
   - '2 Reis 5:14' e '1 Coríntios 13:4' devolviam None — caiam na busca por
     similaridade em vez da exata
   - validate_citations removia citações CORRETAS: '(1 João 4:8)' sustentada
     pelas fontes era apagada e sobrava '(1 João )' na resposta
   O regex agora casa o número junto ('1 João', e '1Sm' aglutinado via \s*).

2. A remoção de citação não sustentada usava o span só do 'Capítulo:Versículo',
   deixando o nome do livro pendurado: '(Gênesis 1:1)' virava '(Gênesis )'.
   O span agora cobre do início do nome do livro ao fim dos versículos, e
   parênteses que ficam vazios são limpos.
2026-11-02 13:14:18 -03:00
omp 816e6be159 feat: cota lexical na fusão, recusa off-topic no prompt, docs medidas
Fusão:
- fuse_rankings() extrai a fusão RRF e aplica MAX_KEYWORD_ONLY (padrão 2):
  quantas posições podem ser ocupadas por trechos que só o BM25 achou. O que os
  dois lados confirmam nunca conta contra a cota.
- O controle é por cota de posições, não por piso de nota. Medido: a razão da
  nota BM25 não separa acerto de ruído — para 'como posso perdoar quem me
  feriu?', Deuteronômio 28:27 pontua 1,000 do topo e o correto Lucas 7:47 fica
  em 0,716 (rank 12). Um piso cortaria o certo e manteria o errado.
- Ponderar o RRF também não resolve: com peso 0,5 no BM25 os achados só-lexicais
  caem abaixo do 6º vetorial (0,5/61 < 1/66) e somem, levando junto Jó 3:13 e
  Joel 3:14, os melhores resultados medidos.

Portão off-topic:
- MIN_SIMILARITY não consegue implementar 'sem evidência'. Medido no índice
  completo: 0/6 perguntas off-topic bloqueadas em qualquer limiar de 0,30 a
  0,50 ('bolo de chocolate' bate 0,544 em Lv 7:14; 'cotação do dólar' 0,494).
  A recusa passa a ser instruída no SYSTEM_PROMPT e o limiar fica documentado
  como filtro de ruído.
2026-11-02 13:04:14 -03:00
omp 8f5e098d8f feat: stemming português no BM25 e índice lexical sobre o versículo
Dois defeitos medidos no índice real (31.098 versículos), ambos na metade
lexical da busca híbrida:

1. Sem normalização morfológica, 'amar' da pergunta nunca casava com 'amai' ou
   'amarás' do versículo. Para 'o que Jesus disse sobre amar os inimigos?',
   Mateus 5:44 ficava em 434º no BM25 enquanto Marcos 12:34 liderava por casar
   apenas 'jesus'+'disse'. keyword.py ganha um stemmer de sufixos em etapas
   (estilo RSLP, sem dependência externa) com guarda de comprimento mínimo do
   radical — sem ela 'deus' viraria 'd' e 'mar' viraria 'm'. Rank de Mt 5:44: 12º.

2. O BM25 indexava a janela de contexto. Os dois lados querem granularidades
   opostas: a janela dá tema ao embedding, mas multiplica os termos incidentais
   que o BM25 pontua. Indexando o versículo isolado, Mt 5:44 vai a 1º e os
   achados só-lexicais deixam de ser aleatórios ('ansioso e não consigo dormir'
   -> Jó 3:13 'teria dormido'; 'não sei que decisão tomar' -> Joel 3:14 'vale da
   decisão').

Colisão conhecida do stemming: 'luto' e 'lutavam' reduzem ao mesmo radical.
2026-11-02 12:39:54 -03:00
omp 957c73b09e feat: busca híbrida (vetorial + BM25 com fusão RRF) e janela de contexto
Recuperação:
- keyword.py: tokenizador PT (acentos, stopwords, dígitos), BM25 Okapi com
  índice invertido, e reciprocal_rank_fusion com desempate determinístico
- retrieval.py: VectorRetriever e HybridRetriever; fusão por posição, não por
  nota (cosseno 0..1 e BM25 sem teto são escalas incomparáveis)
- identidade de fusão é a coordenada (arquivo,livro,capítulo,versículo): o mesmo
  versículo chega das duas buscas com distance diferente e o dataclass nunca os
  igualaria, então o RRF não premiaria o que ambas confirmam
- ChatService recebe retriever injetado; referência explícita mantém precedência

Contexto:
- VerseChunk ganha context (janela de CONTEXT_WINDOW vizinhos por lado, dentro
  do livro, atravessando capítulos) e embedding_text
- store grava a janela como documento e o versículo em verse_text; a UI mostra o
  versículo citado e o modelo recebe a janela
- snapshot() nos dois stores: o BM25 é em memória e o Chroma persiste, então o
  arranque precisa reconstruir o índice lexical ou a busca degrada em silêncio

Motivação medida em 5703 versículos reais: a busca vetorial não liga 'estou com
medo do futuro' a 'não temas' (0,19, nível de par sem relação), e e5-small tem
faixa de similaridade de só 0,02 — inutilizaria o piso de evidência.
2026-11-02 11:57:19 -03:00
omp 2043d5ddcf refactor: troca PDF+OCR por corpus USFX em domínio público
- corpus/: Almeida (João Ferreira de Almeida) em USFX, domínio público,
  66 livros / 1189 capítulos / 31098 versículos, versionado com SHA-256
- app/corpus.py: parser USFX (texto no tail de <v/>), nomes canônicos dos
  livros, aliases (abreviaturas e numerais romanos), VerseChunk sem página
- indexer: descobre *.xml em CORPUS_DIR, progresso em versículos, lote
  configurável, manifesto incremental por SHA-256
- remove extraction.py, parallel.py, bible_parser.py, Tesseract e PyMuPDF
- campo page removido de Source/SearchResult/StatusResponse e do frontend
- embedder: propriedade name; status reporta o embedder em uso, não o do .env
- create_app constrói o embedder ANTES do store (o default HashEmbedder do
  VectorStore impedia o modelo configurado de ser carregado em produção)
- fallback do modelo agora aparece em /api/status.errors
- torch 2.6.0 + transformers 5.22.1 pinados (transformers 5.x exige torch>=2.6)
2026-11-02 11:16:07 -03:00
omp 621679cee5 fix: corrige bugs críticos de índice de páginas e cache de OCR
- C-1: corrige índice de páginas OCR'd (retorna page_number em vez de position)
- C-2: adiciona tratamento para falhas de OCR no cache (não distingue erro de não processado)
- Integra paralelização do OCR via subprocessos (parallel.py)
- Indexador usa extração paralela quando disponível
- Melhora relatório de progresso com contagem de páginas processadas
2026-11-01 20:01:28 -03:00
omp b3f8019e01 feat: report indexing progress with page counts in status panel 2026-11-01 19:32:33 -03:00
omp 8f79993646 fix(backend): detect corrupted PDF text layer by control character density 2026-11-01 19:30:07 -03:00
omp b25bf8ff7d feat: add Docker Compose, environment template and README 2026-11-01 19:25:55 -03:00
omp 7f4d4bde66 feat(frontend): add React chat UI with SSE parsing and source accordion 2026-11-01 19:25:35 -03:00
omp 7a0396422f feat(backend): add FastAPI routes with SSE chat and reindex guard 2026-11-01 19:24:20 -03:00
omp 99bc46f053 feat(backend): add chat service with evidence gating and citation validation 2026-11-01 19:19:45 -03:00
omp 9be09921bc feat(backend): add LLM provider adapters for Ollama, OpenAI, Groq and Anthropic 2026-11-01 19:18:07 -03:00
omp 1748b561bd feat(backend): add incremental indexer with manifest and partial states 2026-11-01 19:17:07 -03:00
omp abf7d802ab feat(backend): add embeddings and ChromaDB vector store 2026-11-01 19:16:40 -03:00
omp 6dc7f634ea feat(backend): add biblical parser with book/chapter/verse reconstruction 2026-11-01 19:15:22 -03:00
omp b75b746a40 feat(backend): add PDF extraction with OCR fallback and cache 2026-11-01 19:14:06 -03:00
omp 1b860dc599 chore: add gitignore 2026-11-01 19:12:42 -03:00