scitex 2.17.0__py3-none-any.whl → 2.17.3__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.
Files changed (73) hide show
  1. scitex/_dev/__init__.py +122 -0
  2. scitex/_dev/_config.py +391 -0
  3. scitex/_dev/_dashboard/__init__.py +11 -0
  4. scitex/_dev/_dashboard/_app.py +89 -0
  5. scitex/_dev/_dashboard/_routes.py +169 -0
  6. scitex/_dev/_dashboard/_scripts.py +301 -0
  7. scitex/_dev/_dashboard/_styles.py +205 -0
  8. scitex/_dev/_dashboard/_templates.py +117 -0
  9. scitex/_dev/_dashboard/static/version-dashboard-favicon.svg +12 -0
  10. scitex/_dev/_ecosystem.py +109 -0
  11. scitex/_dev/_github.py +360 -0
  12. scitex/_dev/_mcp/__init__.py +11 -0
  13. scitex/_dev/_mcp/handlers.py +182 -0
  14. scitex/_dev/_ssh.py +332 -0
  15. scitex/_dev/_versions.py +272 -0
  16. scitex/_mcp_tools/__init__.py +2 -0
  17. scitex/_mcp_tools/dev.py +186 -0
  18. scitex/audio/_audio_check.py +84 -41
  19. scitex/cli/capture.py +45 -22
  20. scitex/cli/dev.py +494 -0
  21. scitex/cli/main.py +2 -0
  22. scitex/cli/stats.py +48 -20
  23. scitex/cli/verify.py +33 -36
  24. scitex/plt/__init__.py +16 -6
  25. scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
  26. scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
  27. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
  28. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
  29. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
  30. scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
  31. scitex/template/__init__.py +18 -1
  32. scitex/template/clone_research_minimal.py +111 -0
  33. scitex/verify/README.md +0 -12
  34. scitex/verify/__init__.py +0 -4
  35. scitex/verify/_visualize.py +0 -4
  36. scitex/verify/_viz/__init__.py +0 -18
  37. {scitex-2.17.0.dist-info → scitex-2.17.3.dist-info}/METADATA +2 -1
  38. {scitex-2.17.0.dist-info → scitex-2.17.3.dist-info}/RECORD +41 -49
  39. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
  40. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
  41. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
  42. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
  43. scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
  44. scitex/scholar/data/.gitkeep +0 -0
  45. scitex/scholar/data/README.md +0 -44
  46. scitex/scholar/data/bib_files/bibliography.bib +0 -1952
  47. scitex/scholar/data/bib_files/neurovista.bib +0 -277
  48. scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
  49. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
  50. scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
  51. scitex/scholar/data/bib_files/openaccess.bib +0 -89
  52. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
  53. scitex/scholar/data/bib_files/pac.bib +0 -698
  54. scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
  55. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  56. scitex/scholar/data/bib_files/pac_titles.txt +0 -75
  57. scitex/scholar/data/bib_files/paywalled.bib +0 -98
  58. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
  59. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
  60. scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
  61. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  62. scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
  63. scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
  64. scitex/scholar/data/bib_files/test_seizure.bib +0 -46
  65. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  66. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  67. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  68. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  69. scitex/scholar/data/impact_factor.db +0 -0
  70. scitex/verify/_viz/_plotly.py +0 -193
  71. {scitex-2.17.0.dist-info → scitex-2.17.3.dist-info}/WHEEL +0 -0
  72. {scitex-2.17.0.dist-info → scitex-2.17.3.dist-info}/entry_points.txt +0 -0
  73. {scitex-2.17.0.dist-info → scitex-2.17.3.dist-info}/licenses/LICENSE +0 -0
scitex/cli/verify.py CHANGED
@@ -262,13 +262,12 @@ def verify_chain_cmd(target_file, verbose, mermaid, as_json):
262
262
  @click.option("--session", "-s", help="Session ID to visualize")
263
263
  @click.option("--file", "-f", "target_file", help="Target file to trace chain")
264
264
  @click.option("--title", "-t", default="Verification DAG", help="Title for output")
265
- @click.option("--plotly", "-p", is_flag=True, help="Use Plotly (interactive)")
266
- def render_cmd(output_path, session, target_file, title, plotly):
265
+ def render_cmd(output_path, session, target_file, title):
267
266
  """
268
267
  Render verification DAG to file (HTML, PNG, SVG, or Mermaid).
269
268
 
270
269
  The output format is determined by the file extension:
271
- - .html: Interactive HTML (Mermaid.js or Plotly with --plotly)
270
+ - .html: Interactive HTML with Mermaid.js
272
271
  - .png: PNG image
273
272
  - .svg: SVG image
274
273
  - .mmd: Raw Mermaid code
@@ -276,7 +275,6 @@ def render_cmd(output_path, session, target_file, title, plotly):
276
275
  \b
277
276
  Examples:
278
277
  scitex verify render dag.html --file ./results/fig.png
279
- scitex verify render dag.html --file ./results/fig.png --plotly
280
278
  scitex verify render dag.png --session 2025Y-11M-18D-09h12m03s
281
279
  """
282
280
  try:
@@ -284,24 +282,14 @@ def render_cmd(output_path, session, target_file, title, plotly):
284
282
  click.secho("Error: Specify --session or --file", fg="red", err=True)
285
283
  sys.exit(1)
286
284
 
287
- if plotly:
288
- from scitex.verify import render_plotly_dag
285
+ from scitex.verify import render_dag
289
286
 
290
- result_path = render_plotly_dag(
291
- output_path=output_path,
292
- session_id=session,
293
- target_file=target_file,
294
- title=title,
295
- )
296
- else:
297
- from scitex.verify import render_dag
298
-
299
- result_path = render_dag(
300
- output_path=output_path,
301
- session_id=session,
302
- target_file=target_file,
303
- title=title,
304
- )
287
+ result_path = render_dag(
288
+ output_path=output_path,
289
+ session_id=session,
290
+ target_file=target_file,
291
+ title=title,
292
+ )
305
293
  click.secho(f"Rendered to: {result_path}", fg="green")
306
294
 
307
295
  except Exception as e:
@@ -433,25 +421,34 @@ def mcp(ctx):
433
421
 
434
422
 
435
423
  @mcp.command("list-tools")
436
- def list_tools():
437
- """
438
- List available MCP tools for verification.
439
-
440
- \b
441
- Example:
442
- scitex verify mcp list-tools
443
- """
424
+ @click.option("-v", "--verbose", count=True, help="-v params, -vv returns")
425
+ def list_tools(verbose):
426
+ """List available MCP tools for verification."""
444
427
  click.secho("Verify MCP Tools", fg="cyan", bold=True)
445
428
  click.echo()
429
+ # (name, desc, params, returns)
446
430
  tools = [
447
- ("verify_list", "List all tracked runs with verification status"),
448
- ("verify_run", "Verify a specific session run"),
449
- ("verify_chain", "Verify dependency chain for a target file"),
450
- ("verify_status", "Show verification status (like git status)"),
451
- ("verify_stats", "Show database statistics"),
431
+ ("verify_list", "List tracked runs", "limit=50, status_filter=None", "JSON"),
432
+ ("verify_run", "Verify a session run", "session_or_path: str", "JSON"),
433
+ ("verify_chain", "Verify dependency chain", "target_file: str", "JSON"),
434
+ ("verify_status", "Show status (like git status)", "", "JSON"),
435
+ ("verify_stats", "Show database statistics", "", "JSON"),
436
+ (
437
+ "verify_mermaid",
438
+ "Generate Mermaid DAG",
439
+ "session_id=None, target_file=None",
440
+ "str",
441
+ ),
452
442
  ]
453
- for name, desc in tools:
454
- click.echo(f" {name}: {desc}")
443
+ for name, desc, params, returns in tools:
444
+ click.secho(f" {name}", fg="green", bold=True, nl=False)
445
+ click.echo(f": {desc}")
446
+ if verbose >= 1 and params:
447
+ click.echo(f" params: {params}")
448
+ if verbose >= 2:
449
+ click.echo(f" returns: {returns}")
450
+ if verbose >= 1:
451
+ click.echo()
455
452
 
456
453
 
457
454
  @verify.command("list-python-apis")
scitex/plt/__init__.py CHANGED
@@ -75,8 +75,8 @@ if _FIGRECIPE_AVAILABLE:
75
75
  from figrecipe import (
76
76
  compose,
77
77
  crop,
78
- edit,
79
78
  extract_data,
79
+ gui,
80
80
  info,
81
81
  list_presets,
82
82
  load_style,
@@ -87,11 +87,17 @@ if _FIGRECIPE_AVAILABLE:
87
87
  validate,
88
88
  )
89
89
 
90
+ # Backward compatibility alias
91
+ edit = gui
92
+
90
93
  # Internal imports (not part of figrecipe public API)
91
94
  from figrecipe._api._notebook import enable_svg
92
95
  from figrecipe._api._seaborn_proxy import sns
93
96
  from figrecipe._api._style_manager import STYLE, apply_style
94
- from figrecipe._composition import align_panels, distribute_panels, smart_align
97
+ from figrecipe._composition import align_panels, align_smart, distribute_panels
98
+
99
+ # Backward compatibility alias
100
+ smart_align = align_smart
95
101
  from figrecipe._graph_presets import get_preset as get_graph_preset
96
102
  from figrecipe._graph_presets import list_presets as list_graph_presets
97
103
  from figrecipe._graph_presets import register_preset as register_graph_preset
@@ -120,11 +126,13 @@ else:
120
126
  validate = _not_available
121
127
  extract_data = _not_available
122
128
  info = _not_available
123
- edit = _not_available
129
+ gui = _not_available
130
+ edit = _not_available # Backward compatibility alias
124
131
  compose = _not_available
125
132
  align_panels = _not_available
126
133
  distribute_panels = _not_available
127
- smart_align = _not_available
134
+ align_smart = _not_available
135
+ smart_align = _not_available # Backward compatibility alias
128
136
  sns = None
129
137
  enable_svg = _not_available
130
138
  get_graph_preset = _not_available
@@ -438,7 +446,8 @@ __all__ = [
438
446
  "validate",
439
447
  "extract_data",
440
448
  "info",
441
- "edit",
449
+ "gui",
450
+ "edit", # Backward compatibility alias for gui
442
451
  # Style management
443
452
  "STYLE",
444
453
  "load_style",
@@ -449,7 +458,8 @@ __all__ = [
449
458
  "compose",
450
459
  "align_panels",
451
460
  "distribute_panels",
452
- "smart_align",
461
+ "align_smart",
462
+ "smart_align", # Backward compatibility alias for align_smart
453
463
  # Graph visualization
454
464
  "draw_graph",
455
465
  "get_graph_preset",
@@ -0,0 +1,462 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Time-stamp: "2025-08-01 13:15:00"
4
+ # Author: Claude
5
+ # File: KNOWN_RESOLVERS.py
6
+
7
+ """
8
+ Known OpenURL resolvers from various institutions worldwide.
9
+
10
+ This module contains a curated list of OpenURL resolvers used by
11
+ academic institutions for accessing scholarly content.
12
+
13
+ Sources:
14
+ - Zotero OpenURL Resolver Directory: https://www.zotero.org/openurl_resolvers
15
+ - Individual institution library websites
16
+ - Common resolver patterns
17
+ """
18
+
19
+ from typing import Dict, List, Optional
20
+
21
+ # Major OpenURL resolver vendors
22
+ RESOLVER_VENDORS = {
23
+ "ExLibris": {
24
+ "patterns": ["sfx", "exlibrisgroup.com"],
25
+ "description": "Ex Libris SFX resolver (very common)"
26
+ },
27
+ "SerialsSolutions": {
28
+ "patterns": ["serialssolutions.com", "360link"],
29
+ "description": "ProQuest SerialsSolutions 360 Link"
30
+ },
31
+ "EBSCO": {
32
+ "patterns": ["ebscohost.com/openurlresolver", "linkssource.ebsco.com"],
33
+ "description": "EBSCO Full Text Finder"
34
+ },
35
+ "OCLC": {
36
+ "patterns": ["worldcat.org", "oclc.org"],
37
+ "description": "OCLC WorldCat resolver"
38
+ },
39
+ "Ovid": {
40
+ "patterns": ["ovid.com", "linksolver"],
41
+ "description": "Ovid LinkSolver"
42
+ }
43
+ }
44
+
45
+ # Known institutional OpenURL resolvers
46
+ KNOWN_RESOLVERS: Dict[str, Dict[str, str]] = {
47
+ # United States
48
+ "Harvard University": {
49
+ "url": "https://sfx.hul.harvard.edu/sfx_local",
50
+ "country": "US",
51
+ "vendor": "ExLibris"
52
+ },
53
+ "MIT": {
54
+ "url": "https://owens.mit.edu/sfx_local",
55
+ "country": "US",
56
+ "vendor": "ExLibris"
57
+ },
58
+ "Stanford University": {
59
+ "url": "https://stanford.idm.oclc.org/login?url=",
60
+ "country": "US",
61
+ "vendor": "OCLC"
62
+ },
63
+ "Yale University": {
64
+ "url": "https://yale.idm.oclc.org/login?url=",
65
+ "country": "US",
66
+ "vendor": "OCLC"
67
+ },
68
+ "University of California, Berkeley": {
69
+ "url": "https://ucelinks.cdlib.org:8443/sfx_ucb",
70
+ "country": "US",
71
+ "vendor": "ExLibris"
72
+ },
73
+ "UCLA": {
74
+ "url": "https://ucelinks.cdlib.org:8443/sfx_ucla",
75
+ "country": "US",
76
+ "vendor": "ExLibris"
77
+ },
78
+ "Columbia University": {
79
+ "url": "https://resolver.library.columbia.edu/openurl",
80
+ "country": "US",
81
+ "vendor": "SerialsSolutions"
82
+ },
83
+ "Princeton University": {
84
+ "url": "https://princeton.idm.oclc.org/login?url=",
85
+ "country": "US",
86
+ "vendor": "OCLC"
87
+ },
88
+ "University of Chicago": {
89
+ "url": "https://proxy.uchicago.edu/login?url=",
90
+ "country": "US",
91
+ "vendor": "Custom"
92
+ },
93
+ "Johns Hopkins": {
94
+ "url": "https://openurl.library.jhu.edu",
95
+ "country": "US",
96
+ "vendor": "Custom"
97
+ },
98
+
99
+ # United Kingdom
100
+ "University of Oxford": {
101
+ "url": "https://fs.oxfordjournals.org/openurl",
102
+ "country": "UK",
103
+ "vendor": "Custom"
104
+ },
105
+ "University of Cambridge": {
106
+ "url": "https://cambridge.idm.oclc.org/login?url=",
107
+ "country": "UK",
108
+ "vendor": "OCLC"
109
+ },
110
+ "Imperial College London": {
111
+ "url": "https://imperial.idm.oclc.org/login?url=",
112
+ "country": "UK",
113
+ "vendor": "OCLC"
114
+ },
115
+ "UCL": {
116
+ "url": "https://ucl.idm.oclc.org/login?url=",
117
+ "country": "UK",
118
+ "vendor": "OCLC"
119
+ },
120
+ "University of Edinburgh": {
121
+ "url": "https://discovered.ed.ac.uk/openurl",
122
+ "country": "UK",
123
+ "vendor": "Custom"
124
+ },
125
+
126
+ # Canada
127
+ "University of Toronto": {
128
+ "url": "https://myaccess.library.utoronto.ca/login?url=",
129
+ "country": "CA",
130
+ "vendor": "Custom"
131
+ },
132
+ "McGill University": {
133
+ "url": "https://mcgill.on.worldcat.org/atoztitles/link",
134
+ "country": "CA",
135
+ "vendor": "OCLC"
136
+ },
137
+ "University of British Columbia": {
138
+ "url": "https://ubc.summon.serialssolutions.com/link",
139
+ "country": "CA",
140
+ "vendor": "SerialsSolutions"
141
+ },
142
+
143
+ # Australia
144
+ "University of Melbourne": {
145
+ "url": "https://unimelb.hosted.exlibrisgroup.com/sfxlcl41",
146
+ "country": "AU",
147
+ "vendor": "ExLibris"
148
+ },
149
+ "University of Sydney": {
150
+ "url": "https://ap01.alma.exlibrisgroup.com/view/uresolver/61USYD_INST/openurl",
151
+ "country": "AU",
152
+ "vendor": "ExLibris"
153
+ },
154
+ "Australian National University": {
155
+ "url": "https://anu.hosted.exlibrisgroup.com/primo-explore/openurl",
156
+ "country": "AU",
157
+ "vendor": "ExLibris"
158
+ },
159
+ "University of Queensland": {
160
+ "url": "https://uq.summon.serialssolutions.com/link",
161
+ "country": "AU",
162
+ "vendor": "SerialsSolutions"
163
+ },
164
+ "Monash University": {
165
+ "url": "https://monash.hosted.exlibrisgroup.com/sfx_local",
166
+ "country": "AU",
167
+ "vendor": "ExLibris"
168
+ },
169
+
170
+ # Germany
171
+ "Max Planck Society": {
172
+ "url": "http://sfx.mpg.de/sfx_local",
173
+ "country": "DE",
174
+ "vendor": "ExLibris"
175
+ },
176
+ "University of Munich (LMU)": {
177
+ "url": "https://sfx.bib.uni-muenchen.de/sfx_lmu",
178
+ "country": "DE",
179
+ "vendor": "ExLibris"
180
+ },
181
+ "Heidelberg University": {
182
+ "url": "https://sfx.bib.uni-heidelberg.de/sfx_heidelberg",
183
+ "country": "DE",
184
+ "vendor": "ExLibris"
185
+ },
186
+
187
+ # Netherlands
188
+ "University of Amsterdam": {
189
+ "url": "https://vu-nl.idm.oclc.org/login?url=",
190
+ "country": "NL",
191
+ "vendor": "OCLC"
192
+ },
193
+ "Delft University of Technology": {
194
+ "url": "https://tudelft.idm.oclc.org/login?url=",
195
+ "country": "NL",
196
+ "vendor": "OCLC"
197
+ },
198
+
199
+ # France
200
+ "Sorbonne University": {
201
+ "url": "https://accesdistant.sorbonne-universite.fr/login?url=",
202
+ "country": "FR",
203
+ "vendor": "Custom"
204
+ },
205
+ "École Polytechnique": {
206
+ "url": "https://portail.polytechnique.edu/openurl",
207
+ "country": "FR",
208
+ "vendor": "Custom"
209
+ },
210
+
211
+ # Switzerland
212
+ "ETH Zurich": {
213
+ "url": "https://www.library.ethz.ch/openurl",
214
+ "country": "CH",
215
+ "vendor": "Custom"
216
+ },
217
+ "EPFL": {
218
+ "url": "https://sfx.epfl.ch/sfx_local",
219
+ "country": "CH",
220
+ "vendor": "ExLibris"
221
+ },
222
+
223
+ # Japan
224
+ "University of Tokyo": {
225
+ "url": "https://vs2ga4mq9g.search.serialssolutions.com",
226
+ "country": "JP",
227
+ "vendor": "SerialsSolutions"
228
+ },
229
+ "Kyoto University": {
230
+ "url": "https://kuline.kulib.kyoto-u.ac.jp/portal/openurl",
231
+ "country": "JP",
232
+ "vendor": "Custom"
233
+ },
234
+
235
+ # Singapore
236
+ "National University of Singapore": {
237
+ "url": "https://libproxy.nus.edu.sg/login?url=",
238
+ "country": "SG",
239
+ "vendor": "Custom"
240
+ },
241
+ "Nanyang Technological University": {
242
+ "url": "https://ap01.alma.exlibrisgroup.com/view/uresolver/65NTU_INST/openurl",
243
+ "country": "SG",
244
+ "vendor": "ExLibris"
245
+ },
246
+
247
+ # China
248
+ "Tsinghua University": {
249
+ "url": "http://sfx.lib.tsinghua.edu.cn/sfx_local",
250
+ "country": "CN",
251
+ "vendor": "ExLibris"
252
+ },
253
+ "Peking University": {
254
+ "url": "http://sfx.lib.pku.edu.cn/sfx_pku",
255
+ "country": "CN",
256
+ "vendor": "ExLibris"
257
+ },
258
+
259
+ # South Korea
260
+ "Seoul National University": {
261
+ "url": "https://sfx.snu.ac.kr/sfx_local",
262
+ "country": "KR",
263
+ "vendor": "ExLibris"
264
+ },
265
+ "KAIST": {
266
+ "url": "https://library.kaist.ac.kr/openurl",
267
+ "country": "KR",
268
+ "vendor": "Custom"
269
+ },
270
+
271
+ # Brazil
272
+ "University of São Paulo": {
273
+ "url": "http://www.buscaintegrada.usp.br/openurl",
274
+ "country": "BR",
275
+ "vendor": "Custom"
276
+ },
277
+
278
+ # Mexico
279
+ "UNAM": {
280
+ "url": "https://pbidi.unam.mx/login?url=",
281
+ "country": "MX",
282
+ "vendor": "Custom"
283
+ },
284
+
285
+ # India
286
+ "IIT Delhi": {
287
+ "url": "https://libproxy.iitd.ac.in/login?url=",
288
+ "country": "IN",
289
+ "vendor": "Custom"
290
+ },
291
+ "Indian Institute of Science": {
292
+ "url": "https://library.iisc.ac.in/openurl",
293
+ "country": "IN",
294
+ "vendor": "Custom"
295
+ }
296
+ }
297
+
298
+ # Generic OpenURL resolver patterns
299
+ GENERIC_PATTERNS = [
300
+ # ExLibris SFX patterns
301
+ r"https?://[^/]+/sfx[^/]*",
302
+ r"https?://sfx\.[^/]+",
303
+ r"https?://[^/]+\.exlibrisgroup\.com",
304
+
305
+ # SerialsSolutions patterns
306
+ r"https?://[^/]+\.serialssolutions\.com",
307
+ r"https?://[^/]+/360link",
308
+
309
+ # OCLC patterns
310
+ r"https?://[^/]+\.idm\.oclc\.org",
311
+ r"https?://[^/]+\.worldcat\.org",
312
+
313
+ # Common proxy patterns
314
+ r"https?://[^/]+/login\?url=",
315
+ r"https?://libproxy\.[^/]+",
316
+ r"https?://proxy\.[^/]+",
317
+
318
+ # OpenURL patterns
319
+ r"https?://[^/]+/openurl",
320
+ r"https?://[^/]+/openurlresolver",
321
+ ]
322
+
323
+
324
+ def get_resolver_by_institution(institution_name: str) -> Optional[Dict[str, str]]:
325
+ """
326
+ Get OpenURL resolver information by institution name.
327
+
328
+ Args:
329
+ institution_name: Name of the institution
330
+
331
+ Returns:
332
+ Dict with 'url', 'country', and 'vendor' if found, None otherwise
333
+ """
334
+ # Try exact match first
335
+ if institution_name in KNOWN_RESOLVERS:
336
+ return KNOWN_RESOLVERS[institution_name].copy()
337
+
338
+ # Try case-insensitive match
339
+ institution_lower = institution_name.lower()
340
+ for name, info in KNOWN_RESOLVERS.items():
341
+ if name.lower() == institution_lower:
342
+ return info.copy()
343
+
344
+ # Try partial match
345
+ for name, info in KNOWN_RESOLVERS.items():
346
+ if institution_lower in name.lower() or name.lower() in institution_lower:
347
+ return info.copy()
348
+
349
+ return None
350
+
351
+
352
+ def get_resolvers_by_country(country_code: str) -> Dict[str, Dict[str, str]]:
353
+ """
354
+ Get all OpenURL resolvers for a specific country.
355
+
356
+ Args:
357
+ country_code: Two-letter country code (e.g., 'US', 'UK', 'AU')
358
+
359
+ Returns:
360
+ Dict of institution names to resolver info
361
+ """
362
+ country_code = country_code.upper()
363
+ return {
364
+ name: info
365
+ for name, info in KNOWN_RESOLVERS.items()
366
+ if info.get('country') == country_code
367
+ }
368
+
369
+
370
+ def get_resolvers_by_vendor(vendor_name: str) -> Dict[str, Dict[str, str]]:
371
+ """
372
+ Get all OpenURL resolvers using a specific vendor.
373
+
374
+ Args:
375
+ vendor_name: Vendor name (e.g., 'ExLibris', 'OCLC')
376
+
377
+ Returns:
378
+ Dict of institution names to resolver info
379
+ """
380
+ return {
381
+ name: info
382
+ for name, info in KNOWN_RESOLVERS.items()
383
+ if info.get('vendor', '').lower() == vendor_name.lower()
384
+ }
385
+
386
+
387
+ def validate_resolver_url(url: str) -> bool:
388
+ """
389
+ Check if a URL looks like a valid OpenURL resolver.
390
+
391
+ Args:
392
+ url: URL to validate
393
+
394
+ Returns:
395
+ True if URL matches known resolver patterns
396
+ """
397
+ import re
398
+
399
+ # Check against known resolver URLs
400
+ for info in KNOWN_RESOLVERS.values():
401
+ if url.startswith(info['url']):
402
+ return True
403
+
404
+ # Check against generic patterns
405
+ for pattern in GENERIC_PATTERNS:
406
+ if re.match(pattern, url):
407
+ return True
408
+
409
+ return False
410
+
411
+
412
+ def get_all_resolvers() -> List[Dict[str, str]]:
413
+ """
414
+ Get all known resolvers as a list.
415
+
416
+ Returns:
417
+ List of dicts with 'name', 'url', 'country', 'vendor'
418
+ """
419
+ return [
420
+ {
421
+ 'name': name,
422
+ 'url': info['url'],
423
+ 'country': info.get('country', 'Unknown'),
424
+ 'vendor': info.get('vendor', 'Unknown')
425
+ }
426
+ for name, info in KNOWN_RESOLVERS.items()
427
+ ]
428
+
429
+
430
+ # Common test DOIs for different publishers
431
+ TEST_DOIS = {
432
+ "Nature": "10.1038/nature12373",
433
+ "Science": "10.1126/science.1234567",
434
+ "Cell": "10.1016/j.cell.2020.01.001",
435
+ "Elsevier": "10.1016/j.neuroimage.2020.116584",
436
+ "Wiley": "10.1111/jnc.15327",
437
+ "Springer": "10.1007/s00401-021-02283-6",
438
+ "Oxford": "10.1093/brain/awaa123",
439
+ "IEEE": "10.1109/TPAMI.2020.2984611",
440
+ "ACS": "10.1021/acs.jmedchem.0c00606",
441
+ "PNAS": "10.1073/pnas.1921909117"
442
+ }
443
+
444
+
445
+ if __name__ == "__main__":
446
+ # Example usage
447
+ print(f"Total known resolvers: {len(KNOWN_RESOLVERS)}")
448
+ print(f"\nCountries represented: {len(set(info['country'] for info in KNOWN_RESOLVERS.values()))}")
449
+ print(f"Vendors: {set(info.get('vendor', 'Unknown') for info in KNOWN_RESOLVERS.values())}")
450
+
451
+ # Example: Find resolver for an institution
452
+ resolver = get_resolver_by_institution("Harvard")
453
+ if resolver:
454
+ print(f"\nHarvard resolver: {resolver['url']}")
455
+
456
+ # Example: Get all US resolvers
457
+ us_resolvers = get_resolvers_by_country("US")
458
+ print(f"\nUS institutions with resolvers: {len(us_resolvers)}")
459
+
460
+ # Example: Get all ExLibris resolvers
461
+ exlibris = get_resolvers_by_vendor("ExLibris")
462
+ print(f"Institutions using ExLibris SFX: {len(exlibris)}")