syntaxmatrix 2.5.6__py3-none-any.whl → 2.6.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 (41) hide show
  1. syntaxmatrix/agentic/agents.py +1220 -169
  2. syntaxmatrix/agentic/agents_orchestrer.py +326 -0
  3. syntaxmatrix/agentic/code_tools_registry.py +27 -32
  4. syntaxmatrix/commentary.py +16 -16
  5. syntaxmatrix/core.py +185 -81
  6. syntaxmatrix/db.py +460 -4
  7. syntaxmatrix/{display.py → display_html.py} +2 -6
  8. syntaxmatrix/gpt_models_latest.py +1 -1
  9. syntaxmatrix/media/__init__.py +0 -0
  10. syntaxmatrix/media/media_pixabay.py +277 -0
  11. syntaxmatrix/models.py +1 -1
  12. syntaxmatrix/page_builder_defaults.py +183 -0
  13. syntaxmatrix/page_builder_generation.py +1122 -0
  14. syntaxmatrix/page_layout_contract.py +644 -0
  15. syntaxmatrix/page_patch_publish.py +1471 -0
  16. syntaxmatrix/preface.py +142 -21
  17. syntaxmatrix/profiles.py +28 -10
  18. syntaxmatrix/routes.py +1740 -453
  19. syntaxmatrix/selftest_page_templates.py +360 -0
  20. syntaxmatrix/settings/client_items.py +28 -0
  21. syntaxmatrix/settings/model_map.py +1022 -207
  22. syntaxmatrix/settings/prompts.py +328 -130
  23. syntaxmatrix/static/assets/hero-default.svg +22 -0
  24. syntaxmatrix/static/icons/bot-icon.png +0 -0
  25. syntaxmatrix/static/icons/favicon.png +0 -0
  26. syntaxmatrix/static/icons/logo.png +0 -0
  27. syntaxmatrix/static/icons/logo3.png +0 -0
  28. syntaxmatrix/templates/admin_branding.html +104 -0
  29. syntaxmatrix/templates/admin_features.html +63 -0
  30. syntaxmatrix/templates/admin_secretes.html +108 -0
  31. syntaxmatrix/templates/dashboard.html +296 -133
  32. syntaxmatrix/templates/dataset_resize.html +535 -0
  33. syntaxmatrix/templates/edit_page.html +2535 -0
  34. syntaxmatrix/utils.py +2431 -2383
  35. {syntaxmatrix-2.5.6.dist-info → syntaxmatrix-2.6.2.dist-info}/METADATA +6 -2
  36. {syntaxmatrix-2.5.6.dist-info → syntaxmatrix-2.6.2.dist-info}/RECORD +39 -24
  37. syntaxmatrix/generate_page.py +0 -644
  38. syntaxmatrix/static/icons/hero_bg.jpg +0 -0
  39. {syntaxmatrix-2.5.6.dist-info → syntaxmatrix-2.6.2.dist-info}/WHEEL +0 -0
  40. {syntaxmatrix-2.5.6.dist-info → syntaxmatrix-2.6.2.dist-info}/licenses/LICENSE.txt +0 -0
  41. {syntaxmatrix-2.5.6.dist-info → syntaxmatrix-2.6.2.dist-info}/top_level.txt +0 -0
syntaxmatrix/preface.py CHANGED
@@ -26,6 +26,7 @@ __all__ = [
26
26
  "SB_boxplot",
27
27
  "SB_scatterplot",
28
28
  "SB_heatmap",
29
+ "viz_stacked_bar",
29
30
 
30
31
  # core helpers (all underscore-prefixed functions)
31
32
  "_SMX_caption_from_ctx",
@@ -68,12 +69,12 @@ except Exception:
68
69
  class _Dummy:
69
70
  def __getattr__(self, name):
70
71
  def _f(*a, **k):
71
- from syntaxmatrix.display import show
72
+ from syntaxmatrix.display_html import show
72
73
  show('⚠ seaborn not available; plot skipped.')
73
74
  return _f
74
75
  sns = _Dummy()
75
76
 
76
- from syntaxmatrix.display import show as _SMX_base_show
77
+ from syntaxmatrix.display_html import show as _SMX_base_show
77
78
 
78
79
 
79
80
  boxplot = barplot = histplot = distplot = lineplot = countplot = heatmap = pairplot = None
@@ -134,11 +135,32 @@ def _SMX_axes_have_titles(fig=None):
134
135
  def _SMX_export_png():
135
136
  import io, base64
136
137
  fig = plt.gcf()
138
+
139
+ # If the figure has no real data, skip exporting to avoid blank images.
140
+ try:
141
+ axes = fig.get_axes()
142
+ has_data = any(
143
+ getattr(ax, "has_data", lambda: False)()
144
+ for ax in axes
145
+ )
146
+ except Exception:
147
+ has_data = True # fail open: better a plot than nothing if check breaks
148
+
149
+ if not has_data:
150
+ try:
151
+ from syntaxmatrix.display_html import show as _show
152
+ _show("⚠ Plot skipped: figure has no data to export.")
153
+ except Exception:
154
+ pass
155
+ plt.close(fig)
156
+ return
157
+
137
158
  try:
138
159
  if not _SMX_axes_have_titles(fig):
139
160
  fig.suptitle(_SMX_caption_from_ctx(), fontsize=10)
140
161
  except Exception:
141
162
  pass
163
+
142
164
  buf = io.BytesIO()
143
165
  plt.savefig(buf, format='png', bbox_inches='tight')
144
166
  buf.seek(0)
@@ -150,7 +172,7 @@ def _SMX_export_png():
150
172
  "style='max-width:100%;height:auto;border:1px solid #ccc;border-radius:4px;'/>"
151
173
  )
152
174
  )
153
- plt.close()
175
+ plt.close(fig)
154
176
 
155
177
 
156
178
  def _pick_df():
@@ -228,7 +250,7 @@ def _safe_plot(func, *args, **kwargs):
228
250
  ax = plt.gca()
229
251
  try:
230
252
  if hasattr(ax, 'has_data') and not ax.has_data():
231
- from syntaxmatrix.display import show as _show
253
+ from syntaxmatrix.display_html import show as _show
232
254
  _show('⚠ Empty plot: no data drawn.')
233
255
  except Exception:
234
256
  pass
@@ -238,7 +260,7 @@ def _safe_plot(func, *args, **kwargs):
238
260
  pass
239
261
  return ax
240
262
  except Exception as e:
241
- from syntaxmatrix.display import show as _show
263
+ from syntaxmatrix.display_html import show as _show
242
264
  _show(f'⚠ Plot skipped: {type(e).__name__}: {e}')
243
265
  return None
244
266
 
@@ -398,7 +420,7 @@ def SB_heatmap(*a, **k):
398
420
  except Exception:
399
421
  data = None
400
422
  if data is None:
401
- from syntaxmatrix.display import show as _show
423
+ from syntaxmatrix.display_html import show as _show
402
424
  _show('⚠ Heatmap skipped: no data.')
403
425
  return None
404
426
  if not _missing and hasattr(sns, 'heatmap'):
@@ -442,6 +464,85 @@ def SB_heatmap(*a, **k):
442
464
  return _safe_plot(lambda **kw: _mat_heat())
443
465
 
444
466
 
467
+ def viz_stacked_bar(df=None, x=None, hue=None, normalise=True, top_k=8):
468
+ """
469
+ Stacked (optionally percentage-stacked) bar chart for two categorical columns.
470
+
471
+ - df: optional dataframe. If None, falls back to the active `df` via _pick_df().
472
+ - x: base categorical axis (e.g. 'state').
473
+ - hue: second categorical (e.g. 'body').
474
+ - normalise: if True, show percentages by x; else raw counts.
475
+ """
476
+ from syntaxmatrix.display_html import show as _show
477
+
478
+ d = df if df is not None else _pick_df()
479
+ if d is None:
480
+ _show("⚠ Stacked bar skipped: no dataframe.")
481
+ return None
482
+
483
+ # Choose categorical candidates with reasonable cardinality
484
+ cat_cols = [
485
+ c for c in d.columns
486
+ if (d[c].dtype == "object" or str(d[c].dtype).startswith("category"))
487
+ and d[c].nunique(dropna=True) > 1
488
+ and d[c].nunique(dropna=True) <= 30
489
+ ]
490
+
491
+ if x is None or x not in d.columns:
492
+ x = cat_cols[0] if cat_cols else None
493
+ if hue is None or hue not in d.columns:
494
+ remaining = [c for c in cat_cols if c != x]
495
+ hue = remaining[0] if remaining else None
496
+
497
+ if x is None or hue is None:
498
+ _show("⚠ Stacked bar skipped: need two categorical columns.")
499
+ return None
500
+
501
+ work = d[[x, hue]].dropna()
502
+ if work.empty:
503
+ _show("⚠ Stacked bar skipped: no data.")
504
+ return None
505
+
506
+ def _draw():
507
+ _work = work.copy()
508
+
509
+ # Compress minor hue categories into "Other" for readability
510
+ keep_h = _work[hue].astype(str).value_counts().index[:top_k]
511
+ _work[hue] = _work[hue].astype(str).where(
512
+ _work[hue].astype(str).isin(keep_h),
513
+ other="Other",
514
+ )
515
+
516
+ tab = pd.crosstab(_work[x].astype(str), _work[hue].astype(str))
517
+
518
+ try:
519
+ _show(tab)
520
+ except Exception:
521
+ pass
522
+
523
+ plot_tab = tab.copy()
524
+ ylabel = "Count"
525
+ if normalise:
526
+ plot_tab = plot_tab.div(plot_tab.sum(axis=1), axis=0) * 100
527
+ ylabel = "Percentage"
528
+
529
+ ax = plot_tab.plot(kind="bar", stacked=True, figsize=(8, 4))
530
+ title = f"{hue} composition by {x}"
531
+ if normalise:
532
+ title += " (%)"
533
+
534
+ if not (ax.get_title() or "").strip():
535
+ ax.set_title(title)
536
+ ax.set_xlabel(str(x))
537
+ ax.set_ylabel(ylabel)
538
+ plt.xticks(rotation=45, ha="right")
539
+ plt.tight_layout()
540
+ return ax
541
+
542
+ # NOTE: _safe_plot handles empty plots and layout, but does NOT export PNGs.
543
+ return _safe_plot(lambda **kw: _draw())
544
+
545
+
445
546
  def _safe_concat(objs, **kwargs):
446
547
  import pandas as _pd
447
548
  if objs is None:
@@ -453,23 +554,24 @@ def _safe_concat(objs, **kwargs):
453
554
  except Exception as e:
454
555
  smx_show(f'⚠ concat skipped: {e}')
455
556
  return _pd.DataFrame()
456
-
557
+
457
558
 
458
559
  def _SMX_OHE(**k):
459
560
  # normalise arg name across sklearn versions
460
- if 'sparse' in k and 'sparse_output' not in k:
461
- k['sparse_output'] = k.pop('sparse')
462
- k.setdefault('handle_unknown', 'ignore')
463
- k.setdefault('sparse_output', False)
561
+ if "sparse" in k and "sparse_output" not in k:
562
+ k["sparse_output"] = k.pop("sparse")
563
+ k.setdefault("handle_unknown", "ignore")
564
+ k.setdefault("sparse_output", False)
464
565
  try:
465
- sig = inspect.signature(OneHotEncoder)
466
- if 'sparse_output' not in sig.parameters and 'sparse_output' in k:
467
- k['sparse'] = k.pop('sparse_output')
468
- except Exception:
469
- if 'sparse_output' in k:
470
- k['sparse'] = k.pop('sparse_output')
471
- return OneHotEncoder(**k)
472
-
566
+ if "sparse_output" not in inspect.signature(OneHotEncoder).parameters:
567
+ if "sparse_output" in k:
568
+ k["sparse"] = k.pop("sparse_output")
569
+ return OneHotEncoder(**k)
570
+ except TypeError:
571
+ if "sparse_output" in k:
572
+ k["sparse"] = k.pop("sparse_output")
573
+ return OneHotEncoder(**k)
574
+
473
575
 
474
576
  def _SMX_mm(a, b):
475
577
  try:
@@ -489,13 +591,32 @@ def _SMX_mm(a, b):
489
591
 
490
592
 
491
593
  def _SMX_call(fn, *a, **k):
594
+ """Safe metric invocation that can handle older sklearn signatures.
595
+
596
+ - If the metric accepts the provided keywords, it just runs.
597
+ - If we hit "unexpected keyword argument 'squared'", we drop that kw
598
+ and retry. When the caller asked for squared=False with
599
+ mean_squared_error, we emulate RMSE by taking the square root of
600
+ the returned MSE.
601
+ """
602
+ squared_flag = k.get("squared", None)
492
603
  try:
493
604
  return fn(*a, **k)
494
605
  except TypeError as e:
495
606
  msg = str(e)
496
607
  if "unexpected keyword argument 'squared'" in msg:
497
- k.pop('squared', None)
498
- return fn(*a, **k)
608
+ # remove unsupported kw and retry
609
+ k.pop("squared", None)
610
+ result = fn(*a, **k)
611
+ # emulate squared=False for old sklearn.mean_squared_error
612
+ try:
613
+ if squared_flag is False and getattr(fn, "__name__", "") == "mean_squared_error":
614
+ import numpy as _np
615
+ return float(_np.asarray(result, dtype=float) ** 0.5)
616
+ except Exception:
617
+ # if anything goes wrong, just fall back to the raw result
618
+ pass
619
+ return result
499
620
  raise
500
621
 
501
622
 
syntaxmatrix/profiles.py CHANGED
@@ -2,7 +2,7 @@
2
2
  from openai import OpenAI
3
3
  from google import genai
4
4
  import anthropic
5
-
5
+ from datetime import date
6
6
  from syntaxmatrix.llm_store import list_profiles, load_profile
7
7
 
8
8
  # Preload once at import-time
@@ -31,6 +31,7 @@ def get_profiles():
31
31
  def get_client(profile):
32
32
 
33
33
  provider = profile["provider"].lower()
34
+ model = profile["model"]
34
35
  api_key = profile["api_key"]
35
36
 
36
37
  #1 - Google - gemini series
@@ -41,25 +42,42 @@ def get_client(profile):
41
42
  if provider == "openai":
42
43
  return OpenAI(api_key=api_key)
43
44
 
44
- #3 - xAI - grok series
45
+ #3 - Anthropic claude series
46
+ if provider == "anthropic":
47
+ return anthropic.Anthropic(api_key=api_key)
48
+
49
+ #4 - xAI - grok series
45
50
  if provider == "xai":
46
51
  return OpenAI(api_key=api_key, base_url="https://api.x.ai/v1")
47
52
 
48
- #4 - DeepSeek chat model
49
- if provider == "deepseek":
50
- return OpenAI(api_key=api_key, base_url="https://api.deepseek.com")
53
+ #5 - DeepSeek chat model
54
+ if provider == "deepseek":
55
+ url_special = "https://api.deepseek.com/v3.2_speciale_expires_on_20251215"
56
+ url_default = "https://api.deepseek.com"
57
+ expiry_date = date(2025, 12, 15)
58
+ today = date.today()
59
+ if today < expiry_date:
60
+ base_url = url_special
61
+ else:
62
+ base_url = url_default
63
+ return OpenAI(api_key=api_key, base_url=base_url)
51
64
 
52
- #5 - Moonshot chat model
65
+ #6 - Moonshot chat model
53
66
  if provider == "moonshot": #5
54
67
  return OpenAI(api_key=api_key, base_url="https://api.moonshot.ai/v1")
55
68
 
56
- #6 - Alibaba qwen series
69
+ #7 - Alibaba qwen series
57
70
  if provider == "alibaba": #6
58
71
  return OpenAI(api_key=api_key, base_url="https://dashscope-intl.aliyuncs.com/compatible-mode/v1",)
59
72
 
60
- #7 - Anthropic claude series
61
- if provider == "anthropic": #7
62
- return anthropic.Anthropic(api_key=api_key)
73
+ #8 - ZAI GLM series
74
+ if provider == "zai":
75
+ url = ""
76
+ if profile.get('name') == "coding":
77
+ url = "https://api.z.ai/api/coding/paas/v4"
78
+ else: url = "https://api.z.ai/api/paas/v4"
79
+ return OpenAI(api_key=api_key, base_url=url)
80
+
63
81
 
64
82
  def drop_cached_profile_by_name(profile_name: str) -> bool:
65
83
  """