Ejemplo n.º 1
0
def test_get_matches(mocker):
    # Given these possible connections where every pair is scored 1, there is only one matching that scores
    # as high as 2, we better get that!
    #
    #    A---B
    #     \ /
    #      C
    #      |
    #      D

    match_maker = MatchMaker(match_request_since_date='does not matter')
    match_maker._pair_score_and_topic = mocker.Mock()
    match_maker._pair_score_and_topic.return_value = (1, 'history')

    match_maker._possible_matches = {
        'A': ['B', 'C'],
        'B': ['A', 'C'],
        'C': ['A', 'B', 'D'],
        'D': ['C'],
    }
    matches = match_maker._get_matches()
    matches = set(
        key(*m) for m in matches
    )  # convert to set of sorted tuples so that ordering doesn't matter
    assert matches == {key('A', 'B'), key('C', 'D')}
    for forbidden_call in [
            call('D', 'A'),
            call('A', 'D'),
            call('D', 'B'),
            call('B', 'D')
    ]:
        assert forbidden_call not in match_maker._pair_score_and_topic.call_args_list, \
            "we should not make score calls for pairs that aren't in _possible_matches (very inefficient)"
Ejemplo n.º 2
0
def test_match_unmatched(mocker):
    # scenario: assume that the matches are A+B in the topic of 'math' and C+D in the topic of 'history', and E and F are
    # unmatched. E is a _possible_match with A or C in the topic of 'math' and F is a _possible_match with A or C in the
    # topic of 'history' therefore the final matching should be A+B+E in 'math' and C+D+F in 'history'
    match_maker = MatchMaker(match_request_since_date='does not matter')
    matches = [['A', 'B'], ['C', 'D']]

    def mock_pair_score_and_topic(p1, p2):
        return {
            key('A', 'B'): (1, 'math'),
            key('C', 'D'): (1, 'history'),
        }[key(p1, p2)]

    match_maker._pair_score_and_topic = mocker.Mock()
    match_maker._pair_score_and_topic.side_effect = mock_pair_score_and_topic
    match_maker._match_requests_profile_to_topic = {
        'A': ['math'],
        'B': ['math'],
        'C': ['history'],
        'D': ['history'],
        'E': ['math'],
        'F': ['history'],
    }
    match_maker._possible_matches = {
        'E': ['A', 'C'],
        'F': ['A', 'C'],
        # really the other keys would be here, but the method wouldn't currently call them
    }
    matches = match_maker._match_unmatched(matches)
    matches = set(
        key(*m) for m in matches
    )  # convert to set of sorted tuples so that ordering doesn't matter
    assert matches == {key('A', 'B', 'E'), key('C', 'D', 'F')}
Ejemplo n.º 3
0
def test_not_memoized_pair_score_and_topic():
    match_maker = MatchMaker(match_request_since_date='does not matter')
    now = datetime.now().astimezone(timezone.utc)
    match_maker._recent_match_by_profile_pair = {
        key('A', 'B'): {
            'date': now - timedelta(weeks=20)
        },
    }
    match_maker._most_recent_match_by_profile = {
        key('A'): {
            'date': now - timedelta(weeks=20)
        },
        key('B'): {
            'date': now - timedelta(weeks=20)
        },
    }
    match_maker._recent_match_by_profile_pair_and_topic = {
        key('A', 'B', 'science'): {
            'date': now - timedelta(weeks=10)
        },
        key('A', 'B', 'art'): {
            'date': now - timedelta(weeks=20)
        },
    }
    match_maker._match_requests_profile_to_topic = {
        'A': {'science', 'art', 'history'},
        'B': {'science', 'art', 'religion'},
    }
    score, topic = match_maker._not_memoized_pair_score_and_topic('A', 'B')
    assert score > 0
    assert score != int(
        score
    ), "score is a whole number, that's unlikely if we're really exercising the functionality"
    assert topic == 'art'
Ejemplo n.º 4
0
def test_select_topic():
    match_maker = MatchMaker(match_request_since_date='does not matter')
    now = datetime.now().astimezone(timezone.utc)
    match_maker._recent_match_by_profile_pair_and_topic = {
        key('A', 'B', 'science'): {
            'date': now - timedelta(weeks=10)
        },
        key('A', 'B', 'art'): {
            'date': now - timedelta(weeks=20)
        },
    }
    met_recently = True

    topics = {'science', 'art', 'history'}
    selected_topic = match_maker._select_topic('A', 'B', met_recently, topics)
    assert selected_topic == 'history', 'choose the topic they have not yet met in'

    topics = {'science', 'art'}
    selected_topic = match_maker._select_topic('A', 'B', met_recently, topics)
    assert selected_topic == 'art', 'if they have already met in all topics, choose the least recent topic'
Ejemplo n.º 5
0
def test_penalty_based_on_recent_pairing():
    match_maker = MatchMaker(match_request_since_date='does not matter')
    now = datetime.now().astimezone(timezone.utc)
    match_maker._recent_match_by_profile_pair = {
        key('A', 'B'): {
            'date': now - timedelta(days=1)
        },
        key('A', 'C'): {
            'date': now - timedelta(weeks=10)
        },
        key('A', 'D'): {
            'date': now - timedelta(weeks=20)
        },
        # key('A', 'E'): {'date': now - infinity},  <- no entry is treated as never having met
    }
    scoreAB = match_maker._penalty_based_on_recent_pairing('A', 'B')
    scoreAC = match_maker._penalty_based_on_recent_pairing('A', 'C')
    scoreAD = match_maker._penalty_based_on_recent_pairing('A', 'D')
    scoreAE = match_maker._penalty_based_on_recent_pairing('A', 'E')
    assert scoreAB > scoreAC > scoreAD > scoreAE, 'the penalty is greater if they have met more recently'
    assert scoreAB == float(
        'infinity'), 'having just met implies "infinite" penalty'
Ejemplo n.º 6
0
def test_boost_based_upon_recency_of_match():
    match_maker = MatchMaker(match_request_since_date='does not matter')
    now = datetime.now().astimezone(timezone.utc)
    match_maker._most_recent_match_by_profile = {
        key('A'): {
            'date': now
        },
        key('B'): {
            'date': now - timedelta(weeks=1)
        },
        key('C'): {
            'date': now - timedelta(weeks=2)
        },
        # key('D'): {'date': now - infinity},  <- no entry is treated as never having met
        # key('E'): {'date': now - infinity},  <- no entry is treated as never having met
    }
    scoreAB = match_maker._boost_based_upon_recency_of_match('A', 'B')
    scoreAC = match_maker._boost_based_upon_recency_of_match('A', 'C')
    scoreAD = match_maker._boost_based_upon_recency_of_match('A', 'D')
    scoreDE = match_maker._boost_based_upon_recency_of_match('D', 'E')
    assert scoreAB < scoreAC < scoreAD < scoreDE, 'there is a larger boost for those who have not met in a while'
    assert scoreDE == 2, \
        'boosts of the individual profiles are additive, and if a player has never been in a match their boost is 1'
Ejemplo n.º 7
0
 def mock_pair_score_and_topic(p1, p2):
     return {
         key('A', 'B'): (1, 'math'),
         key('C', 'D'): (2, 'history'),
     }[key(p1, p2)]
Ejemplo n.º 8
0
def test_gather_data():
    now = datetime.now().astimezone(timezone.utc)
    prof1, prof2, prof3, prof4, prof5, prof6, prof_too_old = [
        SocialProfileFactory(email=f'prof{i+1}@email.com') for i in range(7)
    ]
    prof_too_old.email = '*****@*****.**'
    prof_too_old.save()

    topic_channel_science = TopicChannelFactory(name='science')
    topic_channel_art = TopicChannelFactory(name='art')

    with freeze_time(now - timedelta(weeks=52), tz_offset=0):
        MatchRequestFactory(profile=prof_too_old)

    with freeze_time(now - timedelta(weeks=1), tz_offset=0):
        MatchRequestFactory(
            profile=prof1,
            topic_channel=topic_channel_art,
        )
        MatchRequestFactory(
            profile=prof1,
            topic_channel=topic_channel_science,
        )
        MatchRequestFactory(profile=prof2, topic_channel=topic_channel_art)
        MatchRequestFactory(profile=prof3, topic_channel=topic_channel_science)
        MatchRequestFactory(profile=prof4, topic_channel=topic_channel_science)

    with freeze_time(now - timedelta(weeks=3), tz_offset=0):
        MatchFactory(
            topic_channel=topic_channel_art,
            profiles=(prof1, prof2, prof3),
        )
        MatchFactory(
            topic_channel=topic_channel_science,
            profiles=(prof4, prof5),
        )
        MatchFactory(
            topic_channel=topic_channel_science,
            profiles=(prof6, prof_too_old),
        )

    match_maker = MatchMaker(match_request_since_date=now - timedelta(weeks=2))
    match_maker._gather_data()

    # _recent_match_by_profile_pair, _recent_match_by_profile_pair, and _recent_match_by_profile_pair_and_topic
    # are used to look up information useful in creating a score
    assert 'prof_too_old' not in set(
        chain(*set(match_maker._recent_match_by_profile_pair.keys())))
    assert set(match_maker._recent_match_by_profile_pair.keys()) == {
        key('*****@*****.**', '*****@*****.**'),
        # note that the meeting with 3 people got turned into 3 pairs here
        key('*****@*****.**', '*****@*****.**'),
        key('*****@*****.**', '*****@*****.**'),
        key('*****@*****.**', '*****@*****.**'),
    }
    assert match_maker._recent_match_by_profile_pair[key(
        '*****@*****.**', '*****@*****.**')]['num_attending'] == 3

    assert set(match_maker._recent_match_by_profile_topic) == {
        key('*****@*****.**', 'art'),
        key('*****@*****.**', 'art'),
        key('*****@*****.**', 'art'),
        key('*****@*****.**', 'science'),
        key('*****@*****.**', 'science'),
    }

    assert set(match_maker._recent_match_by_profile_pair_and_topic.keys()) == {
        key('*****@*****.**', '*****@*****.**', 'art'),
        key('*****@*****.**', '*****@*****.**', 'art'),
        key('*****@*****.**', '*****@*****.**', 'art'),
        key('*****@*****.**', '*****@*****.**', 'science'),
    }

    # _match_requests_profile_to_topic, _match_requests_topic_to_profile, and _possible_matches
    # are used largely as filters to quickly find topics for people, people for topics, and people that share a topic
    # respectively
    assert match_maker._match_requests_profile_to_topic == {
        '*****@*****.**': {'science', 'art'},
        '*****@*****.**': {'art'},
        '*****@*****.**': {'science'},
        '*****@*****.**': {'science'},
    }
    assert match_maker._match_requests_topic_to_profile == {
        'art': {'*****@*****.**', '*****@*****.**'},
        'science': {'*****@*****.**', '*****@*****.**', '*****@*****.**'},
    }
    assert match_maker._possible_matches == {
        '*****@*****.**':
        {'*****@*****.**', '*****@*****.**', '*****@*****.**'},
        '*****@*****.**': {'*****@*****.**'},
        '*****@*****.**': {'*****@*****.**', '*****@*****.**'},
        '*****@*****.**': {'*****@*****.**', '*****@*****.**'},
    }