Sfoglia il codice sorgente

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

tags/0.0.1
J 4 anni fa
parent
commit
5b1486a85a

+ 65
- 0
backend/db/mock.js Vedi File

@@ -49,6 +49,55 @@ module.exports = {
49 49
             is_admin: false,
50 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 102
     profiles: [
54 103
         { profile_id: 1, user_id: 1 },
@@ -58,6 +107,14 @@ module.exports = {
58 107
         { profile_id: 5, user_id: 5 },
59 108
         { profile_id: 6, user_id: 6 },
60 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 119
     response_keys: [
63 120
         {
@@ -672,6 +729,14 @@ module.exports = {
672 729
         { response_id: 89, profile_id: 5, response_key_id: 16, val: '90015' },
673 730
         { response_id: 90, profile_id: 6, response_key_id: 16, val: '91203' },
674 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 741
     memberships: [
677 742
         {

+ 0
- 1
backend/db/survey-generator.js Vedi File

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

+ 2
- 0
backend/lib/plugins/profile.js Vedi File

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

+ 76
- 0
backend/lib/routes/profile/match.js Vedi File

@@ -0,0 +1,76 @@
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 Vedi File

@@ -1,7 +1,92 @@
1 1
 const Schmervice = require('@hapipal/schmervice')
2 2
 const cosineSimilarity = require('compute-cosine-similarity')
3 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 91
 const magic = 1000
7 92
 const scoreResponses = (seeker, potentialMatch) => {
@@ -202,18 +287,18 @@ module.exports = class ProfileService extends Schmervice.Service {
202 287
         
203 288
         // Our User Profile to score for
204 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 294
         // Move unneeded responses
210 295
         const userZip = getZipCodeFromProfile(userProfile)
211 296
 
212 297
         // Find all Profiles that are NOT of our userProfile.type
213 298
         // ie. If userProfile.type == seeker, then find: poster
214 299
         let profileIdsOfOppositeType = await Profile.query()
215
-        .withGraphFetched('responses')
216
-        .withGraphFetched('user')
300
+            .withGraphFetched('responses')
301
+            .withGraphFetched('user')
217 302
         
218 303
         // TODO: Let Objection optimize this
219 304
         const isPosterOpposite = userProfile.user.is_poster == 1 ? 0 : 1
@@ -235,7 +320,39 @@ module.exports = class ProfileService extends Schmervice.Service {
235 320
         // Order by score
236 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 357
      * Use the db for zipcode info
241 358
      * @param {string} zipCode

Loading…
Annulla
Salva