@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
|
import "dotenv/config";
|
|
14
14
|
import * as u from "../../lib/utils/utils.js";
|
|
15
15
|
import * as v from "ak-tools";
|
|
16
|
+
import { findFirstSequence, scaleFunnelTTC } from "../../lib/hook-helpers/timing.js";
|
|
16
17
|
|
|
17
18
|
dayjs.extend(utc);
|
|
18
19
|
const chance = u.initChance(SEED);
|
|
@@ -52,15 +53,14 @@ const chance = u.initChance(SEED);
|
|
|
52
53
|
|
|
53
54
|
/*
|
|
54
55
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
55
|
-
* ANALYTICS HOOKS (
|
|
56
|
+
* ANALYTICS HOOKS (11 hooks)
|
|
56
57
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|
57
58
|
*
|
|
58
59
|
* 10 deliberately architected patterns hidden in the data. NOTE: All cohort
|
|
59
60
|
* effects are HIDDEN — no flag stamping. Discoverable via behavioral cohorts
|
|
60
61
|
* or raw-prop breakdowns (company_size, day, doc_section). Adds:
|
|
61
|
-
* 9. INCIDENT RESPONSE TIME-TO-CONVERT (Enterprise
|
|
62
|
-
* [
|
|
63
|
-
* MIN→MIN SQL queries do NOT show this]
|
|
62
|
+
* 9. INCIDENT RESPONSE TIME-TO-CONVERT (Enterprise 0.67x gap vs Startup 1.5x)
|
|
63
|
+
* [everything hook: scales response_time_mins and resolution_time_mins by company_size]
|
|
64
64
|
* 10. DOCS MAGIC NUMBER (sweet 4-7 docs → +40% deploys; over 8+ → drop 25%)
|
|
65
65
|
*
|
|
66
66
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -264,6 +264,120 @@ const chance = u.initChance(SEED);
|
|
|
264
264
|
* customer segments — from a $99/mo startup to a $500K Fortune 500 contract.
|
|
265
265
|
*
|
|
266
266
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
267
|
+
* 9. INCIDENT RESPONSE TTC (everything)
|
|
268
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
269
|
+
*
|
|
270
|
+
* PATTERN: Enterprise companies resolve incidents faster; startups resolve
|
|
271
|
+
* slower. The hook reads company_size from the user profile and scales
|
|
272
|
+
* response_time_mins (on "alert acknowledged") and resolution_time_mins
|
|
273
|
+
* (on "alert resolved") by a factor: enterprise 0.67x, startup 1.5x,
|
|
274
|
+
* smb/mid_market unchanged. Additionally, the first incident-response
|
|
275
|
+
* funnel sequence (alert triggered → alert acknowledged → alert resolved)
|
|
276
|
+
* has its inter-step timestamps scaled via scaleFunnelTTC with the same
|
|
277
|
+
* factor, so Mixpanel funnel TTC reports reflect the gap. This compounds
|
|
278
|
+
* with Hook 4 (integration users) — an enterprise user with both Slack
|
|
279
|
+
* and PagerDuty configured gets the fastest resolution times.
|
|
280
|
+
*
|
|
281
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
282
|
+
*
|
|
283
|
+
* Report 1: Avg Response Time by Company Size
|
|
284
|
+
* - Report type: Insights
|
|
285
|
+
* - Event: "alert acknowledged"
|
|
286
|
+
* - Measure: Average of "response_time_mins"
|
|
287
|
+
* - Breakdown: "company_size" (user property)
|
|
288
|
+
* - Expected: enterprise ~ 0.67x startup
|
|
289
|
+
*
|
|
290
|
+
* Report 2: Incident Funnel TTC by Company Size
|
|
291
|
+
* - Report type: Funnels
|
|
292
|
+
* - Steps: "alert triggered" → "alert acknowledged" → "alert resolved"
|
|
293
|
+
* - Measure: Median time to convert
|
|
294
|
+
* - Breakdown: "company_size"
|
|
295
|
+
* - Expected: enterprise ~ 0.67x median TTC vs startup ~ 1.5x
|
|
296
|
+
*
|
|
297
|
+
* Report 3: Avg Resolution Time by Company Size
|
|
298
|
+
* - Report type: Insights
|
|
299
|
+
* - Event: "alert resolved"
|
|
300
|
+
* - Measure: Average of "resolution_time_mins"
|
|
301
|
+
* - Breakdown: "company_size"
|
|
302
|
+
* - Expected: enterprise ~ 0.67x startup
|
|
303
|
+
*
|
|
304
|
+
* REAL-WORLD ANALOGUE: Enterprise teams have dedicated SRE rotations,
|
|
305
|
+
* automated runbooks, and premium support contracts that compress
|
|
306
|
+
* incident timelines. Startups rely on smaller teams with less tooling.
|
|
307
|
+
*
|
|
308
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
309
|
+
* 10. DOCS MAGIC NUMBER (everything)
|
|
310
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
311
|
+
*
|
|
312
|
+
* PATTERN: Users in the 4-7 documentation-viewed sweet spot get 2-3 extra
|
|
313
|
+
* production "service deployed" events cloned into their stream (boosting
|
|
314
|
+
* deploy frequency ~40%). Users with 8+ documentation views are over-
|
|
315
|
+
* engaged browsers; 25% of their "service deployed" events are dropped.
|
|
316
|
+
* No flag is stamped — discoverable only by binning users on doc-view
|
|
317
|
+
* count and comparing per-user deploy volume.
|
|
318
|
+
*
|
|
319
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
320
|
+
*
|
|
321
|
+
* Report 1: Deploys per User by Docs-View Bucket
|
|
322
|
+
* - Report type: Insights (with cohort)
|
|
323
|
+
* - Cohort A: users with 4-7 "documentation viewed" events
|
|
324
|
+
* - Cohort B: users with 0-3 "documentation viewed" events
|
|
325
|
+
* - Event: "service deployed"
|
|
326
|
+
* - Measure: Total per user
|
|
327
|
+
* - Expected: Cohort A ~ 1.4x deploys per user vs Cohort B
|
|
328
|
+
*
|
|
329
|
+
* Report 2: Deploys per User on Heavy Doc Readers
|
|
330
|
+
* - Report type: Insights (with cohort)
|
|
331
|
+
* - Cohort C: users with >= 8 "documentation viewed" events
|
|
332
|
+
* - Cohort A: users with 4-7
|
|
333
|
+
* - Event: "service deployed"
|
|
334
|
+
* - Measure: Total per user
|
|
335
|
+
* - Expected: Cohort C ~ 25% fewer deploys per user vs Cohort A
|
|
336
|
+
*
|
|
337
|
+
* REAL-WORLD ANALOGUE: Engineers who read just enough docs deploy with
|
|
338
|
+
* confidence; those who read excessively may be stuck troubleshooting
|
|
339
|
+
* and never ship, or are evaluating the product without committing.
|
|
340
|
+
*
|
|
341
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
342
|
+
* 11. DEPLOY PIPELINE EXPERIMENT (funnel experiment — engine-managed)
|
|
343
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
344
|
+
*
|
|
345
|
+
* PATTERN: The deployment funnel (deployment pipeline run → service deployed
|
|
346
|
+
* → dashboard viewed) runs a "Canary Deploys" experiment starting 45 days
|
|
347
|
+
* before dataset end. Users are randomly assigned to Control or "Canary
|
|
348
|
+
* Deploys" variant. The Canary variant gets 1.2x conversion multiplier and
|
|
349
|
+
* 0.85x time-to-convert multiplier (faster + higher conversion). The engine
|
|
350
|
+
* emits `$experiment_started` events with `Experiment Name` and
|
|
351
|
+
* `Variant Name` properties. No hook code needed — the engine handles
|
|
352
|
+
* variant assignment and conversion/TTC scaling.
|
|
353
|
+
*
|
|
354
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
355
|
+
*
|
|
356
|
+
* Report 1: Experiment Enrollment
|
|
357
|
+
* - Report type: Insights
|
|
358
|
+
* - Event: "$experiment_started"
|
|
359
|
+
* - Measure: Total
|
|
360
|
+
* - Breakdown: "Variant Name"
|
|
361
|
+
* - Expected: roughly even split between "Control" and "Canary Deploys"
|
|
362
|
+
*
|
|
363
|
+
* Report 2: Deploy Funnel by Variant
|
|
364
|
+
* - Report type: Funnels
|
|
365
|
+
* - Steps: "deployment pipeline run" → "service deployed" → "dashboard viewed"
|
|
366
|
+
* - Breakdown: "Variant Name" (user property)
|
|
367
|
+
* - Expected: Canary variant ~ 1.2x conversion vs Control
|
|
368
|
+
*
|
|
369
|
+
* Report 3: Deploy TTC by Variant
|
|
370
|
+
* - Report type: Funnels
|
|
371
|
+
* - Steps: same as above
|
|
372
|
+
* - Measure: Median time to convert
|
|
373
|
+
* - Breakdown: "Variant Name"
|
|
374
|
+
* - Expected: Canary variant ~ 0.85x TTC vs Control (faster)
|
|
375
|
+
*
|
|
376
|
+
* REAL-WORLD ANALOGUE: Teams A/B test canary deployment strategies.
|
|
377
|
+
* Canary deploys catch issues earlier, improving both success rate and
|
|
378
|
+
* deployment velocity.
|
|
379
|
+
*
|
|
380
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
267
381
|
* EXPECTED METRICS SUMMARY
|
|
268
382
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
269
383
|
*
|
|
@@ -277,6 +391,12 @@ const chance = u.initChance(SEED);
|
|
|
277
391
|
* Cost Overrun | Scale-down after overrun | 50% | 100% | 2x
|
|
278
392
|
* Failed Deploy Recovery | Deploy duration (sec) | ~500 | ~750 | 1.5x
|
|
279
393
|
* Enterprise vs Startup | ACV range | $0-3.6K | $50K-500K | 100x+
|
|
394
|
+
* Incident Response TTC | Enterprise response_time | 1x | 0.67x | -33%
|
|
395
|
+
* Incident Response TTC | Startup response_time | 1x | 1.5x | +50%
|
|
396
|
+
* Docs Magic Number | sweet (4-7) deploys/user | 1x | ~1.4x | +40%
|
|
397
|
+
* Docs Magic Number | over (8+) deploys/user | 1x | ~0.75x | -25%
|
|
398
|
+
* Deploy Experiment | Canary conversion | 65% | ~78% | 1.2x
|
|
399
|
+
* Deploy Experiment | Canary TTC | 1d | ~0.85d | 0.85x
|
|
280
400
|
*
|
|
281
401
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
282
402
|
* ADVANCED ANALYSIS IDEAS
|
|
@@ -392,6 +512,14 @@ const config = {
|
|
|
392
512
|
conversionRate: 65,
|
|
393
513
|
timeToConvert: 1,
|
|
394
514
|
weight: 3,
|
|
515
|
+
experiment: {
|
|
516
|
+
name: "Canary Deploys",
|
|
517
|
+
variants: [
|
|
518
|
+
{ name: "Control" },
|
|
519
|
+
{ name: "Canary Deploys", conversionMultiplier: 1.2, ttcMultiplier: 0.85 },
|
|
520
|
+
],
|
|
521
|
+
startDaysBeforeEnd: 45,
|
|
522
|
+
},
|
|
395
523
|
},
|
|
396
524
|
{
|
|
397
525
|
// Infrastructure management
|
|
@@ -641,7 +769,9 @@ const config = {
|
|
|
641
769
|
/**
|
|
642
770
|
* ARCHITECTED ANALYTICS HOOKS
|
|
643
771
|
*
|
|
644
|
-
* This hook function creates
|
|
772
|
+
* This hook function creates 10 deliberate patterns in the data.
|
|
773
|
+
* Hook 11 (Deploy Pipeline Experiment) is engine-managed via funnel
|
|
774
|
+
* experiment config — no hook code needed.
|
|
645
775
|
*
|
|
646
776
|
* 1. END-OF-QUARTER SPIKE: Days 100-110 drive plan upgrades and team expansion
|
|
647
777
|
* 2. CHURNED ACCOUNT SILENCING: ~10% of users go completely silent after month 1
|
|
@@ -651,6 +781,9 @@ const config = {
|
|
|
651
781
|
* 6. COST OVERRUN PATTERN: Budget-exceeded users react by scaling down infrastructure
|
|
652
782
|
* 7. FAILED DEPLOYMENT RECOVERY: Recovery deploys take 1.5x longer, tracked across calls
|
|
653
783
|
* 8. ENTERPRISE VS STARTUP: Company size determines seat count, ACV, and health score
|
|
784
|
+
* 9. INCIDENT RESPONSE TTC: Enterprise 0.67x faster, startup 1.5x slower incident resolution
|
|
785
|
+
* 10. DOCS MAGIC NUMBER: Sweet 4-7 docs → extra deploys; over 8+ → drop 25% of deploys
|
|
786
|
+
* 11. DEPLOY PIPELINE EXPERIMENT: Canary Deploys A/B test on deployment funnel (engine-managed)
|
|
654
787
|
*/
|
|
655
788
|
hook: function (record, type, meta) {
|
|
656
789
|
// (Hook 1a moved to everything hook for reliable datasetStart access)
|
|
@@ -803,25 +936,33 @@ const config = {
|
|
|
803
936
|
}
|
|
804
937
|
|
|
805
938
|
// HOOK 9: INCIDENT RESPONSE TTC — enterprise resolves faster,
|
|
806
|
-
// startup resolves slower. Scale
|
|
807
|
-
//
|
|
808
|
-
|
|
939
|
+
// startup resolves slower. Scale response_time_mins on
|
|
940
|
+
// acknowledged events and resolution_time_mins on resolved
|
|
941
|
+
// events by company_size. This compounds with H4 (integration
|
|
942
|
+
// users) — realistic: enterprise + good tooling = fastest.
|
|
943
|
+
const companySegment = profile?.company_size;
|
|
809
944
|
const ttcFactor = (
|
|
810
|
-
|
|
811
|
-
|
|
945
|
+
companySegment === "enterprise" ? 0.67 :
|
|
946
|
+
companySegment === "startup" ? 1.5 :
|
|
812
947
|
1.0
|
|
813
948
|
);
|
|
814
949
|
if (ttcFactor !== 1.0) {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
950
|
+
// Timestamp shift: affects Mixpanel funnel TTC
|
|
951
|
+
const incidentSeq = findFirstSequence(
|
|
952
|
+
userEvents,
|
|
953
|
+
["alert triggered", "alert acknowledged", "alert resolved"],
|
|
954
|
+
60 * 24 * 30
|
|
955
|
+
);
|
|
956
|
+
if (incidentSeq) scaleFunnelTTC(incidentSeq, ttcFactor);
|
|
957
|
+
// Property scale: affects Insights AVG reports
|
|
958
|
+
userEvents.forEach(e => {
|
|
959
|
+
if (e.event === "alert acknowledged" && e.response_time_mins) {
|
|
960
|
+
e.response_time_mins = Math.max(1, Math.round(e.response_time_mins * ttcFactor));
|
|
961
|
+
}
|
|
962
|
+
if (e.event === "alert resolved" && e.resolution_time_mins) {
|
|
963
|
+
e.resolution_time_mins = Math.max(1, Math.round(e.resolution_time_mins * ttcFactor));
|
|
964
|
+
}
|
|
965
|
+
});
|
|
825
966
|
}
|
|
826
967
|
}
|
|
827
968
|
|
|
@@ -252,6 +252,30 @@ const chance = u.initChance(SEED);
|
|
|
252
252
|
* REAL-WORLD ANALOGUE: Moderate posters write thoughtful comments; the
|
|
253
253
|
* over-prolific tend to spam and burn through audience patience.
|
|
254
254
|
*
|
|
255
|
+
* -------------------------------------------------------------------------------------
|
|
256
|
+
* 10. ONBOARDING TIME-TO-CONVERT (funnel-post)
|
|
257
|
+
* -------------------------------------------------------------------------------------
|
|
258
|
+
*
|
|
259
|
+
* PATTERN: Creator and business account_type users complete the
|
|
260
|
+
* onboarding funnel (account created -> profile updated -> post created)
|
|
261
|
+
* 1.4x faster (factor 0.71). Personal account users complete it 1.25x
|
|
262
|
+
* slower (factor 1.25). The hook intercepts funnel-post arrays, computes
|
|
263
|
+
* the time gap between consecutive steps, and scales each gap by the
|
|
264
|
+
* account-type-specific factor before rewriting the step timestamps.
|
|
265
|
+
*
|
|
266
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
267
|
+
*
|
|
268
|
+
* Report 1: Onboarding Funnel Median TTC by Account Type
|
|
269
|
+
* - Report type: Funnels
|
|
270
|
+
* - Steps: "account created" -> "profile updated" -> "post created"
|
|
271
|
+
* - Measure: Median time to convert
|
|
272
|
+
* - Breakdown: "account_type" (superProp)
|
|
273
|
+
* - Expected: creator/business ~ 0.71x baseline; personal ~ 1.25x baseline
|
|
274
|
+
*
|
|
275
|
+
* REAL-WORLD ANALOGUE: Creators and businesses arrive with clear intent
|
|
276
|
+
* and complete profile setup faster; personal users browse casually and
|
|
277
|
+
* take longer to commit to their first post.
|
|
278
|
+
*
|
|
255
279
|
* =====================================================================================
|
|
256
280
|
* EXPECTED METRICS SUMMARY
|
|
257
281
|
* =====================================================================================
|
|
@@ -268,6 +292,7 @@ const chance = u.initChance(SEED);
|
|
|
268
292
|
* Weekend Surge | Weekend vs weekday | 1x | ~ 1.3x | 1.3x
|
|
269
293
|
* Post-Created Magic Number | sweet comment_length | 1x | ~ 1.4x | 1.4x
|
|
270
294
|
* Post-Created Magic Number | over engagement/user | 1x | ~ 0.7x | -30%
|
|
295
|
+
* Onboarding TTC | median TTC by acct type | 1x | 0.71/1.25x | ~ 1.8x range
|
|
271
296
|
* =====================================================================================
|
|
272
297
|
*/
|
|
273
298
|
|
|
@@ -112,7 +112,7 @@ const destinationCities = ["New York", "London", "Paris", "Tokyo", "Barcelona",
|
|
|
112
112
|
* ───────────────────────────────────────────────────────────────
|
|
113
113
|
* 4. CANCELLATION BY BOOKING WINDOW (everything hook)
|
|
114
114
|
* ───────────────────────────────────────────────────────────────
|
|
115
|
-
* PATTERN: Users who book last-minute (< 7 days out) have
|
|
115
|
+
* PATTERN: Users who book last-minute (< 7 days out) have 60% of
|
|
116
116
|
* their "booking cancelled" events dropped — they rarely cancel.
|
|
117
117
|
* Advance bookers keep all cancellation events.
|
|
118
118
|
*
|
|
@@ -192,6 +192,74 @@ const destinationCities = ["New York", "London", "Paris", "Tokyo", "Barcelona",
|
|
|
192
192
|
* REAL-WORLD ANALOGUE: Business travelers book the same hotels
|
|
193
193
|
* repeatedly, leading to faster, more confident conversions.
|
|
194
194
|
*
|
|
195
|
+
* ───────────────────────────────────────────────────────────────
|
|
196
|
+
* 9. BOOKING TIME-TO-CONVERT (funnel-post hook)
|
|
197
|
+
* ───────────────────────────────────────────────────────────────
|
|
198
|
+
*
|
|
199
|
+
* PATTERN: Business travelers complete the Search-to-Book funnel
|
|
200
|
+
* 1.35x faster (time gaps scaled by 0.74). Budget and leisure-family
|
|
201
|
+
* users complete it 1.25x slower (gaps scaled by 1.25). The hook
|
|
202
|
+
* iterates over the funnel-post event array, compresses or stretches
|
|
203
|
+
* the inter-step time gaps based on the user's customer_segment from
|
|
204
|
+
* meta.profile, then rewrites each event's timestamp.
|
|
205
|
+
*
|
|
206
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
207
|
+
*
|
|
208
|
+
* Report 1: Search-to-Book TTC by Segment
|
|
209
|
+
* - Report type: Funnels
|
|
210
|
+
* - Steps: "destination searched" -> "hotel viewed" -> "price compared" -> "booking completed"
|
|
211
|
+
* - Breakdown: user property "customer_segment"
|
|
212
|
+
* - Metric: Median time to convert
|
|
213
|
+
* - Expected: business_traveler median TTC ~ 0.74x of budget/leisure TTC
|
|
214
|
+
* (e.g., business ~ 24h vs budget ~ 43h)
|
|
215
|
+
*
|
|
216
|
+
* NOTE: This effect is visible ONLY in Mixpanel funnel median TTC.
|
|
217
|
+
* Cross-event MIN->MIN SQL queries on raw events do NOT show this
|
|
218
|
+
* because funnel-post mutates timestamps after event generation but
|
|
219
|
+
* before storage.
|
|
220
|
+
*
|
|
221
|
+
* REAL-WORLD ANALOGUE: Business travelers know their preferred hotel
|
|
222
|
+
* chains and corporate rates, moving from search to booking with
|
|
223
|
+
* minimal comparison. Leisure and budget travelers deliberate longer,
|
|
224
|
+
* comparing options and waiting for deals.
|
|
225
|
+
*
|
|
226
|
+
* ───────────────────────────────────────────────────────────────
|
|
227
|
+
* 10. HOTEL-VIEWED MAGIC NUMBER (everything hook)
|
|
228
|
+
* ───────────────────────────────────────────────────────────────
|
|
229
|
+
*
|
|
230
|
+
* PATTERN: Users who viewed 5-10 hotels sit in a "decisive comparison
|
|
231
|
+
* shopper" sweet spot -- all their nightly_rate values on "booking
|
|
232
|
+
* completed" events are boosted by +30% (factor 1.3), indicating they
|
|
233
|
+
* chose higher-tier rooms after deliberate comparison. Users who
|
|
234
|
+
* viewed 11+ hotels trigger analysis paralysis: 35% of their
|
|
235
|
+
* "booking completed" events are dropped entirely. No flag is
|
|
236
|
+
* stamped -- discoverable only by binning users on hotel-viewed
|
|
237
|
+
* COUNT and comparing booking revenue or conversion volume.
|
|
238
|
+
*
|
|
239
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
240
|
+
*
|
|
241
|
+
* Report 1: Nightly Rate by Hotel-View Cohort
|
|
242
|
+
* - Report type: Insights (with cohorts)
|
|
243
|
+
* - Cohort A: users who did "hotel viewed" 5-10 times
|
|
244
|
+
* - Cohort B: users who did "hotel viewed" 0-4 times
|
|
245
|
+
* - Event: "booking completed"
|
|
246
|
+
* - Measure: Average of "nightly_rate"
|
|
247
|
+
* - Compare cohort A vs cohort B
|
|
248
|
+
* - Expected: cohort A ~ 1.3x higher avg nightly_rate
|
|
249
|
+
*
|
|
250
|
+
* Report 2: Bookings per User by Hotel-View Volume
|
|
251
|
+
* - Report type: Insights (with cohorts)
|
|
252
|
+
* - Cohort C: users who did "hotel viewed" 11+ times
|
|
253
|
+
* - Cohort A: users who did "hotel viewed" 5-10 times
|
|
254
|
+
* - Event: "booking completed"
|
|
255
|
+
* - Measure: Total events per user
|
|
256
|
+
* - Compare cohort C vs cohort A
|
|
257
|
+
* - Expected: cohort C ~ 35% fewer bookings per user
|
|
258
|
+
*
|
|
259
|
+
* REAL-WORLD ANALOGUE: Travelers who compare a handful of hotels
|
|
260
|
+
* make confident, higher-value bookings; those who endlessly browse
|
|
261
|
+
* suffer decision fatigue and often abandon the search entirely.
|
|
262
|
+
*
|
|
195
263
|
* ═══════════════════════════════════════════════════════════════
|
|
196
264
|
* EXPECTED METRICS SUMMARY
|
|
197
265
|
* ═══════════════════════════════════════════════════════════════
|
|
@@ -206,6 +274,9 @@ const destinationCities = ["New York", "London", "Paris", "Tokyo", "Barcelona",
|
|
|
206
274
|
* Review Quality | review_length | 120 | 180/80 | 1.5x/0.67x
|
|
207
275
|
* Business Profile | travel_frequency | mixed | weekly | —
|
|
208
276
|
* Repeat Destination | funnel conversion | 40% | 52% | 1.3x
|
|
277
|
+
* Booking TTC | funnel median TTC | 1x | 0.74x | 1.35x faster (business)
|
|
278
|
+
* Hotel-Viewed Magic Num | sweet nightly_rate | 1x | 1.3x | +30%
|
|
279
|
+
* Hotel-Viewed Magic Num | over bookings/user | 1x | 0.65x | -35%
|
|
209
280
|
*/
|
|
210
281
|
|
|
211
282
|
/** @type {Config} */
|
|
@@ -774,10 +845,10 @@ const config = {
|
|
|
774
845
|
}
|
|
775
846
|
});
|
|
776
847
|
|
|
777
|
-
// Last-minute bookers rarely cancel — drop
|
|
848
|
+
// Last-minute bookers rarely cancel — drop 60%.
|
|
778
849
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
779
850
|
if (events[i].event === "booking cancelled" && events[i].booking_window === "last_minute") {
|
|
780
|
-
if (chance.bool({ likelihood:
|
|
851
|
+
if (chance.bool({ likelihood: 60 })) {
|
|
781
852
|
events.splice(i, 1);
|
|
782
853
|
}
|
|
783
854
|
}
|
package/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import { makeMirror } from './lib/generators/mirror.js';
|
|
|
25
25
|
import { makeGroupProfile, makeProfile } from './lib/generators/profiles.js';
|
|
26
26
|
|
|
27
27
|
// Utilities
|
|
28
|
-
import { initChance, setDatasetNow, deleteFile } from './lib/utils/utils.js';
|
|
28
|
+
import { initChance, setDatasetNow, setDatasetBegin, deleteFile } from './lib/utils/utils.js';
|
|
29
29
|
|
|
30
30
|
// External dependencies
|
|
31
31
|
import dayjs from "dayjs";
|
|
@@ -143,6 +143,7 @@ async function runDungeon(config) {
|
|
|
143
143
|
// dungeon configs. Without this, those factories produce values relative
|
|
144
144
|
// to process-start, which leaks wall-clock time into the output.
|
|
145
145
|
setDatasetNow(fixedNow);
|
|
146
|
+
setDatasetBegin(fixedBegin);
|
|
146
147
|
|
|
147
148
|
// Step 2: Create context with validated config (pass time constants explicitly)
|
|
148
149
|
const context = createContext(validatedConfig, null, { fixedNow, fixedBegin });
|
package/lib/utils/utils.js
CHANGED
|
@@ -33,19 +33,29 @@ const weightedArrayCache = new Map();
|
|
|
33
33
|
// window is resolved, so date helpers in dungeon configs produce deterministic
|
|
34
34
|
// values relative to the dataset end (not the process start).
|
|
35
35
|
let DATASET_NOW = dayjs.utc();
|
|
36
|
+
let DATASET_BEGIN = dayjs.utc().subtract(30, 'day');
|
|
36
37
|
|
|
37
|
-
/**
|
|
38
|
-
* Override the reference "now" used by date()/day() factories. Called by the
|
|
39
|
-
* orchestrator with the resolved dataset end so every date helper produces
|
|
40
|
-
* deterministic values relative to the dataset window.
|
|
41
|
-
* @param {number} unixSeconds
|
|
42
|
-
*/
|
|
38
|
+
/** @param {number} unixSeconds */
|
|
43
39
|
function setDatasetNow(unixSeconds) {
|
|
44
40
|
if (typeof unixSeconds === 'number' && Number.isFinite(unixSeconds)) {
|
|
45
41
|
DATASET_NOW = dayjs.unix(unixSeconds).utc();
|
|
46
42
|
}
|
|
47
43
|
}
|
|
48
44
|
|
|
45
|
+
/** @param {number} unixSeconds */
|
|
46
|
+
function setDatasetBegin(unixSeconds) {
|
|
47
|
+
if (typeof unixSeconds === 'number' && Number.isFinite(unixSeconds)) {
|
|
48
|
+
DATASET_BEGIN = dayjs.unix(unixSeconds).utc();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ListValue extends Array {
|
|
54
|
+
constructor(items) {
|
|
55
|
+
super(items.length);
|
|
56
|
+
for (let i = 0; i < items.length; i++) this[i] = items[i];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
49
59
|
|
|
50
60
|
import { Storage as cloudStorage } from '@google-cloud/storage';
|
|
51
61
|
const projectId = 'YOUR_PROJECT_ID';
|
|
@@ -198,11 +208,66 @@ function day(start, end) {
|
|
|
198
208
|
|
|
199
209
|
};
|
|
200
210
|
|
|
211
|
+
/**
|
|
212
|
+
* @param {string|number|dayjs.Dayjs} [start] - Defaults to DATASET_BEGIN.
|
|
213
|
+
* @param {string|number|dayjs.Dayjs} [end] - Defaults to DATASET_NOW.
|
|
214
|
+
* @param {string|null} [format='YYYY-MM-DDTHH:mm:ss'] - null for ISO string.
|
|
215
|
+
* @returns {() => string}
|
|
216
|
+
*/
|
|
217
|
+
function dateRange(start, end, format = 'YYYY-MM-DDTHH:mm:ss') {
|
|
218
|
+
return function () {
|
|
219
|
+
const chance = getChance();
|
|
220
|
+
const s = start != null ? dayjs(typeof start === 'number' ? start * 1000 : start).utc() : DATASET_BEGIN;
|
|
221
|
+
const e = end != null ? dayjs(typeof end === 'number' ? end * 1000 : end).utc() : DATASET_NOW;
|
|
222
|
+
const diffSec = e.diff(s, 'second');
|
|
223
|
+
const offsetSec = chance.integer({ min: 0, max: Math.max(0, diffSec) });
|
|
224
|
+
const result = s.add(offsetSec, 'second');
|
|
225
|
+
return format ? result.format(format) : result.toISOString();
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @param {Array} pool - Values to pick from.
|
|
231
|
+
* @param {{ min?: number, max?: number }} [options]
|
|
232
|
+
* @returns {() => ListValue}
|
|
233
|
+
*/
|
|
234
|
+
function listOf(pool, options = {}) {
|
|
235
|
+
const { min = 1, max = 3 } = options;
|
|
236
|
+
return function () {
|
|
237
|
+
const chance = getChance();
|
|
238
|
+
const count = chance.integer({ min, max: Math.min(max, pool.length) });
|
|
239
|
+
return new ListValue(chance.pickset(pool, count));
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @param {Record<string, import('../../types.js').ValueValid>} template
|
|
245
|
+
* @param {{ min?: number, max?: number }} [options]
|
|
246
|
+
* @returns {() => ListValue}
|
|
247
|
+
*/
|
|
248
|
+
function objectList(template, options = {}) {
|
|
249
|
+
const { min = 1, max = 5 } = options;
|
|
250
|
+
return function () {
|
|
251
|
+
const chance = getChance();
|
|
252
|
+
const count = chance.integer({ min, max });
|
|
253
|
+
const result = [];
|
|
254
|
+
for (let i = 0; i < count; i++) {
|
|
255
|
+
const obj = {};
|
|
256
|
+
for (const [key, val] of Object.entries(template)) {
|
|
257
|
+
obj[key] = choose(val);
|
|
258
|
+
}
|
|
259
|
+
result.push(obj);
|
|
260
|
+
}
|
|
261
|
+
return new ListValue(result);
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
201
265
|
/**
|
|
202
266
|
* similar to pick
|
|
203
267
|
* @param {ValueValid} value
|
|
204
268
|
*/
|
|
205
269
|
function choose(value) {
|
|
270
|
+
if (value instanceof ListValue) return value;
|
|
206
271
|
const chance = getChance();
|
|
207
272
|
|
|
208
273
|
// most of the time this will receive a list of strings;
|
|
@@ -245,6 +310,7 @@ function choose(value) {
|
|
|
245
310
|
}
|
|
246
311
|
|
|
247
312
|
const result = value();
|
|
313
|
+
if (result instanceof ListValue) return result;
|
|
248
314
|
if (Array.isArray(result) && result.length > 10) {
|
|
249
315
|
// Cache large arrays (likely weighted arrays)
|
|
250
316
|
weightedArrayCache.set(funcString, result);
|
|
@@ -252,7 +318,9 @@ function choose(value) {
|
|
|
252
318
|
value = result;
|
|
253
319
|
}
|
|
254
320
|
|
|
255
|
-
|
|
321
|
+
if (value instanceof ListValue) return value;
|
|
322
|
+
|
|
323
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
256
324
|
return ""; // Return empty string if the array is empty
|
|
257
325
|
}
|
|
258
326
|
|
|
@@ -1660,5 +1728,10 @@ export {
|
|
|
1660
1728
|
assignSessionIds,
|
|
1661
1729
|
bunchIntoSessions,
|
|
1662
1730
|
setDatasetNow,
|
|
1731
|
+
setDatasetBegin,
|
|
1732
|
+
dateRange,
|
|
1733
|
+
listOf,
|
|
1734
|
+
objectList,
|
|
1735
|
+
ListValue,
|
|
1663
1736
|
deleteFile,
|
|
1664
1737
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Mixpanel breakdown emulator.
|
|
3
3
|
*
|
|
4
4
|
* Best-effort approximation of the table shapes Mixpanel produces for the five
|
|
5
|
-
* analyses the Phase 4 hook patterns target. Used by `verify-
|
|
5
|
+
* analyses the Phase 4 hook patterns target. Used by `verify-dungeon` to assert
|
|
6
6
|
* that engineered patterns actually produce the expected ratios in the data, AND
|
|
7
7
|
* by consumers who want to validate dungeons against expected business shapes
|
|
8
8
|
* outside of Mixpanel.
|
package/lib/verify/index.js
CHANGED