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.

survey-generator.js 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. const fs = require('fs')
  2. const similarity = require('compute-cosine-similarity')
  3. const config = require('./data-generator/config.js')
  4. const magic = config.magic // Multiply cosine similary by this
  5. const not_important = config.scoreVals[0]
  6. const somewhat_important = config.scoreVals[1]
  7. const important = config.scoreVals[2]
  8. const very_important = config.scoreVals[3]
  9. const extremely_important = config.scoreVals[4]
  10. const mandatory = config.scoreVals[5]
  11. // -
  12. // 1440 - 2400 total range
  13. // 1440 - 1900 limited range
  14. // -
  15. // Reserved for REALLY important answers
  16. // like full-time vs part-time
  17. // and remote vs onsite only
  18. // 400 mandatory
  19. const importance = [
  20. not_important,
  21. somewhat_important,
  22. important,
  23. very_important,
  24. extremely_important,
  25. ]
  26. const langs = [
  27. 'javascript',
  28. 'python',
  29. 'ruby',
  30. 'erlang',
  31. 'haskall',
  32. 'php',
  33. 'swift',
  34. 'rust',
  35. 'objective-c',
  36. 'common lisp',
  37. 'java',
  38. 'perl',
  39. 'cobol',
  40. 'fortran',
  41. 'julia',
  42. 'c#',
  43. 'go',
  44. 'c++',
  45. ]
  46. const duration = ['full', 'part']
  47. const location = ['onsite', 'remote', 'flexible']
  48. const otherWeightLookup = {
  49. full: mandatory,
  50. part: mandatory,
  51. onsite: mandatory,
  52. remote: mandatory,
  53. flexible: mandatory,
  54. }
  55. const rand = max => {
  56. return Math.floor(Math.random() * max) < 1
  57. ? 1
  58. : Math.floor(Math.random() * max)
  59. }
  60. class DummyProfile {
  61. constructor() {
  62. this.id = null
  63. this.profileResponses = null
  64. this.langPref = null
  65. this.durationPref = null
  66. this.locationPref = null
  67. this.shouldDelete = false
  68. // profile is constructed of 12 answers, 2 for each dimension
  69. this.profileResponses = this.getRandomAnswers(12, importance)
  70. this.langPref = this.getRandomAnswers(rand(4), langs)
  71. this.durationPref = this.getRandomAnswers(1, duration)
  72. this.locationPref = this.getRandomAnswers(1, location)
  73. this.matchPref = null
  74. this.candidateIndex = 0
  75. this.otp = null
  76. this.forder = []
  77. }
  78. clearPlaceholderMatches() {
  79. this.matchPref = this.matchPref.filter(
  80. match => match.shouldDelete == false,
  81. )
  82. this.forder = this.forder.filter(
  83. potentialFiance => potentialFiance.shouldDelete == false,
  84. )
  85. if (this.otp.shouldDelete) {
  86. this.otp = null
  87. }
  88. // console.log(this.matchPref)
  89. }
  90. rank(p) {
  91. for (let i = 0; i < this.matchPref.length; i++)
  92. if (this.matchPref[i] === p) return i
  93. return this.matchPref.length + 1
  94. }
  95. prefers(p) {
  96. return this.rank(p) < this.rank(this.otp)
  97. }
  98. nextCandidate() {
  99. if (this.candidateIndex >= this.matchPref.length) return null
  100. return this.matchPref[this.candidateIndex++]
  101. }
  102. engageTo(p) {
  103. if (p.otp) p.otp.otp = null
  104. p.otp = this
  105. p.forder.unshift(this)
  106. if (this.otp) this.otp.otp = null
  107. this.otp = p
  108. this.forder.unshift(p)
  109. }
  110. // My stuff
  111. getRandomAnswers(count, options) {
  112. const answers = []
  113. for (let i = 0; i < count; i++) {
  114. const random = rand(options.length)
  115. answers.push(options[random])
  116. }
  117. return answers
  118. }
  119. }
  120. function engageEveryone(guys) {
  121. var done
  122. do {
  123. done = true
  124. guys.forEach(guy => {
  125. if (!guy.otp) {
  126. done = false
  127. const gal = guy.nextCandidate()
  128. if (!gal.otp || gal.prefers(guy)) guy.engageTo(gal)
  129. }
  130. })
  131. } while (!done)
  132. }
  133. const generateDummyProfiles = (count, startFrom) => {
  134. const profiles = []
  135. for (let i = 0; i < count; i++) {
  136. const dummyProfile = new DummyProfile()
  137. dummyProfile.id = startFrom + i + 1
  138. profiles.push(dummyProfile)
  139. }
  140. profiles.forEach(dummy => {
  141. dummy.profileResponses = dummy.profileResponses.map(
  142. (answer, response_key_id) => {
  143. const answerObj = {}
  144. // aka: id for the question we asked
  145. answerObj[response_key_id] = answer
  146. return answerObj
  147. },
  148. )
  149. })
  150. return profiles
  151. }
  152. const generatedSeekers = generateDummyProfiles(20, 0)
  153. const generatedProviders = generateDummyProfiles(5, generatedSeekers.length)
  154. const balanceSeekersAndProviders = (seekers, providers) => {
  155. let diff = 0
  156. let smallerList = null
  157. if (seekers.length < providers.length) {
  158. diff = providers.length - seekers.length
  159. smallerList = seekers
  160. } else {
  161. diff = seekers.length - providers.length
  162. smallerList = providers
  163. }
  164. let fillerId = seekers.length + providers.length
  165. for (let i = 0; i < diff; i++) {
  166. const filler = new DummyProfile()
  167. filler.id = fillerId + i + 1
  168. filler.profileResponses = filler.profileResponses.map(
  169. (answer, response_key_id) => {
  170. const answerObj = {}
  171. // aka: id for the question we asked
  172. answerObj[response_key_id] = answer
  173. return answerObj
  174. },
  175. )
  176. filler.shouldDelete = true
  177. smallerList.push(filler)
  178. }
  179. }
  180. const scoreMatch = (seeker, potentialMatch) => {
  181. const seekerResponseValues = seeker.profileResponses.map(res =>
  182. parseInt(Object.values(res)),
  183. )
  184. const potentialMatchResponseValues = potentialMatch.profileResponses.map(
  185. res => parseInt(Object.values(res)),
  186. )
  187. return Math.floor(
  188. similarity(seekerResponseValues, potentialMatchResponseValues) * magic,
  189. )
  190. }
  191. const compareProfile = (seeker, unorderedPotentialMatches) => {
  192. const scored = unorderedPotentialMatches
  193. .map(potentialMatch => {
  194. // add the match to object keyed by score
  195. return {
  196. profileMatchScore: scoreMatch(seeker, potentialMatch),
  197. profile: potentialMatch,
  198. }
  199. })
  200. .sort((a, b) => a.profileMatchScore - b.profileMatchScore)
  201. // return ordered by score
  202. return scored.map(profileScore => profileScore.profile)
  203. }
  204. balanceSeekersAndProviders(generatedSeekers, generatedProviders)
  205. // Score
  206. generatedSeekers.forEach(seeker => {
  207. seeker.matchPref = compareProfile(seeker, generatedProviders)
  208. })
  209. generatedProviders.forEach(provider => {
  210. provider.matchPref = compareProfile(provider, generatedSeekers)
  211. })
  212. // Everything balanced ready for stable marriage
  213. if (generatedSeekers.length == generatedProviders.length) {
  214. engageEveryone(generatedProviders)
  215. generatedSeekers.forEach(seeker => seeker.clearPlaceholderMatches())
  216. generatedProviders.forEach(provider => provider.clearPlaceholderMatches())
  217. console.log('\nuser:', generatedProviders[0].id, '| provider')
  218. console.log(
  219. 'otp',
  220. generatedProviders[0].otp ? generatedProviders[0].otp.id : null,
  221. 'rank',
  222. generatedProviders[0].otp
  223. ? generatedProviders[0].matchPref
  224. .map(m => m.id)
  225. .indexOf(generatedProviders[0].otp.id) + 1
  226. : generatedProviders[0].otp,
  227. )
  228. // console.log(generatedProviders[0].matchPref.map(m => m.id))
  229. console.log(generatedProviders[0])
  230. console.log('---')
  231. console.log('user:', generatedSeekers[0].id, '| seeker')
  232. console.log(
  233. 'otp',
  234. generatedSeekers[0].otp ? generatedSeekers[0].otp.id : null,
  235. 'rank',
  236. generatedSeekers[0].otp
  237. ? generatedSeekers[0].matchPref
  238. .map(m => m.id)
  239. .indexOf(generatedSeekers[0].otp.id) + 1
  240. : generatedSeekers[0].otp,
  241. )
  242. console.log(generatedSeekers[0])
  243. // console.log(generatedSeekers[0].matchPref.map(m => m.id))
  244. }