scitex 2.11.0__py3-none-any.whl → 2.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. scitex/__main__.py +24 -5
  2. scitex/__version__.py +1 -1
  3. scitex/_optional_deps.py +33 -0
  4. scitex/ai/classification/reporters/_ClassificationReporter.py +1 -1
  5. scitex/ai/classification/timeseries/_TimeSeriesBlockingSplit.py +2 -2
  6. scitex/ai/classification/timeseries/_TimeSeriesCalendarSplit.py +2 -2
  7. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +2 -2
  8. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit_v01-not-using-n_splits.py +2 -2
  9. scitex/ai/classification/timeseries/_TimeSeriesStratifiedSplit.py +2 -2
  10. scitex/ai/classification/timeseries/_normalize_timestamp.py +1 -1
  11. scitex/ai/metrics/_calc_seizure_prediction_metrics.py +1 -1
  12. scitex/ai/plt/_plot_feature_importance.py +1 -1
  13. scitex/ai/plt/_plot_learning_curve.py +1 -1
  14. scitex/ai/plt/_plot_optuna_study.py +1 -1
  15. scitex/ai/plt/_plot_pre_rec_curve.py +1 -1
  16. scitex/ai/plt/_plot_roc_curve.py +1 -1
  17. scitex/ai/plt/_stx_conf_mat.py +1 -1
  18. scitex/ai/training/_LearningCurveLogger.py +1 -1
  19. scitex/audio/mcp_server.py +38 -8
  20. scitex/browser/automation/CookieHandler.py +1 -1
  21. scitex/browser/core/BrowserMixin.py +1 -1
  22. scitex/browser/core/ChromeProfileManager.py +1 -1
  23. scitex/browser/debugging/_browser_logger.py +1 -1
  24. scitex/browser/debugging/_highlight_element.py +1 -1
  25. scitex/browser/debugging/_show_grid.py +1 -1
  26. scitex/browser/interaction/click_center.py +1 -1
  27. scitex/browser/interaction/click_with_fallbacks.py +1 -1
  28. scitex/browser/interaction/close_popups.py +1 -1
  29. scitex/browser/interaction/fill_with_fallbacks.py +1 -1
  30. scitex/browser/pdf/click_download_for_chrome_pdf_viewer.py +1 -1
  31. scitex/browser/pdf/detect_chrome_pdf_viewer.py +1 -1
  32. scitex/browser/stealth/HumanBehavior.py +1 -1
  33. scitex/browser/stealth/StealthManager.py +1 -1
  34. scitex/canvas/_mcp_handlers.py +372 -0
  35. scitex/canvas/_mcp_tool_schemas.py +219 -0
  36. scitex/canvas/mcp_server.py +151 -0
  37. scitex/capture/mcp_server.py +41 -12
  38. scitex/cli/audio.py +233 -0
  39. scitex/cli/capture.py +307 -0
  40. scitex/cli/main.py +27 -4
  41. scitex/cli/repro.py +233 -0
  42. scitex/cli/resource.py +240 -0
  43. scitex/cli/stats.py +325 -0
  44. scitex/cli/template.py +236 -0
  45. scitex/cli/tex.py +286 -0
  46. scitex/cli/web.py +11 -12
  47. scitex/dev/__init__.py +3 -0
  48. scitex/dev/_pyproject.py +405 -0
  49. scitex/dev/plt/__init__.py +2 -2
  50. scitex/dev/plt/mpl/get_dir_ax.py +1 -1
  51. scitex/dev/plt/mpl/get_signatures.py +1 -1
  52. scitex/dev/plt/mpl/get_signatures_details.py +1 -1
  53. scitex/diagram/_mcp_handlers.py +400 -0
  54. scitex/diagram/_mcp_tool_schemas.py +157 -0
  55. scitex/diagram/mcp_server.py +151 -0
  56. scitex/dsp/_demo_sig.py +51 -5
  57. scitex/dsp/_mne.py +13 -2
  58. scitex/dsp/_modulation_index.py +15 -3
  59. scitex/dsp/_pac.py +23 -5
  60. scitex/dsp/_psd.py +16 -4
  61. scitex/dsp/_resample.py +24 -4
  62. scitex/dsp/_transform.py +16 -3
  63. scitex/dsp/add_noise.py +15 -1
  64. scitex/dsp/norm.py +17 -2
  65. scitex/dsp/reference.py +17 -1
  66. scitex/dsp/utils/_differential_bandpass_filters.py +20 -2
  67. scitex/dsp/utils/_zero_pad.py +18 -4
  68. scitex/dt/_normalize_timestamp.py +1 -1
  69. scitex/git/_session.py +1 -1
  70. scitex/io/_load_modules/_con.py +12 -1
  71. scitex/io/_load_modules/_eeg.py +12 -1
  72. scitex/io/_load_modules/_optuna.py +21 -63
  73. scitex/io/_load_modules/_torch.py +11 -3
  74. scitex/io/_save_modules/_optuna_study_as_csv_and_pngs.py +13 -2
  75. scitex/io/_save_modules/_torch.py +11 -3
  76. scitex/mcp_server.py +159 -0
  77. scitex/plt/_mcp_handlers.py +361 -0
  78. scitex/plt/_mcp_tool_schemas.py +169 -0
  79. scitex/plt/mcp_server.py +205 -0
  80. scitex/repro/README_RandomStateManager.md +3 -3
  81. scitex/repro/_RandomStateManager.py +14 -14
  82. scitex/repro/_gen_ID.py +1 -1
  83. scitex/repro/_gen_timestamp.py +1 -1
  84. scitex/repro/_hash_array.py +4 -4
  85. scitex/scholar/__main__.py +24 -2
  86. scitex/scholar/_mcp_handlers.py +685 -0
  87. scitex/scholar/_mcp_tool_schemas.py +339 -0
  88. scitex/scholar/docs/template.py +1 -1
  89. scitex/scholar/examples/07_storage_integration.py +1 -1
  90. scitex/scholar/impact_factor/jcr/ImpactFactorJCREngine.py +1 -1
  91. scitex/scholar/impact_factor/jcr/build_database.py +1 -1
  92. scitex/scholar/mcp_server.py +315 -0
  93. scitex/scholar/pdf_download/ScholarPDFDownloader.py +1 -1
  94. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +1 -1
  95. scitex/scholar/pipelines/ScholarPipelineParallel.py +1 -1
  96. scitex/scholar/pipelines/ScholarPipelineSingle.py +1 -1
  97. scitex/scholar/storage/PaperIO.py +1 -1
  98. scitex/session/README.md +4 -4
  99. scitex/session/__init__.py +1 -1
  100. scitex/session/_decorator.py +9 -9
  101. scitex/session/_lifecycle.py +5 -5
  102. scitex/session/template.py +1 -1
  103. scitex/stats/__main__.py +281 -0
  104. scitex/stats/_mcp_handlers.py +1191 -0
  105. scitex/stats/_mcp_tool_schemas.py +384 -0
  106. scitex/stats/correct/_correct_bonferroni.py +1 -1
  107. scitex/stats/correct/_correct_fdr.py +1 -1
  108. scitex/stats/correct/_correct_fdr_.py +1 -1
  109. scitex/stats/correct/_correct_holm.py +1 -1
  110. scitex/stats/correct/_correct_sidak.py +1 -1
  111. scitex/stats/effect_sizes/_cliffs_delta.py +1 -1
  112. scitex/stats/effect_sizes/_cohens_d.py +1 -1
  113. scitex/stats/effect_sizes/_epsilon_squared.py +1 -1
  114. scitex/stats/effect_sizes/_eta_squared.py +1 -1
  115. scitex/stats/effect_sizes/_prob_superiority.py +1 -1
  116. scitex/stats/mcp_server.py +405 -0
  117. scitex/stats/posthoc/_dunnett.py +1 -1
  118. scitex/stats/posthoc/_games_howell.py +1 -1
  119. scitex/stats/posthoc/_tukey_hsd.py +1 -1
  120. scitex/stats/power/_power.py +1 -1
  121. scitex/stats/utils/_effect_size.py +1 -1
  122. scitex/stats/utils/_formatters.py +1 -1
  123. scitex/stats/utils/_power.py +1 -1
  124. scitex/template/_mcp_handlers.py +259 -0
  125. scitex/template/_mcp_tool_schemas.py +112 -0
  126. scitex/template/mcp_server.py +186 -0
  127. scitex/utils/_verify_scitex_format.py +2 -2
  128. scitex/utils/template.py +1 -1
  129. scitex/web/__init__.py +12 -11
  130. scitex/web/_scraping.py +26 -265
  131. scitex/web/download_images.py +316 -0
  132. scitex/writer/Writer.py +1 -1
  133. scitex/writer/_clone_writer_project.py +1 -1
  134. scitex/writer/_validate_tree_structures.py +1 -1
  135. scitex/writer/dataclasses/config/_WriterConfig.py +1 -1
  136. scitex/writer/dataclasses/contents/_ManuscriptContents.py +1 -1
  137. scitex/writer/dataclasses/core/_Document.py +1 -1
  138. scitex/writer/dataclasses/core/_DocumentSection.py +1 -1
  139. scitex/writer/dataclasses/results/_CompilationResult.py +1 -1
  140. scitex/writer/dataclasses/results/_LaTeXIssue.py +1 -1
  141. scitex/writer/utils/.legacy_git_retry.py +7 -5
  142. scitex/writer/utils/_parse_latex_logs.py +1 -1
  143. {scitex-2.11.0.dist-info → scitex-2.13.0.dist-info}/METADATA +431 -269
  144. {scitex-2.11.0.dist-info → scitex-2.13.0.dist-info}/RECORD +147 -118
  145. scitex-2.13.0.dist-info/entry_points.txt +11 -0
  146. scitex-2.11.0.dist-info/entry_points.txt +0 -2
  147. {scitex-2.11.0.dist-info → scitex-2.13.0.dist-info}/WHEEL +0 -0
  148. {scitex-2.11.0.dist-info → scitex-2.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -660,7 +660,7 @@ def run_main() -> None:
660
660
 
661
661
  args = parse_args()
662
662
 
663
- CONFIG, sys.stdout, sys.stderr, plt, CC, rng_manager = stx.session.start(
663
+ CONFIG, sys.stdout, sys.stderr, plt, CC, rng = stx.session.start(
664
664
  sys,
665
665
  plt,
666
666
  args=args,
@@ -316,7 +316,7 @@ def run_main() -> None:
316
316
 
317
317
  args = parse_args()
318
318
 
319
- CONFIG, sys.stdout, sys.stderr, plt, CC, rng_manager = stx.session.start(
319
+ CONFIG, sys.stdout, sys.stderr, plt, CC, rng = stx.session.start(
320
320
  sys,
321
321
  plt,
322
322
  args=args,
@@ -406,7 +406,7 @@ def run_main() -> None:
406
406
 
407
407
  args = parse_args()
408
408
 
409
- CONFIG, sys.stdout, sys.stderr, plt, CC, rng_manager = stx.session.start(
409
+ CONFIG, sys.stdout, sys.stderr, plt, CC, rng = stx.session.start(
410
410
  sys,
411
411
  plt,
412
412
  args=args,
@@ -694,7 +694,7 @@ def run_main() -> None:
694
694
 
695
695
  args = parse_args()
696
696
 
697
- CONFIG, sys.stdout, sys.stderr, plt, CC, rng_manager = stx.session.start(
697
+ CONFIG, sys.stdout, sys.stderr, plt, CC, rng = stx.session.start(
698
698
  sys,
699
699
  plt,
700
700
  args=args,
@@ -438,7 +438,7 @@ def run_main() -> None:
438
438
 
439
439
  args = parse_args()
440
440
 
441
- CONFIG, sys.stdout, sys.stderr, plt, CC, rng_manager = stx.session.start(
441
+ CONFIG, sys.stdout, sys.stderr, plt, CC, rng = stx.session.start(
442
442
  sys,
443
443
  plt,
444
444
  args=args,
scitex/session/README.md CHANGED
@@ -22,7 +22,7 @@ def main(
22
22
  CONFIG=scitex.INJECTED,
23
23
  plt=scitex.INJECTED,
24
24
  COLORS=scitex.INJECTED,
25
- rng_manager=scitex.INJECTED,
25
+ rng=scitex.INJECTED,
26
26
  ):
27
27
  """Args injected by @scitex.session decorator"""
28
28
  print(f"Session ID: {CONFIG['ID']}")
@@ -46,7 +46,7 @@ def main(
46
46
  CONFIG=scitex.INJECTED,
47
47
  plt=scitex.INJECTED,
48
48
  COLORS=scitex.INJECTED,
49
- rng_manager=scitex.INJECTED,
49
+ rng=scitex.INJECTED,
50
50
  ):
51
51
  """Args injected by @scitex.session decorator"""
52
52
  print(f"Session ID: {CONFIG['ID']}")
@@ -59,7 +59,7 @@ The decorator automatically injects the following parameters:
59
59
  - `CONFIG`: Configuration dictionary with session metadata
60
60
  - `plt`: Configured matplotlib.pyplot module
61
61
  - `COLORS`: Color cycle dictionary
62
- - `rng_manager`: RandomStateManager instance
62
+ - `rng`: RandomStateManager instance
63
63
 
64
64
  Using `scitex.INJECTED` as default values makes it explicit that these parameters are injected by the decorator.
65
65
 
@@ -249,4 +249,4 @@ if __name__ == "__main__":
249
249
  - Automatically fixes seeds for all libraries
250
250
  - Reproducible across runs
251
251
 
252
- <!-- EOF -->
252
+ <!-- EOF -->
@@ -22,7 +22,7 @@ Usage:
22
22
  from scitex import session
23
23
 
24
24
  # Start a session
25
- CONFIG, sys.stdout, sys.stderr, plt, COLORS, rng_manager = session.start(sys, plt)
25
+ CONFIG, sys.stdout, sys.stderr, plt, COLORS, rng = session.start(sys, plt)
26
26
 
27
27
  # Your experiment code here
28
28
 
@@ -79,7 +79,7 @@ def session(
79
79
  # - CONFIG: Session configuration dict
80
80
  # - plt: Matplotlib pyplot (configured for session)
81
81
  # - COLORS: Custom Colors
82
- # - rng_manager: RandomStateManager (fixes seeds, creates named generators)
82
+ # - rng: RandomStateManager (fixes seeds, creates named generators)
83
83
  logger.info(f"Session ID: {CONFIG['ID']}")
84
84
  logger.info(f"Output directory: {CONFIG['SDIR_RUN']}")
85
85
  # ... training code ...
@@ -100,8 +100,8 @@ def session(
100
100
  - CONFIG (dict): Session configuration with ID, SDIR, paths, etc.
101
101
  - plt (module): matplotlib.pyplot configured with session settings
102
102
  - COLORS (CustomColors): Custom Colors for consistent plotting
103
- - rng_manager (RandomStateManager): Manages reproducibility by fixing global seeds
104
- and creating named generators via rng_manager("name")
103
+ - rng (RandomStateManager): Manages reproducibility by fixing global seeds
104
+ and creating named generators via rng("name")
105
105
  """
106
106
 
107
107
  def decorator(func: Callable) -> Callable:
@@ -163,7 +163,7 @@ def _run_with_session(
163
163
  # Start session
164
164
  import matplotlib.pyplot as plt
165
165
 
166
- CONFIG, stdout, stderr, plt, COLORS, rng_manager = start(
166
+ CONFIG, stdout, stderr, plt, COLORS, rng = start(
167
167
  sys=sys_module,
168
168
  plt=plt,
169
169
  args=cleaned_args,
@@ -182,7 +182,7 @@ def _run_with_session(
182
182
  func_globals["CONFIG"] = CONFIG
183
183
  func_globals["plt"] = plt
184
184
  func_globals["COLORS"] = COLORS
185
- func_globals["rng_manager"] = rng_manager
185
+ func_globals["rng"] = rng
186
186
  func_globals["logger"] = script_logger
187
187
 
188
188
  # Log injected globals for user awareness (only in verbose mode)
@@ -195,7 +195,7 @@ def _run_with_session(
195
195
  _decorator_logger.info(f" - CONFIG['PID']: {CONFIG['PID']}")
196
196
  _decorator_logger.info(" • plt - matplotlib.pyplot (configured for session)")
197
197
  _decorator_logger.info(" • COLORS - CustomColors (for consistent plotting)")
198
- _decorator_logger.info(" • rng_manager - RandomStateManager (for reproducibility)")
198
+ _decorator_logger.info(" • rng - RandomStateManager (for reproducibility)")
199
199
  _decorator_logger.info(" • logger - SciTeX logger (configured for your script)")
200
200
  _decorator_logger.info("=" * 60)
201
201
 
@@ -216,7 +216,7 @@ def _run_with_session(
216
216
  "CONFIG": CONFIG,
217
217
  "plt": plt,
218
218
  "COLORS": COLORS,
219
- "rng_manager": rng_manager,
219
+ "rng": rng,
220
220
  "logger": script_logger,
221
221
  }
222
222
 
@@ -419,7 +419,7 @@ Global Variables Injected by @session Decorator:
419
419
  plt.plot(x, y, color=COLORS.blue)
420
420
  plt.plot(x, y, color=COLORS['blue'])
421
421
 
422
- rng_manager (RandomStateManager)
422
+ rng (RandomStateManager)
423
423
  Manages reproducible randomness
424
424
 
425
425
  logger (SciTeXLogger)
@@ -596,7 +596,7 @@ def run(func: Callable, parse_args: Callable = None, **session_kwargs) -> Any:
596
596
  # Start session
597
597
  import matplotlib.pyplot as plt
598
598
 
599
- CONFIG, stdout, stderr, plt, COLORS, rng_manager = start(
599
+ CONFIG, stdout, stderr, plt, COLORS, rng = start(
600
600
  sys=sys_module,
601
601
  plt=plt,
602
602
  args=args,
@@ -411,12 +411,12 @@ def start(
411
411
  Returns
412
412
  -------
413
413
  tuple
414
- (CONFIGS, stdout, stderr, plt, COLORS, rng_manager)
414
+ (CONFIGS, stdout, stderr, plt, COLORS, rng)
415
415
  - CONFIGS: Configuration dictionary
416
416
  - stdout, stderr: Redirected output streams
417
417
  - plt: Configured matplotlib.pyplot module
418
418
  - COLORS: Color cycle dictionary
419
- - rng_manager: Global RandomStateManager instance for reproducible random generation
419
+ - rng: Global RandomStateManager instance for reproducible random generation
420
420
  """
421
421
  IS_DEBUG = _get_debug_mode()
422
422
  ID, PID = _initialize_env(IS_DEBUG)
@@ -516,7 +516,7 @@ def start(
516
516
  pass
517
517
 
518
518
  # Initialize RandomStateManager (automatically fixes all seeds)
519
- rng_manager = RandomStateManager(seed=seed, verbose=verbose)
519
+ rng = RandomStateManager(seed=seed, verbose=verbose)
520
520
  if verbose:
521
521
  logger.info(f"Initialized RandomStateManager with seed {seed}")
522
522
 
@@ -554,9 +554,9 @@ def start(
554
554
 
555
555
  # Return appropriate values based on whether sys was provided
556
556
  if sys is not None:
557
- return CONFIGS, sys.stdout, sys.stderr, plt, COLORS, rng_manager
557
+ return CONFIGS, sys.stdout, sys.stderr, plt, COLORS, rng
558
558
  else:
559
- return CONFIGS, None, None, plt, COLORS, rng_manager
559
+ return CONFIGS, None, None, plt, COLORS, rng
560
560
 
561
561
 
562
562
  def _format_diff_time(diff_time):
@@ -16,7 +16,7 @@ def main(
16
16
  CONFIG=INJECTED,
17
17
  plt=INJECTED,
18
18
  COLORS=INJECTED,
19
- rng_manager=INJECTED,
19
+ rng=INJECTED,
20
20
  logger=INJECTED,
21
21
  ):
22
22
  """Demonstration for scitex.session.session"""
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2026-01-08
3
+ # File: src/scitex/stats/__main__.py
4
+ # ----------------------------------------
5
+
6
+ """Stats CLI entry point.
7
+
8
+ Subcommand-based interface:
9
+ - recommend: Recommend statistical tests based on data characteristics
10
+ - test: Run a statistical test
11
+ - power: Power analysis and sample size calculation
12
+ - mcp: Start MCP server for LLM integration
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import asyncio
19
+ import json
20
+ import sys
21
+
22
+
23
+ def create_parser():
24
+ """Create main argument parser with subcommands."""
25
+ parser = argparse.ArgumentParser(
26
+ prog="python -m scitex.stats",
27
+ description="""
28
+ SciTeX Stats - Statistical Testing Framework
29
+ ═══════════════════════════════════════════
30
+
31
+ Subcommand interface:
32
+ recommend - Recommend statistical tests
33
+ test - Run a statistical test
34
+ power - Power analysis
35
+ mcp - Start MCP server for LLM integration
36
+ """,
37
+ formatter_class=argparse.RawDescriptionHelpFormatter,
38
+ )
39
+
40
+ subparsers = parser.add_subparsers(
41
+ dest="command",
42
+ help="Available commands",
43
+ required=True,
44
+ )
45
+
46
+ # ========================================
47
+ # Subcommand: recommend
48
+ # ========================================
49
+ recommend_parser = subparsers.add_parser(
50
+ "recommend",
51
+ help="Recommend statistical tests",
52
+ description="Recommend appropriate statistical tests based on data characteristics",
53
+ formatter_class=argparse.RawDescriptionHelpFormatter,
54
+ )
55
+ recommend_parser.add_argument(
56
+ "--n-groups",
57
+ type=int,
58
+ default=2,
59
+ help="Number of groups to compare (default: 2)",
60
+ )
61
+ recommend_parser.add_argument(
62
+ "--sample-sizes",
63
+ type=int,
64
+ nargs="+",
65
+ help="Sample sizes for each group",
66
+ )
67
+ recommend_parser.add_argument(
68
+ "--outcome",
69
+ type=str,
70
+ choices=["continuous", "ordinal", "categorical", "binary"],
71
+ default="continuous",
72
+ help="Outcome variable type (default: continuous)",
73
+ )
74
+ recommend_parser.add_argument(
75
+ "--design",
76
+ type=str,
77
+ choices=["between", "within", "mixed"],
78
+ default="between",
79
+ help="Study design (default: between)",
80
+ )
81
+ recommend_parser.add_argument(
82
+ "--paired",
83
+ action="store_true",
84
+ help="Data is paired/matched",
85
+ )
86
+ recommend_parser.add_argument(
87
+ "--has-control",
88
+ action="store_true",
89
+ help="Has a control group",
90
+ )
91
+ recommend_parser.add_argument(
92
+ "--top-k",
93
+ type=int,
94
+ default=3,
95
+ help="Number of recommendations (default: 3)",
96
+ )
97
+
98
+ # ========================================
99
+ # Subcommand: test
100
+ # ========================================
101
+ test_parser = subparsers.add_parser(
102
+ "test",
103
+ help="Run a statistical test",
104
+ description="Execute a statistical test on provided data",
105
+ formatter_class=argparse.RawDescriptionHelpFormatter,
106
+ )
107
+ test_parser.add_argument(
108
+ "test_name",
109
+ type=str,
110
+ help="Name of the test to run",
111
+ )
112
+ test_parser.add_argument(
113
+ "--data",
114
+ type=str,
115
+ help="JSON array of group data, e.g., '[[1,2,3],[4,5,6]]'",
116
+ )
117
+ test_parser.add_argument(
118
+ "--alternative",
119
+ type=str,
120
+ choices=["two-sided", "less", "greater"],
121
+ default="two-sided",
122
+ help="Alternative hypothesis (default: two-sided)",
123
+ )
124
+
125
+ # ========================================
126
+ # Subcommand: power
127
+ # ========================================
128
+ power_parser = subparsers.add_parser(
129
+ "power",
130
+ help="Power analysis",
131
+ description="Calculate statistical power or required sample size",
132
+ formatter_class=argparse.RawDescriptionHelpFormatter,
133
+ )
134
+ power_parser.add_argument(
135
+ "--test-type",
136
+ type=str,
137
+ choices=["ttest", "anova", "correlation", "chi2"],
138
+ default="ttest",
139
+ help="Type of statistical test (default: ttest)",
140
+ )
141
+ power_parser.add_argument(
142
+ "--effect-size",
143
+ type=float,
144
+ help="Expected effect size (Cohen's d, f, r, or w)",
145
+ )
146
+ power_parser.add_argument(
147
+ "--n",
148
+ type=int,
149
+ help="Sample size per group (for power calculation)",
150
+ )
151
+ power_parser.add_argument(
152
+ "--power",
153
+ type=float,
154
+ default=0.8,
155
+ help="Desired power (default: 0.8)",
156
+ )
157
+ power_parser.add_argument(
158
+ "--alpha",
159
+ type=float,
160
+ default=0.05,
161
+ help="Significance level (default: 0.05)",
162
+ )
163
+
164
+ # ========================================
165
+ # Subcommand: mcp
166
+ # ========================================
167
+ subparsers.add_parser(
168
+ "mcp",
169
+ help="Start MCP server for LLM integration",
170
+ description="Start the MCP (Model Context Protocol) server for Claude/LLM integration",
171
+ formatter_class=argparse.RawDescriptionHelpFormatter,
172
+ )
173
+
174
+ return parser
175
+
176
+
177
+ def run_recommend(args):
178
+ """Run test recommendation."""
179
+ from scitex.stats.auto import StatContext, recommend_tests
180
+ from scitex.stats.auto._rules import TEST_RULES
181
+
182
+ ctx = StatContext(
183
+ n_groups=args.n_groups,
184
+ sample_sizes=args.sample_sizes or [30] * args.n_groups,
185
+ outcome_type=args.outcome,
186
+ design=args.design,
187
+ paired=args.paired,
188
+ has_control_group=args.has_control,
189
+ n_factors=1,
190
+ )
191
+
192
+ tests = recommend_tests(ctx, top_k=args.top_k)
193
+
194
+ print("\n=== Recommended Statistical Tests ===\n")
195
+ for i, test_name in enumerate(tests, 1):
196
+ rule = TEST_RULES.get(test_name)
197
+ if rule:
198
+ print(f"{i}. {test_name}")
199
+ print(f" Family: {rule.family}")
200
+ print(f" Priority: {rule.priority}")
201
+ if rule.needs_normality:
202
+ print(" Requires: normality assumption")
203
+ if rule.needs_equal_variance:
204
+ print(" Requires: equal variance assumption")
205
+ print()
206
+
207
+ return 0
208
+
209
+
210
+ def run_test(args):
211
+ """Run a statistical test."""
212
+ if not args.data:
213
+ print("Error: --data is required")
214
+ return 1
215
+
216
+ data = json.loads(args.data)
217
+
218
+ from scitex.stats._mcp_handlers import run_test_handler
219
+
220
+ result = asyncio.run(
221
+ run_test_handler(
222
+ test_name=args.test_name,
223
+ data=data,
224
+ alternative=args.alternative,
225
+ )
226
+ )
227
+
228
+ print(json.dumps(result, indent=2))
229
+ return 0 if result.get("success") else 1
230
+
231
+
232
+ def run_power(args):
233
+ """Run power analysis."""
234
+ from scitex.stats._mcp_handlers import power_analysis_handler
235
+
236
+ result = asyncio.run(
237
+ power_analysis_handler(
238
+ test_type=args.test_type,
239
+ effect_size=args.effect_size,
240
+ n=args.n,
241
+ power=args.power,
242
+ alpha=args.alpha,
243
+ )
244
+ )
245
+
246
+ print(json.dumps(result, indent=2))
247
+ return 0 if result.get("success") else 1
248
+
249
+
250
+ async def run_mcp_server():
251
+ """Run MCP server."""
252
+ from .mcp_server import main as mcp_main
253
+
254
+ print("Starting Stats MCP server...", file=sys.stderr)
255
+ await mcp_main()
256
+ return 0
257
+
258
+
259
+ def main():
260
+ """Main entry point."""
261
+ parser = create_parser()
262
+ args = parser.parse_args()
263
+
264
+ if args.command == "recommend":
265
+ return run_recommend(args)
266
+ elif args.command == "test":
267
+ return run_test(args)
268
+ elif args.command == "power":
269
+ return run_power(args)
270
+ elif args.command == "mcp":
271
+ return asyncio.run(run_mcp_server())
272
+ else:
273
+ print(f"Unknown command: {args.command}")
274
+ return 1
275
+
276
+
277
+ if __name__ == "__main__":
278
+ sys.exit(main())
279
+
280
+
281
+ # EOF