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.
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.
- 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)