Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

profile.js 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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}`,
  8. }
  9. const checkValCb = res => {
  10. const val = parseInt(Object.values(res))
  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. const userTypes = {
  21. seeker: 'seeker',
  22. poster: 'poster',
  23. }
  24. /**
  25. * Class to hold our retrieved profile information
  26. * in a convenient wrapper
  27. * !: This needs to match the responseSchema in profiles.js
  28. */
  29. class CompleteProfile {
  30. constructor(profile, type) {
  31. this.user_id = profile.user_id // int user_id
  32. this.profile_id = profile.profile_id // int profile_id
  33. this.responses = profile.responses // [] of all responses
  34. this.user_type = type
  35. }
  36. }
  37. module.exports = class ProfileService extends Schmervice.Service {
  38. constructor(...args) {
  39. super(...args)
  40. }
  41. /**
  42. * Internal method to get list of profile_ids for this user
  43. * @param {number} userId
  44. * @returns {Array} List of all profile_ids for user
  45. */
  46. async _getProfileIdsForUserId(userId) {
  47. const { Profile } = this.server.models()
  48. /** Grab every Profile associated with this id */
  49. const allProfiles = await Profile.query().where('user_id', userId)
  50. /** Copy a list of the just the Profiles */
  51. const profileIdsToGrab = allProfiles.map(profile => profile.profile_id)
  52. /** Uncomment to dedupe the list just in case */
  53. return [...new Set(profileIdsToGrab)]
  54. }
  55. async getCompleteProfilesFor(userId, type) {
  56. const { Profile } = this.server.models()
  57. const dedupedProfileIds = await this._getProfileIdsForUserId(userId)
  58. const profilesEntries = await Profile.query()
  59. .whereIn('profile_id', dedupedProfileIds)
  60. .withGraphFetched('responses')
  61. //** Get responses asociated with each profile_id */
  62. return profilesEntries.map(profile => {
  63. return new CompleteProfile(profile, type)
  64. })
  65. }
  66. /**
  67. * Save responses in a profile
  68. * @param {number} userId
  69. * @param {Array} responses
  70. * @returns {object}
  71. */
  72. async saveResponsesCreateProfileFor(userId, responses, txn) {
  73. const { Profile, Response } = this.server.models()
  74. const profile = await Profile.query(txn).insert({
  75. user_id: userId,
  76. })
  77. for (const responseToSave of responses) {
  78. const responseInfo = {
  79. profile_id: profile.id,
  80. response_key_id: responseToSave.response_key_id,
  81. val: responseToSave.val,
  82. }
  83. await Response.query(txn).insert(responseInfo)
  84. }
  85. //** Work around for HAPI returning profile_id as id */
  86. return { user_id: profile.user_id, profile_id: profile.id }
  87. }
  88. /** Update responses in place
  89. * @param {number} profileId
  90. * @param {Array} responses
  91. * @returns {Array} updated responses
  92. */
  93. async updateResponsesInProfile(profileId, responses, txn) {
  94. const { Response } = this.server.models()
  95. for (const responseToSave of responses) {
  96. await Response.query(txn)
  97. .update({
  98. response_id: responseToSave.response_id,
  99. profile_id: responseToSave.profile_id,
  100. response_key_id: responseToSave.response_key_id,
  101. val: responseToSave.val,
  102. })
  103. .where({
  104. profile_id: profileId,
  105. })
  106. .where({
  107. response_id: responseToSave.response_id,
  108. })
  109. }
  110. return await Response.query(txn).where({
  111. profile_id: profileId,
  112. })
  113. }
  114. /**
  115. * Delete a profile
  116. * @param {number} userId
  117. * @param {number} profileId
  118. * @returns
  119. */
  120. async deleteProfile(userId, profileId) {
  121. const { Profile } = this.server.models()
  122. const dedupedGroupings = await this._getProfileIdsForUserId(userId)
  123. /** Do NOTHING if NOT in Grouping */
  124. if (!dedupedGroupings.includes(profileId)) return
  125. return await Profile.query().delete().where('profile_id', profileId)
  126. }
  127. /**
  128. * Score a profile
  129. * @param {number} userId
  130. * @returns {Array} Ordered and scored Profiles
  131. */
  132. async scoreProfilesFor(userId) {
  133. const { User } = this.server.models(['user'])
  134. const { Profile } = this.server.models()
  135. const user = await User.query().findOne('user_id', userId)
  136. const userProfile = await Profile.query()
  137. .findOne('user_id', userId)
  138. .withGraphFetched('responses')
  139. const isPosterOpposite = user.is_poster == 1 ? 0 : 1
  140. const userType = Object.keys(userTypes)[isPosterOpposite]
  141. const profileIdsOfOppositeType = await Profile.query().withGraphFetched(
  142. 'responses',
  143. )
  144. return profileIdsOfOppositeType.map(profile => ({
  145. profile_id: profile.profile_id,
  146. score: scoreResponses(
  147. userProfile,
  148. new CompleteProfile(profile, userType),
  149. ),
  150. }))
  151. }
  152. }