def test_isPlaceholder_personWithExternalId(self):
     sentence_group = StateSentenceGroup.new_with_defaults(
         state_code=_STATE_CODE)
     person = StatePerson.new_with_defaults(
         sentence_groups=[sentence_group])
     self.assertTrue(is_placeholder(person))
     person.external_ids.append(
         StatePersonExternalId.new_with_defaults(state_code=_STATE_CODE,
                                                 external_id=_EXTERNAL_ID,
                                                 id_type=_ID_TYPE))
     self.assertFalse(is_placeholder(person))
 def test_isPlaceholder_personWithExternalId(self) -> None:
     sentence_group = schema.StateSentenceGroup(
         state_code=_STATE_CODE,
         status=StateSentenceStatus.PRESENT_WITHOUT_INFO)
     person = schema.StatePerson(state_code=_STATE_CODE,
                                 sentence_groups=[sentence_group])
     self.assertTrue(is_placeholder(person))
     person.external_ids.append(
         schema.StatePersonExternalId(state_code=_STATE_CODE,
                                      external_id=_EXTERNAL_ID,
                                      id_type=_ID_TYPE))
     self.assertFalse(is_placeholder(person))
예제 #3
0
def _move_periods_onto_sentences_for_sentence_group(
        sentence_group: schema.StateSentenceGroup, period_type: Type[schema.SchemaPeriodType]):
    """Looks at all SupervisionPeriods in the provided |sentence_group|, and attempts to match them to any
    corresponding sentences, based on date.
    """
    sentences = sentence_group.supervision_sentences + sentence_group.incarceration_sentences

    # Get all periods from sentence group
    periods = get_all_entities_of_cls([sentence_group], period_type)

    # Clear non-placeholder links from sentence to period. We will re-add/update these relationships below.
    for sentence in sentences:
        _only_keep_placeholder_periods_on_sentence(sentence, period_type)

    unmatched_periods = []
    matchable_sentences = _get_date_matchable_sentences(sentences)

    non_placeholder_periods = [p for p in periods if not is_placeholder(p)]

    # Match periods to non_placeholder_sentences by date.
    for p in non_placeholder_periods:
        matched = False
        p_start_date = _get_period_start_date(p)
        p_end_date = _get_period_end_date(p)

        for s in matchable_sentences:
            s_start_date = s.start_date
            if not s_start_date:
                continue

            s_completion_date = s.completion_date if s.completion_date else datetime.date.max

            if date_spans_overlap_exclusive(
                    start_1=p_start_date, end_1=p_end_date, start_2=s_start_date, end_2=s_completion_date):
                matched = True
                _add_period_to_sentence(p, s)

        # Unmatched periods will be re-added to a placeholder sentence at the end.
        if not matched:
            unmatched_periods.append(p)

    # Add unmatched periods to a placeholder sentence
    if unmatched_periods:
        placeholder_sentences = [s for s in sentences if is_placeholder(s)]
        if not placeholder_sentences:
            placeholder_sentence = get_or_create_placeholder_child(
                sentence_group, 'supervision_sentences', schema.StateSupervisionSentence,
                state_code=sentence_group.state_code, status=StateSentenceStatus.PRESENT_WITHOUT_INFO.value,
                person=sentence_group.person)
        else:
            placeholder_sentence = placeholder_sentences[0]
        for unmatched_period in unmatched_periods:
            _add_period_to_sentence(unmatched_period, placeholder_sentence)
예제 #4
0
def _is_match(*, ingested_entity: Entity, db_entity: Entity) -> bool:
    """Returns true if the provided |ingested_entity| matches the provided
    |db_entity|. Otherwise returns False.
    """
    if not ingested_entity or not db_entity:
        return ingested_entity == db_entity

    if ingested_entity.__class__ != db_entity.__class__:
        raise EntityMatchingError(
            f"is_match received entities of two different classes: "
            f"ingested entity {ingested_entity.__class__.__name__} and "
            f"db_entity {db_entity.__class__.__name__}",
            ingested_entity.get_entity_name())

    if isinstance(ingested_entity, StatePerson):
        db_entity = cast(StatePerson, db_entity)
        for ingested_external_id in ingested_entity.external_ids:
            for db_external_id in db_entity.external_ids:
                if _is_match(ingested_entity=ingested_external_id,
                             db_entity=db_external_id):
                    return True
        return False

    if isinstance(ingested_entity, StatePersonExternalId):
        db_entity = cast(StatePersonExternalId, db_entity)
        return ingested_entity.state_code == db_entity.state_code \
               and ingested_entity.external_id == db_entity.external_id \
               and ingested_entity.id_type == db_entity.id_type

    # As person has already been matched, assume that any of these 'person
    # attribute' entities are matches if their state_codes align.
    if isinstance(ingested_entity, StatePersonAlias):
        db_entity = cast(StatePersonAlias, db_entity)
        return ingested_entity.state_code == db_entity.state_code \
               and ingested_entity.full_name == db_entity.full_name
    if isinstance(ingested_entity, StatePersonRace):
        db_entity = cast(StatePersonRace, db_entity)
        return ingested_entity.state_code == db_entity.state_code \
               and ingested_entity.race == db_entity.race
    if isinstance(ingested_entity, StatePersonEthnicity):
        db_entity = cast(StatePersonEthnicity, db_entity)
        return ingested_entity.state_code == db_entity.state_code \
               and ingested_entity.ethnicity == db_entity.ethnicity

    db_entity = cast(ExternalIdEntity, db_entity)
    ingested_entity = cast(ExternalIdEntity, ingested_entity)

    # Placeholders entities are considered equal
    if ingested_entity.external_id is None and db_entity.external_id is None:
        return is_placeholder(ingested_entity) and is_placeholder(db_entity)
    return ingested_entity.external_id == db_entity.external_id
예제 #5
0
def _match_entity_tree(*, ingested_entity_tree: EntityTree,
                       db_entity_trees: List[EntityTree],
                       matched_entities_by_db_ids: Dict[int, Entity],
                       root_entity_cls: Type) -> IndividualMatchResult:
    """Attempts to match the provided |ingested_entity_tree| to one of the
    provided |db_entity_trees|. If a successful match is found, merges the
    ingested entity onto the matching database entity and performs entity
    matching on all children of the matched entities.

    Returns the results of matching as an IndividualMatchResult.
    """

    if is_placeholder(ingested_entity_tree.entity):
        return _match_placeholder_tree(
            ingested_placeholder_tree=ingested_entity_tree,
            db_entity_trees=db_entity_trees,
            matched_entities_by_db_ids=matched_entities_by_db_ids,
            root_entity_cls=root_entity_cls)

    db_match_tree = _get_match(ingested_entity_tree, db_entity_trees)

    if not db_match_tree:
        return _match_unmatched_tree(
            ingested_unmatched_entity_tree=ingested_entity_tree,
            db_entity_trees=db_entity_trees,
            root_entity_cls=root_entity_cls)

    return _match_matched_tree(
        ingested_entity_tree=ingested_entity_tree,
        db_match_tree=db_match_tree,
        matched_entities_by_db_ids=matched_entities_by_db_ids,
        root_entity_cls=root_entity_cls)
예제 #6
0
    def _find_violation_events(
        self,
        violations: List[StateSupervisionViolation],
    ) -> List[ViolationEvent]:
        """Finds instances of a violation.

        Identifies instance of a violation occurring.

        Args:
            - violations: All of the person's StateSupervisionViolations
            - violation_responses: All of the person's StateSupervisionViolationResponses

        Returns:
            A list of ViolationEvents for the person.
        """

        violation_events: List[ViolationEvent] = []

        for violation in violations:
            if is_placeholder(violation):
                continue
            violation_with_response_events = self._find_violation_with_response_events(
                violation)
            violation_events.extend(violation_with_response_events)
        return violation_events
예제 #7
0
def validate_admission_data(
        incarceration_periods: List[StateIncarcerationPeriod]) -> \
        List[StateIncarcerationPeriod]:
    """Removes any incarceration periods that don't have admission dates
    or admission reasons.

    Returns the valid incarceration periods.
    """
    validated_incarceration_periods: List[StateIncarcerationPeriod] = []

    for incarceration_period in incarceration_periods:
        if is_placeholder(incarceration_period):
            # Drop any placeholder incarceration periods from the calculations
            continue
        if not incarceration_period.admission_date:
            logging.info(
                "No admission_date on incarceration period with"
                " id: %d", incarceration_period.incarceration_period_id)
            continue
        if not incarceration_period.admission_reason:
            logging.info(
                "No admission_reason on incarceration period with"
                " id: %d", incarceration_period.incarceration_period_id)
            continue

        validated_incarceration_periods.append(incarceration_period)

    return validated_incarceration_periods
def validate_admission_data(
        incarceration_periods: List[StateIncarcerationPeriod]) -> \
        List[StateIncarcerationPeriod]:
    """Removes any incarceration periods that don't have admission dates
    or admission reasons.

    Returns the valid incarceration periods.
    """

    if incarceration_periods and incarceration_periods[0].state_code == 'US_ND':
        # If these are North Dakota incarceration periods, send to the
        # state-specific ND data validation function
        incarceration_periods = us_nd_utils.set_missing_admission_data(
            incarceration_periods)

    validated_incarceration_periods: List[StateIncarcerationPeriod] = []

    for incarceration_period in incarceration_periods:
        if is_placeholder(incarceration_period):
            # Drop any placeholder incarceration periods from the calculations
            continue
        if not incarceration_period.admission_date:
            logging.info(
                "No admission_date on incarceration period with"
                " id: %d", incarceration_period.incarceration_period_id)
            continue
        if not incarceration_period.admission_reason:
            logging.info(
                "No admission_reason on incarceration period with"
                " id: %d", incarceration_period.incarceration_period_id)
            continue

        validated_incarceration_periods.append(incarceration_period)

    return validated_incarceration_periods
예제 #9
0
def _drop_placeholder_periods(supervision_periods: List[StateSupervisionPeriod]) -> \
        List[StateSupervisionPeriod]:
    """Drops all supervision periods where the custodial_authority is not the state DOC of the state_code on the
    supervision_period."""
    return [
        period for period in supervision_periods if not is_placeholder(period)
    ]
예제 #10
0
def _base_entity_match(
        a: DatabaseEntity,
        b: DatabaseEntity,
        skip_fields: Set[str],
        allow_null_mismatch: bool = False
) -> bool:
    """Returns whether two objects of the same type are an entity match.

    Args:
        a: The first entity to match
        b: The second entity to match
        skip_fields: A list of names of fields that should be ignored when determining if two objects match based on
            flat fields.
        allow_null_mismatch: Allow for two objects to still match if one has a null value in a field where the other's
            is nonnull.
    """

    # Placeholders never match
    if is_placeholder(a) or is_placeholder(b):
        return False

    # Compare external ids if one is present
    if a.get_external_id() or b.get_external_id():
        return a.get_external_id() == b.get_external_id()

    # Compare all flat fields of the two entities
    all_set_flat_field_names = \
        get_set_entity_field_names(a, EntityFieldType.FLAT_FIELD) | \
        get_set_entity_field_names(b, EntityFieldType.FLAT_FIELD)
    for field_name in all_set_flat_field_names:
        # Skip primary key
        if field_name == a.get_class_id_name() or field_name in skip_fields:
            continue
        a_field = a.get_field(field_name)
        b_field = b.get_field(field_name)

        if allow_null_mismatch and (a_field is None or b_field is None):
            # Do not disqualify a match if one of the fields is null
            continue

        if a_field != b_field:
            return False

    return True
예제 #11
0
def _get_root_entity_helper(entity: Entity) -> Optional[Type]:
    if not is_placeholder(entity):
        return entity.__class__

    for field_name in get_set_entity_field_names(entity,
                                                 EntityFieldType.FORWARD_EDGE):
        field = get_field_as_list(entity, field_name)[0]
        result = _get_root_entity_helper(field)
        if result is not None:
            return result
    return None
예제 #12
0
def _add_match_to_matched_entities_cache(
        *, db_entity_match: Entity, ingested_entity: Entity,
        matched_entities_by_db_ids: Dict[int, Entity]):
    """Records a new ingested_entity/db_entity match. If the DB entity has
    already been matched to a different ingested_entity, it raises an error.
    """
    matched_db_id = db_entity_match.get_id()

    if matched_db_id in matched_entities_by_db_ids:
        if ingested_entity != matched_entities_by_db_ids[matched_db_id]:
            matches = [
                ingested_entity, matched_entities_by_db_ids[matched_db_id]
            ]
            # It's ok for a DB object to match multiple ingested placeholders.
            if is_placeholder(matches[0]) and is_placeholder(matches[1]):
                return
            raise MatchedMultipleIngestedEntitiesError(db_entity_match,
                                                       matches)
    else:
        matched_entities_by_db_ids[matched_db_id] = ingested_entity
예제 #13
0
def find_supervision_periods_overlapping_with_date(
    event_date: date,
    supervision_periods: List[StateSupervisionPeriod],
) -> List[StateSupervisionPeriod]:
    """Identifies supervision_periods where the event_date falls between the start and end of the supervision period,
    inclusive of the start date and exclusive of the end date."""
    return [
        sp for sp in supervision_periods if not is_placeholder(sp)
        and sp.start_date is not None and sp.start_date <= event_date and (
            sp.termination_date is None or event_date < sp.termination_date)
    ]
예제 #14
0
def _merge_incarceration_periods_helper(
        incomplete_incarceration_periods: List[StateIncarcerationPeriod]) \
        -> List[StateIncarcerationPeriod]:
    """Using the provided |incomplete_incarceration_periods|, attempts to merge
    consecutive admission and release periods from the same facility.

    Returns a list containing all merged incarceration periods as well as all
    incarceration periods that could not be merged, all ordered chronologically
    (based on the movement sequence number provided directly from ND).
    """

    placeholder_periods = [
        p for p in incomplete_incarceration_periods if is_placeholder(p)
    ]
    non_placeholder_periods = [
        p for p in incomplete_incarceration_periods if not is_placeholder(p)
    ]

    # Within any IncarcerationSentence, IncarcerationPeriod external_ids are all
    # equivalent, except for their suffixes. Each suffix is based on the
    # ND-provided movement sequence number. Therefore, sorting by external_ids
    # is equivalent to sorting by this movement sequence number.
    sorted_periods = sorted(non_placeholder_periods, key=_get_sequence_no)
    merged_periods = []
    last_period = None
    for period in sorted_periods:
        if not last_period:
            last_period = period
            continue
        if is_incomplete_incarceration_period_match(last_period, period):
            merged_periods.append(
                _merge_incomplete_periods(period, last_period))
            last_period = None
        else:
            merged_periods.append(last_period)
            last_period = period

    if last_period:
        merged_periods.append(last_period)
    merged_periods.extend(placeholder_periods)
    return merged_periods
예제 #15
0
def _only_keep_placeholder_periods_on_sentence(
        sentence: schema.SchemaSentenceType, period_type: Type[schema.SchemaPeriodType]):
    """Removes all non placeholder periods of type |period_type| from the provided |sentence|."""
    sentence_periods = sentence.supervision_periods \
        if period_type == schema.StateSupervisionPeriod \
        else sentence.incarceration_periods

    placeholder_periods = [p for p in sentence_periods if is_placeholder(p)]

    if period_type == schema.StateSupervisionPeriod:
        sentence.supervision_periods = placeholder_periods
    else:
        sentence.incarceration_periods = placeholder_periods
예제 #16
0
    def test_set_missing_admission_data_placeholders(self):
        """Tests that the function removes placeholder incarceration periods."""
        placeholder = StateIncarcerationPeriod.new_with_defaults(
            state_code='US_ND')

        incarceration_periods = [placeholder]

        assert is_placeholder(placeholder)

        updated_incarceration_periods = \
            set_missing_admission_data(incarceration_periods)

        assert updated_incarceration_periods == []
예제 #17
0
def is_incomplete_incarceration_period_match(
        ingested_entity: Union[EntityTree, Entity],
        db_entity: Union[EntityTree, Entity]) -> bool:
    """Given two incomplete StateIncarcerationPeriods, determines if they
    should be considered the same StateIncarcerationPeriod.
    """

    a, b = ingested_entity, db_entity
    if isinstance(ingested_entity, EntityTree):
        db_entity = cast(EntityTree, db_entity)
        a, b = ingested_entity.entity, db_entity.entity
    a = cast(StateIncarcerationPeriod, a)
    b = cast(StateIncarcerationPeriod, b)

    # Cannot match with a placeholder StateIncarcerationPeriod
    if is_placeholder(a) or is_placeholder(b):
        return False

    a_seq_no = _get_sequence_no(a)
    b_seq_no = _get_sequence_no(b)

    # Only match incomplete periods if they are adjacent based on seq no.
    if abs(a_seq_no - b_seq_no) != 1:
        return False

    # Check that the first period is an admission and second a release
    if a_seq_no < b_seq_no:
        first, second = a, b
    else:
        first, second = b, a
    if not first.admission_date or not second.release_date:
        return False

    # Must have same facility
    if a.facility != b.facility:
        return False

    return True
예제 #18
0
def _base_entity_match(a: DatabaseEntity, b: DatabaseEntity) -> bool:
    # Placeholders never match
    if is_placeholder(a) or is_placeholder(b):
        return False

    # Compare external ids if one is present
    if a.get_external_id() or b.get_external_id():
        return a.get_external_id() == b.get_external_id()

    # Compare all flat fields of the two entities
    all_set_flat_field_names = \
        get_set_entity_field_names(a, EntityFieldType.FLAT_FIELD) | \
        get_set_entity_field_names(b, EntityFieldType.FLAT_FIELD)
    for field_name in all_set_flat_field_names:
        # Skip primary key
        if field_name == a.get_class_id_name():
            continue
        a_field = a.get_field(field_name)
        b_field = b.get_field(field_name)
        if a_field != b_field:
            return False

    return True
예제 #19
0
def _filter_sentences_with_missing_fields(
        sentences: List[SentenceType]) -> List[SentenceType]:
    valid_sentences: List[SentenceType] = []
    for sentence in sentences:
        if is_placeholder(sentence):
            continue

        if not sentence.start_date:
            logging.error(
                "Non-placeholder sentence [%s] for state [%s] has no start date - ignoring.",
                sentence.external_id, sentence.state_code)
            continue
        valid_sentences.append(sentence)
    return valid_sentences
예제 #20
0
def get_or_create_placeholder_child(
        parent_entity: DatabaseEntity, child_field_name: str, child_class: Type[DatabaseEntity], **child_kwargs):
    """Checks all the entities in the |parent_entity|'s field |child_field_name|. If there is a placeholder entity,
    returns that. Otherwise creates a new placeholder entity of type |child_class| on the parent's |child_field_name|
    using |child_kw_args|.
    """
    children = parent_entity.get_field_as_list(child_field_name)
    placeholder_children = [c for c in children if is_placeholder(c)]

    if placeholder_children:
        return placeholder_children[0]

    logging.info(
        'No placeholder children on entity with id [%s] of type [%s] exist on field [%s]. Have to create one.',
        parent_entity.get_external_id(), parent_entity.get_entity_name(), child_field_name)
    new_child = child_class(**child_kwargs)
    if not is_placeholder(new_child):
        raise EntityMatchingError(
            f'Child created with kwargs is not a placeholder [{child_kwargs}]. Parent entity: [{parent_entity}]. Child '
            f'field name: [{child_field_name}]. Child class: [{child_class}].', parent_entity.get_entity_name())

    children.append(new_child)
    parent_entity.set_field_from_list(child_field_name, children)
    return new_child
예제 #21
0
def _match_persons(
        *, ingested_persons: List[StatePerson], db_persons: List[StatePerson]) \
        -> MatchedEntities:
    """Attempts to match all persons from |ingested_persons| with the provided
    |db_persons|. Results are returned in the MatchedEntities object which
    contains all successfully matched and merged persons as well as an error
    count that is incremented every time an error is raised matching an
    ingested person.
    """
    db_person_trees = [
        EntityTree(entity=db_person, ancestor_chain=[])
        for db_person in db_persons
    ]
    ingested_person_trees = [
        EntityTree(entity=ingested_person, ancestor_chain=[])
        for ingested_person in ingested_persons
    ]

    root_entity_cls = get_root_entity_cls(ingested_persons)
    total_root_entities = get_total_entities_of_cls(ingested_persons,
                                                    root_entity_cls)
    persons_match_results = _match_entity_trees(
        ingested_entity_trees=ingested_person_trees,
        db_entity_trees=db_person_trees,
        root_entity_cls=root_entity_cls)

    updated_persons = []
    for match_result in persons_match_results.individual_match_results:
        if not match_result.merged_entity_trees:
            updated_persons.append(match_result.ingested_entity_tree.entity)
        else:
            # It is possible that multiple ingested people match to the same
            # DB person, in which case we should only keep one reference to
            # that object.
            for merged_person_tree in match_result.merged_entity_trees:
                if merged_person_tree.entity not in updated_persons:
                    updated_persons.append(merged_person_tree.entity)

    # The only database persons that are unmatched that we potentially want to
    # update are placeholder persons. These may have had children removed as
    # a part of the matching process and therefore would need updating.
    for db_person in persons_match_results.unmatched_db_entities:
        if is_placeholder(db_person):
            updated_persons.append(db_person)

    return MatchedEntities(people=updated_persons,
                           error_count=persons_match_results.error_count,
                           total_root_entities=total_root_entities)
예제 #22
0
def add_supervising_officer_to_open_supervision_periods(persons: List[schema.StatePerson]):
    """For each person in the provided |persons|, adds the supervising_officer from the person entity onto all open
    StateSupervisionPeriods.
    """
    for person in persons:
        if not person.supervising_officer:
            continue

        supervision_periods = get_all_entities_of_cls([person], schema.StateSupervisionPeriod)
        for supervision_period in supervision_periods:
            # Skip placeholders
            if is_placeholder(supervision_period):
                continue

            if not supervision_period.termination_date:
                supervision_period.supervising_officer = person.supervising_officer
예제 #23
0
def _get_date_matchable_sentences(sentences: List[schema.SchemaSentenceType]) -> List[schema.SchemaSentenceType]:
    """Filters the provided list of |sentences| to only include sentences which are able to be matched to periods based
    on date. Returns this filtered list.
    """
    valid_sentences = []

    for sentence in sentences:
        if is_placeholder(sentence):
            continue
        # If we have sentences which we know are complete, but we have no completion date, exclude them from date
        # matching.
        if _is_sentence_ended_by_status(sentence) and not sentence.completion_date:
            continue
        valid_sentences.append(sentence)

    return valid_sentences
예제 #24
0
def _get_periods_in_sentence_group(sentence_group: schema.StateSentenceGroup) \
        -> Tuple[List[schema.StateIncarcerationPeriod], List[schema.StateIncarcerationPeriod]]:
    """Finds all placeholder and non-placeholder StateIncarcerationPeriods in the provided |sentence_group|, and
    returns the two lists in a tuple."""
    placeholder_periods = []
    non_placeholder_periods = []

    for incarceration_sentence in \
            sentence_group.incarceration_sentences:
        for incarceration_period in \
                incarceration_sentence.incarceration_periods:
            if is_placeholder(incarceration_period):
                placeholder_periods.append(incarceration_period)
            else:
                non_placeholder_periods.append(incarceration_period)
    return placeholder_periods, non_placeholder_periods
예제 #25
0
def find_supervision_periods_during_referral(
    referral_date: date,
    supervision_periods: List[StateSupervisionPeriod],
) -> List[StateSupervisionPeriod]:
    """Identifies supervision_periods where the referral_date falls between
    the start and end of the supervision period, indicating that the person
    was serving this supervision period at the time of the referral."""

    # Get all valid supervision periods with a start date before or on the
    # referral date
    applicable_supervision_periods = [
        sp for sp in supervision_periods if not is_placeholder(sp)
        and sp.start_date is not None and sp.start_date <= referral_date and
        (sp.termination_date is None or sp.termination_date >= referral_date)
    ]

    return applicable_supervision_periods
예제 #26
0
def set_missing_admission_data(
        incarceration_periods: List[StateIncarcerationPeriod]) -> \
        List[StateIncarcerationPeriod]:
    """Sets missing admission data on incarceration period inputs according to
    logic specific to North Dakota data.

    In the ND data, there are over 1,000 incarceration periods with empty
    admission dates and reasons that follow a release for a transfer. These
    incarceration periods have valid release data, so we know that this
    individual was transferred back into a facility at some point. For every
    incarceration period where this is the case, this function sets the
    admission date to the date of the transfer out, and the admission reason to
    be a transfer back in.
    """
    filtered_incarceration_periods: List[StateIncarcerationPeriod] = []

    # Remove placeholder incarceration periods and any without an external_id
    for incarceration_period in incarceration_periods:
        if not is_placeholder(incarceration_period) and \
                incarceration_period.external_id is not None:
            filtered_incarceration_periods.append(incarceration_period)

    updated_incarceration_periods: List[StateIncarcerationPeriod] = []

    if filtered_incarceration_periods:
        filtered_incarceration_periods.sort(key=lambda b: b.external_id)

        for index, incarceration_period in \
                enumerate(filtered_incarceration_periods):
            if not incarceration_period.admission_date and \
                    not incarceration_period.admission_reason and index > 0:
                previous_incarceration_period = \
                    filtered_incarceration_periods[index - 1]
                if previous_incarceration_period.release_reason == \
                        StateIncarcerationPeriodReleaseReason.TRANSFER and \
                        previous_incarceration_period.release_date \
                        is not None:
                    incarceration_period.admission_reason = \
                        StateIncarcerationPeriodAdmissionReason.TRANSFER
                    incarceration_period.admission_date = \
                        previous_incarceration_period.release_date

            updated_incarceration_periods.append(incarceration_period)

    return updated_incarceration_periods
예제 #27
0
def set_current_supervising_officer_from_supervision_periods(
        matched_persons: List[schema.StatePerson]):
    """For every matched person, update the supervising_officer field to pull in the supervising_officer from the latest
    supervision period (sorted by termination date).
    """
    for person in matched_persons:

        sps = get_all_entities_of_cls(person.sentence_groups, schema.StateSupervisionPeriod)

        non_placeholder_sps = [sp for sp in sps if not is_placeholder(sp)]

        if not non_placeholder_sps:
            continue

        non_placeholder_sps.sort(key=lambda sp: sp.termination_date if sp.termination_date else datetime.date.max)

        latest_supervision_period = non_placeholder_sps[-1]
        person.supervising_officer = latest_supervision_period.supervising_officer
def _move_events_onto_supervision_periods(
    source: DatabaseEntity, event_cls: Type[DatabaseEntity], event_field_name: str
) -> List[DatabaseEntity]:
    """Looks at all events of type |event_cls| in the provided |source|, and attempts to place them onto a matching
    SupervisionPeriod's |event_field_name| field. Matching is based on dates, and all unmatched events are returned
    to the caller to store.
    """
    supervision_periods = get_all_entities_of_cls(
        [source], schema.StateSupervisionPeriod
    )
    events = get_all_entities_of_cls(supervision_periods, event_cls)

    # Clear the links from supervision period to supervision violations. We will
    # re-add/update these relationships below.
    for supervision_period in supervision_periods:
        supervision_period.set_field(event_field_name, [])

    unmatched_events = []
    non_placeholder_periods = [
        sp for sp in supervision_periods if not is_placeholder(sp)
    ]

    # Match events onto to non_placeholder_periods by date.
    for event in events:
        matched = False
        event_date = _get_event_date(event)
        if event_date:
            for sp in non_placeholder_periods:
                sp_end_date = (
                    sp.termination_date if sp.termination_date else datetime.date.max
                )
                sp_start_date = sp.start_date
                if sp_start_date <= event_date < sp_end_date:
                    matched = True
                    sp_events = sp.get_field(event_field_name)
                    sp_events.append(event)
                    sp.set_field(event_field_name, sp_events)

        # Unmatched SVs will be returned
        if not matched:
            unmatched_events.append(event)

    return unmatched_events
예제 #29
0
    def test_set_missing_admission_data_no_external_id(self):
        """Tests that the function removes incarceration periods without
        external_ids."""
        incarceration_period = StateIncarcerationPeriod.new_with_defaults(
            state_code='US_ND',
            incarceration_period_id=2222,
            status=StateIncarcerationPeriodStatus.NOT_IN_CUSTODY,
            admission_date=date(2008, 4, 14),
            admission_reason=AdmissionReason.TRANSFER,
            release_date=date(2010, 4, 14),
            release_reason=ReleaseReason.SENTENCE_SERVED)

        incarceration_periods = [incarceration_period]

        assert not is_placeholder(incarceration_period)
        assert incarceration_period.external_id is None

        updated_incarceration_periods = \
            set_missing_admission_data(incarceration_periods)

        assert updated_incarceration_periods == []
예제 #30
0
def get_month_supervision_type(
    any_date_in_month: datetime.date,
    supervision_sentences: List[StateSupervisionSentence],
    incarceration_sentences: List[StateIncarcerationSentence],
    supervision_period: StateSupervisionPeriod
) -> StateSupervisionPeriodSupervisionType:
    """Supervision type can change over time even if the period does not change. This function calculates the
    supervision type that a given supervision period represents during the month that |any_date_in_month| falls in. We
    do this by looking at all sentences attached to this supervision period, then determining which ones overlap with
    any day in the month, and using the sentence supervision types to determine the period supervision type at this
    point in time.

    Args:
    any_date_in_month: (date) Any day in the month to consider
    supervision_period: (StateSupervisionPeriod) The supervision period we want to associate a supervision type with
    supervision_sentences: (List[StateSupervisionSentence]) All supervision sentences for a given person.
    """
    if not supervision_period.supervision_period_id:
        raise ValueError('All objects should have database ids.')

    if is_placeholder(supervision_period):
        raise ValueError('Do not expect placeholder periods!')

    start_of_month = first_day_of_month(any_date_in_month)
    end_of_month = last_day_of_month(any_date_in_month)

    # Find sentences that are attached to the period and overlap with the month
    incarceration_sentences = _get_valid_attached_sentences(
        incarceration_sentences, supervision_period)
    incarceration_sentences = _get_sentences_overlapping_with_dates(
        start_of_month, end_of_month, incarceration_sentences)

    supervision_sentences = _get_valid_attached_sentences(
        supervision_sentences, supervision_period)
    supervision_sentences = _get_sentences_overlapping_with_dates(
        start_of_month, end_of_month, supervision_sentences)

    return _get_supervision_type_from_sentences(incarceration_sentences,
                                                supervision_sentences)