@datarails-ai/dr-cli 0.0.1-locked → 1.6.71
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.
- package/README.md +608 -0
- package/dist/chunk-DWMCBGQH.js +242 -0
- package/dist/cli.js +64468 -0
- package/dist/kb-6UWI7Q32.js +21 -0
- package/dist/scopes-map.json +4412 -0
- package/dist/skills/connector-sandbox/SKILL.md +95 -0
- package/dist/skills/datarails-connect-workato/SKILL.md +140 -0
- package/dist/skills/datarails-connectors-onboard-connector/SKILL.md +813 -0
- package/dist/skills/dr-cli/SKILL.md +659 -0
- package/dist/skills/dr-cli/reference/admin.md +796 -0
- package/dist/skills/dr-cli/reference/budget-builder.md +151 -0
- package/dist/skills/dr-cli/reference/calc-vals.md +73 -0
- package/dist/skills/dr-cli/reference/calcmodels.md +76 -0
- package/dist/skills/dr-cli/reference/cash.md +61 -0
- package/dist/skills/dr-cli/reference/dashboards-widgets.md +85 -0
- package/dist/skills/dr-cli/reference/datamappers.md +150 -0
- package/dist/skills/dr-cli/reference/desk.md +472 -0
- package/dist/skills/dr-cli/reference/filebox-documents.md +373 -0
- package/dist/skills/dr-cli/reference/forecast.md +51 -0
- package/dist/skills/dr-cli/reference/integrations.md +57 -0
- package/dist/skills/dr-cli/reference/lookups.md +168 -0
- package/dist/skills/dr-cli/reference/metrics.md +512 -0
- package/dist/skills/dr-cli/reference/multi-product.md +180 -0
- package/dist/skills/dr-cli/reference/reconciliation-mec.md +76 -0
- package/dist/skills/dr-cli/reference/rh-config.md +84 -0
- package/dist/skills/dr-cli/reference/safety.md +143 -0
- package/dist/skills/dr-cli/reference/spend-control.md +206 -0
- package/dist/skills/dr-cli/reference/templates.md +163 -0
- package/dist/skills/dr-cli/reference/workflows.md +137 -0
- package/dist/skills/dr-error-enrichment/SKILL.md +80 -0
- package/dist/skills/dr-error-enrichment/reference/core.md +257 -0
- package/dist/skills/dr-error-enrichment/reference/groundcover.md +84 -0
- package/dist/skills/dr-error-enrichment/reference/jira-bugs.md +219 -0
- package/dist/skills/dr-error-enrichment/reference/safety-backup.md +122 -0
- package/dist/skills/dr-error-enrichment-author/SKILL.md +219 -0
- package/dist/skills/dr-error-enrichment-improve/SKILL.md +218 -0
- package/dist/skills/dr-error-enrichment-maintain/SKILL.md +237 -0
- package/dist/skills/dr-error-enrichment-reconcile/SKILL.md +167 -0
- package/dist/skills/dr-filebox-api-setup/SKILL.md +170 -0
- package/dist/skills/dr-switch-user/SKILL.md +395 -0
- package/dist/skills/dr-validate/SKILL.md +392 -0
- package/dist/skills/dr-validate/report-template.html +950 -0
- package/dist/skills/fx-conversion-matrix/SKILL.md +763 -0
- package/dist/skills/fx-conversion-matrix/scripts/_args.mjs +52 -0
- package/dist/skills/fx-conversion-matrix/scripts/_xlsx.mjs +241 -0
- package/dist/skills/fx-conversion-matrix/scripts/build-matrix.mjs +366 -0
- package/dist/skills/fx-conversion-matrix/scripts/gen-mappers.mjs +171 -0
- package/dist/skills/fx-conversion-matrix/scripts/rates-to-xlsx.mjs +172 -0
- package/dist/skills/fx-onboard/SKILL.md +325 -0
- package/dist/skills/mec-table-validation/SKILL.md +383 -0
- package/dist/skills/plugins.json +24 -0
- package/dist/skills/validate-workato-migration/SKILL.md +167 -0
- package/dist/skills/validate-workato-migration/reference/gotchas.md +16 -0
- package/dist/skills/validate-workato-migration/reference/staging-orgs.md +26 -0
- package/dist/skills/validate-workato-migration/scripts/compare_files.py +255 -0
- package/dist/zendeskKb-JPSHVOPH.js +243 -0
- package/package.json +56 -9
- package/scripts/postinstall.mjs +91 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
# Metrics (KPIs), categories, dimensions, functions
|
|
2
|
+
|
|
3
|
+
## Read
|
|
4
|
+
|
|
5
|
+
- `metrics list` (`--raw --json` for the raw shape), `metrics get <id>`.
|
|
6
|
+
`--json` emits the FinanceOS `MetricItem` shape: `template_id`,
|
|
7
|
+
`date_field_id`, `value_field_id`, `dimension_fields`, `organization_id`,
|
|
8
|
+
`base_metrics`, `configuration`, `created_at`, `updated_at`. For CALC metrics
|
|
9
|
+
the formula is at **`configuration.presented_formula`** — NOT a top-level
|
|
10
|
+
field (`--json | jq '.presented_formula'` returns null; read
|
|
11
|
+
`.configuration.presented_formula`).
|
|
12
|
+
- `metrics data --id <id> --scenario Actuals --from 2026-01-01 --to 2026-12-31` —
|
|
13
|
+
the KPI value over a range. `--scenario` / `--cycle` are repeatable and also
|
|
14
|
+
accept comma-separated values (e.g. `Actuals,Budget`) to pull several at once.
|
|
15
|
+
- `--period <day|month|quarter|year>` (default `month`) sets the grain of the
|
|
16
|
+
returned buckets.
|
|
17
|
+
- `--agg <DEFAULT|MTD|QTD|YTD|MTG|QTG|YTG>` (default `DEFAULT`) sets how each
|
|
18
|
+
bucket accumulates. `DEFAULT` = the bucket's own value.
|
|
19
|
+
- **`*TD` ("to date")** accumulate backward: from the start of the
|
|
20
|
+
month/quarter/year up to and including the current bucket.
|
|
21
|
+
- **`*TG` ("to go")** accumulate forward: from the current bucket
|
|
22
|
+
(**inclusive**) to the end of the month/quarter/year. So at month grain
|
|
23
|
+
`YTG` on January = the full-year total, and `MTG` = the month's own
|
|
24
|
+
value. The identity `YTD[N-1] + YTG[N] = annual total` holds.
|
|
25
|
+
- **Accumulation is clipped to the requested `--from`/`--to` window** —
|
|
26
|
+
there is no separate as-of parameter, and the server never looks outside
|
|
27
|
+
the range. A too-narrow window silently returns a smaller number rather
|
|
28
|
+
than an error, in both directions: `--from 2025-06-01` makes June's `YTD`
|
|
29
|
+
equal June alone, and `--to 2025-06-30` makes January's `YTG` equal
|
|
30
|
+
Jan–Jun. Always span the full period you're accumulating over.
|
|
31
|
+
- `--period day` is rejected for metrics whose formula uses
|
|
32
|
+
`PERIODS_IN_YEAR` — that's "not a valid question" (an annualization factor
|
|
33
|
+
has no day-grain meaning), NOT missing data. Re-query with
|
|
34
|
+
`--period month|quarter|year`. With multiple `--id`s the response reports
|
|
35
|
+
the rejection per-metric (`INVALID_PARAMS` + an actionable message); with
|
|
36
|
+
a single `--id` the endpoint collapses ANY per-metric failure into a bare
|
|
37
|
+
HTTP 404 — the CLI appends a note explaining both readings (id missing on
|
|
38
|
+
this tenant vs. metric rejected the query) and how to disambiguate.
|
|
39
|
+
- `metrics drilldown --id <id> --from … --to …` — the source rows behind the
|
|
40
|
+
value ("where is this number coming from").
|
|
41
|
+
|
|
42
|
+
Names, descriptions, and CALC `presented_formula` are HTML-decoded before render,
|
|
43
|
+
so both `--json` and the table show clean text.
|
|
44
|
+
|
|
45
|
+
## Create BASE (`metrics create base`)
|
|
46
|
+
|
|
47
|
+
Required: `--name`, `--template <id>`, `--value-field <id>`, `--date-field <id>`,
|
|
48
|
+
`--aggregator <agg>`.
|
|
49
|
+
|
|
50
|
+
- **Aggregator is one of `SUM`, `COUNT`, `CUMULATIVE_SUM`, `CUMULATIVE_COUNT`.**
|
|
51
|
+
`AVG`/`MIN`/`MAX` are not valid for metric value fields and are rejected.
|
|
52
|
+
- `--time-aggregation` is `SUM`, `EOP`, or `AVG`. A cumulative aggregator cannot
|
|
53
|
+
use `SUM` and defaults to `EOP`.
|
|
54
|
+
- `--filter <field_id>=<v>` / `--exclude-filter …`; `--dimensions` is PK-validated
|
|
55
|
+
client-side.
|
|
56
|
+
|
|
57
|
+
## Create CALC (`metrics create calc`)
|
|
58
|
+
|
|
59
|
+
`--formula "[Revenue]-[COGS]"`:
|
|
60
|
+
|
|
61
|
+
- Each referenced metric name is wrapped in `[ ]`; bare names (`revenue/cogs`)
|
|
62
|
+
are rejected. The formula must reference ≥1 metric (constants-only like
|
|
63
|
+
`5 * 12` is rejected) and `()` must balance.
|
|
64
|
+
- **Time modifiers** (optional, exactly two forms):
|
|
65
|
+
- `[M].SHIFT(<year|quarter|month|day|consumption_period>, <offset≤0>)` — "N
|
|
66
|
+
periods ago". `|offset|` ≤ 1200. `consumption_period` is the **dynamic**
|
|
67
|
+
unit (UI label "Period"): it resolves to the grain of the query, so
|
|
68
|
+
`[M].SHIFT(consumption_period, -1)` reads one viewing-grain step back at
|
|
69
|
+
every grain — the opening-balance pattern on cumulative metrics. Fixed
|
|
70
|
+
units always step the named unit regardless of grain (`-1 year` viewed
|
|
71
|
+
quarterly = same quarter last year); use them for prior-year comparisons.
|
|
72
|
+
- `[M].AS_OF(<year_end|quarter_end|month_end>)` — "end of last period"
|
|
73
|
+
(requires an EOP metric, enforced server-side). Anchors are fixed only —
|
|
74
|
+
there is no dynamic AS_OF anchor.
|
|
75
|
+
- A bare `[M]` (no suffix) **is** the current period — do NOT add a modifier to
|
|
76
|
+
mean "current". Modifiers cannot be chained on one reference.
|
|
77
|
+
- **Reserved tokens** resolve at runtime — write them **bare**, not bracketed,
|
|
78
|
+
and never attach a time modifier to one:
|
|
79
|
+
- `DAYS_IN_PERIOD` = days in the aggregation period:
|
|
80
|
+
`"[Revenue] / DAYS_IN_PERIOD"`.
|
|
81
|
+
- `PERIODS_IN_YEAR` = annualization factor of the query window (12 at month
|
|
82
|
+
grain, 4 at quarter, 1 at year; pro-rated for partial windows):
|
|
83
|
+
`"[Churn] * PERIODS_IN_YEAR"`. One definition annualizes correctly at every
|
|
84
|
+
grain — no per-grain ×12/×4 metric variants needed. A metric using it
|
|
85
|
+
cannot be queried at day grain (see `metrics data` above).
|
|
86
|
+
- The `create calc --json` response flags dynamic-time metrics:
|
|
87
|
+
`uses_dynamic_time_units: true`, plus `unsupported_grains: ["day"]` when
|
|
88
|
+
`PERIODS_IN_YEAR` is used. The `get`/`list` reads do **not** return these
|
|
89
|
+
two fields, so record them at create time if you need them.
|
|
90
|
+
- Formula length is capped at 5000 characters (server limit, checked client-side).
|
|
91
|
+
- No `--time-aggregation` — a CALC rollup inherits time-aggregation from each
|
|
92
|
+
referenced BASE, so a single CALC-level value would be meaningless.
|
|
93
|
+
|
|
94
|
+
## Update / delete
|
|
95
|
+
|
|
96
|
+
- `metrics update <id>` — partial PATCH. Flags: `--name`, `--description`,
|
|
97
|
+
`--category`, `--dimensions`, `--hide`/`--unhide`, `--time-aggregation`,
|
|
98
|
+
`--aggregator`, `--template`, `--value-field`, `--date-field`, `--filter`,
|
|
99
|
+
`--exclude-filter`, `--clear-filters`. `--description ""` clears to `null` on
|
|
100
|
+
the wire (matching how `get` round-trips an untouched field).
|
|
101
|
+
- **No `--formula`** — CALC formulas and other create-time fields (kind,
|
|
102
|
+
template, value/date field, aggregator) are intentionally not editable in
|
|
103
|
+
place; changing them yields a different metric. To fix a formula:
|
|
104
|
+
`metrics delete <id>` then `metrics create calc`.
|
|
105
|
+
- `metrics delete <ids...>` — two-step confirm. One id deletes that metric;
|
|
106
|
+
passing several ids (`metrics delete 100 200 300`) deletes them as a single
|
|
107
|
+
batch — the plan lists every target and mints ONE `--confirm-token` bound to
|
|
108
|
+
the whole set, so the confirm is two calls total, not two per id. Ids are
|
|
109
|
+
order-independent and de-duplicated. A failed id doesn't abort the batch: each
|
|
110
|
+
outcome is reported and the command exits non-zero if any delete failed.
|
|
111
|
+
|
|
112
|
+
## Re-aggregate (`metrics re-aggregate`)
|
|
113
|
+
|
|
114
|
+
Manually trigger the semantic-layer pipeline that rebuilds pre-aggregated
|
|
115
|
+
metric values. Use after bulk data/mapping changes when metric values look
|
|
116
|
+
stale. **By default a 202 means the rebuild was QUEUED in the background, not
|
|
117
|
+
finished** — add `--wait` (targeted ids only) to block until it completes.
|
|
118
|
+
|
|
119
|
+
- `metrics re-aggregate <id> [<id> …]` — targeted refresh (max 100 distinct
|
|
120
|
+
ids, duplicates deduped client-side; ids from `metrics list`). The server
|
|
121
|
+
**silently drops** ids the caller can't access; the CLI diffs the response
|
|
122
|
+
against the request and prints a `warning:` line listing any skipped ids —
|
|
123
|
+
check for it before assuming every requested metric was refreshed.
|
|
124
|
+
- `metrics re-aggregate <id> … --wait [--timeout <seconds>]` — after queuing,
|
|
125
|
+
poll each **accepted** id's `status_info` (the same signal that drives the
|
|
126
|
+
server's completion log) until every one reaches a terminal state, then print
|
|
127
|
+
a `succeeded / completed-with-warnings / failed` summary. Default timeout is
|
|
128
|
+
300s. **Exits non-zero** if any metric fails or the wait times out (the error
|
|
129
|
+
names the still-processing ids so you can re-check rather than blindly
|
|
130
|
+
re-trigger). Only the dropped-but-accepted ids are waited on. `--wait` is
|
|
131
|
+
**not** supported with `--all` (org-wide queues per-table jobs with no
|
|
132
|
+
per-metric status to poll — trigger without `--wait`, then check
|
|
133
|
+
`metrics list`).
|
|
134
|
+
- `metrics re-aggregate --all` — org-wide rebuild of **every** table (except
|
|
135
|
+
the internal `dr_metrics` table). Heavy: queues one background job per
|
|
136
|
+
table. Two-step confirm, same flow as `delete`; the confirm token is bound
|
|
137
|
+
to the `--server` it was planned against and will not validate elsewhere.
|
|
138
|
+
- `--json` — without `--wait`, the raw 202 body (`status`, `metric_ids` or
|
|
139
|
+
`table_ids`, `message`) on stdout. With `--wait`, stdout is instead ONE final
|
|
140
|
+
summary object (`outcome`, `succeeded`, `completed_with_warnings`, `failed`,
|
|
141
|
+
`pending`, `waited_seconds`); either way the dropped-ids `warning:` moves to
|
|
142
|
+
**stderr** so stdout stays parseable.
|
|
143
|
+
- Rejections: 403 = `semantic_layer` org feature flag off (don't retry);
|
|
144
|
+
409 = org clone in progress (retry after it completes); 404 = none of the
|
|
145
|
+
ids are visible on this tenant.
|
|
146
|
+
|
|
147
|
+
## Migrate (`metrics migrate`)
|
|
148
|
+
|
|
149
|
+
Run the org-wide metrics + dimensions migration: backfill `category` /
|
|
150
|
+
`time_aggregation` / source-table ids on **every** metric in the org, and
|
|
151
|
+
convert V1 KPI dimension fields to V2 Dimension objects. Org-admin only.
|
|
152
|
+
Synchronous — the 200 response IS the result (nothing queued, nothing to
|
|
153
|
+
poll), with `metrics` and `dimensions` summary blocks. The backfill
|
|
154
|
+
**overwrites** `category`/`time_aggregation` with the name-derived mapping —
|
|
155
|
+
hand-set values included; `source_table_ids` fills only when empty. Per-metric
|
|
156
|
+
before→after is in `details[]` (`--json`), so a replaced value can be restored
|
|
157
|
+
with `metrics update --category` / `--time-aggregation`.
|
|
158
|
+
|
|
159
|
+
- `metrics migrate --dry-run` — free preview: the server computes the full
|
|
160
|
+
summary and rolls back every write. Runs immediately, no confirm. **Always
|
|
161
|
+
run this first.**
|
|
162
|
+
- `metrics migrate` — live run; two-step confirm, token-only (Tier B — no
|
|
163
|
+
prod word gate, unlike `delete`); the confirm token is bound to the
|
|
164
|
+
`--server` it was planned against. A transaction-level failure (the 500
|
|
165
|
+
below) rolls back every write — org unchanged. Per-metric errors are
|
|
166
|
+
non-fatal: rows that succeeded commit, and the 200 reports them in
|
|
167
|
+
`metrics.errors[]` (next bullet).
|
|
168
|
+
- Per-metric failures do NOT fail the request: a 200 can still carry
|
|
169
|
+
`metrics.errors[]`, which the CLI prints as a `warning:` block — check for
|
|
170
|
+
it before assuming every metric migrated. With `--json` the warning moves
|
|
171
|
+
to **stderr** so stdout stays parseable.
|
|
172
|
+
- `--json` — raw 200 body (`organization_id`, `dry_run`, `metrics`,
|
|
173
|
+
`dimensions`) including the per-metric/per-KPI `details` rows the text
|
|
174
|
+
summary omits.
|
|
175
|
+
- Rejections: 403 = not an org admin (switch to an admin identity; don't
|
|
176
|
+
retry as-is); 409 = org clone in progress (retry after it completes);
|
|
177
|
+
500 = migration failed and rolled back — org unchanged, reproduce with
|
|
178
|
+
`--dry-run` before retrying.
|
|
179
|
+
|
|
180
|
+
## Categories
|
|
181
|
+
|
|
182
|
+
- `metrics categories list`, `metrics categories get <id>`.
|
|
183
|
+
- `metrics categories create --name <s>` — color is server-assigned (not a CLI
|
|
184
|
+
input).
|
|
185
|
+
- `metrics categories update <id> --name <s>` — rename only; color not editable;
|
|
186
|
+
org-admin only (non-admin gets 403).
|
|
187
|
+
- `metrics categories delete <id>` — two-step confirm; metrics in the category
|
|
188
|
+
are un-categorized (not deleted); org-admin only.
|
|
189
|
+
|
|
190
|
+
## Dimensions
|
|
191
|
+
|
|
192
|
+
- `metrics dimensions list [--search <s>] [--template <id>] [--metric <id>]`,
|
|
193
|
+
`metrics dimensions get <id>`.
|
|
194
|
+
- `metrics dimensions create --name <s> --mapping <table_id>:<field_id>
|
|
195
|
+
[--mapping …] [--description <s>]` — ≥1 mapping; client-side dup-table check.
|
|
196
|
+
- `metrics dimensions update <id> [--name <s>] [--description <s>]
|
|
197
|
+
[--mapping <table>:<field> …] [--clear-mappings]` — `--mapping` replaces ALL
|
|
198
|
+
mappings; `--clear-mappings` wipes them; the two are mutually exclusive.
|
|
199
|
+
- `metrics dimensions delete <id>` — two-step confirm.
|
|
200
|
+
- `metrics dimensions preview <id>` — unique values across the mapped tables
|
|
201
|
+
(`--limit <n>`, `--json`).
|
|
202
|
+
|
|
203
|
+
## AI suggestions (`metrics suggestions`, `metrics dimensions suggestions`)
|
|
204
|
+
|
|
205
|
+
Agent-generated metric/dimension proposals — the CLI surface behind the FE's
|
|
206
|
+
"generate suggestions" buttons. Two parallel command groups with identical
|
|
207
|
+
shapes:
|
|
208
|
+
|
|
209
|
+
- `metrics suggestions list|refresh` — metric suggestions.
|
|
210
|
+
- `metrics dimensions suggestions list|refresh` — dimension suggestions.
|
|
211
|
+
- `metrics dimensions suggestions profile` — read-only candidate profiler.
|
|
212
|
+
Dimensions only, and the one command in this group that needs **no** Unleash
|
|
213
|
+
flag (see below).
|
|
214
|
+
|
|
215
|
+
Gating: each group needs its own **Unleash** flag enabled for the org
|
|
216
|
+
(`semantic_layer__ai_metric_suggestions` / `semantic_layer__ai_dimension_suggestions`)
|
|
217
|
+
AND an org-admin identity. A persistent 403 means one of the two is missing —
|
|
218
|
+
tenant configuration, not a CLI error.
|
|
219
|
+
|
|
220
|
+
- `… suggestions list [--json]` — active suggestions (id, name, kind for
|
|
221
|
+
metrics, created date, rationale) plus the **`last_run`** block: the newest
|
|
222
|
+
generation run's `id`, `status`, `trigger_source`, `updated_at`.
|
|
223
|
+
`last_run: none` = the agent has never run for this org. Statuses:
|
|
224
|
+
`PENDING`/`RUNNING` = live (at most one live run per org), `COMPLETED` /
|
|
225
|
+
`FAILED` / `NO_DATA` = terminal (`NO_DATA` = ran fine, produced nothing).
|
|
226
|
+
Dismissed rows and full payloads are in `--json` only.
|
|
227
|
+
- `… suggestions refresh [--wait] [--timeout <seconds>] [--json]` — trigger a
|
|
228
|
+
generation run. **202 = queued, not done**: the server debounces triggers
|
|
229
|
+
(15-minute window) and dispatches the agent with a ~5-minute countdown, so
|
|
230
|
+
results land minutes later — live runs measure 13-15 minutes end to end.
|
|
231
|
+
`--wait` polls `last_run` until terminal (default timeout 1200s, sized for
|
|
232
|
+
that measured pipeline) and **exits non-zero** on
|
|
233
|
+
`FAILED`/timeout/cancel; `NO_DATA` exits zero. `--json` without `--wait` is
|
|
234
|
+
the raw `{run_id}`; with `--wait`, ONE summary object (`outcome`, `run_id`,
|
|
235
|
+
`last_run`, `active_suggestions`, `dismissed_suggestions`,
|
|
236
|
+
`waited_seconds`). `outcome` is one of `completed`, `no_data`, `superseded`,
|
|
237
|
+
`failed`, `timed_out`, `cancelled` — `superseded` means a newer run started
|
|
238
|
+
before ours was observed terminal (ours must have finished; one live run per
|
|
239
|
+
org); it exits zero, check `list` for the current state.
|
|
240
|
+
- Rejections: 409 = a run is already PENDING/RUNNING for the org — **not a
|
|
241
|
+
failure**; check `list` instead of re-triggering. 403 = Unleash flag off or
|
|
242
|
+
not org-admin (see gating above).
|
|
243
|
+
- **Semantic-layer opening interplay**: a successful live `metrics migrate`
|
|
244
|
+
(and an org clone) auto-triggers BOTH agents, provided the Unleash flags
|
|
245
|
+
were already on at migration time. In an SL-opening flow: enable the flags
|
|
246
|
+
first, run the migration, then verify with `… suggestions list` — and expect
|
|
247
|
+
`refresh` to 409 while the auto-triggered run is live. If `last_run` is
|
|
248
|
+
`none` after a migration, the flags were off when it ran — enable them and
|
|
249
|
+
`refresh` manually.
|
|
250
|
+
|
|
251
|
+
### `metrics suggestions profile`
|
|
252
|
+
|
|
253
|
+
Profiles every table on the tenant and returns, per table, the facts a metric
|
|
254
|
+
needs before it can be proposed. Read-only — it never writes a suggestion.
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
dr metrics suggestions profile -s DEV --account support@acme.com --json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Run this before proposing any BASE metric.** Nothing in the templates payload
|
|
261
|
+
marks which date column is the reporting axis, so a caller working from the raw
|
|
262
|
+
payload falls back to "any column of type Date" — and a headcount table carries
|
|
263
|
+
nine of them. Bucketing headcount by hire date instead of reporting month passes
|
|
264
|
+
every validation the platform performs and the number is silently wrong. This
|
|
265
|
+
command answers the question mechanically instead.
|
|
266
|
+
|
|
267
|
+
- Needs only table read access — **not** the AI-suggestions Unleash flag.
|
|
268
|
+
- Measured on a 43-table org: **233 requests, ~32 s**, 2729 fields examined,
|
|
269
|
+
118 measures, 31 tables with an axis.
|
|
270
|
+
- Flags: `--json`, `--concurrency <n>` (1-64, default 6), `--tables <ids...>`,
|
|
271
|
+
`--sample-rows <n>` (1-1000, default 200), `--windows <n>` (1-10, default 3),
|
|
272
|
+
`--max-values <n>` (default 120), `--out <file>` (mode `0600`),
|
|
273
|
+
`--timeout <seconds>` (default 600).
|
|
274
|
+
|
|
275
|
+
#### The four things to read
|
|
276
|
+
|
|
277
|
+
- **`reporting_axis`** — the date column a BASE metric on this table should use
|
|
278
|
+
as `date_field`, with `chosen_by` recording which rule fired: `named` (the
|
|
279
|
+
platform's own reporting-column name — the strongest), `month_end_values`
|
|
280
|
+
(every value falls on a month boundary; a transaction date does not do that),
|
|
281
|
+
or `existing_metric` (what metrics on this table already use — weakest, but
|
|
282
|
+
it means somebody looked at this table once).
|
|
283
|
+
- **`null` is a real answer, not a gap in the run.** It means no rule fired,
|
|
284
|
+
so there is no defensible `date_field`. Do not substitute the first Date
|
|
285
|
+
column — confirm the right one with someone who knows the data. On the org
|
|
286
|
+
measured, 12 of 43 tables answered `null`.
|
|
287
|
+
- **`row_grain.reads_as`** — `movements` (many rows per entity per period; a
|
|
288
|
+
journal) means `time_aggregation: SUM`. `balances` (one row per entity per
|
|
289
|
+
period; a snapshot) means `EOP`. Summing snapshots multiplies the total by
|
|
290
|
+
the number of periods, which is the most common modelling error on this
|
|
291
|
+
platform.
|
|
292
|
+
- `unknown` carries `unknown_because`: `no_entity_field`, `no_reporting_axis`,
|
|
293
|
+
`no_row_count`, or `between_thresholds` (the ratio landed between the two
|
|
294
|
+
cutoffs, where both aggregations are defensible — that is a real finding,
|
|
295
|
+
not a failed measurement).
|
|
296
|
+
- Check `entity_chosen_by`. `named` means a real entity column.
|
|
297
|
+
`lowest_cardinality` is a fallback that picks the column with the fewest
|
|
298
|
+
values, which is sometimes a status or scenario flag rather than an entity —
|
|
299
|
+
discount `rows_per_entity_period` accordingly.
|
|
300
|
+
- **`measures[]`** — numeric columns that survived screening, each with a
|
|
301
|
+
`verdict` of `measure` or (rejected) `identifier_like` / `too_null`. A high
|
|
302
|
+
distinct ratio alone never condemns a column: a per-transaction amount is
|
|
303
|
+
near-unique by nature, so a key also has to be made of integers that look
|
|
304
|
+
generated.
|
|
305
|
+
- **`existing_metrics`** per table, plus an org-level `library` block
|
|
306
|
+
(`metrics_count`, `unhealthy`, `tables_with_metrics`). Saves a library sweep
|
|
307
|
+
of your own and answers "which tables are analytically load-bearing".
|
|
308
|
+
|
|
309
|
+
#### Every measure statistic is SAMPLED — read it as a screen
|
|
310
|
+
|
|
311
|
+
The sample endpoint returns rows in **storage order, not at random**. Measured
|
|
312
|
+
on a 333k-row table, a 200-row window held a single document id and a single
|
|
313
|
+
month. So:
|
|
314
|
+
|
|
315
|
+
- Every sampled figure is named `sampled_*`, and `summary.thresholds` records
|
|
316
|
+
the window count and size behind it.
|
|
317
|
+
- `sample_disagreement: true` means the windows disagreed about that column —
|
|
318
|
+
evidence the head of the table is not representative of the rest. Common, and
|
|
319
|
+
worth knowing.
|
|
320
|
+
- **There is deliberately no `sum` and no `avg`.** A total extrapolated from a
|
|
321
|
+
biased window is exactly the plausible-looking wrong number this command
|
|
322
|
+
exists to prevent. If you need an exact total for one field, ask for it
|
|
323
|
+
directly at the point you create the metric:
|
|
324
|
+
`dr templates aggregate <id> --group-by <dim> --metric "<field>:SUM"`.
|
|
325
|
+
- Note that endpoint honours **one metric per request** — extra `--metric`
|
|
326
|
+
flags are silently dropped, so ask one field at a time.
|
|
327
|
+
|
|
328
|
+
#### Reading the summary
|
|
329
|
+
|
|
330
|
+
`summary` distinguishes a full sweep from a thin one; read it before trusting a
|
|
331
|
+
short result. `requests_issued` and `elapsed_ms` say what the run cost.
|
|
332
|
+
`library_source: unavailable` means the metrics library could not be read — do
|
|
333
|
+
**not** read the empty `library` as "this org has no metrics". A table that the
|
|
334
|
+
tables API will not serve rejects every one of its fields as `profile_error`,
|
|
335
|
+
so one unserved table can account for most of that count; `warnings` names such
|
|
336
|
+
tables so the number is not misread as a widespread failure.
|
|
337
|
+
|
|
338
|
+
Rejection reasons: `system_field`, `hidden_field`, `scenario_field`,
|
|
339
|
+
`generated_field` (a date part — `System_Year`, `Reporting Year`, anything whose
|
|
340
|
+
name ends in a singular calendar unit), `unsupported_type`,
|
|
341
|
+
`field_name_unaddressable`, `profile_error`, `no_sample` (the table is empty),
|
|
342
|
+
`all_null`, `too_null`, `identifier_like`, `cardinality_too_high`.
|
|
343
|
+
|
|
344
|
+
#### What the caller still owns
|
|
345
|
+
|
|
346
|
+
Naming, descriptions, rationale, confidence, and whether a statistically valid
|
|
347
|
+
measure is a business-meaningful metric. This command reports structure, not
|
|
348
|
+
meaning.
|
|
349
|
+
|
|
350
|
+
### `metrics dimensions suggestions profile`
|
|
351
|
+
|
|
352
|
+
Profiles every table and field on the tenant and returns scored **candidate
|
|
353
|
+
dimension groups**. Read-only — it never writes a suggestion. Use it when you
|
|
354
|
+
need to reason about what the org's dimensions *should* be: it answers in one
|
|
355
|
+
call what would otherwise take one distinct-values call per field.
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
dr metrics dimensions suggestions profile -s DEV --account support@acme.com --json
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
- Needs only table read access — **not** the AI-suggestions Unleash flag. It
|
|
362
|
+
does read the dismissed-suggestion list, which is flag-gated; if that read
|
|
363
|
+
fails the command warns, reports `dismissed: []`, and carries on.
|
|
364
|
+
- Flags: `--json`, `--concurrency <n>` (1-64, default 6 — measured: the
|
|
365
|
+
finance-os gateway rate-limits a large sweep at 16, and *which* requests it
|
|
366
|
+
rejects varies per run, so a higher value trades determinism for ~10% wall
|
|
367
|
+
clock; transient rejections are retried three times regardless, and the run
|
|
368
|
+
warns when more than 10% of the fan-out needed a retry),
|
|
369
|
+
`--tables <ids...>` (comma- or space-separated; default every table),
|
|
370
|
+
`--max-values <n>` (cap values echoed per field, default 120; `0` = counts
|
|
371
|
+
only), `--out <file>` (written mode `0600` — the payload carries raw tenant
|
|
372
|
+
values from every profiled column), `--suppression <file>`,
|
|
373
|
+
`--timeout <seconds>` (default 600 — the whole sweep shares one budget).
|
|
374
|
+
- Output has four keys:
|
|
375
|
+
- `summary` — coverage (`tables_total` vs `tables_screened`,
|
|
376
|
+
`fields_examined` / `fields_eligible` / `fields_profiled`,
|
|
377
|
+
`profile_errors`, `table_errors`, `tables_without_row_count`),
|
|
378
|
+
`rejected_by_reason` counts, `thresholds` (every constant the run used),
|
|
379
|
+
`warnings`, `elapsed_ms`. **Read this before trusting an empty result** —
|
|
380
|
+
it distinguishes a full sweep from a thin one.
|
|
381
|
+
- `groups` — the candidates. Each has `members` (`table_id`, `table_name`,
|
|
382
|
+
`field_id`, `field_name`, `distinct_count`, `sample_values`,
|
|
383
|
+
`values_truncated`), `scope` (`cross_table` | `single_table`),
|
|
384
|
+
`pairwise` containment scores, `min_containment`, `weak_link`, and
|
|
385
|
+
`verdict`.
|
|
386
|
+
- `rejected` — every field that did not become a candidate, each with a
|
|
387
|
+
machine-readable `reason`. Runs to thousands of rows on a large org; use
|
|
388
|
+
`--out` when you only need `summary` + `groups`.
|
|
389
|
+
- `near_misses` — pairs that overlapped between 0.60 and 0.80: enough to be
|
|
390
|
+
worth a look, not enough to merge. Each carries both sides, the
|
|
391
|
+
`containment` and the shared-value count. **The 0.80 cutoff is a suggestion
|
|
392
|
+
to you, not a verdict** — a pair at 0.78 is often one dimension with a
|
|
393
|
+
coverage gap, and this is where it surfaces instead of vanishing. Pairs that
|
|
394
|
+
already merged are not repeated here; the group's own `pairwise` and
|
|
395
|
+
`weak_link` cover those.
|
|
396
|
+
- `dismissed` — names the operator already dismissed. Do not re-propose them.
|
|
397
|
+
- **`--suppression <file>`** supplies `{existing_dimensions, dismissed}` from the
|
|
398
|
+
caller instead of fetching them. Use it when you already hold them — an agent
|
|
399
|
+
receives both in its trigger message — because refetching creates a second
|
|
400
|
+
source of truth that disagrees the moment a dimension is created mid-run.
|
|
401
|
+
Either key wins on its own; the other is still fetched. Shape is tolerant:
|
|
402
|
+
the list may be keyed `existing_dimensions` or `dimensions`, mappings may be
|
|
403
|
+
`mappings: [{table_id, field_id}]` or `field_mappings: [{table, field}]`, and
|
|
404
|
+
dismissed entries may be `{name}` objects or bare strings. A malformed file is
|
|
405
|
+
an error, never a silent skip — a suppression list that quietly parsed to
|
|
406
|
+
nothing would report duplicates as new. `summary.suppression_source` records
|
|
407
|
+
where each list came from (`file` / `api` / `unavailable`).
|
|
408
|
+
- `verdict` decides what to do with a group: `new` = propose it;
|
|
409
|
+
`duplicate_of_existing` = the org already has a dimension covering exactly
|
|
410
|
+
these mappings, skip it; `extends_existing` = some members are already
|
|
411
|
+
mapped — **do not broaden an existing dimension from here**, skip it too.
|
|
412
|
+
- `weak_link: true` means the group was merged transitively (A–B and B–C both
|
|
413
|
+
cleared the bar, A–C did not). Split it or drop it rather than trusting it.
|
|
414
|
+
- Grouping is by **value overlap, never by name**: containment
|
|
415
|
+
`|A ∩ B| / min(|A|,|B|)` at 0.80 with an absolute intersection floor of 3,
|
|
416
|
+
filler values (`N/A`, `Other`, `Total`, `Unknown`, …) stripped first. So
|
|
417
|
+
`Region` / `Geo` / `Territory` group when their values agree, and two
|
|
418
|
+
columns both called `Code` do not when they don't.
|
|
419
|
+
- Rejection reasons: `system_field` (the platform's `category: ["system"]`
|
|
420
|
+
flag, or a known plumbing name), `hidden_field`, `scenario_field` (scenario
|
|
421
|
+
is already a first-class Datarails axis), `not_text` (text only, by product
|
|
422
|
+
decision), `cardinality_too_low` (< 2), `cardinality_too_high` (> 100),
|
|
423
|
+
`identifier_like` (fewer than 10 rows per distinct value),
|
|
424
|
+
`no_usable_values` (every value was filler, or none came back — so there is
|
|
425
|
+
no vocabulary to compare), `field_name_unaddressable` (the name contains
|
|
426
|
+
`/`, which the by-name distinct endpoint cannot address in a URL path
|
|
427
|
+
segment — no request is issued), `empty_unverified` (see below),
|
|
428
|
+
`profile_error` (that field's distinct call failed after retries, OR it
|
|
429
|
+
succeeded while returning values that a COUNT_UNIQUE aggregation
|
|
430
|
+
contradicts; the run continues), `no_distinct_count`, and `too_many_nulls`.
|
|
431
|
+
- **An empty distinct result is never taken at face value.** A genuinely empty
|
|
432
|
+
column and a silent read failure look identical, so a field that comes back
|
|
433
|
+
with zero values is re-checked with a `COUNT_UNIQUE` aggregation. Three
|
|
434
|
+
outcomes: the count agrees (`cardinality_too_low`), the count disagrees
|
|
435
|
+
(`profile_error` — the values read was wrong), or the check itself could not
|
|
436
|
+
be made (`empty_unverified`, counted in `summary.empty_unverified`). Treat
|
|
437
|
+
`empty_unverified` as unknown, not as empty: on one org 174 fields on a
|
|
438
|
+
single table all returned zero values, and the corroborating call revealed
|
|
439
|
+
the table was not served by the tables API at all.
|
|
440
|
+
- **Server-side truncation is not `--max-values`.** If the server ever holds
|
|
441
|
+
back part of a value list, the retained count is not the field's cardinality
|
|
442
|
+
— a field with thousands of values truncated to 80 would otherwise pass the
|
|
443
|
+
<= 100 test and be proposed as a dimension. Such fields are rejected as
|
|
444
|
+
`cardinality_too_high` without being measured, and counted in
|
|
445
|
+
`summary.api_truncated`. This is separate from `values_truncated`, which is
|
|
446
|
+
only ever about the `--max-values` cap. Note the distinct endpoint has not
|
|
447
|
+
been seen to truncate: it returned all 121,664 values of a high-cardinality
|
|
448
|
+
field. The endpoint that does hold back rows is `.../data`, which paginates
|
|
449
|
+
and reports `metadata.pagination.has_more`; this command reads it only for
|
|
450
|
+
row counts, where `total_count` is the figure that matters.
|
|
451
|
+
- `too_many_nulls` does not fire on this surface: nothing in the tables API
|
|
452
|
+
reports per-field null counts, so the screen has no input. `max_null_rate`
|
|
453
|
+
still appears in `summary.thresholds` because that block echoes every
|
|
454
|
+
constant the scorer holds — read it as the cutoff that *would* apply, not as
|
|
455
|
+
evidence that null-heavy columns were filtered out. Two tables in a row of a
|
|
456
|
+
mostly-empty column will both reach you as candidates.
|
|
457
|
+
- The identifier and null-rate screens also need a row count, and some tables
|
|
458
|
+
report none (`summary.tables_without_row_count`). On those tables a
|
|
459
|
+
high-cardinality column can survive that a counted table would reject.
|
|
460
|
+
- **What the caller still owns**: naming, descriptions, rationale, confidence,
|
|
461
|
+
and semantic judgment. The command reports structure, not meaning — it will
|
|
462
|
+
hand back a statistically valid but meaningless group (a two-value
|
|
463
|
+
`Heads`/`Tails` column). Deciding which groups are real dimensions, and what
|
|
464
|
+
to call them, is yours.
|
|
465
|
+
- A run with zero groups exits **0**. `rejected_by_reason` says why.
|
|
466
|
+
- **On repeat runs**: `groups` is byte-identical over unchanged data. That is
|
|
467
|
+
the guarantee to rely on — the candidate set does not move.
|
|
468
|
+
- The **request count** is not stable: when a table turns out to be
|
|
469
|
+
unreadable, its remaining fields are skipped, and how many get skipped
|
|
470
|
+
depends on what was already in flight. This never changes a verdict — a
|
|
471
|
+
skipped field and a failed one resolve the same way — so `elapsed_ms` and
|
|
472
|
+
the retry counts in `warnings` move while the answer does not.
|
|
473
|
+
- The **split between `empty_unverified` and `cardinality_too_low`** can move
|
|
474
|
+
by a few fields. Both are rejections, so `groups` is unaffected, but which
|
|
475
|
+
reason a zero-value field gets depends on whether its corroborating
|
|
476
|
+
`COUNT_UNIQUE` call completed. Retries make that mostly stable, not
|
|
477
|
+
perfectly: measured 188 and 221 unverified across builds on the same
|
|
478
|
+
51-table org. Read `summary.empty_unverified` as "at least this many
|
|
479
|
+
fields' emptiness is unproven", not as an exact census.
|
|
480
|
+
|
|
481
|
+
## Functions (DR.GET definitions)
|
|
482
|
+
|
|
483
|
+
- `functions list`, `functions get <id>` — `get` returns the **definition**,
|
|
484
|
+
**not** the live value. Live function values are computed in Excel via
|
|
485
|
+
`DR.GET(...)` through the add-in and are not exposed by this CLI.
|
|
486
|
+
- The core row is `id / name / agg / template / value_field`; below it `get`
|
|
487
|
+
prints a **FILTERS** section (one row per persisted filter: `field`, `name`,
|
|
488
|
+
`type`, `excluded`, `values`) and the **default agg date field** when set.
|
|
489
|
+
`--json` adds `filters`, `default_agg_datefield`, `default_agg_config`.
|
|
490
|
+
`list` stays compact (core columns only) — filters show on `get` alone.
|
|
491
|
+
- A **date/Report-Date filter's From/To lives inside that filter's `values`**
|
|
492
|
+
blob (owned by the add-in), shown verbatim as compact JSON — read it there
|
|
493
|
+
rather than asking the customer to re-check the UI.
|
|
494
|
+
- `functions create`, `functions update`, `functions delete` (two-step confirm).
|
|
495
|
+
- `functions set-filters <id> --file <json> [--confirm]` — **the only way to
|
|
496
|
+
author a function's filters** (`create`/`update` don't touch them). **FULL
|
|
497
|
+
REPLACE**: the server deletes every existing filter and recreates from the
|
|
498
|
+
file, so pass the complete desired set — an **empty array clears them all**.
|
|
499
|
+
`--file` is a JSON array (or `{"filters":[…]}`) of
|
|
500
|
+
`{field:<FlatTableField id>, values:[…], is_excluded?, show_in_graph?,
|
|
501
|
+
override_global_filter?}`; `name`/`type` are server-derived, don't send them.
|
|
502
|
+
For a **dimension** filter `values` is a plain list (`["EMEA","APAC"]`); a
|
|
503
|
+
**date** filter's `values` is the add-in's opaque From/To blob (copy it from a
|
|
504
|
+
`get` on a function that already has the filter you want). Dry-run unless
|
|
505
|
+
`--confirm`; internally a read-modify-write PUT that preserves the core fields
|
|
506
|
+
and calculated-value links.
|
|
507
|
+
|
|
508
|
+
## Customer-facing docs (KB)
|
|
509
|
+
|
|
510
|
+
Help Center → the **"Semantic Layer"** category. Find current articles with `dr kb search "metric KPI"` (or
|
|
511
|
+
browse `dr kb categories` / `dr kb sections`). Cite the article; don't
|
|
512
|
+
paraphrase from memory.
|