make-mp-data 2.0.19 → 2.0.22
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/big.js +7 -6
- package/dungeons/business.js +21 -3
- package/dungeons/experiments.js +8 -7
- package/dungeons/media.js +7 -7
- package/dungeons/sanity.js +8 -14
- package/dungeons/simple.js +1 -0
- package/dungeons/student-teacher.js +426 -0
- package/dungeons/userAgent.js +7 -7
- package/entry.js +19 -3
- package/index.js +107 -7
- package/lib/cli/cli.js +8 -0
- package/lib/core/config-validator.js +244 -218
- package/lib/core/context.js +31 -16
- package/lib/core/storage.js +61 -27
- package/lib/generators/events.js +41 -18
- package/lib/orchestrators/mixpanel-sender.js +5 -2
- package/lib/orchestrators/user-loop.js +212 -181
- package/lib/orchestrators/worker-manager.js +5 -2
- package/lib/templates/abbreviated.d.ts +159 -0
- package/lib/{data → templates}/defaults.js +2 -2
- package/lib/templates/instructions.txt +78 -0
- package/lib/templates/scratch-dungeon-template.js +116 -0
- package/lib/templates/verbose-schema.js +338 -0
- package/lib/utils/ai.js +42 -64
- package/lib/utils/chart.js +5 -0
- package/lib/utils/utils.js +116 -55
- package/package.json +9 -10
- package/types.d.ts +138 -125
- package/lib/cloud-function.js +0 -20
- /package/lib/{utils/prompt.txt → templates/prompt (old).txt} +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
You are an AI assistant that generates a structured JavaScript object ("Dungeon") that defines a synthetic analytics dataset designed to simulate realistic user behavior, based on a prompt describing a business or product use case.
|
|
2
|
+
|
|
3
|
+
the spec you are building is a javascript object... the high level spec looks like this:
|
|
4
|
+
|
|
5
|
+
--------------
|
|
6
|
+
|
|
7
|
+
const my_dungeon = {
|
|
8
|
+
funnels: [],
|
|
9
|
+
events: [],
|
|
10
|
+
superProps: {},
|
|
11
|
+
userProps: {},
|
|
12
|
+
scdProps: {},
|
|
13
|
+
groupKeys: [],
|
|
14
|
+
groupProps: {},
|
|
15
|
+
lookupTables: [],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
--------------
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
here's the full typescript definition for each of the properties in the entire spec; we call the specs 'DUNGEONS' and each dungeon is a javascript object:
|
|
22
|
+
|
|
23
|
+
--------------
|
|
24
|
+
//types.d.ts
|
|
25
|
+
|
|
26
|
+
<TYPES>
|
|
27
|
+
|
|
28
|
+
--------------
|
|
29
|
+
|
|
30
|
+
here is now an implementation of the dungeon spec which shows how these above features work together to create a data set that tells a story!
|
|
31
|
+
|
|
32
|
+
Pay close attention to how function calls like weighNumRange(...) and chance.company() are written directly into the object without quotes.
|
|
33
|
+
|
|
34
|
+
Your output must follow this exact format:
|
|
35
|
+
|
|
36
|
+
--------------
|
|
37
|
+
|
|
38
|
+
//my-dungeon.js
|
|
39
|
+
/** @type {import('../../types.js').Dungeon} */
|
|
40
|
+
|
|
41
|
+
<VERBOSE_SCHEMA>
|
|
42
|
+
|
|
43
|
+
--------------
|
|
44
|
+
|
|
45
|
+
Final Instructions & Rules:
|
|
46
|
+
|
|
47
|
+
Your job is to build a dungeon based on the analytics use case described in the prompt. Your only output will be a single, valid JavaScript object literal.
|
|
48
|
+
|
|
49
|
+
Core Requirements:
|
|
50
|
+
|
|
51
|
+
1. Output Format: Your output must be a valid JavaScript object literal, NOT JSON. This is crucial. Your entire response should start with { and end with } and contain nothing else.
|
|
52
|
+
|
|
53
|
+
2. Mandatory Fields: You MUST create funnels, events, superProps, and userProps for every dungeon. They should be directly related to the analytics use cases stated in the prompt.
|
|
54
|
+
|
|
55
|
+
3. Optional Fields: You should include scdProps, groupKeys, groupProps, and lookupTables only if they are clearly relevant based on the use case prompt.
|
|
56
|
+
- Use groups if the prompt involves B2B, teams, companies, or organizations.
|
|
57
|
+
- Use SCD if user or group traits change over time (e.g., subscription tier).
|
|
58
|
+
- Use lookup tables if events reference external entities with their own attributes (e.g., product_id, video_id).
|
|
59
|
+
- Use funnel conditions when different user segments or cohorts should have different behavioral patterns (e.g., premium vs free users, students vs teachers, rider vs driver, doctor vs patient).
|
|
60
|
+
|
|
61
|
+
4. Available Functions: You have access to these built-in functions: date, weighNumRange, range, and the chance library.
|
|
62
|
+
|
|
63
|
+
❌ Critical Rules to Follow:
|
|
64
|
+
|
|
65
|
+
- Do NOT wrap function calls in quotes.
|
|
66
|
+
|
|
67
|
+
Correct: amount: weighNumRange(5, 500)
|
|
68
|
+
Incorrect: amount: "weighNumRange(5, 500)"
|
|
69
|
+
|
|
70
|
+
- Do NOT output comments, explanations, or any text outside of the JavaScript object.
|
|
71
|
+
|
|
72
|
+
- Do NOT generate vague or placeholder values like "value1", "example", or "random_string".
|
|
73
|
+
|
|
74
|
+
- Do NOT leave required arrays empty (funnels, events, etc.).
|
|
75
|
+
|
|
76
|
+
Your only task is to generate a single JavaScript object that follows this structure based on the user's request.
|
|
77
|
+
=================
|
|
78
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* this file is NOT currently used
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import dayjs from "dayjs";
|
|
6
|
+
import utc from "dayjs/plugin/utc.js";
|
|
7
|
+
dayjs.extend(utc);
|
|
8
|
+
import "dotenv/config";
|
|
9
|
+
import { weighNumRange, range, date, initChance, exhaust, choose, integer, decimal, odds } from "../utils/utils.js";
|
|
10
|
+
const { NODE_ENV = "unknown" } = process.env;
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Dungeon template for AI-generated configurations
|
|
16
|
+
* This template provides the structure and standard configuration
|
|
17
|
+
* AI-generated content will be injected into the placeholders
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export function createDungeonTemplate({
|
|
21
|
+
seed = "ai-generated-seed",
|
|
22
|
+
numUsers = 1000,
|
|
23
|
+
numDays = 100,
|
|
24
|
+
events = [],
|
|
25
|
+
funnels = [],
|
|
26
|
+
superProps = {},
|
|
27
|
+
userProps = {},
|
|
28
|
+
scdProps = {},
|
|
29
|
+
mirrorProps = {},
|
|
30
|
+
groupKeys = [],
|
|
31
|
+
groupProps = {},
|
|
32
|
+
lookupTables = [],
|
|
33
|
+
hookFunction = null
|
|
34
|
+
}) {
|
|
35
|
+
const chance = u.initChance(seed);
|
|
36
|
+
const numEvents = numUsers * 100;
|
|
37
|
+
|
|
38
|
+
/** @typedef {import("../../types.js").Dungeon} Config */
|
|
39
|
+
|
|
40
|
+
/** @type {Config} */
|
|
41
|
+
const config = {
|
|
42
|
+
token: "",
|
|
43
|
+
seed: seed,
|
|
44
|
+
numDays: numDays,
|
|
45
|
+
numEvents: numEvents,
|
|
46
|
+
numUsers: numUsers,
|
|
47
|
+
hasAnonIds: false,
|
|
48
|
+
hasSessionIds: false,
|
|
49
|
+
format: "json",
|
|
50
|
+
alsoInferFunnels: true,
|
|
51
|
+
hasLocation: true,
|
|
52
|
+
hasAndroidDevices: true,
|
|
53
|
+
hasIOSDevices: true,
|
|
54
|
+
hasDesktopDevices: true,
|
|
55
|
+
hasBrowser: true,
|
|
56
|
+
hasCampaigns: true,
|
|
57
|
+
isAnonymous: false,
|
|
58
|
+
hasAdSpend: true,
|
|
59
|
+
|
|
60
|
+
hasAvatar: true,
|
|
61
|
+
makeChart: false,
|
|
62
|
+
|
|
63
|
+
batchSize: 1_500_000,
|
|
64
|
+
concurrency: 1,
|
|
65
|
+
writeToDisk: false,
|
|
66
|
+
|
|
67
|
+
// AI-generated content will be injected here
|
|
68
|
+
funnels: funnels,
|
|
69
|
+
events: events,
|
|
70
|
+
superProps: superProps,
|
|
71
|
+
userProps: userProps,
|
|
72
|
+
scdProps: scdProps,
|
|
73
|
+
mirrorProps: mirrorProps,
|
|
74
|
+
groupKeys: groupKeys,
|
|
75
|
+
groupProps: groupProps,
|
|
76
|
+
lookupTables: lookupTables,
|
|
77
|
+
|
|
78
|
+
hook: hookFunction || function (record, type, meta) {
|
|
79
|
+
// const NOW = dayjs();
|
|
80
|
+
|
|
81
|
+
// CALLED AFTER EACH EVENT IS CREATED
|
|
82
|
+
if (type === "event") {
|
|
83
|
+
// const EVENT_TIME = dayjs(record.time);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// CALLED ONCE FOR EACH USER AND DEFINES THEIR PROPS
|
|
87
|
+
if (type === "user") {
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// CALLED AFTER FUNNELS EVENTS ARE CHOSEN
|
|
92
|
+
if (type === "funnel-post") {
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// CALLED BEFORE FUNNELS EVENTS ARE CHOSEN
|
|
97
|
+
if (type === "funnel-pre") {
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// CALLED BEFORE SCD EVENTS ARE CHOSEN
|
|
102
|
+
if (type === "scd-pre") {
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// CALLED AT THE END WHEN A USERS ENTIRE HISTORY EXISTS
|
|
107
|
+
if (type === "everything") {
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return record;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return config;
|
|
116
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview this is a highly verbose schema for a dungeon that shows all the options available
|
|
3
|
+
* and how they might be implemented with extensive comments so an AI can understand it
|
|
4
|
+
* it is not meant to be used as a template, but rather as a reference for how to create a dungeon
|
|
5
|
+
* it is also used as a test for the AI to see if it can generate a dungeon with the same structure
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
import Chance from "chance";
|
|
11
|
+
const chance = new Chance();
|
|
12
|
+
import dayjs from "dayjs";
|
|
13
|
+
import utc from "dayjs/plugin/utc.js";
|
|
14
|
+
dayjs.extend(utc);
|
|
15
|
+
import "dotenv/config";
|
|
16
|
+
import { weighNumRange, range, date, initChance, exhaust, choose, integer, decimal, odds } from "../../lib/utils/utils.js";
|
|
17
|
+
const { NODE_ENV = "unknown" } = process.env;
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
/** @type {import("../../types.js").Dungeon} */
|
|
21
|
+
//SPLIT HERE
|
|
22
|
+
const DUNGEON = {
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* events are the core building blocks of the dungeon
|
|
26
|
+
* each event has a name, a weight, and properties
|
|
27
|
+
* the weight determines how often the event occurs relative to other events
|
|
28
|
+
* properties are the data associated with the event
|
|
29
|
+
* they can be simple values or functions that return a value
|
|
30
|
+
* we have a few built-in functions to help you generate data
|
|
31
|
+
* you MUST create events for every dungeon
|
|
32
|
+
*/
|
|
33
|
+
events: [
|
|
34
|
+
{
|
|
35
|
+
event: "checkout",
|
|
36
|
+
weight: 2,
|
|
37
|
+
properties: {
|
|
38
|
+
amount: weighNumRange(5, 500, .25), // this is ok if you need to pick a number from a range; params are min, max, skew (opt), and size (size of pool, also opt)
|
|
39
|
+
currency: ["USD", "CAD", "EUR", "BTC", "ETH", "JPY"],
|
|
40
|
+
coupon: ["none", "none", "none", "none", "10%OFF", "20%OFF", "10%OFF", "20%OFF", "30%OFF", "40%OFF", "50%OFF"],
|
|
41
|
+
numItems: weighNumRange(1, 10),
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
event: "add to cart",
|
|
47
|
+
weight: 4,
|
|
48
|
+
properties: {
|
|
49
|
+
amount: weighNumRange(5, 500, .25),
|
|
50
|
+
rating: weighNumRange(1, 5),
|
|
51
|
+
reviews: weighNumRange(0, 35),
|
|
52
|
+
isFeaturedItem: [true, false, false],
|
|
53
|
+
itemCategory: ["Books", "Movies", "Music", "Games", "Electronics", "Computers", "Smart Home", "Home", "Garden", "Pet", "Beauty", "Health", "Toys", "Kids", "Baby", "Handmade", "Sports", "Outdoors", "Automotive", "Industrial", "Entertainment", "Art", "Food", "Appliances", "Office", "Wedding", "Software"],
|
|
54
|
+
dateItemListed: date(30, true, "YYYY-MM-DD") // this is ok if you need to pick a date from a range; params are intheLastDays, isPast, format
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
event: "page view",
|
|
59
|
+
weight: 10,
|
|
60
|
+
properties: {
|
|
61
|
+
page: ["/", "/", "/help", "/account", "/watch", "/listen", "/product", "/people", "/peace"],
|
|
62
|
+
utm_source: ["$organic", "$organic", "$organic", "$organic", "google", "google", "google", "facebook", "facebook", "twitter", "linkedin"],
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
event: "watch video",
|
|
67
|
+
weight: 8,
|
|
68
|
+
properties: {
|
|
69
|
+
videoCategory: ["funny", "educational", "inspirational", "music", "news", "sports", "cooking", "DIY", "travel", "gaming"],
|
|
70
|
+
isFeaturedItem: [true, false, false],
|
|
71
|
+
watchTimeSec: weighNumRange(10, 600, .25),
|
|
72
|
+
quality: ["2160p", "1440p", "1080p", "720p", "480p", "360p", "240p"],
|
|
73
|
+
format: ["mp4", "avi", "mov", "mpg"],
|
|
74
|
+
uploader_id: chance.guid.bind(chance), // You have access to the chance.js library for generating random data. You can assign a chance function directly to a property, like name: chance.company or uploader_id: chance.guid.
|
|
75
|
+
video_id: range(1, 1000) // SEE LOOKUP TABLES BELOW
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
event: "view item",
|
|
81
|
+
weight: 8,
|
|
82
|
+
properties: {
|
|
83
|
+
isFeaturedItem: [true, false, false],
|
|
84
|
+
itemCategory: ["Books", "Movies", "Music", "Games", "Electronics", "Computers", "Smart Home", "Home", "Garden", "Pet", "Beauty", "Health", "Toys", "Kids", "Baby", "Handmade", "Sports", "Outdoors", "Automotive", "Industrial", "Entertainment", "Art", "Food", "Appliances", "Office", "Wedding", "Software"],
|
|
85
|
+
dateItemListed: date(30, true, "YYYY-MM-DD"),
|
|
86
|
+
product_id: range(1, 1000) // SEE LOOKUP TABLES BELOW
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
event: "save item",
|
|
91
|
+
weight: 5,
|
|
92
|
+
properties: {
|
|
93
|
+
isFeaturedItem: [true, false, false],
|
|
94
|
+
itemCategory: ["Books", "Movies", "Music", "Games", "Electronics", "Computers", "Smart Home", "Home", "Garden", "Pet", "Beauty", "Health", "Toys", "Kids", "Baby", "Handmade", "Sports", "Outdoors", "Automotive", "Industrial", "Entertainment", "Art", "Food", "Appliances", "Office", "Wedding", "Software"],
|
|
95
|
+
dateItemListed: date(30, true, "YYYY-MM-DD"),
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
event: "sign up",
|
|
100
|
+
isFirstEvent: true,
|
|
101
|
+
weight: 0,
|
|
102
|
+
properties: {
|
|
103
|
+
variants: ["A", "B", "C", "Control"],
|
|
104
|
+
flows: ["new", "existing", "loyal", "churned"],
|
|
105
|
+
flags: ["on", "off"],
|
|
106
|
+
experiment_ids: ["1234", "5678", "9012", "3456", "7890"],
|
|
107
|
+
multiVariate: [true, false]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
/**
|
|
112
|
+
* superProps are properties that are attached to every event
|
|
113
|
+
* they are selected randomly each time an event is generated
|
|
114
|
+
* they can be simple values or functions that return a value
|
|
115
|
+
* you will LIKELY create superProps for every dungeon
|
|
116
|
+
*/
|
|
117
|
+
superProps: {
|
|
118
|
+
theme: ["light", "dark", "custom", "light", "dark"],
|
|
119
|
+
},
|
|
120
|
+
/**
|
|
121
|
+
* user properties work the same as event properties during the generation phase
|
|
122
|
+
* except each user has a single set of user properties
|
|
123
|
+
* these are properties that are attached to the user profile
|
|
124
|
+
* they can be simple values or functions that return a value
|
|
125
|
+
* you MUST create userProps for every dungeon
|
|
126
|
+
*/
|
|
127
|
+
userProps: {
|
|
128
|
+
title: ["Mr.", "Ms.", "Mrs.", "Dr.", "Prof.", "Sir", "Madam", "Lord", "Lady", "Dame", "Baron", "Baroness", "Count", "Countess", "Viscount", "Viscountess", "Marquis", "Marchioness"],
|
|
129
|
+
role: ["basic", "basic", "basic", "premium", "admin"], // role property that can be used in funnel conditions
|
|
130
|
+
luckyNumber: weighNumRange(42, 420, .3),
|
|
131
|
+
spiritAnimal: ["duck", "dog", "otter", "penguin", "cat", "elephant", "lion", "cheetah", "giraffe", "zebra", "rhino", "hippo", "whale", "dolphin", "shark", "octopus", "squid", "jellyfish", "starfish", "seahorse", "crab", "lobster", "shrimp", "clam", "snail", "slug", "butterfly", "moth", "bee", "wasp", "ant", "beetle", "ladybug", "caterpillar", "centipede", "millipede", "scorpion", "spider", "tarantula", "tick", "mite", "mosquito", "fly", "dragonfly", "damselfly", "grasshopper", "cricket", "locust", "mantis", "cockroach", "termite", "praying mantis", "walking stick", "stick bug", "leaf insect", "lacewing", "aphid", "cicada", "thrips", "psyllid", "scale insect", "whitefly", "mealybug", "planthopper", "leafhopper", "treehopper", "flea", "louse", "bedbug", "flea beetle", "weevil", "longhorn beetle", "leaf beetle", "tiger beetle", "ground beetle", "lady beetle", "firefly", "click beetle", "rove beetle", "scarab beetle", "dung beetle", "stag beetle", "rhinoceus beetle", "hercules beetle", "goliath beetle", "jewel beetle", "tortoise beetle"]
|
|
132
|
+
},
|
|
133
|
+
/**
|
|
134
|
+
* Funnels represent intentional user journeys (e.g., sign-up, checkout),
|
|
135
|
+
* composed of a sequence of events.
|
|
136
|
+
* You should design at least one first-time funnel (e.g., onboarding)
|
|
137
|
+
* and one recurring funnel (e.g., purchases or content browsing)
|
|
138
|
+
* unless otherwise specified in the prompt.
|
|
139
|
+
* Funnels are the primary mechanism used to generate the example data, and it's critical sequences match events in the events array.
|
|
140
|
+
* there are many different options for the funnels like:
|
|
141
|
+
* isFirstFunnel, conversionRate, isChurnFunnel, order, props, requireRepeats, timeToConvert, weight, conditions
|
|
142
|
+
*
|
|
143
|
+
* isFirstFunnel are funnels a user will only go through once (like a sign up)
|
|
144
|
+
* non isFirstFunnel are funnels a user will go through multiple times (like a purchase)
|
|
145
|
+
*
|
|
146
|
+
* conditions are used to filter which users are eligible for a specific funnel based on their user properties
|
|
147
|
+
* this is useful when different user segments should have different behavioral patterns
|
|
148
|
+
* for example: premium users might have access to advanced features, students vs teachers have different workflows
|
|
149
|
+
*
|
|
150
|
+
*/
|
|
151
|
+
funnels: [
|
|
152
|
+
{
|
|
153
|
+
name: "sign up funnel",
|
|
154
|
+
description: "journey for users signing up",
|
|
155
|
+
sequence: ["page view", "page view", "sign up"],
|
|
156
|
+
isFirstFunnel: true,
|
|
157
|
+
conversionRate: 50, // 50% of users will convert
|
|
158
|
+
order: "sequential", // events must occur in order
|
|
159
|
+
requireRepeats: false, // users can repeat events in the funnel
|
|
160
|
+
props: {}, // you can add properties to the funnel
|
|
161
|
+
timeToConvert: 1, // time to convert in hours
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "purchase funnel",
|
|
165
|
+
description: "how users purchase items",
|
|
166
|
+
sequence: ["page view", "view item", "add to cart", "checkout"],
|
|
167
|
+
isFirstFunnel: false,
|
|
168
|
+
conversionRate: 10,
|
|
169
|
+
timeToConvert: 24,
|
|
170
|
+
requireRepeats: true, // users can repeat events in the funnel
|
|
171
|
+
order: "first-and-last-fixed"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "browsing funnel",
|
|
175
|
+
description: "how users browse items",
|
|
176
|
+
sequence: ["page view", "view item", "watch video", "save item"],
|
|
177
|
+
isFirstFunnel: false,
|
|
178
|
+
conversionRate: 65,
|
|
179
|
+
timeToConvert: 2,
|
|
180
|
+
requireRepeats: true, // users can repeat events in the funnel
|
|
181
|
+
order: "random",
|
|
182
|
+
props: {
|
|
183
|
+
"browsing type": ["casual", "intentional", "exploratory"] // you can add properties to the funnel
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "premium user workflow",
|
|
188
|
+
description: "advanced features only available to premium users",
|
|
189
|
+
sequence: ["page view", "view item", "save item", "checkout"],
|
|
190
|
+
isFirstFunnel: false,
|
|
191
|
+
conversionRate: 85,
|
|
192
|
+
timeToConvert: 3,
|
|
193
|
+
conditions: {
|
|
194
|
+
role: "premium" // only users with role "premium" are eligible for this funnel
|
|
195
|
+
},
|
|
196
|
+
order: "sequential",
|
|
197
|
+
props: {
|
|
198
|
+
"feature_tier": "premium"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
],
|
|
202
|
+
/**
|
|
203
|
+
* scdProps are used for to represent slowly changing dimensions (at both a user and group level)
|
|
204
|
+
* these are properties that change over time, but not frequently
|
|
205
|
+
* and importantly we care about behaviors (events) during different epochs of SCD state
|
|
206
|
+
* you do NOT need to make SCD properties for every dungeon; only the ones with a use case for them
|
|
207
|
+
*/
|
|
208
|
+
scdProps: {
|
|
209
|
+
role: {
|
|
210
|
+
type: "user",
|
|
211
|
+
frequency: "week",
|
|
212
|
+
values: ["admin", "collaborator", "user", "view only", "no access"],
|
|
213
|
+
timing: "fuzzy",
|
|
214
|
+
max: 10
|
|
215
|
+
},
|
|
216
|
+
NPS: {
|
|
217
|
+
type: "user",
|
|
218
|
+
frequency: "day",
|
|
219
|
+
values: weighNumRange(1, 10, 2, 150),
|
|
220
|
+
timing: "fuzzy",
|
|
221
|
+
max: 10
|
|
222
|
+
},
|
|
223
|
+
MRR: {
|
|
224
|
+
type: "company_id",
|
|
225
|
+
frequency: "month",
|
|
226
|
+
values: weighNumRange(0, 10000, .15),
|
|
227
|
+
timing: "fixed",
|
|
228
|
+
max: 10
|
|
229
|
+
},
|
|
230
|
+
AccountHealthScore: {
|
|
231
|
+
type: "company_id",
|
|
232
|
+
frequency: "week",
|
|
233
|
+
values: weighNumRange(1, 10, .15),
|
|
234
|
+
timing: "fixed",
|
|
235
|
+
max: 40
|
|
236
|
+
},
|
|
237
|
+
plan: {
|
|
238
|
+
type: "company_id",
|
|
239
|
+
frequency: "month",
|
|
240
|
+
values: ["free", "basic", "premium", "enterprise"],
|
|
241
|
+
timing: "fixed",
|
|
242
|
+
max: 10
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* groupKeys are used to represent entities for custom uniqueBy analysis
|
|
249
|
+
* they are an array of arrays, where each inner array is a pair of group_key and the number of profiles for that key
|
|
250
|
+
* this is useful for group analysis, where you want to analyze data by groups of users (by company, institution, school, etc...)
|
|
251
|
+
* groups have props too (they can also have events) ... but there can be more than one "group entity"
|
|
252
|
+
* you do NOT need to make groupKeys for every dungeon; only the ones with a use case for them
|
|
253
|
+
*/
|
|
254
|
+
groupKeys: [
|
|
255
|
+
["company_id", 500, []],
|
|
256
|
+
["room_id", 10000, ["save video", "comment", "watch video"]]
|
|
257
|
+
],
|
|
258
|
+
groupProps: {
|
|
259
|
+
company_id: {
|
|
260
|
+
name: () => { return chance.company(); }, // YOU CAN USE CHANCE IT"S BUILT IN!
|
|
261
|
+
email: () => { return `CSM: ${chance.pickone(["AK", "Jessica", "Michelle", "Dana", "Brian", "Dave"])}`; },
|
|
262
|
+
"# of employees": weighNumRange(3, 10000),
|
|
263
|
+
"industry": ["tech", "finance", "healthcare", "education", "government", "non-profit"],
|
|
264
|
+
"segment": ["enterprise", "SMB", "mid-market"],
|
|
265
|
+
"products": [["core"], ["core"], ["core", "add-ons"], ["core", "pro-serve"], ["core", "add-ons", "pro-serve"], ["core", "BAA", "enterprise"], ["free"], ["free"], ["free", "addons"]],
|
|
266
|
+
},
|
|
267
|
+
room_id: {
|
|
268
|
+
name: () => { return `#${chance.word({ length: integer(4, 24), capitalize: true })}`; },
|
|
269
|
+
email: ["public", "private"],
|
|
270
|
+
"room provider": ["partner", "core", "core", "core"],
|
|
271
|
+
"room capacity": weighNumRange(3, 1000000),
|
|
272
|
+
"isPublic": [true, false, false, false, false],
|
|
273
|
+
"country": chance.country.bind(chance),
|
|
274
|
+
"isVerified": [true, true, false, false, false],
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* groupEvents are used to represent events that are associated with a group that occur
|
|
280
|
+
* regularly (like a card charged, or a subscription renewed)
|
|
281
|
+
* they are generally NOT attributed to a user, but rather to a group entity
|
|
282
|
+
* you do NOT need to make groupEvents for every dungeon; only the ones with a use case for them
|
|
283
|
+
*/
|
|
284
|
+
groupEvents: [
|
|
285
|
+
{
|
|
286
|
+
attribute_to_user: false,
|
|
287
|
+
event: "card charged",
|
|
288
|
+
weight: 1,
|
|
289
|
+
|
|
290
|
+
frequency: 30,
|
|
291
|
+
group_key: "company_id",
|
|
292
|
+
group_size: 500,
|
|
293
|
+
properties: {
|
|
294
|
+
amount: weighNumRange(5, 500, .25),
|
|
295
|
+
currency: ["USD", "USD", "USD", "CAD", "EUR", "EUR", "BTC", "BTC", "ETH", "JPY"],
|
|
296
|
+
plan: ["basic", "premium", "enterprise"],
|
|
297
|
+
"payment method": []
|
|
298
|
+
}
|
|
299
|
+
}],
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* lookupTables are used to represent static data that can be used in events
|
|
303
|
+
* they are an array of objects, where each object is a key-value pair
|
|
304
|
+
* this is useful for representing static data that can be used in events
|
|
305
|
+
* like product information, user information, etc...
|
|
306
|
+
* you do NOT need to make lookupTables for every dungeon; only the ones with a use case for them
|
|
307
|
+
* if you DO make a lookupTable, you MUST provide a key in some events whose value will be numerical
|
|
308
|
+
*/
|
|
309
|
+
lookupTables: [{
|
|
310
|
+
key: "product_id",
|
|
311
|
+
entries: 1000,
|
|
312
|
+
attributes: {
|
|
313
|
+
category: [
|
|
314
|
+
"Books", "Movies", "Music", "Games", "Electronics", "Computers", "Smart Home", "Home", "Garden & Tools", "Pet Supplies", "Food & Grocery", "Beauty", "Health", "Toys", "Kids", "Baby", "Handmade", "Sports", "Outdoors", "Automotive", "Industrial", "Entertainment", "Art"
|
|
315
|
+
],
|
|
316
|
+
"demand": ["high", "medium", "medium", "low"],
|
|
317
|
+
"supply": ["high", "medium", "medium", "low"],
|
|
318
|
+
"manufacturer": chance.company.bind(chance),
|
|
319
|
+
"price": weighNumRange(5, 500, .25),
|
|
320
|
+
"rating": weighNumRange(1, 5),
|
|
321
|
+
"reviews": weighNumRange(0, 35)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
key: "video_id",
|
|
327
|
+
entries: 50000,
|
|
328
|
+
attributes: {
|
|
329
|
+
isFlagged: [true, false, false, false, false],
|
|
330
|
+
copyright: ["all rights reserved", "creative commons", "creative commons", "public domain", "fair use"],
|
|
331
|
+
uploader_id: chance.guid.bind(chance),
|
|
332
|
+
"uploader influence": ["low", "low", "low", "medium", "medium", "high"],
|
|
333
|
+
thumbs: weighNumRange(0, 35),
|
|
334
|
+
rating: ["G", "PG", "PG-13", "R", "NC-17", "PG-13", "R", "NC-17", "R", "PG", "PG"]
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
}],
|
|
338
|
+
};
|
package/lib/utils/ai.js
CHANGED
|
@@ -1,89 +1,67 @@
|
|
|
1
|
-
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
2
|
-
import * as u from "ak-tools";
|
|
3
1
|
|
|
4
|
-
import
|
|
2
|
+
/** @typedef {import('../../types.js').Dungeon} Dungeon */
|
|
3
|
+
/** @typedef {import('../../types.js').EventConfig} EventConfig */
|
|
5
4
|
|
|
5
|
+
import * as u from "ak-tools";
|
|
6
|
+
import 'dotenv/config';
|
|
6
7
|
const { GEMINI_API_KEY: API_KEY, NODE_ENV = "unknown" } = process.env;
|
|
7
8
|
if (!API_KEY) throw new Error("Please provide a Gemini API key");
|
|
9
|
+
import AITransformer from 'ak-gemini';
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const model = gemini.getGenerativeModel({ model: "gemini-2.0-flash" });
|
|
12
|
-
const PROOMPTY = await u.load("./components/prompt.txt");
|
|
13
|
-
const prompt = `
|
|
14
|
-
Given the following information about a website or app:
|
|
15
|
-
|
|
16
|
-
${userInput}
|
|
17
|
-
|
|
18
|
-
${PROOMPTY}
|
|
11
|
+
let CURRENT_PROMPT = ``;
|
|
12
|
+
CURRENT_PROMPT = `build me a dungeon stream with these events and structure
|
|
19
13
|
|
|
20
|
-
|
|
14
|
+
{ "event": "impression", "carousel": [{"product": "big mac"}] }
|
|
15
|
+
{ "event": "viewed", "product_viewed": "big mac" }
|
|
16
|
+
{ "event": "add to basket", "product_added": "big mac" }
|
|
17
|
+
{ "event": "customized", "product_customized": "big mac" }
|
|
18
|
+
{ "event": "checked out", "cart": [{"item": "big mac"}] }
|
|
21
19
|
|
|
22
|
-
${userInput}
|
|
23
|
-
`.trim();
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
let attempts = 0;
|
|
28
|
-
do {
|
|
29
|
-
attempts++;
|
|
30
|
-
const result = await model.generateContent(prompt);
|
|
31
|
-
const response = await result.response;
|
|
32
|
-
const text = response.text();
|
|
33
|
-
schema = processResponse(text);
|
|
34
|
-
schemaIsValid = validator(schema);
|
|
35
|
-
} while (!schemaIsValid);
|
|
21
|
+
but use all the different mcdonalds products as a possible values`;
|
|
22
|
+
CURRENT_PROMPT = ``;
|
|
36
23
|
|
|
37
|
-
return schema;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function processResponse(text) {
|
|
41
|
-
let json;
|
|
42
|
-
// check for ```json
|
|
43
|
-
const start = text.indexOf("```json");
|
|
44
|
-
const end = text.indexOf("```", start + 1);
|
|
45
|
-
|
|
46
|
-
if (start === -1 || end === -1) {
|
|
47
|
-
const start = text.indexOf("{");
|
|
48
|
-
const end = text.lastIndexOf("}");
|
|
49
|
-
json = text.slice(start, end + 1).trim();
|
|
50
|
-
}
|
|
51
24
|
|
|
52
|
-
json = text.slice(start + 7, end).trim();
|
|
53
25
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
26
|
+
async function main(params) {
|
|
27
|
+
const { prompt } = params;
|
|
28
|
+
if (!prompt) throw new Error("Please provide a prompt");
|
|
29
|
+
let INSTRUCTIONS = await u.load('./lib/templates/instructions.txt', false);
|
|
30
|
+
const TYPES = await u.load('./lib/templates/abbreviated.d.ts', false);
|
|
31
|
+
const VERBOSE_SCHEMA_FILE = await u.load('./lib/templates/verbose-schema.js', false);
|
|
32
|
+
const VERBOSE_SCHEMA = VERBOSE_SCHEMA_FILE.split(`//SPLIT HERE`).pop()?.trim() || ``;
|
|
33
|
+
INSTRUCTIONS = INSTRUCTIONS
|
|
34
|
+
.replace(/<TYPES>/g, TYPES)
|
|
35
|
+
.replace(/<VERBOSE_SCHEMA>/g, VERBOSE_SCHEMA);
|
|
63
36
|
|
|
64
|
-
|
|
65
|
-
|
|
37
|
+
const ai = new AITransformer({
|
|
38
|
+
apiKey: API_KEY,
|
|
39
|
+
onlyJSON: false,
|
|
40
|
+
systemInstructions: INSTRUCTIONS?.trim(),
|
|
41
|
+
modelName: "gemini-2.5-pro",
|
|
66
42
|
|
|
67
|
-
//null schema are always invalid
|
|
68
|
-
if (!schema) valid = false;
|
|
69
43
|
|
|
70
|
-
|
|
71
|
-
|
|
44
|
+
});
|
|
45
|
+
await ai.init();
|
|
46
|
+
const response = await ai.message(prompt);
|
|
72
47
|
|
|
73
|
-
//
|
|
74
|
-
|
|
48
|
+
// if (NODE_ENV === "dev") {
|
|
49
|
+
// debugger;
|
|
50
|
+
// }
|
|
75
51
|
|
|
76
|
-
|
|
77
|
-
if (Object.keys(schema.userProps).length < 2) valid = false;
|
|
52
|
+
return response;
|
|
78
53
|
|
|
79
|
-
return valid;
|
|
80
54
|
}
|
|
81
55
|
|
|
82
56
|
|
|
83
|
-
export
|
|
57
|
+
export default main;
|
|
84
58
|
|
|
85
59
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
86
|
-
|
|
60
|
+
main(
|
|
61
|
+
{
|
|
62
|
+
prompt: CURRENT_PROMPT || "Generate a dungeon spec for a simple e-commerce site with checkout and add to cart events."
|
|
63
|
+
}
|
|
64
|
+
)
|
|
87
65
|
.then((result) => {
|
|
88
66
|
if (NODE_ENV === "dev") debugger;
|
|
89
67
|
})
|
package/lib/utils/chart.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/** @typedef {import('../../types.js').EventSchema} EventSchema */
|
|
2
|
+
/** @typedef {import('../../types.js').Result} Result */
|
|
3
|
+
/** @typedef {import('../../types.js').Context} Context */
|
|
4
|
+
|
|
1
5
|
import { ChartJSNodeCanvas } from 'chartjs-node-canvas';
|
|
2
6
|
import fs from 'fs';
|
|
3
7
|
import * as u from 'ak-tools';
|
|
@@ -179,6 +183,7 @@ async function generateLineChart(rawData, signupEvents = ["sign up"], fileName)
|
|
|
179
183
|
const imageBuffer = await chartJSNodeCanvas.renderToBuffer(configuration);
|
|
180
184
|
const filePath = path.join(tempDir, `${fileName}.png`);
|
|
181
185
|
const removed = await u.rm(filePath);
|
|
186
|
+
// @ts-ignore - imageBuffer is a Buffer but touch accepts it
|
|
182
187
|
const file = await u.touch(filePath, imageBuffer);
|
|
183
188
|
|
|
184
189
|
console.log(`Chart saved as ${fileName}.png`);
|