@ak--47/dungeon-master 1.0.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 +518 -0
- package/dungeons/array-of-object-lookup-schema.json +327 -0
- package/dungeons/array-of-object-lookup.js +220 -0
- package/dungeons/ecommerce-schema.json +462 -0
- package/dungeons/ecommerce.js +447 -0
- package/dungeons/education-schema.json +2409 -0
- package/dungeons/education.js +768 -0
- package/dungeons/fintech-schema.json +14034 -0
- package/dungeons/fintech.js +696 -0
- package/dungeons/foobar-schema.json +403 -0
- package/dungeons/foobar.js +296 -0
- package/dungeons/food-delivery-schema.json +192 -0
- package/dungeons/food-delivery.js +602 -0
- package/dungeons/food-schema.json +1152 -0
- package/dungeons/food.js +754 -0
- package/dungeons/gaming-schema.json +1270 -0
- package/dungeons/gaming.js +508 -0
- package/dungeons/insurance-application-schema.json +204 -0
- package/dungeons/insurance-application.js +605 -0
- package/dungeons/media-schema.json +906 -0
- package/dungeons/media.js +790 -0
- package/dungeons/retention-cadence-schema.json +78 -0
- package/dungeons/retention-cadence.js +244 -0
- package/dungeons/rpg-schema.json +4526 -0
- package/dungeons/rpg.js +919 -0
- package/dungeons/sanity-schema.json +255 -0
- package/dungeons/sanity.js +152 -0
- package/dungeons/sass-schema.json +1291 -0
- package/dungeons/sass.js +795 -0
- package/dungeons/scd-schema.json +919 -0
- package/dungeons/scd.js +277 -0
- package/dungeons/simple-schema.json +608 -0
- package/dungeons/simple.js +285 -0
- package/dungeons/simplest-schema.json +1418 -0
- package/dungeons/simplest.js +392 -0
- package/dungeons/social-schema.json +1118 -0
- package/dungeons/social.js +686 -0
- package/dungeons/text-generation-schema.json +3096 -0
- package/dungeons/text-generation.js +812 -0
- package/index.js +567 -0
- package/lib/core/config-validator.js +395 -0
- package/lib/core/context.js +204 -0
- package/lib/core/dungeon-loader.js +337 -0
- package/lib/core/storage.js +379 -0
- package/lib/generators/adspend.js +132 -0
- package/lib/generators/events.js +271 -0
- package/lib/generators/funnels.js +407 -0
- package/lib/generators/mirror.js +167 -0
- package/lib/generators/product-lookup.js +262 -0
- package/lib/generators/product-names.js +195 -0
- package/lib/generators/profiles.js +93 -0
- package/lib/generators/scd.js +124 -0
- package/lib/generators/text.js +1192 -0
- package/lib/orchestrators/mixpanel-sender.js +266 -0
- package/lib/orchestrators/user-loop.js +335 -0
- package/lib/templates/abbreviated.d.ts +169 -0
- package/lib/templates/defaults.js +1405 -0
- package/lib/templates/phrases.js +2526 -0
- package/lib/templates/schema.d.ts +173 -0
- package/lib/templates/soup-presets.js +188 -0
- package/lib/utils/function-registry.js +302 -0
- package/lib/utils/json-evaluator.js +172 -0
- package/lib/utils/logger.js +34 -0
- package/lib/utils/utils.js +1490 -0
- package/package.json +89 -0
- package/types.d.ts +865 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product Lookup Table Generator
|
|
3
|
+
* Creates a comprehensive product catalog with unique IDs
|
|
4
|
+
* @module product-lookup
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Chance from 'chance';
|
|
8
|
+
import { GENERATION_PATTERNS } from '../templates/phrases.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate a unique product lookup table
|
|
12
|
+
* @param {number} count - Number of products to generate
|
|
13
|
+
* @param {string} seed - Seed for reproducible results
|
|
14
|
+
* @returns {Map<string, Object>} Map of product_id to product details
|
|
15
|
+
*/
|
|
16
|
+
export function generateProductLookup(count = 10000, seed = 'product-catalog') {
|
|
17
|
+
const chance = new Chance(seed);
|
|
18
|
+
const productMap = new Map();
|
|
19
|
+
|
|
20
|
+
// Get all product categories
|
|
21
|
+
const categories = GENERATION_PATTERNS.store_products;
|
|
22
|
+
const categoryNames = Object.keys(categories);
|
|
23
|
+
|
|
24
|
+
// Flatten all products with their categories
|
|
25
|
+
const allProducts = [];
|
|
26
|
+
for (const [categoryKey, products] of Object.entries(categories)) {
|
|
27
|
+
const categoryName = getCategoryDisplayName(categoryKey);
|
|
28
|
+
products.forEach(productName => {
|
|
29
|
+
allProducts.push({
|
|
30
|
+
name: productName,
|
|
31
|
+
category: categoryName,
|
|
32
|
+
categoryKey: categoryKey
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Brand names for variety
|
|
38
|
+
const brands = [
|
|
39
|
+
"TechPro", "HomeEssentials", "FitLife", "StyleCo", "BeautyPlus",
|
|
40
|
+
"OfficeMax", "EcoGoods", "PremiumChoice", "ValueBrand", "GenericCo",
|
|
41
|
+
"ProGear", "UltraLife", "SmartHome", "ActiveWear", "LuxeBeauty",
|
|
42
|
+
"WorkSpace", "GreenLiving", "EliteChoice", "BudgetBuy", "QualityFirst",
|
|
43
|
+
"TechnoEdge", "HomeComfort", "FitnessPro", "FashionForward", "GlowUp",
|
|
44
|
+
"OfficeElite", "EarthFriendly", "PremiumPro", "SmartBuy", "TopTier",
|
|
45
|
+
"InnoTech", "CozyHome", "PowerFit", "TrendSet", "PureBeauty",
|
|
46
|
+
"DeskMaster", "NaturalChoice", "LuxuryLine", "BestValue", "PrimeSelect"
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Generate products
|
|
50
|
+
for (let i = 0; i < count; i++) {
|
|
51
|
+
const productId = `PROD-${String(i + 1).padStart(6, '0')}`;
|
|
52
|
+
|
|
53
|
+
// Select a base product (reuse and vary)
|
|
54
|
+
const baseProduct = chance.pickone(allProducts);
|
|
55
|
+
|
|
56
|
+
// Add variation to product name for uniqueness
|
|
57
|
+
let productName = baseProduct.name;
|
|
58
|
+
if (chance.bool({ likelihood: 30 })) {
|
|
59
|
+
// Add color/size/model variations
|
|
60
|
+
const variations = [
|
|
61
|
+
` - ${chance.pickone(['Black', 'White', 'Blue', 'Red', 'Gray', 'Silver', 'Gold', 'Rose Gold', 'Navy', 'Green'])}`,
|
|
62
|
+
` - ${chance.pickone(['Small', 'Medium', 'Large', 'X-Large', 'One Size'])}`,
|
|
63
|
+
` - ${chance.pickone(['Pro', 'Plus', 'Lite', 'Max', 'Mini', 'Ultra', 'Premium', 'Basic', 'Deluxe'])}`,
|
|
64
|
+
` (${chance.pickone(['2024 Model', '2025 Edition', 'Updated Version', 'Gen 2', 'v2.0', 'Series 2'])})`,
|
|
65
|
+
` - ${chance.integer({ min: 1, max: 50 })} Pack`
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
if (chance.bool({ likelihood: 60 })) {
|
|
69
|
+
productName += chance.pickone(variations);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Generate price based on category
|
|
74
|
+
const priceRange = getPriceRange(baseProduct.categoryKey);
|
|
75
|
+
const basePrice = chance.floating({
|
|
76
|
+
min: priceRange.min,
|
|
77
|
+
max: priceRange.max,
|
|
78
|
+
fixed: 2
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Generate rating
|
|
82
|
+
const rating = chance.weighted(
|
|
83
|
+
[5, 4.5, 4, 3.5, 3, 2.5, 2, 1.5, 1],
|
|
84
|
+
[35, 25, 20, 10, 5, 3, 1, 0.5, 0.5]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Stock levels
|
|
88
|
+
const stockLevel = chance.weighted(
|
|
89
|
+
[500, 250, 100, 50, 25, 0],
|
|
90
|
+
[40, 25, 20, 10, 4, 1]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const product = {
|
|
94
|
+
product_id: productId,
|
|
95
|
+
name: productName,
|
|
96
|
+
brand: chance.pickone(brands),
|
|
97
|
+
category: baseProduct.category,
|
|
98
|
+
subcategory: baseProduct.categoryKey,
|
|
99
|
+
price: basePrice,
|
|
100
|
+
msrp: chance.bool({ likelihood: 40 }) ? basePrice * chance.floating({ min: 1.1, max: 1.5, fixed: 2 }) : basePrice,
|
|
101
|
+
rating: rating,
|
|
102
|
+
review_count: chance.integer({ min: 0, max: 5000 }),
|
|
103
|
+
stock_level: stockLevel,
|
|
104
|
+
stock_status: getStockStatus(stockLevel),
|
|
105
|
+
sku: `SKU-${chance.string({ length: 8, pool: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' })}`,
|
|
106
|
+
weight_oz: chance.floating({ min: 0.5, max: 50, fixed: 2 }),
|
|
107
|
+
is_featured: chance.bool({ likelihood: 10 }),
|
|
108
|
+
is_bestseller: chance.bool({ likelihood: 15 }),
|
|
109
|
+
is_new_arrival: chance.bool({ likelihood: 20 }),
|
|
110
|
+
is_on_sale: chance.bool({ likelihood: 25 }),
|
|
111
|
+
discount_percent: chance.bool({ likelihood: 25 }) ? chance.integer({ min: 5, max: 50 }) : 0,
|
|
112
|
+
shipping_eligible: chance.bool({ likelihood: 90 }),
|
|
113
|
+
free_shipping: chance.bool({ likelihood: 40 }),
|
|
114
|
+
prime_eligible: chance.bool({ likelihood: 60 }),
|
|
115
|
+
returnable: chance.bool({ likelihood: 85 }),
|
|
116
|
+
warranty_months: chance.pickone([0, 3, 6, 12, 24, 36]),
|
|
117
|
+
release_date: chance.date({
|
|
118
|
+
year: chance.integer({ min: 2020, max: 2025 })
|
|
119
|
+
}).toISOString().split('T')[0],
|
|
120
|
+
supplier_id: `SUP-${chance.integer({ min: 1, max: 500 })}`,
|
|
121
|
+
tags: generateTags(baseProduct.categoryKey, chance)
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
productMap.set(productId, product);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return productMap;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Convert a product lookup Map to a plain object for export
|
|
132
|
+
*/
|
|
133
|
+
export function productLookupToObject(productMap) {
|
|
134
|
+
const obj = {};
|
|
135
|
+
for (const [id, product] of productMap.entries()) {
|
|
136
|
+
obj[id] = product;
|
|
137
|
+
}
|
|
138
|
+
return obj;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get a random product from the lookup
|
|
143
|
+
*/
|
|
144
|
+
export function getRandomProduct(productMap, chance) {
|
|
145
|
+
const keys = Array.from(productMap.keys());
|
|
146
|
+
const randomKey = chance.pickone(keys);
|
|
147
|
+
return productMap.get(randomKey);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get products by category
|
|
152
|
+
*/
|
|
153
|
+
export function getProductsByCategory(productMap, category) {
|
|
154
|
+
return Array.from(productMap.values()).filter(p => p.category === category);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get products by price range
|
|
159
|
+
*/
|
|
160
|
+
export function getProductsByPriceRange(productMap, minPrice, maxPrice) {
|
|
161
|
+
return Array.from(productMap.values()).filter(p => p.price >= minPrice && p.price <= maxPrice);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Search products by name
|
|
166
|
+
*/
|
|
167
|
+
export function searchProducts(productMap, query) {
|
|
168
|
+
const lowerQuery = query.toLowerCase();
|
|
169
|
+
return Array.from(productMap.values()).filter(p =>
|
|
170
|
+
p.name.toLowerCase().includes(lowerQuery) ||
|
|
171
|
+
p.brand.toLowerCase().includes(lowerQuery) ||
|
|
172
|
+
p.category.toLowerCase().includes(lowerQuery)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Helper functions
|
|
177
|
+
|
|
178
|
+
function getCategoryDisplayName(categoryKey) {
|
|
179
|
+
const displayNames = {
|
|
180
|
+
electronics: 'Electronics',
|
|
181
|
+
home_kitchen: 'Home & Kitchen',
|
|
182
|
+
clothing: 'Clothing',
|
|
183
|
+
beauty: 'Beauty & Personal Care',
|
|
184
|
+
sports: 'Sports & Outdoors',
|
|
185
|
+
books: 'Books & Media',
|
|
186
|
+
toys: 'Toys & Games',
|
|
187
|
+
pet_supplies: 'Pet Supplies',
|
|
188
|
+
office: 'Office Supplies',
|
|
189
|
+
baby: 'Baby Products',
|
|
190
|
+
automotive: 'Automotive',
|
|
191
|
+
garden: 'Garden & Outdoor'
|
|
192
|
+
};
|
|
193
|
+
return displayNames[categoryKey] || categoryKey;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getPriceRange(categoryKey) {
|
|
197
|
+
const ranges = {
|
|
198
|
+
electronics: { min: 9.99, max: 799.99 },
|
|
199
|
+
home_kitchen: { min: 4.99, max: 399.99 },
|
|
200
|
+
clothing: { min: 9.99, max: 199.99 },
|
|
201
|
+
beauty: { min: 3.99, max: 89.99 },
|
|
202
|
+
sports: { min: 7.99, max: 299.99 },
|
|
203
|
+
books: { min: 4.99, max: 49.99 },
|
|
204
|
+
toys: { min: 5.99, max: 149.99 },
|
|
205
|
+
pet_supplies: { min: 3.99, max: 99.99 },
|
|
206
|
+
office: { min: 1.99, max: 499.99 },
|
|
207
|
+
baby: { min: 4.99, max: 299.99 },
|
|
208
|
+
automotive: { min: 9.99, max: 199.99 },
|
|
209
|
+
garden: { min: 5.99, max: 249.99 }
|
|
210
|
+
};
|
|
211
|
+
return ranges[categoryKey] || { min: 9.99, max: 99.99 };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function getStockStatus(stockLevel) {
|
|
215
|
+
if (stockLevel === 0) return 'Out of Stock';
|
|
216
|
+
if (stockLevel < 10) return 'Low Stock';
|
|
217
|
+
if (stockLevel < 50) return 'Limited Stock';
|
|
218
|
+
return 'In Stock';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function generateTags(categoryKey, chance) {
|
|
222
|
+
const commonTags = ['popular', 'trending', 'recommended', 'verified', 'quality'];
|
|
223
|
+
const categoryTags = {
|
|
224
|
+
electronics: ['tech', 'gadget', 'smart', 'wireless', 'digital'],
|
|
225
|
+
home_kitchen: ['home', 'kitchen', 'cooking', 'storage', 'organization'],
|
|
226
|
+
clothing: ['fashion', 'apparel', 'style', 'comfort', 'casual'],
|
|
227
|
+
beauty: ['skincare', 'makeup', 'grooming', 'wellness', 'organic'],
|
|
228
|
+
sports: ['fitness', 'outdoor', 'active', 'training', 'exercise'],
|
|
229
|
+
books: ['reading', 'literature', 'educational', 'entertainment'],
|
|
230
|
+
toys: ['kids', 'fun', 'educational', 'creative', 'play'],
|
|
231
|
+
pet_supplies: ['pets', 'animals', 'care', 'feeding', 'play'],
|
|
232
|
+
office: ['work', 'productivity', 'organization', 'supplies'],
|
|
233
|
+
baby: ['infant', 'nursery', 'feeding', 'safety', 'care'],
|
|
234
|
+
automotive: ['car', 'vehicle', 'maintenance', 'accessories'],
|
|
235
|
+
garden: ['outdoor', 'plants', 'gardening', 'landscaping']
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const tags = [];
|
|
239
|
+
const catTags = categoryTags[categoryKey] || [];
|
|
240
|
+
|
|
241
|
+
// Add 2-4 category tags
|
|
242
|
+
for (let i = 0; i < chance.integer({ min: 2, max: 4 }); i++) {
|
|
243
|
+
const tag = chance.pickone(catTags);
|
|
244
|
+
if (!tags.includes(tag)) tags.push(tag);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Maybe add a common tag
|
|
248
|
+
if (chance.bool({ likelihood: 40 })) {
|
|
249
|
+
tags.push(chance.pickone(commonTags));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return tags;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export default {
|
|
256
|
+
generateProductLookup,
|
|
257
|
+
productLookupToObject,
|
|
258
|
+
getRandomProduct,
|
|
259
|
+
getProductsByCategory,
|
|
260
|
+
getProductsByPriceRange,
|
|
261
|
+
searchProducts
|
|
262
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E-commerce Product Name Generator
|
|
3
|
+
* Generates realistic product names for store inventories, reviews, and search queries
|
|
4
|
+
* @module product-names
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createTextGenerator } from './text.js';
|
|
8
|
+
|
|
9
|
+
// Product Review Generator - For customer reviews mentioning specific products
|
|
10
|
+
export const productReviewGen = createTextGenerator({
|
|
11
|
+
style: "review",
|
|
12
|
+
tone: "pos",
|
|
13
|
+
intensity: "medium",
|
|
14
|
+
formality: "casual",
|
|
15
|
+
keywords: {
|
|
16
|
+
products: [
|
|
17
|
+
// Electronics
|
|
18
|
+
"Wireless Bluetooth Headphones", "USB-C Charging Cable", "Portable Power Bank 20000mAh",
|
|
19
|
+
"Smart Watch Series 7", "Wireless Gaming Mouse", "4K Webcam Pro",
|
|
20
|
+
"Bluetooth Speaker Waterproof", "Ring Light for Streaming",
|
|
21
|
+
|
|
22
|
+
// Home & Kitchen
|
|
23
|
+
"Stainless Steel Cookware Set", "Air Fryer 6 Quart Digital", "Instant Pot 8 Quart",
|
|
24
|
+
"Coffee Maker Programmable", "Blender High Speed 1200W",
|
|
25
|
+
|
|
26
|
+
// Clothing
|
|
27
|
+
"Men's Cotton T-Shirt Basic", "Women's Yoga Pants High Waist", "Athletic Shorts",
|
|
28
|
+
"Sneakers Running Lightweight", "Fleece Jacket Zip Up"
|
|
29
|
+
],
|
|
30
|
+
features: [
|
|
31
|
+
"battery life", "sound quality", "build quality", "comfort", "durability",
|
|
32
|
+
"ease of use", "design", "value for money", "performance", "fit",
|
|
33
|
+
"material quality", "charging speed", "connectivity", "portability",
|
|
34
|
+
"warranty", "customer service", "packaging", "instructions",
|
|
35
|
+
"compatibility", "functionality", "aesthetics", "weight",
|
|
36
|
+
"size options", "color accuracy", "temperature control", "noise level"
|
|
37
|
+
],
|
|
38
|
+
positive_aspects: [
|
|
39
|
+
"works great", "perfect for my needs", "exceeded expectations",
|
|
40
|
+
"highly recommend", "best purchase ever", "worth every penny",
|
|
41
|
+
"can't live without it", "game changer", "solid quality",
|
|
42
|
+
"does exactly what it claims", "super reliable", "love it so far",
|
|
43
|
+
"impressed with quality", "fast shipping too", "great value"
|
|
44
|
+
],
|
|
45
|
+
negative_aspects: [
|
|
46
|
+
"a bit pricey", "could be better", "takes getting used to",
|
|
47
|
+
"wish it had more features", "broke after a month", "not as advertised",
|
|
48
|
+
"quality could be improved", "too bulky", "battery drains quickly",
|
|
49
|
+
"doesn't fit well", "cheaply made", "stopped working"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
mixedSentiment: true,
|
|
53
|
+
authenticityLevel: 0.6,
|
|
54
|
+
typos: true,
|
|
55
|
+
typoRate: 0.03,
|
|
56
|
+
min: 50,
|
|
57
|
+
max: 200,
|
|
58
|
+
includeMetadata: false
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Product Search Query Generator - For realistic search terms
|
|
62
|
+
export const productSearchGen = createTextGenerator({
|
|
63
|
+
style: "search",
|
|
64
|
+
tone: "neu",
|
|
65
|
+
formality: "casual",
|
|
66
|
+
keywords: {
|
|
67
|
+
products: [
|
|
68
|
+
"headphones", "charging cable", "power bank", "smart watch", "gaming mouse",
|
|
69
|
+
"webcam", "speaker", "ring light", "cookware set", "air fryer",
|
|
70
|
+
"instant pot", "coffee maker", "blender", "yoga pants", "running shoes",
|
|
71
|
+
"fleece jacket", "facial cleanser", "moisturizer", "shampoo", "toothbrush",
|
|
72
|
+
"yoga mat", "dumbbells", "water bottle", "camping tent", "sleeping bag",
|
|
73
|
+
"notebook", "pens", "desk chair", "monitor stand", "baby monitor"
|
|
74
|
+
],
|
|
75
|
+
attributes: [
|
|
76
|
+
"wireless", "bluetooth", "USB-C", "waterproof", "portable", "lightweight",
|
|
77
|
+
"rechargeable", "adjustable", "foldable", "compact", "ergonomic",
|
|
78
|
+
"stainless steel", "non-stick", "organic", "eco-friendly", "sustainable",
|
|
79
|
+
"durable", "heavy duty", "professional", "premium", "budget",
|
|
80
|
+
"best rated", "top seller", "on sale", "discounted", "clearance"
|
|
81
|
+
],
|
|
82
|
+
modifiers: [
|
|
83
|
+
"cheap", "affordable", "best", "good quality", "reliable",
|
|
84
|
+
"for beginners", "professional grade", "commercial", "industrial",
|
|
85
|
+
"for small spaces", "travel size", "family size", "bulk",
|
|
86
|
+
"name brand", "generic", "off brand", "authentic", "genuine",
|
|
87
|
+
"refurbished", "open box", "like new", "gently used"
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
typos: true,
|
|
91
|
+
typoRate: 0.05,
|
|
92
|
+
min: 2,
|
|
93
|
+
max: 40,
|
|
94
|
+
includeMetadata: false
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Product Comparison Generator - For side-by-side product comparisons
|
|
98
|
+
export const productComparisonGen = createTextGenerator({
|
|
99
|
+
style: "feedback",
|
|
100
|
+
tone: "neu",
|
|
101
|
+
formality: "business",
|
|
102
|
+
keywords: {
|
|
103
|
+
products: [
|
|
104
|
+
"Wireless Bluetooth Headphones", "Air Fryer 6 Quart Digital",
|
|
105
|
+
"Smart Watch Series 7", "Coffee Maker Programmable",
|
|
106
|
+
"Yoga Mat Non-Slip 6mm", "Instant Pot 8 Quart"
|
|
107
|
+
],
|
|
108
|
+
comparison_terms: [
|
|
109
|
+
"compared to", "versus", "better than", "not as good as",
|
|
110
|
+
"similar to", "different from", "cheaper alternative to",
|
|
111
|
+
"premium version of", "budget option for", "upgraded from"
|
|
112
|
+
],
|
|
113
|
+
metrics: [
|
|
114
|
+
"price point", "durability", "features", "warranty coverage",
|
|
115
|
+
"brand reputation", "user reviews", "availability",
|
|
116
|
+
"customer support", "build quality", "performance metrics"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
authenticityLevel: 0.7,
|
|
120
|
+
specificityLevel: 0.8,
|
|
121
|
+
min: 100,
|
|
122
|
+
max: 300,
|
|
123
|
+
includeMetadata: false
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Product Question Generator - For Q&A sections
|
|
127
|
+
export const productQuestionGen = createTextGenerator({
|
|
128
|
+
style: "forum",
|
|
129
|
+
tone: "neu",
|
|
130
|
+
formality: "casual",
|
|
131
|
+
keywords: {
|
|
132
|
+
products: [
|
|
133
|
+
"Smart Watch Series 7", "Air Fryer 6 Quart", "Wireless Headphones",
|
|
134
|
+
"Yoga Mat", "Power Bank", "Instant Pot", "Bluetooth Speaker"
|
|
135
|
+
],
|
|
136
|
+
question_starters: [
|
|
137
|
+
"Does this work with", "Is this compatible with", "Can I use this for",
|
|
138
|
+
"Will this fit", "How long does", "What's the difference between",
|
|
139
|
+
"Is this better than", "Should I get", "Any recommendations for",
|
|
140
|
+
"Has anyone tried", "Is it worth", "Does anyone know if"
|
|
141
|
+
],
|
|
142
|
+
concerns: [
|
|
143
|
+
"battery life", "warranty", "return policy", "shipping time",
|
|
144
|
+
"compatibility", "size", "color options", "durability",
|
|
145
|
+
"maintenance", "cleaning", "storage", "safety",
|
|
146
|
+
"noise level", "power consumption", "assembly required"
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
typos: true,
|
|
150
|
+
typoRate: 0.04,
|
|
151
|
+
min: 20,
|
|
152
|
+
max: 100,
|
|
153
|
+
includeMetadata: false
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Product Description Generator - For store listings
|
|
157
|
+
export const productDescriptionGen = createTextGenerator({
|
|
158
|
+
style: "feedback",
|
|
159
|
+
tone: "pos",
|
|
160
|
+
formality: "business",
|
|
161
|
+
keywords: {
|
|
162
|
+
products: [
|
|
163
|
+
"Premium Wireless Headphones", "Professional Air Fryer", "Advanced Smart Watch",
|
|
164
|
+
"Deluxe Coffee Maker", "Ultra Comfort Yoga Mat", "Multi-Function Instant Pot"
|
|
165
|
+
],
|
|
166
|
+
features: [
|
|
167
|
+
"state-of-the-art technology", "innovative design", "superior quality",
|
|
168
|
+
"advanced features", "user-friendly interface", "premium materials",
|
|
169
|
+
"long-lasting durability", "exceptional performance", "modern aesthetics",
|
|
170
|
+
"versatile functionality", "energy efficient", "space-saving design",
|
|
171
|
+
"easy maintenance", "comprehensive warranty", "fast setup",
|
|
172
|
+
"multiple settings", "customizable options", "safety certified"
|
|
173
|
+
],
|
|
174
|
+
benefits: [
|
|
175
|
+
"saves time", "improves efficiency", "enhances experience",
|
|
176
|
+
"provides convenience", "offers flexibility", "ensures reliability",
|
|
177
|
+
"delivers results", "maximizes value", "simplifies tasks",
|
|
178
|
+
"reduces effort", "increases productivity", "optimizes performance"
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
authenticityLevel: 0.5,
|
|
182
|
+
specificityLevel: 0.7,
|
|
183
|
+
min: 80,
|
|
184
|
+
max: 250,
|
|
185
|
+
includeMetadata: false
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Export all generators
|
|
189
|
+
export default {
|
|
190
|
+
productReviewGen,
|
|
191
|
+
productSearchGen,
|
|
192
|
+
productComparisonGen,
|
|
193
|
+
productQuestionGen,
|
|
194
|
+
productDescriptionGen
|
|
195
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile generator module
|
|
3
|
+
* Creates user and group profiles with realistic properties
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @typedef {import('../../types').Context} Context */
|
|
7
|
+
|
|
8
|
+
import * as u from "../utils/utils.js";
|
|
9
|
+
import { dataLogger as logger } from "../utils/logger.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a user or group profile by choosing from available property values
|
|
13
|
+
* @param {Context} context - Context object containing config, defaults, etc.
|
|
14
|
+
* @param {Object} props - Properties to include in the profile
|
|
15
|
+
* @param {Object} defaults - Default values to merge with props
|
|
16
|
+
* @returns {Promise<Object>} Generated profile object
|
|
17
|
+
*/
|
|
18
|
+
export async function makeProfile(context, props = {}, defaults = {}) {
|
|
19
|
+
// Update operation counter
|
|
20
|
+
context.incrementOperations();
|
|
21
|
+
|
|
22
|
+
// Keys that should not be processed with the choose function
|
|
23
|
+
const keysToNotChoose = ["anonymousIds", "sessionIds"];
|
|
24
|
+
|
|
25
|
+
// Start with defaults
|
|
26
|
+
const profile = { ...defaults };
|
|
27
|
+
|
|
28
|
+
// Process default values first
|
|
29
|
+
for (const key in profile) {
|
|
30
|
+
if (keysToNotChoose.includes(key)) continue;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
profile[key] = u.choose(profile[key]);
|
|
34
|
+
} catch (e) {
|
|
35
|
+
logger.error({ err: e, key }, `Error processing default property ${key}`);
|
|
36
|
+
// Keep original value on error
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Process provided props (these override defaults)
|
|
41
|
+
for (const key in props) {
|
|
42
|
+
try {
|
|
43
|
+
profile[key] = u.choose(props[key]);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
logger.error({ err: e, key }, `Error processing property ${key}`);
|
|
46
|
+
// Keep original value on error
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return profile;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a user profile with typical user properties
|
|
55
|
+
* @param {Context} context - Context object
|
|
56
|
+
* @param {Object} userProps - User-specific properties
|
|
57
|
+
* @param {Object} baseProfile - Base profile to extend
|
|
58
|
+
* @returns {Promise<Object>} Generated user profile
|
|
59
|
+
*/
|
|
60
|
+
export async function makeUserProfile(context, userProps = {}, baseProfile = {}) {
|
|
61
|
+
const { config } = context;
|
|
62
|
+
|
|
63
|
+
// Combine user props with any configured user properties
|
|
64
|
+
const combinedProps = {
|
|
65
|
+
...config.userProps,
|
|
66
|
+
...userProps
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return makeProfile(context, combinedProps, baseProfile);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Creates a group profile with group-specific properties
|
|
74
|
+
* @param {Context} context - Context object
|
|
75
|
+
* @param {string} groupKey - Group identifier
|
|
76
|
+
* @param {Object} groupProps - Group-specific properties
|
|
77
|
+
* @param {Object} baseProfile - Base profile to extend
|
|
78
|
+
* @returns {Promise<Object>} Generated group profile
|
|
79
|
+
*/
|
|
80
|
+
export async function makeGroupProfile(context, groupKey, groupProps = {}, baseProfile = {}) {
|
|
81
|
+
const { config } = context;
|
|
82
|
+
|
|
83
|
+
// Get group properties from config for this specific group
|
|
84
|
+
const configGroupProps = config.groupProps?.[groupKey] || {};
|
|
85
|
+
|
|
86
|
+
// Combine with provided props
|
|
87
|
+
const combinedProps = {
|
|
88
|
+
...configGroupProps,
|
|
89
|
+
...groupProps,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return makeProfile(context, combinedProps, baseProfile);
|
|
93
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slowly Changing Dimensions (SCD) generator module
|
|
3
|
+
* Creates time-series data showing how properties change over time
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @typedef {import('../../types').Context} Context */
|
|
7
|
+
|
|
8
|
+
import dayjs from "dayjs";
|
|
9
|
+
import * as u from "../utils/utils.js";
|
|
10
|
+
import { makeProfile } from "./profiles.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates SCD (Slowly Changing Dimensions) entries for a given property
|
|
14
|
+
* @param {Context} context - Context object containing config, defaults, etc.
|
|
15
|
+
* @param {Array|Object} scdProp - SCD property configuration or simple array of values
|
|
16
|
+
* @param {string} scdKey - Key name for the SCD property
|
|
17
|
+
* @param {string} distinct_id - User/entity identifier
|
|
18
|
+
* @param {number} mutations - Number of mutations to create
|
|
19
|
+
* @param {string | number} created - Creation timestamp for the entity
|
|
20
|
+
* @returns {Promise<Array>} Array of SCD entries
|
|
21
|
+
*/
|
|
22
|
+
export async function makeSCD(context, scdProp, scdKey, distinct_id, mutations, created) {
|
|
23
|
+
// Convert simple array to full configuration object
|
|
24
|
+
if (Array.isArray(scdProp)) {
|
|
25
|
+
scdProp = {
|
|
26
|
+
values: scdProp,
|
|
27
|
+
frequency: 'week',
|
|
28
|
+
max: 10,
|
|
29
|
+
timing: 'fuzzy',
|
|
30
|
+
type: 'user'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (typeof created === 'number') created = dayjs.unix(created).toISOString();
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
frequency = 'day', // Default to daily frequency
|
|
37
|
+
max = 10, // Default max mutations
|
|
38
|
+
timing = 'fuzzy', // Default to fuzzy timing
|
|
39
|
+
values,
|
|
40
|
+
type = "user"
|
|
41
|
+
} = scdProp;
|
|
42
|
+
|
|
43
|
+
// Return empty array if no values provided
|
|
44
|
+
if (!values || (Array.isArray(values) && values.length === 0) ||
|
|
45
|
+
(typeof values === 'object' && Object.keys(values).length === 0)) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const scdEntries = [];
|
|
50
|
+
let lastInserted = dayjs(created);
|
|
51
|
+
const now = dayjs();
|
|
52
|
+
const deltaDays = now.diff(lastInserted, "day");
|
|
53
|
+
const uuidKeyName = type === 'user' ? 'distinct_id' : type;
|
|
54
|
+
let lastStartTime = null; // Track for monotonic ordering
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < mutations; i++) {
|
|
57
|
+
// Stop if we've reached the current time
|
|
58
|
+
if (lastInserted.isAfter(now)) break;
|
|
59
|
+
|
|
60
|
+
// Create profile with the SCD property
|
|
61
|
+
const scd = await makeProfile(context, { [scdKey]: values }, { [uuidKeyName]: distinct_id });
|
|
62
|
+
|
|
63
|
+
// Create SCD entry with all required properties
|
|
64
|
+
const scdEntry = {
|
|
65
|
+
...scd,
|
|
66
|
+
[uuidKeyName]: scd.distinct_id || distinct_id,
|
|
67
|
+
startTime: null,
|
|
68
|
+
insertTime: null,
|
|
69
|
+
time: null // Required field for Mixpanel SCD import
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Set start time based on timing strategy
|
|
73
|
+
if (timing === 'fixed') {
|
|
74
|
+
switch (frequency) {
|
|
75
|
+
case "day":
|
|
76
|
+
scdEntry.startTime = lastInserted.add(1, "day").startOf('day').toISOString();
|
|
77
|
+
break;
|
|
78
|
+
case "week":
|
|
79
|
+
scdEntry.startTime = lastInserted.add(1, "week").startOf('week').toISOString();
|
|
80
|
+
break;
|
|
81
|
+
case "month":
|
|
82
|
+
scdEntry.startTime = lastInserted.add(1, "month").startOf('month').toISOString();
|
|
83
|
+
break;
|
|
84
|
+
case "year":
|
|
85
|
+
scdEntry.startTime = lastInserted.add(1, "year").startOf('year').toISOString();
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (timing === 'fuzzy') {
|
|
91
|
+
scdEntry.startTime = lastInserted.toISOString();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Enforce monotonic ordering — startTime must advance
|
|
95
|
+
if (lastStartTime && scdEntry.startTime && scdEntry.startTime <= lastStartTime) {
|
|
96
|
+
scdEntry.startTime = dayjs(lastStartTime).add(1, "second").toISOString();
|
|
97
|
+
}
|
|
98
|
+
lastStartTime = scdEntry.startTime;
|
|
99
|
+
|
|
100
|
+
// Set insert time (slightly after start time)
|
|
101
|
+
const insertTime = dayjs(scdEntry.startTime).add(u.integer(1, 9000), "seconds");
|
|
102
|
+
scdEntry.insertTime = insertTime.toISOString();
|
|
103
|
+
|
|
104
|
+
// Set the time field for Mixpanel SCD import (uses startTime)
|
|
105
|
+
scdEntry.time = scdEntry.startTime;
|
|
106
|
+
|
|
107
|
+
// Only add entry if all required properties are set
|
|
108
|
+
if (scdEntry.startTime && scdEntry.insertTime && scdEntry.time) {
|
|
109
|
+
scdEntries.push(scdEntry);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Advance time for next entry — scale step size to available range
|
|
113
|
+
if (deltaDays > 1) {
|
|
114
|
+
const advance = Math.max(1, u.integer(0, Math.ceil(deltaDays / mutations)));
|
|
115
|
+
lastInserted = dayjs(scdEntry.startTime).add(advance, "day");
|
|
116
|
+
} else {
|
|
117
|
+
// Short range: advance by hours instead of days
|
|
118
|
+
const advance = Math.max(1, u.integer(1, 12));
|
|
119
|
+
lastInserted = dayjs(scdEntry.startTime).add(advance, "hour");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return scdEntries;
|
|
124
|
+
}
|