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
21 changes: 18 additions & 3 deletions app/services/comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
MIN_SHARED_FOR_SCORE = 5
RESULT_LIMIT = 5
METHOD = (
'We compare rank positions directly. Smaller differences mean closer agreement.'
'Each gap is the absolute difference between your two rank positions. '
'Alignment is 100% minus the average gap as a share of the longer shelf’s rank span.'
)


Expand All @@ -17,6 +18,13 @@ def _rank_gap(your_rank: int, their_rank: int) -> int:
return abs(your_rank - their_rank)


def _alignment_score(gaps: list[int], longer_shelf_count: int) -> int:
"""Scale the average raw gap against the longer shelf's possible span."""
rank_span = max(1, longer_shelf_count - 1)
mean_gap = sum(gaps) / len(gaps)
return round(max(0.0, 1 - (mean_gap / rank_span)) * 100)


def _item(catalog, **extra) -> dict:
return {
'id': str(catalog.id),
Expand Down Expand Up @@ -72,6 +80,11 @@ def compare_shelf( # pylint: disable=too-many-locals
getattr(row, shelf.join_col): row
for row in db.query(tracker).filter(tracker.user_id == viewer.pk).all()
}
viewer_ranked_count = sum(
1
for row in viewer_trackers.values()
if row.on_rankings and row.rank is not None
)
base['recommendations'] = [
_item(
item,
Expand Down Expand Up @@ -106,8 +119,10 @@ def compare_shelf( # pylint: disable=too-many-locals
shared, key=lambda item: (item['gap'], item['title'].lower())
)[:RESULT_LIMIT]
if len(shared) >= MIN_SHARED_FOR_SCORE:
mean_gap = sum(item['gap'] for item in shared) / len(shared)
base['alignment_score'] = round(max(0.0, 100 - mean_gap))
base['alignment_score'] = _alignment_score(
[item['gap'] for item in shared],
max(viewer_ranked_count, len(target_rows)),
)
base['alignment_status'] = 'ready'

if watchlist_visible:
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/router_comparison_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def test_comparison_scores_visible_rankings_and_marks_watchlist(
)
assert movies['shared_ranked_count'] == 5
assert movies['alignment_status'] == 'ready'
assert isinstance(movies['alignment_score'], int)
assert movies['alignment_score'] == 60
assert [item['title'] for item in movies['common_watchlist']] == ['Movie 8']
assert [item['title'] for item in movies['recommendations']] == [
'Movie 6',
Expand Down
7 changes: 6 additions & 1 deletion tests/unit/services/comparison_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# pylint: disable=missing-function-docstring
"""The direct rank-distance calculation behind comparisons."""

from app.services.comparison import _rank_gap
from app.services.comparison import _alignment_score, _rank_gap


def test_equal_ranks_have_no_gap():
Expand All @@ -11,3 +11,8 @@ def test_equal_ranks_have_no_gap():
def test_gap_is_the_absolute_rank_difference():
assert _rank_gap(88, 10) == 78
assert _rank_gap(10, 88) == 78


def test_alignment_scales_average_gap_against_longer_shelf_span():
assert _alignment_score([4, 4, 2, 2, 0], longer_shelf_count=7) == 60
assert _alignment_score([0, 0, 0, 0, 0], longer_shelf_count=100) == 100