make-mp-data 1.5.0 → 1.5.2
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/.gcloudignore +17 -0
- package/.vscode/launch.json +54 -19
- package/.vscode/settings.json +2 -0
- package/.vscode/tasks.json +12 -0
- package/components/ai.js +93 -0
- package/{src → components}/chart.js +14 -0
- package/{src → components}/cli.js +7 -1
- package/components/project.js +166 -0
- package/components/prompt.txt +98 -0
- package/{src → components}/utils.js +142 -41
- package/{schemas → dungeons}/adspend.js +2 -2
- package/{schemas → dungeons}/anon.js +2 -2
- package/{schemas → dungeons}/big.js +2 -2
- package/dungeons/business.js +327 -0
- package/{schemas → dungeons}/complex.js +10 -10
- package/dungeons/foobar.js +241 -0
- package/{schemas → dungeons}/funnels.js +3 -4
- package/dungeons/gaming.js +314 -0
- package/{schemas → dungeons}/mirror.js +2 -2
- package/{schemas/foobar.js → dungeons/sanity.js} +20 -27
- package/dungeons/scd.js +205 -0
- package/dungeons/session-replay.js +175 -0
- package/{schemas → dungeons}/simple.js +3 -3
- package/dungeons/userAgent.js +190 -0
- package/env.yaml +1 -0
- package/index.js +482 -167
- package/package.json +13 -6
- package/scripts/deploy.sh +11 -0
- package/scripts/jsdoctest.js +1 -1
- package/scripts/{new.sh → new-dungeon.sh} +39 -10
- package/scripts/new-project.mjs +14 -0
- package/scripts/update-deps.sh +4 -0
- package/tests/benchmark/concurrency.mjs +2 -2
- package/tests/cli.test.js +121 -0
- package/tests/e2e.test.js +134 -186
- package/tests/int.test.js +14 -12
- package/tests/jest.config.js +8 -0
- package/tests/testCases.mjs +1 -1
- package/tests/testSoup.mjs +4 -3
- package/tests/unit.test.js +16 -15
- package/tsconfig.json +1 -1
- package/types.d.ts +40 -8
- package/scripts/deps.sh +0 -3
- /package/{src → components}/defaults.js +0 -0
- /package/dungeons/{.gitkeep → customers/.gitkeep} +0 -0
- /package/scripts/{go.sh → run-index.sh} +0 -0
package/.gcloudignore
ADDED
package/.vscode/launch.json
CHANGED
|
@@ -4,37 +4,72 @@
|
|
|
4
4
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
5
|
"version": "0.2.0",
|
|
6
6
|
"configurations": [
|
|
7
|
-
{
|
|
8
|
-
"command": "npm run simple",
|
|
9
|
-
"name": "simple",
|
|
10
|
-
"request": "launch",
|
|
11
|
-
"type": "node-terminal"
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"command": "npm run complex",
|
|
15
|
-
"name": "complex",
|
|
16
|
-
"request": "launch",
|
|
17
|
-
"type": "node-terminal"
|
|
18
|
-
},
|
|
19
7
|
{
|
|
20
8
|
"command": "npm run dev",
|
|
21
|
-
"name": "
|
|
9
|
+
"name": "scratch",
|
|
22
10
|
"request": "launch",
|
|
23
11
|
"type": "node-terminal"
|
|
24
12
|
},
|
|
13
|
+
{
|
|
14
|
+
"type": "node",
|
|
15
|
+
"request": "launch",
|
|
16
|
+
"name": "run dungeon",
|
|
17
|
+
"runtimeExecutable": "nodemon",
|
|
18
|
+
"runtimeArgs": ["--inspect"],
|
|
19
|
+
"program": "${workspaceFolder}/index.js",
|
|
20
|
+
"args": ["--ignore", "./data/*", "${file}"],
|
|
21
|
+
"restart": true,
|
|
22
|
+
"console": "integratedTerminal",
|
|
23
|
+
"internalConsoleOptions": "neverOpen",
|
|
24
|
+
"skipFiles": ["<node_internals>/**"],
|
|
25
|
+
"preLaunchTask": "npm: prune",
|
|
26
|
+
},
|
|
25
27
|
{
|
|
26
28
|
"type": "node",
|
|
27
29
|
"request": "launch",
|
|
28
|
-
"name": "
|
|
30
|
+
"name": "current file",
|
|
29
31
|
"runtimeExecutable": "nodemon",
|
|
30
32
|
"program": "${file}",
|
|
31
33
|
"restart": true,
|
|
32
34
|
"console": "integratedTerminal",
|
|
33
35
|
"internalConsoleOptions": "neverOpen",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
"env": {
|
|
37
|
+
"NODE_ENV": "dev"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"command": "npm run func:local",
|
|
42
|
+
"name": "cloud local",
|
|
43
|
+
"request": "launch",
|
|
44
|
+
"type": "node-terminal",
|
|
45
|
+
"env": {
|
|
46
|
+
"NODE_ENV": "dev"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "node",
|
|
51
|
+
"request": "launch",
|
|
52
|
+
"name": "sanity",
|
|
53
|
+
"runtimeExecutable": "nodemon",
|
|
54
|
+
"runtimeArgs": ["--inspect"],
|
|
55
|
+
"program": "${workspaceFolder}/index.js",
|
|
56
|
+
"args": ["--ignore", "./data/*", "./schemas/sanity.js"],
|
|
57
|
+
"restart": true,
|
|
58
|
+
"console": "integratedTerminal",
|
|
59
|
+
"internalConsoleOptions": "neverOpen",
|
|
60
|
+
"skipFiles": ["<node_internals>/**"],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"type": "node-terminal",
|
|
64
|
+
"request": "launch",
|
|
65
|
+
"name": "simple",
|
|
66
|
+
"command": "node ${workspaceFolder}/index.js --simple"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"type": "node-terminal",
|
|
70
|
+
"request": "launch",
|
|
71
|
+
"name": "complex",
|
|
72
|
+
"command": "node ${workspaceFolder}/index.js --complex"
|
|
73
|
+
}
|
|
39
74
|
]
|
|
40
75
|
}
|
package/.vscode/settings.json
CHANGED
package/components/ai.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const { GoogleGenerativeAI } = require("@google/generative-ai");
|
|
2
|
+
const u = require("ak-tools");
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const dotenv = require("dotenv");
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
const { GEMINI_API_KEY: API_KEY, NODE_ENV = "unknown" } = process.env;
|
|
9
|
+
if (!API_KEY) throw new Error("Please provide a Gemini API key");
|
|
10
|
+
|
|
11
|
+
async function generateSchema(userInput) {
|
|
12
|
+
const gemini = new GoogleGenerativeAI(API_KEY);
|
|
13
|
+
const model = gemini.getGenerativeModel({ model: "gemini-1.5-flash" });
|
|
14
|
+
const PROOMPTY = await u.load("./components/prompt.txt");
|
|
15
|
+
const prompt = `
|
|
16
|
+
Given the following information about a website or app:
|
|
17
|
+
|
|
18
|
+
${userInput}
|
|
19
|
+
|
|
20
|
+
${PROOMPTY}
|
|
21
|
+
|
|
22
|
+
REMEMBER, YOUR INPUT IS:
|
|
23
|
+
|
|
24
|
+
${userInput}
|
|
25
|
+
`.trim();
|
|
26
|
+
|
|
27
|
+
let schema;
|
|
28
|
+
let schemaIsValid = false;
|
|
29
|
+
let attempts = 0;
|
|
30
|
+
do {
|
|
31
|
+
attempts++;
|
|
32
|
+
const result = await model.generateContent(prompt);
|
|
33
|
+
const response = await result.response;
|
|
34
|
+
const text = response.text();
|
|
35
|
+
schema = processResponse(text);
|
|
36
|
+
schemaIsValid = validator(schema);
|
|
37
|
+
} while (!schemaIsValid);
|
|
38
|
+
|
|
39
|
+
return schema;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function processResponse(text) {
|
|
43
|
+
let json;
|
|
44
|
+
// check for ```json
|
|
45
|
+
const start = text.indexOf("```json");
|
|
46
|
+
const end = text.indexOf("```", start + 1);
|
|
47
|
+
|
|
48
|
+
if (start === -1 || end === -1) {
|
|
49
|
+
const start = text.indexOf("{");
|
|
50
|
+
const end = text.lastIndexOf("}");
|
|
51
|
+
json = text.slice(start, end + 1).trim();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
json = text.slice(start + 7, end).trim();
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(json);
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function validator(schema) {
|
|
67
|
+
let valid = true;
|
|
68
|
+
|
|
69
|
+
//null schema are always invalid
|
|
70
|
+
if (!schema) valid = false;
|
|
71
|
+
|
|
72
|
+
//must have 3 or more events
|
|
73
|
+
if (schema.events.length < 3) valid = false;
|
|
74
|
+
|
|
75
|
+
//must have 2 or more superProps
|
|
76
|
+
if (Object.keys(schema.superProps).length < 2) valid = false;
|
|
77
|
+
|
|
78
|
+
//must have 2 or more userProps
|
|
79
|
+
if (Object.keys(schema.userProps).length < 2) valid = false;
|
|
80
|
+
|
|
81
|
+
return valid;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if (require.main === module) {
|
|
86
|
+
generateSchema(`a dungeons and dragons inspired game where players can create characters, join parties, and go on quests and fight bosses!`)
|
|
87
|
+
.then((result) => {
|
|
88
|
+
if (NODE_ENV === "dev") debugger;
|
|
89
|
+
})
|
|
90
|
+
.catch((error) => {
|
|
91
|
+
if (NODE_ENV === "dev") debugger;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -5,6 +5,8 @@ const dayjs = require('dayjs');
|
|
|
5
5
|
const { openFinder } = require('./utils');
|
|
6
6
|
const { existsSync } = fs;
|
|
7
7
|
const path = require('path');
|
|
8
|
+
require('dotenv').config();
|
|
9
|
+
const { NODE_ENV = "unknown" } = process.env;
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
let tempDir;
|
|
@@ -185,3 +187,15 @@ async function generateLineChart(rawData, signupEvents = ["sign up"], fileName)
|
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
module.exports = { generateLineChart };
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if (require.main === module) {
|
|
194
|
+
generateLineChart()
|
|
195
|
+
.then((result)=>{
|
|
196
|
+
if (NODE_ENV === "dev") debugger;
|
|
197
|
+
})
|
|
198
|
+
.catch((error)=>{
|
|
199
|
+
if (NODE_ENV === "dev") debugger;
|
|
200
|
+
})
|
|
201
|
+
}
|
|
@@ -34,6 +34,12 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
|
|
|
34
34
|
describe: 'project token; if supplied data will be sent to mixpanel',
|
|
35
35
|
type: 'string'
|
|
36
36
|
})
|
|
37
|
+
.option("batchSize", {
|
|
38
|
+
demandOption: false,
|
|
39
|
+
alias: 'b',
|
|
40
|
+
describe: 'batch size for chunking data',
|
|
41
|
+
type: 'number'
|
|
42
|
+
})
|
|
37
43
|
.option("seed", {
|
|
38
44
|
demandOption: false,
|
|
39
45
|
alias: 's',
|
|
@@ -81,7 +87,7 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
|
|
|
81
87
|
demandOption: false,
|
|
82
88
|
default: 'US',
|
|
83
89
|
alias: 'r',
|
|
84
|
-
describe: 'either US or EU',
|
|
90
|
+
describe: 'either US or EU or IN',
|
|
85
91
|
type: 'string'
|
|
86
92
|
})
|
|
87
93
|
.option('concurrency', {
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
require('dotenv').config();
|
|
2
|
+
const akTools = require('ak-tools');
|
|
3
|
+
const { rand, makeName } = akTools;
|
|
4
|
+
let { OAUTH_TOKEN = "" } = process.env;
|
|
5
|
+
const { NODE_ENV = "unknown" } = process.env;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Main function to create a project and add group keys to it.
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} params - Parameters for the function.
|
|
11
|
+
* @param {string} [params.oauth=""] - OAuth token for authentication.
|
|
12
|
+
* @param {string} [params.orgId=""] - Organization ID.
|
|
13
|
+
* @param {Array<Object>} [params.groups=[]] - List of groups to add to the project.
|
|
14
|
+
* @param {string} [params.name=""] - Name of the user.
|
|
15
|
+
* @param {string} [params.email=""] - Email of the user.
|
|
16
|
+
* @param {string} [params.projectName=""] - Name of the project.
|
|
17
|
+
* @returns {Promise<Object>} The created project with additional group keys.
|
|
18
|
+
* @throws Will throw an error if OAUTH_TOKEN is not set.
|
|
19
|
+
* @throws Will throw an error if orgId is not found.
|
|
20
|
+
*/
|
|
21
|
+
async function main(params = {}) {
|
|
22
|
+
let { oauth = "", orgId = "", groups = [], name = "", email = "", projectName } = params;
|
|
23
|
+
if (oauth) OAUTH_TOKEN = oauth;
|
|
24
|
+
if (!OAUTH_TOKEN) throw new Error('No OAUTH_TOKEN in .env');
|
|
25
|
+
if (!orgId) {
|
|
26
|
+
({ orgId, name, email } = await getUser());
|
|
27
|
+
}
|
|
28
|
+
if (!orgId) throw new Error('No orgId found');
|
|
29
|
+
if (!projectName) projectName = makeName();
|
|
30
|
+
const project = await makeProject(orgId);
|
|
31
|
+
project.user = name;
|
|
32
|
+
project.email = email;
|
|
33
|
+
project.groups = groups;
|
|
34
|
+
project.orgId = orgId;
|
|
35
|
+
const groupKeys = [
|
|
36
|
+
// { display_name: 'Account', property_name: 'account_id' },
|
|
37
|
+
];
|
|
38
|
+
groupKeys.push(...groups);
|
|
39
|
+
const addedGroupKeys = await addGroupKeys(groupKeys, project.id);
|
|
40
|
+
project.groupsAdded = addedGroupKeys;
|
|
41
|
+
|
|
42
|
+
return project;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async function makeProject(orgId, oauthToken = OAUTH_TOKEN) {
|
|
47
|
+
const excludedOrgs = [
|
|
48
|
+
1, // Mixpanel
|
|
49
|
+
328203, // Mixpanel Demo
|
|
50
|
+
1673847, // SE Demo
|
|
51
|
+
1866253 // Demo Projects
|
|
52
|
+
];
|
|
53
|
+
if (!orgId || !oauthToken) throw new Error('Missing orgId or oauthToken');
|
|
54
|
+
const url = `https://mixpanel.com/api/app/organizations/${orgId}/create-project`;
|
|
55
|
+
const projectPayload = {
|
|
56
|
+
"cluster_id": 1,
|
|
57
|
+
"project_name": `GTM Metrics: Test Env ${rand(1000, 9999)}`,
|
|
58
|
+
"timezone_id": 404
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const payload = {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: `Bearer ${oauthToken}`,
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify(projectPayload)
|
|
68
|
+
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const projectsReq = await fetch(url, payload);
|
|
72
|
+
const projectsRes = await projectsReq.json();
|
|
73
|
+
const { api_secret, id, name, token } = projectsRes.results;
|
|
74
|
+
|
|
75
|
+
const data = {
|
|
76
|
+
api_secret,
|
|
77
|
+
id,
|
|
78
|
+
name,
|
|
79
|
+
token,
|
|
80
|
+
url: `https://mixpanel.com/project/${id}/app/settings#project/${id}`
|
|
81
|
+
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function getUser(oauthToken = OAUTH_TOKEN) {
|
|
88
|
+
const user = {};
|
|
89
|
+
try {
|
|
90
|
+
if (oauthToken) {
|
|
91
|
+
const info = await fetch(`https://mixpanel.com/api/app/me/?include_workspace_users=false`, { headers: { Authorization: `Bearer ${oauthToken}` } });
|
|
92
|
+
const data = await info.json();
|
|
93
|
+
if (data?.results) {
|
|
94
|
+
const { user_name = "", user_email = "" } = data.results;
|
|
95
|
+
if (user_name) user.name = user_name;
|
|
96
|
+
if (user_email) user.email = user_email;
|
|
97
|
+
const foundOrg = Object.values(data.results.organizations).filter(o => o.name.includes(user_name))?.pop();
|
|
98
|
+
if (foundOrg) {
|
|
99
|
+
user.orgId = foundOrg.id?.toString();
|
|
100
|
+
user.orgName = foundOrg.name;
|
|
101
|
+
}
|
|
102
|
+
if (!foundOrg) {
|
|
103
|
+
// the name is not in the orgs, so we need to find the org in which the user is the owner
|
|
104
|
+
const ignoreProjects = [1673847, 1866253, 328203];
|
|
105
|
+
const possibleOrg = Object.values(data.results.organizations)
|
|
106
|
+
.filter(o => o.role === 'owner')
|
|
107
|
+
.filter(o => !ignoreProjects.includes(o.id))?.pop();
|
|
108
|
+
if (possibleOrg) {
|
|
109
|
+
user.orgId = possibleOrg?.id?.toString();
|
|
110
|
+
user.orgName = possibleOrg.name;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
console.error('get user err', err);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return user;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
async function addGroupKeys(groupKeyDfns = [], projectId, oauthToken = OAUTH_TOKEN) {
|
|
125
|
+
const url = `https://mixpanel.com/api/app/projects/${projectId}/data-groups/`;
|
|
126
|
+
const results = [];
|
|
127
|
+
loopKeys: for (const { display_name, property_name } of groupKeyDfns) {
|
|
128
|
+
const body = {
|
|
129
|
+
display_name,
|
|
130
|
+
property_name
|
|
131
|
+
};
|
|
132
|
+
const payload = {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
Authorization: `Bearer ${oauthToken}`,
|
|
136
|
+
'Content-Type': 'application/json'
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify(body)
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(url, payload);
|
|
143
|
+
const data = await res.json();
|
|
144
|
+
results.push(data?.results);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
console.error('add group keys err', err);
|
|
148
|
+
continue loopKeys;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
return results;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if (require.main === module) {
|
|
157
|
+
main()
|
|
158
|
+
.then((result)=>{
|
|
159
|
+
if (NODE_ENV === "dev") debugger;
|
|
160
|
+
})
|
|
161
|
+
.catch((error)=>{
|
|
162
|
+
if (NODE_ENV === "dev") debugger;
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = main;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Please generate a JSON schema for event analytics that strictly adheres to these TypeScript types:
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
type Primitives = string | number | boolean | Date | Record<string, any>;
|
|
5
|
+
type ValidValue = Primitives | Primitives[];
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* event schema describes a single event that we might track with any number of properties
|
|
9
|
+
*/
|
|
10
|
+
interface EventSchema {
|
|
11
|
+
event: string;
|
|
12
|
+
weight: number;
|
|
13
|
+
properties?: Record<string, ValidValue>;
|
|
14
|
+
isFirstEvent?: boolean;
|
|
15
|
+
isChurnEvent?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* super props are shared across all events; common dimensions
|
|
20
|
+
*/
|
|
21
|
+
type superProps = Record<string, ValidValue>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* user props describe a user independent of their actions; user dimensions
|
|
25
|
+
*/
|
|
26
|
+
type userProps = Record<string, ValidValue>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* the schema YOU need to build
|
|
30
|
+
*/
|
|
31
|
+
type Schema = {
|
|
32
|
+
events: EventSchema[];
|
|
33
|
+
superProps: superProps;
|
|
34
|
+
userProps: userProps;
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
The schema that YOU make should include common events and properties, super properties, and user properties that describe TYPICAL user behavior for the website i mentioned above.
|
|
40
|
+
|
|
41
|
+
Let me give you a few examples, so you know how to respond.
|
|
42
|
+
|
|
43
|
+
**Example 1:**
|
|
44
|
+
|
|
45
|
+
Input: a generic e-commerce website thats sells a variety of products and also video streaming
|
|
46
|
+
|
|
47
|
+
Output:
|
|
48
|
+
```json
|
|
49
|
+
{"events":[{"event":"checkout","weight":2,"properties":{"amount":[5,500],"currency":["USD","CAD","EUR","BTC","ETH","JPY"],"coupon":["none","10%OFF","20%OFF"],"numItems":[1,10]}},{"event":"add to cart","weight":4,"properties":{"amount":[5,500],"rating":[1,5],"reviews":[0,35],"isFeaturedItem":[true,false,false],"itemCategory":["electronics","clothing","accessories","home goods","toys","tools","books","music","movies"]}},{"event":"page view","weight":10,"properties":{"page":["/","/","/help","/account","/watch","/listen","/product","/people","/peace"]}},{"event":"watch video","weight":8,"properties":{"videoCategory":["humor","scary","amsr","music","documentary","educational"],"isFeaturedItem":[true,false,false],"watchTimeSec":[10,600],"quality":["2160p","1440p","1080p","720p","480p","360p","240p"],"format":["mp4","avi","mov","mpg"]}},{"event":"view item","weight":8,"properties":{"isFeaturedItem":[true,false,false],"itemCategory":["office","school","art","crafts","party","wedding","baby","kids","adults","seniors"],"rating":[1,5]}},{"event":"save item","weight":5,"properties":{"isFeaturedItem":[true,false,false],"itemCategory":["electronics","games","food","beverages","health","beauty","sports","outdoors","party","wedding","baby","kids","adults","seniors"]}},{"event":"rent item","weight":2,"properties":{"isFeaturedItem":[true,false,false],"itemCategory":["electronics","clothing","games","food","beauty","sports","outdoors","automotive","wedding","baby","seniors"]}},{"event":"sign up","isFirstEvent":true,"weight":0,"properties":{"variants":["A","B","C","Control"],"flows":["new","existing","loyal","churned"],"flags":["on","off"],"experiment_ids":["1234","5678","9012","3456","7890"],"multiVariate":[true,false]}}],"superProps":{"platform":["web","mobile","kiosk","smartTV"],"currentTheme":["light","dark","custom"]},"userProps":{"loyalty_points":[1,100000],"age":[18,70],"premium":[true,false],"language":["english","spanish","french"]}}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Example 2:**
|
|
53
|
+
|
|
54
|
+
Input: https://www.nytimes.com
|
|
55
|
+
|
|
56
|
+
Output:
|
|
57
|
+
```json
|
|
58
|
+
{"events":[{"event":"page view","weight":10,"properties":{"page":["/","/section/world","/section/us","/section/politics","/section/business","/section/opinion","/section/technology","/section/science","/section/health","/section/sports","/section/arts","/section/fashion","/section/food","/section/travel"]}},{"event":"article read","weight":8,"properties":{"category":["world news","politics","business","technology","science","health","sports","arts","fashion","food","travel"],"length":["short (< 3 mins)","medium (3-5 mins)","long (> 5 mins)"],"author_reputation":["emerging","established","renowned"],"comment_interaction":["low (<10 comments)","medium (10-50 comments)","high (>50 comments)"]}},{"event":"search","weight":6,"properties":{"search_term_length":["short (1-3 words)","medium (4-6 words)","long (7+ words)"],"results_returned":["none","few (1-3 results)","some (4-10 results)","many (>10 results)"],"result_clicked":["first result","within top 5","none clicked"]}},{"event":"subscribe","weight":2,"properties":{"plan":["basic","premium","all-access"],"payment_method":["credit card","paypal","apple pay"],"trial_offer_accepted":[true,false]}},{"event":"newsletter sign up","weight":3,"properties":{"newsletter_type":["daily briefing","morning briefing","opinion","cooking","arts"],"opt_in_for_promotions":[true,false]}},{"event":"comment posted","weight":4,"properties":{"comment_length":["short (< 50 words)","medium (50-150 words)","long (> 150 words)"],"article_category":["news","opinion","review"],"visibility":["public","private","friends only"]}}],"superProps":{"platform":["web","mobile","tablet"],"loggedIn":[true,false],"time_of_day":["morning","afternoon","evening","night"],"browser_type":["Chrome","Firefox","Safari","Edge"]},"userProps":{"subscriptionType":["none","basic","premium","all-access"],"age_group":["<18","18-24","25-34","35-44","45-54","55-64","65+"],"location":["USA","Europe","Asia","Other"],"interests":["politics","technology","health","sports","arts","lifestyle","science","education"]}}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Example 3:**
|
|
62
|
+
|
|
63
|
+
Input: the love's truck stop app (love's connect: https://www.loves.com/en/my-love-rewards/download-loves-connect)
|
|
64
|
+
|
|
65
|
+
Output:
|
|
66
|
+
```json
|
|
67
|
+
{"events":[{"event":"home button","weight":8,"properties":{"button type":["front","side","middle"]}},{"event":"route button","weight":6,"properties":{"from screen":["home","services"]}},{"event":"services button","weight":2,"properties":{"is emergency?":[false,true]}},{"event":"stores button","weight":3,"properties":{"is from push?":[true,false]}},{"event":"email link clicked","weight":1,"properties":{"campaign_name":["Pro Email Campaign 1","Pro Email Campaign 2","Pro Email Campaign 3"]}},{"event":"pay button","weight":4,"properties":{}},{"event":"pay with location","weight":3,"properties":{}},{"event":"payment select","weight":4,"properties":{}},{"event":"select pump","weight":5,"properties":{"pump":[1,32]}},{"event":"select fuel","weight":3,"properties":{"fuel type":["diesel","reefer","def"],"fuel price":[3.99,6.79]}},{"event":"fueling","weight":4,"properties":{"fuel volume":[20,150]}}],"superProps":{"campaign_name":["Pro Email Campaign 1","Pro Email Campaign 2","Pro Email Campaign 3"],"feature flags":[["first 100k miles"],["dark mode","first 100k miles"],["dark mode","free shower"],["free shower"]]},"userProps":{"driver type":["Leased","Owner","Fleet"],"marketing":["in-app","email","sms","call"],"member status":["base","gold","platinum","diamond"],"lifetime value":[0,10000]}}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Example 4:**
|
|
71
|
+
|
|
72
|
+
Input: the web app for one medical, a modern healthcare provider (https://app.onemedical.com/)
|
|
73
|
+
|
|
74
|
+
Output:
|
|
75
|
+
```json
|
|
76
|
+
{"events":[{"event":"login","weight":9,"properties":{"method":["standard email/password","google","apple"],"success":[true,false],"error_type":["none","password incorrect","user not found","account locked"]}},{"event":"appointment booking","weight":8,"properties":{"type":["physical","virtual"],"specialty":["primary care","mental health","pediatrics","nutrition","allergy","dermatology"],"duration":["15 minutes","30 minutes","1 hour"],"booking_advance_days":["same day","1-3 days","4-7 days","more than a week"]}},{"event":"profile update","weight":4,"properties":{"section_updated":["personal information","insurance details","medical history","preferences"],"fields_changed":["1-2","3-5","more than 5"]}},{"event":"message to provider","weight":6,"properties":{"message_length":["short (<50 words)","medium (50-150 words)","long (>150 words)"],"response_time":["<1 hour","1-24 hours","1-2 days","no response"],"urgency":["low","medium","high"]}},{"event":"medication refill request","weight":5,"properties":{"medication":["prescription","over-the-counter"],"quantity":["1 month","3 months","6 months"],"auto_refill_enrolled":[true,false]}},{"event":"health record access","weight":7,"properties":{"record_type":["lab results","vaccination records","prescription history","doctor notes"],"access_device":["desktop","mobile"],"time_spent":["<1 min","1-5 mins","5+ mins"]}},{"event":"feedback submission","weight":3,"properties":{"feedback_type":["service","app usability","provider"],"rating":["1","2","3","4","5"],"comment_provided":[true,false]}},{"event":"notification settings update","weight":2,"properties":{"notifications_enabled":[true,false],"types_enabled":["appointment reminders","health updates","newsletters","promotional offers"]}}],"superProps":{"platform":["web","mobile"],"loggedIn":[true,false],"user_type":["patient","provider","admin"],"subscription_level":["free","standard","premium"]},"userProps":{"membership_status":["active","lapsed","pending renewal"],"age_group":["<18","18-24","25-34","35-44","45-54","55-64","65+"],"has_chronic_conditions":[true,false],"preferred_contact_method":["email","phone","sms"]}}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Example 5:**
|
|
80
|
+
|
|
81
|
+
Input: the web app for coinbase, a cryptocurrency exchange (https://www.coinbase.com/)
|
|
82
|
+
|
|
83
|
+
Output:
|
|
84
|
+
```json
|
|
85
|
+
{"events":[{"event":"login","weight":9,"properties":{"method":["standard email/password","two-factor authentication","biometric"],"success":[true,false],"error_type":["none","password incorrect","user not found","account locked","2FA failed"]}},{"event":"trade executed","weight":8,"properties":{"crypto_pair":["BTC/USD","ETH/USD","LTC/USD","XRP/USD","BCH/USD"],"trade_type":["buy","sell"],"order_type":["market","limit","stop loss","stop limit"],"quantity":["<1","1-5","5-10","10+"],"trade_successful":[true,false],"error_type":["none","insufficient funds","slippage too high","network error"]}},{"event":"account funding","weight":7,"properties":{"method":["bank transfer","credit card","crypto deposit"],"currency":["USD","EUR","GBP"],"amount":["<100","100-1000","1000-5000","5000+"],"successful":[true,false],"error_type":["none","transaction declined","limits exceeded","network error"]}},{"event":"withdrawal request","weight":6,"properties":{"method":["bank transfer","crypto withdrawal"],"currency":["USD","BTC","ETH"],"amount":["<100","100-1000","1000-5000","5000+"],"successful":[true,false],"error_type":["none","insufficient funds","limits exceeded","network error"]}},{"event":"profile update","weight":3,"properties":{"fields_updated":["email","phone","two-factor authentication settings","payment methods"],"successful":[true,false]}},{"event":"security alert","weight":4,"properties":{"alert_type":["login from new device","suspicious transaction","profile changes"],"response_action":["no action","contact support","reset password","enable 2FA"]}},{"event":"price alert set","weight":2,"properties":{"crypto":["BTC","ETH","LTC","XRP"],"threshold":["<5000","5000-10000","10000-20000","20000+"],"alert_type":["price increase","price decrease"]}},{"event":"customer support interaction","weight":5,"properties":{"issue_type":["account access","trade issue","withdrawal problem","other"],"contact_method":["email","phone","live chat"],"resolution":["resolved","unresolved","escalated"]}}],"superProps":{"platform":["web","iOS app","Android app"],"loggedIn":[true,false],"verification_level":["none","basic","verified","pro"]},"userProps":{"account_type":["basic","pro"],"trading_frequency":["daily","weekly","monthly","rarely"],"preferred_crypto":["BTC","ETH","LTC","XRP"],"region":["North America","Europe","Asia","Other"],"investment_level":["<1000","1000-5000","5000-10000","10000+"]}}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**ADDITIONAL GUIDELINES:**
|
|
89
|
+
|
|
90
|
+
* Use specific and descriptive event names.
|
|
91
|
+
* DO NOT declare property types, properties are the key names and include an array of POSSIBLE values which could be chosen for the event or user
|
|
92
|
+
* the properties within each event should adhere to the defined TypeScript types (Primitives and ValidValue)
|
|
93
|
+
* For numerical ranges, you can use [min, max] in the JSON as above
|
|
94
|
+
* Consider both super properties (applied to all events) and user properties; they should be different from event props
|
|
95
|
+
* DO NOT include user IDs, emails, or location properties anywhere data in the schema; ONLY highlight the properties specific to the website or app
|
|
96
|
+
* DO NOT be lazy an say ['array of payment types'] or ['array of product categories'] - be specific ... ['USD', 'CAD', 'EUR', 'BTC', 'ETH', 'JPY'] ... we want to see the actual values that would be passed live production data
|
|
97
|
+
* event names should be lower case by convention; prefer spaces to underscores so "add to cart" instead of "add_to_cart"
|
|
98
|
+
* you should ALWAYS output valid JSON as your output will be fed into a JSON schema validator
|