@coursebuilder/analytics 1.1.0 → 1.1.2-canary.0.0c9beb2ae

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.
@@ -0,0 +1,97 @@
1
+ export const SHORTLINK_RECOVERY_LANE =
2
+ 'recovered_from_shortlink_attribution_table' as const
3
+
4
+ export type PurchaseAttributionClass =
5
+ | 'purchase_field'
6
+ | typeof SHORTLINK_RECOVERY_LANE
7
+ | 'dark'
8
+
9
+ export interface ShortlinkAttributionEvidence {
10
+ type?: string | null
11
+ metadata?: unknown
12
+ }
13
+
14
+ function isRecord(value: unknown): value is Record<string, unknown> {
15
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
16
+ }
17
+
18
+ function nonEmptyString(value: unknown) {
19
+ return typeof value === 'string' && value.trim().length > 0
20
+ }
21
+
22
+ function parseMetadata(value: unknown): Record<string, unknown> | null {
23
+ if (typeof value === 'string') {
24
+ try {
25
+ const parsed = JSON.parse(value) as unknown
26
+ return isRecord(parsed) ? parsed : null
27
+ } catch {
28
+ return null
29
+ }
30
+ }
31
+
32
+ return isRecord(value) ? value : null
33
+ }
34
+
35
+ /**
36
+ * Detects durable purchase-field attribution on a purchase fields object.
37
+ *
38
+ * @param fields Unknown purchase fields payload.
39
+ * @returns True when the purchase already has attribution, legacy UTM source, or legacy GA client ID evidence.
40
+ * @example
41
+ * hasPurchaseFieldAttribution({ attribution: { source: 'email' } })
42
+ */
43
+ export function hasPurchaseFieldAttribution(fields: unknown) {
44
+ const record = isRecord(fields) ? fields : {}
45
+ const attribution = record.attribution
46
+
47
+ return Boolean(
48
+ (isRecord(attribution) && Object.keys(attribution).length > 0) ||
49
+ nonEmptyString(record.utmSource) ||
50
+ nonEmptyString(record.gaClientId),
51
+ )
52
+ }
53
+
54
+ /**
55
+ * Detects exact shortlink purchase evidence for one purchase.
56
+ *
57
+ * @param params.purchaseId Purchase ID that must match attribution metadata.purchaseId.
58
+ * @param params.attributions ShortlinkAttributionEvidence rows to inspect.
59
+ * @returns True only for purchase rows with valid JSON metadata and an exact purchase ID match.
60
+ */
61
+ export function hasExactShortlinkPurchaseAttribution(params: {
62
+ purchaseId: string
63
+ attributions: readonly ShortlinkAttributionEvidence[]
64
+ }) {
65
+ return params.attributions.some((attribution) => {
66
+ if (attribution.type !== 'purchase') return false
67
+ const metadata = parseMetadata(attribution.metadata)
68
+ return metadata?.purchaseId === params.purchaseId
69
+ })
70
+ }
71
+
72
+ /**
73
+ * Classifies purchase attribution with purchase fields taking precedence.
74
+ *
75
+ * @param params.purchaseId Purchase ID being classified.
76
+ * @param params.fields Purchase fields payload.
77
+ * @param params.shortlinkAttributions ShortlinkAttributionEvidence rows used as exact fallback evidence.
78
+ * @returns A PurchaseAttributionClass: 'purchase_field', SHORTLINK_RECOVERY_LANE, or 'dark'.
79
+ * @example
80
+ * classifyPurchaseAttribution({ purchaseId: 'p1', fields: {}, shortlinkAttributions: [] })
81
+ */
82
+ export function classifyPurchaseAttribution(params: {
83
+ purchaseId: string
84
+ fields?: unknown
85
+ shortlinkAttributions?: readonly ShortlinkAttributionEvidence[]
86
+ }): PurchaseAttributionClass {
87
+ if (hasPurchaseFieldAttribution(params.fields)) return 'purchase_field'
88
+ if (
89
+ hasExactShortlinkPurchaseAttribution({
90
+ purchaseId: params.purchaseId,
91
+ attributions: params.shortlinkAttributions ?? [],
92
+ })
93
+ ) {
94
+ return SHORTLINK_RECOVERY_LANE
95
+ }
96
+ return 'dark'
97
+ }