dataspring-cli 0.3.0__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.
- cli/__init__.py +15 -0
- cli/_skills/dataspring-author/SKILL.md +401 -0
- cli/_skills/dataspring-consume/SKILL.md +712 -0
- cli/_skills/dataspring-correct/SKILL.md +124 -0
- cli/auth.py +375 -0
- cli/bundled_manifest.py +36 -0
- cli/contract.py +1138 -0
- cli/generated.py +1297 -0
- cli/main.py +3232 -0
- cli/output.py +266 -0
- cli/runtime.py +201 -0
- cli/skills_commands.py +247 -0
- cli/skilltree.py +350 -0
- cli/upgrade.py +66 -0
- cli/version.py +123 -0
- dataspring_cli-0.3.0.dist-info/METADATA +202 -0
- dataspring_cli-0.3.0.dist-info/RECORD +20 -0
- dataspring_cli-0.3.0.dist-info/WHEEL +4 -0
- dataspring_cli-0.3.0.dist-info/entry_points.txt +2 -0
- settings.py +78 -0
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dataspring-consume
|
|
3
|
+
description: How to query DataSpring correctly to answer analytical questions. Use when the user is asking analytical/business questions, when an agent is connected to a DataSpring MCP server, when running `dataspring query ...` from the terminal, or when scripting against DataSpring's REST API. Covers the three surfaces (MCP, CLI, REST) — same principles, different invocations.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Querying DataSpring to answer analytical questions
|
|
7
|
+
|
|
8
|
+
You are querying DataSpring. Three surfaces are available — pick the
|
|
9
|
+
one the caller is using:
|
|
10
|
+
|
|
11
|
+
- **MCP** — agent connected to a DataSpring MCP server (Claude
|
|
12
|
+
Desktop, Claude Code via stdio, etc.). Tool calls.
|
|
13
|
+
- **CLI** — `dataspring` from the terminal. Shell commands.
|
|
14
|
+
- **REST** — direct HTTP. Scripts, integrations.
|
|
15
|
+
|
|
16
|
+
The principles below are surface-agnostic, drawn from real production
|
|
17
|
+
traces — departing from them produces misleading numbers.
|
|
18
|
+
|
|
19
|
+
## Surface cheatsheet
|
|
20
|
+
|
|
21
|
+
| Operation | MCP tool | CLI | REST |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| List metrics | `list_metrics()` | `dataspring metrics list` | `GET /api/semantic-layer/metrics` |
|
|
24
|
+
| List dimensions | `list_dimensions()` | `dataspring dimensions list` | `GET /api/semantic-layer/dimensions` |
|
|
25
|
+
| Explain one metric | `explain_metric(metric_name)` | `dataspring metrics show <name>` | `GET /api/semantic-layer/metrics/<name>` |
|
|
26
|
+
| Query | `query_metrics(metrics, dimensions?, grain?, start_date?, end_date?, where?, ...)` | `dataspring query -m <name> [-d ...] [-g month] ...` | `POST /api/dispatch/query_metrics` with the same fields |
|
|
27
|
+
| Explain what was computed | `explain_query(metrics, ...)` (same fields as `query_metrics`) | (no CLI command yet) | `POST /api/dispatch/explain_query` |
|
|
28
|
+
| What DataSpring learned / undo | `learned_edit list` / `undo` | `dataspring learned list` | `GET /api/learned` |
|
|
29
|
+
| Verified queries | `verified_query_edit list` / `record` | (no CLI command yet) | (no dedicated route) |
|
|
30
|
+
| Read business_context | `dataspring://context` resource | `dataspring business-context get` | `GET /api/context` |
|
|
31
|
+
| Check business_context size | `business_context_edit show_size` | `dataspring business-context size` | (no dedicated route — use `GET /api/context` and measure) |
|
|
32
|
+
| Org's published skill | (this skill, served via SKILL.md) | `dataspring skill instructions consume` | `GET /api/v1/skill/consume/instructions` |
|
|
33
|
+
|
|
34
|
+
No catalog is pre-loaded. You are handed NO catalog up front — the catalog
|
|
35
|
+
index (`list_metrics` / `dataspring metrics list` / `GET
|
|
36
|
+
/api/semantic-layer/metrics`) is the only place it exists, so start there
|
|
37
|
+
every time (see §2). Pass the user's question verbatim where the surface
|
|
38
|
+
accepts one — it is what lets the index flag an `ambiguous` set of
|
|
39
|
+
candidates (§2.1) instead of leaving you to guess between them.
|
|
40
|
+
|
|
41
|
+
All three surfaces hit the same backend, semantic layer, and authorisation
|
|
42
|
+
rules — same `business_context` and `description` bytes everywhere.
|
|
43
|
+
|
|
44
|
+
## Getting the CLI and this skill
|
|
45
|
+
|
|
46
|
+
One line installs the CLI (it installs `uv` first if the machine has none),
|
|
47
|
+
then log in and install the skills the server serves, so the text an agent
|
|
48
|
+
reads is the one this deployment was built with:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
curl -fsSL https://dataspring.app/install.sh | sh # Windows: irm https://dataspring.app/install.ps1 | iex
|
|
52
|
+
dataspring login
|
|
53
|
+
dataspring skills install --all # into ~/.claude/skills/<name>/, stamped
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Run `dataspring skills install` again after `dataspring upgrade` (the CLI
|
|
57
|
+
upgrades itself in place; `dataspring --version` says when the server has
|
|
58
|
+
operations this CLI does not know). `dataspring skills diff` shows what
|
|
59
|
+
drifted between the installed copy and the served one; `--offline` on any
|
|
60
|
+
`skills` command uses the copy bundled in the CLI instead of the server.
|
|
61
|
+
|
|
62
|
+
## 1. Knowledge layers — consult before querying
|
|
63
|
+
|
|
64
|
+
Before answering any analytical question, gather context from these
|
|
65
|
+
sources, in order:
|
|
66
|
+
|
|
67
|
+
| Layer | Source | What it gives you |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| 1. Org orientation | `business_context` (§13) | What the org sells, what's synced (and what isn't), cross-cutting derivation rules |
|
|
70
|
+
| 2. Semantic layer catalog | `list_metrics()` / `dataspring metrics list` / `GET /api/semantic-layer/metrics`, and the dimensions equivalents | An index of every metric and dimension in the org — names, grain, one-line summaries, most-used first |
|
|
71
|
+
| 3. Metric/dimension detail | `explain_metric(name)` / `dataspring metrics show <name>` / `GET /api/semantic-layer/metrics/<name>` | Unit, source rollup, standing filter rules, caveats (§3–§6) |
|
|
72
|
+
| 4. Runtime state | the query response itself | Whether the assumption actually holds — `NO_DATA`, an error `code`, a row count of zero |
|
|
73
|
+
|
|
74
|
+
Gather each layer ONCE. If you already have a layer, move to the next one; re-reading something
|
|
75
|
+
you have been given is the most common way to burn a turn without
|
|
76
|
+
improving the answer. And this ladder is for ANALYTICAL questions: a
|
|
77
|
+
greeting or a one-word reply needs none of it.
|
|
78
|
+
|
|
79
|
+
Each layer is cheaper to check than the next is to discover the hard way:
|
|
80
|
+
`business_context` says what's plausible, the catalog says what's
|
|
81
|
+
queryable, and only the query result can contradict the first three. Don't
|
|
82
|
+
skip straight to a query on a hunch — "obvious" metric names and assumed
|
|
83
|
+
data coverage are wrong often enough to matter.
|
|
84
|
+
|
|
85
|
+
## 2. The discovery pattern
|
|
86
|
+
|
|
87
|
+
**No catalog is pre-loaded.** Whatever surface you are on, you have
|
|
88
|
+
not been given the org's metrics — not in your system prompt, not in
|
|
89
|
+
this skill, not in an attachment. The catalog exists only behind the
|
|
90
|
+
tools below, and it is only ever as current as the call you make.
|
|
91
|
+
Answering from a metric name you inferred, remembered, or assumed
|
|
92
|
+
because it is the obvious English word is the failure this section
|
|
93
|
+
exists to prevent.
|
|
94
|
+
|
|
95
|
+
For any analytical question, in this order:
|
|
96
|
+
|
|
97
|
+
1. **List the catalog INDEX** — `list_metrics()` plus `list_dimensions()` on MCP; `dataspring metrics
|
|
98
|
+
list` on the CLI. You get names, type, grain and a one-line summary
|
|
99
|
+
per entry, ranked with the org's most-used entries first. It is an
|
|
100
|
+
index: enough to decide what is relevant, deliberately not enough to
|
|
101
|
+
answer with. Pass the user's question verbatim where the surface accepts one (§2.1), and
|
|
102
|
+
narrow a large index with the `query` substring parameter rather than
|
|
103
|
+
paging through it.
|
|
104
|
+
2. **Explain what you picked** — `explain_metric` /
|
|
105
|
+
`explain_metric(names=[...])`, for every entity the question
|
|
106
|
+
touches, in ONE call. This is where the authoritative description
|
|
107
|
+
lives: unit, source rollup, standing filter rules, caveats (§3). Read
|
|
108
|
+
it before you query; do not paraphrase from the name or the summary.
|
|
109
|
+
3. **Query** — only now, with the names and grains you just confirmed.
|
|
110
|
+
|
|
111
|
+
Don't skip step 1: "obvious" metrics like `total_revenue` differ across
|
|
112
|
+
orgs in unit, source, and standing filter rules, and a name matching the
|
|
113
|
+
question's English words is exactly the case where skipping feels safest
|
|
114
|
+
and is not.
|
|
115
|
+
|
|
116
|
+
Once you've read an entity's index row and description this conversation,
|
|
117
|
+
they stay in context — a follow-up or drilldown on the SAME entity goes
|
|
118
|
+
straight to the query. Re-list when the user pivots to a new subject.
|
|
119
|
+
|
|
120
|
+
**A `caveat` on an index row is not optional to skip.** The row
|
|
121
|
+
carries the metric's own KNOWN ISSUE / CAVEAT sentence (for example
|
|
122
|
+
`caveat: "KNOWN ISSUE: only surveyed tickets are counted"`). When a
|
|
123
|
+
row carries a `caveat`, step 1 alone is not enough — you MUST
|
|
124
|
+
call `explain_metric` (`explain_metric`) for that entity before
|
|
125
|
+
answering, and your answer must surface the caveat alongside the
|
|
126
|
+
number. A one-line summary is where a caveat goes silently missing
|
|
127
|
+
(§3).
|
|
128
|
+
|
|
129
|
+
This skill deliberately names no metrics of its own. Any metric or
|
|
130
|
+
dimension list in front of you comes from your org's semantic
|
|
131
|
+
layer, never from this document — a catalog written into a skill
|
|
132
|
+
drifts from the manifest the moment someone edits a dbt model.
|
|
133
|
+
|
|
134
|
+
## 2.1 Disambiguate instead of guessing
|
|
135
|
+
|
|
136
|
+
When two or more metrics plausibly answer the question, **ask — do not
|
|
137
|
+
pick**. Picking one and reporting its number is the most expensive
|
|
138
|
+
mistake available to you: the answer looks finished, so nobody checks it.
|
|
139
|
+
|
|
140
|
+
Calibrate on a real case: one org's revenue metrics exist as explicit
|
|
141
|
+
`*_ex_vat` / `*_inc_vat` pairs because the two readings differ by 25% and an
|
|
142
|
+
agent that silently chose was wrong a quarter of the time.
|
|
143
|
+
|
|
144
|
+
**What counts as ambiguous.** Several metrics match what the question
|
|
145
|
+
named, and nothing in the question separates them. A period, a filter or a
|
|
146
|
+
breakdown does NOT separate them ("churn last month" is exactly as
|
|
147
|
+
ambiguous as "churn" — a window narrows the query, never the metric).
|
|
148
|
+
Endorsement does not either — a metric on ten dashboards is the org's
|
|
149
|
+
*default*, not an answer to "which reading did you mean"; lead your
|
|
150
|
+
question with it, still ask. What DOES separate them is a term the user
|
|
151
|
+
supplied that fits one and not the others ("churn *rate*", "*total*
|
|
152
|
+
revenue"), or a description that answers the question the others cannot —
|
|
153
|
+
one clear winner: use it, and say which you used.
|
|
154
|
+
|
|
155
|
+
**Check before you query, not after.** A bare family term with no
|
|
156
|
+
qualifier of its own — treat it as ambiguous until the catalog says
|
|
157
|
+
otherwise; the ambiguity is only visible once you can see the whole family
|
|
158
|
+
side by side.
|
|
159
|
+
|
|
160
|
+
`list_metrics` runs this check for you when you pass the user's
|
|
161
|
+
question verbatim. When it cannot separate the candidates it returns an
|
|
162
|
+
`ambiguous` block naming them: stop and ask — **do not query any of them**
|
|
163
|
+
without a reason you could state out loud. The check is a floor, not a
|
|
164
|
+
verdict either way: no flag is not a clearance when you can see two
|
|
165
|
+
readings it did not catch (ask anyway); a flag is not always the last
|
|
166
|
+
word if the user's own wording already picks one out (use that one and
|
|
167
|
+
say so).
|
|
168
|
+
|
|
169
|
+
**Querying every candidate is not disambiguation.** Running all eight
|
|
170
|
+
members of a family and reporting the set answers a question the user
|
|
171
|
+
did not ask, costs eight queries, and leaves them to pick the number
|
|
172
|
+
themselves — which was your job to ask about.
|
|
173
|
+
|
|
174
|
+
**How to ask.** One short question that (1) names at least two candidates
|
|
175
|
+
by their exact catalog identifiers — "Net or gross?" without names leaves
|
|
176
|
+
the user guessing which metrics you mean — and (2) says what distinguishes
|
|
177
|
+
them (unit, source, population, period, standing filter; the index
|
|
178
|
+
summaries are usually enough, `explain_metric`/`explain_metric` if
|
|
179
|
+
you need more). Then stop and wait — do not query, and do not answer
|
|
180
|
+
"either way, here is one of them". Example, using the VAT pair above:
|
|
181
|
+
|
|
182
|
+
> "`total_event_revenue_ex_vat` or `total_event_revenue_inc_vat`? The
|
|
183
|
+
> first is net; the second includes 25% VAT."
|
|
184
|
+
|
|
185
|
+
A list of every candidate is not a question; two or three, named, with the
|
|
186
|
+
difference stated, is — and the names must come from the catalog you just
|
|
187
|
+
read, never from this document.
|
|
188
|
+
|
|
189
|
+
This is a different move from declaring a gap (§8): a gap means the
|
|
190
|
+
semantic layer *cannot* answer, ambiguity means it can answer in several
|
|
191
|
+
ways and only the user knows which.
|
|
192
|
+
|
|
193
|
+
## 3. Read the description fields
|
|
194
|
+
|
|
195
|
+
The `description` on each metric and dimension is **authoritative** — the
|
|
196
|
+
source of truth for unit, computation, source rollup, filter rules, and
|
|
197
|
+
caveats. Read it; do not paraphrase or infer the unit from the name.
|
|
198
|
+
|
|
199
|
+
What it tells you:
|
|
200
|
+
|
|
201
|
+
- **Unit** — DKK, count, %, hours. State it back explicitly in the answer.
|
|
202
|
+
- **Source rollup** — which systems contribute; say so if the user asks
|
|
203
|
+
about one system and the metric rolls up several.
|
|
204
|
+
- **Caveats** — temporal discontinuities, methodology changes, bug-fix
|
|
205
|
+
dates (see §6). A catalog row carrying a `caveat` sentence (§2) signals
|
|
206
|
+
this — quote it, don't answer from the index row alone.
|
|
207
|
+
- **Standing filter rules** — "for B2B, pair with…". Apply unless the
|
|
208
|
+
user explicitly overrides.
|
|
209
|
+
- **Derived-field pointers** — "use `event_segment`, not raw
|
|
210
|
+
`event_type`" (see §5).
|
|
211
|
+
|
|
212
|
+
Byte-identical across MCP, CLI, and REST — authored once in dbt YAML,
|
|
213
|
+
served everywhere.
|
|
214
|
+
|
|
215
|
+
## 4. Apply standing filter rules from descriptions
|
|
216
|
+
|
|
217
|
+
If a metric's description says "pair with `status='paid'` to exclude
|
|
218
|
+
voided invoices", include that filter unless the user explicitly asks for
|
|
219
|
+
the unfiltered view — the author wrote it because querying without it is a
|
|
220
|
+
known footgun. **MCP** passes it through `query_metrics(filter=...)` where
|
|
221
|
+
supported (else mention it in the answer); **CLI** (`dataspring query` has no
|
|
222
|
+
ad-hoc filters yet) and **REST** (POST body) mention or include it the same
|
|
223
|
+
way. Don't argue or substitute your own interpretation — the description is
|
|
224
|
+
closer to the warehouse than you are.
|
|
225
|
+
|
|
226
|
+
## 4.1 Echo the defaults that were applied for you
|
|
227
|
+
|
|
228
|
+
The user's saved preferences are applied to the query before it runs — a
|
|
229
|
+
standing filter, a default segment, a metric substitution. The response
|
|
230
|
+
reports them as `applied_defaults`, each with an `echo_text` phrase
|
|
231
|
+
(MCP: in the `query_metrics` response dict; CLI: printed with the
|
|
232
|
+
result; REST: in the `POST /query` body). Say every one in the answer, beside the
|
|
233
|
+
number it changed:
|
|
234
|
+
|
|
235
|
+
> Reseller revenue: $94k (excluding direct billings — your default)
|
|
236
|
+
|
|
237
|
+
A defaulted number is not the unqualified number, and the reader cannot
|
|
238
|
+
see the query. An explicit request in this turn wins over a standing
|
|
239
|
+
default and nothing is reported for it — say that too ("including direct
|
|
240
|
+
billings this time, overriding your standing filter"). Omitting the echo
|
|
241
|
+
is how a filtered figure gets quoted in a board deck as the total.
|
|
242
|
+
|
|
243
|
+
## 5. Prefer derived dimensions when descriptions point that way
|
|
244
|
+
|
|
245
|
+
Descriptions often steer you to a derived dimension over a raw one
|
|
246
|
+
(`customer_segment` over `customer.tier`, `event_segment` over
|
|
247
|
+
`event_type`) — the derived version layers business logic the raw field
|
|
248
|
+
usually lacks or mismodels. Use the qualified name verbatim from the
|
|
249
|
+
dimensions catalog (e.g. `pos_order_line__venue_name`, not `venue_name`) —
|
|
250
|
+
the prefix names the owning semantic model.
|
|
251
|
+
|
|
252
|
+
## 6. Honor temporal discontinuities
|
|
253
|
+
|
|
254
|
+
When a description warns "pre-X date is unreliable" or "ETL bug fixed Y
|
|
255
|
+
date", segment your query and tell the user about the discontinuity.
|
|
256
|
+
Example: "pre-2024-04-01 numbers exclude refunds; ETL bug fixed
|
|
257
|
+
2024-04-15" plus a year-over-year ask across that boundary means running
|
|
258
|
+
two queries (before/after) and calling out the methodology change rather
|
|
259
|
+
than silently quoting one misleading trend. Same rule regardless of
|
|
260
|
+
surface — same warehouse, same boundaries.
|
|
261
|
+
|
|
262
|
+
## 7. Insights, not just data
|
|
263
|
+
|
|
264
|
+
Don't just return raw query results. Interpret what the number
|
|
265
|
+
means in the org's business context.
|
|
266
|
+
|
|
267
|
+
| Bad | Good |
|
|
268
|
+
|-----|------|
|
|
269
|
+
| "Revenue: 2.1M DKK" | "Revenue hit 2.1M DKK in January, up 12% YoY. Lunch drove most of the growth while events dipped seasonally." |
|
|
270
|
+
| "NPS: 48" | "NPS at 48 (strong). Food scores highest (4.2/5) while atmosphere lags (3.8/5). Summer months consistently outperform winter." |
|
|
271
|
+
|
|
272
|
+
Always contextualize with all THREE of these. They are DIFFERENT
|
|
273
|
+
things, and a good answer that does two of them is still missing one:
|
|
274
|
+
|
|
275
|
+
1. **Comparison** — the figure next to another figure: YoY, MoM, vs a
|
|
276
|
+
trailing average, vs another period or segment. A direction word
|
|
277
|
+
alone ("up", "strong") is not one; there must be something it is up
|
|
278
|
+
against.
|
|
279
|
+
2. **Relative size** — the figure sized against a WHOLE: a share or
|
|
280
|
+
percent of a total, a rate, a per-unit figure, or a breakdown of the
|
|
281
|
+
parts. Note what this is NOT: "up 4.4% month over month" is a
|
|
282
|
+
comparison, not a relative size. A percentage CHANGE measures the
|
|
283
|
+
same number against its own past; sizing measures it against
|
|
284
|
+
something else — a total, a denominator, its own components.
|
|
285
|
+
3. **Business implication** — a driver, a risk, a data-quality caveat
|
|
286
|
+
that blocks a decision, or a next step. Restating the trend in words
|
|
287
|
+
("growth moderated") is NOT an implication; it has to say what the
|
|
288
|
+
number means for the reader.
|
|
289
|
+
|
|
290
|
+
**A question whose literal answer is one number is exactly the case
|
|
291
|
+
this rule is for, not an exception to it.** Shipping "Total revenue
|
|
292
|
+
last month was 3,525,734.18" is literally correct and still a failure:
|
|
293
|
+
you already have the query, one more window is one more call, and a
|
|
294
|
+
breakdown you already fetched costs nothing but arithmetic.
|
|
295
|
+
|
|
296
|
+
**But context is built from the metric you were asked about, not from
|
|
297
|
+
extra ones.** Widen the WINDOW of the metric the question names, or
|
|
298
|
+
break it down by a dimension it already has — do not go and fetch
|
|
299
|
+
neighbouring metrics to manufacture a story. Asked for one metric,
|
|
300
|
+
answer with THAT metric over two periods; do not also pull the two
|
|
301
|
+
related ones because they would make the paragraph richer. That reads as
|
|
302
|
+
not knowing which metric the question was about, it is the single
|
|
303
|
+
commonest way to turn a 3-call turn into a 6-call one, and the extra
|
|
304
|
+
metrics are exactly the near-misses you are supposed to avoid.
|
|
305
|
+
|
|
306
|
+
And when the figure is already in the conversation, the context is
|
|
307
|
+
too: answer from it. Do not spend a call re-reading your own previous
|
|
308
|
+
query to remember what you asked.
|
|
309
|
+
|
|
310
|
+
## 8. Declare the gap when the semantic layer can't answer
|
|
311
|
+
|
|
312
|
+
If a question requires a metric, dimension, or join that doesn't exist in
|
|
313
|
+
the catalog, say so explicitly before doing anything else — don't run a
|
|
314
|
+
query against the closest-named metric and report a misleading number,
|
|
315
|
+
and don't improvise a raw-SQL query around the gap: query generation stops
|
|
316
|
+
at the semantic layer and never falls back to hand-written SQL.
|
|
317
|
+
|
|
318
|
+
State the gap in this form, verbatim:
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
SEMANTIC LAYER GAP: [reason]
|
|
322
|
+
Falling back because: [specific missing metric/dimension/join]
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Then offer the checklist:
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
Would you like me to add this to the semantic layer? I can propose:
|
|
329
|
+
- [ ] New metric: `metric_name`
|
|
330
|
+
- [ ] New dimension: `dimension_name`
|
|
331
|
+
- [ ] New semantic model for `entity_name`
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
That checklist is for a gap you cannot compute around. When you CAN compute
|
|
335
|
+
it — every ingredient is in the catalog — the branch below replaces the
|
|
336
|
+
checklist: an admin is owed the definition itself, not a checkbox asking
|
|
337
|
+
whether to write one.
|
|
338
|
+
|
|
339
|
+
Valid reasons: the metric doesn't exist, a required dimension isn't
|
|
340
|
+
exposed, the question needs a join across entities no semantic model
|
|
341
|
+
connects, or it's ad-hoc exploration ahead of defining a metric.
|
|
342
|
+
|
|
343
|
+
Say it exactly this way rather than paraphrasing — the fixed shape is
|
|
344
|
+
what makes it easy to scan in a long conversation.
|
|
345
|
+
|
|
346
|
+
**When the ingredients exist and only the metric is missing.**
|
|
347
|
+
Sometimes every input is in the catalog and only the combination is
|
|
348
|
+
absent — revenue per ticket, where both `total_revenue` and
|
|
349
|
+
`tickets_created` exist and no ratio metric does. Being able to
|
|
350
|
+
compute it does not make it defined. Declare it anyway, in this
|
|
351
|
+
order:
|
|
352
|
+
|
|
353
|
+
1. **Declare** — the verbatim block above, naming the missing metric.
|
|
354
|
+
2. **Compute** — deliver the number as a labelled ephemeral
|
|
355
|
+
computation from rows you fetched (§8.1): say which metrics it was
|
|
356
|
+
computed from — **every** one of them, by name, not just the one the
|
|
357
|
+
unit happens to mention — and that it is not a defined metric. A rate
|
|
358
|
+
phrased as "34.4 per 1,000 usage events" names the denominator and
|
|
359
|
+
leaves the numerator unattributed: the reader cannot tell which metric
|
|
360
|
+
it was.
|
|
361
|
+
3. **Offer the durable path** for this user's role — an admin gets a
|
|
362
|
+
concrete definition, reached by asking `metric_edit` for a `preview`
|
|
363
|
+
and quoting back the `dbt_yaml` it returns; a member or viewer gets a quick metric via
|
|
364
|
+
`quick_metric_edit`. For an admin this happens in the SAME turn and
|
|
365
|
+
without asking: a preview writes nothing, so it needs no permission, and
|
|
366
|
+
only `create` does. Offering to propose a definition is not proposing
|
|
367
|
+
one — the YAML has to be in the answer.
|
|
368
|
+
|
|
369
|
+
Steps 1 and 2 are identical for every role. Only step 3 forks, and the
|
|
370
|
+
fork has to be visible in the answer:
|
|
371
|
+
|
|
372
|
+
| Role | Bad | Good |
|
|
373
|
+
|---|---|---|
|
|
374
|
+
| admin / owner | Opens "## Revenue per Support Ticket", shows a ratio table, offers to save it as a quick metric — or to propose a definition without showing one. No declaration, no YAML; the org still has no metric afterwards. | Opens with the verbatim gap block; gives the ratio computed from `total_revenue` and `tickets_created`, labelled ephemeral; then quotes the dbt YAML the delegated preview returned, in full, and asks whether to create it. |
|
|
375
|
+
| member / viewer | The same ratio table, presented as a finished figure the org agreed on. | The same gap block and the same labelled ratio; then "I can save this as a personal quick metric — `qm:revenue_per_ticket` — queryable from now on." Nothing org-scoped is offered or attempted. |
|
|
376
|
+
|
|
377
|
+
An admin answered the member's way is the exact failure this fork exists
|
|
378
|
+
to end: the arithmetic is right and the semantic layer is unchanged.
|
|
379
|
+
|
|
380
|
+
Do not silently divide two numbers in prose. An unlabelled quotient
|
|
381
|
+
reads as a figure the org has agreed on, and it is not one — that
|
|
382
|
+
substitution, not the arithmetic, is what this branch exists to end.
|
|
383
|
+
|
|
384
|
+
## 8.1 Compute, persist, or declare
|
|
385
|
+
|
|
386
|
+
A question that needs arithmetic over fetched rows — a growth rate, a
|
|
387
|
+
ratio, a share, two queries blended into one number — is answered by
|
|
388
|
+
fetching every ingredient with a metric query and computing from those rows
|
|
389
|
+
yourself (or in your host's own code tool, if it has one). DataSpring does
|
|
390
|
+
not run code for you.
|
|
391
|
+
|
|
392
|
+
### Two rules worth knowing before your first query
|
|
393
|
+
|
|
394
|
+
**Categorical dimension values are stored LOWER-CASED.** Write
|
|
395
|
+
`= 'enterprise'`, not `= 'Enterprise'`. A wrong-cased literal matches
|
|
396
|
+
nothing and returns zero rows with no error — you will get a `hint` naming
|
|
397
|
+
the real values, but that costs a round trip you did not need to spend.
|
|
398
|
+
When you are unsure of a value, the hint on an empty result is
|
|
399
|
+
authoritative: use it rather than guessing again.
|
|
400
|
+
|
|
401
|
+
**`order_by` takes `-field` for descending, not `field desc`.** Both are
|
|
402
|
+
accepted now (the SQL suffix is translated for you), but write
|
|
403
|
+
`-total_revenue` — and leave `order_by` out entirely on a single-row
|
|
404
|
+
aggregate, where there is nothing to sort.
|
|
405
|
+
|
|
406
|
+
Which rung a number belongs on:
|
|
407
|
+
|
|
408
|
+
| The number is... | Path |
|
|
409
|
+
|---|---|
|
|
410
|
+
| One-off arithmetic over rows you fetched | compute it yourself: one metric query per ingredient (`query_metrics` / `dataspring query` / `POST /query`), then the arithmetic, then the attributed answer |
|
|
411
|
+
| A derived number THIS user will want repeatedly | `quick_metric_edit` — persisted, and queried afterwards with the `qm:` prefix |
|
|
412
|
+
| A durable metric the whole org should share | the admin path — `metric_edit` preview, then the `dbt_yaml` it returns |
|
|
413
|
+
|
|
414
|
+
Three rules hold on every rung:
|
|
415
|
+
|
|
416
|
+
- **A computed number is ephemeral and is never a defined metric.** It
|
|
417
|
+
is a number computed for this one answer; nothing was added to the
|
|
418
|
+
semantic layer, and nobody else can query it.
|
|
419
|
+
- **Fetch every ingredient.** Run a metric query for each one; never reuse
|
|
420
|
+
a figure from prose, a dashboard title or an earlier turn as a literal —
|
|
421
|
+
a hardcoded input can't be reproduced from what was actually queried.
|
|
422
|
+
- **Attribute every computed number in the answer, however it was computed.**
|
|
423
|
+
Name the metrics it came from — `total_revenue`, `tickets_created`, not a
|
|
424
|
+
prose label a table happened to use for them ("Total Sales", "New
|
|
425
|
+
Tickets") — and say what it is not — "computed from `total_revenue` and
|
|
426
|
+
`tickets_created`; not a defined metric".
|
|
427
|
+
- **Raw SQL never, on any path.** Rows come from a metric query. There is
|
|
428
|
+
no second way.
|
|
429
|
+
|
|
430
|
+
When the query response carries `applied_defaults` (or `notes`, or `hint`),
|
|
431
|
+
carry it into the answer beside the number (§4.1): a defaulted figure quoted
|
|
432
|
+
as the unqualified one is the mistake this section exists to prevent.
|
|
433
|
+
|
|
434
|
+
## 9. When to update the semantic layer
|
|
435
|
+
|
|
436
|
+
Declaring a gap (§8) doesn't by itself mean the semantic layer
|
|
437
|
+
needs a change. After the gap is declared, judge whether the
|
|
438
|
+
question represents a reusable business need:
|
|
439
|
+
|
|
440
|
+
| Situation | Action |
|
|
441
|
+
|-----------|--------|
|
|
442
|
+
| One-off exploration | No update needed |
|
|
443
|
+
| Recurring metric need | Create a metric |
|
|
444
|
+
| Missing dimension | Add it to the relevant semantic model |
|
|
445
|
+
| New entity relationship | Consider a new semantic model |
|
|
446
|
+
|
|
447
|
+
Separate from *where a fact belongs once captured*: one metric/dimension/
|
|
448
|
+
entity fact goes in that object's `description` in the dbt YAML;
|
|
449
|
+
a cross-cutting organisation fact goes in `business_context`. This table
|
|
450
|
+
answers *whether it's worth capturing*; that rule answers *which document*.
|
|
451
|
+
|
|
452
|
+
Propose the change via `metric_edit` / `semantic_model_edit` (MCP), `dataspring metrics create` /
|
|
453
|
+
`dataspring models create` (CLI), or the equivalent admin REST routes —
|
|
454
|
+
admin/owner only. Show it first (§8): a `preview` compiles the definition
|
|
455
|
+
and writes nothing, so it needs no consent. What needs the user's yes is
|
|
456
|
+
`create`; never make that change unilaterally.
|
|
457
|
+
|
|
458
|
+
*How* to make that proposal — surface ownership, the member alternative,
|
|
459
|
+
when it needs a dbt YAML snippet instead of a write, and undo — is the
|
|
460
|
+
`dataspring-correct` skill (`GET /api/v1/skill/correct/instructions`); this
|
|
461
|
+
section only decides whether the change is worth making.
|
|
462
|
+
|
|
463
|
+
## 10. Refuse hopeless queries honestly
|
|
464
|
+
|
|
465
|
+
If a question requires data the catalog doesn't expose, or the
|
|
466
|
+
`business_context` warns the relevant system isn't synced, say so.
|
|
467
|
+
Don't run a query against the closest-named metric and report a
|
|
468
|
+
misleading number.
|
|
469
|
+
|
|
470
|
+
Examples of honest refusals:
|
|
471
|
+
|
|
472
|
+
- "I can't answer how many emails went out — `business_context`
|
|
473
|
+
notes the org doesn't sync staff mailboxes."
|
|
474
|
+
- "I can't answer attribution by paid-search keyword — there's no
|
|
475
|
+
metric or dimension in the catalog that captures search keyword."
|
|
476
|
+
|
|
477
|
+
## 11. Tool / response error recovery
|
|
478
|
+
|
|
479
|
+
Errors come back with a `code` and a `suggestion` (canonical name; older
|
|
480
|
+
paths still expose `hint` as an alias). Follow the suggestion.
|
|
481
|
+
|
|
482
|
+
Codes you'll see:
|
|
483
|
+
|
|
484
|
+
- `METRIC_NOT_FOUND` / `QUICK_METRIC_NOT_FOUND` — suggestion lists
|
|
485
|
+
available names. Don't retry with the same name; pick from the
|
|
486
|
+
suggestion or ask the user.
|
|
487
|
+
- `NO_DATA` — exists, but the date range / filter combination returned
|
|
488
|
+
nothing. Tell the user; don't fabricate a zero.
|
|
489
|
+
- `NO_BASE_METRICS` — quick metric expression has no dependencies to
|
|
490
|
+
resolve. Almost always a config bug; surface it.
|
|
491
|
+
- `EXCEEDS_INSTRUCTIONS_CAP` — `business_context_edit` write over the
|
|
492
|
+
2000-byte UTF-8 cap. The error includes `current_bytes`/`cap_bytes`;
|
|
493
|
+
trim the content, don't loop.
|
|
494
|
+
|
|
495
|
+
A qualified-dimension complaint (e.g. `venue_name` rejected, expecting
|
|
496
|
+
`pos_order_line__venue_name`) — retry with the qualified name once, don't
|
|
497
|
+
loop on the same error.
|
|
498
|
+
|
|
499
|
+
**Recovery is YOUR business, never the reader's.** Once you have recovered,
|
|
500
|
+
answer as if the failed attempt never happened. The reader asked a
|
|
501
|
+
question about their business; they did not ask what your tool calls did.
|
|
502
|
+
Never open an answer with, or otherwise mention:
|
|
503
|
+
|
|
504
|
+
- "The validation error was caused by an invalid request payload…"
|
|
505
|
+
- "The query is now valid."
|
|
506
|
+
- "I retried with valid structured arguments."
|
|
507
|
+
- "No additional write was needed — it already contains…"
|
|
508
|
+
|
|
509
|
+
Each of those is a real answer this agent has shipped, and each one
|
|
510
|
+
replaced the sentence the reader actually needed. If you could not
|
|
511
|
+
recover, say what you could not find out and what you would need — that
|
|
512
|
+
is about THEM. If you did recover, just answer.
|
|
513
|
+
|
|
514
|
+
Surface shape: **MCP** returns `{error, code, suggestion}` in the tool
|
|
515
|
+
response (`hint` mirrors `suggestion` for back-compat); **CLI** prints
|
|
516
|
+
"✗ Error: …" with a hint line, exit code 1; **REST** is HTTP 4xx/5xx with
|
|
517
|
+
`{"detail": {"message", "code", "suggestion", ...}}`.
|
|
518
|
+
|
|
519
|
+
## 12. Multi-turn / multi-call efficiency
|
|
520
|
+
|
|
521
|
+
After the catalog is loaded once:
|
|
522
|
+
|
|
523
|
+
- Drilldowns ("show me the same broken down by region") go
|
|
524
|
+
straight to the query call. No re-discovery needed.
|
|
525
|
+
- Comparison questions ("how does this compare to last year?")
|
|
526
|
+
are a second query call with adjusted dates.
|
|
527
|
+
- Refinements ("can you exclude internal accounts?") add a
|
|
528
|
+
filter; respect any standing rules from the metric description
|
|
529
|
+
on top of the user's ad-hoc filter.
|
|
530
|
+
|
|
531
|
+
**If the figure is already in this conversation, answer from it.** A
|
|
532
|
+
follow-up like "which of those days was weakest?" is arithmetic over rows
|
|
533
|
+
you already fetched and printed — re-running the same query to re-read
|
|
534
|
+
your own table costs a round trip and risks answering from a different
|
|
535
|
+
result than the one the user is looking at. Query again only when the
|
|
536
|
+
question needs data you have not got: a new metric, a new window, a new
|
|
537
|
+
breakdown.
|
|
538
|
+
|
|
539
|
+
The catalog is only stale if the user pivots to a brand-new
|
|
540
|
+
analytical area; in that case, re-list.
|
|
541
|
+
|
|
542
|
+
## 13. The org's `business_context` document
|
|
543
|
+
|
|
544
|
+
Cross-cutting org orientation lives in `business_context` — what
|
|
545
|
+
the org sells, what's synced, what's NOT synced, cross-cutting
|
|
546
|
+
derivation rules. Hard cap: 2000 UTF-8 bytes.
|
|
547
|
+
|
|
548
|
+
How it reaches you:
|
|
549
|
+
|
|
550
|
+
- **CLI**: `dataspring business-context get` prints it.
|
|
551
|
+
`dataspring business-context size` shows the byte budget.
|
|
552
|
+
- **REST**: `GET /api/context` returns `{"content": "..."}`.
|
|
553
|
+
|
|
554
|
+
Treat `business_context` as part of the same authoritative layer
|
|
555
|
+
as metric descriptions. Same provenance (dbt repo via the
|
|
556
|
+
manifest workflow), same trust level.
|
|
557
|
+
|
|
558
|
+
That's the working agreement. Same principles, three surfaces —
|
|
559
|
+
do this and the answers will be right.
|
|
560
|
+
|
|
561
|
+
## 14. Parameter reference
|
|
562
|
+
|
|
563
|
+
<!-- dispatch-params: query_metrics explain_query run_sql update_context quick_metric_edit learned_edit verified_query_edit business_context_edit -->
|
|
564
|
+
_Generated from the dispatch registry (`GET /api/dispatch`); do not edit by hand. Field descriptions are the models' own, verbatim on every surface._
|
|
565
|
+
|
|
566
|
+
#### `query_metrics`
|
|
567
|
+
|
|
568
|
+
MCP tool `query_metrics`; `POST /api/dispatch/query_metrics`. Fetch metric data from the warehouse.
|
|
569
|
+
|
|
570
|
+
| Parameter | Type | Required | Description |
|
|
571
|
+
|---|---|---|---|
|
|
572
|
+
| `metrics` | list of string | yes | List of metric names to query (e.g., ['total_revenue', 'order_count']) |
|
|
573
|
+
| `dimensions` | list of string | no | Dimensions to group by. Use qualified names from dataspring://dimensions (e.g., ['customer__segment', 'order__region']) |
|
|
574
|
+
| `grain` | string | no | Time granularity: 'day', 'week', 'month', 'quarter', or 'year' |
|
|
575
|
+
| `start_date` | string | no | Start date in YYYY-MM-DD format |
|
|
576
|
+
| `end_date` | string | no | End date in YYYY-MM-DD format |
|
|
577
|
+
| `limit` | integer | no | Maximum number of rows to return |
|
|
578
|
+
| `order_by` | string | no | Column to sort by, append ' desc' for descending (e.g., 'total_revenue desc') |
|
|
579
|
+
| `where` | list of string | no | Row filters in MetricFlow's constraint syntax, one string each, e.g. "{{ Dimension('customer__segment') }} NOT IN ('Direct')". Use qualified names from dataspring://dimensions - an unresolvable name comes back as an error whose suggestion names the qualified form. `where` decides WHICH ROWS the numbers cover; `dimensions` breaks the numbers OUT by a column; send both for a breakdown of a filtered slice. A `where` on a dimension also overrides the user's standing filter on that same dimension for this call. |
|
|
580
|
+
| `warehouse` | string | no | Which of the org's warehouses to run against - an id from dataspring://warehouses ('managed', 'external', ...). Omit for the active one. During a migration ask both and compare. |
|
|
581
|
+
| `suggest_visualization` | boolean | no, default `False` | Also return a suggested visualization type for the result. |
|
|
582
|
+
| `format` | one of `json`, `csv` | no, default `json` | 'json' (default) answers rows in `data`; 'csv' answers the same result as one CSV document in `content` (header row first) for a file or a spreadsheet, with `columns` and `row_count` alongside. |
|
|
583
|
+
|
|
584
|
+
#### `explain_query`
|
|
585
|
+
|
|
586
|
+
MCP tool `explain_query`; `POST /api/dispatch/explain_query`. Show what a query computes without running it: the resolved metrics and dimensions, the effective time window, the filters (including any standing preference folded in) and the compiled SQL. Use it when the user asks what you actually computed, disputes a number, or before proposing a correction. ``warehouse`` picks one of the org's warehouses; omit it for the active one.
|
|
587
|
+
|
|
588
|
+
| Parameter | Type | Required | Description |
|
|
589
|
+
|---|---|---|---|
|
|
590
|
+
| `metrics` | list of string | yes | List of metric names to query (e.g., ['total_revenue', 'order_count']) |
|
|
591
|
+
| `dimensions` | list of string | no | Dimensions to group by. Use qualified names from dataspring://dimensions (e.g., ['customer__segment', 'order__region']) |
|
|
592
|
+
| `grain` | string | no | Time granularity: 'day', 'week', 'month', 'quarter', or 'year' |
|
|
593
|
+
| `start_date` | string | no | Start date in YYYY-MM-DD format |
|
|
594
|
+
| `end_date` | string | no | End date in YYYY-MM-DD format |
|
|
595
|
+
| `limit` | integer | no | Maximum number of rows to return |
|
|
596
|
+
| `order_by` | string | no | Column to sort by, append ' desc' for descending (e.g., 'total_revenue desc') |
|
|
597
|
+
| `where` | list of string | no | Row filters in MetricFlow's constraint syntax, one string each, e.g. "{{ Dimension('customer__segment') }} NOT IN ('Direct')". Use qualified names from dataspring://dimensions - an unresolvable name comes back as an error whose suggestion names the qualified form. `where` decides WHICH ROWS the numbers cover; `dimensions` breaks the numbers OUT by a column; send both for a breakdown of a filtered slice. A `where` on a dimension also overrides the user's standing filter on that same dimension for this call. |
|
|
598
|
+
| `warehouse` | string | no | Which of the org's warehouses to run against - an id from dataspring://warehouses ('managed', 'external', ...). Omit for the active one. During a migration ask both and compare. |
|
|
599
|
+
|
|
600
|
+
#### `run_sql`
|
|
601
|
+
|
|
602
|
+
MCP tool `run_sql`; `POST /api/dispatch/run_sql`. Run ONE read-only SQL statement against the org's warehouse and get the rows back, ungoverned.
|
|
603
|
+
|
|
604
|
+
| Parameter | Type | Required | Description |
|
|
605
|
+
|---|---|---|---|
|
|
606
|
+
| `sql` | string | yes | One SQL statement (BigQuery Standard SQL on a managed warehouse). Name tables as <org>_marts.<table>, <org>_core.<table> or <org>_staging.<table>; the identity it runs as can read those and nothing else, so a write or a reach outside them is refused by the warehouse. |
|
|
607
|
+
| `warehouse` | string | no | One of the org's warehouses (see dataspring://warehouses); omit for the active one. |
|
|
608
|
+
| `max_rows` | integer | no, default `1000` | Rows to return at most (default 1000, cap 10000). Aggregate in SQL rather than paging. |
|
|
609
|
+
| `format` | one of `json`, `csv`, `markdown` | no, default `json` | 'json' answers rows in `data`; 'csv' or 'markdown' answer one document in `content`. |
|
|
610
|
+
| `dry_run` | boolean | no, default `False` | Only estimate the bytes the statement would process (and check it parses); run nothing. |
|
|
611
|
+
|
|
612
|
+
#### `update_context`
|
|
613
|
+
|
|
614
|
+
MCP tool `update_context`; `POST /api/dispatch/update_context`. Update user preferences (merge, not replace).
|
|
615
|
+
|
|
616
|
+
| Parameter | Type | Required | Description |
|
|
617
|
+
|---|---|---|---|
|
|
618
|
+
| `updates` | object | yes | Fields to update; only these change. Presentation: default_currency, default_grain, decimal_places, preferred_chart_type. Lists and maps REPLACE what is stored: favorite_metrics, standing_filters ([{dimension, operator: in\|not_in\|eq\|neq, values}]), metric_substitutions ({asked: preferred}), default_segment ({dimension, value} or null). |
|
|
619
|
+
|
|
620
|
+
#### `quick_metric_edit`
|
|
621
|
+
|
|
622
|
+
MCP tool `quick_metric_edit`; `POST /api/dispatch/quick_metric_edit`. Edit user-defined quick metrics (arithmetic expressions over existing metrics). The body is `{"action": {"action": "<verb>", ...}}`, one verb per table below.
|
|
623
|
+
|
|
624
|
+
**`create`** —
|
|
625
|
+
|
|
626
|
+
| Parameter | Type | Required | Description |
|
|
627
|
+
|---|---|---|---|
|
|
628
|
+
| `name` | string | yes | Metric name (e.g., "revenue_per_order") |
|
|
629
|
+
| `expression` | string | yes | Arithmetic expression over existing metrics (e.g., "total_revenue / order_count") |
|
|
630
|
+
| `description` | string | no | |
|
|
631
|
+
|
|
632
|
+
**`update`** —
|
|
633
|
+
|
|
634
|
+
| Parameter | Type | Required | Description |
|
|
635
|
+
|---|---|---|---|
|
|
636
|
+
| `metric_id` | string | yes | |
|
|
637
|
+
| `name` | string | no | |
|
|
638
|
+
| `expression` | string | no | |
|
|
639
|
+
| `description` | string | no | |
|
|
640
|
+
|
|
641
|
+
**`delete`** —
|
|
642
|
+
|
|
643
|
+
| Parameter | Type | Required | Description |
|
|
644
|
+
|---|---|---|---|
|
|
645
|
+
| `metric_id` | string | yes | |
|
|
646
|
+
|
|
647
|
+
#### `learned_edit`
|
|
648
|
+
|
|
649
|
+
MCP tool `learned_edit`; `POST /api/dispatch/learned_edit`. Browse and undo the learned trail — what DataSpring learned for this org. The body is `{"action": {"action": "<verb>", ...}}`, one verb per table below.
|
|
650
|
+
|
|
651
|
+
**`list`** — List what DataSpring learned for this org (plus your personal entries).
|
|
652
|
+
|
|
653
|
+
| Parameter | Type | Required | Description |
|
|
654
|
+
|---|---|---|---|
|
|
655
|
+
| `limit` | integer | no, default `20` | |
|
|
656
|
+
| `include_reverted` | boolean | no, default `False` | |
|
|
657
|
+
|
|
658
|
+
**`undo`** — Revert one learning (or, with no id, your most recent reversible one).
|
|
659
|
+
|
|
660
|
+
| Parameter | Type | Required | Description |
|
|
661
|
+
|---|---|---|---|
|
|
662
|
+
| `learning_id` | string | no | Learning to revert; omit for your most recent reversible entry |
|
|
663
|
+
|
|
664
|
+
#### `verified_query_edit`
|
|
665
|
+
|
|
666
|
+
MCP tool `verified_query_edit`; `POST /api/dispatch/verified_query_edit`. Curate verified queries — confirmed (question, query) pairs. The body is `{"action": {"action": "<verb>", ...}}`, one verb per table below.
|
|
667
|
+
|
|
668
|
+
**`list`** — The org's verified queries plus your own personal ones.
|
|
669
|
+
|
|
670
|
+
| Parameter | Type | Required | Description |
|
|
671
|
+
|---|---|---|---|
|
|
672
|
+
| `limit` | integer | no, default `20` | |
|
|
673
|
+
|
|
674
|
+
**`record`** — Record one confirmed (question, query) pair.
|
|
675
|
+
|
|
676
|
+
| Parameter | Type | Required | Description |
|
|
677
|
+
|---|---|---|---|
|
|
678
|
+
| `question` | string | yes | The user's question, verbatim |
|
|
679
|
+
| `params` | object | yes | The query that answers it, in the query_metrics wire shape: metrics, dimensions, grain, start_date, end_date, where, order_by, limit |
|
|
680
|
+
| `note` | string | no | One line on WHY this is the right query for that question |
|
|
681
|
+
| `scope` | one of `org`, `user` | no, default `user` | 'user' (the default) records it in this user's personal set; 'org' curates it for everyone and requires admin/owner |
|
|
682
|
+
|
|
683
|
+
**`delete`** — Remove one verified query by id.
|
|
684
|
+
|
|
685
|
+
| Parameter | Type | Required | Description |
|
|
686
|
+
|---|---|---|---|
|
|
687
|
+
| `id` | string | yes | |
|
|
688
|
+
|
|
689
|
+
#### `business_context_edit`
|
|
690
|
+
|
|
691
|
+
MCP tool `business_context_edit`; `POST /api/dispatch/business_context_edit`. Edit the org's cross-cutting business_context document. The body is `{"action": {"action": "<verb>", ...}}`, one verb per table below.
|
|
692
|
+
|
|
693
|
+
**`get`** — Read the current org business_context document.
|
|
694
|
+
|
|
695
|
+
No parameters.
|
|
696
|
+
|
|
697
|
+
**`show_size`** — Report current size and remaining room within the 2000-byte UTF-8 cap.
|
|
698
|
+
|
|
699
|
+
No parameters.
|
|
700
|
+
|
|
701
|
+
**`set`** — Replace the org's business_context with ``content`` (admin/owner).
|
|
702
|
+
|
|
703
|
+
| Parameter | Type | Required | Description |
|
|
704
|
+
|---|---|---|---|
|
|
705
|
+
| `content` | string | yes | Full document content to save |
|
|
706
|
+
|
|
707
|
+
**`append`** — Append ``content`` to the existing business_context (admin/owner).
|
|
708
|
+
|
|
709
|
+
| Parameter | Type | Required | Description |
|
|
710
|
+
|---|---|---|---|
|
|
711
|
+
| `content` | string | yes | Text to append to the current document |
|
|
712
|
+
<!-- /dispatch-params -->
|