diff --git a/tests/match_test.py b/tests/match_test.py index fb487b1d..984c86c4 100644 --- a/tests/match_test.py +++ b/tests/match_test.py @@ -9,8 +9,10 @@ from yelp_beans.logic.subscription import get_specs_from_subscription from yelp_beans.logic.subscription import store_specs_from_subscription +from yelp_beans.matching.group_match import get_previous_meetings_counts from yelp_beans.matching.match import generate_meetings -from yelp_beans.matching.pair_match import get_previous_pair_meetings +from yelp_beans.matching.match_utils import get_counts_for_pairs +from yelp_beans.matching.match_utils import get_previous_meetings from yelp_beans.matching.pair_match import save_pair_meetings from yelp_beans.models import Meeting from yelp_beans.models import MeetingParticipant @@ -98,7 +100,7 @@ def test_get_previous_meetings(minimal_database): MeetingParticipant(meeting=meeting, user=user2).put() MeetingParticipant(meeting=meeting, user=user1).put() - assert get_previous_pair_meetings(subscription) == set([(user1.id(), user2.id())]) + assert get_previous_meetings(subscription) == set([(user1.id(), user2.id())]) def test_get_previous_meetings_multi_subscription(minimal_database): @@ -114,8 +116,25 @@ def test_get_previous_meetings_multi_subscription(minimal_database): MeetingParticipant(meeting=meeting, user=user2).put() MeetingParticipant(meeting=meeting, user=user1).put() - assert get_previous_pair_meetings(subscription1) == set([(user1.id(), user2.id())]) - assert get_previous_pair_meetings(subscription2) == set([]) + assert get_previous_meetings(subscription1) == set([(user1.id(), user2.id())]) + assert get_previous_meetings(subscription2) == set([]) + + +def test_get_previous_multi_meetings(minimal_database): + pref_1 = SubscriptionDateTime(datetime=datetime.now() - timedelta(weeks=MEETING_COOLDOWN_WEEKS - 1)).put() + subscription = MeetingSubscription(title='all engineering weekly', datetime=[pref_1]).put() + user_pref = UserSubscriptionPreferences(preference=pref_1, subscription=subscription).put() + user1 = User(email='a@yelp.com', metadata={'department': 'dept'}, subscription_preferences=[user_pref]).put() + user2 = User(email='a@yelp.com', metadata={'department': 'dept2'}, subscription_preferences=[user_pref]).put() + meeting_spec = MeetingSpec(meeting_subscription=subscription, datetime=pref_1.get().datetime).put() + meeting1 = Meeting(meeting_spec=meeting_spec, cancelled=False).put() + meeting2 = Meeting(meeting_spec=meeting_spec, cancelled=False).put() + MeetingParticipant(meeting=meeting1, user=user2).put() + MeetingParticipant(meeting=meeting1, user=user1).put() + MeetingParticipant(meeting=meeting2, user=user2).put() + MeetingParticipant(meeting=meeting2, user=user1).put() + + assert get_previous_meetings(subscription) == set([(user1.id(), user2.id()), (user1.id(), user2.id())]) def test_get_previous_meetings_no_specs(database_no_specs): @@ -129,7 +148,7 @@ def test_get_previous_meetings_no_specs(database_no_specs): MeetingParticipant(meeting=meeting, user=user2).put() MeetingParticipant(meeting=meeting, user=user1).put() - assert get_previous_pair_meetings(subscription) == set([]) + assert get_previous_meetings(subscription) == set([]) def test_generate_save_meetings(minimal_database, subscription): @@ -177,3 +196,24 @@ def test_no_re_matches(minimal_database): matches, unmatched = generate_meetings(users, meeting_spec, previous_meetings) assert len(unmatched) == num_users - 2 assert [(match[0].key.id(), match[1].key.id()) for match in matches] == [(users[0].key.id(), users[1].key.id())] + + +def test_pair_to_counts(): + pairs = [('user1', 'user2'), ('user1', 'user2'), ('user2', 'user3')] + counts = get_counts_for_pairs(pairs) + assert (counts[('user2', 'user3')] == 1) + assert(counts[('user1', 'user2')] == 2) + + +def test_get_previous_meetings_counts(): + pref_1 = SubscriptionDateTime(datetime=datetime.now() - timedelta(weeks=MEETING_COOLDOWN_WEEKS - 1)).put() + subscription = MeetingSubscription(title='all engineering weekly', datetime=[pref_1]).put() + user_pref = UserSubscriptionPreferences(preference=pref_1, subscription=subscription).put() + user1 = User(email='a@yelp.com', metadata={'department': 'dept'}, subscription_preferences=[user_pref]).put() + user2 = User(email='b@yelp.com', metadata={'department': 'dept2'}, subscription_preferences=[user_pref]).put() + meeting_spec = MeetingSpec(meeting_subscription=subscription, datetime=pref_1.get().datetime).put() + meeting = Meeting(meeting_spec=meeting_spec, cancelled=False).put() + MeetingParticipant(meeting=meeting, user=user2).put() + MeetingParticipant(meeting=meeting, user=user1).put() + + assert(get_previous_meetings_counts([user1.get(), user2.get()], subscription) == {(user1.id(), user2.id()): 1}) diff --git a/yelp_beans/matching/group_match.py b/yelp_beans/matching/group_match.py new file mode 100644 index 00000000..8a570c84 --- /dev/null +++ b/yelp_beans/matching/group_match.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +import itertools + +from yelp_beans.matching.match_utils import get_counts_for_pairs +from yelp_beans.matching.match_utils import get_previous_meetings + + +def get_previous_meetings_counts(users, subscription): + previous_meetings = get_previous_meetings(subscription) + counts_for_pairs = get_counts_for_pairs(previous_meetings) + userids = sorted([user.key.id() for user in users]) + all_pairs_counts = {pair: 0 for pair in itertools.combinations(userids, 2)} + for pair in counts_for_pairs: + all_pairs_counts[pair] = counts_for_pairs[pair] + return all_pairs_counts diff --git a/yelp_beans/matching/match_utils.py b/yelp_beans/matching/match_utils.py new file mode 100644 index 00000000..2212b1b0 --- /dev/null +++ b/yelp_beans/matching/match_utils.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +import logging +from collections import defaultdict +from datetime import datetime +from datetime import timedelta + +from google.appengine.ext import ndb + +from yelp_beans.logic.config import get_config +from yelp_beans.models import Meeting +from yelp_beans.models import MeetingParticipant +from yelp_beans.models import MeetingSpec + + +def get_counts_for_pairs(pairs): + counts = {} + for pair in pairs: + if pair in counts: + counts[pair] += 1 + else: + counts[pair] = 1 + return counts + + +def get_previous_meetings(subscription, cooldown=None): + + if cooldown is None: + cooldown = get_config()['meeting_cooldown_weeks'] + + meetings = defaultdict(list) + + # get all meeting specs from x weeks ago til now + time_threshold_for_meetings = datetime.now() - timedelta(weeks=cooldown) + + meeting_spec_keys = [ + spec.key for spec in MeetingSpec.query( + ndb.AND(MeetingSpec.datetime > time_threshold_for_meetings, + MeetingSpec.meeting_subscription == subscription) + ).fetch() + ] + + logging.info('Previous Meeting History: ') + logging.info([meeting.get().datetime.strftime("%Y-%m-%d %H:%M") for meeting in meeting_spec_keys]) + + if meeting_spec_keys == []: + return set([]) + + # get all meetings from meeting specs + meeting_keys = [meeting.key for meeting in Meeting.query().filter( + Meeting.meeting_spec.IN(meeting_spec_keys)).fetch()] + + if meeting_keys == []: + return set([]) + + # get all participants from meetings + participants = MeetingParticipant.query().filter( + MeetingParticipant.meeting.IN(meeting_keys) + ).fetch() + + if participants == []: + return set([]) + + # group by meeting Id + for participant in participants: + meetings[participant.meeting.id()].append(participant.user) + + # ids are sorted, all matches should be in increasing order by id for the matching algorithm to work + disallowed_meetings = set([tuple(sorted(meeting, key=lambda Key: Key.id())) for meeting in meetings.values()]) + + logging.info('Past Meetings') + logging.info([tuple([meeting.get().get_username() for meeting in meeting]) for meeting in disallowed_meetings]) + + disallowed_meetings = {tuple([meeting.id() for meeting in meeting]) for meeting in disallowed_meetings} + + return disallowed_meetings diff --git a/yelp_beans/matching/pair_match.py b/yelp_beans/matching/pair_match.py index 0cb57740..b85c1b95 100644 --- a/yelp_beans/matching/pair_match.py +++ b/yelp_beans/matching/pair_match.py @@ -5,18 +5,13 @@ import itertools import logging -from collections import defaultdict -from datetime import datetime -from datetime import timedelta import networkx as nx -from google.appengine.ext import ndb -from yelp_beans.logic.config import get_config from yelp_beans.logic.user import user_preference +from yelp_beans.matching.match_utils import get_previous_meetings from yelp_beans.models import Meeting from yelp_beans.models import MeetingParticipant -from yelp_beans.models import MeetingSpec def get_disallowed_meetings(users, prev_meeting_tuples, spec): @@ -53,59 +48,6 @@ def save_pair_meetings(matches, spec): )) -def get_previous_pair_meetings(subscription, cooldown=None): - - if cooldown is None: - cooldown = get_config()['meeting_cooldown_weeks'] - - meetings = defaultdict(list) - - # get all meeting specs from x weeks ago til now - time_threshold_for_meetings = datetime.now() - timedelta(weeks=cooldown) - - meeting_spec_keys = [ - spec.key for spec in MeetingSpec.query( - ndb.AND(MeetingSpec.datetime > time_threshold_for_meetings, - MeetingSpec.meeting_subscription == subscription) - ).fetch() - ] - - logging.info('Previous Meeting History: ') - logging.info([meeting.get().datetime.strftime("%Y-%m-%d %H:%M") for meeting in meeting_spec_keys]) - - if meeting_spec_keys == []: - return set([]) - - # get all meetings from meeting specs - meeting_keys = [meeting.key for meeting in Meeting.query().filter( - Meeting.meeting_spec.IN(meeting_spec_keys)).fetch()] - - if meeting_keys == []: - return set([]) - - # get all participants from meetings - participants = MeetingParticipant.query().filter( - MeetingParticipant.meeting.IN(meeting_keys) - ).fetch() - - if participants == []: - return set([]) - - # group by meeting Id - for participant in participants: - meetings[participant.meeting.id()].append(participant.user) - - # ids are sorted, all matches should be in increasing order by id for the matching algorithm to work - disallowed_meetings = set([tuple(sorted(meeting, key=lambda Key: Key.id())) for meeting in meetings.values()]) - - logging.info('Past Meetings') - logging.info([tuple([meeting.get().get_username() for meeting in meeting]) for meeting in disallowed_meetings]) - - disallowed_meetings = {tuple([meeting.id() for meeting in meeting]) for meeting in disallowed_meetings} - - return disallowed_meetings - - def generate_pair_meetings(users, spec, prev_meeting_tuples=None): """ Returns 2 tuples: @@ -114,7 +56,7 @@ def generate_pair_meetings(users, spec, prev_meeting_tuples=None): - unmatched_user_ids: users with no matches. """ if prev_meeting_tuples is None: - prev_meeting_tuples = get_previous_pair_meetings(spec.meeting_subscription) + prev_meeting_tuples = get_previous_meetings(spec.meeting_subscription) uid_to_users = {user.key.id(): user for user in users} user_ids = sorted(uid_to_users.keys())