kanmi-perf-revenue 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +251 -0
- package/dist/empirical/conversion-curve.d.ts +95 -0
- package/dist/empirical/conversion-curve.d.ts.map +1 -0
- package/dist/empirical/conversion-curve.js +240 -0
- package/dist/empirical/conversion-curve.js.map +1 -0
- package/dist/empirical/data-import.d.ts +59 -0
- package/dist/empirical/data-import.d.ts.map +1 -0
- package/dist/empirical/data-import.js +186 -0
- package/dist/empirical/data-import.js.map +1 -0
- package/dist/empirical/datadog-session-query.d.ts +66 -0
- package/dist/empirical/datadog-session-query.d.ts.map +1 -0
- package/dist/empirical/datadog-session-query.js +260 -0
- package/dist/empirical/datadog-session-query.js.map +1 -0
- package/dist/empirical/index.d.ts +109 -0
- package/dist/empirical/index.d.ts.map +1 -0
- package/dist/empirical/index.js +267 -0
- package/dist/empirical/index.js.map +1 -0
- package/dist/empirical/opportunity-calculator.d.ts +107 -0
- package/dist/empirical/opportunity-calculator.d.ts.map +1 -0
- package/dist/empirical/opportunity-calculator.js +238 -0
- package/dist/empirical/opportunity-calculator.js.map +1 -0
- package/dist/empirical/report.d.ts +37 -0
- package/dist/empirical/report.d.ts.map +1 -0
- package/dist/empirical/report.js +264 -0
- package/dist/empirical/report.js.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Performance Revenue Intelligence Engine
|
|
2
|
+
|
|
3
|
+
An autonomous CLI tool that connects web performance data (Datadog RUM) with business outcomes (GA4) to quantify revenue impact and prioritize fixes.
|
|
4
|
+
|
|
5
|
+
**One question answered:** *"What is this performance issue costing the business?"*
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Install
|
|
11
|
+
npm install -g @perf-revenue/engine
|
|
12
|
+
|
|
13
|
+
# Initialize configuration
|
|
14
|
+
perf-revenue config init
|
|
15
|
+
|
|
16
|
+
# Add a client
|
|
17
|
+
perf-revenue config add-client \
|
|
18
|
+
--name acme \
|
|
19
|
+
--display-name "Acme Corp" \
|
|
20
|
+
--ga4-project my-gcp-project \
|
|
21
|
+
--ga4-property 123456789 \
|
|
22
|
+
--datadog-app rum-app-id
|
|
23
|
+
|
|
24
|
+
# Set credentials
|
|
25
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
26
|
+
export DATADOG_API_KEY=...
|
|
27
|
+
export DATADOG_APP_KEY=...
|
|
28
|
+
|
|
29
|
+
# Run autonomous analysis
|
|
30
|
+
perf-revenue analyze --client acme -v
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## How It Works
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
┌─────────────────────────────────────────────────────┐
|
|
37
|
+
│ perf-revenue analyze --client acme │
|
|
38
|
+
└─────────────────────────────────────────────────────┘
|
|
39
|
+
│
|
|
40
|
+
▼
|
|
41
|
+
┌─────────────────────────────────────────────────────┐
|
|
42
|
+
│ Claude Agent (Autonomous) │
|
|
43
|
+
│ ├── Tool: BigQuery (fetch GA4 business data) │
|
|
44
|
+
│ ├── Tool: Datadog API (fetch RUM performance) │
|
|
45
|
+
│ ├── Tool: Analysis Engine (calculate impact) │
|
|
46
|
+
│ └── Tool: Report Generator (format output) │
|
|
47
|
+
└─────────────────────────────────────────────────────┘
|
|
48
|
+
│
|
|
49
|
+
▼
|
|
50
|
+
┌─────────────────────────────────────────────────────┐
|
|
51
|
+
│ Executive Report (markdown) │
|
|
52
|
+
│ • $47K/month revenue opportunity │
|
|
53
|
+
│ • Prioritized by financial impact │
|
|
54
|
+
│ • CFO-ready format │
|
|
55
|
+
└─────────────────────────────────────────────────────┘
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- **Autonomous Analysis** - Claude fetches, analyzes, and reports without manual intervention
|
|
61
|
+
- **Financial-First** - Revenue impact in dollars, not performance scores
|
|
62
|
+
- **Conservative Estimates** - Underestimates by design, defensible assumptions
|
|
63
|
+
- **Executive Output** - VP/Director/CFO-ready reports
|
|
64
|
+
|
|
65
|
+
## CLI Commands
|
|
66
|
+
|
|
67
|
+
### `perf-revenue analyze`
|
|
68
|
+
|
|
69
|
+
Run autonomous revenue impact analysis.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
perf-revenue analyze \
|
|
73
|
+
--client <name> # Client name (required)
|
|
74
|
+
--start <YYYY-MM-DD> # Start date (default: 14 days ago)
|
|
75
|
+
--end <YYYY-MM-DD> # End date (default: yesterday)
|
|
76
|
+
--output <path> # Output file path
|
|
77
|
+
--verbose # Show agent activity
|
|
78
|
+
--max-iterations <n> # Max agent iterations (default: 20)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `perf-revenue config`
|
|
82
|
+
|
|
83
|
+
Manage configuration.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Initialize config directory
|
|
87
|
+
perf-revenue config init
|
|
88
|
+
|
|
89
|
+
# Show current configuration
|
|
90
|
+
perf-revenue config show
|
|
91
|
+
|
|
92
|
+
# Add a new client
|
|
93
|
+
perf-revenue config add-client \
|
|
94
|
+
--name <id> # Client identifier
|
|
95
|
+
--display-name <name> # Display name
|
|
96
|
+
--ga4-project <id> # GCP project ID
|
|
97
|
+
--ga4-property <id> # GA4 property ID
|
|
98
|
+
--ga4-key-file <path> # Service account JSON (optional)
|
|
99
|
+
--datadog-app <id> # Datadog RUM app ID
|
|
100
|
+
--datadog-site <site> # Datadog site (default: datadoghq.com)
|
|
101
|
+
|
|
102
|
+
# Remove a client
|
|
103
|
+
perf-revenue config remove-client <name>
|
|
104
|
+
|
|
105
|
+
# List clients
|
|
106
|
+
perf-revenue config list-clients
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `perf-revenue validate`
|
|
110
|
+
|
|
111
|
+
Validate configuration and connectivity.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
perf-revenue validate # Validate all
|
|
115
|
+
perf-revenue validate -c <client> # Validate specific client
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Configuration
|
|
119
|
+
|
|
120
|
+
Configuration is stored in `~/.perf-revenue/`:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
~/.perf-revenue/
|
|
124
|
+
├── config.json # Client configurations
|
|
125
|
+
└── .env # API keys (optional)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Environment Variables
|
|
129
|
+
|
|
130
|
+
| Variable | Required | Description |
|
|
131
|
+
|----------|----------|-------------|
|
|
132
|
+
| `ANTHROPIC_API_KEY` | Yes | Claude API key |
|
|
133
|
+
| `DATADOG_API_KEY` | Yes | Datadog API key |
|
|
134
|
+
| `DATADOG_APP_KEY` | Yes | Datadog application key |
|
|
135
|
+
| `GOOGLE_APPLICATION_CREDENTIALS` | No | Path to GCP service account JSON |
|
|
136
|
+
|
|
137
|
+
## Conversion Lift Model
|
|
138
|
+
|
|
139
|
+
Conservative estimates based on industry research:
|
|
140
|
+
|
|
141
|
+
| Improvement | Conversion Lift |
|
|
142
|
+
|-------------|-----------------|
|
|
143
|
+
| 100ms LCP improvement | +0.5% conversion rate |
|
|
144
|
+
| 100ms INP improvement | +0.3% conversion rate |
|
|
145
|
+
| 0.01 CLS improvement | +0.1% conversion rate |
|
|
146
|
+
|
|
147
|
+
## Output Format
|
|
148
|
+
|
|
149
|
+
Reports follow a strict executive-friendly structure:
|
|
150
|
+
|
|
151
|
+
1. **Executive Summary** - Revenue impact in one paragraph
|
|
152
|
+
2. **Revenue Impact Analysis** - Detailed breakdown per issue
|
|
153
|
+
3. **Prioritized Fix Order** - Ranked by financial impact
|
|
154
|
+
4. **What Not To Fix** - Issues below threshold
|
|
155
|
+
5. **Decision Guidance** - Actionable recommendations
|
|
156
|
+
|
|
157
|
+
## Library Usage
|
|
158
|
+
|
|
159
|
+
Can also be used programmatically:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import {
|
|
163
|
+
runAnalysis,
|
|
164
|
+
createGA4Adapter,
|
|
165
|
+
createDatadogAdapter,
|
|
166
|
+
} from '@perf-revenue/engine';
|
|
167
|
+
|
|
168
|
+
// Create adapters
|
|
169
|
+
const ga4 = createGA4Adapter({
|
|
170
|
+
projectId: 'my-project',
|
|
171
|
+
propertyId: '123456789',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const datadog = createDatadogAdapter({
|
|
175
|
+
site: 'datadoghq.com',
|
|
176
|
+
applicationId: 'rum-app-id',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Fetch and analyze
|
|
180
|
+
const ga4Data = await fetchGA4Data(ga4);
|
|
181
|
+
const datadogData = await fetchDatadogData(datadog);
|
|
182
|
+
const result = runAnalysis(ga4Data, datadogData, siteTotals);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Data Sources
|
|
186
|
+
|
|
187
|
+
### GA4 (BigQuery Export)
|
|
188
|
+
|
|
189
|
+
- Sessions, conversions, revenue by page type + device
|
|
190
|
+
- Funnel drop-off analysis
|
|
191
|
+
- Conversion path journeys
|
|
192
|
+
|
|
193
|
+
### Datadog RUM
|
|
194
|
+
|
|
195
|
+
- Core Web Vitals (LCP, INP, CLS) at p75
|
|
196
|
+
- Performance band distribution
|
|
197
|
+
- Frustration signals
|
|
198
|
+
|
|
199
|
+
## Correlation
|
|
200
|
+
|
|
201
|
+
Data is joined on correlation keys:
|
|
202
|
+
- Date (YYYY-MM-DD)
|
|
203
|
+
- Page type (checkout, pdp, plp, etc.)
|
|
204
|
+
- Device category (mobile, desktop, tablet)
|
|
205
|
+
|
|
206
|
+
Join is **probabilistic** (aggregate dimensions), not deterministic.
|
|
207
|
+
|
|
208
|
+
## Development
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Install dependencies
|
|
212
|
+
npm install
|
|
213
|
+
|
|
214
|
+
# Build
|
|
215
|
+
npm run build
|
|
216
|
+
|
|
217
|
+
# Type check
|
|
218
|
+
npm run typecheck
|
|
219
|
+
|
|
220
|
+
# Run CLI locally
|
|
221
|
+
npm run cli -- analyze --client test -v
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Project Structure
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
src/
|
|
228
|
+
├── adapters/ # GA4 + Datadog data adapters
|
|
229
|
+
│ ├── ga4-adapter.ts
|
|
230
|
+
│ ├── datadog-adapter.ts
|
|
231
|
+
│ └── correlation-spec.ts
|
|
232
|
+
├── engine/ # Core analysis logic
|
|
233
|
+
│ ├── orchestrator.ts
|
|
234
|
+
│ ├── assisted-attribution.ts
|
|
235
|
+
│ └── period-utils.ts
|
|
236
|
+
├── schema/ # Type definitions
|
|
237
|
+
├── cli/ # Autonomous CLI
|
|
238
|
+
│ ├── index.ts # Entry point
|
|
239
|
+
│ ├── agent.ts # Claude agent
|
|
240
|
+
│ ├── config.ts # Configuration
|
|
241
|
+
│ └── tools/ # Agent tools
|
|
242
|
+
└── prompts/ # System prompts
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT
|
|
248
|
+
|
|
249
|
+
## Author
|
|
250
|
+
|
|
251
|
+
Kanmi Obasa <i@kanmiobasa.com>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Empirical Conversion Curve Builder
|
|
3
|
+
*
|
|
4
|
+
* Builds conversion rate curves from actual session data.
|
|
5
|
+
* This is the core differentiator from coefficient-based models:
|
|
6
|
+
* we measure CVR at each performance bucket from YOUR data.
|
|
7
|
+
*
|
|
8
|
+
* @author Kanmi Obasa <i@kanmiobasa.com>
|
|
9
|
+
*/
|
|
10
|
+
import type { SessionData } from './datadog-session-query.js';
|
|
11
|
+
export interface ConversionBucket {
|
|
12
|
+
/** Bucket range (e.g., "0-1000" for 0-1s LCP) */
|
|
13
|
+
range: string;
|
|
14
|
+
/** Lower bound in ms */
|
|
15
|
+
lower_ms: number;
|
|
16
|
+
/** Upper bound in ms (Infinity for last bucket) */
|
|
17
|
+
upper_ms: number;
|
|
18
|
+
/** Number of sessions in this bucket */
|
|
19
|
+
sessions: number;
|
|
20
|
+
/** Number of conversions in this bucket */
|
|
21
|
+
conversions: number;
|
|
22
|
+
/** Measured conversion rate (conversions / sessions) */
|
|
23
|
+
conversion_rate: number;
|
|
24
|
+
/** Total revenue from conversions in this bucket */
|
|
25
|
+
revenue: number;
|
|
26
|
+
/** Average order value in this bucket */
|
|
27
|
+
aov: number;
|
|
28
|
+
/** Statistical confidence (based on sample size) */
|
|
29
|
+
confidence: 'high' | 'medium' | 'low';
|
|
30
|
+
}
|
|
31
|
+
export interface ConversionCurve {
|
|
32
|
+
/** The metric being measured (lcp, inp, cls) */
|
|
33
|
+
metric: 'lcp' | 'inp' | 'cls';
|
|
34
|
+
/** Buckets from fastest to slowest */
|
|
35
|
+
buckets: ConversionBucket[];
|
|
36
|
+
/** Summary statistics */
|
|
37
|
+
summary: {
|
|
38
|
+
total_sessions: number;
|
|
39
|
+
total_conversions: number;
|
|
40
|
+
overall_cvr: number;
|
|
41
|
+
total_revenue: number;
|
|
42
|
+
overall_aov: number;
|
|
43
|
+
/** CVR delta between fastest and slowest bucket */
|
|
44
|
+
cvr_range: number;
|
|
45
|
+
/** Relative CVR drop from fast to slow (e.g., 0.35 = 35% lower) */
|
|
46
|
+
relative_cvr_drop: number;
|
|
47
|
+
};
|
|
48
|
+
/** Core Web Vitals thresholds for reference */
|
|
49
|
+
thresholds: {
|
|
50
|
+
good: number;
|
|
51
|
+
needs_improvement: number;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export interface CurveBuilderConfig {
|
|
55
|
+
/** Custom bucket boundaries in ms. Default: CWV thresholds */
|
|
56
|
+
bucketBoundaries?: number[];
|
|
57
|
+
/** Minimum sessions per bucket for high confidence. Default: 100 */
|
|
58
|
+
minSessionsHighConfidence?: number;
|
|
59
|
+
/** Minimum sessions per bucket for medium confidence. Default: 30 */
|
|
60
|
+
minSessionsMediumConfidence?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build an empirical conversion curve from session data.
|
|
64
|
+
*
|
|
65
|
+
* This is the key function: it takes raw session data and produces
|
|
66
|
+
* a measured CVR at each performance bucket.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildConversionCurve(sessions: SessionData[], metric: 'lcp' | 'inp' | 'cls', config?: CurveBuilderConfig): ConversionCurve;
|
|
69
|
+
/**
|
|
70
|
+
* Build curves for all CWV metrics at once.
|
|
71
|
+
*/
|
|
72
|
+
export declare function buildAllCurves(sessions: SessionData[], config?: CurveBuilderConfig): {
|
|
73
|
+
lcp: ConversionCurve;
|
|
74
|
+
inp: ConversionCurve;
|
|
75
|
+
cls: ConversionCurve;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Build curves segmented by device or page type.
|
|
79
|
+
*/
|
|
80
|
+
export declare function buildSegmentedCurves(sessions: SessionData[], segmentBy: 'device' | 'page_type', metric: 'lcp' | 'inp' | 'cls', config?: CurveBuilderConfig): Map<string, ConversionCurve>;
|
|
81
|
+
/**
|
|
82
|
+
* Find the inflection point where CVR starts dropping significantly.
|
|
83
|
+
*/
|
|
84
|
+
export declare function findInflectionPoint(curve: ConversionCurve): {
|
|
85
|
+
bucket: ConversionBucket;
|
|
86
|
+
threshold_ms: number;
|
|
87
|
+
cvr_before: number;
|
|
88
|
+
cvr_after: number;
|
|
89
|
+
} | null;
|
|
90
|
+
/**
|
|
91
|
+
* Calculate the conversion rate at a specific metric value.
|
|
92
|
+
* Uses linear interpolation between buckets.
|
|
93
|
+
*/
|
|
94
|
+
export declare function getCvrAtValue(curve: ConversionCurve, value: number): number;
|
|
95
|
+
//# sourceMappingURL=conversion-curve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversion-curve.d.ts","sourceRoot":"","sources":["../../src/empirical/conversion-curve.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAM9D,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,eAAe,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,oDAAoD;IACpD,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;CACvC;AAED,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;IAC9B,sCAAsC;IACtC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,yBAAyB;IACzB,OAAO,EAAE;QACP,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,mDAAmD;QACnD,SAAS,EAAE,MAAM,CAAC;QAClB,mEAAmE;QACnE,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,+CAA+C;IAC/C,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;QACb,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,oEAAoE;IACpE,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,qEAAqE;IACrE,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC;AAgBD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,WAAW,EAAE,EACvB,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,EAC7B,MAAM,GAAE,kBAAuB,GAC9B,eAAe,CA0GjB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,WAAW,EAAE,EACvB,MAAM,GAAE,kBAAuB,GAC9B;IAAE,GAAG,EAAE,eAAe,CAAC;IAAC,GAAG,EAAE,eAAe,CAAC;IAAC,GAAG,EAAE,eAAe,CAAA;CAAE,CAMtE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,WAAW,EAAE,EACvB,SAAS,EAAE,QAAQ,GAAG,WAAW,EACjC,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,EAC7B,MAAM,GAAE,kBAAuB,GAC9B,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAiB9B;AAMD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG;IAC3D,MAAM,EAAE,gBAAgB,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,IAAI,CA0CP;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAS3E"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Empirical Conversion Curve Builder
|
|
3
|
+
*
|
|
4
|
+
* Builds conversion rate curves from actual session data.
|
|
5
|
+
* This is the core differentiator from coefficient-based models:
|
|
6
|
+
* we measure CVR at each performance bucket from YOUR data.
|
|
7
|
+
*
|
|
8
|
+
* @author Kanmi Obasa <i@kanmiobasa.com>
|
|
9
|
+
*/
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// CWV THRESHOLDS
|
|
12
|
+
// =============================================================================
|
|
13
|
+
const CWV_THRESHOLDS = {
|
|
14
|
+
lcp: { good: 2500, needsImprovement: 4000 },
|
|
15
|
+
inp: { good: 200, needsImprovement: 500 },
|
|
16
|
+
cls: { good: 0.1, needsImprovement: 0.25 },
|
|
17
|
+
};
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// CURVE BUILDER
|
|
20
|
+
// =============================================================================
|
|
21
|
+
/**
|
|
22
|
+
* Build an empirical conversion curve from session data.
|
|
23
|
+
*
|
|
24
|
+
* This is the key function: it takes raw session data and produces
|
|
25
|
+
* a measured CVR at each performance bucket.
|
|
26
|
+
*/
|
|
27
|
+
export function buildConversionCurve(sessions, metric, config = {}) {
|
|
28
|
+
const { minSessionsHighConfidence = 100, minSessionsMediumConfidence = 30, } = config;
|
|
29
|
+
// Get metric value extractor
|
|
30
|
+
const getValue = getMetricExtractor(metric);
|
|
31
|
+
// Filter to sessions with the metric
|
|
32
|
+
const validSessions = sessions.filter((s) => getValue(s) !== null);
|
|
33
|
+
// Determine bucket boundaries
|
|
34
|
+
const boundaries = config.bucketBoundaries || getDefaultBoundaries(metric);
|
|
35
|
+
// Build buckets
|
|
36
|
+
const bucketMap = new Map();
|
|
37
|
+
for (const boundary of boundaries) {
|
|
38
|
+
bucketMap.set(boundary, { sessions: [] });
|
|
39
|
+
}
|
|
40
|
+
bucketMap.set(Infinity, { sessions: [] }); // Catch-all for > last boundary
|
|
41
|
+
// Assign sessions to buckets
|
|
42
|
+
for (const session of validSessions) {
|
|
43
|
+
const value = getValue(session);
|
|
44
|
+
const bucket = findBucket(value, boundaries);
|
|
45
|
+
bucketMap.get(bucket).sessions.push(session);
|
|
46
|
+
}
|
|
47
|
+
// Calculate stats for each bucket
|
|
48
|
+
const buckets = [];
|
|
49
|
+
const sortedBoundaries = [...boundaries, Infinity].sort((a, b) => a - b);
|
|
50
|
+
let prevBoundary = 0;
|
|
51
|
+
for (const boundary of sortedBoundaries) {
|
|
52
|
+
const bucketSessions = bucketMap.get(boundary)?.sessions || [];
|
|
53
|
+
if (bucketSessions.length === 0) {
|
|
54
|
+
prevBoundary = boundary;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const conversions = bucketSessions.filter((s) => s.has_purchase).length;
|
|
58
|
+
const revenue = bucketSessions.reduce((sum, s) => sum + s.purchase_value, 0);
|
|
59
|
+
const cvr = bucketSessions.length > 0 ? conversions / bucketSessions.length : 0;
|
|
60
|
+
const aov = conversions > 0 ? revenue / conversions : 0;
|
|
61
|
+
// Determine confidence based on sample size
|
|
62
|
+
let confidence = 'low';
|
|
63
|
+
if (bucketSessions.length >= minSessionsHighConfidence) {
|
|
64
|
+
confidence = 'high';
|
|
65
|
+
}
|
|
66
|
+
else if (bucketSessions.length >= minSessionsMediumConfidence) {
|
|
67
|
+
confidence = 'medium';
|
|
68
|
+
}
|
|
69
|
+
buckets.push({
|
|
70
|
+
range: formatRange(prevBoundary, boundary, metric),
|
|
71
|
+
lower_ms: prevBoundary,
|
|
72
|
+
upper_ms: boundary,
|
|
73
|
+
sessions: bucketSessions.length,
|
|
74
|
+
conversions,
|
|
75
|
+
conversion_rate: cvr,
|
|
76
|
+
revenue,
|
|
77
|
+
aov,
|
|
78
|
+
confidence,
|
|
79
|
+
});
|
|
80
|
+
prevBoundary = boundary;
|
|
81
|
+
}
|
|
82
|
+
// Calculate summary
|
|
83
|
+
const totalSessions = validSessions.length;
|
|
84
|
+
const totalConversions = validSessions.filter((s) => s.has_purchase).length;
|
|
85
|
+
const totalRevenue = validSessions.reduce((sum, s) => sum + s.purchase_value, 0);
|
|
86
|
+
const overallCvr = totalSessions > 0 ? totalConversions / totalSessions : 0;
|
|
87
|
+
const overallAov = totalConversions > 0 ? totalRevenue / totalConversions : 0;
|
|
88
|
+
// CVR range and relative drop
|
|
89
|
+
const fastestBucket = buckets[0];
|
|
90
|
+
const slowestBucket = buckets[buckets.length - 1];
|
|
91
|
+
const cvrRange = fastestBucket && slowestBucket
|
|
92
|
+
? fastestBucket.conversion_rate - slowestBucket.conversion_rate
|
|
93
|
+
: 0;
|
|
94
|
+
const relativeCvrDrop = fastestBucket?.conversion_rate > 0
|
|
95
|
+
? cvrRange / fastestBucket.conversion_rate
|
|
96
|
+
: 0;
|
|
97
|
+
return {
|
|
98
|
+
metric,
|
|
99
|
+
buckets,
|
|
100
|
+
summary: {
|
|
101
|
+
total_sessions: totalSessions,
|
|
102
|
+
total_conversions: totalConversions,
|
|
103
|
+
overall_cvr: overallCvr,
|
|
104
|
+
total_revenue: totalRevenue,
|
|
105
|
+
overall_aov: overallAov,
|
|
106
|
+
cvr_range: cvrRange,
|
|
107
|
+
relative_cvr_drop: relativeCvrDrop,
|
|
108
|
+
},
|
|
109
|
+
thresholds: {
|
|
110
|
+
good: CWV_THRESHOLDS[metric].good,
|
|
111
|
+
needs_improvement: CWV_THRESHOLDS[metric].needsImprovement,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Build curves for all CWV metrics at once.
|
|
117
|
+
*/
|
|
118
|
+
export function buildAllCurves(sessions, config = {}) {
|
|
119
|
+
return {
|
|
120
|
+
lcp: buildConversionCurve(sessions, 'lcp', config),
|
|
121
|
+
inp: buildConversionCurve(sessions, 'inp', config),
|
|
122
|
+
cls: buildConversionCurve(sessions, 'cls', config),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Build curves segmented by device or page type.
|
|
127
|
+
*/
|
|
128
|
+
export function buildSegmentedCurves(sessions, segmentBy, metric, config = {}) {
|
|
129
|
+
const segments = new Map();
|
|
130
|
+
for (const session of sessions) {
|
|
131
|
+
const key = segmentBy === 'device' ? session.device : session.page_type;
|
|
132
|
+
if (!segments.has(key)) {
|
|
133
|
+
segments.set(key, []);
|
|
134
|
+
}
|
|
135
|
+
segments.get(key).push(session);
|
|
136
|
+
}
|
|
137
|
+
const curves = new Map();
|
|
138
|
+
for (const [segmentName, segmentSessions] of segments) {
|
|
139
|
+
curves.set(segmentName, buildConversionCurve(segmentSessions, metric, config));
|
|
140
|
+
}
|
|
141
|
+
return curves;
|
|
142
|
+
}
|
|
143
|
+
// =============================================================================
|
|
144
|
+
// CURVE ANALYSIS
|
|
145
|
+
// =============================================================================
|
|
146
|
+
/**
|
|
147
|
+
* Find the inflection point where CVR starts dropping significantly.
|
|
148
|
+
*/
|
|
149
|
+
export function findInflectionPoint(curve) {
|
|
150
|
+
if (curve.buckets.length < 2)
|
|
151
|
+
return null;
|
|
152
|
+
let maxDrop = 0;
|
|
153
|
+
let inflectionIndex = 0;
|
|
154
|
+
for (let i = 1; i < curve.buckets.length; i++) {
|
|
155
|
+
const prevBucket = curve.buckets[i - 1];
|
|
156
|
+
const currBucket = curve.buckets[i];
|
|
157
|
+
// Skip low-confidence buckets
|
|
158
|
+
if (prevBucket.confidence === 'low' || currBucket.confidence === 'low') {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const drop = prevBucket.conversion_rate - currBucket.conversion_rate;
|
|
162
|
+
if (drop > maxDrop) {
|
|
163
|
+
maxDrop = drop;
|
|
164
|
+
inflectionIndex = i;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (maxDrop === 0)
|
|
168
|
+
return null;
|
|
169
|
+
const inflectionBucket = curve.buckets[inflectionIndex];
|
|
170
|
+
const prevBuckets = curve.buckets.slice(0, inflectionIndex);
|
|
171
|
+
const afterBuckets = curve.buckets.slice(inflectionIndex);
|
|
172
|
+
const cvrBefore = prevBuckets.reduce((sum, b) => sum + b.conversion_rate * b.sessions, 0) /
|
|
173
|
+
prevBuckets.reduce((sum, b) => sum + b.sessions, 0);
|
|
174
|
+
const cvrAfter = afterBuckets.reduce((sum, b) => sum + b.conversion_rate * b.sessions, 0) /
|
|
175
|
+
afterBuckets.reduce((sum, b) => sum + b.sessions, 0);
|
|
176
|
+
return {
|
|
177
|
+
bucket: inflectionBucket,
|
|
178
|
+
threshold_ms: inflectionBucket.lower_ms,
|
|
179
|
+
cvr_before: cvrBefore,
|
|
180
|
+
cvr_after: cvrAfter,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Calculate the conversion rate at a specific metric value.
|
|
185
|
+
* Uses linear interpolation between buckets.
|
|
186
|
+
*/
|
|
187
|
+
export function getCvrAtValue(curve, value) {
|
|
188
|
+
for (const bucket of curve.buckets) {
|
|
189
|
+
if (value >= bucket.lower_ms && value < bucket.upper_ms) {
|
|
190
|
+
return bucket.conversion_rate;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Value is beyond all buckets, return last bucket's CVR
|
|
194
|
+
return curve.buckets[curve.buckets.length - 1]?.conversion_rate || 0;
|
|
195
|
+
}
|
|
196
|
+
// =============================================================================
|
|
197
|
+
// HELPERS
|
|
198
|
+
// =============================================================================
|
|
199
|
+
function getMetricExtractor(metric) {
|
|
200
|
+
switch (metric) {
|
|
201
|
+
case 'lcp':
|
|
202
|
+
return (s) => s.lcp_ms;
|
|
203
|
+
case 'inp':
|
|
204
|
+
return (s) => s.inp_ms;
|
|
205
|
+
case 'cls':
|
|
206
|
+
return (s) => (s.cls !== null ? s.cls * 1000 : null); // Scale CLS to ms-like for bucketing
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function getDefaultBoundaries(metric) {
|
|
210
|
+
switch (metric) {
|
|
211
|
+
case 'lcp':
|
|
212
|
+
// CWV thresholds + finer granularity
|
|
213
|
+
return [1000, 1500, 2000, 2500, 3000, 3500, 4000, 5000, 6000];
|
|
214
|
+
case 'inp':
|
|
215
|
+
return [50, 100, 150, 200, 300, 400, 500, 750];
|
|
216
|
+
case 'cls':
|
|
217
|
+
// CLS scaled by 1000 (so 0.1 = 100, 0.25 = 250)
|
|
218
|
+
return [25, 50, 75, 100, 150, 200, 250, 350];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function findBucket(value, boundaries) {
|
|
222
|
+
for (const boundary of boundaries.sort((a, b) => a - b)) {
|
|
223
|
+
if (value < boundary) {
|
|
224
|
+
return boundary;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return Infinity;
|
|
228
|
+
}
|
|
229
|
+
function formatRange(lower, upper, metric) {
|
|
230
|
+
if (metric === 'cls') {
|
|
231
|
+
// Convert back from scaled values
|
|
232
|
+
const lowerCls = (lower / 1000).toFixed(2);
|
|
233
|
+
const upperCls = upper === Infinity ? '∞' : (upper / 1000).toFixed(2);
|
|
234
|
+
return `${lowerCls}-${upperCls}`;
|
|
235
|
+
}
|
|
236
|
+
const lowerSec = (lower / 1000).toFixed(1);
|
|
237
|
+
const upperSec = upper === Infinity ? '∞' : (upper / 1000).toFixed(1);
|
|
238
|
+
return `${lowerSec}s-${upperSec}s`;
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=conversion-curve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversion-curve.js","sourceRoot":"","sources":["../../src/empirical/conversion-curve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Import
|
|
3
|
+
*
|
|
4
|
+
* Import session data from CSV/JSON files.
|
|
5
|
+
* Bring your own data from any source.
|
|
6
|
+
*
|
|
7
|
+
* Expected format:
|
|
8
|
+
* - session_id: string
|
|
9
|
+
* - lcp_ms: number (optional)
|
|
10
|
+
* - inp_ms: number (optional)
|
|
11
|
+
* - cls: number (optional)
|
|
12
|
+
* - converted: boolean | 0 | 1
|
|
13
|
+
* - order_value: number (optional, defaults to 0)
|
|
14
|
+
* - device: 'mobile' | 'desktop' | 'tablet' (optional)
|
|
15
|
+
* - page_type: string (optional)
|
|
16
|
+
*
|
|
17
|
+
* @author Kanmi Obasa <i@kanmiobasa.com>
|
|
18
|
+
*/
|
|
19
|
+
import type { QueryResult } from './datadog-session-query.js';
|
|
20
|
+
export interface RawSessionRow {
|
|
21
|
+
session_id?: string;
|
|
22
|
+
sessionId?: string;
|
|
23
|
+
id?: string;
|
|
24
|
+
lcp_ms?: number | string | null;
|
|
25
|
+
lcp?: number | string | null;
|
|
26
|
+
inp_ms?: number | string | null;
|
|
27
|
+
inp?: number | string | null;
|
|
28
|
+
cls?: number | string | null;
|
|
29
|
+
converted?: boolean | number | string;
|
|
30
|
+
has_purchase?: boolean | number | string;
|
|
31
|
+
purchased?: boolean | number | string;
|
|
32
|
+
order_value?: number | string;
|
|
33
|
+
purchase_value?: number | string;
|
|
34
|
+
revenue?: number | string;
|
|
35
|
+
value?: number | string;
|
|
36
|
+
device?: string;
|
|
37
|
+
device_type?: string;
|
|
38
|
+
page_type?: string;
|
|
39
|
+
page?: string;
|
|
40
|
+
timestamp?: string;
|
|
41
|
+
date?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface ImportOptions {
|
|
44
|
+
startDate?: string;
|
|
45
|
+
endDate?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Import sessions from a JSON file.
|
|
49
|
+
*/
|
|
50
|
+
export declare function importFromJson(filePath: string, options?: ImportOptions): QueryResult;
|
|
51
|
+
/**
|
|
52
|
+
* Import sessions from a CSV file.
|
|
53
|
+
*/
|
|
54
|
+
export declare function importFromCsv(filePath: string, options?: ImportOptions): QueryResult;
|
|
55
|
+
/**
|
|
56
|
+
* Import from file (auto-detect format).
|
|
57
|
+
*/
|
|
58
|
+
export declare function importFromFile(filePath: string, options?: ImportOptions): QueryResult;
|
|
59
|
+
//# sourceMappingURL=data-import.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-import.d.ts","sourceRoot":"","sources":["../../src/empirical/data-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAM3E,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACtC,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACzC,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,WAAW,CAQb;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,WAAW,CAwBb;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,WAAW,CAQb"}
|