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)) }