Skip to content

Commit

Permalink
Adds persistance to pairing algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
tarikeshaq committed Oct 13, 2020
1 parent f76a14a commit eb3ff7d
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 15 deletions.
2 changes: 1 addition & 1 deletion app/model/pairing.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self,
self.pairing_id = str(uuid.uuid4())
self.user1_slack_id = user1_slack_id
self.user2_slack_id = user2_slack_id
self.ttl = "" # TODO
self.ttl = "TODO" # TODO

def get_attachment(self) -> Dict[str, Any]:
"""Return slack-formatted attachment (dictionary) for pairing."""
Expand Down
4 changes: 2 additions & 2 deletions app/scheduler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from flask import Flask
from apscheduler.schedulers.background import BackgroundScheduler
from .modules.random_channel import RandomChannelPromoter
from .modules.pairing import Pairing
from .modules.pairing import PairingSchedule
from .modules.base import ModuleBase
from db.facade import DBFacade
from typing import Tuple, List
Expand Down Expand Up @@ -39,4 +39,4 @@ def __add_job(self, module: ModuleBase):
def __init_periodic_tasks(self):
"""Add jobs that fire every interval."""
self.__add_job(RandomChannelPromoter(*self.args))
self.__add_job(Pairing(*self.args, self.facade))
self.__add_job(PairingSchedule(*self.args, self.facade))
58 changes: 48 additions & 10 deletions app/scheduler/modules/pairing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
from interface.slack import Bot
from random import shuffle
from .base import ModuleBase
from typing import Dict, List, Tuple, Any
from typing import Dict, List, Tuple, Any, Set
from flask import Flask
from config import Config
from db.facade import DBFacade
from app.model import Pairing
import logging


class Pairing(ModuleBase):
class PairingSchedule(ModuleBase):
"""Module that matches 2 launchpad members each week"""

NAME = 'Match launch pad members randomly'
Expand Down Expand Up @@ -46,18 +47,55 @@ def do_it(self):
def __pair_users(self, users: List[str]) -> List[List[str]]:
"""
Creates pairs of users that haven't been matched before
:param users: A list of slack ids of all users to match
If a pairing cannot be done, then the history of pairings is
purged, and the algorithm is run again.
"""
# TODO: Clean this up into a more concrete algorithm
shuffle(users)
already_added = set()
pairs = []
pair = []
for i, user in enumerate(users):
pair.append(user)
if i % 2 != 0:
pairs.append(pair)
pair = []
if user in already_added:
continue
previously_paired = self.__get_previous_pairs(user)
for j in range(i + 1, len(users)):
other_user = users[j]
if other_user not in previously_paired and other_user not in already_added:
self.__persist_pairing(user, other_user)
pairs.append([user, other_user])
already_added.add(user)
already_added.add(other_user)
break
not_paired = list(filter(lambda user: user not in already_added, users))
# If we have an odd number of people that is not 1
# We put the odd person out in one of the groups
# So we might have a group of 3
if len(pair) == 1 and len(pairs) > 0:
pairs[len(pairs) - 1].append(pair[0])
return pairs
if len(not_paired) == 1 and len(pairs) > 0:
pairs[len(pairs) - 1].append(not_paired[0])
elif len(not_paired) > 1:
self.__purge_pairings()
return self.__pair_users(users)
else:
return pairs

def __get_previous_pairs(self, user: str) -> Set[str]:
logging.info(f"Getting previous pairs for {user}")
pairings = self.facade.query_or(Pairing, [('user1_slack_id', user), ('user2_slack_id', user)])
res = set()
for pairing in pairings:
other = pairing.user1_slack_id if pairing.user2_slack_id == user else pairing.user2_slack_id
res.add(other)
logging.info(f"Previous pairings are {res}")
return res

def __persist_pairing(self, user1_slack_id: str, user2_slack_id: str):
pairing = Pairing(user1_slack_id, user2_slack_id)
reverse_pairing = Pairing(user2_slack_id, user1_slack_id)
self.facade.store(pairing)
self.facade.store(reverse_pairing)

def __purge_pairings(self):
self.facade.delete_all(Pairing)
12 changes: 10 additions & 2 deletions db/dynamodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def get_set_attrs(self, table_name: str) -> List[str]:
:raise: TypeError if table does not exist
:return: list of strings of set attributes
"""
if table_name == self.users_table or table_name == self.projects_table:
if table_name == self.users_table or table_name == self.pairings_table:
return []
elif table_name == self.teams_table:
return ['team_leads', 'members']
Expand Down Expand Up @@ -209,7 +209,7 @@ def check_valid_table(self, table_name: str) -> bool:

def store(self, obj: T) -> bool:
Model = obj.__class__
if Model not in [User, Team, Project]:
if Model not in [User, Team, Project, Pairing]:
logging.error(f"Cannot store object {str(obj)}")
raise RuntimeError(f'Cannot store object{str(obj)}')

Expand Down Expand Up @@ -309,3 +309,11 @@ def delete(self, Model: Type[T], k: str):
self.CONST.get_key(table_name): k
}
)

def delete_all(self, Model: Type[T]):
logging.info(f"Deleting {Model.__name__}")
table_name = self.CONST.get_table_name(Model)
table = self.ddb.Table(table_name)
table.delete()
table.wait_until_not_exists()
self.__create_table(table_name)
10 changes: 10 additions & 0 deletions db/facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,13 @@ def delete(self, Model: Type[T], k: str):
:param k: ID or key of the object to remove (must be primary key)
"""
raise NotImplementedError

@abstractmethod
def delete_all(self, Model: Type[T]):
"""
Remove all entries from a table
:param Model: The table to remove all entries of
"""

raise NotImplementedError

0 comments on commit eb3ff7d

Please sign in to comment.