Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions backend/api/tests/test_loot_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}')
37 changes: 24 additions & 13 deletions backend/api/views/loot_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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:
Expand All @@ -306,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)

Expand Down Expand Up @@ -401,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:
Expand Down Expand Up @@ -455,8 +465,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],
Expand All @@ -466,14 +476,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(
Expand All @@ -495,8 +505,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],
Expand All @@ -506,16 +516,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.
Expand Down
2 changes: 1 addition & 1 deletion backend/backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

from .celery import app as celery_app

VERSION = '20250108.2'
VERSION = '20250108.3'
2 changes: 1 addition & 1 deletion frontend/.env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VUE_APP_VERSION="20250108.2"
VUE_APP_VERSION="20250108.3"
6 changes: 6 additions & 0 deletions frontend/src/components/modals/changelog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@
</div>
<div class="card-content content">
<h2 class="has-text-primary subtitle">{{ version }}</h2>
<div class="divider"><i class="material-icons icon">expand_more</i> Loot Solver Algorithm Improvement / Bugfix <i class="material-icons icon">expand_more</i></div>
<p>Improved Loot Solver algorithm to handle newfound edgecase.</p>
<p>Also fixed silly bug in the code that was introduced when fixing the previous Loot Solver bug!</p>

<h2 class="has-text-primary subtitle">{{ version.split('.')[0] }}.2</h2>
<div class="divider"><i class="material-icons icon">expand_more</i> Team Delete Bugfix <i class="material-icons icon">expand_more</i></div>
<p>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!</p>
<p>Thankfully it wasn't an issue for deleting anything, just caused an error at the end of the endpoint call!</p>

<h2 class="has-text-primary subtitle">{{ version.split('.')[0] }}</h2>
<div class="divider"><i class="material-icons icon">expand_more</i> Character Verify Bugfix <i class="material-icons icon">expand_more</i></div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Loading