@contractspec/lib.analytics 1.56.1 → 1.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/churn/index.js +77 -0
- package/dist/browser/churn/predictor.js +77 -0
- package/dist/browser/cohort/index.js +117 -0
- package/dist/browser/cohort/tracker.js +117 -0
- package/dist/browser/funnel/analyzer.js +68 -0
- package/dist/browser/funnel/index.js +68 -0
- package/dist/browser/growth/hypothesis-generator.js +46 -0
- package/dist/browser/growth/index.js +46 -0
- package/dist/browser/index.js +723 -0
- package/dist/browser/lifecycle/index.js +287 -0
- package/dist/browser/lifecycle/metric-collectors.js +58 -0
- package/dist/browser/lifecycle/posthog-bridge.js +79 -0
- package/dist/browser/lifecycle/posthog-metric-source.js +205 -0
- package/dist/browser/posthog/event-source.js +138 -0
- package/dist/browser/posthog/index.js +138 -0
- package/dist/browser/types.js +0 -0
- package/dist/churn/index.d.ts +2 -2
- package/dist/churn/index.d.ts.map +1 -0
- package/dist/churn/index.js +77 -2
- package/dist/churn/predictor.d.ts +15 -19
- package/dist/churn/predictor.d.ts.map +1 -1
- package/dist/churn/predictor.js +72 -68
- package/dist/cohort/index.d.ts +2 -2
- package/dist/cohort/index.d.ts.map +1 -0
- package/dist/cohort/index.js +117 -2
- package/dist/cohort/tracker.d.ts +3 -7
- package/dist/cohort/tracker.d.ts.map +1 -1
- package/dist/cohort/tracker.js +106 -90
- package/dist/funnel/analyzer.d.ts +4 -8
- package/dist/funnel/analyzer.d.ts.map +1 -1
- package/dist/funnel/analyzer.js +67 -62
- package/dist/funnel/index.d.ts +2 -2
- package/dist/funnel/index.d.ts.map +1 -0
- package/dist/funnel/index.js +69 -3
- package/dist/growth/hypothesis-generator.d.ts +11 -15
- package/dist/growth/hypothesis-generator.d.ts.map +1 -1
- package/dist/growth/hypothesis-generator.js +46 -39
- package/dist/growth/index.d.ts +2 -2
- package/dist/growth/index.d.ts.map +1 -0
- package/dist/growth/index.js +47 -3
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +724 -10
- package/dist/lifecycle/index.d.ts +4 -3
- package/dist/lifecycle/index.d.ts.map +1 -0
- package/dist/lifecycle/index.js +287 -3
- package/dist/lifecycle/metric-collectors.d.ts +22 -26
- package/dist/lifecycle/metric-collectors.d.ts.map +1 -1
- package/dist/lifecycle/metric-collectors.js +55 -44
- package/dist/lifecycle/posthog-bridge.d.ts +9 -13
- package/dist/lifecycle/posthog-bridge.d.ts.map +1 -1
- package/dist/lifecycle/posthog-bridge.js +77 -25
- package/dist/lifecycle/posthog-metric-source.d.ts +43 -0
- package/dist/lifecycle/posthog-metric-source.d.ts.map +1 -0
- package/dist/lifecycle/posthog-metric-source.js +206 -0
- package/dist/node/churn/index.js +77 -0
- package/dist/node/churn/predictor.js +77 -0
- package/dist/node/cohort/index.js +117 -0
- package/dist/node/cohort/tracker.js +117 -0
- package/dist/node/funnel/analyzer.js +68 -0
- package/dist/node/funnel/index.js +68 -0
- package/dist/node/growth/hypothesis-generator.js +46 -0
- package/dist/node/growth/index.js +46 -0
- package/dist/node/index.js +723 -0
- package/dist/node/lifecycle/index.js +287 -0
- package/dist/node/lifecycle/metric-collectors.js +58 -0
- package/dist/node/lifecycle/posthog-bridge.js +79 -0
- package/dist/node/lifecycle/posthog-metric-source.js +205 -0
- package/dist/node/posthog/event-source.js +138 -0
- package/dist/node/posthog/index.js +138 -0
- package/dist/node/types.js +0 -0
- package/dist/posthog/event-source.d.ts +21 -0
- package/dist/posthog/event-source.d.ts.map +1 -0
- package/dist/posthog/event-source.js +139 -0
- package/dist/posthog/index.d.ts +2 -0
- package/dist/posthog/index.d.ts.map +1 -0
- package/dist/posthog/index.js +139 -0
- package/dist/types.d.ts +52 -55
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/package.json +189 -39
- package/dist/churn/predictor.js.map +0 -1
- package/dist/cohort/tracker.js.map +0 -1
- package/dist/funnel/analyzer.js.map +0 -1
- package/dist/growth/hypothesis-generator.js.map +0 -1
- package/dist/lifecycle/metric-collectors.js.map +0 -1
- package/dist/lifecycle/posthog-bridge.js.map +0 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/funnel/analyzer.ts
|
|
2
|
+
class FunnelAnalyzer {
|
|
3
|
+
analyze(events, definition) {
|
|
4
|
+
const windowMs = (definition.windowHours ?? 72) * 60 * 60 * 1000;
|
|
5
|
+
const eventsByUser = groupByUser(events);
|
|
6
|
+
const stepCounts = definition.steps.map(() => 0);
|
|
7
|
+
for (const userEvents of eventsByUser.values()) {
|
|
8
|
+
const completionIndex = this.evaluateUser(userEvents, definition.steps, windowMs);
|
|
9
|
+
completionIndex.forEach((hit, stepIdx) => {
|
|
10
|
+
if (hit) {
|
|
11
|
+
stepCounts[stepIdx] = (stepCounts[stepIdx] ?? 0) + 1;
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const totalUsers = eventsByUser.size;
|
|
16
|
+
const steps = definition.steps.map((step, index) => {
|
|
17
|
+
const prevCount = index === 0 ? totalUsers : stepCounts[index - 1] || 1;
|
|
18
|
+
const count = stepCounts[index] ?? 0;
|
|
19
|
+
const conversionRate = prevCount === 0 ? 0 : Number((count / prevCount).toFixed(3));
|
|
20
|
+
const dropOffRate = Number((1 - conversionRate).toFixed(3));
|
|
21
|
+
return { step, count, conversionRate, dropOffRate };
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
definition,
|
|
25
|
+
totalUsers,
|
|
26
|
+
steps
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
evaluateUser(events, steps, windowMs) {
|
|
30
|
+
const sorted = [...events].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
31
|
+
const completion = Array(steps.length).fill(false);
|
|
32
|
+
let cursor = 0;
|
|
33
|
+
let anchorTime;
|
|
34
|
+
for (const event of sorted) {
|
|
35
|
+
const step = steps[cursor];
|
|
36
|
+
if (!step)
|
|
37
|
+
break;
|
|
38
|
+
if (event.name !== step.eventName)
|
|
39
|
+
continue;
|
|
40
|
+
if (step.match && !step.match(event))
|
|
41
|
+
continue;
|
|
42
|
+
const eventTime = new Date(event.timestamp).getTime();
|
|
43
|
+
if (cursor === 0) {
|
|
44
|
+
anchorTime = eventTime;
|
|
45
|
+
completion[cursor] = true;
|
|
46
|
+
cursor += 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (anchorTime && eventTime - anchorTime <= windowMs) {
|
|
50
|
+
completion[cursor] = true;
|
|
51
|
+
cursor += 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return completion;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function groupByUser(events) {
|
|
58
|
+
const map = new Map;
|
|
59
|
+
for (const event of events) {
|
|
60
|
+
const list = map.get(event.userId) ?? [];
|
|
61
|
+
list.push(event);
|
|
62
|
+
map.set(event.userId, list);
|
|
63
|
+
}
|
|
64
|
+
return map;
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
FunnelAnalyzer
|
|
68
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/growth/hypothesis-generator.ts
|
|
2
|
+
class GrowthHypothesisGenerator {
|
|
3
|
+
minDelta;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.minDelta = options?.minDelta ?? 0.05;
|
|
6
|
+
}
|
|
7
|
+
generate(metrics) {
|
|
8
|
+
return metrics.map((metric) => this.fromMetric(metric)).filter((hypothesis) => Boolean(hypothesis));
|
|
9
|
+
}
|
|
10
|
+
fromMetric(metric) {
|
|
11
|
+
const change = this.delta(metric);
|
|
12
|
+
if (Math.abs(change) < this.minDelta)
|
|
13
|
+
return null;
|
|
14
|
+
const direction = change > 0 ? "rising" : "declining";
|
|
15
|
+
const statement = this.statement(metric, change, direction);
|
|
16
|
+
return {
|
|
17
|
+
statement,
|
|
18
|
+
metric: metric.name,
|
|
19
|
+
confidence: Math.abs(change) > 0.2 ? "high" : "medium",
|
|
20
|
+
impact: this.impact(metric)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
delta(metric) {
|
|
24
|
+
if (metric.previous == null)
|
|
25
|
+
return 0;
|
|
26
|
+
const prev = metric.previous || 1;
|
|
27
|
+
return (metric.current - prev) / Math.abs(prev);
|
|
28
|
+
}
|
|
29
|
+
impact(metric) {
|
|
30
|
+
if (metric.target && metric.current < metric.target * 0.8)
|
|
31
|
+
return "high";
|
|
32
|
+
if (metric.target && metric.current < metric.target)
|
|
33
|
+
return "medium";
|
|
34
|
+
return "low";
|
|
35
|
+
}
|
|
36
|
+
statement(metric, change, direction) {
|
|
37
|
+
const percent = Math.abs(parseFloat((change * 100).toFixed(1)));
|
|
38
|
+
if (direction === "declining") {
|
|
39
|
+
return `${metric.name} is down ${percent}% vs last period; test new onboarding prompts to recover activation.`;
|
|
40
|
+
}
|
|
41
|
+
return `${metric.name} grew ${percent}% period-over-period; double down with expanded experiment or pricing test.`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
GrowthHypothesisGenerator
|
|
46
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/growth/hypothesis-generator.ts
|
|
2
|
+
class GrowthHypothesisGenerator {
|
|
3
|
+
minDelta;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.minDelta = options?.minDelta ?? 0.05;
|
|
6
|
+
}
|
|
7
|
+
generate(metrics) {
|
|
8
|
+
return metrics.map((metric) => this.fromMetric(metric)).filter((hypothesis) => Boolean(hypothesis));
|
|
9
|
+
}
|
|
10
|
+
fromMetric(metric) {
|
|
11
|
+
const change = this.delta(metric);
|
|
12
|
+
if (Math.abs(change) < this.minDelta)
|
|
13
|
+
return null;
|
|
14
|
+
const direction = change > 0 ? "rising" : "declining";
|
|
15
|
+
const statement = this.statement(metric, change, direction);
|
|
16
|
+
return {
|
|
17
|
+
statement,
|
|
18
|
+
metric: metric.name,
|
|
19
|
+
confidence: Math.abs(change) > 0.2 ? "high" : "medium",
|
|
20
|
+
impact: this.impact(metric)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
delta(metric) {
|
|
24
|
+
if (metric.previous == null)
|
|
25
|
+
return 0;
|
|
26
|
+
const prev = metric.previous || 1;
|
|
27
|
+
return (metric.current - prev) / Math.abs(prev);
|
|
28
|
+
}
|
|
29
|
+
impact(metric) {
|
|
30
|
+
if (metric.target && metric.current < metric.target * 0.8)
|
|
31
|
+
return "high";
|
|
32
|
+
if (metric.target && metric.current < metric.target)
|
|
33
|
+
return "medium";
|
|
34
|
+
return "low";
|
|
35
|
+
}
|
|
36
|
+
statement(metric, change, direction) {
|
|
37
|
+
const percent = Math.abs(parseFloat((change * 100).toFixed(1)));
|
|
38
|
+
if (direction === "declining") {
|
|
39
|
+
return `${metric.name} is down ${percent}% vs last period; test new onboarding prompts to recover activation.`;
|
|
40
|
+
}
|
|
41
|
+
return `${metric.name} grew ${percent}% period-over-period; double down with expanded experiment or pricing test.`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
GrowthHypothesisGenerator
|
|
46
|
+
};
|