From 0e7d7e6e5919b101e07cd6d565c9ec5f38ab9e6f Mon Sep 17 00:00:00 2001 From: Nick Schimek Date: Wed, 2 Oct 2019 14:10:53 -0700 Subject: [PATCH] Update recent conviction algorithm (#429) * Refactor MRC's method Refactored so that MRC and 2MRC each have their own method. * Refactor MRC tests * added test_violation_is_not_most_recent, passed with Expunger change * Add recent_convictions list to expunger Done to make it easier to track recent convictions, so we don't have to continually check if a charge is a recent_conviction. Added test_recent_violation_and_nonrecent_misdemeanor to test if expunger is correctly treating nonrecent charges. * Add test for nonrecent misdemeanor and recent violation The test is test_recent_violation_and_nonrecent_misdemeanor. When there is a recent violation, test checks if nonrecent misdemeanor is set as recent charge (it shouldn't be). * Refactor: Extract Time class This is being done to dry up the code since these date constants are used in other classes. * Remove dupliucate date constants * Refactor: Use the Time data class Using the new Time data class. * Refactor test to dry out code * Create run expunger method to dry out test code * Add tests to handle new rule for considering violations A single violation does not count as a recent conviction. It takes two violations to count. See issue #382 for a detailed explanation. * Update most recent convictions algorithm Please see issue #382 for more details. Fixes #382 * Update changelog --- src/backend/expungeservice/ChangeLog | 1 + .../expungeservice/expunger/expunger.py | 43 +- src/backend/tests/expunger/test_expunger.py | 406 ++++++++++++------ .../tests/expunger/test_time_analyzer.py | 22 +- src/backend/tests/utilities/time.py | 24 ++ 5 files changed, 344 insertions(+), 152 deletions(-) create mode 100644 src/backend/tests/utilities/time.py diff --git a/src/backend/expungeservice/ChangeLog b/src/backend/expungeservice/ChangeLog index 71514b724..74c370ebd 100644 --- a/src/backend/expungeservice/ChangeLog +++ b/src/backend/expungeservice/ChangeLog @@ -7,6 +7,7 @@ v0.3.3 - The function to check if a case is closed now returns true when the term `Purgable` is encountered in the status. (386) - Fixed issue where parking tickets that were acquitted were being tagged as type eligible. Parking tickets are never type eligible. (369) - Fixed issue where some parking tickets were being missed due to the difference in reporting statute codes. Parking tickets are now created by case type `Municipal Parking` after statute creation fails. (#427) + - Fixed issue where a single violation would count as a recent conviction. Updated rules for MRC and second mrc to only apply a violation if there is a second violation. (429) ________________________________________________________________________________________________ diff --git a/src/backend/expungeservice/expunger/expunger.py b/src/backend/expungeservice/expunger/expunger.py index 490a83e90..3d0b6637c 100644 --- a/src/backend/expungeservice/expunger/expunger.py +++ b/src/backend/expungeservice/expunger/expunger.py @@ -16,7 +16,7 @@ class Expunger: """ def __init__(self, record): - ''' + """ Constructor most_recent_conviction: Most recent conviction if one exists from within the last ten years second_most_recent_conviction: Second most recent conviction if one exists from within the last ten years @@ -26,7 +26,7 @@ def __init__(self, record): most_recent_charge: The most recent charge within the last 20yrs; excluding traffic violations :param record: A Record object - ''' + """ self.record = record self.charges = record.charges self.errors = [] @@ -37,6 +37,7 @@ def __init__(self, record): self.most_recent_charge = None self.acquittals = [] self.convictions = [] + self._recent_convictions = [] self.class_b_felonies = [] def run(self): @@ -52,8 +53,10 @@ def run(self): self._tag_skipped_charges() self._remove_skipped_charges() self._categorize_charges() + self._extract_most_recent_convictions() self._set_most_recent_dismissal() - self._set_most_recent_convictions() + self._set_recent_conviction() + self._set_second_most_recent_conviction() self._assign_most_recent_charge() self._assign_class_b_felonies() TimeAnalyzer.evaluate(self) @@ -87,17 +90,37 @@ def _categorize_charges(self): else: self.convictions.append(charge) + def _extract_most_recent_convictions(self): + for charge in self.convictions: + if charge.recent_conviction(): + self._recent_convictions.append(charge) + def _set_most_recent_dismissal(self): self.acquittals.sort(key=lambda charge: charge.date) if self.acquittals and self.acquittals[-1].recent_acquittal(): self.most_recent_dismissal = self.acquittals[-1] - def _set_most_recent_convictions(self): - self.convictions.sort(key=lambda charge: charge.disposition.date) - if len(self.convictions) > 0 and self.convictions[-1].recent_conviction(): - self.most_recent_conviction = self.convictions[-1] - if len(self.convictions) > 1 and self.convictions[-2].recent_conviction(): - self.second_most_recent_conviction = self.convictions[-2] + def _set_recent_conviction(self, set_mrc=True): + self._recent_convictions.sort(key=lambda charge: charge.disposition.date) + if len(self._recent_convictions) > 0: + if self._recent_convictions[-1].level != "Violation": + if set_mrc: + self.most_recent_conviction = self._recent_convictions[-1] + else: + self.second_most_recent_conviction = self._recent_convictions[-1] + elif len(self._recent_convictions) > 1: + if set_mrc: + self.most_recent_conviction = self._recent_convictions[-2] + else: + self.second_most_recent_conviction = self._recent_convictions[-2] + + def _set_second_most_recent_conviction(self): + self._remove_mrc_from_recent_convictions() + self._set_recent_conviction(set_mrc=False) + + def _remove_mrc_from_recent_convictions(self): + if self.most_recent_conviction: + self._recent_convictions.remove(self.most_recent_conviction) def _assign_most_recent_charge(self): self.charges.sort(key=lambda charge: charge.disposition.date, reverse=True) @@ -106,5 +129,5 @@ def _assign_most_recent_charge(self): def _assign_class_b_felonies(self): for charge in self.charges: - if charge.__class__.__name__ == 'FelonyClassB': + if charge.__class__.__name__ == "FelonyClassB": self.class_b_felonies.append(charge) diff --git a/src/backend/tests/expunger/test_expunger.py b/src/backend/tests/expunger/test_expunger.py index 092f3b672..2ddbfedb8 100644 --- a/src/backend/tests/expunger/test_expunger.py +++ b/src/backend/tests/expunger/test_expunger.py @@ -1,31 +1,18 @@ import unittest -from datetime import date -from dateutil.relativedelta import relativedelta from expungeservice.expunger.expunger import Expunger from expungeservice.models.disposition import Disposition from expungeservice.models.record import Record from tests.factories.case_factory import CaseFactory from tests.factories.charge_factory import ChargeFactory +from tests.utilities.time import Time class TestExpungementAnalyzerUnitTests(unittest.TestCase): - TEN_YEARS = (date.today() + relativedelta(years=-10)).strftime('%m/%d/%Y') - LESS_THAN_TEN_YEARS_AGO = (date.today() + relativedelta(years=-10, days=+1)).strftime('%m/%d/%Y') - LESS_THAN_THREE_YEARS_AGO = (date.today() + relativedelta(years=-3, days=+1)).strftime('%m/%d/%Y') - THREE_YEARS_AGO = (date.today() + relativedelta(years=-3)).strftime('%m/%d/%Y') - TWO_YEARS_AGO = (date.today() + relativedelta(years=-2)).strftime('%m/%d/%Y') - ONE_YEAR_AGO = (date.today() + relativedelta(years=-1)).strftime('%m/%d/%Y') - - TWO_YEAR_AGO_DATE = (date.today() + relativedelta(years=-2)).strftime('%m/%d/%Y') - ONE_YEAR_AGO_DATE = (date.today() + relativedelta(years=-1)).strftime('%m/%d/%Y') - THREE_YEAR_AGO_DATE = (date.today() + relativedelta(years=-3)).strftime('%m/%d/%Y') - FOUR_YEAR_AGO_DATE = (date.today() + relativedelta(years=-4)).strftime('%m/%d/%Y') - def test_expunger_sets_most_recent_dismissal_when_charge_is_less_than_3yrs(self): case = CaseFactory.create() - mrd_charge = ChargeFactory.create_dismissed_charge(date=self.LESS_THAN_THREE_YEARS_AGO) + mrd_charge = ChargeFactory.create_dismissed_charge(date=Time.LESS_THAN_THREE_YEARS_AGO) case.charges = [mrd_charge] record = Record([case]) @@ -36,8 +23,8 @@ def test_expunger_sets_most_recent_dismissal_when_charge_is_less_than_3yrs(self) def test_expunger_does_not_set_most_recent_dismissal_when_case_is_older_than_3yrs(self): case = CaseFactory.create() - charge_1 = ChargeFactory.create_dismissed_charge(date=self.THREE_YEARS_AGO) - charge_2 = ChargeFactory.create_dismissed_charge(date=self.THREE_YEARS_AGO) + charge_1 = ChargeFactory.create_dismissed_charge(date=Time.THREE_YEARS_AGO) + charge_2 = ChargeFactory.create_dismissed_charge(date=Time.THREE_YEARS_AGO) case.charges = [charge_1, charge_2] record = Record([case]) @@ -48,8 +35,8 @@ def test_expunger_does_not_set_most_recent_dismissal_when_case_is_older_than_3yr def test_expunger_sets_mrd_when_mrd_is_in_middle_of_list(self): case = CaseFactory.create() - mrd_charge = ChargeFactory.create_dismissed_charge(date=self.TWO_YEARS_AGO) - charge = ChargeFactory.create_dismissed_charge(date=self.LESS_THAN_THREE_YEARS_AGO) + mrd_charge = ChargeFactory.create_dismissed_charge(date=Time.TWO_YEARS_AGO) + charge = ChargeFactory.create_dismissed_charge(date=Time.LESS_THAN_THREE_YEARS_AGO) case.charges = [charge, charge, mrd_charge, charge, charge] record = Record([case]) @@ -60,8 +47,8 @@ def test_expunger_sets_mrd_when_mrd_is_in_middle_of_list(self): def test_expunger_sets_mrd_when_mrd_is_at_end_of_list(self): case = CaseFactory.create() - mrd_charge = ChargeFactory.create_dismissed_charge(date=self.TWO_YEARS_AGO) - charge = ChargeFactory.create_dismissed_charge(date=self.LESS_THAN_THREE_YEARS_AGO) + mrd_charge = ChargeFactory.create_dismissed_charge(date=Time.TWO_YEARS_AGO) + charge = ChargeFactory.create_dismissed_charge(date=Time.LESS_THAN_THREE_YEARS_AGO) case.charges = [charge, charge, mrd_charge] record = Record([case]) @@ -70,158 +57,333 @@ def test_expunger_sets_mrd_when_mrd_is_at_end_of_list(self): assert expunger.most_recent_dismissal is mrd_charge - def test_it_sets_most_recent_conviction_from_the_last_10yrs(self): + def test_it_skips_closed_cases_without_dispositions(self): case = CaseFactory.create() - mrc_charge = ChargeFactory.create() - mrc_charge.disposition = Disposition(self.LESS_THAN_TEN_YEARS_AGO, 'Convicted') - case.charges = [mrc_charge] + charge_without_dispo = ChargeFactory.create() + case.charges = [charge_without_dispo] record = Record([case]) - expunger = Expunger(record) - expunger.run() - assert expunger.most_recent_conviction is mrc_charge + assert expunger.run() - def test_it_sets_the_second_most_recent_conviction_within_the_last_10yrs(self): - case = CaseFactory.create() - mrc_charge = ChargeFactory.create() - mrc_charge.disposition = Disposition(self.TWO_YEARS_AGO, 'Convicted') - second_mrc_charge = ChargeFactory.create() - second_mrc_charge.disposition = Disposition(self.LESS_THAN_TEN_YEARS_AGO, 'Convicted') + def test_it_skips_juvenile_charges(self): + case = CaseFactory.create(type_status=['a juvenile case', 'Closed']) + juvenile_charge = ChargeFactory.create(case=case) + case.charges = [juvenile_charge] - case.charges = [second_mrc_charge, mrc_charge] record = Record([case]) - expunger = Expunger(record) - expunger.run() - assert expunger.second_most_recent_conviction is second_mrc_charge + assert expunger.run() + assert expunger._skipped_charges[0] == juvenile_charge + assert expunger.charges == [] - def test_it_does_not_set_mrc_when_greater_than_10yrs(self): + +class TestDispositionlessCharge(unittest.TestCase): + + def test_charge_is_marked_as_missing_disposition(self): case = CaseFactory.create() - mrc_charge = ChargeFactory.create() - mrc_charge.disposition = Disposition(self.TEN_YEARS, 'Convicted') - case.charges = [mrc_charge] - record = Record([case]) + charge = ChargeFactory.create(case=case, statute='825.999', level='Class C traffic violation') + + case.charges = [charge] + expunger = Expunger(Record([case])) - expunger = Expunger(record) expunger.run() - assert expunger.most_recent_conviction is None + assert charge.expungement_result.type_eligibility_reason == "Disposition not found. Needs further analysis" - def test_it_does_not_set_2nd_mrc_when_greater_than_10yrs(self): - case = CaseFactory.create() - mrc_charge = ChargeFactory.create() - mrc_charge.disposition = Disposition(self.LESS_THAN_TEN_YEARS_AGO, 'Convicted') - second_mrc_charge = ChargeFactory.create() - second_mrc_charge.disposition = Disposition(self.TEN_YEARS, 'Convicted') - case.charges = [mrc_charge, second_mrc_charge] - record = Record([case]) - expunger = Expunger(record) - expunger.run() +class TestMostRecentConviction(unittest.TestCase): - assert expunger.second_most_recent_conviction is None + def setUp(self): + self.case = CaseFactory.create() + self.mrc_charge = ChargeFactory.create() + self.viol_charge = ChargeFactory.create(level='Violation') + self.viol2_charge = ChargeFactory.create(level='Violation') - def test_mrc_and_second_mrc(self): - case = CaseFactory.create() - mrc_charge = ChargeFactory.create() - mrc_charge.disposition = Disposition(self.TWO_YEARS_AGO, 'Convicted') - second_mrc_charge = ChargeFactory.create() - second_mrc_charge.disposition = Disposition(self.LESS_THAN_TEN_YEARS_AGO, 'Convicted') - charge = ChargeFactory.create() - charge.disposition = Disposition(self.TEN_YEARS, 'Convicted') - case.charges = [charge, charge, second_mrc_charge, mrc_charge, charge, charge] - record = Record([case]) + def run_expunger(self, cases): + record = Record(cases) + self.expunger = Expunger(record) + self.expunger.run() - expunger = Expunger(record) - expunger.run() + def test_it_sets_most_recent_conviction_from_the_last_10yrs(self): + self.mrc_charge.disposition = Disposition(Time.LESS_THAN_TEN_YEARS_AGO, 'Convicted') + self.case.charges = [self.mrc_charge] + self.run_expunger([self.case]) + + assert self.expunger.most_recent_conviction is self.mrc_charge + + def test_it_does_not_set_mrc_when_greater_than_10yrs(self): + self.mrc_charge.disposition = Disposition(Time.TEN_YEARS_AGO, 'Convicted') + self.case.charges = [self.mrc_charge] + self.run_expunger([self.case]) - assert expunger.most_recent_conviction is mrc_charge - assert expunger.second_most_recent_conviction is second_mrc_charge + assert self.expunger.most_recent_conviction is None + + def test_mrc_grouped_within_other_charges(self): + self.mrc_charge.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + charge = ChargeFactory.create() + charge.disposition = Disposition(Time.TEN_YEARS_AGO, 'Convicted') + self.case.charges = [charge, charge, self.mrc_charge, charge, charge] + self.run_expunger([self.case]) + + assert self.expunger.most_recent_conviction is self.mrc_charge def test_most_recent_charge(self): - case = CaseFactory.create() one_year_traffic_charge = ChargeFactory.create(name='Traffic Violation', statute='825.999', level='Class C traffic violation', - disposition=['Convicted', self.ONE_YEAR_AGO_DATE]) + disposition=['Convicted', Time.ONE_YEAR_AGO]) - case.charges = [one_year_traffic_charge] - record = Record([case]) + self.case.charges = [one_year_traffic_charge] + self.run_expunger([self.case]) - expunger = Expunger(record) - expunger.run() - - assert expunger.most_recent_charge is None + assert self.expunger.most_recent_charge is None def test_most_recent_charge_with_non_traffic_violations(self): - case = CaseFactory.create() one_year_traffic_charge = ChargeFactory.create(name='Traffic Violation', statute='825.999', level='Class C traffic violation', - disposition=['Convicted', self.ONE_YEAR_AGO_DATE]) - two_year_ago_dismissal = ChargeFactory.create(disposition=['Dismissed', self.TWO_YEAR_AGO_DATE]) - three_year_ago_dismissal = ChargeFactory.create(disposition=['Dismissed', self.THREE_YEAR_AGO_DATE]) + disposition=['Convicted', Time.ONE_YEAR_AGO]) + two_year_ago_dismissal = ChargeFactory.create(disposition=['Dismissed', Time.TWO_YEARS_AGO]) + three_year_ago_dismissal = ChargeFactory.create(disposition=['Dismissed', Time.THREE_YEARS_AGO]) four_year_traffic_charge = ChargeFactory.create(name='Traffic Violation', statute='825.999', level='Class C traffic violation', - disposition=['Convicted', self.FOUR_YEAR_AGO_DATE]) + disposition=['Convicted', Time.FOUR_YEARS_AGO]) - case.charges = [one_year_traffic_charge, two_year_ago_dismissal, three_year_ago_dismissal, four_year_traffic_charge] - record = Record([case]) + self.case.charges = [one_year_traffic_charge, two_year_ago_dismissal, three_year_ago_dismissal, four_year_traffic_charge] + self.run_expunger([self.case]) - expunger = Expunger(record) - expunger.run() - - assert expunger.most_recent_charge == two_year_ago_dismissal + assert self.expunger.most_recent_charge == two_year_ago_dismissal def test_parking_ticket_is_not_recent_charge(self): - case = CaseFactory.create() parking_ticket = ChargeFactory.create(statute='40', - disposition=['Convicted', self.ONE_YEAR_AGO_DATE]) + disposition=['Convicted', Time.ONE_YEAR_AGO]) - case.charges = [parking_ticket] - record = Record([case]) + self.case.charges = [parking_ticket] + self.run_expunger([self.case]) - expunger = Expunger(record) - expunger.run() + assert self.expunger.most_recent_charge is None - assert expunger.most_recent_charge is None + def test_violation_is_not_most_recent(self): + mrc_charge = ChargeFactory.create(level='Violation') + self.mrc_charge.disposition = Disposition(Time.LESS_THAN_TEN_YEARS_AGO, 'Convicted') + self.case.charges = [mrc_charge] + self.run_expunger([self.case]) - def test_it_skips_closed_cases_without_dispositions(self): - case = CaseFactory.create() - charge_without_dispo = ChargeFactory.create() - case.charges = [charge_without_dispo] - record = Record([case]) - expunger = Expunger(record) + assert self.expunger.most_recent_conviction is None - assert expunger.run() + def test_violation_and_misdemeanor_most_recent(self): + viol_case = CaseFactory.create() - def test_it_skips_juvenile_charges(self): - case = CaseFactory.create(type_status=['a juvenile case', 'Closed']) - juvenile_charge = ChargeFactory.create(case=case) - case.charges = [juvenile_charge] + self.viol_charge.disposition = Disposition(Time.ONE_YEAR_AGO, 'Convicted') + self.mrc_charge.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') - record = Record([case]) - expunger = Expunger(record) + viol_case.charges = [self.viol_charge] + self.case.charges = [self.mrc_charge] - assert expunger.run() - assert expunger._skipped_charges[0] == juvenile_charge - assert expunger.charges == [] + self.run_expunger([viol_case, self.case]) + assert self.expunger.most_recent_conviction is self.mrc_charge -class TestDispositionlessCharge(unittest.TestCase): + def test_recent_violation_and_nonrecent_misdemeanor(self): + viol_case = CaseFactory.create() + misd_case = CaseFactory.create() - def test_charge_is_marked_as_missing_disposition(self): - case = CaseFactory.create() - charge = ChargeFactory.create(case=case, statute='825.999', level='Class C traffic violation') - print(charge.skip_analysis()) - print(charge.expungement_result.type_eligibility_reason) + misd_charge = ChargeFactory.create() - case.charges = [charge] - expunger = Expunger(Record([case])) + self.viol_charge.disposition = Disposition(Time.ONE_YEAR_AGO, 'Convicted') + misd_charge.disposition = Disposition(Time.TEN_YEARS_AGO, 'Convicted') - expunger.run() + viol_case.charges = [self.viol_charge] + misd_case.charges = [misd_charge] - assert charge.expungement_result.type_eligibility_reason == "Disposition not found. Needs further analysis" + self.run_expunger([viol_case, misd_case]) + + assert self.expunger.most_recent_conviction is None + + def test_two_most_recent_are_violations(self): + self.viol_charge.disposition = Disposition(Time.ONE_YEAR_AGO, 'Convicted') + self.viol2_charge.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + + self.case.charges = [self.viol_charge, self.viol2_charge] + + self.run_expunger([self.case]) + + assert self.expunger.most_recent_conviction is self.viol2_charge + + def test_mrc_with_two_violations(self): + self.viol_charge.disposition = Disposition(Time.YESTERDAY, 'Convicted') + self.viol2_charge.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + self.mrc_charge.disposition = Disposition(Time.ONE_YEAR_AGO, 'Convicted') + + case_two = CaseFactory.create() + case_three = CaseFactory.create() + + self.case.charges = [self.viol_charge] + case_two.charges = [self.mrc_charge] + case_three.charges = [self.viol2_charge] + + self.run_expunger([self.case, case_two, case_three]) + + assert self.expunger.most_recent_conviction is self.mrc_charge + + def test_second_violation_is_mrc(self): + self.viol_charge.disposition = Disposition(Time.YESTERDAY, 'Convicted') + self.viol2_charge.disposition = Disposition(Time.ONE_YEAR_AGO, 'Convicted') + self.mrc_charge.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + + case_two = CaseFactory.create() + case_three = CaseFactory.create() + + self.case.charges = [self.viol_charge] + case_two.charges = [self.mrc_charge] + case_three.charges = [self.viol2_charge] + + self.run_expunger([self.case, case_two, case_three]) + + assert self.expunger.most_recent_conviction is self.viol2_charge + + def test_misdemeanor_is_mrc_with_two_violations_dispod_after(self): + self.mrc_charge.disposition = Disposition(Time.YESTERDAY, 'Convicted') + self.viol_charge.disposition = Disposition(Time.ONE_YEAR_AGO, 'Convicted') + self.viol2_charge.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + + case_two = CaseFactory.create() + case_three = CaseFactory.create() + + self.case.charges = [self.viol_charge] + case_two.charges = [self.mrc_charge] + case_three.charges = [self.viol2_charge] + + self.run_expunger([self.case, case_two, case_three]) + + assert self.expunger.most_recent_conviction is self.mrc_charge + + def test_three_violations_second_violation_is_mrc(self): + first_violation = ChargeFactory.create(level='Violation') + self.viol2_charge.disposition = Disposition(Time.YESTERDAY, 'Convicted') + self.viol_charge.disposition = Disposition(Time.ONE_YEAR_AGO, 'Convicted') + first_violation.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + + case_two = CaseFactory.create() + case_three = CaseFactory.create() + + self.case.charges = [self.viol_charge] + case_two.charges = [first_violation] + case_three.charges = [self.viol2_charge] + + self.run_expunger([self.case, case_two, case_three]) + + assert self.expunger.most_recent_conviction is self.viol_charge + + +class TestSecondMostRecentConviction(unittest.TestCase): + + def setUp(self): + self.case = CaseFactory.create() + self.mrc_charge = ChargeFactory.create() + self.viol_charge = ChargeFactory.create(level='Violation') + self.viol2_charge = ChargeFactory.create(level='Violation') + + def run_expunger(self, cases): + record = Record(cases) + self.expunger = Expunger(record) + self.expunger.run() + + def test_it_does_not_set_2nd_mrc_when_greater_than_10yrs(self): + + self.mrc_charge.disposition = Disposition(Time.LESS_THAN_TEN_YEARS_AGO, 'Convicted') + second_mrc_charge = ChargeFactory.create() + second_mrc_charge.disposition = Disposition(Time.TEN_YEARS_AGO, 'Convicted') + self.case.charges = [self.mrc_charge, second_mrc_charge] + self.run_expunger([self.case]) + + assert self.expunger.second_most_recent_conviction is None + + def test_it_sets_the_second_most_recent_conviction_within_the_last_10yrs(self): + self.mrc_charge.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + second_mrc_charge = ChargeFactory.create() + second_mrc_charge.disposition = Disposition(Time.LESS_THAN_TEN_YEARS_AGO, 'Convicted') + + self.case.charges = [second_mrc_charge, self.mrc_charge] + self.run_expunger([self.case]) + + assert self.expunger.second_most_recent_conviction is second_mrc_charge + + def test_second_mrc_with_other_charges(self): + self.mrc_charge.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + second_mrc_charge = ChargeFactory.create() + second_mrc_charge.disposition = Disposition(Time.LESS_THAN_TEN_YEARS_AGO, 'Convicted') + charge = ChargeFactory.create() + charge.disposition = Disposition(Time.TEN_YEARS_AGO, 'Convicted') + self.case.charges = [charge, charge, second_mrc_charge, self.mrc_charge, charge, charge] + self.run_expunger([self.case]) + + assert self.expunger.second_most_recent_conviction is second_mrc_charge + + def test_second_mrc_with_two_violations(self): + self.viol_charge.disposition = Disposition(Time.YESTERDAY, 'Convicted') + self.viol2_charge.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + self.mrc_charge.disposition = Disposition(Time.ONE_YEAR_AGO, 'Convicted') + + case_two = CaseFactory.create() + case_three = CaseFactory.create() + + self.case.charges = [self.viol_charge] + case_two.charges = [self.mrc_charge] + case_three.charges = [self.viol2_charge] + + self.run_expunger([self.case, case_two, case_three]) + + assert self.expunger.second_most_recent_conviction is self.viol2_charge + + def test_misdemeanor_is_second_mrc(self): + self.viol_charge.disposition = Disposition(Time.YESTERDAY, 'Convicted') + self.viol2_charge.disposition = Disposition(Time.ONE_YEAR_AGO, 'Convicted') + self.mrc_charge.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + + case_two = CaseFactory.create() + case_three = CaseFactory.create() + + self.case.charges = [self.viol_charge] + case_two.charges = [self.mrc_charge] + case_three.charges = [self.viol2_charge] + + self.run_expunger([self.case, case_two, case_three]) + + assert self.expunger.second_most_recent_conviction is self.mrc_charge + + def test_violation_is_2mrc(self): + self.mrc_charge.disposition = Disposition(Time.YESTERDAY, 'Convicted') + self.viol_charge.disposition = Disposition(Time.ONE_YEAR_AGO, 'Convicted') + self.viol2_charge.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + + case_two = CaseFactory.create() + case_three = CaseFactory.create() + + self.case.charges = [self.viol_charge] + case_two.charges = [self.mrc_charge] + case_three.charges = [self.viol2_charge] + + self.run_expunger([self.case, case_two, case_three]) + + assert self.expunger.second_most_recent_conviction is self.viol2_charge + + def test_three_violations_with_oldest_violation_is_2mrc(self): + first_violation = ChargeFactory.create(level='Violation') + self.viol2_charge.disposition = Disposition(Time.YESTERDAY, 'Convicted') + self.viol_charge.disposition = Disposition(Time.ONE_YEAR_AGO, 'Convicted') + first_violation.disposition = Disposition(Time.TWO_YEARS_AGO, 'Convicted') + + case_two = CaseFactory.create() + case_three = CaseFactory.create() + + self.case.charges = [self.viol_charge] + case_two.charges = [first_violation] + case_three.charges = [self.viol2_charge] + + self.run_expunger([self.case, case_two, case_three]) + + assert self.expunger.second_most_recent_conviction is first_violation diff --git a/src/backend/tests/expunger/test_time_analyzer.py b/src/backend/tests/expunger/test_time_analyzer.py index e7c31e24f..23637f9cc 100644 --- a/src/backend/tests/expunger/test_time_analyzer.py +++ b/src/backend/tests/expunger/test_time_analyzer.py @@ -8,25 +8,7 @@ from tests.factories.case_factory import CaseFactory from tests.factories.charge_factory import ChargeFactory from tests.factories.expunger_factory import ExpungerFactory - - -class Time: - - TWENTY_YEARS_AGO = (date.today() + relativedelta(years=-20)).strftime('%m/%d/%Y') - LESS_THAN_TWENTY_YEARS_AGO = (date.today() + relativedelta(years=-20, days=+1)).strftime('%m/%d/%Y') - TEN_YEARS_AGO = (date.today() + relativedelta(years=-10)).strftime('%m/%d/%Y') - NINE_YEARS_AGO = (date.today() + relativedelta(years=-9)).strftime('%m/%d/%Y') - SEVEN_YEARS_AGO = (date.today() + relativedelta(years=-7)).strftime('%m/%d/%Y') - FIVE_YEARS_AGO = (date.today() + relativedelta(years=-5)).strftime('%m/%d/%Y') - LESS_THAN_THREE_YEARS_AGO = (date.today() + relativedelta(years=-3, days=+1)).strftime('%m/%d/%Y') - THREE_YEARS_AGO = (date.today() + relativedelta(years=-3)).strftime('%m/%d/%Y') - TWO_YEARS_AGO = (date.today() + relativedelta(years=-2)).strftime('%m/%d/%Y') - ONE_YEAR_AGO = (date.today() + relativedelta(years=-1)).strftime('%m/%d/%Y') - TOMORROW = date.today() + relativedelta(days=+1) - - ONE_YEARS_FROM_NOW = date.today() + relativedelta(years=+1) - THREE_YEARS = relativedelta(years=3) - TEN_YEARS = relativedelta(years=10) +from tests.utilities.time import Time class TestSingleChargeAcquittals(unittest.TestCase): @@ -170,7 +152,7 @@ def test_all_mrd_case_related_dismissals_are_expungeable(self): def test_mrd_blocks_dismissals_in_unrelated_cases(self): unrelated_dismissal = ChargeFactory.create_dismissed_charge(case=self.case_2, date=Time.TEN_YEARS_AGO) self.case_2.charges = [unrelated_dismissal] - + record = Record([self.case_1, self.case_2]) expunger = Expunger(record) expunger.run() diff --git a/src/backend/tests/utilities/time.py b/src/backend/tests/utilities/time.py new file mode 100644 index 000000000..6d369fef6 --- /dev/null +++ b/src/backend/tests/utilities/time.py @@ -0,0 +1,24 @@ +from datetime import date +from dateutil.relativedelta import relativedelta + + +class Time: + + TWENTY_YEARS_AGO = (date.today() + relativedelta(years=-20)).strftime('%m/%d/%Y') + LESS_THAN_TWENTY_YEARS_AGO = (date.today() + relativedelta(years=-20, days=+1)).strftime('%m/%d/%Y') + TEN_YEARS_AGO = (date.today() + relativedelta(years=-10)).strftime('%m/%d/%Y') + LESS_THAN_TEN_YEARS_AGO = (date.today() + relativedelta(years=-10, days=+1)).strftime('%m/%d/%Y') + NINE_YEARS_AGO = (date.today() + relativedelta(years=-9)).strftime('%m/%d/%Y') + SEVEN_YEARS_AGO = (date.today() + relativedelta(years=-7)).strftime('%m/%d/%Y') + FIVE_YEARS_AGO = (date.today() + relativedelta(years=-5)).strftime('%m/%d/%Y') + FOUR_YEARS_AGO = (date.today() + relativedelta(years=-4)).strftime('%m/%d/%Y') + LESS_THAN_THREE_YEARS_AGO = (date.today() + relativedelta(years=-3, days=+1)).strftime('%m/%d/%Y') + THREE_YEARS_AGO = (date.today() + relativedelta(years=-3)).strftime('%m/%d/%Y') + TWO_YEARS_AGO = (date.today() + relativedelta(years=-2)).strftime('%m/%d/%Y') + ONE_YEAR_AGO = (date.today() + relativedelta(years=-1)).strftime('%m/%d/%Y') + YESTERDAY = (date.today() + relativedelta(days=-1)).strftime('%m/%d/%Y') + + TOMORROW = date.today() + relativedelta(days=+1) + ONE_YEARS_FROM_NOW = date.today() + relativedelta(years=+1) + THREE_YEARS = relativedelta(years=3) + TEN_YEARS = relativedelta(years=10)