Просмотр исходного кода

:sparkles: addding match endpoint | added stable marriage func | added more mock data

tags/0.0.1
J 4 лет назад
Родитель
Сommit
5b1486a85a

+ 65
- 0
backend/db/mock.js Просмотреть файл

49
             is_admin: false,
49
             is_admin: false,
50
             is_poster: false,
50
             is_poster: false,
51
         },
51
         },
52
+        {
53
+            user_id: 8,
54
+            user_name: 'oscar',
55
+            user_email: 'oscar@doggo.com',
56
+            is_admin: false,
57
+            is_poster: true,
58
+        },
59
+        {
60
+            user_id: 9,
61
+            user_name: 'bingo',
62
+            user_email: 'bb@ismycat.com',
63
+            is_admin: false,
64
+            is_poster: false,
65
+        },
66
+        {
67
+            user_id: 10,
68
+            user_name: 'rufus',
69
+            user_email: 'rufruf@issomeothercat.com',
70
+            is_admin: false,
71
+            is_poster: false,
72
+        },
73
+        {
74
+            user_id: 11,
75
+            user_name: 'mary',
76
+            user_email: 'mary@hires.com',
77
+            is_admin: false,
78
+            is_poster: true,
79
+        },
80
+        {
81
+            user_id: 12,
82
+            user_name: 'pingpong',
83
+            user_email: 'plop@pets.com',
84
+            is_admin: false,
85
+            is_poster: false,
86
+        },
87
+        {
88
+            user_id: 13,
89
+            user_name: 'jennie',
90
+            user_email: 'jj@cats.com',
91
+            is_admin: false,
92
+            is_poster: false,
93
+        },
94
+        {
95
+            user_id: 14,
96
+            user_name: 'goose',
97
+            user_email: 'mywingman@allcats.com',
98
+            is_admin: false,
99
+            is_poster: false,
100
+        },
52
     ],
101
     ],
53
     profiles: [
102
     profiles: [
54
         { profile_id: 1, user_id: 1 },
103
         { profile_id: 1, user_id: 1 },
58
         { profile_id: 5, user_id: 5 },
107
         { profile_id: 5, user_id: 5 },
59
         { profile_id: 6, user_id: 6 },
108
         { profile_id: 6, user_id: 6 },
60
         { profile_id: 7, user_id: 7 },
109
         { profile_id: 7, user_id: 7 },
110
+        { profile_id: 8, user_id: 8 },
111
+        { profile_id: 9, user_id: 9 },
112
+        { profile_id: 10, user_id: 10 },
113
+        { profile_id: 11, user_id: 11 },
114
+        { profile_id: 12, user_id: 12 },
115
+        { profile_id: 13, user_id: 13 },
116
+        { profile_id: 14, user_id: 14 },
117
+        { profile_id: 15, user_id: 11 },
61
     ],
118
     ],
62
     response_keys: [
119
     response_keys: [
63
         {
120
         {
672
         { response_id: 89, profile_id: 5, response_key_id: 16, val: '90015' },
729
         { response_id: 89, profile_id: 5, response_key_id: 16, val: '90015' },
673
         { response_id: 90, profile_id: 6, response_key_id: 16, val: '91203' },
730
         { response_id: 90, profile_id: 6, response_key_id: 16, val: '91203' },
674
         { response_id: 91, profile_id: 7, response_key_id: 16, val: '90210' },
731
         { response_id: 91, profile_id: 7, response_key_id: 16, val: '90210' },
732
+        { response_id: 92, profile_id: 8, response_key_id: 16, val: '90065' },
733
+        { response_id: 93, profile_id: 9, response_key_id: 16, val: '90210' },
734
+        { response_id: 94, profile_id: 10, response_key_id: 16, val: '90001' },
735
+        { response_id: 95, profile_id: 11, response_key_id: 16, val: '90023' },
736
+        { response_id: 96, profile_id: 12, response_key_id: 16, val: '90210' },
737
+        { response_id: 97, profile_id: 13, response_key_id: 16, val: '96701' },
738
+        { response_id: 98, profile_id: 14, response_key_id: 16, val: '90452' },
739
+        { response_id: 99, profile_id: 15, response_key_id: 16, val: '90025' },
675
     ],
740
     ],
676
     memberships: [
741
     memberships: [
677
         {
742
         {

+ 0
- 1
backend/db/survey-generator.js Просмотреть файл

140
         })
140
         })
141
     } while (!done)
141
     } while (!done)
142
 }
142
 }
143
-
144
 const generateDummyProfiles = (count, startFrom) => {
143
 const generateDummyProfiles = (count, startFrom) => {
145
     const profiles = []
144
     const profiles = []
146
     for (let i = 0; i < count; i++) {
145
     for (let i = 0; i < count; i++) {

+ 2
- 0
backend/lib/plugins/profile.js Просмотреть файл

10
 const ProfileScoreRoute = require('../routes/profile/score')
10
 const ProfileScoreRoute = require('../routes/profile/score')
11
 const ProfileUpdateRoute = require('../routes/profile/update')
11
 const ProfileUpdateRoute = require('../routes/profile/update')
12
 const ProfileRespondRoute = require('../routes/profile/respond')
12
 const ProfileRespondRoute = require('../routes/profile/respond')
13
+const ProfileMatchRoute = require('../routes/profile/match')
13
 
14
 
14
 module.exports = {
15
 module.exports = {
15
     name: 'profile-plugin',
16
     name: 'profile-plugin',
31
         await server.route(ProfileScoreRoute)
32
         await server.route(ProfileScoreRoute)
32
         await server.route(ProfileRespondRoute)
33
         await server.route(ProfileRespondRoute)
33
         await server.route(ProfileUpdateRoute)
34
         await server.route(ProfileUpdateRoute)
35
+        await server.route(ProfileMatchRoute)
34
     },
36
     },
35
 }
37
 }

+ 76
- 0
backend/lib/routes/profile/match.js Просмотреть файл

1
+'use strict'
2
+
3
+const Joi = require('joi')
4
+
5
+const pluginConfig = {
6
+    handlerType: 'match',
7
+    docs: {
8
+        description: 'matches',
9
+        notes: 'Match everyone',
10
+    },
11
+}
12
+
13
+const validators = {}
14
+
15
+const responseSchemas = {
16
+    response: Joi.array().items(),
17
+    error: Joi.object({
18
+        error: Joi.string(),
19
+    }),
20
+}
21
+
22
+module.exports = {
23
+    method: 'GET',
24
+    path: '/match',
25
+    options: {
26
+        ...pluginConfig.docs,
27
+        tags: ['api'],
28
+        /** Protect this route with authentication? */
29
+        auth: false,
30
+
31
+        handler: async function (request, h) {
32
+            const { profileService } = request.services()
33
+            const postMatchYins = await profileService.calcMatches()
34
+            console.log(postMatchYins)
35
+            try {
36
+                if(!postMatchYins){
37
+                    throw new RangeError('Unable to match profiles')
38
+                }
39
+                
40
+                return h.response({
41
+                    ok: true,
42
+                    handler: pluginConfig.handlerType,
43
+                    data: postMatchYins,
44
+                }).code(200)
45
+            } catch (err) {
46
+                return h.response({
47
+                    ok: false,
48
+                    handler: pluginConfig.handlerType,
49
+                    data: { error: `${err}` },
50
+                }).code(409)
51
+            }
52
+        },
53
+
54
+        /** Validate based on validators object */
55
+        validate: {
56
+            ...validators,
57
+            failAction: 'log',
58
+        },
59
+
60
+        /** Validate the server response */
61
+        response: {
62
+            status: {
63
+                200: Joi.object({
64
+                    ok: Joi.bool(),
65
+                    handler: Joi.string(),
66
+                    data: responseSchemas.response,
67
+                }),
68
+                409: Joi.object({
69
+                    ok: Joi.bool(),
70
+                    handler: Joi.string(),
71
+                    data: responseSchemas.error,
72
+                }),
73
+            }
74
+        },
75
+    },
76
+}

+ 125
- 8
backend/lib/services/profile.js Просмотреть файл

1
 const Schmervice = require('@hapipal/schmervice')
1
 const Schmervice = require('@hapipal/schmervice')
2
 const cosineSimilarity = require('compute-cosine-similarity')
2
 const cosineSimilarity = require('compute-cosine-similarity')
3
 const haversine = require('haversine')
3
 const haversine = require('haversine')
4
-const profile = require('../plugins/profile')
4
+const { profiles } = require('../../db/mock')
5
+
6
+const rand = max => {
7
+    return Math.floor(Math.random() * max) < 1
8
+        ? 1
9
+        : Math.floor(Math.random() * max)
10
+}
11
+
12
+const runMatch = (allYins, allYangs) => {
13
+    balanceSides(allYins, allYangs)
14
+    
15
+    // You only need to engage from one side
16
+    engageEveryone(allYins)
17
+
18
+    allYins.forEach(yin => yin.clearPlaceholderMatches())
19
+    allYangs.forEach(yang => yang.clearPlaceholderMatches())
20
+
21
+    console.log('---')
22
+}
23
+const engageEveryone = allYinsOrYangs => {
24
+    let done
25
+    do {
26
+        done = true
27
+        allYinsOrYangs.forEach(yinOrYang => {
28
+            if (!yinOrYang.otp) {
29
+                done = false
30
+                const yangOrYin = yinOrYang.getNextCandidate()
31
+                if (!yangOrYin.otp || yangOrYin.prefers(yinOrYang)) {
32
+                    yinOrYang.engageTo(yangOrYin)
33
+                }
34
+            }
35
+        })
36
+    } while (!done)
37
+}
38
+const balanceSides = (yins, yangs) => {
39
+    let diff = Math.abs(yangs.length - yins.length)
40
+    let smallerList = yangs.length < yins.length ? yangs : yins
41
+    const totalProfiles = yangs.length + yins.length + 1
42
+    for (let i = 0; i < diff; i++) {
43
+        smallerList.push(new ProfileFacade(totalProfiles + i))
44
+    }
45
+}
46
+class ProfileFacade {
47
+    constructor(id, matchQueue) {
48
+        this.realId = id ? id : undefined
49
+
50
+        this.matchCandidateIndex = 0
51
+        this.otp = null
52
+        this.matchQueue = matchQueue?.length ? matchQueue : []
53
+        this.fOrder = []
54
+    }
55
+    clearPlaceholderMatches(){
56
+        this.matchQueue = this.matchQueue.filter(
57
+            match => match.realId == false,
58
+        )
59
+        this.fOrder = this.fOrder.filter(
60
+            potentialFiance => potentialFiance.realId == false,
61
+        )
62
+        if (this.otp && this.otp.realId) {
63
+            this.otp = null
64
+        }
65
+    }
66
+    rank(p) {
67
+        for (let i = 0; i < this.matchQueue.length; i++) {
68
+            if (this.matchQueue[i] === p) {
69
+                return i
70
+            }
71
+        }
72
+        return this.matchQueue.length + 1
73
+    }
74
+    prefers(p) {
75
+        return this.rank(p) < this.rank(this.otp)
76
+    }
77
+    getNextCandidate() {
78
+        if (this.matchCandidateIndex >= this.matchQueue.length) return null
79
+        return this.matchQueue[this.matchCandidateIndex++]
80
+    }
81
+    engageTo(p) {
82
+        if (p.otp) { p.otp.otp = null }
83
+        p.otp = this
84
+        p.fOrder.unshift(this)
85
+        if (this.otp) { this.otp.otp = null }
86
+        this.otp = p
87
+        this.fOrder.unshift(p)
88
+    }
89
+}
5
 
90
 
6
 const magic = 1000
91
 const magic = 1000
7
 const scoreResponses = (seeker, potentialMatch) => {
92
 const scoreResponses = (seeker, potentialMatch) => {
202
         
287
         
203
         // Our User Profile to score for
288
         // Our User Profile to score for
204
         const userProfile = await Profile.query()
289
         const userProfile = await Profile.query()
205
-        .findOne('profile_id', profileId)
206
-        .withGraphFetched('responses')
207
-        .withGraphFetched('user')
208
-        
290
+            .findOne('profile_id', profileId)
291
+            .withGraphFetched('responses')
292
+            .withGraphFetched('user')
293
+            
209
         // Move unneeded responses
294
         // Move unneeded responses
210
         const userZip = getZipCodeFromProfile(userProfile)
295
         const userZip = getZipCodeFromProfile(userProfile)
211
 
296
 
212
         // Find all Profiles that are NOT of our userProfile.type
297
         // Find all Profiles that are NOT of our userProfile.type
213
         // ie. If userProfile.type == seeker, then find: poster
298
         // ie. If userProfile.type == seeker, then find: poster
214
         let profileIdsOfOppositeType = await Profile.query()
299
         let profileIdsOfOppositeType = await Profile.query()
215
-        .withGraphFetched('responses')
216
-        .withGraphFetched('user')
300
+            .withGraphFetched('responses')
301
+            .withGraphFetched('user')
217
         
302
         
218
         // TODO: Let Objection optimize this
303
         // TODO: Let Objection optimize this
219
         const isPosterOpposite = userProfile.user.is_poster == 1 ? 0 : 1
304
         const isPosterOpposite = userProfile.user.is_poster == 1 ? 0 : 1
235
         // Order by score
320
         // Order by score
236
         return scoredProfilesWithDistance.sort((a, b) => a.score - b.score)
321
         return scoredProfilesWithDistance.sort((a, b) => a.score - b.score)
237
     }
322
     }
238
-    
323
+    async calcMatches() {
324
+        const { Profile } = this.server.models()
325
+        
326
+        // Grab all profiles with matchQueues
327
+        let allProfiles = await Profile.query()
328
+            .withGraphFetched('user')
329
+        
330
+        // !:FAKE Score everyone
331
+        allProfiles = allProfiles.map(profile => {
332
+            // Copy the profile and add a queue
333
+            const profilePlusFakeScore = {
334
+                ...profile,
335
+                queue: []
336
+            }
337
+            // Generate a random order of profile_ids 1:7
338
+            while (profilePlusFakeScore.queue.length < 4) {
339
+                profilePlusFakeScore.queue.push(rand(4))
340
+                profilePlusFakeScore.queue = [...new Set(profilePlusFakeScore.queue)]
341
+            }
342
+            //Instantiate a profile facade for each id for matching
343
+            profilePlusFakeScore.queue = profilePlusFakeScore.queue.map(randId => new ProfileFacade(randId))
344
+            
345
+            return profilePlusFakeScore
346
+        })
347
+        // ! FAKE -- END
348
+
349
+        const seekers = allProfiles.filter(profile => profile.user.is_poster == 0)
350
+        const posters = allProfiles.filter(profile => profile.user.is_poster == 1)
351
+        const yins = seekers.map(profile => new ProfileFacade(profile.profile_id, profile.queue))
352
+        const yangs = posters.map(profile => new ProfileFacade(profile.profile_id, profile.queue))
353
+        runMatch(yins, yangs)
354
+        return yins
355
+    }
239
     /**
356
     /**
240
      * Use the db for zipcode info
357
      * Use the db for zipcode info
241
      * @param {string} zipCode
358
      * @param {string} zipCode

Загрузка…
Отмена
Сохранить