@ak--47/dungeon-master 1.0.0 → 1.1.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/README.md +7 -7
- package/dungeons/technical/ad-spend-schema.json +128 -0
- package/dungeons/technical/ad-spend.js +116 -0
- package/dungeons/technical/anonymous-users-schema.json +91 -0
- package/dungeons/technical/anonymous-users.js +102 -0
- package/dungeons/technical/array-of-object-lookup-schema.json +191 -0
- package/dungeons/{array-of-object-lookup.js → technical/array-of-object-lookup.js} +11 -4
- package/dungeons/technical/experiments-schema.json +197 -0
- package/dungeons/technical/experiments.js +168 -0
- package/dungeons/technical/foobar-schema.json +362 -0
- package/dungeons/{foobar.js → technical/foobar.js} +2 -2
- package/dungeons/technical/group-analytics-schema.json +149 -0
- package/dungeons/technical/group-analytics.js +151 -0
- package/dungeons/technical/mirror-strategies-schema.json +84 -0
- package/dungeons/technical/mirror-strategies.js +126 -0
- package/dungeons/technical/nested-objects-schema.json +145 -0
- package/dungeons/technical/nested-objects.js +207 -0
- package/dungeons/technical/retention-cadence-schema.json +37 -0
- package/dungeons/{retention-cadence.js → technical/retention-cadence.js} +1 -1
- package/dungeons/technical/sanity-schema.json +185 -0
- package/dungeons/{sanity.js → technical/sanity.js} +3 -2
- package/dungeons/technical/scale-test-schema.json +70 -0
- package/dungeons/technical/scale-test.js +90 -0
- package/dungeons/technical/scd-schema.json +270 -0
- package/dungeons/{scd.js → technical/scd.js} +19 -10
- package/dungeons/technical/simple-schema.json +278 -0
- package/dungeons/{simple.js → technical/simple.js} +6 -6
- package/dungeons/technical/simplest-schema.json +635 -0
- package/dungeons/{simplest.js → technical/simplest.js} +6 -6
- package/dungeons/technical/text-generation-schema.json +1059 -0
- package/dungeons/{text-generation.js → technical/text-generation.js} +23 -9
- package/dungeons/vertical/community-schema.json +557 -0
- package/dungeons/vertical/community.js +788 -0
- package/dungeons/vertical/devtools-schema.json +580 -0
- package/dungeons/vertical/devtools.js +837 -0
- package/dungeons/vertical/ecommerce-schema.json +586 -0
- package/dungeons/{ecommerce.js → vertical/ecommerce.js} +34 -3
- package/dungeons/vertical/education-schema.json +697 -0
- package/dungeons/{education.js → vertical/education.js} +54 -36
- package/dungeons/vertical/fintech-schema.json +563 -0
- package/dungeons/{fintech.js → vertical/fintech.js} +91 -53
- package/dungeons/vertical/fitness-schema.json +508 -0
- package/dungeons/vertical/fitness.js +802 -0
- package/dungeons/vertical/food-delivery-schema.json +678 -0
- package/dungeons/{food.js → vertical/food-delivery.js} +51 -23
- package/dungeons/vertical/gaming-schema.json +394 -0
- package/dungeons/{gaming.js → vertical/gaming.js} +9 -7
- package/dungeons/vertical/healthcare-schema.json +527 -0
- package/dungeons/vertical/healthcare.js +769 -0
- package/dungeons/vertical/insurance-application-schema.json +466 -0
- package/dungeons/{insurance-application.js → vertical/insurance-application.js} +33 -29
- package/dungeons/vertical/logistics-schema.json +552 -0
- package/dungeons/vertical/logistics.js +793 -0
- package/dungeons/vertical/marketplace-schema.json +511 -0
- package/dungeons/vertical/marketplace.js +844 -0
- package/dungeons/vertical/media-schema.json +787 -0
- package/dungeons/{media.js → vertical/media.js} +94 -16
- package/dungeons/vertical/rpg-schema.json +654 -0
- package/dungeons/{rpg.js → vertical/rpg.js} +78 -57
- package/dungeons/vertical/sass-schema.json +653 -0
- package/dungeons/{sass.js → vertical/sass.js} +49 -23
- package/dungeons/vertical/social-schema.json +552 -0
- package/dungeons/{social.js → vertical/social.js} +60 -14
- package/dungeons/vertical/travel-schema.json +558 -0
- package/dungeons/vertical/travel.js +736 -0
- package/index.js +19 -15
- package/lib/core/config-validator.js +286 -1
- package/lib/core/context.js +7 -5
- package/lib/generators/adspend.js +3 -3
- package/lib/generators/events.js +141 -14
- package/lib/generators/funnels.js +67 -7
- package/lib/generators/text.js +2 -2
- package/lib/orchestrators/user-loop.js +441 -22
- package/lib/templates/soup-presets.js +2 -2
- package/lib/utils/utils.js +4 -5
- package/package.json +4 -3
- package/scripts/dungeon-to-json.mjs +163 -0
- package/scripts/extract-dungeon-schema.mjs +297 -0
- package/scripts/json-to-dungeon.mjs +200 -0
- package/scripts/run-dungeon.mjs +76 -0
- package/scripts/run-many.mjs +118 -0
- package/scripts/verify-runner.mjs +42 -0
- package/types.d.ts +313 -1
- package/dungeons/array-of-object-lookup-schema.json +0 -327
- package/dungeons/ecommerce-schema.json +0 -462
- package/dungeons/education-schema.json +0 -2409
- package/dungeons/fintech-schema.json +0 -14034
- package/dungeons/foobar-schema.json +0 -403
- package/dungeons/food-delivery-schema.json +0 -192
- package/dungeons/food-delivery.js +0 -602
- package/dungeons/food-schema.json +0 -1152
- package/dungeons/gaming-schema.json +0 -1270
- package/dungeons/insurance-application-schema.json +0 -204
- package/dungeons/media-schema.json +0 -906
- package/dungeons/retention-cadence-schema.json +0 -78
- package/dungeons/rpg-schema.json +0 -4526
- package/dungeons/sanity-schema.json +0 -255
- package/dungeons/sass-schema.json +0 -1291
- package/dungeons/scd-schema.json +0 -919
- package/dungeons/simple-schema.json +0 -608
- package/dungeons/simplest-schema.json +0 -1418
- package/dungeons/social-schema.json +0 -1118
- package/dungeons/text-generation-schema.json +0 -3096
package/types.d.ts
CHANGED
|
@@ -109,6 +109,26 @@ export interface Dungeon {
|
|
|
109
109
|
/** Hook function called on every data point. The primary mechanism for engineering deliberate trends and patterns. */
|
|
110
110
|
hook?: Hook<any>;
|
|
111
111
|
|
|
112
|
+
// ── Advanced Features ──
|
|
113
|
+
/** User persona/archetype definitions. Each persona defines a behavioral segment with distinct event volumes, conversion rates, and properties. */
|
|
114
|
+
personas?: Persona[];
|
|
115
|
+
/** World events that affect all users simultaneously (outages, campaigns, product launches). */
|
|
116
|
+
worldEvents?: WorldEvent[] | ResolvedWorldEvent[];
|
|
117
|
+
/** Engagement decay configuration. Controls how user activity decreases over their lifetime. */
|
|
118
|
+
engagementDecay?: EngagementDecay;
|
|
119
|
+
/** Data quality imperfections to inject (nulls, duplicates, bots, late-arriving events). */
|
|
120
|
+
dataQuality?: DataQuality;
|
|
121
|
+
/** Subscription/revenue lifecycle configuration. */
|
|
122
|
+
subscription?: Subscription;
|
|
123
|
+
/** Connected attribution configuration linking campaigns to user acquisition. */
|
|
124
|
+
attribution?: Attribution;
|
|
125
|
+
/** Geographic intelligence: sticky locations, timezone-aware activity, regional launches. */
|
|
126
|
+
geo?: GeoConfig;
|
|
127
|
+
/** Progressive feature adoption: features that launch mid-dataset with S-curve adoption. */
|
|
128
|
+
features?: FeatureConfig[];
|
|
129
|
+
/** Anomaly/outlier injection: extreme values, bursts, coordinated spikes. */
|
|
130
|
+
anomalies?: AnomalyConfig[];
|
|
131
|
+
|
|
112
132
|
/** Allow arbitrary additional properties on the config. */
|
|
113
133
|
[key: string]: any;
|
|
114
134
|
|
|
@@ -263,7 +283,7 @@ export interface Defaults {
|
|
|
263
283
|
browsers: () => any[];
|
|
264
284
|
campaigns: () => any[];
|
|
265
285
|
devicePools: { android: any[]; ios: any[]; desktop: any[] };
|
|
266
|
-
allDevices:any[];
|
|
286
|
+
allDevices: any[];
|
|
267
287
|
}
|
|
268
288
|
|
|
269
289
|
/**
|
|
@@ -520,6 +540,298 @@ export type Result = {
|
|
|
520
540
|
avgEPS?: number;
|
|
521
541
|
};
|
|
522
542
|
|
|
543
|
+
// ============= Advanced Feature Types =============
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* User persona/archetype definition.
|
|
547
|
+
* Personas define behavioral segments with distinct event volumes, conversion rates, and properties.
|
|
548
|
+
*/
|
|
549
|
+
export interface Persona {
|
|
550
|
+
/** Unique name for this persona (e.g., "power_user", "casual", "churner"). */
|
|
551
|
+
name: string;
|
|
552
|
+
/** Relative weight for persona assignment (higher = more users get this persona). */
|
|
553
|
+
weight: number;
|
|
554
|
+
/** Multiplier for number of events this persona generates (1.0 = normal). */
|
|
555
|
+
eventMultiplier?: number;
|
|
556
|
+
/** Multiplier for funnel conversion rates (1.0 = normal, 1.3 = 30% better). */
|
|
557
|
+
conversionModifier?: number;
|
|
558
|
+
/** Base churn rate for this persona (0-1). */
|
|
559
|
+
churnRate?: number;
|
|
560
|
+
/** Properties merged into user profiles for this persona. */
|
|
561
|
+
properties?: Record<string, ValueValid>;
|
|
562
|
+
/** Limit how long this persona is active (e.g., trial users active for 14 days). */
|
|
563
|
+
activeWindow?: { maxDays: number };
|
|
564
|
+
/** Per-persona engagement decay override. */
|
|
565
|
+
engagementDecay?: EngagementDecay;
|
|
566
|
+
/** Per-persona soup/timing override. */
|
|
567
|
+
soupOverride?: SoupConfig;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* World event that affects all users simultaneously.
|
|
572
|
+
*/
|
|
573
|
+
export interface WorldEvent {
|
|
574
|
+
/** Name of the world event (e.g., "black_friday", "platform_outage"). */
|
|
575
|
+
name: string;
|
|
576
|
+
/** Type category for the event. */
|
|
577
|
+
type?: "campaign" | "outage" | "product_launch" | "holiday" | "incident" | string;
|
|
578
|
+
/** Start day relative to dataset start (e.g., 60 = day 60). */
|
|
579
|
+
startDay: number;
|
|
580
|
+
/** Duration in days (0.25 = 6 hours, null = permanent from startDay onward). */
|
|
581
|
+
duration?: number | null;
|
|
582
|
+
/** Volume multiplier during this event (3.0 = 3x events, 0.1 = 90% drop). */
|
|
583
|
+
volumeMultiplier?: number;
|
|
584
|
+
/** Conversion rate modifier during this event. */
|
|
585
|
+
conversionModifier?: number;
|
|
586
|
+
/** Properties injected into affected events. */
|
|
587
|
+
injectProps?: Record<string, any>;
|
|
588
|
+
/** Which events are affected ("*" for all, or array of event names). */
|
|
589
|
+
affectsEvents?: string[] | "*";
|
|
590
|
+
/** Aftermath period after the event ends. */
|
|
591
|
+
aftermath?: { duration: number; volumeMultiplier: number };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Resolved world event with absolute timestamps (internal use).
|
|
596
|
+
*/
|
|
597
|
+
export interface ResolvedWorldEvent extends WorldEvent {
|
|
598
|
+
/** Absolute start time (unix seconds). */
|
|
599
|
+
startUnix: number;
|
|
600
|
+
/** Absolute end time (unix seconds), or Infinity for permanent events. */
|
|
601
|
+
endUnix: number;
|
|
602
|
+
/** Aftermath end time (unix seconds), if applicable. */
|
|
603
|
+
aftermathEndUnix?: number;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Engagement decay configuration.
|
|
608
|
+
*/
|
|
609
|
+
export interface EngagementDecay {
|
|
610
|
+
/** Decay model type. "none" preserves flat engagement (default). */
|
|
611
|
+
model: "exponential" | "linear" | "step" | "none";
|
|
612
|
+
/** Days until engagement halves (for exponential model). */
|
|
613
|
+
halfLife?: number;
|
|
614
|
+
/** Minimum engagement ratio (0 = can fully churn, 0.1 = never below 10%). */
|
|
615
|
+
floor?: number;
|
|
616
|
+
/** Per-day chance of re-engagement spike after decay. */
|
|
617
|
+
reactivationChance?: number;
|
|
618
|
+
/** Multiplier for engagement during reactivation. */
|
|
619
|
+
reactivationMultiplier?: number;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Data quality imperfection configuration.
|
|
624
|
+
*/
|
|
625
|
+
export interface DataQuality {
|
|
626
|
+
/** Fraction of property values that become null (0-1). */
|
|
627
|
+
nullRate?: number;
|
|
628
|
+
/** Which properties to null ("*" for any, or array of property names). */
|
|
629
|
+
nullProps?: string[] | "*";
|
|
630
|
+
/** Fraction of events that get duplicated (0-1). */
|
|
631
|
+
duplicateRate?: number;
|
|
632
|
+
/** Fraction of events that arrive late (shifted 1-7 days backward) (0-1). */
|
|
633
|
+
lateArrivingRate?: number;
|
|
634
|
+
/** Number of synthetic bot users to inject. */
|
|
635
|
+
botUsers?: number;
|
|
636
|
+
/** Events per bot user (bots generate repetitive, machine-like patterns). */
|
|
637
|
+
botEventsPerUser?: number;
|
|
638
|
+
/** Fraction of events with timezone offset errors (0-1). */
|
|
639
|
+
timezoneConfusion?: number;
|
|
640
|
+
/** Fraction of events missing their event name (0-1). */
|
|
641
|
+
emptyEvents?: number;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Subscription plan definition.
|
|
646
|
+
*/
|
|
647
|
+
export interface SubscriptionPlan {
|
|
648
|
+
/** Plan name (e.g., "free", "starter", "pro"). */
|
|
649
|
+
name: string;
|
|
650
|
+
/** Monthly price. 0 for free tier. */
|
|
651
|
+
price: number;
|
|
652
|
+
/** If true, users start on this plan. */
|
|
653
|
+
default?: boolean;
|
|
654
|
+
/** Trial period in days before requiring payment. */
|
|
655
|
+
trialDays?: number;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Subscription lifecycle rates.
|
|
660
|
+
*/
|
|
661
|
+
export interface SubscriptionLifecycle {
|
|
662
|
+
/** Rate of trial-to-paid conversion (0-1). */
|
|
663
|
+
trialToPayRate?: number;
|
|
664
|
+
/** Monthly upgrade rate (0-1). */
|
|
665
|
+
upgradeRate?: number;
|
|
666
|
+
/** Monthly downgrade rate (0-1). */
|
|
667
|
+
downgradeRate?: number;
|
|
668
|
+
/** Monthly churn/cancellation rate (0-1). */
|
|
669
|
+
churnRate?: number;
|
|
670
|
+
/** Rate of churned users who come back (0-1). */
|
|
671
|
+
winBackRate?: number;
|
|
672
|
+
/** Days before win-back attempt. */
|
|
673
|
+
winBackDelay?: number;
|
|
674
|
+
/** Rate of payment failures (0-1). */
|
|
675
|
+
paymentFailureRate?: number;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Subscription configuration.
|
|
680
|
+
*/
|
|
681
|
+
export interface Subscription {
|
|
682
|
+
/** Available plans, ordered from lowest to highest tier. */
|
|
683
|
+
plans: SubscriptionPlan[];
|
|
684
|
+
/** Lifecycle transition rates. */
|
|
685
|
+
lifecycle?: SubscriptionLifecycle;
|
|
686
|
+
/** Event names for subscription lifecycle events. */
|
|
687
|
+
events?: {
|
|
688
|
+
trialStarted?: string;
|
|
689
|
+
subscribed?: string;
|
|
690
|
+
upgraded?: string;
|
|
691
|
+
downgraded?: string;
|
|
692
|
+
renewed?: string;
|
|
693
|
+
cancelled?: string;
|
|
694
|
+
paymentFailed?: string;
|
|
695
|
+
wonBack?: string;
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Attribution campaign definition.
|
|
701
|
+
*/
|
|
702
|
+
export interface AttributionCampaign {
|
|
703
|
+
/** Campaign name. */
|
|
704
|
+
name: string;
|
|
705
|
+
/** UTM source (e.g., "google", "facebook"). */
|
|
706
|
+
source: string;
|
|
707
|
+
/** UTM medium (e.g., "search_ad", "social"). */
|
|
708
|
+
medium?: string;
|
|
709
|
+
/** UTM content (e.g., "variant_a", "hero_image"). */
|
|
710
|
+
utm_content?: string;
|
|
711
|
+
/** UTM term (e.g., "running+shoes", "best+deals"). */
|
|
712
|
+
utm_term?: string;
|
|
713
|
+
/** Active days range [startDay, endDay] relative to dataset start. */
|
|
714
|
+
activeDays: [number, number];
|
|
715
|
+
/** Daily budget range [min, max]. */
|
|
716
|
+
dailyBudget?: [number, number];
|
|
717
|
+
/** Fraction of impressions that become users (0-1). */
|
|
718
|
+
acquisitionRate?: number;
|
|
719
|
+
/** Persona weight biases for users acquired by this campaign. */
|
|
720
|
+
userPersonaBias?: Record<string, number>;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Connected attribution configuration.
|
|
725
|
+
*/
|
|
726
|
+
export interface Attribution {
|
|
727
|
+
/** Attribution model type. */
|
|
728
|
+
model?: "last_touch" | "first_touch" | "linear" | "time_decay";
|
|
729
|
+
/** Attribution window in days. */
|
|
730
|
+
window?: number;
|
|
731
|
+
/** Campaign definitions. */
|
|
732
|
+
campaigns: AttributionCampaign[];
|
|
733
|
+
/** Fraction of users who arrive organically (no campaign) (0-1). */
|
|
734
|
+
organicRate?: number;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Geographic region definition.
|
|
739
|
+
*/
|
|
740
|
+
export interface GeoRegion {
|
|
741
|
+
/** Region name (e.g., "north_america"). */
|
|
742
|
+
name: string;
|
|
743
|
+
/** Country codes in this region. */
|
|
744
|
+
countries: string[];
|
|
745
|
+
/** Weight for user assignment (higher = more users). */
|
|
746
|
+
weight: number;
|
|
747
|
+
/** UTC timezone offset for this region (e.g., -5 for EST). */
|
|
748
|
+
timezoneOffset: number;
|
|
749
|
+
/** Properties injected for users in this region. */
|
|
750
|
+
properties?: Record<string, any>;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Regional feature launch definition.
|
|
755
|
+
*/
|
|
756
|
+
export interface RegionalLaunch {
|
|
757
|
+
/** Region name to match. */
|
|
758
|
+
region: string;
|
|
759
|
+
/** Feature name. */
|
|
760
|
+
featureName: string;
|
|
761
|
+
/** Day the feature launches in this region. */
|
|
762
|
+
startDay: number;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Geographic intelligence configuration.
|
|
767
|
+
*/
|
|
768
|
+
export interface GeoConfig {
|
|
769
|
+
/** If true, users keep their location across all events (default: false for backwards compat). */
|
|
770
|
+
sticky?: boolean;
|
|
771
|
+
/** Region definitions with timezone offsets and properties. */
|
|
772
|
+
regions?: GeoRegion[];
|
|
773
|
+
/** Regional feature launches. */
|
|
774
|
+
regionalLaunches?: RegionalLaunch[];
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Progressive feature adoption configuration.
|
|
779
|
+
*/
|
|
780
|
+
export interface FeatureConfig {
|
|
781
|
+
/** Feature name (e.g., "dark_mode", "ai_recommendations"). */
|
|
782
|
+
name: string;
|
|
783
|
+
/** Day the feature launches (relative to dataset start). */
|
|
784
|
+
launchDay: number;
|
|
785
|
+
/** Adoption curve speed or custom logistic params. */
|
|
786
|
+
adoptionCurve?: "fast" | "slow" | "instant" | { k: number; midpoint: number };
|
|
787
|
+
/** Property name to inject on events. */
|
|
788
|
+
property: string;
|
|
789
|
+
/** Possible values for the property. First value is the "before" default if defaultBefore not set. */
|
|
790
|
+
values: any[];
|
|
791
|
+
/** Default value before the feature launches. If not set, property doesn't exist before launch. */
|
|
792
|
+
defaultBefore?: any;
|
|
793
|
+
/** Which events are affected ("*" for all, or array of event names). */
|
|
794
|
+
affectsEvents?: string[] | "*";
|
|
795
|
+
/** Conversion rate lift for users who adopted the feature. */
|
|
796
|
+
conversionLift?: number;
|
|
797
|
+
/** Resolved logistic curve params (set by config-validator). */
|
|
798
|
+
_resolvedCurve?: { k: number; midpoint: number };
|
|
799
|
+
/** Pre-computed adopted values (set by config-validator). */
|
|
800
|
+
_adoptedValues?: any[];
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Anomaly/outlier configuration.
|
|
805
|
+
*/
|
|
806
|
+
export interface AnomalyConfig {
|
|
807
|
+
/** Type of anomaly. */
|
|
808
|
+
type: "extreme_value" | "burst" | "coordinated";
|
|
809
|
+
/** Event name this anomaly applies to. */
|
|
810
|
+
event: string;
|
|
811
|
+
/** For extreme_value: property to modify. */
|
|
812
|
+
property?: string;
|
|
813
|
+
/** For extreme_value: fraction of events affected (0-1). */
|
|
814
|
+
frequency?: number;
|
|
815
|
+
/** For extreme_value: multiplier applied to the property value. */
|
|
816
|
+
multiplier?: number;
|
|
817
|
+
/** Tag property added to anomalous events. */
|
|
818
|
+
tag?: string;
|
|
819
|
+
/** For burst/coordinated: day when the anomaly occurs. */
|
|
820
|
+
day?: number;
|
|
821
|
+
/** For burst: duration in days (0.083 = ~2 hours). */
|
|
822
|
+
duration?: number;
|
|
823
|
+
/** For burst/coordinated: time window in days (0.01 = ~15 minutes). */
|
|
824
|
+
window?: number;
|
|
825
|
+
/** For burst/coordinated: number of events to inject. */
|
|
826
|
+
count?: number;
|
|
827
|
+
/** Properties injected on anomalous events. */
|
|
828
|
+
properties?: Record<string, any>;
|
|
829
|
+
/** Resolved absolute start time in unix seconds (set by config-validator). */
|
|
830
|
+
_startUnix?: number;
|
|
831
|
+
/** Resolved absolute end time in unix seconds (set by config-validator). */
|
|
832
|
+
_endUnix?: number;
|
|
833
|
+
}
|
|
834
|
+
|
|
523
835
|
/**
|
|
524
836
|
* dungeon-master: generate realistic Mixpanel data at scale
|
|
525
837
|
*
|
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema": {
|
|
3
|
-
"seed": "test array of objects lookup",
|
|
4
|
-
"name": "array-of-object-lookup",
|
|
5
|
-
"numDays": 60,
|
|
6
|
-
"numEvents": 100000,
|
|
7
|
-
"numUsers": 1000,
|
|
8
|
-
"format": "json",
|
|
9
|
-
"region": "US",
|
|
10
|
-
"hasAnonIds": true,
|
|
11
|
-
"hasSessionIds": true,
|
|
12
|
-
"hasAdSpend": false,
|
|
13
|
-
"hasLocation": true,
|
|
14
|
-
"hasAndroidDevices": false,
|
|
15
|
-
"hasIOSDevices": false,
|
|
16
|
-
"hasDesktopDevices": true,
|
|
17
|
-
"hasBrowser": true,
|
|
18
|
-
"hasCampaigns": false,
|
|
19
|
-
"isAnonymous": false,
|
|
20
|
-
"alsoInferFunnels": true,
|
|
21
|
-
"concurrency": 1,
|
|
22
|
-
"batchSize": 250000,
|
|
23
|
-
"writeToDisk": false,
|
|
24
|
-
"events": [
|
|
25
|
-
{
|
|
26
|
-
"event": "checkout",
|
|
27
|
-
"weight": 2,
|
|
28
|
-
"properties": {
|
|
29
|
-
"currency": [
|
|
30
|
-
"USD",
|
|
31
|
-
"CAD",
|
|
32
|
-
"EUR",
|
|
33
|
-
"BTC",
|
|
34
|
-
"ETH",
|
|
35
|
-
"JPY"
|
|
36
|
-
],
|
|
37
|
-
"coupon": {
|
|
38
|
-
"functionName": "arrow",
|
|
39
|
-
"body": "function generateWeightedArray() {\n\t\tconst weightedArray = [];\n\n\t\t// Add each value to the array the number of times specified by its weight\n\t\tweightedItems.forEach(({ value, weight }) => {\n\t\t\tif (!weight) weight = 1;\n\t\t\tfor (let i = 0; i < weight; i++) {\n\t\t\t\tweightedArray.push(value);\n\t\t\t}\n\t\t});\n\n\t\treturn weightedArray;\n\t}"
|
|
40
|
-
},
|
|
41
|
-
"cart": {
|
|
42
|
-
"functionName": "arrow",
|
|
43
|
-
"body": "function () {\n\t\tconst categories = [\"electronics\", \"books\", \"clothing\", \"home\", \"garden\", \"toys\", \"sports\", \"automotive\", \"beauty\", \"health\", \"grocery\", \"jewelry\", \"shoes\", \"tools\", \"office supplies\"];\n\t\tconst descriptors = [\"brand new\", \"open box\", \"refurbished\", \"used\", \"like new\", \"vintage\", \"antique\", \"collectible\"];\n\t\tconst suffix = [\"item\", \"product\", \"good\", \"merchandise\", \"thing\", \"object\", \"widget\", \"gadget\", \"device\", \"apparatus\", \"contraption\", \"instrument\", \"tool\", \"implement\", \"utensil\", \"appliance\", \"machine\", \"equipment\", \"gear\", \"kit\", \"set\", \"package\"];\n\t\tconst assetPreview = ['.png', '.jpg', '.jpeg', '.heic', '.mp4', '.mov', '.avi'];\n\t\tconst data = [];\n\t\tconst numOfItems = integer(1, maxItems);\n\n\t\tfor (var i = 0; i < numOfItems; i++) {\n\t\t\tconst category = chance.pickone(categories);\n\t\t\tconst descriptor = chance.pickone(descriptors);\n\t\t\tconst suffixWord = chance.pickone(suffix);\n\t\t\tconst slug = `${descriptor.replace(/\\s+/g, '-').toLowerCase()}-${suffixWord.replace(/\\s+/g, '-').toLowerCase()}`;\n\t\t\tconst asset = chance.pickone(assetPreview);\n\n\t\t\t// const product_id = chance.guid();\n\t\t\tconst price = integer(1, 100);\n\t\t\tconst quantity = integer(1, 5);\n\t\t\tconst product_id = integer(1, 1_000);\n\n\t\t\tconst item = {\n\t\t\t\tproduct_id: product_id,\n\t\t\t\tproduct_url: `https://example.com/assets/${product_id}`,\n\t\t\t\t// sku: integer(11111, 99999),\n\t\t\t\t// amount: price,\n\t\t\t\t// quantity: quantity,\n\t\t\t\t// total_value: price * quantity,\n\t\t\t\t// featured: chance.pickone([true, false, false]),\n\t\t\t\t// category: category,\n\t\t\t\t// descriptor: descriptor,\n\t\t\t\t// slug: slug,\n\t\t\t\t\n\t\t\t\t// assetType: asset\n\n\t\t\t};\n\n\t\t\tdata.push(item);\n\t\t}\n\n\t\treturn () => [data];\n\t}"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"event": "add to cart",
|
|
49
|
-
"weight": 4,
|
|
50
|
-
"properties": {
|
|
51
|
-
"item": {
|
|
52
|
-
"functionName": "arrow",
|
|
53
|
-
"body": "function () {\n\t\tconst categories = [\"electronics\", \"books\", \"clothing\", \"home\", \"garden\", \"toys\", \"sports\", \"automotive\", \"beauty\", \"health\", \"grocery\", \"jewelry\", \"shoes\", \"tools\", \"office supplies\"];\n\t\tconst descriptors = [\"brand new\", \"open box\", \"refurbished\", \"used\", \"like new\", \"vintage\", \"antique\", \"collectible\"];\n\t\tconst suffix = [\"item\", \"product\", \"good\", \"merchandise\", \"thing\", \"object\", \"widget\", \"gadget\", \"device\", \"apparatus\", \"contraption\", \"instrument\", \"tool\", \"implement\", \"utensil\", \"appliance\", \"machine\", \"equipment\", \"gear\", \"kit\", \"set\", \"package\"];\n\t\tconst assetPreview = ['.png', '.jpg', '.jpeg', '.heic', '.mp4', '.mov', '.avi'];\n\t\tconst data = [];\n\t\tconst numOfItems = integer(1, maxItems);\n\n\t\tfor (var i = 0; i < numOfItems; i++) {\n\t\t\tconst category = chance.pickone(categories);\n\t\t\tconst descriptor = chance.pickone(descriptors);\n\t\t\tconst suffixWord = chance.pickone(suffix);\n\t\t\tconst slug = `${descriptor.replace(/\\s+/g, '-').toLowerCase()}-${suffixWord.replace(/\\s+/g, '-').toLowerCase()}`;\n\t\t\tconst asset = chance.pickone(assetPreview);\n\n\t\t\t// const product_id = chance.guid();\n\t\t\tconst price = integer(1, 100);\n\t\t\tconst quantity = integer(1, 5);\n\t\t\tconst product_id = integer(1, 1_000);\n\n\t\t\tconst item = {\n\t\t\t\tproduct_id: product_id,\n\t\t\t\tproduct_url: `https://example.com/assets/${product_id}`,\n\t\t\t\t// sku: integer(11111, 99999),\n\t\t\t\t// amount: price,\n\t\t\t\t// quantity: quantity,\n\t\t\t\t// total_value: price * quantity,\n\t\t\t\t// featured: chance.pickone([true, false, false]),\n\t\t\t\t// category: category,\n\t\t\t\t// descriptor: descriptor,\n\t\t\t\t// slug: slug,\n\t\t\t\t\n\t\t\t\t// assetType: asset\n\n\t\t\t};\n\n\t\t\tdata.push(item);\n\t\t}\n\n\t\treturn () => [data];\n\t}"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"event": "view item",
|
|
59
|
-
"weight": 8,
|
|
60
|
-
"properties": {
|
|
61
|
-
"item": {
|
|
62
|
-
"functionName": "arrow",
|
|
63
|
-
"body": "function () {\n\t\tconst categories = [\"electronics\", \"books\", \"clothing\", \"home\", \"garden\", \"toys\", \"sports\", \"automotive\", \"beauty\", \"health\", \"grocery\", \"jewelry\", \"shoes\", \"tools\", \"office supplies\"];\n\t\tconst descriptors = [\"brand new\", \"open box\", \"refurbished\", \"used\", \"like new\", \"vintage\", \"antique\", \"collectible\"];\n\t\tconst suffix = [\"item\", \"product\", \"good\", \"merchandise\", \"thing\", \"object\", \"widget\", \"gadget\", \"device\", \"apparatus\", \"contraption\", \"instrument\", \"tool\", \"implement\", \"utensil\", \"appliance\", \"machine\", \"equipment\", \"gear\", \"kit\", \"set\", \"package\"];\n\t\tconst assetPreview = ['.png', '.jpg', '.jpeg', '.heic', '.mp4', '.mov', '.avi'];\n\t\tconst data = [];\n\t\tconst numOfItems = integer(1, maxItems);\n\n\t\tfor (var i = 0; i < numOfItems; i++) {\n\t\t\tconst category = chance.pickone(categories);\n\t\t\tconst descriptor = chance.pickone(descriptors);\n\t\t\tconst suffixWord = chance.pickone(suffix);\n\t\t\tconst slug = `${descriptor.replace(/\\s+/g, '-').toLowerCase()}-${suffixWord.replace(/\\s+/g, '-').toLowerCase()}`;\n\t\t\tconst asset = chance.pickone(assetPreview);\n\n\t\t\t// const product_id = chance.guid();\n\t\t\tconst price = integer(1, 100);\n\t\t\tconst quantity = integer(1, 5);\n\t\t\tconst product_id = integer(1, 1_000);\n\n\t\t\tconst item = {\n\t\t\t\tproduct_id: product_id,\n\t\t\t\tproduct_url: `https://example.com/assets/${product_id}`,\n\t\t\t\t// sku: integer(11111, 99999),\n\t\t\t\t// amount: price,\n\t\t\t\t// quantity: quantity,\n\t\t\t\t// total_value: price * quantity,\n\t\t\t\t// featured: chance.pickone([true, false, false]),\n\t\t\t\t// category: category,\n\t\t\t\t// descriptor: descriptor,\n\t\t\t\t// slug: slug,\n\t\t\t\t\n\t\t\t\t// assetType: asset\n\n\t\t\t};\n\n\t\t\tdata.push(item);\n\t\t}\n\n\t\treturn () => [data];\n\t}"
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
"event": "save item",
|
|
69
|
-
"weight": 5,
|
|
70
|
-
"properties": {
|
|
71
|
-
"item": {
|
|
72
|
-
"functionName": "arrow",
|
|
73
|
-
"body": "function () {\n\t\tconst categories = [\"electronics\", \"books\", \"clothing\", \"home\", \"garden\", \"toys\", \"sports\", \"automotive\", \"beauty\", \"health\", \"grocery\", \"jewelry\", \"shoes\", \"tools\", \"office supplies\"];\n\t\tconst descriptors = [\"brand new\", \"open box\", \"refurbished\", \"used\", \"like new\", \"vintage\", \"antique\", \"collectible\"];\n\t\tconst suffix = [\"item\", \"product\", \"good\", \"merchandise\", \"thing\", \"object\", \"widget\", \"gadget\", \"device\", \"apparatus\", \"contraption\", \"instrument\", \"tool\", \"implement\", \"utensil\", \"appliance\", \"machine\", \"equipment\", \"gear\", \"kit\", \"set\", \"package\"];\n\t\tconst assetPreview = ['.png', '.jpg', '.jpeg', '.heic', '.mp4', '.mov', '.avi'];\n\t\tconst data = [];\n\t\tconst numOfItems = integer(1, maxItems);\n\n\t\tfor (var i = 0; i < numOfItems; i++) {\n\t\t\tconst category = chance.pickone(categories);\n\t\t\tconst descriptor = chance.pickone(descriptors);\n\t\t\tconst suffixWord = chance.pickone(suffix);\n\t\t\tconst slug = `${descriptor.replace(/\\s+/g, '-').toLowerCase()}-${suffixWord.replace(/\\s+/g, '-').toLowerCase()}`;\n\t\t\tconst asset = chance.pickone(assetPreview);\n\n\t\t\t// const product_id = chance.guid();\n\t\t\tconst price = integer(1, 100);\n\t\t\tconst quantity = integer(1, 5);\n\t\t\tconst product_id = integer(1, 1_000);\n\n\t\t\tconst item = {\n\t\t\t\tproduct_id: product_id,\n\t\t\t\tproduct_url: `https://example.com/assets/${product_id}`,\n\t\t\t\t// sku: integer(11111, 99999),\n\t\t\t\t// amount: price,\n\t\t\t\t// quantity: quantity,\n\t\t\t\t// total_value: price * quantity,\n\t\t\t\t// featured: chance.pickone([true, false, false]),\n\t\t\t\t// category: category,\n\t\t\t\t// descriptor: descriptor,\n\t\t\t\t// slug: slug,\n\t\t\t\t\n\t\t\t\t// assetType: asset\n\n\t\t\t};\n\n\t\t\tdata.push(item);\n\t\t}\n\n\t\treturn () => [data];\n\t}"
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
],
|
|
78
|
-
"funnels": [],
|
|
79
|
-
"superProps": {
|
|
80
|
-
"theme": {
|
|
81
|
-
"functionName": "arrow",
|
|
82
|
-
"body": "function () {\n\t\tconst weighted = [];\n\t\tfor (let i = 0; i < 10; i++) {\n\t\t\tconst rand = chance.d10(); // Random number between 1 and 10\n\n\t\t\t// 35% chance to favor the most chosen index\n\t\t\tif (chance.bool({ likelihood: 35 })) {\n\t\t\t\t// 50% chance to slightly alter the index\n\t\t\t\tif (chance.bool({ likelihood: 50 })) {\n\t\t\t\t\tweighted.push(items[mostChosenIndex]);\n\t\t\t\t} else {\n\t\t\t\t\tconst addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;\n\t\t\t\t\tlet newIndex = mostChosenIndex + addOrSubtract;\n\n\t\t\t\t\t// Ensure newIndex is within bounds\n\t\t\t\t\tif (newIndex < 0) newIndex = 0;\n\t\t\t\t\tif (newIndex >= items.length) newIndex = items.length - 1;\n\t\t\t\t\tweighted.push(items[newIndex]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 25% chance to favor the second most chosen index\n\t\t\telse if (chance.bool({ likelihood: 25 })) {\n\t\t\t\tweighted.push(items[secondMostChosenIndex]);\n\t\t\t}\n\t\t\t// 15% chance to favor the third most chosen index\n\t\t\telse if (chance.bool({ likelihood: 15 })) {\n\t\t\t\tweighted.push(items[thirdMostChosenIndex]);\n\t\t\t}\n\t\t\t// Otherwise, pick a random item from the list\n\t\t\telse {\n\t\t\t\tweighted.push(chance.pickone(items));\n\t\t\t}\n\t\t}\n\t\treturn weighted;\n\t}"
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
"userProps": {
|
|
86
|
-
"spiritAnimal": [
|
|
87
|
-
"duck",
|
|
88
|
-
"dog",
|
|
89
|
-
"otter",
|
|
90
|
-
"penguin",
|
|
91
|
-
"cat",
|
|
92
|
-
"elephant",
|
|
93
|
-
"lion",
|
|
94
|
-
"cheetah",
|
|
95
|
-
"giraffe",
|
|
96
|
-
"zebra",
|
|
97
|
-
"rhino",
|
|
98
|
-
"hippo",
|
|
99
|
-
"whale",
|
|
100
|
-
"dolphin",
|
|
101
|
-
"shark",
|
|
102
|
-
"octopus",
|
|
103
|
-
"squid",
|
|
104
|
-
"jellyfish",
|
|
105
|
-
"starfish",
|
|
106
|
-
"seahorse",
|
|
107
|
-
"crab",
|
|
108
|
-
"lobster",
|
|
109
|
-
"shrimp",
|
|
110
|
-
"clam",
|
|
111
|
-
"snail",
|
|
112
|
-
"slug",
|
|
113
|
-
"butterfly",
|
|
114
|
-
"moth",
|
|
115
|
-
"bee",
|
|
116
|
-
"wasp",
|
|
117
|
-
"ant",
|
|
118
|
-
"beetle",
|
|
119
|
-
"ladybug",
|
|
120
|
-
"caterpillar",
|
|
121
|
-
"centipede",
|
|
122
|
-
"millipede",
|
|
123
|
-
"scorpion",
|
|
124
|
-
"spider",
|
|
125
|
-
"tarantula",
|
|
126
|
-
"tick",
|
|
127
|
-
"mite",
|
|
128
|
-
"mosquito",
|
|
129
|
-
"fly",
|
|
130
|
-
"dragonfly",
|
|
131
|
-
"damselfly",
|
|
132
|
-
"grasshopper",
|
|
133
|
-
"cricket",
|
|
134
|
-
"locust",
|
|
135
|
-
"mantis",
|
|
136
|
-
"cockroach",
|
|
137
|
-
"termite",
|
|
138
|
-
"praying mantis",
|
|
139
|
-
"walking stick",
|
|
140
|
-
"stick bug",
|
|
141
|
-
"leaf insect",
|
|
142
|
-
"lacewing",
|
|
143
|
-
"aphid",
|
|
144
|
-
"cicada",
|
|
145
|
-
"thrips",
|
|
146
|
-
"psyllid",
|
|
147
|
-
"scale insect",
|
|
148
|
-
"whitefly",
|
|
149
|
-
"mealybug",
|
|
150
|
-
"planthopper",
|
|
151
|
-
"leafhopper",
|
|
152
|
-
"treehopper",
|
|
153
|
-
"flea",
|
|
154
|
-
"louse",
|
|
155
|
-
"bedbug",
|
|
156
|
-
"flea beetle",
|
|
157
|
-
"weevil",
|
|
158
|
-
"longhorn beetle",
|
|
159
|
-
"leaf beetle",
|
|
160
|
-
"tiger beetle",
|
|
161
|
-
"ground beetle",
|
|
162
|
-
"lady beetle",
|
|
163
|
-
"firefly",
|
|
164
|
-
"click beetle",
|
|
165
|
-
"rove beetle",
|
|
166
|
-
"scarab beetle",
|
|
167
|
-
"dung beetle",
|
|
168
|
-
"stag beetle",
|
|
169
|
-
"rhinoceros beetle",
|
|
170
|
-
"hercules beetle",
|
|
171
|
-
"goliath beetle",
|
|
172
|
-
"jewel beetle",
|
|
173
|
-
"tortoise beetle"
|
|
174
|
-
]
|
|
175
|
-
},
|
|
176
|
-
"scdProps": {},
|
|
177
|
-
"mirrorProps": {},
|
|
178
|
-
"groupKeys": [],
|
|
179
|
-
"groupProps": {},
|
|
180
|
-
"lookupTables": [
|
|
181
|
-
{
|
|
182
|
-
"key": "product_id",
|
|
183
|
-
"entries": 1000,
|
|
184
|
-
"attributes": {
|
|
185
|
-
"amount": [
|
|
186
|
-
259,
|
|
187
|
-
247,
|
|
188
|
-
271,
|
|
189
|
-
269,
|
|
190
|
-
209,
|
|
191
|
-
305,
|
|
192
|
-
706,
|
|
193
|
-
344,
|
|
194
|
-
350,
|
|
195
|
-
327,
|
|
196
|
-
259,
|
|
197
|
-
225,
|
|
198
|
-
269,
|
|
199
|
-
267,
|
|
200
|
-
256,
|
|
201
|
-
705,
|
|
202
|
-
196,
|
|
203
|
-
588,
|
|
204
|
-
775,
|
|
205
|
-
235,
|
|
206
|
-
737,
|
|
207
|
-
341,
|
|
208
|
-
268,
|
|
209
|
-
409,
|
|
210
|
-
233,
|
|
211
|
-
246,
|
|
212
|
-
239,
|
|
213
|
-
737,
|
|
214
|
-
701,
|
|
215
|
-
741,
|
|
216
|
-
227,
|
|
217
|
-
230,
|
|
218
|
-
693,
|
|
219
|
-
719,
|
|
220
|
-
277,
|
|
221
|
-
706,
|
|
222
|
-
292,
|
|
223
|
-
274,
|
|
224
|
-
596,
|
|
225
|
-
263,
|
|
226
|
-
250,
|
|
227
|
-
279,
|
|
228
|
-
275,
|
|
229
|
-
799,
|
|
230
|
-
236,
|
|
231
|
-
267,
|
|
232
|
-
221,
|
|
233
|
-
230,
|
|
234
|
-
730,
|
|
235
|
-
789
|
|
236
|
-
],
|
|
237
|
-
"quantity": [
|
|
238
|
-
4,
|
|
239
|
-
5,
|
|
240
|
-
7,
|
|
241
|
-
4,
|
|
242
|
-
8,
|
|
243
|
-
6,
|
|
244
|
-
3,
|
|
245
|
-
3,
|
|
246
|
-
4,
|
|
247
|
-
6,
|
|
248
|
-
3,
|
|
249
|
-
3,
|
|
250
|
-
8,
|
|
251
|
-
3,
|
|
252
|
-
3,
|
|
253
|
-
8,
|
|
254
|
-
3,
|
|
255
|
-
8,
|
|
256
|
-
7,
|
|
257
|
-
4,
|
|
258
|
-
8,
|
|
259
|
-
4,
|
|
260
|
-
4,
|
|
261
|
-
4,
|
|
262
|
-
4,
|
|
263
|
-
3,
|
|
264
|
-
5,
|
|
265
|
-
4,
|
|
266
|
-
3,
|
|
267
|
-
3,
|
|
268
|
-
3,
|
|
269
|
-
7,
|
|
270
|
-
3,
|
|
271
|
-
3,
|
|
272
|
-
4,
|
|
273
|
-
4,
|
|
274
|
-
6,
|
|
275
|
-
4,
|
|
276
|
-
7,
|
|
277
|
-
7,
|
|
278
|
-
4,
|
|
279
|
-
3,
|
|
280
|
-
3,
|
|
281
|
-
3,
|
|
282
|
-
3,
|
|
283
|
-
4,
|
|
284
|
-
8,
|
|
285
|
-
3,
|
|
286
|
-
3,
|
|
287
|
-
3
|
|
288
|
-
],
|
|
289
|
-
"featured": {
|
|
290
|
-
"functionName": "arrow",
|
|
291
|
-
"body": "function flip(likelihood = 50) {\n\treturn chance.bool({ likelihood });\n}"
|
|
292
|
-
},
|
|
293
|
-
"category": [
|
|
294
|
-
"electronics",
|
|
295
|
-
"books",
|
|
296
|
-
"clothing",
|
|
297
|
-
"home",
|
|
298
|
-
"garden",
|
|
299
|
-
"toys",
|
|
300
|
-
"sports",
|
|
301
|
-
"automotive",
|
|
302
|
-
"beauty",
|
|
303
|
-
"health",
|
|
304
|
-
"grocery",
|
|
305
|
-
"jewelry",
|
|
306
|
-
"shoes",
|
|
307
|
-
"tools",
|
|
308
|
-
"office supplies"
|
|
309
|
-
],
|
|
310
|
-
"descriptor": [
|
|
311
|
-
"brand new",
|
|
312
|
-
"open box",
|
|
313
|
-
"refurbished",
|
|
314
|
-
"used",
|
|
315
|
-
"like new",
|
|
316
|
-
"vintage",
|
|
317
|
-
"antique",
|
|
318
|
-
"collectible"
|
|
319
|
-
]
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
]
|
|
323
|
-
},
|
|
324
|
-
"hooks": "function (record, type, meta) {\n\n\t\tconst NOW = dayjs();\n\n\t\tif (type === \"event\") {\n\t\t\t// Pattern 1: Checkouts with coupons get a discount_applied flag and adjusted total\n\t\t\tif (record.event === \"checkout\" && record.coupon && record.coupon !== \"none\") {\n\t\t\t\trecord.discount_applied = true;\n\t\t\t\tconst pctMatch = record.coupon.match(/(\\d+)%/);\n\t\t\t\tif (pctMatch) {\n\t\t\t\t\trecord.discount_percent = parseInt(pctMatch[1]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Pattern 2: \"save item\" events on weekends are tagged as wishlist behavior\n\t\t\tif (record.event === \"save item\") {\n\t\t\t\tconst dow = dayjs(record.time).day();\n\t\t\t\tif (dow === 0 || dow === 6) {\n\t\t\t\t\trecord.save_context = \"weekend_browse\";\n\t\t\t\t} else {\n\t\t\t\t\trecord.save_context = \"weekday_intent\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (type === \"everything\") {\n\t\t\t// Pattern 3: Users who view 5+ items but never checkout are tagged as window shoppers\n\t\t\tconst views = record.filter(e => e.event === \"view item\").length;\n\t\t\tconst checkouts = record.filter(e => e.event === \"checkout\").length;\n\t\t\tif (views >= 5 && checkouts === 0) {\n\t\t\t\tfor (const e of record) {\n\t\t\t\t\te.user_segment = \"window_shopper\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn record;\n\t}",
|
|
325
|
-
"timestamp": "2026-04-10T01:39:06.554Z",
|
|
326
|
-
"version": "4.0"
|
|
327
|
-
}
|