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.

profile.js 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. const Schmervice = require('@hapipal/schmervice')
  2. const cosineSimilarity = require('compute-cosine-similarity')
  3. const magic = 1000
  4. const scoreResponses = (seeker, potentialMatch) => {
  5. if (seeker.responses.length != potentialMatch.responses.length)
  6. return {
  7. error: `complete responses for profile: ${seeker.profile_id} unqeual to profile: ${potentialMatch.profile_id} | ${seeker.responses.length}:${potentialMatch.responses.length}`,
  8. }
  9. const checkValCb = res => {
  10. const val = parseInt(res.val)
  11. return isNaN(val) ? 0 : val
  12. }
  13. return Math.floor(
  14. cosineSimilarity(
  15. seeker.responses.map(checkValCb),
  16. potentialMatch.responses.map(checkValCb),
  17. ) * magic,
  18. )
  19. }
  20. /**
  21. * Class to hold our retrieved profile information
  22. * in a convenient wrapper
  23. * !: This needs to match the responseSchema in profiles.js
  24. */
  25. class CompleteProfile {
  26. constructor(profile, type) {
  27. this.user_id = profile.user_id // int user_id
  28. this.profile_id = profile.profile_id // int profile_id
  29. this.responses = profile.responses // [] of all responses
  30. this.user_type = type
  31. }
  32. }
  33. module.exports = class ProfileService extends Schmervice.Service {
  34. constructor(...args) {
  35. super(...args)
  36. }
  37. /**
  38. * Internal method to get list of profile_ids for this user
  39. * @param {number} userId
  40. * @returns {Array} List of all profile_ids for user
  41. */
  42. async _getProfileIdsForUserId(userId) {
  43. const { Profile } = this.server.models()
  44. /** Grab every Profile associated with this id */
  45. const allProfiles = await Profile.query().where('user_id', userId)
  46. /** Copy a list of the just the Profiles */
  47. const profileIdsToGrab = allProfiles.map(profile => profile.profile_id)
  48. /** Uncomment to dedupe the list just in case */
  49. return [...new Set(profileIdsToGrab)]
  50. }
  51. async getCompleteProfilesFor(userId, type) {
  52. const { Profile } = this.server.models()
  53. const dedupedProfileIds = await this._getProfileIdsForUserId(userId)
  54. const profilesEntries = await Profile.query()
  55. .whereIn('profile_id', dedupedProfileIds)
  56. .withGraphFetched('responses')
  57. //** Get responses asociated with each profile_id */
  58. return profilesEntries.map(profile => {
  59. return new CompleteProfile(profile, type)
  60. })
  61. }
  62. /**
  63. * Save responses in a profile
  64. * @param {number} userId
  65. * @param {Array} responses
  66. * @returns {object}
  67. */
  68. async saveResponsesCreateProfileFor(userId, responses, txn) {
  69. const { Profile, Response } = this.server.models()
  70. const profile = await Profile.query(txn).insert({
  71. user_id: userId,
  72. })
  73. for (const responseToSave of responses) {
  74. const responseInfo = {
  75. profile_id: profile.id,
  76. response_key_id: responseToSave.response_key_id,
  77. val: responseToSave.val,
  78. }
  79. await Response.query(txn).insert(responseInfo)
  80. }
  81. //** Work around for HAPI returning profile_id as id */
  82. return { user_id: profile.user_id, profile_id: profile.id }
  83. }
  84. /** Update responses in place
  85. * @param {number} profileId
  86. * @param {Array} responses
  87. * @returns {Array} updated responses
  88. */
  89. async updateResponsesInProfile(profileId, responses, txn) {
  90. const { Response } = this.server.models()
  91. for (const responseToSave of responses) {
  92. await Response.query(txn)
  93. .update({
  94. response_id: responseToSave.response_id,
  95. profile_id: responseToSave.profile_id,
  96. response_key_id: responseToSave.response_key_id,
  97. val: responseToSave.val,
  98. })
  99. .where({
  100. profile_id: profileId,
  101. })
  102. .where({
  103. response_id: responseToSave.response_id,
  104. })
  105. }
  106. return await Response.query(txn).where({
  107. profile_id: profileId,
  108. })
  109. }
  110. /** Add response
  111. * @param {Object} response to save
  112. * @returns {null} updated responses
  113. * @returns {Array} updated responses
  114. */
  115. async saveResponseForProfile(profileId, responseToSave) {
  116. const { Response } = this.server.models()
  117. let allResponses = await Response.query().where({
  118. profile_id: profileId,
  119. })
  120. const matchingResponses = allResponses.filter(response => response.response_key_id == responseToSave.response_key_id)
  121. // ?:Maybe bad idea
  122. if(matchingResponses.length > 0) { return null }
  123. await await Response.query().insert(responseToSave)
  124. return allResponses
  125. }
  126. /**
  127. * Delete a profile
  128. * @param {number} userId
  129. * @param {number} profileId
  130. * @returns
  131. */
  132. async deleteProfile(userId, profileId) {
  133. const { Profile } = this.server.models()
  134. const dedupedGroupings = await this._getProfileIdsForUserId(userId)
  135. /** Do NOTHING if NOT in Grouping */
  136. if (!dedupedGroupings.includes(profileId)) return
  137. return await Profile.query().delete().where('profile_id', profileId)
  138. }
  139. /**
  140. * Score a profile
  141. * @param {number} profileId
  142. * @returns {Array} Ordered and scored Profiles
  143. */
  144. async scoreProfilesFor(profileId) {
  145. const { Profile } = this.server.models()
  146. // Our User Profile to score for
  147. const userProfile = await Profile.query()
  148. .findOne('profile_id', profileId)
  149. .withGraphFetched('responses')
  150. .withGraphFetched('user')
  151. const isPosterOpposite = userProfile.user.is_poster == 1 ? 0 : 1
  152. // Find all Profiles that are NOT of our userProfile.type
  153. // ie. If userProfile.type == seeker, then find: poster
  154. let profileIdsOfOppositeType = await Profile.query()
  155. .withGraphFetched('responses')
  156. .withGraphFetched('user')
  157. // TODO: Let Objection optimize this
  158. profileIdsOfOppositeType = profileIdsOfOppositeType.filter(
  159. profile => profile.user.is_poster == isPosterOpposite,
  160. )
  161. const scored = profileIdsOfOppositeType.map(profile => ({
  162. profile_id: profile.profile_id,
  163. score: scoreResponses(userProfile, profile),
  164. }))
  165. return scored.sort((a, b) => a.score - b.score)
  166. }
  167. }