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.
This commit is contained in:
omp committed 2026-11-02 13:14:18 -03:00
1 parent 816e6be159
commit 0e498e2826
2 files changed
+125 -8

No files matched your search

+107
View File
@@ -356,3 +356,110 @@ def test_source_shows_the_cited_verse_not_the_context_window(settings):
assert source.reference == "Salmos 23:1"
assert source.text == "O Senhor é o meu pastor."
assert "Versículo anterior" in result.text, "o modelo recebe a janela completa"
def test_unsupported_citation_is_removed_whole_not_just_the_numbers():
"""Remover só o `1:1` deixa `(Gênesis )` pendurado na resposta do usuário.
Defeito observado na UI: a validação cortava o trecho `Capítulo:Versículo` e
preservava o nome do livro, que é justamente o que não tem sustentação.
"""
from app.chat import validate_citations
cleaned, citations = validate_citations(
"O Senhor é o meu pastor (Salmos 23:1) e no princípio criou Deus (Gênesis 1:1).",
{"Salmos 23:1"},
)
assert citations == ["Salmos 23:1"]
assert "Gênesis" not in cleaned, "o nome do livro não sustentado tem de sair junto"
assert cleaned == "O Senhor é o meu pastor (Salmos 23:1) e no princípio criou Deus."
def test_removal_does_not_leave_empty_parentheses():
from app.chat import validate_citations
cleaned, citations = validate_citations(
"Deus é amor (1 João 4:8) e o texto continua (Apocalipse 9:9).",
{"1 João 4:8"},
)
assert citations == ["1 João 4:8"]
assert "()" not in cleaned
assert "( )" not in cleaned
assert cleaned == "Deus é amor (1 João 4:8) e o texto continua."
def test_removal_of_a_range_citation_takes_the_whole_range():
from app.chat import validate_citations
cleaned, citations = validate_citations(
"Conforme Salmos 23:1-3 e também Jó 1:2-3, há esperança.",
{"Salmos 23:1-3"},
)
assert citations == ["Salmos 23:1-3"]
assert "Jó" not in cleaned
# --------------------------------------------------------------------------
# Livros numerados (18 dos 66: 1/2/3 Samuel, Reis, Crônicas, Coríntios, ...)
# --------------------------------------------------------------------------
@pytest.mark.parametrize(
"text,expected",
[
("1 João 4:8", ("1 João", 4, 8)),
("2 Reis 5:14", ("2 Reis", 5, 14)),
("1 Coríntios 13:4", ("1 Coríntios", 13, 4)),
("3 João 1:4", ("3 João", 1, 4)),
("I Samuel 17:4", ("1 Samuel", 17, 4)),
("II Crônicas 7:14", ("2 Crônicas", 7, 14)),
("1Sm 18:6", ("1 Samuel", 18, 6)),
("João 3:16", ("João", 3, 16)),
("Salmos 23:1", ("Salmos", 23, 1)),
],
)
def test_extract_reference_keeps_the_book_number(text, expected):
"""Perder o número devolve outro livro: '1 João 4:8' viraria João 4:8."""
from app.chat import extract_reference
assert extract_reference(text) == expected
def test_extract_reference_inside_a_sentence_keeps_the_book_number():
from app.chat import extract_reference
assert extract_reference("Leia 2 Reis 5:14 com atenção") == ("2 Reis", 5, 14)
def test_reference_after_another_reference_keeps_its_own_book():
"""O dígito do livro não pode ser confundido com o versículo anterior."""
from app.chat import extract_reference
assert extract_reference("Salmos 23:1 e também 1 João 4:8") == ("Salmos", 23, 1)
def test_supported_numbered_book_citation_is_kept():
"""Uma citação correta de livro numerado não pode ser apagada da resposta."""
from app.chat import validate_citations
cleaned, citations = validate_citations(
"Deus é amor (1 João 4:8).", {"1 João 4:8"}
)
assert citations == ["1 João 4:8"]
assert cleaned == "Deus é amor (1 João 4:8)."
def test_unsupported_numbered_book_citation_is_removed_whole():
from app.chat import validate_citations
cleaned, citations = validate_citations(
"Há esperança (2 Reis 5:14).", {"Salmos 23:1"}
)
assert citations == []
assert "Reis" not in cleaned
assert cleaned == "Há esperança."