kanmi-perf-revenue 1.0.0 → 1.2.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 +347 -173
- package/dist/empirical/ab-testing.d.ts +83 -0
- package/dist/empirical/ab-testing.d.ts.map +1 -0
- package/dist/empirical/ab-testing.js +281 -0
- package/dist/empirical/ab-testing.js.map +1 -0
- package/dist/empirical/alerting.d.ts +85 -0
- package/dist/empirical/alerting.d.ts.map +1 -0
- package/dist/empirical/alerting.js +358 -0
- package/dist/empirical/alerting.js.map +1 -0
- package/dist/empirical/attribution.d.ts +80 -0
- package/dist/empirical/attribution.d.ts.map +1 -0
- package/dist/empirical/attribution.js +305 -0
- package/dist/empirical/attribution.js.map +1 -0
- package/dist/empirical/cohort.d.ts +75 -0
- package/dist/empirical/cohort.d.ts.map +1 -0
- package/dist/empirical/cohort.js +305 -0
- package/dist/empirical/cohort.js.map +1 -0
- package/dist/empirical/conversion-curve.d.ts +7 -10
- package/dist/empirical/conversion-curve.d.ts.map +1 -1
- package/dist/empirical/conversion-curve.js +37 -4
- package/dist/empirical/conversion-curve.js.map +1 -1
- package/dist/empirical/correlation.d.ts +91 -0
- package/dist/empirical/correlation.d.ts.map +1 -0
- package/dist/empirical/correlation.js +461 -0
- package/dist/empirical/correlation.js.map +1 -0
- package/dist/empirical/data-import.d.ts +22 -0
- package/dist/empirical/data-import.d.ts.map +1 -1
- package/dist/empirical/data-import.js +44 -0
- package/dist/empirical/data-import.js.map +1 -1
- package/dist/empirical/datadog-product-analytics.d.ts +192 -0
- package/dist/empirical/datadog-product-analytics.d.ts.map +1 -0
- package/dist/empirical/datadog-product-analytics.js +632 -0
- package/dist/empirical/datadog-product-analytics.js.map +1 -0
- package/dist/empirical/datadog-session-query.d.ts +32 -0
- package/dist/empirical/datadog-session-query.d.ts.map +1 -1
- package/dist/empirical/datadog-session-query.js +238 -17
- package/dist/empirical/datadog-session-query.js.map +1 -1
- package/dist/empirical/engagement-analysis.d.ts +112 -0
- package/dist/empirical/engagement-analysis.d.ts.map +1 -0
- package/dist/empirical/engagement-analysis.js +354 -0
- package/dist/empirical/engagement-analysis.js.map +1 -0
- package/dist/empirical/export.d.ts +75 -0
- package/dist/empirical/export.d.ts.map +1 -0
- package/dist/empirical/export.js +392 -0
- package/dist/empirical/export.js.map +1 -0
- package/dist/empirical/forecasting.d.ts +80 -0
- package/dist/empirical/forecasting.d.ts.map +1 -0
- package/dist/empirical/forecasting.js +287 -0
- package/dist/empirical/forecasting.js.map +1 -0
- package/dist/empirical/funnel.d.ts +66 -0
- package/dist/empirical/funnel.d.ts.map +1 -0
- package/dist/empirical/funnel.js +293 -0
- package/dist/empirical/funnel.js.map +1 -0
- package/dist/empirical/history.d.ts +198 -0
- package/dist/empirical/history.d.ts.map +1 -0
- package/dist/empirical/history.js +396 -0
- package/dist/empirical/history.js.map +1 -0
- package/dist/empirical/index.d.ts +41 -16
- package/dist/empirical/index.d.ts.map +1 -1
- package/dist/empirical/index.js +96 -13
- package/dist/empirical/index.js.map +1 -1
- package/dist/empirical/interactions.d.ts +89 -0
- package/dist/empirical/interactions.d.ts.map +1 -0
- package/dist/empirical/interactions.js +346 -0
- package/dist/empirical/interactions.js.map +1 -0
- package/dist/empirical/opportunity-calculator.d.ts +6 -18
- package/dist/empirical/opportunity-calculator.d.ts.map +1 -1
- package/dist/empirical/opportunity-calculator.js +19 -1
- package/dist/empirical/opportunity-calculator.js.map +1 -1
- package/dist/empirical/report.d.ts +3 -11
- package/dist/empirical/report.d.ts.map +1 -1
- package/dist/empirical/report.js +11 -7
- package/dist/empirical/report.js.map +1 -1
- package/dist/empirical/roi-calculator.d.ts +104 -0
- package/dist/empirical/roi-calculator.d.ts.map +1 -0
- package/dist/empirical/roi-calculator.js +403 -0
- package/dist/empirical/roi-calculator.js.map +1 -0
- package/dist/empirical/seasonality.d.ts +80 -0
- package/dist/empirical/seasonality.d.ts.map +1 -0
- package/dist/empirical/seasonality.js +340 -0
- package/dist/empirical/seasonality.js.map +1 -0
- package/dist/empirical/segmentation.d.ts +135 -0
- package/dist/empirical/segmentation.d.ts.map +1 -0
- package/dist/empirical/segmentation.js +379 -0
- package/dist/empirical/segmentation.js.map +1 -0
- package/dist/empirical/statistics.d.ts +118 -0
- package/dist/empirical/statistics.d.ts.map +1 -0
- package/dist/empirical/statistics.js +344 -0
- package/dist/empirical/statistics.js.map +1 -0
- package/dist/empirical/sweet-spot.d.ts +81 -0
- package/dist/empirical/sweet-spot.d.ts.map +1 -0
- package/dist/empirical/sweet-spot.js +198 -0
- package/dist/empirical/sweet-spot.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prediction & Forecasting
|
|
3
|
+
*
|
|
4
|
+
* Project future revenue based on planned performance improvements.
|
|
5
|
+
* Uses empirical data and improvement scenarios.
|
|
6
|
+
*
|
|
7
|
+
* @author Kanmi Obasa <i@kanmiobasa.com>
|
|
8
|
+
*/
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// CORE FORECASTING
|
|
11
|
+
// =============================================================================
|
|
12
|
+
const MONTH_NAMES = [
|
|
13
|
+
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
|
14
|
+
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Calculate baseline metrics from sessions.
|
|
18
|
+
*/
|
|
19
|
+
function calculateBaseline(sessions) {
|
|
20
|
+
const conversions = sessions.filter(s => s.has_purchase);
|
|
21
|
+
const cvr = sessions.length > 0 ? conversions.length / sessions.length : 0;
|
|
22
|
+
const totalRevenue = conversions.reduce((sum, s) => sum + s.purchase_value, 0);
|
|
23
|
+
const aov = conversions.length > 0 ? totalRevenue / conversions.length : 100;
|
|
24
|
+
// Assume data is ~2 weeks, project to monthly
|
|
25
|
+
const multiplier = 2;
|
|
26
|
+
return {
|
|
27
|
+
monthlyRevenue: totalRevenue * multiplier,
|
|
28
|
+
monthlySessions: sessions.length * multiplier,
|
|
29
|
+
cvr,
|
|
30
|
+
aov,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Estimate CVR lift from improvement scenario.
|
|
35
|
+
*/
|
|
36
|
+
function estimateLift(scenario, sessions, curve) {
|
|
37
|
+
// Use curve data if available
|
|
38
|
+
if (curve && curve.buckets.length > 0) {
|
|
39
|
+
// Find sessions that would be improved
|
|
40
|
+
const getValue = (s) => {
|
|
41
|
+
switch (scenario.metric) {
|
|
42
|
+
case 'lcp': return s.lcp_ms;
|
|
43
|
+
case 'inp': return s.inp_ms;
|
|
44
|
+
case 'cls': return s.cls;
|
|
45
|
+
case 'fcp': return s.fcp_ms;
|
|
46
|
+
case 'ttfb': return s.ttfb_ms;
|
|
47
|
+
default: return null;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const sessionsAboveTarget = sessions.filter(s => {
|
|
51
|
+
const value = getValue(s);
|
|
52
|
+
return value !== null && value > scenario.targetP75;
|
|
53
|
+
});
|
|
54
|
+
const sessionsAtTarget = sessions.filter(s => {
|
|
55
|
+
const value = getValue(s);
|
|
56
|
+
return value !== null && value <= scenario.targetP75;
|
|
57
|
+
});
|
|
58
|
+
if (sessionsAboveTarget.length >= 50 && sessionsAtTarget.length >= 50) {
|
|
59
|
+
const currentCvr = sessionsAboveTarget.filter(s => s.has_purchase).length / sessionsAboveTarget.length;
|
|
60
|
+
const targetCvr = sessionsAtTarget.filter(s => s.has_purchase).length / sessionsAtTarget.length;
|
|
61
|
+
// Only sessions above target will improve
|
|
62
|
+
const proportionAffected = sessionsAboveTarget.length / sessions.length;
|
|
63
|
+
const lift = (targetCvr - currentCvr) * proportionAffected;
|
|
64
|
+
return lift * scenario.confidence;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Fallback to benchmark estimates
|
|
68
|
+
return estimateLiftFromBenchmarks(scenario);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Benchmark-based lift estimation.
|
|
72
|
+
*/
|
|
73
|
+
function estimateLiftFromBenchmarks(scenario) {
|
|
74
|
+
// Rough estimates based on industry data
|
|
75
|
+
const benchmarks = {
|
|
76
|
+
lcp: 0.003, // ~0.3% per 100ms improvement
|
|
77
|
+
inp: 0.002, // ~0.2% per 50ms improvement
|
|
78
|
+
cls: 0.005, // ~0.5% per 0.1 improvement
|
|
79
|
+
fcp: 0.002,
|
|
80
|
+
ttfb: 0.0015,
|
|
81
|
+
};
|
|
82
|
+
const baseRate = benchmarks[scenario.metric] || 0.002;
|
|
83
|
+
// Assume moving from poor (4000ms LCP) to target
|
|
84
|
+
const estimatedDelta = {
|
|
85
|
+
lcp: Math.max(0, 4000 - scenario.targetP75),
|
|
86
|
+
inp: Math.max(0, 500 - scenario.targetP75),
|
|
87
|
+
cls: Math.max(0, 0.25 - scenario.targetP75),
|
|
88
|
+
fcp: Math.max(0, 3000 - scenario.targetP75),
|
|
89
|
+
ttfb: Math.max(0, 1800 - scenario.targetP75),
|
|
90
|
+
};
|
|
91
|
+
const delta = estimatedDelta[scenario.metric] || 0;
|
|
92
|
+
const lift = (delta / 100) * baseRate;
|
|
93
|
+
return lift * scenario.confidence * 0.5; // Conservative estimate
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Generate revenue forecast for a single improvement scenario.
|
|
97
|
+
*/
|
|
98
|
+
export function forecastRevenue(scenario, sessions, curve, config = {}) {
|
|
99
|
+
const forecastMonths = config.forecastMonths || 12;
|
|
100
|
+
const growthRate = config.growthRate || 0; // Monthly baseline growth
|
|
101
|
+
const seasonality = config.seasonalityFactors || Array(12).fill(1);
|
|
102
|
+
const baseline = calculateBaseline(sessions);
|
|
103
|
+
const cvrLift = estimateLift(scenario, sessions, curve);
|
|
104
|
+
const periods = [];
|
|
105
|
+
let cumulativeAdditional = 0;
|
|
106
|
+
const startMonth = new Date().getMonth();
|
|
107
|
+
for (let i = 0; i < forecastMonths; i++) {
|
|
108
|
+
const monthIndex = (startMonth + i) % 12;
|
|
109
|
+
const seasonFactor = seasonality[monthIndex] || 1;
|
|
110
|
+
// Baseline grows over time
|
|
111
|
+
const growthFactor = Math.pow(1 + growthRate, i);
|
|
112
|
+
const baselineRevenue = baseline.monthlyRevenue * growthFactor * seasonFactor;
|
|
113
|
+
// Additional revenue from CVR improvement
|
|
114
|
+
const additionalConversions = baseline.monthlySessions * growthFactor * cvrLift;
|
|
115
|
+
const additionalRevenue = additionalConversions * baseline.aov * seasonFactor;
|
|
116
|
+
cumulativeAdditional += additionalRevenue;
|
|
117
|
+
periods.push({
|
|
118
|
+
month: i + 1,
|
|
119
|
+
monthName: MONTH_NAMES[monthIndex],
|
|
120
|
+
baselineRevenue,
|
|
121
|
+
forecastedRevenue: baselineRevenue + additionalRevenue,
|
|
122
|
+
additionalRevenue,
|
|
123
|
+
cumulativeAdditional,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const totalAdditionalRevenue = cumulativeAdditional;
|
|
127
|
+
const avgMonthlyLift = totalAdditionalRevenue / forecastMonths;
|
|
128
|
+
const baselineAnnual = periods.reduce((sum, p) => sum + p.baselineRevenue, 0);
|
|
129
|
+
const revenueGrowthPercent = baselineAnnual > 0
|
|
130
|
+
? (totalAdditionalRevenue / baselineAnnual) * 100
|
|
131
|
+
: 0;
|
|
132
|
+
const assumptions = [
|
|
133
|
+
`Base CVR: ${(baseline.cvr * 100).toFixed(2)}%`,
|
|
134
|
+
`Expected CVR lift: ${(cvrLift * 100).toFixed(3)}%`,
|
|
135
|
+
`Average Order Value: $${baseline.aov.toFixed(2)}`,
|
|
136
|
+
`Confidence: ${(scenario.confidence * 100).toFixed(0)}%`,
|
|
137
|
+
`Monthly sessions: ${baseline.monthlySessions.toLocaleString()}`,
|
|
138
|
+
];
|
|
139
|
+
return {
|
|
140
|
+
scenario,
|
|
141
|
+
periods,
|
|
142
|
+
summary: {
|
|
143
|
+
totalAdditionalRevenue,
|
|
144
|
+
avgMonthlyLift,
|
|
145
|
+
revenueGrowthPercent,
|
|
146
|
+
},
|
|
147
|
+
assumptions,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Generate combined forecast for multiple improvements.
|
|
152
|
+
*/
|
|
153
|
+
export function forecastMultipleImprovements(scenarios, sessions, curves, config = {}) {
|
|
154
|
+
const forecastMonths = config.forecastMonths || 12;
|
|
155
|
+
const synergisticBonus = config.synergisticBonus || 1.1; // 10% bonus for multiple improvements
|
|
156
|
+
const forecasts = [];
|
|
157
|
+
for (const scenario of scenarios) {
|
|
158
|
+
const curve = curves[scenario.metric] || null;
|
|
159
|
+
const forecast = forecastRevenue(scenario, sessions, curve, { forecastMonths });
|
|
160
|
+
forecasts.push(forecast);
|
|
161
|
+
}
|
|
162
|
+
// Combined forecast (with synergy bonus if multiple improvements)
|
|
163
|
+
const combinedPeriods = [];
|
|
164
|
+
let combinedCumulative = 0;
|
|
165
|
+
const synergyMultiplier = scenarios.length > 1 ? synergisticBonus : 1;
|
|
166
|
+
for (let i = 0; i < forecastMonths; i++) {
|
|
167
|
+
const baselineRevenue = forecasts[0]?.periods[i]?.baselineRevenue || 0;
|
|
168
|
+
// Sum additional revenue from all improvements
|
|
169
|
+
let totalAdditional = 0;
|
|
170
|
+
for (const forecast of forecasts) {
|
|
171
|
+
totalAdditional += forecast.periods[i]?.additionalRevenue || 0;
|
|
172
|
+
}
|
|
173
|
+
// Apply synergy bonus
|
|
174
|
+
totalAdditional *= synergyMultiplier;
|
|
175
|
+
combinedCumulative += totalAdditional;
|
|
176
|
+
combinedPeriods.push({
|
|
177
|
+
month: i + 1,
|
|
178
|
+
monthName: forecasts[0]?.periods[i]?.monthName || '',
|
|
179
|
+
baselineRevenue,
|
|
180
|
+
forecastedRevenue: baselineRevenue + totalAdditional,
|
|
181
|
+
additionalRevenue: totalAdditional,
|
|
182
|
+
cumulativeAdditional: combinedCumulative,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const recommendations = [];
|
|
186
|
+
// Find highest impact scenario
|
|
187
|
+
const sorted = [...forecasts].sort((a, b) => b.summary.totalAdditionalRevenue - a.summary.totalAdditionalRevenue);
|
|
188
|
+
if (sorted.length > 0) {
|
|
189
|
+
const top = sorted[0];
|
|
190
|
+
recommendations.push(`Prioritize ${top.scenario.name}: $${top.summary.totalAdditionalRevenue.toLocaleString()} potential annual impact`);
|
|
191
|
+
}
|
|
192
|
+
if (scenarios.length > 1) {
|
|
193
|
+
recommendations.push(`Combined improvements yield ${((synergyMultiplier - 1) * 100).toFixed(0)}% synergy bonus`);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
forecasts,
|
|
197
|
+
combined: {
|
|
198
|
+
periods: combinedPeriods,
|
|
199
|
+
totalAdditionalRevenue: combinedCumulative,
|
|
200
|
+
netImpact: combinedCumulative,
|
|
201
|
+
},
|
|
202
|
+
recommendations,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// =============================================================================
|
|
206
|
+
// REPORT GENERATION
|
|
207
|
+
// =============================================================================
|
|
208
|
+
/**
|
|
209
|
+
* Generate markdown report for forecast.
|
|
210
|
+
*/
|
|
211
|
+
export function generateForecastMarkdown(forecast) {
|
|
212
|
+
const lines = [];
|
|
213
|
+
lines.push('## Revenue Forecast');
|
|
214
|
+
lines.push('');
|
|
215
|
+
lines.push(`### ${forecast.scenario.name}`);
|
|
216
|
+
lines.push('');
|
|
217
|
+
lines.push(`**Target**: ${forecast.scenario.metric.toUpperCase()} p75 ≤ ${forecast.scenario.targetP75}`);
|
|
218
|
+
lines.push('');
|
|
219
|
+
// Summary
|
|
220
|
+
lines.push('### Summary');
|
|
221
|
+
lines.push('');
|
|
222
|
+
lines.push(`| Metric | Value |`);
|
|
223
|
+
lines.push(`|--------|-------|`);
|
|
224
|
+
lines.push(`| 12-Month Additional Revenue | $${forecast.summary.totalAdditionalRevenue.toLocaleString()} |`);
|
|
225
|
+
lines.push(`| Average Monthly Lift | $${forecast.summary.avgMonthlyLift.toLocaleString()} |`);
|
|
226
|
+
lines.push(`| Revenue Growth | ${forecast.summary.revenueGrowthPercent.toFixed(2)}% |`);
|
|
227
|
+
lines.push('');
|
|
228
|
+
// Assumptions
|
|
229
|
+
lines.push('### Assumptions');
|
|
230
|
+
lines.push('');
|
|
231
|
+
for (const assumption of forecast.assumptions) {
|
|
232
|
+
lines.push(`- ${assumption}`);
|
|
233
|
+
}
|
|
234
|
+
lines.push('');
|
|
235
|
+
// Monthly breakdown
|
|
236
|
+
lines.push('### Monthly Breakdown');
|
|
237
|
+
lines.push('');
|
|
238
|
+
lines.push('| Month | Baseline | Forecasted | Additional | Cumulative |');
|
|
239
|
+
lines.push('|-------|----------|------------|------------|------------|');
|
|
240
|
+
for (const period of forecast.periods) {
|
|
241
|
+
lines.push(`| ${period.monthName} | $${period.baselineRevenue.toLocaleString()} | $${period.forecastedRevenue.toLocaleString()} | $${period.additionalRevenue.toLocaleString()} | $${period.cumulativeAdditional.toLocaleString()} |`);
|
|
242
|
+
}
|
|
243
|
+
lines.push('');
|
|
244
|
+
return lines.join('\n');
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Generate multi-scenario comparison markdown.
|
|
248
|
+
*/
|
|
249
|
+
export function generateMultiForecastMarkdown(multiForecast) {
|
|
250
|
+
const lines = [];
|
|
251
|
+
lines.push('## Multi-Scenario Revenue Forecast');
|
|
252
|
+
lines.push('');
|
|
253
|
+
// Scenario comparison
|
|
254
|
+
lines.push('### Scenario Comparison');
|
|
255
|
+
lines.push('');
|
|
256
|
+
lines.push('| Scenario | 12-Month Impact | Avg Monthly | Growth % |');
|
|
257
|
+
lines.push('|----------|-----------------|-------------|----------|');
|
|
258
|
+
for (const forecast of multiForecast.forecasts) {
|
|
259
|
+
lines.push(`| ${forecast.scenario.name} | $${forecast.summary.totalAdditionalRevenue.toLocaleString()} | $${forecast.summary.avgMonthlyLift.toLocaleString()} | ${forecast.summary.revenueGrowthPercent.toFixed(2)}% |`);
|
|
260
|
+
}
|
|
261
|
+
lines.push('');
|
|
262
|
+
// Combined impact
|
|
263
|
+
lines.push('### Combined Impact');
|
|
264
|
+
lines.push('');
|
|
265
|
+
lines.push(`**Total 12-Month Additional Revenue**: $${multiForecast.combined.totalAdditionalRevenue.toLocaleString()}`);
|
|
266
|
+
lines.push('');
|
|
267
|
+
// Recommendations
|
|
268
|
+
if (multiForecast.recommendations.length > 0) {
|
|
269
|
+
lines.push('### Recommendations');
|
|
270
|
+
lines.push('');
|
|
271
|
+
for (const rec of multiForecast.recommendations) {
|
|
272
|
+
lines.push(`- ${rec}`);
|
|
273
|
+
}
|
|
274
|
+
lines.push('');
|
|
275
|
+
}
|
|
276
|
+
// Combined monthly breakdown
|
|
277
|
+
lines.push('### Combined Monthly Breakdown');
|
|
278
|
+
lines.push('');
|
|
279
|
+
lines.push('| Month | Baseline | Combined Forecast | Combined Additional |');
|
|
280
|
+
lines.push('|-------|----------|-------------------|---------------------|');
|
|
281
|
+
for (const period of multiForecast.combined.periods) {
|
|
282
|
+
lines.push(`| ${period.monthName} | $${period.baselineRevenue.toLocaleString()} | $${period.forecastedRevenue.toLocaleString()} | $${period.additionalRevenue.toLocaleString()} |`);
|
|
283
|
+
}
|
|
284
|
+
lines.push('');
|
|
285
|
+
return lines.join('\n');
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=forecasting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forecasting.js","sourceRoot":"","sources":["../../src/empirical/forecasting.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Funnel Analysis
|
|
3
|
+
*
|
|
4
|
+
* Analyzes where in the user journey performance matters most.
|
|
5
|
+
* Tracks progression through funnel stages and identifies
|
|
6
|
+
* performance-related drop-offs.
|
|
7
|
+
*
|
|
8
|
+
* @author Kanmi Obasa <i@kanmiobasa.com>
|
|
9
|
+
*/
|
|
10
|
+
import type { SessionData } from './datadog-session-query.js';
|
|
11
|
+
export type FunnelStage = 'landing' | 'browse' | 'product' | 'cart' | 'checkout' | 'purchase';
|
|
12
|
+
export interface FunnelStageStats {
|
|
13
|
+
stage: FunnelStage;
|
|
14
|
+
sessions: number;
|
|
15
|
+
conversions: number;
|
|
16
|
+
dropoffRate: number;
|
|
17
|
+
avgLcp: number | null;
|
|
18
|
+
avgInp: number | null;
|
|
19
|
+
/** CVR difference between good vs poor performance at this stage */
|
|
20
|
+
performanceImpact: number | null;
|
|
21
|
+
}
|
|
22
|
+
export interface FunnelStep {
|
|
23
|
+
from: FunnelStage;
|
|
24
|
+
to: FunnelStage;
|
|
25
|
+
sessions: number;
|
|
26
|
+
progressions: number;
|
|
27
|
+
progressionRate: number;
|
|
28
|
+
avgLcp: number | null;
|
|
29
|
+
/** Performance-based progression rates */
|
|
30
|
+
goodPerfProgressionRate: number | null;
|
|
31
|
+
poorPerfProgressionRate: number | null;
|
|
32
|
+
performanceGap: number | null;
|
|
33
|
+
}
|
|
34
|
+
export interface FunnelReport {
|
|
35
|
+
stages: FunnelStageStats[];
|
|
36
|
+
steps: FunnelStep[];
|
|
37
|
+
overallConversionRate: number;
|
|
38
|
+
biggestDropoff: {
|
|
39
|
+
step: string;
|
|
40
|
+
dropoffRate: number;
|
|
41
|
+
performanceGap: number | null;
|
|
42
|
+
} | null;
|
|
43
|
+
performanceHotspots: Array<{
|
|
44
|
+
stage: FunnelStage;
|
|
45
|
+
impact: number;
|
|
46
|
+
message: string;
|
|
47
|
+
}>;
|
|
48
|
+
recommendations: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Infer funnel stage from session data.
|
|
52
|
+
*/
|
|
53
|
+
export declare function inferFunnelStage(session: SessionData): FunnelStage;
|
|
54
|
+
/**
|
|
55
|
+
* Infer deepest stage reached from conversion events.
|
|
56
|
+
*/
|
|
57
|
+
export declare function inferDeepestStage(session: SessionData): FunnelStage;
|
|
58
|
+
/**
|
|
59
|
+
* Analyze funnel progression and performance impact.
|
|
60
|
+
*/
|
|
61
|
+
export declare function analyzeFunnel(sessions: SessionData[]): FunnelReport;
|
|
62
|
+
/**
|
|
63
|
+
* Generate markdown report for funnel analysis.
|
|
64
|
+
*/
|
|
65
|
+
export declare function generateFunnelMarkdown(report: FunnelReport): string;
|
|
66
|
+
//# sourceMappingURL=funnel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"funnel.d.ts","sourceRoot":"","sources":["../../src/empirical/funnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAM9D,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC;AAE9F,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,oEAAoE;IACpE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,EAAE,EAAE,WAAW,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,0CAA0C;IAC1C,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,GAAG,IAAI,CAAC;IACT,mBAAmB,EAAE,KAAK,CAAC;QACzB,KAAK,EAAE,WAAW,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CAsClE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CAmBnE;AAQD;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,YAAY,CAiMnE;AAMD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAwEnE"}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Funnel Analysis
|
|
3
|
+
*
|
|
4
|
+
* Analyzes where in the user journey performance matters most.
|
|
5
|
+
* Tracks progression through funnel stages and identifies
|
|
6
|
+
* performance-related drop-offs.
|
|
7
|
+
*
|
|
8
|
+
* @author Kanmi Obasa <i@kanmiobasa.com>
|
|
9
|
+
*/
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// STAGE DETECTION
|
|
12
|
+
// =============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Infer funnel stage from session data.
|
|
15
|
+
*/
|
|
16
|
+
export function inferFunnelStage(session) {
|
|
17
|
+
// Use page_type if available
|
|
18
|
+
const pageType = session.page_type?.toLowerCase() || '';
|
|
19
|
+
if (pageType.includes('checkout') || pageType.includes('payment')) {
|
|
20
|
+
return 'checkout';
|
|
21
|
+
}
|
|
22
|
+
if (pageType.includes('cart') || pageType.includes('basket')) {
|
|
23
|
+
return 'cart';
|
|
24
|
+
}
|
|
25
|
+
if (pageType.includes('product') || pageType.includes('pdp') || pageType.includes('item')) {
|
|
26
|
+
return 'product';
|
|
27
|
+
}
|
|
28
|
+
if (pageType.includes('category') ||
|
|
29
|
+
pageType.includes('listing') ||
|
|
30
|
+
pageType.includes('search') ||
|
|
31
|
+
pageType.includes('browse') ||
|
|
32
|
+
pageType.includes('plp')) {
|
|
33
|
+
return 'browse';
|
|
34
|
+
}
|
|
35
|
+
if (pageType.includes('home') || pageType.includes('landing') || pageType === '/') {
|
|
36
|
+
return 'landing';
|
|
37
|
+
}
|
|
38
|
+
// Infer from page views
|
|
39
|
+
const pageViews = session.page_views || 1;
|
|
40
|
+
if (session.has_purchase) {
|
|
41
|
+
return 'purchase';
|
|
42
|
+
}
|
|
43
|
+
if (pageViews === 1) {
|
|
44
|
+
return 'landing';
|
|
45
|
+
}
|
|
46
|
+
if (pageViews <= 3) {
|
|
47
|
+
return 'browse';
|
|
48
|
+
}
|
|
49
|
+
return 'product';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Infer deepest stage reached from conversion events.
|
|
53
|
+
*/
|
|
54
|
+
export function inferDeepestStage(session) {
|
|
55
|
+
if (session.has_purchase) {
|
|
56
|
+
return 'purchase';
|
|
57
|
+
}
|
|
58
|
+
const events = session.conversions || [];
|
|
59
|
+
const eventNames = events.map((e) => e.name.toLowerCase());
|
|
60
|
+
if (eventNames.some((n) => n.includes('checkout') || n.includes('payment'))) {
|
|
61
|
+
return 'checkout';
|
|
62
|
+
}
|
|
63
|
+
if (eventNames.some((n) => n.includes('cart') || n.includes('add_to_cart'))) {
|
|
64
|
+
return 'cart';
|
|
65
|
+
}
|
|
66
|
+
if (eventNames.some((n) => n.includes('product_view') || n.includes('view_item'))) {
|
|
67
|
+
return 'product';
|
|
68
|
+
}
|
|
69
|
+
return inferFunnelStage(session);
|
|
70
|
+
}
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// FUNNEL ANALYSIS
|
|
73
|
+
// =============================================================================
|
|
74
|
+
const STAGE_ORDER = ['landing', 'browse', 'product', 'cart', 'checkout', 'purchase'];
|
|
75
|
+
/**
|
|
76
|
+
* Analyze funnel progression and performance impact.
|
|
77
|
+
*/
|
|
78
|
+
export function analyzeFunnel(sessions) {
|
|
79
|
+
// Group sessions by deepest stage reached
|
|
80
|
+
const stageData = new Map();
|
|
81
|
+
for (const stage of STAGE_ORDER) {
|
|
82
|
+
stageData.set(stage, []);
|
|
83
|
+
}
|
|
84
|
+
for (const session of sessions) {
|
|
85
|
+
const deepestStage = inferDeepestStage(session);
|
|
86
|
+
stageData.get(deepestStage).push(session);
|
|
87
|
+
}
|
|
88
|
+
// Calculate cumulative counts (users who reached at least this stage)
|
|
89
|
+
const cumulativeCounts = new Map();
|
|
90
|
+
let cumulative = sessions.length;
|
|
91
|
+
for (let i = 0; i < STAGE_ORDER.length; i++) {
|
|
92
|
+
cumulativeCounts.set(STAGE_ORDER[i], cumulative);
|
|
93
|
+
// Subtract those who dropped off at this stage
|
|
94
|
+
if (i < STAGE_ORDER.length - 1) {
|
|
95
|
+
cumulative -= stageData.get(STAGE_ORDER[i]).length;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Calculate stage statistics
|
|
99
|
+
const stages = [];
|
|
100
|
+
for (let i = 0; i < STAGE_ORDER.length; i++) {
|
|
101
|
+
const stage = STAGE_ORDER[i];
|
|
102
|
+
const sessionsAtStage = sessions.filter((s) => {
|
|
103
|
+
const deepest = inferDeepestStage(s);
|
|
104
|
+
return STAGE_ORDER.indexOf(deepest) >= i;
|
|
105
|
+
});
|
|
106
|
+
const sessionsCount = sessionsAtStage.length;
|
|
107
|
+
const conversions = sessionsAtStage.filter((s) => s.has_purchase).length;
|
|
108
|
+
const dropoffCount = stageData.get(stage).length;
|
|
109
|
+
const dropoffRate = sessionsCount > 0 ? dropoffCount / sessionsCount : 0;
|
|
110
|
+
// Calculate performance metrics
|
|
111
|
+
const lcpValues = sessionsAtStage.map((s) => s.lcp_ms).filter((v) => v !== null);
|
|
112
|
+
const inpValues = sessionsAtStage.map((s) => s.inp_ms).filter((v) => v !== null);
|
|
113
|
+
// Performance impact: compare CVR for good vs poor LCP
|
|
114
|
+
let performanceImpact = null;
|
|
115
|
+
const goodPerf = sessionsAtStage.filter((s) => s.lcp_ms !== null && s.lcp_ms <= 2500);
|
|
116
|
+
const poorPerf = sessionsAtStage.filter((s) => s.lcp_ms !== null && s.lcp_ms >= 4000);
|
|
117
|
+
if (goodPerf.length >= 10 && poorPerf.length >= 10) {
|
|
118
|
+
const goodCvr = goodPerf.filter((s) => s.has_purchase).length / goodPerf.length;
|
|
119
|
+
const poorCvr = poorPerf.filter((s) => s.has_purchase).length / poorPerf.length;
|
|
120
|
+
performanceImpact = goodCvr - poorCvr;
|
|
121
|
+
}
|
|
122
|
+
stages.push({
|
|
123
|
+
stage,
|
|
124
|
+
sessions: sessionsCount,
|
|
125
|
+
conversions,
|
|
126
|
+
dropoffRate,
|
|
127
|
+
avgLcp: lcpValues.length > 0 ? lcpValues.reduce((a, b) => a + b, 0) / lcpValues.length : null,
|
|
128
|
+
avgInp: inpValues.length > 0 ? inpValues.reduce((a, b) => a + b, 0) / inpValues.length : null,
|
|
129
|
+
performanceImpact,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// Calculate step progressions
|
|
133
|
+
const steps = [];
|
|
134
|
+
for (let i = 0; i < STAGE_ORDER.length - 1; i++) {
|
|
135
|
+
const fromStage = STAGE_ORDER[i];
|
|
136
|
+
const toStage = STAGE_ORDER[i + 1];
|
|
137
|
+
const fromSessions = sessions.filter((s) => {
|
|
138
|
+
const deepest = inferDeepestStage(s);
|
|
139
|
+
return STAGE_ORDER.indexOf(deepest) >= i;
|
|
140
|
+
});
|
|
141
|
+
const toSessions = sessions.filter((s) => {
|
|
142
|
+
const deepest = inferDeepestStage(s);
|
|
143
|
+
return STAGE_ORDER.indexOf(deepest) >= i + 1;
|
|
144
|
+
});
|
|
145
|
+
const progressionRate = fromSessions.length > 0 ? toSessions.length / fromSessions.length : 0;
|
|
146
|
+
// Performance-based progression
|
|
147
|
+
const goodPerfFrom = fromSessions.filter((s) => s.lcp_ms !== null && s.lcp_ms <= 2500);
|
|
148
|
+
const poorPerfFrom = fromSessions.filter((s) => s.lcp_ms !== null && s.lcp_ms >= 4000);
|
|
149
|
+
let goodPerfProgressionRate = null;
|
|
150
|
+
let poorPerfProgressionRate = null;
|
|
151
|
+
if (goodPerfFrom.length >= 10) {
|
|
152
|
+
const goodPerfTo = goodPerfFrom.filter((s) => {
|
|
153
|
+
const deepest = inferDeepestStage(s);
|
|
154
|
+
return STAGE_ORDER.indexOf(deepest) >= i + 1;
|
|
155
|
+
});
|
|
156
|
+
goodPerfProgressionRate = goodPerfTo.length / goodPerfFrom.length;
|
|
157
|
+
}
|
|
158
|
+
if (poorPerfFrom.length >= 10) {
|
|
159
|
+
const poorPerfTo = poorPerfFrom.filter((s) => {
|
|
160
|
+
const deepest = inferDeepestStage(s);
|
|
161
|
+
return STAGE_ORDER.indexOf(deepest) >= i + 1;
|
|
162
|
+
});
|
|
163
|
+
poorPerfProgressionRate = poorPerfTo.length / poorPerfFrom.length;
|
|
164
|
+
}
|
|
165
|
+
const performanceGap = goodPerfProgressionRate !== null && poorPerfProgressionRate !== null
|
|
166
|
+
? goodPerfProgressionRate - poorPerfProgressionRate
|
|
167
|
+
: null;
|
|
168
|
+
const lcpValues = fromSessions.map((s) => s.lcp_ms).filter((v) => v !== null);
|
|
169
|
+
steps.push({
|
|
170
|
+
from: fromStage,
|
|
171
|
+
to: toStage,
|
|
172
|
+
sessions: fromSessions.length,
|
|
173
|
+
progressions: toSessions.length,
|
|
174
|
+
progressionRate,
|
|
175
|
+
avgLcp: lcpValues.length > 0 ? lcpValues.reduce((a, b) => a + b, 0) / lcpValues.length : null,
|
|
176
|
+
goodPerfProgressionRate,
|
|
177
|
+
poorPerfProgressionRate,
|
|
178
|
+
performanceGap,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// Find biggest dropoff
|
|
182
|
+
const significantSteps = steps.filter((s) => s.sessions >= 100);
|
|
183
|
+
let biggestDropoff = null;
|
|
184
|
+
if (significantSteps.length > 0) {
|
|
185
|
+
const sorted = [...significantSteps].sort((a, b) => 1 - a.progressionRate - (1 - b.progressionRate));
|
|
186
|
+
const worst = sorted[0];
|
|
187
|
+
biggestDropoff = {
|
|
188
|
+
step: `${worst.from} → ${worst.to}`,
|
|
189
|
+
dropoffRate: 1 - worst.progressionRate,
|
|
190
|
+
performanceGap: worst.performanceGap,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
// Find performance hotspots
|
|
194
|
+
const performanceHotspots = [];
|
|
195
|
+
for (const step of steps) {
|
|
196
|
+
if (step.performanceGap !== null && step.performanceGap > 0.05) {
|
|
197
|
+
performanceHotspots.push({
|
|
198
|
+
stage: step.from,
|
|
199
|
+
impact: step.performanceGap,
|
|
200
|
+
message: `${(step.performanceGap * 100).toFixed(1)}% higher progression with good performance at ${step.from}`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Generate recommendations
|
|
205
|
+
const recommendations = [];
|
|
206
|
+
if (biggestDropoff && biggestDropoff.dropoffRate > 0.5) {
|
|
207
|
+
recommendations.push(`Focus on ${biggestDropoff.step} - ${(biggestDropoff.dropoffRate * 100).toFixed(0)}% drop-off rate`);
|
|
208
|
+
}
|
|
209
|
+
if (performanceHotspots.length > 0) {
|
|
210
|
+
const topHotspot = performanceHotspots.sort((a, b) => b.impact - a.impact)[0];
|
|
211
|
+
recommendations.push(`Prioritize performance on ${topHotspot.stage} pages - ${topHotspot.message}`);
|
|
212
|
+
}
|
|
213
|
+
// Check for checkout performance
|
|
214
|
+
const checkoutStep = steps.find((s) => s.from === 'cart' && s.to === 'checkout');
|
|
215
|
+
if (checkoutStep && checkoutStep.performanceGap && checkoutStep.performanceGap > 0.1) {
|
|
216
|
+
recommendations.push(`Critical: Checkout performance impacts ${(checkoutStep.performanceGap * 100).toFixed(0)}% of cart abandonment`);
|
|
217
|
+
}
|
|
218
|
+
const overallConversionRate = sessions.length > 0 ? sessions.filter((s) => s.has_purchase).length / sessions.length : 0;
|
|
219
|
+
return {
|
|
220
|
+
stages,
|
|
221
|
+
steps,
|
|
222
|
+
overallConversionRate,
|
|
223
|
+
biggestDropoff,
|
|
224
|
+
performanceHotspots,
|
|
225
|
+
recommendations,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
// =============================================================================
|
|
229
|
+
// REPORT GENERATION
|
|
230
|
+
// =============================================================================
|
|
231
|
+
/**
|
|
232
|
+
* Generate markdown report for funnel analysis.
|
|
233
|
+
*/
|
|
234
|
+
export function generateFunnelMarkdown(report) {
|
|
235
|
+
const lines = [];
|
|
236
|
+
lines.push('## Funnel Analysis');
|
|
237
|
+
lines.push('');
|
|
238
|
+
lines.push(`Overall Conversion Rate: **${(report.overallConversionRate * 100).toFixed(2)}%**`);
|
|
239
|
+
lines.push('');
|
|
240
|
+
// Recommendations
|
|
241
|
+
if (report.recommendations.length > 0) {
|
|
242
|
+
lines.push('### Recommendations');
|
|
243
|
+
lines.push('');
|
|
244
|
+
for (const rec of report.recommendations) {
|
|
245
|
+
lines.push(`- ${rec}`);
|
|
246
|
+
}
|
|
247
|
+
lines.push('');
|
|
248
|
+
}
|
|
249
|
+
// Funnel visualization
|
|
250
|
+
lines.push('### Funnel Progression');
|
|
251
|
+
lines.push('');
|
|
252
|
+
lines.push('| Stage | Sessions | CVR | Drop-off | Perf Impact |');
|
|
253
|
+
lines.push('|-------|----------|-----|----------|-------------|');
|
|
254
|
+
for (const stage of report.stages) {
|
|
255
|
+
const perfImpact = stage.performanceImpact
|
|
256
|
+
? `${stage.performanceImpact > 0 ? '+' : ''}${(stage.performanceImpact * 100).toFixed(2)}%`
|
|
257
|
+
: 'N/A';
|
|
258
|
+
const cvr = stage.sessions > 0 ? (stage.conversions / stage.sessions * 100).toFixed(2) : '0.00';
|
|
259
|
+
lines.push(`| ${stage.stage} | ${stage.sessions.toLocaleString()} | ${cvr}% | ${(stage.dropoffRate * 100).toFixed(1)}% | ${perfImpact} |`);
|
|
260
|
+
}
|
|
261
|
+
lines.push('');
|
|
262
|
+
// Step-by-step analysis
|
|
263
|
+
lines.push('### Step Analysis');
|
|
264
|
+
lines.push('');
|
|
265
|
+
lines.push('| Step | Progression | Good Perf | Poor Perf | Gap |');
|
|
266
|
+
lines.push('|------|-------------|-----------|-----------|-----|');
|
|
267
|
+
for (const step of report.steps) {
|
|
268
|
+
const goodPerf = step.goodPerfProgressionRate
|
|
269
|
+
? `${(step.goodPerfProgressionRate * 100).toFixed(1)}%`
|
|
270
|
+
: 'N/A';
|
|
271
|
+
const poorPerf = step.poorPerfProgressionRate
|
|
272
|
+
? `${(step.poorPerfProgressionRate * 100).toFixed(1)}%`
|
|
273
|
+
: 'N/A';
|
|
274
|
+
const gap = step.performanceGap
|
|
275
|
+
? `${step.performanceGap > 0 ? '+' : ''}${(step.performanceGap * 100).toFixed(1)}%`
|
|
276
|
+
: 'N/A';
|
|
277
|
+
lines.push(`| ${step.from} → ${step.to} | ${(step.progressionRate * 100).toFixed(1)}% | ${goodPerf} | ${poorPerf} | ${gap} |`);
|
|
278
|
+
}
|
|
279
|
+
lines.push('');
|
|
280
|
+
// Performance Hotspots
|
|
281
|
+
if (report.performanceHotspots.length > 0) {
|
|
282
|
+
lines.push('### Performance Hotspots');
|
|
283
|
+
lines.push('');
|
|
284
|
+
lines.push('Stages where performance has the highest impact on progression:');
|
|
285
|
+
lines.push('');
|
|
286
|
+
for (const hotspot of report.performanceHotspots) {
|
|
287
|
+
lines.push(`- **${hotspot.stage}**: ${hotspot.message}`);
|
|
288
|
+
}
|
|
289
|
+
lines.push('');
|
|
290
|
+
}
|
|
291
|
+
return lines.join('\n');
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=funnel.js.map
|