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.
Files changed (300) hide show
  1. scitex/__init__.py +71 -17
  2. scitex/_env_loader.py +156 -0
  3. scitex/_mcp_resources/__init__.py +37 -0
  4. scitex/_mcp_resources/_cheatsheet.py +135 -0
  5. scitex/_mcp_resources/_figrecipe.py +138 -0
  6. scitex/_mcp_resources/_formats.py +102 -0
  7. scitex/_mcp_resources/_modules.py +337 -0
  8. scitex/_mcp_resources/_session.py +149 -0
  9. scitex/_mcp_tools/__init__.py +4 -0
  10. scitex/_mcp_tools/audio.py +66 -0
  11. scitex/_mcp_tools/diagram.py +11 -95
  12. scitex/_mcp_tools/introspect.py +210 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +244 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +21 -204
  18. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  19. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  20. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  21. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  22. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  23. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  24. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  25. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  26. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  27. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  28. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
  29. scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
  30. scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
  31. scitex/audio/README.md +40 -36
  32. scitex/audio/__init__.py +129 -61
  33. scitex/audio/_branding.py +185 -0
  34. scitex/audio/_mcp/__init__.py +32 -0
  35. scitex/audio/_mcp/handlers.py +59 -6
  36. scitex/audio/_mcp/speak_handlers.py +238 -0
  37. scitex/audio/_relay.py +225 -0
  38. scitex/audio/_tts.py +18 -10
  39. scitex/audio/engines/base.py +17 -10
  40. scitex/audio/engines/elevenlabs_engine.py +7 -2
  41. scitex/audio/mcp_server.py +228 -75
  42. scitex/canvas/README.md +1 -1
  43. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  44. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  45. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  46. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  47. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  48. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  49. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  50. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  51. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  52. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  53. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  54. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  55. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  56. scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
  57. scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
  58. scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
  59. scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
  60. scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
  61. scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
  62. scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
  63. scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
  64. scitex/canvas/editor/flask_editor/_core.py +25 -1684
  65. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  66. scitex/cli/__init__.py +38 -43
  67. scitex/cli/audio.py +76 -27
  68. scitex/cli/capture.py +13 -20
  69. scitex/cli/introspect.py +481 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +357 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +23 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +314 -0
  79. scitex/cli/stats.py +15 -8
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +132 -8
  83. scitex/cloud/__init__.py +41 -2
  84. scitex/config/README.md +1 -1
  85. scitex/config/__init__.py +16 -2
  86. scitex/config/_env_registry.py +256 -0
  87. scitex/context/__init__.py +22 -0
  88. scitex/dev/__init__.py +20 -1
  89. scitex/diagram/__init__.py +42 -19
  90. scitex/diagram/mcp_server.py +13 -125
  91. scitex/gen/__init__.py +50 -14
  92. scitex/gen/_list_packages.py +4 -4
  93. scitex/introspect/__init__.py +82 -0
  94. scitex/introspect/_call_graph.py +303 -0
  95. scitex/introspect/_class_hierarchy.py +163 -0
  96. scitex/introspect/_core.py +41 -0
  97. scitex/introspect/_docstring.py +131 -0
  98. scitex/introspect/_examples.py +113 -0
  99. scitex/introspect/_imports.py +271 -0
  100. scitex/{gen/_inspect_module.py → introspect/_list_api.py} +43 -54
  101. scitex/introspect/_mcp/__init__.py +41 -0
  102. scitex/introspect/_mcp/handlers.py +233 -0
  103. scitex/introspect/_members.py +155 -0
  104. scitex/introspect/_resolve.py +89 -0
  105. scitex/introspect/_signature.py +131 -0
  106. scitex/introspect/_source.py +80 -0
  107. scitex/introspect/_type_hints.py +172 -0
  108. scitex/io/_save.py +1 -2
  109. scitex/io/bundle/README.md +1 -1
  110. scitex/logging/_formatters.py +19 -9
  111. scitex/mcp_server.py +98 -5
  112. scitex/os/__init__.py +4 -0
  113. scitex/{gen → os}/_check_host.py +4 -5
  114. scitex/plt/__init__.py +245 -550
  115. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  116. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  117. scitex/plt/gallery/README.md +1 -1
  118. scitex/plt/utils/_hitmap/__init__.py +82 -0
  119. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  120. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  121. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  122. scitex/plt/utils/_hitmap/_constants.py +40 -0
  123. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  124. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  125. scitex/plt/utils/_hitmap/_query.py +113 -0
  126. scitex/plt/utils/_hitmap.py +46 -1616
  127. scitex/plt/utils/_metadata/__init__.py +80 -0
  128. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  129. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  130. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  131. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  132. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  133. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  134. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  135. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  136. scitex/plt/utils/_metadata/_csv.py +416 -0
  137. scitex/plt/utils/_metadata/_detect.py +225 -0
  138. scitex/plt/utils/_metadata/_legend.py +127 -0
  139. scitex/plt/utils/_metadata/_rounding.py +117 -0
  140. scitex/plt/utils/_metadata/_verification.py +202 -0
  141. scitex/schema/README.md +1 -1
  142. scitex/scholar/__init__.py +8 -0
  143. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  144. scitex/scholar/core/Scholar.py +63 -1700
  145. scitex/scholar/core/_mixins/__init__.py +36 -0
  146. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  147. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  148. scitex/scholar/core/_mixins/_loaders.py +103 -0
  149. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  150. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  151. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  152. scitex/scholar/core/_mixins/_savers.py +69 -0
  153. scitex/scholar/core/_mixins/_search.py +103 -0
  154. scitex/scholar/core/_mixins/_services.py +88 -0
  155. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  156. scitex/scholar/crossref_scitex.py +367 -0
  157. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  158. scitex/scholar/examples/00_run_all.sh +120 -0
  159. scitex/scholar/jobs/_executors.py +27 -3
  160. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  161. scitex/scholar/pdf_download/_cli.py +154 -0
  162. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  163. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  164. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  165. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  166. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  167. scitex/scholar/pipelines/_single_steps.py +71 -36
  168. scitex/scholar/storage/_LibraryManager.py +97 -1695
  169. scitex/scholar/storage/_mixins/__init__.py +30 -0
  170. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  171. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  172. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  173. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  174. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  175. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  176. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  177. scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
  178. scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
  179. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
  180. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
  181. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
  182. scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
  183. scitex/security/README.md +3 -3
  184. scitex/session/README.md +1 -1
  185. scitex/session/__init__.py +26 -7
  186. scitex/session/_decorator.py +1 -1
  187. scitex/sh/README.md +1 -1
  188. scitex/sh/__init__.py +7 -4
  189. scitex/social/__init__.py +155 -0
  190. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  191. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  192. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  193. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  194. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  195. scitex/stats/_mcp/_handlers/_format.py +94 -0
  196. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  197. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  198. scitex/stats/_mcp/_handlers/_power.py +247 -0
  199. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  200. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  201. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  202. scitex/stats/_mcp/handlers.py +19 -1171
  203. scitex/stats/auto/_stat_style.py +175 -0
  204. scitex/stats/auto/_style_definitions.py +411 -0
  205. scitex/stats/auto/_styles.py +22 -620
  206. scitex/stats/descriptive/__init__.py +11 -8
  207. scitex/stats/descriptive/_ci.py +39 -0
  208. scitex/stats/power/_power.py +15 -4
  209. scitex/str/__init__.py +2 -1
  210. scitex/str/_title_case.py +63 -0
  211. scitex/template/README.md +1 -1
  212. scitex/template/__init__.py +25 -10
  213. scitex/template/_code_templates.py +147 -0
  214. scitex/template/_mcp/handlers.py +81 -0
  215. scitex/template/_mcp/tool_schemas.py +55 -0
  216. scitex/template/_templates/__init__.py +51 -0
  217. scitex/template/_templates/audio.py +233 -0
  218. scitex/template/_templates/canvas.py +312 -0
  219. scitex/template/_templates/capture.py +268 -0
  220. scitex/template/_templates/config.py +43 -0
  221. scitex/template/_templates/diagram.py +294 -0
  222. scitex/template/_templates/io.py +107 -0
  223. scitex/template/_templates/module.py +53 -0
  224. scitex/template/_templates/plt.py +202 -0
  225. scitex/template/_templates/scholar.py +267 -0
  226. scitex/template/_templates/session.py +130 -0
  227. scitex/template/_templates/session_minimal.py +43 -0
  228. scitex/template/_templates/session_plot.py +67 -0
  229. scitex/template/_templates/session_stats.py +77 -0
  230. scitex/template/_templates/stats.py +323 -0
  231. scitex/template/_templates/writer.py +296 -0
  232. scitex/template/clone_writer_directory.py +5 -5
  233. scitex/ui/_backends/_email.py +10 -2
  234. scitex/ui/_backends/_webhook.py +5 -1
  235. scitex/web/_search_pubmed.py +10 -6
  236. scitex/writer/README.md +1 -1
  237. scitex/writer/_mcp/handlers.py +11 -744
  238. scitex/writer/_mcp/tool_schemas.py +5 -335
  239. scitex-2.15.2.dist-info/METADATA +648 -0
  240. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/RECORD +246 -150
  241. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  242. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  243. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
  244. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
  245. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
  246. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
  247. scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
  248. scitex/diagram/_compile.py +0 -312
  249. scitex/diagram/_diagram.py +0 -355
  250. scitex/diagram/_mcp/__init__.py +0 -4
  251. scitex/diagram/_mcp/handlers.py +0 -400
  252. scitex/diagram/_mcp/tool_schemas.py +0 -157
  253. scitex/diagram/_presets.py +0 -173
  254. scitex/diagram/_schema.py +0 -182
  255. scitex/diagram/_split.py +0 -278
  256. scitex/gen/_ci.py +0 -12
  257. scitex/gen/_title_case.py +0 -89
  258. scitex/plt/_mcp/__init__.py +0 -4
  259. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  260. scitex/plt/_mcp/_handlers_figure.py +0 -195
  261. scitex/plt/_mcp/_handlers_plot.py +0 -252
  262. scitex/plt/_mcp/_handlers_style.py +0 -219
  263. scitex/plt/_mcp/handlers.py +0 -74
  264. scitex/plt/_mcp/tool_schemas.py +0 -497
  265. scitex/plt/mcp_server.py +0 -231
  266. scitex/scholar/data/.gitkeep +0 -0
  267. scitex/scholar/data/README.md +0 -44
  268. scitex/scholar/data/bib_files/bibliography.bib +0 -1952
  269. scitex/scholar/data/bib_files/neurovista.bib +0 -277
  270. scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
  271. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
  272. scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
  273. scitex/scholar/data/bib_files/openaccess.bib +0 -89
  274. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
  275. scitex/scholar/data/bib_files/pac.bib +0 -698
  276. scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
  277. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  278. scitex/scholar/data/bib_files/pac_titles.txt +0 -75
  279. scitex/scholar/data/bib_files/paywalled.bib +0 -98
  280. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
  281. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
  282. scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
  283. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  284. scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
  285. scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
  286. scitex/scholar/data/bib_files/test_seizure.bib +0 -46
  287. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  288. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  289. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  290. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  291. scitex/scholar/data/impact_factor.db +0 -0
  292. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  293. scitex/scholar/examples/dev.py +0 -38
  294. scitex-2.14.0.dist-info/METADATA +0 -1238
  295. /scitex/{gen → context}/_detect_environment.py +0 -0
  296. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  297. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  298. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
  299. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
  300. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,149 @@
1
+ # External Package Branding Guide
2
+
3
+ When scitex wraps external packages (like `figrecipe` for `scitex.plt` or `crossref-local` for `scitex.scholar`), those packages should support configurable branding so documentation and error messages show the scitex namespace.
4
+
5
+ ## When to Use Branding
6
+
7
+ - **Use branding**: External packages that scitex wraps (figrecipe, crossref-local, etc.)
8
+ - **Don't use branding**: Internal scitex modules (scitex.audio, scitex.stats, etc.) - just hardcode `SCITEX_*` prefix
9
+
10
+ ## Pattern Overview
11
+
12
+ The external package provides a `_branding.py` module that:
13
+ 1. Reads brand name from environment variable
14
+ 2. Derives environment variable prefix from brand name
15
+ 3. Provides helper functions for rebranding text/docstrings
16
+
17
+ ## Implementation Template
18
+
19
+ ```python
20
+ # external_package/_branding.py
21
+
22
+ import os
23
+ import re
24
+ from typing import Optional
25
+
26
+ # Environment variables for branding
27
+ # Parent package sets these before importing
28
+ BRAND_NAME = os.environ.get("{PACKAGE}_BRAND", "{package}")
29
+ BRAND_ALIAS = os.environ.get("{PACKAGE}_ALIAS", "{alias}")
30
+
31
+ # Original values for replacement
32
+ _ORIGINAL_NAME = "{package}"
33
+ _ORIGINAL_ALIAS = "{alias}"
34
+
35
+
36
+ def _brand_to_env_prefix(brand: str) -> str:
37
+ """Convert brand name to environment variable prefix.
38
+
39
+ Examples:
40
+ "figrecipe" -> "FIGRECIPE"
41
+ "scitex.plt" -> "SCITEX_PLT"
42
+ "crossref-local" -> "CROSSREF_LOCAL"
43
+ """
44
+ return brand.upper().replace(".", "_").replace("-", "_")
45
+
46
+
47
+ # Environment variable prefix based on brand
48
+ ENV_PREFIX = _brand_to_env_prefix(BRAND_NAME)
49
+
50
+
51
+ def get_env(key: str, default: Optional[str] = None) -> Optional[str]:
52
+ """Get environment variable with brand-aware prefix.
53
+
54
+ Checks {ENV_PREFIX}_{key} first, then falls back to original prefix.
55
+ """
56
+ value = os.environ.get(f"{ENV_PREFIX}_{key}")
57
+ if value is not None:
58
+ return value
59
+
60
+ # Fall back to original prefix if different
61
+ original_prefix = _brand_to_env_prefix(_ORIGINAL_NAME)
62
+ if ENV_PREFIX != original_prefix:
63
+ value = os.environ.get(f"{original_prefix}_{key}")
64
+ if value is not None:
65
+ return value
66
+
67
+ return default
68
+
69
+
70
+ def rebrand_text(text: Optional[str]) -> Optional[str]:
71
+ """Apply branding to a text string (docstrings, error messages)."""
72
+ if text is None:
73
+ return None
74
+
75
+ if BRAND_NAME == _ORIGINAL_NAME and BRAND_ALIAS == _ORIGINAL_ALIAS:
76
+ return text
77
+
78
+ result = text
79
+
80
+ # Replace import statements
81
+ result = re.sub(
82
+ rf"import\s+{_ORIGINAL_NAME}\s+as\s+{_ORIGINAL_ALIAS}",
83
+ f"import {BRAND_NAME} as {BRAND_ALIAS}",
84
+ result,
85
+ )
86
+
87
+ # Replace "from package" statements
88
+ result = re.sub(
89
+ rf"from\s+{_ORIGINAL_NAME}(\s+import|\s*\.)",
90
+ lambda m: f"from {BRAND_NAME}{m.group(1)}",
91
+ result,
92
+ )
93
+
94
+ return result
95
+
96
+
97
+ def get_mcp_server_name() -> str:
98
+ """Get the MCP server name based on branding."""
99
+ return BRAND_NAME.replace(".", "-")
100
+ ```
101
+
102
+ ## Usage in Parent Package (scitex)
103
+
104
+ ```python
105
+ # scitex/plt/__init__.py
106
+ import os
107
+
108
+ # Set branding BEFORE importing the external package
109
+ os.environ["FIGRECIPE_BRAND"] = "scitex.plt"
110
+ os.environ["FIGRECIPE_ALIAS"] = "plt"
111
+
112
+ # Now import - docstrings will show scitex.plt instead of figrecipe
113
+ from figrecipe import *
114
+ ```
115
+
116
+ ## Port Scheme
117
+
118
+ SciTeX uses port scheme 3129X (sa-i-te-ku-su → 3-1-2-9 in Japanese):
119
+
120
+ | Port | Service |
121
+ |-------|------------------|
122
+ | 31290 | scitex-cloud |
123
+ | 31291 | crossref-local |
124
+ | 31292 | openalex |
125
+ | 31293 | scitex-audio |
126
+
127
+ ## Environment Variable Pattern
128
+
129
+ External packages should use `{ENV_PREFIX}_{SETTING}`:
130
+
131
+ ```
132
+ SCITEX_PLT_MODE=local
133
+ CROSSREF_LOCAL_API_URL=http://localhost:31291
134
+ SCITEX_AUDIO_RELAY_URL=http://localhost:31293
135
+ ```
136
+
137
+ ## Example: crossref-local
138
+
139
+ See GitHub Issue: https://github.com/ywatanabe1989/crossref-local/issues/11
140
+
141
+ The crossref-local package should implement:
142
+ - `CROSSREF_LOCAL_BRAND` / `CROSSREF_LOCAL_ALIAS` env vars
143
+ - Dynamic `ENV_PREFIX` derived from brand name
144
+ - When used via scitex.scholar, shows `scitex.scholar` in docs
145
+
146
+ ## References
147
+
148
+ - figrecipe/_branding.py - Reference implementation
149
+ - scitex/audio/_branding.py - Simple internal module (no rebranding needed)
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-25
3
+ # File: src/scitex/stats/_mcp/_handlers/__init__.py
4
+
5
+ """Stats MCP handler implementations split into modules."""
6
+
7
+ from ._corrections import correct_pvalues_handler
8
+ from ._descriptive import describe_handler
9
+ from ._effect_size import effect_size_handler
10
+ from ._format import format_results_handler
11
+ from ._normality import normality_test_handler
12
+ from ._posthoc import posthoc_test_handler
13
+ from ._power import power_analysis_handler
14
+ from ._recommend import recommend_tests_handler
15
+ from ._run_test import run_test_handler
16
+ from ._stars import p_to_stars_handler
17
+
18
+ __all__ = [
19
+ "recommend_tests_handler",
20
+ "run_test_handler",
21
+ "format_results_handler",
22
+ "power_analysis_handler",
23
+ "correct_pvalues_handler",
24
+ "describe_handler",
25
+ "effect_size_handler",
26
+ "normality_test_handler",
27
+ "posthoc_test_handler",
28
+ "p_to_stars_handler",
29
+ ]
30
+
31
+ # EOF
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-25
3
+ # File: src/scitex/stats/_mcp/_handlers/_corrections.py
4
+
5
+ """P-value correction handler for multiple comparisons."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ from datetime import datetime
11
+
12
+ import numpy as np
13
+
14
+ __all__ = ["correct_pvalues_handler"]
15
+
16
+
17
+ async def correct_pvalues_handler(
18
+ pvalues: list[float],
19
+ method: str = "fdr_bh",
20
+ alpha: float = 0.05,
21
+ ) -> dict:
22
+ """Apply multiple comparison correction to p-values."""
23
+ try:
24
+ loop = asyncio.get_event_loop()
25
+
26
+ def do_correct():
27
+ from statsmodels.stats.multitest import multipletests
28
+
29
+ # Map method names
30
+ method_map = {
31
+ "bonferroni": "bonferroni",
32
+ "fdr_bh": "fdr_bh",
33
+ "fdr_by": "fdr_by",
34
+ "holm": "holm",
35
+ "sidak": "sidak",
36
+ }
37
+ sm_method = method_map.get(method, "fdr_bh")
38
+
39
+ pvals = np.array(pvalues)
40
+ reject, pvals_corrected, _, _ = multipletests(
41
+ pvals, alpha=alpha, method=sm_method
42
+ )
43
+
44
+ return {
45
+ "original_pvalues": pvalues,
46
+ "corrected_pvalues": pvals_corrected.tolist(),
47
+ "reject_null": reject.tolist(),
48
+ "n_significant": int(reject.sum()),
49
+ "n_tests": len(pvalues),
50
+ }
51
+
52
+ result = await loop.run_in_executor(None, do_correct)
53
+
54
+ return {
55
+ "success": True,
56
+ "method": method,
57
+ "alpha": alpha,
58
+ **result,
59
+ "timestamp": datetime.now().isoformat(),
60
+ }
61
+
62
+ except ImportError:
63
+ # Fallback implementation without statsmodels
64
+ try:
65
+ n = len(pvalues)
66
+ pvals = np.array(pvalues)
67
+
68
+ if method == "bonferroni":
69
+ corrected = np.minimum(pvals * n, 1.0)
70
+ elif method == "holm":
71
+ sorted_idx = np.argsort(pvals)
72
+ corrected = np.empty(n)
73
+ cummax = 0.0
74
+ for rank, idx in enumerate(sorted_idx, start=1):
75
+ adj = min((n - rank + 1) * pvals[idx], 1.0)
76
+ adj = max(adj, cummax)
77
+ corrected[idx] = adj
78
+ cummax = adj
79
+ elif method == "fdr_bh":
80
+ sorted_idx = np.argsort(pvals)
81
+ corrected = np.empty(n)
82
+ prev = 1.0
83
+ for rank in range(n, 0, -1):
84
+ idx = sorted_idx[rank - 1]
85
+ bh = pvals[idx] * n / rank
86
+ val = min(bh, prev, 1.0)
87
+ corrected[idx] = val
88
+ prev = val
89
+ elif method == "sidak":
90
+ corrected = 1 - (1 - pvals) ** n
91
+ else:
92
+ corrected = pvals
93
+
94
+ return {
95
+ "success": True,
96
+ "method": method,
97
+ "alpha": alpha,
98
+ "original_pvalues": pvalues,
99
+ "corrected_pvalues": corrected.tolist(),
100
+ "reject_null": (corrected < alpha).tolist(),
101
+ "n_significant": int((corrected < alpha).sum()),
102
+ "n_tests": n,
103
+ "timestamp": datetime.now().isoformat(),
104
+ }
105
+
106
+ except Exception as e:
107
+ return {"success": False, "error": str(e)}
108
+
109
+ except Exception as e:
110
+ return {"success": False, "error": str(e)}
111
+
112
+
113
+ # EOF
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-25
3
+ # File: src/scitex/stats/_mcp/_handlers/_descriptive.py
4
+
5
+ """Descriptive statistics handler."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ from datetime import datetime
11
+
12
+ import numpy as np
13
+
14
+ __all__ = ["describe_handler"]
15
+
16
+
17
+ async def describe_handler(
18
+ data: list[float],
19
+ percentiles: list[float] | None = None,
20
+ ) -> dict:
21
+ """Calculate descriptive statistics for data."""
22
+ try:
23
+ loop = asyncio.get_event_loop()
24
+
25
+ def do_describe():
26
+ arr = np.array(data, dtype=float)
27
+ arr = arr[~np.isnan(arr)] # Remove NaN
28
+
29
+ if len(arr) == 0:
30
+ return {"error": "No valid data points"}
31
+
32
+ percs = percentiles or [25, 50, 75]
33
+ percentile_values = np.percentile(arr, percs)
34
+
35
+ result = {
36
+ "n": int(len(arr)),
37
+ "mean": float(np.mean(arr)),
38
+ "std": float(np.std(arr, ddof=1)) if len(arr) > 1 else 0.0,
39
+ "var": float(np.var(arr, ddof=1)) if len(arr) > 1 else 0.0,
40
+ "sem": (
41
+ float(np.std(arr, ddof=1) / np.sqrt(len(arr)))
42
+ if len(arr) > 1
43
+ else 0.0
44
+ ),
45
+ "min": float(np.min(arr)),
46
+ "max": float(np.max(arr)),
47
+ "range": float(np.max(arr) - np.min(arr)),
48
+ "median": float(np.median(arr)),
49
+ "percentiles": {
50
+ str(int(p)): float(v) for p, v in zip(percs, percentile_values)
51
+ },
52
+ "iqr": float(np.percentile(arr, 75) - np.percentile(arr, 25)),
53
+ }
54
+
55
+ # Add skewness and kurtosis if scipy available
56
+ try:
57
+ from scipy import stats as scipy_stats
58
+
59
+ result["skewness"] = float(scipy_stats.skew(arr))
60
+ result["kurtosis"] = float(scipy_stats.kurtosis(arr))
61
+ except ImportError:
62
+ pass
63
+
64
+ return result
65
+
66
+ result = await loop.run_in_executor(None, do_describe)
67
+
68
+ return {
69
+ "success": True,
70
+ **result,
71
+ "timestamp": datetime.now().isoformat(),
72
+ }
73
+
74
+ except Exception as e:
75
+ return {"success": False, "error": str(e)}
76
+
77
+
78
+ # EOF
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-25
3
+ # File: src/scitex/stats/_mcp/_handlers/_effect_size.py
4
+
5
+ """Effect size calculation handler."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ from datetime import datetime
11
+
12
+ import numpy as np
13
+
14
+ __all__ = ["effect_size_handler"]
15
+
16
+
17
+ async def effect_size_handler(
18
+ group1: list[float],
19
+ group2: list[float],
20
+ measure: str = "cohens_d",
21
+ pooled: bool = True,
22
+ ) -> dict:
23
+ """Calculate effect size between groups."""
24
+ try:
25
+ from scitex.stats.effect_sizes import (
26
+ cliffs_delta,
27
+ cohens_d,
28
+ interpret_cliffs_delta,
29
+ interpret_cohens_d,
30
+ )
31
+
32
+ loop = asyncio.get_event_loop()
33
+
34
+ def do_effect_size():
35
+ g1 = np.array(group1, dtype=float)
36
+ g2 = np.array(group2, dtype=float)
37
+
38
+ result = {}
39
+
40
+ if measure == "cohens_d":
41
+ d = cohens_d(g1, g2)
42
+ result = {
43
+ "measure": "Cohen's d",
44
+ "value": float(d),
45
+ "interpretation": interpret_cohens_d(d),
46
+ }
47
+
48
+ elif measure == "hedges_g":
49
+ # Hedges' g is Cohen's d with bias correction
50
+ d = cohens_d(g1, g2)
51
+ n1, n2 = len(g1), len(g2)
52
+ correction = 1 - (3 / (4 * (n1 + n2) - 9))
53
+ g = d * correction
54
+ result = {
55
+ "measure": "Hedges' g",
56
+ "value": float(g),
57
+ "interpretation": interpret_cohens_d(g), # Same thresholds
58
+ }
59
+
60
+ elif measure == "glass_delta":
61
+ # Glass's delta uses only control group std
62
+ mean_diff = np.mean(g1) - np.mean(g2)
63
+ delta = mean_diff / np.std(g2, ddof=1)
64
+ result = {
65
+ "measure": "Glass's delta",
66
+ "value": float(delta),
67
+ "interpretation": interpret_cohens_d(delta),
68
+ }
69
+
70
+ elif measure == "cliffs_delta":
71
+ delta = cliffs_delta(g1, g2)
72
+ result = {
73
+ "measure": "Cliff's delta",
74
+ "value": float(delta),
75
+ "interpretation": interpret_cliffs_delta(delta),
76
+ }
77
+
78
+ else:
79
+ raise ValueError(f"Unknown measure: {measure}")
80
+
81
+ # Add confidence interval approximation for Cohen's d
82
+ if measure in ["cohens_d", "hedges_g", "glass_delta"]:
83
+ n1, n2 = len(g1), len(g2)
84
+ se = np.sqrt(
85
+ (n1 + n2) / (n1 * n2) + result["value"] ** 2 / (2 * (n1 + n2))
86
+ )
87
+ result["ci_lower"] = float(result["value"] - 1.96 * se)
88
+ result["ci_upper"] = float(result["value"] + 1.96 * se)
89
+
90
+ return result
91
+
92
+ result = await loop.run_in_executor(None, do_effect_size)
93
+
94
+ return {
95
+ "success": True,
96
+ "group1_n": len(group1),
97
+ "group2_n": len(group2),
98
+ **result,
99
+ "timestamp": datetime.now().isoformat(),
100
+ }
101
+
102
+ except Exception as e:
103
+ return {"success": False, "error": str(e)}
104
+
105
+
106
+ # EOF
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-25
3
+ # File: src/scitex/stats/_mcp/_handlers/_format.py
4
+
5
+ """Results formatting handler."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ from datetime import datetime
11
+
12
+ __all__ = ["format_results_handler"]
13
+
14
+
15
+ async def format_results_handler(
16
+ test_name: str,
17
+ statistic: float,
18
+ p_value: float,
19
+ df: float | None = None,
20
+ effect_size: float | None = None,
21
+ effect_size_name: str | None = None,
22
+ style: str = "apa",
23
+ ci_lower: float | None = None,
24
+ ci_upper: float | None = None,
25
+ ) -> dict:
26
+ """Format statistical results in journal style."""
27
+ try:
28
+ loop = asyncio.get_event_loop()
29
+
30
+ def do_format():
31
+ from scitex.stats.auto import format_test_line, p_to_stars
32
+ from scitex.stats.auto._formatting import EffectResultDict, TestResultDict
33
+
34
+ # Build test result dict
35
+ test_result: TestResultDict = {
36
+ "test_name": test_name,
37
+ "stat": statistic,
38
+ "p_raw": p_value,
39
+ }
40
+ if df is not None:
41
+ test_result["df"] = df
42
+
43
+ # Build effect result if provided
44
+ effects = None
45
+ if effect_size is not None:
46
+ effects = [
47
+ EffectResultDict(
48
+ name=effect_size_name or "d",
49
+ label=effect_size_name or "Cohen's d",
50
+ value=effect_size,
51
+ ci_lower=ci_lower,
52
+ ci_upper=ci_upper,
53
+ )
54
+ ]
55
+
56
+ # Map style names
57
+ style_map = {
58
+ "apa": "apa_latex",
59
+ "nature": "nature",
60
+ "science": "science",
61
+ "brief": "brief",
62
+ }
63
+ style_id = style_map.get(style, "apa_latex")
64
+
65
+ # Format the line
66
+ formatted = format_test_line(
67
+ test_result,
68
+ effects=effects,
69
+ style=style_id,
70
+ include_n=False,
71
+ )
72
+
73
+ # Get stars representation
74
+ stars = p_to_stars(p_value)
75
+
76
+ return {
77
+ "formatted": formatted,
78
+ "stars": stars,
79
+ }
80
+
81
+ result = await loop.run_in_executor(None, do_format)
82
+
83
+ return {
84
+ "success": True,
85
+ "style": style,
86
+ **result,
87
+ "timestamp": datetime.now().isoformat(),
88
+ }
89
+
90
+ except Exception as e:
91
+ return {"success": False, "error": str(e)}
92
+
93
+
94
+ # EOF
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-25
3
+ # File: src/scitex/stats/_mcp/_handlers/_normality.py
4
+
5
+ """Normality test handler."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ from datetime import datetime
11
+
12
+ import numpy as np
13
+
14
+ __all__ = ["normality_test_handler"]
15
+
16
+
17
+ async def normality_test_handler(
18
+ data: list[float],
19
+ method: str = "shapiro",
20
+ ) -> dict:
21
+ """Test whether data follows a normal distribution."""
22
+ try:
23
+ from scipy import stats as scipy_stats
24
+
25
+ loop = asyncio.get_event_loop()
26
+
27
+ def do_normality():
28
+ arr = np.array(data, dtype=float)
29
+ arr = arr[~np.isnan(arr)]
30
+
31
+ if len(arr) < 3:
32
+ return {"error": "Need at least 3 data points"}
33
+
34
+ result = {}
35
+
36
+ if method == "shapiro":
37
+ stat, p_value = scipy_stats.shapiro(arr)
38
+ result = {
39
+ "test": "Shapiro-Wilk",
40
+ "statistic": float(stat),
41
+ "statistic_name": "W",
42
+ "p_value": float(p_value),
43
+ }
44
+
45
+ elif method == "dagostino":
46
+ if len(arr) < 8:
47
+ return {"error": "D'Agostino test requires at least 8 samples"}
48
+ stat, p_value = scipy_stats.normaltest(arr)
49
+ result = {
50
+ "test": "D'Agostino-Pearson",
51
+ "statistic": float(stat),
52
+ "statistic_name": "K2",
53
+ "p_value": float(p_value),
54
+ }
55
+
56
+ elif method == "anderson":
57
+ res = scipy_stats.anderson(arr, dist="norm")
58
+ # Use 5% significance level
59
+ idx = 2 # Index for 5% level
60
+ result = {
61
+ "test": "Anderson-Darling",
62
+ "statistic": float(res.statistic),
63
+ "statistic_name": "A2",
64
+ "critical_value_5pct": float(res.critical_values[idx]),
65
+ "normal": bool(res.statistic < res.critical_values[idx]),
66
+ }
67
+
68
+ elif method == "lilliefors":
69
+ try:
70
+ from statsmodels.stats.diagnostic import lilliefors
71
+
72
+ stat, p_value = lilliefors(arr, dist="norm")
73
+ result = {
74
+ "test": "Lilliefors",
75
+ "statistic": float(stat),
76
+ "statistic_name": "D",
77
+ "p_value": float(p_value),
78
+ }
79
+ except ImportError:
80
+ return {"error": "statsmodels required for Lilliefors test"}
81
+
82
+ else:
83
+ raise ValueError(f"Unknown method: {method}")
84
+
85
+ # Add interpretation
86
+ if "p_value" in result:
87
+ result["is_normal"] = result["p_value"] >= 0.05
88
+ result["interpretation"] = (
89
+ "Data appears normally distributed (p >= 0.05)"
90
+ if result["is_normal"]
91
+ else "Data deviates from normal distribution (p < 0.05)"
92
+ )
93
+
94
+ return result
95
+
96
+ result = await loop.run_in_executor(None, do_normality)
97
+
98
+ return {
99
+ "success": True,
100
+ "method": method,
101
+ "n": len(data),
102
+ **result,
103
+ "timestamp": datetime.now().isoformat(),
104
+ }
105
+
106
+ except Exception as e:
107
+ return {"success": False, "error": str(e)}
108
+
109
+
110
+ # EOF