@ak--47/dungeon-master 1.3.0 → 1.4.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/CHANGELOG.md +58 -0
- package/dungeons/technical/array-of-object-lookup.js +0 -2
- package/dungeons/technical/hook-helpers-verify.js +89 -0
- package/dungeons/technical/identity-model-verify.js +47 -0
- package/dungeons/technical/pattern-aggregate-by-bin.js +41 -0
- package/dungeons/technical/pattern-attributed-by-source.js +42 -0
- package/dungeons/technical/pattern-frequency-by-frequency.js +40 -0
- package/dungeons/technical/pattern-funnel-frequency.js +54 -0
- package/dungeons/technical/pattern-ttc-by-segment.js +45 -0
- package/dungeons/technical/simple.js +3 -4
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/text-generation.js +1 -1
- package/dungeons/vertical/ai-platform.js +216 -164
- package/dungeons/vertical/community.js +74 -41
- package/dungeons/vertical/crypto.js +343 -233
- package/dungeons/vertical/dating.js +246 -292
- package/dungeons/vertical/devtools.js +179 -70
- package/dungeons/vertical/ecommerce.js +275 -122
- package/dungeons/vertical/education.js +345 -312
- package/dungeons/vertical/fintech.js +445 -314
- package/dungeons/vertical/fitness.js +199 -165
- package/dungeons/vertical/food-delivery.js +309 -330
- package/dungeons/vertical/gaming.js +387 -241
- package/dungeons/vertical/healthcare.js +149 -66
- package/dungeons/vertical/insurance-application.js +173 -75
- package/dungeons/vertical/logistics.js +107 -12
- package/dungeons/vertical/marketplace.js +171 -76
- package/dungeons/vertical/media.js +259 -402
- package/dungeons/vertical/real-estate.js +336 -318
- package/dungeons/vertical/sass.js +294 -288
- package/dungeons/vertical/social.js +289 -234
- package/dungeons/vertical/travel.js +66 -20
- package/index.js +17 -17
- package/lib/core/config-validator.js +226 -175
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +57 -104
- package/lib/generators/funnels.js +212 -93
- 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/hook-helpers/_internal.js +23 -0
- package/lib/hook-helpers/cohort.js +124 -0
- package/lib/hook-helpers/identity.js +56 -0
- package/lib/hook-helpers/index.js +44 -0
- package/lib/hook-helpers/inject.js +99 -0
- package/lib/hook-helpers/mutate.js +151 -0
- package/lib/hook-helpers/timing.js +99 -0
- package/lib/hook-patterns/aggregate-per-user-by-bin.js +38 -0
- package/lib/hook-patterns/attributed-by-source.js +72 -0
- package/lib/hook-patterns/frequency-by-frequency.js +46 -0
- package/lib/hook-patterns/funnel-frequency-breakdown.js +73 -0
- package/lib/hook-patterns/index.js +14 -0
- package/lib/hook-patterns/time-to-convert-by-segment.js +41 -0
- package/lib/orchestrators/user-loop.js +143 -293
- package/lib/utils/utils.js +97 -52
- package/lib/verify/emulate-breakdown.js +281 -0
- package/lib/verify/index.js +12 -0
- package/lib/verify/verify-dungeon.js +61 -0
- package/package.json +6 -4
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +434 -225
- 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/vertical/ai-platform-schema.json +0 -617
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/crypto-schema.json +0 -546
- package/dungeons/vertical/dating-schema.json +0 -401
- 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 -2703
- 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/real-estate-schema.json +0 -527
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- package/dungeons/vertical/travel-schema.json +0 -580
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ── TWEAK THESE ──
|
|
2
2
|
const SEED = "dm4-marketplace";
|
|
3
|
-
const num_days =
|
|
4
|
-
const num_users =
|
|
3
|
+
const num_days = 120;
|
|
4
|
+
const num_users = 10_000;
|
|
5
5
|
const avg_events_per_user_per_day = 1.2;
|
|
6
6
|
let token = "your-mixpanel-token";
|
|
7
7
|
|
|
@@ -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,30 +235,84 @@ 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 = {
|
|
306
|
+
version: 2,
|
|
259
307
|
token,
|
|
260
308
|
seed: SEED,
|
|
261
|
-
|
|
309
|
+
datasetStart: "2026-01-01T00:00:00Z",
|
|
310
|
+
datasetEnd: "2026-05-01T23:59:59Z",
|
|
311
|
+
// numDays: num_days,
|
|
262
312
|
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
263
313
|
numUsers: num_users,
|
|
264
|
-
hasAnonIds:
|
|
314
|
+
hasAnonIds: true,
|
|
315
|
+
avgDevicePerUser: 2,
|
|
265
316
|
hasSessionIds: true,
|
|
266
317
|
format: "json",
|
|
267
318
|
gzip: true,
|
|
@@ -294,6 +345,7 @@ const config = {
|
|
|
294
345
|
event: "account created",
|
|
295
346
|
weight: 1,
|
|
296
347
|
isFirstEvent: true,
|
|
348
|
+
isAuthEvent: true,
|
|
297
349
|
properties: {
|
|
298
350
|
signup_source: ["organic", "google_shopping", "tiktok", "seller_referral", "email_campaign", "word_of_mouth"],
|
|
299
351
|
},
|
|
@@ -336,7 +388,6 @@ const config = {
|
|
|
336
388
|
item_count: u.weighNumRange(1, 5, 0.3),
|
|
337
389
|
payment_method: ["credit_card", "credit_card", "paypal", "apple_pay", "debit"],
|
|
338
390
|
shipping_method: ["standard", "standard", "express", "pickup"],
|
|
339
|
-
is_whale_purchase: [false],
|
|
340
391
|
},
|
|
341
392
|
},
|
|
342
393
|
{
|
|
@@ -348,7 +399,6 @@ const config = {
|
|
|
348
399
|
asking_price: u.weighNumRange(5, 500, 0.3, 50),
|
|
349
400
|
condition: ["new", "new", "like_new", "good", "fair"],
|
|
350
401
|
listing_fee: u.weighNumRange(5, 30, 0.5, 15),
|
|
351
|
-
fee_change: ["none"],
|
|
352
402
|
},
|
|
353
403
|
},
|
|
354
404
|
{
|
|
@@ -728,31 +778,34 @@ const config = {
|
|
|
728
778
|
}
|
|
729
779
|
}
|
|
730
780
|
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
781
|
+
// HOOK 9 (T2C): BROWSE TO PURCHASE TIME-TO-CONVERT (funnel-post)
|
|
782
|
+
// Power_seller users complete Browse to Purchase funnel 1.4x faster
|
|
783
|
+
// (factor 0.71); window_shopper 1.4x slower (factor 1.4).
|
|
784
|
+
if (type === "funnel-post") {
|
|
785
|
+
const segment = meta?.profile?.segment;
|
|
786
|
+
if (Array.isArray(record) && record.length > 1) {
|
|
787
|
+
const factor = (
|
|
788
|
+
segment === "power_seller" || segment === "frequent_buyer" ? 0.71 :
|
|
789
|
+
segment === "window_shopper" ? 1.4 :
|
|
790
|
+
1.0
|
|
791
|
+
);
|
|
792
|
+
if (factor !== 1.0) {
|
|
793
|
+
for (let i = 1; i < record.length; i++) {
|
|
794
|
+
const prev = dayjs(record[i - 1].time);
|
|
795
|
+
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
796
|
+
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
797
|
+
}
|
|
746
798
|
}
|
|
747
799
|
}
|
|
748
|
-
|
|
749
|
-
// ── HOOK 2: WEEKEND SHOPPING SURGE ───────────────
|
|
750
|
-
// Moved to the everything hook (after purchase cloning)
|
|
751
|
-
// so the 1.2x boost applies to both original AND cloned purchases.
|
|
752
800
|
}
|
|
753
801
|
|
|
802
|
+
// HOOK 1: FEE CHANGE IMPACT — moved to everything hook below.
|
|
803
|
+
// (event hook fires before bunchIntoSessions reshuffles timestamps)
|
|
804
|
+
|
|
754
805
|
// ── EVERYTHING HOOKS ─────────────────────────────────
|
|
755
806
|
if (type === "everything") {
|
|
807
|
+
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
808
|
+
const FEE_CHANGE_DAY = datasetStart.add(45, "days");
|
|
756
809
|
let events = record;
|
|
757
810
|
if (!events.length) return record;
|
|
758
811
|
|
|
@@ -767,6 +820,14 @@ const config = {
|
|
|
767
820
|
});
|
|
768
821
|
}
|
|
769
822
|
|
|
823
|
+
// HOOK 1: FEE CHANGE IMPACT — listings after d45 get listing_fee 1.3x.
|
|
824
|
+
// In everything hook so timestamp comparison sees post-bunchIntoSessions times.
|
|
825
|
+
events.forEach(e => {
|
|
826
|
+
if (e.event === "listing created" && dayjs(e.time).isAfter(FEE_CHANGE_DAY)) {
|
|
827
|
+
e.listing_fee = Math.floor((e.listing_fee || 15) * 1.3);
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
770
831
|
// ── HOOK 8: FREQUENT BUYER CONVERSION FILTER ────
|
|
771
832
|
// Non-frequent-buyer users drop ~25% of "purchase completed"
|
|
772
833
|
// (last step of Browse to Purchase funnel) to simulate lower conversion.
|
|
@@ -824,55 +885,89 @@ const config = {
|
|
|
824
885
|
}
|
|
825
886
|
|
|
826
887
|
// ── HOOK 5: RESPONSE TIME → CONVERSION ───────────
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
// threshold of 2h was too tight — almost no users qualified.)
|
|
888
|
+
// Users with low avg response_time on messages get extra
|
|
889
|
+
// offer_accepted clones; slow responders lose half their accepts.
|
|
830
890
|
const messages = events.filter(e => e.event === "message sent" && e.response_time_hours);
|
|
831
891
|
if (messages.length > 0) {
|
|
832
892
|
const avgResponseTime = messages.reduce((sum, m) => sum + m.response_time_hours, 0) / messages.length;
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
response_time_hours: chance.floating({ min: 0.1, max: 2, fixed: 1 }),
|
|
844
|
-
});
|
|
845
|
-
}
|
|
893
|
+
const templateOffer = events.find(e => e.event === "offer accepted");
|
|
894
|
+
if (avgResponseTime <= 11 && templateOffer) {
|
|
895
|
+
// Fast responders: clone every offer_received into an offer_accepted
|
|
896
|
+
const offers = events.filter(e => e.event === "offer received");
|
|
897
|
+
offers.forEach(offer => {
|
|
898
|
+
events.push({
|
|
899
|
+
...templateOffer,
|
|
900
|
+
time: dayjs(offer.time).add(chance.integer({ min: 1, max: 4 }), "hours").toISOString(),
|
|
901
|
+
user_id: offer.user_id,
|
|
902
|
+
response_time_hours: chance.floating({ min: 0.1, max: 2, fixed: 1 }),
|
|
846
903
|
});
|
|
904
|
+
});
|
|
905
|
+
} else if (avgResponseTime >= 25 && templateOffer) {
|
|
906
|
+
// Slow responders: drop half of offer_accepted events
|
|
907
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
908
|
+
if (events[i].event === "offer accepted" && chance.bool({ likelihood: 50 })) {
|
|
909
|
+
events.splice(i, 1);
|
|
910
|
+
}
|
|
847
911
|
}
|
|
848
912
|
}
|
|
849
913
|
}
|
|
850
914
|
|
|
851
915
|
// ── HOOK 6: NEW SELLER CHURN ─────────────────────
|
|
852
|
-
// New sellers
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
916
|
+
// New sellers lose 50% of events after their first 14 days.
|
|
917
|
+
// Uses user's first event as anchor (not dataset start).
|
|
918
|
+
if (profile && profile.segment === "new_seller") {
|
|
919
|
+
const firstEventTime = events.length > 0 ? dayjs(events[0].time) : null;
|
|
920
|
+
if (firstEventTime) {
|
|
921
|
+
const userDay14 = firstEventTime.add(14, "days");
|
|
922
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
923
|
+
if (dayjs(events[i].time).isAfter(userDay14) && chance.bool({ likelihood: 50 })) {
|
|
924
|
+
events.splice(i, 1);
|
|
925
|
+
}
|
|
859
926
|
}
|
|
860
927
|
}
|
|
861
928
|
}
|
|
862
929
|
|
|
863
|
-
//
|
|
864
|
-
//
|
|
865
|
-
// 1.2x boost hits both original and injected purchases.
|
|
930
|
+
// HOOK 2: WEEKEND SHOPPING SURGE — Sat/Sun purchases get
|
|
931
|
+
// total_amount 1.2x. Mutates raw prop. No flag.
|
|
866
932
|
events.forEach(e => {
|
|
867
933
|
if (e.event === "purchase completed") {
|
|
868
934
|
const dow = new Date(e.time).getUTCDay();
|
|
869
|
-
// Saturday=6, Sunday=0
|
|
870
935
|
if (dow === 0 || dow === 6) {
|
|
871
936
|
e.total_amount = Math.floor((e.total_amount || 60) * 1.2);
|
|
872
937
|
}
|
|
873
938
|
}
|
|
874
939
|
});
|
|
875
940
|
|
|
941
|
+
// HOOK 10: MESSAGE-COUNT MAGIC NUMBER (in-funnel, no flags)
|
|
942
|
+
// Sweet 2-5 message-sent events between item-viewed and
|
|
943
|
+
// offer-received → +35% on offer-received offer_amount/asking_price.
|
|
944
|
+
// Over 6+ → drop 25% of purchase-completed events (over-message
|
|
945
|
+
// signals haggling deadlock).
|
|
946
|
+
const itemViewed = events.find(e => e.event === "item viewed");
|
|
947
|
+
const offerReceived = events.find(e => e.event === "offer received");
|
|
948
|
+
if (itemViewed && offerReceived) {
|
|
949
|
+
const aTime = dayjs(itemViewed.time);
|
|
950
|
+
const bTime = dayjs(offerReceived.time);
|
|
951
|
+
const msgBetween = events.filter(e =>
|
|
952
|
+
e.event === "message sent" &&
|
|
953
|
+
dayjs(e.time).isAfter(aTime) &&
|
|
954
|
+
dayjs(e.time).isBefore(bTime)
|
|
955
|
+
).length;
|
|
956
|
+
if (msgBetween >= 2 && msgBetween <= 5) {
|
|
957
|
+
events.forEach(e => {
|
|
958
|
+
if (e.event === "offer received" && typeof e.offer_amount === "number") {
|
|
959
|
+
e.offer_amount = Math.round(e.offer_amount * 1.35);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
} else if (msgBetween >= 6) {
|
|
963
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
964
|
+
if (events[i].event === "purchase completed" && chance.bool({ likelihood: 25 })) {
|
|
965
|
+
events.splice(i, 1);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
876
971
|
return record;
|
|
877
972
|
}
|
|
878
973
|
|