scimapper 1.0.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.
- scimapper-1.0.1/LICENSE +21 -0
- scimapper-1.0.1/PKG-INFO +252 -0
- scimapper-1.0.1/README.md +216 -0
- scimapper-1.0.1/core/__init__.py +11 -0
- scimapper-1.0.1/core/clusterer.py +178 -0
- scimapper-1.0.1/core/collector.py +153 -0
- scimapper-1.0.1/core/config_template.json +48 -0
- scimapper-1.0.1/core/doc_builder.py +316 -0
- scimapper-1.0.1/core/embedder.py +70 -0
- scimapper-1.0.1/core/selector.py +129 -0
- scimapper-1.0.1/core/utils.py +79 -0
- scimapper-1.0.1/core/visualizer.py +271 -0
- scimapper-1.0.1/pyproject.toml +69 -0
- scimapper-1.0.1/scimapper.egg-info/PKG-INFO +252 -0
- scimapper-1.0.1/scimapper.egg-info/SOURCES.txt +21 -0
- scimapper-1.0.1/scimapper.egg-info/dependency_links.txt +1 -0
- scimapper-1.0.1/scimapper.egg-info/entry_points.txt +2 -0
- scimapper-1.0.1/scimapper.egg-info/requires.txt +19 -0
- scimapper-1.0.1/scimapper.egg-info/top_level.txt +1 -0
- scimapper-1.0.1/setup.cfg +4 -0
- scimapper-1.0.1/tests/test_clusterer.py +34 -0
- scimapper-1.0.1/tests/test_collector.py +28 -0
- scimapper-1.0.1/tests/test_utils.py +26 -0
scimapper-1.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gustavo Andrés Futo
|
|
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.
|
scimapper-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scimapper
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: An automated scientific mapping and systematic literature review framework with dense semantic clustering and PRISMA 2020 compliance.
|
|
5
|
+
Author-email: Gustavo Andrés Futo <gustavoandresfu@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/gustavoandresfu/SciMapper
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/gustavoandresfu/SciMapper/issues
|
|
9
|
+
Keywords: systematic-literature-review,systematic-mapping-study,scimapper,scientific-mapping,topic-modeling,sentence-transformers,bertopic,prisma-2020,openalex,evidence-synthesis,nlp
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
22
|
+
Provides-Extra: web
|
|
23
|
+
Requires-Dist: streamlit>=1.30.0; extra == "web"
|
|
24
|
+
Provides-Extra: dependencies
|
|
25
|
+
Requires-Dist: requests>=2.28.0; extra == "dependencies"
|
|
26
|
+
Requires-Dist: pandas>=1.5.0; extra == "dependencies"
|
|
27
|
+
Requires-Dist: numpy>=1.23.0; extra == "dependencies"
|
|
28
|
+
Requires-Dist: scikit-learn>=1.2.0; extra == "dependencies"
|
|
29
|
+
Requires-Dist: scipy>=1.10.0; extra == "dependencies"
|
|
30
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == "dependencies"
|
|
31
|
+
Requires-Dist: torch>=2.0.0; extra == "dependencies"
|
|
32
|
+
Requires-Dist: plotly>=5.13.0; extra == "dependencies"
|
|
33
|
+
Requires-Dist: python-docx>=0.8.11; extra == "dependencies"
|
|
34
|
+
Requires-Dist: tqdm>=4.65.0; extra == "dependencies"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
<div align="center">
|
|
38
|
+
|
|
39
|
+
# 🚀 SciMapper
|
|
40
|
+
### Automated Scientific Mapping & Systematic Literature Reviews with Dense Semantic Clustering
|
|
41
|
+
|
|
42
|
+
[](https://www.python.org/)
|
|
43
|
+
[](https://opensource.org/licenses/MIT)
|
|
44
|
+
[](https://www.prisma-statement.org/)
|
|
45
|
+
[](https://openalex.org/)
|
|
46
|
+
[](https://www.sbert.net/)
|
|
47
|
+
|
|
48
|
+
**An end-to-end, domain-agnostic framework designed to conduct reproducible Systematic Literature Reviews (SLR) and Systematic Mapping Studies (SMS).**
|
|
49
|
+
It automates academic corpus harvesting, deduplication, dense semantic embedding, unsupervised clustering, taxonomic discovery, interactive 2D cartography, and manuscript drafting—while keeping **researchers firmly in the loop**.
|
|
50
|
+
|
|
51
|
+
🌐 **Languages:** **English** • [Español (Guía en Castellano)](README_ES.md)
|
|
52
|
+
|
|
53
|
+
[Quickstart](#-quickstart-in-3-steps) • [Architecture](#-framework-architecture) • [Comparison](#-comparison-with-the-state-of-the-art) • [Theoretical Foundation](#-theoretical-foundations) • [Citation](#-citation)
|
|
54
|
+
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 🌟 Key Features
|
|
60
|
+
|
|
61
|
+
* 📡 **Automated Scholarly Harvesting:** Direct connection to the **OpenAlex API** (*Polite Pool* with 250M+ works) executing boolean queries and reconstructing inverted-index abstracts in $O(N \log N)$.
|
|
62
|
+
* 🧹 **Bidirectional Deduplication:** Collapses duplicates across arXiv, IEEE, ACM, Springer, and PubMed via canonical lowercase DOI and normalized lexical title hashing.
|
|
63
|
+
* 🧠 **Domain-Specific Transformer Presets:** Plug-and-play dense representations via `Sentence-Transformers`:
|
|
64
|
+
* `software_engineering`: `sentence-transformers/all-MiniLM-L6-v2`
|
|
65
|
+
* `biomedical_health`: `microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract`
|
|
66
|
+
* `scientific_general`: `allenai/scibert_scivocab_uncased`
|
|
67
|
+
* `finance_business`: `ProsusAI/finbert`
|
|
68
|
+
* `multilingual`: `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2`
|
|
69
|
+
* 🔬 **Unsupervised Topic Discovery ($c$-TF-IDF):** Discovers latent research themes without generative hallucination, extracting mathematically distinctive n-grams using *Class-based TF-IDF*.
|
|
70
|
+
* 🗺️ **Interactive Cartography & Cluster Hulls:** Produces publication-grade 2D HTML dashboards (Plotly) featuring:
|
|
71
|
+
* Regional boundaries via **Convex Hulls** with smoothed spline contours.
|
|
72
|
+
* Cluster centroids with real-time metadata cards ($N$, percentage, top keywords).
|
|
73
|
+
* Layer switching (*Full View*, *Cluster Boundaries Only*, *Paper Scatter Points*).
|
|
74
|
+
* 📈 **Temporal Dynamics & Hot Topic Radar:** Computes Compound Annual Growth Rates (CAGR) per cluster to classify topics into 🚀 *Emerging*, ⚖️ *Mature*, or 📉 *Declining*.
|
|
75
|
+
* 🛡️ **Mathematical PRISMA 2020 Audit:** Objective isolation of out-of-scope or foreign language clusters as documented exclusion criteria.
|
|
76
|
+
* 📝 **Automated Manuscript Generation:** Compiles an executive Microsoft Word (`.docx`) template complete with cover page, research questions (RQs), PRISMA summary tables, taxonomy distribution, and structured discussion sections.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 🏗️ Framework Architecture
|
|
81
|
+
|
|
82
|
+
```mermaid
|
|
83
|
+
graph TD
|
|
84
|
+
A["⚙️ 1. Project Configuration<br><code>scimapper_config.json</code>"] --> B["📡 2. Harvester & Deduplicator<br><code>collector.py (OpenAlex API)</code>"]
|
|
85
|
+
B --> C["🧠 3. Semantic Embeddings<br><code>embedder.py (SBERT / MiniLM)</code>"]
|
|
86
|
+
C --> D["🔬 4. Clustering & c-TF-IDF<br><code>clusterer.py (PCA + t-SNE)</code>"]
|
|
87
|
+
D --> E1["🗺️ 5a. Interactive Cartography<br><code>visualizer.py (Plotly 2D + Hulls)</code>"]
|
|
88
|
+
D --> E2["⚖️ 5b. Human-in-the-Loop Review<br><code>discovered_topics.json</code>"]
|
|
89
|
+
E2 --> F["📝 6. Automated Manuscript<br><code>doc_builder.py (.docx Output)</code>"]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## ⚡ Quickstart in 3 Steps
|
|
95
|
+
|
|
96
|
+
### 1. Installation
|
|
97
|
+
|
|
98
|
+
Clone the repository and install the dependencies:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
git clone https://github.com/gustavoandresfu/SciMapper.git
|
|
102
|
+
cd SciMapper
|
|
103
|
+
pip install -r requirements.txt
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
*(Optional)* Install as an editable command-line utility:
|
|
107
|
+
```bash
|
|
108
|
+
pip install -e .
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### 2. Initialize a Project
|
|
114
|
+
|
|
115
|
+
Launch the interactive configuration wizard (prompts for domain preset, search queries, and project metadata):
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
python main.py --init
|
|
119
|
+
# Or if installed via pip:
|
|
120
|
+
scimapper --init
|
|
121
|
+
```
|
|
122
|
+
*(Or use `python main.py --init --quick` to generate the baseline template directly without prompts).*
|
|
123
|
+
|
|
124
|
+
The generated `sms_config.json` is ready to inspect and refine:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"project_metadata": {
|
|
129
|
+
"title": "Systematic Literature Review: Generative AI in Healthcare",
|
|
130
|
+
"short_name": "SciMapper_GenAI_Health",
|
|
131
|
+
"authors": ["Your Name"],
|
|
132
|
+
"temporal_window": [2019, 2026],
|
|
133
|
+
"output_dir": "./output"
|
|
134
|
+
},
|
|
135
|
+
"research_questions": {
|
|
136
|
+
"RQ1": "What foundational architectures and LLMs are predominantly adopted in clinical workflows?",
|
|
137
|
+
"RQ2": "What empirical metrics and validation benchmarks are reported for diagnostic accuracy?",
|
|
138
|
+
"RQ3": "What open challenges regarding hallucinations and ethical safety remain unaddressed?"
|
|
139
|
+
},
|
|
140
|
+
"search_strategy": {
|
|
141
|
+
"openalex_email": "researcher@university.edu",
|
|
142
|
+
"max_results_per_query": 100,
|
|
143
|
+
"queries": [
|
|
144
|
+
"\"generative ai\" AND \"clinical diagnostics\"",
|
|
145
|
+
"\"large language models\" AND \"medical decision support\""
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
"nlp_tuning": {
|
|
149
|
+
"domain_preset": "biomedical_health",
|
|
150
|
+
"n_clusters": 5
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### 3. Run the Automated Pipeline
|
|
158
|
+
|
|
159
|
+
Execute the discovery phase:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
python main.py --run-discovery --config sms_config.json
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
1. Review and refine taxonomic names in `output/discovered_topics.json`.
|
|
166
|
+
2. Inspect the pre-selected seminal papers in `output/candidatos_discusion.csv`.
|
|
167
|
+
3. Compile the interactive dashboard and Word document:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
python main.py --compile --config sms_config.json
|
|
171
|
+
python main.py --build-doc --config sms_config.json
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Or execute everything end-to-end in a single run:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
python main.py --all --config sms_config.json
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 📊 Comparison with the State-of-the-Art
|
|
183
|
+
|
|
184
|
+
| Capability | ASReview | Generic BERTopic | Manual Review Tools (Covidence / Rayyan) | **SciMapper** |
|
|
185
|
+
| :--- | :---: | :---: | :---: | :---: |
|
|
186
|
+
| **Academic Ingestion** | ❌ Manual (CSV/RIS) | ❌ None | ❌ Manual Import | **✅ Automated OpenAlex API** |
|
|
187
|
+
| **Deduplication** | ⚠️ Basic | ❌ None | ⚠️ Exact match | **✅ Canonical DOI + Title Hash** |
|
|
188
|
+
| **Semantic Representations** | ✅ Optional | ✅ Native | ❌ None | **✅ Pre-tuned Domain Transformers** |
|
|
189
|
+
| **Topic Modeling ($c$-TF-IDF)** | ❌ Binary only | ✅ Yes | ❌ None | **✅ Mathematical $c$-TF-IDF** |
|
|
190
|
+
| **Objective PRISMA Exclusion** | ❌ Human judgment | ❌ None | ❌ Human labeling | **✅ Algorithmic Outlier Clustering** |
|
|
191
|
+
| **Cartography with Convex Hulls** | ❌ None | ⚠️ Scatter only | ❌ None | **✅ 2D Hulls + Centroids + Density** |
|
|
192
|
+
| **Temporal CAGR Tracking** | ❌ None | ❌ Manual | ❌ None | **✅ Automated Hot-Topic Radar** |
|
|
193
|
+
| **Automated Manuscript (.docx)** | ❌ None | ❌ None | ❌ Export tables | **✅ Formatted PRISMA Word Paper** |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 🔬 Theoretical Foundations
|
|
198
|
+
|
|
199
|
+
The framework's methodological and algorithmic foundations are grounded in established literature:
|
|
200
|
+
|
|
201
|
+
1. **Topic Modeling & Semantic Representation:**
|
|
202
|
+
Based on the modular architecture described by **Jay Alammar and Maarten Grootendorst** in *Hands-On Large Language Models* (O'Reilly, Chapter 5: *Text Clustering and Topic Modeling*), decoupling dense vector embeddings, dimensionality reduction, clustering, and class-based TF-IDF ($c$-TF-IDF).
|
|
203
|
+
2. **Systematic Mapping Methodology:**
|
|
204
|
+
Follows the seminal guidelines of **Petersen et al. (2008)** (*Systematic Mapping Studies in Software Engineering*) and **Kitchenham & Charters (2007)**.
|
|
205
|
+
3. **Auditability & Reporting Standards:**
|
|
206
|
+
Strict adherence to the **PRISMA 2020** statement (*Preferred Reporting Items for Systematic Reviews and Meta-Analyses*), ensuring transparent and reproducible record accounting.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 📁 Repository Structure
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
SciMapper/
|
|
214
|
+
├── config_template.json # Master template for project setup
|
|
215
|
+
├── test_config.json # Quick verification test suite
|
|
216
|
+
├── main.py # Unified CLI orchestrator
|
|
217
|
+
├── requirements.txt # Python dependency specification
|
|
218
|
+
├── pyproject.toml # Standard packaging & CLI entry point (scimapper)
|
|
219
|
+
├── CITATION.cff # Academic citation metadata
|
|
220
|
+
├── LICENSE # MIT Open Source License
|
|
221
|
+
└── core/
|
|
222
|
+
├── collector.py # OpenAlex API ingestion & abstract reconstruction
|
|
223
|
+
├── embedder.py # Multi-domain Sentence-Transformer pipeline
|
|
224
|
+
├── clusterer.py # Dimensionality reduction (PCA+t-SNE) & c-TF-IDF
|
|
225
|
+
├── selector.py # Stratified seminal paper recommendation
|
|
226
|
+
├── visualizer.py # Plotly 2D interactive maps with Convex Hulls
|
|
227
|
+
└── doc_builder.py # Automated PRISMA Word manuscript generation
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 📖 Citation
|
|
233
|
+
|
|
234
|
+
If you use **SciMapper** in your research, academic reviews, or dissertations, please cite it as:
|
|
235
|
+
|
|
236
|
+
```bibtex
|
|
237
|
+
@software{futo_scimapper_2026,
|
|
238
|
+
author = {Gustavo Andrés Futo},
|
|
239
|
+
title = {SciMapper: Automated Scientific Mapping & Systematic Literature Reviews with Dense Semantic Clustering and PRISMA 2020 Compliance},
|
|
240
|
+
year = {2026},
|
|
241
|
+
publisher = {GitHub},
|
|
242
|
+
journal = {GitHub repository},
|
|
243
|
+
howpublished = {\url{https://github.com/gustavoandresfu/SciMapper}}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 📄 License
|
|
250
|
+
|
|
251
|
+
This project is licensed under the **MIT License** — see the [LICENSE](LICENSE) file for details.
|
|
252
|
+
Author: **Gustavo Andrés Futo** (2026).
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 🚀 SciMapper
|
|
4
|
+
### Automated Scientific Mapping & Systematic Literature Reviews with Dense Semantic Clustering
|
|
5
|
+
|
|
6
|
+
[](https://www.python.org/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://www.prisma-statement.org/)
|
|
9
|
+
[](https://openalex.org/)
|
|
10
|
+
[](https://www.sbert.net/)
|
|
11
|
+
|
|
12
|
+
**An end-to-end, domain-agnostic framework designed to conduct reproducible Systematic Literature Reviews (SLR) and Systematic Mapping Studies (SMS).**
|
|
13
|
+
It automates academic corpus harvesting, deduplication, dense semantic embedding, unsupervised clustering, taxonomic discovery, interactive 2D cartography, and manuscript drafting—while keeping **researchers firmly in the loop**.
|
|
14
|
+
|
|
15
|
+
🌐 **Languages:** **English** • [Español (Guía en Castellano)](README_ES.md)
|
|
16
|
+
|
|
17
|
+
[Quickstart](#-quickstart-in-3-steps) • [Architecture](#-framework-architecture) • [Comparison](#-comparison-with-the-state-of-the-art) • [Theoretical Foundation](#-theoretical-foundations) • [Citation](#-citation)
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🌟 Key Features
|
|
24
|
+
|
|
25
|
+
* 📡 **Automated Scholarly Harvesting:** Direct connection to the **OpenAlex API** (*Polite Pool* with 250M+ works) executing boolean queries and reconstructing inverted-index abstracts in $O(N \log N)$.
|
|
26
|
+
* 🧹 **Bidirectional Deduplication:** Collapses duplicates across arXiv, IEEE, ACM, Springer, and PubMed via canonical lowercase DOI and normalized lexical title hashing.
|
|
27
|
+
* 🧠 **Domain-Specific Transformer Presets:** Plug-and-play dense representations via `Sentence-Transformers`:
|
|
28
|
+
* `software_engineering`: `sentence-transformers/all-MiniLM-L6-v2`
|
|
29
|
+
* `biomedical_health`: `microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract`
|
|
30
|
+
* `scientific_general`: `allenai/scibert_scivocab_uncased`
|
|
31
|
+
* `finance_business`: `ProsusAI/finbert`
|
|
32
|
+
* `multilingual`: `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2`
|
|
33
|
+
* 🔬 **Unsupervised Topic Discovery ($c$-TF-IDF):** Discovers latent research themes without generative hallucination, extracting mathematically distinctive n-grams using *Class-based TF-IDF*.
|
|
34
|
+
* 🗺️ **Interactive Cartography & Cluster Hulls:** Produces publication-grade 2D HTML dashboards (Plotly) featuring:
|
|
35
|
+
* Regional boundaries via **Convex Hulls** with smoothed spline contours.
|
|
36
|
+
* Cluster centroids with real-time metadata cards ($N$, percentage, top keywords).
|
|
37
|
+
* Layer switching (*Full View*, *Cluster Boundaries Only*, *Paper Scatter Points*).
|
|
38
|
+
* 📈 **Temporal Dynamics & Hot Topic Radar:** Computes Compound Annual Growth Rates (CAGR) per cluster to classify topics into 🚀 *Emerging*, ⚖️ *Mature*, or 📉 *Declining*.
|
|
39
|
+
* 🛡️ **Mathematical PRISMA 2020 Audit:** Objective isolation of out-of-scope or foreign language clusters as documented exclusion criteria.
|
|
40
|
+
* 📝 **Automated Manuscript Generation:** Compiles an executive Microsoft Word (`.docx`) template complete with cover page, research questions (RQs), PRISMA summary tables, taxonomy distribution, and structured discussion sections.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 🏗️ Framework Architecture
|
|
45
|
+
|
|
46
|
+
```mermaid
|
|
47
|
+
graph TD
|
|
48
|
+
A["⚙️ 1. Project Configuration<br><code>scimapper_config.json</code>"] --> B["📡 2. Harvester & Deduplicator<br><code>collector.py (OpenAlex API)</code>"]
|
|
49
|
+
B --> C["🧠 3. Semantic Embeddings<br><code>embedder.py (SBERT / MiniLM)</code>"]
|
|
50
|
+
C --> D["🔬 4. Clustering & c-TF-IDF<br><code>clusterer.py (PCA + t-SNE)</code>"]
|
|
51
|
+
D --> E1["🗺️ 5a. Interactive Cartography<br><code>visualizer.py (Plotly 2D + Hulls)</code>"]
|
|
52
|
+
D --> E2["⚖️ 5b. Human-in-the-Loop Review<br><code>discovered_topics.json</code>"]
|
|
53
|
+
E2 --> F["📝 6. Automated Manuscript<br><code>doc_builder.py (.docx Output)</code>"]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## ⚡ Quickstart in 3 Steps
|
|
59
|
+
|
|
60
|
+
### 1. Installation
|
|
61
|
+
|
|
62
|
+
Clone the repository and install the dependencies:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
git clone https://github.com/gustavoandresfu/SciMapper.git
|
|
66
|
+
cd SciMapper
|
|
67
|
+
pip install -r requirements.txt
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
*(Optional)* Install as an editable command-line utility:
|
|
71
|
+
```bash
|
|
72
|
+
pip install -e .
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### 2. Initialize a Project
|
|
78
|
+
|
|
79
|
+
Launch the interactive configuration wizard (prompts for domain preset, search queries, and project metadata):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python main.py --init
|
|
83
|
+
# Or if installed via pip:
|
|
84
|
+
scimapper --init
|
|
85
|
+
```
|
|
86
|
+
*(Or use `python main.py --init --quick` to generate the baseline template directly without prompts).*
|
|
87
|
+
|
|
88
|
+
The generated `sms_config.json` is ready to inspect and refine:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"project_metadata": {
|
|
93
|
+
"title": "Systematic Literature Review: Generative AI in Healthcare",
|
|
94
|
+
"short_name": "SciMapper_GenAI_Health",
|
|
95
|
+
"authors": ["Your Name"],
|
|
96
|
+
"temporal_window": [2019, 2026],
|
|
97
|
+
"output_dir": "./output"
|
|
98
|
+
},
|
|
99
|
+
"research_questions": {
|
|
100
|
+
"RQ1": "What foundational architectures and LLMs are predominantly adopted in clinical workflows?",
|
|
101
|
+
"RQ2": "What empirical metrics and validation benchmarks are reported for diagnostic accuracy?",
|
|
102
|
+
"RQ3": "What open challenges regarding hallucinations and ethical safety remain unaddressed?"
|
|
103
|
+
},
|
|
104
|
+
"search_strategy": {
|
|
105
|
+
"openalex_email": "researcher@university.edu",
|
|
106
|
+
"max_results_per_query": 100,
|
|
107
|
+
"queries": [
|
|
108
|
+
"\"generative ai\" AND \"clinical diagnostics\"",
|
|
109
|
+
"\"large language models\" AND \"medical decision support\""
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"nlp_tuning": {
|
|
113
|
+
"domain_preset": "biomedical_health",
|
|
114
|
+
"n_clusters": 5
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### 3. Run the Automated Pipeline
|
|
122
|
+
|
|
123
|
+
Execute the discovery phase:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
python main.py --run-discovery --config sms_config.json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
1. Review and refine taxonomic names in `output/discovered_topics.json`.
|
|
130
|
+
2. Inspect the pre-selected seminal papers in `output/candidatos_discusion.csv`.
|
|
131
|
+
3. Compile the interactive dashboard and Word document:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
python main.py --compile --config sms_config.json
|
|
135
|
+
python main.py --build-doc --config sms_config.json
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or execute everything end-to-end in a single run:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
python main.py --all --config sms_config.json
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 📊 Comparison with the State-of-the-Art
|
|
147
|
+
|
|
148
|
+
| Capability | ASReview | Generic BERTopic | Manual Review Tools (Covidence / Rayyan) | **SciMapper** |
|
|
149
|
+
| :--- | :---: | :---: | :---: | :---: |
|
|
150
|
+
| **Academic Ingestion** | ❌ Manual (CSV/RIS) | ❌ None | ❌ Manual Import | **✅ Automated OpenAlex API** |
|
|
151
|
+
| **Deduplication** | ⚠️ Basic | ❌ None | ⚠️ Exact match | **✅ Canonical DOI + Title Hash** |
|
|
152
|
+
| **Semantic Representations** | ✅ Optional | ✅ Native | ❌ None | **✅ Pre-tuned Domain Transformers** |
|
|
153
|
+
| **Topic Modeling ($c$-TF-IDF)** | ❌ Binary only | ✅ Yes | ❌ None | **✅ Mathematical $c$-TF-IDF** |
|
|
154
|
+
| **Objective PRISMA Exclusion** | ❌ Human judgment | ❌ None | ❌ Human labeling | **✅ Algorithmic Outlier Clustering** |
|
|
155
|
+
| **Cartography with Convex Hulls** | ❌ None | ⚠️ Scatter only | ❌ None | **✅ 2D Hulls + Centroids + Density** |
|
|
156
|
+
| **Temporal CAGR Tracking** | ❌ None | ❌ Manual | ❌ None | **✅ Automated Hot-Topic Radar** |
|
|
157
|
+
| **Automated Manuscript (.docx)** | ❌ None | ❌ None | ❌ Export tables | **✅ Formatted PRISMA Word Paper** |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 🔬 Theoretical Foundations
|
|
162
|
+
|
|
163
|
+
The framework's methodological and algorithmic foundations are grounded in established literature:
|
|
164
|
+
|
|
165
|
+
1. **Topic Modeling & Semantic Representation:**
|
|
166
|
+
Based on the modular architecture described by **Jay Alammar and Maarten Grootendorst** in *Hands-On Large Language Models* (O'Reilly, Chapter 5: *Text Clustering and Topic Modeling*), decoupling dense vector embeddings, dimensionality reduction, clustering, and class-based TF-IDF ($c$-TF-IDF).
|
|
167
|
+
2. **Systematic Mapping Methodology:**
|
|
168
|
+
Follows the seminal guidelines of **Petersen et al. (2008)** (*Systematic Mapping Studies in Software Engineering*) and **Kitchenham & Charters (2007)**.
|
|
169
|
+
3. **Auditability & Reporting Standards:**
|
|
170
|
+
Strict adherence to the **PRISMA 2020** statement (*Preferred Reporting Items for Systematic Reviews and Meta-Analyses*), ensuring transparent and reproducible record accounting.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 📁 Repository Structure
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
SciMapper/
|
|
178
|
+
├── config_template.json # Master template for project setup
|
|
179
|
+
├── test_config.json # Quick verification test suite
|
|
180
|
+
├── main.py # Unified CLI orchestrator
|
|
181
|
+
├── requirements.txt # Python dependency specification
|
|
182
|
+
├── pyproject.toml # Standard packaging & CLI entry point (scimapper)
|
|
183
|
+
├── CITATION.cff # Academic citation metadata
|
|
184
|
+
├── LICENSE # MIT Open Source License
|
|
185
|
+
└── core/
|
|
186
|
+
├── collector.py # OpenAlex API ingestion & abstract reconstruction
|
|
187
|
+
├── embedder.py # Multi-domain Sentence-Transformer pipeline
|
|
188
|
+
├── clusterer.py # Dimensionality reduction (PCA+t-SNE) & c-TF-IDF
|
|
189
|
+
├── selector.py # Stratified seminal paper recommendation
|
|
190
|
+
├── visualizer.py # Plotly 2D interactive maps with Convex Hulls
|
|
191
|
+
└── doc_builder.py # Automated PRISMA Word manuscript generation
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 📖 Citation
|
|
197
|
+
|
|
198
|
+
If you use **SciMapper** in your research, academic reviews, or dissertations, please cite it as:
|
|
199
|
+
|
|
200
|
+
```bibtex
|
|
201
|
+
@software{futo_scimapper_2026,
|
|
202
|
+
author = {Gustavo Andrés Futo},
|
|
203
|
+
title = {SciMapper: Automated Scientific Mapping & Systematic Literature Reviews with Dense Semantic Clustering and PRISMA 2020 Compliance},
|
|
204
|
+
year = {2026},
|
|
205
|
+
publisher = {GitHub},
|
|
206
|
+
journal = {GitHub repository},
|
|
207
|
+
howpublished = {\url{https://github.com/gustavoandresfu/SciMapper}}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 📄 License
|
|
214
|
+
|
|
215
|
+
This project is licensed under the **MIT License** — see the [LICENSE](LICENSE) file for details.
|
|
216
|
+
Author: **Gustavo Andrés Futo** (2026).
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AutoSMS Framework - Módulo de Clustering, c-TF-IDF y Dinámica Temporal
|
|
3
|
+
=====================================================================
|
|
4
|
+
Ejecuta la reducción bidimensional (PCA + t-SNE), agrupamiento semántico,
|
|
5
|
+
extracción formal de palabras clave diferenciales (c-TF-IDF) y el cálculo
|
|
6
|
+
del Radar de Madurez Temporal (CAGR y clasificación de tendencias).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pandas as pd
|
|
14
|
+
from sklearn.decomposition import PCA
|
|
15
|
+
from sklearn.manifold import TSNE
|
|
16
|
+
from sklearn.cluster import KMeans
|
|
17
|
+
from sklearn.feature_extraction.text import CountVectorizer
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("scimapper.clusterer")
|
|
20
|
+
|
|
21
|
+
class AutoClusterer:
|
|
22
|
+
def __init__(self, config: dict):
|
|
23
|
+
self.config = config
|
|
24
|
+
self.nlp_cfg = config.get("nlp_tuning", {})
|
|
25
|
+
self.output_dir = config.get("project_metadata", {}).get("output_dir", "./output")
|
|
26
|
+
os.makedirs(self.output_dir, exist_ok=True)
|
|
27
|
+
self.k = self.nlp_cfg.get("n_clusters", 6)
|
|
28
|
+
|
|
29
|
+
def reduce_2d(self, embeddings: np.ndarray) -> np.ndarray:
|
|
30
|
+
logger.info("Reduciendo dimensionalidad a espacio bidimensional (PCA + t-SNE)...")
|
|
31
|
+
n_samples = embeddings.shape[0]
|
|
32
|
+
if n_samples <= 2:
|
|
33
|
+
return np.zeros((n_samples, 2))
|
|
34
|
+
|
|
35
|
+
pca_dims = min(50, embeddings.shape[1], n_samples - 1)
|
|
36
|
+
pca = PCA(n_components=pca_dims, random_state=42)
|
|
37
|
+
pca_feats = pca.fit_transform(embeddings)
|
|
38
|
+
|
|
39
|
+
perp = min(30, max(2, (n_samples - 1) // 3))
|
|
40
|
+
tsne = TSNE(n_components=2, perplexity=perp, learning_rate="auto", init="pca", random_state=42)
|
|
41
|
+
coords = tsne.fit_transform(pca_feats)
|
|
42
|
+
return coords
|
|
43
|
+
|
|
44
|
+
def cluster(self, embeddings: np.ndarray) -> np.ndarray:
|
|
45
|
+
n_samples = embeddings.shape[0]
|
|
46
|
+
actual_k = min(self.k, max(1, n_samples // 2))
|
|
47
|
+
logger.info("Ejecutando agrupamiento no supervisado en k=%d clusters...", actual_k)
|
|
48
|
+
if actual_k <= 1:
|
|
49
|
+
return np.zeros(n_samples, dtype=int)
|
|
50
|
+
kmeans = KMeans(n_clusters=actual_k, random_state=42, n_init=10)
|
|
51
|
+
labels = kmeans.fit_predict(embeddings)
|
|
52
|
+
return labels
|
|
53
|
+
|
|
54
|
+
def extract_c_tf_idf(self, texts: list, labels: np.ndarray, top_n: int = 8) -> dict:
|
|
55
|
+
logger.info("Calculando Class-based TF-IDF (c-TF-IDF) para extraccion lexica...")
|
|
56
|
+
unique_labels = sorted(list(set(labels)))
|
|
57
|
+
docs_per_cluster = []
|
|
58
|
+
for l in unique_labels:
|
|
59
|
+
cluster_docs = [texts[i] for i in range(len(texts)) if labels[i] == l]
|
|
60
|
+
docs_per_cluster.append(" ".join(cluster_docs))
|
|
61
|
+
|
|
62
|
+
stop_words = self.nlp_cfg.get("stop_words", "english")
|
|
63
|
+
if isinstance(stop_words, str) and stop_words.lower() in ("none", "null", ""):
|
|
64
|
+
stop_words = None
|
|
65
|
+
|
|
66
|
+
vec = CountVectorizer(ngram_range=(1, 2), stop_words=stop_words, min_df=1, max_df=1.0)
|
|
67
|
+
X = vec.fit_transform(docs_per_cluster)
|
|
68
|
+
words = vec.get_feature_names_out()
|
|
69
|
+
|
|
70
|
+
tf = X.toarray().astype(float)
|
|
71
|
+
w_sum = tf.sum(axis=1, keepdims=True)
|
|
72
|
+
tf_norm = tf / np.maximum(w_sum, 1)
|
|
73
|
+
|
|
74
|
+
# IDF clásico por documento (cluster): log(1 + N / (df + 1))
|
|
75
|
+
doc_freq = (tf > 0).sum(axis=0).astype(float)
|
|
76
|
+
n_clusters = max(1, len(docs_per_cluster))
|
|
77
|
+
idf = np.log(1.0 + (n_clusters / (doc_freq + 1.0)))
|
|
78
|
+
c_tf_idf = tf_norm * idf
|
|
79
|
+
|
|
80
|
+
keywords_dict = {}
|
|
81
|
+
for idx, l in enumerate(unique_labels):
|
|
82
|
+
top_idx = np.argsort(c_tf_idf[idx])[::-1][:top_n]
|
|
83
|
+
top_words = [words[i] for i in top_idx if len(words[i]) > 2]
|
|
84
|
+
keywords_dict[int(l)] = top_words
|
|
85
|
+
|
|
86
|
+
return keywords_dict
|
|
87
|
+
|
|
88
|
+
def compute_temporal_dynamics(self, df: pd.DataFrame, labels: np.ndarray) -> dict:
|
|
89
|
+
logger.info("Calculando Radar de Madurez Temporal y CAGR por Topico...")
|
|
90
|
+
dynamics = {}
|
|
91
|
+
unique_labels = sorted(list(set(labels)))
|
|
92
|
+
|
|
93
|
+
# Convertir años a enteros válidos
|
|
94
|
+
years_series = pd.to_numeric(df["publication_year"], errors="coerce").dropna()
|
|
95
|
+
if years_series.empty:
|
|
96
|
+
min_year, max_year = 2018, 2026
|
|
97
|
+
else:
|
|
98
|
+
min_year, max_year = int(years_series.min()), int(years_series.max())
|
|
99
|
+
|
|
100
|
+
span = max(1, max_year - min_year)
|
|
101
|
+
|
|
102
|
+
for l in unique_labels:
|
|
103
|
+
sub = df[df["topic_id"] == l]
|
|
104
|
+
sub_years = pd.to_numeric(sub["publication_year"], errors="coerce").dropna().astype(int)
|
|
105
|
+
|
|
106
|
+
yearly_counts = {}
|
|
107
|
+
for y in range(min_year, max_year + 1):
|
|
108
|
+
yearly_counts[y] = int((sub_years == y).sum())
|
|
109
|
+
|
|
110
|
+
# Cálculo de CAGR suavizado (Laplace +1 para evitar ceros)
|
|
111
|
+
start_val = yearly_counts.get(min_year, 0) + yearly_counts.get(min_year + 1, 0)
|
|
112
|
+
end_val = yearly_counts.get(max_year, 0) + yearly_counts.get(max_year - 1, 0)
|
|
113
|
+
|
|
114
|
+
cagr = ((end_val + 1.0) / (start_val + 1.0)) ** (1.0 / max(1, span - 1)) - 1.0
|
|
115
|
+
cagr_pct = round(cagr * 100, 1)
|
|
116
|
+
|
|
117
|
+
# Proporción de producción en los últimos 3 años
|
|
118
|
+
recent_years = [max_year, max_year - 1, max_year - 2]
|
|
119
|
+
recent_count = sum(yearly_counts.get(y, 0) for y in recent_years)
|
|
120
|
+
recent_ratio = round((recent_count / max(1, len(sub))) * 100, 1)
|
|
121
|
+
|
|
122
|
+
# Clasificación de la dinámica
|
|
123
|
+
if cagr_pct >= 12.0 or recent_ratio >= 45.0:
|
|
124
|
+
category = "Emergente (Hot Topic)"
|
|
125
|
+
badge = "🚀 Emergente"
|
|
126
|
+
elif cagr_pct <= -8.0 and recent_ratio <= 22.0:
|
|
127
|
+
category = "En Declive"
|
|
128
|
+
badge = "📉 En Declive"
|
|
129
|
+
else:
|
|
130
|
+
category = "Maduro / Consolidado"
|
|
131
|
+
badge = "⚖️ Maduro"
|
|
132
|
+
|
|
133
|
+
dynamics[int(l)] = {
|
|
134
|
+
"cagr_percent": cagr_pct,
|
|
135
|
+
"recent_ratio_percent": recent_ratio,
|
|
136
|
+
"trend_category": category,
|
|
137
|
+
"badge": badge,
|
|
138
|
+
"yearly_distribution": yearly_counts
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return dynamics
|
|
142
|
+
|
|
143
|
+
def run(self, df: pd.DataFrame, embeddings: np.ndarray) -> tuple:
|
|
144
|
+
df = df.reset_index(drop=True).copy()
|
|
145
|
+
coords = self.reduce_2d(embeddings)
|
|
146
|
+
df["coord_x"] = coords[:, 0]
|
|
147
|
+
df["coord_y"] = coords[:, 1]
|
|
148
|
+
|
|
149
|
+
labels = self.cluster(embeddings)
|
|
150
|
+
df["topic_id"] = labels
|
|
151
|
+
|
|
152
|
+
texts = (df["title"] + ". " + df["abstract"]).tolist()
|
|
153
|
+
keywords = self.extract_c_tf_idf(texts, labels, top_n=8)
|
|
154
|
+
temporal_info = self.compute_temporal_dynamics(df, labels)
|
|
155
|
+
|
|
156
|
+
# Generar JSON de tópicos descubiertos enriquecido
|
|
157
|
+
topics_summary = {}
|
|
158
|
+
for l in sorted(keywords.keys()):
|
|
159
|
+
sub = df[df["topic_id"] == l]
|
|
160
|
+
sample_titles = sub["title"].head(4).tolist()
|
|
161
|
+
t_dyn = temporal_info.get(l, {})
|
|
162
|
+
|
|
163
|
+
topics_summary[l] = {
|
|
164
|
+
"count": len(sub),
|
|
165
|
+
"percentage": round(len(sub) / len(df) * 100, 1),
|
|
166
|
+
"keywords": keywords[l],
|
|
167
|
+
"sample_titles": sample_titles,
|
|
168
|
+
"assigned_name": f"Tópico {l}: {', '.join(keywords[l][:3]).title()}",
|
|
169
|
+
"exclude_prisma": False,
|
|
170
|
+
"temporal_dynamics": t_dyn
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
topics_json_path = os.path.join(self.output_dir, "discovered_topics.json")
|
|
174
|
+
with open(topics_json_path, "w", encoding="utf-8") as f:
|
|
175
|
+
json.dump(topics_summary, f, indent=2, ensure_ascii=False)
|
|
176
|
+
|
|
177
|
+
logger.info("Archivo de ajuste humano generado en: %s", topics_json_path)
|
|
178
|
+
return df, topics_summary
|