@graphit/cli 0.1.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/dist/api/client.d.ts +16 -0
- package/dist/api/client.js +67 -0
- package/dist/api/client.js.map +1 -0
- package/dist/auth/credentials.d.ts +15 -0
- package/dist/auth/credentials.js +34 -0
- package/dist/auth/credentials.js.map +1 -0
- package/dist/auth/login.d.ts +2 -0
- package/dist/auth/login.js +229 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/token.d.ts +5 -0
- package/dist/auth/token.js +42 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +68 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/connector.d.ts +2 -0
- package/dist/commands/connector.js +97 -0
- package/dist/commands/connector.js.map +1 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +124 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/ds.d.ts +2 -0
- package/dist/commands/ds.js +53 -0
- package/dist/commands/ds.js.map +1 -0
- package/dist/commands/kb.d.ts +2 -0
- package/dist/commands/kb.js +259 -0
- package/dist/commands/kb.js.map +1 -0
- package/dist/commands/query.d.ts +2 -0
- package/dist/commands/query.js +61 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +173 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/output/format.d.ts +8 -0
- package/dist/output/format.js +30 -0
- package/dist/output/format.js.map +1 -0
- package/dist/output/json.d.ts +1 -0
- package/dist/output/json.js +4 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/table.d.ts +2 -0
- package/dist/output/table.js +17 -0
- package/dist/output/table.js.map +1 -0
- package/package.json +38 -0
- package/skill/SKILL.md +163 -0
- package/skill/cursor/graphit-chart-patterns.mdc +148 -0
- package/skill/cursor/graphit-chart-selection.mdc +89 -0
- package/skill/cursor/graphit-dashboard-planning.mdc +100 -0
- package/skill/cursor/graphit-domain-lenses.mdc +120 -0
- package/skill/cursor/graphit-kb-exploration.mdc +75 -0
- package/skill/cursor/graphit-sql-reference.mdc +127 -0
- package/skill/cursor/graphit-style.mdc +123 -0
- package/skill/graphit.mdc +135 -0
- package/skill/references/chart-patterns.md +142 -0
- package/skill/references/chart-selection.md +83 -0
- package/skill/references/dashboard-planning.md +94 -0
- package/skill/references/domain-lenses.md +114 -0
- package/skill/references/graphit-style.md +117 -0
- package/skill/references/kb-exploration.md +69 -0
- package/skill/references/sql-reference.md +121 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Domain Lenses
|
|
2
|
+
|
|
3
|
+
Consult when the user's data signals match a specific business domain. Each lens provides domain-specific metrics, chart types, and anti-patterns that make dashboards feel expert-built.
|
|
4
|
+
|
|
5
|
+
Detect the domain from column names and user intent. Apply universal planning rules first (see dashboard-planning.md), then layer domain-specific patterns.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Marketing & Attribution
|
|
10
|
+
|
|
11
|
+
**Signals:** utm_*, channel, creative_id, spend, cpi, impressions, clicks, roas, cac, ltv, attribution_*, skan_*, conversions, ctr.
|
|
12
|
+
|
|
13
|
+
**Key metrics:**
|
|
14
|
+
|
|
15
|
+
| Metric | Formula pattern | Chart |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Blended CAC / Paid CAC | spend / new_customers | bar/KPI, target + YoY |
|
|
18
|
+
| ROAS D7/D30/D90 | revenue_D0-N / spend | cumulative cohort line + 100% breakeven |
|
|
19
|
+
| MER | revenue / marketing_spend | KPI |
|
|
20
|
+
| LTV:CAC Ratio | LTV / CAC | bar, floor 3:1 |
|
|
21
|
+
| Payback Period | days to recoup CAC | cumulative line |
|
|
22
|
+
|
|
23
|
+
**Must-have charts:** Cumulative cohort ROAS curve with 100% breakeven line. Channel-mix sorted bar (never pie for 6+). Creative scorecard table. Attribution model comparison stacked bar.
|
|
24
|
+
|
|
25
|
+
**Anti-patterns:** Mismatched attribution windows across channels (7d vs 30d = 30% artifact). ROAS without payback context. Summing self-reported channel conversions without dedup (totals 110-180%). Spend without incrementality context. Creative table without refresh ratio.
|
|
26
|
+
|
|
27
|
+
**Clarify first:** Attribution model + window? ROAS gross or margin-adjusted?
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Financial / Executive
|
|
32
|
+
|
|
33
|
+
**Signals:** revenue, mrr, arr, cogs, gross_margin, opex, ebitda, budget, forecast, actual, variance, runway, burn, cash_balance, gl_account.
|
|
34
|
+
|
|
35
|
+
**Key metrics:**
|
|
36
|
+
|
|
37
|
+
| Metric | Formula pattern | Chart |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| P&L Cascade | Rev->COGS->GP->Opex->EBITDA->Net | waterfall |
|
|
40
|
+
| Variance $/% | Actual-Plan, sign-aware per line type | semantic-color bar |
|
|
41
|
+
| GM% by segment | (Rev-COGS)/Rev per product/cohort | line + small multiples |
|
|
42
|
+
| Burn Multiple | net_burn / net_new_ARR | KPI + trend |
|
|
43
|
+
| Runway Months | Cash / abs(Net Burn) | KPI color-banded |
|
|
44
|
+
| Rule of 40 | Growth% + EBITDA margin% | KPI + trend |
|
|
45
|
+
|
|
46
|
+
**Must-have charts:** Waterfall (P&L, variance bridge, ARR bridge). Bullet chart for KPI vs target. Plan vs Forecast vs Actual (3 series, never 2). Variance bars with semantic color (green=favorable, red=unfavorable regardless of direction).
|
|
47
|
+
|
|
48
|
+
**Anti-patterns:** Direction-based green/red instead of favorable/unfavorable. Blended GM hiding AI inference cost compression. YTD comparison in Jan/Dec (use TTM). Cash burn without runway. Revenue without ASC 606 distinction. "Three-line" missing Forecast (Plan vs Actual vs Forecast = 3).
|
|
49
|
+
|
|
50
|
+
**Clarify first:** Revenue = bookings, ARR, or GAAP recognized?
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Product / Growth
|
|
55
|
+
|
|
56
|
+
**Signals:** install_date, signup_at, cohort_*, retention_d*, dau, mau, activation_*, payer_*, funnel_step, session_*, ltv, arpu, arpdau.
|
|
57
|
+
|
|
58
|
+
**Key metrics:**
|
|
59
|
+
|
|
60
|
+
| Metric | Formula pattern | Chart |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| D1/D7/D30 Retention | active_dN / cohort_size | heatmap or overlay |
|
|
63
|
+
| Stickiness | DAU/MAU | line + ref bands |
|
|
64
|
+
| ARPDAU | revenue_day / DAU_day | line + 7d MA |
|
|
65
|
+
| Payer Conversion D7 | first_payers_d7 / cohort | cumulative line |
|
|
66
|
+
| Whale Share | sum(rev) top-X% / total_rev | Lorenz / decile bar |
|
|
67
|
+
| Activation Rate | reached_aha / signups | cohort heatmap |
|
|
68
|
+
|
|
69
|
+
**Must-have charts:** Cohort retention heatmap (X=days since signup, Y=cohort, color=retention%). Cumulative cohort overlay (max 6 series). Lorenz/decile bar for revenue concentration. FTUE funnel with per-cohort drift.
|
|
70
|
+
|
|
71
|
+
**Anti-patterns:** Period retention instead of cohort retention. Bare ARPU without distribution (whales break every mean). Mean on heavy-tailed metrics (use median + p90). LTV without cohort horizon day. Vanity cumulative tiles.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Operational Monitoring
|
|
76
|
+
|
|
77
|
+
**Signals:** latency, p50/p95/p99, error_rate, slo, uptime, mttr, throughput, oee, cycle_time, otif, fill_rate, stockout, deployment_frequency.
|
|
78
|
+
|
|
79
|
+
**Key metrics:**
|
|
80
|
+
|
|
81
|
+
| Metric | Formula pattern | Chart |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| Latency p50/p95/p99 | percentile buckets | multi-percentile line + SLO |
|
|
84
|
+
| Error Budget | 100% - (errors/total) rolling 28d | bullet chart |
|
|
85
|
+
| DORA Quartet | deploy freq, lead time, CFR, recovery | 4-tile KPI |
|
|
86
|
+
| OEE | Availability x Performance x Quality | KPI + 3-tile decomp |
|
|
87
|
+
| OTIF | on-time AND in-full | trend + decomp stacked bar |
|
|
88
|
+
|
|
89
|
+
**Must-have charts:** Multi-percentile line + SLO threshold band. Bullet chart (not gauge). Latency heatmap. Error sources Pareto.
|
|
90
|
+
|
|
91
|
+
**Anti-patterns:** Averages without percentiles. KPI without threshold band. Vanity uptime % (use error-budget framing). Aggregating across segments (global 0.4% hides a region at 8%).
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Sales / Pipeline
|
|
96
|
+
|
|
97
|
+
**Signals:** opp_id, stage, amount, close_date, forecast_category, mql, sql, arr, acv, tcv, quota, attainment, loss_reason, expansion_arr.
|
|
98
|
+
|
|
99
|
+
**Key metrics:**
|
|
100
|
+
|
|
101
|
+
| Metric | Formula pattern | Chart |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| Pipeline Coverage | open_pipe / quota_remaining | KPI, target=1/win_rate |
|
|
104
|
+
| Sales Velocity | (opps x win_rate x avg_acv) / cycle_days | KPI + trend |
|
|
105
|
+
| Win Rate by ACV | won / (won+lost) per band | sorted bar, min-N 30 |
|
|
106
|
+
| Quota Attainment | closed_won / quota | histogram (bimodal!) |
|
|
107
|
+
| Net New ARR | new + expansion - contraction - churn | waterfall |
|
|
108
|
+
| Forecast MAPE | mean(abs(actual-forecast)/actual) | line + target |
|
|
109
|
+
|
|
110
|
+
**Must-have charts:** Net New ARR waterfall (start + new + expansion - contraction - churn = end). Forecast convergence curve (13 weeks). Coverage curve by quarter-week. Win rate by ACV band.
|
|
111
|
+
|
|
112
|
+
**Anti-patterns:** Fixed 3x coverage instead of 1/win_rate per segment. ACV/ARR/TCV/bookings conflated. Quota attainment as bare mean (bimodal - use histogram). Stage-weighted pipe using CRM defaults (30-50% overstate). "No decision" lumped (40-60% of B2B - split it).
|
|
113
|
+
|
|
114
|
+
**Clarify first:** MQL/SQL definitions (same column = 13-40% conversion depending on org). Revenue = bookings vs ARR vs GAAP. Coverage target: derive from historical win rate per segment, not fixed multiple.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Graphit Visual Style
|
|
2
|
+
|
|
3
|
+
Consult when building the HTML dashboard. Covers design principles, the Graphit aesthetic, typography, color system, layout patterns, and all inline chart implementations.
|
|
4
|
+
|
|
5
|
+
## Design Principles
|
|
6
|
+
|
|
7
|
+
Graphit dashboards are clean, minimal, and data-forward:
|
|
8
|
+
- **Card on themed surface** - content cards on `var(--graphit-surface)` background
|
|
9
|
+
- **Generous whitespace** - 24px page padding, 16px card gaps, 24px card padding
|
|
10
|
+
- **Subtle depth** - `box-shadow: 0 1px 3px rgba(0,0,0,0.08)`, not heavy borders
|
|
11
|
+
- **Uppercase micro-labels** - 12px, 600 weight, `var(--graphit-fg-subtle)`, letter-spacing 0.05em for section headers
|
|
12
|
+
- **Data is hero** - numbers are large and bold; chrome is minimal and muted
|
|
13
|
+
- **Responsive** - grids collapse at 900px breakpoint
|
|
14
|
+
|
|
15
|
+
## Color Tokens
|
|
16
|
+
|
|
17
|
+
NEVER use hardcoded hex colors. Use CSS custom properties that automatically adapt to light/dark theme:
|
|
18
|
+
|
|
19
|
+
| Token | Usage |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `var(--graphit-surface)` | Page background |
|
|
22
|
+
| `var(--graphit-surface-raised)` | Cards, panels |
|
|
23
|
+
| `var(--graphit-surface-sunken)` | Inset areas, wells |
|
|
24
|
+
| `var(--graphit-fg)` | Primary text |
|
|
25
|
+
| `var(--graphit-fg-muted)` | Secondary text |
|
|
26
|
+
| `var(--graphit-fg-subtle)` | Tertiary text, placeholders, labels |
|
|
27
|
+
| `var(--graphit-border)` | Default borders |
|
|
28
|
+
| `var(--graphit-border-strong)` | Emphasized borders |
|
|
29
|
+
| `var(--graphit-accent)` | Brand teal, primary action color |
|
|
30
|
+
| `var(--graphit-error)` | Error/danger text |
|
|
31
|
+
| `var(--graphit-success)` | Success indicators |
|
|
32
|
+
| `var(--graphit-warning)` | Warning indicators |
|
|
33
|
+
| `var(--graphit-highlight)` | Highlighted/selected backgrounds |
|
|
34
|
+
|
|
35
|
+
### Semantic Colors
|
|
36
|
+
- **Positive / good:** `var(--graphit-accent)` (teal) or `var(--graphit-success)`
|
|
37
|
+
- **Warning:** `var(--graphit-warning)`
|
|
38
|
+
- **Danger / bad:** `var(--graphit-error)`
|
|
39
|
+
|
|
40
|
+
### Multi-Series
|
|
41
|
+
Use `var(--graphit-accent)` as the primary series. For additional series, choose muted complementary colors that work on both light and dark backgrounds.
|
|
42
|
+
|
|
43
|
+
### Usage Rules
|
|
44
|
+
- Accent teal for primary bars, lines, and positive indicators
|
|
45
|
+
- Assign series colors consistently across charts sharing a dimension
|
|
46
|
+
- Financial variance: green = favorable, red = unfavorable (regardless of sign direction)
|
|
47
|
+
|
|
48
|
+
## Typography Scale
|
|
49
|
+
|
|
50
|
+
Use the system font stack everywhere: `-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif`
|
|
51
|
+
|
|
52
|
+
| Element | Size | Weight | Color | Use |
|
|
53
|
+
|---|---|---|---|---|
|
|
54
|
+
| KPI number | 32px | 700 | `var(--graphit-fg)` | Primary metric value |
|
|
55
|
+
| Card title / section header | 12px | 600 | `var(--graphit-fg-subtle)` | Uppercase, letter-spaced |
|
|
56
|
+
| Body text / table cells | 14px | 400 | `var(--graphit-fg)` | Default content |
|
|
57
|
+
| Labels / subtitles | 13px | 500 | `var(--graphit-fg-subtle)` | Supporting text |
|
|
58
|
+
| Badges / deltas | 12px | 700 | contextual | Change indicators |
|
|
59
|
+
| Chart axis labels | 11px | 400 | `var(--graphit-fg-subtle)` | Axis ticks |
|
|
60
|
+
|
|
61
|
+
## Layout Patterns
|
|
62
|
+
|
|
63
|
+
### Page Structure
|
|
64
|
+
```html
|
|
65
|
+
<style>
|
|
66
|
+
* { margin:0; padding:0; box-sizing:border-box; }
|
|
67
|
+
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;
|
|
68
|
+
background:var(--graphit-surface); color:var(--graphit-fg); padding:24px; }
|
|
69
|
+
.kpi-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:24px; }
|
|
70
|
+
.charts-grid { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:24px; }
|
|
71
|
+
.full-width { grid-column:1/-1; }
|
|
72
|
+
.card { background:var(--graphit-surface-raised); border-radius:12px; padding:24px;
|
|
73
|
+
box-shadow:0 1px 3px rgba(0,0,0,0.08); }
|
|
74
|
+
.card h3 { font-size:12px; font-weight:600; color:var(--graphit-fg-subtle); text-transform:uppercase;
|
|
75
|
+
letter-spacing:0.05em; margin-bottom:16px; }
|
|
76
|
+
@media(max-width:900px) {
|
|
77
|
+
.kpi-grid { grid-template-columns:repeat(2,1fr); }
|
|
78
|
+
.charts-grid { grid-template-columns:1fr; }
|
|
79
|
+
}
|
|
80
|
+
</style>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Dashboard Composition (top to bottom)
|
|
84
|
+
1. **Title row** - dashboard name, optional subtitle with date range
|
|
85
|
+
2. **KPI row** - 3-4 cards in a grid. Each: big number, label, delta badge
|
|
86
|
+
3. **Primary charts** - 2-column grid of detail visualizations
|
|
87
|
+
4. **Detail table** - full-width card at bottom for raw data exploration
|
|
88
|
+
|
|
89
|
+
### KPI Card
|
|
90
|
+
```html
|
|
91
|
+
<div class="card">
|
|
92
|
+
<div style="font-size:32px;font-weight:700;line-height:1.2">$144,661</div>
|
|
93
|
+
<div style="font-size:13px;color:var(--graphit-fg-subtle);margin-top:4px">Total Ad Spend</div>
|
|
94
|
+
<div style="font-size:12px;margin-top:8px;font-weight:600;color:var(--graphit-accent)">+12% vs prior</div>
|
|
95
|
+
</div>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Data Table
|
|
99
|
+
```html
|
|
100
|
+
<style>
|
|
101
|
+
table { width:100%; border-collapse:collapse; }
|
|
102
|
+
th { text-align:left; font-size:12px; font-weight:600; color:var(--graphit-fg-subtle); text-transform:uppercase;
|
|
103
|
+
letter-spacing:0.05em; padding:10px 16px; border-bottom:2px solid var(--graphit-border); }
|
|
104
|
+
th:not(:first-child) { text-align:right; }
|
|
105
|
+
td { padding:12px 16px; font-size:14px; border-bottom:1px solid var(--graphit-border); }
|
|
106
|
+
td:not(:first-child) { text-align:right; }
|
|
107
|
+
tr:hover td { background:var(--graphit-surface-sunken); }
|
|
108
|
+
.badge { display:inline-block; padding:2px 8px; border-radius:12px; font-size:12px; font-weight:700; }
|
|
109
|
+
.badge-good { background:var(--graphit-highlight); color:var(--graphit-success); }
|
|
110
|
+
.badge-warn { background:var(--graphit-highlight); color:var(--graphit-warning); }
|
|
111
|
+
.badge-bad { background:var(--graphit-highlight); color:var(--graphit-error); }
|
|
112
|
+
</style>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
For inline chart implementations (bar, line, donut, heatmap, funnel, sparkline, gauge, stacked bar, multi-series), see `chart-patterns.md`.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# KB Exploration
|
|
2
|
+
|
|
3
|
+
Consult when starting a dashboard build. The Knowledge Base contains reusable metrics, dimensions, rules, and table schemas that ensure consistent formulas across dashboards.
|
|
4
|
+
|
|
5
|
+
## KB-First Discovery
|
|
6
|
+
|
|
7
|
+
Before writing raw SQL with inline aggregations or column references:
|
|
8
|
+
|
|
9
|
+
1. List what exists: `graphit kb list metrics`, `graphit kb list dimensions`, `graphit kb list rules`
|
|
10
|
+
2. If a KB metric matches the user's request, use its formula: `graphit kb get metric REVENUE`
|
|
11
|
+
3. If no match exists, consider whether to build the dashboard from raw columns or suggest KB asset creation first
|
|
12
|
+
4. Explore relationships: `graphit kb explore metric REVENUE` shows which tables and dimensions connect to a metric
|
|
13
|
+
|
|
14
|
+
KB assets ensure consistent formulas across dashboards. If 3 charts all need `SUM(ORDERS.AMOUNT)`, one TOTAL_REVENUE metric is better than 3 inline aggregations.
|
|
15
|
+
|
|
16
|
+
## Metric vs Dimension
|
|
17
|
+
|
|
18
|
+
| Property | Metric | Dimension |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Formula | Aggregation required (SUM, COUNT, AVG, MIN, MAX) | Row-level only (no aggregates) |
|
|
21
|
+
| Table scope | Can reference multiple tables | Exactly one table |
|
|
22
|
+
| Purpose | Measures - what you count/sum | Grouping axes - how you slice |
|
|
23
|
+
| Example | `SUM(ORDERS.AMOUNT)` | `DATE_TRUNC('month', EVENTS.EVENT_TS)` |
|
|
24
|
+
| Invalid | `ORDERS.AMOUNT` (no aggregate) | `SUM(EVENTS.DURATION)` (has aggregate) |
|
|
25
|
+
|
|
26
|
+
## Naming Conventions
|
|
27
|
+
|
|
28
|
+
All KB assets use UPPER_SNAKE_CASE. The names are auto-sanitized:
|
|
29
|
+
|
|
30
|
+
| Pattern | Example | Use when |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `TOTAL_*` | `TOTAL_REVENUE`, `TOTAL_ORDERS` | Sum aggregations |
|
|
33
|
+
| `AVG_*` | `AVG_ORDER_VALUE` | Average metrics |
|
|
34
|
+
| `COUNT_*` | `COUNT_ACTIVE_USERS` | Count metrics |
|
|
35
|
+
| `*_RATE` | `CONVERSION_RATE`, `CHURN_RATE` | Ratios/percentages |
|
|
36
|
+
|
|
37
|
+
## Reuse Over Reinvention
|
|
38
|
+
|
|
39
|
+
When building multi-chart dashboards, identify shared concepts:
|
|
40
|
+
- If 3 charts use `SUM(ORDERS.AMOUNT)`, reference one TOTAL_REVENUE metric
|
|
41
|
+
- If 2 charts group by `DATE_TRUNC('month', TS)`, reference one MONTHLY dimension
|
|
42
|
+
- Discover existing assets: `graphit kb search "revenue"`
|
|
43
|
+
|
|
44
|
+
## When to Suggest KB Asset Creation
|
|
45
|
+
|
|
46
|
+
| Signal | Propose |
|
|
47
|
+
|---|---|
|
|
48
|
+
| User requests a business metric with no KB match | Metric - reusable formula |
|
|
49
|
+
| User groups by a derived expression | Dimension - consistent grouping |
|
|
50
|
+
| User describes a business rule ("active = logged in within 30d") | Rule - applied to future queries |
|
|
51
|
+
| User uses a business term not in KB | Synonym - maps colloquial to defined |
|
|
52
|
+
|
|
53
|
+
## Formula Syntax
|
|
54
|
+
|
|
55
|
+
Metrics require `TABLE.COLUMN` references with UPPERCASE naming:
|
|
56
|
+
|
|
57
|
+
```sql
|
|
58
|
+
-- Valid metric formulas
|
|
59
|
+
SUM(ORDERS.AMOUNT)
|
|
60
|
+
COUNT(DISTINCT EVENTS.USER_ID) WHERE EVENTS.EVENT_TS >= DATEADD(day, -30, CURRENT_DATE)
|
|
61
|
+
SUM(ORDERS.REVENUE) / NULLIF(SUM(ORDERS.COST), 0)
|
|
62
|
+
|
|
63
|
+
-- Valid dimension formulas (no aggregates)
|
|
64
|
+
EVENTS.PLATFORM
|
|
65
|
+
DATE_TRUNC('month', EVENTS.EVENT_TS)
|
|
66
|
+
CASE WHEN USERS.AGE >= 18 THEN 'adult' ELSE 'minor' END
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Conditionals use CASE WHEN (not FILTER WHERE - Snowflake doesn't support it). Always guard division with NULLIF.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# SQL Reference
|
|
2
|
+
|
|
3
|
+
Consult when writing queries. Data source queries (`graphit query --ds`) run DuckDB. Warehouse queries (`graphit query --warehouse`) run Snowflake. You MUST use the correct dialect.
|
|
4
|
+
|
|
5
|
+
## DuckDB vs Snowflake Translation
|
|
6
|
+
|
|
7
|
+
| Snowflake | DuckDB equivalent |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `DATEADD(day, N, date)` | `date + INTERVAL N DAY` |
|
|
10
|
+
| `DATEDIFF(day, a, b)` | `DATE_DIFF('day', a, b)` |
|
|
11
|
+
| `NVL(a, b)` | `COALESCE(a, b)` |
|
|
12
|
+
| `NVL2(a, b, c)` | `CASE WHEN a IS NOT NULL THEN b ELSE c END` |
|
|
13
|
+
| `IFF(cond, a, b)` | `CASE WHEN cond THEN a ELSE b END` |
|
|
14
|
+
| `TO_CHAR(date, fmt)` | `strftime(date, fmt)` |
|
|
15
|
+
| `ARRAY_AGG(x) WITHIN GROUP (ORDER BY y)` | `LIST(x ORDER BY y)` |
|
|
16
|
+
| `LISTAGG(col, sep)` | `STRING_AGG(col, sep ORDER BY ...)` |
|
|
17
|
+
| `COUNT_IF(cond)` | `COUNT(*) FILTER (WHERE cond)` |
|
|
18
|
+
| `GENERATOR(ROWCOUNT => N)` | `generate_series(0, N-1)` |
|
|
19
|
+
| `CONVERT_TIMEZONE('tz', ts)` | `timezone('tz', ts)` |
|
|
20
|
+
|
|
21
|
+
### JSON / VARIANT Access
|
|
22
|
+
|
|
23
|
+
| Engine | Syntax | Example |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Snowflake | Colon notation | `col:field::STRING`, `col:parent:child::NUMBER` |
|
|
26
|
+
| DuckDB | Arrow operators | `col->>'field'` (text), `col->'field'` (JSON) |
|
|
27
|
+
|
|
28
|
+
NEVER use `->>` in Snowflake or `:field::STRING` in DuckDB.
|
|
29
|
+
|
|
30
|
+
### Timezone
|
|
31
|
+
|
|
32
|
+
| Engine | Correct | Wrong |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| DuckDB | `timezone('Asia/Jerusalem', ts_col)` | `AT TIME ZONE` chains (reverses direction on TIMESTAMPTZ) |
|
|
35
|
+
| Snowflake | `CONVERT_TIMEZONE('Asia/Jerusalem', ts_col)` (2-arg for _TZ) | `AT TIME ZONE` |
|
|
36
|
+
|
|
37
|
+
## DuckDB Superpowers (not in Snowflake)
|
|
38
|
+
|
|
39
|
+
| Feature | Example |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `GROUP BY ALL` | `SELECT region, SUM(sales) FROM t GROUP BY ALL` |
|
|
42
|
+
| `SELECT * EXCLUDE` | `SELECT * EXCLUDE (internal_id) FROM t` |
|
|
43
|
+
| `FILTER (WHERE)` | `COUNT(*) FILTER (WHERE status='active')` |
|
|
44
|
+
| `UNION BY NAME` | `SELECT ... UNION ALL BY NAME SELECT ...` |
|
|
45
|
+
|
|
46
|
+
## Snowflake Notes
|
|
47
|
+
|
|
48
|
+
- Use `DATE_TRUNC('month', date)` for grouping (not EXTRACT/MONTH/YEAR)
|
|
49
|
+
- Snowflake does NOT support `FILTER (WHERE)` - use `CASE WHEN` instead
|
|
50
|
+
- `COUNT_IF` MUST receive a boolean expression, not a raw INT column. Use `COUNT_IF(is_active = 1)`, not `COUNT_IF(is_active)`
|
|
51
|
+
- String matching: prefer `ILIKE` (case-insensitive) over `LIKE`
|
|
52
|
+
|
|
53
|
+
## SQL Formatting Standards (Both Engines)
|
|
54
|
+
|
|
55
|
+
- Keywords UPPERCASE: `SELECT`, `FROM`, `WHERE`, `JOIN`, `GROUP BY`, `ORDER BY`
|
|
56
|
+
- Table/column names UPPERCASE: `ORDERS`, `CUSTOMER_ID`, `TOTAL_REVENUE`
|
|
57
|
+
- Qualify every column with its table alias: `o.ORDER_DATE`, not bare `ORDER_DATE`
|
|
58
|
+
- Use descriptive aliases: `total_revenue`, not `sum1`
|
|
59
|
+
- String literals in single quotes: `'active'`, `'2024-01-01'`
|
|
60
|
+
- Non-ASCII identifiers MUST be double-quoted: `SELECT * FROM "hebrew_table"`
|
|
61
|
+
|
|
62
|
+
## CTE Pattern
|
|
63
|
+
|
|
64
|
+
Use CTEs for queries with 3+ JOINs or complex subqueries:
|
|
65
|
+
|
|
66
|
+
```sql
|
|
67
|
+
WITH MONTHLY_ORDERS AS (
|
|
68
|
+
SELECT o.CUSTOMER_ID, DATE_TRUNC('month', o.ORDER_DATE) AS MONTH,
|
|
69
|
+
SUM(o.AMOUNT) AS MONTHLY_REVENUE
|
|
70
|
+
FROM ORDERS o WHERE o.STATUS = 'complete'
|
|
71
|
+
GROUP BY o.CUSTOMER_ID, DATE_TRUNC('month', o.ORDER_DATE)
|
|
72
|
+
)
|
|
73
|
+
SELECT c.SEGMENT, AVG(mo.MONTHLY_REVENUE) AS AVG_MONTHLY_REVENUE
|
|
74
|
+
FROM MONTHLY_ORDERS mo JOIN CUSTOMERS c ON mo.CUSTOMER_ID = c.ID
|
|
75
|
+
GROUP BY c.SEGMENT ORDER BY AVG_MONTHLY_REVENUE DESC
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## ORDER BY Rules (Always Include)
|
|
79
|
+
|
|
80
|
+
| Query type | ORDER BY |
|
|
81
|
+
|---|---|
|
|
82
|
+
| Time series | `ORDER BY date_column ASC` |
|
|
83
|
+
| Category breakdowns | `ORDER BY metric_column DESC` |
|
|
84
|
+
| Rankings / top N | `ORDER BY metric_column DESC LIMIT N` |
|
|
85
|
+
|
|
86
|
+
Do NOT add LIMIT unless the user requests it or the query is a ranking.
|
|
87
|
+
|
|
88
|
+
## Gap-Filling Pattern (DuckDB)
|
|
89
|
+
|
|
90
|
+
For heatmaps or continuous time-series needing every cell (even zeros):
|
|
91
|
+
|
|
92
|
+
```sql
|
|
93
|
+
WITH grid AS (
|
|
94
|
+
SELECT UNNEST(generate_series(0, 23)) AS hour_of_day
|
|
95
|
+
)
|
|
96
|
+
SELECT g.hour_of_day, COALESCE(t.count, 0) AS count
|
|
97
|
+
FROM grid g
|
|
98
|
+
LEFT JOIN (SELECT hour, COUNT(*) AS count FROM data_source GROUP BY 1) t
|
|
99
|
+
ON g.hour_of_day = t.hour
|
|
100
|
+
ORDER BY 1
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Date series:
|
|
104
|
+
```sql
|
|
105
|
+
SELECT UNNEST(generate_series(
|
|
106
|
+
CURRENT_DATE - INTERVAL 30 DAY, CURRENT_DATE, INTERVAL 1 DAY
|
|
107
|
+
))::DATE AS date
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Subquery Column Scope
|
|
111
|
+
|
|
112
|
+
The outer SELECT can ONLY reference columns the subquery exposes (its aliases). Base-table columns aggregated away in the subquery do NOT exist at the outer level.
|
|
113
|
+
|
|
114
|
+
## Data Source Routing
|
|
115
|
+
|
|
116
|
+
| Situation | Command | Speed |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| Table has a cached data source | `graphit query "SQL" --ds <id>` | ~100ms, DuckDB |
|
|
119
|
+
| No data source | `graphit query "SQL" --warehouse --connection <id>` | ~10s, Snowflake |
|
|
120
|
+
|
|
121
|
+
Always prefer cached data sources. Check with `graphit ds list`. If no data source covers the table, suggest creating one for future speed.
|