@ak--47/dungeon-master 1.4.2 → 1.4.3
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/dungeons/vertical/community.js +75 -6
- package/dungeons/vertical/crypto.js +63 -8
- package/dungeons/vertical/dating.js +36 -4
- package/dungeons/vertical/devtools.js +25 -0
- package/dungeons/vertical/ecommerce.js +260 -31
- package/dungeons/vertical/education.js +107 -39
- package/dungeons/vertical/fintech.js +28 -28
- package/dungeons/vertical/fitness.js +48 -34
- package/dungeons/vertical/food-delivery.js +112 -30
- package/dungeons/vertical/gaming.js +48 -58
- package/dungeons/vertical/healthcare.js +47 -37
- package/dungeons/vertical/insurance-application.js +342 -61
- package/dungeons/vertical/logistics.js +37 -5
- package/dungeons/vertical/marketplace.js +42 -18
- package/dungeons/vertical/media.js +125 -57
- package/dungeons/vertical/real-estate.js +25 -0
- package/dungeons/vertical/sass.js +161 -20
- package/dungeons/vertical/social.js +25 -0
- package/dungeons/vertical/travel.js +74 -3
- package/index.js +2 -1
- package/lib/utils/utils.js +80 -7
- package/lib/verify/emulate-breakdown.js +1 -1
- package/lib/verify/index.js +1 -0
- package/lib/verify/schema-validator.js +258 -0
- package/lib/verify/verify-dungeon.js +6 -5
- package/package.json +1 -1
- package/scripts/verify-runner.mjs +1 -1
- package/types.d.ts +29 -1
|
@@ -13,6 +13,7 @@ import utc from "dayjs/plugin/utc.js";
|
|
|
13
13
|
dayjs.extend(utc);
|
|
14
14
|
import { uid, comma } from 'ak-tools';
|
|
15
15
|
import { weighNumRange, date, integer, weighChoices, decimal, initChance } from "../../lib/utils/utils.js";
|
|
16
|
+
import { scaleFunnelTTC } from "../../lib/hook-helpers/timing.js";
|
|
16
17
|
const chance = initChance(SEED);
|
|
17
18
|
|
|
18
19
|
/** @typedef {import("../../types").Dungeon} Config */
|
|
@@ -49,13 +50,27 @@ const spiritAnimals = ["duck", "dog", "otter", "penguin", "cat", "elephant", "li
|
|
|
49
50
|
|
|
50
51
|
/*
|
|
51
52
|
* ============================================================================
|
|
52
|
-
* ANALYTICS HOOKS (
|
|
53
|
-
*
|
|
54
|
-
* Adds 7. SIGNUP FLOW TIME-TO-CONVERT: gold/platinum loyalty 0.
|
|
55
|
-
* bronze 1.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
53
|
+
* ANALYTICS HOOKS (10 hooks)
|
|
54
|
+
*
|
|
55
|
+
* Adds 7. SIGNUP FLOW TIME-TO-CONVERT: gold/platinum loyalty 0.67x faster,
|
|
56
|
+
* bronze 1.33x slower (everything hook via scaleFunnelTTC on the page-view-
|
|
57
|
+
* to-sign-up window). Loyalty tier is determined from SCD data
|
|
58
|
+
* (meta.scd.loyalty_tier) with deterministic hash fallback. Discover via
|
|
59
|
+
* bound-sequence funnel TTC breakdown by loyalty tier.
|
|
60
|
+
*
|
|
61
|
+
* Adds 8. CHECKOUT FLOW EXPERIMENT: A/B/C experiment on the eCommerce
|
|
62
|
+
* Purchase funnel — "Control", "Express Checkout" (1.25x conversion),
|
|
63
|
+
* "Social Proof" (1.15x conversion, 0.9x TTC). Engine-managed via funnel
|
|
64
|
+
* experiment config; starts 30 days before dataset end.
|
|
65
|
+
*
|
|
66
|
+
* Adds 9. DARK THEME POWER USERS: funnel-pre hook that boosts purchase
|
|
67
|
+
* funnel conversion 1.3x for dark-theme users and penalizes light-theme
|
|
68
|
+
* users to 0.85x. Discoverable by breakdown of funnel conversion by theme.
|
|
69
|
+
*
|
|
70
|
+
* Adds 10. SAVE-ITEM RETENTION: born-in-dataset users who save 2+ items
|
|
71
|
+
* in their first 10 days retain long-term; those below threshold lose ~70%
|
|
72
|
+
* of post-day-25 events. Discoverable by retention or frequency analysis
|
|
73
|
+
* segmented by early save-item count.
|
|
59
74
|
* ============================================================================
|
|
60
75
|
*
|
|
61
76
|
* ----------------------------------------------------------------------------
|
|
@@ -200,10 +215,12 @@ const spiritAnimals = ["duck", "dog", "otter", "penguin", "cat", "elephant", "li
|
|
|
200
215
|
*
|
|
201
216
|
* PATTERN: Users who view 3-8 items in the dataset window are in the
|
|
202
217
|
* "considered buyer" sweet spot — every cart item amount and total_value
|
|
203
|
-
* gets boosted ~25
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
218
|
+
* gets boosted ~25%, AND ~45% of their add-to-cart events are cloned
|
|
219
|
+
* (with time offsets) to elevate their cart add rate. Users who view
|
|
220
|
+
* 9 or more items are over-engaged window-shoppers; ~30% of their
|
|
221
|
+
* checkout events are dropped (decision paralysis / browse without buy).
|
|
222
|
+
* No flag is stamped — discoverable only by binning users on view-item
|
|
223
|
+
* COUNT and comparing avg cart total or add-to-cart rate.
|
|
207
224
|
*
|
|
208
225
|
* HOW TO FIND IT IN MIXPANEL:
|
|
209
226
|
*
|
|
@@ -229,6 +246,121 @@ const spiritAnimals = ["duck", "dog", "otter", "penguin", "cat", "elephant", "li
|
|
|
229
246
|
* tends to convert at higher cart value; an excessive browser is a
|
|
230
247
|
* tire-kicker who abandons more often.
|
|
231
248
|
*
|
|
249
|
+
* ----------------------------------------------------------------------------
|
|
250
|
+
* Hook 7: Signup Flow Time-to-Convert (everything)
|
|
251
|
+
* ----------------------------------------------------------------------------
|
|
252
|
+
*
|
|
253
|
+
* PATTERN: The Signup Flow funnel (page view → view item → save item →
|
|
254
|
+
* page view → sign up) has its time-to-convert scaled by the user's
|
|
255
|
+
* loyalty tier from SCD data (meta.scd.loyalty_tier). Gold and platinum
|
|
256
|
+
* users complete the funnel 0.67x faster; bronze users take 1.33x longer;
|
|
257
|
+
* silver is unaffected. The hook reads the latest SCD entry for
|
|
258
|
+
* loyalty_tier, falling back to a deterministic hash of user_id for
|
|
259
|
+
* consistent tier assignment. It anchors on the first "sign up" event,
|
|
260
|
+
* looks back up to 2 days for all funnel-step events in that window,
|
|
261
|
+
* and scales the whole cluster via scaleFunnelTTC.
|
|
262
|
+
*
|
|
263
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
264
|
+
*
|
|
265
|
+
* Report 1: Signup Funnel TTC by Loyalty Tier
|
|
266
|
+
* - Report type: Funnels
|
|
267
|
+
* - Steps: "page view" → "view item" → "save item" → "page view" → "sign up"
|
|
268
|
+
* - Measure: Median time to convert
|
|
269
|
+
* - Breakdown: "loyalty_tier" (SCD property)
|
|
270
|
+
* - Expected: gold/platinum ~ 0.67x median TTC vs silver; bronze ~ 1.33x
|
|
271
|
+
*
|
|
272
|
+
* Report 2: Signup Funnel Conversion by Loyalty Tier
|
|
273
|
+
* - Report type: Funnels
|
|
274
|
+
* - Steps: same as above
|
|
275
|
+
* - Breakdown: "loyalty_tier"
|
|
276
|
+
* - Expected: conversion rate similar across tiers (TTC changes, not conversion)
|
|
277
|
+
*
|
|
278
|
+
* REAL-WORLD ANALOGUE: Loyal, high-tier customers already trust the
|
|
279
|
+
* platform and breeze through signup flows, while new or low-engagement
|
|
280
|
+
* users deliberate longer before committing.
|
|
281
|
+
*
|
|
282
|
+
* ----------------------------------------------------------------------------
|
|
283
|
+
* Hook 8: Checkout Flow Experiment (funnel experiment config)
|
|
284
|
+
* ----------------------------------------------------------------------------
|
|
285
|
+
*
|
|
286
|
+
* PATTERN: A/B/C experiment on the eCommerce Purchase funnel. Control
|
|
287
|
+
* group uses base conversion, "Express Checkout" variant gets 1.25x
|
|
288
|
+
* conversion multiplier, "Social Proof" gets 1.15x conversion and 0.9x
|
|
289
|
+
* time-to-convert. Starts 30 days before dataset end.
|
|
290
|
+
*
|
|
291
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
292
|
+
*
|
|
293
|
+
* Report 1: Experiment Events by Variant
|
|
294
|
+
* - Report type: Insights
|
|
295
|
+
* - Event: "$experiment_started"
|
|
296
|
+
* - Measure: Total
|
|
297
|
+
* - Breakdown: "$experiment_variant"
|
|
298
|
+
* - Expected: roughly equal distribution across Control, Express Checkout, Social Proof
|
|
299
|
+
*
|
|
300
|
+
* Report 2: Purchase Funnel Conversion by Variant
|
|
301
|
+
* - Report type: Funnels
|
|
302
|
+
* - Funnel: view item → add to cart → checkout
|
|
303
|
+
* - Breakdown: "$experiment_variant"
|
|
304
|
+
* - Expected: Express Checkout > Social Proof > Control conversion rate
|
|
305
|
+
*
|
|
306
|
+
* REAL-WORLD ANALOGUE: Product team tests a streamlined checkout and a
|
|
307
|
+
* social proof variant against the existing flow to lift conversion.
|
|
308
|
+
*
|
|
309
|
+
* ----------------------------------------------------------------------------
|
|
310
|
+
* Hook 9: Dark Theme Power Users (funnel-pre)
|
|
311
|
+
* ----------------------------------------------------------------------------
|
|
312
|
+
*
|
|
313
|
+
* PATTERN: Dark-theme users convert 1.3x better on the purchase funnel;
|
|
314
|
+
* light-theme users convert at 0.85x. Custom theme is unaffected.
|
|
315
|
+
*
|
|
316
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
317
|
+
*
|
|
318
|
+
* Report 1: Purchase Funnel by Theme
|
|
319
|
+
* - Report type: Funnels
|
|
320
|
+
* - Funnel: view item → add to cart → checkout
|
|
321
|
+
* - Breakdown: "theme"
|
|
322
|
+
* - Expected: dark > custom > light conversion rate
|
|
323
|
+
*
|
|
324
|
+
* Report 2: Checkout Count by Theme
|
|
325
|
+
* - Report type: Insights
|
|
326
|
+
* - Event: "checkout"
|
|
327
|
+
* - Measure: Total
|
|
328
|
+
* - Breakdown: "theme"
|
|
329
|
+
* - Expected: dark-theme users over-index on checkout counts
|
|
330
|
+
*
|
|
331
|
+
* REAL-WORLD ANALOGUE: Power users who customize their UI (dark mode)
|
|
332
|
+
* tend to be more committed and convert at higher rates.
|
|
333
|
+
*
|
|
334
|
+
* ----------------------------------------------------------------------------
|
|
335
|
+
* Hook 10: Save-Item Retention (everything — retention magic number)
|
|
336
|
+
* ----------------------------------------------------------------------------
|
|
337
|
+
*
|
|
338
|
+
* PATTERN: Born-in-dataset users who perform 2+ "save item" events in
|
|
339
|
+
* their first 10 days retain long-term. Users below that threshold lose
|
|
340
|
+
* ~70% of events after day 25, simulating churn. No flag is stamped —
|
|
341
|
+
* discoverable only by segmenting users on early save-item count and
|
|
342
|
+
* comparing retention or late-period activity.
|
|
343
|
+
*
|
|
344
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
345
|
+
*
|
|
346
|
+
* Report 1: Retention by Early Save Count
|
|
347
|
+
* - Report type: Retention
|
|
348
|
+
* - First event: any event
|
|
349
|
+
* - Return event: any event
|
|
350
|
+
* - Cohort A: users with >= 2 "save item" in first 10 days
|
|
351
|
+
* - Cohort B: users with < 2 "save item" in first 10 days
|
|
352
|
+
* - Expected: Cohort A retains at 4+ weeks; Cohort B drops sharply after week 3
|
|
353
|
+
*
|
|
354
|
+
* Report 2: Post-Day-25 Activity
|
|
355
|
+
* - Report type: Insights
|
|
356
|
+
* - Event: any event
|
|
357
|
+
* - Filter: time > first_event + 25 days
|
|
358
|
+
* - Cohort A: >= 2 early saves; Cohort B: < 2 early saves
|
|
359
|
+
* - Expected: Cohort B has ~70% fewer events in the late window
|
|
360
|
+
*
|
|
361
|
+
* REAL-WORLD ANALOGUE: Saving items signals purchase intent and product
|
|
362
|
+
* engagement; users who curate a wishlist early are more likely to return.
|
|
363
|
+
*
|
|
232
364
|
* ============================================================================
|
|
233
365
|
* EXPECTED METRICS SUMMARY
|
|
234
366
|
* ============================================================================
|
|
@@ -240,8 +372,17 @@ const spiritAnimals = ["duck", "dog", "otter", "penguin", "cat", "elephant", "li
|
|
|
240
372
|
* Toys + Shoes Correlation | toys/shoes co-occurrence| ~ 7% | ~ 50%+ | ~ 7x
|
|
241
373
|
* Quality -> Watch Time | Avg watchTimeSec (240p->2160p) | 0.7x | 1.5x | ~ 2.1x
|
|
242
374
|
* Item Flattening | category breakdown | nested | flat | n/a
|
|
243
|
-
* View-Item Magic Number | sweet (3-8) cart
|
|
375
|
+
* View-Item Magic Number | sweet (3-8) cart rate | 1x | ~ 1.45x | 1.45x
|
|
244
376
|
* View-Item Magic Number | over (9+) checkouts/user| 1x | ~ 0.7x | -30%
|
|
377
|
+
* Signup TTC by Loyalty | gold/plat median TTC | 1x | ~ 0.67x | -33%
|
|
378
|
+
* Signup TTC by Loyalty | bronze median TTC | 1x | ~ 1.33x | +33%
|
|
379
|
+
* Checkout Experiment | Express Checkout conv | 1x | ~ 1.25x | +25%
|
|
380
|
+
* Checkout Experiment | Social Proof conv | 1x | ~ 1.15x | +15%
|
|
381
|
+
* Checkout Experiment | Social Proof TTC | 1x | ~ 0.9x | -10%
|
|
382
|
+
* Dark Theme Power Users | dark-theme conv rate | 1x | ~ 1.3x | +30%
|
|
383
|
+
* Dark Theme Power Users | light-theme conv rate | 1x | ~ 0.85x | -15%
|
|
384
|
+
* Save-Item Retention | post-d25 events (saver) | 1x | ~ 1x | baseline
|
|
385
|
+
* Save-Item Retention | post-d25 events (non-s) | 1x | ~ 0.3x | -70%
|
|
245
386
|
* ============================================================================
|
|
246
387
|
*/
|
|
247
388
|
|
|
@@ -457,6 +598,15 @@ const config = {
|
|
|
457
598
|
requireRepeats: true,
|
|
458
599
|
weight: 10,
|
|
459
600
|
order: "last-fixed",
|
|
601
|
+
experiment: {
|
|
602
|
+
name: "Express Checkout",
|
|
603
|
+
variants: [
|
|
604
|
+
{ name: "Control" },
|
|
605
|
+
{ name: "Express Checkout", conversionMultiplier: 1.25 },
|
|
606
|
+
{ name: "Social Proof", conversionMultiplier: 1.15, ttcMultiplier: 0.9 },
|
|
607
|
+
],
|
|
608
|
+
startDaysBeforeEnd: 30,
|
|
609
|
+
},
|
|
460
610
|
}
|
|
461
611
|
|
|
462
612
|
],
|
|
@@ -492,24 +642,15 @@ const config = {
|
|
|
492
642
|
lookupTables: [],
|
|
493
643
|
hook: function (record, type, meta) {
|
|
494
644
|
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
-
if (type === "funnel-
|
|
499
|
-
const
|
|
500
|
-
if (
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
1.0
|
|
505
|
-
);
|
|
506
|
-
if (factor !== 1.0) {
|
|
507
|
-
for (let i = 1; i < record.length; i++) {
|
|
508
|
-
const prev = dayjs(record[i - 1].time);
|
|
509
|
-
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
510
|
-
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
511
|
-
}
|
|
512
|
-
}
|
|
645
|
+
// H9: Dark Theme Power Users (funnel-pre)
|
|
646
|
+
// Dark-theme users convert 1.3x better on the purchase funnel;
|
|
647
|
+
// light-theme users convert 0.85x. Custom-theme is unchanged.
|
|
648
|
+
if (type === "funnel-pre") {
|
|
649
|
+
const isPurchaseFunnel = meta.funnel?.sequence?.includes("checkout");
|
|
650
|
+
if (isPurchaseFunnel) {
|
|
651
|
+
const theme = meta.profile?.theme;
|
|
652
|
+
if (theme === "dark") record.conversionRate = Math.min(95, Math.round(record.conversionRate * 1.3));
|
|
653
|
+
else if (theme === "light") record.conversionRate = Math.round(record.conversionRate * 0.85);
|
|
513
654
|
}
|
|
514
655
|
}
|
|
515
656
|
|
|
@@ -614,7 +755,8 @@ const config = {
|
|
|
614
755
|
});
|
|
615
756
|
|
|
616
757
|
// Hook 6: View-Item Magic Number (behavioral, no flags)
|
|
617
|
-
// Users in 3-8 view-item sweet spot show ~25% higher cart amounts
|
|
758
|
+
// Users in 3-8 view-item sweet spot show ~25% higher cart amounts
|
|
759
|
+
// AND elevated add-to-cart rate (~45% boost via cloned events).
|
|
618
760
|
// Users >=9 are over-engaged window-shoppers; ~30% of their checkouts drop.
|
|
619
761
|
const viewItemCount = record.filter(e => e.event === 'view item').length;
|
|
620
762
|
if (viewItemCount >= 3 && viewItemCount <= 8) {
|
|
@@ -627,6 +769,20 @@ const config = {
|
|
|
627
769
|
}));
|
|
628
770
|
}
|
|
629
771
|
});
|
|
772
|
+
// Clone ~45% of add-to-cart events to boost cart add rate
|
|
773
|
+
const addToCartEvents = record.filter(e => e.event === 'add to cart');
|
|
774
|
+
const clones = [];
|
|
775
|
+
addToCartEvents.forEach(e => {
|
|
776
|
+
if (chance.bool({ likelihood: 45 })) {
|
|
777
|
+
const offsetMs = integer(60_000, 600_000); // 1-10 min offset
|
|
778
|
+
clones.push({
|
|
779
|
+
...e,
|
|
780
|
+
insert_id: uid(),
|
|
781
|
+
time: dayjs(e.time).add(offsetMs, 'milliseconds').toISOString()
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
if (clones.length > 0) record.push(...clones);
|
|
630
786
|
} else if (viewItemCount >= 9) {
|
|
631
787
|
for (let i = record.length - 1; i >= 0; i--) {
|
|
632
788
|
if (record[i].event === 'checkout' && chance.bool({ likelihood: 30 })) {
|
|
@@ -635,6 +791,79 @@ const config = {
|
|
|
635
791
|
}
|
|
636
792
|
}
|
|
637
793
|
|
|
794
|
+
// Hook 7 (TTC): SIGNUP FLOW TIME-TO-CONVERT (everything)
|
|
795
|
+
// Gold/platinum loyalty users complete the Signup Flow funnel
|
|
796
|
+
// 0.67x faster; bronze users 1.33x slower. Loyalty tier is read
|
|
797
|
+
// from SCD data (meta.scd.loyalty_tier); falls back to a
|
|
798
|
+
// deterministic hash of user_id for consistent assignment.
|
|
799
|
+
//
|
|
800
|
+
// The funnel uses order:"first-and-last-fixed" so middle steps
|
|
801
|
+
// can reorder. We anchor on the first "sign up" event, look back
|
|
802
|
+
// up to 2 days for all funnel-step events in that window, and
|
|
803
|
+
// scale the whole cluster via scaleFunnelTTC.
|
|
804
|
+
{
|
|
805
|
+
// Determine loyalty tier from SCD (latest entry) or hash fallback
|
|
806
|
+
let loyaltyTier = "silver"; // default baseline
|
|
807
|
+
const scdEntries = meta?.scd?.loyalty_tier;
|
|
808
|
+
if (Array.isArray(scdEntries) && scdEntries.length > 0) {
|
|
809
|
+
// Pick the latest SCD entry
|
|
810
|
+
const latest = scdEntries.reduce((a, b) =>
|
|
811
|
+
(a.time > b.time ? a : b));
|
|
812
|
+
loyaltyTier = latest.loyalty_tier || "silver";
|
|
813
|
+
} else {
|
|
814
|
+
// Deterministic hash fallback: gold ~20%, silver ~30%, bronze ~50%
|
|
815
|
+
const uid = record[0]?.user_id || "";
|
|
816
|
+
const hash = uid.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0);
|
|
817
|
+
const bucket = hash % 100;
|
|
818
|
+
loyaltyTier = bucket < 20 ? "gold" : bucket < 50 ? "silver" : "bronze";
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const ttcFactor = (
|
|
822
|
+
loyaltyTier === "gold" || loyaltyTier === "platinum" ? 0.67 :
|
|
823
|
+
loyaltyTier === "bronze" ? 1.33 :
|
|
824
|
+
1.0
|
|
825
|
+
);
|
|
826
|
+
if (ttcFactor !== 1.0) {
|
|
827
|
+
// Find signup funnel: anchor on first "sign up", look back 2 days
|
|
828
|
+
// for the cluster of funnel-step events
|
|
829
|
+
const funnelSteps = new Set(["page view", "view item", "save item", "sign up"]);
|
|
830
|
+
const sorted = record.slice().sort((a, b) =>
|
|
831
|
+
new Date(a.time) - new Date(b.time));
|
|
832
|
+
const signupEvent = sorted.find(e => e.event === "sign up");
|
|
833
|
+
if (signupEvent) {
|
|
834
|
+
const signupMs = new Date(signupEvent.time).getTime();
|
|
835
|
+
const windowMs = 2 * 24 * 60 * 60 * 1000; // 2-day lookback
|
|
836
|
+
const funnelEvents = sorted.filter(e =>
|
|
837
|
+
funnelSteps.has(e.event) &&
|
|
838
|
+
new Date(e.time).getTime() >= signupMs - windowMs &&
|
|
839
|
+
new Date(e.time).getTime() <= signupMs
|
|
840
|
+
);
|
|
841
|
+
if (funnelEvents.length >= 2) {
|
|
842
|
+
scaleFunnelTTC(funnelEvents, ttcFactor);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// H10: Save-Item Retention (everything — retention magic number)
|
|
851
|
+
// Born-in-dataset users with 2+ "save item" in their first 10 days
|
|
852
|
+
// retain; users below the threshold lose ~70% of events after day 25.
|
|
853
|
+
if (type === "everything" && meta.userIsBornInDataset) {
|
|
854
|
+
const firstT = record[0]?.time;
|
|
855
|
+
if (firstT) {
|
|
856
|
+
const window10 = dayjs(firstT).add(10, 'days').toISOString();
|
|
857
|
+
const saves = record.filter(e => e.event === 'save item' && e.time <= window10).length;
|
|
858
|
+
if (saves < 2) {
|
|
859
|
+
const cutoff = dayjs(firstT).add(25, 'days');
|
|
860
|
+
for (let i = record.length - 1; i >= 0; i--) {
|
|
861
|
+
if (dayjs(record[i].time).isAfter(cutoff) && chance.bool({ likelihood: 70 })) {
|
|
862
|
+
record.splice(i, 1);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
638
867
|
}
|
|
639
868
|
|
|
640
869
|
// Filter out events tagged for removal in the event hook
|
|
@@ -52,7 +52,7 @@ const chance = u.initChance(SEED);
|
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
55
|
-
* ANALYTICS HOOKS (
|
|
55
|
+
* ANALYTICS HOOKS (10 hooks)
|
|
56
56
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
57
57
|
*
|
|
58
58
|
* NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable via
|
|
@@ -216,7 +216,7 @@ const chance = u.initChance(SEED);
|
|
|
216
216
|
* ---------------------------------------------------------------
|
|
217
217
|
* 7. FREE VS PAID COURSES (funnel-pre + everything)
|
|
218
218
|
*
|
|
219
|
-
* PATTERN: Free users get 0.5x funnel conversion rate; paid
|
|
219
|
+
* PATTERN: Free users get 0.5x funnel conversion rate on the cert funnel only; paid
|
|
220
220
|
* subscribers get 1.5x. Free users also lose 55% of their
|
|
221
221
|
* certificates, producing a ~2.2x completion gap.
|
|
222
222
|
*
|
|
@@ -266,10 +266,13 @@ const chance = u.initChance(SEED);
|
|
|
266
266
|
* despite spending less time.
|
|
267
267
|
*
|
|
268
268
|
* ---------------------------------------------------------------
|
|
269
|
-
* 9. COURSE COMPLETION TIME-TO-CONVERT (
|
|
269
|
+
* 9. COURSE COMPLETION TIME-TO-CONVERT (everything)
|
|
270
270
|
*
|
|
271
271
|
* PATTERN: Annual subscribers complete the course-completion funnel
|
|
272
|
-
*
|
|
272
|
+
* 2x faster (factor 0.5); Free users 1.8x slower (factor 1.8).
|
|
273
|
+
* Applied in the everything hook by scaling the enrolled-to-cert
|
|
274
|
+
* gap on the raw events. Stronger factors compensate for the
|
|
275
|
+
* composition effect from H7's conversion-rate gating.
|
|
273
276
|
*
|
|
274
277
|
* HOW TO FIND IT IN MIXPANEL:
|
|
275
278
|
*
|
|
@@ -277,15 +280,40 @@ const chance = u.initChance(SEED);
|
|
|
277
280
|
* - Funnels > "course enrolled" -> "lecture completed" -> "quiz completed" -> "certificate earned"
|
|
278
281
|
* - Measure: Median time to convert
|
|
279
282
|
* - Breakdown: subscription_status
|
|
280
|
-
* - Expected: annual
|
|
283
|
+
* - Expected: annual < monthly < free (direction)
|
|
281
284
|
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* across the user's full event history.
|
|
285
|
+
* Also visible via cross-event SQL: MIN("course enrolled" time) to
|
|
286
|
+
* MIN("certificate earned" time) per user, broken down by
|
|
287
|
+
* subscription_status. Annual < monthly < free.
|
|
286
288
|
*
|
|
287
289
|
* REAL-WORLD ANALOGUE: Paid commitment accelerates throughput.
|
|
288
290
|
*
|
|
291
|
+
* ---------------------------------------------------------------
|
|
292
|
+
* 10. SOCIAL LEARNING EXPERIMENT (funnel experiment)
|
|
293
|
+
*
|
|
294
|
+
* PATTERN: A/B experiment on the Social Learning funnel (discussion
|
|
295
|
+
* posted → study group joined → resource downloaded). "AI Study
|
|
296
|
+
* Buddy" variant boosts conversion 1.4x and speeds TTC to 0.85x.
|
|
297
|
+
* Activates 30 days before dataset end.
|
|
298
|
+
*
|
|
299
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
300
|
+
*
|
|
301
|
+
* Report 1: $experiment_started by Variant
|
|
302
|
+
* - Report type: Insights
|
|
303
|
+
* - Event: "$experiment_started"
|
|
304
|
+
* - Measure: Total
|
|
305
|
+
* - Breakdown: "$experiment_name" and "Variant"
|
|
306
|
+
* - Expected: ~50% Control, ~50% AI Study Buddy
|
|
307
|
+
*
|
|
308
|
+
* Report 2: Social Learning Funnel by Variant
|
|
309
|
+
* - Report type: Funnels
|
|
310
|
+
* - Steps: "discussion posted" → "study group joined" → "resource downloaded"
|
|
311
|
+
* - Breakdown: Variant
|
|
312
|
+
* - Expected: AI Study Buddy ~ 1.4x conversion vs Control
|
|
313
|
+
*
|
|
314
|
+
* REAL-WORLD ANALOGUE: AI-powered study companions boost social
|
|
315
|
+
* engagement and resource discovery in cohort-based courses.
|
|
316
|
+
*
|
|
289
317
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
290
318
|
* EXPECTED METRICS SUMMARY
|
|
291
319
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -301,7 +329,9 @@ const chance = u.initChance(SEED);
|
|
|
301
329
|
* Semester-End Spike | Assessment volume | 1x | ~ 2x | 2x
|
|
302
330
|
* Free vs Paid | Course completion | 15% | 33% | 2.2x
|
|
303
331
|
* Playback Speed | Quiz score (speed) | ~ 65 | ~ 73 | +8 pt
|
|
304
|
-
* Course Completion T2C | median min by tier | 1x | 0.
|
|
332
|
+
* Course Completion T2C | median min by tier | 1x | 0.5x/1.8x | ~ 3.6x range
|
|
333
|
+
* Social Learning Exp | AI variant conversion | 1x | 1.4x | 1.4x
|
|
334
|
+
* Social Learning Exp | AI variant TTC | 1x | 0.85x | -15%
|
|
305
335
|
*/
|
|
306
336
|
|
|
307
337
|
// Generate consistent IDs for lookup tables and event properties
|
|
@@ -374,6 +404,14 @@ const config = {
|
|
|
374
404
|
conversionRate: 50,
|
|
375
405
|
timeToConvert: 12,
|
|
376
406
|
weight: 2,
|
|
407
|
+
experiment: {
|
|
408
|
+
name: "AI Study Buddy",
|
|
409
|
+
variants: [
|
|
410
|
+
{ name: "Control" },
|
|
411
|
+
{ name: "AI Study Buddy", conversionMultiplier: 1.4, ttcMultiplier: 0.85 },
|
|
412
|
+
],
|
|
413
|
+
startDaysBeforeEnd: 30,
|
|
414
|
+
},
|
|
377
415
|
},
|
|
378
416
|
{
|
|
379
417
|
// Instructor interaction loop
|
|
@@ -617,7 +655,7 @@ const config = {
|
|
|
617
655
|
/**
|
|
618
656
|
* ARCHITECTED ANALYTICS HOOKS
|
|
619
657
|
*
|
|
620
|
-
* This hook function creates
|
|
658
|
+
* This hook function creates 10 deliberate patterns in the data:
|
|
621
659
|
*
|
|
622
660
|
* 1. STUDENT VS INSTRUCTOR PROFILES: Instructor profiles get teaching attributes; students get learning attributes
|
|
623
661
|
* 2. DEADLINE CRAMMING: Assignments submitted on Sun/Mon are rushed and lower quality
|
|
@@ -625,8 +663,10 @@ const config = {
|
|
|
625
663
|
* 4. STUDY GROUP RETENTION: Early study group joiners retain; non-joiners with low scores churn
|
|
626
664
|
* 5. HINT DEPENDENCY: Hint users get locked into easy problems; non-hint users tackle harder ones
|
|
627
665
|
* 6. SEMESTER-END SPIKE: Days 75-85 see doubled assessment activity (cramming period)
|
|
628
|
-
* 7. FREE VS PAID COURSES: Paid subscribers convert through Course Completion funnel at ~2.2x rate
|
|
666
|
+
* 7. FREE VS PAID COURSES: Paid subscribers convert through Course Completion funnel at ~2.2x rate (funnel-pre scoped to cert funnel only)
|
|
629
667
|
* 8. PLAYBACK SPEED CORRELATION: Speed learners paradoxically score higher; thorough learners get extended time
|
|
668
|
+
* 9. COURSE COMPLETION TTC: Annual subscribers complete cert funnel faster; free users slower (everything hook)
|
|
669
|
+
* 10. SOCIAL LEARNING EXPERIMENT: A/B test on Social Learning funnel with AI Study Buddy variant (funnel experiment config)
|
|
630
670
|
*/
|
|
631
671
|
hook: function (record, type, meta) {
|
|
632
672
|
// HOOK 1: STUDENT VS INSTRUCTOR PROFILES (user) — role-based attributes.
|
|
@@ -664,33 +704,18 @@ const config = {
|
|
|
664
704
|
}
|
|
665
705
|
|
|
666
706
|
// HOOK 7 (funnel-pre): FREE VS PAID — free users get 0.5x conversion rate;
|
|
667
|
-
// paid subscribers get 1.5x.
|
|
707
|
+
// paid subscribers get 1.5x. Scoped to the course-completion funnel ONLY
|
|
708
|
+
// (sequence ending in "certificate earned") to avoid displacing standalone
|
|
709
|
+
// events for paid users and triggering unintended churn in H4.
|
|
668
710
|
if (type === "funnel-pre") {
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
// HOOK 9 (T2C): COURSE COMPLETION TIME-TO-CONVERT (funnel-post)
|
|
678
|
-
// Annual subscribers complete the Course Completion funnel 1.4x
|
|
679
|
-
// faster (factor 0.71); Free users 1.4x slower (factor 1.4).
|
|
680
|
-
if (type === "funnel-post") {
|
|
681
|
-
const segment = meta?.profile?.subscription_status;
|
|
682
|
-
if (Array.isArray(record) && record.length > 1) {
|
|
683
|
-
const factor = (
|
|
684
|
-
segment === "annual" ? 0.71 :
|
|
685
|
-
segment === "free" ? 1.4 :
|
|
686
|
-
1.0
|
|
687
|
-
);
|
|
688
|
-
if (factor !== 1.0) {
|
|
689
|
-
for (let i = 1; i < record.length; i++) {
|
|
690
|
-
const prev = dayjs(record[i - 1].time);
|
|
691
|
-
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
692
|
-
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
693
|
-
}
|
|
711
|
+
const isCertFunnel = Array.isArray(meta?.funnel?.sequence) &&
|
|
712
|
+
meta.funnel.sequence.includes("certificate earned");
|
|
713
|
+
if (isCertFunnel) {
|
|
714
|
+
const subStatus = meta?.profile?.subscription_status;
|
|
715
|
+
if (subStatus === "free") {
|
|
716
|
+
record.conversionRate = Math.round(record.conversionRate * 0.5);
|
|
717
|
+
} else if (subStatus === "monthly" || subStatus === "annual") {
|
|
718
|
+
record.conversionRate = Math.min(100, Math.round(record.conversionRate * 1.5));
|
|
694
719
|
}
|
|
695
720
|
}
|
|
696
721
|
}
|
|
@@ -779,8 +804,51 @@ const config = {
|
|
|
779
804
|
});
|
|
780
805
|
if (duplicates.length > 0) userEvents.push(...duplicates);
|
|
781
806
|
|
|
782
|
-
// HOOK 7: FREE VS PAID — free users lose 55% of certificates.
|
|
783
807
|
const subStatus = profile ? profile.subscription_status : "free";
|
|
808
|
+
|
|
809
|
+
// HOOK 9 (T2C): COURSE COMPLETION TIME-TO-CONVERT (everything)
|
|
810
|
+
// Annual subscribers complete the cert funnel 2x faster (factor 0.5);
|
|
811
|
+
// Free users 1.8x slower (factor 1.8). For each "certificate earned"
|
|
812
|
+
// event, find the nearest preceding "course enrolled" and scale the gap.
|
|
813
|
+
// Runs BEFORE cert-dropping (H7) so TTC adjustments aren't masked by
|
|
814
|
+
// survivorship bias from the 55% free cert removal.
|
|
815
|
+
{
|
|
816
|
+
const ttcFactor = (
|
|
817
|
+
subStatus === "annual" ? 0.5 :
|
|
818
|
+
subStatus === "free" ? 1.8 :
|
|
819
|
+
1.0
|
|
820
|
+
);
|
|
821
|
+
if (ttcFactor !== 1.0) {
|
|
822
|
+
// Collect all "course enrolled" times (sorted) for binary lookup
|
|
823
|
+
const enrolledTimes = userEvents
|
|
824
|
+
.filter(e => e.event === "course enrolled")
|
|
825
|
+
.map(e => dayjs(e.time))
|
|
826
|
+
.sort((a, b) => a.valueOf() - b.valueOf());
|
|
827
|
+
|
|
828
|
+
if (enrolledTimes.length > 0) {
|
|
829
|
+
for (const event of userEvents) {
|
|
830
|
+
if (event.event === "certificate earned") {
|
|
831
|
+
const certTime = dayjs(event.time);
|
|
832
|
+
// Find the latest enrolled time before this cert
|
|
833
|
+
let anchor = null;
|
|
834
|
+
for (let k = enrolledTimes.length - 1; k >= 0; k--) {
|
|
835
|
+
if (enrolledTimes[k].isBefore(certTime)) {
|
|
836
|
+
anchor = enrolledTimes[k];
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (anchor) {
|
|
841
|
+
const gap = certTime.diff(anchor);
|
|
842
|
+
const newGap = Math.round(gap * ttcFactor);
|
|
843
|
+
event.time = anchor.add(newGap, "milliseconds").toISOString();
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// HOOK 7: FREE VS PAID — free users lose 55% of certificates.
|
|
784
852
|
if (subStatus === "free") {
|
|
785
853
|
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
786
854
|
if (userEvents[i].event === "certificate earned" && chance.bool({ likelihood: 55 })) {
|