Skip to content

feat(search): Add API for document-to-opinion citations (opinions-cited-by-recap-document)#7448

Open
montenegroleticia wants to merge 11 commits into
freelawproject:mainfrom
montenegroleticia:7354-pacer-citation-network-api-20260613
Open

feat(search): Add API for document-to-opinion citations (opinions-cited-by-recap-document)#7448
montenegroleticia wants to merge 11 commits into
freelawproject:mainfrom
montenegroleticia:7354-pacer-citation-network-api-20260613

Conversation

@montenegroleticia

@montenegroleticia montenegroleticia commented Jun 14, 2026

Copy link
Copy Markdown

Summary

Part of #7354 step 1 of 4, make the API. CourtListener already exposes the opinion-to-opinion citation network via /api/rest/v4/opinions-cited/, but there's no equivalent for the document <-> opinion citation network stored in OpinionsCitedByRECAPDocument, so API clients can't build a "table of authorities" for a RECAP document or find which documents cite a given opinion.

This PR adds a new v4 endpoint, /api/rest/v4/opinions-cited-by-recap-document/, mirroring /api/rest/v4/opinions-cited/ in structure and conventions:

  • OpinionsCitedByRECAPDocumentSerializer: same base classes as OpinionsCitedSerializer (RetrieveFilteredFieldsMixin, DynamicFieldsMixin, HyperlinkedModelSerializerWithId), plus two read-only denormalized fields, cited_opinion_case_name and citing_document_description, so both directions ("table of authorities" / "who cites this opinion") return everything needed in one page, without a follow-up request per row.
  • OpinionsCitedByRECAPDocumentFilter: exact-match filters on citing_document / cited_opinion (both indexed FKs), depth with integer lookups, and RelatedFilters into RECAPDocumentFilter / OpinionFilter.
  • OpinionsCitedByRECAPDocumentViewSet: same mixin stack and permissions as OpinionsCitedViewSet (LoggingMixin, NoFilterCacheListMixin, DeferredFieldsMixin, DjangoModelPermissions + V3APIPermission), cursor pagination ordered by -id. ordering_fields is restricted to ("id", "depth"): DRF's OrderingFilter can't sort on the dotted source paths behind the two denormalized fields.
  • OpinionsCitedByRECAPDocumentFactory added to cl/search/factories.py and wired into make_dev_data for local testing.
  • Tests added to cl/api/tests.py covering both filter directions, depth filtering, and the denormalized fields, plus test_list_does_not_n_plus_one, which captures query counts for 1 vs. 2 result rows (each pointing at a distinct cited_opinion/cluster/citing_document) and asserts they're equal, confirming select_related("cited_opinion__cluster", "citing_document") resolves the two denormalized fields without growing the query count per row.

Known trade-off: the cited_opinion filter direction (fan-in on heavily-cited opinions, ORDER BY id) isn't covered by a single index today, same gap exists on the reference opinions-cited endpoint. Left as a follow-up rather than a speculative migration on a multi-million-row table.

Deployment

This PR should:

  • skip-deploy (skips everything below)
  • skip-web-deploy
  • skip-celery-deploy
  • skip-cronjob-deploy
  • skip-daemon-deploy
    No special deploy steps required.

@CLAassistant

CLAassistant commented Jun 14, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@montenegroleticia montenegroleticia marked this pull request as draft June 14, 2026 12:23
@montenegroleticia montenegroleticia changed the title feat(search): Add API for filing-to-opinion citations (opinions-cited-by-recap-document) feat(search): Add API for document-to-opinion citations (opinions-cited-by-recap-document) Jun 14, 2026
@montenegroleticia montenegroleticia marked this pull request as ready for review June 14, 2026 13:42
ERosendo and others added 5 commits June 17, 2026 12:53
The serializer no longer exposes the derived cited_opinion_case_name
and citing_document_description fields and now lists its fields
explicitly, so the viewset doesn't need select_related anymore: the
remaining serializer fields are the model's own columns, and the
citing_document/cited_opinion hyperlinks are built from the FK ids via
DRF's pk-only optimization without touching the related objects.

only() and ordering_fields both list every current model field
explicitly, so a field added to the model in the future won't be
fetched or become orderable automatically. Every listed field is
backed by an index (pk, the depth index, the FK indexes and the
unique_together index).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the RelatedFilters on OpinionsCitedByRECAPDocumentFilter with
plain ModelChoiceFilters. RelatedFilter exposed the full
RECAPDocumentFilter/OpinionFilter under the citing_document__ and
cited_opinion__ prefixes, allowing arbitrary traversal filtering like
?citing_document__is_available=True. With exact-only filters, such
params are now rejected as unknown with an HTTP 400.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop assertions on the removed cited_opinion_case_name and
citing_document_description serializer fields, assert the
citing_document hyperlink instead, refresh the stale N+1 test
docstring, and add a regression test confirming related-field
traversal filters (e.g. citing_document__is_available) return 400.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@albertisfu albertisfu force-pushed the 7354-pacer-citation-network-api-20260613 branch from f2d1d3f to dc28754 Compare July 9, 2026 22:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants