make-mp-data 2.1.6 → 3.0.1

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.
Files changed (76) hide show
  1. package/README.md +31 -0
  2. package/dungeons/adspend.js +2 -2
  3. package/dungeons/ai-chat-analytics-ed.js +3 -2
  4. package/dungeons/anon.js +2 -2
  5. package/dungeons/array-of-object-loopup.js +181 -0
  6. package/dungeons/benchmark-heavy.js +241 -0
  7. package/dungeons/benchmark-light.js +141 -0
  8. package/dungeons/big.js +9 -8
  9. package/dungeons/business.js +2 -1
  10. package/dungeons/clinch-agi.js +632 -0
  11. package/dungeons/complex.js +3 -2
  12. package/dungeons/copilot.js +383 -0
  13. package/dungeons/ecommerce-store.js +0 -0
  14. package/dungeons/experiments.js +5 -4
  15. package/dungeons/foobar.js +101 -101
  16. package/dungeons/funnels.js +2 -2
  17. package/dungeons/gaming.js +3 -2
  18. package/dungeons/harness/harness-education.js +988 -0
  19. package/dungeons/harness/harness-fintech.js +976 -0
  20. package/dungeons/harness/harness-food.js +985 -0
  21. package/dungeons/harness/harness-gaming.js +1178 -0
  22. package/dungeons/harness/harness-media.js +961 -0
  23. package/dungeons/harness/harness-sass.js +923 -0
  24. package/dungeons/harness/harness-social.js +928 -0
  25. package/dungeons/kurby.js +211 -0
  26. package/dungeons/media.js +5 -4
  27. package/dungeons/mil.js +4 -3
  28. package/dungeons/mirror.js +2 -2
  29. package/dungeons/money2020-ed.js +8 -7
  30. package/dungeons/sanity.js +3 -2
  31. package/dungeons/scd.js +3 -2
  32. package/dungeons/simple.js +29 -14
  33. package/dungeons/strict-event-test.js +30 -0
  34. package/dungeons/student-teacher.js +3 -2
  35. package/dungeons/text-generation.js +84 -85
  36. package/dungeons/too-big-events.js +166 -0
  37. package/dungeons/uday-schema.json +220 -0
  38. package/dungeons/userAgent.js +4 -3
  39. package/index.js +41 -54
  40. package/lib/core/config-validator.js +122 -7
  41. package/lib/core/context.js +7 -14
  42. package/lib/core/storage.js +60 -30
  43. package/lib/generators/adspend.js +12 -27
  44. package/lib/generators/events.js +6 -7
  45. package/lib/generators/funnels.js +16 -5
  46. package/lib/generators/product-lookup.js +262 -0
  47. package/lib/generators/product-names.js +195 -0
  48. package/lib/generators/profiles.js +3 -3
  49. package/lib/generators/scd.js +13 -3
  50. package/lib/generators/text.js +17 -4
  51. package/lib/orchestrators/mixpanel-sender.js +251 -208
  52. package/lib/orchestrators/user-loop.js +57 -19
  53. package/lib/templates/funnels-instructions.txt +272 -0
  54. package/lib/templates/hook-examples.json +187 -0
  55. package/lib/templates/hooks-instructions.txt +295 -8
  56. package/lib/templates/phrases.js +473 -16
  57. package/lib/templates/refine-instructions.txt +485 -0
  58. package/lib/templates/schema-instructions.txt +239 -109
  59. package/lib/templates/schema.d.ts +173 -0
  60. package/lib/templates/verbose-schema.js +140 -206
  61. package/lib/utils/ai.js +853 -77
  62. package/lib/utils/chart.js +210 -0
  63. package/lib/utils/function-registry.js +285 -0
  64. package/lib/utils/json-evaluator.js +172 -0
  65. package/lib/utils/logger.js +38 -0
  66. package/lib/utils/mixpanel.js +101 -0
  67. package/lib/utils/project.js +3 -2
  68. package/lib/utils/utils.js +41 -4
  69. package/package.json +13 -19
  70. package/types.d.ts +15 -5
  71. package/lib/generators/text-bak-old.js +0 -1121
  72. package/lib/orchestrators/worker-manager.js +0 -203
  73. package/lib/templates/phrases-bak.js +0 -925
  74. package/lib/templates/prompt (old).txt +0 -98
  75. package/lib/templates/scratch-dungeon-template.js +0 -116
  76. package/lib/templates/textQuickTest.js +0 -172
@@ -0,0 +1,166 @@
1
+ /**
2
+ * "Too Big Events" dungeon
3
+ * Tests Mixpanel row limit (1MB) and column limit (8KB for arrays of objects)
4
+ * Generates events with absurd numbers of properties and oversized array-of-object columns
5
+ */
6
+
7
+ function integer(min = 1, max = 100) {
8
+ if (min === max) return min;
9
+ if (min > max) [min, max] = [max, min];
10
+ return Math.floor(Math.random() * (max - min + 1)) + min;
11
+ }
12
+
13
+ function loremChunk(size = 200) {
14
+ const words = ["foo", "bar", "baz", "qux", "garply", "waldo", "fred", "plugh", "xyzzy", "thud", "corge", "grault", "flob", "zorp", "narf", "blip", "snork", "quux", "wobble", "crumn"];
15
+ let result = "";
16
+ while (result.length < size) {
17
+ result += words[Math.floor(Math.random() * words.length)] + " ";
18
+ }
19
+ return result.trim();
20
+ }
21
+
22
+ // Generate a fat array of objects (targeting >8KB per column)
23
+ function bigArrayOfObjects(numItems = 50) {
24
+ numItems = numItems * 2
25
+ return () => {
26
+ const arr = [];
27
+ for (let i = 0; i < numItems; i++) {
28
+ arr.push({
29
+ id: `item_${i}_${Math.random().toString(36).slice(2, 10)}`,
30
+ name: loremChunk(80),
31
+ description: loremChunk(150),
32
+ category: ["foo", "bar", "baz", "qux"][Math.floor(Math.random() * 4)],
33
+ price: parseFloat((Math.random() * 999).toFixed(2)),
34
+ quantity: integer(1, 100),
35
+ tags: ["alpha", "beta", "gamma", "delta", "epsilon"].slice(0, integer(1, 5)),
36
+ metadata: {
37
+ source: loremChunk(40),
38
+ ref: Math.random().toString(36).slice(2, 14),
39
+ ts: new Date().toISOString()
40
+ }
41
+ });
42
+ }
43
+ return arr;
44
+ };
45
+ }
46
+
47
+ // Generate a truly massive array of objects (way over 8KB)
48
+ function hugeArrayOfObjects() {
49
+ return bigArrayOfObjects(150)();
50
+ }
51
+
52
+ // Build a ton of flat properties to bloat row size toward 1MB
53
+ function manyProperties(count = 200) {
54
+ count = count * 2
55
+ const props = {};
56
+ for (let i = 0; i < count; i++) {
57
+ const type = i % 4;
58
+ if (type === 0) props[`str_prop_${i}`] = () => loremChunk(300);
59
+ else if (type === 1) props[`num_prop_${i}`] = () => integer(1, 999999);
60
+ else if (type === 2) props[`bool_prop_${i}`] = [true, false];
61
+ else props[`list_prop_${i}`] = () => Array.from({ length: integer(5, 20) }, () => loremChunk(50));
62
+ }
63
+ return props;
64
+ }
65
+
66
+ const seed = "too-big-" + Math.random().toString(36).slice(2, 8);
67
+
68
+ /** @type {import('../types').Dungeon} */
69
+ const config = {
70
+ token: "",
71
+ seed: seed,
72
+ numDays: 30,
73
+ numEvents: 1000,
74
+ numUsers: 50,
75
+ format: 'json',
76
+ region: "US",
77
+ hasAnonIds: false,
78
+ hasSessionIds: false,
79
+ batchSize: 10000,
80
+ hasAdSpend: false,
81
+ hasAvatar: false,
82
+ hasBrowser: false,
83
+ hasCampaigns: false,
84
+ hasIOSDevices: false,
85
+ hasLocation: false,
86
+ isAnonymous: true,
87
+ hasAndroidDevices: false,
88
+ hasDesktopDevices: false,
89
+ writeToDisk: true,
90
+ concurrency: 1,
91
+
92
+ events: [
93
+ {
94
+ // ~200 string/number/bool/list properties = fat rows approaching 1MB
95
+ event: "mega_row",
96
+ weight: 5,
97
+ properties: {
98
+ ...manyProperties(250),
99
+ // also throw in some big array-of-object columns
100
+ cart_items: bigArrayOfObjects(60),
101
+ order_history: bigArrayOfObjects(80),
102
+ }
103
+ },
104
+ {
105
+ // single event focused on huge array-of-object columns (well over 8KB each)
106
+ event: "array_bomb",
107
+ weight: 5,
108
+ properties: {
109
+ massive_list_a: bigArrayOfObjects(150),
110
+ massive_list_b: bigArrayOfObjects(150),
111
+ massive_list_c: bigArrayOfObjects(100),
112
+ some_prop: ["foo", "bar", "baz"],
113
+ another_prop: () => integer(1, 1000),
114
+ }
115
+ },
116
+ {
117
+ // moderate event with a mix of oversized columns
118
+ event: "chonky_boi",
119
+ weight: 3,
120
+ properties: {
121
+ ...manyProperties(100),
122
+ nested_blob: () => {
123
+ const obj = {};
124
+ for (let i = 0; i < 50; i++) {
125
+ obj[`key_${i}`] = {
126
+ value: loremChunk(200),
127
+ items: Array.from({ length: 20 }, (_, j) => ({
128
+ id: j,
129
+ data: loremChunk(100),
130
+ flag: Math.random() > 0.5
131
+ }))
132
+ };
133
+ }
134
+ return obj;
135
+ },
136
+ product_catalog: bigArrayOfObjects(120),
137
+ }
138
+ },
139
+ {
140
+ // normal-ish event for contrast
141
+ event: "smol_event",
142
+ weight: 2,
143
+ properties: {
144
+ color: ["red", "blue", "green"],
145
+ count: () => integer(1, 10),
146
+ }
147
+ }
148
+ ],
149
+
150
+ superProps: {},
151
+ userProps: {
152
+ name: () => `user_${Math.random().toString(36).slice(2, 8)}`,
153
+ },
154
+
155
+ scdProps: {},
156
+ mirrorProps: {},
157
+ lookupTables: [],
158
+ groupKeys: [],
159
+ groupProps: {},
160
+
161
+ hook: function (record, type, meta) {
162
+ return record;
163
+ }
164
+ };
165
+
166
+ export default config;
@@ -0,0 +1,220 @@
1
+ {
2
+ "events": [
3
+ {
4
+ "event": "account created",
5
+ "weight": 0,
6
+ "isFirstEvent": true,
7
+ "properties": {
8
+ "signup_source": ["organic", "referral", "demo_request", "product_hunt", "g2_review"],
9
+ "initial_plan": ["free_trial", "free_trial", "free_trial", "growth", "enterprise"]
10
+ }
11
+ },
12
+ {
13
+ "event": "agent deployed",
14
+ "weight": 3,
15
+ "properties": {
16
+ "agent_type": ["pre_call_researcher", "meeting_assistant", "follow_up_writer", "crm_updater", "pipeline_monitor", "outreach_agent"],
17
+ "configuration_time_min": {"$range": [2, 45]}
18
+ }
19
+ },
20
+ {
21
+ "event": "meeting joined",
22
+ "weight": 5,
23
+ "properties": {
24
+ "meeting_platform": ["zoom", "zoom", "google_meet", "google_meet", "teams", "webex"],
25
+ "meeting_type": ["discovery", "demo", "negotiation", "check_in", "onboarding", "qbr"],
26
+ "participants": {"$range": [2, 12]},
27
+ "duration_min": {"$range": [10, 90]}
28
+ }
29
+ },
30
+ {
31
+ "event": "meeting summary generated",
32
+ "weight": 5,
33
+ "properties": {
34
+ "summary_length_words": {"$range": [100, 800]},
35
+ "action_items_count": {"$range": [0, 8]},
36
+ "quality_score": {"$range": [50, 95]}
37
+ }
38
+ },
39
+ {
40
+ "event": "crm auto-updated",
41
+ "weight": 8,
42
+ "properties": {
43
+ "crm_platform": ["hubspot", "hubspot", "salesforce", "salesforce", "pipedrive"],
44
+ "fields_updated": {"$range": [1, 6]},
45
+ "update_type": ["contact_info", "deal_stage", "activity_log", "meeting_notes", "next_steps"]
46
+ }
47
+ },
48
+ {
49
+ "event": "follow-up drafted",
50
+ "weight": 4,
51
+ "properties": {
52
+ "personalization_score": {"$range": [30, 98]},
53
+ "template_used": ["post_meeting", "deal_nudge", "re_engagement", "intro", "proposal"],
54
+ "word_count": {"$range": [50, 400]}
55
+ }
56
+ },
57
+ {
58
+ "event": "follow-up sent",
59
+ "weight": 3,
60
+ "properties": {
61
+ "channel": ["email", "email", "email", "linkedin", "slack"],
62
+ "personalization_score": {"$range": [30, 98]},
63
+ "time_to_send_hr": {"$range": [0, 48]},
64
+ "email_outcome": ["pending", "pending", "pending", "opened", "opened", "clicked", "bounced", "replied"]
65
+ }
66
+ },
67
+ {
68
+ "event": "pipeline alert received",
69
+ "weight": 4,
70
+ "properties": {
71
+ "alert_type": ["deal_stalling", "champion_left", "competitor_mentioned", "no_activity", "budget_risk"],
72
+ "severity": ["low", "medium", "high", "critical"],
73
+ "deal_value": {"$range": [5000, 500000]}
74
+ }
75
+ },
76
+ {
77
+ "event": "deal stage changed",
78
+ "weight": 5,
79
+ "properties": {
80
+ "from_stage": ["lead", "qualified", "proposal", "negotiation", "verbal_commit"],
81
+ "to_stage": ["qualified", "proposal", "negotiation", "verbal_commit", "closed_won", "closed_lost"],
82
+ "days_in_previous_stage": {"$range": [1, 60]}
83
+ }
84
+ },
85
+ {
86
+ "event": "insight generated",
87
+ "weight": 6,
88
+ "properties": {
89
+ "insight_type": ["deal_risk", "buyer_intent", "competitive_intel", "engagement_trend", "revenue_forecast", "coaching_opportunity"],
90
+ "confidence_score": {"$range": [55, 98]},
91
+ "data_sources_used": {"$range": [1, 5]}
92
+ }
93
+ },
94
+ {
95
+ "event": "agent feedback",
96
+ "weight": 4,
97
+ "properties": {
98
+ "rating": [1, 2, 3, 3, 4, 4, 4, 5, 5, 5],
99
+ "feedback_type": ["accuracy", "relevance", "timeliness", "completeness", "tone"],
100
+ "agent_type": ["meeting_assistant", "follow_up_writer", "crm_updater", "pre_call_researcher"]
101
+ }
102
+ },
103
+ {
104
+ "event": "dashboard viewed",
105
+ "weight": 7,
106
+ "properties": {
107
+ "dashboard_type": ["pipeline_overview", "team_performance", "ai_agent_stats", "revenue_forecast", "activity_feed"],
108
+ "time_spent_sec": {"$range": [10, 300]}
109
+ }
110
+ },
111
+ {
112
+ "event": "integration connected",
113
+ "weight": 1,
114
+ "properties": {
115
+ "integration_name": ["hubspot", "salesforce", "slack", "google_calendar", "zoom", "linkedin", "gmail", "outlook"],
116
+ "setup_time_min": {"$range": [2, 30]}
117
+ }
118
+ },
119
+ {
120
+ "event": "search performed",
121
+ "weight": 3,
122
+ "properties": {
123
+ "search_type": ["deals", "contacts", "meetings", "insights", "activities"],
124
+ "results_count": {"$range": [0, 50]},
125
+ "clicked_result": [true, true, true, false]
126
+ }
127
+ },
128
+ {
129
+ "event": "workflow created",
130
+ "weight": 2,
131
+ "properties": {
132
+ "workflow_type": ["meeting_prep", "post_meeting", "deal_update", "weekly_digest", "pipeline_review"],
133
+ "steps_count": {"$range": [2, 8]},
134
+ "trigger_type": ["time_based", "event_based", "manual"]
135
+ }
136
+ },
137
+ {
138
+ "event": "contact enriched",
139
+ "weight": 5,
140
+ "properties": {
141
+ "enrichment_source": ["linkedin", "company_website", "crm_history", "email_threads", "meeting_notes"],
142
+ "fields_enriched": {"$range": [2, 12]},
143
+ "confidence_level": ["high", "high", "medium", "medium", "low"]
144
+ }
145
+ },
146
+ {
147
+ "event": "deal created",
148
+ "weight": 2,
149
+ "properties": {
150
+ "deal_source": ["inbound", "outbound", "referral", "upsell", "expansion"],
151
+ "estimated_value": {"$range": [5000, 250000]},
152
+ "close_date_days": {"$range": [14, 120]}
153
+ }
154
+ },
155
+ {
156
+ "event": "coaching tip viewed",
157
+ "weight": 3,
158
+ "properties": {
159
+ "tip_category": ["objection_handling", "discovery_questions", "closing_techniques", "rapport_building", "negotiation"],
160
+ "tip_relevance_score": {"$range": [40, 98]},
161
+ "applied": [true, false, false, false]
162
+ }
163
+ }
164
+ ],
165
+ "superProps": {
166
+ "subscription_tier": ["starter", "growth", "growth", "enterprise"],
167
+ "team_role": ["sales_rep", "sales_rep", "sales_rep", "sales_manager", "rev_ops", "marketing"],
168
+ "ai_agent_version": ["v1.0", "v1.0", "v1.5", "v1.5", "v1.5", "v2.0", "v2.0", "v2.0"]
169
+ },
170
+ "userProps": {
171
+ "company_size": ["SMB", "mid_market", "enterprise"],
172
+ "industry": ["SaaS", "fintech", "healthcare", "e_commerce", "consulting", "manufacturing"],
173
+ "deals_in_pipeline": {"$range": [2, 50]},
174
+ "onboarding_cohort": ["Q1_2025", "Q2_2025", "Q3_2025", "Q4_2025"],
175
+ "integrations_connected": {"$range": [1, 8]}
176
+ },
177
+ "funnels": [
178
+ {
179
+ "name": "Onboarding",
180
+ "sequence": ["account created", "integration connected", "agent deployed", "meeting joined"],
181
+ "isFirstFunnel": true,
182
+ "conversionRate": 65,
183
+ "timeToConvert": 48,
184
+ "order": "sequential",
185
+ "weight": 1
186
+ },
187
+ {
188
+ "name": "Meeting to Action",
189
+ "sequence": ["meeting joined", "meeting summary generated", "follow-up drafted", "follow-up sent"],
190
+ "conversionRate": 70,
191
+ "timeToConvert": 4,
192
+ "order": "sequential",
193
+ "weight": 8
194
+ },
195
+ {
196
+ "name": "Pipeline Management",
197
+ "sequence": ["pipeline alert received", "dashboard viewed", "deal stage changed"],
198
+ "conversionRate": 45,
199
+ "timeToConvert": 24,
200
+ "order": "sequential",
201
+ "weight": 5
202
+ },
203
+ {
204
+ "name": "AI Adoption",
205
+ "sequence": ["agent deployed", "insight generated", "agent feedback"],
206
+ "conversionRate": 55,
207
+ "timeToConvert": 72,
208
+ "order": "sequential",
209
+ "weight": 4
210
+ },
211
+ {
212
+ "name": "Value Realization",
213
+ "sequence": ["dashboard viewed", "workflow created", "agent deployed"],
214
+ "conversionRate": 40,
215
+ "timeToConvert": 96,
216
+ "order": "sequential",
217
+ "weight": 3
218
+ }
219
+ ]
220
+ }
@@ -1,10 +1,10 @@
1
1
 
2
2
  const SEED = "my-seed";
3
3
  import dayjs from 'dayjs';
4
- import utc from 'dayjs/plugin/utc';
4
+ import utc from 'dayjs/plugin/utc.js';
5
5
  dayjs.extend(utc);
6
6
  import 'dotenv/config';
7
- import * as u from '../lib/utils/utils.js';
7
+ import * as u from "../lib/utils/utils.js";
8
8
  import * as v from 'ak-tools';
9
9
  const chance = u.initChance(SEED);
10
10
  const num_users = 1000;
@@ -14,7 +14,7 @@ const days = 30;
14
14
 
15
15
  /** @type {Config} */
16
16
  const config = {
17
- token: "",
17
+ token: process.env.MASTER_PROJECT_TOKEN || "",
18
18
  seed: SEED,
19
19
  numDays: days,
20
20
  numEvents: num_users * 100,
@@ -33,6 +33,7 @@ const config = {
33
33
  hasAdSpend: true,
34
34
 
35
35
  hasAvatar: true,
36
+ makeChart: false,
36
37
 
37
38
  batchSize: 500_000,
38
39
  concurrency: 500,
package/index.js CHANGED
@@ -21,8 +21,6 @@ import { StorageManager } from './lib/core/storage.js';
21
21
  // Orchestrators
22
22
  import { userLoop } from './lib/orchestrators/user-loop.js';
23
23
  import { sendToMixpanel } from './lib/orchestrators/mixpanel-sender.js';
24
- import { handleCloudFunctionEntry } from './lib/orchestrators/worker-manager.js';
25
-
26
24
  // Generators
27
25
  import { makeAdSpend } from './lib/generators/adspend.js';
28
26
  import { makeMirror } from './lib/generators/mirror.js';
@@ -33,9 +31,9 @@ import { makeGroupProfile, makeProfile } from './lib/generators/profiles.js';
33
31
  // External dependencies
34
32
  import dayjs from "dayjs";
35
33
  import utc from "dayjs/plugin/utc.js";
36
- import functions from '@google-cloud/functions-framework';
37
- import { timer, sLog } from 'ak-tools';
34
+ import { timer } from 'ak-tools';
38
35
  import { existsSync } from 'fs';
36
+ import { dataLogger as logger } from './lib/utils/logger.js';
39
37
 
40
38
  // Initialize dayjs and time constants
41
39
  dayjs.extend(utc);
@@ -134,19 +132,23 @@ async function main(config) {
134
132
 
135
133
  }
136
134
 
137
- if (config.verbose) console.log(`\nšŸ”§ Configuring dungeon with seed: ${config.seed}`);
135
+ if (config.verbose) logger.info({ seed: config.seed }, 'Configuring dungeon');
138
136
  let validatedConfig;
139
137
  try {
140
138
  // Step 1: Validate and enrich configuration
141
139
  validatedConfig = validateDungeonConfig(config);
142
-
140
+
141
+ // Update FIXED_BEGIN based on configured numDays
142
+ const configNumDays = validatedConfig.numDays || 30;
143
+ global.FIXED_BEGIN = dayjs.unix(FIXED_NOW).subtract(configNumDays, 'd').unix();
144
+
143
145
  // Step 1.5: Display configuration summary (CLI mode only)
144
146
  if (isCLI && validatedConfig.verbose) {
145
147
  displayConfigurationSummary(validatedConfig);
146
148
  }
147
149
 
148
150
  // Step 2: Create context with validated config
149
- const context = createContext(validatedConfig, null, isCLI);
151
+ const context = createContext(validatedConfig);
150
152
 
151
153
  // Step 3: Initialize storage containers
152
154
  const storageManager = new StorageManager(context);
@@ -160,7 +162,7 @@ async function main(config) {
160
162
  await generateAdSpendData(context);
161
163
  }
162
164
 
163
- if (context.config.verbose) console.log(`\nšŸ”„ Starting user and event generation...\n`);
165
+ if (context.config.verbose) logger.info('Starting user and event generation...');
164
166
  // Step 5: Main user and event generation
165
167
  await userLoop(context);
166
168
 
@@ -184,27 +186,27 @@ async function main(config) {
184
186
  await makeMirror(context);
185
187
  }
186
188
 
187
- if (context.config.verbose) console.log(`\nāœ… Data generation completed successfully!\n`);
189
+ if (context.config.verbose) logger.info('Data generation completed successfully');
188
190
 
189
191
  // ! DATA GENERATION ENDS HERE
190
192
 
191
- // Step 10: Send to Mixpanel (if token provided)
192
- // IMPORTANT: Must happen BEFORE flushing to disk, because flush() clears the arrays
193
- let importResults;
194
- if (validatedConfig.token) {
195
- importResults = await sendToMixpanel(context);
196
- }
197
-
198
- // Step 11: Flush lookup tables to disk (always as CSVs)
193
+ // Step 10: Flush lookup tables to disk (always as CSVs)
199
194
  if (validatedConfig.writeToDisk) {
200
195
  await flushLookupTablesToDisk(storage, validatedConfig);
201
196
  }
202
197
 
203
- // Step 12: Flush other storage containers to disk (if writeToDisk enabled)
198
+ // Step 11: Flush other storage containers to disk (if writeToDisk enabled)
204
199
  if (validatedConfig.writeToDisk) {
205
200
  await flushStorageToDisk(storage, validatedConfig);
206
201
  }
207
202
 
203
+ // Step 12: Send to Mixpanel (if token provided)
204
+ // Now happens AFTER disk flush so batch files are available for import
205
+ let importResults;
206
+ if (validatedConfig.token) {
207
+ importResults = await sendToMixpanel(context);
208
+ }
209
+
208
210
  // Step 13: Compile results
209
211
  jobTimer.stop(false);
210
212
  const { start, end, delta, human } = jobTimer.report(false);
@@ -222,12 +224,7 @@ async function main(config) {
222
224
  };
223
225
 
224
226
  } catch (error) {
225
- if (validatedConfig?.verbose) {
226
- console.error(`\nāŒ Error: ${error.message}\n`);
227
- console.error(error.stack);
228
- } else {
229
- sLog("Main execution error", { error: error.message, stack: error.stack }, "ERROR");
230
- }
227
+ logger.error({ err: error }, `Error: ${error.message}`);
231
228
  throw error;
232
229
  }
233
230
  }
@@ -260,8 +257,8 @@ async function generateGroupProfiles(context) {
260
257
  const { config, storage } = context;
261
258
  const { groupKeys, groupProps = {} } = config;
262
259
 
263
- if (context.isCLI() || config.verbose) {
264
- console.log('\nšŸ‘„ Generating group profiles...');
260
+ if (config.verbose) {
261
+ logger.info('Generating group profiles...');
265
262
  }
266
263
 
267
264
  for (let i = 0; i < groupKeys.length; i++) {
@@ -273,8 +270,8 @@ async function generateGroupProfiles(context) {
273
270
  continue;
274
271
  }
275
272
 
276
- if (context.isCLI() || config.verbose) {
277
- console.log(` Creating ${groupCount.toLocaleString()} ${groupKey} profiles...`);
273
+ if (config.verbose) {
274
+ logger.info({ groupKey, groupCount }, `Creating ${groupCount.toLocaleString()} ${groupKey} profiles...`);
278
275
  }
279
276
 
280
277
  // Get group-specific props if available
@@ -282,15 +279,15 @@ async function generateGroupProfiles(context) {
282
279
 
283
280
  for (let j = 0; j < groupCount; j++) {
284
281
  const groupProfile = await makeGroupProfile(context, groupKey, specificGroupProps, {
285
- [groupKey]: `${groupKey}_${j + 1}`
282
+ [groupKey]: String(j + 1)
286
283
  });
287
284
 
288
285
  await groupContainer.hookPush(groupProfile);
289
286
  }
290
287
  }
291
288
 
292
- if (context.isCLI() || config.verbose) {
293
- console.log('āœ… Group profiles generated successfully');
289
+ if (config.verbose) {
290
+ logger.info('Group profiles generated successfully');
294
291
  }
295
292
  }
296
293
 
@@ -302,8 +299,8 @@ async function generateLookupTables(context) {
302
299
  const { config, storage } = context;
303
300
  const { lookupTables } = config;
304
301
 
305
- if (context.isCLI() || config.verbose) {
306
- console.log('\nšŸ” Generating lookup tables...');
302
+ if (config.verbose) {
303
+ logger.info('Generating lookup tables...');
307
304
  }
308
305
 
309
306
  for (let i = 0; i < lookupTables.length; i++) {
@@ -316,8 +313,8 @@ async function generateLookupTables(context) {
316
313
  continue;
317
314
  }
318
315
 
319
- if (context.isCLI() || config.verbose) {
320
- console.log(` Creating ${entries.toLocaleString()} ${key} lookup entries...`);
316
+ if (config.verbose) {
317
+ logger.info({ key, entries }, `Creating ${entries.toLocaleString()} ${key} lookup entries...`);
321
318
  }
322
319
 
323
320
  for (let j = 0; j < entries; j++) {
@@ -330,8 +327,8 @@ async function generateLookupTables(context) {
330
327
  }
331
328
  }
332
329
 
333
- if (context.isCLI() || config.verbose) {
334
- console.log('āœ… Lookup tables generated successfully');
330
+ if (config.verbose) {
331
+ logger.info('Lookup tables generated successfully');
335
332
  }
336
333
  }
337
334
 
@@ -343,8 +340,8 @@ async function generateGroupSCDs(context) {
343
340
  const { config, storage } = context;
344
341
  const { scdProps, groupKeys } = config;
345
342
 
346
- if (context.isCLI() || config.verbose) {
347
- console.log('\nšŸ“Š Generating group SCDs...');
343
+ if (config.verbose) {
344
+ logger.info('Generating group SCDs...');
348
345
  }
349
346
 
350
347
  // Import utilities and generators
@@ -366,13 +363,13 @@ async function generateGroupSCDs(context) {
366
363
  continue; // No SCDs for this group type
367
364
  }
368
365
 
369
- if (context.isCLI() || config.verbose) {
370
- console.log(` Generating SCDs for ${groupCount.toLocaleString()} ${groupKey} entities...`);
366
+ if (config.verbose) {
367
+ logger.info({ groupKey, groupCount }, `Generating SCDs for ${groupCount.toLocaleString()} ${groupKey} entities...`);
371
368
  }
372
369
 
373
370
  // Generate SCDs for each group entity
374
371
  for (let i = 0; i < groupCount; i++) {
375
- const groupId = `${groupKey}_${i + 1}`;
372
+ const groupId = String(i + 1);
376
373
 
377
374
  // Generate SCDs for this group entity
378
375
  for (const [scdKey, scdConfig] of Object.entries(groupSpecificSCDs)) {
@@ -408,8 +405,8 @@ async function generateGroupSCDs(context) {
408
405
  }
409
406
  }
410
407
 
411
- if (context.isCLI() || config.verbose) {
412
- console.log('āœ… Group SCDs generated successfully');
408
+ if (config.verbose) {
409
+ logger.info('Group SCDs generated successfully');
413
410
  }
414
411
  }
415
412
 
@@ -566,16 +563,6 @@ function extractStorageData(storage) {
566
563
  };
567
564
  }
568
565
 
569
- // Cloud Functions setup
570
- functions.http('entry', async (req, res) => {
571
- await handleCloudFunctionEntry(req, res, main);
572
- });
573
-
574
566
  // ES Module export
575
567
  export default main;
576
568
 
577
- // CommonJS compatibility
578
- if (typeof module !== 'undefined' && module.exports) {
579
- module.exports = main;
580
- }
581
-