scitex 2.14.0__py3-none-any.whl → 2.15.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scitex/__init__.py +71 -17
- scitex/_env_loader.py +156 -0
- scitex/_mcp_resources/__init__.py +37 -0
- scitex/_mcp_resources/_cheatsheet.py +135 -0
- scitex/_mcp_resources/_figrecipe.py +138 -0
- scitex/_mcp_resources/_formats.py +102 -0
- scitex/_mcp_resources/_modules.py +337 -0
- scitex/_mcp_resources/_session.py +149 -0
- scitex/_mcp_tools/__init__.py +4 -0
- scitex/_mcp_tools/audio.py +66 -0
- scitex/_mcp_tools/diagram.py +11 -95
- scitex/_mcp_tools/introspect.py +210 -0
- scitex/_mcp_tools/plt.py +260 -305
- scitex/_mcp_tools/scholar.py +74 -0
- scitex/_mcp_tools/social.py +244 -0
- scitex/_mcp_tools/template.py +24 -0
- scitex/_mcp_tools/writer.py +21 -204
- scitex/ai/_gen_ai/_PARAMS.py +10 -7
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
- scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
- scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
- scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
- scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
- scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
- scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
- scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
- scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
- scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
- scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
- scitex/audio/README.md +40 -36
- scitex/audio/__init__.py +129 -61
- scitex/audio/_branding.py +185 -0
- scitex/audio/_mcp/__init__.py +32 -0
- scitex/audio/_mcp/handlers.py +59 -6
- scitex/audio/_mcp/speak_handlers.py +238 -0
- scitex/audio/_relay.py +225 -0
- scitex/audio/_tts.py +18 -10
- scitex/audio/engines/base.py +17 -10
- scitex/audio/engines/elevenlabs_engine.py +7 -2
- scitex/audio/mcp_server.py +228 -75
- scitex/canvas/README.md +1 -1
- scitex/canvas/editor/_dearpygui/__init__.py +25 -0
- scitex/canvas/editor/_dearpygui/_editor.py +147 -0
- scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
- scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
- scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
- scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
- scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
- scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
- scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
- scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
- scitex/canvas/editor/_dearpygui/_selection.py +295 -0
- scitex/canvas/editor/_dearpygui/_state.py +93 -0
- scitex/canvas/editor/_dearpygui/_utils.py +61 -0
- scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
- scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
- scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
- scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
- scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
- scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
- scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
- scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
- scitex/canvas/editor/flask_editor/_core.py +25 -1684
- scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
- scitex/cli/__init__.py +38 -43
- scitex/cli/audio.py +76 -27
- scitex/cli/capture.py +13 -20
- scitex/cli/introspect.py +481 -0
- scitex/cli/main.py +200 -109
- scitex/cli/mcp.py +60 -34
- scitex/cli/plt.py +357 -0
- scitex/cli/repro.py +15 -8
- scitex/cli/resource.py +15 -8
- scitex/cli/scholar/__init__.py +23 -8
- scitex/cli/scholar/_crossref_scitex.py +296 -0
- scitex/cli/scholar/_fetch.py +25 -3
- scitex/cli/social.py +314 -0
- scitex/cli/stats.py +15 -8
- scitex/cli/template.py +129 -12
- scitex/cli/tex.py +15 -8
- scitex/cli/writer.py +132 -8
- scitex/cloud/__init__.py +41 -2
- scitex/config/README.md +1 -1
- scitex/config/__init__.py +16 -2
- scitex/config/_env_registry.py +256 -0
- scitex/context/__init__.py +22 -0
- scitex/dev/__init__.py +20 -1
- scitex/diagram/__init__.py +42 -19
- scitex/diagram/mcp_server.py +13 -125
- scitex/gen/__init__.py +50 -14
- scitex/gen/_list_packages.py +4 -4
- scitex/introspect/__init__.py +82 -0
- scitex/introspect/_call_graph.py +303 -0
- scitex/introspect/_class_hierarchy.py +163 -0
- scitex/introspect/_core.py +41 -0
- scitex/introspect/_docstring.py +131 -0
- scitex/introspect/_examples.py +113 -0
- scitex/introspect/_imports.py +271 -0
- scitex/{gen/_inspect_module.py → introspect/_list_api.py} +43 -54
- scitex/introspect/_mcp/__init__.py +41 -0
- scitex/introspect/_mcp/handlers.py +233 -0
- scitex/introspect/_members.py +155 -0
- scitex/introspect/_resolve.py +89 -0
- scitex/introspect/_signature.py +131 -0
- scitex/introspect/_source.py +80 -0
- scitex/introspect/_type_hints.py +172 -0
- scitex/io/_save.py +1 -2
- scitex/io/bundle/README.md +1 -1
- scitex/logging/_formatters.py +19 -9
- scitex/mcp_server.py +98 -5
- scitex/os/__init__.py +4 -0
- scitex/{gen → os}/_check_host.py +4 -5
- scitex/plt/__init__.py +245 -550
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
- scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/plt/gallery/README.md +1 -1
- scitex/plt/utils/_hitmap/__init__.py +82 -0
- scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
- scitex/plt/utils/_hitmap/_color_application.py +346 -0
- scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
- scitex/plt/utils/_hitmap/_constants.py +40 -0
- scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
- scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
- scitex/plt/utils/_hitmap/_query.py +113 -0
- scitex/plt/utils/_hitmap.py +46 -1616
- scitex/plt/utils/_metadata/__init__.py +80 -0
- scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
- scitex/plt/utils/_metadata/_artists/_base.py +195 -0
- scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
- scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
- scitex/plt/utils/_metadata/_artists/_images.py +80 -0
- scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
- scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
- scitex/plt/utils/_metadata/_artists/_text.py +106 -0
- scitex/plt/utils/_metadata/_csv.py +416 -0
- scitex/plt/utils/_metadata/_detect.py +225 -0
- scitex/plt/utils/_metadata/_legend.py +127 -0
- scitex/plt/utils/_metadata/_rounding.py +117 -0
- scitex/plt/utils/_metadata/_verification.py +202 -0
- scitex/schema/README.md +1 -1
- scitex/scholar/__init__.py +8 -0
- scitex/scholar/_mcp/crossref_handlers.py +265 -0
- scitex/scholar/core/Scholar.py +63 -1700
- scitex/scholar/core/_mixins/__init__.py +36 -0
- scitex/scholar/core/_mixins/_enrichers.py +270 -0
- scitex/scholar/core/_mixins/_library_handlers.py +100 -0
- scitex/scholar/core/_mixins/_loaders.py +103 -0
- scitex/scholar/core/_mixins/_pdf_download.py +375 -0
- scitex/scholar/core/_mixins/_pipeline.py +312 -0
- scitex/scholar/core/_mixins/_project_handlers.py +125 -0
- scitex/scholar/core/_mixins/_savers.py +69 -0
- scitex/scholar/core/_mixins/_search.py +103 -0
- scitex/scholar/core/_mixins/_services.py +88 -0
- scitex/scholar/core/_mixins/_url_finding.py +105 -0
- scitex/scholar/crossref_scitex.py +367 -0
- scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/scholar/examples/00_run_all.sh +120 -0
- scitex/scholar/jobs/_executors.py +27 -3
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
- scitex/scholar/pdf_download/_cli.py +154 -0
- scitex/scholar/pdf_download/strategies/__init__.py +11 -8
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
- scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
- scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
- scitex/scholar/pipelines/_single_steps.py +71 -36
- scitex/scholar/storage/_LibraryManager.py +97 -1695
- scitex/scholar/storage/_mixins/__init__.py +30 -0
- scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
- scitex/scholar/storage/_mixins/_library_operations.py +218 -0
- scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
- scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
- scitex/scholar/storage/_mixins/_resolution.py +376 -0
- scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
- scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
- scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
- scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
- scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
- scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
- scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
- scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
- scitex/security/README.md +3 -3
- scitex/session/README.md +1 -1
- scitex/session/__init__.py +26 -7
- scitex/session/_decorator.py +1 -1
- scitex/sh/README.md +1 -1
- scitex/sh/__init__.py +7 -4
- scitex/social/__init__.py +155 -0
- scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/stats/_mcp/_handlers/__init__.py +31 -0
- scitex/stats/_mcp/_handlers/_corrections.py +113 -0
- scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
- scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
- scitex/stats/_mcp/_handlers/_format.py +94 -0
- scitex/stats/_mcp/_handlers/_normality.py +110 -0
- scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
- scitex/stats/_mcp/_handlers/_power.py +247 -0
- scitex/stats/_mcp/_handlers/_recommend.py +102 -0
- scitex/stats/_mcp/_handlers/_run_test.py +279 -0
- scitex/stats/_mcp/_handlers/_stars.py +48 -0
- scitex/stats/_mcp/handlers.py +19 -1171
- scitex/stats/auto/_stat_style.py +175 -0
- scitex/stats/auto/_style_definitions.py +411 -0
- scitex/stats/auto/_styles.py +22 -620
- scitex/stats/descriptive/__init__.py +11 -8
- scitex/stats/descriptive/_ci.py +39 -0
- scitex/stats/power/_power.py +15 -4
- scitex/str/__init__.py +2 -1
- scitex/str/_title_case.py +63 -0
- scitex/template/README.md +1 -1
- scitex/template/__init__.py +25 -10
- scitex/template/_code_templates.py +147 -0
- scitex/template/_mcp/handlers.py +81 -0
- scitex/template/_mcp/tool_schemas.py +55 -0
- scitex/template/_templates/__init__.py +51 -0
- scitex/template/_templates/audio.py +233 -0
- scitex/template/_templates/canvas.py +312 -0
- scitex/template/_templates/capture.py +268 -0
- scitex/template/_templates/config.py +43 -0
- scitex/template/_templates/diagram.py +294 -0
- scitex/template/_templates/io.py +107 -0
- scitex/template/_templates/module.py +53 -0
- scitex/template/_templates/plt.py +202 -0
- scitex/template/_templates/scholar.py +267 -0
- scitex/template/_templates/session.py +130 -0
- scitex/template/_templates/session_minimal.py +43 -0
- scitex/template/_templates/session_plot.py +67 -0
- scitex/template/_templates/session_stats.py +77 -0
- scitex/template/_templates/stats.py +323 -0
- scitex/template/_templates/writer.py +296 -0
- scitex/template/clone_writer_directory.py +5 -5
- scitex/ui/_backends/_email.py +10 -2
- scitex/ui/_backends/_webhook.py +5 -1
- scitex/web/_search_pubmed.py +10 -6
- scitex/writer/README.md +1 -1
- scitex/writer/_mcp/handlers.py +11 -744
- scitex/writer/_mcp/tool_schemas.py +5 -335
- scitex-2.15.2.dist-info/METADATA +648 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/RECORD +246 -150
- scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
- scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
- scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
- scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
- scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
- scitex/diagram/_compile.py +0 -312
- scitex/diagram/_diagram.py +0 -355
- scitex/diagram/_mcp/__init__.py +0 -4
- scitex/diagram/_mcp/handlers.py +0 -400
- scitex/diagram/_mcp/tool_schemas.py +0 -157
- scitex/diagram/_presets.py +0 -173
- scitex/diagram/_schema.py +0 -182
- scitex/diagram/_split.py +0 -278
- scitex/gen/_ci.py +0 -12
- scitex/gen/_title_case.py +0 -89
- scitex/plt/_mcp/__init__.py +0 -4
- scitex/plt/_mcp/_handlers_annotation.py +0 -102
- scitex/plt/_mcp/_handlers_figure.py +0 -195
- scitex/plt/_mcp/_handlers_plot.py +0 -252
- scitex/plt/_mcp/_handlers_style.py +0 -219
- scitex/plt/_mcp/handlers.py +0 -74
- scitex/plt/_mcp/tool_schemas.py +0 -497
- scitex/plt/mcp_server.py +0 -231
- scitex/scholar/data/.gitkeep +0 -0
- scitex/scholar/data/README.md +0 -44
- scitex/scholar/data/bib_files/bibliography.bib +0 -1952
- scitex/scholar/data/bib_files/neurovista.bib +0 -277
- scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
- scitex/scholar/data/bib_files/openaccess.bib +0 -89
- scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
- scitex/scholar/data/bib_files/pac.bib +0 -698
- scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
- scitex/scholar/data/bib_files/pac_processed.bib +0 -0
- scitex/scholar/data/bib_files/pac_titles.txt +0 -75
- scitex/scholar/data/bib_files/paywalled.bib +0 -98
- scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
- scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
- scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
- scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
- scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_seizure.bib +0 -46
- scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
- scitex/scholar/data/impact_factor.db +0 -0
- scitex/scholar/examples/SUGGESTIONS.md +0 -865
- scitex/scholar/examples/dev.py +0 -38
- scitex-2.14.0.dist-info/METADATA +0 -1238
- /scitex/{gen → context}/_detect_environment.py +0 -0
- /scitex/{gen → context}/_get_notebook_path.py +0 -0
- /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-01-24 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/scholar/core/_mixins/__init__.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Scholar mixin classes for modular functionality.
|
|
7
|
+
|
|
8
|
+
Each mixin provides a specific set of methods for the Scholar class.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from ._enrichers import EnricherMixin
|
|
12
|
+
from ._library_handlers import LibraryHandlerMixin
|
|
13
|
+
from ._loaders import LoaderMixin
|
|
14
|
+
from ._pdf_download import PDFDownloadMixin
|
|
15
|
+
from ._pipeline import PipelineMixin
|
|
16
|
+
from ._project_handlers import ProjectHandlerMixin
|
|
17
|
+
from ._savers import SaverMixin
|
|
18
|
+
from ._search import SearchMixin
|
|
19
|
+
from ._services import ServiceMixin
|
|
20
|
+
from ._url_finding import URLFindingMixin
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"EnricherMixin",
|
|
24
|
+
"URLFindingMixin",
|
|
25
|
+
"PDFDownloadMixin",
|
|
26
|
+
"LoaderMixin",
|
|
27
|
+
"SearchMixin",
|
|
28
|
+
"SaverMixin",
|
|
29
|
+
"ProjectHandlerMixin",
|
|
30
|
+
"LibraryHandlerMixin",
|
|
31
|
+
"PipelineMixin",
|
|
32
|
+
"ServiceMixin",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# EOF
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-01-24 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/scholar/core/_mixins/_enrichers.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Enricher mixin for Scholar class.
|
|
7
|
+
|
|
8
|
+
Provides paper enrichment functionality including metadata enrichment,
|
|
9
|
+
impact factor lookup, and citation count retrieval.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
from copy import deepcopy
|
|
16
|
+
from typing import TYPE_CHECKING, Dict, Optional, Union
|
|
17
|
+
|
|
18
|
+
import nest_asyncio
|
|
19
|
+
|
|
20
|
+
from scitex import logging
|
|
21
|
+
from scitex.scholar.impact_factor.ImpactFactorEngine import ImpactFactorEngine
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from ..Paper import Paper
|
|
25
|
+
from ..Papers import Papers
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class EnricherMixin:
|
|
31
|
+
"""Mixin providing paper enrichment methods."""
|
|
32
|
+
|
|
33
|
+
async def enrich_papers_async(self, papers: Papers) -> Papers:
|
|
34
|
+
"""Async version of enrich_papers for use in async contexts.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
papers: Papers collection to enrich.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
Enriched Papers collection
|
|
42
|
+
"""
|
|
43
|
+
from ..Papers import Papers
|
|
44
|
+
|
|
45
|
+
enriched_list = []
|
|
46
|
+
|
|
47
|
+
for paper in papers:
|
|
48
|
+
try:
|
|
49
|
+
results = await self._scholar_engine.search_async(
|
|
50
|
+
title=paper.metadata.basic.title,
|
|
51
|
+
year=paper.metadata.basic.year,
|
|
52
|
+
authors=(
|
|
53
|
+
paper.metadata.basic.authors[0]
|
|
54
|
+
if paper.metadata.basic.authors
|
|
55
|
+
else None
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
enriched_paper = self._merge_enrichment_data(paper, results)
|
|
60
|
+
enriched_list.append(enriched_paper)
|
|
61
|
+
title = paper.metadata.basic.title or "No title"
|
|
62
|
+
logger.info(f"{self.name}: Enriched: {title[:50]}...")
|
|
63
|
+
|
|
64
|
+
except Exception as e:
|
|
65
|
+
title = paper.metadata.basic.title or "No title"
|
|
66
|
+
logger.warning(
|
|
67
|
+
f"{self.name}: Failed to enrich paper '{title[:50]}...': {e}"
|
|
68
|
+
)
|
|
69
|
+
enriched_list.append(paper)
|
|
70
|
+
|
|
71
|
+
enriched_papers = Papers(enriched_list, project=self.project)
|
|
72
|
+
|
|
73
|
+
if self.config.resolve("enrich_impact_factors", None, True):
|
|
74
|
+
enriched_papers = self._enrich_impact_factors(enriched_papers)
|
|
75
|
+
|
|
76
|
+
return enriched_papers
|
|
77
|
+
|
|
78
|
+
def enrich_papers(
|
|
79
|
+
self, papers: Optional[Papers] = None
|
|
80
|
+
) -> Union[Papers, Dict[str, int]]:
|
|
81
|
+
"""Enrich papers with metadata from multiple sources.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
papers: Papers collection to enrich. If None, enriches all papers
|
|
85
|
+
in current project.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
- If papers provided: Returns enriched Papers collection
|
|
90
|
+
- If no papers: Returns dict with enrichment statistics for project
|
|
91
|
+
"""
|
|
92
|
+
from ..Papers import Papers
|
|
93
|
+
|
|
94
|
+
if papers is None:
|
|
95
|
+
return self._enrich_current_project()
|
|
96
|
+
|
|
97
|
+
enriched_list = []
|
|
98
|
+
nest_asyncio.apply()
|
|
99
|
+
|
|
100
|
+
for paper in papers:
|
|
101
|
+
try:
|
|
102
|
+
results = asyncio.run(
|
|
103
|
+
self._scholar_engine.search_async(
|
|
104
|
+
title=paper.metadata.basic.title,
|
|
105
|
+
year=paper.metadata.basic.year,
|
|
106
|
+
authors=(
|
|
107
|
+
paper.metadata.basic.authors[0]
|
|
108
|
+
if paper.metadata.basic.authors
|
|
109
|
+
else None
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
enriched_paper = self._merge_enrichment_data(paper, results)
|
|
115
|
+
enriched_list.append(enriched_paper)
|
|
116
|
+
title = paper.metadata.basic.title or "No title"
|
|
117
|
+
logger.info(f"{self.name}: Enriched: {title[:50]}...")
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
title = paper.metadata.basic.title or "No title"
|
|
121
|
+
logger.warning(
|
|
122
|
+
f"{self.name}: Failed to enrich paper '{title[:50]}...': {e}"
|
|
123
|
+
)
|
|
124
|
+
enriched_list.append(paper)
|
|
125
|
+
|
|
126
|
+
enriched_papers = Papers(enriched_list, project=self.project)
|
|
127
|
+
|
|
128
|
+
if self.config.resolve("enrich_impact_factors", None, True):
|
|
129
|
+
enriched_papers = self._enrich_impact_factors(enriched_papers)
|
|
130
|
+
|
|
131
|
+
return enriched_papers
|
|
132
|
+
|
|
133
|
+
def _enrich_impact_factors(self, papers: Papers) -> Papers:
|
|
134
|
+
"""Add journal impact factors to papers.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
papers: Papers collection to enrich with impact factors
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
Papers collection with impact factors added where available
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
jcr_engine = ImpactFactorEngine()
|
|
145
|
+
papers = jcr_engine.enrich_papers(papers)
|
|
146
|
+
return papers
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.debug(
|
|
149
|
+
f"{self.name}: JCR engine unavailable: {e}, "
|
|
150
|
+
"falling back to calculation method"
|
|
151
|
+
)
|
|
152
|
+
return papers
|
|
153
|
+
|
|
154
|
+
def _merge_enrichment_data(self, paper: Paper, results: Dict) -> Paper:
|
|
155
|
+
"""Merge enrichment results into paper object.
|
|
156
|
+
|
|
157
|
+
Creates a new Paper object with merged data to avoid modifying the original.
|
|
158
|
+
"""
|
|
159
|
+
enriched = deepcopy(paper)
|
|
160
|
+
|
|
161
|
+
if not results:
|
|
162
|
+
return enriched
|
|
163
|
+
|
|
164
|
+
# ID section
|
|
165
|
+
if "id" in results:
|
|
166
|
+
if results["id"].get("doi") and not enriched.metadata.id.doi:
|
|
167
|
+
enriched.metadata.set_doi(results["id"]["doi"])
|
|
168
|
+
if results["id"].get("pmid") and not enriched.metadata.id.pmid:
|
|
169
|
+
enriched.metadata.id.pmid = results["id"]["pmid"]
|
|
170
|
+
if results["id"].get("arxiv_id") and not enriched.metadata.id.arxiv_id:
|
|
171
|
+
enriched.metadata.id.arxiv_id = results["id"]["arxiv_id"]
|
|
172
|
+
|
|
173
|
+
# Basic metadata section
|
|
174
|
+
if "basic" in results:
|
|
175
|
+
if results["basic"].get("abstract"):
|
|
176
|
+
enriched.metadata.basic.abstract = results["basic"]["abstract"]
|
|
177
|
+
|
|
178
|
+
if results["basic"].get("title"):
|
|
179
|
+
new_title = results["basic"]["title"]
|
|
180
|
+
current_title = enriched.metadata.basic.title or ""
|
|
181
|
+
if not current_title or len(new_title) > len(current_title):
|
|
182
|
+
enriched.metadata.basic.title = new_title
|
|
183
|
+
|
|
184
|
+
if results["basic"].get("authors") and not enriched.metadata.basic.authors:
|
|
185
|
+
enriched.metadata.basic.authors = results["basic"]["authors"]
|
|
186
|
+
|
|
187
|
+
if results["basic"].get("year") and not enriched.metadata.basic.year:
|
|
188
|
+
enriched.metadata.basic.year = results["basic"]["year"]
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
results["basic"].get("keywords")
|
|
192
|
+
and not enriched.metadata.basic.keywords
|
|
193
|
+
):
|
|
194
|
+
enriched.metadata.basic.keywords = results["basic"]["keywords"]
|
|
195
|
+
|
|
196
|
+
# Publication metadata
|
|
197
|
+
if "publication" in results:
|
|
198
|
+
pub = results["publication"]
|
|
199
|
+
meta_pub = enriched.metadata.publication
|
|
200
|
+
if pub.get("journal") and not meta_pub.journal:
|
|
201
|
+
meta_pub.journal = pub["journal"]
|
|
202
|
+
if pub.get("publisher") and not meta_pub.publisher:
|
|
203
|
+
meta_pub.publisher = pub["publisher"]
|
|
204
|
+
if pub.get("volume") and not meta_pub.volume:
|
|
205
|
+
meta_pub.volume = pub["volume"]
|
|
206
|
+
if pub.get("issue") and not meta_pub.issue:
|
|
207
|
+
meta_pub.issue = pub["issue"]
|
|
208
|
+
if pub.get("pages") and not meta_pub.pages:
|
|
209
|
+
meta_pub.pages = pub["pages"]
|
|
210
|
+
|
|
211
|
+
# Citation metadata
|
|
212
|
+
if "citation_count" in results:
|
|
213
|
+
count = results["citation_count"].get("count") or results[
|
|
214
|
+
"citation_count"
|
|
215
|
+
].get("total")
|
|
216
|
+
if count:
|
|
217
|
+
current_count = enriched.metadata.citation_count.total or 0
|
|
218
|
+
if not current_count or count > current_count:
|
|
219
|
+
enriched.metadata.citation_count.total = count
|
|
220
|
+
|
|
221
|
+
# URL metadata
|
|
222
|
+
if "url" in results:
|
|
223
|
+
if results["url"].get("pdf"):
|
|
224
|
+
pdf_url = results["url"]["pdf"]
|
|
225
|
+
if not any(p.get("url") == pdf_url for p in enriched.metadata.url.pdfs):
|
|
226
|
+
enriched.metadata.url.pdfs.append(
|
|
227
|
+
{"url": pdf_url, "source": "enrichment"}
|
|
228
|
+
)
|
|
229
|
+
if results["url"].get("url") and not enriched.metadata.url.publisher:
|
|
230
|
+
enriched.metadata.url.publisher = results["url"]["url"]
|
|
231
|
+
|
|
232
|
+
return enriched
|
|
233
|
+
|
|
234
|
+
def _enrich_current_project(self) -> Dict[str, int]:
|
|
235
|
+
"""Enrich all papers in the current project.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
Dictionary with enrichment statistics
|
|
240
|
+
"""
|
|
241
|
+
if not self.project:
|
|
242
|
+
raise ValueError(
|
|
243
|
+
"No project specified. Use Scholar(project='name') "
|
|
244
|
+
"or provide papers to enrich()."
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
papers = self.load_project(self.project)
|
|
248
|
+
logger.info(
|
|
249
|
+
f"{self.name}: Enriching {len(papers)} papers in project '{self.project}'"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
enriched_papers = self.enrich_papers(papers)
|
|
253
|
+
|
|
254
|
+
enriched_count = sum(
|
|
255
|
+
1
|
|
256
|
+
for i, p in enumerate(enriched_papers)
|
|
257
|
+
if p.abstract and not papers[i].abstract
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
saved_ids = self.save_papers_to_library(enriched_papers)
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
"enriched": enriched_count,
|
|
264
|
+
"failed": len(papers) - enriched_count,
|
|
265
|
+
"total": len(papers),
|
|
266
|
+
"saved": len(saved_ids),
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# EOF
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-01-24 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/scholar/core/_mixins/_library_handlers.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Library handler mixin for Scholar class.
|
|
7
|
+
|
|
8
|
+
Provides library-wide statistics and backup functionality.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import shutil
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Dict, Union
|
|
18
|
+
|
|
19
|
+
from scitex import logging
|
|
20
|
+
from scitex.logging import ScholarError
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LibraryHandlerMixin:
|
|
26
|
+
"""Mixin providing library management methods."""
|
|
27
|
+
|
|
28
|
+
def get_library_statistics(self) -> Dict[str, Any]:
|
|
29
|
+
"""Get comprehensive statistics for the entire Scholar library.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
Dictionary with library-wide statistics
|
|
34
|
+
"""
|
|
35
|
+
master_dir = self.config.get_library_master_dir()
|
|
36
|
+
projects = self.list_projects()
|
|
37
|
+
|
|
38
|
+
stats = {
|
|
39
|
+
"total_projects": len(projects),
|
|
40
|
+
"total_papers": (
|
|
41
|
+
len(list(master_dir.glob("*"))) if master_dir.exists() else 0
|
|
42
|
+
),
|
|
43
|
+
"projects": projects,
|
|
44
|
+
"library_path": str(self.config.path_manager.library_dir),
|
|
45
|
+
"master_path": str(master_dir),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if master_dir.exists():
|
|
49
|
+
total_size = sum(
|
|
50
|
+
f.stat().st_size for f in master_dir.rglob("*") if f.is_file()
|
|
51
|
+
)
|
|
52
|
+
stats["storage_mb"] = total_size / (1024 * 1024)
|
|
53
|
+
else:
|
|
54
|
+
stats["storage_mb"] = 0
|
|
55
|
+
|
|
56
|
+
return stats
|
|
57
|
+
|
|
58
|
+
def backup_library(self, backup_path: Union[str, Path]) -> Dict[str, Any]:
|
|
59
|
+
"""Create a backup of the Scholar library.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
backup_path: Path for the backup
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
Dictionary with backup information
|
|
67
|
+
"""
|
|
68
|
+
backup_path = Path(backup_path)
|
|
69
|
+
library_path = self.config.path_manager.library_dir
|
|
70
|
+
|
|
71
|
+
if not library_path.exists():
|
|
72
|
+
raise ScholarError("Library directory does not exist")
|
|
73
|
+
|
|
74
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
75
|
+
backup_dir = backup_path / f"scholar_library_backup_{timestamp}"
|
|
76
|
+
|
|
77
|
+
logger.info(f"{self.name}: Creating library backup at {backup_dir}")
|
|
78
|
+
shutil.copytree(library_path, backup_dir)
|
|
79
|
+
|
|
80
|
+
backup_info = {
|
|
81
|
+
"timestamp": timestamp,
|
|
82
|
+
"source": str(library_path),
|
|
83
|
+
"backup": str(backup_dir),
|
|
84
|
+
"size_mb": sum(
|
|
85
|
+
f.stat().st_size for f in backup_dir.rglob("*") if f.is_file()
|
|
86
|
+
)
|
|
87
|
+
/ (1024 * 1024),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
metadata_file = backup_dir / "backup_metadata.json"
|
|
91
|
+
with open(metadata_file, "w") as f:
|
|
92
|
+
json.dump(backup_info, f, indent=2)
|
|
93
|
+
|
|
94
|
+
logger.info(
|
|
95
|
+
f"{self.name}: Library backup completed: {backup_info['size_mb']:.2f} MB"
|
|
96
|
+
)
|
|
97
|
+
return backup_info
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# EOF
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-01-24 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/scholar/core/_mixins/_loaders.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Loader mixin for Scholar class.
|
|
7
|
+
|
|
8
|
+
Provides methods for loading papers from projects and BibTeX files.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
16
|
+
|
|
17
|
+
from scitex import logging
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from ..Papers import Papers
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LoaderMixin:
|
|
26
|
+
"""Mixin providing paper loading methods."""
|
|
27
|
+
|
|
28
|
+
def load_project(self, project: Optional[str] = None) -> Papers:
|
|
29
|
+
"""Load papers from a project using library manager service.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
project: Project name (uses self.project if None)
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
Papers collection from the project
|
|
37
|
+
"""
|
|
38
|
+
from ..Paper import Paper
|
|
39
|
+
from ..Papers import Papers
|
|
40
|
+
|
|
41
|
+
project_name = project or self.project
|
|
42
|
+
if not project_name:
|
|
43
|
+
raise ValueError("No project specified")
|
|
44
|
+
|
|
45
|
+
logger.info(f"{self.name}: Loading papers from project: {project_name}")
|
|
46
|
+
|
|
47
|
+
library_dir = self.config.path_manager.library_dir
|
|
48
|
+
project_dir = library_dir / project_name
|
|
49
|
+
|
|
50
|
+
if not project_dir.exists():
|
|
51
|
+
logger.warning(
|
|
52
|
+
f"{self.name}: Project directory does not exist: {project_dir}"
|
|
53
|
+
)
|
|
54
|
+
return Papers([], project=project_name)
|
|
55
|
+
|
|
56
|
+
papers = []
|
|
57
|
+
for item in project_dir.iterdir():
|
|
58
|
+
if item.name in ["info", "project_metadata.json", "README.md"]:
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
if item.is_symlink():
|
|
62
|
+
master_path = item.resolve()
|
|
63
|
+
if master_path.exists():
|
|
64
|
+
metadata_file = master_path / "metadata.json"
|
|
65
|
+
if metadata_file.exists():
|
|
66
|
+
try:
|
|
67
|
+
with open(metadata_file) as f:
|
|
68
|
+
metadata = json.load(f)
|
|
69
|
+
|
|
70
|
+
paper = Paper.from_dict(metadata)
|
|
71
|
+
papers.append(paper)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.warning(
|
|
74
|
+
f"{self.name}: Failed to load metadata "
|
|
75
|
+
f"from {metadata_file}: {e}"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
logger.info(
|
|
79
|
+
f"{self.name}: Loaded {len(papers)} papers from project: {project_name}"
|
|
80
|
+
)
|
|
81
|
+
return Papers(papers, project=project_name)
|
|
82
|
+
|
|
83
|
+
def load_bibtex(self, bibtex_input: Union[str, Path]) -> Papers:
|
|
84
|
+
"""Load Papers collection from BibTeX file or content.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
bibtex_input: BibTeX file path or content string
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
Papers collection
|
|
92
|
+
"""
|
|
93
|
+
from ..Papers import Papers
|
|
94
|
+
|
|
95
|
+
papers = self._library.papers_from_bibtex(bibtex_input)
|
|
96
|
+
|
|
97
|
+
papers_collection = Papers(papers, config=self.config, project=self.project)
|
|
98
|
+
papers_collection.library = self._library
|
|
99
|
+
|
|
100
|
+
return papers_collection
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# EOF
|