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..22c2021ea 100644 --- a/src/backend/expungeservice/models/case.py +++ b/src/backend/expungeservice/models/case.py @@ -22,6 +22,7 @@ class CaseSummary: violation_type: str current_status: str case_detail_link: str + restitution: bool balance_due_in_cents: int edit_status: EditStatus @@ -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) @@ -133,6 +136,7 @@ def create( violation_type, current_status, case_detail_link, + restitution, balance_due_in_cents, 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_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/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/factories/case_factory.py b/src/backend/tests/factories/case_factory.py index e85ecbd16..e365fa3d6 100644 --- a/src/backend/tests/factories/case_factory.py +++ b/src/backend/tests/factories/case_factory.py @@ -12,6 +12,7 @@ def create( date_location=["1/1/1995", "Multnomah"], type_status=["Offense Misdemeanor", "Closed"], case_detail_link="?404", + restitution=False, balance="0", ) -> CaseSummary: return CaseCreator.create( @@ -23,6 +24,7 @@ def create( date_location, type_status, case_detail_link, + restitution, balance, ) @@ -39,6 +41,7 @@ def create( type_status=["Offense Misdemeanor", "Closed"], charges=[], case_detail_link="?404", + restitution=False, balance="0", ) -> Case: case_summary = CaseSummaryFactory.create( @@ -50,6 +53,7 @@ def create( date_location, type_status, case_detail_link, + restitution, balance, ) return Case(case_summary, tuple(charges)) 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/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 + +
+
+ + +
+
+ + +
+