From d40ae3fba9cb9dda449be34b1a8c584c7a7b9c20 Mon Sep 17 00:00:00 2001 From: Jordan Witte Date: Fri, 2 Feb 2024 10:52:08 -0800 Subject: [PATCH 1/3] Lesser Charge is identified by disposition string and has eligible/ineligible options --- .../expungeservice/charge_classifier.py | 9 ++++++ .../models/charge_types/lesser_charge.py | 29 +++++++++++++++++++ .../expungeservice/models/disposition.py | 4 ++- .../models/charge_types/test_lesser_charge.py | 22 ++++++++++++++ src/backend/tests/models/test_charge.py | 1 + 5 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/backend/expungeservice/models/charge_types/lesser_charge.py create mode 100644 src/backend/tests/models/charge_types/test_lesser_charge.py diff --git a/src/backend/expungeservice/charge_classifier.py b/src/backend/expungeservice/charge_classifier.py index d7b28aee5..de1eaf4a2 100644 --- a/src/backend/expungeservice/charge_classifier.py +++ b/src/backend/expungeservice/charge_classifier.py @@ -40,6 +40,7 @@ ) from expungeservice.models.disposition import DispositionStatus, Disposition from expungeservice.models.record import Question, Answer +from expungeservice.models.charge_types.lesser_charge import LesserChargeEligible, LesserChargeIneligible @dataclass @@ -67,6 +68,7 @@ def __classifications_list(self) -> Iterator[AmbiguousChargeTypeWithQuestion]: name = self.name.lower() level = self.level.lower() location = self.location.lower() + yield ChargeClassifier._lesser_charge(self.disposition) yield ChargeClassifier._juvenile_charge(self.violation_type) yield ChargeClassifier._parking_ticket(self.violation_type) yield ChargeClassifier._fare_violation(name) @@ -102,6 +104,13 @@ def _criminal_charge( yield ChargeClassifier._attempt_to_commit(name, level, statute) yield ChargeClassifier._classification_by_level(level, statute) + @staticmethod + def _lesser_charge(dispostion: Disposition): + if dispostion.lesser_charge: + question_string = "Is the convicted charge on this case that this charge was reduced to eligible?" + options = {"Yes": LesserChargeEligible(), "No": LesserChargeIneligible()} + return ChargeClassifier._build_ambiguous_charge_type_with_question(question_string, options) + @staticmethod def _juvenile_charge(violation_type: str): if "juvenile" in violation_type.lower(): diff --git a/src/backend/expungeservice/models/charge_types/lesser_charge.py b/src/backend/expungeservice/models/charge_types/lesser_charge.py new file mode 100644 index 000000000..ba1ec4c65 --- /dev/null +++ b/src/backend/expungeservice/models/charge_types/lesser_charge.py @@ -0,0 +1,29 @@ +from dataclasses import dataclass + +from expungeservice.models.charge import ChargeType +from expungeservice.models.expungement_result import TypeEligibility, EligibilityStatus + + +@dataclass(frozen=True) +class LesserChargeEligible(ChargeType): + type_name: str = "Lesser Charge, Eligible" + expungement_rules: str = """A conviction that has been dismissed due to a reduction to a lesser charge is eligible if and only if the new charge is eligible.""" + blocks_other_charges: bool = False + + def type_eligibility(self, disposition): + return TypeEligibility( + EligibilityStatus.ELIGIBLE, + reason="Reduced to another charge; eligible because the new charge is eligible.", + ) + +@dataclass(frozen=True) +class LesserChargeIneligible(ChargeType): + type_name: str = "Lesser Charge, Ineligible" + expungement_rules: str = """A conviction that has been dismissed due to a reduction to a lesser charge is eligible if and only if the new charge is eligible.""" + blocks_other_charges: bool = False + + def type_eligibility(self, disposition): + return TypeEligibility( + EligibilityStatus.INELIGIBLE, + reason="Reduced to another charge; ineligible because the new charge is ineligible.", + ) diff --git a/src/backend/expungeservice/models/disposition.py b/src/backend/expungeservice/models/disposition.py index b86220a1f..cb7a9791f 100644 --- a/src/backend/expungeservice/models/disposition.py +++ b/src/backend/expungeservice/models/disposition.py @@ -21,6 +21,7 @@ class Disposition: ruling: str status: DispositionStatus amended: bool = False + lesser_charge: bool = False class DispositionCreator: @@ -31,7 +32,8 @@ def empty(): @staticmethod def create(date: date, ruling: str, amended: bool = False) -> Disposition: status = DispositionCreator.__build_status(ruling) - return Disposition(date, ruling, status, amended) + lesser_charge = "lesser charge" in ruling.lower() + return Disposition(date, ruling, status, amended, lesser_charge) @staticmethod def __build_status(ruling_string): diff --git a/src/backend/tests/models/charge_types/test_lesser_charge.py b/src/backend/tests/models/charge_types/test_lesser_charge.py new file mode 100644 index 000000000..2fc396990 --- /dev/null +++ b/src/backend/tests/models/charge_types/test_lesser_charge.py @@ -0,0 +1,22 @@ +from expungeservice.models.charge_types.felony_class_c import FelonyClassC +from expungeservice.models.charge_types.sex_crimes import SexCrime +from expungeservice.models.charge_types.traffic_offense import TrafficOffense +from expungeservice.models.charge_types.misdemeanor_class_bc import MisdemeanorClassBC +from expungeservice.models.charge_types.marijuana_eligible import MarijuanaEligible +from expungeservice.models.charge_types.person_felony import PersonFelonyClassB +from expungeservice.models.charge_types.felony_class_b import FelonyClassB +from expungeservice.models.charge_types.lesser_charge import LesserChargeEligible, LesserChargeIneligible + +from tests.factories.charge_factory import ChargeFactory +from tests.models.test_charge import Dispositions + + +def test_lesser_charge(): + charge = ChargeFactory.create_ambiguous_charge( + name="Manufacture/Delivery", + statute="4759922b", + level="Felony Class A", + disposition=Dispositions.LESSER_CHARGE, + ) + assert isinstance(charge[0].charge_type, LesserChargeEligible) + assert isinstance(charge[1].charge_type, LesserChargeIneligible) diff --git a/src/backend/tests/models/test_charge.py b/src/backend/tests/models/test_charge.py index 0b834e77a..5743d6d5e 100644 --- a/src/backend/tests/models/test_charge.py +++ b/src/backend/tests/models/test_charge.py @@ -22,6 +22,7 @@ class Dispositions: DISMISSED = DispositionCreator.create(ruling="Dismissed", date=LAST_WEEK) UNRECOGNIZED_DISPOSITION = DispositionCreator.create(ruling="Something unrecognized", date=LAST_WEEK) NO_COMPLAINT = DispositionCreator.create(ruling="No Complaint", date=LAST_WEEK) + LESSER_CHARGE = DispositionCreator.create(ruling="Convicted - Lesser Charge", date=LAST_WEEK) class TestChargeClass(unittest.TestCase): From f55f051396db6865dfdd2fa824d2bff9a9ab1f5e Mon Sep 17 00:00:00 2001 From: Jordan Witte Date: Fri, 2 Feb 2024 10:59:14 -0800 Subject: [PATCH 2/3] fixed this to pass a test; has no effect because this charge type is always a dismissal --- src/backend/expungeservice/models/charge_types/lesser_charge.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backend/expungeservice/models/charge_types/lesser_charge.py b/src/backend/expungeservice/models/charge_types/lesser_charge.py index ba1ec4c65..1d0d2d969 100644 --- a/src/backend/expungeservice/models/charge_types/lesser_charge.py +++ b/src/backend/expungeservice/models/charge_types/lesser_charge.py @@ -8,7 +8,6 @@ class LesserChargeEligible(ChargeType): type_name: str = "Lesser Charge, Eligible" expungement_rules: str = """A conviction that has been dismissed due to a reduction to a lesser charge is eligible if and only if the new charge is eligible.""" - blocks_other_charges: bool = False def type_eligibility(self, disposition): return TypeEligibility( @@ -20,7 +19,6 @@ def type_eligibility(self, disposition): class LesserChargeIneligible(ChargeType): type_name: str = "Lesser Charge, Ineligible" expungement_rules: str = """A conviction that has been dismissed due to a reduction to a lesser charge is eligible if and only if the new charge is eligible.""" - blocks_other_charges: bool = False def type_eligibility(self, disposition): return TypeEligibility( From a1e27ecbf3d9dc0c88dff7cffcb2ef43a39a95d8 Mon Sep 17 00:00:00 2001 From: Jordan Witte Date: Fri, 2 Feb 2024 11:05:13 -0800 Subject: [PATCH 3/3] format and import fixes --- src/backend/expungeservice/charge_classifier.py | 2 +- .../tests/models/charge_types/test_lesser_charge.py | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/backend/expungeservice/charge_classifier.py b/src/backend/expungeservice/charge_classifier.py index de1eaf4a2..2b7f7c7d7 100644 --- a/src/backend/expungeservice/charge_classifier.py +++ b/src/backend/expungeservice/charge_classifier.py @@ -106,7 +106,7 @@ def _criminal_charge( @staticmethod def _lesser_charge(dispostion: Disposition): - if dispostion.lesser_charge: + if dispostion.lesser_charge: question_string = "Is the convicted charge on this case that this charge was reduced to eligible?" options = {"Yes": LesserChargeEligible(), "No": LesserChargeIneligible()} return ChargeClassifier._build_ambiguous_charge_type_with_question(question_string, options) diff --git a/src/backend/tests/models/charge_types/test_lesser_charge.py b/src/backend/tests/models/charge_types/test_lesser_charge.py index 2fc396990..f783d289d 100644 --- a/src/backend/tests/models/charge_types/test_lesser_charge.py +++ b/src/backend/tests/models/charge_types/test_lesser_charge.py @@ -1,10 +1,3 @@ -from expungeservice.models.charge_types.felony_class_c import FelonyClassC -from expungeservice.models.charge_types.sex_crimes import SexCrime -from expungeservice.models.charge_types.traffic_offense import TrafficOffense -from expungeservice.models.charge_types.misdemeanor_class_bc import MisdemeanorClassBC -from expungeservice.models.charge_types.marijuana_eligible import MarijuanaEligible -from expungeservice.models.charge_types.person_felony import PersonFelonyClassB -from expungeservice.models.charge_types.felony_class_b import FelonyClassB from expungeservice.models.charge_types.lesser_charge import LesserChargeEligible, LesserChargeIneligible from tests.factories.charge_factory import ChargeFactory @@ -13,8 +6,8 @@ def test_lesser_charge(): charge = ChargeFactory.create_ambiguous_charge( - name="Manufacture/Delivery", - statute="4759922b", + name="Manufacture/Delivery", + statute="4759922b", level="Felony Class A", disposition=Dispositions.LESSER_CHARGE, )