Exemple #1
0
 def set_time_eligibility(self, eligibility_dates):
     date_will_be_eligible, reason = max(eligibility_dates)
     if date_will_be_eligible and date_class.today() >= date_will_be_eligible:
         time_eligibility = TimeEligibility(status=EligibilityStatus.ELIGIBLE, reason="", date_will_be_eligible=None)
     else:
         time_eligibility = TimeEligibility(
             status=EligibilityStatus.INELIGIBLE, reason=reason, date_will_be_eligible=date_will_be_eligible
         )
     self.expungement_result.time_eligibility = time_eligibility
Exemple #2
0
def test_possibly_eligible():
    type_eligibility = TypeEligibility(EligibilityStatus.NEEDS_MORE_ANALYSIS, "Unrecognized charge")
    time_eligibility = TimeEligibility(EligibilityStatus.ELIGIBLE, "Eligible under for some reason", date.today())
    time_eligibility_2 = TimeEligibility(EligibilityStatus.INELIGIBLE, "Ineligible under some statute", date.max())
    charge_eligibility = RecordMerger.compute_charge_eligibility(
        type_eligibility, [time_eligibility, time_eligibility_2]
    )

    assert charge_eligibility.status == ChargeEligibilityStatus.POSSIBLY_ELIGIBILE
    assert charge_eligibility.label == "Possibly Eligible Now"
Exemple #3
0
def test_multiple_will_be_eligible():
    type_eligibility = TypeEligibility(EligibilityStatus.ELIGIBLE, "Eligible under some statute")
    time_eligibility = TimeEligibility(EligibilityStatus.ELIGIBLE, "Eligible Now", Time.THREE_YEARS_AGO)
    time_eligibility_2 = TimeEligibility(
        EligibilityStatus.INELIGIBLE, "Ineligible under some statute", Time.ONE_YEARS_FROM_NOW
    )
    charge_eligibility = RecordMerger.compute_charge_eligibility(
        type_eligibility, [time_eligibility, time_eligibility_2]
    )

    assert charge_eligibility.status == ChargeEligibilityStatus.WILL_BE_ELIGIBLE
    assert charge_eligibility.label == f"Eligible Now or {Time.ONE_YEARS_FROM_NOW.strftime('%b %-d, %Y')}"
Exemple #4
0
def test_possibly_will_be_eligible():
    type_eligibility = TypeEligibility(EligibilityStatus.NEEDS_MORE_ANALYSIS, "Unrecognized charge")
    time_eligibility = TimeEligibility(
        EligibilityStatus.INELIGIBLE, "Ineligible for some reason", Time.ONE_YEARS_FROM_NOW
    )
    time_eligibility_2 = TimeEligibility(EligibilityStatus.INELIGIBLE, "Ineligible under some statute", date.max())
    charge_eligibility = RecordMerger.compute_charge_eligibility(
        type_eligibility, [time_eligibility, time_eligibility_2]
    )

    assert charge_eligibility.status == ChargeEligibilityStatus.POSSIBLY_WILL_BE_ELIGIBLE
    assert charge_eligibility.label == f"Possibly Eligible {Time.ONE_YEARS_FROM_NOW.strftime('%b %-d, %Y')}"
 def merge_time_eligibilities(
     time_eligibilities: Optional[List[TimeEligibility]]
 ) -> Optional[TimeEligibility]:
     if time_eligibilities:
         status = RecordMerger.compute_time_eligibility_status(
             time_eligibilities)
         reasons = [
             time_eligibility.reason
             for time_eligibility in time_eligibilities
         ]
         reason = " OR ".join(list(unique_everseen(reasons)))
         date_will_be_eligible = time_eligibilities[0].date_will_be_eligible
         if len(
                 set([
                     time_eligibility.date_will_be_eligible
                     for time_eligibility in time_eligibilities
                 ])) == 1:
             unique_date = True
         else:
             unique_date = False
         return TimeEligibility(status=status,
                                reason=reason,
                                date_will_be_eligible=date_will_be_eligible,
                                unique_date=unique_date)
     else:
         return None
Exemple #6
0
def test_type_possibly_eligible_never_becomes_eligible():
    type_eligibility = TypeEligibility(EligibilityStatus.NEEDS_MORE_ANALYSIS, "Unrecognized charge")
    time_eligibility = TimeEligibility(EligibilityStatus.INELIGIBLE, "Never eligible under some statute", date.max())
    charge_eligibility = RecordMerger.compute_charge_eligibility(type_eligibility, [time_eligibility])

    assert charge_eligibility.status == ChargeEligibilityStatus.INELIGIBLE
    assert charge_eligibility.label == "Ineligible"
Exemple #7
0
def test_type_eligible_never_becomes_eligible():
    type_eligibility = TypeEligibility(EligibilityStatus.ELIGIBLE, "Eligible under some statute")
    time_eligibility = TimeEligibility(EligibilityStatus.INELIGIBLE, "Never eligible under some statute", date.max())
    charge_eligibility = RecordMerger.compute_charge_eligibility(type_eligibility, [time_eligibility])

    assert charge_eligibility.status == ChargeEligibilityStatus.INELIGIBLE
    assert charge_eligibility.label == "Ineligible"
Exemple #8
0
def test_eligible():
    type_eligibility = TypeEligibility(EligibilityStatus.ELIGIBLE, "Eligible under some statute")
    time_eligibility = TimeEligibility(EligibilityStatus.ELIGIBLE, "Eligible under some statute", date.today())
    charge_eligibility = RecordMerger.compute_charge_eligibility(type_eligibility, [time_eligibility])

    assert charge_eligibility.status == ChargeEligibilityStatus.ELIGIBLE_NOW
    assert charge_eligibility.label == "Eligible Now"
Exemple #9
0
def test_expunger_for_record_with_odd_event_table_contents(
        record_with_odd_event_table_contents):
    expunger_result = Expunger.run(record_with_odd_event_table_contents)
    assert expunger_result == {
        "CASEJD1-1":
        TimeEligibility(
            status=EligibilityStatus.INELIGIBLE,
            reason="Never. Type ineligible charges are always time ineligible.",
            date_will_be_eligible=date.max(),
        ),
        "CASEJD1-2":
        TimeEligibility(
            status=EligibilityStatus.INELIGIBLE,
            reason="Never. Type ineligible charges are always time ineligible.",
            date_will_be_eligible=date.max(),
        ),
    }
Exemple #10
0
def test_expunger_for_record_with_mj_over_21(record_with_mj_over_21):
    expunger_result = Expunger.run(record_with_mj_over_21)
    assert expunger_result == {
        "CASEJD1-1":
        TimeEligibility(status=EligibilityStatus.ELIGIBLE,
                        reason="Eligible now",
                        date_will_be_eligible=date(2001, 3, 3))
    }
def test_expunger_for_record_with_mj_under_21(record_with_mj_under_21):
    assert isinstance(record_with_mj_under_21.charges[0], MarijuanaUnder21)
    expunger_result = Expunger.run(record_with_mj_under_21)
    assert expunger_result == {
        "CASEJD1-1":
        TimeEligibility(status=EligibilityStatus.ELIGIBLE,
                        reason="Eligible now",
                        date_will_be_eligible=date_class(1999, 3, 3))
    }
Exemple #12
0
 def set_time_ineligible(self, reason, date_of_eligibility):
     status = self.expungement_result.type_eligibility.status
     if status == EligibilityStatus.ELIGIBLE or status == EligibilityStatus.NEEDS_MORE_ANALYSIS:
         date_will_be_eligible = date_of_eligibility
     else:
         date_will_be_eligible = None
     time_eligibility = TimeEligibility(
         status=EligibilityStatus.INELIGIBLE,
         reason=reason,
         date_will_be_eligible=date_will_be_eligible)
     self.expungement_result.time_eligibility = time_eligibility
Exemple #13
0
def test_expunger_for_record_with_mj_under_21_not_blocked_by_traffic(
        record_with_mj_under_21_and_traffic_violation):
    expunger_result = Expunger.run(
        record_with_mj_under_21_and_traffic_violation)
    mj_charge = record_with_mj_under_21_and_traffic_violation.charges[0]
    traffic_charge = record_with_mj_under_21_and_traffic_violation.charges[1]

    assert isinstance(mj_charge.charge_type, MarijuanaUnder21)
    assert isinstance(traffic_charge.charge_type, TrafficViolation)

    assert expunger_result["CASEJD1-1"] == TimeEligibility(
        status=EligibilityStatus.ELIGIBLE,
        reason="Eligible now",
        date_will_be_eligible=mj_charge.disposition.date +
        relativedelta(years=1),
    )
    assert expunger_result["CASEJD1-2"] == TimeEligibility(
        status=EligibilityStatus.INELIGIBLE,
        reason="Never. Type ineligible charges are always time ineligible.",
        date_will_be_eligible=date.max(),
    )
Exemple #14
0
def test_expunger_for_record_with_mj_under_21_blocked_by_blocking_conviction():
    record = CrawlerFactory.create(
        record=YoungDoe.SINGLE_CASE_RECORD,
        cases={"CASEJD1": CaseDetails.CASE_MJ_AND_FELONY_CONVICTION})
    expunger_result = Expunger.run(record)

    mj_charge = record.charges[0]
    felony_charge = record.charges[1]

    assert isinstance(mj_charge.charge_type, MarijuanaUnder21)
    assert isinstance(felony_charge.charge_type, FelonyClassA)

    assert expunger_result["CASEJD1-1"] == TimeEligibility(
        status=EligibilityStatus.ELIGIBLE,
        reason="Eligible now",
        date_will_be_eligible=date(1998, 3, 3) + relativedelta(years=10),
    )
    assert expunger_result["CASEJD1-2"] == TimeEligibility(
        status=EligibilityStatus.INELIGIBLE,
        reason="Never. Type ineligible charges are always time ineligible.",
        date_will_be_eligible=date.max(),
    )
Exemple #15
0
def test_expunger_for_record_with_mj_under_21_with_non_traffic_charge():
    record = CrawlerFactory.create(
        record=YoungDoe.SINGLE_CASE_RECORD,
        cases={"CASEJD1": CaseDetails.CASE_MJ_AND_FUGITIVE_CONVICTION})

    expunger_result = Expunger.run(record)
    mj_charge = record.charges[0]

    assert isinstance(mj_charge.charge_type, MarijuanaUnder21)
    assert isinstance(record.charges[1].charge_type, CivilOffense)

    assert expunger_result["CASEJD1-1"] == TimeEligibility(
        status=EligibilityStatus.ELIGIBLE,
        reason="Eligible now",
        date_will_be_eligible=mj_charge.disposition.date +
        relativedelta(years=3),
    )

    assert expunger_result["CASEJD1-2"] == TimeEligibility(
        status=EligibilityStatus.INELIGIBLE,
        reason="Never. Type ineligible charges are always time ineligible.",
        date_will_be_eligible=date.max(),
    )
Exemple #16
0
 def merge_time_eligibilities(
     time_eligibilities: Optional[List[TimeEligibility]]
 ) -> Optional[TimeEligibility]:
     if time_eligibilities:
         status = RecordMerger.compute_time_eligibility_status(
             time_eligibilities)
         reasons = [
             time_eligibility.reason
             for time_eligibility in time_eligibilities
         ]
         reason = " ⬥ ".join(list(unique_everseen(reasons)))
         date_will_be_eligible = time_eligibilities[
             0].date_will_be_eligible  # TODO: Fix
         return TimeEligibility(status=status,
                                reason=reason,
                                date_will_be_eligible=date_will_be_eligible)
     else:
         return None
Exemple #17
0
    def run(record: Record) -> Dict[str, TimeEligibility]:
        """
        Evaluates the expungement eligibility of a record.
        """
        analyzable_record = Expunger._without_skippable_charges(record)
        ambiguous_charge_id_to_time_eligibility = {}
        cases = analyzable_record.cases
        for charge in analyzable_record.charges:
            eligibility_dates: List[Tuple[date, str]] = []

            other_charges = [
                c for c in analyzable_record.charges
                if c.id != charge.id and c.edit_status != EditStatus.DELETE
            ]

            other_blocking_charges = [
                c for c in other_charges if c.charge_type.blocks_other_charges
            ]

            _, convictions = Case.categorize_charges(other_charges)
            blocking_dismissals, blocking_convictions = Case.categorize_charges(
                other_blocking_charges)

            most_recent_blocking_dismissal = Expunger._most_recent_different_case_dismissal(
                charge, blocking_dismissals)
            most_recent_blocking_conviction = Expunger._most_recent_convictions(
                blocking_convictions)

            other_convictions_all_traffic = Expunger._is_other_convictions_all_traffic(
                convictions)

            if charge.convicted():
                if isinstance(
                        charge.charge_type,
                        MarijuanaUnder21) and other_convictions_all_traffic:
                    eligibility_dates.append((
                        charge.disposition.date + relativedelta(years=1),
                        "One year from date of conviction (137.226)",
                    ))
                else:
                    eligibility_dates.append((
                        charge.disposition.date + relativedelta(years=3),
                        "Three years from date of conviction (137.225(1)(a))",
                    ))
            elif charge.dismissed():
                eligibility_dates.append(
                    (charge.date, "Eligible immediately (137.225(1)(b))"))
            else:
                raise ValueError(
                    "Charge should always convicted or dismissed at this point."
                )

            if charge.type_eligibility.status == EligibilityStatus.INELIGIBLE:
                eligibility_dates.append((date.max(
                ), "Never. Type ineligible charges are always time ineligible."
                                          ))

            if charge.disposition.status == DispositionStatus.NO_COMPLAINT:
                eligibility_dates.append((
                    charge.date + relativedelta(years=1),
                    "One year from date of no-complaint arrest (137.225(1)(b))",
                ))

            if charge.convicted() and charge.probation_revoked:
                eligibility_dates.append((
                    charge.probation_revoked + relativedelta(years=10),
                    "Time-ineligible under 137.225(1)(c) (Probation Revoked). Inspect further if the case has multiple convictions on the case.",
                ))

            if most_recent_blocking_conviction:
                conviction_string = "other conviction" if charge.convicted(
                ) else "conviction"
                summary = most_recent_blocking_conviction.case(cases).summary
                potential = "potential " if not summary.closed() else ""
                eligibility_dates.append((
                    most_recent_blocking_conviction.disposition.date +
                    relativedelta(years=10),
                    f"137.225(7)(b) – Ten years from most recent {potential}{conviction_string} from case [{summary.case_number}].",
                ))

            if charge.dismissed() and most_recent_blocking_dismissal:
                eligibility_dates.append((
                    most_recent_blocking_dismissal.date +
                    relativedelta(years=3),
                    "Three years from most recent other arrest (137.225(8)(a))",
                ))

            if charge.convicted() and isinstance(charge.charge_type,
                                                 FelonyClassB):
                if Expunger._calculate_has_subsequent_charge(
                        charge, other_blocking_charges):
                    eligibility_dates.append((
                        date.max(),
                        "Never. Class B felony can have no subsequent arrests or convictions (137.225(5)(a)(A)(ii))",
                    ))
                else:
                    eligibility_dates.append((
                        charge.disposition.date + relativedelta(years=20),
                        "Twenty years from date of class B felony conviction (137.225(5)(a)(A)(i))",
                    ))

            if isinstance(charge.charge_type, MarijuanaViolation):
                date_will_be_eligible = charge.disposition.date
                reason = "Eligible immediately (475B.401)"
            else:
                date_will_be_eligible, reason = max(eligibility_dates)

            if date_will_be_eligible and date.today() >= date_will_be_eligible:
                time_eligibility = TimeEligibility(
                    status=EligibilityStatus.ELIGIBLE,
                    reason="Eligible now",
                    date_will_be_eligible=date_will_be_eligible,
                )
            else:
                time_eligibility = TimeEligibility(
                    status=EligibilityStatus.INELIGIBLE,
                    reason=reason,
                    date_will_be_eligible=date_will_be_eligible)
            ambiguous_charge_id_to_time_eligibility[
                charge.ambiguous_charge_id] = time_eligibility
        for case in cases:
            non_violation_convictions_in_case = []
            violations_in_case = []
            for charge in case.charges:
                if charge.convicted():
                    if "violation" in charge.level.lower():
                        violations_in_case.append(charge)
                    else:
                        non_violation_convictions_in_case.append(charge)
            violations_in_case.sort(key=lambda charge: charge.disposition.date,
                                    reverse=True)
            if len(non_violation_convictions_in_case
                   ) == 1 and len(violations_in_case) <= 1:
                attractor = non_violation_convictions_in_case[0]
            elif len(violations_in_case) == 1:
                attractor = violations_in_case[0]
            elif len(violations_in_case) in [2, 3]:
                attractor = violations_in_case[1]
            else:
                attractor = None

            if attractor:
                for charge in case.charges:
                    if (charge.type_eligibility.status !=
                            EligibilityStatus.INELIGIBLE
                            and charge.dismissed()
                            and ambiguous_charge_id_to_time_eligibility[
                                charge.
                                ambiguous_charge_id].date_will_be_eligible >
                            ambiguous_charge_id_to_time_eligibility[
                                attractor.
                                ambiguous_charge_id].date_will_be_eligible):
                        time_eligibility = TimeEligibility(
                            status=ambiguous_charge_id_to_time_eligibility[
                                attractor.ambiguous_charge_id].status,
                            reason=
                            'Time eligibility of the arrest matches conviction on the same case (the "friendly" rule)',
                            date_will_be_eligible=
                            ambiguous_charge_id_to_time_eligibility[
                                attractor.
                                ambiguous_charge_id].date_will_be_eligible,
                        )
                        ambiguous_charge_id_to_time_eligibility[
                            charge.ambiguous_charge_id] = time_eligibility
        return ambiguous_charge_id_to_time_eligibility
Exemple #18
0
 def set_time_eligible(self, reason=""):
     time_eligibility = TimeEligibility(status=EligibilityStatus.ELIGIBLE,
                                        reason=reason,
                                        date_will_be_eligible=None)
     self.expungement_result.time_eligibility = time_eligibility
Exemple #19
0
    def run(record: Record, today: date = date.today()) -> Dict[str, TimeEligibility]:
        """
        Evaluates the expungement eligibility of a record.
        """
        analyzable_record = Expunger._without_skippable_charges(record)
        ambiguous_charge_id_to_time_eligibility = {}
        cases = analyzable_record.cases
        for charge in analyzable_record.charges:
            eligibility_dates: List[Tuple[date, str]] = []

            other_charges = [
                c for c in analyzable_record.charges if c.id != charge.id and c.edit_status != EditStatus.DELETE
            ]

            other_blocking_charges = [c for c in other_charges if c.charge_type.blocks_other_charges]

            convictions = [c for c in other_charges if c.convicted()]
            blocking_convictions = [c for c in other_blocking_charges if c.convicted()]

            most_recent_blocking_conviction = Expunger._most_recent_convictions(blocking_convictions)

            other_convictions_all_traffic = Expunger._is_other_convictions_all_traffic(convictions)

            if charge.convicted():
                if isinstance(charge.charge_type, MarijuanaUnder21) and other_convictions_all_traffic:
                    eligibility_dates.append(
                        (
                            charge.disposition.date + relativedelta(years=1),
                            "One year from date of conviction (137.226)",
                        )
                    )
                elif isinstance(charge.charge_type, MarijuanaEligible):
                    eligibility_dates.append(
                        (
                            charge.disposition.date + relativedelta(years=5),
                            "Conservatively reclassified as Class C Felony (137.226)",
                        )
                    )
                else:
                    eligibility_dates.append(
                        Expunger._single_conviction_years_by_level(
                            charge.level, charge.disposition.date, charge.charge_type
                        )
                    )
            elif charge.dismissed():
                eligibility_dates.append((charge.date, "Eligible immediately (137.225(1)(d))"))
            else:
                raise ValueError("Charge should always convicted or dismissed at this point.")

            if charge.type_eligibility.status == EligibilityStatus.INELIGIBLE:
                eligibility_dates.append((date.max(), "Never. Type ineligible charges are always time ineligible."))

            if charge.disposition.status == DispositionStatus.NO_COMPLAINT:
                eligibility_dates.append(
                    (
                        charge.disposition.date + relativedelta(days=60),
                        "Sixty days from date of no-complaint disposition (137.225(1)(c))",
                    )
                )

            if charge.convicted() and charge.probation_revoked:
                eligibility_dates.append(
                    (
                        charge.probation_revoked + relativedelta(years=3),
                        "Time-ineligible under 137.225(1)(c) (Probation Revoked). Inspect further if the case has multiple convictions on the case.",
                    )
                )

            if charge.convicted() and most_recent_blocking_conviction:
                relative_case_summary = most_recent_blocking_conviction.case(cases).summary
                blocking_convictions_time_eligibility = Expunger._other_blocking_conviction_years_by_level(
                    charge.level,
                    most_recent_blocking_conviction.disposition.date,
                    relative_case_summary,
                    charge.charge_type,
                )
                eligibility_dates.append(blocking_convictions_time_eligibility)

            if isinstance(charge.charge_type, MarijuanaViolation):
                date_will_be_eligible = charge.disposition.date
                reason = "Eligible immediately (475B.401)"
            else:
                date_will_be_eligible, reason = max(eligibility_dates)

            if date_will_be_eligible and today >= date_will_be_eligible:
                time_eligibility = TimeEligibility(
                    status=EligibilityStatus.ELIGIBLE,
                    reason="Eligible now",
                    date_will_be_eligible=date_will_be_eligible,
                )
            else:
                time_eligibility = TimeEligibility(
                    status=EligibilityStatus.INELIGIBLE, reason=reason, date_will_be_eligible=date_will_be_eligible
                )
            ambiguous_charge_id_to_time_eligibility[charge.ambiguous_charge_id] = time_eligibility
        return ambiguous_charge_id_to_time_eligibility