scitex 2.15.2__py3-none-any.whl → 2.15.4__py3-none-any.whl
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.
- scitex/_mcp_resources/__init__.py +2 -0
- scitex/_mcp_resources/_scholar.py +148 -0
- scitex/_mcp_tools/scholar.py +50 -99
- scitex/_mcp_tools/social.py +15 -232
- scitex/_mcp_tools/writer.py +7 -17
- scitex/canvas/mcp_server.py +16 -3
- scitex/capture/mcp_server.py +16 -2
- scitex/cli/audio.py +90 -20
- scitex/cli/capture.py +120 -0
- scitex/cli/introspect.py +19 -12
- scitex/cli/plt.py +78 -21
- scitex/cli/scholar/__init__.py +160 -2
- scitex/cli/scholar/_crossref_scitex.py +25 -266
- scitex/cli/scholar/_openalex_scitex.py +55 -0
- scitex/cli/social.py +63 -22
- scitex/cli/stats.py +121 -3
- scitex/cli/writer.py +49 -423
- scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +90 -0
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +1571 -0
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +6262 -0
- scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +1274 -0
- scitex/dev/plt/data/mpl/dir_ax.txt +459 -0
- scitex/introspect/_list_api.py +5 -2
- scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +2 -2
- scitex/scholar/__init__.py +14 -9
- scitex/scholar/_mcp/crossref_tool_schemas.py +133 -0
- scitex/scholar/_mcp/openalex_handlers.py +212 -0
- scitex/scholar/_mcp/openalex_tool_schemas.py +96 -0
- scitex/scholar/_mcp/tool_schemas.py +16 -1
- scitex/scholar/data/.gitkeep +0 -0
- scitex/scholar/data/README.md +44 -0
- scitex/scholar/data/bib_files/bibliography.bib +1952 -0
- scitex/scholar/data/bib_files/neurovista.bib +277 -0
- scitex/scholar/data/bib_files/neurovista_enriched.bib +441 -0
- scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +441 -0
- scitex/scholar/data/bib_files/neurovista_processed.bib +338 -0
- scitex/scholar/data/bib_files/openaccess.bib +89 -0
- scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +2178 -0
- scitex/scholar/data/bib_files/pac.bib +698 -0
- scitex/scholar/data/bib_files/pac_enriched.bib +1061 -0
- scitex/scholar/data/bib_files/pac_processed.bib +0 -0
- scitex/scholar/data/bib_files/pac_titles.txt +75 -0
- scitex/scholar/data/bib_files/paywalled.bib +98 -0
- scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +58 -0
- scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +87 -0
- scitex/scholar/data/bib_files/seizure_prediction.bib +694 -0
- scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
- scitex/scholar/data/bib_files/test_complete_enriched.bib +437 -0
- scitex/scholar/data/bib_files/test_final_enriched.bib +437 -0
- scitex/scholar/data/bib_files/test_seizure.bib +46 -0
- scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
- scitex/scholar/data/impact_factor.db +0 -0
- scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +2 -2
- scitex/scholar/local_dbs/__init__.py +31 -0
- scitex/scholar/local_dbs/crossref_scitex.py +30 -0
- scitex/scholar/local_dbs/openalex_scitex.py +30 -0
- scitex/scholar/mcp_server.py +59 -4
- scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +2 -2
- scitex/stats/mcp_server.py +16 -3
- scitex/template/mcp_server.py +16 -3
- scitex/ui/mcp_server.py +16 -3
- scitex/writer/__init__.py +43 -34
- {scitex-2.15.2.dist-info → scitex-2.15.4.dist-info}/METADATA +22 -3
- {scitex-2.15.2.dist-info → scitex-2.15.4.dist-info}/RECORD +70 -38
- scitex/scholar/crossref_scitex.py +0 -367
- scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +0 -462
- scitex/scholar/url_finder/.tmp/open_url/README.md +0 -223
- scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +0 -694
- scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +0 -1160
- scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +0 -344
- scitex/scholar/url_finder/.tmp/open_url/__init__.py +0 -24
- {scitex-2.15.2.dist-info → scitex-2.15.4.dist-info}/WHEEL +0 -0
- {scitex-2.15.2.dist-info → scitex-2.15.4.dist-info}/entry_points.txt +0 -0
- {scitex-2.15.2.dist-info → scitex-2.15.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-29
|
|
3
|
+
# File: src/scitex/scholar/_mcp/openalex_handlers.py
|
|
4
|
+
"""OpenAlex-SciTeX handler implementations via openalex-local delegation.
|
|
5
|
+
|
|
6
|
+
These handlers delegate to openalex-local for fast access to 250M+ papers.
|
|
7
|
+
Branded as openalex-scitex to distinguish from official OpenAlex API.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"openalex_search_handler",
|
|
17
|
+
"openalex_get_handler",
|
|
18
|
+
"openalex_count_handler",
|
|
19
|
+
"openalex_info_handler",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _ensure_openalex():
|
|
24
|
+
"""Ensure openalex_scitex module is available."""
|
|
25
|
+
try:
|
|
26
|
+
from scitex.scholar import openalex_scitex
|
|
27
|
+
|
|
28
|
+
return openalex_scitex
|
|
29
|
+
except ImportError as e:
|
|
30
|
+
raise RuntimeError(
|
|
31
|
+
"openalex-local not installed. Install with: pip install openalex-local"
|
|
32
|
+
) from e
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def openalex_search_handler(
|
|
36
|
+
query: str,
|
|
37
|
+
limit: int = 20,
|
|
38
|
+
offset: int = 0,
|
|
39
|
+
year_min: int | None = None,
|
|
40
|
+
year_max: int | None = None,
|
|
41
|
+
) -> dict:
|
|
42
|
+
"""Search OpenAlex database (250M+ papers) via openalex-local.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
query: Search query string (full-text search)
|
|
46
|
+
limit: Maximum number of results (default: 20)
|
|
47
|
+
offset: Number of results to skip for pagination
|
|
48
|
+
year_min: Minimum publication year filter
|
|
49
|
+
year_max: Maximum publication year filter
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
openalex = _ensure_openalex()
|
|
53
|
+
loop = asyncio.get_running_loop()
|
|
54
|
+
|
|
55
|
+
def do_search():
|
|
56
|
+
# Fetch more results for filtering
|
|
57
|
+
fetch_limit = limit * 2 if (year_min or year_max) else limit
|
|
58
|
+
results = openalex.search(query, limit=fetch_limit, offset=offset)
|
|
59
|
+
|
|
60
|
+
papers = []
|
|
61
|
+
for work in results:
|
|
62
|
+
# Apply year filters
|
|
63
|
+
if year_min and work.year and work.year < year_min:
|
|
64
|
+
continue
|
|
65
|
+
if year_max and work.year and work.year > year_max:
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
papers.append(
|
|
69
|
+
{
|
|
70
|
+
"doi": work.doi,
|
|
71
|
+
"title": work.title,
|
|
72
|
+
"authors": work.authors[:10] if work.authors else [],
|
|
73
|
+
"year": work.year,
|
|
74
|
+
"journal": work.journal,
|
|
75
|
+
"abstract": (
|
|
76
|
+
work.abstract[:500] + "..."
|
|
77
|
+
if work.abstract and len(work.abstract) > 500
|
|
78
|
+
else work.abstract
|
|
79
|
+
),
|
|
80
|
+
"citation_count": getattr(work, "citation_count", None),
|
|
81
|
+
"reference_count": getattr(work, "reference_count", None),
|
|
82
|
+
"type": getattr(work, "type", None),
|
|
83
|
+
"openalex_id": getattr(work, "openalex_id", None),
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
if len(papers) >= limit:
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
return papers, getattr(results, "total", len(papers))
|
|
90
|
+
|
|
91
|
+
papers, total = await loop.run_in_executor(None, do_search)
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"success": True,
|
|
95
|
+
"query": query,
|
|
96
|
+
"total": total,
|
|
97
|
+
"count": len(papers),
|
|
98
|
+
"offset": offset,
|
|
99
|
+
"limit": limit,
|
|
100
|
+
"papers": papers,
|
|
101
|
+
"source": "openalex_local",
|
|
102
|
+
"timestamp": datetime.now().isoformat(),
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return {"success": False, "error": str(e)}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def openalex_get_handler(
|
|
110
|
+
doi: str = None,
|
|
111
|
+
openalex_id: str = None,
|
|
112
|
+
) -> dict:
|
|
113
|
+
"""Get a paper by DOI or OpenAlex ID from OpenAlex database.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
doi: DOI of the paper (e.g., '10.1038/nature12373')
|
|
117
|
+
openalex_id: OpenAlex ID (e.g., 'W2100837269')
|
|
118
|
+
"""
|
|
119
|
+
if not doi and not openalex_id:
|
|
120
|
+
return {"success": False, "error": "Must provide either doi or openalex_id"}
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
openalex = _ensure_openalex()
|
|
124
|
+
loop = asyncio.get_running_loop()
|
|
125
|
+
|
|
126
|
+
def do_get():
|
|
127
|
+
identifier = doi or openalex_id
|
|
128
|
+
work = openalex.get(identifier)
|
|
129
|
+
if not work:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
result = {
|
|
133
|
+
"doi": work.doi,
|
|
134
|
+
"title": work.title,
|
|
135
|
+
"authors": work.authors,
|
|
136
|
+
"year": work.year,
|
|
137
|
+
"journal": work.journal,
|
|
138
|
+
"abstract": work.abstract,
|
|
139
|
+
"citation_count": getattr(work, "citation_count", None),
|
|
140
|
+
"reference_count": getattr(work, "reference_count", None),
|
|
141
|
+
"type": getattr(work, "type", None),
|
|
142
|
+
"openalex_id": getattr(work, "openalex_id", None),
|
|
143
|
+
"publisher": getattr(work, "publisher", None),
|
|
144
|
+
"url": getattr(work, "url", None),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
result = await loop.run_in_executor(None, do_get)
|
|
150
|
+
|
|
151
|
+
if result is None:
|
|
152
|
+
identifier = doi or openalex_id
|
|
153
|
+
return {
|
|
154
|
+
"success": False,
|
|
155
|
+
"error": f"Paper not found: {identifier}",
|
|
156
|
+
"identifier": identifier,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
"success": True,
|
|
161
|
+
"paper": result,
|
|
162
|
+
"source": "openalex_local",
|
|
163
|
+
"timestamp": datetime.now().isoformat(),
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
return {"success": False, "error": str(e)}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
async def openalex_count_handler(query: str) -> dict:
|
|
171
|
+
"""Count papers matching a search query.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
query: Search query string
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
openalex = _ensure_openalex()
|
|
178
|
+
loop = asyncio.get_running_loop()
|
|
179
|
+
|
|
180
|
+
count = await loop.run_in_executor(None, openalex.count, query)
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
"success": True,
|
|
184
|
+
"query": query,
|
|
185
|
+
"count": count,
|
|
186
|
+
"source": "openalex_local",
|
|
187
|
+
"timestamp": datetime.now().isoformat(),
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
return {"success": False, "error": str(e)}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def openalex_info_handler() -> dict:
|
|
195
|
+
"""Get information about OpenAlex database configuration and status."""
|
|
196
|
+
try:
|
|
197
|
+
openalex = _ensure_openalex()
|
|
198
|
+
loop = asyncio.get_running_loop()
|
|
199
|
+
|
|
200
|
+
info = await loop.run_in_executor(None, openalex.info)
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
"success": True,
|
|
204
|
+
"info": info,
|
|
205
|
+
"timestamp": datetime.now().isoformat(),
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
return {"success": False, "error": str(e)}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# EOF
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-29
|
|
3
|
+
# File: src/scitex/scholar/_mcp/openalex_tool_schemas.py
|
|
4
|
+
"""OpenAlex-Local tool schemas for MCP server.
|
|
5
|
+
|
|
6
|
+
Provides access to 250M+ papers via openalex-local database.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import mcp.types as types
|
|
12
|
+
|
|
13
|
+
__all__ = ["get_openalex_tool_schemas"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_openalex_tool_schemas() -> list[types.Tool]:
|
|
17
|
+
"""Return OpenAlex-Local tool schemas."""
|
|
18
|
+
return [
|
|
19
|
+
types.Tool(
|
|
20
|
+
name="openalex_search",
|
|
21
|
+
description=(
|
|
22
|
+
"Search OpenAlex database (250M+ papers) via openalex-local. "
|
|
23
|
+
"Fast full-text search with year filtering. Includes citation data."
|
|
24
|
+
),
|
|
25
|
+
inputSchema={
|
|
26
|
+
"type": "object",
|
|
27
|
+
"properties": {
|
|
28
|
+
"query": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Search query string (full-text search)",
|
|
31
|
+
},
|
|
32
|
+
"limit": {
|
|
33
|
+
"type": "integer",
|
|
34
|
+
"description": "Maximum number of results",
|
|
35
|
+
"default": 20,
|
|
36
|
+
},
|
|
37
|
+
"offset": {
|
|
38
|
+
"type": "integer",
|
|
39
|
+
"description": "Number of results to skip for pagination",
|
|
40
|
+
"default": 0,
|
|
41
|
+
},
|
|
42
|
+
"year_min": {
|
|
43
|
+
"type": "integer",
|
|
44
|
+
"description": "Minimum publication year filter",
|
|
45
|
+
},
|
|
46
|
+
"year_max": {
|
|
47
|
+
"type": "integer",
|
|
48
|
+
"description": "Maximum publication year filter",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
"required": ["query"],
|
|
52
|
+
},
|
|
53
|
+
),
|
|
54
|
+
types.Tool(
|
|
55
|
+
name="openalex_get",
|
|
56
|
+
description="Get a paper by DOI or OpenAlex ID from OpenAlex database.",
|
|
57
|
+
inputSchema={
|
|
58
|
+
"type": "object",
|
|
59
|
+
"properties": {
|
|
60
|
+
"doi": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "DOI of the paper (e.g., '10.1038/nature12373')",
|
|
63
|
+
},
|
|
64
|
+
"openalex_id": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"description": "OpenAlex ID (e.g., 'W2100837269')",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
types.Tool(
|
|
72
|
+
name="openalex_count",
|
|
73
|
+
description="Count papers matching a search query in OpenAlex database.",
|
|
74
|
+
inputSchema={
|
|
75
|
+
"type": "object",
|
|
76
|
+
"properties": {
|
|
77
|
+
"query": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"description": "Search query string",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
"required": ["query"],
|
|
83
|
+
},
|
|
84
|
+
),
|
|
85
|
+
types.Tool(
|
|
86
|
+
name="openalex_info",
|
|
87
|
+
description="Get OpenAlex database configuration and status.",
|
|
88
|
+
inputSchema={
|
|
89
|
+
"type": "object",
|
|
90
|
+
"properties": {},
|
|
91
|
+
},
|
|
92
|
+
),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# EOF
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
# File: src/scitex/scholar/_mcp.tool_schemas.py
|
|
4
4
|
# ----------------------------------------
|
|
5
5
|
"""Tool schemas for the scitex-scholar MCP server."""
|
|
6
|
+
|
|
6
7
|
from __future__ import annotations
|
|
8
|
+
|
|
7
9
|
import mcp.types as types
|
|
10
|
+
|
|
8
11
|
__all__ = ["get_tool_schemas"]
|
|
12
|
+
|
|
13
|
+
|
|
9
14
|
def get_tool_schemas() -> list[types.Tool]:
|
|
10
15
|
"""Return all tool schemas for the Scholar MCP server."""
|
|
11
16
|
base_schemas = [
|
|
@@ -500,6 +505,16 @@ def get_tool_schemas() -> list[types.Tool]:
|
|
|
500
505
|
},
|
|
501
506
|
),
|
|
502
507
|
]
|
|
508
|
+
from .crossref_tool_schemas import get_crossref_tool_schemas
|
|
503
509
|
from .job_tool_schemas import get_job_tool_schemas
|
|
504
|
-
|
|
510
|
+
from .openalex_tool_schemas import get_openalex_tool_schemas
|
|
511
|
+
|
|
512
|
+
return (
|
|
513
|
+
base_schemas
|
|
514
|
+
+ get_job_tool_schemas()
|
|
515
|
+
+ get_crossref_tool_schemas()
|
|
516
|
+
+ get_openalex_tool_schemas()
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
|
|
505
520
|
# EOF
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Scholar Data Directory
|
|
2
|
+
|
|
3
|
+
User-provided data files. This directory is gitignored.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
data/
|
|
9
|
+
└── impact_factor/
|
|
10
|
+
├── JCR_IF_2024.xlsx # JCR Excel file (user-provided)
|
|
11
|
+
├── JCR_IF_2024.db # SQLite database (generated)
|
|
12
|
+
└── impact_factor.db -> JCR_IF_2024.db (symlink to active DB)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Important
|
|
16
|
+
|
|
17
|
+
- **Data NOT included in git**: This directory is gitignored
|
|
18
|
+
- **User responsibility**: Users must provide their own JCR data
|
|
19
|
+
- **Licensing**: Users must ensure proper licensing for any data
|
|
20
|
+
|
|
21
|
+
## Adding JCR Data
|
|
22
|
+
|
|
23
|
+
1. Obtain JCR Excel file from Clarivate or authorized source
|
|
24
|
+
2. Place in `src/scitex/scholar/data/impact_factor/JCR_IF_YYYY.xlsx`
|
|
25
|
+
3. Build database (optional - will auto-build if needed):
|
|
26
|
+
```python
|
|
27
|
+
from scitex.scholar.impact_factor.jcr import build_database
|
|
28
|
+
build_database("JCR_IF_2024.xlsx")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## File Naming Convention
|
|
32
|
+
|
|
33
|
+
- Excel: `JCR_IF_YYYY.xlsx` (e.g., JCR_IF_2024.xlsx)
|
|
34
|
+
- Database: `JCR_IF_YYYY.db` (e.g., JCR_IF_2024.db)
|
|
35
|
+
- Symlink: `impact_factor.db` → points to current year DB
|
|
36
|
+
|
|
37
|
+
## Legal Notice
|
|
38
|
+
|
|
39
|
+
JCR data is proprietary (Clarivate Analytics). Users are responsible for:
|
|
40
|
+
- Obtaining data through authorized channels
|
|
41
|
+
- Compliance with licensing terms
|
|
42
|
+
- Not distributing data files
|
|
43
|
+
|
|
44
|
+
We provide only the code to use the data, not the data itself.
|