-
-
Notifications
You must be signed in to change notification settings - Fork 268
Florida docket merger and abstraction prototype #7421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
46d7081
eb253d4
686e722
d6a4b8c
5716940
3a3f7c6
3e6478a
d4314dc
df851e6
e02e086
66031be
2127551
64aa594
fc0002d
1b5cb44
1683ccc
18df01e
181bd30
85bd3bf
3dec12e
6d94c73
d8284eb
fb92877
83104e6
d1a0736
b1c0f67
dd9c453
557a379
aa71a92
24124a3
2487b86
e8f12a2
07ad840
c2906d9
6bea3bd
d862131
c732800
8128c5b
54b5150
b27985a
8437d5c
ff0a52b
f333efb
eb136c9
8c28bca
16b7303
7df13ff
4d5ee58
73c2a4f
c6014bf
d9b8c22
43827ec
68b0f98
446f58b
b61d121
0ae68ec
171a8c2
62bda29
92c1946
3bda8ad
4499e3a
4fb2ae4
ec22316
5fcdff7
a81d79b
c5b22fc
62da576
1e9ff9b
d14643a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| """Factories for mocking output of Florida Juriscraper modules.""" | ||
|
|
||
| from factory.base import Factory | ||
| from factory.declarations import LazyAttribute, List, SubFactory | ||
| from factory.faker import Faker | ||
| from juriscraper.state.docket import DocketTransfer | ||
| from juriscraper.state.florida.cases import ( | ||
| FLORIDA_DOCKET_TYPE_MAP, | ||
| FloridaCase, | ||
| FloridaOriginatingCase, | ||
| ) | ||
| from juriscraper.state.florida.courts import FloridaCourtID | ||
| from juriscraper.state.florida.docket_entries import FloridaDocketEntry | ||
| from juriscraper.state.florida.parties import FloridaParty | ||
|
|
||
|
|
||
| class _PydanticConstructFactory(Factory): | ||
| """Builds Pydantic models via ``model_construct`` so factories can use | ||
| field names directly even though the upstream models declare | ||
| ``validation_alias``\\es matching the Florida API payload shape.""" | ||
|
|
||
| class Meta: | ||
| abstract = True | ||
|
|
||
| @classmethod | ||
| def _build(cls, model_class, *args, **kwargs): | ||
| return model_class.model_construct(**kwargs) | ||
|
|
||
| @classmethod | ||
| def _create(cls, model_class, *args, **kwargs): | ||
| return model_class.model_construct(**kwargs) | ||
|
|
||
|
|
||
| class FloridaOriginatingCaseFactory(_PydanticConstructFactory): | ||
| class Meta: | ||
| model = FloridaOriginatingCase | ||
|
|
||
| court_name = Faker("court_name") | ||
| court_id = Faker( | ||
| "random_element", | ||
| elements=(FloridaCourtID.CIRCUIT, FloridaCourtID.COUNTY), | ||
| ) | ||
| case_number = Faker("federal_district_docket_number") | ||
|
|
||
|
|
||
| # The merger does not currently read fields off these objects, so the factories | ||
| # just produce stubs. | ||
| class FloridaDocketTransferFactory(_PydanticConstructFactory): | ||
| class Meta: | ||
| model = DocketTransfer | ||
|
|
||
|
|
||
| class FloridaCasePartyFactory(_PydanticConstructFactory): | ||
| class Meta: | ||
| model = FloridaParty | ||
|
|
||
|
|
||
| class FloridaDocketEntryFactory(_PydanticConstructFactory): | ||
| class Meta: | ||
| model = FloridaDocketEntry | ||
|
|
||
| date_filed = Faker("date_object") | ||
|
|
||
|
|
||
| class FloridaCaseFactory(_PydanticConstructFactory): | ||
| class Meta: | ||
| model = FloridaCase | ||
|
|
||
| case_uuid = Faker("uuid4") | ||
| docket_number = Faker("federal_district_docket_number") | ||
| case_name = Faker("case_name") | ||
| case_name_full = Faker("case_name", full=True) | ||
| case_caption = Faker("text") | ||
| closed_flag = Faker("pybool") | ||
| class_group_type = Faker("pystr") | ||
| class_group_type_id = Faker("pyint") | ||
| docket_type = Faker( | ||
| "random_element", elements=list(FLORIDA_DOCKET_TYPE_MAP.values()) | ||
| ) | ||
| classification_id = Faker("pyint") | ||
| court_id = Faker( | ||
| "random_element", | ||
| elements=( | ||
| FloridaCourtID.FIRST_COA.value, | ||
| FloridaCourtID.SECOND_COA.value, | ||
| FloridaCourtID.SIXTH_COA.value, | ||
| FloridaCourtID.SUPREME_COURT.value, | ||
| ), | ||
| ) | ||
| court_abbreviation = Faker("pystr", max_chars=3) | ||
| location = Faker("city") | ||
| location_id = Faker("pyint") | ||
| datetime_filed = Faker("date_time") | ||
| date_filed = LazyAttribute(lambda o: o.datetime_filed.date()) | ||
| case_group_flag = Faker("pybool") | ||
| panel_flag = Faker("pybool") | ||
| originating_cases = List([SubFactory(FloridaOriginatingCaseFactory)]) | ||
| transfers = List([SubFactory(FloridaDocketTransferFactory)]) | ||
| entries = List([SubFactory(FloridaDocketEntryFactory)]) | ||
| parties = List([SubFactory(FloridaCasePartyFactory)]) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| import logging | ||
| from datetime import date | ||
| from typing import ClassVar, override | ||
|
|
||
| from asgiref.sync import async_to_sync | ||
| from django.db.models import Model, QuerySet | ||
| from juriscraper.state.florida import FloridaCase, FloridaOriginatingCase | ||
| from juriscraper.state.florida.cases import FloridaCourtID | ||
|
|
||
| from cl.corpus_importer.state.florida.utils import ( | ||
| FL_APPELLATE_COURT_ID, | ||
| FLORIDA_COURT_ID_MAP, | ||
| make_docket_number_core, | ||
| ) | ||
| from cl.corpus_importer.state.merger import ( | ||
| Attribute, | ||
| Merger, | ||
| OneToOneRelation, | ||
| overwrite, | ||
| ) | ||
| from cl.recap.mergers import find_docket_object_query | ||
| from cl.search.models import Docket, OriginatingCourtInformation | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def add_scraper_source(scrape: int | None, db: int | None) -> int: | ||
| if not db: | ||
| db = 0 | ||
| if db in Docket.NON_SCRAPER_SOURCES(): | ||
| return db + Docket.SCRAPER | ||
| return db | ||
|
|
||
|
|
||
| def _date_last_filing(docket_data: FloridaCase, params: None) -> date | None: | ||
| filing_dates = sorted( | ||
| e.date_filed for e in docket_data.entries if e.date_filed | ||
| ) | ||
| return filing_dates[-1] if filing_dates else docket_data.date_filed | ||
|
|
||
|
|
||
| def _appeal_from_id(docket_data: FloridaCase, params: None) -> str | None: | ||
| # Multiple originating cases are ambiguous, so leave the field unset. | ||
| if len(docket_data.originating_cases) != 1: | ||
| return None | ||
| return FLORIDA_COURT_ID_MAP.get( | ||
| docket_data.originating_cases[0].court_id.value, None | ||
| ) | ||
|
|
||
|
|
||
| def _appeal_from_str(docket_data: FloridaCase, params: None) -> str | None: | ||
| # Multiple originating cases are ambiguous, so leave the field unset. | ||
| if len(docket_data.originating_cases) != 1: | ||
| return "" | ||
| return docket_data.originating_cases[0].court_name | ||
|
|
||
|
|
||
| class FloridaOriginatingCourtInformationMerger( | ||
| Merger[FloridaOriginatingCase, None, OriginatingCourtInformation] | ||
| ): | ||
| model: ClassVar[type[Model]] = OriginatingCourtInformation | ||
|
|
||
| docket_number: str = Attribute( | ||
| lambda oc, params: oc.case_number, strategy=overwrite | ||
| ) | ||
| docket_number_raw: str = Attribute( | ||
| lambda oc, params: oc.case_number, strategy=overwrite | ||
| ) | ||
|
|
||
| def query(self) -> QuerySet[OriginatingCourtInformation]: | ||
| return OriginatingCourtInformation.objects.none() | ||
|
|
||
|
|
||
| def _originating_case( | ||
| docket_data: FloridaCase, params: None | ||
| ) -> FloridaOriginatingCase | None: | ||
| if docket_data.court_id != FloridaCourtID.SUPREME_COURT.value: | ||
| return None | ||
| if not docket_data.originating_cases: | ||
| return None | ||
| if len(docket_data.originating_cases) > 1: | ||
| logger.warning( | ||
| "Florida docket %s in court %s has multiple originating cases. Using the first one.", | ||
| docket_data.docket_number, | ||
| docket_data.court_id, | ||
| ) | ||
| return docket_data.originating_cases[0] | ||
|
|
||
|
|
||
| class FloridaDocketMerger(Merger[FloridaCase, None, Docket]): | ||
| model: ClassVar[type[Model]] = Docket | ||
|
|
||
| atomic = True | ||
|
|
||
| court_id: str = Attribute( | ||
| lambda d, params: FLORIDA_COURT_ID_MAP[d.court_id], | ||
| strategy=overwrite, | ||
| ) | ||
| source: int = Attribute( | ||
| lambda _, params: Docket.SCRAPER, | ||
| strategy=add_scraper_source, | ||
| ) | ||
| date_filed: date | None = Attribute( | ||
| lambda d, params: d.date_filed, | ||
| strategy=overwrite, | ||
| ) | ||
| date_last_filing: date | None = Attribute( | ||
| _date_last_filing, | ||
| strategy=overwrite, | ||
| ) | ||
| case_name: str = Attribute( | ||
| lambda d, params: d.case_name, strategy=overwrite | ||
| ) | ||
| case_name_full: str = Attribute( | ||
| lambda d, params: d.case_name_full, | ||
| strategy=overwrite, | ||
| ) | ||
| case_name_short: str = Attribute( | ||
| lambda d, params: d.case_name, strategy=overwrite | ||
| ) | ||
| docket_number: str = Attribute( | ||
| lambda d, params: d.docket_number, | ||
| strategy=overwrite, | ||
| ) | ||
| docket_number_raw: str = Attribute( | ||
| lambda d, params: d.docket_number, strategy=overwrite | ||
| ) | ||
| docket_number_core: str = Attribute( | ||
| lambda d, params: make_docket_number_core( | ||
| d.docket_number, court_id=FLORIDA_COURT_ID_MAP[d.court_id] | ||
| ), | ||
| strategy=overwrite, | ||
| ) | ||
| appeal_from_id: str | None = Attribute(_appeal_from_id, strategy=overwrite) | ||
| appeal_from_str: str | None = Attribute( | ||
| _appeal_from_str, strategy=overwrite | ||
| ) | ||
| # See https://github.com/freelawproject/courtlistener/issues/7361#issuecomment-4566459292 | ||
| pacer_case_id: str = Attribute( | ||
| lambda d, params: str(d.case_uuid), strategy=overwrite | ||
| ) | ||
| originating_court_information: OriginatingCourtInformation = ( | ||
| OneToOneRelation( | ||
| FloridaOriginatingCourtInformationMerger, | ||
| _originating_case, | ||
| ) | ||
| ) | ||
|
|
||
| @override | ||
| def query(self) -> QuerySet[Docket]: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was testing this and found that two cases with the same UUID but different docket numbers are not matching. We should prioritize the UUID ( We could use logic similar to Why wasn't it possible to continue using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When I wrote this, I didn't want to update
Then Florida and future What do you think? |
||
| supreme_court_id = FLORIDA_COURT_ID_MAP[ | ||
| FloridaCourtID.SUPREME_COURT.value | ||
| ] | ||
| court_id = FLORIDA_COURT_ID_MAP[self.scrape.court_id] | ||
| dn_core = make_docket_number_core( | ||
| self.scrape.docket_number, court_id=court_id | ||
| ) | ||
|
|
||
| query_narrow = async_to_sync(find_docket_object_query)( | ||
| court_id=court_id, | ||
| pacer_case_id=str(self.scrape.case_uuid), | ||
| docket_number=self.scrape.docket_number, | ||
| docket_number_core=dn_core, | ||
| federal_defendant_number=None, | ||
| federal_dn_judge_initials_assigned=None, | ||
| federal_dn_judge_initials_referred=None, | ||
| skip_dn_core_confirmation=True, | ||
| ) | ||
|
|
||
| if court_id == supreme_court_id: | ||
| return query_narrow | ||
|
|
||
| if query_narrow.count() == 0: | ||
| return async_to_sync(find_docket_object_query)( | ||
| court_id=FL_APPELLATE_COURT_ID, | ||
| pacer_case_id=str(self.scrape.case_uuid), | ||
| docket_number=self.scrape.docket_number, | ||
| docket_number_core=dn_core, | ||
| federal_defendant_number=None, | ||
| federal_dn_judge_initials_assigned=None, | ||
| federal_dn_judge_initials_referred=None, | ||
| skip_dn_core_confirmation=True, | ||
| ) | ||
|
|
||
| return query_narrow | ||
|
|
||
| @staticmethod | ||
| def validate(scrape: FloridaCase) -> bool: | ||
| if scrape.court_id not in FLORIDA_COURT_ID_MAP: | ||
| logger.error("Unknown court id: %s", scrape.court_id) | ||
| return False | ||
| return True | ||
Uh oh!
There was an error while loading. Please reload this page.