syntaxmatrix 2.6.4.4__py3-none-any.whl → 3.0.1__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/__init__.py +6 -4
  2. syntaxmatrix/agentic/agents.py +206 -26
  3. syntaxmatrix/agentic/agents_orchestrer.py +16 -10
  4. syntaxmatrix/client_docs.py +237 -0
  5. syntaxmatrix/commentary.py +96 -25
  6. syntaxmatrix/core.py +142 -56
  7. syntaxmatrix/dataset_preprocessing.py +2 -2
  8. syntaxmatrix/db.py +0 -17
  9. syntaxmatrix/kernel_manager.py +174 -150
  10. syntaxmatrix/page_builder_generation.py +656 -63
  11. syntaxmatrix/page_layout_contract.py +25 -3
  12. syntaxmatrix/page_patch_publish.py +368 -15
  13. syntaxmatrix/plugins/__init__.py +0 -0
  14. syntaxmatrix/premium/__init__.py +10 -2
  15. syntaxmatrix/premium/catalogue/__init__.py +121 -0
  16. syntaxmatrix/premium/gate.py +15 -3
  17. syntaxmatrix/premium/state.py +507 -0
  18. syntaxmatrix/premium/verify.py +222 -0
  19. syntaxmatrix/profiles.py +1 -1
  20. syntaxmatrix/routes.py +9847 -8004
  21. syntaxmatrix/settings/model_map.py +50 -65
  22. syntaxmatrix/settings/prompts.py +1186 -414
  23. syntaxmatrix/settings/string_navbar.py +4 -4
  24. syntaxmatrix/static/icons/bot_icon.png +0 -0
  25. syntaxmatrix/static/icons/bot_icon2.png +0 -0
  26. syntaxmatrix/templates/admin_billing.html +408 -0
  27. syntaxmatrix/templates/admin_branding.html +65 -2
  28. syntaxmatrix/templates/admin_features.html +54 -0
  29. syntaxmatrix/templates/dashboard.html +285 -8
  30. syntaxmatrix/templates/edit_page.html +199 -18
  31. syntaxmatrix/themes.py +17 -17
  32. syntaxmatrix/workspace_db.py +0 -23
  33. syntaxmatrix-3.0.1.dist-info/METADATA +219 -0
  34. {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.1.dist-info}/RECORD +38 -33
  35. {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.1.dist-info}/WHEEL +1 -1
  36. syntaxmatrix/settings/default.yaml +0 -13
  37. syntaxmatrix-2.6.4.4.dist-info/METADATA +0 -539
  38. syntaxmatrix-2.6.4.4.dist-info/licenses/LICENSE.txt +0 -21
  39. /syntaxmatrix/{plugin_manager.py → plugins/plugin_manager.py} +0 -0
  40. /syntaxmatrix/static/icons/{logo3.png → logo2.png} +0 -0
  41. {syntaxmatrix-2.6.4.4.dist-info → syntaxmatrix-3.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,237 @@
1
+ # syntaxmatrix/client_docs.py
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import re
6
+ import html
7
+ from dataclasses import dataclass
8
+ from typing import List, Tuple
9
+
10
+ from flask import abort, render_template
11
+ from markupsafe import Markup
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class TocItem:
16
+ level: int
17
+ id: str
18
+ text: str
19
+
20
+
21
+ _slug_rx = re.compile(r"[^a-z0-9\- ]+")
22
+ _ws_rx = re.compile(r"\s+")
23
+
24
+
25
+ def _slugify(text: str) -> str:
26
+ t = text.strip().lower()
27
+ t = _slug_rx.sub("", t)
28
+ t = _ws_rx.sub("-", t)
29
+ t = t.strip("-")
30
+ return t or "section"
31
+
32
+
33
+ def _extract_headings(md: str) -> List[Tuple[int, str]]:
34
+ """
35
+ Extract ATX-style markdown headings (#, ##, ###, ...), ignoring fenced code blocks.
36
+ """
37
+ headings: List[Tuple[int, str]] = []
38
+ in_code = False
39
+ for line in md.splitlines():
40
+ if line.strip().startswith("```"):
41
+ in_code = not in_code
42
+ continue
43
+ if in_code:
44
+ continue
45
+
46
+ m = re.match(r"^(#{1,6})\s+(.+?)\s*$", line)
47
+ if not m:
48
+ continue
49
+ level = len(m.group(1))
50
+ title = m.group(2).strip()
51
+ # Avoid weird headings like "### ----"
52
+ if title and not all(ch in "-_=*" for ch in title):
53
+ headings.append((level, title))
54
+ return headings
55
+
56
+
57
+ def _render_markdown_minimal(md: str, heading_ids: dict[str, str]) -> str:
58
+ """
59
+ Minimal markdown renderer (safe-by-default):
60
+ - headings, paragraphs, bullet lists, code fences, inline code, links, images
61
+ - everything else is HTML-escaped
62
+ """
63
+ lines = md.splitlines()
64
+ out: List[str] = []
65
+
66
+ in_code = False
67
+ code_lang = ""
68
+ code_buf: List[str] = []
69
+
70
+ in_ul = False
71
+
72
+ def flush_ul():
73
+ nonlocal in_ul
74
+ if in_ul:
75
+ out.append("</ul>")
76
+ in_ul = False
77
+
78
+ def flush_code():
79
+ nonlocal in_code, code_lang, code_buf
80
+ if not in_code:
81
+ return
82
+ code_text = "\n".join(code_buf)
83
+ out.append(
84
+ f'<pre class="smx-code"><code class="language-{html.escape(code_lang)}">'
85
+ f"{html.escape(code_text)}</code></pre>"
86
+ )
87
+ in_code = False
88
+ code_lang = ""
89
+ code_buf = []
90
+
91
+ def inline_fmt(s: str) -> str:
92
+ s = html.escape(s)
93
+
94
+ # inline code: `code`
95
+ s = re.sub(r"`([^`]+)`", lambda m: f"<code>{html.escape(m.group(1))}</code>", s)
96
+
97
+ # images: ![alt](url)
98
+ s = re.sub(
99
+ r"!\[([^\]]*)\]\(([^)]+)\)",
100
+ lambda m: f'<img alt="{html.escape(m.group(1))}" src="{html.escape(m.group(2))}" />',
101
+ s,
102
+ )
103
+
104
+ # links: [text](url)
105
+ s = re.sub(
106
+ r"\[([^\]]+)\]\(([^)]+)\)",
107
+ lambda m: f'<a href="{html.escape(m.group(2))}" target="_blank" rel="noopener noreferrer">{html.escape(m.group(1))}</a>',
108
+ s,
109
+ )
110
+
111
+ # bold **text**
112
+ s = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", s)
113
+
114
+ # italics *text* (simple)
115
+ s = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"<em>\1</em>", s)
116
+
117
+ return s
118
+
119
+ # Pre-map heading text to stable IDs (supports duplicates)
120
+ # heading_ids is already built with de-duplication.
121
+ for raw in lines:
122
+ line = raw.rstrip("\n")
123
+
124
+ # fenced code blocks
125
+ m_code = re.match(r"^\s*```(\w+)?\s*$", line)
126
+ if m_code:
127
+ flush_ul()
128
+ if in_code:
129
+ flush_code()
130
+ else:
131
+ in_code = True
132
+ code_lang = (m_code.group(1) or "").strip()
133
+ code_buf = []
134
+ continue
135
+
136
+ if in_code:
137
+ code_buf.append(line)
138
+ continue
139
+
140
+ # headings
141
+ m_h = re.match(r"^(#{1,6})\s+(.+?)\s*$", line)
142
+ if m_h:
143
+ flush_ul()
144
+ level = len(m_h.group(1))
145
+ text = m_h.group(2).strip()
146
+ hid = heading_ids.get(text) or _slugify(text)
147
+ out.append(
148
+ f'<h{level} id="{html.escape(hid)}" class="smx-h">{inline_fmt(text)}</h{level}>'
149
+ )
150
+ continue
151
+
152
+ # bullet list items (supports "-" or "*")
153
+ m_li = re.match(r"^\s*[-*]\s+(.+)\s*$", line)
154
+ if m_li:
155
+ if not in_ul:
156
+ out.append("<ul>")
157
+ in_ul = True
158
+ out.append(f"<li>{inline_fmt(m_li.group(1).strip())}</li>")
159
+ continue
160
+
161
+ # blank line ends lists/paragraph chunks
162
+ if line.strip() == "":
163
+ flush_ul()
164
+ out.append("")
165
+ continue
166
+
167
+ # blockquote (single-line)
168
+ m_bq = re.match(r"^\s*>\s+(.+)\s*$", line)
169
+ if m_bq:
170
+ flush_ul()
171
+ out.append(f"<blockquote>{inline_fmt(m_bq.group(1).strip())}</blockquote>")
172
+ continue
173
+
174
+ # horizontal rule
175
+ if re.match(r"^\s*---\s*$", line):
176
+ flush_ul()
177
+ out.append("<hr/>")
178
+ continue
179
+
180
+ # normal paragraph line
181
+ flush_ul()
182
+ out.append(f"<p>{inline_fmt(line.strip())}</p>")
183
+
184
+ flush_ul()
185
+ flush_code()
186
+
187
+ return "\n".join(out)
188
+
189
+
190
+ def build_docs_html_and_toc(md: str) -> Tuple[str, List[TocItem]]:
191
+ headings = _extract_headings(md)
192
+
193
+ # Build stable unique IDs
194
+ used: dict[str, int] = {}
195
+ heading_ids: dict[str, str] = {}
196
+
197
+ toc: List[TocItem] = []
198
+ for level, title in headings:
199
+ base = _slugify(title)
200
+ n = used.get(base, 0)
201
+ used[base] = n + 1
202
+ hid = base if n == 0 else f"{base}-{n+1}"
203
+
204
+ # map by raw title (good enough for this README)
205
+ # if duplicates exist with same title, only first maps here; duplicates are still in TOC correctly
206
+ if title not in heading_ids:
207
+ heading_ids[title] = hid
208
+
209
+ toc.append(TocItem(level=level, id=hid, text=title))
210
+
211
+ html_content = _render_markdown_minimal(md, heading_ids)
212
+ return html_content, toc
213
+
214
+
215
+ def register_client_docs_routes(app, client_dir: str) -> None:
216
+ """
217
+ Call this once during app initialisation.
218
+ Exposes:
219
+ GET /docs
220
+ """
221
+ @app.get("/docs")
222
+ def smx_client_docs():
223
+ readme_path = os.path.join(client_dir, "README.md")
224
+ if not os.path.exists(readme_path):
225
+ abort(404, description="README.md not found in client root")
226
+
227
+ with open(readme_path, "r", encoding="utf-8") as f:
228
+ md = f.read()
229
+
230
+ docs_html, toc = build_docs_html_and_toc(md)
231
+
232
+ return render_template(
233
+ "client_docs.html",
234
+ page_title="System Documentation",
235
+ toc=toc,
236
+ docs_html=Markup(docs_html),
237
+ )
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os, io, re, json, base64
3
+ import re, json
4
+ import html as _html
5
+ import re as _re
4
6
  from typing import Any, Dict, List, Optional
5
7
 
6
8
  from syntaxmatrix import profiles as _prof
@@ -114,11 +116,49 @@ def sniff_tables_from_html(html: str) -> List[Dict[str, Any]]:
114
116
  return tables
115
117
 
116
118
 
119
+ def sniff_pre_text_from_html(html: str, *, max_lines: int = 18, max_chars: int = 900) -> List[str]:
120
+ """Extract short, useful plain-text snippets from <pre> blocks (metrics, tests, notes)."""
121
+ if not html:
122
+ return []
123
+ pres = _re.findall(r"<pre[^>]*>(.*?)</pre>", html, flags=_re.DOTALL | _re.IGNORECASE)
124
+ out: List[str] = []
125
+ for p in pres:
126
+ t = _strip_tags(p)
127
+ t = _html.unescape(t)
128
+ t = t.replace("\r\n", "\n").replace("\r", "\n")
129
+
130
+ for ln in t.split("\n"):
131
+ ln = (ln or "").strip()
132
+ if not ln:
133
+ continue
134
+ # keep only “result-ish” lines; drop giant dumps if any sneak in
135
+ if len(ln) > 260:
136
+ ln = ln[:260].rstrip() + "…"
137
+ out.append(ln)
138
+
139
+ # de-dup, preserve order
140
+ seen = set()
141
+ cleaned = []
142
+ for ln in out:
143
+ k = ln.lower()
144
+ if k in seen:
145
+ continue
146
+ seen.add(k)
147
+ cleaned.append(ln)
148
+
149
+ # cap total length
150
+ joined = "\n".join(cleaned)
151
+ joined = joined[:max_chars]
152
+ cleaned = joined.split("\n")[:max_lines]
153
+ return [c for c in (x.strip() for x in cleaned) if c]
154
+
155
+
117
156
  def build_display_summary(question: str,
118
157
  mpl_axes: List[Dict[str, Any]],
119
158
  html_blocks: List[str]) -> Dict[str, Any]:
120
159
  html_joined = "\n".join(str(b) for b in html_blocks)
121
160
  tables = sniff_tables_from_html(html_joined)
161
+ pre_snips = sniff_pre_text_from_html(html_joined)
122
162
 
123
163
  axes_clean=[]
124
164
  for ax in mpl_axes:
@@ -132,11 +172,14 @@ def build_display_summary(question: str,
132
172
  return {
133
173
  "question": (question or "").strip(),
134
174
  "axes": axes_clean,
135
- "tables": tables
175
+ "tables": tables,
176
+ "text_snippets": pre_snips,
136
177
  }
137
178
 
138
179
  def _context_strings(context: Dict[str, Any]) -> List[str]:
139
180
  s = [context.get("question","")]
181
+ s += (context.get("text_snippets", []) or [])
182
+
140
183
  for ax in context.get("axes", []) or []:
141
184
  s += [ax.get("title",""), ax.get("x_label",""), ax.get("y_label","")]
142
185
  s += (ax.get("legend", []) or [])
@@ -159,35 +202,63 @@ def phrase_commentary_vision(context: Dict[str, Any], images_b64: List[str]) ->
159
202
  send figures + text; otherwise fall back to a text-only prompt grounded by labels.
160
203
  """
161
204
 
162
- _SYSTEM_VISION = ("""
163
- You are a plots, graphs, and tables data analyst. You analyse and interprete in details and give your responses in plain english what the already-rendered plots and visuals mean as a response to the question. If the relevant information is made available, then, you must first answer the question explicitly and then proceed to explain the plots and tables.
164
- Use the information visible in the attached figures and the provided context strings (texts, tables, plot field names, labels).
165
- You should provide interpretations without prelude or preamble.
166
- """)
205
+ _SYSTEM_VISION = """
206
+ You are an applied data analyst writing an answer to the user's question.
207
+
208
+ Your priority:
209
+ 1) Answer the question directly (clear verdict first).
210
+ 2) Justify the verdict using evidence from the figures and the provided context.
211
+ 3) Keep it readable for a non-technical stakeholder.
212
+
213
+ Rules:
214
+ - Do NOT write a preamble.
215
+ - Do NOT narrate what a chart “looks like”; interpret it in relation to the question.
216
+ - Only use numbers if they are visible in the figures or included in the text snippets/context.
217
+ - Output must be safe HTML only: <b>, <p>, <ul>, <li>, <br>. No <style>, no <script>, no images.
218
+ """.strip()
167
219
 
168
220
  _USER_TMPL_VISION = """
169
- question:
170
- {q}
171
-
172
- Visible context strings (tables, plots: titles, axes, legends, headers):
173
- {ctx}
174
-
175
- Write a comprehensive conclusion (~250-350 words) as follows:
176
- - <b>Headline</b>
177
- 2-3 sentence answering the question from an overview of all the output.
178
- - <b>Evidence</b>
179
- 8-10 bullets referencing the (output-texts/tables/panels/axes/legend groups) seen in the output.
180
- As you reference the visuals, you should interprete them in a way to show how they answer the question.
181
- - <b>Limitations</b>
182
- 1 bullet; avoid quoting numbers unless present in context.
183
- - <b>Recommendations</b>
184
- 1 bullet.
185
- """
221
+ <b>Question</b>
222
+ <p>{q}</p>
223
+
224
+ <b>Available evidence</b>
225
+ <p><b>Text snippets:</b><br>{snips}</p>
226
+ <p><b>Plot/table context strings (titles, axes, legends, headers):</b><br>{ctx}</p>
227
+
228
+ Write the response in this exact structure:
229
+
230
+ <b>Answer</b>
231
+ <p>
232
+ Give a direct answer to the question in 2-4 sentences.
233
+ If the correct output is a decision (e.g., association vs none, higher vs lower, best model, significant vs not),
234
+ state it explicitly.
235
+ </p>
236
+
237
+ <b>Key evidence</b>
238
+ <ul>
239
+ <li>5–8 bullets. Each bullet must link evidence → conclusion.</li>
240
+ <li>Reference plots/tables by their titles/axes/headers when possible.</li>
241
+ <li>Use numbers only if present in snippets or clearly visible.</li>
242
+ </ul>
243
+
244
+ <b>What this means</b>
245
+ <ul>
246
+ <li>2-4 bullets translating the finding into a practical takeaway.</li>
247
+ </ul>
248
+
249
+ <b>Limitations</b>
250
+ <ul><li>1-2 bullets (short).</li></ul>
251
+
252
+ <b>Next steps</b>
253
+ <ul><li>2 bullets (actionable).</li></ul>
254
+ """.strip()
186
255
 
187
256
  visible = _context_strings(context)
257
+ snips = "\n".join(context.get("text_snippets", []) or [])
188
258
  user = _USER_TMPL_VISION.format(
189
259
  q=context.get("question",""),
190
- ctx=json.dumps(visible, ensure_ascii=False, indent=2)
260
+ snips=_html.escape(snips).replace("\n", "<br>"),
261
+ ctx=_html.escape(json.dumps(visible, ensure_ascii=False, indent=2)).replace("\n", "<br>")
191
262
  )
192
263
 
193
264
  commentary_profile = _prof.get_profile("imagetexter") or _prof.get_profile("admin")