sofhiaazzhr commited on
Commit
db217d6
·
1 Parent(s): 726faa2

[KM-625] Prompt-style Tool Descriptions

Browse files

Add a DESCRIPTION constant to each of the 8 analytics compute tools,
written prompt-style (Summary / USE WHEN / DON'T USE WHEN -> other tool /
example questions) for the Planner to decide which tool to pick. English
body with Indonesian trigger-word equivalents in parentheses. comparison
also documents the group_a baseline / comparison-direction rule.

Final destination is ToolSpec.description once the wrapper layer is built.
No behavior change; 72 tests, ruff, and mypy strict all green.

src/tools/analytics/aggregation.py CHANGED
@@ -34,6 +34,31 @@ def _clean(value: object) -> object:
34
  return value
35
 
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  def analyze_aggregate(
38
  df: pd.DataFrame,
39
  aggregations: dict[str, list[str]],
 
34
  return value
35
 
36
 
37
+ # Prompt-style description read by the Planner to decide WHEN to pick this tool.
38
+ # Final destination is ToolSpec.description once the wrapper layer is built.
39
+ DESCRIPTION = """\
40
+ Summary: Group-by aggregation. Splits rows by one or more key columns and \
41
+ computes aggregates per group (sum, mean, count, min, max, median, nunique). \
42
+ Returns one row per group.
43
+
44
+ USE WHEN the question groups a metric by a category — the tell-tale sign is \
45
+ "per"/"each"/"by" a dimension. Trigger words: "per/each" (per/tiap), "by" \
46
+ (berdasarkan), "breakdown", "total/average ... per ...".
47
+
48
+ DON'T USE WHEN:
49
+ - it summarizes a column with no grouping -> analyze_descriptive
50
+ - it compares two specific groups (A vs B) -> analyze_comparison
51
+ - it splits a single total into shares -> analyze_contribution
52
+ - the grouping is over time periods -> analyze_trend
53
+
54
+ Example questions:
55
+ - "total revenue per region"
56
+ - "average order value by customer segment"
57
+ - "how many distinct products were sold per store?"
58
+ - "count of orders for each status"
59
+ """
60
+
61
+
62
  def analyze_aggregate(
63
  df: pd.DataFrame,
64
  aggregations: dict[str, list[str]],
src/tools/analytics/comparison.py CHANGED
@@ -33,6 +33,35 @@ class GroupNotFoundError(ValueError):
33
 
34
 
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  def analyze_comparison(
37
  df: pd.DataFrame,
38
  dimension: str,
 
33
 
34
 
35
 
36
+ # Prompt-style description read by the Planner to decide WHEN to pick this tool.
37
+ # Final destination is ToolSpec.description once the wrapper layer is built.
38
+ DESCRIPTION = """\
39
+ Summary: Head-to-head comparison of one aggregated metric between TWO specific \
40
+ groups of a dimension (group_a is the baseline). Reports each group's value, \
41
+ the absolute and percent difference, and which side is higher.
42
+
43
+ USE WHEN the question pits two named groups against each other. Trigger words: \
44
+ "vs"/"versus", "compare" (bandingkan), "A or B", "difference between" \
45
+ (selisih/beda antara), "higher/lower than".
46
+
47
+ SETTING GROUPS: group_a is the BASELINE (the reference). The "comparison" field \
48
+ reads as "group_b is {higher/lower/equal} than group_a", and diff = value_b - \
49
+ value_a. Put the reference/older/expected side in group_a. E.g. "is this year \
50
+ higher than last year" -> group_a=last year, group_b=this year.
51
+
52
+ DON'T USE WHEN:
53
+ - it aggregates across many groups at once -> analyze_aggregate
54
+ - it splits a single total into shares -> analyze_contribution
55
+ - it tracks change over time -> analyze_trend
56
+
57
+ Example questions:
58
+ - "compare revenue between Jakarta and Surabaya"
59
+ - "is the average order value higher for members or non-members?"
60
+ - "difference in churn between plan A and plan B"
61
+ - "male vs female average spend"
62
+ """
63
+
64
+
65
  def analyze_comparison(
66
  df: pd.DataFrame,
67
  dimension: str,
src/tools/analytics/decomposition.py CHANGED
@@ -36,6 +36,31 @@ def _clean(value: object) -> object:
36
  return value
37
 
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  def analyze_contribution(
40
  df: pd.DataFrame,
41
  dimension: str,
 
36
  return value
37
 
38
 
39
+ # Prompt-style description read by the Planner to decide WHEN to pick this tool.
40
+ # Final destination is ToolSpec.description once the wrapper layer is built.
41
+ DESCRIPTION = """\
42
+ Summary: Breaks a single total into per-category contributions (share of total) \
43
+ in one snapshot, largest first, with cumulative share. Supports Pareto (80/20) \
44
+ reasoning. Optional top_n folds the long tail into "Others".
45
+
46
+ USE WHEN the question is about how a whole splits into parts, or which \
47
+ categories dominate. Trigger words: "contribution" (kontribusi), "share" \
48
+ (porsi/proporsi), "% of total" (persen dari total), "Pareto/80-20", "top \
49
+ contributors", "which ... make up most".
50
+
51
+ DON'T USE WHEN:
52
+ - it pits two specific groups against each other -> analyze_comparison
53
+ - it tracks change over time -> analyze_trend
54
+ - it just aggregates per group without share-of-total -> analyze_aggregate
55
+
56
+ Example questions:
57
+ - "which products contribute most to total sales?"
58
+ - "what share of revenue comes from each region?"
59
+ - "top 5 customers by contribution to profit"
60
+ - "do 20% of items make up 80% of revenue?"
61
+ """
62
+
63
+
64
  def analyze_contribution(
65
  df: pd.DataFrame,
66
  dimension: str,
src/tools/analytics/descriptive.py CHANGED
@@ -83,6 +83,33 @@ def _describe_one(series: pd.Series, metrics: tuple[str, ...]) -> dict[str, obje
83
  return out
84
 
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  def analyze_descriptive(
87
  df: pd.DataFrame,
88
  column_ids: list[str],
 
83
  return out
84
 
85
 
86
+ # Prompt-style description read by the Planner to decide WHEN to pick this tool.
87
+ # Final destination is ToolSpec.description once the wrapper layer is built.
88
+ DESCRIPTION = """\
89
+ Summary: Descriptive statistics (EDA) for one or several columns in a single \
90
+ call — center (mean, median, mode), spread (std, variance, min, max, Q1/Q3 \
91
+ quartiles), distribution shape (skew), and completeness (null count & rate).
92
+
93
+ USE WHEN the user asks for an overview, summary, or single-column statistics \
94
+ of ONE or SEVERAL columns as a whole, with NO grouping and NO comparison \
95
+ between groups. Trigger words: "overview/summary" (ringkasan), "average" \
96
+ (rata-rata), "median", "spread/distribution" (sebaran), "how many nulls" \
97
+ (berapa nilai kosong).
98
+
99
+ DON'T USE WHEN:
100
+ - the question groups by something ("per"/"each"/"by") -> analyze_aggregate
101
+ - it compares two specific groups (A vs B) -> analyze_comparison
102
+ - it tracks a metric over time -> analyze_trend
103
+ - it checks data type, quality, duplicates, outliers, constants -> analyze_profile
104
+
105
+ Example questions:
106
+ - "what's the average and median customer age?"
107
+ - "summarize the income column"
108
+ - "how is product price distributed?"
109
+ - "how many nulls are in the email column?"
110
+ """
111
+
112
+
113
  def analyze_descriptive(
114
  df: pd.DataFrame,
115
  column_ids: list[str],
src/tools/analytics/quality.py CHANGED
@@ -82,6 +82,31 @@ def _profile_one(series: pd.Series) -> dict[str, object]:
82
  return out
83
 
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  def analyze_profile(
86
  df: pd.DataFrame,
87
  column_ids: list[str] | None = None,
 
82
  return out
83
 
84
 
85
+ # Prompt-style description read by the Planner to decide WHEN to pick this tool.
86
+ # Final destination is ToolSpec.description once the wrapper layer is built.
87
+ DESCRIPTION = """\
88
+ Summary: Per-column data-quality profile. For each column reports dtype, \
89
+ inferred type, completeness (null count/rate), cardinality (distinct count/rate, \
90
+ constant flag), and — for numeric columns — min/max/mean plus an IQR-based \
91
+ outlier count; for non-numeric columns the most frequent value.
92
+
93
+ USE WHEN the question is about the HEALTH of the data, not its statistics: \
94
+ missing values, duplicates, data types, outliers, "is this clean enough to \
95
+ analyze". Trigger words: "quality" (kualitas), "missing/nulls" (data kosong), \
96
+ "data type" (tipe data), "duplicates/unique" (duplikat/unik), "outliers".
97
+
98
+ DON'T USE WHEN:
99
+ - the user wants statistics like mean/median/std/skew -> analyze_descriptive
100
+ - it groups or compares -> analyze_aggregate / analyze_comparison
101
+
102
+ Example questions:
103
+ - "is this dataset clean enough to analyze?"
104
+ - "which columns have a lot of missing values?"
105
+ - "what are the data types and unique counts per column?"
106
+ - "are there outliers in the amount column?"
107
+ """
108
+
109
+
110
  def analyze_profile(
111
  df: pd.DataFrame,
112
  column_ids: list[str] | None = None,
src/tools/analytics/relationship.py CHANGED
@@ -44,6 +44,30 @@ def _clean(value: object) -> float | None:
44
  return None if math.isnan(f) else f
45
 
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  def analyze_correlation(
48
  df: pd.DataFrame,
49
  column_ids: list[str] | None = None,
 
44
  return None if math.isnan(f) else f
45
 
46
 
47
+ # Prompt-style description read by the Planner to decide WHEN to pick this tool.
48
+ # Final destination is ToolSpec.description once the wrapper layer is built.
49
+ DESCRIPTION = """\
50
+ Summary: Pairwise correlation across numeric columns (pearson, spearman, or \
51
+ kendall). Returns a correlation matrix plus the strongest pairs ranked by \
52
+ absolute strength.
53
+
54
+ USE WHEN the question is about relationship or association between numeric \
55
+ variables. Trigger words: "correlation" (korelasi), "related/relationship" \
56
+ (hubungan/keterkaitan), "does X affect Y", "move together".
57
+
58
+ DON'T USE WHEN:
59
+ - it implies causation — correlation is not causality; stay descriptive
60
+ - it compares two groups of one metric -> analyze_comparison
61
+ - it summarizes a single column -> analyze_descriptive
62
+
63
+ Example questions:
64
+ - "is there a correlation between price and quantity sold?"
65
+ - "which variables are most related to revenue?"
66
+ - "do age and spending move together?"
67
+ - "show the correlation matrix for the numeric columns"
68
+ """
69
+
70
+
71
  def analyze_correlation(
72
  df: pd.DataFrame,
73
  column_ids: list[str] | None = None,
src/tools/analytics/segmentation.py CHANGED
@@ -52,6 +52,31 @@ def _clean(value: object) -> object:
52
  return value
53
 
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  def analyze_segment(
56
  df: pd.DataFrame,
57
  column: str,
 
52
  return value
53
 
54
 
55
+ # Prompt-style description read by the Planner to decide WHEN to pick this tool.
56
+ # Final destination is ToolSpec.description once the wrapper layer is built.
57
+ DESCRIPTION = """\
58
+ Summary: Bins a NUMERIC column into segments and counts how rows distribute \
59
+ across them (optionally aggregating another column per segment). Two modes: \
60
+ explicit cut edges (e.g. age 0-18-35-60) or equal-frequency quantile buckets \
61
+ (quartiles, deciles).
62
+
63
+ USE WHEN the question asks to bucket/bracket a continuous number into ranges. \
64
+ Trigger words: "segment" (segmen), "bucket/bracket" (kelompokkan ke rentang), \
65
+ "age groups/tiers" (kelompok umur/tingkatan), "quartiles/deciles", "bins".
66
+
67
+ DON'T USE WHEN:
68
+ - the category already exists (no binning needed) -> analyze_contribution
69
+ - it aggregates by an existing key -> analyze_aggregate
70
+ - it compares two named groups -> analyze_comparison
71
+
72
+ Example questions:
73
+ - "split customers into age brackets 0-18, 18-35, 35-60"
74
+ - "bucket orders into value tiers"
75
+ - "divide users into spending quartiles"
76
+ - "how many customers fall in each income band?"
77
+ """
78
+
79
+
80
  def analyze_segment(
81
  df: pd.DataFrame,
82
  column: str,
src/tools/analytics/temporal.py CHANGED
@@ -64,6 +64,30 @@ def _period_label(ts: pd.Timestamp, freq: str) -> str:
64
  return str(ts.strftime("%Y-%m-%d")) # day / week
65
 
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  def analyze_trend(
68
  df: pd.DataFrame,
69
  date_column: str,
 
64
  return str(ts.strftime("%Y-%m-%d")) # day / week
65
 
66
 
67
+ # Prompt-style description read by the Planner to decide WHEN to pick this tool.
68
+ # Final destination is ToolSpec.description once the wrapper layer is built.
69
+ DESCRIPTION = """\
70
+ Summary: Time-series trend of one metric over evenly-spaced periods (day, week, \
71
+ month, quarter, year). Reports per-period points plus direction, absolute and \
72
+ percent change, and a linear slope.
73
+
74
+ USE WHEN the question is about movement over time — growth, decline, trend, \
75
+ seasonality. Trigger words: "over time" (dari waktu ke waktu), "trend" (tren), \
76
+ "monthly/yearly" (bulanan/tahunan), "growth" (pertumbuhan), "since/last N months".
77
+
78
+ DON'T USE WHEN:
79
+ - it groups by a non-time category -> analyze_aggregate
80
+ - it compares two specific groups (A vs B) -> analyze_comparison
81
+ - it summarizes a column with no time axis -> analyze_descriptive
82
+
83
+ Example questions:
84
+ - "how did monthly revenue change this year?"
85
+ - "show the sales trend over the last 12 months"
86
+ - "is the number of signups growing quarter over quarter?"
87
+ - "yearly profit from 2019 to 2024"
88
+ """
89
+
90
+
91
  def analyze_trend(
92
  df: pd.DataFrame,
93
  date_column: str,