def _get_piv_higher_trip_disruption(piv_disruptions, piv_data_freshness_source): """ Get (or build) the single reference tag from "evenement" list (the most important) :param piv_disruptions: list of json objects for tag "evenement" :param piv_data_freshness_source: value of json attribute "planTransportSource" :return: reference object "evenement" to be considered everywhere """ if not piv_disruptions: if piv_data_freshness_source in ["PTP", "OPE"]: return ujson.loads('{"type": "CREATION", "texte": ""}') else: raise UnsupportedValue( "planTransportSource {} is not supported".format( piv_data_freshness_source)) higher_trip_disruption = ujson.loads('{"type": "UNDEFINED", "texte": ""}') for piv_disruption in piv_disruptions: piv_disruption_type = get_value(piv_disruption, "type", nullable=True) if piv_disruption_type and piv_disruption_type in trip_piv_status_to_effect: higher_trip_disruption = ( piv_disruption if _get_trip_effect_order_from_piv_status(piv_disruption_type) < _get_trip_effect_order_from_piv_status( get_value(higher_trip_disruption, "type")) else higher_trip_disruption) if trip_piv_status_to_effect.get( higher_trip_disruption.get("type")) is None: raise UnsupportedValue( "None of the disruption-types {} are supported".format( piv_disruptions)) return higher_trip_disruption
def _is_fully_added_stop(ad): """ Check if an arretDesserte object has any event that was present in base-schedule (and return the opposite boolean) """ for event_toggle in ["arrivee", "depart"]: event = get_value(ad, event_toggle, nullable=True) if event and get_value(event, "planTransportSource", nullable=True) not in ["OPE", "PTP"]: return False return True
def _get_first_stop_base_datetime(list_ads, hour_obj_name, skip_fully_added_stops=True): if skip_fully_added_stops: s = next((s for s in list_ads if not _is_fully_added_stop(s)), None) else: s = next((s for s in list_ads), None) str_time = get_value(get_value(s, hour_obj_name), "dateHeure") if s else None return str_to_utc_naive_dt(str_time) if str_time else None
def _get_first_stop_datetime(list_pdps, hour_obj_name, skip_fully_added_stops=True): if skip_fully_added_stops: p = next((p for p in list_pdps if not _is_fully_added_pdp(p)), None) else: p = next((p for p in list_pdps), None) str_time = get_value(get_value(p, hour_obj_name), "dateHeure") if p else None return str_to_utc_naive_dt(str_time) if str_time else None
def _get_message(arret): arrival_stop = get_value(arret, "arrivee", nullable=True) departure_stop = get_value(arret, "depart", nullable=True) motif = None if departure_stop: motif = get_value(departure_stop, "motifModification", nullable=True) if not motif and arrival_stop: motif = get_value(arrival_stop, "motifModification", nullable=True) if not motif and departure_stop: motif = departure_stop.get("evenement", {}).get("texte", None) if not motif and arrival_stop: motif = arrival_stop.get("evenement", {}).get("texte", None) return motif
def _retrieve_projected_time(source_ref, list_proj_time): """ pick the good projected arrival/departure objects from the list provided, using the source-reference if existing """ # if a source-reference is defined if source_ref is not None: # retrieve the one mentioned if it exists if list_proj_time: for p in list_proj_time: s = get_value(p, "source", nullable=True) if s is not None and s == source_ref: return p # if a reference is provided but impossible to retrieve corresponding element, reject whole COTS feed raise InvalidArguments( 'invalid json, impossible to find source "{s}" in any json dict ' "of list: {list}".format(s=source_ref, list=ujson.dumps(list_proj_time))) elif list_proj_time: # if no source-reference exists, but only one element in the list, we take it if len(list_proj_time) == 1: return list_proj_time[0] # if list has multiple elements but no source-reference, reject whole COTS feed raise InvalidArguments( "invalid json, impossible no source but multiple json dicts " "in list: {list}".format(list=ujson.dumps(list_proj_time))) return None
def _retrieve_stop_event_delay(pdp, arrival_departure_toggle): 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 not None: delay = get_value(cots_planned_stop_time, "pronosticIV", nullable=True) return as_duration(delay) return None
def is_station(pdp): """ determine if a Point de Parcours is a legit station :param pdp: stop_time to be checked :return: True if pdp is a station, False otherwise """ t = get_value(pdp, "typeArret", nullable=True) return (t is None) or (t in ["", "CD", "CH", "FD", "FH"])
def _retrieve_interesting_pdp(list_pdp): """ Filter and sort "Points de Parcours" (corresponding to stop_times in Navitia) to get only the relevant ones from a Navitia's perspective (stations, where travelers can hop on or hop off) Context: COTS may contain operating informations, useless for traveler :param list_pdp: an array of "Point de Parcours" (typically the one from the feed) :return: Filtered array Notes: - written in a yield-fashion to switch implementation if possible, but we need random access for now - see 'test_retrieve_interesting_pdp' for a functional example """ res = [] picked_one = False sorted_list_pdp = sorted(list_pdp, key=itemgetter("rang")) for idx, pdp in enumerate(sorted_list_pdp): # At start, do not consume until there's a departure time (horaireVoyageurDepart) if not picked_one and not get_value( pdp, "horaireVoyageurDepart", nullable=True): continue # exclude stop_times that are not legit stations if not is_station(pdp): continue # exclude stop_times that have no departure nor arrival time (empty stop_times) if not get_value(pdp, "horaireVoyageurDepart", nullable=True) and not get_value( pdp, "horaireVoyageurArrivee", nullable=True): continue # stop consuming once all following stop_times are missing arrival time # * if a stop_time only has departure time, travelers can only hop in, but if they are be able to # hop off later because some stop_time has arrival time then the current stop_time is useful, # so we keep current stop_time. # * if no stop_time has arrival time anymore, then stop_times are useless as traveler cannot # hop off, so no point hopping in anymore, so we remove all the stop_times until the end # (should not happen in practice). if not get_value(pdp, "horaireVoyageurArrivee", nullable=True): has_following_arrival = any( get_value(follow_pdp, "horaireVoyageurArrivee", nullable=True) and is_station(follow_pdp) for follow_pdp in sorted_list_pdp[idx:]) if not has_following_arrival: break picked_one = True res.append(pdp) return res
def _is_fully_added_pdp(pdp): """ Check if a projected arrival/departure object is fully created """ # retrieve expressed statuses dep_arr_statuses = [] for arrival_departure_toggle in ["Arrivee", "Depart"]: cots_traveler_time = get_value( pdp, "horaireVoyageur{}".format(arrival_departure_toggle), nullable=True) if cots_traveler_time: dep_arr_statuses.append( get_value(cots_traveler_time, "statutCirculationOPE", nullable=True)) # if there are expressed cots_traveler_times and all are 'CREATION', pdp is fully added return dep_arr_statuses and all(s == "CREATION" for s in dep_arr_statuses)
def _get_navitia_stop_point(self, arret, nav_vj): """ Get a navitia stop point from the stop_time in a 'Point de Parcours' dict. The dict MUST contain cr, ci, ch tags. It searches in the vj's stops for a stop_area with the external code cr-ci-ch If the stop_time isn't found in the vj, in case of an additional stop_time, a request is made to Navitia. Error messages are also returned as 'missing stop point', 'duplicate stops' """ uic8 = get_value(get_value(arret, "emplacement"), "code") nav_st, log_dict = _extract_navitia_stop_time(uic8, nav_vj) if not nav_st: nav_stop, req_log_dict = self._request_navitia_stop_point(uic8) log_dict.update(req_log_dict) else: nav_stop = nav_st.get("stop_point", None) return nav_stop, log_dict
def _get_action_on_trip(train_numbers, dict_version, pdps): """ Verify if trip in cots feed is a newly added one and check possible actions :param dict_version: Value of attribut nouvelleVersion in cots feed :return: NOT_ADDED if the trip is not added by current feed, FIRST_TIME_ADDED if it's the first time this trip is added, PREVIOUSLY_ADDED if this train is already added (present in db) """ cots_trip_status = get_value(dict_version, "statutOperationnel", TripStatus.PERTURBEE.name) # We have to verify if the trip exists in database vj_start = _get_first_stop_datetime(pdps, "horaireVoyageurDepart", skip_fully_added_stops=False) vj_end = _get_first_stop_datetime(reversed(pdps), "horaireVoyageurArrivee", skip_fully_added_stops=False) train_id = TRAIN_ID_FORMAT.format(train_numbers) trip_added_in_db = model.TripUpdate.find_vj_by_period( train_id, start_date=vj_start - SNCF_SEARCH_MARGIN, end_date=vj_end + SNCF_SEARCH_MARGIN) action_on_trip = ActionOnTrip.NOT_ADDED.name if trip_added_in_db: # Raise exception on forbidden / inconsistent actions # No addition multiple times # No update or delete on trip already deleted. if cots_trip_status == TripStatus.AJOUTEE.name and trip_added_in_db.status == ModificationType.add.name: raise InvalidArguments( "Invalid action, trip {} can not be added multiple times". format(train_numbers)) elif (cots_trip_status != TripStatus.AJOUTEE.name and trip_added_in_db.status == ModificationType.delete.name): raise InvalidArguments( "Invalid action, trip {} already deleted in database".format( train_numbers)) # Trip added then deleted followed by add should be handled as FIRST_TIME_ADDED if (cots_trip_status == TripStatus.AJOUTEE.name and trip_added_in_db.status == ModificationType.delete.name): action_on_trip = ActionOnTrip.FIRST_TIME_ADDED.name # Trip already added should be handled as PREVIOUSLY_ADDED elif trip_added_in_db.status == ModificationType.add.name: action_on_trip = ActionOnTrip.PREVIOUSLY_ADDED.name else: if cots_trip_status == TripStatus.AJOUTEE.name: action_on_trip = ActionOnTrip.FIRST_TIME_ADDED.name return action_on_trip
def build_trip_updates(self, rt_update): """ parse the COTS raw json stored in the rt_update object (in Kirin db) and return a list of trip updates The TripUpdates are not yet associated with the RealTimeUpdate Most of the realtime information parsed is contained in the 'nouvelleVersion' sub-object (see fixtures and documentation) """ try: json = ujson.loads(rt_update.raw_data) except ValueError as e: raise InvalidArguments("invalid json: {}".format(str(e))) if "nouvelleVersion" not in json: raise InvalidArguments( 'No object "nouvelleVersion" available in feed provided') dict_version = get_value(json, "nouvelleVersion") train_numbers = get_value(dict_version, "numeroCourse") pdps = _retrieve_interesting_pdp( get_value(dict_version, "listePointDeParcours")) if len(pdps) < 2: raise InvalidArguments( 'invalid json, "listePointDeParcours" has less than 2 valid stop_times in ' "json elt {elt}".format(elt=ujson.dumps(dict_version))) action_on_trip = _get_action_on_trip(train_numbers, dict_version, pdps) vjs = self._get_vjs(train_numbers, pdps, action_on_trip=action_on_trip) trip_updates = [ self._make_trip_update(dict_version, vj, action_on_trip=action_on_trip) for vj in vjs ] log_dict = {} return trip_updates, log_dict
def _get_navitia_stop_point(self, pdp, nav_vj): """ Get a navitia stop point from the stop_time in a 'Point de Parcours' dict. The dict MUST contain cr, ci, ch tags. It searches in the vj's stops for a stop_area with the external code cr-ci-ch If the stop_time isn't found in the vj, in case of an additional stop_time, a request is made to Navitia. Error messages are also returned as 'missing stop point', 'duplicate stops' """ nav_st, log_dict = get_navitia_stop_time_sncf(cr=get_value(pdp, "cr"), ci=get_value(pdp, "ci"), ch=get_value(pdp, "ch"), nav_vj=nav_vj) if not nav_st: nav_stop, log_dict = self._request_navitia_stop_point( cr=get_value(pdp, "cr"), ci=get_value(pdp, "ci"), ch=get_value(pdp, "ch")) else: nav_stop = nav_st.get("stop_point", None) return nav_stop, log_dict
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 build_trip_updates(self, rt_update): """ parse the PIV raw json stored in the rt_update object (in Kirin db) and return a list of trip updates The TripUpdates are not associated with the RealTimeUpdate at this point """ log_dict = {} try: json = ujson.loads(rt_update.raw_data) except ValueError as e: raise InvalidArguments("invalid json: {}".format(str(e))) dict_objects = get_value(json, "objects") json_train = get_value( dict_objects[0], "object") # TODO: can we get more than 1 relevant in objects[]? brand_code = json_train.get("marque", {}).get("code") if brand_code in ["TN", "TNRER"]: raise InvalidArguments( '"{}" marque code is not supported'.format(brand_code)) piv_disruptions = get_value(json_train, "evenement", nullable=True) plan_transport_source = get_value(json_train, "planTransportSource", nullable=True) if not piv_disruptions and not plan_transport_source: raise InvalidArguments( 'No object "evenement" or "planTransportSource" available in feed provided' ) higher_trip_disruption = _get_piv_higher_trip_disruption( piv_disruptions, plan_transport_source) json_train["evenement"] = higher_trip_disruption train_date_str = get_value(json_train, "dateCirculation") train_numbers = get_value(json_train, "numero") train_company = get_value(get_value(json_train, "operateur"), "codeOperateur") mode_dict = get_value(json_train, "modeTransport") train_mode = get_value(mode_dict, "codeMode") train_submode = get_value(mode_dict, "codeSousMode") train_typemode = get_value(mode_dict, "typeMode") piv_key = "{d}:{n}:{c}:{m}:{s}:{t}".format(d=train_date_str, n=train_numbers, c=train_company, m=train_mode, s=train_submode, t=train_typemode) list_ads = get_value(json_train, "listeArretsDesserte") ads = _retrieve_interesting_stops(get_value(list_ads, "arret")) if len(ads) < 2: raise InvalidArguments( 'invalid json, "listeArretsDesserte/arret" has less than 2 valid stop_times in ' "json elt {elt}".format(elt=ujson.dumps(json_train))) # replace by cleaned and sorted version of stoptimes list. json_train["listeArretsDesserte"]["arret"] = ads is_trip_addition = higher_trip_disruption.get("type") == "CREATION" train_date = str_to_date(train_date_str) vj = self._get_navitia_vj(piv_key, train_date, ads, is_trip_addition) trip_updates = [self._make_trip_update(json_train, vj)] return trip_updates, log_dict
def _retrieve_stop_event_datetime(cots_traveler_time): base_schedule_datetime = get_value(cots_traveler_time, "dateHeure", nullable=True) return str_to_utc_naive_dt( base_schedule_datetime) if base_schedule_datetime else None
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/hove-io/kirin/blob/master/documentation/cots_connector.md """ trip_update = model.TripUpdate(vj=vj, contributor_id=self.contributor.id) 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.id) log_dict.update({"contributor": self.contributor.id}) 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 or cots_stop_time_status == "REACTIVATION": # if no cots_stop_time_status, it is considered an 'update' of the stop_time # (can be a delay, back to normal, normal, reactivation...) 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_from_modification_type( highest_st_status) return trip_update
def _has_arrival(stop): return get_value(stop, "arrivee", nullable=True) and not get_value( stop, "indicateurDescenteInterdite", nullable=True)
def _has_departure(stop): return get_value(stop, "depart", nullable=True) and not get_value( stop, "indicateurMonteeInterdite", nullable=True)