scitex 2.5.0__py3-none-any.whl → 2.7.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 (1091) hide show
  1. scitex/__init__.py +19 -8
  2. scitex/__main__.py +2 -1
  3. scitex/__version__.py +1 -1
  4. scitex/_optional_deps.py +13 -20
  5. scitex/ai/__init__.py +5 -0
  6. scitex/ai/_gen_ai/_Anthropic.py +3 -1
  7. scitex/ai/_gen_ai/_BaseGenAI.py +3 -2
  8. scitex/ai/_gen_ai/_DeepSeek.py +1 -1
  9. scitex/ai/_gen_ai/_Google.py +3 -2
  10. scitex/ai/_gen_ai/_Llama.py +4 -2
  11. scitex/ai/_gen_ai/_OpenAI.py +3 -1
  12. scitex/ai/_gen_ai/_PARAMS.py +1 -0
  13. scitex/ai/_gen_ai/_Perplexity.py +3 -1
  14. scitex/ai/_gen_ai/__init__.py +1 -0
  15. scitex/ai/_gen_ai/_format_output_func.py +3 -1
  16. scitex/ai/classification/CrossValidationExperiment.py +8 -14
  17. scitex/ai/classification/examples/timeseries_cv_demo.py +128 -112
  18. scitex/ai/classification/reporters/_BaseClassificationReporter.py +2 -0
  19. scitex/ai/classification/reporters/_ClassificationReporter.py +30 -45
  20. scitex/ai/classification/reporters/_MultiClassificationReporter.py +8 -11
  21. scitex/ai/classification/reporters/_SingleClassificationReporter.py +126 -182
  22. scitex/ai/classification/reporters/__init__.py +1 -1
  23. scitex/ai/classification/reporters/reporter_utils/_Plotter.py +213 -119
  24. scitex/ai/classification/reporters/reporter_utils/__init__.py +28 -36
  25. scitex/ai/classification/reporters/reporter_utils/aggregation.py +125 -143
  26. scitex/ai/classification/reporters/reporter_utils/data_models.py +128 -120
  27. scitex/ai/classification/reporters/reporter_utils/reporting.py +507 -340
  28. scitex/ai/classification/reporters/reporter_utils/storage.py +4 -1
  29. scitex/ai/classification/reporters/reporter_utils/validation.py +141 -154
  30. scitex/ai/classification/timeseries/_TimeSeriesBlockingSplit.py +204 -129
  31. scitex/ai/classification/timeseries/_TimeSeriesCalendarSplit.py +215 -171
  32. scitex/ai/classification/timeseries/_TimeSeriesMetadata.py +17 -17
  33. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +67 -143
  34. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit_v01-not-using-n_splits.py +67 -143
  35. scitex/ai/classification/timeseries/_TimeSeriesStrategy.py +12 -13
  36. scitex/ai/classification/timeseries/_TimeSeriesStratifiedSplit.py +231 -144
  37. scitex/ai/classification/timeseries/__init__.py +2 -4
  38. scitex/ai/classification/timeseries/_normalize_timestamp.py +3 -0
  39. scitex/ai/clustering/_pca.py +0 -1
  40. scitex/ai/clustering/_umap.py +1 -2
  41. scitex/ai/feature_extraction/__init__.py +10 -8
  42. scitex/ai/feature_extraction/vit.py +0 -1
  43. scitex/ai/feature_selection/feature_selection.py +3 -8
  44. scitex/ai/metrics/_calc_conf_mat.py +2 -0
  45. scitex/ai/metrics/_calc_feature_importance.py +3 -7
  46. scitex/ai/metrics/_calc_pre_rec_auc.py +5 -5
  47. scitex/ai/metrics/_calc_roc_auc.py +4 -2
  48. scitex/ai/metrics/_calc_seizure_prediction_metrics.py +35 -20
  49. scitex/ai/metrics/_calc_silhouette_score.py +1 -3
  50. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger.py +0 -3
  51. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger2020.py +0 -3
  52. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger913A.py +0 -3
  53. scitex/ai/optim/_optimizers.py +1 -1
  54. scitex/ai/plt/__init__.py +6 -1
  55. scitex/ai/plt/_plot_feature_importance.py +1 -3
  56. scitex/ai/plt/_plot_learning_curve.py +9 -24
  57. scitex/ai/plt/_plot_optuna_study.py +4 -3
  58. scitex/ai/plt/_plot_pre_rec_curve.py +9 -15
  59. scitex/ai/plt/_plot_roc_curve.py +6 -8
  60. scitex/ai/plt/_stx_conf_mat.py +121 -122
  61. scitex/ai/sampling/undersample.py +3 -2
  62. scitex/ai/sklearn/__init__.py +2 -2
  63. scitex/ai/training/_LearningCurveLogger.py +23 -10
  64. scitex/ai/utils/_check_params.py +0 -1
  65. scitex/benchmark/__init__.py +15 -25
  66. scitex/benchmark/benchmark.py +124 -117
  67. scitex/benchmark/monitor.py +117 -107
  68. scitex/benchmark/profiler.py +61 -58
  69. scitex/bridge/__init__.py +110 -0
  70. scitex/bridge/_helpers.py +149 -0
  71. scitex/bridge/_plt_vis.py +529 -0
  72. scitex/bridge/_protocol.py +283 -0
  73. scitex/bridge/_stats_plt.py +261 -0
  74. scitex/bridge/_stats_vis.py +265 -0
  75. scitex/browser/__init__.py +0 -2
  76. scitex/browser/auth/__init__.py +0 -0
  77. scitex/browser/auth/google.py +16 -11
  78. scitex/browser/automation/CookieHandler.py +2 -3
  79. scitex/browser/collaboration/__init__.py +3 -0
  80. scitex/browser/collaboration/auth_helpers.py +3 -1
  81. scitex/browser/collaboration/collaborative_agent.py +2 -0
  82. scitex/browser/collaboration/interactive_panel.py +2 -2
  83. scitex/browser/collaboration/shared_session.py +20 -11
  84. scitex/browser/collaboration/standard_interactions.py +1 -0
  85. scitex/browser/core/BrowserMixin.py +12 -30
  86. scitex/browser/core/ChromeProfileManager.py +9 -24
  87. scitex/browser/debugging/_browser_logger.py +15 -25
  88. scitex/browser/debugging/_failure_capture.py +9 -2
  89. scitex/browser/debugging/_highlight_element.py +15 -6
  90. scitex/browser/debugging/_show_grid.py +5 -6
  91. scitex/browser/debugging/_sync_session.py +4 -3
  92. scitex/browser/debugging/_test_monitor.py +14 -5
  93. scitex/browser/debugging/_visual_cursor.py +46 -35
  94. scitex/browser/interaction/click_center.py +4 -3
  95. scitex/browser/interaction/click_with_fallbacks.py +7 -10
  96. scitex/browser/interaction/close_popups.py +79 -66
  97. scitex/browser/interaction/fill_with_fallbacks.py +8 -8
  98. scitex/browser/pdf/__init__.py +3 -1
  99. scitex/browser/pdf/click_download_for_chrome_pdf_viewer.py +11 -10
  100. scitex/browser/pdf/detect_chrome_pdf_viewer.py +3 -6
  101. scitex/browser/remote/CaptchaHandler.py +109 -96
  102. scitex/browser/remote/ZenRowsAPIClient.py +91 -97
  103. scitex/browser/remote/ZenRowsBrowserManager.py +138 -112
  104. scitex/browser/stealth/HumanBehavior.py +4 -9
  105. scitex/browser/stealth/StealthManager.py +11 -26
  106. scitex/capture/__init__.py +17 -17
  107. scitex/capture/__main__.py +2 -3
  108. scitex/capture/capture.py +23 -51
  109. scitex/capture/cli.py +14 -39
  110. scitex/capture/gif.py +5 -9
  111. scitex/capture/mcp_server.py +7 -20
  112. scitex/capture/session.py +4 -3
  113. scitex/capture/utils.py +18 -53
  114. scitex/cli/__init__.py +1 -1
  115. scitex/cli/cloud.py +158 -116
  116. scitex/cli/config.py +224 -0
  117. scitex/cli/main.py +41 -40
  118. scitex/cli/scholar.py +60 -27
  119. scitex/cli/security.py +14 -20
  120. scitex/cli/web.py +87 -90
  121. scitex/cli/writer.py +51 -45
  122. scitex/cloud/__init__.py +14 -11
  123. scitex/cloud/_matplotlib_hook.py +6 -6
  124. scitex/config/README.md +313 -0
  125. scitex/config/{PriorityConfig.py → _PriorityConfig.py} +114 -17
  126. scitex/config/_ScitexConfig.py +319 -0
  127. scitex/config/__init__.py +41 -9
  128. scitex/config/_paths.py +325 -0
  129. scitex/config/default.yaml +81 -0
  130. scitex/context/_suppress_output.py +2 -3
  131. scitex/db/_BaseMixins/_BaseBackupMixin.py +3 -1
  132. scitex/db/_BaseMixins/_BaseBatchMixin.py +3 -1
  133. scitex/db/_BaseMixins/_BaseBlobMixin.py +3 -1
  134. scitex/db/_BaseMixins/_BaseImportExportMixin.py +1 -3
  135. scitex/db/_BaseMixins/_BaseIndexMixin.py +3 -1
  136. scitex/db/_BaseMixins/_BaseMaintenanceMixin.py +1 -3
  137. scitex/db/_BaseMixins/_BaseQueryMixin.py +3 -1
  138. scitex/db/_BaseMixins/_BaseRowMixin.py +3 -1
  139. scitex/db/_BaseMixins/_BaseTableMixin.py +3 -1
  140. scitex/db/_BaseMixins/_BaseTransactionMixin.py +1 -3
  141. scitex/db/_BaseMixins/__init__.py +1 -1
  142. scitex/db/__init__.py +9 -1
  143. scitex/db/__main__.py +8 -21
  144. scitex/db/_check_health.py +15 -31
  145. scitex/db/_delete_duplicates.py +7 -4
  146. scitex/db/_inspect.py +22 -38
  147. scitex/db/_inspect_optimized.py +89 -85
  148. scitex/db/_postgresql/_PostgreSQL.py +0 -1
  149. scitex/db/_postgresql/_PostgreSQLMixins/_BlobMixin.py +3 -1
  150. scitex/db/_postgresql/_PostgreSQLMixins/_ConnectionMixin.py +1 -3
  151. scitex/db/_postgresql/_PostgreSQLMixins/_ImportExportMixin.py +1 -3
  152. scitex/db/_postgresql/_PostgreSQLMixins/_MaintenanceMixin.py +1 -4
  153. scitex/db/_postgresql/_PostgreSQLMixins/_QueryMixin.py +3 -3
  154. scitex/db/_postgresql/_PostgreSQLMixins/_RowMixin.py +3 -1
  155. scitex/db/_postgresql/_PostgreSQLMixins/_TransactionMixin.py +1 -3
  156. scitex/db/_postgresql/__init__.py +1 -1
  157. scitex/db/_sqlite3/_SQLite3.py +2 -4
  158. scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin.py +11 -12
  159. scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin_v01-need-_hash-col.py +19 -14
  160. scitex/db/_sqlite3/_SQLite3Mixins/_BatchMixin.py +3 -1
  161. scitex/db/_sqlite3/_SQLite3Mixins/_BlobMixin.py +7 -7
  162. scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin.py +118 -111
  163. scitex/db/_sqlite3/_SQLite3Mixins/_ConnectionMixin.py +8 -10
  164. scitex/db/_sqlite3/_SQLite3Mixins/_GitMixin.py +17 -45
  165. scitex/db/_sqlite3/_SQLite3Mixins/_ImportExportMixin.py +1 -3
  166. scitex/db/_sqlite3/_SQLite3Mixins/_IndexMixin.py +3 -1
  167. scitex/db/_sqlite3/_SQLite3Mixins/_QueryMixin.py +3 -4
  168. scitex/db/_sqlite3/_SQLite3Mixins/_RowMixin.py +9 -9
  169. scitex/db/_sqlite3/_SQLite3Mixins/_TableMixin.py +18 -11
  170. scitex/db/_sqlite3/_SQLite3Mixins/__init__.py +1 -0
  171. scitex/db/_sqlite3/__init__.py +1 -1
  172. scitex/db/_sqlite3/_delete_duplicates.py +13 -11
  173. scitex/decorators/__init__.py +29 -4
  174. scitex/decorators/_auto_order.py +43 -43
  175. scitex/decorators/_batch_fn.py +12 -6
  176. scitex/decorators/_cache_disk.py +8 -9
  177. scitex/decorators/_cache_disk_async.py +8 -7
  178. scitex/decorators/_combined.py +19 -13
  179. scitex/decorators/_converters.py +16 -3
  180. scitex/decorators/_deprecated.py +32 -22
  181. scitex/decorators/_numpy_fn.py +18 -4
  182. scitex/decorators/_pandas_fn.py +17 -5
  183. scitex/decorators/_signal_fn.py +17 -3
  184. scitex/decorators/_torch_fn.py +32 -15
  185. scitex/decorators/_xarray_fn.py +23 -9
  186. scitex/dev/_analyze_code_flow.py +0 -2
  187. scitex/dict/_DotDict.py +15 -19
  188. scitex/dict/_flatten.py +1 -0
  189. scitex/dict/_listed_dict.py +1 -0
  190. scitex/dict/_pop_keys.py +1 -0
  191. scitex/dict/_replace.py +1 -0
  192. scitex/dict/_safe_merge.py +1 -0
  193. scitex/dict/_to_str.py +2 -3
  194. scitex/dsp/__init__.py +13 -4
  195. scitex/dsp/_crop.py +3 -1
  196. scitex/dsp/_detect_ripples.py +3 -1
  197. scitex/dsp/_modulation_index.py +3 -1
  198. scitex/dsp/_time.py +3 -1
  199. scitex/dsp/_wavelet.py +0 -1
  200. scitex/dsp/example.py +0 -5
  201. scitex/dsp/filt.py +4 -0
  202. scitex/dsp/utils/__init__.py +4 -1
  203. scitex/dsp/utils/pac.py +3 -3
  204. scitex/dt/_normalize_timestamp.py +4 -1
  205. scitex/errors.py +3 -6
  206. scitex/etc/__init__.py +1 -1
  207. scitex/gen/_DimHandler.py +6 -6
  208. scitex/gen/__init__.py +5 -1
  209. scitex/gen/_deprecated_close.py +1 -0
  210. scitex/gen/_deprecated_start.py +5 -3
  211. scitex/gen/_detect_environment.py +44 -41
  212. scitex/gen/_detect_notebook_path.py +51 -47
  213. scitex/gen/_embed.py +1 -1
  214. scitex/gen/_get_notebook_path.py +81 -62
  215. scitex/gen/_inspect_module.py +0 -1
  216. scitex/gen/_norm.py +16 -7
  217. scitex/gen/_norm_cache.py +78 -65
  218. scitex/gen/_print_config.py +0 -3
  219. scitex/gen/_src.py +2 -3
  220. scitex/gen/_title_case.py +3 -2
  221. scitex/gen/_to_even.py +8 -8
  222. scitex/gen/_transpose.py +3 -3
  223. scitex/gen/misc.py +0 -3
  224. scitex/gists/_SigMacro_processFigure_S.py +2 -2
  225. scitex/gists/_SigMacro_toBlue.py +2 -2
  226. scitex/gists/__init__.py +4 -1
  227. scitex/git/_branch.py +19 -11
  228. scitex/git/_clone.py +23 -15
  229. scitex/git/_commit.py +10 -12
  230. scitex/git/_init.py +15 -38
  231. scitex/git/_remote.py +9 -3
  232. scitex/git/_result.py +3 -0
  233. scitex/git/_retry.py +2 -5
  234. scitex/git/_types.py +4 -0
  235. scitex/git/_validation.py +8 -8
  236. scitex/git/_workflow.py +4 -4
  237. scitex/io/__init__.py +2 -1
  238. scitex/io/_glob.py +2 -2
  239. scitex/io/_json2md.py +3 -3
  240. scitex/io/_load.py +6 -8
  241. scitex/io/_load_cache.py +71 -71
  242. scitex/io/_load_configs.py +2 -3
  243. scitex/io/_load_modules/_H5Explorer.py +6 -12
  244. scitex/io/_load_modules/_ZarrExplorer.py +3 -3
  245. scitex/io/_load_modules/_bibtex.py +62 -63
  246. scitex/io/_load_modules/_canvas.py +4 -9
  247. scitex/io/_load_modules/_catboost.py +7 -2
  248. scitex/io/_load_modules/_hdf5.py +2 -0
  249. scitex/io/_load_modules/_image.py +5 -1
  250. scitex/io/_load_modules/_matlab.py +3 -1
  251. scitex/io/_load_modules/_optuna.py +0 -1
  252. scitex/io/_load_modules/_pdf.py +38 -29
  253. scitex/io/_load_modules/_sqlite3.py +1 -0
  254. scitex/io/_load_modules/_txt.py +2 -0
  255. scitex/io/_load_modules/_xml.py +9 -9
  256. scitex/io/_load_modules/_zarr.py +12 -10
  257. scitex/io/_metadata.py +76 -37
  258. scitex/io/_qr_utils.py +18 -13
  259. scitex/io/_save.py +220 -63
  260. scitex/io/_save_modules/__init__.py +7 -2
  261. scitex/io/_save_modules/_bibtex.py +66 -61
  262. scitex/io/_save_modules/_canvas.py +5 -6
  263. scitex/io/_save_modules/_catboost.py +2 -2
  264. scitex/io/_save_modules/_csv.py +4 -4
  265. scitex/io/_save_modules/_excel.py +5 -9
  266. scitex/io/_save_modules/_hdf5.py +9 -21
  267. scitex/io/_save_modules/_html.py +5 -5
  268. scitex/io/_save_modules/_image.py +105 -8
  269. scitex/io/_save_modules/_joblib.py +2 -2
  270. scitex/io/_save_modules/_json.py +51 -6
  271. scitex/io/_save_modules/_listed_dfs_as_csv.py +2 -1
  272. scitex/io/_save_modules/_listed_scalars_as_csv.py +2 -1
  273. scitex/io/_save_modules/_matlab.py +2 -2
  274. scitex/io/_save_modules/_numpy.py +6 -8
  275. scitex/io/_save_modules/_pickle.py +4 -4
  276. scitex/io/_save_modules/_plotly.py +3 -3
  277. scitex/io/_save_modules/_tex.py +23 -25
  278. scitex/io/_save_modules/_text.py +2 -2
  279. scitex/io/_save_modules/_yaml.py +9 -9
  280. scitex/io/_save_modules/_zarr.py +15 -15
  281. scitex/io/utils/__init__.py +2 -1
  282. scitex/io/utils/h5_to_zarr.py +173 -155
  283. scitex/linalg/__init__.py +1 -1
  284. scitex/linalg/_geometric_median.py +4 -3
  285. scitex/logging/_Tee.py +5 -7
  286. scitex/logging/__init__.py +18 -19
  287. scitex/logging/_config.py +4 -1
  288. scitex/logging/_context.py +6 -5
  289. scitex/logging/_formatters.py +2 -3
  290. scitex/logging/_handlers.py +19 -20
  291. scitex/logging/_levels.py +9 -17
  292. scitex/logging/_logger.py +74 -15
  293. scitex/logging/_print_capture.py +17 -17
  294. scitex/nn/_BNet.py +1 -3
  295. scitex/nn/_Filters.py +6 -2
  296. scitex/nn/_ModulationIndex.py +3 -1
  297. scitex/nn/_PAC.py +3 -2
  298. scitex/nn/_PSD.py +0 -1
  299. scitex/nn/__init__.py +16 -3
  300. scitex/path/_clean.py +10 -8
  301. scitex/path/_find.py +1 -1
  302. scitex/path/_get_spath.py +1 -2
  303. scitex/path/_mk_spath.py +1 -1
  304. scitex/path/_symlink.py +5 -10
  305. scitex/pd/__init__.py +4 -1
  306. scitex/pd/_force_df.py +24 -24
  307. scitex/pd/_get_unique.py +1 -0
  308. scitex/pd/_merge_columns.py +1 -1
  309. scitex/pd/_round.py +11 -7
  310. scitex/pd/_to_xy.py +0 -1
  311. scitex/plt/REQUESTS.md +191 -0
  312. scitex/plt/__init__.py +185 -87
  313. scitex/plt/_subplots/_AxesWrapper.py +22 -6
  314. scitex/plt/_subplots/_AxisWrapper.py +100 -39
  315. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +74 -52
  316. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +183 -73
  317. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +61 -45
  318. scitex/plt/_subplots/_AxisWrapperMixins/_TrackingMixin.py +26 -14
  319. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +80 -73
  320. scitex/plt/_subplots/_FigWrapper.py +93 -60
  321. scitex/plt/_subplots/_SubplotsWrapper.py +135 -68
  322. scitex/plt/_subplots/__init__.py +10 -0
  323. scitex/plt/_subplots/_export_as_csv.py +89 -47
  324. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +1 -0
  325. scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +6 -4
  326. scitex/plt/_subplots/_export_as_csv_formatters/_format_bar.py +88 -38
  327. scitex/plt/_subplots/_export_as_csv_formatters/_format_barh.py +25 -31
  328. scitex/plt/_subplots/_export_as_csv_formatters/_format_boxplot.py +53 -23
  329. scitex/plt/_subplots/_export_as_csv_formatters/_format_contour.py +38 -25
  330. scitex/plt/_subplots/_export_as_csv_formatters/_format_contourf.py +17 -9
  331. scitex/plt/_subplots/_export_as_csv_formatters/_format_errorbar.py +70 -124
  332. scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +12 -10
  333. scitex/plt/_subplots/_export_as_csv_formatters/_format_fill.py +31 -17
  334. scitex/plt/_subplots/_export_as_csv_formatters/_format_fill_between.py +33 -21
  335. scitex/plt/_subplots/_export_as_csv_formatters/_format_hexbin.py +14 -4
  336. scitex/plt/_subplots/_export_as_csv_formatters/_format_hist.py +43 -29
  337. scitex/plt/_subplots/_export_as_csv_formatters/_format_hist2d.py +14 -4
  338. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow.py +27 -11
  339. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +7 -5
  340. scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +9 -7
  341. scitex/plt/_subplots/_export_as_csv_formatters/_format_pie.py +15 -6
  342. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +85 -46
  343. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_box.py +52 -27
  344. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +1 -0
  345. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +16 -17
  346. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +7 -5
  347. scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +10 -8
  348. scitex/plt/_subplots/_export_as_csv_formatters/_format_scatter.py +17 -6
  349. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_barplot.py +43 -26
  350. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_boxplot.py +68 -47
  351. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_heatmap.py +52 -64
  352. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_histplot.py +55 -50
  353. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +9 -11
  354. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_kdeplot.py +63 -29
  355. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +4 -4
  356. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +6 -4
  357. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_scatterplot.py +44 -40
  358. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_stripplot.py +46 -39
  359. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_swarmplot.py +46 -39
  360. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_violinplot.py +75 -94
  361. scitex/plt/_subplots/_export_as_csv_formatters/_format_stem.py +12 -3
  362. scitex/plt/_subplots/_export_as_csv_formatters/_format_step.py +12 -3
  363. scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +10 -8
  364. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +17 -15
  365. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +10 -9
  366. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_fillv.py +35 -31
  367. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +18 -18
  368. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +24 -18
  369. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +9 -7
  370. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_line.py +34 -23
  371. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +15 -13
  372. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +12 -10
  373. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +15 -13
  374. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +11 -9
  375. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_rectangle.py +84 -56
  376. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +35 -32
  377. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_shaded_line.py +46 -30
  378. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_violin.py +51 -51
  379. scitex/plt/_subplots/_export_as_csv_formatters/_format_text.py +32 -31
  380. scitex/plt/_subplots/_export_as_csv_formatters/_format_violin.py +34 -31
  381. scitex/plt/_subplots/_export_as_csv_formatters/_format_violinplot.py +44 -37
  382. scitex/plt/_subplots/_export_as_csv_formatters/verify_formatters.py +91 -74
  383. scitex/plt/_tpl.py +6 -5
  384. scitex/plt/ax/_plot/__init__.py +24 -0
  385. scitex/plt/ax/_plot/_add_fitted_line.py +12 -11
  386. scitex/plt/ax/_plot/_plot_circular_hist.py +3 -1
  387. scitex/plt/ax/_plot/_plot_statistical_shaded_line.py +25 -19
  388. scitex/plt/ax/_plot/_stx_conf_mat.py +6 -3
  389. scitex/plt/ax/_plot/_stx_ecdf.py +5 -3
  390. scitex/plt/ax/_plot/_stx_fillv.py +4 -2
  391. scitex/plt/ax/_plot/_stx_heatmap.py +7 -4
  392. scitex/plt/ax/_plot/_stx_image.py +7 -5
  393. scitex/plt/ax/_plot/_stx_joyplot.py +32 -10
  394. scitex/plt/ax/_plot/_stx_raster.py +26 -11
  395. scitex/plt/ax/_plot/_stx_rectangle.py +2 -2
  396. scitex/plt/ax/_plot/_stx_shaded_line.py +15 -11
  397. scitex/plt/ax/_plot/_stx_violin.py +3 -1
  398. scitex/plt/ax/_style/_add_marginal_ax.py +6 -4
  399. scitex/plt/ax/_style/_auto_scale_axis.py +14 -10
  400. scitex/plt/ax/_style/_extend.py +3 -1
  401. scitex/plt/ax/_style/_force_aspect.py +5 -3
  402. scitex/plt/ax/_style/_format_units.py +2 -2
  403. scitex/plt/ax/_style/_hide_spines.py +5 -1
  404. scitex/plt/ax/_style/_map_ticks.py +5 -3
  405. scitex/plt/ax/_style/_rotate_labels.py +5 -4
  406. scitex/plt/ax/_style/_rotate_labels_v01.py +73 -63
  407. scitex/plt/ax/_style/_set_log_scale.py +120 -85
  408. scitex/plt/ax/_style/_set_meta.py +99 -76
  409. scitex/plt/ax/_style/_set_supxyt.py +33 -16
  410. scitex/plt/ax/_style/_set_xyt.py +27 -18
  411. scitex/plt/ax/_style/_share_axes.py +15 -5
  412. scitex/plt/ax/_style/_show_spines.py +58 -57
  413. scitex/plt/ax/_style/_style_barplot.py +1 -1
  414. scitex/plt/ax/_style/_style_boxplot.py +25 -14
  415. scitex/plt/ax/_style/_style_errorbar.py +0 -0
  416. scitex/plt/ax/_style/_style_scatter.py +1 -1
  417. scitex/plt/ax/_style/_style_suptitles.py +3 -3
  418. scitex/plt/ax/_style/_style_violinplot.py +8 -2
  419. scitex/plt/color/__init__.py +34 -2
  420. scitex/plt/color/_add_hue_col.py +1 -0
  421. scitex/plt/color/_colors.py +0 -1
  422. scitex/plt/color/_get_colors_from_conf_matap.py +3 -1
  423. scitex/plt/color/_vizualize_colors.py +0 -1
  424. scitex/plt/docs/FIGURE_ARCHITECTURE.md +155 -97
  425. scitex/plt/gallery/README.md +75 -0
  426. scitex/plt/gallery/__init__.py +29 -0
  427. scitex/plt/gallery/_generate.py +153 -0
  428. scitex/plt/gallery/_plots.py +594 -0
  429. scitex/plt/gallery/_registry.py +153 -0
  430. scitex/plt/styles/__init__.py +9 -9
  431. scitex/plt/styles/_plot_defaults.py +62 -61
  432. scitex/plt/styles/_plot_postprocess.py +126 -77
  433. scitex/plt/styles/_style_loader.py +0 -0
  434. scitex/plt/styles/presets.py +43 -18
  435. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_between.json +110 -0
  436. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_betweenx.json +88 -0
  437. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fill_between.json +103 -0
  438. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fillv.json +106 -0
  439. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/bar.json +92 -0
  440. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/barh.json +92 -0
  441. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/boxplot.json +92 -0
  442. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_bar.json +84 -0
  443. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_barh.json +84 -0
  444. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_box.json +83 -0
  445. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_boxplot.json +93 -0
  446. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violin.json +91 -0
  447. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violinplot.json +91 -0
  448. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/violinplot.json +91 -0
  449. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contour.json +97 -0
  450. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contourf.json +98 -0
  451. scitex/plt/templates/research-master/scitex/vis/gallery/contour/stx_contour.json +84 -0
  452. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist.json +101 -0
  453. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist2d.json +96 -0
  454. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_ecdf.json +95 -0
  455. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_joyplot.json +95 -0
  456. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_kde.json +93 -0
  457. scitex/plt/templates/research-master/scitex/vis/gallery/grid/imshow.json +95 -0
  458. scitex/plt/templates/research-master/scitex/vis/gallery/grid/matshow.json +95 -0
  459. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_conf_mat.json +83 -0
  460. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_heatmap.json +92 -0
  461. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_image.json +121 -0
  462. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_imshow.json +84 -0
  463. scitex/plt/templates/research-master/scitex/vis/gallery/line/plot.json +110 -0
  464. scitex/plt/templates/research-master/scitex/vis/gallery/line/step.json +92 -0
  465. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_line.json +95 -0
  466. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_shaded_line.json +96 -0
  467. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/hexbin.json +95 -0
  468. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/scatter.json +95 -0
  469. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stem.json +92 -0
  470. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stx_scatter.json +84 -0
  471. scitex/plt/templates/research-master/scitex/vis/gallery/special/pie.json +94 -0
  472. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_raster.json +109 -0
  473. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_rectangle.json +108 -0
  474. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/errorbar.json +93 -0
  475. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_errorbar.json +84 -0
  476. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_ci.json +96 -0
  477. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_std.json +96 -0
  478. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_median_iqr.json +96 -0
  479. scitex/plt/templates/research-master/scitex/vis/gallery/vector/quiver.json +99 -0
  480. scitex/plt/templates/research-master/scitex/vis/gallery/vector/streamplot.json +100 -0
  481. scitex/plt/utils/__init__.py +29 -2
  482. scitex/plt/utils/_close.py +8 -3
  483. scitex/plt/utils/_collect_figure_metadata.py +3031 -265
  484. scitex/plt/utils/_colorbar.py +15 -17
  485. scitex/plt/utils/_configure_mpl.py +22 -14
  486. scitex/plt/utils/_crop.py +60 -27
  487. scitex/plt/utils/_csv_column_naming.py +123 -72
  488. scitex/plt/utils/_dimension_viewer.py +7 -19
  489. scitex/plt/utils/_figure_from_axes_mm.py +70 -16
  490. scitex/plt/utils/_figure_mm.py +3 -2
  491. scitex/plt/utils/_get_actual_font.py +5 -4
  492. scitex/plt/utils/_histogram_utils.py +52 -48
  493. scitex/plt/utils/_is_valid_axis.py +19 -13
  494. scitex/plt/utils/_mk_colorbar.py +3 -3
  495. scitex/plt/utils/_scientific_captions.py +202 -139
  496. scitex/plt/utils/_scitex_config.py +98 -98
  497. scitex/plt/utils/_units.py +0 -0
  498. scitex/plt/utils/metadata/__init__.py +36 -0
  499. scitex/plt/utils/metadata/_artist_extraction.py +119 -0
  500. scitex/plt/utils/metadata/_axes_metadata.py +93 -0
  501. scitex/plt/utils/metadata/_collection_artists.py +292 -0
  502. scitex/plt/utils/metadata/_core.py +208 -0
  503. scitex/plt/utils/metadata/_csv_column_extraction.py +186 -0
  504. scitex/plt/utils/metadata/_csv_hash.py +115 -0
  505. scitex/plt/utils/metadata/_csv_verification.py +95 -0
  506. scitex/plt/utils/metadata/_data_linkage.py +263 -0
  507. scitex/plt/utils/metadata/_dimensions.py +239 -0
  508. scitex/plt/utils/metadata/_figure_metadata.py +58 -0
  509. scitex/plt/utils/metadata/_image_text_artists.py +168 -0
  510. scitex/plt/utils/metadata/_label_parsing.py +82 -0
  511. scitex/plt/utils/metadata/_legend_extraction.py +120 -0
  512. scitex/plt/utils/metadata/_line_artists.py +367 -0
  513. scitex/plt/utils/metadata/_line_semantic_handling.py +173 -0
  514. scitex/plt/utils/metadata/_patch_artists.py +211 -0
  515. scitex/plt/utils/metadata/_plot_content.py +26 -0
  516. scitex/plt/utils/metadata/_plot_type_detection.py +184 -0
  517. scitex/plt/utils/metadata/_precision.py +134 -0
  518. scitex/plt/utils/metadata/_precision_config.py +68 -0
  519. scitex/plt/utils/metadata/_precision_sections.py +211 -0
  520. scitex/plt/utils/metadata/_recipe_extraction.py +267 -0
  521. scitex/plt/utils/metadata/_style_parsing.py +174 -0
  522. scitex/repro/_RandomStateManager.py +33 -38
  523. scitex/repro/__init__.py +16 -7
  524. scitex/repro/_gen_ID.py +7 -9
  525. scitex/repro/_gen_timestamp.py +7 -6
  526. scitex/repro/_hash_array.py +8 -12
  527. scitex/reproduce/__init__.py +1 -1
  528. scitex/resource/_get_processor_usages.py +3 -1
  529. scitex/resource/_log_processor_usages.py +3 -1
  530. scitex/rng/__init__.py +1 -1
  531. scitex/schema/README.md +178 -0
  532. scitex/schema/__init__.py +144 -0
  533. scitex/schema/_canvas.py +444 -0
  534. scitex/schema/_stats.py +762 -0
  535. scitex/schema/_validation.py +590 -0
  536. scitex/scholar/.legacy/Scholar.py +5 -12
  537. scitex/scholar/.legacy/_Scholar.py +66 -99
  538. scitex/scholar/.legacy/_ScholarAPI.py +75 -66
  539. scitex/scholar/.legacy/_tmp/search_engine/_BaseSearchEngine.py +3 -3
  540. scitex/scholar/.legacy/_tmp/search_engine/_UnifiedSearcher.py +4 -9
  541. scitex/scholar/.legacy/_tmp/search_engine/__init__.py +14 -21
  542. scitex/scholar/.legacy/_tmp/search_engine/local/_LocalSearchEngine.py +40 -37
  543. scitex/scholar/.legacy/_tmp/search_engine/local/_VectorSearchEngine.py +31 -28
  544. scitex/scholar/.legacy/_tmp/search_engine/web/_ArxivSearchEngine.py +74 -65
  545. scitex/scholar/.legacy/_tmp/search_engine/web/_CrossRefSearchEngine.py +122 -116
  546. scitex/scholar/.legacy/_tmp/search_engine/web/_GoogleScholarSearchEngine.py +65 -59
  547. scitex/scholar/.legacy/_tmp/search_engine/web/_PubMedSearchEngine.py +121 -107
  548. scitex/scholar/.legacy/_tmp/search_engine/web/_SemanticScholarSearchEngine.py +5 -12
  549. scitex/scholar/.legacy/database/_DatabaseEntry.py +49 -45
  550. scitex/scholar/.legacy/database/_DatabaseIndex.py +131 -94
  551. scitex/scholar/.legacy/database/_LibraryManager.py +65 -63
  552. scitex/scholar/.legacy/database/_PaperDatabase.py +138 -124
  553. scitex/scholar/.legacy/database/_ScholarDatabaseIntegration.py +14 -36
  554. scitex/scholar/.legacy/database/_StorageIntegratedDB.py +192 -156
  555. scitex/scholar/.legacy/database/_ZoteroCompatibleDB.py +300 -237
  556. scitex/scholar/.legacy/database/__init__.py +2 -1
  557. scitex/scholar/.legacy/database/manage.py +92 -84
  558. scitex/scholar/.legacy/lookup/_LookupIndex.py +157 -101
  559. scitex/scholar/.legacy/lookup/__init__.py +2 -1
  560. scitex/scholar/.legacy/metadata/doi/batch/_MetadataHandlerForBatchDOIResolution.py +4 -9
  561. scitex/scholar/.legacy/metadata/doi/batch/_ProgressManagerForBatchDOIResolution.py +10 -23
  562. scitex/scholar/.legacy/metadata/doi/batch/_SourceStatsManagerForBatchDOIResolution.py +4 -9
  563. scitex/scholar/.legacy/metadata/doi/batch/__init__.py +3 -1
  564. scitex/scholar/.legacy/metadata/doi/resolvers/_BatchDOIResolver.py +10 -25
  565. scitex/scholar/.legacy/metadata/doi/resolvers/_BibTeXDOIResolver.py +19 -49
  566. scitex/scholar/.legacy/metadata/doi/resolvers/_DOIResolver.py +1 -0
  567. scitex/scholar/.legacy/metadata/doi/resolvers/_SingleDOIResolver.py +8 -20
  568. scitex/scholar/.legacy/metadata/doi/sources/.combined-SemanticScholarSource/_SemanticScholarSource.py +37 -35
  569. scitex/scholar/.legacy/metadata/doi/sources/.combined-SemanticScholarSource/_SemanticScholarSourceEnhanced.py +49 -37
  570. scitex/scholar/.legacy/metadata/doi/sources/_ArXivSource.py +11 -30
  571. scitex/scholar/.legacy/metadata/doi/sources/_BaseDOISource.py +19 -47
  572. scitex/scholar/.legacy/metadata/doi/sources/_CrossRefLocalSource.py +1 -0
  573. scitex/scholar/.legacy/metadata/doi/sources/_CrossRefSource.py +12 -33
  574. scitex/scholar/.legacy/metadata/doi/sources/_OpenAlexSource.py +8 -20
  575. scitex/scholar/.legacy/metadata/doi/sources/_PubMedSource.py +10 -27
  576. scitex/scholar/.legacy/metadata/doi/sources/_SemanticScholarSource.py +11 -29
  577. scitex/scholar/.legacy/metadata/doi/sources/_SourceManager.py +8 -21
  578. scitex/scholar/.legacy/metadata/doi/sources/_SourceResolutionStrategy.py +24 -55
  579. scitex/scholar/.legacy/metadata/doi/sources/_SourceRotationManager.py +8 -21
  580. scitex/scholar/.legacy/metadata/doi/sources/_URLDOISource.py +9 -16
  581. scitex/scholar/.legacy/metadata/doi/sources/_UnifiedSource.py +8 -22
  582. scitex/scholar/.legacy/metadata/doi/sources/__init__.py +1 -0
  583. scitex/scholar/.legacy/metadata/doi/utils/_PubMedConverter.py +4 -8
  584. scitex/scholar/.legacy/metadata/doi/utils/_RateLimitHandler.py +17 -43
  585. scitex/scholar/.legacy/metadata/doi/utils/_TextNormalizer.py +8 -18
  586. scitex/scholar/.legacy/metadata/doi/utils/_URLDOIExtractor.py +4 -8
  587. scitex/scholar/.legacy/metadata/doi/utils/__init__.py +1 -0
  588. scitex/scholar/.legacy/metadata/doi/utils/_to_complete_metadata_structure.py +1 -0
  589. scitex/scholar/.legacy/metadata/enrichment/_LibraryEnricher.py +2 -3
  590. scitex/scholar/.legacy/metadata/enrichment/enrichers/_ImpactFactorEnricher.py +6 -12
  591. scitex/scholar/.legacy/metadata/enrichment/enrichers/_SmartEnricher.py +5 -10
  592. scitex/scholar/.legacy/metadata/enrichment/sources/_UnifiedMetadataSource.py +4 -5
  593. scitex/scholar/.legacy/metadata/query_to_full_meta_json.py +8 -12
  594. scitex/scholar/.legacy/metadata/urls/_URLMetadataHandler.py +3 -3
  595. scitex/scholar/.legacy/metadata/urls/_ZoteroTranslatorRunner.py +15 -21
  596. scitex/scholar/.legacy/metadata/urls/__init__.py +3 -3
  597. scitex/scholar/.legacy/metadata/urls/_finder.py +4 -6
  598. scitex/scholar/.legacy/metadata/urls/_handler.py +7 -15
  599. scitex/scholar/.legacy/metadata/urls/_resolver.py +6 -12
  600. scitex/scholar/.legacy/search/_Embedder.py +74 -69
  601. scitex/scholar/.legacy/search/_SemanticSearch.py +91 -90
  602. scitex/scholar/.legacy/search/_SemanticSearchEngine.py +104 -109
  603. scitex/scholar/.legacy/search/_UnifiedSearcher.py +530 -471
  604. scitex/scholar/.legacy/search/_VectorDatabase.py +111 -92
  605. scitex/scholar/.legacy/search/__init__.py +1 -0
  606. scitex/scholar/.legacy/storage/_EnhancedStorageManager.py +182 -154
  607. scitex/scholar/.legacy/storage/__init__.py +2 -1
  608. scitex/scholar/__init__.py +0 -2
  609. scitex/scholar/__main__.py +1 -3
  610. scitex/scholar/auth/ScholarAuthManager.py +13 -36
  611. scitex/scholar/auth/core/AuthenticationGateway.py +15 -29
  612. scitex/scholar/auth/core/BrowserAuthenticator.py +22 -57
  613. scitex/scholar/auth/core/StrategyResolver.py +10 -27
  614. scitex/scholar/auth/core/__init__.py +5 -1
  615. scitex/scholar/auth/gateway/_OpenURLLinkFinder.py +11 -21
  616. scitex/scholar/auth/gateway/_OpenURLResolver.py +10 -18
  617. scitex/scholar/auth/gateway/_resolve_functions.py +3 -3
  618. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -0
  619. scitex/scholar/auth/providers/EZProxyAuthenticator.py +7 -14
  620. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +29 -57
  621. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +87 -73
  622. scitex/scholar/auth/session/AuthCacheManager.py +12 -22
  623. scitex/scholar/auth/session/SessionManager.py +4 -6
  624. scitex/scholar/auth/sso/BaseSSOAutomator.py +13 -19
  625. scitex/scholar/auth/sso/OpenAthensSSOAutomator.py +16 -45
  626. scitex/scholar/auth/sso/SSOAutomator.py +8 -15
  627. scitex/scholar/auth/sso/UniversityOfMelbourneSSOAutomator.py +13 -23
  628. scitex/scholar/browser/ScholarBrowserManager.py +31 -56
  629. scitex/scholar/browser/__init__.py +1 -0
  630. scitex/scholar/browser/utils/click_and_wait.py +3 -4
  631. scitex/scholar/browser/utils/close_unwanted_pages.py +4 -7
  632. scitex/scholar/browser/utils/wait_redirects.py +15 -40
  633. scitex/scholar/citation_graph/__init__.py +0 -0
  634. scitex/scholar/citation_graph/builder.py +3 -7
  635. scitex/scholar/citation_graph/database.py +4 -11
  636. scitex/scholar/citation_graph/example.py +5 -10
  637. scitex/scholar/citation_graph/models.py +0 -0
  638. scitex/scholar/cli/_url_utils.py +1 -1
  639. scitex/scholar/cli/chrome.py +5 -3
  640. scitex/scholar/cli/download_pdf.py +13 -14
  641. scitex/scholar/cli/handlers/bibtex_handler.py +4 -12
  642. scitex/scholar/cli/handlers/doi_handler.py +1 -3
  643. scitex/scholar/cli/handlers/project_handler.py +6 -20
  644. scitex/scholar/cli/open_browser.py +41 -39
  645. scitex/scholar/cli/open_browser_auto.py +31 -39
  646. scitex/scholar/cli/open_browser_monitored.py +27 -24
  647. scitex/scholar/config/ScholarConfig.py +5 -8
  648. scitex/scholar/config/__init__.py +1 -0
  649. scitex/scholar/config/core/_CascadeConfig.py +3 -3
  650. scitex/scholar/config/core/_PathManager.py +16 -28
  651. scitex/scholar/core/Paper.py +79 -78
  652. scitex/scholar/core/Papers.py +16 -27
  653. scitex/scholar/core/Scholar.py +98 -229
  654. scitex/scholar/core/journal_normalizer.py +52 -49
  655. scitex/scholar/core/oa_cache.py +27 -23
  656. scitex/scholar/core/open_access.py +17 -8
  657. scitex/scholar/docs/template.py +4 -3
  658. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/clf_svm.py +0 -0
  659. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/download.py +0 -0
  660. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_conf_mat.py +0 -0
  661. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_digits.py +0 -0
  662. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_umap_space.py +0 -0
  663. scitex/scholar/examples/00_config.py +10 -9
  664. scitex/scholar/examples/01_auth.py +3 -0
  665. scitex/scholar/examples/02_browser.py +14 -10
  666. scitex/scholar/examples/03_01-engine.py +3 -0
  667. scitex/scholar/examples/03_02-engine-for-bibtex.py +4 -3
  668. scitex/scholar/examples/04_01-url.py +9 -9
  669. scitex/scholar/examples/04_02-url-for-bibtex.py +7 -3
  670. scitex/scholar/examples/04_02-url-for-dois.py +87 -97
  671. scitex/scholar/examples/05_download_pdf.py +10 -4
  672. scitex/scholar/examples/06_find_and_download.py +6 -6
  673. scitex/scholar/examples/06_parse_bibtex.py +17 -17
  674. scitex/scholar/examples/07_storage_integration.py +6 -9
  675. scitex/scholar/examples/99_fullpipeline-for-bibtex.py +14 -15
  676. scitex/scholar/examples/99_fullpipeline-for-one-entry.py +31 -23
  677. scitex/scholar/examples/99_maintenance.py +3 -0
  678. scitex/scholar/examples/dev.py +2 -3
  679. scitex/scholar/examples/zotero_integration.py +11 -18
  680. scitex/scholar/impact_factor/ImpactFactorEngine.py +7 -9
  681. scitex/scholar/impact_factor/estimation/__init__.py +4 -4
  682. scitex/scholar/impact_factor/estimation/core/__init__.py +3 -7
  683. scitex/scholar/impact_factor/estimation/core/cache_manager.py +223 -211
  684. scitex/scholar/impact_factor/estimation/core/calculator.py +165 -131
  685. scitex/scholar/impact_factor/estimation/core/journal_matcher.py +217 -172
  686. scitex/scholar/impact_factor/jcr/ImpactFactorJCREngine.py +6 -14
  687. scitex/scholar/impact_factor/jcr/build_database.py +4 -3
  688. scitex/scholar/integration/base.py +9 -17
  689. scitex/scholar/integration/mendeley/exporter.py +2 -4
  690. scitex/scholar/integration/mendeley/importer.py +3 -3
  691. scitex/scholar/integration/mendeley/linker.py +3 -3
  692. scitex/scholar/integration/mendeley/mapper.py +9 -6
  693. scitex/scholar/integration/zotero/__main__.py +26 -43
  694. scitex/scholar/integration/zotero/exporter.py +15 -11
  695. scitex/scholar/integration/zotero/importer.py +12 -10
  696. scitex/scholar/integration/zotero/linker.py +8 -12
  697. scitex/scholar/integration/zotero/mapper.py +17 -12
  698. scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSource.py +37 -35
  699. scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSourceEnhanced.py +47 -35
  700. scitex/scholar/metadata_engines/ScholarEngine.py +21 -43
  701. scitex/scholar/metadata_engines/__init__.py +1 -0
  702. scitex/scholar/metadata_engines/individual/ArXivEngine.py +15 -37
  703. scitex/scholar/metadata_engines/individual/CrossRefEngine.py +15 -42
  704. scitex/scholar/metadata_engines/individual/CrossRefLocalEngine.py +24 -45
  705. scitex/scholar/metadata_engines/individual/OpenAlexEngine.py +11 -21
  706. scitex/scholar/metadata_engines/individual/PubMedEngine.py +10 -27
  707. scitex/scholar/metadata_engines/individual/SemanticScholarEngine.py +28 -35
  708. scitex/scholar/metadata_engines/individual/URLDOIEngine.py +11 -22
  709. scitex/scholar/metadata_engines/individual/_BaseDOIEngine.py +20 -49
  710. scitex/scholar/metadata_engines/utils/_PubMedConverter.py +4 -8
  711. scitex/scholar/metadata_engines/utils/_URLDOIExtractor.py +5 -10
  712. scitex/scholar/metadata_engines/utils/__init__.py +2 -0
  713. scitex/scholar/metadata_engines/utils/_metadata2bibtex.py +3 -0
  714. scitex/scholar/metadata_engines/utils/_standardize_metadata.py +2 -3
  715. scitex/scholar/pdf_download/ScholarPDFDownloader.py +25 -37
  716. scitex/scholar/pdf_download/strategies/chrome_pdf_viewer.py +11 -19
  717. scitex/scholar/pdf_download/strategies/direct_download.py +5 -9
  718. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +3 -3
  719. scitex/scholar/pdf_download/strategies/manual_download_utils.py +6 -13
  720. scitex/scholar/pdf_download/strategies/open_access_download.py +49 -31
  721. scitex/scholar/pdf_download/strategies/response_body.py +8 -19
  722. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +9 -18
  723. scitex/scholar/pipelines/ScholarPipelineMetadataParallel.py +25 -26
  724. scitex/scholar/pipelines/ScholarPipelineMetadataSingle.py +62 -23
  725. scitex/scholar/pipelines/ScholarPipelineParallel.py +13 -30
  726. scitex/scholar/pipelines/ScholarPipelineSearchParallel.py +299 -220
  727. scitex/scholar/pipelines/ScholarPipelineSearchSingle.py +202 -165
  728. scitex/scholar/pipelines/ScholarPipelineSingle.py +25 -51
  729. scitex/scholar/pipelines/SearchQueryParser.py +55 -55
  730. scitex/scholar/search_engines/ScholarSearchEngine.py +31 -27
  731. scitex/scholar/search_engines/_BaseSearchEngine.py +20 -23
  732. scitex/scholar/search_engines/individual/ArXivSearchEngine.py +53 -35
  733. scitex/scholar/search_engines/individual/CrossRefSearchEngine.py +47 -40
  734. scitex/scholar/search_engines/individual/OpenAlexSearchEngine.py +55 -50
  735. scitex/scholar/search_engines/individual/PubMedSearchEngine.py +8 -10
  736. scitex/scholar/search_engines/individual/SemanticScholarSearchEngine.py +55 -49
  737. scitex/scholar/storage/BibTeXHandler.py +150 -95
  738. scitex/scholar/storage/PaperIO.py +3 -6
  739. scitex/scholar/storage/ScholarLibrary.py +70 -49
  740. scitex/scholar/storage/_DeduplicationManager.py +52 -25
  741. scitex/scholar/storage/_LibraryCacheManager.py +19 -46
  742. scitex/scholar/storage/_LibraryManager.py +65 -175
  743. scitex/scholar/url_finder/ScholarURLFinder.py +9 -25
  744. scitex/scholar/url_finder/strategies/find_pdf_urls_by_direct_links.py +1 -1
  745. scitex/scholar/url_finder/strategies/find_pdf_urls_by_href.py +6 -10
  746. scitex/scholar/url_finder/strategies/find_pdf_urls_by_navigation.py +4 -6
  747. scitex/scholar/url_finder/strategies/find_pdf_urls_by_publisher_patterns.py +8 -15
  748. scitex/scholar/url_finder/strategies/find_pdf_urls_by_zotero_translators.py +3 -3
  749. scitex/scholar/url_finder/strategies/find_supplementary_urls_by_href.py +3 -3
  750. scitex/scholar/url_finder/translators/core/patterns.py +6 -4
  751. scitex/scholar/url_finder/translators/core/registry.py +6 -9
  752. scitex/scholar/url_finder/translators/individual/BOFiP_Impots.py +60 -52
  753. scitex/scholar/url_finder/translators/individual/Baidu_Scholar.py +54 -62
  754. scitex/scholar/url_finder/translators/individual/Bangkok_Post.py +38 -44
  755. scitex/scholar/url_finder/translators/individual/Baruch_Foundation.py +43 -47
  756. scitex/scholar/url_finder/translators/individual/Beobachter.py +46 -50
  757. scitex/scholar/url_finder/translators/individual/Bezneng_Gajit.py +37 -41
  758. scitex/scholar/url_finder/translators/individual/BibLaTeX.py +59 -52
  759. scitex/scholar/url_finder/translators/individual/BibTeX.py +83 -79
  760. scitex/scholar/url_finder/translators/individual/Biblio_com.py +48 -51
  761. scitex/scholar/url_finder/translators/individual/Bibliontology_RDF.py +58 -56
  762. scitex/scholar/url_finder/translators/individual/Camara_Brasileira_do_Livro_ISBN.py +102 -99
  763. scitex/scholar/url_finder/translators/individual/CanLII.py +49 -43
  764. scitex/scholar/url_finder/translators/individual/Canada_com.py +36 -40
  765. scitex/scholar/url_finder/translators/individual/Canadian_Letters_and_Images.py +43 -43
  766. scitex/scholar/url_finder/translators/individual/Canadiana_ca.py +77 -66
  767. scitex/scholar/url_finder/translators/individual/Cascadilla_Proceedings_Project.py +68 -62
  768. scitex/scholar/url_finder/translators/individual/Central_and_Eastern_European_Online_Library_Journals.py +60 -60
  769. scitex/scholar/url_finder/translators/individual/Champlain_Society_Collection.py +63 -61
  770. scitex/scholar/url_finder/translators/individual/Chicago_Journal_of_Theoretical_Computer_Science.py +74 -58
  771. scitex/scholar/url_finder/translators/individual/Christian_Science_Monitor.py +32 -38
  772. scitex/scholar/url_finder/translators/individual/Columbia_University_Press.py +51 -47
  773. scitex/scholar/url_finder/translators/individual/Common_Place.py +66 -57
  774. scitex/scholar/url_finder/translators/individual/Cornell_LII.py +66 -62
  775. scitex/scholar/url_finder/translators/individual/Cornell_University_Press.py +38 -45
  776. scitex/scholar/url_finder/translators/individual/CourtListener.py +52 -56
  777. scitex/scholar/url_finder/translators/individual/DAI_Zenon.py +53 -54
  778. scitex/scholar/url_finder/translators/individual/access_medicine.py +27 -33
  779. scitex/scholar/url_finder/translators/individual/acm.py +1 -1
  780. scitex/scholar/url_finder/translators/individual/acm_digital_library.py +93 -63
  781. scitex/scholar/url_finder/translators/individual/airiti.py +3 -1
  782. scitex/scholar/url_finder/translators/individual/aosic.py +3 -1
  783. scitex/scholar/url_finder/translators/individual/archive_ouverte_aosic.py +3 -1
  784. scitex/scholar/url_finder/translators/individual/archive_ouverte_en_sciences_de_l_information_et_de_la_communication___aosic_.py +6 -2
  785. scitex/scholar/url_finder/translators/individual/artforum.py +35 -27
  786. scitex/scholar/url_finder/translators/individual/arxiv.py +1 -1
  787. scitex/scholar/url_finder/translators/individual/arxiv_org.py +8 -4
  788. scitex/scholar/url_finder/translators/individual/atlanta_journal_constitution.py +22 -18
  789. scitex/scholar/url_finder/translators/individual/atypon_journals.py +19 -11
  790. scitex/scholar/url_finder/translators/individual/austlii_and_nzlii.py +48 -44
  791. scitex/scholar/url_finder/translators/individual/australian_dictionary_of_biography.py +21 -17
  792. scitex/scholar/url_finder/translators/individual/bailii.py +22 -19
  793. scitex/scholar/url_finder/translators/individual/bbc.py +46 -42
  794. scitex/scholar/url_finder/translators/individual/bbc_genome.py +37 -25
  795. scitex/scholar/url_finder/translators/individual/biblioteca_nacional_de_maestros.py +24 -20
  796. scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationale_quebec_pistard.py +42 -43
  797. scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationales_quebec.py +87 -81
  798. scitex/scholar/url_finder/translators/individual/bibliotheque_nationale_france.py +39 -37
  799. scitex/scholar/url_finder/translators/individual/bibsys.py +32 -28
  800. scitex/scholar/url_finder/translators/individual/bioconductor.py +58 -52
  801. scitex/scholar/url_finder/translators/individual/biomed_central.py +23 -15
  802. scitex/scholar/url_finder/translators/individual/biorxiv.py +26 -13
  803. scitex/scholar/url_finder/translators/individual/blogger.py +39 -43
  804. scitex/scholar/url_finder/translators/individual/bloomberg.py +48 -52
  805. scitex/scholar/url_finder/translators/individual/bloomsbury_food_library.py +37 -37
  806. scitex/scholar/url_finder/translators/individual/bluesky.py +30 -28
  807. scitex/scholar/url_finder/translators/individual/bnf_isbn.py +1 -1
  808. scitex/scholar/url_finder/translators/individual/bocc.py +66 -60
  809. scitex/scholar/url_finder/translators/individual/boe.py +52 -52
  810. scitex/scholar/url_finder/translators/individual/brill.py +3 -1
  811. scitex/scholar/url_finder/translators/individual/business_standard.py +36 -38
  812. scitex/scholar/url_finder/translators/individual/cabi_cab_abstracts.py +39 -41
  813. scitex/scholar/url_finder/translators/individual/cambridge.py +3 -1
  814. scitex/scholar/url_finder/translators/individual/cambridge_core.py +30 -24
  815. scitex/scholar/url_finder/translators/individual/caod.py +50 -46
  816. scitex/scholar/url_finder/translators/individual/cbc.py +91 -67
  817. scitex/scholar/url_finder/translators/individual/ccfr_bnf.py +49 -53
  818. scitex/scholar/url_finder/translators/individual/cia_world_factbook.py +43 -33
  819. scitex/scholar/url_finder/translators/individual/crossref_rest.py +208 -174
  820. scitex/scholar/url_finder/translators/individual/current_affairs.py +29 -35
  821. scitex/scholar/url_finder/translators/individual/dabi.py +70 -66
  822. scitex/scholar/url_finder/translators/individual/dagens_nyheter.py +3 -1
  823. scitex/scholar/url_finder/translators/individual/dagstuhl.py +10 -15
  824. scitex/scholar/url_finder/translators/individual/dar_almandumah.py +13 -9
  825. scitex/scholar/url_finder/translators/individual/dart_europe.py +19 -22
  826. scitex/scholar/url_finder/translators/individual/data_gov.py +2 -2
  827. scitex/scholar/url_finder/translators/individual/databrary.py +27 -28
  828. scitex/scholar/url_finder/translators/individual/datacite_json.py +152 -137
  829. scitex/scholar/url_finder/translators/individual/dataverse.py +68 -64
  830. scitex/scholar/url_finder/translators/individual/daum_news.py +38 -38
  831. scitex/scholar/url_finder/translators/individual/dblp.py +4 -8
  832. scitex/scholar/url_finder/translators/individual/dblp_computer_science_bibliography.py +8 -3
  833. scitex/scholar/url_finder/translators/individual/dbpia.py +5 -3
  834. scitex/scholar/url_finder/translators/individual/defense_technical_information_center.py +30 -28
  835. scitex/scholar/url_finder/translators/individual/delpher.py +102 -79
  836. scitex/scholar/url_finder/translators/individual/demographic_research.py +35 -31
  837. scitex/scholar/url_finder/translators/individual/denik_cz.py +58 -54
  838. scitex/scholar/url_finder/translators/individual/depatisnet.py +7 -10
  839. scitex/scholar/url_finder/translators/individual/der_freitag.py +81 -66
  840. scitex/scholar/url_finder/translators/individual/der_spiegel.py +56 -54
  841. scitex/scholar/url_finder/translators/individual/digibib_net.py +3 -1
  842. scitex/scholar/url_finder/translators/individual/digizeitschriften.py +3 -1
  843. scitex/scholar/url_finder/translators/individual/dpla.py +13 -14
  844. scitex/scholar/url_finder/translators/individual/dspace.py +2 -2
  845. scitex/scholar/url_finder/translators/individual/ebrary.py +3 -1
  846. scitex/scholar/url_finder/translators/individual/ebscohost.py +3 -1
  847. scitex/scholar/url_finder/translators/individual/electronic_colloquium_on_computational_complexity.py +3 -1
  848. scitex/scholar/url_finder/translators/individual/elife.py +3 -1
  849. scitex/scholar/url_finder/translators/individual/elsevier_health_journals.py +3 -1
  850. scitex/scholar/url_finder/translators/individual/emerald.py +3 -1
  851. scitex/scholar/url_finder/translators/individual/emerald_insight.py +3 -1
  852. scitex/scholar/url_finder/translators/individual/epicurious.py +3 -1
  853. scitex/scholar/url_finder/translators/individual/eurogamerusgamer.py +3 -1
  854. scitex/scholar/url_finder/translators/individual/fachportal_padagogik.py +3 -1
  855. scitex/scholar/url_finder/translators/individual/frontiers.py +1 -1
  856. scitex/scholar/url_finder/translators/individual/gale_databases.py +3 -1
  857. scitex/scholar/url_finder/translators/individual/gms_german_medical_science.py +6 -2
  858. scitex/scholar/url_finder/translators/individual/ieee_computer_society.py +6 -2
  859. scitex/scholar/url_finder/translators/individual/ieee_xplore.py +41 -35
  860. scitex/scholar/url_finder/translators/individual/inter_research_science_center.py +6 -2
  861. scitex/scholar/url_finder/translators/individual/jisc_historical_texts.py +3 -1
  862. scitex/scholar/url_finder/translators/individual/jstor.py +14 -12
  863. scitex/scholar/url_finder/translators/individual/korean_national_library.py +3 -1
  864. scitex/scholar/url_finder/translators/individual/la_times.py +3 -1
  865. scitex/scholar/url_finder/translators/individual/landesbibliographie_baden_wurttemberg.py +3 -1
  866. scitex/scholar/url_finder/translators/individual/legislative_insight.py +3 -1
  867. scitex/scholar/url_finder/translators/individual/libraries_tasmania.py +3 -1
  868. scitex/scholar/url_finder/translators/individual/library_catalog__koha_.py +3 -1
  869. scitex/scholar/url_finder/translators/individual/lingbuzz.py +2 -2
  870. scitex/scholar/url_finder/translators/individual/max_planck_institute_for_the_history_of_science_virtual_laboratory_library.py +3 -1
  871. scitex/scholar/url_finder/translators/individual/mdpi.py +12 -6
  872. scitex/scholar/url_finder/translators/individual/microbiology_society_journals.py +3 -1
  873. scitex/scholar/url_finder/translators/individual/midas_journals.py +3 -1
  874. scitex/scholar/url_finder/translators/individual/nagoya_university_opac.py +3 -1
  875. scitex/scholar/url_finder/translators/individual/nature_publishing_group.py +32 -19
  876. scitex/scholar/url_finder/translators/individual/ntsb_accident_reports.py +3 -1
  877. scitex/scholar/url_finder/translators/individual/openedition_journals.py +8 -4
  878. scitex/scholar/url_finder/translators/individual/orcid.py +16 -15
  879. scitex/scholar/url_finder/translators/individual/oxford.py +25 -19
  880. scitex/scholar/url_finder/translators/individual/oxford_dictionaries_premium.py +3 -1
  881. scitex/scholar/url_finder/translators/individual/ozon_ru.py +3 -1
  882. scitex/scholar/url_finder/translators/individual/plos.py +9 -12
  883. scitex/scholar/url_finder/translators/individual/polygon.py +3 -1
  884. scitex/scholar/url_finder/translators/individual/primo.py +3 -1
  885. scitex/scholar/url_finder/translators/individual/project_muse.py +3 -1
  886. scitex/scholar/url_finder/translators/individual/pubfactory_journals.py +3 -1
  887. scitex/scholar/url_finder/translators/individual/pubmed.py +71 -65
  888. scitex/scholar/url_finder/translators/individual/pubmed_central.py +8 -6
  889. scitex/scholar/url_finder/translators/individual/rechtspraak_nl.py +3 -1
  890. scitex/scholar/url_finder/translators/individual/sage_journals.py +25 -17
  891. scitex/scholar/url_finder/translators/individual/sciencedirect.py +36 -17
  892. scitex/scholar/url_finder/translators/individual/semantics_visual_library.py +3 -1
  893. scitex/scholar/url_finder/translators/individual/silverchair.py +70 -52
  894. scitex/scholar/url_finder/translators/individual/sora.py +3 -1
  895. scitex/scholar/url_finder/translators/individual/springer.py +15 -11
  896. scitex/scholar/url_finder/translators/individual/ssrn.py +3 -3
  897. scitex/scholar/url_finder/translators/individual/stanford_encyclopedia_of_philosophy.py +3 -1
  898. scitex/scholar/url_finder/translators/individual/superlib.py +3 -1
  899. scitex/scholar/url_finder/translators/individual/treesearch.py +3 -1
  900. scitex/scholar/url_finder/translators/individual/university_of_chicago_press_books.py +3 -1
  901. scitex/scholar/url_finder/translators/individual/vlex.py +3 -1
  902. scitex/scholar/url_finder/translators/individual/web_of_science.py +3 -1
  903. scitex/scholar/url_finder/translators/individual/web_of_science_nextgen.py +3 -1
  904. scitex/scholar/url_finder/translators/individual/wiley.py +31 -25
  905. scitex/scholar/url_finder/translators/individual/wilson_center_digital_archive.py +3 -1
  906. scitex/scholar/utils/bibtex/_parse_bibtex.py +3 -3
  907. scitex/scholar/utils/cleanup/_cleanup_scholar_processes.py +5 -9
  908. scitex/scholar/utils/text/_TextNormalizer.py +249 -176
  909. scitex/scholar/utils/validation/DOIValidator.py +31 -28
  910. scitex/scholar/utils/validation/__init__.py +0 -0
  911. scitex/scholar/utils/validation/validate_library_dois.py +61 -57
  912. scitex/scholar/zotero/__init__.py +1 -1
  913. scitex/security/cli.py +7 -20
  914. scitex/security/github.py +45 -32
  915. scitex/session/__init__.py +8 -9
  916. scitex/session/_decorator.py +49 -42
  917. scitex/session/_lifecycle.py +39 -39
  918. scitex/session/_manager.py +24 -20
  919. scitex/sh/__init__.py +4 -3
  920. scitex/sh/_execute.py +10 -7
  921. scitex/sh/_security.py +3 -3
  922. scitex/sh/_types.py +2 -3
  923. scitex/stats/__init__.py +57 -6
  924. scitex/stats/_schema.py +42 -569
  925. scitex/stats/auto/__init__.py +188 -0
  926. scitex/stats/auto/_context.py +331 -0
  927. scitex/stats/auto/_formatting.py +679 -0
  928. scitex/stats/auto/_rules.py +901 -0
  929. scitex/stats/auto/_selector.py +554 -0
  930. scitex/stats/auto/_styles.py +721 -0
  931. scitex/stats/correct/__init__.py +4 -4
  932. scitex/stats/correct/_correct_bonferroni.py +43 -34
  933. scitex/stats/correct/_correct_fdr.py +14 -40
  934. scitex/stats/correct/_correct_fdr_.py +39 -46
  935. scitex/stats/correct/_correct_holm.py +14 -32
  936. scitex/stats/correct/_correct_sidak.py +36 -21
  937. scitex/stats/descriptive/_circular.py +20 -21
  938. scitex/stats/descriptive/_describe.py +19 -5
  939. scitex/stats/descriptive/_nan.py +5 -7
  940. scitex/stats/descriptive/_real.py +4 -3
  941. scitex/stats/effect_sizes/__init__.py +10 -11
  942. scitex/stats/effect_sizes/_cliffs_delta.py +35 -32
  943. scitex/stats/effect_sizes/_cohens_d.py +30 -31
  944. scitex/stats/effect_sizes/_epsilon_squared.py +19 -22
  945. scitex/stats/effect_sizes/_eta_squared.py +23 -27
  946. scitex/stats/effect_sizes/_prob_superiority.py +18 -21
  947. scitex/stats/posthoc/__init__.py +3 -3
  948. scitex/stats/posthoc/_dunnett.py +75 -55
  949. scitex/stats/posthoc/_games_howell.py +61 -43
  950. scitex/stats/posthoc/_tukey_hsd.py +42 -34
  951. scitex/stats/power/__init__.py +2 -2
  952. scitex/stats/power/_power.py +56 -56
  953. scitex/stats/tests/__init__.py +1 -1
  954. scitex/stats/tests/correlation/__init__.py +1 -1
  955. scitex/stats/tests/correlation/_test_pearson.py +28 -38
  956. scitex/stats/utils/__init__.py +14 -17
  957. scitex/stats/utils/_effect_size.py +85 -78
  958. scitex/stats/utils/_formatters.py +49 -43
  959. scitex/stats/utils/_normalizers.py +7 -14
  960. scitex/stats/utils/_power.py +56 -56
  961. scitex/str/__init__.py +1 -0
  962. scitex/str/_clean_path.py +3 -3
  963. scitex/str/_factor_out_digits.py +86 -58
  964. scitex/str/_format_plot_text.py +180 -111
  965. scitex/str/_latex.py +19 -19
  966. scitex/str/_latex_fallback.py +9 -10
  967. scitex/str/_parse.py +3 -6
  968. scitex/str/_print_debug.py +13 -13
  969. scitex/str/_printc.py +2 -0
  970. scitex/str/_search.py +3 -3
  971. scitex/template/.legacy/_clone_project.py +9 -13
  972. scitex/template/__init__.py +10 -2
  973. scitex/template/_clone_project.py +7 -2
  974. scitex/template/_copy.py +1 -0
  975. scitex/template/_customize.py +3 -6
  976. scitex/template/_git_strategy.py +2 -3
  977. scitex/template/_rename.py +1 -0
  978. scitex/template/clone_pip_project.py +6 -7
  979. scitex/template/clone_research.py +7 -10
  980. scitex/template/clone_singularity.py +6 -7
  981. scitex/template/clone_writer_directory.py +6 -7
  982. scitex/tex/_preview.py +26 -11
  983. scitex/tex/_to_vec.py +10 -7
  984. scitex/torch/__init__.py +11 -1
  985. scitex/types/_ArrayLike.py +2 -0
  986. scitex/types/_is_listed_X.py +3 -3
  987. scitex/units.py +110 -77
  988. scitex/utils/_compress_hdf5.py +3 -3
  989. scitex/utils/_email.py +8 -4
  990. scitex/utils/_notify.py +14 -8
  991. scitex/utils/_search.py +6 -6
  992. scitex/utils/_verify_scitex_format.py +17 -42
  993. scitex/utils/_verify_scitex_format_v01.py +12 -34
  994. scitex/utils/template.py +4 -3
  995. scitex/vis/__init__.py +0 -0
  996. scitex/vis/backend/__init__.py +3 -3
  997. scitex/vis/backend/{export.py → _export.py} +1 -1
  998. scitex/vis/backend/{parser.py → _parser.py} +1 -3
  999. scitex/vis/backend/{render.py → _render.py} +1 -1
  1000. scitex/vis/canvas.py +15 -3
  1001. scitex/vis/editor/__init__.py +0 -0
  1002. scitex/vis/editor/_dearpygui_editor.py +450 -304
  1003. scitex/vis/editor/_defaults.py +114 -123
  1004. scitex/vis/editor/_edit.py +38 -26
  1005. scitex/vis/editor/_flask_editor.py +8 -8
  1006. scitex/vis/editor/_mpl_editor.py +63 -48
  1007. scitex/vis/editor/_qt_editor.py +210 -159
  1008. scitex/vis/editor/_tkinter_editor.py +146 -89
  1009. scitex/vis/editor/flask_editor/__init__.py +10 -10
  1010. scitex/vis/editor/flask_editor/_bbox.py +529 -0
  1011. scitex/vis/editor/flask_editor/{core.py → _core.py} +45 -29
  1012. scitex/vis/editor/flask_editor/_plotter.py +567 -0
  1013. scitex/vis/editor/flask_editor/_renderer.py +393 -0
  1014. scitex/vis/editor/flask_editor/{utils.py → _utils.py} +13 -14
  1015. scitex/vis/editor/flask_editor/templates/__init__.py +5 -5
  1016. scitex/vis/editor/flask_editor/templates/{html.py → _html.py} +234 -16
  1017. scitex/vis/editor/flask_editor/templates/_scripts.py +1261 -0
  1018. scitex/vis/editor/flask_editor/templates/{styles.py → _styles.py} +192 -2
  1019. scitex/vis/io/__init__.py +5 -5
  1020. scitex/vis/io/{canvas.py → _canvas.py} +8 -4
  1021. scitex/vis/io/{data.py → _data.py} +13 -9
  1022. scitex/vis/io/{directory.py → _directory.py} +7 -4
  1023. scitex/vis/io/{export.py → _export.py} +15 -12
  1024. scitex/vis/io/{load.py → _load.py} +1 -1
  1025. scitex/vis/io/{panel.py → _panel.py} +21 -13
  1026. scitex/vis/io/{save.py → _save.py} +0 -0
  1027. scitex/vis/model/__init__.py +7 -7
  1028. scitex/vis/model/{annotations.py → _annotations.py} +2 -4
  1029. scitex/vis/model/{axes.py → _axes.py} +1 -1
  1030. scitex/vis/model/{figure.py → _figure.py} +0 -0
  1031. scitex/vis/model/{guides.py → _guides.py} +1 -1
  1032. scitex/vis/model/{plot.py → _plot.py} +2 -4
  1033. scitex/vis/model/{plot_types.py → _plot_types.py} +0 -0
  1034. scitex/vis/model/{styles.py → _styles.py} +0 -0
  1035. scitex/vis/utils/__init__.py +2 -2
  1036. scitex/vis/utils/{defaults.py → _defaults.py} +1 -2
  1037. scitex/vis/utils/{validate.py → _validate.py} +3 -9
  1038. scitex/web/__init__.py +7 -1
  1039. scitex/web/_scraping.py +54 -38
  1040. scitex/web/_search_pubmed.py +30 -14
  1041. scitex/writer/.legacy/Writer_v01-refactored.py +4 -4
  1042. scitex/writer/.legacy/_compile.py +18 -28
  1043. scitex/writer/Writer.py +8 -21
  1044. scitex/writer/__init__.py +11 -11
  1045. scitex/writer/_clone_writer_project.py +2 -6
  1046. scitex/writer/_compile/__init__.py +1 -0
  1047. scitex/writer/_compile/_parser.py +1 -0
  1048. scitex/writer/_compile/_runner.py +35 -38
  1049. scitex/writer/_compile/_validator.py +1 -0
  1050. scitex/writer/_compile/manuscript.py +1 -0
  1051. scitex/writer/_compile/revision.py +1 -0
  1052. scitex/writer/_compile/supplementary.py +1 -0
  1053. scitex/writer/_compile_async.py +5 -12
  1054. scitex/writer/_project/__init__.py +1 -0
  1055. scitex/writer/_project/_create.py +10 -25
  1056. scitex/writer/_project/_trees.py +4 -9
  1057. scitex/writer/_project/_validate.py +2 -3
  1058. scitex/writer/_validate_tree_structures.py +7 -18
  1059. scitex/writer/dataclasses/__init__.py +8 -10
  1060. scitex/writer/dataclasses/config/_CONSTANTS.py +2 -3
  1061. scitex/writer/dataclasses/config/_WriterConfig.py +4 -9
  1062. scitex/writer/dataclasses/contents/_ManuscriptContents.py +14 -25
  1063. scitex/writer/dataclasses/contents/_RevisionContents.py +21 -16
  1064. scitex/writer/dataclasses/contents/_SupplementaryContents.py +21 -24
  1065. scitex/writer/dataclasses/core/_Document.py +2 -3
  1066. scitex/writer/dataclasses/core/_DocumentSection.py +8 -23
  1067. scitex/writer/dataclasses/results/_CompilationResult.py +2 -3
  1068. scitex/writer/dataclasses/results/_LaTeXIssue.py +3 -6
  1069. scitex/writer/dataclasses/results/_SaveSectionsResponse.py +20 -9
  1070. scitex/writer/dataclasses/results/_SectionReadResponse.py +24 -10
  1071. scitex/writer/dataclasses/tree/_ConfigTree.py +7 -4
  1072. scitex/writer/dataclasses/tree/_ManuscriptTree.py +10 -13
  1073. scitex/writer/dataclasses/tree/_RevisionTree.py +16 -17
  1074. scitex/writer/dataclasses/tree/_ScriptsTree.py +10 -5
  1075. scitex/writer/dataclasses/tree/_SharedTree.py +10 -13
  1076. scitex/writer/dataclasses/tree/_SupplementaryTree.py +15 -14
  1077. scitex/writer/utils/.legacy_git_retry.py +3 -8
  1078. scitex/writer/utils/_parse_latex_logs.py +2 -3
  1079. scitex/writer/utils/_parse_script_args.py +20 -23
  1080. scitex/writer/utils/_watch.py +5 -5
  1081. {scitex-2.5.0.dist-info → scitex-2.7.0.dist-info}/METADATA +4 -10
  1082. {scitex-2.5.0.dist-info → scitex-2.7.0.dist-info}/RECORD +1071 -975
  1083. scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin_v01-indentation-issues.py +0 -583
  1084. scitex/plt/_subplots/_export_as_csv_formatters.py +0 -112
  1085. scitex/vis/editor/flask_editor/bbox.py +0 -216
  1086. scitex/vis/editor/flask_editor/plotter.py +0 -130
  1087. scitex/vis/editor/flask_editor/renderer.py +0 -184
  1088. scitex/vis/editor/flask_editor/templates/scripts.py +0 -614
  1089. {scitex-2.5.0.dist-info → scitex-2.7.0.dist-info}/WHEEL +0 -0
  1090. {scitex-2.5.0.dist-info → scitex-2.7.0.dist-info}/entry_points.txt +0 -0
  1091. {scitex-2.5.0.dist-info → scitex-2.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,6 +5,7 @@
5
5
  # ----------------------------------------
6
6
  from __future__ import annotations
7
7
  import os
8
+
8
9
  __FILE__ = __file__
9
10
  __DIR__ = os.path.dirname(__FILE__)
10
11
  # ----------------------------------------
@@ -44,11 +45,11 @@ logger = logging.getLogger(__name__)
44
45
 
45
46
  class ZoteroCompatibleDB:
46
47
  """Zotero-compatible database with enrichment workflow.
47
-
48
+
48
49
  Follows Zotero's schema and storage patterns for compatibility while
49
50
  adding source tracking and enrichment capabilities.
50
51
  """
51
-
52
+
52
53
  # Zotero-compatible item types
53
54
  ITEM_TYPES = {
54
55
  "journalArticle": 1,
@@ -58,13 +59,13 @@ class ZoteroCompatibleDB:
58
59
  "conferencePaper": 5,
59
60
  "preprint": 6,
60
61
  "report": 7,
61
- "webpage": 8
62
+ "webpage": 8,
62
63
  }
63
-
64
+
64
65
  # Field mappings (Zotero fieldID to our field names)
65
66
  FIELD_MAP = {
66
67
  1: "title",
67
- 2: "abstract",
68
+ 2: "abstract",
68
69
  3: "journal",
69
70
  4: "volume",
70
71
  5: "issue",
@@ -88,12 +89,12 @@ class ZoteroCompatibleDB:
88
89
  23: "rights",
89
90
  24: "language",
90
91
  25: "dateAdded",
91
- 26: "dateModified"
92
+ 26: "dateModified",
92
93
  }
93
-
94
+
94
95
  def __init__(self, base_dir: Optional[Path] = None, library_name: str = "default"):
95
96
  """Initialize Zotero-compatible database.
96
-
97
+
97
98
  Args:
98
99
  base_dir: Base directory (default: $SCITEX_DIR/scholar/library/<library_name>)
99
100
  library_name: Name of the library (default: "default")
@@ -106,49 +107,52 @@ class ZoteroCompatibleDB:
106
107
  base_dir = Path(scitex_dir) / "scholar" / "library" / library_name
107
108
  else:
108
109
  # Fallback to ~/.scitex
109
- base_dir = Path.home() / ".scitex" / "scholar" / "library" / library_name
110
-
110
+ base_dir = (
111
+ Path.home() / ".scitex" / "scholar" / "library" / library_name
112
+ )
113
+
111
114
  logger.info(f"Using library directory: {base_dir}")
112
-
115
+
113
116
  self.base_dir = Path(base_dir)
114
117
  self.library_name = library_name
115
118
  self.db_path = self.base_dir / "zotero.sqlite"
116
119
  self.storage_dir = self.base_dir / "storage"
117
-
120
+
118
121
  # Detect platform
119
122
  import platform
123
+
120
124
  self.is_windows = platform.system() == "Windows"
121
125
  self.is_wsl = "microsoft" in platform.uname().release.lower()
122
-
126
+
123
127
  # Create structure
124
128
  self._init_directories()
125
129
  self._init_database()
126
-
130
+
127
131
  # Create library config
128
132
  self._save_library_config()
129
-
133
+
130
134
  def _init_directories(self):
131
135
  """Create Zotero-style directory structure with human-readable organization."""
132
136
  self.base_dir.mkdir(parents=True, exist_ok=True)
133
-
137
+
134
138
  # Primary storage (Zotero-style)
135
139
  (self.storage_dir / "by_key").mkdir(parents=True, exist_ok=True)
136
-
140
+
137
141
  # Human-readable organization
138
142
  (self.storage_dir / "by_citation").mkdir(exist_ok=True)
139
143
  (self.storage_dir / "by_year").mkdir(exist_ok=True)
140
144
  (self.storage_dir / "by_journal").mkdir(exist_ok=True)
141
145
  (self.storage_dir / "by_topic").mkdir(exist_ok=True)
142
-
146
+
143
147
  # Windows shortcuts directory (separate from symlinks)
144
148
  if self.is_windows or self.is_wsl:
145
149
  (self.storage_dir / "shortcuts_windows").mkdir(exist_ok=True)
146
-
150
+
147
151
  # Zotero compatibility directories
148
152
  (self.base_dir / "translators").mkdir(exist_ok=True)
149
153
  (self.base_dir / "styles").mkdir(exist_ok=True)
150
154
  (self.base_dir / "locate").mkdir(exist_ok=True)
151
-
155
+
152
156
  def _init_database(self):
153
157
  """Initialize Zotero-compatible database schema."""
154
158
  with self._get_connection() as conn:
@@ -156,7 +160,7 @@ class ZoteroCompatibleDB:
156
160
  conn.execute("PRAGMA page_size=4096")
157
161
  conn.execute("PRAGMA journal_mode=WAL")
158
162
  conn.execute("PRAGMA synchronous=NORMAL")
159
-
163
+
160
164
  # Version info (Zotero compatibility)
161
165
  conn.execute("""
162
166
  CREATE TABLE IF NOT EXISTS version (
@@ -165,7 +169,7 @@ class ZoteroCompatibleDB:
165
169
  )
166
170
  """)
167
171
  conn.execute("INSERT OR REPLACE INTO version VALUES ('userdata', 120)")
168
-
172
+
169
173
  # Libraries (simplified - we use single library)
170
174
  conn.execute("""
171
175
  CREATE TABLE IF NOT EXISTS libraries (
@@ -178,7 +182,7 @@ class ZoteroCompatibleDB:
178
182
  conn.execute("""
179
183
  INSERT OR IGNORE INTO libraries VALUES (1, 'user', 1, 1)
180
184
  """)
181
-
185
+
182
186
  # Items (main table)
183
187
  conn.execute("""
184
188
  CREATE TABLE IF NOT EXISTS items (
@@ -195,7 +199,7 @@ class ZoteroCompatibleDB:
195
199
  FOREIGN KEY (libraryID) REFERENCES libraries(libraryID)
196
200
  )
197
201
  """)
198
-
202
+
199
203
  # Item data values
200
204
  conn.execute("""
201
205
  CREATE TABLE IF NOT EXISTS itemDataValues (
@@ -203,7 +207,7 @@ class ZoteroCompatibleDB:
203
207
  value TEXT UNIQUE
204
208
  )
205
209
  """)
206
-
210
+
207
211
  # Item data (field values)
208
212
  conn.execute("""
209
213
  CREATE TABLE IF NOT EXISTS itemData (
@@ -215,7 +219,7 @@ class ZoteroCompatibleDB:
215
219
  FOREIGN KEY (valueID) REFERENCES itemDataValues(valueID)
216
220
  )
217
221
  """)
218
-
222
+
219
223
  # Creators
220
224
  conn.execute("""
221
225
  CREATE TABLE IF NOT EXISTS creators (
@@ -226,7 +230,7 @@ class ZoteroCompatibleDB:
226
230
  UNIQUE(firstName, lastName)
227
231
  )
228
232
  """)
229
-
233
+
230
234
  # Item creators
231
235
  conn.execute("""
232
236
  CREATE TABLE IF NOT EXISTS itemCreators (
@@ -239,7 +243,7 @@ class ZoteroCompatibleDB:
239
243
  FOREIGN KEY (creatorID) REFERENCES creators(creatorID)
240
244
  )
241
245
  """)
242
-
246
+
243
247
  # Collections
244
248
  conn.execute("""
245
249
  CREATE TABLE IF NOT EXISTS collections (
@@ -258,7 +262,7 @@ class ZoteroCompatibleDB:
258
262
  FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID)
259
263
  )
260
264
  """)
261
-
265
+
262
266
  # Collection items
263
267
  conn.execute("""
264
268
  CREATE TABLE IF NOT EXISTS collectionItems (
@@ -270,7 +274,7 @@ class ZoteroCompatibleDB:
270
274
  FOREIGN KEY (itemID) REFERENCES items(itemID)
271
275
  )
272
276
  """)
273
-
277
+
274
278
  # Tags
275
279
  conn.execute("""
276
280
  CREATE TABLE IF NOT EXISTS tags (
@@ -278,7 +282,7 @@ class ZoteroCompatibleDB:
278
282
  name TEXT NOT NULL UNIQUE
279
283
  )
280
284
  """)
281
-
285
+
282
286
  # Item tags
283
287
  conn.execute("""
284
288
  CREATE TABLE IF NOT EXISTS itemTags (
@@ -290,7 +294,7 @@ class ZoteroCompatibleDB:
290
294
  FOREIGN KEY (tagID) REFERENCES tags(tagID)
291
295
  )
292
296
  """)
293
-
297
+
294
298
  # Item attachments
295
299
  conn.execute("""
296
300
  CREATE TABLE IF NOT EXISTS itemAttachments (
@@ -307,7 +311,7 @@ class ZoteroCompatibleDB:
307
311
  FOREIGN KEY (parentItemID) REFERENCES items(itemID)
308
312
  )
309
313
  """)
310
-
314
+
311
315
  # Item notes
312
316
  conn.execute("""
313
317
  CREATE TABLE IF NOT EXISTS itemNotes (
@@ -319,7 +323,7 @@ class ZoteroCompatibleDB:
319
323
  FOREIGN KEY (parentItemID) REFERENCES items(itemID)
320
324
  )
321
325
  """)
322
-
326
+
323
327
  # Deleted items
324
328
  conn.execute("""
325
329
  CREATE TABLE IF NOT EXISTS deletedItems (
@@ -327,7 +331,7 @@ class ZoteroCompatibleDB:
327
331
  dateDeleted DEFAULT CURRENT_TIMESTAMP
328
332
  )
329
333
  """)
330
-
334
+
331
335
  # Full text content
332
336
  conn.execute("""
333
337
  CREATE TABLE IF NOT EXISTS fulltextItems (
@@ -341,7 +345,7 @@ class ZoteroCompatibleDB:
341
345
  FOREIGN KEY (itemID) REFERENCES items(itemID)
342
346
  )
343
347
  """)
344
-
348
+
345
349
  # Full text words
346
350
  conn.execute("""
347
351
  CREATE TABLE IF NOT EXISTS fulltextWords (
@@ -349,7 +353,7 @@ class ZoteroCompatibleDB:
349
353
  word TEXT UNIQUE
350
354
  )
351
355
  """)
352
-
356
+
353
357
  # Full text item words
354
358
  conn.execute("""
355
359
  CREATE TABLE IF NOT EXISTS fulltextItemWords (
@@ -360,9 +364,9 @@ class ZoteroCompatibleDB:
360
364
  FOREIGN KEY (itemID) REFERENCES items(itemID)
361
365
  )
362
366
  """)
363
-
367
+
364
368
  # SciTeX extensions
365
-
369
+
366
370
  # Source tracking table
367
371
  conn.execute("""
368
372
  CREATE TABLE IF NOT EXISTS scitex_field_sources (
@@ -375,7 +379,7 @@ class ZoteroCompatibleDB:
375
379
  FOREIGN KEY (itemID) REFERENCES items(itemID)
376
380
  )
377
381
  """)
378
-
382
+
379
383
  # Enrichment status
380
384
  conn.execute("""
381
385
  CREATE TABLE IF NOT EXISTS scitex_enrichment_status (
@@ -389,15 +393,21 @@ class ZoteroCompatibleDB:
389
393
  FOREIGN KEY (itemID) REFERENCES items(itemID)
390
394
  )
391
395
  """)
392
-
396
+
393
397
  # Create indexes
394
398
  conn.execute("CREATE INDEX IF NOT EXISTS idx_items_key ON items(key)")
395
- conn.execute("CREATE INDEX IF NOT EXISTS idx_items_dateAdded ON items(dateAdded)")
396
- conn.execute("CREATE INDEX IF NOT EXISTS idx_items_dateModified ON items(dateModified)")
397
- conn.execute("CREATE INDEX IF NOT EXISTS idx_itemData_value ON itemData(itemID, valueID)")
398
-
399
+ conn.execute(
400
+ "CREATE INDEX IF NOT EXISTS idx_items_dateAdded ON items(dateAdded)"
401
+ )
402
+ conn.execute(
403
+ "CREATE INDEX IF NOT EXISTS idx_items_dateModified ON items(dateModified)"
404
+ )
405
+ conn.execute(
406
+ "CREATE INDEX IF NOT EXISTS idx_itemData_value ON itemData(itemID, valueID)"
407
+ )
408
+
399
409
  conn.commit()
400
-
410
+
401
411
  @contextmanager
402
412
  def _get_connection(self):
403
413
  """Get database connection."""
@@ -407,37 +417,37 @@ class ZoteroCompatibleDB:
407
417
  yield conn
408
418
  finally:
409
419
  conn.close()
410
-
420
+
411
421
  def _generate_key(self, length: int = 8) -> str:
412
422
  """Generate Zotero-style key."""
413
423
  # Zotero uses base32-like encoding
414
424
  chars = string.ascii_uppercase + string.digits
415
425
  # Avoid ambiguous characters
416
- chars = chars.replace('0', '').replace('O', '').replace('1', '').replace('I', '')
417
- return ''.join(random.choice(chars) for _ in range(length))
418
-
426
+ chars = (
427
+ chars.replace("0", "").replace("O", "").replace("1", "").replace("I", "")
428
+ )
429
+ return "".join(random.choice(chars) for _ in range(length))
430
+
419
431
  def _get_or_create_value(self, conn: sqlite3.Connection, value: Any) -> int:
420
432
  """Get or create value in itemDataValues table."""
421
433
  if value is None:
422
434
  return None
423
-
435
+
424
436
  value_str = str(value)
425
-
437
+
426
438
  cursor = conn.execute(
427
- "SELECT valueID FROM itemDataValues WHERE value = ?",
428
- (value_str,)
439
+ "SELECT valueID FROM itemDataValues WHERE value = ?", (value_str,)
429
440
  )
430
441
  row = cursor.fetchone()
431
-
442
+
432
443
  if row:
433
444
  return row["valueID"]
434
445
  else:
435
446
  cursor = conn.execute(
436
- "INSERT INTO itemDataValues (value) VALUES (?)",
437
- (value_str,)
447
+ "INSERT INTO itemDataValues (value) VALUES (?)", (value_str,)
438
448
  )
439
449
  return cursor.lastrowid
440
-
450
+
441
451
  def _get_field_id(self, field_name: str) -> Optional[int]:
442
452
  """Get Zotero fieldID from field name."""
443
453
  # Reverse lookup in field map
@@ -448,15 +458,16 @@ class ZoteroCompatibleDB:
448
458
  if field_name == "doi":
449
459
  return 8 # DOI fieldID
450
460
  return None
451
-
452
- def add_item_from_partial(self, partial_info: Dict[str, Any],
453
- item_type: str = "journalArticle") -> int:
461
+
462
+ def add_item_from_partial(
463
+ self, partial_info: Dict[str, Any], item_type: str = "journalArticle"
464
+ ) -> int:
454
465
  """Add item from partial information (step 1 of workflow).
455
-
466
+
456
467
  Args:
457
468
  partial_info: Dict with title, authors, year, etc.
458
469
  item_type: Zotero item type
459
-
470
+
460
471
  Returns:
461
472
  Item ID
462
473
  """
@@ -464,115 +475,122 @@ class ZoteroCompatibleDB:
464
475
  # Create item
465
476
  key = self._generate_key()
466
477
  item_type_id = self.ITEM_TYPES.get(item_type, 1)
467
-
468
- cursor = conn.execute("""
478
+
479
+ cursor = conn.execute(
480
+ """
469
481
  INSERT INTO items (itemTypeID, libraryID, key)
470
482
  VALUES (?, 1, ?)
471
- """, (item_type_id, key))
483
+ """,
484
+ (item_type_id, key),
485
+ )
472
486
  item_id = cursor.lastrowid
473
-
487
+
474
488
  # Add basic fields
475
489
  fields_to_add = {
476
490
  "title": partial_info.get("title"),
477
- "date": str(partial_info.get("year")) if partial_info.get("year") else None,
491
+ "date": str(partial_info.get("year"))
492
+ if partial_info.get("year")
493
+ else None,
478
494
  "DOI": partial_info.get("doi"),
479
495
  "url": partial_info.get("url"),
480
496
  "journal": partial_info.get("journal"),
481
- "abstract": partial_info.get("abstract")
497
+ "abstract": partial_info.get("abstract"),
482
498
  }
483
-
499
+
484
500
  for field_name, value in fields_to_add.items():
485
501
  if value:
486
502
  self._set_item_field(conn, item_id, field_name, value)
487
-
503
+
488
504
  # Track source
489
505
  source = partial_info.get(f"{field_name}_source", "initial_import")
490
506
  self._track_field_source(conn, item_id, field_name, source)
491
-
507
+
492
508
  # Add creators
493
509
  authors = partial_info.get("authors", [])
494
510
  for idx, author in enumerate(authors):
495
511
  self._add_creator(conn, item_id, author, idx)
496
-
512
+
497
513
  # Add tags/keywords
498
514
  keywords = partial_info.get("keywords", [])
499
515
  for keyword in keywords:
500
516
  self._add_tag(conn, item_id, keyword)
501
-
517
+
502
518
  # Initialize enrichment status
503
- conn.execute("""
519
+ conn.execute(
520
+ """
504
521
  INSERT INTO scitex_enrichment_status (itemID)
505
522
  VALUES (?)
506
- """, (item_id,))
507
-
523
+ """,
524
+ (item_id,),
525
+ )
526
+
508
527
  conn.commit()
509
-
528
+
510
529
  # Create storage directory
511
530
  storage_path = self.storage_dir / key
512
531
  storage_path.mkdir(exist_ok=True)
513
-
514
- logger.info(f"Added item {item_id} with key {key}: {partial_info.get('title', '')[:50]}...")
515
-
532
+
533
+ logger.info(
534
+ f"Added item {item_id} with key {key}: {partial_info.get('title', '')[:50]}..."
535
+ )
536
+
516
537
  # Create human-readable links
517
538
  self._create_human_readable_links(item_id, key, partial_info)
518
-
539
+
519
540
  return item_id
520
-
521
- def _create_human_readable_links(self, item_id: int, key: str,
522
- metadata: Dict[str, Any]):
541
+
542
+ def _create_human_readable_links(
543
+ self, item_id: int, key: str, metadata: Dict[str, Any]
544
+ ):
523
545
  """Create human-readable symlinks and Windows shortcuts."""
524
546
  # Generate citation-style name
525
547
  citation_name = self._generate_citation_name(metadata)
526
-
548
+
527
549
  # Primary storage path
528
550
  primary_path = self.storage_dir / "by_key" / key
529
-
551
+
530
552
  # Create by_citation links
531
553
  citation_dir = self.storage_dir / "by_citation" / citation_name
532
554
  citation_dir.mkdir(parents=True, exist_ok=True)
533
-
555
+
534
556
  # Create symlinks (works on Linux/Mac/WSL)
535
557
  if not self.is_windows:
536
- self._create_symlink(
537
- primary_path,
538
- citation_dir / f"{citation_name}-{key}"
539
- )
540
-
558
+ self._create_symlink(primary_path, citation_dir / f"{citation_name}-{key}")
559
+
541
560
  # Create Windows shortcuts
542
561
  if self.is_windows or self.is_wsl:
543
562
  self._create_windows_shortcut(
544
563
  primary_path,
545
- self.storage_dir / "shortcuts_windows" / citation_name / f"{citation_name}-{key}.lnk"
564
+ self.storage_dir
565
+ / "shortcuts_windows"
566
+ / citation_name
567
+ / f"{citation_name}-{key}.lnk",
546
568
  )
547
-
569
+
548
570
  # Create by_year organization
549
571
  year = metadata.get("year")
550
572
  if year:
551
573
  year_dir = self.storage_dir / "by_year" / str(year)
552
574
  year_dir.mkdir(parents=True, exist_ok=True)
553
-
575
+
554
576
  if not self.is_windows:
555
- self._create_symlink(
556
- primary_path,
557
- year_dir / f"{citation_name}-{key}"
558
- )
559
-
577
+ self._create_symlink(primary_path, year_dir / f"{citation_name}-{key}")
578
+
560
579
  # Create by_journal organization
561
580
  journal = metadata.get("journal")
562
581
  if journal:
563
582
  safe_journal = self._sanitize_filename(journal)[:50]
564
583
  journal_dir = self.storage_dir / "by_journal" / safe_journal
565
584
  journal_dir.mkdir(parents=True, exist_ok=True)
566
-
585
+
567
586
  if not self.is_windows:
568
587
  self._create_symlink(
569
- primary_path,
570
- journal_dir / f"{citation_name}-{key}"
588
+ primary_path, journal_dir / f"{citation_name}-{key}"
571
589
  )
572
-
590
+
573
591
  def _generate_citation_name(self, metadata: Dict[str, Any]) -> str:
574
592
  """Generate human-readable citation name.
575
-
593
+
576
594
  Format: FIRSTAUTHOR-YEAR-SOURCE
577
595
  Where SOURCE can be journal abbreviation, conference, book publisher, etc.
578
596
  """
@@ -586,16 +604,16 @@ class ZoteroCompatibleDB:
586
604
  author_name = first_author.split()[-1] if first_author else "Unknown"
587
605
  else:
588
606
  author_name = "Unknown"
589
-
607
+
590
608
  # Sanitize author name
591
609
  author_name = self._sanitize_filename(author_name)
592
-
610
+
593
611
  # Get year
594
612
  year = metadata.get("year", "XXXX")
595
-
613
+
596
614
  # Get source (journal, conference, publisher, etc.)
597
615
  source = None
598
-
616
+
599
617
  # Priority order for source
600
618
  if metadata.get("journal"):
601
619
  source = self._abbreviate_journal(metadata["journal"])
@@ -611,11 +629,11 @@ class ZoteroCompatibleDB:
611
629
  source = "arXiv"
612
630
  else:
613
631
  source = "Misc"
614
-
632
+
615
633
  source = self._sanitize_filename(source)[:30] # Limit length
616
-
634
+
617
635
  return f"{author_name}-{year}-{source}"
618
-
636
+
619
637
  def _abbreviate_journal(self, journal_name: str) -> str:
620
638
  """Create journal abbreviation."""
621
639
  # Common journal abbreviations
@@ -633,35 +651,36 @@ class ZoteroCompatibleDB:
633
651
  "acm transactions": "ACM-T",
634
652
  "neural information processing systems": "NeurIPS",
635
653
  "international conference on machine learning": "ICML",
636
- "conference on computer vision and pattern recognition": "CVPR"
654
+ "conference on computer vision and pattern recognition": "CVPR",
637
655
  }
638
-
656
+
639
657
  # Check for exact matches
640
658
  journal_lower = journal_name.lower()
641
659
  for key, abbrev in abbreviations.items():
642
660
  if key in journal_lower:
643
661
  return abbrev
644
-
662
+
645
663
  # Create abbreviation from first letters of significant words
646
664
  words = journal_name.split()
647
665
  stop_words = {"of", "the", "and", "in", "on", "for", "a", "an"}
648
666
  significant_words = [w for w in words if w.lower() not in stop_words]
649
-
667
+
650
668
  if len(significant_words) <= 3:
651
669
  return "-".join(significant_words)
652
670
  else:
653
671
  # Use first letter of each significant word
654
672
  return "".join(w[0].upper() for w in significant_words[:6])
655
-
673
+
656
674
  def _abbreviate_conference(self, conference_name: str) -> str:
657
675
  """Create conference abbreviation."""
658
676
  # Extract year if present
659
677
  import re
660
- year_match = re.search(r'\b(19|20)\d{2}\b', conference_name)
661
-
678
+
679
+ year_match = re.search(r"\b(19|20)\d{2}\b", conference_name)
680
+
662
681
  # Remove year for abbreviation
663
- name_without_year = re.sub(r'\b(19|20)\d{2}\b', '', conference_name).strip()
664
-
682
+ name_without_year = re.sub(r"\b(19|20)\d{2}\b", "", conference_name).strip()
683
+
665
684
  # Common conference patterns
666
685
  if "workshop" in name_without_year.lower():
667
686
  return "Workshop"
@@ -672,9 +691,9 @@ class ZoteroCompatibleDB:
672
691
  words = name_without_year.lower().split("conference")[0].split()
673
692
  if words:
674
693
  return "".join(w[0].upper() for w in words if len(w) > 2)[:6] + "-Conf"
675
-
694
+
676
695
  return self._abbreviate_journal(name_without_year)
677
-
696
+
678
697
  def _sanitize_filename(self, name: str) -> str:
679
698
  """Sanitize string for use in filename."""
680
699
  # Replace problematic characters
@@ -684,7 +703,7 @@ class ZoteroCompatibleDB:
684
703
  ":": "-",
685
704
  "*": "-",
686
705
  "?": "",
687
- "\"": "",
706
+ '"': "",
688
707
  "<": "",
689
708
  ">": "",
690
709
  "|": "-",
@@ -709,23 +728,23 @@ class ZoteroCompatibleDB:
709
728
  "=": "-",
710
729
  "+": "plus",
711
730
  "~": "-",
712
- "`": ""
731
+ "`": "",
713
732
  }
714
-
733
+
715
734
  result = name
716
735
  for old, new in replacements.items():
717
736
  result = result.replace(old, new)
718
-
737
+
719
738
  # Remove multiple underscores/hyphens
720
- result = re.sub(r'[-_]+', '-', result)
721
- result = result.strip('-_')
722
-
739
+ result = re.sub(r"[-_]+", "-", result)
740
+ result = result.strip("-_")
741
+
723
742
  # Ensure it's not empty
724
743
  if not result:
725
744
  result = "Unknown"
726
-
745
+
727
746
  return result
728
-
747
+
729
748
  def _create_symlink(self, target: Path, link: Path):
730
749
  """Create symlink (Linux/Mac/WSL)."""
731
750
  try:
@@ -736,15 +755,16 @@ class ZoteroCompatibleDB:
736
755
  logger.debug(f"Created symlink: {link} -> {target}")
737
756
  except Exception as e:
738
757
  logger.warning(f"Failed to create symlink: {e}")
739
-
758
+
740
759
  def _create_windows_shortcut(self, target: Path, shortcut_path: Path):
741
760
  """Create Windows .lnk shortcut file."""
742
761
  try:
743
762
  shortcut_path.parent.mkdir(parents=True, exist_ok=True)
744
-
763
+
745
764
  # Use Windows COM to create shortcut
746
765
  if self.is_windows:
747
766
  import win32com.client
767
+
748
768
  shell = win32com.client.Dispatch("WScript.Shell")
749
769
  shortcut = shell.CreateShortCut(str(shortcut_path))
750
770
  shortcut.Targetpath = str(target)
@@ -754,15 +774,17 @@ class ZoteroCompatibleDB:
754
774
  elif self.is_wsl:
755
775
  # For WSL, create a simple text file with the path
756
776
  # (Real .lnk files need to be created from Windows side)
757
- info_file = shortcut_path.with_suffix('.txt')
758
- with open(info_file, 'w') as f:
777
+ info_file = shortcut_path.with_suffix(".txt")
778
+ with open(info_file, "w") as f:
759
779
  f.write(f"Target: {target}\n")
760
- f.write(f"This is a placeholder. Create real .lnk file from Windows.\n")
780
+ f.write(
781
+ f"This is a placeholder. Create real .lnk file from Windows.\n"
782
+ )
761
783
  logger.debug(f"Created shortcut info file: {info_file}")
762
-
784
+
763
785
  except Exception as e:
764
786
  logger.warning(f"Failed to create Windows shortcut: {e}")
765
-
787
+
766
788
  def _save_library_config(self):
767
789
  """Save library configuration for easy access."""
768
790
  config = {
@@ -775,65 +797,79 @@ class ZoteroCompatibleDB:
775
797
  "human_readable_links": True,
776
798
  "metadata_source_tracking": True,
777
799
  "enrichment_workflow": True,
778
- "windows_shortcuts": self.is_windows or self.is_wsl
779
- }
800
+ "windows_shortcuts": self.is_windows or self.is_wsl,
801
+ },
780
802
  }
781
-
803
+
782
804
  config_path = self.base_dir / "library.json"
783
- with open(config_path, 'w') as f:
805
+ with open(config_path, "w") as f:
784
806
  json.dump(config, f, indent=2)
785
-
807
+
786
808
  # Also save to global library registry
787
809
  registry_dir = self.base_dir.parent.parent / "config"
788
810
  registry_dir.mkdir(parents=True, exist_ok=True)
789
-
811
+
790
812
  registry_path = registry_dir / "libraries.json"
791
-
813
+
792
814
  # Load existing registry
793
815
  if registry_path.exists():
794
- with open(registry_path, 'r') as f:
816
+ with open(registry_path, "r") as f:
795
817
  registry = json.load(f)
796
818
  else:
797
819
  registry = {"libraries": {}}
798
-
820
+
799
821
  # Update registry
800
822
  registry["libraries"][self.library_name] = {
801
823
  "path": str(self.base_dir),
802
824
  "created_at": config["created_at"],
803
- "last_accessed": datetime.now().isoformat()
825
+ "last_accessed": datetime.now().isoformat(),
804
826
  }
805
-
806
- with open(registry_path, 'w') as f:
827
+
828
+ with open(registry_path, "w") as f:
807
829
  json.dump(registry, f, indent=2)
808
-
830
+
809
831
  logger.info(f"Library config saved: {config_path}")
810
-
811
- def _set_item_field(self, conn: sqlite3.Connection, item_id: int,
812
- field_name: str, value: Any):
832
+
833
+ def _set_item_field(
834
+ self, conn: sqlite3.Connection, item_id: int, field_name: str, value: Any
835
+ ):
813
836
  """Set field value for item."""
814
837
  field_id = self._get_field_id(field_name)
815
838
  if not field_id:
816
839
  logger.warning(f"Unknown field: {field_name}")
817
840
  return
818
-
841
+
819
842
  value_id = self._get_or_create_value(conn, value)
820
843
  if value_id:
821
- conn.execute("""
844
+ conn.execute(
845
+ """
822
846
  INSERT OR REPLACE INTO itemData (itemID, fieldID, valueID)
823
847
  VALUES (?, ?, ?)
824
- """, (item_id, field_id, value_id))
825
-
826
- def _track_field_source(self, conn: sqlite3.Connection, item_id: int,
827
- field_name: str, source: str, confidence: float = 1.0):
848
+ """,
849
+ (item_id, field_id, value_id),
850
+ )
851
+
852
+ def _track_field_source(
853
+ self,
854
+ conn: sqlite3.Connection,
855
+ item_id: int,
856
+ field_name: str,
857
+ source: str,
858
+ confidence: float = 1.0,
859
+ ):
828
860
  """Track metadata source for field."""
829
- conn.execute("""
861
+ conn.execute(
862
+ """
830
863
  INSERT OR REPLACE INTO scitex_field_sources
831
864
  (itemID, fieldName, source, confidence)
832
865
  VALUES (?, ?, ?, ?)
833
- """, (item_id, field_name, source, confidence))
834
-
835
- def _add_creator(self, conn: sqlite3.Connection, item_id: int,
836
- author_name: str, order_index: int):
866
+ """,
867
+ (item_id, field_name, source, confidence),
868
+ )
869
+
870
+ def _add_creator(
871
+ self, conn: sqlite3.Connection, item_id: int, author_name: str, order_index: int
872
+ ):
837
873
  """Add creator to item."""
838
874
  # Parse name
839
875
  if "," in author_name:
@@ -846,71 +882,78 @@ class ZoteroCompatibleDB:
846
882
  first_name = author_name
847
883
  last_name = ""
848
884
  field_mode = 1
849
-
885
+
850
886
  # Get or create creator
851
887
  cursor = conn.execute(
852
888
  "SELECT creatorID FROM creators WHERE firstName = ? AND lastName = ?",
853
- (first_name, last_name)
889
+ (first_name, last_name),
854
890
  )
855
891
  row = cursor.fetchone()
856
-
892
+
857
893
  if row:
858
894
  creator_id = row["creatorID"]
859
895
  else:
860
896
  cursor = conn.execute(
861
897
  "INSERT INTO creators (firstName, lastName, fieldMode) VALUES (?, ?, ?)",
862
- (first_name, last_name, field_mode)
898
+ (first_name, last_name, field_mode),
863
899
  )
864
900
  creator_id = cursor.lastrowid
865
-
901
+
866
902
  # Link to item
867
- conn.execute("""
903
+ conn.execute(
904
+ """
868
905
  INSERT OR REPLACE INTO itemCreators
869
906
  (itemID, creatorID, creatorTypeID, orderIndex)
870
907
  VALUES (?, ?, 1, ?)
871
- """, (item_id, creator_id, order_index))
872
-
908
+ """,
909
+ (item_id, creator_id, order_index),
910
+ )
911
+
873
912
  def _add_tag(self, conn: sqlite3.Connection, item_id: int, tag_name: str):
874
913
  """Add tag to item."""
875
914
  # Get or create tag
876
915
  cursor = conn.execute(
877
- "SELECT tagID FROM tags WHERE name = ?",
878
- (tag_name.lower(),)
916
+ "SELECT tagID FROM tags WHERE name = ?", (tag_name.lower(),)
879
917
  )
880
918
  row = cursor.fetchone()
881
-
919
+
882
920
  if row:
883
921
  tag_id = row["tagID"]
884
922
  else:
885
923
  cursor = conn.execute(
886
- "INSERT INTO tags (name) VALUES (?)",
887
- (tag_name.lower(),)
924
+ "INSERT INTO tags (name) VALUES (?)", (tag_name.lower(),)
888
925
  )
889
926
  tag_id = cursor.lastrowid
890
-
927
+
891
928
  # Link to item
892
- conn.execute("""
929
+ conn.execute(
930
+ """
893
931
  INSERT OR IGNORE INTO itemTags (itemID, tagID)
894
932
  VALUES (?, ?)
895
- """, (item_id, tag_id))
896
-
933
+ """,
934
+ (item_id, tag_id),
935
+ )
936
+
897
937
  def update_item_doi(self, item_id: int, doi: str, source: str) -> bool:
898
938
  """Update item with resolved DOI (step 2 of workflow)."""
899
939
  with self._get_connection() as conn:
900
940
  self._set_item_field(conn, item_id, "DOI", doi)
901
941
  self._track_field_source(conn, item_id, "DOI", source)
902
-
942
+
903
943
  # Update enrichment status
904
- conn.execute("""
944
+ conn.execute(
945
+ """
905
946
  UPDATE scitex_enrichment_status
906
947
  SET doi_resolved = 1
907
948
  WHERE itemID = ?
908
- """, (item_id,))
909
-
949
+ """,
950
+ (item_id,),
951
+ )
952
+
910
953
  conn.commit()
911
954
  logger.info(f"Updated item {item_id} with DOI: {doi}")
912
955
  return True
913
-
956
+
914
957
  def enrich_item_metadata(self, item_id: int, metadata: Dict[str, Any]) -> bool:
915
958
  """Enrich item with metadata from various sources (step 3 of workflow)."""
916
959
  with self._get_connection() as conn:
@@ -918,105 +961,125 @@ class ZoteroCompatibleDB:
918
961
  for field_name, value in metadata.items():
919
962
  if field_name.endswith("_source"):
920
963
  continue
921
-
964
+
922
965
  if value:
923
966
  self._set_item_field(conn, item_id, field_name, value)
924
-
967
+
925
968
  # Track source
926
969
  source = metadata.get(f"{field_name}_source", "enrichment")
927
970
  self._track_field_source(conn, item_id, field_name, source)
928
-
971
+
929
972
  # Update modified timestamp
930
- conn.execute("""
973
+ conn.execute(
974
+ """
931
975
  UPDATE items
932
976
  SET dateModified = CURRENT_TIMESTAMP
933
977
  WHERE itemID = ?
934
- """, (item_id,))
935
-
978
+ """,
979
+ (item_id,),
980
+ )
981
+
936
982
  # Update enrichment status
937
- conn.execute("""
983
+ conn.execute(
984
+ """
938
985
  UPDATE scitex_enrichment_status
939
986
  SET metadata_enriched = 1, last_enrichment = CURRENT_TIMESTAMP
940
987
  WHERE itemID = ?
941
- """, (item_id,))
942
-
988
+ """,
989
+ (item_id,),
990
+ )
991
+
943
992
  conn.commit()
944
993
  logger.info(f"Enriched item {item_id} with {len(metadata)} fields")
945
994
  return True
946
-
947
- def attach_pdf(self, parent_item_id: int, pdf_path: Path,
948
- title: str = "Full Text PDF") -> int:
995
+
996
+ def attach_pdf(
997
+ self, parent_item_id: int, pdf_path: Path, title: str = "Full Text PDF"
998
+ ) -> int:
949
999
  """Attach PDF to item (Zotero-style)."""
950
1000
  if not pdf_path.exists():
951
1001
  logger.error(f"PDF not found: {pdf_path}")
952
1002
  return None
953
-
1003
+
954
1004
  with self._get_connection() as conn:
955
1005
  # Get parent key for storage
956
1006
  cursor = conn.execute(
957
- "SELECT key FROM items WHERE itemID = ?",
958
- (parent_item_id,)
1007
+ "SELECT key FROM items WHERE itemID = ?", (parent_item_id,)
959
1008
  )
960
1009
  parent = cursor.fetchone()
961
1010
  if not parent:
962
1011
  logger.error(f"Parent item {parent_item_id} not found")
963
1012
  return None
964
-
1013
+
965
1014
  parent_key = parent["key"]
966
-
1015
+
967
1016
  # Create attachment item
968
1017
  attachment_key = self._generate_key()
969
- cursor = conn.execute("""
1018
+ cursor = conn.execute(
1019
+ """
970
1020
  INSERT INTO items (itemTypeID, libraryID, key)
971
1021
  VALUES (14, 1, ?)
972
- """, (attachment_key,)) # 14 = attachment
1022
+ """,
1023
+ (attachment_key,),
1024
+ ) # 14 = attachment
973
1025
  attachment_id = cursor.lastrowid
974
-
1026
+
975
1027
  # Copy PDF to storage
976
1028
  storage_path = self.storage_dir / parent_key
977
1029
  storage_path.mkdir(exist_ok=True)
978
-
1030
+
979
1031
  dest_path = storage_path / f"{attachment_key}.pdf"
980
1032
  shutil.copy2(pdf_path, dest_path)
981
-
1033
+
982
1034
  # Calculate hash
983
- with open(dest_path, 'rb') as f:
1035
+ with open(dest_path, "rb") as f:
984
1036
  content = f.read()
985
1037
  storage_hash = hashlib.md5(content).hexdigest()
986
-
1038
+
987
1039
  # Create attachment record
988
- conn.execute("""
1040
+ conn.execute(
1041
+ """
989
1042
  INSERT INTO itemAttachments
990
1043
  (itemID, parentItemID, linkMode, contentType, path, storageHash)
991
1044
  VALUES (?, ?, 0, 'application/pdf', ?, ?)
992
- """, (attachment_id, parent_item_id, f"storage:{attachment_key}.pdf", storage_hash))
993
-
1045
+ """,
1046
+ (
1047
+ attachment_id,
1048
+ parent_item_id,
1049
+ f"storage:{attachment_key}.pdf",
1050
+ storage_hash,
1051
+ ),
1052
+ )
1053
+
994
1054
  # Set title
995
1055
  self._set_item_field(conn, attachment_id, "title", title)
996
-
1056
+
997
1057
  # Update enrichment status
998
- conn.execute("""
1058
+ conn.execute(
1059
+ """
999
1060
  UPDATE scitex_enrichment_status
1000
1061
  SET pdf_download = 1
1001
1062
  WHERE itemID = ?
1002
- """, (parent_item_id,))
1003
-
1063
+ """,
1064
+ (parent_item_id,),
1065
+ )
1066
+
1004
1067
  conn.commit()
1005
-
1068
+
1006
1069
  logger.info(f"Attached PDF to item {parent_item_id}")
1007
1070
  return attachment_id
1008
-
1071
+
1009
1072
  def get_items_needing_enrichment(self, stage: str = "doi") -> List[Dict[str, Any]]:
1010
1073
  """Get items that need enrichment at specific stage."""
1011
1074
  stage_conditions = {
1012
1075
  "doi": "doi_resolved = 0",
1013
1076
  "metadata": "doi_resolved = 1 AND metadata_enriched = 0",
1014
1077
  "pdf": "metadata_enriched = 1 AND pdf_download = 0",
1015
- "fulltext": "pdf_download = 1 AND fulltext_extracted = 0"
1078
+ "fulltext": "pdf_download = 1 AND fulltext_extracted = 0",
1016
1079
  }
1017
-
1080
+
1018
1081
  condition = stage_conditions.get(stage, "1=1")
1019
-
1082
+
1020
1083
  with self._get_connection() as conn:
1021
1084
  cursor = conn.execute(f"""
1022
1085
  SELECT i.itemID, i.key,
@@ -1033,27 +1096,27 @@ class ZoteroCompatibleDB:
1033
1096
  AND {condition}
1034
1097
  ORDER BY i.dateAdded
1035
1098
  """)
1036
-
1099
+
1037
1100
  return [dict(row) for row in cursor]
1038
-
1101
+
1039
1102
  def export_to_zotero_rdf(self, output_path: Path):
1040
1103
  """Export database in Zotero RDF format."""
1041
1104
  # Implementation would generate Zotero RDF/XML format
1042
1105
  # This is a placeholder for the full implementation
1043
1106
  logger.info(f"Exporting to Zotero RDF: {output_path}")
1044
-
1107
+
1045
1108
  def import_from_zotero(self, zotero_db_path: Path):
1046
1109
  """Import from existing Zotero database."""
1047
1110
  logger.info(f"Importing from Zotero: {zotero_db_path}")
1048
-
1111
+
1049
1112
  # This would copy relevant tables and data
1050
1113
  # Placeholder for full implementation
1051
-
1114
+
1052
1115
  def get_statistics(self) -> Dict[str, Any]:
1053
1116
  """Get database statistics."""
1054
1117
  with self._get_connection() as conn:
1055
1118
  stats = {}
1056
-
1119
+
1057
1120
  # Item counts by type
1058
1121
  cursor = conn.execute("""
1059
1122
  SELECT
@@ -1064,7 +1127,7 @@ class ZoteroCompatibleDB:
1064
1127
  WHERE itemID NOT IN (SELECT itemID FROM deletedItems)
1065
1128
  """)
1066
1129
  stats["items"] = dict(cursor.fetchone())
1067
-
1130
+
1068
1131
  # Enrichment status
1069
1132
  cursor = conn.execute("""
1070
1133
  SELECT
@@ -1075,14 +1138,14 @@ class ZoteroCompatibleDB:
1075
1138
  FROM scitex_enrichment_status
1076
1139
  """)
1077
1140
  stats["enrichment"] = dict(cursor.fetchone())
1078
-
1141
+
1079
1142
  # Storage info
1080
1143
  pdf_count = len(list(self.storage_dir.glob("*/*.pdf")))
1081
1144
  stats["storage"] = {
1082
1145
  "pdf_count": pdf_count,
1083
- "storage_path": str(self.storage_dir)
1146
+ "storage_path": str(self.storage_dir),
1084
1147
  }
1085
-
1148
+
1086
1149
  return stats
1087
1150
 
1088
1151
 
@@ -1095,7 +1158,7 @@ if __name__ == "__main__":
1095
1158
  print("3. Enrich metadata using DOI")
1096
1159
  print("4. Download and attach PDF")
1097
1160
  print("5. Extract full text for search")
1098
-
1161
+
1099
1162
  print("\nExample usage:")
1100
1163
  print("""
1101
1164
  # Initialize
@@ -1128,4 +1191,4 @@ if __name__ == "__main__":
1128
1191
  needs_metadata = db.get_items_needing_enrichment("metadata")
1129
1192
  """)
1130
1193
 
1131
- # EOF
1194
+ # EOF