sectionminer 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sectionminer-0.1.1/LICENSE +22 -0
- sectionminer-0.1.1/PKG-INFO +385 -0
- sectionminer-0.1.1/README.md +356 -0
- sectionminer-0.1.1/pyproject.toml +46 -0
- sectionminer-0.1.1/sectionminer/__init__.py +7 -0
- sectionminer-0.1.1/sectionminer/__main__.py +6 -0
- sectionminer-0.1.1/sectionminer/cli.py +147 -0
- sectionminer-0.1.1/sectionminer/client.py +69 -0
- sectionminer-0.1.1/sectionminer/miner.py +422 -0
- sectionminer-0.1.1/sectionminer/prompts.py +74 -0
- sectionminer-0.1.1/sectionminer.egg-info/PKG-INFO +385 -0
- sectionminer-0.1.1/sectionminer.egg-info/SOURCES.txt +15 -0
- sectionminer-0.1.1/sectionminer.egg-info/dependency_links.txt +1 -0
- sectionminer-0.1.1/sectionminer.egg-info/entry_points.txt +2 -0
- sectionminer-0.1.1/sectionminer.egg-info/requires.txt +7 -0
- sectionminer-0.1.1/sectionminer.egg-info/top_level.txt +1 -0
- sectionminer-0.1.1/setup.cfg +4 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SectionMiner Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sectionminer
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Extract sections and subsections from academic PDFs
|
|
5
|
+
Author: SectionMiner Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ehodiogo/SectionMiner
|
|
8
|
+
Project-URL: Repository, https://github.com/ehodiogo/SectionMiner
|
|
9
|
+
Keywords: pdf,nlp,llm,sections,academic
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Text Processing
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: pymupdf
|
|
22
|
+
Requires-Dist: langchain
|
|
23
|
+
Requires-Dist: langchain-openai
|
|
24
|
+
Requires-Dist: langchain-text-splitters
|
|
25
|
+
Requires-Dist: langchain-community
|
|
26
|
+
Requires-Dist: python-decouple
|
|
27
|
+
Requires-Dist: google-generativeai
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# SectionMiner
|
|
31
|
+
|
|
32
|
+
Biblioteca Python para extrair secoes e subsecoes de PDFs academicos com heuristicas de layout + consolidacao por LLM.
|
|
33
|
+
|
|
34
|
+
Suporta dois backends de extracao de texto:
|
|
35
|
+
|
|
36
|
+
- `pymupdf` (local, via spans/layout do PDF)
|
|
37
|
+
- `gemini` (OCR/extracao via Google Gemini)
|
|
38
|
+
|
|
39
|
+
Em ambos os casos, a consolidacao final da arvore de secoes ainda e feita por OpenAI no estado atual do projeto.
|
|
40
|
+
|
|
41
|
+
## Visao geral
|
|
42
|
+
|
|
43
|
+
O fluxo do projeto e:
|
|
44
|
+
|
|
45
|
+
1. Ler spans do PDF com fonte/tamanho (`PyMuPDF`).
|
|
46
|
+
2. Detectar titulos provaveis (heading).
|
|
47
|
+
3. Montar secoes com intervalos de caracteres (`start`, `end`).
|
|
48
|
+
4. Enviar um indice de headings para LLM consolidar a arvore final.
|
|
49
|
+
5. Buscar texto de uma secao pelo titulo.
|
|
50
|
+
|
|
51
|
+
## Requisitos
|
|
52
|
+
|
|
53
|
+
- Python 3.10+
|
|
54
|
+
- Dependencias em `requirements.txt`
|
|
55
|
+
- Chaves de API:
|
|
56
|
+
- `OPENAI_API_KEY` (obrigatoria para consolidacao LLM)
|
|
57
|
+
- `GEMINI_API_KEY` (obrigatoria quando usar `extraction_backend="gemini"`)
|
|
58
|
+
|
|
59
|
+
## Instalacao (modo biblioteca)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/ehodiogo/SectionMiner.git
|
|
63
|
+
cd SectionMiner
|
|
64
|
+
python3 -m venv .venv
|
|
65
|
+
source .venv/bin/activate
|
|
66
|
+
pip install -r requirements.txt
|
|
67
|
+
pip install -e .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Depois disso, voce pode importar com `from sectionminer import SectionMiner` em qualquer script do ambiente.
|
|
71
|
+
|
|
72
|
+
Tambem instala a CLI `sectionminer`.
|
|
73
|
+
|
|
74
|
+
Instalacao direta do PyPI (apos publicacao):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install sectionminer
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Configuracao das chaves
|
|
81
|
+
|
|
82
|
+
Os scripts usam `python-decouple` para ler variaveis de ambiente.
|
|
83
|
+
|
|
84
|
+
Opcao 1 (rapida, terminal atual):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
export OPENAI_API_KEY="sua-chave-aqui"
|
|
88
|
+
export GEMINI_API_KEY="sua-chave-aqui"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Opcao 2 (`.env` na raiz do projeto):
|
|
92
|
+
|
|
93
|
+
```env
|
|
94
|
+
OPENAI_API_KEY=sua-chave-aqui
|
|
95
|
+
GEMINI_API_KEY=sua-chave-aqui
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Se voce nao for usar Gemini, pode omitir `GEMINI_API_KEY`.
|
|
99
|
+
|
|
100
|
+
## Backends de extracao
|
|
101
|
+
|
|
102
|
+
- `pymupdf` (padrao): extrai texto a partir do layout interno do PDF.
|
|
103
|
+
- `gemini`: envia o PDF para o Gemini e usa o texto retornado para o pipeline.
|
|
104
|
+
|
|
105
|
+
Exemplo de construtor com Gemini:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
miner = SectionMiner(
|
|
109
|
+
"files/Artigo_Provatis.pdf",
|
|
110
|
+
api_key=openai_api_key,
|
|
111
|
+
extraction_backend="gemini",
|
|
112
|
+
gemini_api_key=gemini_api_key,
|
|
113
|
+
gemini_model="gemini-2.5-flash-lite",
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Fluxo principal (o que o `test.py` faz)
|
|
118
|
+
|
|
119
|
+
Arquivo: `test.py` (exemplo usando a biblioteca)
|
|
120
|
+
|
|
121
|
+
1. Le a chave com `config("OPENAI_API_KEY")`.
|
|
122
|
+
2. Cria `SectionMiner("files/Artigo_Provatis.pdf", api_key)`.
|
|
123
|
+
3. Executa `extract_structure(return_tokens=True)` para obter:
|
|
124
|
+
- arvore de secoes/subsecoes
|
|
125
|
+
- uso de tokens/custo
|
|
126
|
+
4. Consulta uma secao (ex.: `introducao`) por offsets com `get_section_start_and_end_chars`.
|
|
127
|
+
5. Recupera texto completo com `get_full_text()` e fatia por `[start:end]`.
|
|
128
|
+
6. Reexecuta pipeline manual (`extract_blocks`, `build_full_text`, `build_sections`) e imprime secoes com `get_sections()` e `get_section_text()`.
|
|
129
|
+
7. Fecha o PDF com `close()` no `finally`.
|
|
130
|
+
|
|
131
|
+
Executar:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
python3 test.py
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Exemplo alternativo (arquivo dedicado em `examples/`):
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
python3 examples/basic_usage.py
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Fluxo com Gemini (o que o `test_gemini.py` faz)
|
|
144
|
+
|
|
145
|
+
Arquivo: `test_gemini.py`
|
|
146
|
+
|
|
147
|
+
1. Le `OPENAI_API_KEY` e `GEMINI_API_KEY`.
|
|
148
|
+
2. Cria `SectionMiner(..., extraction_backend="gemini", gemini_api_key=...)`.
|
|
149
|
+
3. Executa `extract_structure(return_tokens=True)` e imprime `usage`.
|
|
150
|
+
4. Consulta secao por offsets com `get_section_start_and_end_chars`.
|
|
151
|
+
5. Lista secoes com `get_sections()` e imprime com `get_section_text()`.
|
|
152
|
+
|
|
153
|
+
Executar:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
python3 test_gemini.py
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## CLI inicial
|
|
160
|
+
|
|
161
|
+
Comando raiz:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
sectionminer --help
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Observacao importante: a CLI atual usa backend `pymupdf` (ou `--heuristic-only`) e nao expoe flag para `extraction_backend="gemini"` ainda.
|
|
168
|
+
Para Gemini, use a API Python (`test_gemini.py` como referencia).
|
|
169
|
+
|
|
170
|
+
Extrair estrutura (com LLM):
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
sectionminer extract files/Artigo_Provatis.pdf --tokens --pretty
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Extrair estrutura e mostrar custo total da chamada:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
sectionminer extract files/Artigo_Provatis.pdf --show-cost --pretty
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Extrair estrutura heuristica (sem LLM/OpenAI):
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
sectionminer extract files/Artigo_Provatis.pdf --heuristic-only --pretty
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Salvar saida JSON em arquivo:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
sectionminer extract files/Artigo_Provatis.pdf --heuristic-only --output out.json --pretty
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Buscar texto de secao por titulo:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
sectionminer section-text files/Artigo_Provatis.pdf "introducao"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Buscar texto e mostrar custo total da chamada:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
sectionminer section-text files/Artigo_Provatis.pdf "introducao" --show-cost
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Buscar texto de secao sem LLM (heuristica):
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
sectionminer section-text files/Artigo_Provatis.pdf "introducao" --heuristic-only
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Observacao: `--show-cost` imprime o resumo de custo no `stderr` (nao polui JSON de saida).
|
|
213
|
+
|
|
214
|
+
## Exemplos de custo (extracao + obtencao dos textos)
|
|
215
|
+
|
|
216
|
+
Base de medicao local em `2026-03-21`, com modelo `gpt-4o-mini`, usando os PDFs em `files/`.
|
|
217
|
+
|
|
218
|
+
- `files/Artigo_Provatis.pdf`: 0.736 MB, 21 paginas
|
|
219
|
+
- Extracao da estrutura: 2297 tokens, `US$ 0.00047505`
|
|
220
|
+
- Obtencao dos textos das secoes no mesmo processo: `US$ 0.00` adicional (usa offsets locais)
|
|
221
|
+
- `files/Artigo_Mae.pdf`: 0.036 MB, 4 paginas
|
|
222
|
+
- Extracao da estrutura: 356 tokens, `US$ 0.00005970`
|
|
223
|
+
- Obtencao dos textos das secoes no mesmo processo: `US$ 0.00` adicional
|
|
224
|
+
|
|
225
|
+
Comando para reproduzir no seu ambiente:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
sectionminer extract files/Artigo_Provatis.pdf --show-cost --pretty
|
|
229
|
+
sectionminer section-text files/Artigo_Provatis.pdf "introducao" --show-cost
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Funcoes principais da API (`SectionMiner`)
|
|
233
|
+
|
|
234
|
+
Arquivo: `sectionminer/miner.py`
|
|
235
|
+
|
|
236
|
+
- `extract_structure(return_tokens=False)`
|
|
237
|
+
- Pipeline completo (extracao, deteccao, merge com LLM).
|
|
238
|
+
- Retorna a arvore final; com `return_tokens=True`, retorna `(arvore, usage)`.
|
|
239
|
+
|
|
240
|
+
- `get_section_start_and_end_chars(title)`
|
|
241
|
+
- Retorna `(start, end)` da secao localizada por titulo.
|
|
242
|
+
- Bom para recortar diretamente em `get_full_text()`.
|
|
243
|
+
|
|
244
|
+
- `get_full_text()`
|
|
245
|
+
- Retorna o texto linear completo do PDF processado.
|
|
246
|
+
|
|
247
|
+
- `get_section_text(title)`
|
|
248
|
+
- Busca no tree consolidado e devolve o texto da secao.
|
|
249
|
+
|
|
250
|
+
- `get_sections()`
|
|
251
|
+
- Retorna lista de titulos detectados a partir das estruturas internas.
|
|
252
|
+
|
|
253
|
+
- `extract_blocks()`, `build_full_text()`, `build_sections()`
|
|
254
|
+
- Etapas internas do pipeline usadas no `test.py` para depuracao/inspecao.
|
|
255
|
+
|
|
256
|
+
- `close()`
|
|
257
|
+
- Fecha o documento PDF aberto em memoria.
|
|
258
|
+
|
|
259
|
+
## Exemplo minimo (mesma ideia do teste)
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
import json
|
|
263
|
+
from decouple import config
|
|
264
|
+
from sectionminer import SectionMiner
|
|
265
|
+
|
|
266
|
+
api_key = config("OPENAI_API_KEY")
|
|
267
|
+
miner = SectionMiner("files/Artigo_Provatis.pdf", api_key)
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
structure, tokens = miner.extract_structure(return_tokens=True)
|
|
271
|
+
print(tokens)
|
|
272
|
+
print(json.dumps(structure, indent=2, ensure_ascii=False))
|
|
273
|
+
|
|
274
|
+
start, end = miner.get_section_start_and_end_chars("introducao")
|
|
275
|
+
if start is not None and end is not None:
|
|
276
|
+
print(miner.get_full_text()[start:end][:800])
|
|
277
|
+
|
|
278
|
+
print(miner.get_section_text("conclusao"))
|
|
279
|
+
finally:
|
|
280
|
+
miner.close()
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Exemplo minimo com Gemini:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
from decouple import config
|
|
287
|
+
from sectionminer import SectionMiner
|
|
288
|
+
|
|
289
|
+
openai_api_key = config("OPENAI_API_KEY")
|
|
290
|
+
gemini_api_key = config("GEMINI_API_KEY")
|
|
291
|
+
|
|
292
|
+
miner = SectionMiner(
|
|
293
|
+
"files/Artigo_Provatis.pdf",
|
|
294
|
+
api_key=openai_api_key,
|
|
295
|
+
extraction_backend="gemini",
|
|
296
|
+
gemini_api_key=gemini_api_key,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
structure, usage = miner.extract_structure(return_tokens=True)
|
|
301
|
+
print(usage)
|
|
302
|
+
print(structure.get("title"))
|
|
303
|
+
finally:
|
|
304
|
+
miner.close()
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Estrutura do projeto
|
|
308
|
+
|
|
309
|
+
```text
|
|
310
|
+
SectionMiner/
|
|
311
|
+
sectionminer/
|
|
312
|
+
__init__.py # API publica da biblioteca
|
|
313
|
+
miner.py # classe SectionMiner
|
|
314
|
+
client.py # cliente LLM e merge da arvore
|
|
315
|
+
prompts.py # prompt de consolidacao
|
|
316
|
+
base.py # compatibilidade com import legado
|
|
317
|
+
client.py # compatibilidade com import legado
|
|
318
|
+
prompts.py # compatibilidade com import legado
|
|
319
|
+
test.py # fluxo de uso principal
|
|
320
|
+
test_gemini.py # fluxo com backend Gemini
|
|
321
|
+
examples/ # exemplos prontos de execucao
|
|
322
|
+
files/ # PDFs de exemplo
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Problemas comuns
|
|
326
|
+
|
|
327
|
+
### 1) "As secoes estao vindo quebradas"
|
|
328
|
+
|
|
329
|
+
- Revise filtros em `_is_noise_heading` e `_looks_like_heading` em `sectionminer/miner.py`.
|
|
330
|
+
- Ajuste threshold em `_detect_threshold` para o padrao do seu PDF.
|
|
331
|
+
- PDFs com layout irregular (duas colunas, rodape intrusivo, OCR ruim) tendem a piorar a deteccao.
|
|
332
|
+
|
|
333
|
+
### 2) Secao nao encontrada por titulo
|
|
334
|
+
|
|
335
|
+
- Tente variacao sem acento/caixa (a busca normaliza texto).
|
|
336
|
+
- Verifique os titulos retornados por `get_sections()`.
|
|
337
|
+
|
|
338
|
+
### 3) Erro de chave OpenAI
|
|
339
|
+
|
|
340
|
+
- Confirme `OPENAI_API_KEY` no mesmo ambiente da execucao.
|
|
341
|
+
- Se usar `.env`, confirme que esta na raiz do projeto.
|
|
342
|
+
|
|
343
|
+
## TODO (coisas a fazer)
|
|
344
|
+
|
|
345
|
+
- [ ] Criar testes automatizados para `detect_headings`, `build_sections` e `get_section_text`.
|
|
346
|
+
- [ ] Adicionar modo sem LLM (somente heuristica local) para uso offline.
|
|
347
|
+
- [x] Criar CLI inicial: `sectionminer extract arquivo.pdf --output out.json`.
|
|
348
|
+
- [ ] Expor parametros de heuristica por configuracao (threshold, filtros de ruido).
|
|
349
|
+
- [ ] Melhorar merge para manter apenas secoes/subsecoes validas (sem fragmentos quebrados).
|
|
350
|
+
|
|
351
|
+
## Publicacao (preparo inicial)
|
|
352
|
+
|
|
353
|
+
O projeto ja possui `pyproject.toml`, `LICENSE` e entrypoint de CLI.
|
|
354
|
+
|
|
355
|
+
Gerar artefatos:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
python3 -m pip install --upgrade build twine
|
|
359
|
+
python3 -m build
|
|
360
|
+
python3 -m twine check dist/*
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Teste local do wheel:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
python3 -m pip install dist/*.whl
|
|
367
|
+
sectionminer --help
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Publicar (TestPyPI primeiro, recomendado):
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
python3 -m twine upload --repository testpypi dist/*
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Publicar no PyPI oficial:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
python3 -m twine upload dist/*
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Licenca
|
|
383
|
+
|
|
384
|
+
MIT (arquivo `LICENSE`).
|
|
385
|
+
|