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
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
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
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
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
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
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
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)
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
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
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
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