ソースを参照

:recycle: breakig out matching to separate service

tags/0.0.1
j 4年前
コミット
2e6f1d6a1a

+ 17
- 1
backend/lib/models/matchqueue.js ファイルの表示

1
 const Schwifty = require('@hapipal/schwifty')
1
 const Schwifty = require('@hapipal/schwifty')
2
 const Joi = require('joi')
2
 const Joi = require('joi')
3
+const User = require('./user')
3
 
4
 
4
 module.exports = class MatchQueue extends Schwifty.Model {
5
 module.exports = class MatchQueue extends Schwifty.Model {
5
     static get tableName() {
6
     static get tableName() {
6
         return 'match_queues'
7
         return 'match_queues'
7
     }
8
     }
8
-
9
+    static get relationMappings() {
10
+        return {
11
+            user: {
12
+                relation: Schwifty.Model.HasOneThroughRelation,
13
+                modelClass: User,
14
+                join: {
15
+                    from: 'match_queues.target_id',
16
+                    through: {
17
+                        from: 'profiles.profile_id',
18
+                        to: 'profiles.user_id',
19
+                    },
20
+                    to: 'users.user_id',
21
+                },
22
+            },
23
+        }
24
+    }
9
     static get joiSchema() {
25
     static get joiSchema() {
10
         return Joi.object({
26
         return Joi.object({
11
             match_queue_id: Joi.number(),
27
             match_queue_id: Joi.number(),

+ 2
- 0
backend/lib/plugins/profile.js ファイルの表示

8
 
8
 
9
 const ProfileService = require('../services/profile')
9
 const ProfileService = require('../services/profile')
10
 const MatchQueueService = require('../services/matchqueue')
10
 const MatchQueueService = require('../services/matchqueue')
11
+const MatchService = require('../services/match')
11
 
12
 
12
 const ProfileScoreRoute = require('../routes/profile/score')
13
 const ProfileScoreRoute = require('../routes/profile/score')
13
 const ProfileUpdateRoute = require('../routes/profile/update')
14
 const ProfileUpdateRoute = require('../routes/profile/update')
34
         await server.register(Schmervice)
35
         await server.register(Schmervice)
35
         await server.registerService(ProfileService)
36
         await server.registerService(ProfileService)
36
         await server.registerService(MatchQueueService)
37
         await server.registerService(MatchQueueService)
38
+        await server.registerService(MatchService)
37
 
39
 
38
         await server.route(ProfileScoreRoute)
40
         await server.route(ProfileScoreRoute)
39
         await server.route(ProfileRespondRoute)
41
         await server.route(ProfileRespondRoute)

+ 7
- 5
backend/lib/routes/profile/match.js ファイルの表示

13
 const validators = {}
13
 const validators = {}
14
 
14
 
15
 const responseSchemas = {
15
 const responseSchemas = {
16
-    response: Joi.array().items(),
16
+    response: Joi.object(),
17
     error: Joi.object({
17
     error: Joi.object({
18
         error: Joi.string(),
18
         error: Joi.string(),
19
     }),
19
     }),
29
         auth: false,
29
         auth: false,
30
 
30
 
31
         handler: async function (request, h) {
31
         handler: async function (request, h) {
32
-            const { profileService } = request.services()
33
-            const postMatchYins = await profileService.calcMatches()
32
+            const { matchService, matchQueueService } =
33
+                request.server.services()
34
+            const allQueues = await matchQueueService.getAllQueues()
35
+            matchService.calcMatches(allQueues)
34
             try {
36
             try {
35
-                if (!postMatchYins) {
37
+                if (!allQueues) {
36
                     throw new RangeError('Unable to match profiles')
38
                     throw new RangeError('Unable to match profiles')
37
                 }
39
                 }
38
 
40
 
40
                     .response({
42
                     .response({
41
                         ok: true,
43
                         ok: true,
42
                         handler: pluginConfig.handlerType,
44
                         handler: pluginConfig.handlerType,
43
-                        data: postMatchYins,
45
+                        data: allQueues,
44
                     })
46
                     })
45
                     .code(200)
47
                     .code(200)
46
             } catch (err) {
48
             } catch (err) {

+ 0
- 61
backend/lib/services/match-maker.js ファイルの表示

1
-const similarity = require('compute-cosine-similarity')
2
-const magic = 1000
3
-
4
-const ScoreKeeper = {
5
-    score: (seeker, potentialMatch) => {
6
-        const seekerResponseValues = seeker.profileResponses.map(res =>
7
-            parseInt(Object.values(res)),
8
-        )
9
-        console.log(seeker)
10
-        const potentialMatchResponseValues =
11
-            potentialMatch.profileResponses.map(res =>
12
-                parseInt(Object.values(res)),
13
-            )
14
-        return Math.floor(
15
-            similarity(seekerResponseValues, potentialMatchResponseValues) *
16
-                magic,
17
-        )
18
-    },
19
-}
20
-
21
-module.exports = class MatchMaker {
22
-    constructor(settings) {
23
-        this.proposer = settings.proposer
24
-
25
-        // Score main profile
26
-        this.keeper = ScoreKeeper
27
-    }
28
-    runPrematch(settings) {
29
-        // grab all profiles form the db
30
-
31
-        // grab all responses
32
-        // grab all response keys
33
-        // create a full response object
34
-        // create a full profile of responses
35
-
36
-        const unscreenedProfiles = []
37
-        const screenedProfiles = []
38
-
39
-        for (const profile in unscreenedProfiles) {
40
-            // Do something here
41
-            if (!settings) {
42
-                return
43
-            }
44
-            screenedProfiles.push(profile)
45
-        }
46
-
47
-        return screenedProfiles
48
-    }
49
-    matchFor(proposer, profiles, settings) {
50
-        // Do something here
51
-        return this.runPrematch(profiles, settings)
52
-    }
53
-    scoreProfiles(profiles) {
54
-        const matchScores = []
55
-        for (const profile in profiles) {
56
-            const scored = this.keeper.score(profile)
57
-            matchScores.push(scored)
58
-        }
59
-        return matchScores
60
-    }
61
-}

+ 108
- 0
backend/lib/services/match.js ファイルの表示

1
+const Schmervice = require('@hapipal/schmervice')
2
+
3
+const engageEveryone = allYins => {
4
+    let done
5
+    do {
6
+        console.log('rerunning...')
7
+        done = true
8
+        allYins.forEach(yin => {
9
+            // Keep matching if no true pairing is found
10
+            console.log(yin.realId, yin.otp?.realId)
11
+            if (!yin.otp) {
12
+                done = false
13
+                const yang = yin.getNextCandidate()
14
+                if (!yang.otp || yang.prefers(yin)) {
15
+                    yin.engageTo(yang)
16
+                }
17
+            } else {
18
+                console.log(yin.otp.realId)
19
+            }
20
+        })
21
+    } while (!done)
22
+}
23
+class ProfileFacade {
24
+    constructor(id, matchQueue) {
25
+        this.realId = id ? id : undefined
26
+
27
+        this.matchCandidateIndex = 0
28
+        this.otp = null
29
+        this.matchQueue = matchQueue?.length ? matchQueue : []
30
+        this.fOrder = []
31
+    }
32
+    clearPlaceholderMatches() {
33
+        this.matchQueue = this.matchQueue.filter(match => match.realId == false)
34
+        this.fOrder = this.fOrder.filter(
35
+            potentialFiance => potentialFiance.realId == false,
36
+        )
37
+        if (this.otp && this.otp.realId) {
38
+            this.otp = null
39
+        }
40
+    }
41
+    rank(id) {
42
+        const idQueue = this.matchQueue.map(
43
+            profileFacade => profileFacade.realId,
44
+        )
45
+        return idQueue.includes(id)
46
+            ? idQueue.indexOf(id)
47
+            : this.matchQueue.length + 1
48
+    }
49
+    prefers(p) {
50
+        return this.rank(p.realId) < this.rank(this.otp.realId)
51
+    }
52
+    getNextCandidate() {
53
+        if (this.matchCandidateIndex >= this.matchQueue.length) return null
54
+        this.matchCandidateIndex = this.matchCandidateIndex + 1
55
+        return this.matchQueue[this.matchCandidateIndex - 1]
56
+    }
57
+    engageTo(p) {
58
+        if (p.otp) {
59
+            p.otp.otp = null
60
+        }
61
+        if (this.otp) {
62
+            this.otp.otp = null
63
+        }
64
+
65
+        p.otp = this
66
+        p.fOrder.unshift(this)
67
+
68
+        this.otp = p
69
+        this.fOrder.unshift(p)
70
+        console.log(
71
+            'partners pref: ',
72
+            this.otp.matchQueue.map(f => f.realId).indexOf(this.realId) + 1,
73
+            'choice',
74
+        )
75
+    }
76
+}
77
+
78
+module.exports = class MatchService extends Schmervice.Service {
79
+    constructor(...args) {
80
+        super(...args)
81
+    }
82
+    async calcMatches(allQueuesByType) {
83
+        const seekerIds = Object.keys(allQueuesByType['seeker']).map(id => ({
84
+            profile_id: parseInt(id),
85
+            queue: allQueuesByType['seeker'][id],
86
+        }))
87
+        const posterIds = Object.keys(allQueuesByType['poster']).map(id => ({
88
+            profile_id: parseInt(id),
89
+            queue: allQueuesByType['poster'][id],
90
+        }))
91
+
92
+        const diff = Math.abs(posterIds.length - seekerIds.length)
93
+        const smallerList =
94
+            posterIds.length < seekerIds.length ? posterIds : seekerIds
95
+        // ADD DUMMY IDS TO THE SMALLER LIST
96
+        for (let d = 0; d < diff; d++) {
97
+            smallerList.push({ profile_id: 'dummy', queue: [] })
98
+        }
99
+        console.log(seekerIds)
100
+        console.log(posterIds)
101
+        // const yins = seekerIds.map(id => allProfileFacadesWithQueue[id])
102
+        // const yangs = posterIds.map(id => allProfileFacadesWithQueue[id])
103
+
104
+        // You only need to engage from one side
105
+        // engageEveryone(yins)
106
+        return []
107
+    }
108
+}

+ 28
- 1
backend/lib/services/matchqueue.js ファイルの表示

4
     constructor(...args) {
4
     constructor(...args) {
5
         super(...args)
5
         super(...args)
6
     }
6
     }
7
-
7
+    /**
8
+     * Gets a list of queue entries for profileId
9
+     * @param {number} profileId
10
+     * @returns {array} MatchQueue
11
+     */
8
     async getQueue(profileId) {
12
     async getQueue(profileId) {
9
         const { MatchQueue } = this.server.models()
13
         const { MatchQueue } = this.server.models()
10
         return await MatchQueue.query()
14
         return await MatchQueue.query()
11
             .where('profile_id', profileId)
15
             .where('profile_id', profileId)
12
             .andWhere('is_deleted', false)
16
             .andWhere('is_deleted', false)
13
     }
17
     }
18
+    /**
19
+     * Returns queues by profile id by user type
20
+     * @returns {object}
21
+     */
22
+    async getAllQueues() {
23
+        const { MatchQueue } = this.server.models()
24
+        const queueEntries = await MatchQueue.query()
25
+            .andWhere('is_deleted', false)
26
+            .withGraphFetched('user')
27
+
28
+        const queueByProfileId = {
29
+            poster: {},
30
+            seeker: {},
31
+        }
32
+        queueEntries.forEach(entry => {
33
+            const type = entry.user.is_poster == 0 ? 'poster' : 'seeker'
34
+            if (!queueByProfileId[type][entry.profile_id]) {
35
+                queueByProfileId[type][entry.profile_id] = []
36
+            }
37
+            queueByProfileId[type][entry.profile_id].push(entry.target_id)
38
+        })
39
+        return queueByProfileId
40
+    }
14
     /**
41
     /**
15
      * Saves Scored Profile Ids to MatchQue IN ORDER
42
      * Saves Scored Profile Ids to MatchQue IN ORDER
16
      * @param {number} profileId
43
      * @param {number} profileId

+ 0
- 129
backend/lib/services/profile.js ファイルの表示

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
 
4
 
5
-const runMatch = (allYins, allYangs) => {
6
-    console.log(allYins.length, ':', allYangs.length)
7
-    // You only need to engage from one side
8
-    engageEveryone(allYins)
9
-
10
-    console.log('---')
11
-}
12
-const engageEveryone = allYins => {
13
-    let done
14
-    do {
15
-        console.log('rerunning...')
16
-        done = true
17
-        allYins.forEach(yin => {
18
-            // Keep matching if no true pairing is found
19
-            console.log(yin.realId, yin.otp?.realId)
20
-            if (!yin.otp) {
21
-                done = false
22
-                const yang = yin.getNextCandidate()
23
-                if (!yang.otp || yang.prefers(yin)) {
24
-                    yin.engageTo(yang)
25
-                }
26
-            } else {
27
-                console.log(yin.otp.realId)
28
-            }
29
-        })
30
-    } while (!done)
31
-}
32
-class ProfileFacade {
33
-    constructor(id, matchQueue) {
34
-        this.realId = id ? id : undefined
35
-
36
-        this.matchCandidateIndex = 0
37
-        this.otp = null
38
-        this.matchQueue = matchQueue?.length ? matchQueue : []
39
-        this.fOrder = []
40
-    }
41
-    clearPlaceholderMatches() {
42
-        this.matchQueue = this.matchQueue.filter(match => match.realId == false)
43
-        this.fOrder = this.fOrder.filter(
44
-            potentialFiance => potentialFiance.realId == false,
45
-        )
46
-        if (this.otp && this.otp.realId) {
47
-            this.otp = null
48
-        }
49
-    }
50
-    rank(id) {
51
-        const idQueue = this.matchQueue.map(
52
-            profileFacade => profileFacade.realId,
53
-        )
54
-        return idQueue.includes(id)
55
-            ? idQueue.indexOf(id)
56
-            : this.matchQueue.length + 1
57
-    }
58
-    prefers(p) {
59
-        return this.rank(p.realId) < this.rank(this.otp.realId)
60
-    }
61
-    getNextCandidate() {
62
-        if (this.matchCandidateIndex >= this.matchQueue.length) return null
63
-        this.matchCandidateIndex = this.matchCandidateIndex + 1
64
-        return this.matchQueue[this.matchCandidateIndex - 1]
65
-    }
66
-    engageTo(p) {
67
-        if (p.otp) {
68
-            p.otp.otp = null
69
-        }
70
-        if (this.otp) {
71
-            this.otp.otp = null
72
-        }
73
-
74
-        p.otp = this
75
-        p.fOrder.unshift(this)
76
-
77
-        this.otp = p
78
-        this.fOrder.unshift(p)
79
-        console.log(
80
-            'partners pref: ',
81
-            this.otp.matchQueue.map(f => f.realId).indexOf(this.realId) + 1,
82
-            'choice',
83
-        )
84
-    }
85
-}
86
-
87
 const magic = 1000
5
 const magic = 1000
88
 const scoreResponses = (seeker, potentialMatch) => {
6
 const scoreResponses = (seeker, potentialMatch) => {
89
     if (seeker.responses.length != potentialMatch.responses.length)
7
     if (seeker.responses.length != potentialMatch.responses.length)
349
         // Order by score
267
         // Order by score
350
         return scoredProfilesWithDistance.sort((a, b) => a.score - b.score)
268
         return scoredProfilesWithDistance.sort((a, b) => a.score - b.score)
351
     }
269
     }
352
-    async calcMatches() {
353
-        const { Profile } = this.server.models()
354
 
270
 
355
-        // Grab all profiles with matchQueues
356
-        let allProfiles = await Profile.query().withGraphFetched('user')
357
-        const seekerIds = allProfiles
358
-            .filter(profile => profile.user.is_poster == 0)
359
-            .map(profile => profile.profile_id)
360
-        const posterIds = allProfiles
361
-            .filter(profile => profile.user.is_poster == 1)
362
-            .map(profile => profile.profile_id)
363
-        let diff = Math.abs(posterIds.length - seekerIds.length)
364
-        let smallerList =
365
-            posterIds.length < seekerIds.length ? posterIds : seekerIds
366
-        // ADD DUMMY IDS TO THE SMALLER LIST
367
-        for (let d = 0; d < diff; d++) {
368
-            smallerList.push(allProfiles.length + d)
369
-        }
370
-        // !:FAKE Score everyone
371
-        const scoredProfileQueuesById = {}
372
-        for (let profile of allProfiles) {
373
-            const profileQueue = await this.scoreProfilesFor(
374
-                profile.profile_id,
375
-                10000,
376
-                'mile',
377
-            )
378
-            scoredProfileQueuesById[profile.profile_id] = profileQueue.map(
379
-                profile => profile.profile_id,
380
-            )
381
-        }
382
-        const allProfileFacadesWithQueue = allProfiles.map(profile => {
383
-            const profileFacadeQueue = scoredProfileQueuesById[
384
-                profile.profile_id
385
-            ].map(id => {
386
-                const subQueue = scoredProfileQueuesById[id].map(
387
-                    id => new ProfileFacade(id),
388
-                )
389
-                return new ProfileFacade(id, subQueue)
390
-            })
391
-            return new ProfileFacade(profile.profile_id, profileFacadeQueue)
392
-        })
393
-        // // ! FAKE -- END
394
-        const yins = seekerIds.map(id => allProfileFacadesWithQueue[id])
395
-        const yangs = posterIds.map(id => allProfileFacadesWithQueue[id])
396
-
397
-        runMatch(yins, yangs)
398
-        return yins
399
-    }
400
     /**
271
     /**
401
      * Use the db for zipcode info
272
      * Use the db for zipcode info
402
      * @param {string} zipCode
273
      * @param {string} zipCode

読み込み中…
キャンセル
保存