@lssm/lib.analytics 0.0.0-canary-20251217062943 → 0.0.0-canary-20251217072406

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.
@@ -1 +1,3 @@
1
- import{ChurnPredictor as e}from"./predictor.js";export{e as ChurnPredictor};
1
+ import { ChurnPredictor } from "./predictor.js";
2
+
3
+ export { ChurnPredictor };
@@ -1 +1,73 @@
1
- import e from"dayjs";var t=class{recencyWeight;frequencyWeight;errorWeight;decayDays;constructor(e){this.recencyWeight=e?.recencyWeight??.5,this.frequencyWeight=e?.frequencyWeight??.3,this.errorWeight=e?.errorWeight??.2,this.decayDays=e?.decayDays??14}score(e){let t=n(e,e=>e.userId),r=[];for(let[e,n]of t.entries()){let t=this.computeScore(n);r.push({userId:e,score:t,bucket:t>=.7?`high`:t>=.4?`medium`:`low`,drivers:this.drivers(n)})}return r.sort((e,t)=>t.score-e.score)}computeScore(t){if(!t.length)return 0;let n=t.sort((e,t)=>r(e)-r(t)),i=n[n.length-1];if(!i)return 0;let a=e().diff(e(i.timestamp),`day`),o=Math.max(0,1-a/this.decayDays),s=e().subtract(this.decayDays,`day`),c=n.filter(t=>e(t.timestamp).isAfter(s)),l=c.length/Math.max(this.decayDays,1),u=Math.min(1,l*5),d=c.filter(e=>e.properties?.error!==void 0||/error|failed/i.test(e.name)).length,f=Math.min(1,d/3),p=o*this.recencyWeight+u*this.frequencyWeight+(1-f)*this.errorWeight;return Number(p.toFixed(3))}drivers(t){let n=[],i=t.sort((e,t)=>r(e)-r(t)),a=i[i.length-1];if(a){let t=e().diff(e(a.timestamp),`day`);t>this.decayDays&&n.push(`Inactive for ${t} days`)}let o=t.filter(e=>e.properties?.error!==void 0||/error|failed/i.test(e.name));return o.length&&n.push(`${o.length} errors logged`),n}};function n(e,t){let n=new Map;for(let r of e){let e=t(r),i=n.get(e)??[];i.push(r),n.set(e,i)}return n}function r(e){return new Date(e.timestamp).getTime()}export{t as ChurnPredictor};
1
+ import dayjs from "dayjs";
2
+
3
+ //#region src/churn/predictor.ts
4
+ var ChurnPredictor = class {
5
+ recencyWeight;
6
+ frequencyWeight;
7
+ errorWeight;
8
+ decayDays;
9
+ constructor(options) {
10
+ this.recencyWeight = options?.recencyWeight ?? .5;
11
+ this.frequencyWeight = options?.frequencyWeight ?? .3;
12
+ this.errorWeight = options?.errorWeight ?? .2;
13
+ this.decayDays = options?.decayDays ?? 14;
14
+ }
15
+ score(events) {
16
+ const grouped = groupBy(events, (event) => event.userId);
17
+ const signals = [];
18
+ for (const [userId, userEvents] of grouped.entries()) {
19
+ const score = this.computeScore(userEvents);
20
+ signals.push({
21
+ userId,
22
+ score,
23
+ bucket: score >= .7 ? "high" : score >= .4 ? "medium" : "low",
24
+ drivers: this.drivers(userEvents)
25
+ });
26
+ }
27
+ return signals.sort((a, b) => b.score - a.score);
28
+ }
29
+ computeScore(events) {
30
+ if (!events.length) return 0;
31
+ const sorted = events.sort((a, b) => dateMs(a) - dateMs(b));
32
+ const lastEvent = sorted[sorted.length - 1];
33
+ if (!lastEvent) return 0;
34
+ const daysSinceLast = dayjs().diff(dayjs(lastEvent.timestamp), "day");
35
+ const recencyScore = Math.max(0, 1 - daysSinceLast / this.decayDays);
36
+ const windowStart = dayjs().subtract(this.decayDays, "day");
37
+ const recentEvents = sorted.filter((event) => dayjs(event.timestamp).isAfter(windowStart));
38
+ const averagePerDay = recentEvents.length / Math.max(this.decayDays, 1);
39
+ const frequencyScore = Math.min(1, averagePerDay * 5);
40
+ const errorEvents = recentEvents.filter((event) => typeof event.properties?.error !== "undefined" || /error|failed/i.test(event.name)).length;
41
+ const errorScore = Math.min(1, errorEvents / 3);
42
+ const score = recencyScore * this.recencyWeight + frequencyScore * this.frequencyWeight + (1 - errorScore) * this.errorWeight;
43
+ return Number(score.toFixed(3));
44
+ }
45
+ drivers(events) {
46
+ const drivers = [];
47
+ const sorted = events.sort((a, b) => dateMs(a) - dateMs(b));
48
+ const lastEvent = sorted[sorted.length - 1];
49
+ if (lastEvent) {
50
+ const days = dayjs().diff(dayjs(lastEvent.timestamp), "day");
51
+ if (days > this.decayDays) drivers.push(`Inactive for ${days} days`);
52
+ }
53
+ const errorEvents = events.filter((event) => typeof event.properties?.error !== "undefined" || /error|failed/i.test(event.name));
54
+ if (errorEvents.length) drivers.push(`${errorEvents.length} errors logged`);
55
+ return drivers;
56
+ }
57
+ };
58
+ function groupBy(items, selector) {
59
+ const map = /* @__PURE__ */ new Map();
60
+ for (const item of items) {
61
+ const key = selector(item);
62
+ const list = map.get(key) ?? [];
63
+ list.push(item);
64
+ map.set(key, list);
65
+ }
66
+ return map;
67
+ }
68
+ function dateMs(event) {
69
+ return new Date(event.timestamp).getTime();
70
+ }
71
+
72
+ //#endregion
73
+ export { ChurnPredictor };
@@ -1 +1,3 @@
1
- import{CohortTracker as e}from"./tracker.js";export{e as CohortTracker};
1
+ import { CohortTracker } from "./tracker.js";
2
+
3
+ export { CohortTracker };
@@ -1 +1,101 @@
1
- import e from"dayjs";var t=class{analyze(e,t){let a=r(e,e=>e.userId),o=new Map;for(let[e,r]of a.entries()){r.sort((e,t)=>s(e)-s(t));let a=r[0];if(!a)continue;let c=i(a.timestamp,t.bucket),l=o.get(c)??new n(c,t);l.addUser(e);for(let t of r)l.addEvent(e,t);o.set(c,l)}return{definition:t,cohorts:[...o.values()].map(e=>e.build())}}},n=class{users=new Set;retentionMap=new Map;ltv=0;constructor(e,t){this.key=e,this.definition=t}addUser(e){this.users.add(e)}addEvent(e,t){let n=a(this.key,t.timestamp,this.definition.bucket);if(n<0||n>=this.definition.periods)return;let r=this.retentionMap.get(n)??new Set;r.add(e),this.retentionMap.set(n,r);let i=typeof t.properties?.amount==`number`?t.properties.amount:0;this.ltv+=i}build(){let e=this.users.size||1,t=[];for(let n=0;n<this.definition.periods;n++){let r=this.retentionMap.get(n)?.size??0;t.push(Number((r/e).toFixed(3)))}return{cohortKey:this.key,users:this.users.size,retention:t,ltv:Number(this.ltv.toFixed(2))}}};function r(e,t){let n=new Map;for(let r of e){let e=t(r),i=n.get(e)??[];i.push(r),n.set(e,i)}return n}function i(t,n){let r=e(t);switch(n){case`day`:return r.startOf(`day`).format(`YYYY-MM-DD`);case`week`:return r.startOf(`week`).format(`YYYY-[W]WW`);case`month`:default:return r.startOf(`month`).format(`YYYY-MM`)}}function a(t,n,r){let i=o(t,r),a=e(n);switch(r){case`day`:return a.diff(i,`day`);case`week`:return a.diff(i,`week`);case`month`:default:return a.diff(i,`month`)}}function o(t,n){switch(n){case`day`:return e(t,`YYYY-MM-DD`);case`week`:return e(t.replace(`W`,``),`YYYY-ww`);case`month`:default:return e(t,`YYYY-MM`)}}function s(e){return new Date(e.timestamp).getTime()}export{t as CohortTracker};
1
+ import dayjs from "dayjs";
2
+
3
+ //#region src/cohort/tracker.ts
4
+ var CohortTracker = class {
5
+ analyze(events, definition) {
6
+ const groupedByUser = groupBy(events, (event) => event.userId);
7
+ const cohorts = /* @__PURE__ */ new Map();
8
+ for (const [userId, userEvents] of groupedByUser.entries()) {
9
+ userEvents.sort((a, b) => dateMs(a) - dateMs(b));
10
+ const signup = userEvents[0];
11
+ if (!signup) continue;
12
+ const cohortKey = bucketKey(signup.timestamp, definition.bucket);
13
+ const builder = cohorts.get(cohortKey) ?? new CohortStatsBuilder(cohortKey, definition);
14
+ builder.addUser(userId);
15
+ for (const event of userEvents) builder.addEvent(userId, event);
16
+ cohorts.set(cohortKey, builder);
17
+ }
18
+ return {
19
+ definition,
20
+ cohorts: [...cohorts.values()].map((builder) => builder.build())
21
+ };
22
+ }
23
+ };
24
+ var CohortStatsBuilder = class {
25
+ users = /* @__PURE__ */ new Set();
26
+ retentionMap = /* @__PURE__ */ new Map();
27
+ ltv = 0;
28
+ constructor(key, definition) {
29
+ this.key = key;
30
+ this.definition = definition;
31
+ }
32
+ addUser(userId) {
33
+ this.users.add(userId);
34
+ }
35
+ addEvent(userId, event) {
36
+ const period = bucketDiff(this.key, event.timestamp, this.definition.bucket);
37
+ if (period < 0 || period >= this.definition.periods) return;
38
+ const bucket = this.retentionMap.get(period) ?? /* @__PURE__ */ new Set();
39
+ bucket.add(userId);
40
+ this.retentionMap.set(period, bucket);
41
+ const amount = typeof event.properties?.amount === "number" ? event.properties.amount : 0;
42
+ this.ltv += amount;
43
+ }
44
+ build() {
45
+ const totalUsers = this.users.size || 1;
46
+ const retention = [];
47
+ for (let period = 0; period < this.definition.periods; period++) {
48
+ const active = this.retentionMap.get(period)?.size ?? 0;
49
+ retention.push(Number((active / totalUsers).toFixed(3)));
50
+ }
51
+ return {
52
+ cohortKey: this.key,
53
+ users: this.users.size,
54
+ retention,
55
+ ltv: Number(this.ltv.toFixed(2))
56
+ };
57
+ }
58
+ };
59
+ function groupBy(items, selector) {
60
+ const map = /* @__PURE__ */ new Map();
61
+ for (const item of items) {
62
+ const key = selector(item);
63
+ const list = map.get(key) ?? [];
64
+ list.push(item);
65
+ map.set(key, list);
66
+ }
67
+ return map;
68
+ }
69
+ function bucketKey(timestamp, bucket) {
70
+ const dt = dayjs(timestamp);
71
+ switch (bucket) {
72
+ case "day": return dt.startOf("day").format("YYYY-MM-DD");
73
+ case "week": return dt.startOf("week").format("YYYY-[W]WW");
74
+ case "month":
75
+ default: return dt.startOf("month").format("YYYY-MM");
76
+ }
77
+ }
78
+ function bucketDiff(cohortKey, timestamp, bucket) {
79
+ const start = parseBucketKey(cohortKey, bucket);
80
+ const target = dayjs(timestamp);
81
+ switch (bucket) {
82
+ case "day": return target.diff(start, "day");
83
+ case "week": return target.diff(start, "week");
84
+ case "month":
85
+ default: return target.diff(start, "month");
86
+ }
87
+ }
88
+ function parseBucketKey(key, bucket) {
89
+ switch (bucket) {
90
+ case "day": return dayjs(key, "YYYY-MM-DD");
91
+ case "week": return dayjs(key.replace("W", ""), "YYYY-ww");
92
+ case "month":
93
+ default: return dayjs(key, "YYYY-MM");
94
+ }
95
+ }
96
+ function dateMs(event) {
97
+ return new Date(event.timestamp).getTime();
98
+ }
99
+
100
+ //#endregion
101
+ export { CohortTracker };
@@ -1 +1,63 @@
1
- var e=class{analyze(e,n){let r=(n.windowHours??72)*60*60*1e3,i=t(e),a=n.steps.map(()=>0);for(let e of i.values())this.evaluateUser(e,n.steps,r).forEach((e,t)=>{e&&(a[t]=(a[t]??0)+1)});let o=i.size;return{definition:n,totalUsers:o,steps:n.steps.map((e,t)=>{let n=t===0?o:a[t-1]||1,r=a[t]??0,i=n===0?0:Number((r/n).toFixed(3));return{step:e,count:r,conversionRate:i,dropOffRate:Number((1-i).toFixed(3))}})}}evaluateUser(e,t,n){let r=[...e].sort((e,t)=>new Date(e.timestamp).getTime()-new Date(t.timestamp).getTime()),i=Array(t.length).fill(!1),a=0,o;for(let e of r){let r=t[a];if(!r)break;if(e.name!==r.eventName||r.match&&!r.match(e))continue;let s=new Date(e.timestamp).getTime();if(a===0){o=s,i[a]=!0,a+=1;continue}o&&s-o<=n&&(i[a]=!0,a+=1)}return i}};function t(e){let t=new Map;for(let n of e){let e=t.get(n.userId)??[];e.push(n),t.set(n.userId,e)}return t}export{e as FunnelAnalyzer};
1
+ //#region src/funnel/analyzer.ts
2
+ var FunnelAnalyzer = class {
3
+ analyze(events, definition) {
4
+ const windowMs = (definition.windowHours ?? 72) * 60 * 60 * 1e3;
5
+ const eventsByUser = groupByUser(events);
6
+ const stepCounts = definition.steps.map(() => 0);
7
+ for (const userEvents of eventsByUser.values()) this.evaluateUser(userEvents, definition.steps, windowMs).forEach((hit, stepIdx) => {
8
+ if (hit) stepCounts[stepIdx] = (stepCounts[stepIdx] ?? 0) + 1;
9
+ });
10
+ const totalUsers = eventsByUser.size;
11
+ return {
12
+ definition,
13
+ totalUsers,
14
+ steps: definition.steps.map((step, index) => {
15
+ const prevCount = index === 0 ? totalUsers : stepCounts[index - 1] || 1;
16
+ const count = stepCounts[index] ?? 0;
17
+ const conversionRate = prevCount === 0 ? 0 : Number((count / prevCount).toFixed(3));
18
+ return {
19
+ step,
20
+ count,
21
+ conversionRate,
22
+ dropOffRate: Number((1 - conversionRate).toFixed(3))
23
+ };
24
+ })
25
+ };
26
+ }
27
+ evaluateUser(events, steps, windowMs) {
28
+ const sorted = [...events].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
29
+ const completion = Array(steps.length).fill(false);
30
+ let cursor = 0;
31
+ let anchorTime;
32
+ for (const event of sorted) {
33
+ const step = steps[cursor];
34
+ if (!step) break;
35
+ if (event.name !== step.eventName) continue;
36
+ if (step.match && !step.match(event)) continue;
37
+ const eventTime = new Date(event.timestamp).getTime();
38
+ if (cursor === 0) {
39
+ anchorTime = eventTime;
40
+ completion[cursor] = true;
41
+ cursor += 1;
42
+ continue;
43
+ }
44
+ if (anchorTime && eventTime - anchorTime <= windowMs) {
45
+ completion[cursor] = true;
46
+ cursor += 1;
47
+ }
48
+ }
49
+ return completion;
50
+ }
51
+ };
52
+ function groupByUser(events) {
53
+ const map = /* @__PURE__ */ new Map();
54
+ for (const event of events) {
55
+ const list = map.get(event.userId) ?? [];
56
+ list.push(event);
57
+ map.set(event.userId, list);
58
+ }
59
+ return map;
60
+ }
61
+
62
+ //#endregion
63
+ export { FunnelAnalyzer };
@@ -1 +1,3 @@
1
- import{FunnelAnalyzer as e}from"./analyzer.js";export{e as FunnelAnalyzer};
1
+ import { FunnelAnalyzer } from "./analyzer.js";
2
+
3
+ export { FunnelAnalyzer };
@@ -1 +1,39 @@
1
- var e=class{minDelta;constructor(e){this.minDelta=e?.minDelta??.05}generate(e){return e.map(e=>this.fromMetric(e)).filter(e=>!!e)}fromMetric(e){let t=this.delta(e);if(Math.abs(t)<this.minDelta)return null;let n=t>0?`rising`:`declining`;return{statement:this.statement(e,t,n),metric:e.name,confidence:Math.abs(t)>.2?`high`:`medium`,impact:this.impact(e)}}delta(e){if(e.previous==null)return 0;let t=e.previous||1;return(e.current-t)/Math.abs(t)}impact(e){return e.target&&e.current<e.target*.8?`high`:e.target&&e.current<e.target?`medium`:`low`}statement(e,t,n){let r=Math.abs(parseFloat((t*100).toFixed(1)));return n===`declining`?`${e.name} is down ${r}% vs last period; test new onboarding prompts to recover activation.`:`${e.name} grew ${r}% period-over-period; double down with expanded experiment or pricing test.`}};export{e as GrowthHypothesisGenerator};
1
+ //#region src/growth/hypothesis-generator.ts
2
+ var GrowthHypothesisGenerator = class {
3
+ minDelta;
4
+ constructor(options) {
5
+ this.minDelta = options?.minDelta ?? .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) return null;
13
+ const direction = change > 0 ? "rising" : "declining";
14
+ return {
15
+ statement: this.statement(metric, change, direction),
16
+ metric: metric.name,
17
+ confidence: Math.abs(change) > .2 ? "high" : "medium",
18
+ impact: this.impact(metric)
19
+ };
20
+ }
21
+ delta(metric) {
22
+ if (metric.previous == null) return 0;
23
+ const prev = metric.previous || 1;
24
+ return (metric.current - prev) / Math.abs(prev);
25
+ }
26
+ impact(metric) {
27
+ if (metric.target && metric.current < metric.target * .8) return "high";
28
+ if (metric.target && metric.current < metric.target) return "medium";
29
+ return "low";
30
+ }
31
+ statement(metric, change, direction) {
32
+ const percent = Math.abs(parseFloat((change * 100).toFixed(1)));
33
+ if (direction === "declining") return `${metric.name} is down ${percent}% vs last period; test new onboarding prompts to recover activation.`;
34
+ return `${metric.name} grew ${percent}% period-over-period; double down with expanded experiment or pricing test.`;
35
+ }
36
+ };
37
+
38
+ //#endregion
39
+ export { GrowthHypothesisGenerator };
@@ -1 +1,3 @@
1
- import{GrowthHypothesisGenerator as e}from"./hypothesis-generator.js";export{e as GrowthHypothesisGenerator};
1
+ import { GrowthHypothesisGenerator } from "./hypothesis-generator.js";
2
+
3
+ export { GrowthHypothesisGenerator };
package/dist/index.js CHANGED
@@ -1 +1,10 @@
1
- import{FunnelAnalyzer as e}from"./funnel/analyzer.js";import{CohortTracker as t}from"./cohort/tracker.js";import"./cohort/index.js";import{ChurnPredictor as n}from"./churn/predictor.js";import"./churn/index.js";import{GrowthHypothesisGenerator as r}from"./growth/hypothesis-generator.js";import{collectLifecycleMetrics as i,createStageChangeEvent as a,lifecycleEventNames as o,metricsToSignals as s}from"./lifecycle/metric-collectors.js";import{trackLifecycleAssessment as c,trackLifecycleStageChange as l}from"./lifecycle/posthog-bridge.js";export{n as ChurnPredictor,t as CohortTracker,e as FunnelAnalyzer,r as GrowthHypothesisGenerator,i as collectLifecycleMetrics,a as createStageChangeEvent,o as lifecycleEventNames,s as metricsToSignals,c as trackLifecycleAssessment,l as trackLifecycleStageChange};
1
+ import { FunnelAnalyzer } from "./funnel/analyzer.js";
2
+ import { CohortTracker } from "./cohort/tracker.js";
3
+ import "./cohort/index.js";
4
+ import { ChurnPredictor } from "./churn/predictor.js";
5
+ import "./churn/index.js";
6
+ import { GrowthHypothesisGenerator } from "./growth/hypothesis-generator.js";
7
+ import { collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals } from "./lifecycle/metric-collectors.js";
8
+ import { trackLifecycleAssessment, trackLifecycleStageChange } from "./lifecycle/posthog-bridge.js";
9
+
10
+ export { ChurnPredictor, CohortTracker, FunnelAnalyzer, GrowthHypothesisGenerator, collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals, trackLifecycleAssessment, trackLifecycleStageChange };
@@ -1 +1,4 @@
1
- import{collectLifecycleMetrics as e,createStageChangeEvent as t,lifecycleEventNames as n,metricsToSignals as r}from"./metric-collectors.js";import{trackLifecycleAssessment as i,trackLifecycleStageChange as a}from"./posthog-bridge.js";export{e as collectLifecycleMetrics,t as createStageChangeEvent,n as lifecycleEventNames,r as metricsToSignals,i as trackLifecycleAssessment,a as trackLifecycleStageChange};
1
+ import { collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals } from "./metric-collectors.js";
2
+ import { trackLifecycleAssessment, trackLifecycleStageChange } from "./posthog-bridge.js";
3
+
4
+ export { collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals, trackLifecycleAssessment, trackLifecycleStageChange };
@@ -1 +1,47 @@
1
- const e=async e=>{let[t,n,r,i,a,o,s]=await Promise.all([e.getActiveUsers(),e.getWeeklyActiveUsers?.(),e.getRetentionRate?.(),e.getMonthlyRecurringRevenue?.(),e.getCustomerCount?.(),e.getTeamSize?.(),e.getBurnMultiple?.()]);return{activeUsers:t,weeklyActiveUsers:n,retentionRate:r,monthlyRecurringRevenue:i,customerCount:a,teamSize:o,burnMultiple:s}},t=(e,t)=>Object.entries(e).filter(([,e])=>e!=null).map(([e,n])=>({id:`lifecycle-metric:${e}`,kind:`metric`,source:`analytics`,name:e,value:n,weight:1,confidence:.8,details:t?{tenantId:t}:void 0,capturedAt:new Date().toISOString()})),n={assessmentRun:`lifecycle_assessment_run`,stageChanged:`lifecycle_stage_changed`,guidanceConsumed:`lifecycle_guidance_consumed`},r=e=>({name:n.stageChanged,userId:`system`,tenantId:e.tenantId,timestamp:new Date,properties:{...e}});export{e as collectLifecycleMetrics,r as createStageChangeEvent,n as lifecycleEventNames,t as metricsToSignals};
1
+ //#region src/lifecycle/metric-collectors.ts
2
+ const collectLifecycleMetrics = async (source) => {
3
+ const [activeUsers, weeklyActiveUsers, retentionRate, monthlyRecurringRevenue, customerCount, teamSize, burnMultiple] = await Promise.all([
4
+ source.getActiveUsers(),
5
+ source.getWeeklyActiveUsers?.(),
6
+ source.getRetentionRate?.(),
7
+ source.getMonthlyRecurringRevenue?.(),
8
+ source.getCustomerCount?.(),
9
+ source.getTeamSize?.(),
10
+ source.getBurnMultiple?.()
11
+ ]);
12
+ return {
13
+ activeUsers,
14
+ weeklyActiveUsers,
15
+ retentionRate,
16
+ monthlyRecurringRevenue,
17
+ customerCount,
18
+ teamSize,
19
+ burnMultiple
20
+ };
21
+ };
22
+ const metricsToSignals = (metrics, tenantId) => Object.entries(metrics).filter(([, value]) => value !== void 0 && value !== null).map(([metricKey, value]) => ({
23
+ id: `lifecycle-metric:${metricKey}`,
24
+ kind: "metric",
25
+ source: "analytics",
26
+ name: metricKey,
27
+ value,
28
+ weight: 1,
29
+ confidence: .8,
30
+ details: tenantId ? { tenantId } : void 0,
31
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
32
+ }));
33
+ const lifecycleEventNames = {
34
+ assessmentRun: "lifecycle_assessment_run",
35
+ stageChanged: "lifecycle_stage_changed",
36
+ guidanceConsumed: "lifecycle_guidance_consumed"
37
+ };
38
+ const createStageChangeEvent = (payload) => ({
39
+ name: lifecycleEventNames.stageChanged,
40
+ userId: "system",
41
+ tenantId: payload.tenantId,
42
+ timestamp: /* @__PURE__ */ new Date(),
43
+ properties: { ...payload }
44
+ });
45
+
46
+ //#endregion
47
+ export { collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals };
@@ -1 +1,27 @@
1
- import{lifecycleEventNames as e}from"./metric-collectors.js";const t=async(t,n,r)=>{await t.capture({distinctId:n,event:e.assessmentRun,properties:{stage:r.stage,confidence:r.confidence,axes:r.axes}})},n=async(t,n,r,i)=>{await t.capture({distinctId:n,event:e.stageChanged,properties:{previousStage:r,nextStage:i}})};export{t as trackLifecycleAssessment,n as trackLifecycleStageChange};
1
+ import { lifecycleEventNames } from "./metric-collectors.js";
2
+
3
+ //#region src/lifecycle/posthog-bridge.ts
4
+ const trackLifecycleAssessment = async (client, tenantId, assessment) => {
5
+ await client.capture({
6
+ distinctId: tenantId,
7
+ event: lifecycleEventNames.assessmentRun,
8
+ properties: {
9
+ stage: assessment.stage,
10
+ confidence: assessment.confidence,
11
+ axes: assessment.axes
12
+ }
13
+ });
14
+ };
15
+ const trackLifecycleStageChange = async (client, tenantId, previousStage, nextStage) => {
16
+ await client.capture({
17
+ distinctId: tenantId,
18
+ event: lifecycleEventNames.stageChanged,
19
+ properties: {
20
+ previousStage,
21
+ nextStage
22
+ }
23
+ });
24
+ };
25
+
26
+ //#endregion
27
+ export { trackLifecycleAssessment, trackLifecycleStageChange };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lssm/lib.analytics",
3
- "version": "0.0.0-canary-20251217062943",
3
+ "version": "0.0.0-canary-20251217072406",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -23,12 +23,12 @@
23
23
  "test": "bun run"
24
24
  },
25
25
  "dependencies": {
26
- "@lssm/lib.lifecycle": "0.0.0-canary-20251217062943",
26
+ "@lssm/lib.lifecycle": "0.0.0-canary-20251217072406",
27
27
  "dayjs": "^1.11.13"
28
28
  },
29
29
  "devDependencies": {
30
- "@lssm/tool.tsdown": "0.0.0-canary-20251217062943",
31
- "@lssm/tool.typescript": "0.0.0-canary-20251217062943",
30
+ "@lssm/tool.tsdown": "0.0.0-canary-20251217072406",
31
+ "@lssm/tool.typescript": "0.0.0-canary-20251217072406",
32
32
  "tsdown": "^0.17.4",
33
33
  "typescript": "^5.9.3"
34
34
  },