Ejemplo n.º 1
0
    def _make_stoptime_update(self, input_st_update, navitia_vj):
        nav_st = self._get_navitia_stop_time(input_st_update, navitia_vj)

        if nav_st is None:
            self.log.debug('impossible to find stop point {} in the vj {}, skipping it'.format(
                input_st_update.stop_id, navitia_vj.get('id')))
            return None

        nav_stop = nav_st.get('stop_point', {})

        # TODO handle delay uncertainty
        # TODO handle schedule_relationship
        def read_delay(st_event):
            if st_event and st_event.delay:
                return datetime.timedelta(seconds=st_event.delay)
        dep_delay = read_delay(input_st_update.departure)
        arr_delay = read_delay(input_st_update.arrival)
        dep_status = 'none' if dep_delay is None else 'update'
        arr_status = 'none' if arr_delay is None else 'update'


        st_update = model.StopTimeUpdate(nav_stop, departure_delay=dep_delay, arrival_delay=arr_delay,
                                         dep_status=dep_status, arr_status=arr_status)

        return st_update
Ejemplo n.º 2
0
    def _build_one_stop_time_update(self, gtfsrt_stop, nav_vj, previous_rt_stop_event_time):
        """
        Build a stop time update from given GTFSRT STU, navitia VJ, and
        check consistency versus previous stop-event datetime (should be ascending)
        :param gtfsrt_stop: GTFS StopTimeUpdate to convert
        :param nav_vj: Navitia VJ to be used to retrieve information on stop
        :param previous_rt_stop_event_time: Previous stop-event datetime
        :return: a couple (converted STU, last_stop_event_datetime) if conversion was a success and consistent
        """
        # retrieve navitia's stop_point corresponding to the current gtfsrt stop
        nav_stop, log_dict = self._get_navitia_stop_point(gtfsrt_stop, nav_vj)

        if log_dict:
            record_internal_failure(log_dict["log"], contributor=self.contributor.id)
            log_dict.update({"contributor": self.contributor.id})
            self.log.info("metrology", extra=log_dict)

        if nav_stop is None:
            # TODO: log and count in NR
            return None, None  # simply skip stop_times at unknown stop points

        st_update = model.StopTimeUpdate(nav_stop)
        for event_toggle in ["arrival", "departure"]:
            previous_rt_stop_event_time = _fill_and_check_one_stop_event(
                st_update=st_update,
                event_toggle=event_toggle,
                gtfsrt_stop=gtfsrt_stop,
                previous_rt_stop_event_time=previous_rt_stop_event_time,
            )

        return st_update, previous_rt_stop_event_time
Ejemplo n.º 3
0
 def _init_stop_update(self, nav_stop, stop_sequence):
     st_update = model.StopTimeUpdate(nav_stop,
                                      departure_delay=None,
                                      arrival_delay=None,
                                      dep_status='none',
                                      arr_status='none',
                                      order=stop_sequence)
     return st_update
Ejemplo n.º 4
0
    def _make_trip_update(self, vj, xml_modification):
        """
        create the TripUpdate object
        """
        trip_update = model.TripUpdate(vj=vj)
        trip_update.contributor = self.contributor

        delay = xml_modification.find('HoraireProjete')
        if delay:
            trip_update.status = 'update'
            for downstream_point in delay.iter('PointAval'):
                # we need only to consider the station
                if not as_bool(get_value(downstream_point,
                                         'IndicateurPRGare')):
                    continue
                nav_st = self._get_navitia_stop_time(downstream_point,
                                                     vj.navitia_vj)

                if nav_st is None:
                    continue

                nav_stop = nav_st.get('stop_point', {})

                dep_delay, dep_status = self._get_delay(
                    downstream_point.find('TypeHoraire/Depart'))
                arr_delay, arr_status = self._get_delay(
                    downstream_point.find('TypeHoraire/Arrivee'))

                message = get_value(downstream_point, 'MotifExterne')
                st_update = model.StopTimeUpdate(nav_stop,
                                                 departure_delay=dep_delay,
                                                 arrival_delay=arr_delay,
                                                 dep_status=dep_status,
                                                 arr_status=arr_status,
                                                 message=message)
                trip_update.stop_time_updates.append(st_update)

        removal = xml_modification.find('Suppression')
        if removal:
            if get_value(removal, 'TypeSuppression') == 'T':
                trip_update.status = 'delete'
                trip_update.stop_time_updates = []
            elif get_value(removal, 'TypeSuppression') == 'P':
                trip_update.status = 'update'
            xml_prdebut = removal.find('PRDebut')
            if xml_prdebut:
                trip_update.message = get_value(xml_prdebut, 'MotifExterne')

        return trip_update
Ejemplo n.º 5
0
    def _make_stoptime_update(self, input_st_update, nav_stop):
        # TODO handle delay uncertainty
        # TODO handle schedule_relationship
        def read_delay(st_event):
            if st_event and st_event.delay:
                return datetime.timedelta(seconds=st_event.delay)

        dep_delay = read_delay(input_st_update.departure)
        arr_delay = read_delay(input_st_update.arrival)
        dep_status = 'none' if dep_delay is None else 'update'
        arr_status = 'none' if arr_delay is None else 'update'
        st_update = model.StopTimeUpdate(nav_stop,
                                         departure_delay=dep_delay,
                                         arrival_delay=arr_delay,
                                         dep_status=dep_status,
                                         arr_status=arr_status,
                                         order=input_st_update.stop_sequence)

        return st_update
Ejemplo n.º 6
0
def build_new_stu(new_stu, nav_stu, result_idx_offset, propagated_delay):
    """
    Build a consistent STU from a provided new STU and its corresponding navitia STU
    :param new_stu: STU from new feed being processed
    :param nav_stu: STU from base-schedule
    :param result_idx_offset: order offset (in case result is appended to previously existing list)
    :param propagated_delay: delay to use for possible propagation
    :return: resulting STU (None if STU is inconsistent), and delay to be propagated
    """
    # populate new_stu present in old_stus (and maybe in nav_stus)
    updated_propagated_delay = propagated_delay
    new_arr, new_arr_delay, new_arr_status, updated_propagated_delay = build_new_stop_event_info(
        new_stu, nav_stu, updated_propagated_delay, "arrival"
    )
    new_dep, new_dep_delay, new_dep_status, updated_propagated_delay = build_new_stop_event_info(
        new_stu, nav_stu, updated_propagated_delay, "departure"
    )

    if nav_stu is not None or is_stop_absent_from_base_schedule_valid(
        arrival=new_arr,
        departure=new_dep,
        arrival_status=new_arr_status,
        departure_status=new_dep_status,
    ):
        return (
            model.StopTimeUpdate(
                navitia_stop=new_stu.navitia_stop,
                arrival=new_arr,
                departure=new_dep,
                arrival_delay=new_arr_delay,
                departure_delay=new_dep_delay,
                arr_status=new_arr_status,
                dep_status=new_dep_status,
                message=new_stu.message,
                order=result_idx_offset,
            ),
            updated_propagated_delay,
        )
    return None, propagated_delay
Ejemplo n.º 7
0
    def _make_trip_update(self, vj, xml_modification):
        """
        create the TripUpdate object
        """
        trip_update = model.TripUpdate(vj=vj)
        trip_update.contributor = self.contributor

        delay = xml_modification.find('HoraireProjete')
        if delay:
            trip_update.status = 'update'
            for downstream_point in delay.iter('PointAval'):
                # we need only to consider the station
                if not as_bool(get_value(downstream_point,
                                         'IndicateurPRGare')):
                    continue
                nav_st, log_dict = self._get_navitia_stop_time(
                    downstream_point, vj.navitia_vj)
                if log_dict:
                    record_internal_failure(log_dict['log'],
                                            contributor=self.contributor)
                    log_dict.update({'contributor': self.contributor})
                    logging.getLogger(__name__).info('metrology',
                                                     extra=log_dict)

                if nav_st is None:
                    continue

                nav_stop = nav_st.get('stop_point', {})

                dep_delay, dep_status = self._get_delay(
                    downstream_point.find('TypeHoraire/Depart'))
                arr_delay, arr_status = self._get_delay(
                    downstream_point.find('TypeHoraire/Arrivee'))

                message = get_value(downstream_point,
                                    'MotifExterne',
                                    nullabe=True)
                st_update = model.StopTimeUpdate(nav_stop,
                                                 departure_delay=dep_delay,
                                                 arrival_delay=arr_delay,
                                                 dep_status=dep_status,
                                                 arr_status=arr_status,
                                                 message=message)
                trip_update.stop_time_updates.append(st_update)

        removal = xml_modification.find('Suppression')
        if removal:
            xml_prdebut = removal.find('PRDebut')
            if get_value(removal, 'TypeSuppression') == 'T':
                trip_update.status = 'delete'
                trip_update.stop_time_updates = []
            elif get_value(removal, 'TypeSuppression') == 'P':
                # it's a partial delete
                trip_update.status = 'update'
                deleted_points = itertools.chain([removal.find('PRDebut')],
                                                 removal.iter('PointSupprime'),
                                                 [removal.find('PRFin')])
                for deleted_point in deleted_points:
                    # we need only to consider the stations
                    if not as_bool(get_value(deleted_point,
                                             'IndicateurPRGare')):
                        continue
                    nav_st, log_dict = self._get_navitia_stop_time(
                        deleted_point, vj.navitia_vj)
                    if log_dict:
                        record_internal_failure(log_dict['log'],
                                                contributor=self.contributor)
                        log_dict.update({'contributor': self.contributor})
                        logging.getLogger(__name__).info('metrology',
                                                         extra=log_dict)

                    if nav_st is None:
                        continue

                    nav_stop = nav_st.get('stop_point', {})

                    # if the <Depart>/<Arrivee> tags are there, the departure/arrival has been deleted
                    # regardless of the <Etat> tag
                    dep_deleted = deleted_point.find(
                        'TypeHoraire/Depart') is not None
                    arr_deleted = deleted_point.find(
                        'TypeHoraire/Arrivee') is not None

                    dep_status = 'delete' if dep_deleted else 'none'
                    arr_status = 'delete' if arr_deleted else 'none'

                    message = get_value(deleted_point,
                                        'MotifExterne',
                                        nullabe=True)
                    st_update = model.StopTimeUpdate(nav_stop,
                                                     dep_status=dep_status,
                                                     arr_status=arr_status,
                                                     message=message)
                    trip_update.stop_time_updates.append(st_update)

            if xml_prdebut:
                trip_update.message = get_value(xml_prdebut,
                                                'MotifExterne',
                                                nullabe=True)

        return trip_update
Ejemplo n.º 8
0
    def merge_trip_updates(self, navitia_vj, db_trip_update, new_trip_update):
        """
        Steps:
        1. Build TripUpdate info resulting of merge
        2. Adjust TripUpdate info to have it be self-consistent (call adjust_trip_update_consistency())
        3. If resulting TripUpdate info are new compared to the one from previous RT info: send it

        NB:
        * Working with ORM objects directly: no persistence of new object before knowing it's final version and
          it's not a duplicate (performance and db unicity constraints)
        """
        res_stus = [
        ]  # final list of StopTimeUpdates (to be attached to res in the end)

        circulation_date = new_trip_update.vj.get_circulation_date()
        # last info known about stops in trip (before processing new feed):
        # either from previous RT feed or base-schedule
        old_stus = (db_trip_update.stop_time_updates
                    if db_trip_update else convert_nav_stop_list_to_stu_list(
                        navitia_vj.get("stop_times", []), circulation_date))

        # build resulting STU list
        old_stus_unprocessed_start = 0  # keeps track of what was processed in previous STU info

        for new_order, new_stu in enumerate(new_trip_update.stop_time_updates):
            is_new_stu_last = new_order == len(
                new_trip_update.stop_time_updates) - 1

            # find corresponding stop in last known information
            match_old_stu_order, match_old_stu = find_enumerate_stu_in_stus(
                new_stu, old_stus, start=old_stus_unprocessed_start)
            is_match_old_stu_last = match_old_stu_order is not None and match_old_stu_order == len(
                old_stus) - 1

            # If new stop-time is not added, or if it is and was already added in last known information
            # Progress on stop-times from last-known information and process them
            if not new_stu.is_fully_added(
                    index=new_order, is_last=is_new_stu_last) or (
                        match_old_stu is not None
                        and match_old_stu.is_fully_added(
                            index=match_old_stu_order,
                            is_last=is_match_old_stu_last)):
                # Populate with old stops not found in new feed (the ones skipped to find current new_stu)
                populate_res_stus_with_unfound_old_stus(
                    res_stus=res_stus,
                    old_stus=old_stus,
                    unfound_start=old_stus_unprocessed_start,
                    unfound_end=match_old_stu_order
                    if match_old_stu_order is not None else len(old_stus),
                )
                # Remember progress (to avoid matching the same old stop-time for 2 different new stop-times)
                old_stus_unprocessed_start = match_old_stu_order + 1 if match_old_stu_order is not None else None

            # mark new stop-time as added if it was not known previously
            # Ex: a "delay" on a stop that doesn't exist in base-schedule is actually an "add"
            if match_old_stu is None:
                if new_stu.arrival_status in SIMPLE_MODIF_STATUSES:
                    new_stu.arrival_status = ModificationType.add.name
                if new_stu.departure_status in SIMPLE_MODIF_STATUSES:
                    new_stu.departure_status = ModificationType.add.name

            if new_stu.departure_delay is None:
                new_stu.departure_delay = as_duration(0)
            if new_stu.arrival_delay is None:
                new_stu.arrival_delay = as_duration(0)
            # add stop currently processed
            # GOTCHA: need a StopTimeUpdate detached of new_trip_update to avoid persisting new_trip_update
            # (this would lead to 2 TripUpdates for the same trip on the same day, forbidden by unicity constraint)
            res_stus.append(
                model.StopTimeUpdate(
                    navitia_stop=new_stu.navitia_stop,
                    departure=new_stu.departure,
                    arrival=new_stu.arrival,
                    departure_delay=new_stu.departure_delay,
                    arrival_delay=new_stu.arrival_delay,
                    dep_status=new_stu.departure_status,
                    arr_status=new_stu.arrival_status,
                    message=new_stu.message,
                    order=len(res_stus),
                ))
        # Finish populating with old stops not found in new feed
        # (the ones at the end after searching stops from new feed, if any)
        populate_res_stus_with_unfound_old_stus(
            res_stus=res_stus,
            old_stus=old_stus,
            unfound_start=old_stus_unprocessed_start,
            unfound_end=len(old_stus),
        )

        # if navitia_vj is empty, it's a creation
        if not navitia_vj.get("stop_times", []):
            new_trip_update.effect = TripEffect.ADDITIONAL_SERVICE.name
            new_trip_update.status = ModificationType.add.name

        # adjust consistency for resulting trip_update
        adjust_trip_update_consistency(new_trip_update, res_stus)

        return build_trip_update_if_modified(db_trip_update, old_stus,
                                             new_trip_update, res_stus,
                                             self.contributor.id)
Ejemplo n.º 9
0
    def _make_trip_update(self, json_train, vj):
        trip_update = model.TripUpdate(vj=vj,
                                       contributor_id=self.contributor.id)
        trip_update.headsign = json_train.get("numero")
        company_id = json_train.get("operateur").get("codeOperateur")
        trip_update.company_id = self._get_navitia_company_id(company_id)
        physical_mode = json_train.get("modeTransport").get("typeMode")
        trip_update.physical_mode_id = self._get_navitia_physical_mode_id(
            physical_mode)
        trip_status_type = trip_piv_status_to_effect.get(
            json_train.get("evenement").get("type"), TripEffect.UNDEFINED)
        trip_update.message = json_train.get("evenement").get("texte")
        trip_update.effect = trip_status_type.name
        if trip_status_type == TripEffect.NO_SERVICE:
            # the whole trip is deleted
            trip_update.status = ModificationType.delete.name
            trip_update.stop_time_updates = []
            return trip_update
        elif trip_status_type == TripEffect.ADDITIONAL_SERVICE:
            trip_update.status = ModificationType.add.name
        else:
            trip_update.status = ModificationType.update.name

        highest_st_status = ModificationType.none.name
        ads = get_value(get_value(json_train, "listeArretsDesserte"), "arret")

        # this variable is used to store the last stop_time's departure in order to check the stop_time consistency
        # ex. stop_time[i].arrival/departure must be greater than stop_time[i-1].departure
        previous_rt_stop_event_time = datetime.datetime.utcfromtimestamp(0)
        for arret in ads:
            # retrieve navitia's stop_point corresponding to the current PIV ad
            nav_stop, log_dict = self._get_navitia_stop_point(
                arret, vj.navitia_vj)

            if log_dict:
                record_internal_failure(log_dict["log"],
                                        contributor=self.contributor.id)
                log_dict.update({"contributor": self.contributor.id})
                logging.getLogger(__name__).info("metrology", extra=log_dict)

            if nav_stop is None:
                continue  # simply skip stop_times at unknown stop areas

            st_update = model.StopTimeUpdate(nav_stop)
            trip_update.stop_time_updates.append(st_update)

            st_update.message = _get_message(arret)
            for event_toggle in ["arrivee", "depart"]:
                event = get_value(arret, event_toggle, nullable=True)
                if event is None:
                    continue

                piv_disruption = event.get("evenement", {})
                piv_event_status = event.get("statutModification",
                                             piv_disruption.get("type", None))

                if not piv_event_status or piv_event_status in MANAGED_STOP_EVENTS:
                    piv_event_datetime = get_value(event,
                                                   "dateHeureReelle",
                                                   nullable=True)
                    event_datetime = str_to_utc_naive_dt(
                        piv_event_datetime) if piv_event_datetime else None
                    if event_datetime:
                        setattr(st_update,
                                STOP_EVENT_DATETIME_MAP[event_toggle],
                                event_datetime)

                    if piv_event_status in ["SUPPRESSION_PARTIELLE"]:
                        setattr(st_update, STATUS_MAP[event_toggle],
                                ModificationType.delete.name)
                    elif piv_event_status in ["SUPPRESSION_DETOURNEMENT"]:
                        setattr(st_update, STATUS_MAP[event_toggle],
                                ModificationType.deleted_for_detour.name)
                    elif piv_event_status in ["CREATION"]:
                        setattr(st_update, STATUS_MAP[event_toggle],
                                ModificationType.add.name)
                    elif piv_event_status in ["CREATION_DETOURNEMENT"]:
                        setattr(st_update, STATUS_MAP[event_toggle],
                                ModificationType.added_for_detour.name)
                    elif trip_status_type == TripEffect.ADDITIONAL_SERVICE:
                        # In this case we want to create schedules taking into account the delay
                        # without adding the delay a second time
                        setattr(st_update, STATUS_MAP[event_toggle],
                                ModificationType.add.name)
                    elif piv_event_status in [
                            "RETARD_OBSERVE", "RETARD_PROJETE"
                    ]:
                        setattr(st_update, STATUS_MAP[event_toggle],
                                ModificationType.update.name)
                        if piv_disruption:
                            piv_event_delay = piv_disruption.get(
                                "retard", {}).get("duree", 0)
                            setattr(st_update, DELAY_MAP[event_toggle],
                                    as_duration(piv_event_delay *
                                                60))  # minutes
                    else:
                        setattr(st_update, STATUS_MAP[event_toggle],
                                ModificationType.none.name)
                    # otherwise let those be none

                else:
                    raise InvalidArguments(
                        "invalid value {s} for field {t}/statutModification or {t}/evenement/type"
                        .format(s=piv_event_status, t=event_toggle))

                event_status = getattr(st_update, STATUS_MAP[event_toggle],
                                       ModificationType.none.name)
                highest_st_status = get_higher_status(highest_st_status,
                                                      event_status)

                mdi = (arret[STOP_EVENT_MDI_MAP[event_toggle]] if arret.get(
                    STOP_EVENT_MDI_MAP[event_toggle]) else False)
                if _is_stop_event_served(event_datetime, event_status, mdi):
                    if previous_rt_stop_event_time > event_datetime:
                        raise InvalidArguments(
                            "invalid feed: stop_point's({}) time is not consistent"
                            .format(
                                get_value(get_value(arret, "emplacement"),
                                          "code")))
                    previous_rt_stop_event_time = event_datetime

        # Calculates effect from stop_time status list (this work is also done in kraken and has to be deleted there)
        if trip_update.effect == TripEffect.MODIFIED_SERVICE.name:
            trip_update.effect = get_effect_from_modification_type(
                highest_st_status)
        return trip_update
Ejemplo n.º 10
0
    def _make_trip_update(self, vj, json_train):
        """
        create the new TripUpdate object
        Following the COTS spec: https://github.com/CanalTP/kirin/blob/master/documentation/cots_connector.md
        """
        logger = logging.getLogger(__name__)
        trip_update = model.TripUpdate(vj=vj)
        trip_update.contributor = self.contributor
        trip_message_id = get_value(json_train, 'idMotifInterneReference', nullable=True)
        if trip_message_id:
            trip_update.message = self.message_handler.get_message(index=trip_message_id)

        trip_status = get_value(json_train, 'statutOperationnel')

        if trip_status == 'SUPPRIMEE':
            # the whole trip is deleted
            trip_update.status = 'delete'
            trip_update.stop_time_updates = []
            return trip_update

        elif trip_status == 'AJOUTEE':
            # the trip is created from scratch
            # not handled yet
            self._record_and_log(logger, 'nouvelleVersion/statutOperationnel == "AJOUTEE" is not handled (yet)')
            return trip_update

        # all other status is considered an 'update' of the trip
        trip_update.status = 'update'
        pdps = _retrieve_interesting_pdp(get_value(json_train, 'listePointDeParcours'))

        # manage realtime information stop_time by stop_time
        for pdp in pdps:
            # retrieve navitia's stop_time information corresponding to the current COTS pdp
            nav_st, log_dict = self._get_navitia_stop_time(pdp, vj.navitia_vj)
            if log_dict:
                record_internal_failure(log_dict['log'], contributor=self.contributor)
                log_dict.update({'contributor': self.contributor})
                logging.getLogger(__name__).info('metrology', extra=log_dict)

            if nav_st is None:
                continue

            nav_stop = nav_st.get('stop_point', {})
            st_update = model.StopTimeUpdate(nav_stop)
            trip_update.stop_time_updates.append(st_update)
            # using the message from departure-time in priority, if absent fallback on arrival-time's message
            st_message_id = get_value(pdp, 'idMotifInterneDepartReference', nullable=True)
            if not st_message_id:
                st_message_id = get_value(pdp, 'idMotifInterneArriveeReference', nullable=True)
            if st_message_id:
                st_update.message = self.message_handler.get_message(index=st_message_id)

            _status_map = {'Arrivee': 'arrival_status', 'Depart': 'departure_status'}
            _delay_map = {'Arrivee': 'arrival_delay', 'Depart': 'departure_delay'}
            # compute realtime information and fill st_update for arrival and departure
            for arrival_departure_toggle in ['Arrivee', 'Depart']:
                cots_traveler_time = get_value(pdp,
                                               'horaireVoyageur{}'.format(arrival_departure_toggle),
                                               nullable=True)
                if cots_traveler_time is None:
                    continue
                cots_stop_time_status = get_value(cots_traveler_time,
                                                  'statutCirculationOPE',
                                                  nullable=True)
                if cots_stop_time_status is None:
                    # if no cots_stop_time_status, it is considered an 'update' of the stop_time
                    # (can be a delay, back to normal, normal, ...)
                    cots_ref_planned = get_value(pdp,
                                                 'sourceHoraireProjete{}Reference'.format(
                                                     arrival_departure_toggle),
                                                 nullable=True)
                    cots_planned_stop_times = get_value(pdp,
                                                        'listeHoraireProjete{}'.format(arrival_departure_toggle),
                                                        nullable=True)
                    cots_planned_stop_time = _retrieve_projected_time(cots_ref_planned, cots_planned_stop_times)
                    if cots_planned_stop_time is None:
                        continue

                    cots_delay = get_value(cots_planned_stop_time, 'pronosticIV', nullable=True)
                    if cots_delay is None:
                        continue

                    setattr(st_update, _status_map[arrival_departure_toggle], 'update')
                    setattr(st_update, _delay_map[arrival_departure_toggle], as_duration(cots_delay))

                elif cots_stop_time_status == 'SUPPRESSION':
                    # partial delete
                    setattr(st_update, _status_map[arrival_departure_toggle], 'delete')

                elif cots_stop_time_status == 'SUPPRESSION_DETOURNEMENT':
                    # stop_time is replaced by another one
                    self._record_and_log(logger, 'nouvelleVersion/listePointDeParcours/statutCirculationOPE == '
                                                 '"{}" is not handled completely (yet), only removal'
                                                 .format(cots_stop_time_status))
                    setattr(st_update, _status_map[arrival_departure_toggle], 'delete')

                elif cots_stop_time_status == 'CREATION':
                    # new stop_time added
                    self._record_and_log(logger, 'nouvelleVersion/listePointDeParcours/statutCirculationOPE == '
                                                 '"{}" is not handled (yet)'.format(cots_stop_time_status))

                elif cots_stop_time_status == 'DETOURNEMENT':
                    # new stop_time added also?
                    self._record_and_log(logger, 'nouvelleVersion/listePointDeParcours/statutCirculationOPE == '
                                                 '"{}" is not handled (yet)'.format(cots_stop_time_status))

                else:
                    raise InvalidArguments('invalid value {} for field horaireVoyageur{}/statutCirculationOPE'.
                                           format(cots_stop_time_status, arrival_departure_toggle))

        return trip_update
Ejemplo n.º 11
0
    def _make_trip_update(self,
                          json_train,
                          vj,
                          action_on_trip=ActionOnTrip.NOT_ADDED.name):
        """
        create the new TripUpdate object
        Following the COTS spec: https://github.com/CanalTP/kirin/blob/master/documentation/cots_connector.md
        """
        trip_update = model.TripUpdate(vj=vj)
        trip_update.contributor = self.contributor
        trip_message_id = get_value(json_train,
                                    'idMotifInterneReference',
                                    nullable=True)
        if trip_message_id:
            trip_update.message = self.message_handler.get_message(
                index=trip_message_id)
        cots_company_id = get_value(json_train,
                                    'codeCompagnieTransporteur',
                                    nullable=True) or DEFAULT_COMPANY_ID
        trip_update.company_id = self._get_navitia_company(cots_company_id)

        trip_status = get_value(json_train, 'statutOperationnel')

        if trip_status == TripStatus.SUPPRIMEE.name:
            # the whole trip is deleted
            trip_update.status = ModificationType.delete.name
            trip_update.stop_time_updates = []
            trip_update.effect = TripEffect.NO_SERVICE.name
            return trip_update

        elif action_on_trip != ActionOnTrip.NOT_ADDED.name:
            # the trip is created from scratch
            trip_update.effect = TripEffect.ADDITIONAL_SERVICE.name
            trip_update.status = ModificationType.add.name
            cots_physical_mode = get_value(json_train,
                                           'indicateurFer',
                                           nullable=True)
            trip_update.physical_mode_id = self._get_navitia_physical_mode(
                cots_physical_mode)
            trip_update.headsign = get_value(json_train,
                                             'numeroCourse',
                                             nullable=True)

        # all other status is considered an 'update' of the trip_update and effect is calculated
        # from stop_time status list. This part is also done in kraken and is to be deleted later
        # Ordered stop_time status= 'nochange', 'add', 'delete', 'update'
        # 'nochange' or 'update' -> SIGNIFICANT_DELAYS, add -> MODIFIED_SERVICE, delete = DETOUR
        else:
            trip_update.status = ModificationType.update.name
            trip_update.effect = TripEffect.MODIFIED_SERVICE.name

        # Initialize stop_time status to nochange
        highest_st_status = ModificationType.none.name
        pdps = _retrieve_interesting_pdp(
            get_value(json_train, 'listePointDeParcours'))

        # this variable is used to memoize the last stop_time's departure in order to check the stop_time consistency
        # ex. stop_time[i].arrival/departure must be greater than stop_time[i-1].departure
        last_stop_time_depart = None

        # manage realtime information stop_time by stop_time
        for pdp in pdps:
            # retrieve navitia's stop_point corresponding to the current COTS pdp
            nav_stop, log_dict = self._get_navitia_stop_point(
                pdp, vj.navitia_vj)
            projected_stop_time = {
                'Arrivee': None,
                'Depart': None
            }  # used to check consistency

            if log_dict:
                record_internal_failure(log_dict['log'],
                                        contributor=self.contributor)
                log_dict.update({'contributor': self.contributor})
                logging.getLogger(__name__).info('metrology', extra=log_dict)

            if nav_stop is None:
                continue

            st_update = model.StopTimeUpdate(nav_stop)
            trip_update.stop_time_updates.append(st_update)
            # using the message from departure-time in priority, if absent fallback on arrival-time's message
            st_message_id = get_value(pdp,
                                      'idMotifInterneDepartReference',
                                      nullable=True)
            if not st_message_id:
                st_message_id = get_value(pdp,
                                          'idMotifInterneArriveeReference',
                                          nullable=True)
            if st_message_id:
                st_update.message = self.message_handler.get_message(
                    index=st_message_id)

            _status_map = {
                'Arrivee': 'arrival_status',
                'Depart': 'departure_status'
            }
            _delay_map = {
                'Arrivee': 'arrival_delay',
                'Depart': 'departure_delay'
            }
            _stop_event_datetime_map = {
                'Arrivee': 'arrival',
                'Depart': 'departure'
            }

            # compute realtime information and fill st_update for arrival and departure
            for arrival_departure_toggle in ['Arrivee', 'Depart']:
                cots_traveler_time = get_value(
                    pdp,
                    'horaireVoyageur{}'.format(arrival_departure_toggle),
                    nullable=True)

                if cots_traveler_time is None:
                    continue

                cots_stop_time_status = get_value(cots_traveler_time,
                                                  'statutCirculationOPE',
                                                  nullable=True)

                if cots_stop_time_status is None:
                    # if no cots_stop_time_status, it is considered an 'update' of the stop_time
                    # (can be a delay, back to normal, normal, ...)
                    cots_base_datetime = _retrieve_stop_event_datetime(
                        cots_traveler_time)
                    if cots_base_datetime:
                        projected_stop_time[
                            arrival_departure_toggle] = cots_base_datetime
                    cots_delay = _retrieve_stop_event_delay(
                        pdp, arrival_departure_toggle)

                    if cots_delay is not None:
                        # It's an update only if there is delay
                        projected_stop_time[
                            arrival_departure_toggle] += cots_delay
                        setattr(st_update,
                                _status_map[arrival_departure_toggle],
                                ModificationType.update.name)
                        setattr(st_update,
                                _delay_map[arrival_departure_toggle],
                                cots_delay)
                    # otherwise nothing to do (status none, delay none, time none)

                elif cots_stop_time_status == 'SUPPRESSION':
                    # partial delete
                    setattr(st_update, _status_map[arrival_departure_toggle],
                            ModificationType.delete.name)

                elif cots_stop_time_status == 'SUPPRESSION_DETOURNEMENT':
                    # stop_time is replaced by another one
                    setattr(st_update, _status_map[arrival_departure_toggle],
                            ModificationType.deleted_for_detour.name)

                elif cots_stop_time_status in ['CREATION', 'DETOURNEMENT']:
                    # new stop_time added
                    cots_base_datetime = _retrieve_stop_event_datetime(
                        cots_traveler_time)
                    if cots_base_datetime:
                        projected_stop_time[
                            arrival_departure_toggle] = cots_base_datetime
                    cots_delay = _retrieve_stop_event_delay(
                        pdp, arrival_departure_toggle)
                    if cots_delay is not None:
                        projected_stop_time[
                            arrival_departure_toggle] += cots_delay

                    setattr(st_update,
                            _stop_event_datetime_map[arrival_departure_toggle],
                            projected_stop_time[arrival_departure_toggle])
                    # delay already added to stop_event datetime
                    setattr(st_update, _delay_map[arrival_departure_toggle],
                            None)

                    if cots_stop_time_status == 'CREATION':
                        # pure add
                        setattr(st_update,
                                _status_map[arrival_departure_toggle],
                                ModificationType.add.name)
                    elif cots_stop_time_status == 'DETOURNEMENT':
                        # add to replace another stop_time
                        setattr(st_update,
                                _status_map[arrival_departure_toggle],
                                ModificationType.added_for_detour.name)

                else:
                    raise InvalidArguments(
                        'invalid value {} for field horaireVoyageur{}/statutCirculationOPE'
                        .format(cots_stop_time_status,
                                arrival_departure_toggle))

                arr_dep_status = getattr(st_update,
                                         _status_map[arrival_departure_toggle],
                                         ModificationType.none.name)
                highest_st_status = get_higher_status(highest_st_status,
                                                      arr_dep_status)

            self._check_stop_time_consistency(
                last_stop_time_depart,
                projected_stop_time,
                pdp_code='-'.join(pdp[key] for key in ['cr', 'ci', 'ch']))
            last_stop_time_depart = projected_stop_time['Depart']

        # Calculates effect from stop_time status list(this work is also done in kraken and has to be deleted)
        if trip_update.effect == TripEffect.MODIFIED_SERVICE.name:
            trip_update.effect = get_effect_by_stop_time_status(
                highest_st_status)
        return trip_update
Ejemplo n.º 12
0
    def _build_trip_update(self, feed):
        feed_vj = self._get_vj(feed)
        if feed_vj is None:
            self.log.info(
                "Missing object in SIRI-ET-XML-TN feed to evaluate the real-time information; the feed should contain 'EstimatedVehicleJourney' XML object; no 'TripUpdate' will be created"
            )
            return None
        vj_name = self._get_vj_name(feed_vj)
        line_ref = self._get_line_ref(feed_vj)
        origin_dt = self._get_origin_dt(feed_vj)
        since_dt = origin_dt - timedelta(minutes=60)
        until_dt = origin_dt + timedelta(minutes=60)
        if vj_name is None or line_ref is None or origin_dt is None:
            self.log.info(
                "Missing information to identify the impacted vehicle journey in SIRI-ET-XML-TN; the feed should contain: 'VehicleJourneyName', 'LineRef' and 'OriginAimedDepartureTime'; no 'TripUpdate' will be created"
            )
            return None

        vj = self._get_navitia_vj(vj_name, line_ref, since_dt, until_dt)
        if vj is None:
            self.log.info(
                "Failed to find the vehicle journey in navitia from 'VehicleJourneyName={}', 'LineRef={}' and 'OriginAimedDepartureTime={}'; no 'TripUpdate' will be created"
                .format(vj_name, line_ref, origin_dt))
            return None

        trip_update = model.TripUpdate(vj=vj,
                                       contributor_id=self.contributor.id)

        cancellation = self._get_cancellation(feed_vj)
        extra_journey = self._get_extra_journey(feed_vj)

        # Trip cancelled, no more processing is needed
        if cancellation:
            trip_update.effect = TripEffect.NO_SERVICE.name
            trip_update.status = ModificationType.delete.name
            return trip_update

        # If neither Cancellation nor ExtraJourney is true,
        # then we only handle delayed stop times
        if not cancellation and not extra_journey:
            trip_update.effect = TripEffect.SIGNIFICANT_DELAYS.name
            trip_update.status = ModificationType.update.name
            st_idx = 0
            for feed_st in self._iter_stop_times(feed_vj):
                st_stop_point_ref = self._get_st_stop_point_ref(feed_st)
                st_departure_dt = self._get_st_departure_dt(feed_st)
                st_arrival_dt = self._get_st_arrival_dt(feed_st)
                st_cancellation = self._get_st_cancellation(feed_st)
                st_extra_call = self._get_st_extra_call(feed_st)
                st_platform_traversal = self._get_st_platform_traversal(
                    feed_st)

                if st_platform_traversal:
                    # The stop time is ignored
                    continue
                if st_cancellation or st_extra_call:
                    # We do not process (yet!) stop_time deletion or addition
                    self.log.info(
                        "Deletion or addition of stops are not yet supported; no 'TripUpdate' will be created"
                    )
                    return None
                st = vj.navitia_vj.get("stop_times")[st_idx]
                sp = st.get("stop_point")
                base_sp = sp.get("id")
                if st_stop_point_ref != base_sp:
                    # Stop points from base schedule and feed don't match
                    self.log.info(
                        "Stop from base schedule '{}' don't match the one '{}' in SIRI-ET-XML-TN feed; no 'TripUpdate' will be created"
                        .format(st_stop_point_ref, base_sp))
                    return None

                stu = model.StopTimeUpdate(sp)
                departure_delay = st_departure_dt - datetime.combine(
                    origin_dt.date(), st.get("utc_departure_time"))
                stu.update_departure(st_departure_dt, departure_delay,
                                     ModificationType.update.name)
                arrival_delay = st_arrival_dt - datetime.combine(
                    origin_dt.date(), st.get("utc_arrival_time"))
                stu.update_arrival(st_arrival_dt, arrival_delay,
                                   ModificationType.update.name)
                trip_update.stop_time_updates.append(stu)
                st_idx += 1

        return trip_update