From 3d35cc467fa7b8c351f8bbfab0b2518dd261efa9 Mon Sep 17 00:00:00 2001 From: Jordan Witte Date: Tue, 21 Jan 2025 16:56:08 -0800 Subject: [PATCH 1/4] cases with restitution are ineligible --- src/backend/expungeservice/charges_summarizer.py | 4 ++-- src/backend/expungeservice/crawler/crawler.py | 2 ++ src/backend/expungeservice/crawler/parsers/case_parser.py | 4 +++- .../expungeservice/crawler/parsers/record_parser.py | 2 ++ src/backend/expungeservice/demo_records.py | 1 + src/backend/expungeservice/models/case.py | 6 +++++- src/backend/expungeservice/models/expungement_result.py | 1 + src/backend/expungeservice/record_merger.py | 4 ++++ src/backend/expungeservice/serializer.py | 1 + src/backend/tests/models/test_case.py | 2 +- src/backend/tests/test_edit_results.py | 2 ++ src/frontend/src/components/RecordSearch/Record/Case.tsx | 7 ++++++- .../src/components/RecordSearch/Record/Eligibility.tsx | 4 +++- src/frontend/src/components/RecordSearch/Record/index.tsx | 1 + src/frontend/src/components/RecordSearch/Record/types.ts | 1 + src/frontend/src/redux/search/selectors.test.ts | 3 +++ 16 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/backend/expungeservice/charges_summarizer.py b/src/backend/expungeservice/charges_summarizer.py index 742c10011..8ab2b9859 100644 --- a/src/backend/expungeservice/charges_summarizer.py +++ b/src/backend/expungeservice/charges_summarizer.py @@ -38,7 +38,7 @@ def _primary_sort(charge: Charge, record: Record): ''' Order is: - 0 Needs More Analysis + 0 Needs More Analysis / Restitution Owed 1 Ineligible 2 Eligible Now 3 Eligible on case with Ineligible charge @@ -56,7 +56,7 @@ def _primary_sort(charge: Charge, record: Record): label = charge_eligibility.label has_balance = this_case.summary.balance_due_in_cents != 0 - if label == "Needs More Analysis": + if label == "Needs More Analysis" or label == "Ineligible If Restitution Owed": return 0, label, charge.case_number elif label == "Ineligible": return 1, label, charge.case_number diff --git a/src/backend/expungeservice/crawler/crawler.py b/src/backend/expungeservice/crawler/crawler.py index a14bc4814..7d8d14c4f 100644 --- a/src/backend/expungeservice/crawler/crawler.py +++ b/src/backend/expungeservice/crawler/crawler.py @@ -89,6 +89,7 @@ def _read_case(session: Session, case_summary: CaseSummary) -> OeciCase: case_parser_data = Crawler._parse_case(session, case_summary) district_attorney_number = case_parser_data.district_attorney_number sid = case_parser_data.sid + resitution = case_parser_data.restitution balance_due_in_cents = CaseCreator.compute_balance_due_in_cents(case_parser_data.balance_due) charges: List[OeciCharge] = [] for charge_id, charge_dict in case_parser_data.hashed_charge_data.items(): @@ -102,6 +103,7 @@ def _read_case(session: Session, case_summary: CaseSummary) -> OeciCase: district_attorney_number=district_attorney_number, sid=sid, balance_due_in_cents=balance_due_in_cents, + restitution=resitution, edit_status=EditStatus.UNCHANGED, ) return OeciCase(updated_case_summary, charges=tuple(charges)) diff --git a/src/backend/expungeservice/crawler/parsers/case_parser.py b/src/backend/expungeservice/crawler/parsers/case_parser.py index afc6db360..05ea9da5f 100644 --- a/src/backend/expungeservice/crawler/parsers/case_parser.py +++ b/src/backend/expungeservice/crawler/parsers/case_parser.py @@ -22,6 +22,7 @@ class CaseParserData: hashed_charge_data: Dict[int, Dict[str, str]] hashed_dispo_data: Dict[int, Dict[str, str]] balance_due: str + restitution: bool probation_revoked: Optional[date] @@ -41,8 +42,9 @@ def feed(data) -> CaseParserData: probation_revoked = date.fromdatetime(datetime.strptime(probation_revoked_date_string, "%m/%d/%Y")) else: probation_revoked = None # type: ignore + restitution = "restitution" in soup.text.lower() return CaseParserData( - district_attorney_number, sid, hashed_charge_data, hashed_dispo_data, balance_due, probation_revoked + district_attorney_number, sid, hashed_charge_data, hashed_dispo_data, balance_due, restitution, probation_revoked ) @staticmethod diff --git a/src/backend/expungeservice/crawler/parsers/record_parser.py b/src/backend/expungeservice/crawler/parsers/record_parser.py index 7aa936586..3a641b4b2 100644 --- a/src/backend/expungeservice/crawler/parsers/record_parser.py +++ b/src/backend/expungeservice/crawler/parsers/record_parser.py @@ -83,6 +83,8 @@ def __record_case(self): self.date_location, self.type_status, self.case_detail_link, + False, + "0" ) ) diff --git a/src/backend/expungeservice/demo_records.py b/src/backend/expungeservice/demo_records.py index 89754b0b1..46dfd56b4 100644 --- a/src/backend/expungeservice/demo_records.py +++ b/src/backend/expungeservice/demo_records.py @@ -50,6 +50,7 @@ def build_search_results( "date": date_class.today(), "district_attorney_number": "01234567", "sid": "OR12345678", + "restitution": False } shared_charge_data = { "balance_due_in_cents": 0, diff --git a/src/backend/expungeservice/models/case.py b/src/backend/expungeservice/models/case.py index 4b4d042fa..4203db67e 100644 --- a/src/backend/expungeservice/models/case.py +++ b/src/backend/expungeservice/models/case.py @@ -23,6 +23,7 @@ class CaseSummary: current_status: str case_detail_link: str balance_due_in_cents: int + restitution: bool edit_status: EditStatus def get_balance_due(self): @@ -61,6 +62,7 @@ def empty(case_number: str): current_status="", case_detail_link="", balance_due_in_cents=0, + restitution=False, edit_status=EditStatus.UNCHANGED, ), (), @@ -112,7 +114,8 @@ def create( date_location, type_status, case_detail_link, - balance="0", + restitution, + balance, ) -> CaseSummary: name = info[0] birth_year = CaseSummary._parse_birth_year(info) @@ -134,6 +137,7 @@ def create( current_status, case_detail_link, balance_due_in_cents, + restitution, EditStatus.UNCHANGED, ) diff --git a/src/backend/expungeservice/models/expungement_result.py b/src/backend/expungeservice/models/expungement_result.py index 5a756fc8e..e734858fa 100644 --- a/src/backend/expungeservice/models/expungement_result.py +++ b/src/backend/expungeservice/models/expungement_result.py @@ -21,6 +21,7 @@ class ChargeEligibilityStatus(str, Enum): POSSIBLY_WILL_BE_ELIGIBLE = "Possibly Will Be Eligible" INELIGIBLE = "Ineligible" NEEDS_MORE_ANALYSIS = "Needs More Analysis" + INELIGIBLE_IF_RESTITUTION_OWED = "Ineligible If Restitution Owed" def __repr__(self): return f"{self.__class__.__name__}.{self.name}" diff --git a/src/backend/expungeservice/record_merger.py b/src/backend/expungeservice/record_merger.py index db8ca02ee..805cfb98b 100644 --- a/src/backend/expungeservice/record_merger.py +++ b/src/backend/expungeservice/record_merger.py @@ -61,6 +61,10 @@ def merge( charge_eligibility, label=f"Eligibility date dependent on open charge: {charge_eligibility.label}", ) + if case.summary.restitution and charge_eligibility.status != ChargeEligibilityStatus.INELIGIBLE : + charge_eligibility = ChargeEligibility( + ChargeEligibilityStatus.INELIGIBLE_IF_RESTITUTION_OWED, "Ineligible If Restitution Owed" + ) expungement_result = ExpungementResult( type_eligibility=merged_type_eligibility, time_eligibility=merged_time_eligibility, diff --git a/src/backend/expungeservice/serializer.py b/src/backend/expungeservice/serializer.py index 374390007..4603aa9e2 100644 --- a/src/backend/expungeservice/serializer.py +++ b/src/backend/expungeservice/serializer.py @@ -52,6 +52,7 @@ def case_summary_to_json(self, case): "case_detail_link": case.case_detail_link, "district_attorney_number": case.district_attorney_number, "sid": case.sid, + "restitution": case.restitution, "edit_status": case.edit_status, } diff --git a/src/backend/tests/models/test_case.py b/src/backend/tests/models/test_case.py index fc6bef6ab..c4cb582e2 100644 --- a/src/backend/tests/models/test_case.py +++ b/src/backend/tests/models/test_case.py @@ -6,7 +6,7 @@ class TestCaseBalanceDue(unittest.TestCase): def test_balance_due_getter_setter(self): - case_args = [("John Doe", "1990"), "", "", "", "", ("1/1/2019", ""), ("", ""), ""] + case_args = [("John Doe", "1990"), "", "", "", "", ("1/1/2019", ""), ("", ""), "", False] case_1 = CaseCreator.create(*case_args, "123.45") # type: ignore assert case_1.get_balance_due() == 123.45 diff --git a/src/backend/tests/test_edit_results.py b/src/backend/tests/test_edit_results.py index 89627a189..a3d9a0460 100644 --- a/src/backend/tests/test_edit_results.py +++ b/src/backend/tests/test_edit_results.py @@ -24,6 +24,7 @@ current_status="CLOSED", case_detail_link="alink", balance_due_in_cents=0, + restitution=False, edit_status=EditStatus.UNCHANGED, ), ( @@ -75,6 +76,7 @@ current_status="CLOSED", case_detail_link="alink", balance_due_in_cents=0, + restitution=False, edit_status=EditStatus.UNCHANGED, ), ( diff --git a/src/frontend/src/components/RecordSearch/Record/Case.tsx b/src/frontend/src/components/RecordSearch/Record/Case.tsx index 26b61ebad..d7436776d 100644 --- a/src/frontend/src/components/RecordSearch/Record/Case.tsx +++ b/src/frontend/src/components/RecordSearch/Record/Case.tsx @@ -83,6 +83,7 @@ export default class Case extends React.Component { location, current_status, district_attorney_number, + restitution, edit_status, } = this.props.case; const allIneligible = charges.every( @@ -180,7 +181,11 @@ export default class Case extends React.Component { ) : null)} - + {restitution && !allIneligible && ( +
+ Eligible charges are ineligible if restitution is owed +
+ )} {balance_due > 0 && !allIneligible && (
Eligible charges are ineligible until balance is paid diff --git a/src/frontend/src/components/RecordSearch/Record/Eligibility.tsx b/src/frontend/src/components/RecordSearch/Record/Eligibility.tsx index 4c4543daf..8e8bc6ecb 100644 --- a/src/frontend/src/components/RecordSearch/Record/Eligibility.tsx +++ b/src/frontend/src/components/RecordSearch/Record/Eligibility.tsx @@ -26,7 +26,9 @@ export default class Eligibility extends React.Component { return "red bg-washed-red"; case "Needs More Analysis": return "purple bg-washed-purple"; - } + case "Ineligible If Restitution Owed": + return "purple bg-washed-purple"; + } }; const eligibility = () => { diff --git a/src/frontend/src/components/RecordSearch/Record/index.tsx b/src/frontend/src/components/RecordSearch/Record/index.tsx index caeb362a2..c650287af 100644 --- a/src/frontend/src/components/RecordSearch/Record/index.tsx +++ b/src/frontend/src/components/RecordSearch/Record/index.tsx @@ -32,6 +32,7 @@ export default function Record() { name: "", violation_type: "", district_attorney_number: "", + restitution: false, edit_status: "ADD", }; }; diff --git a/src/frontend/src/components/RecordSearch/Record/types.ts b/src/frontend/src/components/RecordSearch/Record/types.ts index 1e94ba355..60c69d740 100644 --- a/src/frontend/src/components/RecordSearch/Record/types.ts +++ b/src/frontend/src/components/RecordSearch/Record/types.ts @@ -45,6 +45,7 @@ export interface CaseData { location: string; name: string; violation_type: string; + restitution: boolean; } export interface RecordData { diff --git a/src/frontend/src/redux/search/selectors.test.ts b/src/frontend/src/redux/search/selectors.test.ts index 04c7e2612..0f44fdb45 100644 --- a/src/frontend/src/redux/search/selectors.test.ts +++ b/src/frontend/src/redux/search/selectors.test.ts @@ -27,6 +27,7 @@ const mockData = { location: "Josephine", name: "Last, First Middle", violation_type: "Offense Felony", + restitution: false }, { balance_due: 0.0, @@ -42,6 +43,7 @@ const mockData = { location: "Josephine", name: "Last, First Middle", violation_type: "Offense Felony", + restitution: false }, { balance_due: 1763.0, @@ -57,6 +59,7 @@ const mockData = { location: "Josephine", name: "Last, First Middle", violation_type: "Offense Misdemeanor", + restitution: false }, ], }, From 33e051b1503156c16f4a49e7e54ff7aacef94b35 Mon Sep 17 00:00:00 2001 From: Jordan Witte Date: Wed, 22 Jan 2025 14:35:07 -0800 Subject: [PATCH 2/4] Restitution added to the record editor --- src/backend/expungeservice/record_editor.py | 2 + .../RecordSearch/Record/EditCasePanel.tsx | 44 +++++++++++++++++++ src/frontend/src/redux/search/actions.ts | 2 + src/frontend/src/redux/search/reducer.ts | 1 + src/frontend/src/redux/search/types.ts | 1 + 5 files changed, 50 insertions(+) diff --git a/src/backend/expungeservice/record_editor.py b/src/backend/expungeservice/record_editor.py index bf83edc6d..88e338767 100644 --- a/src/backend/expungeservice/record_editor.py +++ b/src/backend/expungeservice/record_editor.py @@ -46,6 +46,8 @@ def _edit_case(case: OeciCase, case_edits) -> Tuple[OeciCase, List[Charge]]: case_summary_edits["balance_due_in_cents"] = CaseCreator.compute_balance_due_in_cents(value) elif key == "birth_year": case_summary_edits["birth_year"] = int(value) + elif key == "restitution": + case_summary_edits["restitution"] = value == "True" else: case_summary_edits[key] = value edited_summary = replace(case.summary, **case_summary_edits) diff --git a/src/frontend/src/components/RecordSearch/Record/EditCasePanel.tsx b/src/frontend/src/components/RecordSearch/Record/EditCasePanel.tsx index 9c858dd80..0a3de8de7 100644 --- a/src/frontend/src/components/RecordSearch/Record/EditCasePanel.tsx +++ b/src/frontend/src/components/RecordSearch/Record/EditCasePanel.tsx @@ -26,6 +26,7 @@ interface State { missingBalance: boolean; missingBirthYear: boolean; invalidBirthYear: boolean; + restitution: string; } const counties = [ @@ -78,11 +79,13 @@ export default class EditCasePanel extends React.Component { missingBalance: false, missingBirthYear: false, invalidBirthYear: false, + restitution: this.props.case.restitution ? "True" : "False", }; anyFieldsChanged = () => { return !( this.props.case.current_status === this.state.current_status && + ((this.props.case.restitution && this.state.restitution === "True") || (!this.props.case.restitution && this.state.restitution === "False")) && this.props.case.location === this.state.location && this.props.case.balance_due.toFixed(2) === this.state.balance_due && this.props.case.birth_year.toString() === this.state.birth_year @@ -117,6 +120,7 @@ export default class EditCasePanel extends React.Component { : "UPDATE", this.props.case.case_number, this.state.current_status, + this.state.restitution, this.state.location, this.state.balance_due, this.state.birth_year @@ -240,6 +244,46 @@ export default class EditCasePanel extends React.Component {
+ Restitution Owed + +
+
+ + +
+
+ + +
+