Nolan Zandi Claude Sonnet 4.6 commited on
Commit
7afc8ec
·
1 Parent(s): c007902

feat: add rolling stats, K-Means clustering, and hypothesis testing

Browse files

- Add rolling_stats_func: overlays rolling mean/std/min/max on a time
series using plotly.graph_objects; supports optional category grouping
- Add kmeans_clustering_func: StandardScaler + KMeans (sklearn), scatter
plot coloured by cluster, centroid summary table
- Add hypothesis_test_func: independent/one-sample t-test (scipy) and
chi-square test of independence; returns styled HTML results table
- Register all three in tool schemas, __init__.py, available_functions,
and system message; add scipy and scikit-learn to requirements.txt
- Refactor chart_tools.py: extract _LAYOUT_PARAM and _TRACE_STYLE_PARAM
constants to eliminate ~8x copy-paste; remove layout from required
arrays on line/bar/pie; fix hardcoded 'SQLite' references; standardise
all tool descriptions to consistent what/when/returns format
- Refactor stats_tools.py: same description standardisation
- Shorten tools_desc in system message from 13 lines to 2 sentences
(LLM already receives full schema descriptions separately)
- Fix copy-paste bug: sql example-question generator was using MongoDB
system persona instead of PostgreSQL
- Fix ROOT_URL defaulting to None when env var unset, which caused
chart iframe src to contain literal 'None' in the path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

functions/__init__.py CHANGED
@@ -1,15 +1,17 @@
1
  from .query_functions import graphql_schema_query, graphql_csv_query, query_func
2
  from .chart_functions import table_generation_func, scatter_chart_generation_func, \
3
  line_chart_generation_func, bar_chart_generation_func, pie_chart_generation_func, \
4
- histogram_generation_func, box_chart_generation_func, correlation_heatmap_func, scatter_chart_fig
 
5
  from .chat_functions import example_question_generator, chatbot_func
6
- from .stat_functions import regression_func, descriptive_stats_func
 
7
 
8
  __all__ = [
9
  "query_func", "graphql_schema_query", "graphql_csv_query",
10
  "table_generation_func", "scatter_chart_generation_func", "line_chart_generation_func",
11
  "bar_chart_generation_func", "pie_chart_generation_func", "histogram_generation_func",
12
- "box_chart_generation_func", "correlation_heatmap_func",
13
- "regression_func", "descriptive_stats_func",
14
  "scatter_chart_fig", "example_question_generator", "chatbot_func",
15
  ]
 
1
  from .query_functions import graphql_schema_query, graphql_csv_query, query_func
2
  from .chart_functions import table_generation_func, scatter_chart_generation_func, \
3
  line_chart_generation_func, bar_chart_generation_func, pie_chart_generation_func, \
4
+ histogram_generation_func, box_chart_generation_func, correlation_heatmap_func, \
5
+ scatter_chart_fig, rolling_stats_func
6
  from .chat_functions import example_question_generator, chatbot_func
7
+ from .stat_functions import regression_func, descriptive_stats_func, \
8
+ kmeans_clustering_func, hypothesis_test_func
9
 
10
  __all__ = [
11
  "query_func", "graphql_schema_query", "graphql_csv_query",
12
  "table_generation_func", "scatter_chart_generation_func", "line_chart_generation_func",
13
  "bar_chart_generation_func", "pie_chart_generation_func", "histogram_generation_func",
14
+ "box_chart_generation_func", "correlation_heatmap_func", "rolling_stats_func",
15
+ "regression_func", "descriptive_stats_func", "kmeans_clustering_func", "hypothesis_test_func",
16
  "scatter_chart_fig", "example_question_generator", "chatbot_func",
17
  ]
functions/chart_functions.py CHANGED
@@ -9,7 +9,7 @@ from dotenv import load_dotenv
9
 
10
  load_dotenv()
11
 
12
- root_url = os.getenv("ROOT_URL")
13
 
14
  def llm_chart_data_scrub(data, layout):
15
  #Processing data to account for variation from LLM
@@ -427,6 +427,75 @@ def correlation_heatmap_func(session_hash, session_folder, columns: List[str]=[]
427
  return {"reply": f"There was an error generating the correlation heatmap. Error: {e}. You should probably try again."}
428
 
429
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  def table_generation_func(session_hash, session_folder, **kwargs):
431
  print("TABLE GENERATION")
432
  try:
 
9
 
10
  load_dotenv()
11
 
12
+ root_url = os.getenv("ROOT_URL", "")
13
 
14
  def llm_chart_data_scrub(data, layout):
15
  #Processing data to account for variation from LLM
 
427
  return {"reply": f"There was an error generating the correlation heatmap. Error: {e}. You should probably try again."}
428
 
429
 
430
+ def rolling_stats_func(x_column: str, y_column: str, session_hash, session_folder,
431
+ window: int = 7, stats: List[str] = ["mean"],
432
+ layout: List[dict] = [{}], category: str = "", **kwargs):
433
+ try:
434
+ import plotly.graph_objects as go
435
+
436
+ dir_path = TEMP_DIR / str(session_hash) / str(session_folder)
437
+ chart_path = f'{dir_path}/chart.html'
438
+ csv_query_path = f'{dir_path}/query.csv'
439
+
440
+ df = pd.read_csv(csv_query_path)
441
+
442
+ try:
443
+ df[x_column] = pd.to_datetime(df[x_column])
444
+ except Exception:
445
+ pass
446
+ df = df.sort_values(x_column)
447
+
448
+ valid_stats = {"mean", "std", "min", "max"}
449
+ selected_stats = [s for s in stats if s in valid_stats] or ["mean"]
450
+
451
+ fig = go.Figure()
452
+
453
+ groups = df[category].unique().tolist() if category and category in df.columns else [None]
454
+
455
+ for group in groups:
456
+ group_df = df[df[category] == group] if group is not None else df
457
+ prefix = f"{group} — " if group is not None else ""
458
+
459
+ fig.add_trace(go.Scatter(
460
+ x=group_df[x_column].values, y=group_df[y_column].values,
461
+ mode="lines", name=f"{prefix}{y_column} (raw)",
462
+ opacity=0.35, line=dict(width=1)
463
+ ))
464
+
465
+ rolling_obj = group_df[y_column].rolling(window)
466
+ for stat in selected_stats:
467
+ rolled = getattr(rolling_obj, stat)()
468
+ fig.add_trace(go.Scatter(
469
+ x=group_df[x_column].values, y=rolled.values,
470
+ mode="lines", name=f"{prefix}Rolling {stat.capitalize()} (w={window})",
471
+ line=dict(width=2.5)
472
+ ))
473
+
474
+ fig.update_layout(
475
+ title=f"Rolling Statistics (window={window}) — {y_column}",
476
+ xaxis_title=x_column,
477
+ yaxis_title=y_column,
478
+ font=dict(family="Inter, system-ui, sans-serif"),
479
+ legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
480
+ )
481
+
482
+ _, layout_dict = llm_chart_data_scrub({}, layout)
483
+ if layout_dict:
484
+ fig.update_layout(**layout_dict)
485
+
486
+ pio.write_html(fig, chart_path, full_html=False)
487
+
488
+ chart_url = f'{root_url}/gradio_api/file/temp/{session_hash}/{session_folder}/chart.html'
489
+ iframe = ('Please display this iframe: <div style=overflow:auto;><iframe\n scrolling="yes"\n width="1000px"\n height="500px"\n src="'
490
+ + chart_url + '"\n frameborder="0"\n allowfullscreen\n></iframe>\n</div>')
491
+ return {"reply": iframe}
492
+
493
+ except Exception as e:
494
+ print("ROLLING STATS ERROR")
495
+ print(e)
496
+ return {"reply": f"There was an error generating the rolling statistics chart. Error: {e}. You should probably try again."}
497
+
498
+
499
  def table_generation_func(session_hash, session_folder, **kwargs):
500
  print("TABLE GENERATION")
501
  try:
functions/chat_functions.py CHANGED
@@ -27,7 +27,7 @@ def example_question_message(data_source, name, titles, schema):
27
  data insights. Return nothing more than the array of questions because I need that specific data structure
28
  to process your response. No other response type or data structure will work."""],
29
 
30
- 'sql' : [f"You are a helpful and knowledgeable agent who has access to an MongoDB NoSQL document database called {name}.",
31
  f"""We have a PostgreSQL database with the following tables: {titles}.
32
  We also have an AI agent with access to the same database that will be performing data analysis.
33
  Please return an array of seven strings, each one being a question for our data analysis agent
@@ -82,18 +82,11 @@ def system_message(data_source, titles, schema=""):
82
  print("TITLES")
83
  print(titles)
84
 
85
- tools_desc = """
86
- You also have access to a table function, called table_generation_func, that takes query results from query.csv and returns a formatted inline table.
87
- You also have access to a scatter plot function, called scatter_chart_generation_func, that uses plotly dictionaries to generate a scatter plot and returns an iframe to display.
88
- You also have access to a line chart function, called line_chart_generation_func, that uses plotly dictionaries to generate a line chart and returns an iframe to display.
89
- You also have access to a bar chart function, called bar_chart_generation_func, that uses plotly dictionaries to generate a bar chart and returns an iframe to display.
90
- You also have access to a pie chart function, called pie_chart_generation_func, that uses plotly dictionaries to generate a pie chart and returns an iframe to display.
91
- You also have access to a histogram function, called histogram_generation_func, that uses plotly dictionaries to generate a histogram and returns an iframe to display.
92
- You also have access to a box plot function, called box_chart_generation_func, that shows the distribution of a numeric column optionally grouped by a category, and returns an iframe to display.
93
- You also have access to a correlation heatmap function, called correlation_heatmap_func, that computes pairwise correlations between numeric columns and returns a colour-coded heatmap iframe to display.
94
- You also have access to a descriptive statistics function, called descriptive_stats_func, that returns a summary statistics table (count, mean, std, min, quartiles, max) for numeric columns.
95
- You also have access to a linear regression function, called regression_func, that takes column names for independent and dependent variables and returns a regression summary and chart iframe.
96
- Could you please always display the generated charts, tables, and visualizations as part of your output?"""
97
 
98
  system_message_dict = {
99
  'file_upload': (
@@ -128,8 +121,9 @@ def chatbot_func(message, history, session_hash, data_source, titles, schema, *a
128
  table_generation_func, regression_func, descriptive_stats_func,
129
  scatter_chart_generation_func, line_chart_generation_func, bar_chart_generation_func,
130
  pie_chart_generation_func, histogram_generation_func,
131
- box_chart_generation_func, correlation_heatmap_func,
132
  query_func, graphql_schema_query, graphql_csv_query,
 
133
  )
134
  import tools.tools as tools
135
 
@@ -145,8 +139,11 @@ def chatbot_func(message, history, session_hash, data_source, titles, schema, *a
145
  "histogram_generation_func": histogram_generation_func,
146
  "box_chart_generation_func": box_chart_generation_func,
147
  "correlation_heatmap_func": correlation_heatmap_func,
 
148
  "regression_func": regression_func,
149
  "descriptive_stats_func": descriptive_stats_func,
 
 
150
  }
151
 
152
  if message_dict[session_hash][data_source] != None:
 
27
  data insights. Return nothing more than the array of questions because I need that specific data structure
28
  to process your response. No other response type or data structure will work."""],
29
 
30
+ 'sql' : [f"You are a helpful and knowledgeable agent who has access to a PostgreSQL database called {name}.",
31
  f"""We have a PostgreSQL database with the following tables: {titles}.
32
  We also have an AI agent with access to the same database that will be performing data analysis.
33
  Please return an array of seven strings, each one being a question for our data analysis agent
 
82
  print("TITLES")
83
  print(titles)
84
 
85
+ tools_desc = (
86
+ " You have access to tools for querying the data source, generating charts and visualisations,"
87
+ " and performing statistical analyses — use them proactively whenever they would help answer the user's question."
88
+ " Always display any charts, tables, and visualisations inline in your responses by outputting the returned HTML verbatim."
89
+ )
 
 
 
 
 
 
 
90
 
91
  system_message_dict = {
92
  'file_upload': (
 
121
  table_generation_func, regression_func, descriptive_stats_func,
122
  scatter_chart_generation_func, line_chart_generation_func, bar_chart_generation_func,
123
  pie_chart_generation_func, histogram_generation_func,
124
+ box_chart_generation_func, correlation_heatmap_func, rolling_stats_func,
125
  query_func, graphql_schema_query, graphql_csv_query,
126
+ kmeans_clustering_func, hypothesis_test_func,
127
  )
128
  import tools.tools as tools
129
 
 
139
  "histogram_generation_func": histogram_generation_func,
140
  "box_chart_generation_func": box_chart_generation_func,
141
  "correlation_heatmap_func": correlation_heatmap_func,
142
+ "rolling_stats_func": rolling_stats_func,
143
  "regression_func": regression_func,
144
  "descriptive_stats_func": descriptive_stats_func,
145
+ "kmeans_clustering_func": kmeans_clustering_func,
146
+ "hypothesis_test_func": hypothesis_test_func,
147
  }
148
 
149
  if message_dict[session_hash][data_source] != None:
functions/stat_functions.py CHANGED
@@ -5,12 +5,12 @@ from utils import TEMP_DIR
5
  import plotly.express as px
6
  import plotly.io as pio
7
  import os
8
- from functions import scatter_chart_fig
9
  from dotenv import load_dotenv
10
 
11
  load_dotenv()
12
 
13
- root_url = os.getenv("ROOT_URL")
14
 
15
  def descriptive_stats_func(session_hash, session_folder, columns: List[str]=[], **kwargs):
16
  print("DESCRIPTIVE STATISTICS")
@@ -63,6 +63,190 @@ def descriptive_stats_func(session_hash, session_folder, columns: List[str]=[],
63
  return {"reply": f"There was an error generating descriptive statistics. Error: {e}. You should probably try again."}
64
 
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  def regression_func(independent_variables: List[str], dependent_variable: str, session_hash, session_folder, category: str='', **kwargs):
67
  print("LINEAR REGRESSION CALCULATION")
68
  print(independent_variables)
 
5
  import plotly.express as px
6
  import plotly.io as pio
7
  import os
8
+ from functions.chart_functions import scatter_chart_fig, llm_chart_data_scrub
9
  from dotenv import load_dotenv
10
 
11
  load_dotenv()
12
 
13
+ root_url = os.getenv("ROOT_URL", "")
14
 
15
  def descriptive_stats_func(session_hash, session_folder, columns: List[str]=[], **kwargs):
16
  print("DESCRIPTIVE STATISTICS")
 
63
  return {"reply": f"There was an error generating descriptive statistics. Error: {e}. You should probably try again."}
64
 
65
 
66
+ def kmeans_clustering_func(feature_columns: List[str], x_column: str, y_column: str,
67
+ session_hash, session_folder, n_clusters: int = 3,
68
+ layout: List[dict] = [{}], **kwargs):
69
+ print("KMEANS CLUSTERING")
70
+ try:
71
+ from sklearn.cluster import KMeans
72
+ from sklearn.preprocessing import StandardScaler
73
+ from html import escape
74
+
75
+ dir_path = TEMP_DIR / str(session_hash) / str(session_folder)
76
+ chart_path = f'{dir_path}/chart.html'
77
+ csv_query_path = f'{dir_path}/query.csv'
78
+
79
+ df = pd.read_csv(csv_query_path)
80
+
81
+ feature_df = df[feature_columns].select_dtypes(include='number').dropna()
82
+ if feature_df.shape[1] < 1:
83
+ return {"reply": "No numeric feature columns found for clustering. Please refine your query to include numeric columns."}
84
+
85
+ X_scaled = StandardScaler().fit_transform(feature_df)
86
+ labels = KMeans(n_clusters=n_clusters, random_state=42, n_init=10).fit_predict(X_scaled)
87
+
88
+ df_clustered = df.loc[feature_df.index].copy()
89
+ df_clustered['Cluster'] = [f'Cluster {l}' for l in labels]
90
+
91
+ fig = px.scatter(
92
+ df_clustered, x=x_column, y=y_column, color='Cluster',
93
+ title=f'K-Means Clustering (k={n_clusters})',
94
+ )
95
+ fig.update_layout(font=dict(family='Inter, system-ui, sans-serif'))
96
+
97
+ _, layout_dict = llm_chart_data_scrub({}, layout)
98
+ if layout_dict:
99
+ fig.update_layout(**layout_dict)
100
+
101
+ pio.write_html(fig, chart_path, full_html=False)
102
+
103
+ chart_url = f'{root_url}/gradio_api/file/temp/{session_hash}/{session_folder}/chart.html'
104
+ iframe = ('Please display this iframe: <div style=overflow:auto;><iframe\n scrolling="yes"\n width="1000px"\n height="500px"\n src="'
105
+ + chart_url + '"\n frameborder="0"\n allowfullscreen\n></iframe>\n</div>')
106
+
107
+ cluster_summary = df_clustered.groupby('Cluster')[feature_columns].mean().round(3)
108
+ header_cells = '<th style="background:#1e40af;">Cluster</th>' + ''.join(
109
+ f'<th>{escape(str(col))}</th>' for col in cluster_summary.columns
110
+ )
111
+ row_html = [
112
+ '<tr>'
113
+ + f'<td style="font-weight:600;color:#1e40af;background:#eff6ff;white-space:nowrap;">{escape(str(idx))}</td>'
114
+ + ''.join(f'<td>{escape(str(val))}</td>' for val in row)
115
+ + '</tr>'
116
+ for idx, row in cluster_summary.iterrows()
117
+ ]
118
+ style = (
119
+ '<style>'
120
+ '.vda-table-wrap{overflow-x:auto;margin:8px 0;border-radius:8px;border:1px solid #e5e7eb;}'
121
+ '.vda-table{width:100%;border-collapse:collapse;font-size:13px;font-family:Inter,system-ui,sans-serif;}'
122
+ '.vda-table thead th{background:#3B82F6;color:#fff;padding:9px 14px;text-align:left;white-space:nowrap;font-weight:600;}'
123
+ '.vda-table tbody td{padding:7px 14px;border-bottom:1px solid #f1f5f9;white-space:nowrap;}'
124
+ '.vda-table tbody tr:nth-child(even){background:#f8fafc;}'
125
+ '.vda-table tbody tr:last-child td{border-bottom:none;}'
126
+ '</style>'
127
+ )
128
+ summary_table = (
129
+ '<div class="vda-table-wrap"><table class="vda-table">'
130
+ f'<thead><tr>{header_cells}</tr></thead>'
131
+ '<tbody>' + '\n'.join(row_html) + '</tbody>'
132
+ '</table></div>'
133
+ )
134
+
135
+ return {"reply": f'{iframe}\n\n**Cluster Centroids (feature means per cluster):**\n{style}{summary_table}'}
136
+
137
+ except Exception as e:
138
+ print("KMEANS CLUSTERING ERROR")
139
+ print(e)
140
+ return {"reply": f"There was an error running K-Means clustering. Error: {e}. You should probably try again."}
141
+
142
+
143
+ def hypothesis_test_func(test_type: str, column: str, session_hash, session_folder,
144
+ column2: str = "", group_column: str = "",
145
+ group_values: List[str] = [], pop_mean: float = 0.0, **kwargs):
146
+ print("HYPOTHESIS TEST")
147
+ try:
148
+ from scipy import stats
149
+ from html import escape
150
+
151
+ dir_path = TEMP_DIR / str(session_hash) / str(session_folder)
152
+ csv_query_path = f'{dir_path}/query.csv'
153
+ df = pd.read_csv(csv_query_path)
154
+
155
+ if test_type == "t_test_independent":
156
+ if not group_column or group_column not in df.columns:
157
+ return {"reply": "Please specify a valid group_column for the independent t-test."}
158
+ unique_groups = df[group_column].dropna().unique().tolist()
159
+ if group_values and len(group_values) == 2:
160
+ g1_label, g2_label = group_values[0], group_values[1]
161
+ elif len(unique_groups) == 2:
162
+ g1_label, g2_label = unique_groups[0], unique_groups[1]
163
+ else:
164
+ return {"reply": f"For an independent t-test, exactly 2 groups are needed. Found: {unique_groups}. Specify group_values with 2 entries."}
165
+
166
+ g1 = df[df[group_column] == g1_label][column].dropna()
167
+ g2 = df[df[group_column] == g2_label][column].dropna()
168
+ t_stat, p_value = stats.ttest_ind(g1, g2)
169
+
170
+ result_rows = [
171
+ ("Test", "Independent Samples T-Test"),
172
+ ("Column", column),
173
+ ("Group Column", group_column),
174
+ (f"Group 1", str(g1_label)),
175
+ (f"Group 2", str(g2_label)),
176
+ (f"Group 1 Mean (n={len(g1)})", f"{g1.mean():.4f}"),
177
+ (f"Group 2 Mean (n={len(g2)})", f"{g2.mean():.4f}"),
178
+ ("T-Statistic", f"{t_stat:.4f}"),
179
+ ("P-Value", f"{p_value:.6f}"),
180
+ ("Significant at α=0.05", "Yes ✓" if p_value < 0.05 else "No ✗"),
181
+ ]
182
+ title = f"T-Test: {column} by {group_column}"
183
+
184
+ elif test_type == "t_test_one_sample":
185
+ sample = df[column].dropna()
186
+ t_stat, p_value = stats.ttest_1samp(sample, pop_mean)
187
+ result_rows = [
188
+ ("Test", "One-Sample T-Test"),
189
+ ("Column", column),
190
+ ("Hypothesized Mean (μ₀)", f"{pop_mean:.4f}"),
191
+ (f"Sample Mean (n={len(sample)})", f"{sample.mean():.4f}"),
192
+ ("Sample Std Dev", f"{sample.std():.4f}"),
193
+ ("T-Statistic", f"{t_stat:.4f}"),
194
+ ("P-Value", f"{p_value:.6f}"),
195
+ ("Significant at α=0.05", "Yes ✓" if p_value < 0.05 else "No ✗"),
196
+ ]
197
+ title = f"One-Sample T-Test: {column} vs μ={pop_mean}"
198
+
199
+ elif test_type == "chi_square":
200
+ if not column2 or column2 not in df.columns:
201
+ return {"reply": "Please specify a valid column2 for the chi-square test."}
202
+ contingency = pd.crosstab(df[column], df[column2])
203
+ chi2, p_value, dof, _ = stats.chi2_contingency(contingency)
204
+ result_rows = [
205
+ ("Test", "Chi-Square Test of Independence"),
206
+ ("Column 1", column),
207
+ ("Column 2", column2),
208
+ ("Chi-Square Statistic", f"{chi2:.4f}"),
209
+ ("Degrees of Freedom", str(dof)),
210
+ ("P-Value", f"{p_value:.6f}"),
211
+ ("Significant at α=0.05", "Yes ✓" if p_value < 0.05 else "No ✗"),
212
+ ]
213
+ title = f"Chi-Square: {column} × {column2}"
214
+
215
+ else:
216
+ return {"reply": f"Unknown test_type '{test_type}'. Use one of: t_test_independent, t_test_one_sample, chi_square."}
217
+
218
+ style = (
219
+ '<style>'
220
+ '.vda-table-wrap{overflow-x:auto;margin:8px 0;border-radius:8px;border:1px solid #e5e7eb;}'
221
+ '.vda-table{width:100%;border-collapse:collapse;font-size:13px;font-family:Inter,system-ui,sans-serif;}'
222
+ '.vda-table thead th{background:#3B82F6;color:#fff;padding:9px 14px;text-align:left;white-space:nowrap;font-weight:600;}'
223
+ '.vda-table tbody td{padding:7px 14px;border-bottom:1px solid #f1f5f9;white-space:nowrap;}'
224
+ '.vda-table tbody tr:nth-child(even){background:#f8fafc;}'
225
+ '.vda-table tbody tr:last-child td{border-bottom:none;}'
226
+ '</style>'
227
+ )
228
+ header_cells = f'<th style="background:#1e40af;" colspan="2">{escape(title)}</th>'
229
+ row_html = [
230
+ '<tr>'
231
+ + f'<td style="font-weight:600;color:#1e40af;background:#eff6ff;white-space:nowrap;">{escape(label)}</td>'
232
+ + f'<td>{escape(value)}</td>'
233
+ + '</tr>'
234
+ for label, value in result_rows
235
+ ]
236
+ table = (
237
+ '<div class="vda-table-wrap"><table class="vda-table">'
238
+ f'<thead><tr>{header_cells}</tr></thead>'
239
+ '<tbody>' + '\n'.join(row_html) + '</tbody>'
240
+ '</table></div>'
241
+ )
242
+ return {"reply": style + table}
243
+
244
+ except Exception as e:
245
+ print("HYPOTHESIS TEST ERROR")
246
+ print(e)
247
+ return {"reply": f"There was an error running the hypothesis test. Error: {e}. You should probably try again."}
248
+
249
+
250
  def regression_func(independent_variables: List[str], dependent_variable: str, session_hash, session_folder, category: str='', **kwargs):
251
  print("LINEAR REGRESSION CALCULATION")
252
  print(independent_variables)
requirements.txt CHANGED
@@ -14,3 +14,5 @@ pymongo_schema
14
  pandasql
15
  pluck-graphql
16
  certifi==2025.1.31
 
 
 
14
  pandasql
15
  pluck-graphql
16
  certifi==2025.1.31
17
+ scipy
18
+ scikit-learn
tools/chart_tools.py CHANGED
@@ -1,315 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  chart_tool_schemas = [
2
  {
3
  "name": "scatter_chart_generation_func",
4
- "description": """This is a scatter plot generation tool useful to generate scatter plots from queried data from our data source that we are querying.
5
- The data values will come from the columns of our query.csv (the 'x' and 'y' values of each graph) file but the layout section of the plotly dictionary objects will be generated by you.
6
- Returns an iframe string which will be displayed inline in our chat window. Do not edit the iframe string returned
7
- from the scatter_chart_generation_func function in any way and always display the iframe fully to the user in the chat window. You can add your own text supplementary
8
- to it for context if desired.""",
 
9
  "parameters": {
10
  "type": "object",
11
  "properties": {
12
- "data": {
13
- "type": "array",
14
- "description": """The array containing a dictionary that contains the 'data' portion of the plotly chart generation and will include the options requested by the user.
15
- The array must contain a json formatted dictionary with outer brackets included, any other format will not work.
16
- Do not include the 'x' or 'y' portions of the object as this will come from the query.csv file generated by our SQLite query.
17
- Infer this from the user's message.""",
18
- "items": {"type": "string"}
19
- },
20
  "x_column": {
21
  "type": "array",
22
- "description": """An array of strings that correspond to the column names in our query.csv file that contain the x values of the graph. There can be more than one column
23
- that can each be plotted against the y_column, if needed.""",
24
- "items": {"type": "string"}
 
 
25
  },
26
  "y_column": {
27
  "type": "string",
28
- "description": """The column name in our query.csv file that contain the y values of the graph."""
29
  },
30
  "category": {
31
  "type": "string",
32
- "description": """An optional column name in our query.csv file that contain a parameter that will define the category for the data."""
33
  },
34
  "trendline": {
35
  "type": "string",
36
- "description": """An optional field to specify the type of plotly trendline we wish to use in the scatter plot.
37
- This trendline value can be one of ['ols','lowess','rolling','ewm','expanding'].
38
- Do not send any values outside of this array as the function will fail.
39
- Infer this from the user's message."""
 
40
  },
41
  "trendline_options": {
42
  "type": "array",
43
- "description": """An array containing a dictionary that contains the 'trendline_options' portion of the plotly chart generation.
44
- The 'lowess', 'rolling', and 'ewm' options require trendline_options to be included.
45
- The array must contain a json formatted dictionary with outer brackets included, any other format will not work.""",
46
- "items": {"type": "string"}
 
 
47
  },
48
  "marginal_x": {
49
  "type": "string",
50
- "description": """The type of marginal distribution plot we'd like to specify for the plotly scatter plot for the x axis.
51
- This marginal_x value can be one of ['histogram','rug','box','violin'].
52
- Do not send any values outside of this array as the function will fail.
53
- Infer this from the user's message."""
54
  },
55
  "marginal_y": {
56
  "type": "string",
57
- "description": """The type of marginal distribution plot we'd like to specify for the plotly scatter plot for the y axis.
58
- This marginal_y value can be one of ['histogram','rug','box','violin'].
59
- Do not send any values outside of this array as the function will fail.
60
- Infer this from the user's message."""
61
- },
62
- "layout": {
63
- "type": "array",
64
- "description": """An array containing a dictionary that contains the 'layout' portion of the plotly chart generation.
65
- The array must contain a json formatted dictionary with outer brackets included, any other format will not work.""",
66
- "items": {"type": "string"}
67
  },
68
  "size": {
69
  "type": "string",
70
- "description": """An optional column in our query.csv file that contain a parameter that will define the size of each plot point.
71
- This is useful for a bubble chart where another value in our query can be represented by the size of the plotted point.
72
- Values must be greater than or equal to 0 and so in our query, all values less than 0 should be set equal to zero."""
73
- }
74
  },
75
- "required": ["x_column", "y_column"]
76
  },
77
  },
78
  {
79
  "name": "line_chart_generation_func",
80
- "description": """This is a line chart generation tool useful to generate line charts from queried data from our data source that we are querying.
81
- The data values will come from the columns of our query.csv (the 'x' and 'y' values of each graph) file but the layout section of the plotly dictionary objects will be generated by you.
82
- Returns an iframe string which will be displayed inline in our chat window. Do not edit the iframe string returned
83
- from the line_chart_generation_func function in any way and always display the iframe fully to the user in the chat window. You can add your own text supplementary
84
- to it for context if desired.""",
85
  "parameters": {
86
  "type": "object",
87
  "properties": {
88
- "data": {
89
- "type": "array",
90
- "description": """The array containing a dictionary that contains the 'data' portion of the plotly chart generation and will include the options requested by the user.
91
- The array must contain a json formatted dictionary with outer brackets included, any other format will not work.
92
- Do not include the 'x' or 'y' portions of the object as this will come from the query.csv file generated by our SQLite query.
93
- Infer this from the user's message.""",
94
- "items": {"type": "string"}
95
- },
96
  "x_column": {
97
  "type": "string",
98
- "description": """The column name in our query.csv file that contain the x values of the graph."""
99
  },
100
  "y_column": {
101
  "type": "string",
102
- "description": """The column name in our query.csv file that contain the y values of the graph."""
103
  },
104
  "category": {
105
  "type": "string",
106
- "description": """An optional column name in our query.csv file that contain a parameter that will define the category for the data."""
107
  },
108
- "layout": {
109
- "type": "array",
110
- "description": """An array containing a dictionary that contains the 'layout' portion of the plotly chart generation.
111
- The array must contain a json formatted dictionary with outer brackets included, any other format will not work.""",
112
- "items": {"type": "string"}
113
- }
114
  },
115
- "required": ["x_column", "y_column", "layout"]
116
  },
117
  },
118
  {
119
  "name": "bar_chart_generation_func",
120
- "description": """This is a bar chart generation tool useful to generate bar charts from queried data from our data source that we are querying.
121
- The data values will come from the columns of our query.csv (the 'x' and 'y' values of each graph) file but the layout section of the plotly dictionary objects will be generated by you.
122
- Returns an iframe string which will be displayed inline in our chat window. Do not edit the iframe string returned
123
- from the bar_chart_generation_func function in any way and always display the iframe fully to the user in the chat window. You can add your own text supplementary
124
- to it for context if desired.""",
 
125
  "parameters": {
126
  "type": "object",
127
  "properties": {
128
- "data": {
129
- "type": "array",
130
- "description": """The array containing a dictionary that contains the 'data' portion of the plotly chart generation and will include the options requested by the user.
131
- The array must contain a json formatted dictionary with outer brackets included, any other format will not work.
132
- Do not include the 'x' or 'y' portions of the object as this will come from the query.csv file generated by our SQLite query.
133
- Infer this from the user's message.""",
134
- "items": {"type": "string"}
135
- },
136
  "x_column": {
137
  "type": "string",
138
- "description": """The column name in our query.csv file that contains the x values of the graph."""
139
  },
140
  "y_column": {
141
  "type": "string",
142
- "description": """The column name in our query.csv file that contains the y values of the graph."""
143
  },
144
  "category": {
145
  "type": "string",
146
- "description": """An optional column name in our query.csv file that contains a parameter that will define the category for the data."""
147
  },
148
  "facet_row": {
149
  "type": "string",
150
- "description": """An optional column name in our query.csv file that contains a parameter that will define a faceted subplot, where different rows
151
- correspond to different values of the query specified in this parameter."""
152
  },
153
  "facet_col": {
154
  "type": "string",
155
- "description": """An optional column name in our query.csv file that contain a parameter that will define the faceted column, corresponding to
156
- different values of our query specified in this parameter."""
157
  },
158
- "layout": {
159
- "type": "array",
160
- "description": """An array containing a dictionary that contains the 'layout' portion of the plotly chart generation.
161
- The array must contain a json formatted dictionary with outer brackets included, any other format will not work.""",
162
- "items": {"type": "string"}
163
- }
164
  },
165
- "required": ["x_column", "y_column", "layout"]
166
  },
167
  },
168
  {
169
  "name": "pie_chart_generation_func",
170
- "description": """This is a pie chart generation tool useful to generate pie charts from queried data from our data source that we are querying.
171
- The data values will come from the columns of our query.csv (the 'values' and 'names' values of each graph) file but the layout section of the plotly dictionary objects will be generated by you.
172
- Returns an iframe string which will be displayed inline in our chat window. Do not edit the iframe string returned
173
- from the pie_chart_generation_func function in any way and always display the iframe fully to the user in the chat window. You can add your own text supplementary
174
- to it for context if desired.""",
175
  "parameters": {
176
  "type": "object",
177
  "properties": {
178
- "data": {
179
- "type": "array",
180
- "description": """The array containing a dictionary that contains the 'data' portion of the plotly chart generation and will include the options requested by the user.
181
- The array must contain a json formatted dictionary with outer brackets included, any other format will not work.
182
- Do not include the 'x' or 'y' portions of the object as this will come from the query.csv file generated by our SQLite query.
183
- Infer this from the user's message.""",
184
- "items": {"type": "string"}
185
- },
186
  "values": {
187
  "type": "string",
188
- "description": """The column name in our query.csv file that contain the values of the pie chart."""
189
  },
190
  "names": {
191
  "type": "string",
192
- "description": """The column name in our query.csv file that contain the label or section of each piece of the pie graph and allow us to know what each piece of the pie chart represents."""
193
  },
194
- "layout": {
195
- "type": "array",
196
- "description": """An array containing a dictionary that contains the 'layout' portion of the plotly chart generation.
197
- The array must contain a json formatted dictionary with outer brackets included, any other format will not work.""",
198
- "items": {"type": "string"}
199
- }
200
  },
201
- "required": ["values", "names", "layout"]
202
  },
203
  },
204
  {
205
  "name": "histogram_generation_func",
206
- "description": """This is a histogram generation tool useful to generate histograms from queried data from our data source that we are querying.
207
- The data values will come from the columns of our query.csv file but the layout section of the plotly dictionary objects will be generated by you.
208
- Returns an iframe string which will be displayed inline in our chat window. Do not edit the iframe string returned
209
- from the histogram_generation_func function in any way and always display the iframe fully to the user in the chat window. You can add your own text supplementary
210
- to it for context if desired.""",
 
211
  "parameters": {
212
  "type": "object",
213
  "properties": {
214
- "data": {
215
- "type": "array",
216
- "description": """The array containing a dictionary that contains the 'data' portion of the plotly chart generation and will include the options requested by the user.
217
- The array must contain a json formatted dictionary with outer brackets included, any other format will not work.
218
- Do not include the 'x' or 'y' portions of the object as this will come from the query.csv file generated by our SQLite query.
219
- Infer this from the user's message.""",
220
- "items": {"type": "string"}
221
- },
222
  "x_column": {
223
  "type": "string",
224
- "description": """The column name in our query.csv file that contains the x values of the histogram.
225
- This would correspond to the counts that would be distributed in the histogram."""
226
  },
227
  "y_column": {
228
  "type": "string",
229
- "description": """An optional column name in our query.csv file that contains the y values of the histogram."""
230
  },
231
  "histnorm": {
232
  "type": "string",
233
- "description": """An optional argument to specify the type of normalization if the default isn't used.
234
- This histnorm value can be one of ['percent','probability','density','probability density'].
235
- Do not send any values outside of this array as the function will fail."""
236
  },
237
  "category": {
238
  "type": "string",
239
- "description": """An optional column name in our query.csv file that contains a parameter that will define the category for the data."""
240
  },
241
  "histfunc": {
242
  "type": "string",
243
- "description": """An optional value that represents the function of data to compute the function which is used on the optional y column.
244
- This histfunc value can be one of ['avg','sum','count'].
245
- Do not send any values outside of this array as the function will fail."""
246
  },
247
- "layout": {
248
- "type": "array",
249
- "description": """An array containing a dictionary that contains the 'layout' portion of the plotly chart generation.
250
- The array must contain a json formatted dictionary with outer brackets included, any other format will not work.""",
251
- "items": {"type": "string"}
252
- }
253
  },
254
- "required": ["x_column"]
255
  },
256
  },
257
  {
258
  "name": "box_chart_generation_func",
259
- "description": """This is a box plot generation tool useful for visualising the distribution of a numeric column and identifying outliers.
260
- Particularly valuable when comparing distributions across categories (e.g., revenue by country, price by product type).
261
- The data values come from the columns of our query.csv file.
262
- Returns an iframe string to display inline in the chat window.""",
 
 
263
  "parameters": {
264
  "type": "object",
265
  "properties": {
266
  "y_column": {
267
  "type": "string",
268
- "description": "The column name containing the numeric values whose distribution should be shown on the y-axis."
269
  },
270
  "x_column": {
271
  "type": "string",
272
- "description": "An optional column name to group the boxes by on the x-axis (e.g., a category or label column). When provided, one box is drawn per unique value."
273
  },
274
  "category": {
275
  "type": "string",
276
- "description": "An optional column name used to colour-code the boxes by a secondary grouping."
277
  },
278
- "layout": {
279
- "type": "array",
280
- "description": """An array containing a dictionary with the 'layout' portion of the plotly chart.
281
- The array must contain a json formatted dictionary with outer brackets included.""",
282
- "items": {"type": "string"}
283
- }
284
  },
285
- "required": ["y_column"]
286
  },
287
  },
288
  {
289
  "name": "correlation_heatmap_func",
290
- "description": """This is a correlation heatmap tool that computes the pairwise Pearson correlation between numeric columns
291
- in our query.csv file and displays the result as a colour-coded heatmap (blue = positive, red = negative correlation).
292
- Use this when the user asks which variables are related, correlated, or associated with each other.
293
- Returns an iframe string to display inline in the chat window.""",
 
 
294
  "parameters": {
295
  "type": "object",
296
  "properties": {
297
  "columns": {
298
  "type": "array",
299
- "description": "An optional list of numeric column names to include in the correlation matrix. If omitted, all numeric columns from the query results are used. Avoid including ID or index columns.",
300
- "items": {"type": "string"}
301
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  },
303
- "required": []
304
  },
305
  },
306
  {
307
  "name": "table_generation_func",
308
- "description": """This is a table generation tool useful to format data as a table from queried data from our data source that we are querying.
309
- Takes no parameters as it uses data queried in our query.csv file to build the table.
310
- Call this function after running our query and generating query.csv.
311
- Returns an iframe string which will be displayed inline in our chat window. Do not edit the iframe string returned
312
- from the table_generation_func function in any way and always display the iframe fully to the user in the chat window.""",
 
313
  "parameters": {"type": "object", "properties": {}},
314
  },
315
  ]
 
1
+ # Shared parameter snippets reused across chart tool schemas.
2
+ # Update here to change the description everywhere at once.
3
+
4
+ _LAYOUT_PARAM = {
5
+ "type": "array",
6
+ "description": (
7
+ "Optional. An array containing a single JSON-formatted Plotly layout dictionary. "
8
+ "Use to set chart title, axis labels, colours, fonts, and other layout properties. "
9
+ "Example: [{\"title\": \"Monthly Sales\", \"xaxis\": {\"title\": \"Month\"}}]"
10
+ ),
11
+ "items": {"type": "string"},
12
+ }
13
+
14
+ _TRACE_STYLE_PARAM = {
15
+ "type": "array",
16
+ "description": (
17
+ "Optional. An array containing a single JSON-formatted Plotly trace styling dictionary. "
18
+ "Use to control visual properties such as line colour, opacity, and marker style. "
19
+ "Do NOT include 'x', 'y', or 'type' keys — those are set automatically from query.csv."
20
+ ),
21
+ "items": {"type": "string"},
22
+ }
23
+
24
  chart_tool_schemas = [
25
  {
26
  "name": "scatter_chart_generation_func",
27
+ "description": (
28
+ "Generates a Plotly scatter plot from query.csv data. "
29
+ "Use when the user wants to visualise the relationship between two numeric columns, "
30
+ "create a bubble chart (via the size parameter), or overlay a trendline. "
31
+ "Returns an HTML iframe — display it verbatim in the chat."
32
+ ),
33
  "parameters": {
34
  "type": "object",
35
  "properties": {
 
 
 
 
 
 
 
 
36
  "x_column": {
37
  "type": "array",
38
+ "description": (
39
+ "One or more column names from query.csv to plot on the x-axis. "
40
+ "Multiple columns produce multiple series, each plotted against y_column."
41
+ ),
42
+ "items": {"type": "string"},
43
  },
44
  "y_column": {
45
  "type": "string",
46
+ "description": "Column name from query.csv to plot on the y-axis.",
47
  },
48
  "category": {
49
  "type": "string",
50
+ "description": "Optional column name used to colour-code points by a categorical grouping.",
51
  },
52
  "trendline": {
53
  "type": "string",
54
+ "description": (
55
+ "Optional trendline type. One of: 'ols' (linear regression), "
56
+ "'lowess' (local smoothing), 'rolling', 'ewm', 'expanding'. "
57
+ "Requires trendline_options when using 'lowess', 'rolling', or 'ewm'."
58
+ ),
59
  },
60
  "trendline_options": {
61
  "type": "array",
62
+ "description": (
63
+ "Required when trendline is 'lowess', 'rolling', or 'ewm'. "
64
+ "An array containing a single JSON-formatted dict of trendline options "
65
+ "(e.g. [{\"window\": 7}] for a 7-point rolling average)."
66
+ ),
67
+ "items": {"type": "string"},
68
  },
69
  "marginal_x": {
70
  "type": "string",
71
+ "description": "Optional marginal distribution plot along the x-axis. One of: 'histogram', 'rug', 'box', 'violin'.",
 
 
 
72
  },
73
  "marginal_y": {
74
  "type": "string",
75
+ "description": "Optional marginal distribution plot along the y-axis. One of: 'histogram', 'rug', 'box', 'violin'.",
 
 
 
 
 
 
 
 
 
76
  },
77
  "size": {
78
  "type": "string",
79
+ "description": "Optional column name whose values control the size of each point (bubble chart). Negative values are clamped to zero.",
80
+ },
81
+ "data": _TRACE_STYLE_PARAM,
82
+ "layout": _LAYOUT_PARAM,
83
  },
84
+ "required": ["x_column", "y_column"],
85
  },
86
  },
87
  {
88
  "name": "line_chart_generation_func",
89
+ "description": (
90
+ "Generates a Plotly line chart from query.csv data. "
91
+ "Use for trends over time or any ordered sequence. "
92
+ "Returns an HTML iframe — display it verbatim in the chat."
93
+ ),
94
  "parameters": {
95
  "type": "object",
96
  "properties": {
 
 
 
 
 
 
 
 
97
  "x_column": {
98
  "type": "string",
99
+ "description": "Column name from query.csv for the x-axis (typically a date or ordered index).",
100
  },
101
  "y_column": {
102
  "type": "string",
103
+ "description": "Column name from query.csv for the y-axis (numeric values).",
104
  },
105
  "category": {
106
  "type": "string",
107
+ "description": "Optional column name used to split the data into multiple colour-coded lines.",
108
  },
109
+ "data": _TRACE_STYLE_PARAM,
110
+ "layout": _LAYOUT_PARAM,
 
 
 
 
111
  },
112
+ "required": ["x_column", "y_column"],
113
  },
114
  },
115
  {
116
  "name": "bar_chart_generation_func",
117
+ "description": (
118
+ "Generates a Plotly bar chart from query.csv data. "
119
+ "Use for comparing values across categories. Supports grouped/stacked bars via category, "
120
+ "and faceted subplots via facet_row or facet_col. "
121
+ "Returns an HTML iframe — display it verbatim in the chat."
122
+ ),
123
  "parameters": {
124
  "type": "object",
125
  "properties": {
 
 
 
 
 
 
 
 
126
  "x_column": {
127
  "type": "string",
128
+ "description": "Column name from query.csv for the x-axis (category labels).",
129
  },
130
  "y_column": {
131
  "type": "string",
132
+ "description": "Column name from query.csv for the y-axis (numeric values).",
133
  },
134
  "category": {
135
  "type": "string",
136
+ "description": "Optional column name used to colour-code bars into grouped or stacked series.",
137
  },
138
  "facet_row": {
139
  "type": "string",
140
+ "description": "Optional column name. Creates one subplot row per unique value — useful for comparing distributions across a second dimension.",
 
141
  },
142
  "facet_col": {
143
  "type": "string",
144
+ "description": "Optional column name. Creates one subplot column per unique value.",
 
145
  },
146
+ "data": _TRACE_STYLE_PARAM,
147
+ "layout": _LAYOUT_PARAM,
 
 
 
 
148
  },
149
+ "required": ["x_column", "y_column"],
150
  },
151
  },
152
  {
153
  "name": "pie_chart_generation_func",
154
+ "description": (
155
+ "Generates a Plotly pie chart from query.csv data. "
156
+ "Use when the user wants to show part-to-whole proportions. "
157
+ "Returns an HTML iframe — display it verbatim in the chat."
158
+ ),
159
  "parameters": {
160
  "type": "object",
161
  "properties": {
 
 
 
 
 
 
 
 
162
  "values": {
163
  "type": "string",
164
+ "description": "Column name from query.csv containing the numeric value for each slice.",
165
  },
166
  "names": {
167
  "type": "string",
168
+ "description": "Column name from query.csv containing the label for each slice.",
169
  },
170
+ "data": _TRACE_STYLE_PARAM,
171
+ "layout": _LAYOUT_PARAM,
 
 
 
 
172
  },
173
+ "required": ["values", "names"],
174
  },
175
  },
176
  {
177
  "name": "histogram_generation_func",
178
+ "description": (
179
+ "Generates a Plotly histogram from query.csv data. "
180
+ "Use to show the frequency distribution of a numeric column. "
181
+ "Supports normalisation (percent, probability, density) and aggregation functions per bin. "
182
+ "Returns an HTML iframe — display it verbatim in the chat."
183
+ ),
184
  "parameters": {
185
  "type": "object",
186
  "properties": {
 
 
 
 
 
 
 
 
187
  "x_column": {
188
  "type": "string",
189
+ "description": "Column name from query.csv whose values are binned on the x-axis.",
 
190
  },
191
  "y_column": {
192
  "type": "string",
193
+ "description": "Optional column name aggregated per bin via histfunc (e.g. sum of sales per price bucket).",
194
  },
195
  "histnorm": {
196
  "type": "string",
197
+ "description": "Optional normalisation. One of: 'percent', 'probability', 'density', 'probability density'.",
 
 
198
  },
199
  "category": {
200
  "type": "string",
201
+ "description": "Optional column name used to overlay multiple colour-coded histograms.",
202
  },
203
  "histfunc": {
204
  "type": "string",
205
+ "description": "Optional aggregation function applied to y_column per bin. One of: 'avg', 'sum', 'count'.",
 
 
206
  },
207
+ "data": _TRACE_STYLE_PARAM,
208
+ "layout": _LAYOUT_PARAM,
 
 
 
 
209
  },
210
+ "required": ["x_column"],
211
  },
212
  },
213
  {
214
  "name": "box_chart_generation_func",
215
+ "description": (
216
+ "Generates a Plotly box plot from query.csv data. "
217
+ "Use to visualise the distribution of a numeric column and identify outliers. "
218
+ "Especially useful for comparing distributions across categories. "
219
+ "Returns an HTML iframe — display it verbatim in the chat."
220
+ ),
221
  "parameters": {
222
  "type": "object",
223
  "properties": {
224
  "y_column": {
225
  "type": "string",
226
+ "description": "Column name from query.csv containing the numeric values to distribute on the y-axis.",
227
  },
228
  "x_column": {
229
  "type": "string",
230
+ "description": "Optional column name. Groups data into one box per unique value on the x-axis.",
231
  },
232
  "category": {
233
  "type": "string",
234
+ "description": "Optional column name used to colour-code boxes by a secondary grouping.",
235
  },
236
+ "layout": _LAYOUT_PARAM,
 
 
 
 
 
237
  },
238
+ "required": ["y_column"],
239
  },
240
  },
241
  {
242
  "name": "correlation_heatmap_func",
243
+ "description": (
244
+ "Computes pairwise Pearson correlations between numeric columns in query.csv and renders "
245
+ "the result as a colour-coded heatmap (blue = positive, red = negative). "
246
+ "Use when the user asks which variables are related, correlated, or associated with each other. "
247
+ "Returns an HTML iframe — display it verbatim in the chat."
248
+ ),
249
  "parameters": {
250
  "type": "object",
251
  "properties": {
252
  "columns": {
253
  "type": "array",
254
+ "description": "Optional list of numeric column names to include in the matrix. If omitted, all numeric columns from query.csv are used. Avoid ID or index columns.",
255
+ "items": {"type": "string"},
256
+ },
257
+ },
258
+ "required": [],
259
+ },
260
+ },
261
+ {
262
+ "name": "rolling_stats_func",
263
+ "description": (
264
+ "Generates a rolling statistics / moving average chart from query.csv data. "
265
+ "Overlays rolling aggregations (mean, std, min, max) on top of the original series. "
266
+ "Use when the user asks for a moving average, rolling average, rolling statistics, or wants to smooth a time series. "
267
+ "Returns an HTML iframe — display it verbatim in the chat."
268
+ ),
269
+ "parameters": {
270
+ "type": "object",
271
+ "properties": {
272
+ "x_column": {
273
+ "type": "string",
274
+ "description": "Column name from query.csv for the x-axis — typically a date or sequential index.",
275
+ },
276
+ "y_column": {
277
+ "type": "string",
278
+ "description": "Column name from query.csv containing the numeric values to compute rolling stats on.",
279
+ },
280
+ "window": {
281
+ "type": "integer",
282
+ "description": "Rolling window size in number of rows. Default 7. Infer from the user's request.",
283
+ },
284
+ "stats": {
285
+ "type": "array",
286
+ "description": "Statistics to overlay. Valid values: 'mean', 'std', 'min', 'max'. Defaults to ['mean'] if omitted.",
287
+ "items": {"type": "string"},
288
+ },
289
+ "category": {
290
+ "type": "string",
291
+ "description": "Optional column name to group the data, producing separate rolling stat lines per group.",
292
+ },
293
+ "layout": _LAYOUT_PARAM,
294
  },
295
+ "required": ["x_column", "y_column"],
296
  },
297
  },
298
  {
299
  "name": "table_generation_func",
300
+ "description": (
301
+ "Formats query.csv results as a styled HTML table. "
302
+ "Use when the user wants to view raw query results in a readable format, "
303
+ "or when result data is too large to describe in text. Displays up to 200 rows. "
304
+ "Returns an HTML table — display it verbatim in the chat."
305
+ ),
306
  "parameters": {"type": "object", "properties": {}},
307
  },
308
  ]
tools/stats_tools.py CHANGED
@@ -1,50 +1,130 @@
1
  stats_tool_schemas = [
2
  {
3
  "name": "descriptive_stats_func",
4
- "description": """This tool computes summary statistics for numeric columns in our query.csv file and returns them as a formatted table.
5
- Statistics include count, mean, standard deviation, min, 25th percentile, median, 75th percentile, and max.
6
- Use this when the user asks for summary statistics, descriptive statistics, or a statistical overview of the data.""",
 
 
 
7
  "parameters": {
8
  "type": "object",
9
  "properties": {
10
  "columns": {
11
  "type": "array",
12
- "description": "An optional list of column names to compute statistics for. If omitted, statistics are computed for all numeric columns in the query results. Avoid including ID or index columns.",
13
- "items": {"type": "string"}
14
- }
15
  },
16
- "required": []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  },
18
  },
19
  {
20
  "name": "regression_func",
21
- "description": """This a tool to calculate regressions on our data source that we are querying.
22
- We can run queries with our 'sql_query_func' function and they will be available to use in this function via the query.csv file that is generated.
23
- Returns a dictionary of values that includes a regression_summary and a regression chart (which is an iframe displaying the
24
- linear regression in chart form and should be shown to the user).""",
 
25
  "parameters": {
26
  "type": "object",
27
  "properties": {
28
  "independent_variables": {
29
  "type": "array",
30
- "description": """An array of strings that states the independent variables in our data set which should be column names in our query.csv file that is generated
31
- in the 'sql_query_func' function. This will allow us to identify the data to use for our independent variables.
32
- Infer this from the user's message.""",
33
- "items": {"type": "string"}
34
  },
35
  "dependent_variable": {
36
  "type": "string",
37
- "description": """A string that states the dependent variable in our data set which should be a column name in our query.csv file that is generated
38
- in the 'sql_query_func' function. This will allow us to identify the data to use for our dependent variable.
39
- Infer this from the user's message."""
40
  },
41
  "category": {
42
  "type": "string",
43
- "description": """An optional column name in our query.csv file that contain a parameter that will define the category for the data.
44
- Do not send value if no category is needed or specified. This category must be present in our query.csv file to be valid."""
45
- }
46
  },
47
- "required": ["independent_variables", "dependent_variable"]
48
  },
49
- }
50
  ]
 
1
  stats_tool_schemas = [
2
  {
3
  "name": "descriptive_stats_func",
4
+ "description": (
5
+ "Computes summary statistics for numeric columns in query.csv: "
6
+ "count, mean, std, min, 25th/50th/75th percentile, and max. "
7
+ "Use when the user asks for summary statistics, descriptive statistics, or a statistical overview. "
8
+ "Returns a formatted HTML table."
9
+ ),
10
  "parameters": {
11
  "type": "object",
12
  "properties": {
13
  "columns": {
14
  "type": "array",
15
+ "description": "Optional list of column names to include. If omitted, all numeric columns from query.csv are used. Avoid ID or index columns.",
16
+ "items": {"type": "string"},
17
+ },
18
  },
19
+ "required": [],
20
+ },
21
+ },
22
+ {
23
+ "name": "kmeans_clustering_func",
24
+ "description": (
25
+ "Runs K-Means clustering on numeric feature columns from query.csv. "
26
+ "Groups rows into k clusters, displays a scatter plot coloured by cluster assignment, "
27
+ "and returns a centroid summary table showing the mean of each feature per cluster. "
28
+ "Use when the user asks to cluster the data, find natural segments or groups, or apply K-Means. "
29
+ "Returns an HTML iframe and summary table."
30
+ ),
31
+ "parameters": {
32
+ "type": "object",
33
+ "properties": {
34
+ "feature_columns": {
35
+ "type": "array",
36
+ "description": "List of numeric column names from query.csv to use as clustering features.",
37
+ "items": {"type": "string"},
38
+ },
39
+ "x_column": {
40
+ "type": "string",
41
+ "description": "Column name from query.csv for the x-axis of the scatter plot. Usually one of the feature columns.",
42
+ },
43
+ "y_column": {
44
+ "type": "string",
45
+ "description": "Column name from query.csv for the y-axis of the scatter plot. Usually one of the feature columns.",
46
+ },
47
+ "n_clusters": {
48
+ "type": "integer",
49
+ "description": "Number of clusters (k). Default 3. Infer from the user's request.",
50
+ },
51
+ "layout": {
52
+ "type": "array",
53
+ "description": "Optional. An array containing a single JSON-formatted Plotly layout dictionary.",
54
+ "items": {"type": "string"},
55
+ },
56
+ },
57
+ "required": ["feature_columns", "x_column", "y_column"],
58
+ },
59
+ },
60
+ {
61
+ "name": "hypothesis_test_func",
62
+ "description": (
63
+ "Performs a statistical hypothesis test on query.csv data and returns a formatted results table "
64
+ "with test statistic, p-value, and significance at α=0.05. "
65
+ "Supported tests:\n"
66
+ "- 't_test_independent': compare means of a numeric column across two groups "
67
+ "(requires group_column; use group_values if the column has more than 2 unique values).\n"
68
+ "- 't_test_one_sample': test whether a column's mean equals a hypothesized value (requires pop_mean).\n"
69
+ "- 'chi_square': test independence between two categorical columns (requires column and column2)."
70
+ ),
71
+ "parameters": {
72
+ "type": "object",
73
+ "properties": {
74
+ "test_type": {
75
+ "type": "string",
76
+ "description": "Test to run. One of: 't_test_independent', 't_test_one_sample', 'chi_square'.",
77
+ },
78
+ "column": {
79
+ "type": "string",
80
+ "description": "Primary column for the test. Numeric for t-tests; first categorical column for chi-square.",
81
+ },
82
+ "column2": {
83
+ "type": "string",
84
+ "description": "Second categorical column. Required for 'chi_square'.",
85
+ },
86
+ "group_column": {
87
+ "type": "string",
88
+ "description": "Grouping column. Required for 't_test_independent'. Must have exactly 2 unique values, or specify group_values.",
89
+ },
90
+ "group_values": {
91
+ "type": "array",
92
+ "description": "Exactly 2 group labels to compare. Use when group_column has more than 2 unique values.",
93
+ "items": {"type": "string"},
94
+ },
95
+ "pop_mean": {
96
+ "type": "number",
97
+ "description": "Hypothesized population mean (μ₀). Required for 't_test_one_sample'.",
98
+ },
99
+ },
100
+ "required": ["test_type", "column"],
101
  },
102
  },
103
  {
104
  "name": "regression_func",
105
+ "description": (
106
+ "Runs an OLS linear regression on query.csv data. "
107
+ "Use when the user wants to model the relationship between variables, assess predictors, or run a regression. "
108
+ "Returns a regression summary (coefficients, R², p-values) and a scatter plot with the fitted line as an HTML iframe."
109
+ ),
110
  "parameters": {
111
  "type": "object",
112
  "properties": {
113
  "independent_variables": {
114
  "type": "array",
115
+ "description": "Column names from query.csv to use as independent (predictor) variables.",
116
+ "items": {"type": "string"},
 
 
117
  },
118
  "dependent_variable": {
119
  "type": "string",
120
+ "description": "Column name from query.csv to use as the dependent (outcome) variable.",
 
 
121
  },
122
  "category": {
123
  "type": "string",
124
+ "description": "Optional column name used to colour-code points and fit separate regression lines per group.",
125
+ },
 
126
  },
127
+ "required": ["independent_variables", "dependent_variable"],
128
  },
129
+ },
130
  ]