pdf-file-renamer 0.5.0__tar.gz → 0.6.0__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.
Files changed (41) hide show
  1. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/PKG-INFO +38 -10
  2. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/README.md +36 -9
  3. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/coverage.xml +230 -102
  4. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/pyproject.toml +3 -1
  5. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/__init__.py +1 -1
  6. pdf_file_renamer-0.6.0/src/pdf_file_renamer/application/filename_service.py +172 -0
  7. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/application/pdf_rename_workflow.py +27 -2
  8. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/domain/models.py +29 -0
  9. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/domain/ports.py +18 -1
  10. pdf_file_renamer-0.6.0/src/pdf_file_renamer/infrastructure/doi/__init__.py +5 -0
  11. pdf_file_renamer-0.6.0/src/pdf_file_renamer/infrastructure/doi/pdf2doi_extractor.py +129 -0
  12. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/presentation/cli.py +5 -0
  13. pdf_file_renamer-0.5.0/src/pdf_file_renamer/application/filename_service.py +0 -70
  14. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/.env.example +0 -0
  15. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/.github/workflows/ci.yml +0 -0
  16. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/.github/workflows/release.yml +0 -0
  17. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/.gitignore +0 -0
  18. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/.python-version +0 -0
  19. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/LICENSE +0 -0
  20. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/REFACTORING_SUMMARY.md +0 -0
  21. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/application/__init__.py +0 -0
  22. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/application/rename_service.py +0 -0
  23. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/domain/__init__.py +0 -0
  24. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/infrastructure/__init__.py +0 -0
  25. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/infrastructure/config.py +0 -0
  26. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/infrastructure/llm/__init__.py +0 -0
  27. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/infrastructure/llm/pydantic_ai_provider.py +0 -0
  28. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/infrastructure/pdf/__init__.py +0 -0
  29. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/infrastructure/pdf/composite.py +0 -0
  30. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/infrastructure/pdf/docling_extractor.py +0 -0
  31. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/infrastructure/pdf/pymupdf_extractor.py +0 -0
  32. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/main.py +0 -0
  33. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/presentation/__init__.py +0 -0
  34. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/src/pdf_file_renamer/presentation/formatters.py +0 -0
  35. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/tests/__init__.py +0 -0
  36. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/tests/data/2025-dennis-managing-complexity.pdf +0 -0
  37. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/tests/data/Camp_of_the_Saints.pdf +0 -0
  38. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/tests/data/s43588-025-00854-1.pdf +0 -0
  39. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/tests/test_domain_models.py +0 -0
  40. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/tests/test_filename_service.py +0 -0
  41. {pdf_file_renamer-0.5.0 → pdf_file_renamer-0.6.0}/tests/test_rename_service.py +0 -0
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdf-file-renamer
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Intelligent PDF renaming using LLMs
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
7
7
  Requires-Dist: docling-core>=2.0.0
8
8
  Requires-Dist: docling-parse>=2.0.0
9
+ Requires-Dist: pdf2doi>=1.7
9
10
  Requires-Dist: pydantic-ai>=1.0.17
10
11
  Requires-Dist: pydantic-settings>=2.7.1
11
12
  Requires-Dist: pydantic>=2.10.6
@@ -43,9 +44,11 @@ Intelligent PDF file renaming using LLMs. This tool analyzes PDF content and met
43
44
 
44
45
  ## Features
45
46
 
47
+ - **DOI-based naming** - Automatically extracts DOI and fetches authoritative metadata for academic papers
46
48
  - **Advanced PDF parsing** using docling-parse for better structure-aware extraction
47
49
  - **OCR fallback** for scanned PDFs with low text content
48
50
  - **Smart LLM prompting** with multi-pass analysis for improved accuracy
51
+ - **Hybrid approach** - Uses DOI metadata when available, falls back to LLM analysis for other documents
49
52
  - Suggests filenames in format: `Author-Topic-Year.pdf`
50
53
  - Dry-run mode to preview changes before applying
51
54
  - **Enhanced interactive mode** with options to accept, manually edit, retry, or skip each file
@@ -208,19 +211,44 @@ You can use interactive mode with `--dry-run` to preview without actually renami
208
211
 
209
212
  ## How It Works
210
213
 
211
- 1. **Extract**: Uses docling-parse to read first 5 pages with structure-aware parsing, falls back to PyMuPDF if needed
212
- 2. **OCR**: Automatically applies OCR for scanned PDFs with minimal text
213
- 3. **Metadata Enhancement**: Extracts focused hints (years, emails, author sections) to supplement unreliable PDF metadata
214
- 4. **Analyze**: Sends full content excerpt to LLM with enhanced metadata and detailed extraction instructions
215
- 5. **Multi-pass Review**: Low-confidence results trigger a second analysis pass with focused prompts
216
- 6. **Suggest**: LLM returns filename in `Author-Topic-Year` format with confidence level and reasoning
217
- 7. **Interactive Review** (optional): User can accept, edit, retry, or skip each suggestion
218
- 8. **Rename**: Applies suggestions (if not in dry-run mode)
214
+ ### Intelligent Hybrid Approach
215
+
216
+ The tool uses a multi-strategy approach to generate accurate filenames:
217
+
218
+ 1. **DOI Detection** (for academic papers)
219
+ - Searches PDF for DOI identifiers using [pdf2doi](https://github.com/MicheleCotrufo/pdf2doi)
220
+ - If found, queries authoritative metadata (title, authors, year, journal)
221
+ - Generates filename with **very high confidence** from validated metadata
222
+ - **Saves API costs** - no LLM call needed for papers with DOIs
223
+
224
+ 2. **LLM Analysis** (fallback for non-academic PDFs)
225
+ - **Extract**: Uses docling-parse to read first 5 pages with structure-aware parsing, falls back to PyMuPDF if needed
226
+ - **OCR**: Automatically applies OCR for scanned PDFs with minimal text
227
+ - **Metadata Enhancement**: Extracts focused hints (years, emails, author sections) to supplement unreliable PDF metadata
228
+ - **Analyze**: Sends full content excerpt to LLM with enhanced metadata and detailed extraction instructions
229
+ - **Multi-pass Review**: Low-confidence results trigger a second analysis pass with focused prompts
230
+ - **Suggest**: LLM returns filename in `Author-Topic-Year` format with confidence level and reasoning
231
+
232
+ 3. **Interactive Review** (optional): User can accept, edit, retry, or skip each suggestion
233
+ 4. **Rename**: Applies suggestions (if not in dry-run mode)
234
+
235
+ ### Benefits of DOI Integration
236
+
237
+ - **Accuracy**: DOI metadata is canonical and verified
238
+ - **Speed**: Instant lookup vs. LLM processing time
239
+ - **Cost**: Free DOI lookups save on API costs for academic papers
240
+ - **Reliability**: Works even when PDF text extraction is poor
219
241
 
220
242
  ## Cost Considerations
221
243
 
222
- **OpenAI:**
244
+ **DOI-based Naming (Academic Papers):**
245
+ - **Completely free** - No API costs
246
+ - **No LLM needed** - Direct metadata lookup
247
+ - Works for most academic papers with embedded DOIs
248
+
249
+ **OpenAI (Fallback):**
223
250
  - Uses `gpt-4o-mini` by default (very cost-effective)
251
+ - Only called when DOI not found
224
252
  - Processes first ~4500 characters per PDF
225
253
  - Typical cost: ~$0.001-0.003 per PDF
226
254
 
@@ -18,9 +18,11 @@ Intelligent PDF file renaming using LLMs. This tool analyzes PDF content and met
18
18
 
19
19
  ## Features
20
20
 
21
+ - **DOI-based naming** - Automatically extracts DOI and fetches authoritative metadata for academic papers
21
22
  - **Advanced PDF parsing** using docling-parse for better structure-aware extraction
22
23
  - **OCR fallback** for scanned PDFs with low text content
23
24
  - **Smart LLM prompting** with multi-pass analysis for improved accuracy
25
+ - **Hybrid approach** - Uses DOI metadata when available, falls back to LLM analysis for other documents
24
26
  - Suggests filenames in format: `Author-Topic-Year.pdf`
25
27
  - Dry-run mode to preview changes before applying
26
28
  - **Enhanced interactive mode** with options to accept, manually edit, retry, or skip each file
@@ -183,19 +185,44 @@ You can use interactive mode with `--dry-run` to preview without actually renami
183
185
 
184
186
  ## How It Works
185
187
 
186
- 1. **Extract**: Uses docling-parse to read first 5 pages with structure-aware parsing, falls back to PyMuPDF if needed
187
- 2. **OCR**: Automatically applies OCR for scanned PDFs with minimal text
188
- 3. **Metadata Enhancement**: Extracts focused hints (years, emails, author sections) to supplement unreliable PDF metadata
189
- 4. **Analyze**: Sends full content excerpt to LLM with enhanced metadata and detailed extraction instructions
190
- 5. **Multi-pass Review**: Low-confidence results trigger a second analysis pass with focused prompts
191
- 6. **Suggest**: LLM returns filename in `Author-Topic-Year` format with confidence level and reasoning
192
- 7. **Interactive Review** (optional): User can accept, edit, retry, or skip each suggestion
193
- 8. **Rename**: Applies suggestions (if not in dry-run mode)
188
+ ### Intelligent Hybrid Approach
189
+
190
+ The tool uses a multi-strategy approach to generate accurate filenames:
191
+
192
+ 1. **DOI Detection** (for academic papers)
193
+ - Searches PDF for DOI identifiers using [pdf2doi](https://github.com/MicheleCotrufo/pdf2doi)
194
+ - If found, queries authoritative metadata (title, authors, year, journal)
195
+ - Generates filename with **very high confidence** from validated metadata
196
+ - **Saves API costs** - no LLM call needed for papers with DOIs
197
+
198
+ 2. **LLM Analysis** (fallback for non-academic PDFs)
199
+ - **Extract**: Uses docling-parse to read first 5 pages with structure-aware parsing, falls back to PyMuPDF if needed
200
+ - **OCR**: Automatically applies OCR for scanned PDFs with minimal text
201
+ - **Metadata Enhancement**: Extracts focused hints (years, emails, author sections) to supplement unreliable PDF metadata
202
+ - **Analyze**: Sends full content excerpt to LLM with enhanced metadata and detailed extraction instructions
203
+ - **Multi-pass Review**: Low-confidence results trigger a second analysis pass with focused prompts
204
+ - **Suggest**: LLM returns filename in `Author-Topic-Year` format with confidence level and reasoning
205
+
206
+ 3. **Interactive Review** (optional): User can accept, edit, retry, or skip each suggestion
207
+ 4. **Rename**: Applies suggestions (if not in dry-run mode)
208
+
209
+ ### Benefits of DOI Integration
210
+
211
+ - **Accuracy**: DOI metadata is canonical and verified
212
+ - **Speed**: Instant lookup vs. LLM processing time
213
+ - **Cost**: Free DOI lookups save on API costs for academic papers
214
+ - **Reliability**: Works even when PDF text extraction is poor
194
215
 
195
216
  ## Cost Considerations
196
217
 
197
- **OpenAI:**
218
+ **DOI-based Naming (Academic Papers):**
219
+ - **Completely free** - No API costs
220
+ - **No LLM needed** - Direct metadata lookup
221
+ - Works for most academic papers with embedded DOIs
222
+
223
+ **OpenAI (Fallback):**
198
224
  - Uses `gpt-4o-mini` by default (very cost-effective)
225
+ - Only called when DOI not found
199
226
  - Processes first ~4500 characters per PDF
200
227
  - Typical cost: ~$0.001-0.003 per PDF
201
228
 
@@ -1,5 +1,5 @@
1
1
  <?xml version="1.0" ?>
2
- <coverage version="7.10.7" timestamp="1760274944253" lines-valid="588" lines-covered="123" line-rate="0.2092" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
2
+ <coverage version="7.10.7" timestamp="1760461998034" lines-valid="702" lines-covered="142" line-rate="0.2023" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
3
3
  <!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.10.7 -->
4
4
  <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
5
5
  <sources>
@@ -22,7 +22,7 @@
22
22
  </class>
23
23
  </classes>
24
24
  </package>
25
- <package name="src.pdf_file_renamer.application" line-rate="0.7" branch-rate="0" complexity="0">
25
+ <package name="src.pdf_file_renamer.application" line-rate="0.5194" branch-rate="0" complexity="0">
26
26
  <classes>
27
27
  <class name="__init__.py" filename="src/pdf_file_renamer/application/__init__.py" complexity="0" line-rate="1" branch-rate="0">
28
28
  <methods/>
@@ -33,7 +33,7 @@
33
33
  <line number="7" hits="1"/>
34
34
  </lines>
35
35
  </class>
36
- <class name="filename_service.py" filename="src/pdf_file_renamer/application/filename_service.py" complexity="0" line-rate="1" branch-rate="0">
36
+ <class name="filename_service.py" filename="src/pdf_file_renamer/application/filename_service.py" complexity="0" line-rate="0.4773" branch-rate="0">
37
37
  <methods/>
38
38
  <lines>
39
39
  <line number="3" hits="1"/>
@@ -44,57 +44,96 @@
44
44
  <line number="19" hits="1"/>
45
45
  <line number="21" hits="1"/>
46
46
  <line number="33" hits="1"/>
47
- <line number="36" hits="1"/>
48
- <line number="43" hits="1"/>
49
- <line number="45" hits="1"/>
50
- <line number="47" hits="1"/>
51
- <line number="58" hits="1"/>
52
- <line number="61" hits="1"/>
53
- <line number="64" hits="1"/>
54
- <line number="67" hits="1"/>
55
- <line number="68" hits="1"/>
56
- <line number="70" hits="1"/>
47
+ <line number="34" hits="0"/>
48
+ <line number="38" hits="1"/>
49
+ <line number="41" hits="1"/>
50
+ <line number="48" hits="1"/>
51
+ <line number="50" hits="1"/>
52
+ <line number="52" hits="1"/>
53
+ <line number="62" hits="0"/>
54
+ <line number="63" hits="0"/>
55
+ <line number="64" hits="0"/>
56
+ <line number="65" hits="0"/>
57
+ <line number="68" hits="0"/>
58
+ <line number="71" hits="0"/>
59
+ <line number="73" hits="0"/>
60
+ <line number="75" hits="0"/>
61
+ <line number="78" hits="0"/>
62
+ <line number="79" hits="0"/>
63
+ <line number="80" hits="0"/>
64
+ <line number="81" hits="0"/>
65
+ <line number="82" hits="0"/>
66
+ <line number="84" hits="0"/>
67
+ <line number="85" hits="0"/>
68
+ <line number="87" hits="0"/>
69
+ <line number="94" hits="1"/>
70
+ <line number="106" hits="0"/>
71
+ <line number="136" hits="0"/>
72
+ <line number="139" hits="0"/>
73
+ <line number="142" hits="0"/>
74
+ <line number="145" hits="0"/>
75
+ <line number="147" hits="0"/>
76
+ <line number="149" hits="1"/>
77
+ <line number="160" hits="1"/>
78
+ <line number="163" hits="1"/>
79
+ <line number="166" hits="1"/>
80
+ <line number="169" hits="1"/>
81
+ <line number="170" hits="1"/>
82
+ <line number="172" hits="1"/>
57
83
  </lines>
58
84
  </class>
59
- <class name="pdf_rename_workflow.py" filename="src/pdf_file_renamer/application/pdf_rename_workflow.py" complexity="0" line-rate="0.2778" branch-rate="0">
85
+ <class name="pdf_rename_workflow.py" filename="src/pdf_file_renamer/application/pdf_rename_workflow.py" complexity="0" line-rate="0.2245" branch-rate="0">
60
86
  <methods/>
61
87
  <lines>
62
88
  <line number="3" hits="1"/>
63
89
  <line number="4" hits="1"/>
64
90
  <line number="5" hits="1"/>
65
- <line number="7" hits="1"/>
91
+ <line number="6" hits="1"/>
66
92
  <line number="8" hits="1"/>
67
- <line number="15" hits="1"/>
68
- <line number="23" hits="1"/>
69
- <line number="41" hits="0"/>
70
- <line number="42" hits="0"/>
71
- <line number="43" hits="0"/>
72
- <line number="44" hits="0"/>
93
+ <line number="9" hits="1"/>
94
+ <line number="17" hits="1"/>
95
+ <line number="25" hits="1"/>
73
96
  <line number="45" hits="0"/>
74
- <line number="47" hits="1"/>
75
- <line number="62" hits="0"/>
76
- <line number="64" hits="0"/>
77
- <line number="66" hits="0"/>
97
+ <line number="46" hits="0"/>
98
+ <line number="47" hits="0"/>
99
+ <line number="48" hits="0"/>
100
+ <line number="49" hits="0"/>
101
+ <line number="50" hits="0"/>
102
+ <line number="52" hits="1"/>
78
103
  <line number="67" hits="0"/>
79
- <line number="70" hits="0"/>
104
+ <line number="69" hits="0"/>
80
105
  <line number="71" hits="0"/>
106
+ <line number="72" hits="0"/>
107
+ <line number="73" hits="0"/>
81
108
  <line number="74" hits="0"/>
82
- <line number="75" hits="0"/>
83
109
  <line number="77" hits="0"/>
84
110
  <line number="78" hits="0"/>
85
111
  <line number="81" hits="0"/>
86
112
  <line number="82" hits="0"/>
87
- <line number="91" hits="0"/>
88
- <line number="100" hits="0"/>
113
+ <line number="83" hits="0"/>
114
+ <line number="86" hits="0"/>
115
+ <line number="87" hits="0"/>
116
+ <line number="90" hits="0"/>
117
+ <line number="92" hits="0"/>
118
+ <line number="94" hits="0"/>
119
+ <line number="97" hits="0"/>
120
+ <line number="98" hits="0"/>
121
+ <line number="99" hits="0"/>
89
122
  <line number="101" hits="0"/>
90
123
  <line number="102" hits="0"/>
91
- <line number="103" hits="0"/>
92
- <line number="105" hits="1"/>
93
- <line number="120" hits="0"/>
94
- <line number="121" hits="0"/>
95
- <line number="123" hits="1"/>
96
- <line number="143" hits="0"/>
97
- <line number="144" hits="0"/>
124
+ <line number="105" hits="0"/>
125
+ <line number="106" hits="0"/>
126
+ <line number="115" hits="0"/>
127
+ <line number="125" hits="0"/>
128
+ <line number="126" hits="0"/>
129
+ <line number="127" hits="0"/>
130
+ <line number="128" hits="0"/>
131
+ <line number="130" hits="1"/>
132
+ <line number="145" hits="0"/>
133
+ <line number="146" hits="0"/>
134
+ <line number="148" hits="1"/>
135
+ <line number="168" hits="0"/>
136
+ <line number="169" hits="0"/>
98
137
  </lines>
99
138
  </class>
100
139
  <class name="rename_service.py" filename="src/pdf_file_renamer/application/rename_service.py" complexity="0" line-rate="0.9688" branch-rate="0">
@@ -136,7 +175,7 @@
136
175
  </class>
137
176
  </classes>
138
177
  </package>
139
- <package name="src.pdf_file_renamer.domain" line-rate="1" branch-rate="0" complexity="0">
178
+ <package name="src.pdf_file_renamer.domain" line-rate="0.9136" branch-rate="0" complexity="0">
140
179
  <classes>
141
180
  <class name="__init__.py" filename="src/pdf_file_renamer/domain/__init__.py" complexity="0" line-rate="1" branch-rate="0">
142
181
  <methods/>
@@ -146,7 +185,7 @@
146
185
  <line number="16" hits="1"/>
147
186
  </lines>
148
187
  </class>
149
- <class name="models.py" filename="src/pdf_file_renamer/domain/models.py" complexity="0" line-rate="1" branch-rate="0">
188
+ <class name="models.py" filename="src/pdf_file_renamer/domain/models.py" complexity="0" line-rate="0.9" branch-rate="0">
150
189
  <methods/>
151
190
  <lines>
152
191
  <line number="3" hits="1"/>
@@ -158,46 +197,67 @@
158
197
  <line number="14" hits="1"/>
159
198
  <line number="15" hits="1"/>
160
199
  <line number="16" hits="1"/>
161
- <line number="19" hits="1"/>
162
- <line number="22" hits="1"/>
200
+ <line number="17" hits="1"/>
201
+ <line number="20" hits="1"/>
202
+ <line number="21" hits="1"/>
163
203
  <line number="24" hits="1"/>
164
204
  <line number="25" hits="1"/>
165
205
  <line number="26" hits="1"/>
206
+ <line number="27" hits="1"/>
207
+ <line number="28" hits="1"/>
166
208
  <line number="29" hits="1"/>
167
209
  <line number="30" hits="1"/>
210
+ <line number="32" hits="1"/>
168
211
  <line number="33" hits="1"/>
169
- <line number="34" hits="1"/>
170
- <line number="35" hits="1"/>
171
- <line number="36" hits="1"/>
172
- <line number="37" hits="1"/>
173
- <line number="38" hits="1"/>
174
- <line number="39" hits="1"/>
175
- <line number="40" hits="1"/>
176
- <line number="42" hits="1"/>
177
- <line number="43" hits="1"/>
178
- <line number="44" hits="1"/>
179
- <line number="45" hits="1"/>
180
- <line number="47" hits="1"/>
212
+ <line number="35" hits="0"/>
213
+ <line number="36" hits="0"/>
214
+ <line number="38" hits="0"/>
215
+ <line number="39" hits="0"/>
216
+ <line number="40" hits="0"/>
217
+ <line number="42" hits="0"/>
218
+ <line number="43" hits="0"/>
219
+ <line number="46" hits="1"/>
181
220
  <line number="49" hits="1"/>
221
+ <line number="51" hits="1"/>
182
222
  <line number="52" hits="1"/>
183
223
  <line number="53" hits="1"/>
184
224
  <line number="56" hits="1"/>
185
225
  <line number="57" hits="1"/>
186
- <line number="58" hits="1"/>
226
+ <line number="60" hits="1"/>
187
227
  <line number="61" hits="1"/>
188
228
  <line number="62" hits="1"/>
229
+ <line number="63" hits="1"/>
230
+ <line number="64" hits="1"/>
189
231
  <line number="65" hits="1"/>
190
232
  <line number="66" hits="1"/>
191
233
  <line number="67" hits="1"/>
192
- <line number="68" hits="1"/>
193
234
  <line number="69" hits="1"/>
194
235
  <line number="70" hits="1"/>
236
+ <line number="71" hits="1"/>
195
237
  <line number="72" hits="1"/>
196
- <line number="73" hits="1"/>
197
- <line number="75" hits="1"/>
198
- <line number="77" hits="1"/>
238
+ <line number="74" hits="1"/>
239
+ <line number="76" hits="1"/>
199
240
  <line number="79" hits="1"/>
200
241
  <line number="80" hits="1"/>
242
+ <line number="83" hits="1"/>
243
+ <line number="84" hits="1"/>
244
+ <line number="85" hits="1"/>
245
+ <line number="86" hits="1"/>
246
+ <line number="89" hits="1"/>
247
+ <line number="90" hits="1"/>
248
+ <line number="93" hits="1"/>
249
+ <line number="94" hits="1"/>
250
+ <line number="95" hits="1"/>
251
+ <line number="96" hits="1"/>
252
+ <line number="97" hits="1"/>
253
+ <line number="98" hits="1"/>
254
+ <line number="99" hits="1"/>
255
+ <line number="101" hits="1"/>
256
+ <line number="102" hits="1"/>
257
+ <line number="104" hits="1"/>
258
+ <line number="106" hits="1"/>
259
+ <line number="108" hits="1"/>
260
+ <line number="109" hits="1"/>
201
261
  </lines>
202
262
  </class>
203
263
  <class name="ports.py" filename="src/pdf_file_renamer/domain/ports.py" complexity="0" line-rate="1" branch-rate="0">
@@ -207,9 +267,10 @@
207
267
  <line number="4" hits="1"/>
208
268
  <line number="6" hits="1"/>
209
269
  <line number="9" hits="1"/>
210
- <line number="29" hits="1"/>
211
- <line number="56" hits="1"/>
212
- <line number="87" hits="1"/>
270
+ <line number="26" hits="1"/>
271
+ <line number="46" hits="1"/>
272
+ <line number="73" hits="1"/>
273
+ <line number="104" hits="1"/>
213
274
  </lines>
214
275
  </class>
215
276
  </classes>
@@ -251,6 +312,71 @@
251
312
  </class>
252
313
  </classes>
253
314
  </package>
315
+ <package name="src.pdf_file_renamer.infrastructure.doi" line-rate="0" branch-rate="0" complexity="0">
316
+ <classes>
317
+ <class name="__init__.py" filename="src/pdf_file_renamer/infrastructure/doi/__init__.py" complexity="0" line-rate="0" branch-rate="0">
318
+ <methods/>
319
+ <lines>
320
+ <line number="3" hits="0"/>
321
+ <line number="5" hits="0"/>
322
+ </lines>
323
+ </class>
324
+ <class name="pdf2doi_extractor.py" filename="src/pdf_file_renamer/infrastructure/doi/pdf2doi_extractor.py" complexity="0" line-rate="0" branch-rate="0">
325
+ <methods/>
326
+ <lines>
327
+ <line number="3" hits="0"/>
328
+ <line number="4" hits="0"/>
329
+ <line number="5" hits="0"/>
330
+ <line number="7" hits="0"/>
331
+ <line number="9" hits="0"/>
332
+ <line number="10" hits="0"/>
333
+ <line number="13" hits="0"/>
334
+ <line number="16" hits="0"/>
335
+ <line number="19" hits="0"/>
336
+ <line number="21" hits="0"/>
337
+ <line number="31" hits="0"/>
338
+ <line number="33" hits="0"/>
339
+ <line number="34" hits="0"/>
340
+ <line number="38" hits="0"/>
341
+ <line number="39" hits="0"/>
342
+ <line number="42" hits="0"/>
343
+ <line number="45" hits="0"/>
344
+ <line number="46" hits="0"/>
345
+ <line number="47" hits="0"/>
346
+ <line number="49" hits="0"/>
347
+ <line number="50" hits="0"/>
348
+ <line number="51" hits="0"/>
349
+ <line number="54" hits="0"/>
350
+ <line number="57" hits="0"/>
351
+ <line number="58" hits="0"/>
352
+ <line number="59" hits="0"/>
353
+ <line number="60" hits="0"/>
354
+ <line number="61" hits="0"/>
355
+ <line number="63" hits="0"/>
356
+ <line number="73" hits="0"/>
357
+ <line number="75" hits="0"/>
358
+ <line number="77" hits="0"/>
359
+ <line number="88" hits="0"/>
360
+ <line number="89" hits="0"/>
361
+ <line number="92" hits="0"/>
362
+ <line number="93" hits="0"/>
363
+ <line number="95" hits="0"/>
364
+ <line number="96" hits="0"/>
365
+ <line number="98" hits="0"/>
366
+ <line number="100" hits="0"/>
367
+ <line number="110" hits="0"/>
368
+ <line number="111" hits="0"/>
369
+ <line number="114" hits="0"/>
370
+ <line number="115" hits="0"/>
371
+ <line number="117" hits="0"/>
372
+ <line number="118" hits="0"/>
373
+ <line number="120" hits="0"/>
374
+ <line number="123" hits="0"/>
375
+ <line number="129" hits="0"/>
376
+ </lines>
377
+ </class>
378
+ </classes>
379
+ </package>
254
380
  <package name="src.pdf_file_renamer.infrastructure.llm" line-rate="0" branch-rate="0" complexity="0">
255
381
  <classes>
256
382
  <class name="__init__.py" filename="src/pdf_file_renamer/infrastructure/llm/__init__.py" complexity="0" line-rate="0" branch-rate="0">
@@ -518,88 +644,90 @@
518
644
  <line number="17" hits="0"/>
519
645
  <line number="18" hits="0"/>
520
646
  <line number="19" hits="0"/>
521
- <line number="24" hits="0"/>
522
- <line number="30" hits="0"/>
647
+ <line number="20" hits="0"/>
648
+ <line number="25" hits="0"/>
523
649
  <line number="31" hits="0"/>
524
- <line number="34" hits="0"/>
525
- <line number="47" hits="0"/>
526
- <line number="55" hits="0"/>
527
- <line number="58" hits="0"/>
528
- <line number="68" hits="0"/>
650
+ <line number="32" hits="0"/>
651
+ <line number="35" hits="0"/>
652
+ <line number="48" hits="0"/>
653
+ <line number="56" hits="0"/>
654
+ <line number="59" hits="0"/>
529
655
  <line number="69" hits="0"/>
530
656
  <line number="72" hits="0"/>
531
- <line number="81" hits="0"/>
532
- <line number="82" hits="0"/>
533
- <line number="108" hits="0"/>
534
- <line number="111" hits="0"/>
535
- <line number="112" hits="0"/>
657
+ <line number="73" hits="0"/>
658
+ <line number="76" hits="0"/>
659
+ <line number="86" hits="0"/>
660
+ <line number="87" hits="0"/>
536
661
  <line number="113" hits="0"/>
537
- <line number="114" hits="0"/>
662
+ <line number="116" hits="0"/>
538
663
  <line number="117" hits="0"/>
539
664
  <line number="118" hits="0"/>
540
665
  <line number="119" hits="0"/>
541
- <line number="120" hits="0"/>
542
- <line number="121" hits="0"/>
666
+ <line number="122" hits="0"/>
667
+ <line number="123" hits="0"/>
543
668
  <line number="124" hits="0"/>
544
669
  <line number="125" hits="0"/>
545
670
  <line number="126" hits="0"/>
546
- <line number="127" hits="0"/>
547
671
  <line number="129" hits="0"/>
672
+ <line number="130" hits="0"/>
673
+ <line number="131" hits="0"/>
548
674
  <line number="132" hits="0"/>
549
- <line number="135" hits="0"/>
550
- <line number="136" hits="0"/>
551
- <line number="138" hits="0"/>
552
- <line number="139" hits="0"/>
553
- <line number="142" hits="0"/>
675
+ <line number="134" hits="0"/>
676
+ <line number="137" hits="0"/>
677
+ <line number="140" hits="0"/>
678
+ <line number="141" hits="0"/>
679
+ <line number="143" hits="0"/>
554
680
  <line number="144" hits="0"/>
555
- <line number="145" hits="0"/>
556
- <line number="146" hits="0"/>
557
681
  <line number="147" hits="0"/>
558
682
  <line number="149" hits="0"/>
683
+ <line number="150" hits="0"/>
559
684
  <line number="151" hits="0"/>
560
- <line number="153" hits="0"/>
685
+ <line number="152" hits="0"/>
561
686
  <line number="154" hits="0"/>
562
- <line number="155" hits="0"/>
563
- <line number="157" hits="0"/>
687
+ <line number="156" hits="0"/>
688
+ <line number="158" hits="0"/>
564
689
  <line number="159" hits="0"/>
690
+ <line number="160" hits="0"/>
565
691
  <line number="162" hits="0"/>
566
- <line number="166" hits="0"/>
567
- <line number="169" hits="0"/>
692
+ <line number="164" hits="0"/>
693
+ <line number="167" hits="0"/>
568
694
  <line number="171" hits="0"/>
569
- <line number="172" hits="0"/>
570
- <line number="173" hits="0"/>
695
+ <line number="174" hits="0"/>
571
696
  <line number="176" hits="0"/>
572
697
  <line number="177" hits="0"/>
573
- <line number="180" hits="0"/>
698
+ <line number="178" hits="0"/>
574
699
  <line number="181" hits="0"/>
575
700
  <line number="182" hits="0"/>
576
- <line number="184" hits="0"/>
701
+ <line number="185" hits="0"/>
702
+ <line number="186" hits="0"/>
577
703
  <line number="187" hits="0"/>
578
704
  <line number="189" hits="0"/>
579
- <line number="191" hits="0"/>
580
705
  <line number="192" hits="0"/>
581
- <line number="193" hits="0"/>
582
706
  <line number="194" hits="0"/>
583
- <line number="195" hits="0"/>
707
+ <line number="196" hits="0"/>
584
708
  <line number="197" hits="0"/>
709
+ <line number="198" hits="0"/>
710
+ <line number="199" hits="0"/>
585
711
  <line number="200" hits="0"/>
586
- <line number="201" hits="0"/>
587
712
  <line number="202" hits="0"/>
588
713
  <line number="205" hits="0"/>
589
714
  <line number="206" hits="0"/>
590
715
  <line number="207" hits="0"/>
591
- <line number="208" hits="0"/>
592
- <line number="209" hits="0"/>
716
+ <line number="210" hits="0"/>
717
+ <line number="211" hits="0"/>
718
+ <line number="212" hits="0"/>
719
+ <line number="213" hits="0"/>
593
720
  <line number="214" hits="0"/>
594
- <line number="215" hits="0"/>
595
- <line number="218" hits="0"/>
596
721
  <line number="219" hits="0"/>
597
722
  <line number="220" hits="0"/>
598
723
  <line number="223" hits="0"/>
724
+ <line number="224" hits="0"/>
599
725
  <line number="225" hits="0"/>
600
- <line number="226" hits="0"/>
601
726
  <line number="228" hits="0"/>
602
- <line number="229" hits="0"/>
727
+ <line number="230" hits="0"/>
728
+ <line number="231" hits="0"/>
729
+ <line number="233" hits="0"/>
730
+ <line number="234" hits="0"/>
603
731
  </lines>
604
732
  </class>
605
733
  <class name="formatters.py" filename="src/pdf_file_renamer/presentation/formatters.py" complexity="0" line-rate="0" branch-rate="0">