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)
This commit is contained in:
omp committed 2026-11-02 11:16:07 -03:00
1 parent 621679cee5
commit 2043d5ddcf
34 files changed
+34561 -1842

No files matched your search

+4 -5
View File
@@ -6,21 +6,20 @@ export interface Source {
book: string;
chapter: number;
verse: number;
page: number;
file: string;
score: number;
}
export interface Status {
state: "ready" | "indexing" | "error";
pdfs_total: number;
pdfs_indexed: number;
sources_total: number;
sources_indexed: number;
chunks: number;
model: string;
last_indexed_at: string | null;
errors: string[];
pages_processed: number;
pages_total: number;
verses_indexed: number;
verses_total: number;
current_file: string | null;
}
+1 -3
View File
@@ -24,9 +24,7 @@ export function SourcesAccordion({ sources, citations = [] }: SourcesAccordionPr
<blockquote key={source.reference} className="sources__item">
<cite className="sources__reference">{source.reference}</cite>
<p className="sources__text">{source.text}</p>
<footer className="sources__meta">
página {source.page} · {source.file}
</footer>
<footer className="sources__meta">{source.file}</footer>
</blockquote>
))}
</div>
+17 -20
View File
@@ -7,7 +7,7 @@ interface StatusPanelProps {
}
/**
* Painel de status da indexação: quantos PDFs foram lidos, quantos vetores
* Painel de status da indexação: quantas fontes foram lidas, quantos versículos
* estão indexados e um botão para forçar a reindexação.
*/
export function StatusPanel({ status, onReindex, reindexing }: StatusPanelProps) {
@@ -19,6 +19,10 @@ export function StatusPanel({ status, onReindex, reindexing }: StatusPanelProps)
? "Indexando"
: "Erro";
const versesTotal = status?.verses_total ?? 0;
const versesIndexed = status?.verses_indexed ?? 0;
const percent = versesTotal > 0 ? Math.min(100, (versesIndexed / versesTotal) * 100) : 0;
return (
<section className="status" aria-label="Status da indexação">
<header className="status__header">
@@ -28,15 +32,15 @@ export function StatusPanel({ status, onReindex, reindexing }: StatusPanelProps)
<dl className="status__grid">
<div className="status__item">
<dt>PDFs lidos</dt>
<dt>Fontes lidas</dt>
<dd>
{status?.pdfs_indexed ?? 0}
{status ? ` / ${status.pdfs_total}` : ""}
{status?.sources_indexed ?? 0}
{status ? ` / ${status.sources_total}` : ""}
</dd>
</div>
<div className="status__item">
<dt>Vetores indexados</dt>
<dd>{status?.chunks ?? 0}</dd>
<dt>Versículos indexados</dt>
<dd>{(status?.chunks ?? 0).toLocaleString("pt-BR")}</dd>
</div>
<div className="status__item">
<dt>Modelo</dt>
@@ -47,25 +51,18 @@ export function StatusPanel({ status, onReindex, reindexing }: StatusPanelProps)
{status?.state === "indexing" && (
<div className="status__progress">
<div className="status__progress-label">
{status.current_file ? `Lendo ${status.current_file}` : "Lendo PDFs"}
{status.pages_total > 0 && ` · ${status.pages_processed}/${status.pages_total} páginas`}
{status.current_file ? `Indexando ${status.current_file}` : "Indexando corpus"}
{versesTotal > 0 && ` · ${versesIndexed.toLocaleString("pt-BR")}/${versesTotal.toLocaleString("pt-BR")} versículos`}
</div>
{status.pages_total > 0 && (
{versesTotal > 0 && (
<div
className="status__progress-bar"
role="progressbar"
aria-valuenow={status.pages_processed}
aria-valuenow={versesIndexed}
aria-valuemin={0}
aria-valuemax={status.pages_total}
aria-valuemax={versesTotal}
>
<span
style={{
width: `${Math.min(
100,
(status.pages_processed / status.pages_total) * 100,
)}%`,
}}
/>
<span style={{ width: `${percent}%` }} />
</div>
)}
</div>
@@ -85,7 +82,7 @@ export function StatusPanel({ status, onReindex, reindexing }: StatusPanelProps)
onClick={onReindex}
disabled={reindexing || state === "indexing"}
>
{reindexing ? "Reindexando…" : "Reindexar PDFs"}
{reindexing ? "Reindexando…" : "Reindexar corpus"}
</button>
</section>
);