Пример #1
0
def summary(pdf_summary: str, tempdir: str, redis_collect: str) -> None:
    """
    Analyze a single summary sheet for all sealings and expungements.
    """

    rec = CRecord()
    if pdf_summary is not None:
        rec.add_summary(parse_pdf_summary(pdf_summary, tempdir = tempdir))

    if redis_collect is not None:
        try:
            redis_options = redis_collect.split(":")
            rh = RedisHelper(host=redis_options[0], port=redis_options[1],
                             db=redis_options[2],env=redis_options[3])
            rh.sadd_crecord(rec)
        except Exception as e:
            logging.error("You supplied --redis-collect, but collection failed.")

    analysis = (
        Analysis(rec)
        .rule(expunge_deceased)
        .rule(expunge_over_70)
        .rule(expunge_nonconvictions)
        .rule(expunge_summary_convictions)
        .rule(seal_convictions)
    )

    print(json.dumps(analysis, indent=4, default=to_serializable)) #cls=DataClassJSONEncoder))
Пример #2
0
def integrate_dockets(
    crecord: CRecord,
    docket_source_records: List[SourceRecord],
    nonfatal_errors: List[str],
) -> Tuple[CRecord, List[str]]:
    """ Combine a set of source records representing 'dockets' with a criminal record"""
    for docket_source_record in docket_source_records:
        try:
            # get a RecordLib SourceRecord from the webapp sourcerecord model. The RecordLib SourceRecord has the machinery for
            # parsing the record to get a Person and Cases out of it.
            rlsource = RLSourceRecord(
                docket_source_record.file.path,
                parser=docket_source_record.get_parser(),
            )
            # If we reach this line, the parse succeeded.
            docket_source_record.parse_status = SourceRecord.ParseStatuses.SUCCESS
            # Integrate this docket with the full crecord.
            crecord.add_sourcerecord(
                rlsource,
                case_merge_strategy="overwrite_old",
                override_person=True,
                docket_number=docket_source_record.docket_num,
            )
        except Exception:
            docket_source_record.parse_status = SourceRecord.ParseStatuses.FAILURE
            nonfatal_errors.append(
                f"Could not parse {docket_source_record.docket_num} ({docket_source_record.record_type})"
            )
        finally:
            docket_source_record.save()
    return crecord, nonfatal_errors
Пример #3
0
def test_add_summary_to_crecord():
    summary = parse_pdf(pdf="tests/data/CourtSummaryReport.pdf",
                        tempdir="tests/data/tmp")
    rec = CRecord(Person("John", "Smith", date(1998, 1, 1)))
    rec.add_summary(summary, override_person=True)
    assert len(rec.person.first_name) > 0
    assert rec.person.first_name != "John"
Пример #4
0
def expunge_over_70(crecord: CRecord, analysis: dict) -> Tuple[CRecord, dict]:
    """
    Analyze a crecord for expungements if the defendant is over 70.

    18 Pa.C.S. 9122(b)(1) provides for expungements of an individual who
    is 70 or older, and has been free of arrest or prosecution for 10
    years following the final release from confinement or supervision.
    """
    conditions = {
        "age_over_70":
        crecord.person.age() > 70,
        "years_since_last_arrested_or_prosecuted":
        crecord.years_since_last_arrested_or_prosecuted() > 10,
        "years_since_final_release":
        crecord.years_since_final_release() > 10,
    }

    if all(conditions.values()):
        conclusion = "Expunge cases"
        modified_record = CRecord(person=copy.deepcopy(crecord.person),
                                  cases=[])
    else:
        conclusion = "No expungements possible"
        modified_record = crecord
    analysis.update({
        "age_over_70_expungements": {
            "conditions": conditions,
            "conclusion": conclusion,
        }
    })

    return modified_record, analysis
Пример #5
0
def dir(input_dir, output_json, output_html, email, log_level):
    """
    Analyze a record given a directory of dockets relating to a single person and write a plain-english 
    explanation of the analysis.
    """
    if not os.path.exists(input_dir):
        raise ValueError(f"Directory {input_dir} doesn't exist.")

    logger.setLevel(log_level)
    docket_files = [f for f in os.listdir(input_dir) if "docket_sheet" in f]

    source_records = []
    for df in docket_files:
        parser = pick_pdf_parser(df)
        if parser is None:
            continue
        source_records.append(
            SourceRecord(get_text_from_pdf(os.path.join(input_dir, df)),
                         parser))

    crecord = CRecord()
    for source_rec in source_records:
        crecord.add_sourcerecord(source_rec, override_person=True)

    analysis = (Analysis(crecord).rule(rd.expunge_deceased).rule(
        rd.expunge_over_70).rule(rd.expunge_nonconvictions).rule(
            rd.expunge_summary_convictions).rule(rd.seal_convictions))

    # email the results.
    communicate_results(source_records, analysis, output_json, output_html,
                        email)

    click.echo("Finished.")
Пример #6
0
def dir(directory, archive, expungement_template, sealing_template, atty_name, atty_org, atty_org_addr, atty_org_phone, atty_bar_id, tempdir):
    if not os.path.exists(directory):
        print(f"The directory {directory} does not exist.")
        return
    files = [os.path.join(directory, f) for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))]
    summaries = []
    dockets = []
    atty = Attorney(full_name=atty_name, organization=atty_org, organization_address=atty_org_addr, organization_phone=atty_org_phone, bar_id=atty_bar_id)
    for f in files:
        print(f"  Processing {f}")
        try:
            dk = Docket.from_pdf(f, tempdir=tempdir)
            print(f"    It looks like {f} is a docket.")
            dockets.append(dk)
        except:
            try:
                sm = parse_pdf(f, tempdir=tempdir)
                print(f"    It looks like {f} is a summary.")
                summaries.append(sm)
            except:
                print(f"    It seems {f} is neither a summary nor a docket.")
    
    crec = CRecord()
    [crec.add_summary(summary) for summary in summaries]
    [crec.add_docket(docket) for docket in dockets]

    analysis = (
        Analysis(crec)
        .rule(expunge_deceased)
        .rule(expunge_over_70)
        .rule(expunge_nonconvictions)
        .rule(expunge_summary_convictions)
        .rule(seal_convictions)
    )

    petitions = [petition for decision in analysis.decisions for petition in decision.value] 
    for petition in petitions: petition.attorney = atty
    with open(sealing_template, "rb") as doc:
        for petition in petitions:
            if petition.petition_type == "Sealing":
                petition.set_template(doc)

    with open(expungement_template, "rb") as doc:
        for petition in petitions:
            if petition.petition_type == "Expungement":
                petition.set_template(doc)


    petition_tuples = []
    for pt in petitions:
        petition_tuples.append((pt.file_name(), pt.render()))
    pkg = Compressor(archive, petition_tuples, tempdir=tempdir)
    pkg.save()
    print ("*********************************")
    print ("****** COMPLETE *****************")
    print ("*********************************")
Пример #7
0
def test_integrate_sources_with_crecord(dclient, admin_user, example_crecord):
    """
    User can post source records and an empty crecord to the server and receive a 
    crecord with the cases from the source record incorporated.
    """
    dclient.force_authenticate(user=admin_user)
    docket = os.listdir("tests/data/dockets/")[0]
    with open(f"tests/data/dockets/{docket}", "rb") as d:
        doc_1 = SourceRecord.objects.create(
            caption="Hello v. World",
            docket_num="MC-1234",
            court=SourceRecord.Courts.CP,
            url="https://abc.def",
            record_type=SourceRecord.RecTypes.DOCKET_PDF,
            file=File(d),
            owner=admin_user,
        )
    summary = os.listdir("tests/data/summaries")[0]
    with open(f"tests/data/summaries/{summary}", "rb") as s:
        doc_2 = SourceRecord.objects.create(
            caption="Hello v. Goodbye",
            docket_num="MC-1235",
            court=SourceRecord.Courts.MDJ,
            url="https://def.ghi",
            record_type=SourceRecord.RecTypes.SUMMARY_PDF,
            file=File(s),
            owner=admin_user,
        )

    # when sent to api, serialized document data won't have a file included.
    # The request is asking to do stuff using the file that is on the server.
    doc_1_data = SourceRecordSerializer(doc_1).data
    # doc_1_data.pop("file")

    doc_2_data = SourceRecordSerializer(doc_2).data
    # doc_2_data.pop("file")
    source_records = [doc_1_data, doc_2_data]
    data = {
        "crecord": CRecordSerializer(example_crecord).data,
        "source_records": source_records,
    }

    resp = dclient.put("/api/record/cases/", data=data)
    assert resp.status_code == 200
    assert "crecord" in resp.data
    assert "source_records" in resp.data
    # the response source_records list might include new source records, so will be at
    # least as long as the original source records list.
    assert len(resp.data["source_records"]) >= len(source_records)
    try:

        CRecord.from_dict(resp.data["crecord"])
    except Exception as err:
        pytest.fail(err)
Пример #8
0
def expunge_summary_convictions(crecord: CRecord,
                                analysis: dict) -> Tuple[CRecord, dict]:
    """
    Analyze crecord for expungements of summary convictions.

    18 Pa.C.S. 9122(b)(3)(i) and (ii) provide for expungement of summary convictions if the individual has been free of arrest or prosecution for five years following the conviction for the offense.

    Not available if person got ARD for certain offenses listed in (b.1)

    TODO excluding ARD offenses from expungements here.

    TODO grades are often missing. We should tell users we're uncertain.
    """
    conditions = {
        "arrest_free_five_years":
        crecord.years_since_last_arrested_or_prosecuted() > 5
    }
    expungements = []
    num_charges = 0
    num_expungeable_charges = 0
    modified_record = CRecord(person=crecord.person)
    for case in crecord.cases:
        any_expungements = False
        expungements_this_case = {"docket_number": case.docket_number}
        for charge in case.charges:
            num_charges += 1
            if charge.grade.strip() == "S":
                num_expungeable_charges += 1
                expungements_this_case.update({"charge": charge})
                any_expungements = True
        expungements.append(expungements_this_case)

        if any_expungements is False:
            modified_record.cases.append(copy.deepcopy(case))

    if (not all(conditions.values())) or num_expungeable_charges == 0:
        conclusion = "No expungements possible"
    elif all(conditions.values()) and num_charges == num_expungeable_charges:
        conclusion = "Expunge all cases"
    else:
        conclusion = (
            f"Expunge {num_expungeable_charges} charges in {len(crecord.cases)} cases"
        )

    analysis.update({
        "summary_conviction_expungements": {
            "conditions": conditions,
            "conclusion": conclusion,
            "expungements": expungements if all(conditions.values()) else [],
        }
    })

    return modified_record, analysis
Пример #9
0
def test_init():
    dob = date(2010, 1, 1)
    person = Person(**{
        "first_name": "Joe",
        "last_name": "Smith",
        "date_of_birth": dob
    })
    rec = CRecord(**{"person": person})
    assert rec.person.first_name == "Joe"
    rec = CRecord(person=Person(
        first_name="Joan", last_name="Smythe", date_of_birth=dob))
    assert rec.person.last_name == "Smythe"
Пример #10
0
def test_invalid_schema():
    with pytest.raises(TypeError):
        CRecord(**{
            "persons": {
                "first_name": "Blank"
            }
        })
Пример #11
0
def years_since_last_contact(crec: CRecord, year_min: int) -> Decision:
    return Decision(
        name=
        f"Has {crec.person.first_name} been free of arrest or prosecution for {year_min} years?",
        value=crec.years_since_last_arrested_or_prosecuted() >= 10,
        reasoning=
        f"It has been {crec.years_since_last_arrested_or_prosecuted()} years.")
Пример #12
0
def expunge_deceased(crecord: CRecord) -> Tuple[CRecord, Decision]:
    """
    Analyze a crecord for expungments if the individual has been dead for three years.

    18 Pa.C.S. 9122(b)(2) provides for expungement of records for an individual who has been dead for three years.
    """
    conclusion = Decision(
        name="A deceased person's record can be expunged after three years.",
        reasoning=[
            Decision(
                name=
                f"Has {crecord.person.first_name} been deceased for 3 years?",
                value=crecord.person.years_dead() > 3,
                reasoning=
                f"{crecord.person.first_name} is not dead, as far as I know."
                if crecord.person.years_dead() < 0 else
                f"It has been {crecord.person.years_dead()} since {crecord.person.first_name}'s death."
            )
        ])

    if all(conclusion.reasoning):
        exps = [Expungement(crecord.person, c) for c in crecord.cases]
        for e in exps:
            e.expungement_type = Expungement.ExpungementTypes.FULL_EXPUNGEMENT
        conclusion.value = exps
        remaining_recordord = CRecord(person=copy.deepcopy(crecord.person),
                                      cases=[])
    else:
        conclusion.value = []
        remaining_recordord = crecord

    return remaining_recordord, conclusion
Пример #13
0
def years_since_final_release(crec: CRecord, year_min: int) -> Decision:
    return Decision(
        name=
        f"Has it been at least {year_min} years since {crec.person.first_name}'s final release from custody?",
        value=crec.years_since_final_release() > year_min,
        reasoning=f"It has been {crec.years_since_final_release()}.",
    )
Пример #14
0
def expunge_over_70(crecord: CRecord) -> Tuple[CRecord, Decision]:
    """
    Analyze a crecord for expungements if the defendant is over 70.

    18 Pa.C.S. 9122(b)(1) provides for expungements of an individual who
    is 70 or older, and has been free of arrest or prosecution for 10
    years following the final release from confinement or supervision.
    """
    conclusion = Decision(
        name="A record can be expunged for a person over 70.",
        reasoning=[
            is_over_age(crecord.person, 70),
            years_since_last_contact(crecord, 10),
            years_since_final_release(crecord, 10)
        ])

    if all(conclusion.reasoning):
        exps = [
            Expungement(
                client=crecord.person,
                cases=[c],
                summary_expungement_language=
                "and the Petitioner is over 70 years old has been free of arrest or prosecution for ten years following from completion the sentence"
            ) for c in crecord.cases
        ]
        for e in exps:
            e.expungement_type = Expungement.ExpungementTypes.FULL_EXPUNGEMENT
        conclusion.value = exps
        remaining_recordord = CRecord(person=copy.deepcopy(crecord.person),
                                      cases=[])
    else:
        conclusion.value = []
        remaining_recordord = crecord

    return remaining_recordord, conclusion
Пример #15
0
    def post(self, request):
        """ Analyze a Criminal Record for expungeable and sealable cases and charges.
        
        POST body should be json-endoded CRecord object. 

        Return, if not an error, will be a json-encoded Decision that explains the expungements
        and sealings that can be generated for this record.

        """
        try:
            serializer = CRecordSerializer(data=request.data)
            if serializer.is_valid():
                rec = CRecord.from_dict(serializer.validated_data)
                analysis = (Analysis(rec).rule(expunge_deceased).rule(
                    expunge_over_70).rule(expunge_nonconvictions).rule(
                        expunge_summary_convictions).rule(seal_convictions))
                return Response(to_serializable(analysis))
            return Response(
                {"validation_errors": serializer.errors},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )
        except Exception as err:
            logger.error(err)
            return Response("Something went wrong",
                            status=status.HTTP_400_BAD_REQUEST)
Пример #16
0
def arrest_free_for_n_years(crec: CRecord, year_min=5) -> Decision:
    return Decision(
        name=
        f"Has {crec.person.first_name} been arrest free and prosecution free for five years?",
        value=crec.years_since_last_arrested_or_prosecuted() > year_min,
        reasoning=
        f"It has been {crec.years_since_last_arrested_or_prosecuted()} since the last arrest or prosecection.",
    )
Пример #17
0
def test_integrate_sources_with_crecord(dclient, admin_user, example_crecord):
    dclient.force_authenticate(user=admin_user)
    docket = os.listdir("tests/data/dockets/")[0]
    with open(f"tests/data/dockets/{docket}", "rb") as d:
        doc_1 = SourceRecord.objects.create(
            caption="Hello v. World",
            docket_num="MC-1234",
            court=SourceRecord.Courts.CP,
            url="https://abc.def",
            record_type=SourceRecord.RecTypes.DOCKET_PDF,
            file=File(d),
            owner=admin_user,
        )
    summary = os.listdir("tests/data/summaries")[0]
    with open(f"tests/data/summaries/{summary}", "rb") as s:
        doc_2 = SourceRecord.objects.create(
            caption="Hello v. Goodbye",
            docket_num="MC-1235",
            court=SourceRecord.Courts.MDJ,
            url="https://def.ghi",
            record_type=SourceRecord.RecTypes.SUMMARY_PDF,
            file=File(s),
            owner=admin_user,
        )

    # when sent to api, serialized document data won't have a file included.
    # The request is asking to do stuff using the file that is on the server.
    doc_1_data = SourceRecordSerializer(doc_1).data
    doc_1_data.pop("file")

    doc_2_data = SourceRecordSerializer(doc_2).data
    doc_2_data.pop("file")
    data = {
        "crecord": CRecordSerializer(example_crecord).data,
        "source_records": [doc_1_data, doc_2_data]
    }

    resp = dclient.put("/record/sources/", data=data)
    assert resp.status_code == 200
    assert "crecord" in resp.data
    try:
        CRecord.from_dict(resp.data["crecord"])
    except Exception as err:
        pytest.fail(err)
Пример #18
0
def expunge_nonconvictions(
        crecord: CRecord) -> Tuple[CRecord, PetitionDecision]:
    """
    18 Pa.C.S. 9122(a) provides that non-convictions (cases are closed with no disposition recorded) "shall be expunged."
    
    Returns:
        a Decision with:
            name: str,
            value: [Petition],
            reasoning: [Decision]
    """
    conclusion = Decision(name="Expungements of nonconvictions.",
                          value=[],
                          reasoning=[])

    remaining_recordord = CRecord(person=crecord.person)
    for case in crecord.cases:
        case_d = Decision(
            name=f"Does {case.docket_number} have expungeable nonconvictions?",
            reasoning=[],
        )
        unexpungeable_case = case.partialcopy()
        expungeable_case = case.partialcopy()
        for charge in case.charges:
            charge_d = Decision(
                name=f"Is the charge for {charge.offense} a nonconviction?",
                value=not charge.is_conviction(),
                reasoning=
                f"The charge's disposition {charge.disposition} indicates a conviction"
                if charge.is_conviction() else
                f"The charge's disposition {charge.disposition} indicates its not a conviction.",
            )

            if bool(charge_d) is True:
                expungeable_case.charges.append(charge)
            else:
                unexpungeable_case.charges.append(charge)
            case_d.reasoning.append(charge_d)

        # If there are any expungeable charges, add an Expungepent to the Value of the decision about
        # this whole record.
        if len(expungeable_case.charges) > 0:
            case_d.value = True
            exp = Expungement(client=crecord.person, cases=[expungeable_case])
            if len(expungeable_case.charges) == len(case.charges):
                exp.expungement_type = Expungement.ExpungementTypes.FULL_EXPUNGEMENT
            else:
                exp.expungement_type = Expungement.ExpungementTypes.PARTIAL_EXPUNGEMENT
            conclusion.value.append(exp)
        else:
            case_d.value = False

        if len(unexpungeable_case.charges) > 0:
            remaining_recordord.cases.append(unexpungeable_case)
        conclusion.reasoning.append(case_d)
Пример #19
0
def triage(directory, tempdir, output):
    """
    Read through a set of directories each containing records for a single person. Screen each person for obviously disqualifying elements in their record.
    """
    logging.basicConfig(level=logging.ERROR)
    if not os.path.exists(directory):
        logging.info(f"{directory} does not exist.")
        return
    subdirs = os.listdir(directory)
    recs = []
    logging.info(f"Constructing {len(subdirs)} records.")
    for sd in subdirs:
        rec = CRecord()
        pdfs = glob.glob(os.path.join(directory, sd, "*_Summary.pdf"))
        try:
            for pdf in pdfs: 
                try:
                    rec.add_summary(parse_pdf_summary(pdf, tempdir=tempdir))
                except:
                    try:
                        d, _ = Docket.from_pdf(pdf, tempdir=tempdir)
                        rec.add_docket(d)
                    except Exception as e:
                        raise e
            logging.info(f"Constructed a record for {rec.person.full_name()}, with {len(rec.cases)} cases.")
            recs.append((sd, rec))
        except Exception as e:
            logging.error(f"Error for {sd}: {str(e)}")
    logging.info(f"Now analyzing {len(recs)} records.")
    results = []
    for sd, rec in recs:
        
        res = {
                "dir": sd,
                "name": rec.person.full_name(),
                "cases": len(rec.cases),
                "felony_5_yrs": bool(any_felony_convictions_n_years(rec, 5)),
                "2plus_m1s_15yrs": bool(more_than_x_convictions_y_grade_z_years(rec, 2, "M1", 15)),
                "4plus_m2s_20yrs": bool(more_than_x_convictions_y_grade_z_years(rec, 4, "M2", 20)),
                "any_f1_convictions": not no_f1_convictions(rec),
        }
        res["any_disqualifiers"] = any([
            res["felony_5_yrs"],
            res["2plus_m1s_15yrs"],
            res["4plus_m2s_20yrs"],
            res["any_f1_convictions"],
        ])
        results.append(res)
    with open(output, 'w') as f:
        writer = csv.DictWriter(f, fieldnames=[
            "dir", "name", "cases", "felony_5_yrs", "2plus_m1s_15yrs", 
            "4plus_m2s_20yrs", "any_f1_convictions", "any_disqualifiers"])
        writer.writeheader()
        for res in results:
            writer.writerow(res)
    
    logging.info("Complete.")
Пример #20
0
def expunge_nonconvictions(crecord: CRecord,
                           analysis: dict) -> Tuple[CRecord, dict]:
    """
    18 Pa.C.S. 9122(a) provides that non-convictions (cases are closed with no disposition recorded) "shall be expunged."
    """
    conditions = {"Nonconvictions can always be expunged.": True}
    expungements = []
    num_charges = 0
    num_expungeable_charges = 0
    modified_record = CRecord(person=crecord.person)
    if all(conditions.values()):
        for case in crecord.cases:
            unexpunged_case = copy.deepcopy(case)
            unexpunged_case.charges = []
            expungements_this_case = {
                "docket_number": case.docket_number,
                "charges": list()
            }
            for charge in case.charges:
                num_charges += 1
                if charge.disposition.strip() == "" or any([
                        re.match(disp, charge.disposition, re.IGNORECASE)
                        for disp in [
                            "Nolle Prossed",
                            "Withdrawn",
                            "Not Guilty",
                            "Dismissed",
                        ]
                ]):
                    num_expungeable_charges += 1
                    expungements_this_case["charges"].append(charge)
                else:
                    unexpunged_case.charges.append(charge)
            if len(expungements_this_case["charges"]) > 0:
                expungements.append(expungements_this_case)
            if len(expungements_this_case["charges"]) < len(case.charges):
                modified_record.cases.append(unexpunged_case)
Пример #21
0
def expunge_deceased(crecord: CRecord, analysis: dict) -> Tuple[CRecord, dict]:
    """
    Analyze a crecord for expungments if the individual has been dead for three years.

    18 Pa.C.S. 9122(b)(2) provides for expungement of records for an individual who has been dead for three years.
    """
    conditions = {"deceased_three_years": crecord.person.years_dead() > 3}

    if all(conditions.values()):
        conclusion = "Expunge cases"
        modified_record = CRecord(person=copy.deepcopy(crecord.person),
                                  cases=[])
    else:
        conclusion = "No expungements possible"
        modified_record = crecord

    analysis.update({
        "deceased_expungements": {
            "conditions": conditions,
            "conclusion": conclusion
        }
    })

    return modified_record, analysis
Пример #22
0
    def put(self, request, *args, **kwargs):
        """
        Accept a CRecord and a set of SourceRecords. Incorporate the information that the SourceRecords contain into the CRecord.

        TODO this should replace FileUpload view. 
        """
        try:
            serializer = IntegrateSourcesSerializer(data=request.data)
            if serializer.is_valid():
                crecord = CRecord.from_dict(
                    serializer.validated_data["crecord"])
                for source_record_data in serializer.validated_data[
                        "source_records"]:
                    source_record = SourceRecord.objects.get(
                        id=source_record_data["id"])
                    if source_record.record_type == SourceRecord.RecTypes.SUMMARY_PDF:
                        summary = parse_pdf(source_record.file.path)
                        crecord.add_summary(
                            summary,
                            case_merge_strategy="overwrite_old",
                            override_person=True)
                    elif source_record.record_type == SourceRecord.RecTypes.DOCKET_PDF:
                        docket, errs = Docket.from_pdf(source_record.file.path)
                        crecord.add_docket(docket)
                    else:
                        logger.error(
                            f"Cannot parse a source record with type {source_record.record_type}"
                        )
                return Response({'crecord': CRecordSerializer(crecord).data},
                                status=status.HTTP_200_OK)
            else:
                return Response({"errors": serializer.errors},
                                status=status.HTTP_400_BAD_REQUEST)
        except Exception as err:
            return Response({"errors": [str(err)]},
                            status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Пример #23
0
def seal_convictions(crecord: CRecord) -> Tuple[CRecord, Decision]:
    """
    Pa.C.S. 9122.1 provides for petition-based sealing of records when certain
    conditions are met.

    Paragraph (a) provides a general rule that sealing is available when someone has been free of conviction for 10 years of certain offenses, and has paid fines and costs.


    Returns:
        A Decision. The decision's name is "Sealable Convictions". Its `value` is a list of the Cases and 
            charges that are selable from `crecord`. 
            Its `reasoning` is a list of all the decisions relating to the whole record, and all the decisions 
            relating to each case.

    TODO Paragraph (b)(2) provides conditions that disqualify a person's whole record from sealing.

    TODO Paragraph (b)(1) provides conditions that exclude convictions from sealing.
    """
    conclusion = Decision(
        name="Convictions can be sealed under the Clean Slate reforms.",
        value=[])
    mod_rec = CRecord(person=crecord.person, cases=[])

    # Requirements for sealing any part of a record
    conclusion.reasoning = [
        ten_years_since_last_conviction(crecord),  # 18 Pa.C.S. 9122.1(a)
        fines_and_costs_paid(crecord),  # 18 Pa.C.S. 9122.1(a)
        no_f1_convictions(crecord),  # 18 Pa.C.S. 9122.1(b)(2)(i)
        no_danger_to_person_offense(
            crecord, penalty_limit=7, conviction_limit=1, within_years=20
        ),
        no_offense_against_family(
            crecord, penalty_limit=7, conviction_limit=1, within_years=20
        ),
        no_firearms_offense(
            crecord, penalty_limit=7, conviction_limit=1, within_years=20
        ),
        no_sexual_offense(
            crecord, penalty_limit=7, conviction_limit=1, within_years=20
        ),
        offenses_punishable_by_two_or_more_years(
            crecord, conviction_limit=4, within_years=20
        ),
        offenses_punishable_by_two_or_more_years(
            crecord, conviction_limit=2, within_years=15
        ),
        no_indecent_exposure(crecord, conviction_limit=1, within_years=15),
        no_sexual_intercourse_w_animal(crecord, conviction_limit=1, within_years=15),
        no_failure_to_register(crecord, conviction_limit=1, within_years=15),
        no_weapons_of_escape(crecord, conviction_limit=1, within_years=15),
        no_abuse_of_corpse(crecord, conviction_limit=1, within_years=15),
        no_paramilitary_training(crecord, conviction_limit=1, within_years=15),
    ]
    if all(conclusion.reasoning):
        for case in crecord.cases:
            # The sealability of each case is its own decision
            case_decision = Decision(
                name=f"Sealing case {case.docket_number}", reasoning=[]
            )
            # create copies of a case that don't include any charges.
            # sealable or unsealable charges will be added to these.
            sealable_parts_of_case = case.partialcopy()
            unsealable_parts_of_case = case.partialcopy()
            for charge in case.charges:
                # The sealability of each charge is its own Decision.
                charge_decision = Decision(name=f"Sealing charge {charge.offense}")
                # Conditions that determine whether this charge is sealable
                #  See 91 Pa.C.S. 9122.1(b)(1)
                charge_decision.reasoning = [
                    charge.set_grade(),
                    is_misdemeanor_or_ungraded(charge),
                    no_danger_to_person_offense(
                        charge,
                        penalty_limit=2,
                        conviction_limit=1,
                        within_years=float("Inf"),
                    ),
                    no_offense_against_family(
                        charge,
                        penalty_limit=2,
                        conviction_limit=1,
                        within_years=float("Inf"),
                    ),
                    no_firearms_offense(
                        charge,
                        penalty_limit=2,
                        conviction_limit=1,
                        within_years=float("Inf"),
                    ),
                    no_sexual_offense(
                        charge,
                        penalty_limit=2,
                        conviction_limit=1,
                        within_years=float("Inf"),
                    ),
                    no_corruption_of_minors_offense(
                        charge,
                        penalty_limit=2,
                        conviction_limit=1,
                        within_years=float("Inf"),
                    ),
                ]
                if all(charge_decision.reasoning):
                    charge_decision.value = "Sealable"
                    sealable_parts_of_case.charges.append(charge)
                    # N.B. Appending charge, not deepcopy(charge), because I don't think
                    # the overhead is neccessary. But that could turn out to be wrong someday.
                else:
                    charge_decision.value = "Not sealable"
                    unsealable_parts_of_case.charges.append(charge)
                case_decision.reasoning.append(charge_decision)
            if all([reason.value == "Sealable" for reason in case_decision.reasoning]):
                # All the charges in the current case are sealable.
                case_decision.value = "All charges sealable"
                conclusion.value.append(Sealing(client=crecord.person, cases=[sealable_parts_of_case]))
            elif any(
                [reason.value == "Sealable" for reason in case_decision.reasoning]
            ):
                # At least one charge in the current case is sealable.
                case_decision.value = "Some charges sealable"
                mod_rec.cases.append(unsealable_parts_of_case)
                conclusion.value.append(Sealing(client=crecord.person, cases=[sealable_parts_of_case]))
            else:
                case_decision.value = "No charges sealable"
                mod_rec.cases.append(unsealable_parts_of_case)
            conclusion.reasoning.append(case_decision)

    return mod_rec, conclusion
Пример #24
0
def test_add_summary(example_summary):
    rec = CRecord()
    rec.add_summary(example_summary)
    assert rec.person.first_name == example_summary.get_defendant().first_name
    assert ((len(rec.cases) == len(example_summary.get_cases())) and
            (len(rec.cases) > 0))
Пример #25
0
def test_from_dict(example_crecord):
    serialized = to_serializable(example_crecord)
    crec2 = CRecord.from_dict(serialized)
    assert example_crecord.person.last_name == crec2.person.last_name
Пример #26
0
def test_add_summary_doesnt_add_duplicates(example_summary):
    summary2 = copy.deepcopy(example_summary)
    rec = CRecord(Person("Dummy", "Name", None))
    rec.add_summary(example_summary)
    rec.add_summary(summary2)
    assert len(rec.cases) == len(example_summary.get_cases())
Пример #27
0
def example_crecord(example_person, example_case):
    return CRecord(person=example_person, cases=[example_case])
Пример #28
0
def test_add_summary_merge_strategies(example_summary):
    summary2 = copy.deepcopy(example_summary)
    summary2.get_cases()[0].otn = "a_different_otn"
    # default merge_strategy is to ignore new duplicates or
    # new Person
    rec = CRecord(Person("Dummy", "Name", None))
    rec.add_summary(example_summary)
    rec.add_summary(summary2)
    assert rec.cases[0].otn == example_summary.get_cases()[0].otn
    assert rec.person.first_name == "Dummy"

    # alternate merge strategy overwrites duplicates w/ new case
    # but doesn't touch the Person
    rec = CRecord(Person("Dummy", "Name", None))
    rec.add_summary(example_summary)
    rec.add_summary(summary2, case_merge_strategy="overwrite_old")
    assert rec.cases[0].otn == summary2.get_cases()[0].otn
    assert rec.person.first_name == "Dummy"

    # override_person param provides for overwriting the Person with the new summary's
    # Person
    rec = CRecord(Person("Dummy", "Name", None))
    rec.add_summary(example_summary)
    rec.add_summary(summary2, override_person=True)
    assert rec.cases[0].otn != summary2.get_cases()[0].otn
    assert rec.person.first_name == summary2.get_defendant().first_name
Пример #29
0
def test_init_empty():
    try:
        rec = CRecord()
    except:
        pytest.fail("Should be able to create an empty CRecord.")
Пример #30
0
def test_add_docket(example_docket):
    rec = CRecord(Person("dummy", "name", None))
    rec.add_docket(example_docket)
    assert len(rec.cases) == 1
    assert rec.person.first_name != "dummy"