You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

generator.js 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. const fs = require('fs')
  2. const cosineSimilarity = require('compute-cosine-similarity')
  3. const mockOutputPath = './db/generated/_batch'
  4. const magic = 1000
  5. // Insert here how many users you would like to generate:
  6. const total = 1000
  7. const batchSize = 100
  8. let batchCount = 1 // Counter to track how many things we've generated
  9. let extraProfilesToGenerate = 0
  10. let extraProfileCount = 0 // Counter to track how many EXTRA profiles we've generated
  11. let generatedResponseCount = 0
  12. // Amount of responses for a complete survey
  13. const questions = 13
  14. // Seekers per 100 profiles
  15. const percentageOfSeekers = 90
  16. const scoreVals = [100, 140, 180, 220, 260, 400]
  17. // Values for responsess as strings
  18. const possibleResponses = {
  19. not_important: null,
  20. some_what_important: null,
  21. important: null,
  22. very_important: null,
  23. extremely_important: null,
  24. mandatory: null,
  25. }
  26. for (let i = 0; i < Object.keys(possibleResponses).length; i++) {
  27. const key = Object.keys(possibleResponses)[i]
  28. possibleResponses[key] = scoreVals[i].toString()
  29. }
  30. /**
  31. * Our initial file setup
  32. */
  33. const header = `/**
  34. * GENERATED MOCK SIIMEE DATA
  35. * Generated at: ${Date.now()}
  36. */
  37. `
  38. const write = async (batchNum, outputDataObject) => {
  39. await fs.writeFile(`${mockOutputPath}_${batchNum}.js`, '', () => {})
  40. fs.appendFile(
  41. `${mockOutputPath}_${batchNum}.js`,
  42. header + 'module.exports = ' + JSON.stringify(outputDataObject),
  43. err => {
  44. if (err) {
  45. console.error(err)
  46. return
  47. }
  48. },
  49. )
  50. }
  51. /**
  52. * [100, 140, 180, 220, 260, 400]
  53. */
  54. const preComputedScores = {
  55. 100: {
  56. 100: 0,
  57. 140: 0,
  58. 180: 0,
  59. 220: 0,
  60. 260: 0,
  61. 400: 0,
  62. },
  63. 140: {
  64. 100: 0,
  65. 140: 0,
  66. 180: 0,
  67. 220: 0,
  68. 260: 0,
  69. 400: 0,
  70. },
  71. 180: {
  72. 100: 0,
  73. 140: 0,
  74. 180: 0,
  75. 220: 0,
  76. 260: 0,
  77. 400: 0,
  78. },
  79. 220: {
  80. 100: 0,
  81. 140: 0,
  82. 180: 0,
  83. 220: 0,
  84. 260: 0,
  85. 400: 0,
  86. },
  87. 260: {
  88. 100: 0,
  89. 140: 0,
  90. 180: 0,
  91. 220: 0,
  92. 260: 0,
  93. 400: 0,
  94. },
  95. 400: {
  96. 100: 0,
  97. 140: 0,
  98. 180: 0,
  99. 220: 0,
  100. 260: 0,
  101. 400: 0,
  102. },
  103. }
  104. const score2d = (a, b) => {
  105. const aScorePlusBase = [100]
  106. const bScorePlusBase = [100]
  107. aScorePlusBase.push(a)
  108. bScorePlusBase.push(b)
  109. return Math.round(
  110. Math.pow(cosineSimilarity(aScorePlusBase, bScorePlusBase), 10) * magic,
  111. )
  112. }
  113. scoreVals.forEach(val => {
  114. scoreVals.forEach(v => {
  115. preComputedScores[val][v] = score2d(val, v)
  116. })
  117. })
  118. const possibleZipcodes = [
  119. '90065', // Glassel
  120. '90012', // Chinatown
  121. '90240', // Downey
  122. '91030', // South Pasadena
  123. '91201', // Glendale
  124. '91399', // Woodland Hills
  125. '91401', // Van Nuys
  126. '90840', // Long Beach
  127. '91001', // Altadena
  128. '91011', // La Canada Flintridge
  129. '97075', // Beaverton
  130. ]
  131. // Helper functions
  132. const randomNumber = max => {
  133. return Math.floor(Math.random() * max) < 1
  134. ? 1
  135. : Math.floor(Math.random() * max)
  136. }
  137. const randomValFrom = arr => arr[randomNumber(arr.length)]
  138. const randomEmail = (length = 5) => {
  139. let chars =
  140. 'abcdefghijklmnopqrstuvwxyz-_abcdefghijklmnopqrstuvwxyz0123456789'
  141. let str = ''
  142. for (let i = 0; i < length + randomNumber(9); i++) {
  143. str += chars.charAt(Math.floor(Math.random() * chars.length))
  144. }
  145. const suffixs = [
  146. '@gmail.com',
  147. '@aol.com',
  148. '@yahoo.com',
  149. '@apple.com',
  150. '@hotmail.com',
  151. '@rocket-mail.com',
  152. '@mail.com',
  153. ]
  154. return str + randomValFrom(suffixs)
  155. }
  156. const randomName = (length = 4) => {
  157. let chars = 'aeiouaeiouabcdefghijklmnoprstuvwyabcdefghijklmnopqrstuvwxyz'
  158. let str = ''
  159. for (let i = 0; i < length + randomNumber(9); i++) {
  160. str += chars.charAt(Math.floor(Math.random() * chars.length))
  161. }
  162. return str
  163. }
  164. const generate = (classObj, amount, meta) => {
  165. const instances = []
  166. for (let i = 0; i < amount; i++) {
  167. let startFrom = meta?.starting ? meta.starting - batchSize : 0
  168. instances.push(new classObj(i + startFrom + 1, meta))
  169. }
  170. return instances
  171. }
  172. class User {
  173. constructor(id) {
  174. this.user_id = id
  175. this.user_name = ''
  176. this.user_email = ''
  177. this.is_admin = false
  178. this.is_poster = false
  179. }
  180. }
  181. class Profile {
  182. constructor(id, override) {
  183. this.user_id = override?.user_id ? override.user_id : id
  184. this.profile_id = override?.profile_id ? override.profile_id + id : id
  185. }
  186. }
  187. class Response {
  188. constructor(id) {
  189. this.response_id = id
  190. this.profile_id = null
  191. this.response_key_id = null
  192. this.val = null
  193. }
  194. }
  195. console.log('\nStarting...\n---')
  196. for (let batch = batchSize; batch <= total; batch += batchSize) {
  197. /**
  198. * Generate Users
  199. */
  200. let users = generate(User, batchSize, {
  201. starting: batchSize * batchCount,
  202. })
  203. users.forEach(user => {
  204. user.is_poster = randomNumber(100) > percentageOfSeekers ? 1 : 0
  205. if (user.is_poster) {
  206. extraProfilesToGenerate = extraProfilesToGenerate + randomNumber(2)
  207. }
  208. user.user_name = randomName() + ' ' + randomName()
  209. user.user_email = randomEmail()
  210. })
  211. let jobPosterIds = users
  212. .filter(user => user.is_poster > 0)
  213. .map(user => user.user_id)
  214. console.log('COMPLETED: Generated Users...')
  215. // Guarentee ONE job poster
  216. if (!jobPosterIds.length) {
  217. randomValFrom(users).is_poster = 1
  218. jobPosterIds = users
  219. .filter(user => user.is_poster > 0)
  220. .map(user => user.user_id)
  221. }
  222. /**
  223. * Generate Profiles
  224. */
  225. let profiles = generate(Profile, batchSize, {
  226. starting: batchSize * batchCount,
  227. profile_id: extraProfileCount,
  228. })
  229. // Generate extra job posting profiles
  230. // attributed to random user.is_poster === true
  231. // TODO: Clean this up. Hard to read...
  232. if (extraProfilesToGenerate > 0) {
  233. let extras = []
  234. for (let l = 0; l < extraProfilesToGenerate; l++) {
  235. const generatedExtraProfiles = generate(Profile, 1, {
  236. user_id:
  237. jobPosterIds.length > 1
  238. ? randomValFrom(jobPosterIds)
  239. : jobPosterIds[0],
  240. profile_id: batchSize * batchCount + extraProfileCount + l,
  241. })
  242. extras = [...extras, ...generatedExtraProfiles]
  243. }
  244. extras.forEach(profile => {
  245. profiles.push(profile)
  246. extraProfileCount++
  247. })
  248. }
  249. console.log('COMPLETED: Generated Profiles...')
  250. /**
  251. * Generate Responses
  252. */
  253. // Generate responses first, before filling in details
  254. let responses = generate(
  255. Response,
  256. (batchSize + extraProfilesToGenerate) * questions,
  257. { starting: generatedResponseCount + batchSize },
  258. )
  259. profiles.forEach((profile, i) => {
  260. const startingIndex = i * questions
  261. for (let k = 0; k < questions; k++) {
  262. const resToEdit = responses[startingIndex + k]
  263. resToEdit.response_key_id = k + 1
  264. resToEdit.profile_id = profile.profile_id
  265. resToEdit.val =
  266. k + 1 == questions
  267. ? randomValFrom(possibleZipcodes)
  268. : randomValFrom(Object.values(possibleResponses))
  269. }
  270. })
  271. generatedResponseCount = generatedResponseCount + responses.length
  272. console.log('COMPLETED: Generated Responses...')
  273. /**
  274. * Our output format
  275. */
  276. const outputDataObject = { users, profiles, responses }
  277. // const outputDataObject = { users, profiles, responses, match_queues }
  278. write(batchSize * batchCount, outputDataObject)
  279. batchCount++
  280. }
  281. // /**
  282. // * Score all the profiles!
  283. // */
  284. // const compareProfileResponses = (seeker, potentialMatch) => {
  285. // const checkValCb = res => {
  286. // const val = parseInt(res.val)
  287. // return isNaN(val) ? 0 : val
  288. // }
  289. // const filterBy = idToCheckFor => {
  290. // return responses
  291. // .filter(
  292. // response =>
  293. // response.profile_id == idToCheckFor &&
  294. // response.val.length < 4,
  295. // )
  296. // .map(checkValCb)
  297. // }
  298. // const seekerResponses = filterBy(seeker.profile_id)
  299. // const potentialMatchResponses = filterBy(potentialMatch.profile_id)
  300. // const cachedScores = []
  301. // seekerResponses.forEach(seekerResponse => {
  302. // potentialMatchResponses.forEach(potentialResponse => {
  303. // cachedScores.push(
  304. // preComputedScores[seekerResponse][potentialResponse],
  305. // )
  306. // })
  307. // })
  308. // return Math.round(
  309. // cachedScores.reduce((a, b) => a + b) / cachedScores.length,
  310. // )
  311. // }
  312. // const scoreProfile = (profile, potentialMatchList) => {
  313. // return potentialMatchList
  314. // .map(profileToCompare => {
  315. // return {
  316. // match_queue_id: null,
  317. // profile_id: profile.profile_id,
  318. // target_id: profileToCompare.profile_id,
  319. // is_deleted: false,
  320. // score: compareProfileResponses(profile, profileToCompare),
  321. // }
  322. // })
  323. // .sort((a, b) => a.score - b.score)
  324. // }
  325. // const scoreAll = () => {
  326. // process.stdout.write('\nScoring Profiles')
  327. // let scores = []
  328. // const posterProfiles = profiles.filter(profile =>
  329. // jobPosterIds.includes(profile.user_id),
  330. // )
  331. // const seekerProfiles = profiles.filter(
  332. // profile => !jobPosterIds.includes(profile.user_id),
  333. // )
  334. // process.stdout.write('.')
  335. // for (let i = 0; i < seekerProfiles.length; i++) {
  336. // const scored = scoreProfile(seekerProfiles[i], posterProfiles)
  337. // scores.push(...scored)
  338. // }
  339. // process.stdout.write('.')
  340. // for (let j = 0; j < posterProfiles.length; j++) {
  341. // const scored = scoreProfile(posterProfiles[j], seekerProfiles)
  342. // scores.push(...scored)
  343. // }
  344. // process.stdout.write('.')
  345. // console.log('\n\nCOMPLETED: Scoring Profiles...')
  346. // return scores.reverse()
  347. // }
  348. // const match_queues = scoreAll().map((score, i) => {
  349. // score.match_queue_id = i + 1
  350. // // Comment out next line to see the scores
  351. // delete score.score
  352. // return score
  353. // })
  354. console.log('---\nFINISHED...\n===\n')