Metadata-Version: 2.4
Name: sectionminer
Version: 0.1.3
Summary: Extract sections and subsections from academic PDFs
Author: SectionMiner Contributors
License-Expression: MIT
Project-URL: Homepage, https://github.com/ehodiogo/SectionMiner
Project-URL: Repository, https://github.com/ehodiogo/SectionMiner
Keywords: pdf,nlp,llm,sections,academic
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Text Processing
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pymupdf
Requires-Dist: langchain
Requires-Dist: langchain-openai
Requires-Dist: langchain-text-splitters
Requires-Dist: langchain-community
Requires-Dist: python-decouple
Requires-Dist: google-generativeai
Dynamic: license-file

# SectionMiner

Biblioteca Python para extrair secoes e subsecoes de PDFs academicos com heuristicas de layout + consolidacao por LLM.

Suporta dois backends de extracao de texto:

- `pymupdf` (local, via spans/layout do PDF)
- `gemini` (OCR/extracao via Google Gemini)

Em ambos os casos, a consolidacao final da arvore de secoes ainda e feita por OpenAI no estado atual do projeto.

## Visao geral

O fluxo do projeto e:

1. Ler spans do PDF com fonte/tamanho (`PyMuPDF`).
2. Detectar titulos provaveis (heading).
3. Montar secoes com intervalos de caracteres (`start`, `end`).
4. Enviar um indice de headings para LLM consolidar a arvore final.
5. Buscar texto de uma secao pelo titulo.

## Requisitos

- Python 3.10+
- Dependencias em `requirements.txt`
- Chaves de API:
  - `OPENAI_API_KEY` (obrigatoria para consolidacao LLM)
  - `GEMINI_API_KEY` (obrigatoria quando usar `extraction_backend="gemini"`)

## Instalacao (modo biblioteca)

```bash
git clone https://github.com/ehodiogo/SectionMiner.git
cd SectionMiner
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e .
```

Depois disso, voce pode importar com `from sectionminer import SectionMiner` em qualquer script do ambiente.

Tambem instala a CLI `sectionminer`.

Instalacao direta do PyPI (apos publicacao):

```bash
pip install sectionminer
```

## Configuracao das chaves

Os scripts usam `python-decouple` para ler variaveis de ambiente.

Opcao 1 (rapida, terminal atual):

```bash
export OPENAI_API_KEY="sua-chave-aqui"
export GEMINI_API_KEY="sua-chave-aqui"
```

Opcao 2 (`.env` na raiz do projeto):

```env
OPENAI_API_KEY=sua-chave-aqui
GEMINI_API_KEY=sua-chave-aqui
```

Se voce nao for usar Gemini, pode omitir `GEMINI_API_KEY`.

## Backends de extracao

- `pymupdf` (padrao): extrai texto a partir do layout interno do PDF.
- `gemini`: envia o PDF para o Gemini e usa o texto retornado para o pipeline.

Exemplo de construtor com Gemini:

```python
miner = SectionMiner(
    "files/Artigo_Provatis.pdf",
    api_key=openai_api_key,
    extraction_backend="gemini",
    gemini_api_key=gemini_api_key,
    gemini_model="gemini-2.5-flash-lite",
)
```

## Fluxo principal (o que o `test.py` faz)

Arquivo: `test.py` (exemplo usando a biblioteca)

1. Le a chave com `config("OPENAI_API_KEY")`.
2. Cria `SectionMiner("files/Artigo_Provatis.pdf", api_key)`.
3. Executa `extract_structure(return_tokens=True)` para obter:
   - arvore de secoes/subsecoes
   - uso de tokens/custo
4. Consulta uma secao (ex.: `introducao`) por offsets com `get_section_start_and_end_chars`.
5. Recupera texto completo com `get_full_text()` e fatia por `[start:end]`.
6. Reexecuta pipeline manual (`extract_blocks`, `build_full_text`, `build_sections`) e imprime secoes com `get_sections()` e `get_section_text()`.
7. Fecha o PDF com `close()` no `finally`.

Executar:

```bash
python3 test.py
```

Exemplo alternativo (arquivo dedicado em `examples/`):

```bash
python3 examples/basic_usage.py
```

## Fluxo com Gemini (o que o `test_gemini.py` faz)

Arquivo: `test_gemini.py`

1. Le `OPENAI_API_KEY` e `GEMINI_API_KEY`.
2. Cria `SectionMiner(..., extraction_backend="gemini", gemini_api_key=...)`.
3. Executa `extract_structure(return_tokens=True)` e imprime `usage`.
4. Consulta secao por offsets com `get_section_start_and_end_chars`.
5. Lista secoes com `get_sections()` e imprime com `get_section_text()`.

Executar:

```bash
python3 test_gemini.py
```

## CLI inicial

Comando raiz:

```bash
sectionminer --help
```

Observacao importante: a CLI atual usa backend `pymupdf` (ou `--heuristic-only`) e nao expoe flag para `extraction_backend="gemini"` ainda.
Para Gemini, use a API Python (`test_gemini.py` como referencia).

Extrair estrutura (com LLM):

```bash
sectionminer extract files/Artigo_Provatis.pdf --tokens --pretty
```

Extrair estrutura e mostrar custo total da chamada:

```bash
sectionminer extract files/Artigo_Provatis.pdf --show-cost --pretty
```

Extrair estrutura heuristica (sem LLM/OpenAI):

```bash
sectionminer extract files/Artigo_Provatis.pdf --heuristic-only --pretty
```

Salvar saida JSON em arquivo:

```bash
sectionminer extract files/Artigo_Provatis.pdf --heuristic-only --output out.json --pretty
```

Buscar texto de secao por titulo:

```bash
sectionminer section-text files/Artigo_Provatis.pdf "introducao"
```

Buscar texto e mostrar custo total da chamada:

```bash
sectionminer section-text files/Artigo_Provatis.pdf "introducao" --show-cost
```

Buscar texto de secao sem LLM (heuristica):

```bash
sectionminer section-text files/Artigo_Provatis.pdf "introducao" --heuristic-only
```

Observacao: `--show-cost` imprime o resumo de custo no `stderr` (nao polui JSON de saida).

## Exemplos de custo (extracao + obtencao dos textos)

Base de medicao local em `2026-03-21`, com modelo `gpt-4o-mini`, usando os PDFs em `files/`.

- `files/Artigo_Provatis.pdf`: 0.736 MB, 21 paginas
  - Extracao da estrutura: 2297 tokens, `US$ 0.00047505`
  - Obtencao dos textos das secoes no mesmo processo: `US$ 0.00` adicional (usa offsets locais)
- `files/Artigo_Mae.pdf`: 0.036 MB, 4 paginas
  - Extracao da estrutura: 356 tokens, `US$ 0.00005970`
  - Obtencao dos textos das secoes no mesmo processo: `US$ 0.00` adicional

Comando para reproduzir no seu ambiente:

```bash
sectionminer extract files/Artigo_Provatis.pdf --show-cost --pretty
sectionminer section-text files/Artigo_Provatis.pdf "introducao" --show-cost
```

## Funcoes principais da API (`SectionMiner`)

Arquivo: `sectionminer/miner.py`

- `extract_structure(return_tokens=False)`
  - Pipeline completo (extracao, deteccao, merge com LLM).
  - Retorna a arvore final; com `return_tokens=True`, retorna `(arvore, usage)`.

- `get_section_start_and_end_chars(title)`
  - Retorna `(start, end)` da secao localizada por titulo.
  - Bom para recortar diretamente em `get_full_text()`.

- `get_full_text()`
  - Retorna o texto linear completo do PDF processado.

- `get_section_text(title)`
  - Busca no tree consolidado e devolve o texto da secao.

- `get_sections()`
  - Retorna lista de titulos detectados a partir das estruturas internas.

- `extract_blocks()`, `build_full_text()`, `build_sections()`
  - Etapas internas do pipeline usadas no `test.py` para depuracao/inspecao.

- `close()`
  - Fecha o documento PDF aberto em memoria.

## Exemplo minimo (mesma ideia do teste)

```python
import json
from decouple import config
from sectionminer import SectionMiner

api_key = config("OPENAI_API_KEY")
miner = SectionMiner("files/Artigo_Provatis.pdf", api_key)

try:
    structure, tokens = miner.extract_structure(return_tokens=True)
    print(tokens)
    print(json.dumps(structure, indent=2, ensure_ascii=False))

    start, end = miner.get_section_start_and_end_chars("introducao")
    if start is not None and end is not None:
        print(miner.get_full_text()[start:end][:800])

    print(miner.get_section_text("conclusao"))
finally:
    miner.close()
```

Exemplo minimo com Gemini:

```python
from decouple import config
from sectionminer import SectionMiner

openai_api_key = config("OPENAI_API_KEY")
gemini_api_key = config("GEMINI_API_KEY")

miner = SectionMiner(
    "files/Artigo_Provatis.pdf",
    api_key=openai_api_key,
    extraction_backend="gemini",
    gemini_api_key=gemini_api_key,
)

try:
    structure, usage = miner.extract_structure(return_tokens=True)
    print(usage)
    print(structure.get("title"))
finally:
    miner.close()
```

## Estrutura do projeto

```text
SectionMiner/
  sectionminer/
    __init__.py  # API publica da biblioteca
    miner.py     # classe SectionMiner
    client.py    # cliente LLM e merge da arvore
    prompts.py   # prompt de consolidacao
  base.py        # compatibilidade com import legado
  client.py      # compatibilidade com import legado
  prompts.py     # compatibilidade com import legado
  test.py        # fluxo de uso principal
  test_gemini.py # fluxo com backend Gemini
  examples/      # exemplos prontos de execucao
  files/         # PDFs de exemplo
```

## Problemas comuns

### 1) "As secoes estao vindo quebradas"

- Revise filtros em `_is_noise_heading` e `_looks_like_heading` em `sectionminer/miner.py`.
- Ajuste threshold em `_detect_threshold` para o padrao do seu PDF.
- PDFs com layout irregular (duas colunas, rodape intrusivo, OCR ruim) tendem a piorar a deteccao.

### 2) Secao nao encontrada por titulo

- Tente variacao sem acento/caixa (a busca normaliza texto).
- Verifique os titulos retornados por `get_sections()`.

### 3) Erro de chave OpenAI

- Confirme `OPENAI_API_KEY` no mesmo ambiente da execucao.
- Se usar `.env`, confirme que esta na raiz do projeto.

## TODO (coisas a fazer)

- [ ] Criar testes automatizados para `detect_headings`, `build_sections` e `get_section_text`.
- [ ] Adicionar modo sem LLM (somente heuristica local) para uso offline.
- [x] Criar CLI inicial: `sectionminer extract arquivo.pdf --output out.json`.
- [ ] Expor parametros de heuristica por configuracao (threshold, filtros de ruido).
- [X] Melhorar merge para manter apenas secoes/subsecoes validas (sem fragmentos quebrados).

