|
|
@@ -1,4 +1,42 @@
|
|
1
|
1
|
const Schmervice = require('@hapipal/schmervice')
|
|
|
2
|
+const cosineSimilarity = require('compute-cosine-similarity')
|
|
|
3
|
+
|
|
|
4
|
+const magic = 1000
|
|
|
5
|
+const scoreResponses = (seeker, potentialMatch) => {
|
|
|
6
|
+ if (seeker.responses.length != potentialMatch.responses.length)
|
|
|
7
|
+ return {
|
|
|
8
|
+ error: `complete responses for profile: ${seeker.profile_id} unqeual to profile: ${potentialMatch.profile_id}`,
|
|
|
9
|
+ }
|
|
|
10
|
+
|
|
|
11
|
+ const checkValCb = res => {
|
|
|
12
|
+ const val = parseInt(Object.values(res))
|
|
|
13
|
+ return isNaN(val) ? 0 : val
|
|
|
14
|
+ }
|
|
|
15
|
+ return Math.floor(
|
|
|
16
|
+ cosineSimilarity(
|
|
|
17
|
+ seeker.responses.map(checkValCb),
|
|
|
18
|
+ potentialMatch.responses.map(checkValCb),
|
|
|
19
|
+ ) * magic,
|
|
|
20
|
+ )
|
|
|
21
|
+}
|
|
|
22
|
+const userTypes = {
|
|
|
23
|
+ seeker: 'seeker',
|
|
|
24
|
+ poster: 'poster',
|
|
|
25
|
+}
|
|
|
26
|
+
|
|
|
27
|
+/**
|
|
|
28
|
+ * Class to hold our retrieved profile information
|
|
|
29
|
+ * in a convenient wrapper
|
|
|
30
|
+ * !: This needs to match the responseSchema in profiles.js
|
|
|
31
|
+ */
|
|
|
32
|
+class CompleteProfile {
|
|
|
33
|
+ constructor(profile, type) {
|
|
|
34
|
+ this.user_id = profile.user_id // int user_id
|
|
|
35
|
+ this.profile_id = profile.profile_id // int profile_id
|
|
|
36
|
+ this.responses = profile.responses // [] of all responses
|
|
|
37
|
+ this.user_type = type
|
|
|
38
|
+ }
|
|
|
39
|
+}
|
|
2
|
40
|
|
|
3
|
41
|
module.exports = class ProfileService extends Schmervice.Service {
|
|
4
|
42
|
constructor(...args) {
|
|
|
@@ -23,30 +61,18 @@ module.exports = class ProfileService extends Schmervice.Service {
|
|
23
|
61
|
return [...new Set(profileIdsToGrab)]
|
|
24
|
62
|
}
|
|
25
|
63
|
|
|
26
|
|
- async getCompleteProfilesFor(userId) {
|
|
27
|
|
- const { Profile, Response } = this.server.models()
|
|
|
64
|
+ async getCompleteProfilesFor(userId, type) {
|
|
|
65
|
+ const { Profile } = this.server.models()
|
|
28
|
66
|
|
|
29
|
|
- const dedupedProfiles = await this._getProfileIdsForUserId(userId)
|
|
|
67
|
+ const dedupedProfileIds = await this._getProfileIdsForUserId(userId)
|
|
30
|
68
|
|
|
31
|
|
- const responses = await Response.query().whereIn(
|
|
32
|
|
- 'profile_id',
|
|
33
|
|
- dedupedProfiles,
|
|
34
|
|
- )
|
|
35
|
|
- const profiles = await Profile.query().whereIn(
|
|
36
|
|
- 'profile_id',
|
|
37
|
|
- dedupedProfiles,
|
|
38
|
|
- )
|
|
|
69
|
+ const profilesEntries = await Profile.query()
|
|
|
70
|
+ .whereIn('profile_id', dedupedProfileIds)
|
|
|
71
|
+ .withGraphFetched('responses')
|
|
39
|
72
|
|
|
40
|
73
|
//** Get responses asociated with each profile_id */
|
|
41
|
|
- return profiles.map(profile => {
|
|
42
|
|
- if (!profile.responses) profile.responses = []
|
|
43
|
|
- profile.response_keys = []
|
|
44
|
|
- responses.forEach(response => {
|
|
45
|
|
- if (response.profile_id !== profile.profile_id) return
|
|
46
|
|
- profile.response_keys.push(response.response_key_id)
|
|
47
|
|
- profile.responses.push(response)
|
|
48
|
|
- })
|
|
49
|
|
- return profile
|
|
|
74
|
+ return profilesEntries.map(profile => {
|
|
|
75
|
+ return new CompleteProfile(profile, type)
|
|
50
|
76
|
})
|
|
51
|
77
|
}
|
|
52
|
78
|
|
|
|
@@ -57,6 +83,8 @@ module.exports = class ProfileService extends Schmervice.Service {
|
|
57
|
83
|
* @returns {object}
|
|
58
|
84
|
*/
|
|
59
|
85
|
async saveResponsesCreateProfileFor(userId, responses, txn) {
|
|
|
86
|
+ const { Profile, Response } = this.server.models()
|
|
|
87
|
+
|
|
60
|
88
|
const profile = await Profile.query(txn).insert({
|
|
61
|
89
|
user_id: userId,
|
|
62
|
90
|
})
|
|
|
@@ -78,7 +106,7 @@ module.exports = class ProfileService extends Schmervice.Service {
|
|
78
|
106
|
* @returns {Array} updated responses
|
|
79
|
107
|
*/
|
|
80
|
108
|
async updateResponsesInProfile(profileId, responses, txn) {
|
|
81
|
|
- const { Profile, Response } = this.server.models()
|
|
|
109
|
+ const { Response } = this.server.models()
|
|
82
|
110
|
for (const responseToSave of responses) {
|
|
83
|
111
|
await Response.query(txn)
|
|
84
|
112
|
.update({
|
|
|
@@ -115,4 +143,33 @@ module.exports = class ProfileService extends Schmervice.Service {
|
|
115
|
143
|
|
|
116
|
144
|
return await Profile.query().delete().where('profile_id', profileId)
|
|
117
|
145
|
}
|
|
|
146
|
+
|
|
|
147
|
+ /**
|
|
|
148
|
+ * Score a profile
|
|
|
149
|
+ * @param {number} userId
|
|
|
150
|
+ * @returns {Array} Ordered and scored Profiles
|
|
|
151
|
+ */
|
|
|
152
|
+ async scoreProfilesFor(userId) {
|
|
|
153
|
+ const { User } = this.server.models(['user'])
|
|
|
154
|
+ const { Profile } = this.server.models()
|
|
|
155
|
+
|
|
|
156
|
+ const user = await User.query().findOne('user_id', userId)
|
|
|
157
|
+ const userProfile = await Profile.query()
|
|
|
158
|
+ .findOne('user_id', userId)
|
|
|
159
|
+ .withGraphFetched('responses')
|
|
|
160
|
+ const isPosterOpposite = user.is_poster == 1 ? 0 : 1
|
|
|
161
|
+ const userType = Object.keys(userTypes)[isPosterOpposite]
|
|
|
162
|
+
|
|
|
163
|
+ const profileIdsOfOppositeType = await Profile.query().withGraphFetched(
|
|
|
164
|
+ 'responses',
|
|
|
165
|
+ )
|
|
|
166
|
+
|
|
|
167
|
+ return profileIdsOfOppositeType.map(profile => ({
|
|
|
168
|
+ profile_id: profile.profile_id,
|
|
|
169
|
+ score: scoreResponses(
|
|
|
170
|
+ userProfile,
|
|
|
171
|
+ new CompleteProfile(profile, userType),
|
|
|
172
|
+ ),
|
|
|
173
|
+ }))
|
|
|
174
|
+ }
|
|
118
|
175
|
}
|