Skip to content

Commit

Permalink
Assault convictions now provide warning
Browse files Browse the repository at this point in the history
  • Loading branch information
georgehelman committed Nov 16, 2024
1 parent c65fbe6 commit 7ec4f02
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 10 deletions.
98 changes: 96 additions & 2 deletions dear_petition/petition/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from dateutil.relativedelta import relativedelta

from dear_petition.users.models import User
from dear_petition.petition.models import (
Expand All @@ -15,7 +16,14 @@
Petition,
PetitionDocument,
)
from dear_petition.petition.constants import ATTACHMENT, DISMISSED, UNDERAGED_CONVICTIONS
from dear_petition.petition.constants import (
ATTACHMENT,
DISMISSED,
UNDERAGED_CONVICTIONS,
NOT_GUILTY,
ADULT_FELONIES,
ADULT_MISDEMEANORS,
)

from .fields import ValidationField

Expand Down Expand Up @@ -92,6 +100,91 @@ class Meta:
]


class DismissedOffenseRecordSerializer(OffenseRecordSerializer):
warnings = serializers.SerializerMethodField()

def get_warnings(self, offense_record):
warnings = []
dob = self.get_dob(offense_record)
if dob:
eighteenth_birthday = dob + relativedelta(years=18)
if offense_record.offense.ciprs_record.offense_date.date() < eighteenth_birthday:
warnings.append("This offense may be a candidate for the AOC-CR-293 petition form")
return warnings

class Meta:
model = OffenseRecord
fields = OffenseRecordSerializer.Meta.fields + ["warnings"]


class NotGuiltyOffenseRecordSerializer(OffenseRecordSerializer):
warnings = serializers.SerializerMethodField()

def get_warnings(self, offense_record):
warnings = []
dob = self.get_dob(offense_record)
if dob:
eighteenth_birthday = dob + relativedelta(years=18)
if offense_record.offense.ciprs_record.offense_date.date() < eighteenth_birthday:
warnings.append("This offense may be a candidate for the AOC-CR-293 petition form")
return warnings

class Meta:
model = OffenseRecord
fields = OffenseRecordSerializer.Meta.fields + ["warnings"]


class UnderagedConvictionOffenseRecordSerializer(OffenseRecordSerializer):
warnings = serializers.SerializerMethodField()

def get_warnings(self, offense_record):
warnings = []
if "assault" in offense_record.description.lower():
warnings.append("This is an assault conviction")
return warnings

class Meta:
model = OffenseRecord
fields = OffenseRecordSerializer.Meta.fields + ["warnings"]


class AdultFelonyOffenseRecordSerializer(OffenseRecordSerializer):
warnings = serializers.SerializerMethodField()

def get_warnings(self, offense_record):
warnings = []
if "assault" in offense_record.description.lower():
warnings.append("This is an assault conviction")
return warnings

class Meta:
model = OffenseRecord
fields = OffenseRecordSerializer.Meta.fields + ["warnings"]


class AdultMisdemeanorOffenseRecordSerializer(OffenseRecordSerializer):
warnings = serializers.SerializerMethodField()

def get_warnings(self, offense_record):
warnings = []
if "assault" in offense_record.description.lower():
warnings.append("This is an assault conviction")
return warnings

class Meta:
model = OffenseRecord
fields = OffenseRecordSerializer.Meta.fields + ["warnings"]


offense_record_serializer_map = {
DISMISSED: DismissedOffenseRecordSerializer,
NOT_GUILTY: NotGuiltyOffenseRecordSerializer,
UNDERAGED_CONVICTIONS: UnderagedConvictionOffenseRecordSerializer,
ADULT_FELONIES: AdultFelonyOffenseRecordSerializer,
ADULT_MISDEMEANORS: AdultMisdemeanorOffenseRecordSerializer,
}


class OffenseSerializer(serializers.ModelSerializer):
offense_records = OffenseRecordSerializer(many=True, read_only=True)

Expand Down Expand Up @@ -297,7 +390,8 @@ def get_attachments(self, instance):

def get_offense_records(self, petition):
offense_records = petition.offense_records.all()
return OffenseRecordSerializer(offense_records, many=True).data
Serializer = offense_record_serializer_map[petition.form_type]
return Serializer(offense_records, many=True).data

def get_active_records(self, petition):
return petition.offense_records.filter(petitionoffenserecord__active=True).values_list(
Expand Down
76 changes: 74 additions & 2 deletions dear_petition/petition/api/tests/test_serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import pytest
from datetime import timedelta, datetime

from dear_petition.petition.api.serializers import OffenseRecordSerializer
import pytest
from dear_petition.petition.api.serializers import (
AdultFelonyOffenseRecordSerializer,
AdultMisdemeanorOffenseRecordSerializer,
DismissedOffenseRecordSerializer,
NotGuiltyOffenseRecordSerializer,
OffenseRecordSerializer,
UnderagedConvictionOffenseRecordSerializer,
)
from dear_petition.petition.tests.factories import OffenseRecordFactory
import dear_petition.petition.constants as pc


@pytest.mark.django_db
Expand All @@ -15,3 +24,66 @@ def test_offense_date_none(self):
record = OffenseRecordFactory(offense__ciprs_record__offense_date=None)
serializer = OffenseRecordSerializer(record)
assert serializer.data["offense_date"] is None

def test_dismissed_record_underaged_warning(self, charged_dismissed_record):
charged_dismissed_record.offense.ciprs_record.dob = (
charged_dismissed_record.offense.ciprs_record.offense_date.date()
- timedelta(days=365 * 16)
)
charged_dismissed_record.offense.ciprs_record.save()

serializer = DismissedOffenseRecordSerializer(charged_dismissed_record)
assert serializer.data["warnings"] == [
"This offense may be a candidate for the AOC-CR-293 petition form"
]

def test_not_guilty_underaged_warning(self, charged_not_guilty_record):
charged_not_guilty_record.offense.ciprs_record.dob = (
charged_not_guilty_record.offense.ciprs_record.offense_date.date()
- timedelta(days=365 * 16)
)
charged_not_guilty_record.offense.ciprs_record.save()

serializer = NotGuiltyOffenseRecordSerializer(charged_not_guilty_record)
assert serializer.data["warnings"] == [
"This offense may be a candidate for the AOC-CR-293 petition form"
]

def test_underaged_conviction_assault_warning(self, record1, non_dismissed_offense):
record1.dob = datetime(2000, 1, 2)
record1.offense_date = datetime(2018, 1, 1)
record1.save()

offense_record = OffenseRecordFactory(
action="CONVICTED", description="Assault", offense=non_dismissed_offense
)
serializer = UnderagedConvictionOffenseRecordSerializer(offense_record)
assert serializer.data["warnings"] == ["This is an assault conviction"]

def test_adult_felony_assault_warning(self, record1, non_dismissed_offense):
record1.dob = datetime(2000, 1, 2)
record1.offense_date = datetime(2019, 1, 1)
record1.save()

offense_record = OffenseRecordFactory(
action="CONVICTED",
description="Assault",
severity=pc.SEVERITY_FELONY,
offense=non_dismissed_offense,
)
serializer = AdultFelonyOffenseRecordSerializer(offense_record)
assert serializer.data["warnings"] == ["This is an assault conviction"]

def test_adult_misdemeanor_assault_warning(self, record1, non_dismissed_offense):
record1.dob = datetime(2000, 1, 2)
record1.offense_date = datetime(2019, 1, 1)
record1.save()

offense_record = OffenseRecordFactory(
action="CONVICTED",
description="Assault",
severity=pc.SEVERITY_MISDEMEANOR,
offense=non_dismissed_offense,
)
serializer = AdultFelonyOffenseRecordSerializer(offense_record)
assert serializer.data["warnings"] == ["This is an assault conviction"]
11 changes: 5 additions & 6 deletions src/features/OffenseTable/OffenseTable.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronRight, faChevronDown, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { formatDistance, isBefore, isValid } from 'date-fns';
import { formatDistance, isValid } from 'date-fns';
import { TableBody, TableCell, TableHeader, TableRow, TableStyle } from '../../components/elements/Table';
import { Tooltip } from '../../components/elements/Tooltip/Tooltip';

Expand All @@ -21,11 +21,9 @@ const toNormalCaseEachWord = (str) =>
.reduce((acc, s) => `${acc} ${s}`);
const toNormalCase = (str) => `${str.charAt(0).toUpperCase()}${str.slice(1).toLowerCase()}`;

function OffenseRow({ offenseRecord, selected, onSelect, dob }) {
function OffenseRow({ offenseRecord, selected, onSelect, dob, warnings }) {
const [showDetails, setShowDetails] = useState(false);

const dateAt18YearsOld = isValid(dob) && new Date(dob.getFullYear() + 18, dob.getMonth() + dob.getDay());

return (
<TableRow key={offenseRecord.pk}>
<TableCell>
Expand All @@ -36,8 +34,8 @@ function OffenseRow({ offenseRecord, selected, onSelect, dob }) {
<TableCell tooltip={offenseRecord.action}>{toNormalCaseEachWord(offenseRecord.action)}</TableCell>
<TableCell tooltip={offenseRecord.severity}>{toNormalCaseEachWord(offenseRecord.severity)}</TableCell>
<TableCell>
{isValid(dob) && isBefore(new Date(offenseRecord.offense_date), dateAt18YearsOld) && (
<Tooltip tooltipContent="This offense may be a candidate for the AOC-CR-293 petition form" offset={[0, 10]}>
{warnings.length > 0 && (
<Tooltip tooltipContent={warnings.join('\n')} offset={[0, 10]}>
<FontAwesomeIcon className="text-xl text-red-600" icon={faExclamationTriangle} />
</Tooltip>
)}
Expand Down Expand Up @@ -95,6 +93,7 @@ function OffenseTable({ offenseRecords, selectedRows, onSelect, dob }) {
offenseRecord={offenseRecord}
onSelect={() => onSelect(offenseRecord.pk)}
dob={dob}
warnings={offenseRecord.warnings}
/>
))}
</TableBody>
Expand Down

0 comments on commit 7ec4f02

Please sign in to comment.