| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- const Schmervice = require('@hapipal/schmervice')
- const cosineSimilarity = require('compute-cosine-similarity')
-
- const magic = 1000
- const scoreResponses = (seeker, potentialMatch) => {
- if (seeker.responses.length != potentialMatch.responses.length)
- return {
- error: `complete responses for profile: ${seeker.profile_id} unqeual to profile: ${potentialMatch.profile_id} | ${seeker.responses.length}:${potentialMatch.responses.length}`,
- }
-
- const checkValCb = res => {
- const val = parseInt(Object.values(res))
- return isNaN(val) ? 0 : val
- }
- return Math.floor(
- cosineSimilarity(
- seeker.responses.map(checkValCb),
- potentialMatch.responses.map(checkValCb),
- ) * magic,
- )
- }
-
- /**
- * Class to hold our retrieved profile information
- * in a convenient wrapper
- * !: This needs to match the responseSchema in profiles.js
- */
- class CompleteProfile {
- constructor(profile, type) {
- this.user_id = profile.user_id // int user_id
- this.profile_id = profile.profile_id // int profile_id
- this.responses = profile.responses // [] of all responses
- this.user_type = type
- }
- }
-
- module.exports = class ProfileService extends Schmervice.Service {
- constructor(...args) {
- super(...args)
- }
-
- /**
- * Internal method to get list of profile_ids for this user
- * @param {number} userId
- * @returns {Array} List of all profile_ids for user
- */
- async _getProfileIdsForUserId(userId) {
- const { Profile } = this.server.models()
-
- /** Grab every Profile associated with this id */
- const allProfiles = await Profile.query().where('user_id', userId)
-
- /** Copy a list of the just the Profiles */
- const profileIdsToGrab = allProfiles.map(profile => profile.profile_id)
-
- /** Uncomment to dedupe the list just in case */
- return [...new Set(profileIdsToGrab)]
- }
-
- async getCompleteProfilesFor(userId, type) {
- const { Profile } = this.server.models()
-
- const dedupedProfileIds = await this._getProfileIdsForUserId(userId)
-
- const profilesEntries = await Profile.query()
- .whereIn('profile_id', dedupedProfileIds)
- .withGraphFetched('responses')
-
- //** Get responses asociated with each profile_id */
- return profilesEntries.map(profile => {
- return new CompleteProfile(profile, type)
- })
- }
-
- /**
- * Save responses in a profile
- * @param {number} userId
- * @param {Array} responses
- * @returns {object}
- */
- async saveResponsesCreateProfileFor(userId, responses, txn) {
- const { Profile, Response } = this.server.models()
-
- const profile = await Profile.query(txn).insert({
- user_id: userId,
- })
- for (const responseToSave of responses) {
- const responseInfo = {
- profile_id: profile.id,
- response_key_id: responseToSave.response_key_id,
- val: responseToSave.val,
- }
- await Response.query(txn).insert(responseInfo)
- }
- //** Work around for HAPI returning profile_id as id */
- return { user_id: profile.user_id, profile_id: profile.id }
- }
-
- /** Update responses in place
- * @param {number} profileId
- * @param {Array} responses
- * @returns {Array} updated responses
- */
- async updateResponsesInProfile(profileId, responses, txn) {
- const { Response } = this.server.models()
- for (const responseToSave of responses) {
- await Response.query(txn)
- .update({
- response_id: responseToSave.response_id,
- profile_id: responseToSave.profile_id,
- response_key_id: responseToSave.response_key_id,
- val: responseToSave.val,
- })
- .where({
- profile_id: profileId,
- })
- .where({
- response_id: responseToSave.response_id,
- })
- }
- return await Response.query(txn).where({
- profile_id: profileId,
- })
- }
-
- /**
- * Delete a profile
- * @param {number} userId
- * @param {number} profileId
- * @returns
- */
- async deleteProfile(userId, profileId) {
- const { Profile } = this.server.models()
-
- const dedupedGroupings = await this._getProfileIdsForUserId(userId)
-
- /** Do NOTHING if NOT in Grouping */
- if (!dedupedGroupings.includes(profileId)) return
-
- return await Profile.query().delete().where('profile_id', profileId)
- }
-
- /**
- * Score a profile
- * @param {number} userId
- * @returns {Array} Ordered and scored Profiles
- */
- async scoreProfilesFor(userId) {
- const { Profile } = this.server.models()
-
- // Our User Profile to score for
- const userProfile = await Profile.query()
- .findOne('user_id', userId)
- .withGraphFetched('responses')
- .withGraphFetched('user')
-
- const isPosterOpposite = userProfile.user.is_poster == 1 ? 0 : 1
-
- // Find all Profiles that are NOT of our userProfile.type
- // ie. If userProfile.type == seeker, then find: poster
- let profileIdsOfOppositeType = await Profile.query()
- .withGraphFetched('responses')
- .withGraphFetched('user')
-
- // TODO: Let Objection optimize this
- profileIdsOfOppositeType = profileIdsOfOppositeType.filter(
- profile => profile.user.is_poster == isPosterOpposite,
- )
-
- const scored = profileIdsOfOppositeType.map(profile => ({
- profile_id: profile.profile_id,
- score: scoreResponses(userProfile, profile),
- }))
- return scored.sort((a, b) => a.score - b.score)
- }
- }
|