| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- const fs = require('fs')
- const similarity = require('compute-cosine-similarity')
- const config = require('./data-generator/config.js')
-
- const magic = config.magic // Multiply cosine similary by this
-
- const not_important = config.scoreVals[0]
- const somewhat_important = config.scoreVals[1]
- const important = config.scoreVals[2]
- const very_important = config.scoreVals[3]
- const extremely_important = config.scoreVals[4]
- const mandatory = config.scoreVals[5]
-
- // -
- // 1440 - 2400 total range
- // 1440 - 1900 limited range
- // -
- // Reserved for REALLY important answers
- // like full-time vs part-time
- // and remote vs onsite only
- // 400 mandatory
-
- const importance = [
- not_important,
- somewhat_important,
- important,
- very_important,
- extremely_important,
- ]
-
- const langs = [
- 'javascript',
- 'python',
- 'ruby',
- 'erlang',
- 'haskall',
- 'php',
- 'swift',
- 'rust',
- 'objective-c',
- 'common lisp',
- 'java',
- 'perl',
- 'cobol',
- 'fortran',
- 'julia',
- 'c#',
- 'go',
- 'c++',
- ]
- const duration = ['full', 'part']
- const location = ['onsite', 'remote', 'flexible']
-
- const otherWeightLookup = {
- full: mandatory,
- part: mandatory,
- onsite: mandatory,
- remote: mandatory,
- flexible: mandatory,
- }
- const rand = max => {
- return Math.floor(Math.random() * max) < 1
- ? 1
- : Math.floor(Math.random() * max)
- }
-
- class DummyProfile {
- constructor() {
- this.id = null
- this.profileResponses = null
- this.langPref = null
- this.durationPref = null
- this.locationPref = null
-
- this.shouldDelete = false
-
- // profile is constructed of 12 answers, 2 for each dimension
- this.profileResponses = this.getRandomAnswers(12, importance)
- this.langPref = this.getRandomAnswers(rand(4), langs)
- this.durationPref = this.getRandomAnswers(1, duration)
- this.locationPref = this.getRandomAnswers(1, location)
-
- this.matchPref = null
-
- this.candidateIndex = 0
- this.otp = null
- this.forder = []
- }
- clearPlaceholderMatches() {
- this.matchPref = this.matchPref.filter(
- match => match.shouldDelete == false,
- )
- this.forder = this.forder.filter(
- potentialFiance => potentialFiance.shouldDelete == false,
- )
- if (this.otp.shouldDelete) {
- this.otp = null
- }
- // console.log(this.matchPref)
- }
- rank(p) {
- for (let i = 0; i < this.matchPref.length; i++)
- if (this.matchPref[i] === p) return i
- return this.matchPref.length + 1
- }
- prefers(p) {
- return this.rank(p) < this.rank(this.otp)
- }
- nextCandidate() {
- if (this.candidateIndex >= this.matchPref.length) return null
- return this.matchPref[this.candidateIndex++]
- }
- engageTo(p) {
- if (p.otp) p.otp.otp = null
- p.otp = this
- p.forder.unshift(this)
- if (this.otp) this.otp.otp = null
- this.otp = p
- this.forder.unshift(p)
- }
- // My stuff
- getRandomAnswers(count, options) {
- const answers = []
- for (let i = 0; i < count; i++) {
- const random = rand(options.length)
- answers.push(options[random])
- }
- return answers
- }
- }
-
- function engageEveryone(guys) {
- var done
- do {
- done = true
- guys.forEach(guy => {
- if (!guy.otp) {
- done = false
- const gal = guy.nextCandidate()
- if (!gal.otp || gal.prefers(guy)) guy.engageTo(gal)
- }
- })
- } while (!done)
- }
- const generateDummyProfiles = (count, startFrom) => {
- const profiles = []
- for (let i = 0; i < count; i++) {
- const dummyProfile = new DummyProfile()
- dummyProfile.id = startFrom + i + 1
- profiles.push(dummyProfile)
- }
- profiles.forEach(dummy => {
- dummy.profileResponses = dummy.profileResponses.map(
- (answer, response_key_id) => {
- const answerObj = {}
- // aka: id for the question we asked
- answerObj[response_key_id] = answer
- return answerObj
- },
- )
- })
- return profiles
- }
-
- const generatedSeekers = generateDummyProfiles(20, 0)
- const generatedProviders = generateDummyProfiles(5, generatedSeekers.length)
- const balanceSeekersAndProviders = (seekers, providers) => {
- let diff = 0
- let smallerList = null
-
- if (seekers.length < providers.length) {
- diff = providers.length - seekers.length
- smallerList = seekers
- } else {
- diff = seekers.length - providers.length
- smallerList = providers
- }
- let fillerId = seekers.length + providers.length
- for (let i = 0; i < diff; i++) {
- const filler = new DummyProfile()
- filler.id = fillerId + i + 1
- filler.profileResponses = filler.profileResponses.map(
- (answer, response_key_id) => {
- const answerObj = {}
- // aka: id for the question we asked
- answerObj[response_key_id] = answer
- return answerObj
- },
- )
- filler.shouldDelete = true
- smallerList.push(filler)
- }
- }
-
- const scoreMatch = (seeker, potentialMatch) => {
- const seekerResponseValues = seeker.profileResponses.map(res =>
- parseInt(Object.values(res)),
- )
- const potentialMatchResponseValues = potentialMatch.profileResponses.map(
- res => parseInt(Object.values(res)),
- )
- return Math.floor(
- similarity(seekerResponseValues, potentialMatchResponseValues) * magic,
- )
- }
- const compareProfile = (seeker, unorderedPotentialMatches) => {
- const scored = unorderedPotentialMatches
- .map(potentialMatch => {
- // add the match to object keyed by score
- return {
- profileMatchScore: scoreMatch(seeker, potentialMatch),
- profile: potentialMatch,
- }
- })
- .sort((a, b) => a.profileMatchScore - b.profileMatchScore)
- // return ordered by score
- return scored.map(profileScore => profileScore.profile)
- }
-
- balanceSeekersAndProviders(generatedSeekers, generatedProviders)
-
- // Score
- generatedSeekers.forEach(seeker => {
- seeker.matchPref = compareProfile(seeker, generatedProviders)
- })
- generatedProviders.forEach(provider => {
- provider.matchPref = compareProfile(provider, generatedSeekers)
- })
-
- // Everything balanced ready for stable marriage
- if (generatedSeekers.length == generatedProviders.length) {
- engageEveryone(generatedProviders)
- generatedSeekers.forEach(seeker => seeker.clearPlaceholderMatches())
- generatedProviders.forEach(provider => provider.clearPlaceholderMatches())
-
- console.log('\nuser:', generatedProviders[0].id, '| provider')
- console.log(
- 'otp',
- generatedProviders[0].otp ? generatedProviders[0].otp.id : null,
- 'rank',
- generatedProviders[0].otp
- ? generatedProviders[0].matchPref
- .map(m => m.id)
- .indexOf(generatedProviders[0].otp.id) + 1
- : generatedProviders[0].otp,
- )
- // console.log(generatedProviders[0].matchPref.map(m => m.id))
- console.log(generatedProviders[0])
-
- console.log('---')
-
- console.log('user:', generatedSeekers[0].id, '| seeker')
- console.log(
- 'otp',
- generatedSeekers[0].otp ? generatedSeekers[0].otp.id : null,
- 'rank',
- generatedSeekers[0].otp
- ? generatedSeekers[0].matchPref
- .map(m => m.id)
- .indexOf(generatedSeekers[0].otp.id) + 1
- : generatedSeekers[0].otp,
- )
- console.log(generatedSeekers[0])
- // console.log(generatedSeekers[0].matchPref.map(m => m.id))
- }
|