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 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. const fs = require('fs')
  2. const cosineSimilarity = require('compute-cosine-similarity')
  3. const mockOutputPath = './db/_generated.js'
  4. // Insert here how many users you would like to generate:
  5. const total = 10
  6. let extraProfilesToGenerate = 0
  7. // Amount of responses for a complete survey
  8. const questions = 13
  9. // Seekers per 100 profiles
  10. const percentageOfSeekers = 90
  11. // Values for responses
  12. const possibleResponses = {
  13. not_important: '120',
  14. some_what_important: '140',
  15. important: '160',
  16. very_important: '180',
  17. extremely_important: '200',
  18. mandatory: '400',
  19. }
  20. const possibleZipcodes = [
  21. '90065', // Glassel
  22. '90012', // Chinatown
  23. '90240', // Downey
  24. '91030', // South Pasadena
  25. '91201', // Glendale
  26. '91399', // Woodland Hills
  27. '91401', // Van Nuys
  28. '90840', // Long Beach
  29. '91001', // Altadena
  30. '91011', // La Canada Flintridge
  31. '97075', // Beaverton
  32. ]
  33. // Helper functions
  34. const randomNumber = max => {
  35. return Math.floor(Math.random() * max) < 1
  36. ? 1
  37. : Math.floor(Math.random() * max)
  38. }
  39. const randomValFrom = arr => arr[randomNumber(arr.length)]
  40. const randomEmail = (length = 5) => {
  41. let chars =
  42. 'abcdefghijklmnopqrstuvwxyz-_abcdefghijklmnopqrstuvwxyz0123456789'
  43. let str = ''
  44. for (let i = 0; i < length + randomNumber(9); i++) {
  45. str += chars.charAt(Math.floor(Math.random() * chars.length))
  46. }
  47. const suffixs = [
  48. '@gmail.com',
  49. '@aol.com',
  50. '@yahoo.com',
  51. '@apple.com',
  52. '@hotmail.com',
  53. '@rocket-mail.com',
  54. '@mail.com',
  55. ]
  56. return str + randomValFrom(suffixs)
  57. }
  58. const randomName = (length = 4) => {
  59. let chars = 'aeiouaeiouabcdefghijklmnoprstuvwyabcdefghijklmnopqrstuvwxyz'
  60. let str = ''
  61. for (let i = 0; i < length + randomNumber(9); i++) {
  62. str += chars.charAt(Math.floor(Math.random() * chars.length))
  63. }
  64. return str
  65. }
  66. const generate = (classObj, amount, meta) => {
  67. const instances = []
  68. for (let i = 0; i < amount; i++) {
  69. instances.push(new classObj(i + 1, meta))
  70. }
  71. return instances
  72. }
  73. class User {
  74. constructor(id) {
  75. this.user_id = id
  76. this.user_name = ''
  77. this.user_email = ''
  78. this.is_admin = false
  79. this.is_poster = false
  80. }
  81. }
  82. class Profile {
  83. constructor(id, override) {
  84. this.user_id = override ? override.user_id : id
  85. this.profile_id = override ? override.profile_id + id : id
  86. }
  87. }
  88. class Response {
  89. constructor(id) {
  90. this.response_id = id
  91. this.profile_id = null
  92. this.response_key_id = null
  93. this.val = null
  94. }
  95. }
  96. const users = generate(User, total)
  97. users.forEach(user => {
  98. user.is_poster = randomNumber(100) > percentageOfSeekers ? 1 : 0
  99. if (user.is_poster) {
  100. extraProfilesToGenerate = extraProfilesToGenerate + randomNumber(2)
  101. }
  102. user.user_name = randomName() + ' ' + randomName()
  103. user.user_email = randomEmail()
  104. })
  105. let jobPosterIds = users
  106. .filter(user => user.is_poster > 0)
  107. .map(user => user.user_id)
  108. // Guarentee ONE job poster
  109. if (!jobPosterIds.length) {
  110. randomValFrom(users).is_poster = 1
  111. jobPosterIds = users
  112. .filter(user => user.is_poster > 0)
  113. .map(user => user.user_id)
  114. }
  115. const profiles = generate(Profile, total)
  116. // Generate extra job posting profiles
  117. // attributed to random user.is_poster === true
  118. // TODO: Clean this up. Hard to read...
  119. if (extraProfilesToGenerate > 0) {
  120. let extras = []
  121. for (let l = 0; l < extraProfilesToGenerate; l++) {
  122. const generatedExtraProfiles = generate(Profile, 1, {
  123. user_id:
  124. jobPosterIds.length > 1
  125. ? randomValFrom(jobPosterIds)
  126. : jobPosterIds[0],
  127. profile_id: profiles.length + l,
  128. })
  129. extras = [...extras, ...generatedExtraProfiles]
  130. }
  131. extras.forEach(profile => profiles.push(profile))
  132. }
  133. // Generate responses, then fill in details
  134. const responses = generate(
  135. Response,
  136. (total + extraProfilesToGenerate) * questions,
  137. )
  138. profiles.forEach((profile, i) => {
  139. const startingIndex = i * questions
  140. for (let k = 0; k < questions; k++) {
  141. const resToEdit = responses[startingIndex + k]
  142. resToEdit.response_key_id = k + 1
  143. resToEdit.profile_id = profile.profile_id
  144. resToEdit.val =
  145. k + 1 == questions
  146. ? randomValFrom(possibleZipcodes)
  147. : randomValFrom(Object.values(possibleResponses))
  148. }
  149. })
  150. /**
  151. * Score all the profiles!
  152. */
  153. const scoreResponses = (seeker, potentialMatch) => {
  154. const seekerResponses = responses
  155. .filter(response => response.profile_id == seeker.profile_id)
  156. .filter(response => response.val.length < 4)
  157. const potentialMatchResponses = responses
  158. .filter(response => response.profile_id == potentialMatch.profile_id)
  159. .filter(response => response.val.length < 4)
  160. const checkValCb = res => {
  161. const val = parseInt(res.val)
  162. return isNaN(val) ? 0 : val
  163. }
  164. return Math.floor(
  165. cosineSimilarity(
  166. seekerResponses.map(checkValCb),
  167. potentialMatchResponses.map(checkValCb),
  168. ) * 1000,
  169. )
  170. }
  171. const scoreProfile = (profile, potentialMatchList) => {
  172. const scored = potentialMatchList.map(profileToCompare => {
  173. return {
  174. match_queue_id: null,
  175. profile_id: profile.profile_id,
  176. target_id: profileToCompare.profile_id,
  177. is_deleted: false,
  178. score: scoreResponses(profile, profileToCompare),
  179. }
  180. })
  181. return scored.sort((a, b) => a.score - b.score)
  182. }
  183. const scoreAll = () => {
  184. let scores = []
  185. const posterProfiles = profiles.filter(profile =>
  186. jobPosterIds.includes(profile.user_id),
  187. )
  188. const seekerProfiles = profiles.filter(
  189. profile => !jobPosterIds.includes(profile.user_id),
  190. )
  191. seekerProfiles.forEach(seeker => {
  192. const scored = scoreProfile(seeker, posterProfiles)
  193. scores = [...scored, ...scores]
  194. })
  195. posterProfiles.forEach(poster => {
  196. const scored = scoreProfile(poster, seekerProfiles)
  197. scores = [...scored, ...scores]
  198. })
  199. return scores.reverse()
  200. }
  201. const match_queues = scoreAll().map((score, i) => {
  202. score.match_queue_id = i + 1
  203. // Comment out to see the score
  204. delete score.score
  205. return score
  206. })
  207. /**
  208. * Our output format
  209. */
  210. const outputDataObject = { users, profiles, responses, match_queues }
  211. const jobPostings = profiles.filter(profile =>
  212. jobPosterIds.includes(profile.user_id),
  213. ).length
  214. const jobPosters = users.filter(user => user.is_poster > 0).length
  215. const header = `/**
  216. * GENERATED MOCK SIIMEE DATA
  217. * Generated at: ${Date.now()}
  218. * ---
  219. * ${jobPostings} positions listed by ${jobPosters} job posters
  220. * ${total + extraProfilesToGenerate - jobPostings} candidate profiles by ${
  221. total + extraProfilesToGenerate - jobPostings
  222. } job seekers
  223. * ---
  224. * ${total + extraProfilesToGenerate} Profiles
  225. * ${total} Users
  226. */
  227. `
  228. const write = async () => {
  229. await fs.writeFile(mockOutputPath, '', () => {})
  230. fs.appendFile(
  231. mockOutputPath,
  232. header + 'module.exports = ' + JSON.stringify(outputDataObject),
  233. err => {
  234. if (err) {
  235. console.error(err)
  236. return
  237. }
  238. },
  239. )
  240. }
  241. write()