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).
11 KiB
Pastor Online Implementation Plan
Superado em 2026-11-02. As tarefas de ingestão deste plano (PDF → OCR → parser de índice → reconstrução de capítulo/versículo) foram substituídas por um corpus USFX em domínio público. O código correspondente (
extraction.py,bible_parser.py, Tesseract, PyMuPDF) foi removido. Leia a atualização no topo dedocs/superpowers/specs/2026-11-01-pastor-online-design.mdantes de seguir qualquer passo abaixo.
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Entregar um sistema web RAG "Pastor Online" containerizado, que indexa PDFs da Bíblia (incluindo o PDF fornecido, que exige OCR) e responde em chat pastoral ancorado exclusivamente nos versículos recuperados.
Architecture: Monólito modular FastAPI + ChromaDB persistente + sentence-transformers multilíngue. Adaptadores HTTP próprios (httpx) para Ollama, OpenAI, Groq e Anthropic. Frontend React/Vite servido por Nginx, que fala SSE com o backend. Pipeline de ingestão: PyMuPDF → (fallback Tesseract) → parser bíblico → embeddings → Chroma, com cache de OCR por página e manifesto incremental por SHA-256.
Tech Stack: Python 3.11, FastAPI, ChromaDB, sentence-transformers, PyMuPDF, Tesseract, httpx, pytest; React 18, TypeScript, Vite, Vitest, Nginx, Docker Compose.
Spec: docs/superpowers/specs/2026-11-01-pastor-online-design.md
Global Constraints
- Python 3.11 (
python:3.11-slim); React 18 + TypeScript; Docker Compose único. - PDFs em
./PDF, montados read-only em/data/pdfs;./pdfssuportado viaPDF_DIR. - Vetores em
/data/vector_db(volume nomeadovector_db), compartilhado com o cache de OCR. - System prompt pastoral é o da especificação, acrescido de escopo de crise (§10.2 do spec).
- Citações no formato único
Livro Capítulo:Versículo; validação antes de exibir (buffer completo). - Resposta sem evidência é fixa e não chama o LLM.
- Nenhum segredo no bundle do frontend; chaves só no backend via env.
- Reindexação concorrente → 409.
REINDEX_TOKENopcional protegePOST /api/reindex. - Texto do corpus indexado é dado, nunca instrução (prompt injection).
- OCR: 200 DPI,
tesseract -l por+eng --psm 6; cache por(sha256, página).
Review Focus
- PDF com camada textual corrompida (o arquivo real) ⇒ precisa cair no OCR e ainda assim produzir
Gênesis 1:1correto. - Pergunta com referência explícita (
João 3:16) ⇒ resposta ancorada no versículo pedido, não no mais similar. - Resposta do LLM com citação inventada ⇒ citação removida antes de chegar à tela.
- Índice do PDF ausente/ilegível ⇒ atribuição de livro por cabeçalho da página, sem perder o arquivo inteiro.
- Reindexação com PDF alterado ⇒ só o arquivo alterado é reprocessado; o resto do índice permanece.
- OCR de página que falha ⇒ arquivo fica
partial, páginas com falha são reprocessadas depois, backend não cai.
Task 1: Fundação do backend — config, modelos e extração de PDF
Files:
- Create:
backend/requirements.txt - Create:
backend/requirements-dev.txt - Create:
backend/pyproject.toml - Create:
backend/app/__init__.py - Create:
backend/app/config.py - Create:
backend/app/models.py - Create:
backend/app/extraction.py - Test:
backend/tests/test_extraction.py
Interfaces:
-
Produces:
Settings(pydantic-settings,get_settings() -> Settings);PageText(page:int, text:str);PdfExtractor(cache_dir: Path, dpi:int=200, ocr_lang:str="por+eng", ocr_timeout:int=120)comlegible_ratio(text:str)->float,extract_page(page, file_hash, page_number)->str,extract_document(path)->Iterator[PageText],ocr_page_image(pixmap)->str. -
Step 1: Escrever
backend/tests/test_extraction.py—legible_ratioalto para texto normal, baixo para controles C0; página legível não chama OCR; página corrompida chama OCR; cache reutilizado na segunda chamada; erro de OCR não propaga. -
Step 2:
pytest backend/tests/test_extraction.py -v→ FAIL (módulo inexistente). -
Step 3: Implementar
config.py,models.py,extraction.py. -
Step 4:
pytest backend/tests/test_extraction.py -v→ PASS. -
Step 5: Commit.
Task 2: Parser bíblico
Files:
- Create:
backend/app/bible_parser.py - Test:
backend/tests/test_bible_parser.py
Interfaces:
-
Consumes:
PageText(Task 1). -
Produces:
VerseChunk(book, chapter, verse, page, file, text);BOOKS(66 livros canônicos em português);parse_book_index(text)->dict[str,int];BibleParser.parse(pages, file_name, book_starts)->list[VerseChunk]. -
Step 1: Escrever testes — atribui livro por faixa de páginas; detecta
[N]; reconstróiGênesis 1:1; de-hifenizaesconde-\nme; número de versículo isolado vira versículo com texto na linha seguinte; página de índice/copyright fora da faixa é descartada. -
Step 2: Rodar → FAIL.
-
Step 3: Implementar
bible_parser.py(lista canônica + aliases; normalização sem acentos). -
Step 4: Rodar → PASS.
-
Step 5: Commit.
Task 3: Embeddings e ChromaDB
Files:
- Create:
backend/app/embedding.py - Create:
backend/app/vector_store.py - Test:
backend/tests/test_vector_store.py
Interfaces:
-
Produces:
Embedderprotocol (encode(texts:list[str])->list[list[float]]),SentenceTransformerEmbedder(model_name),HashEmbedder(testes, determinístico);VectorStore(collection_name, persist_dir)comupsert(chunks, vectors),delete_file(file),query(vector, n_results, min_similarity)->list[SearchResult],get_by_reference(book, chapter, verse)->list[SearchResult],count()->int. -
Step 1: Escrever testes — upsert + query;
delete_fileremove só do arquivo; filtro por referência;min_similaritydescarta resultado abaixo do limiar; vetores normalizados. -
Step 2: Rodar → FAIL.
-
Step 3: Implementar
embedding.pyevector_store.py(Chroma comhnsw:space=cosine). -
Step 4: Rodar → PASS.
-
Step 5: Commit.
Task 4: Indexador com manifesto e cache de OCR
Files:
- Create:
backend/app/indexer.py - Test:
backend/tests/test_indexer.py
Interfaces:
-
Consumes:
PdfExtractor,BibleParser,Embedder,VectorStore. -
Produces:
IndexStatus(state,pdfs_total,pdfs_indexed,chunks,last_indexed_at,errors);Indexer.reindex(force:bool=False)->IndexStatus; manifesto em/data/vector_db/manifest.jsoncom estadoscomplete|partial|error. -
Step 1: Escrever testes — só o PDF com SHA alterado é reprocessado; manifestos persistidos; arquivo com página falha fica
partial;count()reflete os chunks. -
Step 2: Rodar → FAIL.
-
Step 3: Implementar
indexer.py. -
Step 4: Rodar → PASS.
-
Step 5: Commit.
Task 5: Adaptadores de LLM com streaming
Files:
- Create:
backend/app/llm.py - Test:
backend/tests/test_llm.py
Interfaces:
-
Produces:
LLMProviderprotocol (async stream(messages, system)->AsyncIterator[str]);OllamaProvider,OpenAIProvider(cobre Groq viabase_url),AnthropicProvider,StubProvider;build_provider(settings)->LLMProvider;SYSTEM_PROMPTcom as diretrizes da spec + escopo de crise. -
Step 1: Escrever testes — cada adaptador converte o formato do provedor em deltas de texto;
build_providerescolhe peloLLM_PROVIDER; erro HTTP viraLLMProviderErrorsem expor a chave. -
Step 2: Rodar → FAIL.
-
Step 3: Implementar
llm.py. -
Step 4: Rodar → PASS.
-
Step 5: Commit.
Task 6: Chat — contexto, validação de citações e resposta pastoral
Files:
- Create:
backend/app/chat.py - Test:
backend/tests/test_chat.py
Interfaces:
-
Produces:
ChatService(settings, store, embedder, provider)comasync answer(message, history)->ChatResult;ChatResult(text, sources, citations);NO_EVIDENCE_MESSAGE;validate_citations(text, allowed)->tuple[str,list[str]];extract_reference(text)->tuple[str,int,int]|None. -
Step 1: Escrever testes — sem evidência ⇒ texto fixo e provider não chamado; citação não sustentada é removida; sustentada é preservada; pergunta com
João 3:16usa o chunk exato; system prompt contém as 4 diretrizes da spec e o escopo de crise. -
Step 2: Rodar → FAIL.
-
Step 3: Implementar
chat.py. -
Step 4: Rodar → PASS.
-
Step 5: Commit.
Task 7: API FastAPI
Files:
- Create:
backend/app/main.py - Test:
backend/tests/test_main.py
Interfaces:
-
Produces:
GET /api/health,GET /api/status,POST /api/reindex,POST /api/chat(SSE),GET /api/sources;create_app(settings=None, services=None)para testes. -
Step 1: Escrever testes — status; reindex 202/409/token; chat 422 para mensagem longa; formato SSE
sources→token→done; CORS restrito à origem configurada. -
Step 2: Rodar → FAIL.
-
Step 3: Implementar
main.py. -
Step 4: Rodar → PASS.
-
Step 5: Commit.
Task 8: Frontend React
Files:
- Create:
frontend/package.json,frontend/tsconfig.json,frontend/vite.config.ts,frontend/index.html,frontend/Dockerfile,frontend/nginx.conf - Create:
frontend/src/main.tsx,frontend/src/App.tsx,frontend/src/api.ts,frontend/src/styles.css,frontend/src/lib/sse.ts - Create:
frontend/src/components/ChatPanel.tsx,MessageBubble.tsx,SourcesAccordion.tsx,StatusPanel.tsx - Test:
frontend/src/lib/sse.test.ts,frontend/src/components/ChatPanel.test.tsx
Interfaces:
-
Consumes: API do backend (§8.2 do spec).
-
Produces:
parseSSE(stream:ReadableStream<Uint8Array>):AsyncGenerator<SSEEvent>;sendMessage(body):AsyncGenerator<SSEEvent>. -
Step 1: Escrever testes Vitest — parser SSE divide eventos; Enter envia, Shift+Enter quebra linha.
-
Step 2:
npm test→ FAIL. -
Step 3: Implementar o frontend.
-
Step 4:
npm test→ PASS;npm run build→ PASS. -
Step 5: Commit.
Task 9: Docker Compose, env e README
Files:
- Create:
docker-compose.yml,.env.example,.dockerignore,README.md - Modify:
backend/Dockerfile
Interfaces:
-
Produces: serviços
backend,frontend,ollama,ollama-pull; volumesvector_db,ollama;COMPOSE_PROFILES=localpadrão. -
Step 1: Escrever
README.mdcom preparación de.env, volume de PDFs, comandos de start, provedores, troubleshooting de OCR e de Ollama. -
Step 2:
docker compose config→ exit 0. -
Step 3:
docker compose build→ exit 0. -
Step 4: Commit.
Task 10: Verificação de integração
- Step 1:
docker compose up -deGET /api/health→ 200. - Step 2:
POST /api/reindex→ 202;/api/status→indexing→ready. - Step 3:
POST /api/chatcomLLM_PROVIDER=stub→sources+token+done. - Step 4: Abrir o frontend no navegador e verificar chat, citações, accordion e painel de status.
- Step 5:
pytest backend/tests -v→ 0 falhas;npm test→ 0 falhas.