Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

survey-generator.js 7.4KB

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