diff --git a/experiments/test_intermediate_votes.py b/experiments/test_intermediate_votes.py new file mode 100644 index 00000000..403f18fd --- /dev/null +++ b/experiments/test_intermediate_votes.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Simple test script to validate intermediate votes functionality +""" + +# Mock user data structure +class MockUser(dict): + def __init__(self, uid, supporters=None, opponents=None, karma=0, name="Test User"): + super().__init__() + self['uid'] = uid + self['supporters'] = supporters or [] + self['opponents'] = opponents or [] + self['karma'] = karma + self['name'] = name + self['programming_languages'] = [] + +# Mock config values (from the actual config) +POSITIVE_VOTES_PER_KARMA = 2 +NEGATIVE_VOTES_PER_KARMA = 3 + +def calculate_intermediate_votes(user): + """Calculate only intermediate votes (pending supporters/opponents)""" + up_votes = len(user["supporters"]) / POSITIVE_VOTES_PER_KARMA + down_votes = len(user["opponents"]) / NEGATIVE_VOTES_PER_KARMA + return up_votes - down_votes + +def test_intermediate_votes_calculation(): + print("Testing intermediate votes calculation...") + + # User with no pending votes + user1 = MockUser(1, supporters=[], opponents=[], karma=100) + intermediate1 = calculate_intermediate_votes(user1) + print(f"User 1 (no votes): {intermediate1} (expected: 0.0)") + assert intermediate1 == 0.0, f"Expected 0.0, got {intermediate1}" + + # User with 2 supporters (should give +1.0 intermediate votes) + user2 = MockUser(2, supporters=[3, 4], opponents=[], karma=50) + intermediate2 = calculate_intermediate_votes(user2) + print(f"User 2 (2 supporters): {intermediate2} (expected: 1.0)") + assert intermediate2 == 1.0, f"Expected 1.0, got {intermediate2}" + + # User with 3 opponents (should give -1.0 intermediate votes) + user3 = MockUser(3, supporters=[], opponents=[1, 2, 4], karma=25) + intermediate3 = calculate_intermediate_votes(user3) + print(f"User 3 (3 opponents): {intermediate3} (expected: -1.0)") + assert intermediate3 == -1.0, f"Expected -1.0, got {intermediate3}" + + # User with mixed votes + user4 = MockUser(4, supporters=[1, 5], opponents=[2, 6, 7], karma=75) + intermediate4 = calculate_intermediate_votes(user4) + expected4 = 2/POSITIVE_VOTES_PER_KARMA - 3/NEGATIVE_VOTES_PER_KARMA # 1.0 - 1.0 = 0.0 + print(f"User 4 (mixed votes): {intermediate4} (expected: {expected4})") + assert intermediate4 == expected4, f"Expected {expected4}, got {intermediate4}" + + print("All intermediate votes calculations passed!") + +def test_sorting_by_intermediate_votes(): + print("\nTesting sorting by intermediate votes...") + + users = [ + MockUser(1, supporters=[], opponents=[]), # 0.0 + MockUser(2, supporters=[3, 4], opponents=[]), # +1.0 + MockUser(3, supporters=[], opponents=[1, 2, 5]), # -1.0 + MockUser(4, supporters=[1, 2, 3, 4], opponents=[]), # +2.0 + MockUser(5, supporters=[1], opponents=[2, 3]), # +0.5 - 0.67 = -0.17 + ] + + # Sort by intermediate votes (descending) + users_sorted = sorted(users, key=calculate_intermediate_votes, reverse=True) + + print("Users sorted by intermediate votes (highest to lowest):") + for user in users_sorted: + intermediate = calculate_intermediate_votes(user) + print(f" User {user['uid']}: {intermediate}") + + # Verify order + expected_order = [4, 2, 1, 5, 3] # Based on intermediate votes + actual_order = [user['uid'] for user in users_sorted] + print(f"Expected order: {expected_order}") + print(f"Actual order: {actual_order}") + + assert actual_order == expected_order, f"Expected {expected_order}, got {actual_order}" + print("Sorting test passed!") + +if __name__ == "__main__": + test_intermediate_votes_calculation() + test_sorting_by_intermediate_votes() + print("\n✅ All tests passed! The intermediate votes functionality is working correctly.") \ No newline at end of file diff --git a/python/__main__.py b/python/__main__.py index cdcbf7f6..2721363b 100644 --- a/python/__main__.py +++ b/python/__main__.py @@ -60,6 +60,9 @@ def __init__( (patterns.PEOPLE_LANGUAGES, self.commands.top_langs), (patterns.BOTTOM_LANGUAGES, lambda: self.commands.top_langs(True)), + (patterns.TOP_VOTES, self.commands.top_votes), + (patterns.BOTTOM_VOTES, + lambda: self.commands.top_votes(True)), (patterns.WHAT_IS, self.commands.what_is), (patterns.WHAT_MEAN, self.commands.what_is), (patterns.APPLY_KARMA, self.commands.apply_karma), diff --git a/python/modules/commands.py b/python/modules/commands.py index 93d99817..7539b2ee 100644 --- a/python/modules/commands.py +++ b/python/modules/commands.py @@ -161,6 +161,27 @@ def top_langs( self.vk_instance.send_msg(built, self.peer_id) return + def top_votes( + self, + reverse: bool = False + ) -> NoReturn: + """Sends users top by intermediate votes.""" + if self.peer_id < 2e9: + return + maximum_users = self.matched.group("maximum_users") + maximum_users = int(maximum_users) if maximum_users else -1 + users = DataBuilder.get_users_sorted_by_intermediate_votes( + self.vk_instance, self.data_service, self.peer_id, not reverse) + users = [i for i in users if + (len(i["supporters"]) > 0 or len(i["opponents"]) > 0) or + ("programming_languages" in i and len(i["programming_languages"]) > 0) + ] + self.vk_instance.send_msg( + CommandsBuilder.build_top_users( + users, self.data_service, reverse, + self.karma_enabled, maximum_users), + self.peer_id) + def apply_karma(self) -> NoReturn: """Changes user karma.""" if self.peer_id < 2e9 or not self.karma_enabled or not self.matched or self.is_bot_selected: diff --git a/python/modules/data_builder.py b/python/modules/data_builder.py index c594f7e1..142216db 100644 --- a/python/modules/data_builder.py +++ b/python/modules/data_builder.py @@ -88,6 +88,35 @@ def get_users_sorted_by_name( users.reverse() return users + @staticmethod + def calculate_intermediate_votes( + user: BetterUser, + data: BetterBotBaseDataService + ) -> float: + """Calculate only intermediate votes (pending supporters/opponents)""" + up_votes = len(user["supporters"])/config.POSITIVE_VOTES_PER_KARMA + down_votes = len(user["opponents"])/config.NEGATIVE_VOTES_PER_KARMA + return up_votes - down_votes + + @staticmethod + def get_users_sorted_by_intermediate_votes( + vk_instance: Vk, + data: BetterBotBaseDataService, + peer_id: int, + reverse_sort: bool = True + ) -> List[BetterUser]: + """Get users sorted by intermediate votes only (pending supporters/opponents)""" + members = vk_instance.get_members_ids(peer_id) + users = data.get_users( + other_keys=[ + "karma", "name", "programming_languages", + "supporters", "opponents", "github_profile", "uid"], + sort_key=lambda u: DataBuilder.calculate_intermediate_votes(u, data), + reverse_sort=reverse_sort) + if members: + users = [u for u in users if u["uid"] in members] + return users + @staticmethod def calculate_real_karma( user: BetterUser, diff --git a/python/patterns.py b/python/patterns.py index 1834c72c..d38f05bf 100644 --- a/python/patterns.py +++ b/python/patterns.py @@ -54,6 +54,12 @@ r'\A\s*(люди|народ|people)\s*(?P(' + DEFAULT_LANGUAGES + r')(\s+(' + DEFAULT_LANGUAGES + r'))*)\s*\Z', IGNORECASE) +TOP_VOTES = recompile( + r'\A\s*(топ голоса|топ голосов|top votes|votes top|голоса)\s*(?P\d+)?\s*\Z', IGNORECASE) + +BOTTOM_VOTES = recompile( + r'\A\s*(низ голоса|низ голосов|bottom votes|votes bottom)\s*(?P\d+)?\s*\Z', IGNORECASE) + WHAT_IS = recompile( r'\A\s*(what is|что такое|що таке)\s+(?P[\S\s]+?)\??\s*\Z', IGNORECASE) diff --git a/python/tests.py b/python/tests.py index 4be4241e..2951a012 100644 --- a/python/tests.py +++ b/python/tests.py @@ -111,6 +111,24 @@ def test_build_karma( print(DataBuilder.build_karma(user_1, db)) print(DataBuilder.build_karma(user_2, db)) + @ordered + def test_calculate_intermediate_votes( + self + ) -> NoReturn: + user_1 = db.get_user(1) + user_2 = db.get_user(2) + + # user_2 should have supporters from previous test + intermediate_votes_1 = DataBuilder.calculate_intermediate_votes(user_1, db) + intermediate_votes_2 = DataBuilder.calculate_intermediate_votes(user_2, db) + + print() + print(f"User 1 intermediate votes: {intermediate_votes_1}") + print(f"User 2 intermediate votes: {intermediate_votes_2}") + + assert intermediate_votes_1 >= 0 or intermediate_votes_1 < 0 # Just ensure it returns a number + assert intermediate_votes_2 >= 0 or intermediate_votes_2 < 0 # Just ensure it returns a number + class Test3Commands(TestCase): commands = Commands(VkInstance(), BetterBotBaseDataService("test_db")) @@ -222,6 +240,20 @@ def test_apply_karma_change( self.commands.apply_karma_change('-', 6) self.commands.karma_message() + @ordered + def test_top_votes( + self + ) -> NoReturn: + self.commands.msg = 'top votes' + self.commands.match_command(patterns.TOP_VOTES) + self.commands.top_votes() + self.commands.top_votes(True) + + self.commands.msg = 'bottom votes' + self.commands.match_command(patterns.BOTTOM_VOTES) + self.commands.top_votes() + self.commands.top_votes(True) + if __name__ == '__main__': db = BetterBotBaseDataService("test_db")