@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,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: false
|
|
3
|
+
description: "Graphit: Inline CSS/SVG/canvas implementations for dashboards. No external libraries - the iframe CSP blocks all external resources."
|
|
4
|
+
globs: []
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Chart Patterns
|
|
8
|
+
|
|
9
|
+
Inline CSS/SVG/canvas implementations for dashboards. No external libraries - the iframe CSP blocks all external resources.
|
|
10
|
+
|
|
11
|
+
## CSS Horizontal Bar
|
|
12
|
+
```js
|
|
13
|
+
var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
|
|
14
|
+
data.forEach(function(d) {
|
|
15
|
+
var pct = (d.value / maxVal * 100).toFixed(1);
|
|
16
|
+
container.innerHTML += '<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">'
|
|
17
|
+
+ '<span style="width:100px;font-size:13px;font-weight:500;text-align:right">' + d.name + '</span>'
|
|
18
|
+
+ '<div style="flex:1;height:28px;background:var(--graphit-surface-sunken);border-radius:6px;overflow:hidden">'
|
|
19
|
+
+ '<div style="width:' + pct + '%;height:100%;background:var(--graphit-accent);border-radius:6px;min-width:2px"></div></div>'
|
|
20
|
+
+ '<span style="width:70px;font-size:13px;font-weight:600;text-align:right">' + fmt(d.value) + '</span></div>';
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## CSS Vertical Bar (Column)
|
|
25
|
+
```js
|
|
26
|
+
var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
|
|
27
|
+
container.style.cssText = 'display:flex;align-items:flex-end;gap:8px;height:200px;padding-top:20px';
|
|
28
|
+
data.forEach(function(d) {
|
|
29
|
+
var pct = (d.value / maxVal * 100).toFixed(1);
|
|
30
|
+
container.innerHTML += '<div style="flex:1;height:' + pct + '%;background:var(--graphit-accent);border-radius:4px 4px 0 0;'
|
|
31
|
+
+ 'position:relative;min-width:30px">'
|
|
32
|
+
+ '<span style="position:absolute;top:-20px;left:50%;transform:translateX(-50%);font-size:12px;font-weight:600">'
|
|
33
|
+
+ d.value + '</span>'
|
|
34
|
+
+ '<span style="position:absolute;bottom:-22px;left:50%;transform:translateX(-50%);font-size:11px;color:var(--graphit-fg-subtle);white-space:nowrap">'
|
|
35
|
+
+ d.name + '</span></div>';
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## CSS Stacked Bar
|
|
40
|
+
```js
|
|
41
|
+
var colors = ['var(--graphit-accent)','#80CBC4','#26A69A','#5FA39B'];
|
|
42
|
+
data.forEach(function(row) {
|
|
43
|
+
var total = row.segments.reduce(function(a,s){return a+s.value},0);
|
|
44
|
+
var html = '<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">'
|
|
45
|
+
+ '<span style="width:100px;font-size:13px;font-weight:500;text-align:right">' + row.name + '</span>'
|
|
46
|
+
+ '<div style="flex:1;height:28px;display:flex;border-radius:6px;overflow:hidden">';
|
|
47
|
+
row.segments.forEach(function(s, i) {
|
|
48
|
+
html += '<div style="width:' + (s.value/total*100).toFixed(1) + '%;height:100%;background:' + colors[i%colors.length] + '"></div>';
|
|
49
|
+
});
|
|
50
|
+
html += '</div><span style="width:70px;font-size:13px;font-weight:600;text-align:right">' + total + '</span></div>';
|
|
51
|
+
container.innerHTML += html;
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## SVG Line/Area Chart
|
|
56
|
+
```js
|
|
57
|
+
var w = 600, h = 200, pad = 20;
|
|
58
|
+
var maxVal = Math.max.apply(null, data.map(function(d){return d.value}));
|
|
59
|
+
var points = data.map(function(d, i) {
|
|
60
|
+
var x = pad + (i / (data.length - 1)) * (w - 2*pad);
|
|
61
|
+
var y = h - pad - ((d.value / maxVal) * (h - 2*pad));
|
|
62
|
+
return x + ',' + y;
|
|
63
|
+
});
|
|
64
|
+
svg.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
|
|
65
|
+
svg.innerHTML =
|
|
66
|
+
'<path d="M' + points.join(' L') + ' L' + (w-pad) + ',' + (h-pad) + ' L' + pad + ',' + (h-pad) + ' Z" fill="rgba(77,182,172,0.15)"/>'
|
|
67
|
+
+ '<path d="M' + points.join(' L') + '" fill="none" stroke="var(--graphit-accent)" stroke-width="2"/>';
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## SVG Multi-Series Line
|
|
71
|
+
```js
|
|
72
|
+
var colors = ['var(--graphit-accent)','#FF7043','#7E57C2','#42A5F5'];
|
|
73
|
+
series.forEach(function(s, si) {
|
|
74
|
+
var pts = s.values.map(function(v, i) {
|
|
75
|
+
var x = pad + (i / (s.values.length - 1)) * (w - 2*pad);
|
|
76
|
+
var y = h - pad - ((v / maxAll) * (h - 2*pad));
|
|
77
|
+
return x + ',' + y;
|
|
78
|
+
});
|
|
79
|
+
svg.innerHTML += '<path d="M' + pts.join(' L') + '" fill="none" stroke="' + colors[si] + '" stroke-width="2"/>';
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## SVG Sparkline (for KPI cards)
|
|
84
|
+
```js
|
|
85
|
+
var vals = [10,15,12,18,22,19,25];
|
|
86
|
+
var max = Math.max.apply(null,vals), min = Math.min.apply(null,vals);
|
|
87
|
+
var pts = vals.map(function(v,i){
|
|
88
|
+
return (i/(vals.length-1))*120 + ',' + (30 - ((v-min)/(max-min||1))*26 - 2);
|
|
89
|
+
}).join(' L');
|
|
90
|
+
sparkSvg.innerHTML = '<path d="M' + pts + '" fill="none" stroke="var(--graphit-accent)" stroke-width="1.5"/>';
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Canvas Donut Chart
|
|
94
|
+
```js
|
|
95
|
+
var ctx = canvas.getContext('2d');
|
|
96
|
+
var slices = [{v:40,c:'var(--graphit-accent)'},{v:25,c:'#80CBC4'},{v:20,c:'#26A69A'},{v:15,c:'#009688'}];
|
|
97
|
+
var total = slices.reduce(function(a,s){return a+s.v},0);
|
|
98
|
+
var cx=150, cy=150, r=120, hole=70, angle = -Math.PI/2;
|
|
99
|
+
slices.forEach(function(s) {
|
|
100
|
+
var arc = (s.v/total)*Math.PI*2;
|
|
101
|
+
ctx.beginPath(); ctx.moveTo(cx,cy);
|
|
102
|
+
ctx.arc(cx,cy,r,angle,angle+arc);
|
|
103
|
+
ctx.fillStyle=s.c; ctx.fill(); angle+=arc;
|
|
104
|
+
});
|
|
105
|
+
ctx.beginPath(); ctx.arc(cx,cy,hole,0,Math.PI*2);
|
|
106
|
+
ctx.fillStyle='white'; ctx.fill();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## CSS Heatmap Grid
|
|
110
|
+
```js
|
|
111
|
+
var max = Math.max.apply(null, cells.map(function(c){return c.value}));
|
|
112
|
+
container.style.cssText = 'display:grid;grid-template-columns:repeat(' + cols + ',1fr);gap:2px';
|
|
113
|
+
cells.forEach(function(c) {
|
|
114
|
+
var intensity = c.value / max;
|
|
115
|
+
var bg = 'rgba(77,182,172,' + (0.1 + intensity*0.9).toFixed(2) + ')';
|
|
116
|
+
var fg = intensity > 0.6 ? 'white' : 'var(--graphit-fg)';
|
|
117
|
+
container.innerHTML += '<div style="aspect-ratio:1;border-radius:3px;display:flex;align-items:center;'
|
|
118
|
+
+ 'justify-content:center;font-size:11px;font-weight:600;background:' + bg + ';color:' + fg + '">'
|
|
119
|
+
+ c.value + '</div>';
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## CSS Funnel
|
|
124
|
+
```js
|
|
125
|
+
var maxVal = data[0].value;
|
|
126
|
+
data.forEach(function(d, i) {
|
|
127
|
+
var pct = (d.value / maxVal * 100).toFixed(1);
|
|
128
|
+
var rate = i > 0 ? (d.value / data[i-1].value * 100).toFixed(1) + '%' : '';
|
|
129
|
+
container.innerHTML += '<div style="display:flex;align-items:center;gap:12px;margin-bottom:6px">'
|
|
130
|
+
+ '<span style="width:120px;font-size:13px;text-align:right">' + d.name + '</span>'
|
|
131
|
+
+ '<div style="width:' + pct + '%;height:36px;background:var(--graphit-accent);border-radius:4px;'
|
|
132
|
+
+ 'display:flex;align-items:center;padding:0 12px;color:white;font-weight:600;font-size:13px">'
|
|
133
|
+
+ d.value.toLocaleString() + '</div>'
|
|
134
|
+
+ '<span style="font-size:12px;color:var(--graphit-fg-subtle)">' + rate + '</span></div>';
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## CSS Gauge / Progress
|
|
139
|
+
```html
|
|
140
|
+
<div style="position:relative;width:200px;height:100px;overflow:hidden">
|
|
141
|
+
<div id="gauge-arc" style="width:200px;height:200px;border-radius:50%;
|
|
142
|
+
background:conic-gradient(var(--graphit-accent) 0% 72%, var(--graphit-surface-sunken) 72% 100%);
|
|
143
|
+
clip-path:inset(0 0 50% 0)"></div>
|
|
144
|
+
<div style="position:absolute;bottom:0;left:50%;transform:translateX(-50%);
|
|
145
|
+
font-size:28px;font-weight:700">72%</div>
|
|
146
|
+
</div>
|
|
147
|
+
```
|
|
148
|
+
Dynamic: set `background: conic-gradient(var(--graphit-accent) 0% ${pct}%, var(--graphit-surface-sunken) ${pct}% 100%)`.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: false
|
|
3
|
+
description: "Graphit: Consult when choosing chart types for dashboard elements. Matches data shapes to the right visualization."
|
|
4
|
+
globs: []
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Chart Selection
|
|
8
|
+
|
|
9
|
+
Consult when choosing chart types for dashboard elements. Matches data shapes to the right visualization.
|
|
10
|
+
|
|
11
|
+
## Dimension/Measure Defaults
|
|
12
|
+
|
|
13
|
+
| Dimensions | Measures | Default chart |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| 1 temporal | 1+ | line |
|
|
16
|
+
| 1 categorical | 1 | bar |
|
|
17
|
+
| 1 categorical | 2+ | bar (grouped or dual axis) |
|
|
18
|
+
| 2 categorical | 1 | bar with series on 2nd dim |
|
|
19
|
+
| 0 | 1 | KPI card |
|
|
20
|
+
| 1 categorical (50+ values) | 1 | table |
|
|
21
|
+
|
|
22
|
+
When ambiguous, propose 2-3 options and ask the user. Do not guess.
|
|
23
|
+
|
|
24
|
+
## Full Chart Type Table
|
|
25
|
+
|
|
26
|
+
| Data shape | Chart type | Required columns |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Time series | line | 1 temporal + 1 numeric |
|
|
29
|
+
| Categories | bar | 1 categorical + 1 numeric |
|
|
30
|
+
| Stacked areas | area | 1 temporal/categorical + 1 numeric |
|
|
31
|
+
| Part-whole (max 5 slices) | pie | 1 categorical + 1 numeric |
|
|
32
|
+
| Single metric | KPI card | 1 numeric |
|
|
33
|
+
| Stages | funnel | 1 categorical + 1 numeric |
|
|
34
|
+
| Target/progress | gauge | 1 numeric |
|
|
35
|
+
| Matrix (unpivoted) | heatmap | 2 categorical + 1 numeric |
|
|
36
|
+
| Hierarchy | treemap | 1 categorical + 1 numeric |
|
|
37
|
+
| Flows | sankey | 2 categorical + 1 numeric |
|
|
38
|
+
| Correlation | scatter | 2 numeric |
|
|
39
|
+
| 3 variables | bubble | 3 numeric |
|
|
40
|
+
| Countries | choropleth map | 1 categorical + 1 numeric |
|
|
41
|
+
| Lat/lng | scatter map | 2 numeric (lat + lng) |
|
|
42
|
+
| Detail/raw data | table | any columns |
|
|
43
|
+
|
|
44
|
+
## Perception Ranking (Cleveland-McGill)
|
|
45
|
+
|
|
46
|
+
Position > length > angle > area > color.
|
|
47
|
+
|
|
48
|
+
| Goal | First choice | Never |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| Trend (8+ points) | line | bar, pie |
|
|
51
|
+
| Trend (2-7 points) | bar (column) | line with dots |
|
|
52
|
+
| Compare (12 or fewer categories) | sorted bar | pie, radar |
|
|
53
|
+
| Compare (13-30 categories) | horizontal bar + top-N | column |
|
|
54
|
+
| Part-to-whole | stacked bar, treemap | pie with 6+ |
|
|
55
|
+
| Distribution | histogram, box | pie, line |
|
|
56
|
+
| Correlation | scatter, bubble | dual-axis |
|
|
57
|
+
| Ranking | sorted bar + min-N | pie, treemap |
|
|
58
|
+
| Funnel | funnel + stage% | pie |
|
|
59
|
+
| KPI vs target | big-number + sparkline | gauge (unbounded) |
|
|
60
|
+
| 2D matrix / cohort | heatmap | bar with 100+ |
|
|
61
|
+
|
|
62
|
+
## Cardinality Guards
|
|
63
|
+
|
|
64
|
+
| Cardinality of categorical dim | Action |
|
|
65
|
+
|---|---|
|
|
66
|
+
| 1-5 | Show data labels, no rotation |
|
|
67
|
+
| 6-20 | Default styling |
|
|
68
|
+
| 21-50 | Rotate labels 45 deg, consider Top-N + Other |
|
|
69
|
+
| 50+ | Use table chart or require a filter |
|
|
70
|
+
|
|
71
|
+
COALESCE categorical dimensions in SQL (`COALESCE(region, 'Other')`) to prevent blank axis labels. For 21+ categories on a bar/line, use a "Top N + Other" window-function pattern:
|
|
72
|
+
|
|
73
|
+
```sql
|
|
74
|
+
WITH ranked AS (
|
|
75
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY metric DESC) AS rn
|
|
76
|
+
FROM data
|
|
77
|
+
)
|
|
78
|
+
SELECT CASE WHEN rn <= 10 THEN category ELSE 'Other' END AS category,
|
|
79
|
+
SUM(metric) AS metric
|
|
80
|
+
FROM ranked GROUP BY 1 ORDER BY 2 DESC
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Hard Caps
|
|
84
|
+
|
|
85
|
+
- Pie: max 5 slices (3 preferred)
|
|
86
|
+
- Line series: max 5-7 (else use small multiples)
|
|
87
|
+
- Stacked bar segments: max 4
|
|
88
|
+
- Categorical colors: max 7 distinct
|
|
89
|
+
- Sort by value DESC unless the axis is ordinal or temporal
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: false
|
|
3
|
+
description: "Graphit: Consult before building any multi-chart dashboard. Transforms vague requests into structured, cohesive dashboards."
|
|
4
|
+
globs: []
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Dashboard Planning
|
|
8
|
+
|
|
9
|
+
Consult before building any multi-chart dashboard. Transforms vague requests into structured, cohesive dashboards.
|
|
10
|
+
|
|
11
|
+
## Frame the Question
|
|
12
|
+
|
|
13
|
+
Every dashboard answers ONE primary question. Name the shape first:
|
|
14
|
+
|
|
15
|
+
| Shape | Lead chart | Example question |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Trend | line/area | "How is revenue changing?" |
|
|
18
|
+
| Comparison | sorted bar | "Revenue by segment?" |
|
|
19
|
+
| Ranking | sorted bar + top-N | "Top 10 customers?" |
|
|
20
|
+
| Composition | stacked bar/treemap | "Revenue share by product?" |
|
|
21
|
+
| Distribution | histogram | "Spread of order values?" |
|
|
22
|
+
| Correlation | scatter | "Spend vs conversion?" |
|
|
23
|
+
| Funnel | funnel + stage% | "Signup-to-purchase conversion?" |
|
|
24
|
+
| Cohort | heatmap/overlay lines | "D1/D7/D30 retention?" |
|
|
25
|
+
| Deviation | diverging bar | "Budget vs actual variance?" |
|
|
26
|
+
|
|
27
|
+
Compound intent ("what and why") - sequence trend then root-cause as two charts.
|
|
28
|
+
|
|
29
|
+
## Pick the Archetype
|
|
30
|
+
|
|
31
|
+
| Audience | Archetype | KPIs | Charts | Interactivity |
|
|
32
|
+
|---|---|---|---|---|
|
|
33
|
+
| Exec / "are we on track" | Strategic | 4-6 hero | 5-9 | Low |
|
|
34
|
+
| Analyst / "why is X" | Analytical | 3-5 | 6-12 + detail | High |
|
|
35
|
+
| Ops / "what's broken now" | Operational | 8-16 dense | 6-12 | Medium |
|
|
36
|
+
|
|
37
|
+
Never mix archetypes on a single page.
|
|
38
|
+
|
|
39
|
+
## Mandatory Rules
|
|
40
|
+
|
|
41
|
+
- **Time-series**: if data has a date column, include at least 1 line/area trend. A dashboard without a trend is a snapshot - it cannot answer "is it improving?"
|
|
42
|
+
- **Cohort**: if data has install/signup date + retention/LTV, add cohort curves (D0/D3/D7/D30). Period-over-period is NOT a substitute.
|
|
43
|
+
- **Diversity**: 4+ charts must use 3+ distinct chart types. All-bar or all-line wastes the visual encoding.
|
|
44
|
+
- **KPI-first**: lead with 1-3 KPI cards before detail charts unless the user says otherwise.
|
|
45
|
+
- **Distribution over averages**: heavy-tailed metrics (spend, session length, deal size) need percentile bands or tier breakdowns. Median + p90, never bare mean.
|
|
46
|
+
- **Insight titles**: state the takeaway ("Revenue grew 23% Q3"), not the metric name.
|
|
47
|
+
- **Reference lines**: goal-oriented metrics carry target/prior-period/benchmark lines. KPI cards need delta + comparison label.
|
|
48
|
+
|
|
49
|
+
## Metric Contract
|
|
50
|
+
|
|
51
|
+
Every KPI or measure needs all 6 properties:
|
|
52
|
+
|
|
53
|
+
1. **Definition** - what counts, what's excluded (bots, refunds, internal)
|
|
54
|
+
2. **Grain** - per-user-per-day, per-cohort, per-org-per-month
|
|
55
|
+
3. **Window** - rolling 7d/28d, MTD/QTD/YTD, cohort-anchored
|
|
56
|
+
4. **Population** - all users, payers, certified, segment
|
|
57
|
+
5. **Baseline** - vs prior period / target / cohort avg / benchmark
|
|
58
|
+
6. **Direction** - up=good or up=bad (drives color meaning)
|
|
59
|
+
|
|
60
|
+
Pair lagging + leading: revenue (lagging) needs retention (leading). Replace vanity cumulative counts ("total signups") with cohort/period rates. Never show AVG of a rate column - rates must be weighted. Show denominators on rates (or hide when N < 30).
|
|
61
|
+
|
|
62
|
+
## Anti-Patterns to Block
|
|
63
|
+
|
|
64
|
+
- Pie with 6+ slices (max 5, prefer 3)
|
|
65
|
+
- 3D charts of any kind
|
|
66
|
+
- Dual Y-axis without clear justification (use small multiples)
|
|
67
|
+
- Truncated bar baselines (always start at 0)
|
|
68
|
+
- Stacked bar with 5+ segments
|
|
69
|
+
- Gauge for unbounded KPIs (use bullet chart)
|
|
70
|
+
- Rainbow palette on ordinal data
|
|
71
|
+
- Red-green without shape encoding (accessibility)
|
|
72
|
+
- Aggregate-only without segment drill-down (Simpson's paradox)
|
|
73
|
+
- Rate without denominator visible
|
|
74
|
+
- KPI without baseline comparison
|
|
75
|
+
- Vanity cumulative counts ("total signups since launch")
|
|
76
|
+
|
|
77
|
+
## Asking Good Questions
|
|
78
|
+
|
|
79
|
+
Purpose before data. The first response should mirror the user's intent and ask ONE narrowing question - never start querying immediately.
|
|
80
|
+
|
|
81
|
+
**Batch related questions** - ask multiple things at once instead of sequential single questions. Each option should lead to a different path, not variations of the same thing.
|
|
82
|
+
|
|
83
|
+
**Use open questions for exploration** - "What business decision will this dashboard support?" beats presenting a restrictive multiple-choice.
|
|
84
|
+
|
|
85
|
+
**Clarification triggers:**
|
|
86
|
+
- User says "revenue" - ask: bookings, ARR, or GAAP recognized?
|
|
87
|
+
- User says "conversion" - ask: what's the start and end event?
|
|
88
|
+
- User says "active users" - ask: what defines active? (logged in? performed action? within what window?)
|
|
89
|
+
- User mentions MQL/SQL - ask: how does your org define the handoff? (Same column can mean 13% or 40%)
|
|
90
|
+
|
|
91
|
+
## Pre-Build Checklist
|
|
92
|
+
|
|
93
|
+
Before generating the HTML:
|
|
94
|
+
- [ ] Question shape stated (trend, comparison, funnel, etc.)
|
|
95
|
+
- [ ] Archetype + audience identified
|
|
96
|
+
- [ ] Time-series present if date column exists
|
|
97
|
+
- [ ] Cohort view present if install/signup + retention data
|
|
98
|
+
- [ ] 3+ chart types for 4+ graphs
|
|
99
|
+
- [ ] Every KPI has definition + baseline + direction
|
|
100
|
+
- [ ] No anti-patterns present
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: false
|
|
3
|
+
description: "Graphit: 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
|
+
globs: []
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Domain Lenses
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
Detect the domain from column names and user intent. Apply universal planning rules first (see dashboard-planning.md), then layer domain-specific patterns.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Marketing & Attribution
|
|
16
|
+
|
|
17
|
+
**Signals:** utm_*, channel, creative_id, spend, cpi, impressions, clicks, roas, cac, ltv, attribution_*, skan_*, conversions, ctr.
|
|
18
|
+
|
|
19
|
+
**Key metrics:**
|
|
20
|
+
|
|
21
|
+
| Metric | Formula pattern | Chart |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Blended CAC / Paid CAC | spend / new_customers | bar/KPI, target + YoY |
|
|
24
|
+
| ROAS D7/D30/D90 | revenue_D0-N / spend | cumulative cohort line + 100% breakeven |
|
|
25
|
+
| MER | revenue / marketing_spend | KPI |
|
|
26
|
+
| LTV:CAC Ratio | LTV / CAC | bar, floor 3:1 |
|
|
27
|
+
| Payback Period | days to recoup CAC | cumulative line |
|
|
28
|
+
|
|
29
|
+
**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.
|
|
30
|
+
|
|
31
|
+
**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.
|
|
32
|
+
|
|
33
|
+
**Clarify first:** Attribution model + window? ROAS gross or margin-adjusted?
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Financial / Executive
|
|
38
|
+
|
|
39
|
+
**Signals:** revenue, mrr, arr, cogs, gross_margin, opex, ebitda, budget, forecast, actual, variance, runway, burn, cash_balance, gl_account.
|
|
40
|
+
|
|
41
|
+
**Key metrics:**
|
|
42
|
+
|
|
43
|
+
| Metric | Formula pattern | Chart |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| P&L Cascade | Rev->COGS->GP->Opex->EBITDA->Net | waterfall |
|
|
46
|
+
| Variance $/% | Actual-Plan, sign-aware per line type | semantic-color bar |
|
|
47
|
+
| GM% by segment | (Rev-COGS)/Rev per product/cohort | line + small multiples |
|
|
48
|
+
| Burn Multiple | net_burn / net_new_ARR | KPI + trend |
|
|
49
|
+
| Runway Months | Cash / abs(Net Burn) | KPI color-banded |
|
|
50
|
+
| Rule of 40 | Growth% + EBITDA margin% | KPI + trend |
|
|
51
|
+
|
|
52
|
+
**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).
|
|
53
|
+
|
|
54
|
+
**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).
|
|
55
|
+
|
|
56
|
+
**Clarify first:** Revenue = bookings, ARR, or GAAP recognized?
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Product / Growth
|
|
61
|
+
|
|
62
|
+
**Signals:** install_date, signup_at, cohort_*, retention_d*, dau, mau, activation_*, payer_*, funnel_step, session_*, ltv, arpu, arpdau.
|
|
63
|
+
|
|
64
|
+
**Key metrics:**
|
|
65
|
+
|
|
66
|
+
| Metric | Formula pattern | Chart |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| D1/D7/D30 Retention | active_dN / cohort_size | heatmap or overlay |
|
|
69
|
+
| Stickiness | DAU/MAU | line + ref bands |
|
|
70
|
+
| ARPDAU | revenue_day / DAU_day | line + 7d MA |
|
|
71
|
+
| Payer Conversion D7 | first_payers_d7 / cohort | cumulative line |
|
|
72
|
+
| Whale Share | sum(rev) top-X% / total_rev | Lorenz / decile bar |
|
|
73
|
+
| Activation Rate | reached_aha / signups | cohort heatmap |
|
|
74
|
+
|
|
75
|
+
**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.
|
|
76
|
+
|
|
77
|
+
**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.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Operational Monitoring
|
|
82
|
+
|
|
83
|
+
**Signals:** latency, p50/p95/p99, error_rate, slo, uptime, mttr, throughput, oee, cycle_time, otif, fill_rate, stockout, deployment_frequency.
|
|
84
|
+
|
|
85
|
+
**Key metrics:**
|
|
86
|
+
|
|
87
|
+
| Metric | Formula pattern | Chart |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| Latency p50/p95/p99 | percentile buckets | multi-percentile line + SLO |
|
|
90
|
+
| Error Budget | 100% - (errors/total) rolling 28d | bullet chart |
|
|
91
|
+
| DORA Quartet | deploy freq, lead time, CFR, recovery | 4-tile KPI |
|
|
92
|
+
| OEE | Availability x Performance x Quality | KPI + 3-tile decomp |
|
|
93
|
+
| OTIF | on-time AND in-full | trend + decomp stacked bar |
|
|
94
|
+
|
|
95
|
+
**Must-have charts:** Multi-percentile line + SLO threshold band. Bullet chart (not gauge). Latency heatmap. Error sources Pareto.
|
|
96
|
+
|
|
97
|
+
**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%).
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Sales / Pipeline
|
|
102
|
+
|
|
103
|
+
**Signals:** opp_id, stage, amount, close_date, forecast_category, mql, sql, arr, acv, tcv, quota, attainment, loss_reason, expansion_arr.
|
|
104
|
+
|
|
105
|
+
**Key metrics:**
|
|
106
|
+
|
|
107
|
+
| Metric | Formula pattern | Chart |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| Pipeline Coverage | open_pipe / quota_remaining | KPI, target=1/win_rate |
|
|
110
|
+
| Sales Velocity | (opps x win_rate x avg_acv) / cycle_days | KPI + trend |
|
|
111
|
+
| Win Rate by ACV | won / (won+lost) per band | sorted bar, min-N 30 |
|
|
112
|
+
| Quota Attainment | closed_won / quota | histogram (bimodal!) |
|
|
113
|
+
| Net New ARR | new + expansion - contraction - churn | waterfall |
|
|
114
|
+
| Forecast MAPE | mean(abs(actual-forecast)/actual) | line + target |
|
|
115
|
+
|
|
116
|
+
**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.
|
|
117
|
+
|
|
118
|
+
**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).
|
|
119
|
+
|
|
120
|
+
**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,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: false
|
|
3
|
+
description: "Graphit: Consult when starting a dashboard build. The Knowledge Base contains reusable metrics, dimensions, rules, and table schemas that ensure consistent formulas across dashboards."
|
|
4
|
+
globs: []
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# KB Exploration
|
|
8
|
+
|
|
9
|
+
Consult when starting a dashboard build. The Knowledge Base contains reusable metrics, dimensions, rules, and table schemas that ensure consistent formulas across dashboards.
|
|
10
|
+
|
|
11
|
+
## KB-First Discovery
|
|
12
|
+
|
|
13
|
+
Before writing raw SQL with inline aggregations or column references:
|
|
14
|
+
|
|
15
|
+
1. List what exists: `graphit kb list metrics`, `graphit kb list dimensions`, `graphit kb list rules`
|
|
16
|
+
2. If a KB metric matches the user's request, use its formula: `graphit kb get metric REVENUE`
|
|
17
|
+
3. If no match exists, consider whether to build the dashboard from raw columns or suggest KB asset creation first
|
|
18
|
+
4. Explore relationships: `graphit kb explore metric REVENUE` shows which tables and dimensions connect to a metric
|
|
19
|
+
|
|
20
|
+
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.
|
|
21
|
+
|
|
22
|
+
## Metric vs Dimension
|
|
23
|
+
|
|
24
|
+
| Property | Metric | Dimension |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Formula | Aggregation required (SUM, COUNT, AVG, MIN, MAX) | Row-level only (no aggregates) |
|
|
27
|
+
| Table scope | Can reference multiple tables | Exactly one table |
|
|
28
|
+
| Purpose | Measures - what you count/sum | Grouping axes - how you slice |
|
|
29
|
+
| Example | `SUM(ORDERS.AMOUNT)` | `DATE_TRUNC('month', EVENTS.EVENT_TS)` |
|
|
30
|
+
| Invalid | `ORDERS.AMOUNT` (no aggregate) | `SUM(EVENTS.DURATION)` (has aggregate) |
|
|
31
|
+
|
|
32
|
+
## Naming Conventions
|
|
33
|
+
|
|
34
|
+
All KB assets use UPPER_SNAKE_CASE. The names are auto-sanitized:
|
|
35
|
+
|
|
36
|
+
| Pattern | Example | Use when |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `TOTAL_*` | `TOTAL_REVENUE`, `TOTAL_ORDERS` | Sum aggregations |
|
|
39
|
+
| `AVG_*` | `AVG_ORDER_VALUE` | Average metrics |
|
|
40
|
+
| `COUNT_*` | `COUNT_ACTIVE_USERS` | Count metrics |
|
|
41
|
+
| `*_RATE` | `CONVERSION_RATE`, `CHURN_RATE` | Ratios/percentages |
|
|
42
|
+
|
|
43
|
+
## Reuse Over Reinvention
|
|
44
|
+
|
|
45
|
+
When building multi-chart dashboards, identify shared concepts:
|
|
46
|
+
- If 3 charts use `SUM(ORDERS.AMOUNT)`, reference one TOTAL_REVENUE metric
|
|
47
|
+
- If 2 charts group by `DATE_TRUNC('month', TS)`, reference one MONTHLY dimension
|
|
48
|
+
- Discover existing assets: `graphit kb search "revenue"`
|
|
49
|
+
|
|
50
|
+
## When to Suggest KB Asset Creation
|
|
51
|
+
|
|
52
|
+
| Signal | Propose |
|
|
53
|
+
|---|---|
|
|
54
|
+
| User requests a business metric with no KB match | Metric - reusable formula |
|
|
55
|
+
| User groups by a derived expression | Dimension - consistent grouping |
|
|
56
|
+
| User describes a business rule ("active = logged in within 30d") | Rule - applied to future queries |
|
|
57
|
+
| User uses a business term not in KB | Synonym - maps colloquial to defined |
|
|
58
|
+
|
|
59
|
+
## Formula Syntax
|
|
60
|
+
|
|
61
|
+
Metrics require `TABLE.COLUMN` references with UPPERCASE naming:
|
|
62
|
+
|
|
63
|
+
```sql
|
|
64
|
+
-- Valid metric formulas
|
|
65
|
+
SUM(ORDERS.AMOUNT)
|
|
66
|
+
COUNT(DISTINCT EVENTS.USER_ID) WHERE EVENTS.EVENT_TS >= DATEADD(day, -30, CURRENT_DATE)
|
|
67
|
+
SUM(ORDERS.REVENUE) / NULLIF(SUM(ORDERS.COST), 0)
|
|
68
|
+
|
|
69
|
+
-- Valid dimension formulas (no aggregates)
|
|
70
|
+
EVENTS.PLATFORM
|
|
71
|
+
DATE_TRUNC('month', EVENTS.EVENT_TS)
|
|
72
|
+
CASE WHEN USERS.AGE >= 18 THEN 'adult' ELSE 'minor' END
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Conditionals use CASE WHEN (not FILTER WHERE - Snowflake doesn't support it). Always guard division with NULLIF.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: false
|
|
3
|
+
description: "Graphit: 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
|
+
globs: []
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# SQL Reference
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
## DuckDB vs Snowflake Translation
|
|
12
|
+
|
|
13
|
+
| Snowflake | DuckDB equivalent |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `DATEADD(day, N, date)` | `date + INTERVAL N DAY` |
|
|
16
|
+
| `DATEDIFF(day, a, b)` | `DATE_DIFF('day', a, b)` |
|
|
17
|
+
| `NVL(a, b)` | `COALESCE(a, b)` |
|
|
18
|
+
| `NVL2(a, b, c)` | `CASE WHEN a IS NOT NULL THEN b ELSE c END` |
|
|
19
|
+
| `IFF(cond, a, b)` | `CASE WHEN cond THEN a ELSE b END` |
|
|
20
|
+
| `TO_CHAR(date, fmt)` | `strftime(date, fmt)` |
|
|
21
|
+
| `ARRAY_AGG(x) WITHIN GROUP (ORDER BY y)` | `LIST(x ORDER BY y)` |
|
|
22
|
+
| `LISTAGG(col, sep)` | `STRING_AGG(col, sep ORDER BY ...)` |
|
|
23
|
+
| `COUNT_IF(cond)` | `COUNT(*) FILTER (WHERE cond)` |
|
|
24
|
+
| `GENERATOR(ROWCOUNT => N)` | `generate_series(0, N-1)` |
|
|
25
|
+
| `CONVERT_TIMEZONE('tz', ts)` | `timezone('tz', ts)` |
|
|
26
|
+
|
|
27
|
+
### JSON / VARIANT Access
|
|
28
|
+
|
|
29
|
+
| Engine | Syntax | Example |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| Snowflake | Colon notation | `col:field::STRING`, `col:parent:child::NUMBER` |
|
|
32
|
+
| DuckDB | Arrow operators | `col->>'field'` (text), `col->'field'` (JSON) |
|
|
33
|
+
|
|
34
|
+
NEVER use `->>` in Snowflake or `:field::STRING` in DuckDB.
|
|
35
|
+
|
|
36
|
+
### Timezone
|
|
37
|
+
|
|
38
|
+
| Engine | Correct | Wrong |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| DuckDB | `timezone('Asia/Jerusalem', ts_col)` | `AT TIME ZONE` chains (reverses direction on TIMESTAMPTZ) |
|
|
41
|
+
| Snowflake | `CONVERT_TIMEZONE('Asia/Jerusalem', ts_col)` (2-arg for _TZ) | `AT TIME ZONE` |
|
|
42
|
+
|
|
43
|
+
## DuckDB Superpowers (not in Snowflake)
|
|
44
|
+
|
|
45
|
+
| Feature | Example |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `GROUP BY ALL` | `SELECT region, SUM(sales) FROM t GROUP BY ALL` |
|
|
48
|
+
| `SELECT * EXCLUDE` | `SELECT * EXCLUDE (internal_id) FROM t` |
|
|
49
|
+
| `FILTER (WHERE)` | `COUNT(*) FILTER (WHERE status='active')` |
|
|
50
|
+
| `UNION BY NAME` | `SELECT ... UNION ALL BY NAME SELECT ...` |
|
|
51
|
+
|
|
52
|
+
## Snowflake Notes
|
|
53
|
+
|
|
54
|
+
- Use `DATE_TRUNC('month', date)` for grouping (not EXTRACT/MONTH/YEAR)
|
|
55
|
+
- Snowflake does NOT support `FILTER (WHERE)` - use `CASE WHEN` instead
|
|
56
|
+
- `COUNT_IF` MUST receive a boolean expression, not a raw INT column. Use `COUNT_IF(is_active = 1)`, not `COUNT_IF(is_active)`
|
|
57
|
+
- String matching: prefer `ILIKE` (case-insensitive) over `LIKE`
|
|
58
|
+
|
|
59
|
+
## SQL Formatting Standards (Both Engines)
|
|
60
|
+
|
|
61
|
+
- Keywords UPPERCASE: `SELECT`, `FROM`, `WHERE`, `JOIN`, `GROUP BY`, `ORDER BY`
|
|
62
|
+
- Table/column names UPPERCASE: `ORDERS`, `CUSTOMER_ID`, `TOTAL_REVENUE`
|
|
63
|
+
- Qualify every column with its table alias: `o.ORDER_DATE`, not bare `ORDER_DATE`
|
|
64
|
+
- Use descriptive aliases: `total_revenue`, not `sum1`
|
|
65
|
+
- String literals in single quotes: `'active'`, `'2024-01-01'`
|
|
66
|
+
- Non-ASCII identifiers MUST be double-quoted: `SELECT * FROM "hebrew_table"`
|
|
67
|
+
|
|
68
|
+
## CTE Pattern
|
|
69
|
+
|
|
70
|
+
Use CTEs for queries with 3+ JOINs or complex subqueries:
|
|
71
|
+
|
|
72
|
+
```sql
|
|
73
|
+
WITH MONTHLY_ORDERS AS (
|
|
74
|
+
SELECT o.CUSTOMER_ID, DATE_TRUNC('month', o.ORDER_DATE) AS MONTH,
|
|
75
|
+
SUM(o.AMOUNT) AS MONTHLY_REVENUE
|
|
76
|
+
FROM ORDERS o WHERE o.STATUS = 'complete'
|
|
77
|
+
GROUP BY o.CUSTOMER_ID, DATE_TRUNC('month', o.ORDER_DATE)
|
|
78
|
+
)
|
|
79
|
+
SELECT c.SEGMENT, AVG(mo.MONTHLY_REVENUE) AS AVG_MONTHLY_REVENUE
|
|
80
|
+
FROM MONTHLY_ORDERS mo JOIN CUSTOMERS c ON mo.CUSTOMER_ID = c.ID
|
|
81
|
+
GROUP BY c.SEGMENT ORDER BY AVG_MONTHLY_REVENUE DESC
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## ORDER BY Rules (Always Include)
|
|
85
|
+
|
|
86
|
+
| Query type | ORDER BY |
|
|
87
|
+
|---|---|
|
|
88
|
+
| Time series | `ORDER BY date_column ASC` |
|
|
89
|
+
| Category breakdowns | `ORDER BY metric_column DESC` |
|
|
90
|
+
| Rankings / top N | `ORDER BY metric_column DESC LIMIT N` |
|
|
91
|
+
|
|
92
|
+
Do NOT add LIMIT unless the user requests it or the query is a ranking.
|
|
93
|
+
|
|
94
|
+
## Gap-Filling Pattern (DuckDB)
|
|
95
|
+
|
|
96
|
+
For heatmaps or continuous time-series needing every cell (even zeros):
|
|
97
|
+
|
|
98
|
+
```sql
|
|
99
|
+
WITH grid AS (
|
|
100
|
+
SELECT UNNEST(generate_series(0, 23)) AS hour_of_day
|
|
101
|
+
)
|
|
102
|
+
SELECT g.hour_of_day, COALESCE(t.count, 0) AS count
|
|
103
|
+
FROM grid g
|
|
104
|
+
LEFT JOIN (SELECT hour, COUNT(*) AS count FROM data_source GROUP BY 1) t
|
|
105
|
+
ON g.hour_of_day = t.hour
|
|
106
|
+
ORDER BY 1
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Date series:
|
|
110
|
+
```sql
|
|
111
|
+
SELECT UNNEST(generate_series(
|
|
112
|
+
CURRENT_DATE - INTERVAL 30 DAY, CURRENT_DATE, INTERVAL 1 DAY
|
|
113
|
+
))::DATE AS date
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Subquery Column Scope
|
|
117
|
+
|
|
118
|
+
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.
|
|
119
|
+
|
|
120
|
+
## Data Source Routing
|
|
121
|
+
|
|
122
|
+
| Situation | Command | Speed |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| Table has a cached data source | `graphit query "SQL" --ds <id>` | ~100ms, DuckDB |
|
|
125
|
+
| No data source | `graphit query "SQL" --warehouse --connection <id>` | ~10s, Snowflake |
|
|
126
|
+
|
|
127
|
+
Always prefer cached data sources. Check with `graphit ds list`. If no data source covers the table, suggest creating one for future speed.
|