From 097614683885b70c39d8663897cfea74a25e27c4 Mon Sep 17 00:00:00 2001 From: Alberto Islas Date: Fri, 16 Aug 2024 10:31:11 -0500 Subject: [PATCH 1/7] fix(elasticsearch): Fixed query citation for clusters with multiple sibling opinions. Fixes: #4211 --- cl/citations/match_citations_queries.py | 19 +++++++++++--- cl/search/tests/tests_es_opinion.py | 35 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/cl/citations/match_citations_queries.py b/cl/citations/match_citations_queries.py index 0e6ed1abe8..a320ac860b 100644 --- a/cl/citations/match_citations_queries.py +++ b/cl/citations/match_citations_queries.py @@ -131,11 +131,13 @@ def es_case_name_query( def es_search_db_for_full_citation( - full_citation: FullCaseCitation, + full_citation: FullCaseCitation, query_citation: bool = False ) -> list[Hit]: """For a citation object, try to match it to an item in the database using a variety of heuristics. :param full_citation: A FullCaseCitation instance. + :param query_citation: Whether this is related to es_get_query_citation + resolution return: A ElasticSearch Result object with the results, or an empty list if no hits """ @@ -147,12 +149,20 @@ def es_search_db_for_full_citation( Q( "term", **{"status.raw": "Published"} ), # Non-precedential documents aren't cited - Q("match", cluster_child="opinion"), ] + + if query_citation: + # If this is related to query citation resolution, look for + # opinion_cluster to determine if a citation matched a single cluster. + filters.append(Q("match", cluster_child="opinion_cluster")) + else: + filters.append(Q("match", cluster_child="opinion")) + must_not = [] if full_citation.citing_opinion is not None: # Eliminate self-cites. must_not.append(Q("match", id=full_citation.citing_opinion.pk)) + # Set up filter parameters if full_citation.year: start_year = end_year = full_citation.year @@ -204,7 +214,6 @@ def es_search_db_for_full_citation( full_citation.citing_opinion, ) return results - # Give up. return [] @@ -225,7 +234,9 @@ def es_get_query_citation(cd: CleanData) -> Hit | None: matches = None if len(citations) == 1: # If it's not exactly one match, user doesn't get special help. - matches = es_search_db_for_full_citation(citations[0]) + matches = es_search_db_for_full_citation( + citations[0], query_citation=True + ) if len(matches) == 1: # If more than one match, don't show the tip return matches[0] diff --git a/cl/search/tests/tests_es_opinion.py b/cl/search/tests/tests_es_opinion.py index 16d3a94516..ddb8839798 100644 --- a/cl/search/tests/tests_es_opinion.py +++ b/cl/search/tests/tests_es_opinion.py @@ -1982,6 +1982,7 @@ def test_display_query_citation_frontend(self) -> None: """Confirm if the query citation alert is shown on the frontend when querying a single citation, and it's found into ES.""" + # Cluster with citation and multiple sibling opinions is properly matched. with self.captureOnCommitCallbacks(execute=True): cluster = OpinionClusterFactory.create( precedential_status=PRECEDENTIAL_STATUS.PUBLISHED, @@ -1989,6 +1990,7 @@ def test_display_query_citation_frontend(self) -> None: date_filed=datetime.date(2024, 8, 23), ) OpinionFactory.create(cluster=cluster, plain_text="") + OpinionFactory.create(cluster=cluster, plain_text="") CitationWithParentsFactory.create( volume=31, reporter="Pa. D. & C.", @@ -2009,6 +2011,39 @@ def test_display_query_citation_frontend(self) -> None: self.assertIn( "It looks like you're trying to search for", r.content.decode() ) + + # Add a new cluster for the same citation. This time, it is not + # possible to identify a unique case for the citation. + with self.captureOnCommitCallbacks(execute=True): + cluster_2 = OpinionClusterFactory.create( + case_name="Test case", + precedential_status=PRECEDENTIAL_STATUS.PUBLISHED, + docket=self.docket_1, + date_filed=datetime.date(2024, 8, 23), + ) + OpinionFactory.create(cluster=cluster_2, plain_text="") + CitationWithParentsFactory.create( + volume=31, + reporter="Pa. D. & C.", + page="445", + type=2, + cluster=cluster_2, + ) + + search_params = { + "type": SEARCH_TYPES.OPINION, + "q": "31 Pa. D. & C. 445", + "order_by": "score desc", + } + r = self.client.get( + reverse("show_results"), + search_params, + ) + self.assertNotIn( + "It looks like you're trying to search for", r.content.decode() + ) + + cluster_2.delete() cluster.delete() From d28f261d4694b9d89f2b24e8891b6b00441458fd Mon Sep 17 00:00:00 2001 From: Alberto Islas Date: Fri, 16 Aug 2024 17:58:11 -0500 Subject: [PATCH 2/7] feat(elasticsearch): Display the initial complaint button in RECAP search results. Fixes: #3798 --- cl/lib/elasticsearch_utils.py | 58 +++++++ .../templates/includes/search_result.html | 9 ++ cl/search/tests/tests_es_recap.py | 145 ++++++++++++++++++ 3 files changed, 212 insertions(+) diff --git a/cl/lib/elasticsearch_utils.py b/cl/lib/elasticsearch_utils.py index cb68c1dc7d..aed77cd155 100644 --- a/cl/lib/elasticsearch_utils.py +++ b/cl/lib/elasticsearch_utils.py @@ -1789,6 +1789,64 @@ def merge_unavailable_fields_on_parent_document( result["id"], "" ) + case ( + SEARCH_TYPES.RECAP | SEARCH_TYPES.DOCKETS + ) if request_type == "frontend": + # Merge initial complaint button to the frontend search results. + docket_ids = {doc["docket_id"] for doc in results} + # This query retrieves initial complaint documents considering two + # possibilities: + # 1. For district, bankruptcy, and appellate entries where we don't know + # if the entry contains attachments, it considers: + # document_number=1 and attachment_number=None and document_type=PACER_DOCUMENT + # This represents the main document with document_number 1. + # 2. For appellate entries where the attachment page has already been + # merged, it considers: + # document_number=1 and attachment_number=1 and document_type=ATTACHMENT + # This represents document_number 1 that has been converted to an attachment. + initial_complaints = ( + RECAPDocument.objects.filter( + QObject( + QObject( + attachment_number=None, + document_type=RECAPDocument.PACER_DOCUMENT, + ) + | QObject( + attachment_number=1, + document_type=RECAPDocument.ATTACHMENT, + ) + ), + docket_entry__docket_id__in=docket_ids, + document_number="1", + ) + .select_related("docket_entry") + .only( + "document_number", + "attachment_number", + "pacer_doc_id", + "is_available", + "filepath_local", + "docket_entry__docket_id", + ) + ) + initial_complaints_in_page = {} + for initial_complaint in initial_complaints: + if initial_complaint.has_valid_pdf: + initial_complaints_in_page[ + initial_complaint.docket_entry.docket_id + ] = (initial_complaint.get_absolute_url(), True) + else: + initial_complaints_in_page[ + initial_complaint.docket_entry.docket_id + ] = (initial_complaint.pacer_url, False) + + for result in results: + complaint_url, available = initial_complaints_in_page.get( + result.docket_id, (None, None) + ) + result["initial_complaint_url"] = complaint_url + result["available_initial_complaint"] = available + case SEARCH_TYPES.OPINION if request_type == "v4" and not highlight: # Retrieves the Opinion plain_text from the DB to fill the snippet # when highlighting is disabled. Considering the same prioritization diff --git a/cl/search/templates/includes/search_result.html b/cl/search/templates/includes/search_result.html index 635300b8c0..24900362b2 100644 --- a/cl/search/templates/includes/search_result.html +++ b/cl/search/templates/includes/search_result.html @@ -207,6 +207,15 @@

{% endwith %} {% endfor %}
+ {% if result.available_initial_complaint %} + + Initial Complaint + + {% elif result.available_initial_complaint == False %} + + Buy Initial Complaint + + {% endif %} {% if result.child_remaining %} View Additional Results for this Case diff --git a/cl/search/tests/tests_es_recap.py b/cl/search/tests/tests_es_recap.py index 8f9b8d4202..73a5c14f5d 100644 --- a/cl/search/tests/tests_es_recap.py +++ b/cl/search/tests/tests_es_recap.py @@ -222,6 +222,21 @@ def _count_child_documents_dict( " Got: %s\n\n" % (field_name, expected_count, got), ) + @staticmethod + def _parse_initial_complaint_button(response): + """Parse the initial complaint button within the HTML response.""" + tree = html.fromstring(response.content.decode()) + try: + initial_complaint = tree.xpath( + "//a[contains(@class, 'initial-complaint')]" + )[0] + except IndexError: + return None, None + return ( + initial_complaint.get("href"), + initial_complaint.text_content().strip(), + ) + def test_has_child_text_queries(self) -> None: """Test has_child text queries.""" cd = { @@ -2235,6 +2250,136 @@ async def test_fail_rd_type_gracefully_frontend(self) -> None: self.assertEqual(r.status_code, 200) self.assertIn("encountered an error", r.content.decode()) + def test_initial_complaint_button(self) -> None: + """Confirm the initial complaint button is properly shown on different + scenarios""" + + # Add dockets with no documents + with self.captureOnCommitCallbacks(execute=True): + + # District document initial complaint available + de_1 = DocketEntryWithParentsFactory( + docket=DocketFactory( + court=self.court, + case_name="Lorem District vs Complaint Available", + docket_number="1:21-bk-1234", + source=Docket.RECAP, + ), + entry_number=1, + date_filed=datetime.date(2015, 8, 19), + description="MOTION for Leave to File Amicus Curiae Lorem Served", + ) + sample_file = SimpleUploadedFile("recap_filename.pdf", b"file") + initial_complaint_1 = RECAPDocumentFactory( + docket_entry=de_1, + document_number="1", + document_type=RECAPDocument.PACER_DOCUMENT, + is_available=True, + filepath_local=sample_file, + ) + + # District document initial complaint not available + de_2 = DocketEntryWithParentsFactory( + docket=DocketFactory( + court=self.court, + case_name="Lorem District vs Complaint Not Available", + docket_number="1:21-bk-1235", + source=Docket.RECAP, + ), + entry_number=1, + date_filed=datetime.date(2015, 8, 19), + description="MOTION for Leave to File Amicus Curiae Lorem Served", + ) + initial_complaint_2 = RECAPDocumentFactory( + docket_entry=de_2, + document_number="1", + is_available=False, + pacer_doc_id="234563", + ) + + # Appellate document initial complaint available + de_3 = DocketEntryWithParentsFactory( + docket=DocketFactory( + court=self.court, + case_name="Lorem Appellate vs Complaint Available", + docket_number="1:21-bk-1236", + source=Docket.RECAP, + ), + entry_number=1, + date_filed=datetime.date(2015, 8, 19), + description="MOTION for Leave to File Amicus Curiae Lorem Served", + ) + sample_file = SimpleUploadedFile("recap_filename.pdf", b"file") + initial_complaint_3 = RECAPDocumentFactory( + docket_entry=de_3, + document_number="1", + attachment_number=1, + document_type=RECAPDocument.ATTACHMENT, + is_available=True, + filepath_local=sample_file, + ) + + # No DocketEntry for the initial complaint available + empty_docket = DocketFactory( + court=self.court, + case_name="Lorem No Initial Complaint Entry", + docket_number="1:21-bk-1237", + source=Docket.RECAP, + ) + + # District document initial complaint available + cd = { + "type": SEARCH_TYPES.RECAP, + "q": '"Lorem District vs Complaint Available"', + } + r = async_to_sync(self._test_article_count)( + cd, 1, "Complaint available" + ) + button_url, button_text = self._parse_initial_complaint_button(r) + self.assertEqual("Initial Complaint", button_text) + self.assertEqual(initial_complaint_1.get_absolute_url(), button_url) + + # District document initial complaint not available. Show Buy button. + cd = { + "type": SEARCH_TYPES.RECAP, + "q": '"Lorem District vs Complaint Not Available"', + } + r = async_to_sync(self._test_article_count)( + cd, 1, "Complaint Not available" + ) + button_url, button_text = self._parse_initial_complaint_button(r) + self.assertEqual("Buy Initial Complaint", button_text) + self.assertEqual(initial_complaint_2.pacer_url, button_url) + + # Appellate document initial complaint available + cd = { + "type": SEARCH_TYPES.RECAP, + "q": '"Lorem Appellate vs Complaint Available"', + } + r = async_to_sync(self._test_article_count)( + cd, 1, "Complaint Appellate available" + ) + button_url, button_text = self._parse_initial_complaint_button(r) + self.assertEqual("Initial Complaint", button_text) + self.assertEqual(initial_complaint_3.get_absolute_url(), button_url) + + # No docket entry is available for the initial complaint. No button is shown. + cd = { + "type": SEARCH_TYPES.RECAP, + "q": '"Lorem No Initial Complaint Entry"', + } + r = async_to_sync(self._test_article_count)( + cd, 1, "Complaint Entry no available" + ) + button_url, button_text = self._parse_initial_complaint_button(r) + self.assertIsNone(button_text) + self.assertIsNone(button_url) + + de_1.docket.delete() + de_2.docket.delete() + de_3.docket.delete() + empty_docket.delete() + class RECAPSearchAPICommonTests(RECAPSearchTestCase): From 872efb3dca1b0c98580f02c62c0f0c0baba9aaf4 Mon Sep 17 00:00:00 2001 From: grossir Date: Mon, 19 Aug 2024 15:51:27 +0000 Subject: [PATCH 3/7] Update freelawproject dependencies --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index acbbfed285..80970a13f1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2236,13 +2236,13 @@ setuptools = "*" [[package]] name = "juriscraper" -version = "2.6.16" +version = "2.6.17" description = "An API to scrape American court websites for metadata." optional = false python-versions = "*" files = [ - {file = "juriscraper-2.6.16-py27-none-any.whl", hash = "sha256:ffaefea58f3fb88a195bd50f6cb783ca9b0029a421ef0b1118568fc5dd303377"}, - {file = "juriscraper-2.6.16.tar.gz", hash = "sha256:42a2c95e36300369bba0496eea618401f5c409033d87ba6cd83074a988ed72b3"}, + {file = "juriscraper-2.6.17-py27-none-any.whl", hash = "sha256:c73fb29ebbb6faa489507a000880c3d8c5681cce4a4ef0e9afa9674075a9b826"}, + {file = "juriscraper-2.6.17.tar.gz", hash = "sha256:16b2b2fc0cacdf09ffb1712d42fe3ca055722414e1fec2082f65cd467fe09f82"}, ] [package.dependencies] From 9f7254cd4e6ea05debf70d2997bc22fa7b640488 Mon Sep 17 00:00:00 2001 From: Alberto Islas Date: Mon, 19 Aug 2024 14:12:33 -0500 Subject: [PATCH 4/7] fix(elasticsearch): Eliminated unnecessary queries related to the initial complaint - Added additional test related to appellate documents without pacer_doc_id --- cl/lib/elasticsearch_utils.py | 24 +++++++--- cl/search/signals.py | 5 ++- .../templates/includes/search_result.html | 6 +-- cl/search/tests/tests_es_recap.py | 45 ++++++++++++++++--- 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/cl/lib/elasticsearch_utils.py b/cl/lib/elasticsearch_utils.py index aed77cd155..2d229e0015 100644 --- a/cl/lib/elasticsearch_utils.py +++ b/cl/lib/elasticsearch_utils.py @@ -1819,14 +1819,24 @@ def merge_unavailable_fields_on_parent_document( docket_entry__docket_id__in=docket_ids, document_number="1", ) - .select_related("docket_entry") + .select_related( + "docket_entry", + "docket_entry__docket", + "docket_entry__docket__court", + ) .only( + "pk", + "document_type", "document_number", "attachment_number", "pacer_doc_id", "is_available", "filepath_local", "docket_entry__docket_id", + "docket_entry__docket__slug", + "docket_entry__docket__pacer_case_id", + "docket_entry__docket__court__jurisdiction", + "docket_entry__docket__court_id", ) ) initial_complaints_in_page = {} @@ -1834,18 +1844,20 @@ def merge_unavailable_fields_on_parent_document( if initial_complaint.has_valid_pdf: initial_complaints_in_page[ initial_complaint.docket_entry.docket_id - ] = (initial_complaint.get_absolute_url(), True) + ] = (initial_complaint.get_absolute_url(), None) else: initial_complaints_in_page[ initial_complaint.docket_entry.docket_id - ] = (initial_complaint.pacer_url, False) + ] = (None, initial_complaint.pacer_url) for result in results: - complaint_url, available = initial_complaints_in_page.get( - result.docket_id, (None, None) + complaint_url, buy_complaint_url = ( + initial_complaints_in_page.get( + result.docket_id, (None, None) + ) ) result["initial_complaint_url"] = complaint_url - result["available_initial_complaint"] = available + result["buy_initial_complaint_url"] = buy_complaint_url case SEARCH_TYPES.OPINION if request_type == "v4" and not highlight: # Retrieves the Opinion plain_text from the DB to fill the snippet diff --git a/cl/search/signals.py b/cl/search/signals.py index 2d78f81c20..87d356d717 100644 --- a/cl/search/signals.py +++ b/cl/search/signals.py @@ -329,11 +329,11 @@ RECAPDocument: { "self": { "description": ["short_description"], - "document_type": ["document_type"], + "document_type": ["document_type", "absolute_url"], "document_number": ["document_number", "absolute_url"], "pacer_doc_id": ["pacer_doc_id"], "plain_text": ["plain_text"], - "attachment_number": ["attachment_number"], + "attachment_number": ["attachment_number", "absolute_url"], "is_available": ["is_available"], "page_count": ["page_count"], "filepath_local": ["filepath_local"], @@ -364,6 +364,7 @@ "assigned_to_str": ["assignedTo"], "referred_to_str": ["referredTo"], "pacer_case_id": ["pacer_case_id"], + "slug": ["absolute_url"], } }, Person: { diff --git a/cl/search/templates/includes/search_result.html b/cl/search/templates/includes/search_result.html index 24900362b2..85cc6706e1 100644 --- a/cl/search/templates/includes/search_result.html +++ b/cl/search/templates/includes/search_result.html @@ -207,12 +207,12 @@

{% endwith %} {% endfor %}
- {% if result.available_initial_complaint %} + {% if result.initial_complaint_url %} Initial Complaint - {% elif result.available_initial_complaint == False %} - + {% elif result.buy_initial_complaint_url %} + Buy Initial Complaint {% endif %} diff --git a/cl/search/tests/tests_es_recap.py b/cl/search/tests/tests_es_recap.py index 73a5c14f5d..6bd02259ac 100644 --- a/cl/search/tests/tests_es_recap.py +++ b/cl/search/tests/tests_es_recap.py @@ -2297,10 +2297,29 @@ def test_initial_complaint_button(self) -> None: pacer_doc_id="234563", ) - # Appellate document initial complaint available + # Appellate document initial not available and not pacer_doc_id de_3 = DocketEntryWithParentsFactory( docket=DocketFactory( - court=self.court, + court=self.court_2, + case_name="Appellate Complaint Not Available no pacer_doc_id", + docket_number="1:21-bk-1235", + source=Docket.RECAP, + ), + entry_number=1, + date_filed=datetime.date(2015, 8, 19), + description="MOTION for Leave to File Amicus Curiae Lorem Served", + ) + initial_complaint_3 = RECAPDocumentFactory( + docket_entry=de_3, + document_number="1", + is_available=False, + pacer_doc_id=None, + ) + + # Appellate document initial complaint available + de_4 = DocketEntryWithParentsFactory( + docket=DocketFactory( + court=self.court_2, case_name="Lorem Appellate vs Complaint Available", docket_number="1:21-bk-1236", source=Docket.RECAP, @@ -2310,8 +2329,8 @@ def test_initial_complaint_button(self) -> None: description="MOTION for Leave to File Amicus Curiae Lorem Served", ) sample_file = SimpleUploadedFile("recap_filename.pdf", b"file") - initial_complaint_3 = RECAPDocumentFactory( - docket_entry=de_3, + initial_complaint_4 = RECAPDocumentFactory( + docket_entry=de_4, document_number="1", attachment_number=1, document_type=RECAPDocument.ATTACHMENT, @@ -2348,7 +2367,7 @@ def test_initial_complaint_button(self) -> None: cd, 1, "Complaint Not available" ) button_url, button_text = self._parse_initial_complaint_button(r) - self.assertEqual("Buy Initial Complaint", button_text) + self.assertEqual("Buy Initial Complaint", button_text, msg="Error 1") self.assertEqual(initial_complaint_2.pacer_url, button_url) # Appellate document initial complaint available @@ -2361,7 +2380,7 @@ def test_initial_complaint_button(self) -> None: ) button_url, button_text = self._parse_initial_complaint_button(r) self.assertEqual("Initial Complaint", button_text) - self.assertEqual(initial_complaint_3.get_absolute_url(), button_url) + self.assertEqual(initial_complaint_4.get_absolute_url(), button_url) # No docket entry is available for the initial complaint. No button is shown. cd = { @@ -2375,9 +2394,23 @@ def test_initial_complaint_button(self) -> None: self.assertIsNone(button_text) self.assertIsNone(button_url) + # Appellate document initial not available and not pacer_doc_id. + # No button is shown. + cd = { + "type": SEARCH_TYPES.RECAP, + "q": '"Appellate Complaint Not Available no pacer_doc_id"', + } + r = async_to_sync(self._test_article_count)( + cd, 1, "Appellate Complaint button no available" + ) + button_url, button_text = self._parse_initial_complaint_button(r) + self.assertIsNone(button_text) + self.assertIsNone(button_url) + de_1.docket.delete() de_2.docket.delete() de_3.docket.delete() + de_4.docket.delete() empty_docket.delete() From 59e18ad144092c41ea831c723c2656cddc763d55 Mon Sep 17 00:00:00 2001 From: Alberto Islas Date: Mon, 19 Aug 2024 15:30:33 -0500 Subject: [PATCH 5/7] build(deps): bump django-debug-toolbar from 4.2.0 to 4.4.6 --- poetry.lock | 12 ++++++------ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 80970a13f1..13937db205 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "ada-url" @@ -977,17 +977,17 @@ tests = ["jinja2 (>=2.9.6)", "pytest", "pytest-cov", "pytest-django", "pytest-ru [[package]] name = "django-debug-toolbar" -version = "4.2.0" +version = "4.4.6" description = "A configurable set of panels that display various debug information about the current request/response." optional = false python-versions = ">=3.8" files = [ - {file = "django_debug_toolbar-4.2.0-py3-none-any.whl", hash = "sha256:af99128c06e8e794479e65ab62cc6c7d1e74e1c19beb44dcbf9bad7a9c017327"}, - {file = "django_debug_toolbar-4.2.0.tar.gz", hash = "sha256:bc7fdaafafcdedefcc67a4a5ad9dac96efd6e41db15bc74d402a54a2ba4854dc"}, + {file = "django_debug_toolbar-4.4.6-py3-none-any.whl", hash = "sha256:3beb671c9ec44ffb817fad2780667f172bd1c067dbcabad6268ce39a81335f45"}, + {file = "django_debug_toolbar-4.4.6.tar.gz", hash = "sha256:36e421cb908c2f0675e07f9f41e3d1d8618dc386392ec82d23bcfcd5d29c7044"}, ] [package.dependencies] -django = ">=3.2.4" +django = ">=4.2.9" sqlparse = ">=0.2" [[package]] @@ -5466,4 +5466,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = ">=3.12, <3.13" -content-hash = "614bfda2bba639b7ec0952bc6833cd5be0771abd15357232149028dc282b060e" +content-hash = "df2df58d268dcb17534329b64dd697df0a141b17d57f0777e6648e9c8cdba493" diff --git a/pyproject.toml b/pyproject.toml index a9934d5f32..20e46f8a2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,6 +116,7 @@ openai = "^1.31.1" seal-rookery = "^2.2.3" types-pytz = "^2024.1.0.20240417" juriscraper = "^2.6.15" +django-debug-toolbar = "^4.4.6" [tool.poetry.group.dev.dependencies] @@ -136,7 +137,6 @@ djangorestframework-stubs = "^3.14.5" black = "^23.12.1" types-simplejson = "^3.19.0.2" lxml-stubs = "^0.5.1" -django-debug-toolbar = "^4.2.0" [tool.black] include = ''' From 41468c9a639d3b67a898e5b535cbc864c16afa69 Mon Sep 17 00:00:00 2001 From: Alberto Islas Date: Mon, 19 Aug 2024 16:34:56 -0500 Subject: [PATCH 6/7] build(deps): Added django-debug-toolbar as dev dep --- poetry.lock | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 13937db205..3419c4fb05 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5466,4 +5466,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = ">=3.12, <3.13" -content-hash = "df2df58d268dcb17534329b64dd697df0a141b17d57f0777e6648e9c8cdba493" +content-hash = "7a2f54103ce6aaa8d20563aa306fcfa93e656b8e10f748e88bad288dc0d44ebe" diff --git a/pyproject.toml b/pyproject.toml index 20e46f8a2f..636a556658 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,6 @@ openai = "^1.31.1" seal-rookery = "^2.2.3" types-pytz = "^2024.1.0.20240417" juriscraper = "^2.6.15" -django-debug-toolbar = "^4.4.6" [tool.poetry.group.dev.dependencies] @@ -137,6 +136,7 @@ djangorestframework-stubs = "^3.14.5" black = "^23.12.1" types-simplejson = "^3.19.0.2" lxml-stubs = "^0.5.1" +django-debug-toolbar = "^4.4.6" [tool.black] include = ''' From d3bd1f0b74b45bef372f8a532a480e3af491c5da Mon Sep 17 00:00:00 2001 From: Alberto Islas Date: Mon, 19 Aug 2024 19:30:09 -0500 Subject: [PATCH 7/7] fix(elasticsearch): Avoid matching Document 1 and Attachment 1 in district and bankruptcy courts. --- cl/lib/elasticsearch_utils.py | 7 +++++++ cl/search/tests/tests_es_recap.py | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/cl/lib/elasticsearch_utils.py b/cl/lib/elasticsearch_utils.py index 2d229e0015..14c872ad79 100644 --- a/cl/lib/elasticsearch_utils.py +++ b/cl/lib/elasticsearch_utils.py @@ -1804,6 +1804,12 @@ def merge_unavailable_fields_on_parent_document( # merged, it considers: # document_number=1 and attachment_number=1 and document_type=ATTACHMENT # This represents document_number 1 that has been converted to an attachment. + + appellate_court_ids = ( + Court.federal_courts.appellate_pacer_courts().values_list( + "pk", flat=True + ) + ) initial_complaints = ( RECAPDocument.objects.filter( QObject( @@ -1814,6 +1820,7 @@ def merge_unavailable_fields_on_parent_document( | QObject( attachment_number=1, document_type=RECAPDocument.ATTACHMENT, + docket_entry__docket__court_id__in=appellate_court_ids, ) ), docket_entry__docket_id__in=docket_ids, diff --git a/cl/search/tests/tests_es_recap.py b/cl/search/tests/tests_es_recap.py index 6bd02259ac..23e98839a3 100644 --- a/cl/search/tests/tests_es_recap.py +++ b/cl/search/tests/tests_es_recap.py @@ -2276,6 +2276,17 @@ def test_initial_complaint_button(self) -> None: document_type=RECAPDocument.PACER_DOCUMENT, is_available=True, filepath_local=sample_file, + pacer_doc_id="1234567", + ) + # This attachment 1 should be ignored for non-appellate courts + RECAPDocumentFactory( + docket_entry=de_1, + document_number="1", + attachment_number=1, + document_type=RECAPDocument.ATTACHMENT, + is_available=True, + filepath_local=sample_file, + pacer_doc_id="1234568", ) # District document initial complaint not available @@ -2293,9 +2304,19 @@ def test_initial_complaint_button(self) -> None: initial_complaint_2 = RECAPDocumentFactory( docket_entry=de_2, document_number="1", + document_type=RECAPDocument.PACER_DOCUMENT, is_available=False, pacer_doc_id="234563", ) + # This attachment 1 should be ignored for non-appellate courts + RECAPDocumentFactory( + docket_entry=de_2, + document_number="1", + attachment_number=1, + document_type=RECAPDocument.ATTACHMENT, + is_available=False, + pacer_doc_id="234564", + ) # Appellate document initial not available and not pacer_doc_id de_3 = DocketEntryWithParentsFactory( @@ -2336,6 +2357,7 @@ def test_initial_complaint_button(self) -> None: document_type=RECAPDocument.ATTACHMENT, is_available=True, filepath_local=sample_file, + pacer_doc_id="7654321", ) # No DocketEntry for the initial complaint available