diff --git a/cl/favorites/tests.py b/cl/favorites/tests.py index b10636ee74..66e189db68 100644 --- a/cl/favorites/tests.py +++ b/cl/favorites/tests.py @@ -798,96 +798,72 @@ async def test_get_top_prayers_by_number(self) -> None: msg="Wrong top_prayers based on prayers count.", ) - async def test_get_top_prayers_by_age(self) -> None: + async def test_get_top_prayers_by_views(self) -> None: """Does the get_top_prayers method work properly?""" - # Test top documents based on prayer age. - current_time = now() - with time_machine.travel( - current_time - timedelta(minutes=1), tick=False - ): - await create_prayer(self.user, self.rd_4) + # Test top documents based on docket views. + self.rd_2.docket_entry.docket.view_count = 4 + self.rd_3.docket_entry.docket.view_count = 12 + self.rd_4.docket_entry.docket.view_count = 6 - with time_machine.travel( - current_time - timedelta(minutes=2), tick=False - ): - await create_prayer(self.user, self.rd_2) + self.rd_2.docket_entry.docket.asave() + self.rd_3.docket_entry.docket.asave() + self.rd_4.docket_entry.docket.asave() - with time_machine.travel( - current_time - timedelta(minutes=3), tick=False - ): - await create_prayer(self.user_2, self.rd_3) + await create_prayer(self.user, self.rd_4) + await create_prayer(self.user, self.rd_2) + await create_prayer(self.user_2, self.rd_3) top_prayers = await get_top_prayers() self.assertEqual(await top_prayers.acount(), 3) - expected_top_prayers = [self.rd_3.pk, self.rd_2.pk, self.rd_4.pk] + expected_top_prayers = [self.rd_3.pk, self.rd_4.pk, self.rd_2.pk] actual_top_prayers = [top_rd.pk async for top_rd in top_prayers] self.assertEqual( actual_top_prayers, expected_top_prayers, - msg="Wrong top_prayers based on prayers age.", + msg="Wrong top_prayers based on docket view count.", ) - async def test_get_top_prayers_by_number_and_age(self) -> None: + async def test_get_top_prayers_by_number_and_views(self) -> None: """Does the get_top_prayers method work properly?""" - # Create prayers with different counts and ages - current_time = now() - with time_machine.travel(current_time - timedelta(days=5), tick=False): - await create_prayer(self.user, self.rd_5) # 1 prayer, 5 days old + self.rd_2.docket_entry.docket.view_count = 4 + self.rd_3.docket_entry.docket.view_count = 1 + self.rd_4.docket_entry.docket.view_count = 6 + self.rd_5.docket_entry.docket.view_count = 8 - with time_machine.travel(current_time - timedelta(days=3), tick=False): - await create_prayer(self.user, self.rd_2) - await create_prayer( - self.user_2, self.rd_2 - ) # 2 prayers, 3 days old + self.rd_2.docket_entry.docket.asave() + self.rd_3.docket_entry.docket.asave() + self.rd_4.docket_entry.docket.asave() + self.rd_5.docket_entry.docket.asave() - with time_machine.travel(current_time - timedelta(days=1), tick=False): - await create_prayer(self.user, self.rd_3) - await create_prayer(self.user_2, self.rd_3) - await create_prayer(self.user_3, self.rd_3) # 3 prayers, 1 day old + # Create prayers with different counts and views - with time_machine.travel(current_time - timedelta(days=4), tick=False): - await create_prayer(self.user, self.rd_4) - await create_prayer( - self.user_2, self.rd_4 - ) # 2 prayers, 4 days old + await create_prayer(self.user, self.rd_5) + await create_prayer(self.user, self.rd_2) + await create_prayer(self.user_2, self.rd_2) + await create_prayer(self.user, self.rd_3) + await create_prayer(self.user_2, self.rd_3) + await create_prayer(self.user_3, self.rd_3) + await create_prayer(self.user, self.rd_4) + await create_prayer(self.user_2, self.rd_4) top_prayers = await get_top_prayers() self.assertEqual(await top_prayers.acount(), 4) expected_top_prayers = [ + self.rd_3.pk, self.rd_4.pk, self.rd_2.pk, self.rd_5.pk, - self.rd_3.pk, ] actual_top_prayers = [top_rd.pk async for top_rd in top_prayers] self.assertEqual( actual_top_prayers, expected_top_prayers, - msg="Wrong top_prayers based on combined prayer count and age.", - ) - - # Compute expected geometric means - rd_4_score = math.sqrt(2 * (4 * 3600 * 24)) - rd_2_score = math.sqrt(2 * (3 * 3600 * 24)) - rd_5_score = math.sqrt(1 * (5 * 3600 * 24)) - rd_3_score = math.sqrt(3 * (1 * 3600 * 24)) - - self.assertAlmostEqual( - top_prayers[0].geometric_mean, rd_4_score, places=2 - ) - self.assertAlmostEqual( - top_prayers[1].geometric_mean, rd_2_score, places=2 - ) - self.assertAlmostEqual( - top_prayers[2].geometric_mean, rd_5_score, places=2 - ) - self.assertAlmostEqual( - top_prayers[3].geometric_mean, rd_3_score, places=2 + msg="Wrong top_prayers based on combined prayer count and docket view count.", ) async def test_get_user_prayers(self) -> None: diff --git a/cl/favorites/utils.py b/cl/favorites/utils.py index d009ebd24b..2d8848897d 100644 --- a/cl/favorites/utils.py +++ b/cl/favorites/utils.py @@ -103,11 +103,13 @@ async def get_existing_prayers_in_bulk( async def get_top_prayers() -> QuerySet[RECAPDocument]: - # Calculate the age of each prayer - prayer_age = ExpressionWrapper( - Extract(Now() - F("prayers__date_created"), "epoch"), - output_field=FloatField(), - ) + """Retrieve the most desired documents that have open prayers. It first + ranks by the number of requests and then by the number of views the particular + docket has received. + + :return: A queryset of RECAPDocuments in descending order of preference. + """ + waiting_prayers = Prayer.objects.filter(status=Prayer.WAITING).values( "recap_document_id" ) @@ -144,20 +146,9 @@ async def get_top_prayers() -> QuerySet[RECAPDocument]: prayer_count=Count( "prayers", filter=Q(prayers__status=Prayer.WAITING) ), - avg_prayer_age=Avg( - prayer_age, filter=Q(prayers__status=Prayer.WAITING) - ), - ) - .annotate( - geometric_mean=Sqrt( - Cast( - F("prayer_count") - * Cast(F("avg_prayer_age"), FloatField()), - FloatField(), - ) - ) + view_count=F("docket_entry__docket__view_count"), ) - .order_by("-geometric_mean") + .order_by("-prayer_count", "-view_count") ) return documents diff --git a/cl/search/factories.py b/cl/search/factories.py index fdf60e379c..ca9243dd25 100644 --- a/cl/search/factories.py +++ b/cl/search/factories.py @@ -284,6 +284,7 @@ class Meta: docket_number = Faker("federal_district_docket_number") slug = Faker("slug") date_argued = Faker("date_object") + view_count = 0 """ This hook is necessary to make this factory compatible with the diff --git a/cl/simple_pages/templates/help/prayer_help.html b/cl/simple_pages/templates/help/prayer_help.html index 766286e0ae..dae5ac449e 100644 --- a/cl/simple_pages/templates/help/prayer_help.html +++ b/cl/simple_pages/templates/help/prayer_help.html @@ -64,7 +64,7 @@
The most-wanted documents are shown on a leaderboard ranked by how many users have requested a particular document and how old the requests are. As requests are fulfilled, they are removed from the leaderboard. +
The most-wanted documents are shown on a leaderboard ranked by how many users have requested a particular document and how many views their corresponding docket has received. As requests are fulfilled, they are removed from the leaderboard.
Fulfilling somebody's prayer only works if you install the RECAP extension. Once it's installed, all your PACER purchases will be sent to CourtListener automatically — Prayers will be granted!