diff --git a/backend/typescript/utilities/delegation-algorithm/index.ts b/backend/typescript/utilities/delegation-algorithm/index.ts new file mode 100644 index 0000000..3e53653 --- /dev/null +++ b/backend/typescript/utilities/delegation-algorithm/index.ts @@ -0,0 +1,49 @@ +import { sequelize } from "../../models"; + +import Application from '../../models/application.model'; +import ApplicationDashboardModel from '../../models/applicationDashboard.model'; +import User from '../../models/user.model'; + +import { roundRobinPairs, assignApplicationsToPairs } from './roundRobinPairs'; + + +async function delegationAlgorithm() { + sequelize.authenticate(); + + const applications = await loadApplications(); + const reviewers = await loadReviewers(); + + const uniquePairs = roundRobinPairs(reviewers); + const totalPairs = assignApplicationsToPairs(uniquePairs, applications); + + await Promise.all(applications.map(async function (application, i) { + return Promise.all(totalPairs[i].map(async function (reviewer) { + await ApplicationDashboardModel.create({ + applicationId: application.id, + reviewerId: reviewer.id, + reviewerEmail: reviewer.email, + passionFSG: 0, + teamPlayer: 0, + desireToLearn: 0, + skill: 0, + skillCategory: 'junior', + reviewerComments: '', + recommendedSecondChoice: 'N/A' + }); + })) + })) +} + +async function loadReviewers(): Promise { + return await User.findAll({ + attributes: ['id', 'email'], + }); +} + +async function loadApplications(): Promise { + return await Application.findAll({ + attributes: ['id'] + }); +} + +delegationAlgorithm(); diff --git a/backend/typescript/utilities/delegation-algorithm/roundRobinPairs.ts b/backend/typescript/utilities/delegation-algorithm/roundRobinPairs.ts new file mode 100644 index 0000000..2e53fab --- /dev/null +++ b/backend/typescript/utilities/delegation-algorithm/roundRobinPairs.ts @@ -0,0 +1,51 @@ +import { isEqual } from 'lodash'; + +import Application from '../../models/application.model'; +import User from '../../models/user.model'; + + +// Generate a list of all unique pairs of users — from a given list of users +// Uses Round Robin algorithm for optimized time complexity +export function roundRobinPairs(developers: (User | {})[]): [User, User][] { + if (developers.length % 2 !== 0) { + developers.push({}); + } + + const fixedDeveloper = developers[0]; + let rotatingDevelopers = developers.slice(1); + const pairs: [(User | {}), (User | {})][] = []; + + for (let i = 0; i < developers.length - 1; i++) { + for (let j = 0; j < developers.length / 2 - 1; j++) { + if (!isEqual(rotatingDevelopers[j], {}) && !isEqual(rotatingDevelopers[developers.length - j - 2], {})) { + pairs.push([rotatingDevelopers[j], rotatingDevelopers[developers.length - j - 2]]); + } + } + if (!isEqual(fixedDeveloper, {}) && !isEqual(rotatingDevelopers[Math.floor(developers.length / 2) - 1], {})) { + pairs.push([fixedDeveloper, rotatingDevelopers[Math.floor(developers.length / 2) - 1]]); + } + rotatingDevelopers = rotatingDevelopers.slice(1).concat(rotatingDevelopers.slice(0, 1)); //rotate list + } + + shuffleArray(pairs); + return pairs as [User, User][]; +} + +// Multiply pairs to equal the number of applications +export function assignApplicationsToPairs(pairs: [User, User][], applications: Application[]): [User, User][] { + const totalPairsNeeded = applications.length; + while (pairs.length < totalPairsNeeded) { + pairs.push(...pairs.slice(0, totalPairsNeeded - pairs.length)); + } + + shuffleArray(pairs); + return pairs; +} + +// Shuffle a list of items +function shuffleArray(array: T[]): void { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } +}