scitex 2.15.1__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 (107) hide show
  1. scitex/__init__.py +68 -61
  2. scitex/_mcp_tools/introspect.py +42 -23
  3. scitex/_mcp_tools/template.py +24 -0
  4. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
  5. scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
  6. scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
  7. scitex/audio/__init__.py +2 -2
  8. scitex/audio/_tts.py +18 -10
  9. scitex/audio/engines/base.py +17 -10
  10. scitex/audio/engines/elevenlabs_engine.py +1 -1
  11. scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
  12. scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
  13. scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
  14. scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
  15. scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
  16. scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
  17. scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
  18. scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
  19. scitex/canvas/editor/flask_editor/_core.py +25 -1684
  20. scitex/cli/introspect.py +112 -74
  21. scitex/cli/main.py +2 -0
  22. scitex/cli/plt.py +357 -0
  23. scitex/cli/repro.py +15 -8
  24. scitex/cli/resource.py +15 -8
  25. scitex/cli/scholar/__init__.py +15 -8
  26. scitex/cli/social.py +6 -6
  27. scitex/cli/stats.py +15 -8
  28. scitex/cli/template.py +129 -12
  29. scitex/cli/tex.py +15 -8
  30. scitex/cli/writer.py +15 -8
  31. scitex/cloud/__init__.py +41 -2
  32. scitex/config/_env_registry.py +84 -19
  33. scitex/context/__init__.py +22 -0
  34. scitex/dev/__init__.py +20 -1
  35. scitex/gen/__init__.py +50 -14
  36. scitex/gen/_list_packages.py +4 -4
  37. scitex/introspect/__init__.py +16 -9
  38. scitex/introspect/_core.py +7 -8
  39. scitex/{gen/_inspect_module.py → introspect/_list_api.py} +43 -54
  40. scitex/introspect/_mcp/__init__.py +10 -6
  41. scitex/introspect/_mcp/handlers.py +37 -12
  42. scitex/introspect/_members.py +7 -3
  43. scitex/introspect/_signature.py +3 -3
  44. scitex/introspect/_source.py +2 -2
  45. scitex/io/_save.py +1 -2
  46. scitex/logging/_formatters.py +19 -9
  47. scitex/mcp_server.py +1 -1
  48. scitex/os/__init__.py +4 -0
  49. scitex/{gen → os}/_check_host.py +4 -5
  50. scitex/plt/__init__.py +11 -14
  51. scitex/session/__init__.py +26 -7
  52. scitex/session/_decorator.py +1 -1
  53. scitex/sh/__init__.py +7 -4
  54. scitex/social/__init__.py +10 -8
  55. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  56. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  57. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  58. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  59. scitex/stats/_mcp/_handlers/_format.py +94 -0
  60. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  61. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  62. scitex/stats/_mcp/_handlers/_power.py +247 -0
  63. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  64. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  65. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  66. scitex/stats/_mcp/handlers.py +19 -1171
  67. scitex/stats/auto/_stat_style.py +175 -0
  68. scitex/stats/auto/_style_definitions.py +411 -0
  69. scitex/stats/auto/_styles.py +22 -620
  70. scitex/stats/descriptive/__init__.py +11 -8
  71. scitex/stats/descriptive/_ci.py +39 -0
  72. scitex/stats/power/_power.py +15 -4
  73. scitex/str/__init__.py +2 -1
  74. scitex/str/_title_case.py +63 -0
  75. scitex/template/__init__.py +25 -10
  76. scitex/template/_code_templates.py +147 -0
  77. scitex/template/_mcp/handlers.py +81 -0
  78. scitex/template/_mcp/tool_schemas.py +55 -0
  79. scitex/template/_templates/__init__.py +51 -0
  80. scitex/template/_templates/audio.py +233 -0
  81. scitex/template/_templates/canvas.py +312 -0
  82. scitex/template/_templates/capture.py +268 -0
  83. scitex/template/_templates/config.py +43 -0
  84. scitex/template/_templates/diagram.py +294 -0
  85. scitex/template/_templates/io.py +107 -0
  86. scitex/template/_templates/module.py +53 -0
  87. scitex/template/_templates/plt.py +202 -0
  88. scitex/template/_templates/scholar.py +267 -0
  89. scitex/template/_templates/session.py +130 -0
  90. scitex/template/_templates/session_minimal.py +43 -0
  91. scitex/template/_templates/session_plot.py +67 -0
  92. scitex/template/_templates/session_stats.py +77 -0
  93. scitex/template/_templates/stats.py +323 -0
  94. scitex/template/_templates/writer.py +296 -0
  95. scitex/ui/_backends/_email.py +10 -2
  96. scitex/ui/_backends/_webhook.py +5 -1
  97. scitex/web/_search_pubmed.py +10 -6
  98. {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/METADATA +1 -1
  99. {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/RECORD +105 -64
  100. scitex/gen/_ci.py +0 -12
  101. scitex/gen/_title_case.py +0 -89
  102. /scitex/{gen → context}/_detect_environment.py +0 -0
  103. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  104. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  105. {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
  106. {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
  107. {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  # Timestamp: "2025-10-11 00:17:43 (ywatanabe)"
4
3
  # File: /home/ywatanabe/proj/scitex_repo/src/scitex/logging/_formatters.py
5
4
  # ----------------------------------------
6
5
  from __future__ import annotations
6
+
7
7
  import os
8
8
 
9
9
  __FILE__ = "./src/scitex/logging/_formatters.py"
@@ -18,12 +18,17 @@ import sys
18
18
 
19
19
  # Global format configuration via environment variable
20
20
  # Options: default, minimal, detailed, debug, full
21
- # SCITEX_LOG_FORMAT=debug python script.py
22
- LOG_FORMAT = os.getenv("SCITEX_LOG_FORMAT", "default")
21
+ # SCITEX_LOGGING_FORMAT=debug python script.py
22
+ LOG_FORMAT = os.getenv("SCITEX_LOGGING_FORMAT") or os.getenv(
23
+ "SCITEX_LOG_FORMAT", "default"
24
+ )
23
25
 
24
26
  # Force color output even when stdout is not a TTY (e.g., when piping through tee)
25
- # SCITEX_FORCE_COLOR=1 python script.py | tee output.log
26
- FORCE_COLOR = os.getenv("SCITEX_FORCE_COLOR", "").lower() in ("1", "true", "yes")
27
+ # SCITEX_LOGGING_FORCE_COLOR=1 python script.py | tee output.log
28
+ _force_color = os.getenv("SCITEX_LOGGING_FORCE_COLOR") or os.getenv(
29
+ "SCITEX_FORCE_COLOR", ""
30
+ )
31
+ FORCE_COLOR = _force_color.lower() in ("1", "true", "yes")
27
32
 
28
33
  # Available format templates
29
34
  FORMAT_TEMPLATES = {
@@ -107,14 +112,19 @@ class SciTeXConsoleFormatter(logging.Formatter):
107
112
  # First line already has prefix from parent formatter
108
113
  # Add prefix to each continuation line
109
114
  prefix = f"{record.levelname}: "
110
- formatted = lines[0] + "\n" + "\n".join(
111
- prefix + line if line.strip() else line
112
- for line in lines[1:]
115
+ formatted = (
116
+ lines[0]
117
+ + "\n"
118
+ + "\n".join(
119
+ prefix + line if line.strip() else line for line in lines[1:]
120
+ )
113
121
  )
114
122
 
115
123
  # Check if we can use colors (stdout is a tty and not closed, or forced)
116
124
  try:
117
- use_colors = FORCE_COLOR or (hasattr(sys.stdout, "isatty") and sys.stdout.isatty())
125
+ use_colors = FORCE_COLOR or (
126
+ hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
127
+ )
118
128
  except ValueError:
119
129
  # stdout/stderr is closed
120
130
  use_colors = FORCE_COLOR
scitex/mcp_server.py CHANGED
@@ -106,7 +106,7 @@ result = stx.stats.test_anova(*groups, return_as="latex")
106
106
  - [scholar] search_papers, enrich_bibtex, download_pdf, fetch_papers
107
107
  - [diagram] create_diagram, compile_mermaid, compile_graphviz
108
108
  - [canvas] create_canvas, add_panel, export_canvas
109
- - [template] list_templates, clone_template
109
+ - [template] list_templates, clone_template, get_code_template, list_code_templates
110
110
  - [ui] notify, list_notification_backends
111
111
  - [writer] usage (LaTeX manuscript compilation)
112
112
  - [introspect] signature, docstring, source, members (like IPython's ? and ??)
scitex/os/__init__.py CHANGED
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env python3
2
2
  """Scitex os module."""
3
3
 
4
+ from ._check_host import check_host, is_host, verify_host
4
5
  from ._mv import mv
5
6
 
6
7
  __all__ = [
8
+ "check_host",
9
+ "is_host",
7
10
  "mv",
11
+ "verify_host",
8
12
  ]
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  # Time-stamp: "2024-11-02 13:43:36 (ywatanabe)"
4
- # File: ./scitex_repo/src/scitex/gen/_check_host.py
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/os/_check_host.py
5
4
 
6
-
7
- from scitex.sh import sh
5
+ import socket
8
6
  import sys
9
7
 
10
8
 
11
9
  def check_host(keyword):
12
- return keyword in sh("echo $(hostname)", verbose=False)
10
+ """Check if the current hostname contains the given keyword."""
11
+ return keyword in socket.gethostname()
13
12
 
14
13
 
15
14
  is_host = check_host
scitex/plt/__init__.py CHANGED
@@ -70,34 +70,31 @@ __DIR__ = os.path.dirname(__FILE__)
70
70
  # Re-export figrecipe public API with scitex branding
71
71
  # ============================================================================
72
72
  if _FIGRECIPE_AVAILABLE:
73
- # Core functions
73
+ # Core public API
74
+ from figrecipe import __version__ as _figrecipe_version
74
75
  from figrecipe import (
75
- STYLE,
76
- align_panels,
77
- apply_style,
78
76
  compose,
79
77
  crop,
80
- distribute_panels,
81
78
  edit,
82
- enable_svg,
83
79
  extract_data,
84
- get_graph_preset,
85
80
  info,
86
- list_graph_presets,
87
81
  list_presets,
88
82
  load_style,
89
- register_graph_preset,
90
83
  reproduce,
91
84
  save,
92
- smart_align,
93
- sns,
94
85
  subplots,
95
86
  unload_style,
96
87
  validate,
97
88
  )
98
- from figrecipe import (
99
- __version__ as _figrecipe_version,
100
- )
89
+
90
+ # Internal imports (not part of figrecipe public API)
91
+ from figrecipe._api._notebook import enable_svg
92
+ from figrecipe._api._seaborn_proxy import sns
93
+ from figrecipe._api._style_manager import STYLE, apply_style
94
+ from figrecipe._composition import align_panels, distribute_panels, smart_align
95
+ from figrecipe._graph_presets import get_preset as get_graph_preset
96
+ from figrecipe._graph_presets import list_presets as list_graph_presets
97
+ from figrecipe._graph_presets import register_preset as register_graph_preset
101
98
 
102
99
  # Also export load as alias for reproduce
103
100
  load = reproduce
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  # Timestamp: "2025-08-21 20:36:45 (ywatanabe)"
4
3
  # File: /home/ywatanabe/proj/SciTeX-Code/src/scitex/session/__init__.py
5
4
  # ----------------------------------------
6
5
  from __future__ import annotations
6
+
7
7
  import os
8
8
 
9
9
  __FILE__ = __file__
@@ -20,27 +20,46 @@ Usage:
20
20
  import sys
21
21
  import matplotlib.pyplot as plt
22
22
  from scitex import session
23
-
23
+
24
24
  # Start a session
25
25
  CONFIG, sys.stdout, sys.stderr, plt, COLORS, rng = session.start(sys, plt)
26
-
26
+
27
27
  # Your experiment code here
28
-
29
- # Close the session
28
+
29
+ # Close the session
30
30
  session.close(CONFIG)
31
31
 
32
32
  # Session manager for advanced use cases
33
33
  manager = session.SessionManager()
34
34
  active_sessions = manager.get_active_sessions()
35
+
36
+ # Using INJECTED sentinel for decorator parameters
37
+ @stx.session
38
+ def main(CONFIG=stx.session.INJECTED, plt=stx.session.INJECTED):
39
+ ...
35
40
  """
36
41
 
42
+
43
+ # Sentinel object for decorator-injected parameters
44
+ class _InjectedSentinel:
45
+ """Sentinel value indicating a parameter will be injected by a decorator."""
46
+
47
+ def __repr__(self):
48
+ return "<INJECTED>"
49
+
50
+
51
+ INJECTED = _InjectedSentinel()
52
+
53
+
37
54
  # Import session management functionality
55
+ from ._decorator import run, session
56
+ from ._lifecycle import close, running2finished, start
38
57
  from ._manager import SessionManager
39
- from ._lifecycle import start, close, running2finished
40
- from ._decorator import session, run
41
58
 
42
59
  # Export public API
43
60
  __all__ = [
61
+ # Sentinel for injected parameters
62
+ "INJECTED",
44
63
  # Session lifecycle (main functions)
45
64
  "start",
46
65
  "close",
@@ -21,7 +21,7 @@ import sys as sys_module
21
21
 
22
22
  from ._lifecycle import start, close
23
23
  from scitex.logging import getLogger
24
- from scitex import INJECTED
24
+ from . import INJECTED # Use local INJECTED from session module
25
25
 
26
26
  # Internal logger for the decorator itself
27
27
  _decorator_logger = getLogger(__name__)
scitex/sh/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  from __future__ import annotations
3
+
4
4
  import os
5
5
 
6
6
  __FILE__ = __file__
@@ -8,9 +8,9 @@ __DIR__ = os.path.dirname(__FILE__)
8
8
 
9
9
  from typing import Union
10
10
 
11
- from ._types import CommandInput, ReturnFormat, ShellResult
12
- from ._security import quote, validate_command
13
11
  from ._execute import execute
12
+ from ._security import quote, validate_command
13
+ from ._types import CommandInput, ReturnFormat, ShellResult
14
14
 
15
15
 
16
16
  def sh(
@@ -88,6 +88,9 @@ def sh_run(command: CommandInput, verbose: bool = True) -> ShellResult:
88
88
  return execute(command, verbose=verbose)
89
89
 
90
90
 
91
- __all__ = ["sh", "sh_run", "quote"]
91
+ # Legacy functions moved from gen module
92
+ from ._shell_legacy import run_shellcommand, run_shellscript
93
+
94
+ __all__ = ["sh", "sh_run", "quote", "run_shellcommand", "run_shellscript"]
92
95
 
93
96
  # EOF
scitex/social/__init__.py CHANGED
@@ -17,13 +17,15 @@ Features
17
17
 
18
18
  Environment Variables
19
19
  ---------------------
20
- Credentials use SCITEX_ prefix (falls back to SOCIALIA_):
21
- - SCITEX_X_CONSUMER_KEY, SCITEX_X_CONSUMER_SECRET
22
- - SCITEX_X_ACCESS_TOKEN, SCITEX_X_ACCESS_TOKEN_SECRET
23
- - SCITEX_LINKEDIN_ACCESS_TOKEN
24
- - SCITEX_REDDIT_CLIENT_ID, SCITEX_REDDIT_CLIENT_SECRET
25
- - SCITEX_YOUTUBE_API_KEY
26
- - SCITEX_GA_PROPERTY_ID
20
+ Credentials use SCITEX_SOCIAL_ prefix (falls back to SOCIALIA_):
21
+ - SCITEX_SOCIAL_X_CONSUMER_KEY, SCITEX_SOCIAL_X_CONSUMER_KEY_SECRET
22
+ - SCITEX_SOCIAL_X_ACCESS_TOKEN, SCITEX_SOCIAL_X_ACCESS_TOKEN_SECRET
23
+ - SCITEX_SOCIAL_X_BEARER_TOKEN
24
+ - SCITEX_SOCIAL_LINKEDIN_CLIENT_ID, SCITEX_SOCIAL_LINKEDIN_CLIENT_SECRET
25
+ - SCITEX_SOCIAL_LINKEDIN_ACCESS_TOKEN
26
+ - SCITEX_SOCIAL_REDDIT_CLIENT_ID, SCITEX_SOCIAL_REDDIT_CLIENT_SECRET
27
+ - SCITEX_SOCIAL_YOUTUBE_API_KEY
28
+ - SCITEX_SOCIAL_GOOGLE_ANALYTICS_PROPERTY_ID
27
29
 
28
30
  Usage
29
31
  -----
@@ -55,7 +57,7 @@ import os as _os
55
57
 
56
58
  # Set branding BEFORE importing socialia
57
59
  _os.environ.setdefault("SOCIALIA_BRAND", "scitex.social")
58
- _os.environ.setdefault("SOCIALIA_ENV_PREFIX", "SCITEX")
60
+ _os.environ.setdefault("SOCIALIA_ENV_PREFIX", "SCITEX_SOCIAL")
59
61
 
60
62
  # Check socialia availability
61
63
  try:
@@ -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