Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

survey-generator.js 7.5KB

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