@ak--47/dungeon-master 1.2.3 → 1.3.1
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/CHANGELOG.md +42 -0
- package/README.md +51 -13
- package/dungeons/technical/ad-spend.js +2 -2
- package/dungeons/technical/anonymous-users.js +2 -2
- package/dungeons/technical/array-of-object-lookup.js +2 -4
- package/dungeons/technical/experiments.js +2 -2
- package/dungeons/technical/foobar.js +2 -2
- package/dungeons/technical/group-analytics.js +2 -2
- package/dungeons/technical/mirror-strategies.js +2 -2
- package/dungeons/technical/nested-objects.js +2 -2
- package/dungeons/technical/retention-cadence.js +2 -3
- package/dungeons/technical/sanity.js +2 -2
- package/dungeons/technical/scale-test.js +2 -2
- package/dungeons/technical/scd.js +2 -2
- package/dungeons/technical/simple.js +5 -6
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/simplest.js +2 -2
- package/dungeons/technical/text-generation.js +3 -3
- package/dungeons/vertical/ai-platform.js +858 -0
- package/dungeons/vertical/community.js +84 -40
- package/dungeons/vertical/crypto.js +830 -0
- package/dungeons/vertical/dating.js +744 -0
- package/dungeons/vertical/devtools.js +175 -69
- package/dungeons/vertical/ecommerce.js +242 -94
- package/dungeons/vertical/education.js +330 -313
- package/dungeons/vertical/fintech.js +442 -313
- package/dungeons/vertical/fitness.js +143 -61
- package/dungeons/vertical/food-delivery.js +327 -353
- package/dungeons/vertical/gaming.js +912 -382
- package/dungeons/vertical/healthcare.js +142 -63
- package/dungeons/vertical/insurance-application.js +170 -76
- package/dungeons/vertical/logistics.js +115 -20
- package/dungeons/vertical/marketplace.js +152 -58
- package/dungeons/vertical/media.js +248 -384
- package/dungeons/vertical/real-estate.js +781 -0
- package/dungeons/vertical/sass.js +255 -266
- package/dungeons/vertical/social.js +264 -206
- package/dungeons/vertical/travel.js +117 -41
- package/index.js +17 -17
- package/lib/core/config-validator.js +159 -31
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +11 -14
- package/lib/generators/funnels.js +12 -4
- package/lib/generators/mirror.js +3 -2
- package/lib/generators/product-names.js +1 -1
- package/lib/generators/scd.js +2 -1
- package/lib/generators/text.js +1 -1
- package/lib/orchestrators/user-loop.js +81 -47
- package/lib/templates/macro-presets.js +111 -0
- package/lib/templates/soup-presets.js +19 -36
- package/lib/utils/utils.js +71 -39
- package/package.json +8 -2
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +251 -51
- package/dungeons/technical/ad-spend-schema.json +0 -128
- package/dungeons/technical/anonymous-users-schema.json +0 -92
- package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
- package/dungeons/technical/experiments-schema.json +0 -203
- package/dungeons/technical/foobar-schema.json +0 -362
- package/dungeons/technical/group-analytics-schema.json +0 -241
- package/dungeons/technical/mirror-strategies-schema.json +0 -84
- package/dungeons/technical/nested-objects-schema.json +0 -145
- package/dungeons/technical/retention-cadence-schema.json +0 -37
- package/dungeons/technical/sanity-schema.json +0 -185
- package/dungeons/technical/scale-test-schema.json +0 -70
- package/dungeons/technical/scd-schema.json +0 -467
- package/dungeons/technical/simple-schema.json +0 -362
- package/dungeons/technical/text-generation-schema.json +0 -1062
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/devtools-schema.json +0 -601
- package/dungeons/vertical/ecommerce-schema.json +0 -604
- package/dungeons/vertical/education-schema.json +0 -5686
- package/dungeons/vertical/fintech-schema.json +0 -630
- package/dungeons/vertical/fitness-schema.json +0 -530
- package/dungeons/vertical/food-delivery-schema.json +0 -36728
- package/dungeons/vertical/gaming-schema.json +0 -438
- package/dungeons/vertical/healthcare-schema.json +0 -549
- package/dungeons/vertical/insurance-application-schema.json +0 -485
- package/dungeons/vertical/logistics-schema.json +0 -574
- package/dungeons/vertical/marketplace-schema.json +0 -533
- package/dungeons/vertical/media-schema.json +0 -4749
- package/dungeons/vertical/rpg-schema.json +0 -2491
- package/dungeons/vertical/rpg.js +0 -976
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- package/dungeons/vertical/travel-schema.json +0 -580
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const SEED = "dm4-logistics";
|
|
3
3
|
const num_days = 100;
|
|
4
4
|
const num_users = 5_000;
|
|
5
|
-
const
|
|
5
|
+
const avg_events_per_user_per_day = 1.2;
|
|
6
6
|
let token = "your-mixpanel-token";
|
|
7
7
|
|
|
8
8
|
// ── env overrides ──
|
|
@@ -16,9 +16,6 @@ import * as v from "ak-tools";
|
|
|
16
16
|
|
|
17
17
|
dayjs.extend(utc);
|
|
18
18
|
const chance = u.initChance(SEED);
|
|
19
|
-
const NOW = dayjs();
|
|
20
|
-
const DATASET_START = NOW.subtract(num_days, "days");
|
|
21
|
-
|
|
22
19
|
/** @typedef {import("../../types").Dungeon} Config */
|
|
23
20
|
|
|
24
21
|
// Generate consistent warehouse/supplier IDs at module level
|
|
@@ -56,7 +53,12 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
|
|
|
56
53
|
|
|
57
54
|
/**
|
|
58
55
|
* ===================================================================
|
|
59
|
-
* ANALYTICS HOOKS (
|
|
56
|
+
* ANALYTICS HOOKS (10 hooks)
|
|
57
|
+
*
|
|
58
|
+
* Adds 10. ONBOARDING TIME-TO-CONVERT: enterprise 0.71x faster, small_business
|
|
59
|
+
* 1.3x slower (funnel-post). Discover via Onboarding funnel median TTC by company_tier.
|
|
60
|
+
* NOTE (funnel-post measurement): visible only via Mixpanel funnel median TTC.
|
|
61
|
+
* Cross-event MIN→MIN SQL queries on raw events do NOT show this.
|
|
60
62
|
* ===================================================================
|
|
61
63
|
*
|
|
62
64
|
* -------------------------------------------------------------------
|
|
@@ -123,6 +125,15 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
|
|
|
123
125
|
* - Breakdown: user property "company_tier"
|
|
124
126
|
* - Expected: enterprise ratio ~0.9x vs small_business baseline
|
|
125
127
|
*
|
|
128
|
+
* Report 3: Stockout-to-Inventory-Check Ratio (NORMALIZED — recommended)
|
|
129
|
+
* - Report type: Insights (formula)
|
|
130
|
+
* - A = "stockout alert" total, B = "inventory checked" total
|
|
131
|
+
* - Formula: A / B
|
|
132
|
+
* - Breakdown: user property "company_tier"
|
|
133
|
+
* - Expected: enterprise ratio ~ 0.9x SMB ratio (10% reduction visible)
|
|
134
|
+
* - WHY THIS METRIC: per-user counts are dominated by persona event multipliers
|
|
135
|
+
* (enterprise has 5x baseline activity); ratio normalizes that out.
|
|
136
|
+
*
|
|
126
137
|
* REAL-WORLD ANALOGUE: Enterprise operations have dedicated
|
|
127
138
|
* procurement teams and predictive analytics reducing stockouts.
|
|
128
139
|
*
|
|
@@ -243,6 +254,41 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
|
|
|
243
254
|
* REAL-WORLD ANALOGUE: Small businesses lack dedicated IT teams,
|
|
244
255
|
* leading to incomplete integration setup and lower alert adoption.
|
|
245
256
|
*
|
|
257
|
+
* -------------------------------------------------------------------
|
|
258
|
+
* 9. INVENTORY-CHECK MAGIC NUMBER (everything hook)
|
|
259
|
+
* -------------------------------------------------------------------
|
|
260
|
+
*
|
|
261
|
+
* PATTERN: Users with 5-15 "inventory checked" events sit in the
|
|
262
|
+
* "engaged-but-focused" sweet spot — every "purchase order created"
|
|
263
|
+
* event gets quantity boosted ~25%. Users with 16 or more inventory
|
|
264
|
+
* checks are over-engaged (paralysis); ~30% of their "purchase order
|
|
265
|
+
* created" events are dropped. No flag is stamped — discoverable only
|
|
266
|
+
* by binning users on inventory-check COUNT and comparing PO totals.
|
|
267
|
+
*
|
|
268
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
269
|
+
*
|
|
270
|
+
* Report 1: PO Quantity by Inventory-Check Bucket
|
|
271
|
+
* - Report type: Insights (with cohort)
|
|
272
|
+
* - Cohort A: users with 5-15 "inventory checked" events
|
|
273
|
+
* - Cohort B: users with 0-4 "inventory checked" events
|
|
274
|
+
* - Event: "purchase order created"
|
|
275
|
+
* - Measure: Average of "quantity"
|
|
276
|
+
* - Compare cohort A vs cohort B
|
|
277
|
+
* - Expected: cohort A ~ 1.25x higher quantity than B
|
|
278
|
+
*
|
|
279
|
+
* Report 2: POs per User by Browse Intensity
|
|
280
|
+
* - Report type: Insights (with cohort)
|
|
281
|
+
* - Cohort C: users with >= 16 "inventory checked" events
|
|
282
|
+
* - Cohort A: users with 5-15 "inventory checked" events
|
|
283
|
+
* - Event: "purchase order created"
|
|
284
|
+
* - Measure: Total events per user
|
|
285
|
+
* - Compare cohort C vs cohort A
|
|
286
|
+
* - Expected: cohort C has ~ 30% fewer POs per user
|
|
287
|
+
*
|
|
288
|
+
* REAL-WORLD ANALOGUE: A focused operations team that monitors
|
|
289
|
+
* stock just enough places larger, more confident orders; an
|
|
290
|
+
* obsessive checker is paralysed and orders less.
|
|
291
|
+
*
|
|
246
292
|
* ===================================================================
|
|
247
293
|
* EXPECTED METRICS SUMMARY
|
|
248
294
|
* ===================================================================
|
|
@@ -256,15 +302,19 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
|
|
|
256
302
|
* Alert Fatigue | response_time_hours | 4h | 8-12h | 2-3x
|
|
257
303
|
* Trial Churn | events after wk 1 | 5 | 2.5 | 0.5x
|
|
258
304
|
* Enterprise Profiles | warehouse_count | 3 | 10 | 3.3x
|
|
259
|
-
* Small-Biz Conversion Drop
|
|
305
|
+
* Small-Biz Conversion Drop | funnel conversion | 30% | 20% | 0.65x
|
|
306
|
+
* Inventory-Check Magic Num | sweet PO quantity | 1x | 1.25x | 1.25x
|
|
307
|
+
* Inventory-Check Magic Num | over POs/user | 1x | 0.7x | -30%
|
|
260
308
|
*/
|
|
261
309
|
|
|
262
310
|
/** @type {Config} */
|
|
263
311
|
const config = {
|
|
264
312
|
token,
|
|
265
313
|
seed: SEED,
|
|
266
|
-
|
|
267
|
-
|
|
314
|
+
datasetStart: "2026-01-01T00:00:00Z",
|
|
315
|
+
datasetEnd: "2026-04-28T23:59:59Z",
|
|
316
|
+
// numDays: num_days,
|
|
317
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
268
318
|
numUsers: num_users,
|
|
269
319
|
hasAnonIds: false,
|
|
270
320
|
hasSessionIds: true,
|
|
@@ -279,7 +329,6 @@ const config = {
|
|
|
279
329
|
hasCampaigns: false,
|
|
280
330
|
isAnonymous: false,
|
|
281
331
|
hasAdSpend: false,
|
|
282
|
-
percentUsersBornInDataset: 35,
|
|
283
332
|
hasAvatar: true,
|
|
284
333
|
concurrency: 1,
|
|
285
334
|
writeToDisk: false,
|
|
@@ -692,6 +741,27 @@ const config = {
|
|
|
692
741
|
|
|
693
742
|
// -- Hook Function ------------------------------------------------
|
|
694
743
|
hook: function (record, type, meta) {
|
|
744
|
+
// HOOK 10 (T2C): ONBOARDING TIME-TO-CONVERT (funnel-post)
|
|
745
|
+
// Enterprise tier completes Onboarding funnel 1.4x faster (factor 0.71);
|
|
746
|
+
// small_business 1.3x slower (factor 1.3).
|
|
747
|
+
if (type === "funnel-post") {
|
|
748
|
+
const segment = meta?.profile?.company_tier;
|
|
749
|
+
if (Array.isArray(record) && record.length > 1) {
|
|
750
|
+
const factor = (
|
|
751
|
+
segment === "enterprise" ? 0.71 :
|
|
752
|
+
segment === "small_business" || segment === "trial" ? 1.3 :
|
|
753
|
+
1.0
|
|
754
|
+
);
|
|
755
|
+
if (factor !== 1.0) {
|
|
756
|
+
for (let i = 1; i < record.length; i++) {
|
|
757
|
+
const prev = dayjs(record[i - 1].time);
|
|
758
|
+
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
759
|
+
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
695
765
|
// -- HOOK 7: ENTERPRISE PROFILES (user) -----------------------
|
|
696
766
|
// Enterprise ops get large warehouse_count and employee_count.
|
|
697
767
|
if (type === "user") {
|
|
@@ -713,18 +783,13 @@ const config = {
|
|
|
713
783
|
// no-op: conversion differentiation handled via event filtering below
|
|
714
784
|
}
|
|
715
785
|
|
|
716
|
-
// -- HOOK 1: MONTH-END REPORTING SURGE
|
|
717
|
-
//
|
|
718
|
-
|
|
719
|
-
if (record.event === "report generated") {
|
|
720
|
-
const dayOfMonth = dayjs(record.time).date();
|
|
721
|
-
if (dayOfMonth >= 28) {
|
|
722
|
-
record.report_pages = Math.floor((record.report_pages || 20) * 2);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
786
|
+
// -- HOOK 1: MONTH-END REPORTING SURGE ------------------------
|
|
787
|
+
// Moved to everything hook (after sessionization) so day-of-month
|
|
788
|
+
// tags match final timestamps. See everything hook below.
|
|
725
789
|
|
|
726
|
-
|
|
727
|
-
|
|
790
|
+
// -- HOOK 2: RUSH ORDER PREMIUM (event) -------------------
|
|
791
|
+
// Urgent purchase orders get 1.5x unit_cost.
|
|
792
|
+
if (type === "event") {
|
|
728
793
|
if (record.event === "purchase order created" && record.priority === "urgent") {
|
|
729
794
|
record.unit_cost = Math.floor((record.unit_cost || 50) * 1.5);
|
|
730
795
|
}
|
|
@@ -746,6 +811,18 @@ const config = {
|
|
|
746
811
|
});
|
|
747
812
|
}
|
|
748
813
|
|
|
814
|
+
// -- HOOK 1: MONTH-END REPORTING SURGE --------------------
|
|
815
|
+
// Reports on days 28-31 get 2x report_pages.
|
|
816
|
+
// Runs after sessionization so day-of-month matches final timestamps.
|
|
817
|
+
for (const e of record) {
|
|
818
|
+
if (e.event === 'report generated') {
|
|
819
|
+
const dayOfMonth = new Date(e.time).getUTCDate();
|
|
820
|
+
if (dayOfMonth >= 28) {
|
|
821
|
+
e.report_pages = Math.floor((e.report_pages || 20) * 2);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
749
826
|
// -- HOOK 8: SMALL-BUSINESS CONVERSION DROP ---------------
|
|
750
827
|
// Small-business users lose ~35% of "alert configured" events
|
|
751
828
|
// (last step of Integration Setup funnel), simulating lower
|
|
@@ -817,6 +894,24 @@ const config = {
|
|
|
817
894
|
}
|
|
818
895
|
}
|
|
819
896
|
|
|
897
|
+
// -- HOOK 9: INVENTORY-CHECK MAGIC NUMBER (no flags) ------
|
|
898
|
+
// Sweet 5-15 inventory checks → +25% PO quantity.
|
|
899
|
+
// Over 16+ → drop 30% of PO created events.
|
|
900
|
+
const invCheckCount = record.filter(e => e.event === 'inventory checked').length;
|
|
901
|
+
if (invCheckCount >= 5 && invCheckCount <= 15) {
|
|
902
|
+
record.forEach(e => {
|
|
903
|
+
if (e.event === 'purchase order created' && typeof e.quantity === 'number') {
|
|
904
|
+
e.quantity = Math.round(e.quantity * 1.25);
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
} else if (invCheckCount >= 16) {
|
|
908
|
+
for (let i = record.length - 1; i >= 0; i--) {
|
|
909
|
+
if (record[i].event === 'purchase order created' && chance.bool({ likelihood: 30 })) {
|
|
910
|
+
record.splice(i, 1);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
820
915
|
return record;
|
|
821
916
|
}
|
|
822
917
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const SEED = "dm4-marketplace";
|
|
3
3
|
const num_days = 100;
|
|
4
4
|
const num_users = 5_000;
|
|
5
|
-
const
|
|
5
|
+
const avg_events_per_user_per_day = 1.2;
|
|
6
6
|
let token = "your-mixpanel-token";
|
|
7
7
|
|
|
8
8
|
// ── env overrides ──
|
|
@@ -16,9 +16,6 @@ import * as v from "ak-tools";
|
|
|
16
16
|
|
|
17
17
|
dayjs.extend(utc);
|
|
18
18
|
const chance = u.initChance(SEED);
|
|
19
|
-
const NOW = dayjs();
|
|
20
|
-
const DATASET_START = NOW.subtract(num_days, "days");
|
|
21
|
-
|
|
22
19
|
/** @typedef {import("../../types").Dungeon} Config */
|
|
23
20
|
|
|
24
21
|
// Generate consistent seller store and listing IDs at module level
|
|
@@ -56,33 +53,33 @@ const listingIds = v.range(1, 500).map(() => `LST_${v.uid(8)}`);
|
|
|
56
53
|
|
|
57
54
|
/**
|
|
58
55
|
* ═══════════════════════════════════════════════════════════════
|
|
59
|
-
* ANALYTICS HOOKS (
|
|
56
|
+
* ANALYTICS HOOKS (10 hooks)
|
|
57
|
+
*
|
|
58
|
+
* NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable via
|
|
59
|
+
* raw-prop breakdowns (segment, day, category) or behavioral cohorts.
|
|
60
|
+
*
|
|
61
|
+
* Adds 9. BROWSE TO PURCHASE TIME-TO-CONVERT and 10. MESSAGE-COUNT MAGIC
|
|
62
|
+
* NUMBER on top of original 8.
|
|
60
63
|
* ═══════════════════════════════════════════════════════════════
|
|
61
64
|
*
|
|
62
65
|
* ───────────────────────────────────────────────────────────────
|
|
63
|
-
* 1. FEE CHANGE IMPACT (
|
|
66
|
+
* 1. FEE CHANGE IMPACT (everything hook)
|
|
64
67
|
* ───────────────────────────────────────────────────────────────
|
|
65
68
|
*
|
|
66
69
|
* PATTERN: After day 45 (permanent marketplace fee increase), all
|
|
67
70
|
* "listing created" events get listing_fee multiplied by 1.3x.
|
|
68
|
-
* Simulates the revenue impact of a platform fee adjustment.
|
|
71
|
+
* Simulates the revenue impact of a platform fee adjustment. No flag —
|
|
72
|
+
* discover via line chart of avg listing_fee over time.
|
|
69
73
|
*
|
|
70
74
|
* HOW TO FIND IT IN MIXPANEL:
|
|
71
75
|
*
|
|
72
|
-
* Report 1: Listing Fee
|
|
76
|
+
* Report 1: Listing Fee Over Time
|
|
73
77
|
* • Report type: Insights
|
|
74
78
|
* • Event: "listing created"
|
|
75
79
|
* • Measure: Average of "listing_fee"
|
|
76
80
|
* • Line chart by week
|
|
77
81
|
* • Expected: Clear step-up around day 45 from ~$15 avg to ~$20 avg
|
|
78
82
|
*
|
|
79
|
-
* Report 2: Fee Change Flag Distribution
|
|
80
|
-
* • Report type: Insights
|
|
81
|
-
* • Event: "listing created"
|
|
82
|
-
* • Measure: Total
|
|
83
|
-
* • Breakdown: "fee_change"
|
|
84
|
-
* • Expected: "increased" appears only after day 45
|
|
85
|
-
*
|
|
86
83
|
* REAL-WORLD ANALOGUE: Marketplace platforms periodically adjust
|
|
87
84
|
* commission/listing fees, impacting seller economics and behavior.
|
|
88
85
|
*
|
|
@@ -238,28 +235,80 @@ const listingIds = v.range(1, 500).map(() => `LST_${v.uid(8)}`);
|
|
|
238
235
|
* REAL-WORLD ANALOGUE: Returning buyers have established trust
|
|
239
236
|
* and familiarity with the platform, converting at higher rates.
|
|
240
237
|
*
|
|
238
|
+
* ───────────────────────────────────────────────────────────────
|
|
239
|
+
* 9. BROWSE TO PURCHASE TIME-TO-CONVERT (funnel-post)
|
|
240
|
+
*
|
|
241
|
+
* PATTERN: Power_seller and frequent_buyer users complete the
|
|
242
|
+
* Browse to Purchase funnel 1.4x faster (factor 0.71); window_shopper
|
|
243
|
+
* 1.4x slower (factor 1.4).
|
|
244
|
+
*
|
|
245
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
246
|
+
*
|
|
247
|
+
* Report 1: Browse to Purchase Median Time-to-Convert by Segment
|
|
248
|
+
* - Funnels > "item searched" -> "item viewed" -> "add to cart" -> "purchase completed"
|
|
249
|
+
* - Measure: Median time to convert
|
|
250
|
+
* - Breakdown: segment
|
|
251
|
+
* - Expected: power/frequent ~ 0.71x; window ~ 1.4x
|
|
252
|
+
*
|
|
253
|
+
* NOTE (funnel-post measurement): visible only via Mixpanel funnel
|
|
254
|
+
* median TTC. Cross-event MIN→MIN SQL queries on raw events do NOT
|
|
255
|
+
* show this — funnel-post adjusts gaps within funnel instances, not
|
|
256
|
+
* across the user's full event history.
|
|
257
|
+
*
|
|
258
|
+
* ───────────────────────────────────────────────────────────────
|
|
259
|
+
* 10. MESSAGE-COUNT MAGIC NUMBER (in-funnel, everything)
|
|
260
|
+
*
|
|
261
|
+
* PATTERN: Sweet 2-5 message-sent events between item-viewed and
|
|
262
|
+
* offer-received → +35% on offer_amount of offer-received events.
|
|
263
|
+
* Over 6+ → drop 25% of purchase-completed events (haggling deadlock).
|
|
264
|
+
* No flag.
|
|
265
|
+
*
|
|
266
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
267
|
+
*
|
|
268
|
+
* Report 1: Avg Offer Amount by Message Bucket
|
|
269
|
+
* - Cohort A: users with 2-5 messages between item-viewed and offer-received
|
|
270
|
+
* - Cohort B: users with 0-1
|
|
271
|
+
* - Event: "offer received"
|
|
272
|
+
* - Measure: Average of "offer_amount"
|
|
273
|
+
* - Expected: A ~ 1.35x B
|
|
274
|
+
*
|
|
275
|
+
* Report 2: Purchase Rate on Heavy Messengers
|
|
276
|
+
* - Cohort C: users with >= 6 messages
|
|
277
|
+
* - Cohort A: users with 2-5
|
|
278
|
+
* - Event: "purchase completed"
|
|
279
|
+
* - Measure: Total per user
|
|
280
|
+
* - Expected: C ~ 25% fewer purchases per user
|
|
281
|
+
*
|
|
282
|
+
* REAL-WORLD ANALOGUE: A few quick clarifying messages close deals;
|
|
283
|
+
* extended haggling kills conversion.
|
|
284
|
+
*
|
|
241
285
|
* ═══════════════════════════════════════════════════════════════
|
|
242
286
|
* EXPECTED METRICS SUMMARY
|
|
243
287
|
* ═══════════════════════════════════════════════════════════════
|
|
244
288
|
*
|
|
245
|
-
* Hook | Metric | Baseline | Effect
|
|
246
|
-
*
|
|
247
|
-
* Fee Change Impact | listing_fee
|
|
248
|
-
* Weekend Shopping Surge | total_amount
|
|
249
|
-
* Seller Success → Trust | purchases/user |
|
|
250
|
-
* Electronics Category Lift | electronics purch. |
|
|
251
|
-
* Response Time → Conversion | offer_accepted/user |
|
|
252
|
-
* New Seller Churn | events post-
|
|
253
|
-
* Power Seller Profiles | total_transactions | 0 | 100-500
|
|
254
|
-
* Frequent Buyer Funnel | funnel conversion | 30% | 39%
|
|
289
|
+
* Hook | Metric | Baseline | Effect | Ratio
|
|
290
|
+
* ----------------------------|---------------------|----------|-----------|------
|
|
291
|
+
* Fee Change Impact | listing_fee post-D45| 1x | 1.3x | 1.3x
|
|
292
|
+
* Weekend Shopping Surge | total_amount Sat/Sun| 1x | 1.2x | 1.2x
|
|
293
|
+
* Seller Success → Trust | purchases/user | 1x | 2x | 2x
|
|
294
|
+
* Electronics Category Lift | electronics purch. | 1x | 1.5x | 1.5x
|
|
295
|
+
* Response Time → Conversion | offer_accepted/user | 1x | 2x | 2x
|
|
296
|
+
* New Seller Churn | events post-D14 | 1x | 0.6x | -40%
|
|
297
|
+
* Power Seller Profiles | total_transactions | 0 | 100-500 | --
|
|
298
|
+
* Frequent Buyer Funnel | funnel conversion | 30% | 39% | 1.3x
|
|
299
|
+
* Browse to Purchase T2C | median min by segment| 1x | 0.71/1.4x | ~ 2x range
|
|
300
|
+
* Message Magic Number | sweet offer_amount | 1x | 1.35x | 1.35x
|
|
301
|
+
* Message Magic Number | over purchases/user | 1x | 0.75x | -25%
|
|
255
302
|
*/
|
|
256
303
|
|
|
257
304
|
/** @type {Config} */
|
|
258
305
|
const config = {
|
|
259
306
|
token,
|
|
260
307
|
seed: SEED,
|
|
261
|
-
|
|
262
|
-
|
|
308
|
+
datasetStart: "2026-01-01T00:00:00Z",
|
|
309
|
+
datasetEnd: "2026-04-28T23:59:59Z",
|
|
310
|
+
// numDays: num_days,
|
|
311
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
263
312
|
numUsers: num_users,
|
|
264
313
|
hasAnonIds: false,
|
|
265
314
|
hasSessionIds: true,
|
|
@@ -274,7 +323,6 @@ const config = {
|
|
|
274
323
|
hasCampaigns: false,
|
|
275
324
|
isAnonymous: false,
|
|
276
325
|
hasAdSpend: false,
|
|
277
|
-
percentUsersBornInDataset: 35,
|
|
278
326
|
hasAvatar: true,
|
|
279
327
|
concurrency: 1,
|
|
280
328
|
writeToDisk: false,
|
|
@@ -337,7 +385,6 @@ const config = {
|
|
|
337
385
|
item_count: u.weighNumRange(1, 5, 0.3),
|
|
338
386
|
payment_method: ["credit_card", "credit_card", "paypal", "apple_pay", "debit"],
|
|
339
387
|
shipping_method: ["standard", "standard", "express", "pickup"],
|
|
340
|
-
is_whale_purchase: [false],
|
|
341
388
|
},
|
|
342
389
|
},
|
|
343
390
|
{
|
|
@@ -349,7 +396,6 @@ const config = {
|
|
|
349
396
|
asking_price: u.weighNumRange(5, 500, 0.3, 50),
|
|
350
397
|
condition: ["new", "new", "like_new", "good", "fair"],
|
|
351
398
|
listing_fee: u.weighNumRange(5, 30, 0.5, 15),
|
|
352
|
-
fee_change: ["none"],
|
|
353
399
|
},
|
|
354
400
|
},
|
|
355
401
|
{
|
|
@@ -729,37 +775,34 @@ const config = {
|
|
|
729
775
|
}
|
|
730
776
|
}
|
|
731
777
|
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
// ── HOOK 2: WEEKEND SHOPPING SURGE (event) ───────
|
|
751
|
-
// Purchases on Sat/Sun get 1.2x total_amount.
|
|
752
|
-
if (record.event === "purchase completed") {
|
|
753
|
-
const dayOfWeek = dayjs(record.time).day();
|
|
754
|
-
// Saturday=6, Sunday=0
|
|
755
|
-
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
|
756
|
-
record.total_amount = Math.floor((record.total_amount || 60) * 1.2);
|
|
778
|
+
// HOOK 9 (T2C): BROWSE TO PURCHASE TIME-TO-CONVERT (funnel-post)
|
|
779
|
+
// Power_seller users complete Browse to Purchase funnel 1.4x faster
|
|
780
|
+
// (factor 0.71); window_shopper 1.4x slower (factor 1.4).
|
|
781
|
+
if (type === "funnel-post") {
|
|
782
|
+
const segment = meta?.profile?.segment;
|
|
783
|
+
if (Array.isArray(record) && record.length > 1) {
|
|
784
|
+
const factor = (
|
|
785
|
+
segment === "power_seller" || segment === "frequent_buyer" ? 0.71 :
|
|
786
|
+
segment === "window_shopper" ? 1.4 :
|
|
787
|
+
1.0
|
|
788
|
+
);
|
|
789
|
+
if (factor !== 1.0) {
|
|
790
|
+
for (let i = 1; i < record.length; i++) {
|
|
791
|
+
const prev = dayjs(record[i - 1].time);
|
|
792
|
+
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
793
|
+
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
794
|
+
}
|
|
757
795
|
}
|
|
758
796
|
}
|
|
759
797
|
}
|
|
760
798
|
|
|
799
|
+
// HOOK 1: FEE CHANGE IMPACT — moved to everything hook below.
|
|
800
|
+
// (event hook fires before bunchIntoSessions reshuffles timestamps)
|
|
801
|
+
|
|
761
802
|
// ── EVERYTHING HOOKS ─────────────────────────────────
|
|
762
803
|
if (type === "everything") {
|
|
804
|
+
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
805
|
+
const FEE_CHANGE_DAY = datasetStart.add(45, "days");
|
|
763
806
|
let events = record;
|
|
764
807
|
if (!events.length) return record;
|
|
765
808
|
|
|
@@ -774,6 +817,14 @@ const config = {
|
|
|
774
817
|
});
|
|
775
818
|
}
|
|
776
819
|
|
|
820
|
+
// HOOK 1: FEE CHANGE IMPACT — listings after d45 get listing_fee 1.3x.
|
|
821
|
+
// In everything hook so timestamp comparison sees post-bunchIntoSessions times.
|
|
822
|
+
events.forEach(e => {
|
|
823
|
+
if (e.event === "listing created" && dayjs(e.time).isAfter(FEE_CHANGE_DAY)) {
|
|
824
|
+
e.listing_fee = Math.floor((e.listing_fee || 15) * 1.3);
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
|
|
777
828
|
// ── HOOK 8: FREQUENT BUYER CONVERSION FILTER ────
|
|
778
829
|
// Non-frequent-buyer users drop ~25% of "purchase completed"
|
|
779
830
|
// (last step of Browse to Purchase funnel) to simulate lower conversion.
|
|
@@ -831,11 +882,13 @@ const config = {
|
|
|
831
882
|
}
|
|
832
883
|
|
|
833
884
|
// ── HOOK 5: RESPONSE TIME → CONVERSION ───────────
|
|
834
|
-
// Sellers with avg response_time <
|
|
885
|
+
// Sellers with avg response_time < 12 hours get more offer_accepted.
|
|
886
|
+
// (response_time_hours centers around ~6h via weighNumRange, so a
|
|
887
|
+
// threshold of 2h was too tight — almost no users qualified.)
|
|
835
888
|
const messages = events.filter(e => e.event === "message sent" && e.response_time_hours);
|
|
836
889
|
if (messages.length > 0) {
|
|
837
890
|
const avgResponseTime = messages.reduce((sum, m) => sum + m.response_time_hours, 0) / messages.length;
|
|
838
|
-
if (avgResponseTime <
|
|
891
|
+
if (avgResponseTime < 12) {
|
|
839
892
|
const templateOffer = events.find(e => e.event === "offer accepted");
|
|
840
893
|
if (templateOffer) {
|
|
841
894
|
const offers = events.filter(e => e.event === "offer received");
|
|
@@ -856,7 +909,7 @@ const config = {
|
|
|
856
909
|
// ── HOOK 6: NEW SELLER CHURN ─────────────────────
|
|
857
910
|
// New sellers with <10 events lose 40% of events after day 14.
|
|
858
911
|
if (profile && profile.segment === "new_seller" && events.length < 10) {
|
|
859
|
-
const DAY_14 =
|
|
912
|
+
const DAY_14 = datasetStart.add(14, "days");
|
|
860
913
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
861
914
|
const eventTime = dayjs(events[i].time);
|
|
862
915
|
if (eventTime.isAfter(DAY_14) && chance.bool({ likelihood: 40 })) {
|
|
@@ -865,6 +918,47 @@ const config = {
|
|
|
865
918
|
}
|
|
866
919
|
}
|
|
867
920
|
|
|
921
|
+
// HOOK 2: WEEKEND SHOPPING SURGE — Sat/Sun purchases get
|
|
922
|
+
// total_amount 1.2x. Mutates raw prop. No flag.
|
|
923
|
+
events.forEach(e => {
|
|
924
|
+
if (e.event === "purchase completed") {
|
|
925
|
+
const dow = new Date(e.time).getUTCDay();
|
|
926
|
+
if (dow === 0 || dow === 6) {
|
|
927
|
+
e.total_amount = Math.floor((e.total_amount || 60) * 1.2);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
// HOOK 10: MESSAGE-COUNT MAGIC NUMBER (in-funnel, no flags)
|
|
933
|
+
// Sweet 2-5 message-sent events between item-viewed and
|
|
934
|
+
// offer-received → +35% on offer-received offer_amount/asking_price.
|
|
935
|
+
// Over 6+ → drop 25% of purchase-completed events (over-message
|
|
936
|
+
// signals haggling deadlock).
|
|
937
|
+
const itemViewed = events.find(e => e.event === "item viewed");
|
|
938
|
+
const offerReceived = events.find(e => e.event === "offer received");
|
|
939
|
+
if (itemViewed && offerReceived) {
|
|
940
|
+
const aTime = dayjs(itemViewed.time);
|
|
941
|
+
const bTime = dayjs(offerReceived.time);
|
|
942
|
+
const msgBetween = events.filter(e =>
|
|
943
|
+
e.event === "message sent" &&
|
|
944
|
+
dayjs(e.time).isAfter(aTime) &&
|
|
945
|
+
dayjs(e.time).isBefore(bTime)
|
|
946
|
+
).length;
|
|
947
|
+
if (msgBetween >= 2 && msgBetween <= 5) {
|
|
948
|
+
events.forEach(e => {
|
|
949
|
+
if (e.event === "offer received" && typeof e.offer_amount === "number") {
|
|
950
|
+
e.offer_amount = Math.round(e.offer_amount * 1.35);
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
} else if (msgBetween >= 6) {
|
|
954
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
955
|
+
if (events[i].event === "purchase completed" && chance.bool({ likelihood: 25 })) {
|
|
956
|
+
events.splice(i, 1);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
868
962
|
return record;
|
|
869
963
|
}
|
|
870
964
|
|