scitex 2.10.3__py3-none-any.whl → 2.13.0__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 (504) hide show
  1. scitex/__init__.py +1 -4
  2. scitex/__main__.py +24 -5
  3. scitex/__version__.py +1 -1
  4. scitex/_install_guide.py +14 -2
  5. scitex/_optional_deps.py +33 -0
  6. scitex/ai/classification/reporters/_ClassificationReporter.py +1 -1
  7. scitex/ai/classification/timeseries/_TimeSeriesBlockingSplit.py +2 -2
  8. scitex/ai/classification/timeseries/_TimeSeriesCalendarSplit.py +2 -2
  9. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +2 -2
  10. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit_v01-not-using-n_splits.py +2 -2
  11. scitex/ai/classification/timeseries/_TimeSeriesStratifiedSplit.py +2 -2
  12. scitex/ai/classification/timeseries/_normalize_timestamp.py +1 -1
  13. scitex/ai/metrics/_calc_seizure_prediction_metrics.py +1 -1
  14. scitex/ai/plt/_plot_feature_importance.py +1 -1
  15. scitex/ai/plt/_plot_learning_curve.py +1 -1
  16. scitex/ai/plt/_plot_optuna_study.py +1 -1
  17. scitex/ai/plt/_plot_pre_rec_curve.py +1 -1
  18. scitex/ai/plt/_plot_roc_curve.py +1 -1
  19. scitex/ai/plt/_stx_conf_mat.py +1 -1
  20. scitex/ai/training/_LearningCurveLogger.py +1 -1
  21. scitex/audio/mcp_server.py +38 -8
  22. scitex/bridge/_figrecipe.py +1 -1
  23. scitex/bridge/_helpers.py +1 -1
  24. scitex/bridge/_plt_vis.py +1 -1
  25. scitex/bridge/_stats_plt.py +1 -1
  26. scitex/bridge/_stats_vis.py +2 -2
  27. scitex/browser/automation/CookieHandler.py +1 -1
  28. scitex/browser/core/BrowserMixin.py +1 -1
  29. scitex/browser/core/ChromeProfileManager.py +1 -1
  30. scitex/browser/debugging/_browser_logger.py +1 -1
  31. scitex/browser/debugging/_highlight_element.py +1 -1
  32. scitex/browser/debugging/_show_grid.py +1 -1
  33. scitex/browser/interaction/click_center.py +1 -1
  34. scitex/browser/interaction/click_with_fallbacks.py +1 -1
  35. scitex/browser/interaction/close_popups.py +1 -1
  36. scitex/browser/interaction/fill_with_fallbacks.py +1 -1
  37. scitex/browser/pdf/click_download_for_chrome_pdf_viewer.py +1 -1
  38. scitex/browser/pdf/detect_chrome_pdf_viewer.py +1 -1
  39. scitex/browser/stealth/HumanBehavior.py +1 -1
  40. scitex/browser/stealth/StealthManager.py +1 -1
  41. scitex/{fig → canvas}/__init__.py +84 -96
  42. scitex/canvas/_mcp_handlers.py +372 -0
  43. scitex/canvas/_mcp_tool_schemas.py +219 -0
  44. scitex/{fig → canvas}/backend/_parser.py +1 -1
  45. scitex/{fig → canvas}/canvas.py +13 -14
  46. scitex/{fts/_fig/_editor → canvas/editor}/_defaults.py +2 -2
  47. scitex/{fig → canvas}/editor/edit/__init__.py +11 -14
  48. scitex/{fig → canvas}/editor/edit/bundle_resolver.py +56 -48
  49. scitex/{fig → canvas}/editor/edit/editor_launcher.py +79 -26
  50. scitex/{fts/_fig/_editor/_cui/_panel_loader.py → canvas/editor/edit/panel_loader.py} +8 -8
  51. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_bbox.py +2 -1
  52. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_core.py +84 -84
  53. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_renderer.py +7 -6
  54. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/static/css/features/canvas.css +2 -2
  55. scitex/{fig → canvas}/editor/flask_editor/static/css/features/panel-grid.css +1 -1
  56. scitex/{fig → canvas}/editor/flask_editor/static/js/core/api.js +3 -4
  57. scitex/{fig → canvas}/editor/flask_editor/static/js/editor/preview.js +5 -5
  58. scitex/{fig → canvas}/editor/flask_editor/templates/_html.py +3 -3
  59. scitex/{fig → canvas}/editor/flask_editor/templates/_scripts.py +10 -10
  60. scitex/{fig → canvas}/editor/flask_editor/templates/_styles.py +3 -3
  61. scitex/{fig → canvas}/io/__init__.py +32 -38
  62. scitex/{fig → canvas}/io/_bundle.py +217 -154
  63. scitex/{fig → canvas}/io/_canvas.py +1 -1
  64. scitex/{fig → canvas}/io/_data.py +1 -1
  65. scitex/{fig → canvas}/io/_export.py +1 -1
  66. scitex/{fig → canvas}/io/_load.py +1 -1
  67. scitex/{fig → canvas}/io/_panel.py +1 -1
  68. scitex/{fig → canvas}/io/_save.py +1 -1
  69. scitex/canvas/mcp_server.py +151 -0
  70. scitex/{fig → canvas}/model/__init__.py +1 -1
  71. scitex/{fig → canvas}/model/_annotations.py +1 -1
  72. scitex/{fig → canvas}/model/_axes.py +1 -1
  73. scitex/{fig → canvas}/model/_figure.py +1 -1
  74. scitex/{fig → canvas}/model/_guides.py +1 -1
  75. scitex/{fig → canvas}/model/_plot.py +1 -1
  76. scitex/{fig → canvas}/model/_styles.py +1 -1
  77. scitex/{fig → canvas}/utils/__init__.py +1 -1
  78. scitex/capture/mcp_server.py +41 -12
  79. scitex/cli/audio.py +233 -0
  80. scitex/cli/capture.py +307 -0
  81. scitex/cli/convert.py +10 -6
  82. scitex/cli/main.py +27 -4
  83. scitex/cli/repro.py +233 -0
  84. scitex/cli/resource.py +240 -0
  85. scitex/cli/stats.py +325 -0
  86. scitex/cli/template.py +236 -0
  87. scitex/cli/tex.py +286 -0
  88. scitex/cli/web.py +11 -12
  89. scitex/dev/__init__.py +3 -0
  90. scitex/dev/_pyproject.py +405 -0
  91. scitex/dev/plt/__init__.py +2 -2
  92. scitex/dev/plt/mpl/get_dir_ax.py +1 -1
  93. scitex/dev/plt/mpl/get_signatures.py +1 -1
  94. scitex/dev/plt/mpl/get_signatures_details.py +1 -1
  95. scitex/diagram/README.md +7 -7
  96. scitex/diagram/_mcp_handlers.py +400 -0
  97. scitex/diagram/_mcp_tool_schemas.py +157 -0
  98. scitex/diagram/mcp_server.py +151 -0
  99. scitex/dsp/_demo_sig.py +51 -5
  100. scitex/dsp/_mne.py +13 -2
  101. scitex/dsp/_modulation_index.py +15 -3
  102. scitex/dsp/_pac.py +23 -5
  103. scitex/dsp/_psd.py +16 -4
  104. scitex/dsp/_resample.py +24 -4
  105. scitex/dsp/_transform.py +16 -3
  106. scitex/dsp/add_noise.py +15 -1
  107. scitex/dsp/norm.py +17 -2
  108. scitex/dsp/reference.py +17 -1
  109. scitex/dsp/utils/_differential_bandpass_filters.py +20 -2
  110. scitex/dsp/utils/_zero_pad.py +18 -4
  111. scitex/dt/_normalize_timestamp.py +1 -1
  112. scitex/git/_session.py +1 -1
  113. scitex/io/__init__.py +7 -19
  114. scitex/io/_load.py +15 -19
  115. scitex/io/_load_modules/_canvas.py +2 -2
  116. scitex/io/_load_modules/_con.py +17 -6
  117. scitex/io/_load_modules/_eeg.py +28 -13
  118. scitex/io/_load_modules/_optuna.py +21 -63
  119. scitex/io/_load_modules/_torch.py +11 -3
  120. scitex/io/_save.py +11 -16
  121. scitex/io/_save_modules/__init__.py +6 -10
  122. scitex/io/_save_modules/_canvas.py +3 -3
  123. scitex/io/_save_modules/_optuna_study_as_csv_and_pngs.py +13 -2
  124. scitex/io/_save_modules/_plot_bundle.py +112 -0
  125. scitex/io/_save_modules/{_pltz_stx.py → _plot_scitex.py} +7 -7
  126. scitex/io/_save_modules/_stx_bundle.py +16 -16
  127. scitex/io/_save_modules/_torch.py +11 -3
  128. scitex/io/bundle/README.md +89 -80
  129. scitex/{fts/_bundle/_FTS.py → io/bundle/_Bundle.py} +197 -95
  130. scitex/io/bundle/__init__.py +67 -35
  131. scitex/{fts/_bundle → io/bundle}/_children.py +32 -40
  132. scitex/io/bundle/_core.py +184 -97
  133. scitex/{fts/_bundle/_dataclasses/_Node.py → io/bundle/_dataclasses/_Spec.py} +29 -23
  134. scitex/{fts/_bundle/_dataclasses/_NodeRefs.py → io/bundle/_dataclasses/_SpecRefs.py} +6 -6
  135. scitex/{fts/_bundle → io/bundle}/_dataclasses/__init__.py +4 -4
  136. scitex/{fts/_bundle → io/bundle}/_loader.py +19 -19
  137. scitex/io/bundle/_manifest.py +99 -0
  138. scitex/{fts/_bundle → io/bundle}/_mpl_helpers.py +119 -28
  139. scitex/io/bundle/_nested.py +113 -100
  140. scitex/{fts/_bundle → io/bundle}/_saver.py +13 -14
  141. scitex/{fts/_bundle → io/bundle}/_storage.py +3 -3
  142. scitex/io/bundle/_types.py +41 -16
  143. scitex/{fts/_bundle → io/bundle}/_validation.py +20 -18
  144. scitex/io/bundle/_zip.py +21 -31
  145. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_backend/_parser.py +1 -1
  146. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Annotations.py +1 -1
  147. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Axes.py +1 -1
  148. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Figure.py +1 -1
  149. scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_Guides.py +1 -1
  150. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Plot.py +1 -1
  151. scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_Styles.py +1 -1
  152. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_utils/_plot_layout.py +1 -1
  153. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/__init__.py +1 -1
  154. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_editor/_app.py +1 -1
  155. scitex/{fts/_tables → io/bundle/kinds/_table}/_latex/_export.py +1 -1
  156. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_figure_exporter.py +1 -1
  157. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_table_exporter.py +1 -1
  158. scitex/io/bundle/schemas/__init__.py +30 -0
  159. scitex/mcp_server.py +159 -0
  160. scitex/parallel/_run.py +5 -4
  161. scitex/path/_find.py +60 -83
  162. scitex/path/_get_module_path.py +23 -21
  163. scitex/path/_get_spath.py +6 -27
  164. scitex/path/_getsize.py +23 -9
  165. scitex/path/_increment_version.py +31 -38
  166. scitex/path/_mk_spath.py +26 -29
  167. scitex/path/_path.py +5 -12
  168. scitex/path/_split.py +27 -15
  169. scitex/path/_this_path.py +23 -9
  170. scitex/plt/_mcp_handlers.py +361 -0
  171. scitex/plt/_mcp_tool_schemas.py +169 -0
  172. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +2 -1
  173. scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +2 -2
  174. scitex/plt/gallery/_generate.py +76 -50
  175. scitex/plt/io/__init__.py +17 -19
  176. scitex/plt/io/_bundle.py +99 -52
  177. scitex/plt/io/_layered_bundle.py +303 -168
  178. scitex/plt/mcp_server.py +205 -0
  179. scitex/plt/utils/_csv_column_naming.py +250 -118
  180. scitex/repro/README_RandomStateManager.md +3 -3
  181. scitex/repro/_RandomStateManager.py +14 -14
  182. scitex/repro/_gen_ID.py +1 -1
  183. scitex/repro/_gen_timestamp.py +1 -1
  184. scitex/repro/_hash_array.py +4 -4
  185. scitex/schema/__init__.py +69 -73
  186. scitex/schema/_canvas.py +1 -1
  187. scitex/schema/_stats.py +2 -2
  188. scitex/scholar/__main__.py +24 -2
  189. scitex/scholar/_mcp_handlers.py +685 -0
  190. scitex/scholar/_mcp_tool_schemas.py +339 -0
  191. scitex/scholar/docs/template.py +1 -1
  192. scitex/scholar/examples/07_storage_integration.py +1 -1
  193. scitex/scholar/impact_factor/jcr/ImpactFactorJCREngine.py +1 -1
  194. scitex/scholar/impact_factor/jcr/build_database.py +1 -1
  195. scitex/scholar/mcp_server.py +315 -0
  196. scitex/scholar/pdf_download/ScholarPDFDownloader.py +1 -1
  197. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +1 -1
  198. scitex/scholar/pipelines/ScholarPipelineParallel.py +1 -1
  199. scitex/scholar/pipelines/ScholarPipelineSingle.py +1 -1
  200. scitex/scholar/storage/PaperIO.py +1 -1
  201. scitex/session/README.md +4 -4
  202. scitex/session/__init__.py +1 -1
  203. scitex/session/_decorator.py +9 -9
  204. scitex/session/_lifecycle.py +5 -5
  205. scitex/session/template.py +1 -1
  206. scitex/stats/__init__.py +30 -33
  207. scitex/stats/__main__.py +281 -0
  208. scitex/stats/_mcp_handlers.py +1191 -0
  209. scitex/stats/_mcp_tool_schemas.py +384 -0
  210. scitex/stats/_schema.py +1 -1
  211. scitex/stats/correct/_correct_bonferroni.py +1 -1
  212. scitex/stats/correct/_correct_fdr.py +1 -1
  213. scitex/stats/correct/_correct_fdr_.py +1 -1
  214. scitex/stats/correct/_correct_holm.py +1 -1
  215. scitex/stats/correct/_correct_sidak.py +1 -1
  216. scitex/stats/effect_sizes/_cliffs_delta.py +1 -1
  217. scitex/stats/effect_sizes/_cohens_d.py +1 -1
  218. scitex/stats/effect_sizes/_epsilon_squared.py +1 -1
  219. scitex/stats/effect_sizes/_eta_squared.py +1 -1
  220. scitex/stats/effect_sizes/_prob_superiority.py +1 -1
  221. scitex/stats/io/__init__.py +10 -11
  222. scitex/stats/io/_bundle.py +16 -16
  223. scitex/stats/mcp_server.py +405 -0
  224. scitex/stats/posthoc/_dunnett.py +1 -1
  225. scitex/stats/posthoc/_games_howell.py +1 -1
  226. scitex/stats/posthoc/_tukey_hsd.py +1 -1
  227. scitex/stats/power/_power.py +1 -1
  228. scitex/stats/utils/_effect_size.py +1 -1
  229. scitex/stats/utils/_formatters.py +1 -1
  230. scitex/stats/utils/_power.py +1 -1
  231. scitex/template/_mcp_handlers.py +259 -0
  232. scitex/template/_mcp_tool_schemas.py +112 -0
  233. scitex/template/mcp_server.py +186 -0
  234. scitex/utils/_verify_scitex_format.py +2 -2
  235. scitex/utils/template.py +1 -1
  236. scitex/web/__init__.py +12 -11
  237. scitex/web/_scraping.py +26 -265
  238. scitex/web/download_images.py +316 -0
  239. scitex/writer/Writer.py +1 -1
  240. scitex/writer/_clone_writer_project.py +1 -1
  241. scitex/writer/_validate_tree_structures.py +1 -1
  242. scitex/writer/dataclasses/config/_WriterConfig.py +1 -1
  243. scitex/writer/dataclasses/contents/_ManuscriptContents.py +1 -1
  244. scitex/writer/dataclasses/core/_Document.py +1 -1
  245. scitex/writer/dataclasses/core/_DocumentSection.py +1 -1
  246. scitex/writer/dataclasses/results/_CompilationResult.py +1 -1
  247. scitex/writer/dataclasses/results/_LaTeXIssue.py +1 -1
  248. scitex/writer/utils/.legacy_git_retry.py +7 -5
  249. scitex/writer/utils/_parse_latex_logs.py +1 -1
  250. scitex-2.13.0.dist-info/METADATA +1231 -0
  251. {scitex-2.10.3.dist-info → scitex-2.13.0.dist-info}/RECORD +376 -470
  252. scitex-2.13.0.dist-info/entry_points.txt +11 -0
  253. scitex/fig/editor/_defaults.py +0 -300
  254. scitex/fig/editor/edit/panel_loader.py +0 -232
  255. scitex/fig/editor/flask_editor/_bbox.py +0 -1299
  256. scitex/fig/editor/flask_editor/_core.py +0 -1429
  257. scitex/fig/editor/flask_editor/_renderer.py +0 -813
  258. scitex/fig/editor/flask_editor/static/css/features/canvas.css +0 -176
  259. scitex/fts/README.md +0 -262
  260. scitex/fts/TODO.md +0 -66
  261. scitex/fts/__init__.py +0 -90
  262. scitex/fts/_bundle/README_IN_BUNDLE.md +0 -102
  263. scitex/fts/_bundle/__init__.py +0 -38
  264. scitex/fts/_bundle/_utils/__init__.py +0 -55
  265. scitex/fts/_bundle/_utils/_const.py +0 -26
  266. scitex/fts/_bundle/_utils/_errors.py +0 -73
  267. scitex/fts/_bundle/_utils/_generate.py +0 -21
  268. scitex/fts/_bundle/_utils/_types.py +0 -76
  269. scitex/fts/_bundle/_zipbundle.py +0 -165
  270. scitex/fts/_fig/__init__.py +0 -22
  271. scitex/fts/_fig/_backend/_parser.py +0 -188
  272. scitex/fts/_fig/_editor/__init__.py +0 -14
  273. scitex/fts/_fig/_editor/_cui/__init__.py +0 -33
  274. scitex/fts/_fig/_editor/_cui/_backend_detector.py +0 -39
  275. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +0 -366
  276. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +0 -175
  277. scitex/fts/_fig/_editor/_cui/_manual_handler.py +0 -52
  278. scitex/fts/_fig/_editor/_cui/_path_resolver.py +0 -66
  279. scitex/fts/_fig/_editor/_gui/__init__.py +0 -11
  280. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +0 -20
  281. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +0 -664
  282. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +0 -79
  283. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +0 -41
  284. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +0 -16
  285. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +0 -85
  286. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +0 -217
  287. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +0 -93
  288. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +0 -57
  289. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +0 -112
  290. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +0 -59
  291. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +0 -212
  292. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +0 -190
  293. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +0 -59
  294. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +0 -45
  295. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +0 -95
  296. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +0 -101
  297. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +0 -138
  298. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +0 -31
  299. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +0 -7
  300. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +0 -56
  301. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +0 -78
  302. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +0 -314
  303. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +0 -107
  304. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +0 -54
  305. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +0 -172
  306. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +0 -258
  307. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +0 -48
  308. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +0 -71
  309. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +0 -288
  310. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +0 -143
  311. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +0 -245
  312. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +0 -992
  313. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +0 -339
  314. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +0 -286
  315. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +0 -371
  316. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +0 -293
  317. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +0 -426
  318. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +0 -152
  319. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +0 -265
  320. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +0 -184
  321. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +0 -57
  322. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +0 -100
  323. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +0 -34
  324. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +0 -124
  325. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +0 -851
  326. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +0 -4932
  327. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +0 -1657
  328. scitex/fts/_fig/_editor/_gui/_flask_editor.py +0 -36
  329. scitex/fts/_fig/_models/_Annotations.py +0 -115
  330. scitex/fts/_fig/_models/_Axes.py +0 -152
  331. scitex/fts/_fig/_models/_Figure.py +0 -138
  332. scitex/fts/_fig/_models/_Plot.py +0 -123
  333. scitex/fts/_fig/_utils/_plot_layout.py +0 -397
  334. scitex/fts/_kinds/_figure/_composite.py +0 -345
  335. scitex/fts/_kinds/_plot/_backend/__init__.py +0 -53
  336. scitex/fts/_kinds/_plot/_backend/_export.py +0 -165
  337. scitex/fts/_kinds/_plot/_backend/_render.py +0 -538
  338. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +0 -46
  339. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +0 -82
  340. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +0 -441
  341. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +0 -52
  342. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +0 -47
  343. scitex/fts/_kinds/_plot/_models/_Guides.py +0 -104
  344. scitex/fts/_kinds/_plot/_models/_Styles.py +0 -245
  345. scitex/fts/_kinds/_plot/_models/__init__.py +0 -80
  346. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +0 -156
  347. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +0 -43
  348. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +0 -38
  349. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +0 -36
  350. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +0 -60
  351. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +0 -30
  352. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +0 -61
  353. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +0 -57
  354. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +0 -30
  355. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +0 -121
  356. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +0 -36
  357. scitex/fts/_kinds/_plot/_utils/__init__.py +0 -129
  358. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +0 -127
  359. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +0 -111
  360. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +0 -48
  361. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +0 -77
  362. scitex/fts/_kinds/_plot/_utils/_get_template.py +0 -178
  363. scitex/fts/_kinds/_plot/_utils/_normalize.py +0 -73
  364. scitex/fts/_kinds/_plot/_utils/_validate.py +0 -197
  365. scitex/fts/_kinds/_table/_latex/_export.py +0 -279
  366. scitex/fts/_stats/__init__.py +0 -48
  367. scitex/fts/_stats/_dataclasses/_Stats.py +0 -423
  368. scitex/fts/_stats/_dataclasses/__init__.py +0 -48
  369. scitex/fts/_tables/__init__.py +0 -65
  370. scitex/fts/_tables/_latex/__init__.py +0 -93
  371. scitex/fts/_tables/_latex/_editor/__init__.py +0 -11
  372. scitex/fts/_tables/_latex/_editor/_app.py +0 -725
  373. scitex/fts/_tables/_latex/_figure_exporter.py +0 -153
  374. scitex/fts/_tables/_latex/_stats_formatter.py +0 -274
  375. scitex/fts/_tables/_latex/_table_exporter.py +0 -362
  376. scitex/fts/_tables/_latex/_utils.py +0 -369
  377. scitex/fts/_tables/_latex/_validator.py +0 -445
  378. scitex/io/_save_modules/_pltz_bundle.py +0 -356
  379. scitex-2.10.3.dist-info/METADATA +0 -952
  380. scitex-2.10.3.dist-info/entry_points.txt +0 -2
  381. /scitex/{fig → canvas}/README.md +0 -0
  382. /scitex/{fig → canvas}/backend/__init__.py +0 -0
  383. /scitex/{fig → canvas}/backend/_export.py +0 -0
  384. /scitex/{fig → canvas}/backend/_render.py +0 -0
  385. /scitex/{fig → canvas}/docs/CANVAS_ARCHITECTURE.md +0 -0
  386. /scitex/{fig → canvas}/editor/__init__.py +0 -0
  387. /scitex/{fig → canvas}/editor/_dearpygui_editor.py +0 -0
  388. /scitex/{fig → canvas}/editor/_flask_editor.py +0 -0
  389. /scitex/{fig → canvas}/editor/_mpl_editor.py +0 -0
  390. /scitex/{fig → canvas}/editor/_qt_editor.py +0 -0
  391. /scitex/{fig → canvas}/editor/_tkinter_editor.py +0 -0
  392. /scitex/{fig → canvas}/editor/edit/backend_detector.py +0 -0
  393. /scitex/{fig → canvas}/editor/edit/manual_handler.py +0 -0
  394. /scitex/{fig → canvas}/editor/edit/path_resolver.py +0 -0
  395. /scitex/{fig → canvas}/editor/flask_editor/__init__.py +0 -0
  396. /scitex/{fig → canvas}/editor/flask_editor/_plotter.py +0 -0
  397. /scitex/{fig → canvas}/editor/flask_editor/_utils.py +0 -0
  398. /scitex/{fig → canvas}/editor/flask_editor/static/css/base/reset.css +0 -0
  399. /scitex/{fig → canvas}/editor/flask_editor/static/css/base/typography.css +0 -0
  400. /scitex/{fig → canvas}/editor/flask_editor/static/css/base/variables.css +0 -0
  401. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/buttons.css +0 -0
  402. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/context-menu.css +0 -0
  403. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/dropdown.css +0 -0
  404. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/forms.css +0 -0
  405. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/modal.css +0 -0
  406. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/sections.css +0 -0
  407. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/element-inspector.css +0 -0
  408. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/loading.css +0 -0
  409. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/overlay.css +0 -0
  410. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/selection.css +0 -0
  411. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/statistics.css +0 -0
  412. /scitex/{fig → canvas}/editor/flask_editor/static/css/index.css +0 -0
  413. /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/container.css +0 -0
  414. /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/controls.css +0 -0
  415. /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/preview.css +0 -0
  416. /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/axis.js +0 -0
  417. /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/basic.js +0 -0
  418. /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/distribute.js +0 -0
  419. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/canvas.js +0 -0
  420. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/dragging.js +0 -0
  421. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/resize.js +0 -0
  422. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/selection.js +0 -0
  423. /scitex/{fig → canvas}/editor/flask_editor/static/js/core/state.js +0 -0
  424. /scitex/{fig → canvas}/editor/flask_editor/static/js/core/utils.js +0 -0
  425. /scitex/{fig → canvas}/editor/flask_editor/static/js/dev/element-inspector.js +0 -0
  426. /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/bbox.js +0 -0
  427. /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/element-drag.js +0 -0
  428. /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/overlay.js +0 -0
  429. /scitex/{fig → canvas}/editor/flask_editor/static/js/main.js +0 -0
  430. /scitex/{fig → canvas}/editor/flask_editor/static/js/shortcuts/context-menu.js +0 -0
  431. /scitex/{fig → canvas}/editor/flask_editor/static/js/shortcuts/keyboard.js +0 -0
  432. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/controls.js +0 -0
  433. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/download.js +0 -0
  434. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/help.js +0 -0
  435. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/theme.js +0 -0
  436. /scitex/{fig → canvas}/editor/flask_editor/templates/__init__.py +0 -0
  437. /scitex/{fig → canvas}/io/_directory.py +0 -0
  438. /scitex/{fig → canvas}/model/_plot_types.py +0 -0
  439. /scitex/{fig → canvas}/utils/_defaults.py +0 -0
  440. /scitex/{fig → canvas}/utils/_validate.py +0 -0
  441. /scitex/{fts/_bundle → io/bundle}/_conversion/__init__.py +0 -0
  442. /scitex/{fts/_bundle → io/bundle}/_conversion/_bundle2dict.py +0 -0
  443. /scitex/{fts/_bundle → io/bundle}/_conversion/_dict2bundle.py +0 -0
  444. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_Axes.py +0 -0
  445. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_BBox.py +0 -0
  446. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_ColumnDef.py +0 -0
  447. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataFormat.py +0 -0
  448. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataInfo.py +0 -0
  449. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataSource.py +0 -0
  450. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_SizeMM.py +0 -0
  451. /scitex/{fts/_bundle → io/bundle}/_extractors/__init__.py +0 -0
  452. /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_bar.py +0 -0
  453. /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_line.py +0 -0
  454. /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_scatter.py +0 -0
  455. /scitex/{fts/_kinds → io/bundle/kinds}/__init__.py +0 -0
  456. /scitex/{fts/_kinds → io/bundle/kinds}/_figure/__init__.py +0 -0
  457. /scitex/{fts/_fig → io/bundle/kinds/_figure}/_composite.py +0 -0
  458. /scitex/{fts/_kinds → io/bundle/kinds}/_plot/__init__.py +0 -0
  459. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/__init__.py +0 -0
  460. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/_export.py +0 -0
  461. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/_render.py +0 -0
  462. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_ChannelEncoding.py +0 -0
  463. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_Encoding.py +0 -0
  464. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_Theme.py +0 -0
  465. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_TraceEncoding.py +0 -0
  466. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/__init__.py +0 -0
  467. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/__init__.py +0 -0
  468. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/__init__.py +0 -0
  469. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_bar.py +0 -0
  470. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_box.py +0 -0
  471. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_distribution.py +0 -0
  472. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_errorbar.py +0 -0
  473. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_histogram.py +0 -0
  474. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_image.py +0 -0
  475. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_line.py +0 -0
  476. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_scatter.py +0 -0
  477. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_seaborn.py +0 -0
  478. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_violin.py +0 -0
  479. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/__init__.py +0 -0
  480. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_auto_layout.py +0 -0
  481. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_calc_bounds.py +0 -0
  482. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_const_sizes.py +0 -0
  483. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_convert_coords.py +0 -0
  484. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_get_template.py +0 -0
  485. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_normalize.py +0 -0
  486. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_validate.py +0 -0
  487. /scitex/{fts/_kinds → io/bundle/kinds}/_shape/__init__.py +0 -0
  488. /scitex/{fts/_kinds → io/bundle/kinds}/_stats/__init__.py +0 -0
  489. /scitex/{fts/_kinds → io/bundle/kinds}/_stats/_dataclasses/_Stats.py +0 -0
  490. /scitex/{fts/_kinds → io/bundle/kinds}/_stats/_dataclasses/__init__.py +0 -0
  491. /scitex/{fts/_kinds → io/bundle/kinds}/_table/__init__.py +0 -0
  492. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_editor/__init__.py +0 -0
  493. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_stats_formatter.py +0 -0
  494. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_utils.py +0 -0
  495. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_validator.py +0 -0
  496. /scitex/{fts/_kinds → io/bundle/kinds}/_text/__init__.py +0 -0
  497. /scitex/{fts/_schemas → io/bundle/schemas}/data_info.schema.json +0 -0
  498. /scitex/{fts/_schemas → io/bundle/schemas}/encoding.schema.json +0 -0
  499. /scitex/{fts/_schemas → io/bundle/schemas}/node.schema.json +0 -0
  500. /scitex/{fts/_schemas → io/bundle/schemas}/render_manifest.schema.json +0 -0
  501. /scitex/{fts/_schemas → io/bundle/schemas}/stats.schema.json +0 -0
  502. /scitex/{fts/_schemas → io/bundle/schemas}/theme.schema.json +0 -0
  503. {scitex-2.10.3.dist-info → scitex-2.13.0.dist-info}/WHEEL +0 -0
  504. {scitex-2.10.3.dist-info → scitex-2.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,685 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-08
3
+ # File: src/scitex/scholar/_mcp_handlers.py
4
+ # ----------------------------------------
5
+
6
+ """Handler implementations for the scitex-scholar MCP server."""
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import json
12
+ import os
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+
16
+ __all__ = [
17
+ "search_papers_handler",
18
+ "resolve_dois_handler",
19
+ "enrich_bibtex_handler",
20
+ "download_pdf_handler",
21
+ "download_pdfs_batch_handler",
22
+ "get_library_status_handler",
23
+ "parse_bibtex_handler",
24
+ "validate_pdfs_handler",
25
+ "resolve_openurls_handler",
26
+ "authenticate_handler",
27
+ "export_papers_handler",
28
+ ]
29
+
30
+
31
+ def _get_scholar_dir() -> Path:
32
+ """Get the scholar data directory."""
33
+ base_dir = Path(os.getenv("SCITEX_DIR", Path.home() / ".scitex"))
34
+ scholar_dir = base_dir / "scholar"
35
+ scholar_dir.mkdir(parents=True, exist_ok=True)
36
+ return scholar_dir
37
+
38
+
39
+ def _ensure_scholar():
40
+ """Ensure Scholar module is available and return instance."""
41
+ try:
42
+ from scitex.scholar import Scholar
43
+
44
+ return Scholar()
45
+ except ImportError as e:
46
+ raise RuntimeError(f"Scholar module not available: {e}")
47
+
48
+
49
+ async def search_papers_handler(
50
+ query: str,
51
+ sources: list[str] | None = None,
52
+ limit: int = 20,
53
+ year_min: int | None = None,
54
+ year_max: int | None = None,
55
+ ) -> dict:
56
+ """Search for scientific papers across multiple databases."""
57
+ try:
58
+ from scitex.scholar import Scholar
59
+
60
+ loop = asyncio.get_event_loop()
61
+ scholar = Scholar()
62
+
63
+ def do_search():
64
+ kwargs = {"limit": limit}
65
+ if sources:
66
+ kwargs["sources"] = sources
67
+ if year_min:
68
+ kwargs["year_min"] = year_min
69
+ if year_max:
70
+ kwargs["year_max"] = year_max
71
+
72
+ papers = scholar.search(query, **kwargs)
73
+ return papers
74
+
75
+ papers = await loop.run_in_executor(None, do_search)
76
+
77
+ results = []
78
+ for paper in papers:
79
+ results.append(
80
+ {
81
+ "title": paper.title,
82
+ "authors": paper.authors[:5] if paper.authors else [],
83
+ "year": paper.year,
84
+ "doi": paper.doi,
85
+ "journal": paper.journal,
86
+ "abstract": (
87
+ paper.abstract[:300] + "..."
88
+ if paper.abstract and len(paper.abstract) > 300
89
+ else paper.abstract
90
+ ),
91
+ "citation_count": paper.citation_count,
92
+ "impact_factor": paper.impact_factor,
93
+ }
94
+ )
95
+
96
+ return {
97
+ "success": True,
98
+ "count": len(results),
99
+ "query": query,
100
+ "papers": results,
101
+ "timestamp": datetime.now().isoformat(),
102
+ }
103
+
104
+ except Exception as e:
105
+ return {"success": False, "error": str(e)}
106
+
107
+
108
+ async def resolve_dois_handler(
109
+ bibtex_path: str | None = None,
110
+ titles: list[str] | None = None,
111
+ resume: bool = True,
112
+ project: str | None = None,
113
+ ) -> dict:
114
+ """Resolve DOIs from paper titles."""
115
+ try:
116
+ from scitex.scholar import Scholar
117
+
118
+ loop = asyncio.get_event_loop()
119
+ scholar = Scholar(project=project) if project else Scholar()
120
+
121
+ def do_resolve():
122
+ if bibtex_path:
123
+ # Load papers from BibTeX and resolve DOIs
124
+ papers = scholar.from_bibtex(bibtex_path)
125
+ resolved = []
126
+ failed = []
127
+
128
+ for paper in papers:
129
+ if not paper.doi:
130
+ # Try to resolve DOI from title
131
+ try:
132
+ doi = scholar.resolve_doi(
133
+ paper.title, paper.authors, paper.year
134
+ )
135
+ if doi:
136
+ paper.doi = doi
137
+ resolved.append({"title": paper.title, "doi": doi})
138
+ else:
139
+ failed.append(
140
+ {"title": paper.title, "reason": "No DOI found"}
141
+ )
142
+ except Exception as e:
143
+ failed.append({"title": paper.title, "reason": str(e)})
144
+ else:
145
+ resolved.append({"title": paper.title, "doi": paper.doi})
146
+
147
+ return {"resolved": resolved, "failed": failed, "total": len(papers)}
148
+
149
+ elif titles:
150
+ resolved = []
151
+ failed = []
152
+
153
+ for title in titles:
154
+ try:
155
+ doi = scholar.resolve_doi(title)
156
+ if doi:
157
+ resolved.append({"title": title, "doi": doi})
158
+ else:
159
+ failed.append({"title": title, "reason": "No DOI found"})
160
+ except Exception as e:
161
+ failed.append({"title": title, "reason": str(e)})
162
+
163
+ return {"resolved": resolved, "failed": failed, "total": len(titles)}
164
+
165
+ else:
166
+ return {"error": "Either bibtex_path or titles required"}
167
+
168
+ result = await loop.run_in_executor(None, do_resolve)
169
+
170
+ return {
171
+ "success": True,
172
+ **result,
173
+ "timestamp": datetime.now().isoformat(),
174
+ }
175
+
176
+ except Exception as e:
177
+ return {"success": False, "error": str(e)}
178
+
179
+
180
+ async def enrich_bibtex_handler(
181
+ bibtex_path: str,
182
+ output_path: str | None = None,
183
+ add_abstracts: bool = True,
184
+ add_citations: bool = True,
185
+ add_impact_factors: bool = True,
186
+ ) -> dict:
187
+ """Enrich BibTeX entries with metadata."""
188
+ try:
189
+ from scitex.scholar import Scholar
190
+
191
+ loop = asyncio.get_event_loop()
192
+ scholar = Scholar()
193
+
194
+ def do_enrich():
195
+ papers = scholar.from_bibtex(bibtex_path)
196
+ enriched_count = 0
197
+
198
+ for paper in papers:
199
+ enriched = False
200
+
201
+ if add_abstracts and not paper.abstract:
202
+ # Try to fetch abstract
203
+ pass # Will be handled by scholar.enrich()
204
+
205
+ if add_citations and not paper.citation_count:
206
+ pass
207
+
208
+ if add_impact_factors and not paper.impact_factor:
209
+ pass
210
+
211
+ if enriched:
212
+ enriched_count += 1
213
+
214
+ # Use scholar's enrich method
215
+ papers = scholar.enrich(papers)
216
+
217
+ # Save to output
218
+ out_path = output_path or bibtex_path.replace(".bib", "-enriched.bib")
219
+ papers.save(out_path)
220
+
221
+ summary = {
222
+ "total": len(papers),
223
+ "with_doi": sum(1 for p in papers if p.doi),
224
+ "with_abstract": sum(1 for p in papers if p.abstract),
225
+ "with_citations": sum(1 for p in papers if p.citation_count),
226
+ "with_impact_factor": sum(1 for p in papers if p.impact_factor),
227
+ }
228
+
229
+ return {"output_path": out_path, "summary": summary}
230
+
231
+ result = await loop.run_in_executor(None, do_enrich)
232
+
233
+ return {
234
+ "success": True,
235
+ **result,
236
+ "timestamp": datetime.now().isoformat(),
237
+ }
238
+
239
+ except Exception as e:
240
+ return {"success": False, "error": str(e)}
241
+
242
+
243
+ async def download_pdf_handler(
244
+ doi: str,
245
+ output_dir: str = "./pdfs",
246
+ auth_method: str = "none",
247
+ use_browser: bool = False,
248
+ ) -> dict:
249
+ """Download a single PDF."""
250
+ try:
251
+ from scitex.scholar import Scholar
252
+ from scitex.scholar.core import Paper
253
+
254
+ loop = asyncio.get_event_loop()
255
+ scholar = Scholar()
256
+
257
+ def do_download():
258
+ paper = Paper(doi=doi)
259
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
260
+
261
+ result = scholar.download_pdf(
262
+ paper,
263
+ output_dir=output_dir,
264
+ use_browser=use_browser,
265
+ )
266
+
267
+ return result
268
+
269
+ result = await loop.run_in_executor(None, do_download)
270
+
271
+ if result:
272
+ return {
273
+ "success": True,
274
+ "doi": doi,
275
+ "path": str(result),
276
+ "timestamp": datetime.now().isoformat(),
277
+ }
278
+ else:
279
+ return {
280
+ "success": False,
281
+ "doi": doi,
282
+ "error": "Download failed",
283
+ }
284
+
285
+ except Exception as e:
286
+ return {"success": False, "doi": doi, "error": str(e)}
287
+
288
+
289
+ async def download_pdfs_batch_handler(
290
+ dois: list[str] | None = None,
291
+ bibtex_path: str | None = None,
292
+ project: str | None = None,
293
+ output_dir: str | None = None,
294
+ max_concurrent: int = 3,
295
+ resume: bool = True,
296
+ ) -> dict:
297
+ """Download PDFs for multiple papers."""
298
+ try:
299
+ from scitex.scholar import Scholar
300
+ from scitex.scholar.core import Paper
301
+
302
+ loop = asyncio.get_event_loop()
303
+ scholar = Scholar(project=project) if project else Scholar()
304
+
305
+ def do_batch_download():
306
+ papers = []
307
+
308
+ if bibtex_path:
309
+ papers = scholar.from_bibtex(bibtex_path)
310
+ elif dois:
311
+ papers = [Paper(doi=d) for d in dois]
312
+ else:
313
+ return {"error": "Either dois or bibtex_path required"}
314
+
315
+ out_dir = output_dir or str(
316
+ _get_scholar_dir() / "library" / (project or "default") / "pdfs"
317
+ )
318
+ Path(out_dir).mkdir(parents=True, exist_ok=True)
319
+
320
+ results = {
321
+ "total": len(papers),
322
+ "downloaded": [],
323
+ "failed": [],
324
+ "skipped": [],
325
+ }
326
+
327
+ for paper in papers:
328
+ if not paper.doi:
329
+ results["skipped"].append(
330
+ {"title": paper.title, "reason": "No DOI"}
331
+ )
332
+ continue
333
+
334
+ try:
335
+ pdf_path = scholar.download_pdf(paper, output_dir=out_dir)
336
+ if pdf_path:
337
+ results["downloaded"].append(
338
+ {
339
+ "doi": paper.doi,
340
+ "path": str(pdf_path),
341
+ }
342
+ )
343
+ else:
344
+ results["failed"].append(
345
+ {
346
+ "doi": paper.doi,
347
+ "reason": "Download returned None",
348
+ }
349
+ )
350
+ except Exception as e:
351
+ results["failed"].append(
352
+ {
353
+ "doi": paper.doi,
354
+ "reason": str(e),
355
+ }
356
+ )
357
+
358
+ return results
359
+
360
+ result = await loop.run_in_executor(None, do_batch_download)
361
+
362
+ return {
363
+ "success": True,
364
+ **result,
365
+ "timestamp": datetime.now().isoformat(),
366
+ }
367
+
368
+ except Exception as e:
369
+ return {"success": False, "error": str(e)}
370
+
371
+
372
+ async def get_library_status_handler(
373
+ project: str | None = None,
374
+ include_details: bool = False,
375
+ ) -> dict:
376
+ """Get library status."""
377
+ try:
378
+
379
+ library_dir = _get_scholar_dir() / "library"
380
+
381
+ if project:
382
+ project_dir = library_dir / project
383
+ else:
384
+ project_dir = library_dir
385
+
386
+ if not project_dir.exists():
387
+ return {
388
+ "success": True,
389
+ "exists": False,
390
+ "message": f"Library directory not found: {project_dir}",
391
+ }
392
+
393
+ # Count PDFs
394
+ pdf_files = list(project_dir.rglob("*.pdf"))
395
+ metadata_files = list(project_dir.rglob("metadata.json"))
396
+
397
+ status = {
398
+ "success": True,
399
+ "exists": True,
400
+ "path": str(project_dir),
401
+ "pdf_count": len(pdf_files),
402
+ "entry_count": len(metadata_files),
403
+ "timestamp": datetime.now().isoformat(),
404
+ }
405
+
406
+ if include_details:
407
+ entries = []
408
+ for meta_file in metadata_files[:50]: # Limit to 50 for performance
409
+ try:
410
+ with open(meta_file) as f:
411
+ meta = json.load(f)
412
+ pdf_exists = any(
413
+ (meta_file.parent / f).exists()
414
+ for f in meta_file.parent.glob("*.pdf")
415
+ )
416
+ entries.append(
417
+ {
418
+ "id": meta_file.parent.name,
419
+ "title": meta.get("title", "Unknown"),
420
+ "doi": meta.get("doi"),
421
+ "has_pdf": pdf_exists,
422
+ }
423
+ )
424
+ except Exception:
425
+ pass
426
+
427
+ status["entries"] = entries
428
+
429
+ return status
430
+
431
+ except Exception as e:
432
+ return {"success": False, "error": str(e)}
433
+
434
+
435
+ async def parse_bibtex_handler(bibtex_path: str) -> dict:
436
+ """Parse a BibTeX file."""
437
+ try:
438
+ from scitex.scholar import Scholar
439
+
440
+ loop = asyncio.get_event_loop()
441
+ scholar = Scholar()
442
+
443
+ def do_parse():
444
+ papers = scholar.from_bibtex(bibtex_path)
445
+ return papers
446
+
447
+ papers = await loop.run_in_executor(None, do_parse)
448
+
449
+ results = []
450
+ for paper in papers:
451
+ results.append(
452
+ {
453
+ "title": paper.title,
454
+ "authors": paper.authors[:5] if paper.authors else [],
455
+ "year": paper.year,
456
+ "doi": paper.doi,
457
+ "journal": paper.journal,
458
+ "bibtex_key": getattr(paper, "bibtex_key", None),
459
+ }
460
+ )
461
+
462
+ return {
463
+ "success": True,
464
+ "count": len(results),
465
+ "path": bibtex_path,
466
+ "papers": results,
467
+ "timestamp": datetime.now().isoformat(),
468
+ }
469
+
470
+ except Exception as e:
471
+ return {"success": False, "error": str(e)}
472
+
473
+
474
+ async def validate_pdfs_handler(
475
+ project: str | None = None,
476
+ pdf_paths: list[str] | None = None,
477
+ ) -> dict:
478
+ """Validate PDF files."""
479
+ try:
480
+ from PyPDF2 import PdfReader
481
+
482
+ if pdf_paths:
483
+ paths = [Path(p) for p in pdf_paths]
484
+ elif project:
485
+ library_dir = _get_scholar_dir() / "library" / project
486
+ paths = list(library_dir.rglob("*.pdf"))
487
+ else:
488
+ library_dir = _get_scholar_dir() / "library"
489
+ paths = list(library_dir.rglob("*.pdf"))
490
+
491
+ results = {
492
+ "total": len(paths),
493
+ "valid": [],
494
+ "invalid": [],
495
+ }
496
+
497
+ for pdf_path in paths:
498
+ try:
499
+ reader = PdfReader(str(pdf_path))
500
+ page_count = len(reader.pages)
501
+
502
+ # Check if it has text content
503
+ has_text = False
504
+ if page_count > 0:
505
+ text = reader.pages[0].extract_text()
506
+ has_text = bool(text and len(text.strip()) > 100)
507
+
508
+ results["valid"].append(
509
+ {
510
+ "path": str(pdf_path),
511
+ "pages": page_count,
512
+ "has_text": has_text,
513
+ "size_kb": round(pdf_path.stat().st_size / 1024, 2),
514
+ }
515
+ )
516
+ except Exception as e:
517
+ results["invalid"].append(
518
+ {
519
+ "path": str(pdf_path),
520
+ "error": str(e),
521
+ }
522
+ )
523
+
524
+ return {
525
+ "success": True,
526
+ **results,
527
+ "valid_count": len(results["valid"]),
528
+ "invalid_count": len(results["invalid"]),
529
+ "timestamp": datetime.now().isoformat(),
530
+ }
531
+
532
+ except ImportError:
533
+ return {"success": False, "error": "PyPDF2 not installed"}
534
+ except Exception as e:
535
+ return {"success": False, "error": str(e)}
536
+
537
+
538
+ async def resolve_openurls_handler(
539
+ dois: list[str],
540
+ resolver_url: str | None = None,
541
+ resume: bool = True,
542
+ ) -> dict:
543
+ """Resolve OpenURLs for DOIs."""
544
+ try:
545
+ from scitex.scholar import Scholar
546
+
547
+ loop = asyncio.get_event_loop()
548
+ scholar = Scholar()
549
+
550
+ def do_resolve():
551
+ results = {
552
+ "resolved": [],
553
+ "failed": [],
554
+ }
555
+
556
+ for doi in dois:
557
+ try:
558
+ # Use scholar's OpenURL resolver
559
+ url = scholar.resolve_openurl(doi, resolver_url=resolver_url)
560
+ if url:
561
+ results["resolved"].append({"doi": doi, "url": url})
562
+ else:
563
+ results["failed"].append({"doi": doi, "reason": "No URL found"})
564
+ except Exception as e:
565
+ results["failed"].append({"doi": doi, "reason": str(e)})
566
+
567
+ return results
568
+
569
+ result = await loop.run_in_executor(None, do_resolve)
570
+
571
+ return {
572
+ "success": True,
573
+ **result,
574
+ "total": len(dois),
575
+ "timestamp": datetime.now().isoformat(),
576
+ }
577
+
578
+ except Exception as e:
579
+ return {"success": False, "error": str(e)}
580
+
581
+
582
+ async def authenticate_handler(
583
+ method: str,
584
+ institution: str | None = None,
585
+ ) -> dict:
586
+ """Authenticate with institutional access."""
587
+ try:
588
+ from scitex.scholar import ScholarAuthManager
589
+
590
+ loop = asyncio.get_event_loop()
591
+
592
+ def do_auth():
593
+ auth_manager = ScholarAuthManager()
594
+
595
+ if method == "openathens":
596
+ success = auth_manager.authenticate_openathens(institution)
597
+ elif method == "shibboleth":
598
+ success = auth_manager.authenticate_shibboleth(institution)
599
+ else:
600
+ return {"error": f"Unknown auth method: {method}"}
601
+
602
+ return {"authenticated": success}
603
+
604
+ result = await loop.run_in_executor(None, do_auth)
605
+
606
+ return {
607
+ "success": True,
608
+ "method": method,
609
+ "institution": institution,
610
+ **result,
611
+ "timestamp": datetime.now().isoformat(),
612
+ }
613
+
614
+ except Exception as e:
615
+ return {"success": False, "error": str(e)}
616
+
617
+
618
+ async def export_papers_handler(
619
+ output_path: str,
620
+ project: str | None = None,
621
+ format: str = "bibtex",
622
+ filter_has_pdf: bool = False,
623
+ ) -> dict:
624
+ """Export papers to various formats."""
625
+ try:
626
+ from scitex.scholar import Scholar
627
+
628
+ loop = asyncio.get_event_loop()
629
+ scholar = Scholar(project=project) if project else Scholar()
630
+
631
+ def do_export():
632
+ # Get papers from library
633
+ papers = scholar.get_library_papers(project=project)
634
+
635
+ if filter_has_pdf:
636
+ papers = [p for p in papers if hasattr(p, "pdf_path") and p.pdf_path]
637
+
638
+ # Export based on format
639
+ out_path = Path(output_path)
640
+ out_path.parent.mkdir(parents=True, exist_ok=True)
641
+
642
+ if format == "bibtex":
643
+ papers.save(str(out_path))
644
+ elif format == "json":
645
+ with open(out_path, "w") as f:
646
+ json.dump([p.to_dict() for p in papers], f, indent=2)
647
+ elif format == "csv":
648
+ import csv
649
+
650
+ with open(out_path, "w", newline="") as f:
651
+ writer = csv.DictWriter(
652
+ f, fieldnames=["title", "authors", "year", "doi", "journal"]
653
+ )
654
+ writer.writeheader()
655
+ for p in papers:
656
+ writer.writerow(
657
+ {
658
+ "title": p.title,
659
+ "authors": (
660
+ "; ".join(p.authors[:3]) if p.authors else ""
661
+ ),
662
+ "year": p.year,
663
+ "doi": p.doi,
664
+ "journal": p.journal,
665
+ }
666
+ )
667
+ elif format == "ris":
668
+ papers.save(str(out_path), format="ris")
669
+
670
+ return {"count": len(papers), "path": str(out_path)}
671
+
672
+ result = await loop.run_in_executor(None, do_export)
673
+
674
+ return {
675
+ "success": True,
676
+ "format": format,
677
+ **result,
678
+ "timestamp": datetime.now().isoformat(),
679
+ }
680
+
681
+ except Exception as e:
682
+ return {"success": False, "error": str(e)}
683
+
684
+
685
+ # EOF