From 224091437d23e07dd03b6e73ef8a44e0ce30ff2a Mon Sep 17 00:00:00 2001 From: freyamade Date: Thu, 9 Jan 2025 01:22:32 +0100 Subject: [PATCH 1/6] made all floor_data functions static --- backend/api/views/loot_solver.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/backend/api/views/loot_solver.py b/backend/api/views/loot_solver.py index befb6be..a0ba783 100644 --- a/backend/api/views/loot_solver.py +++ b/backend/api/views/loot_solver.py @@ -455,8 +455,8 @@ def _get_handout_data(slots: List[str], requirements: Requirements, prio_bracket return handouts + @staticmethod def _get_first_floor_data( - self, requirements: Requirements, history: QuerySet[Loot], id_order: List[int], @@ -466,14 +466,14 @@ def _get_first_floor_data( """ Simulate handing out the loot for a first floor clear. """ - weeks, prio_brackets, floor_requirements = self._get_floor_data( + weeks, prio_brackets, floor_requirements = LootSolver._get_floor_data( requirements, history, - self.FIRST_FLOOR_SLOTS, + LootSolver.FIRST_FLOOR_SLOTS, id_order, non_loot_gear_obtained, ) - return self._get_handout_data(self.FIRST_FLOOR_SLOTS, floor_requirements, prio_brackets, self.FIRST_FLOOR_TOKENS, weeks, greedy) + return LootSolver._get_handout_data(LootSolver.FIRST_FLOOR_SLOTS, floor_requirements, prio_brackets, LootSolver.FIRST_FLOOR_TOKENS, weeks, greedy) @staticmethod def _get_second_floor_data( @@ -495,8 +495,8 @@ def _get_second_floor_data( ) return LootSolver._get_handout_data(LootSolver.SECOND_FLOOR_SLOTS, floor_requirements, prio_brackets, LootSolver.SECOND_FLOOR_TOKENS, weeks, greedy) + @staticmethod def _get_third_floor_data( - self, requirements: Requirements, history: QuerySet[Loot], id_order: List[int], @@ -506,16 +506,17 @@ def _get_third_floor_data( """ Simulate handing out the loot for a third floor clear. """ - weeks, prio_brackets, floor_requirements = self._get_floor_data( + weeks, prio_brackets, floor_requirements = LootSolver._get_floor_data( requirements, history, - self.THIRD_FLOOR_SLOTS, + LootSolver.THIRD_FLOOR_SLOTS, id_order, non_loot_gear_obtained, ) - return self._get_handout_data(self.THIRD_FLOOR_SLOTS, floor_requirements, prio_brackets, self.THIRD_FLOOR_TOKENS, weeks, greedy) + return LootSolver._get_handout_data(LootSolver.THIRD_FLOOR_SLOTS, floor_requirements, prio_brackets, LootSolver.THIRD_FLOOR_TOKENS, weeks, greedy) - def _get_fourth_floor_data(self, history: QuerySet[Loot], team_size: int, non_loot_gear_obtained: NonLootGear) -> HandoutData: + @staticmethod + def _get_fourth_floor_data(history: QuerySet[Loot], team_size: int, non_loot_gear_obtained: NonLootGear) -> HandoutData: """ Simulate handing out the loot for a fourth floor clear. Different from how the others are handled, because we just check how many people already have bis weapon, and also how many mounts have been obtained. From ad0773be28021cb06b7f5d3aa3e102789581b089 Mon Sep 17 00:00:00 2001 From: freyamade Date: Thu, 9 Jan 2025 01:49:57 +0100 Subject: [PATCH 2/6] add more deepcopies so that sentry details will contain untouched data to use to build tests --- backend/api/views/loot_solver.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/api/views/loot_solver.py b/backend/api/views/loot_solver.py index a0ba783..1453cd5 100644 --- a/backend/api/views/loot_solver.py +++ b/backend/api/views/loot_solver.py @@ -197,7 +197,7 @@ def _get_floor_data( """ # Limit floor requirements to the items that were important, then remove from this list as we update the prios below floor_requirements = { - slot: requirements.get(slot, []) + slot: deepcopy(requirements.get(slot, [])) for slot in slots } relevant_history = history.filter(item__in=slots).order_by('obtained') @@ -285,8 +285,11 @@ def _get_handout_data(slots: List[str], requirements: Requirements, prio_bracket """ handouts = [] remove_slots = slots.copy() - # Deepcopy the prio brackets dict so that sentry errors can print the upper level prio brackets for more debugging ease + + # Deepcopy passed dicts for Sentry debugging prio_brackets = deepcopy(prio_brackets) + requirements = deepcopy(requirements) + if 'augment' in slots[-1]: remove_slots = [remove_slots[-1]] while len(prio_brackets) > 0: From 7b86c5e74e7ca5c2235e22dab10ec07a14ca9a8c Mon Sep 17 00:00:00 2001 From: freyamade Date: Thu, 9 Jan 2025 11:03:18 +0100 Subject: [PATCH 3/6] write test using latest sentry issue data --- backend/api/tests/test_loot_solver.py | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/backend/api/tests/test_loot_solver.py b/backend/api/tests/test_loot_solver.py index cb2004c..cf00623 100644 --- a/backend/api/tests/test_loot_solver.py +++ b/backend/api/tests/test_loot_solver.py @@ -1689,3 +1689,39 @@ def test_dev_setup_edgecase_bug_solution(self): self.assertEqual(len(expected), len(received), received) for i in range(len(expected)): self.assertDictEqual(expected[i], received[i], f'{i+1}/{len(received)}') + + def test_removed_pop_was_none_bug(self): + """ + Test Plan: + - Run Loot Solver with data from the latest sentry issue where it got a None.remove call + - See what happened, and fix the bug, to make sure it never happens again + """ + weeks = 0 + prio_brackets = { + 1: [1, 2], + 2: [3, 4, 5, 6], + } + floor_requirements = { + 'body': [5, 6, 3], + 'legs': [4, 1, 2], + 'tome-armour-augment': [4, 5, 6, 3], + } + + expected = [ + {'token': False, 'Body': 3, 'Legs': 4, 'Tome Armour Augment': 5}, + {'token': False, 'Body': 6, 'Legs': 1, 'Tome Armour Augment': 4}, + {'token': False, 'Body': 5, 'Legs': 2, 'Tome Armour Augment': 3}, + {'token': True, 'Body': None, 'Legs': None, 'Tome Armour Augment': 6}, + ] + + received = LootSolver._get_handout_data( + LootSolver.THIRD_FLOOR_SLOTS, + floor_requirements, + prio_brackets, + LootSolver.THIRD_FLOOR_TOKENS, + weeks, + False, + ) + self.assertEqual(len(expected), len(received), received) + for i in range(len(expected)): + self.assertDictEqual(expected[i], received[i], f'{i+1}/{len(received)}') From c972dc734a731a48cce5fe3d8e3d46af9bccf976 Mon Sep 17 00:00:00 2001 From: freyamade Date: Thu, 9 Jan 2025 11:03:35 +0100 Subject: [PATCH 4/6] fix bug and improve algorithm to handle newfound edgecase --- backend/api/views/loot_solver.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/api/views/loot_solver.py b/backend/api/views/loot_solver.py index 1453cd5..a392bed 100644 --- a/backend/api/views/loot_solver.py +++ b/backend/api/views/loot_solver.py @@ -309,11 +309,18 @@ def _get_handout_data(slots: List[str], requirements: Requirements, prio_bracket # This ensures each item is given to the person with the highest priority of getting it done = False potential_loot_members: Dict[int, List[str]] = {} + # Skip repeated single item lists + single_item_entries = set() for priority in sorted(prio_brackets, reverse=True): for member_id in prio_brackets[priority]: required = [slot for slot in requirements if member_id in requirements[slot]] - potential_loot_members[member_id] = required + if len(required) == 1: + if required[0] in single_item_entries: + continue + single_item_entries.add(required[0]) + + potential_loot_members[member_id] = required # Subtract from the set of things needed this week required_slots_for_week -= set(required) @@ -404,7 +411,7 @@ def _get_handout_data(slots: List[str], requirements: Requirements, prio_bracket prio_brackets[new_prio].append(member_id) # Now we need to remove the member_id from potentials and remove the item from the popped list in case we need to re-insert - removed = potential_loot_members.pop(member_id, None) + removed = potential_loot_members.pop(member_id, []) try: removed.remove(item) except ValueError: From 90eaa3342a448fcc1c2ee7b9b31b8e4dc3f6ed28 Mon Sep 17 00:00:00 2001 From: freyamade Date: Thu, 9 Jan 2025 11:03:43 +0100 Subject: [PATCH 5/6] update changelog --- frontend/src/components/modals/changelog.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/components/modals/changelog.vue b/frontend/src/components/modals/changelog.vue index 745a4f5..160b6c4 100644 --- a/frontend/src/components/modals/changelog.vue +++ b/frontend/src/components/modals/changelog.vue @@ -12,8 +12,14 @@

{{ version }}

+
expand_more Loot Solver Algorithm Improvement / Bugfix expand_more
+

Improved Loot Solver algorithm to handle newfound edgecase.

+

Also fixed silly bug in the code that was introduced when fixing the previous Loot Solver bug!

+ +

{{ version.split('.')[0] }}.2

expand_more Team Delete Bugfix expand_more

Quick extra push today to deliver a bugfix to a long hidden bug during Team deletion. Sorry for the inconvenience but thank you for catching it!

+

Thankfully it wasn't an issue for deleting anything, just caused an error at the end of the endpoint call!

{{ version.split('.')[0] }}

expand_more Character Verify Bugfix expand_more
From 0476d48f69db0f570b77fbe15d7020405a2e7d0c Mon Sep 17 00:00:00 2001 From: freyamade Date: Thu, 9 Jan 2025 11:04:42 +0100 Subject: [PATCH 6/6] bump to yesterday's version.3 --- backend/backend/__init__.py | 2 +- frontend/.env | 2 +- frontend/src/main.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/backend/__init__.py b/backend/backend/__init__.py index 0a10300..c4c2608 100644 --- a/backend/backend/__init__.py +++ b/backend/backend/__init__.py @@ -2,4 +2,4 @@ from .celery import app as celery_app -VERSION = '20250108.2' +VERSION = '20250108.3' diff --git a/frontend/.env b/frontend/.env index c19ca02..00891bd 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1 +1 @@ -VUE_APP_VERSION="20250108.2" +VUE_APP_VERSION="20250108.3" diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 50587d1..04f74e8 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -30,7 +30,7 @@ Sentry.init({ Vue, dsn: 'https://06f41b525a40497a848fb726f6d03244@o242258.ingest.sentry.io/6180221', logErrors: true, - release: 'savageaim@20250108.2', + release: 'savageaim@20250108.3', integrations: [ Sentry.browserTracingIntegration(), Sentry.replayIntegration(),