@oxy-hq/cli 0.7.0 → 0.9.0
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 +131 -8
- package/dist/main.mjs +4909 -3551
- package/dist/main.mjs.map +1 -1
- package/dist/{mcp-xxWL3_dz.mjs → mcp-Bdp0rX0l.mjs} +2 -2
- package/dist/{mcp-xxWL3_dz.mjs.map → mcp-Bdp0rX0l.mjs.map} +1 -1
- package/package.json +5 -5
- package/skills/oxy-agents/SKILL.md +318 -0
- package/skills/oxy-agents/references/quick-reference.md +222 -0
- package/skills/oxy-agents/templates/agentic-template.yml +141 -0
- package/skills/oxy-airway-pipelines/SKILL.md +223 -316
- package/skills/oxy-automations/SKILL.md +61 -15
- package/skills/oxy-cli/SKILL.md +37 -4
- package/skills/oxy-custom-apps/SKILL.md +193 -291
- package/skills/oxy-custom-apps/references/minimal-starter.md +63 -0
- package/skills/oxy-custom-apps/references/performance-and-usage.md +105 -0
- package/skills/oxy-custom-apps/references/reading-data.md +106 -0
- package/skills/oxy-custom-apps/references/sandbox-agent-loop.md +267 -0
- package/skills/oxy-custom-apps/references/verifying-in-the-browser.md +65 -0
- package/skills/oxy-data-apps/SKILL.md +362 -0
- package/skills/oxy-data-apps/references/examples.md +263 -0
- package/skills/oxy-data-apps/references/quick-reference.md +171 -0
- package/skills/oxy-data-apps/templates/dashboard-with-controls.app.yml +128 -0
- package/skills/oxy-data-apps/templates/simple-dashboard.app.yml +102 -0
- package/skills/oxy-data-architecture/SKILL.md +137 -152
- package/skills/oxy-evals/SKILL.md +362 -0
- package/skills/oxy-evals/references/diagnosis.md +243 -0
- package/skills/oxy-evals/references/results-json.md +145 -0
- package/skills/oxy-evals/references/writing-expected.md +112 -0
- package/skills/oxy-semantic-modeling/SKILL.md +239 -59
- package/skills/oxy-semantic-modeling/references/warehouse-casts.md +57 -0
- package/skills/oxy-semantic-modeling/templates/topic-template.yml +38 -0
- package/skills/oxy-semantic-modeling/templates/view-template.yml +119 -0
- package/template/.github/workflows/publish.yaml +15 -13
- package/template/.nvmrc +1 -1
- package/template/.oxyc-managed +5 -5
- package/template/README.md +21 -10
- package/template/package.json +3 -2
- package/template/pnpm-workspace.yaml +28 -17
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Data App quick reference
|
|
2
|
+
|
|
3
|
+
Field-by-field reference for `.app.yml`. The why and the traps live in
|
|
4
|
+
[SKILL.md](../SKILL.md); this file is for looking things up.
|
|
5
|
+
|
|
6
|
+
## Top level
|
|
7
|
+
|
|
8
|
+
| Key | Type | Required | Notes |
|
|
9
|
+
| --- | --- | --- | --- |
|
|
10
|
+
| `title` | string | no | Display name in listings. Unset → humanized filename. |
|
|
11
|
+
| `description` | string | no | Free text. |
|
|
12
|
+
| `published` | bool | no | Default `false` = hidden from the sidebar (still in the IDE). |
|
|
13
|
+
| `controls` | list | no | Top-level control declarations (items use `type:`). |
|
|
14
|
+
| `tasks` | list | **yes** (≥ 1) | Data producers. |
|
|
15
|
+
| `display` | list | **yes** (≥ 1) | Rendered blocks, top to bottom. |
|
|
16
|
+
|
|
17
|
+
No other key is accepted. Omit `name:` — the filename is the name, and
|
|
18
|
+
`oxyc validate` reports `name` as unknown.
|
|
19
|
+
|
|
20
|
+
## Task fields (all types)
|
|
21
|
+
|
|
22
|
+
| Key | Notes |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `name` | Unique in the app. Letter first; letters, digits, underscores. |
|
|
25
|
+
| `type` | `execute_sql`, `semantic_query` (use these); `workflow`, `formatter`, `conditional`, `loop_sequential`, `http_request`, `omni_query`, `looker_query` are accepted; `agent` and `airway` fail at run time. |
|
|
26
|
+
| `mode` | `client` (default) or `server`. Forced to `server` unless the task is inline `execute_sql` on a local DuckDB. |
|
|
27
|
+
| `cache` | Optional `{ enabled, path }`. Rarely needed in an app. |
|
|
28
|
+
|
|
29
|
+
### `execute_sql`
|
|
30
|
+
|
|
31
|
+
| Key | Required | Notes |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `database` | yes | A `config.yml` database name. Checked by `oxy validate`. |
|
|
34
|
+
| `sql_query` | one of | Inline SQL. Jinja: `{{ controls.x }}` and friends. |
|
|
35
|
+
| `sql_file` | one of | Workspace-relative `.sql` path. Rendered with the same Jinja; always server-side. |
|
|
36
|
+
| `variables` | no | Map of string → string, available to the SQL template. |
|
|
37
|
+
| `dry_run_limit` | no | ≥ 1. |
|
|
38
|
+
| `export` | no | `{ path, format: sql\|csv\|json\|txt\|docx }` — an automation feature; not needed in apps. |
|
|
39
|
+
|
|
40
|
+
Rows returned: at most 10,000.
|
|
41
|
+
|
|
42
|
+
### `semantic_query`
|
|
43
|
+
|
|
44
|
+
| Key | Notes |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `topic` | Topic name from a `.topic.yml`. |
|
|
47
|
+
| `measures` | `[view.measure, …]` |
|
|
48
|
+
| `dimensions` | `[view.dimension, …]` |
|
|
49
|
+
| `time_dimensions` | `[{ dimension: view.field, granularity: year\|quarter\|month\|week\|day\|hour\|minute\|second }]` |
|
|
50
|
+
| `filters` | `[{ field, op, … }]` — see below |
|
|
51
|
+
| `orders` | `[{ field, direction: asc\|desc }]` |
|
|
52
|
+
| `limit`, `offset` | integers |
|
|
53
|
+
| `variables` | map |
|
|
54
|
+
|
|
55
|
+
Filter ops:
|
|
56
|
+
|
|
57
|
+
| `op` | Extra keys |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `eq`, `neq`, `gt`, `gte`, `lt`, `lte` | `value` |
|
|
60
|
+
| `in`, `not_in` | `values: [...]` |
|
|
61
|
+
| `in_date_range`, `not_in_date_range` | `from`, `to` |
|
|
62
|
+
|
|
63
|
+
Output columns: `view__field`; with granularity, `view__field__<granularity>`.
|
|
64
|
+
Controls are **not** rendered into any `semantic_query` field.
|
|
65
|
+
|
|
66
|
+
### `workflow` (runs an automation)
|
|
67
|
+
|
|
68
|
+
| Key | Notes |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `src` | Path to an `.automation.yml` (legacy `.procedure.yml` still works). Don't point it at a `.workflow.yml` — that extension is no longer a recognised automation file. |
|
|
71
|
+
| `variables` | Map passed to the automation. |
|
|
72
|
+
|
|
73
|
+
Its result reaches the app as one text value, so displays cannot address its
|
|
74
|
+
inner steps.
|
|
75
|
+
|
|
76
|
+
## Displays
|
|
77
|
+
|
|
78
|
+
| `type` | Required | Optional |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| `markdown` | `content` | — |
|
|
81
|
+
| `table` | `data` | `title`, `formats: { column: currency\|percent\|number }` |
|
|
82
|
+
| `line_chart` | `data`, `x`, `y` | `title`, `series`, `x_axis_label`, `y_axis_label`, `y_format` |
|
|
83
|
+
| `bar_chart` | `data`, `x`, `y` | `title`, `series`, `y_format` |
|
|
84
|
+
| `pie_chart` | `data`, `name`, `value` | `title`, `value_format` |
|
|
85
|
+
| `row` | `children` (≥ 1 display) | `columns` (≥ 1; default = number of children) |
|
|
86
|
+
| `control` | `name`, `control_type` | `label`, `options` or `source`, `default` |
|
|
87
|
+
| `controls` | `items` (control objects using `type:`) | — |
|
|
88
|
+
|
|
89
|
+
Formats: `currency` (USD), `percent` (input already 0–100), `number`
|
|
90
|
+
(thousands separators). Unset → default numeric formatting.
|
|
91
|
+
|
|
92
|
+
`data:` takes a task name. `markdown` `content` is a template over task
|
|
93
|
+
results: `{{ task_name }}` inserts a text result; a table result renders as a
|
|
94
|
+
JSON code block.
|
|
95
|
+
|
|
96
|
+
## Controls
|
|
97
|
+
|
|
98
|
+
| Field | Required | Applies to | Notes |
|
|
99
|
+
| --- | --- | --- | --- |
|
|
100
|
+
| `name` | yes | all | Read as `{{ controls.<name> }}`. |
|
|
101
|
+
| `control_type` | yes (inline `- type: control`) | all | `select` \| `date` \| `toggle` |
|
|
102
|
+
| `type` | yes (top-level `controls:` and `controls.items`) | all | same values |
|
|
103
|
+
| `label` | no | all | Defaults to `name`. |
|
|
104
|
+
| `default` | no | all | May be Jinja, rendered once at load: `"{{ now(fmt='%Y-%m-%d') }}"`. |
|
|
105
|
+
| `options` | no | `select` | Static list; entries may be Jinja too. |
|
|
106
|
+
| `source` | no | `select` | Task name; its first column fills the dropdown. Use instead of `options`. |
|
|
107
|
+
|
|
108
|
+
Value types: `select` → string, `date` → `YYYY-MM-DD` string, `toggle` →
|
|
109
|
+
boolean. A viewer's empty value falls back to `default`; a value for an
|
|
110
|
+
undeclared control is dropped.
|
|
111
|
+
|
|
112
|
+
### Jinja the browser can run
|
|
113
|
+
|
|
114
|
+
| Form | Effect |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `{{ controls.x }}` | raw value (single quotes doubled) |
|
|
117
|
+
| `{{ controls.x \| sqlquote }}` | `'value'` with embedded quotes escaped |
|
|
118
|
+
| `{{ controls.x \| default('v') }}` | value, or `v` when unset |
|
|
119
|
+
| `{% if controls.x %}…{% endif %}` | include the body when truthy |
|
|
120
|
+
|
|
121
|
+
Anything else runs on the server instead (full Jinja, `sqlquote` included).
|
|
122
|
+
|
|
123
|
+
## SQL dialect cheatsheet
|
|
124
|
+
|
|
125
|
+
| Dialect | Month bucket | Std-dev | Notes |
|
|
126
|
+
| --- | --- | --- | --- |
|
|
127
|
+
| BigQuery | `DATE_TRUNC(col, MONTH)` | `STDDEV(col)` | backtick identifiers; `DATE_ADD(d, INTERVAL 1 DAY)` |
|
|
128
|
+
| Snowflake | `DATE_TRUNC('month', col)` | `STDDEV(col)` | `"col"` identifiers |
|
|
129
|
+
| Postgres | `DATE_TRUNC('month', col)` | `STDDEV(col)` | `x::date` works here only |
|
|
130
|
+
| DuckDB | `DATE_TRUNC('month', col)` | `STDDEV(col)` | `INTERVAL '1 day'` |
|
|
131
|
+
| ClickHouse | `toStartOfMonth(col)` | `stddevPop(col)` | functions are case-sensitive |
|
|
132
|
+
|
|
133
|
+
`CAST(x AS DATE)` is the portable cast.
|
|
134
|
+
|
|
135
|
+
## Profiling query
|
|
136
|
+
|
|
137
|
+
Run once per candidate table before choosing a trend or a ranking. Substitute
|
|
138
|
+
real expressions from the table or view — never guess column names.
|
|
139
|
+
|
|
140
|
+
```sql
|
|
141
|
+
SELECT
|
|
142
|
+
COUNT(*) AS row_count,
|
|
143
|
+
COUNT(DISTINCT <entity_expr>) AS entity_count,
|
|
144
|
+
MIN(<time_expr>) AS min_date,
|
|
145
|
+
MAX(<time_expr>) AS max_date,
|
|
146
|
+
COUNT(DISTINCT DATE_TRUNC('month', <time_expr>)) AS month_count,
|
|
147
|
+
MIN(<measure_expr>) AS min_val,
|
|
148
|
+
MAX(<measure_expr>) AS max_val,
|
|
149
|
+
STDDEV(<measure_expr>) AS measure_stddev
|
|
150
|
+
FROM <table>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Worth charting when `row_count ≥ 100`, `month_count ≥ 3` (for a trend),
|
|
154
|
+
`measure_stddev > 0` (not a flat line), and `entity_count` between 5 and 500
|
|
155
|
+
(for a top-N). If the query fails, simplify once (drop `STDDEV`, swap the date
|
|
156
|
+
function for the dialect's); if it fails again, skip that chart.
|
|
157
|
+
|
|
158
|
+
## Checklist
|
|
159
|
+
|
|
160
|
+
- [ ] `oxyc validate --file <app>` and `oxy validate --file <app>` pass
|
|
161
|
+
- [ ] No top-level `name:`; `published:` set as intended
|
|
162
|
+
- [ ] Task names unique; every display `data:` names a real task
|
|
163
|
+
- [ ] Chart columns match the task's output columns exactly (`view__field`,
|
|
164
|
+
`__<granularity>` for semantic queries)
|
|
165
|
+
- [ ] No IDs on axes or pie labels
|
|
166
|
+
- [ ] Inline `- type: control` blocks use `control_type:`; no controls inside a `row`
|
|
167
|
+
- [ ] Every `{{ controls.x }}` matches a declared control; every `source:` names a task
|
|
168
|
+
- [ ] Strings and dates go through `| sqlquote` with no extra quotes
|
|
169
|
+
- [ ] No `type: agent` or `type: airway` tasks; no `data: task.step` references
|
|
170
|
+
- [ ] Each task's SQL has been run once against the real database
|
|
171
|
+
- [ ] The app opens with every block populated, and each control re-runs its tasks
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Dashboard with interactive controls — copy, rename to <your_app>.app.yml, customize.
|
|
2
|
+
#
|
|
3
|
+
# Shows all three control kinds, a data-driven dropdown (`source:`), a row
|
|
4
|
+
# layout, and control values injected into SQL.
|
|
5
|
+
#
|
|
6
|
+
# As written, every task reads a CSV through a local DuckDB database, so the
|
|
7
|
+
# browser can re-run the SQL itself when a control changes (client mode).
|
|
8
|
+
# - `database: local` must be a DuckDB entry in your config.yml; `'sales.csv'`
|
|
9
|
+
# is a file in that database's dataset directory.
|
|
10
|
+
# - For a warehouse (Postgres, Snowflake, BigQuery, ClickHouse…), change
|
|
11
|
+
# `database:` and the FROM clauses. Those tasks run on the server
|
|
12
|
+
# automatically; adding `mode: server` makes that explicit.
|
|
13
|
+
# - Only {{ controls.x }}, {{ controls.x | sqlquote }},
|
|
14
|
+
# {{ controls.x | default('v') }} and {% if controls.x %}…{% endif %} run in the
|
|
15
|
+
# browser. Anything richer still works, on the server.
|
|
16
|
+
|
|
17
|
+
title: Sales Explorer
|
|
18
|
+
description: |
|
|
19
|
+
Sales filtered live by region, start date, and a holidays-only toggle.
|
|
20
|
+
Every chart and table re-runs when a control changes.
|
|
21
|
+
published: false # flip to true to show the app in the sidebar
|
|
22
|
+
|
|
23
|
+
tasks:
|
|
24
|
+
# Feeds the `region` dropdown: the first column becomes the option list.
|
|
25
|
+
# The 'All' sentinel row lets a viewer clear the filter.
|
|
26
|
+
- name: region_list
|
|
27
|
+
type: execute_sql
|
|
28
|
+
database: local
|
|
29
|
+
sql_query: |
|
|
30
|
+
SELECT region
|
|
31
|
+
FROM (
|
|
32
|
+
SELECT 'All' AS region, 0 AS sort_key
|
|
33
|
+
UNION ALL
|
|
34
|
+
SELECT DISTINCT region, 1 AS sort_key FROM 'sales.csv'
|
|
35
|
+
) AS options
|
|
36
|
+
ORDER BY sort_key, region
|
|
37
|
+
|
|
38
|
+
- name: revenue_by_category
|
|
39
|
+
type: execute_sql
|
|
40
|
+
database: local
|
|
41
|
+
sql_query: |
|
|
42
|
+
SELECT category, SUM(revenue) AS total_revenue
|
|
43
|
+
FROM 'sales.csv'
|
|
44
|
+
WHERE sale_date >= {{ controls.start_date | sqlquote }}
|
|
45
|
+
AND ({{ controls.region | sqlquote }} = 'All'
|
|
46
|
+
OR region = {{ controls.region | sqlquote }})
|
|
47
|
+
{% if controls.holidays_only %}AND period = 'Holiday'{% endif %}
|
|
48
|
+
GROUP BY category
|
|
49
|
+
ORDER BY total_revenue DESC
|
|
50
|
+
|
|
51
|
+
- name: revenue_by_region
|
|
52
|
+
type: execute_sql
|
|
53
|
+
database: local
|
|
54
|
+
sql_query: |
|
|
55
|
+
SELECT region, SUM(revenue) AS total_revenue
|
|
56
|
+
FROM 'sales.csv'
|
|
57
|
+
WHERE sale_date >= {{ controls.start_date | sqlquote }}
|
|
58
|
+
AND ({{ controls.region | sqlquote }} = 'All'
|
|
59
|
+
OR region = {{ controls.region | sqlquote }})
|
|
60
|
+
{% if controls.holidays_only %}AND period = 'Holiday'{% endif %}
|
|
61
|
+
GROUP BY region
|
|
62
|
+
ORDER BY total_revenue DESC
|
|
63
|
+
|
|
64
|
+
- name: sales_detail
|
|
65
|
+
type: execute_sql
|
|
66
|
+
database: local
|
|
67
|
+
sql_query: |
|
|
68
|
+
SELECT region, category, period, sale_date, revenue
|
|
69
|
+
FROM 'sales.csv'
|
|
70
|
+
WHERE sale_date >= {{ controls.start_date | sqlquote }}
|
|
71
|
+
AND ({{ controls.region | sqlquote }} = 'All'
|
|
72
|
+
OR region = {{ controls.region | sqlquote }})
|
|
73
|
+
{% if controls.holidays_only %}AND period = 'Holiday'{% endif %}
|
|
74
|
+
ORDER BY sale_date DESC
|
|
75
|
+
LIMIT 500
|
|
76
|
+
|
|
77
|
+
display:
|
|
78
|
+
# Controls bar: inline `- type: control` blocks at the top level of `display:`.
|
|
79
|
+
# The kind is `control_type:`, NOT `type:`. Never put a control inside a `row`.
|
|
80
|
+
- type: control
|
|
81
|
+
name: region
|
|
82
|
+
control_type: select
|
|
83
|
+
label: Region
|
|
84
|
+
source: region_list # data-driven dropdown (instead of static `options:`)
|
|
85
|
+
default: All
|
|
86
|
+
|
|
87
|
+
- type: control
|
|
88
|
+
name: start_date
|
|
89
|
+
control_type: date
|
|
90
|
+
label: From date
|
|
91
|
+
default: "2024-01-01"
|
|
92
|
+
|
|
93
|
+
- type: control
|
|
94
|
+
name: holidays_only
|
|
95
|
+
control_type: toggle
|
|
96
|
+
label: Holidays only
|
|
97
|
+
default: false
|
|
98
|
+
|
|
99
|
+
- type: markdown
|
|
100
|
+
content: |
|
|
101
|
+
# Sales Explorer
|
|
102
|
+
|
|
103
|
+
Filter by **region**, **start date**, and **holiday periods** with the
|
|
104
|
+
controls above. Charts and tables update automatically.
|
|
105
|
+
|
|
106
|
+
# Two charts side by side
|
|
107
|
+
- type: row
|
|
108
|
+
columns: 2
|
|
109
|
+
children:
|
|
110
|
+
- type: bar_chart
|
|
111
|
+
title: Revenue by category
|
|
112
|
+
data: revenue_by_category
|
|
113
|
+
x: category
|
|
114
|
+
y: total_revenue
|
|
115
|
+
y_format: currency
|
|
116
|
+
|
|
117
|
+
- type: pie_chart
|
|
118
|
+
title: Revenue by region
|
|
119
|
+
data: revenue_by_region
|
|
120
|
+
name: region
|
|
121
|
+
value: total_revenue
|
|
122
|
+
value_format: currency
|
|
123
|
+
|
|
124
|
+
- type: table
|
|
125
|
+
title: Filtered sales (latest 500)
|
|
126
|
+
data: sales_detail
|
|
127
|
+
formats:
|
|
128
|
+
revenue: currency
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Simple dashboard — copy, rename to <your_app>.app.yml, and customize.
|
|
2
|
+
#
|
|
3
|
+
# The app's name is its filename; there is no `name:` key.
|
|
4
|
+
# Customize:
|
|
5
|
+
# - `database:` must be a database name from your config.yml
|
|
6
|
+
# - replace `orders`, `order_date`, `category` and `amount` with your table and columns
|
|
7
|
+
# - the SQL below is Postgres / DuckDB / Snowflake dialect; for BigQuery use
|
|
8
|
+
# DATE_TRUNC(order_date, MONTH), for ClickHouse toStartOfMonth(order_date)
|
|
9
|
+
|
|
10
|
+
title: Orders Overview
|
|
11
|
+
description: |
|
|
12
|
+
Key order metrics, the monthly trend, and a breakdown by category.
|
|
13
|
+
published: false # flip to true to show the app in the sidebar
|
|
14
|
+
|
|
15
|
+
tasks:
|
|
16
|
+
# One row of headline numbers
|
|
17
|
+
- name: summary_metrics
|
|
18
|
+
type: execute_sql
|
|
19
|
+
database: warehouse
|
|
20
|
+
sql_query: |
|
|
21
|
+
SELECT
|
|
22
|
+
COUNT(*) AS order_count,
|
|
23
|
+
COUNT(DISTINCT category) AS category_count,
|
|
24
|
+
ROUND(SUM(amount), 2) AS total_amount,
|
|
25
|
+
ROUND(AVG(amount), 2) AS avg_amount
|
|
26
|
+
FROM orders
|
|
27
|
+
WHERE order_date >= '2024-01-01'
|
|
28
|
+
|
|
29
|
+
# Trend over time
|
|
30
|
+
- name: monthly_trend
|
|
31
|
+
type: execute_sql
|
|
32
|
+
database: warehouse
|
|
33
|
+
sql_query: |
|
|
34
|
+
SELECT
|
|
35
|
+
DATE_TRUNC('month', order_date) AS month,
|
|
36
|
+
COUNT(*) AS order_count,
|
|
37
|
+
ROUND(SUM(amount), 2) AS total_amount
|
|
38
|
+
FROM orders
|
|
39
|
+
WHERE order_date >= '2024-01-01'
|
|
40
|
+
GROUP BY 1
|
|
41
|
+
ORDER BY 1
|
|
42
|
+
|
|
43
|
+
# Breakdown by category
|
|
44
|
+
- name: by_category
|
|
45
|
+
type: execute_sql
|
|
46
|
+
database: warehouse
|
|
47
|
+
sql_query: |
|
|
48
|
+
SELECT
|
|
49
|
+
category,
|
|
50
|
+
COUNT(*) AS order_count,
|
|
51
|
+
ROUND(SUM(amount), 2) AS total_amount
|
|
52
|
+
FROM orders
|
|
53
|
+
WHERE order_date >= '2024-01-01'
|
|
54
|
+
GROUP BY category
|
|
55
|
+
ORDER BY total_amount DESC
|
|
56
|
+
|
|
57
|
+
display:
|
|
58
|
+
- type: markdown
|
|
59
|
+
content: |
|
|
60
|
+
# Orders Overview
|
|
61
|
+
|
|
62
|
+
Key metrics since January 2024.
|
|
63
|
+
|
|
64
|
+
- type: table
|
|
65
|
+
title: Summary
|
|
66
|
+
data: summary_metrics
|
|
67
|
+
formats:
|
|
68
|
+
total_amount: currency
|
|
69
|
+
avg_amount: currency
|
|
70
|
+
order_count: number
|
|
71
|
+
|
|
72
|
+
- type: line_chart
|
|
73
|
+
title: Monthly total
|
|
74
|
+
data: monthly_trend
|
|
75
|
+
x: month
|
|
76
|
+
y: total_amount
|
|
77
|
+
x_axis_label: Month
|
|
78
|
+
y_axis_label: Amount
|
|
79
|
+
y_format: currency
|
|
80
|
+
|
|
81
|
+
- type: row
|
|
82
|
+
columns: 2
|
|
83
|
+
children:
|
|
84
|
+
- type: bar_chart
|
|
85
|
+
title: By category
|
|
86
|
+
data: by_category
|
|
87
|
+
x: category
|
|
88
|
+
y: total_amount
|
|
89
|
+
y_format: currency
|
|
90
|
+
|
|
91
|
+
- type: pie_chart
|
|
92
|
+
title: Share by category
|
|
93
|
+
data: by_category
|
|
94
|
+
name: category
|
|
95
|
+
value: total_amount
|
|
96
|
+
value_format: currency
|
|
97
|
+
|
|
98
|
+
- type: table
|
|
99
|
+
title: Category details
|
|
100
|
+
data: by_category
|
|
101
|
+
formats:
|
|
102
|
+
total_amount: currency
|