@quarri/claude-data-tools 1.0.2 → 1.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/.claude-plugin/plugin.json +12 -1
- package/dist/api/client.d.ts +36 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +58 -2
- package/dist/api/client.js.map +1 -1
- package/dist/auth-cli.d.ts +7 -0
- package/dist/auth-cli.d.ts.map +1 -0
- package/dist/auth-cli.js +361 -0
- package/dist/auth-cli.js.map +1 -0
- package/dist/index.js +227 -17
- package/dist/index.js.map +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +199 -283
- package/dist/tools/definitions.js.map +1 -1
- package/package.json +8 -2
- package/skills/SKILL_CHAINING_DEMO.md +335 -0
- package/skills/TEST_SCENARIOS.md +189 -0
- package/skills/quarri-analyze/SKILL.md +274 -0
- package/skills/quarri-chart/SKILL.md +415 -0
- package/skills/quarri-debug-connector/SKILL.md +338 -0
- package/skills/quarri-diagnose/SKILL.md +372 -0
- package/skills/quarri-explain/SKILL.md +184 -0
- package/skills/quarri-extract/SKILL.md +353 -0
- package/skills/quarri-insights/SKILL.md +328 -0
- package/skills/quarri-metric/SKILL.md +400 -0
- package/skills/quarri-query/SKILL.md +159 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Statistical analysis and business insights from data
|
|
3
|
+
globs:
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /quarri-insights - Statistical Analysis & Business Insights
|
|
8
|
+
|
|
9
|
+
Perform statistical analysis on data and generate actionable business insights with recommendations.
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
Use `/quarri-insights` when users need:
|
|
14
|
+
- Statistical analysis: "What's the distribution of order values?"
|
|
15
|
+
- Business interpretation: "What insights can you give me from this data?"
|
|
16
|
+
- Correlation analysis: "Is there a relationship between price and quantity?"
|
|
17
|
+
- Actionable recommendations: "What should we do based on these numbers?"
|
|
18
|
+
|
|
19
|
+
## Part 1: Statistical Analysis
|
|
20
|
+
|
|
21
|
+
### Analysis Types
|
|
22
|
+
|
|
23
|
+
#### 1. Descriptive Statistics
|
|
24
|
+
|
|
25
|
+
For numeric columns, calculate:
|
|
26
|
+
- **Central tendency**: mean, median, mode
|
|
27
|
+
- **Spread**: std, variance, range, IQR
|
|
28
|
+
- **Shape**: skewness, kurtosis
|
|
29
|
+
- **Percentiles**: 25th, 50th, 75th, 90th, 95th, 99th
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import pandas as pd
|
|
33
|
+
import numpy as np
|
|
34
|
+
|
|
35
|
+
def descriptive_stats(df, column):
|
|
36
|
+
return {
|
|
37
|
+
'count': df[column].count(),
|
|
38
|
+
'mean': df[column].mean(),
|
|
39
|
+
'median': df[column].median(),
|
|
40
|
+
'std': df[column].std(),
|
|
41
|
+
'min': df[column].min(),
|
|
42
|
+
'max': df[column].max(),
|
|
43
|
+
'q25': df[column].quantile(0.25),
|
|
44
|
+
'q75': df[column].quantile(0.75),
|
|
45
|
+
'skew': df[column].skew(),
|
|
46
|
+
'kurtosis': df[column].kurtosis()
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
#### 2. Distribution Analysis
|
|
51
|
+
|
|
52
|
+
Analyze how values are distributed:
|
|
53
|
+
- **Histogram bins**: Frequency distribution
|
|
54
|
+
- **Normality test**: Shapiro-Wilk or D'Agostino
|
|
55
|
+
- **Distribution fit**: Best-fit distribution type
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from scipy import stats
|
|
59
|
+
|
|
60
|
+
def distribution_analysis(df, column):
|
|
61
|
+
data = df[column].dropna()
|
|
62
|
+
hist, bin_edges = np.histogram(data, bins='auto')
|
|
63
|
+
|
|
64
|
+
if len(data) >= 20:
|
|
65
|
+
stat, p_value = stats.shapiro(data[:5000])
|
|
66
|
+
is_normal = p_value > 0.05
|
|
67
|
+
else:
|
|
68
|
+
is_normal = None
|
|
69
|
+
p_value = None
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
'histogram': {'counts': hist.tolist(), 'edges': bin_edges.tolist()},
|
|
73
|
+
'is_normal': is_normal,
|
|
74
|
+
'normality_p_value': p_value
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### 3. Correlation Analysis
|
|
79
|
+
|
|
80
|
+
Find relationships between numeric columns:
|
|
81
|
+
- **Pearson correlation**: Linear relationships
|
|
82
|
+
- **Spearman correlation**: Monotonic relationships
|
|
83
|
+
- **Strong correlations**: |r| > 0.5
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
def correlation_analysis(df, columns):
|
|
87
|
+
numeric_df = df[columns].select_dtypes(include=[np.number])
|
|
88
|
+
pearson = numeric_df.corr(method='pearson')
|
|
89
|
+
spearman = numeric_df.corr(method='spearman')
|
|
90
|
+
|
|
91
|
+
strong_correlations = []
|
|
92
|
+
for i, col1 in enumerate(numeric_df.columns):
|
|
93
|
+
for j, col2 in enumerate(numeric_df.columns):
|
|
94
|
+
if i < j:
|
|
95
|
+
r = pearson.loc[col1, col2]
|
|
96
|
+
if abs(r) > 0.5:
|
|
97
|
+
strong_correlations.append({
|
|
98
|
+
'columns': [col1, col2],
|
|
99
|
+
'pearson_r': r,
|
|
100
|
+
'spearman_r': spearman.loc[col1, col2]
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
'correlation_matrix': pearson.to_dict(),
|
|
105
|
+
'strong_correlations': strong_correlations
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### 4. Outlier Detection
|
|
110
|
+
|
|
111
|
+
Identify unusual values:
|
|
112
|
+
- **Z-score method**: Values > 3 std from mean
|
|
113
|
+
- **IQR method**: Values outside 1.5*IQR from quartiles
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
def detect_outliers(df, column, method='iqr'):
|
|
117
|
+
data = df[column].dropna()
|
|
118
|
+
|
|
119
|
+
if method == 'zscore':
|
|
120
|
+
z_scores = np.abs(stats.zscore(data))
|
|
121
|
+
outliers = data[z_scores > 3]
|
|
122
|
+
elif method == 'iqr':
|
|
123
|
+
q1, q3 = data.quantile([0.25, 0.75])
|
|
124
|
+
iqr = q3 - q1
|
|
125
|
+
lower_bound = q1 - 1.5 * iqr
|
|
126
|
+
upper_bound = q3 + 1.5 * iqr
|
|
127
|
+
outliers = data[(data < lower_bound) | (data > upper_bound)]
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
'outlier_count': len(outliers),
|
|
131
|
+
'outlier_percentage': len(outliers) / len(data) * 100,
|
|
132
|
+
'outlier_values': outliers.head(20).tolist(),
|
|
133
|
+
'bounds': {'lower': lower_bound, 'upper': upper_bound} if method == 'iqr' else None
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### 5. Time Series Analysis
|
|
138
|
+
|
|
139
|
+
For time-based data:
|
|
140
|
+
- **Trend detection**: Linear regression on time
|
|
141
|
+
- **Growth rate**: Period-over-period changes
|
|
142
|
+
- **Volatility**: Coefficient of variation
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
def time_series_analysis(df, date_column, value_column):
|
|
146
|
+
df_sorted = df.sort_values(date_column)
|
|
147
|
+
df_sorted['pct_change'] = df_sorted[value_column].pct_change()
|
|
148
|
+
|
|
149
|
+
x = np.arange(len(df_sorted))
|
|
150
|
+
slope, intercept, r_value, p_value, std_err = stats.linregress(x, df_sorted[value_column])
|
|
151
|
+
|
|
152
|
+
trend_direction = 'increasing' if slope > 0 else 'decreasing' if slope < 0 else 'stable'
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
'trend_direction': trend_direction,
|
|
156
|
+
'trend_slope': slope,
|
|
157
|
+
'trend_r_squared': r_value ** 2,
|
|
158
|
+
'average_growth_rate': df_sorted['pct_change'].mean(),
|
|
159
|
+
'volatility': df_sorted[value_column].std() / df_sorted[value_column].mean()
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### 6. Segment Comparison
|
|
164
|
+
|
|
165
|
+
Compare groups within data:
|
|
166
|
+
- **Group statistics**: Mean, median by group
|
|
167
|
+
- **Statistical tests**: t-test, ANOVA for differences
|
|
168
|
+
- **Effect size**: Cohen's d for magnitude
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
def segment_comparison(df, group_column, value_column):
|
|
172
|
+
groups = df.groupby(group_column)[value_column]
|
|
173
|
+
group_stats = groups.agg(['count', 'mean', 'median', 'std']).to_dict('index')
|
|
174
|
+
|
|
175
|
+
group_values = [group.values for name, group in groups]
|
|
176
|
+
if len(group_values) >= 2:
|
|
177
|
+
f_stat, p_value = stats.f_oneway(*group_values)
|
|
178
|
+
significant_difference = p_value < 0.05
|
|
179
|
+
else:
|
|
180
|
+
f_stat, p_value = None, None
|
|
181
|
+
significant_difference = None
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
'group_statistics': group_stats,
|
|
185
|
+
'anova_f_statistic': f_stat,
|
|
186
|
+
'anova_p_value': p_value,
|
|
187
|
+
'significant_difference': significant_difference
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Part 2: Business Insight Generation
|
|
192
|
+
|
|
193
|
+
### Pattern Recognition
|
|
194
|
+
|
|
195
|
+
Identify these patterns in the data:
|
|
196
|
+
|
|
197
|
+
**Trends**
|
|
198
|
+
- Is the metric growing, declining, or stable?
|
|
199
|
+
- What's the rate of change?
|
|
200
|
+
- Are there inflection points?
|
|
201
|
+
|
|
202
|
+
**Concentrations**
|
|
203
|
+
- Does a small portion drive most results? (Pareto principle)
|
|
204
|
+
- Are there dominant segments?
|
|
205
|
+
|
|
206
|
+
**Anomalies**
|
|
207
|
+
- Are there outliers?
|
|
208
|
+
- Are there unexpected values?
|
|
209
|
+
- Are there gaps or missing patterns?
|
|
210
|
+
|
|
211
|
+
**Relationships**
|
|
212
|
+
- Do variables correlate?
|
|
213
|
+
- Are there surprising connections?
|
|
214
|
+
- What drives what?
|
|
215
|
+
|
|
216
|
+
### Insight Categories
|
|
217
|
+
|
|
218
|
+
#### Key Finding
|
|
219
|
+
The single most important takeaway:
|
|
220
|
+
> "Electronics drives 68% of total revenue but represents only 25% of product categories."
|
|
221
|
+
|
|
222
|
+
#### Performance Insights
|
|
223
|
+
How things are performing:
|
|
224
|
+
> "Revenue grew 23% YoY, outpacing the industry average of 15%."
|
|
225
|
+
|
|
226
|
+
#### Comparison Insights
|
|
227
|
+
How segments differ:
|
|
228
|
+
> "Enterprise customers spend 4.2x more per order than SMB customers."
|
|
229
|
+
|
|
230
|
+
#### Trend Insights
|
|
231
|
+
What's changing over time:
|
|
232
|
+
> "Mobile orders increased from 12% to 47% of total orders over 18 months."
|
|
233
|
+
|
|
234
|
+
#### Risk Insights
|
|
235
|
+
Warning signs and concerns:
|
|
236
|
+
> "Three of top 10 customers reduced orders by >50% this quarter."
|
|
237
|
+
|
|
238
|
+
#### Opportunity Insights
|
|
239
|
+
Potential for growth or improvement:
|
|
240
|
+
> "Cross-sell rate for Product A is only 8%, compared to 28% category average."
|
|
241
|
+
|
|
242
|
+
### Insight Quality Criteria
|
|
243
|
+
|
|
244
|
+
Good insights are:
|
|
245
|
+
|
|
246
|
+
**Specific**: Include actual numbers
|
|
247
|
+
- Bad: "Revenue increased"
|
|
248
|
+
- Good: "Revenue increased 23% from $4.2M to $5.2M"
|
|
249
|
+
|
|
250
|
+
**Contextual**: Provide comparison points
|
|
251
|
+
- Bad: "We have 1,200 customers"
|
|
252
|
+
- Good: "Customer count grew 15% to 1,200, vs. 8% industry average"
|
|
253
|
+
|
|
254
|
+
**Actionable**: Suggest what to do
|
|
255
|
+
- Bad: "Conversion rate varies by channel"
|
|
256
|
+
- Good: "Email conversion is 2.3x higher than social - consider reallocating ad spend"
|
|
257
|
+
|
|
258
|
+
**Relevant**: Connect to business goals
|
|
259
|
+
- Bad: "The median is 45"
|
|
260
|
+
- Good: "Half of orders are under $45, suggesting opportunity for upselling"
|
|
261
|
+
|
|
262
|
+
## Workflow
|
|
263
|
+
|
|
264
|
+
1. **Receive data**: From a previous query or via `quarri_execute_sql`
|
|
265
|
+
2. **Identify analysis type**: Based on data shape and user question
|
|
266
|
+
3. **Perform statistical analysis**: Run appropriate calculations
|
|
267
|
+
4. **Generate insights**: Interpret results in business context
|
|
268
|
+
5. **Prioritize findings**: Rank by impact, actionability, urgency
|
|
269
|
+
6. **Frame recommendations**: Suggest specific actions
|
|
270
|
+
|
|
271
|
+
## Output Format
|
|
272
|
+
|
|
273
|
+
```markdown
|
|
274
|
+
## Analysis: [Data Description]
|
|
275
|
+
|
|
276
|
+
### Data Overview
|
|
277
|
+
- Rows: [count]
|
|
278
|
+
- Numeric columns: [list]
|
|
279
|
+
- Categorical columns: [list]
|
|
280
|
+
- Date range: [if applicable]
|
|
281
|
+
|
|
282
|
+
### Statistical Findings
|
|
283
|
+
|
|
284
|
+
#### Descriptive Statistics
|
|
285
|
+
| Metric | Value |
|
|
286
|
+
|--------|-------|
|
|
287
|
+
| Mean | X |
|
|
288
|
+
| Median | Y |
|
|
289
|
+
| Std | Z |
|
|
290
|
+
|
|
291
|
+
#### Key Patterns
|
|
292
|
+
- [Pattern 1 with numbers]
|
|
293
|
+
- [Pattern 2 with numbers]
|
|
294
|
+
- [Pattern 3 with numbers]
|
|
295
|
+
|
|
296
|
+
### Business Insights
|
|
297
|
+
|
|
298
|
+
#### Key Finding
|
|
299
|
+
[The single most important insight - bolded and specific]
|
|
300
|
+
|
|
301
|
+
#### Insights
|
|
302
|
+
|
|
303
|
+
**1. [Category]: [Insight Title]**
|
|
304
|
+
[Specific insight with numbers]
|
|
305
|
+
- **Implication**: [What this means]
|
|
306
|
+
- **Recommended Action**: [What to do]
|
|
307
|
+
|
|
308
|
+
**2. [Category]: [Insight Title]**
|
|
309
|
+
[Specific insight with numbers]
|
|
310
|
+
- **Implication**: [What this means]
|
|
311
|
+
- **Recommended Action**: [What to do]
|
|
312
|
+
|
|
313
|
+
### Risks to Monitor
|
|
314
|
+
- [Risk 1 with trigger condition]
|
|
315
|
+
- [Risk 2 with trigger condition]
|
|
316
|
+
|
|
317
|
+
### Recommended Next Steps
|
|
318
|
+
1. [Action 1]
|
|
319
|
+
2. [Action 2]
|
|
320
|
+
3. [Suggested follow-up analysis]
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Integration
|
|
324
|
+
|
|
325
|
+
This skill works well with:
|
|
326
|
+
- `/quarri-query`: Get data first, then analyze
|
|
327
|
+
- `/quarri-chart`: Visualize statistical findings
|
|
328
|
+
- `/quarri-analyze`: Called as part of the full analysis pipeline
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Define business metrics and build metric trees for KPI decomposition
|
|
3
|
+
globs:
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /quarri-metric - Metric Definition & Metric Trees
|
|
8
|
+
|
|
9
|
+
Define new business metrics and build metric trees that decompose KPIs into component drivers for root cause analysis.
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
Use `/quarri-metric` when users want to:
|
|
14
|
+
- Create metrics: "Create a metric for customer lifetime value"
|
|
15
|
+
- Define KPIs: "Define a retention rate metric"
|
|
16
|
+
- Build metric trees: "Decompose revenue into its drivers"
|
|
17
|
+
- Understand relationships: "What metrics drive conversion rate?"
|
|
18
|
+
|
|
19
|
+
## Part 1: Metric Definition
|
|
20
|
+
|
|
21
|
+
### Step 1: Understand the Metric
|
|
22
|
+
|
|
23
|
+
Gather these details through conversation:
|
|
24
|
+
|
|
25
|
+
1. **Name**: What should this metric be called?
|
|
26
|
+
- Use clear, business-friendly names
|
|
27
|
+
- Examples: "Monthly Recurring Revenue", "Customer Churn Rate"
|
|
28
|
+
|
|
29
|
+
2. **Description**: What does it measure and why is it important?
|
|
30
|
+
|
|
31
|
+
3. **Calculation**: How is it computed?
|
|
32
|
+
- What's the formula?
|
|
33
|
+
- What columns are involved?
|
|
34
|
+
|
|
35
|
+
4. **Dimensions**: How can it be broken down?
|
|
36
|
+
- By region, product, customer segment?
|
|
37
|
+
- Time granularity (daily, monthly, yearly)?
|
|
38
|
+
|
|
39
|
+
5. **Synonyms**: What else might users call this?
|
|
40
|
+
- "MRR" for "Monthly Recurring Revenue"
|
|
41
|
+
- "AOV" for "Average Order Value"
|
|
42
|
+
|
|
43
|
+
### Step 2: Map to Schema
|
|
44
|
+
|
|
45
|
+
1. Fetch schema using `quarri_get_schema`
|
|
46
|
+
2. Identify relevant tables and columns
|
|
47
|
+
3. Validate column names and types
|
|
48
|
+
|
|
49
|
+
### Step 3: Write SQL Template
|
|
50
|
+
|
|
51
|
+
```sql
|
|
52
|
+
-- Template with placeholders for dimensions
|
|
53
|
+
SELECT
|
|
54
|
+
{dimension_columns},
|
|
55
|
+
SUM(order_total) / COUNT(DISTINCT customer_id) as average_order_value
|
|
56
|
+
FROM quarri.bridge
|
|
57
|
+
WHERE {filter_conditions}
|
|
58
|
+
GROUP BY {dimension_columns}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Step 4: Save
|
|
62
|
+
|
|
63
|
+
Create the metric using `quarri_create_metric`:
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"name": "Average Order Value",
|
|
67
|
+
"description": "Average revenue per order",
|
|
68
|
+
"sql_template": "SELECT SUM(order_total) / COUNT(*) as aov FROM quarri.bridge",
|
|
69
|
+
"dimensions": ["region", "product_category", "month"]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Part 2: Metric Trees
|
|
74
|
+
|
|
75
|
+
Metric trees decompose a top-level KPI into its component drivers, enabling systematic root cause analysis.
|
|
76
|
+
|
|
77
|
+
### What is a Metric Tree?
|
|
78
|
+
|
|
79
|
+
A metric tree shows how a high-level metric breaks down into component parts:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
Revenue
|
|
83
|
+
├── = Customers × Orders/Customer × Revenue/Order
|
|
84
|
+
│
|
|
85
|
+
├── Customers
|
|
86
|
+
│ ├── New Customers (acquisition)
|
|
87
|
+
│ └── Returning Customers (retention)
|
|
88
|
+
│
|
|
89
|
+
├── Orders per Customer (frequency)
|
|
90
|
+
│ ├── Purchase occasions
|
|
91
|
+
│ └── Repeat purchase rate
|
|
92
|
+
│
|
|
93
|
+
└── Revenue per Order (basket size)
|
|
94
|
+
├── Units per order
|
|
95
|
+
├── Price per unit
|
|
96
|
+
└── Discount rate
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Common Metric Tree Patterns
|
|
100
|
+
|
|
101
|
+
#### E-Commerce Revenue Tree
|
|
102
|
+
```
|
|
103
|
+
Revenue = Traffic × Conversion Rate × Average Order Value
|
|
104
|
+
|
|
105
|
+
├── Traffic
|
|
106
|
+
│ ├── Organic (SEO, direct)
|
|
107
|
+
│ ├── Paid (ads, affiliates)
|
|
108
|
+
│ └── Referral (social, email)
|
|
109
|
+
│
|
|
110
|
+
├── Conversion Rate
|
|
111
|
+
│ ├── Add-to-cart rate
|
|
112
|
+
│ ├── Cart-to-checkout rate
|
|
113
|
+
│ └── Checkout completion rate
|
|
114
|
+
│
|
|
115
|
+
└── Average Order Value
|
|
116
|
+
├── Items per order
|
|
117
|
+
└── Price per item
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### SaaS Revenue Tree
|
|
121
|
+
```
|
|
122
|
+
MRR = Customers × ARPU
|
|
123
|
+
|
|
124
|
+
├── Customers
|
|
125
|
+
│ ├── New MRR (new customers)
|
|
126
|
+
│ ├── Expansion MRR (upgrades)
|
|
127
|
+
│ ├── Contraction MRR (downgrades)
|
|
128
|
+
│ └── Churned MRR (cancellations)
|
|
129
|
+
│
|
|
130
|
+
└── ARPU (Average Revenue Per User)
|
|
131
|
+
├── Plan mix
|
|
132
|
+
├── Add-on adoption
|
|
133
|
+
└── Usage-based fees
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Customer Lifetime Value Tree
|
|
137
|
+
```
|
|
138
|
+
CLV = ARPU × Avg Lifetime × Gross Margin
|
|
139
|
+
|
|
140
|
+
├── ARPU (Average Revenue Per User)
|
|
141
|
+
│ ├── Base subscription
|
|
142
|
+
│ └── Additional services
|
|
143
|
+
│
|
|
144
|
+
├── Average Customer Lifetime
|
|
145
|
+
│ ├── 1 / Churn Rate
|
|
146
|
+
│ └── Retention by cohort
|
|
147
|
+
│
|
|
148
|
+
└── Gross Margin
|
|
149
|
+
├── Revenue
|
|
150
|
+
└── Cost of goods/service
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Sales Pipeline Tree
|
|
154
|
+
```
|
|
155
|
+
Revenue = Leads × Conversion Rate × Deal Size
|
|
156
|
+
|
|
157
|
+
├── Leads
|
|
158
|
+
│ ├── Marketing Qualified (MQL)
|
|
159
|
+
│ ├── Sales Qualified (SQL)
|
|
160
|
+
│ └── Opportunity Created
|
|
161
|
+
│
|
|
162
|
+
├── Conversion Rate
|
|
163
|
+
│ ├── MQL → SQL rate
|
|
164
|
+
│ ├── SQL → Opportunity rate
|
|
165
|
+
│ ├── Opportunity → Proposal rate
|
|
166
|
+
│ └── Proposal → Close rate
|
|
167
|
+
│
|
|
168
|
+
└── Deal Size
|
|
169
|
+
├── Contract value
|
|
170
|
+
├── Upsell/cross-sell
|
|
171
|
+
└── Discounting
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Building a Metric Tree
|
|
175
|
+
|
|
176
|
+
#### Step 1: Identify the Top-Level Metric
|
|
177
|
+
- What KPI are you trying to understand or improve?
|
|
178
|
+
- Example: "Revenue", "Conversion Rate", "Retention"
|
|
179
|
+
|
|
180
|
+
#### Step 2: Find the Mathematical Relationship
|
|
181
|
+
Express the metric as a formula:
|
|
182
|
+
- **Multiplicative**: Revenue = Customers × ARPU
|
|
183
|
+
- **Additive**: Revenue = Product A + Product B + Product C
|
|
184
|
+
- **Ratio**: Conversion = Conversions / Visitors
|
|
185
|
+
|
|
186
|
+
#### Step 3: Decompose Each Component
|
|
187
|
+
For each component, ask: "What drives this?"
|
|
188
|
+
- Keep decomposing until you reach actionable metrics
|
|
189
|
+
- Stop when you reach metrics you can directly measure and influence
|
|
190
|
+
|
|
191
|
+
#### Step 4: Validate the Tree
|
|
192
|
+
- Components should be MECE (mutually exclusive, collectively exhaustive)
|
|
193
|
+
- Math should work: components should sum/multiply to parent
|
|
194
|
+
- Each leaf should be measurable in your data
|
|
195
|
+
|
|
196
|
+
### Using Metric Trees for Analysis
|
|
197
|
+
|
|
198
|
+
Once you have a metric tree, use it for:
|
|
199
|
+
|
|
200
|
+
**1. Performance Attribution**
|
|
201
|
+
When a metric changes, quantify how much each driver contributed:
|
|
202
|
+
```
|
|
203
|
+
Revenue dropped 10% ($100K → $90K)
|
|
204
|
+
|
|
205
|
+
Attribution:
|
|
206
|
+
├── Customer count: -5% impact ($5K)
|
|
207
|
+
├── Order frequency: -3% impact ($3K)
|
|
208
|
+
└── Average order value: -2% impact ($2K)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**2. Root Cause Analysis**
|
|
212
|
+
Drill into the largest impact driver:
|
|
213
|
+
```
|
|
214
|
+
Customer count dropped 5%
|
|
215
|
+
├── New customers: -8% (PRIMARY CAUSE)
|
|
216
|
+
│ └── Paid acquisition: -15%
|
|
217
|
+
│ └── Organic: +2%
|
|
218
|
+
└── Retention: +3%
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**3. Opportunity Sizing**
|
|
222
|
+
Identify highest-leverage improvements:
|
|
223
|
+
```
|
|
224
|
+
If we improve conversion rate by 10%:
|
|
225
|
+
├── Current: 2.5% conversion
|
|
226
|
+
├── Target: 2.75% conversion
|
|
227
|
+
└── Revenue impact: +$50K/month
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### SQL for Metric Tree Analysis
|
|
231
|
+
|
|
232
|
+
Generate SQL that calculates all components of a metric tree:
|
|
233
|
+
|
|
234
|
+
```sql
|
|
235
|
+
-- Revenue metric tree components
|
|
236
|
+
WITH metrics AS (
|
|
237
|
+
SELECT
|
|
238
|
+
period,
|
|
239
|
+
COUNT(DISTINCT customer_id) as customers,
|
|
240
|
+
COUNT(*) as orders,
|
|
241
|
+
SUM(revenue) as revenue,
|
|
242
|
+
-- Derived metrics
|
|
243
|
+
COUNT(*)::float / COUNT(DISTINCT customer_id) as orders_per_customer,
|
|
244
|
+
SUM(revenue)::float / COUNT(*) as revenue_per_order
|
|
245
|
+
FROM quarri.bridge
|
|
246
|
+
WHERE order_date >= DATE '2024-01-01'
|
|
247
|
+
GROUP BY period
|
|
248
|
+
)
|
|
249
|
+
SELECT
|
|
250
|
+
period,
|
|
251
|
+
customers,
|
|
252
|
+
orders_per_customer,
|
|
253
|
+
revenue_per_order,
|
|
254
|
+
revenue,
|
|
255
|
+
-- Verify: customers * orders_per_customer * revenue_per_order ≈ revenue
|
|
256
|
+
customers * orders_per_customer * revenue_per_order as calculated_revenue
|
|
257
|
+
FROM metrics
|
|
258
|
+
ORDER BY period;
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Period-over-Period Comparison
|
|
262
|
+
|
|
263
|
+
```sql
|
|
264
|
+
-- Compare current vs previous period for root cause analysis
|
|
265
|
+
WITH current_period AS (
|
|
266
|
+
SELECT
|
|
267
|
+
COUNT(DISTINCT customer_id) as customers,
|
|
268
|
+
COUNT(*)::float / COUNT(DISTINCT customer_id) as frequency,
|
|
269
|
+
SUM(revenue)::float / COUNT(*) as aov,
|
|
270
|
+
SUM(revenue) as revenue
|
|
271
|
+
FROM quarri.bridge
|
|
272
|
+
WHERE order_date >= DATE '2024-12-01'
|
|
273
|
+
),
|
|
274
|
+
previous_period AS (
|
|
275
|
+
SELECT
|
|
276
|
+
COUNT(DISTINCT customer_id) as customers,
|
|
277
|
+
COUNT(*)::float / COUNT(DISTINCT customer_id) as frequency,
|
|
278
|
+
SUM(revenue)::float / COUNT(*) as aov,
|
|
279
|
+
SUM(revenue) as revenue
|
|
280
|
+
FROM quarri.bridge
|
|
281
|
+
WHERE order_date >= DATE '2024-11-01' AND order_date < DATE '2024-12-01'
|
|
282
|
+
)
|
|
283
|
+
SELECT
|
|
284
|
+
'Customers' as metric,
|
|
285
|
+
p.customers as previous,
|
|
286
|
+
c.customers as current,
|
|
287
|
+
(c.customers - p.customers)::float / p.customers * 100 as pct_change
|
|
288
|
+
FROM current_period c, previous_period p
|
|
289
|
+
UNION ALL
|
|
290
|
+
SELECT
|
|
291
|
+
'Frequency' as metric,
|
|
292
|
+
p.frequency as previous,
|
|
293
|
+
c.frequency as current,
|
|
294
|
+
(c.frequency - p.frequency)::float / p.frequency * 100 as pct_change
|
|
295
|
+
FROM current_period c, previous_period p
|
|
296
|
+
-- ... continue for all components
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Common Metric Patterns
|
|
300
|
+
|
|
301
|
+
### Simple Aggregation
|
|
302
|
+
```sql
|
|
303
|
+
-- Total Revenue
|
|
304
|
+
SELECT SUM(revenue) as total_revenue FROM quarri.bridge
|
|
305
|
+
|
|
306
|
+
-- Order Count
|
|
307
|
+
SELECT COUNT(*) as order_count FROM quarri.bridge
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Ratio Metrics
|
|
311
|
+
```sql
|
|
312
|
+
-- Conversion Rate
|
|
313
|
+
SELECT
|
|
314
|
+
COUNT(CASE WHEN status = 'completed' THEN 1 END)::float /
|
|
315
|
+
COUNT(*) as conversion_rate
|
|
316
|
+
FROM orders
|
|
317
|
+
|
|
318
|
+
-- Gross Margin
|
|
319
|
+
SELECT
|
|
320
|
+
(SUM(revenue) - SUM(cost)) / SUM(revenue) as gross_margin
|
|
321
|
+
FROM quarri.bridge
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Time-Based Metrics
|
|
325
|
+
```sql
|
|
326
|
+
-- Monthly Recurring Revenue
|
|
327
|
+
SELECT
|
|
328
|
+
DATE_TRUNC('month', subscription_date) as month,
|
|
329
|
+
SUM(monthly_amount) as mrr
|
|
330
|
+
FROM subscriptions
|
|
331
|
+
WHERE status = 'active'
|
|
332
|
+
GROUP BY month
|
|
333
|
+
|
|
334
|
+
-- Year-over-Year Growth
|
|
335
|
+
SELECT
|
|
336
|
+
current.period,
|
|
337
|
+
(current.revenue - previous.revenue) / previous.revenue as yoy_growth
|
|
338
|
+
FROM (...) current
|
|
339
|
+
JOIN (...) previous ON current.period = previous.period + INTERVAL '1 year'
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Customer Metrics
|
|
343
|
+
```sql
|
|
344
|
+
-- Customer Lifetime Value
|
|
345
|
+
SELECT
|
|
346
|
+
customer_id,
|
|
347
|
+
SUM(order_total) as lifetime_value,
|
|
348
|
+
COUNT(*) as order_count,
|
|
349
|
+
MIN(order_date) as first_order,
|
|
350
|
+
MAX(order_date) as last_order
|
|
351
|
+
FROM orders
|
|
352
|
+
GROUP BY customer_id
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Output Format
|
|
356
|
+
|
|
357
|
+
```markdown
|
|
358
|
+
## Metric Definition: [Metric Name]
|
|
359
|
+
|
|
360
|
+
### Summary
|
|
361
|
+
**Name**: [Metric Name]
|
|
362
|
+
**Description**: [What it measures]
|
|
363
|
+
**Synonyms**: [Alternative names]
|
|
364
|
+
|
|
365
|
+
### Calculation
|
|
366
|
+
[Plain English explanation of the formula]
|
|
367
|
+
|
|
368
|
+
### SQL Template
|
|
369
|
+
```sql
|
|
370
|
+
[SQL code]
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Dimensions
|
|
374
|
+
- [Dimension 1]: [Description]
|
|
375
|
+
- [Dimension 2]: [Description]
|
|
376
|
+
|
|
377
|
+
### Metric Tree (if applicable)
|
|
378
|
+
```
|
|
379
|
+
[Top-level metric]
|
|
380
|
+
├── [Component 1]
|
|
381
|
+
│ ├── [Sub-component 1a]
|
|
382
|
+
│ └── [Sub-component 1b]
|
|
383
|
+
├── [Component 2]
|
|
384
|
+
└── [Component 3]
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Validation Results
|
|
388
|
+
- Query executed successfully
|
|
389
|
+
- Sample result: [Sample value]
|
|
390
|
+
|
|
391
|
+
### Status
|
|
392
|
+
[Ready to save / Needs revision]
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Integration
|
|
396
|
+
|
|
397
|
+
After creating a metric:
|
|
398
|
+
- Use with `/quarri-query` for natural language queries
|
|
399
|
+
- Reference in `/quarri-analyze` for comprehensive analysis
|
|
400
|
+
- Use `/quarri-diagnose` for root cause analysis with metric trees
|