Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delegation Algorithm #48

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions backend/typescript/delegation-algorithm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Delegation Algorithm

Run the delegation algorithm from the command line of the running application:

Install ts-node:

```
npm install -g ts-node
```

Run the algorithm:

```
ts-node /delegation-algorithm/src/index.ts
```

The `applicationdashboardtable` should be populated with the review data.
64 changes: 64 additions & 0 deletions backend/typescript/delegation-algorithm/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Sequelize from "sequelize"

import Application from "../../models/application.model";
import ApplicationDashboardModel from "../../models/applicationDashboard.model";
import User from "../../models/user.model";
import { sequelize } from "../../models";

import { roundRobinPairs, assignApplicationsToPairs } from "./roundRobinPairs";


const roles = ["Developer", "Designer"];

async function runDelegationAlgorithms() {
await Promise.all(roles.map(async function (role) {
await delegationAlgorithm(role);
}))
}

async function delegationAlgorithm(role: string) {
sequelize.authenticate();

const applications = await loadApplications(role);
const reviewers = await loadReviewers(role);

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(role: string): Promise<User[]> {
return await User.findAll({
attributes: ["id", "email"],
where: { role: role }
});
}

async function loadApplications(role: string): Promise<Application[]> {
return await Application.findAll({
attributes: ["id"],
where: { firstChoiceRole: { [Sequelize.Op.like]: `%${role}%` } }
});
}

runDelegationAlgorithms()
51 changes: 51 additions & 0 deletions backend/typescript/delegation-algorithm/src/roundRobinPairs.ts
Original file line number Diff line number Diff line change
@@ -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(reviewers: (User | {})[]): [User, User][] {
if (reviewers.length % 2 !== 0) {
reviewers.push({});
}

const fixedReviewer = reviewers[0];
let rotatingReviewers = reviewers.slice(1);
const pairs: [(User | {}), (User | {})][] = [];

for (let i = 0; i < reviewers.length - 1; i++) {
for (let j = 0; j < reviewers.length / 2 - 1; j++) {
if (!isEqual(rotatingReviewers[j], {}) && !isEqual(rotatingReviewers[reviewers.length - j - 2], {})) {
pairs.push([rotatingReviewers[j], rotatingReviewers[reviewers.length - j - 2]]);
}
}
if (!isEqual(fixedReviewer, {}) && !isEqual(rotatingReviewers[Math.floor(reviewers.length / 2) - 1], {})) {
pairs.push([fixedReviewer, rotatingReviewers[Math.floor(reviewers.length / 2) - 1]]);
}
rotatingReviewers = rotatingReviewers.slice(1).concat(rotatingReviewers.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<T>(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]];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,24 @@ const SEEDED_DATA = [
last_name: "Doe",
email: "[email protected]",
auth_id: "bide",
role: "User",
permission: "Reviewers",
role: "Developer",
},
{
first_name: "Jane",
last_name: "Doe",
email: "[email protected]",
auth_id: "none",
role: "Admin",
permission: "Reviewers",
role: "Developer",
},
{
first_name: "UW",
last_name: "Blueprint",
email: "[email protected]",
auth_id: "1Z4wyuonu9MhAi4VoAEiTMVj1iT2",
role: "Admin",
permission: "Reviewers",
role: "Developer",
},
];
// [email protected]
Expand Down Expand Up @@ -55,8 +58,13 @@ export const up: Migration = async ({ context: sequelize }) => {
primaryKey: true,
allowNull: false,
},
permission: {
type: DataType.ENUM("VP Talent", "Eteam", "Engineering", "Product", "Design", "Reviewers"),
allowNull: false,
defaultValue: "Reviewers"
},
role: {
type: DataType.ENUM("User", "Admin"),
type: DataType.ENUM("Developer", "Designer"),
allowNull: false,
},
createdAt: DataType.DATE,
Expand Down
7 changes: 5 additions & 2 deletions backend/typescript/models/user.model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint import/no-cycle: 0 */

import { Column, DataType, HasMany, Model, Table } from "sequelize-typescript";
import { Role } from "../types";
import { Permission, Role } from "../types";
import ApplicationDashboardTable from "./applicationDashboard.model";

@Table({ tableName: "users" })
Expand All @@ -21,7 +21,10 @@ export default class User extends Model {
@Column({ type: DataType.INTEGER, primaryKey: true, autoIncrement: true })
id!: number;

@Column({ type: DataType.ENUM("User", "Admin") })
@Column({ type: DataType.ENUM("VP Talent", "Eteam", "Engineering", "Product", "Design", "Reviewers"), allowNull: false, defaultValue: 'Reviewers' })
permission!: Permission;

@Column({ type: DataType.ENUM("Developer, Designer"), allowNull: false })
role!: Role;

@HasMany(() => ApplicationDashboardTable)
Expand Down
4 changes: 3 additions & 1 deletion backend/typescript/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type Role = "User" | "Admin";
export type Permission = "VP Talent" | "Eteam" | "Engineering" | "Product" | "Design" | "Reviewers";

export type Role = "Developer" | "Designer";

export enum StatusType {
ACCEPTED = "accepted",
Expand Down
Loading