def get_local_circulation_date(self): from kirin.utils import get_timezone if self.navitia_vj is None: return None first_stop_time = self.navitia_vj.get('stop_times', [{}])[0] tzinfo = get_timezone(first_stop_time) return self.get_start_timestamp().astimezone(tzinfo).date()
def __init__(self, navitia_vj, local_circulation_date): from kirin.utils import get_timezone self.id = gen_uuid() if 'trip' in navitia_vj and 'id' in navitia_vj['trip']: self.navitia_trip_id = navitia_vj['trip']['id'] first_stop_time = navitia_vj.get('stop_times', [{}])[0] start_time = first_stop_time['arrival_time'] if start_time is None: start_time = first_stop_time['departure_time'] tzinfo = get_timezone(first_stop_time) self.start_timestamp = tzinfo.localize( datetime.datetime.combine(local_circulation_date, start_time)).astimezone(utc) self.navitia_vj = navitia_vj # Not persisted
def merge(navitia_vj, db_trip_update, new_trip_update, is_new_complete=False): """ We need to merge the info from 3 sources: * the navitia base schedule * the trip update already in the db (potentially nonexistent) * the incoming trip update The result is either the db_trip_update if it exists, or the new_trip_update (it is updated as a side effect) The mechanism is quite simple: * the result trip status is the new_trip_update's status (ie in the db the trip was cancelled, and a new update is only an update, the trip update is not cancelled anymore, only updated) * for each navitia's stop_time and for departure|arrival: - if there is an update on this stoptime (in new_trip_update): we compute the new datetime based on the new information and the navitia's base schedule - else if there is the stoptime in the db: we keep this db stoptime - else we keep the navitia's base schedule Note that the results is either 'db_trip_update' or 'new_trip_update'. Side effects on this object are thus wanted because of database persistency (update or creation of new objects) If is_new_complete==True, then new_trip_update is considered as a complete trip, so it will erase and replace the (old) db_trip_update. Detail: is_new_complete changes the way None is interpreted in the new_trip_update: - if is_new_complete==False, None means there is no new information, so we keep old ones - if is_new_complete==True, None means we are back to normal, so we keep the new None (for now it only impacts messages to allow removal) ** Important Note **: we DO NOT HANDLE changes in navitia's schedule for the moment it will need to be handled, but it will be done after """ res = db_trip_update if db_trip_update else new_trip_update res_stoptime_updates = [] res.status = new_trip_update.status if new_trip_update.message is not None or is_new_complete: res.message = new_trip_update.message res.contributor = new_trip_update.contributor if res.status == 'delete': # for trip cancellation, we delete all stoptimes update res.stop_time_updates = [] return res last_nav_dep = None last_departure = None local_circulation_date = new_trip_update.vj.get_local_circulation_date() has_changes = False for nav_order, navitia_stop in enumerate(navitia_vj.get('stop_times', [])): # TODO handle forbidden pickup/dropoff (in those case set departure/arrival at None) nav_departure_time = navitia_stop.get('departure_time') nav_arrival_time = navitia_stop.get('arrival_time') timezone = get_timezone(navitia_stop) # we compute the arrival time and departure time on base schedule and take past mid-night into # consideration base_arrival = base_departure = None if nav_arrival_time is not None: if last_nav_dep is not None and last_nav_dep > nav_arrival_time: # last departure is after arrival, it's a past-midnight local_circulation_date += timedelta(days=1) base_arrival = _get_datetime(local_circulation_date, nav_arrival_time, timezone) if nav_departure_time is not None: if nav_arrival_time is not None and nav_arrival_time > nav_departure_time: # departure is before arrival, it's a past-midnight local_circulation_date += timedelta(days=1) base_departure = _get_datetime(local_circulation_date, nav_departure_time, timezone) stop_id = navitia_stop.get('stop_point', {}).get('id') new_st = new_trip_update.find_stop(stop_id, nav_order) if db_trip_update is not None and new_st is not None: """ First case: we already have recorded the delay and we find update info in the new trip update Then : we should probably update it or not if the input info is exactly the same as the one in db """ db_st = db_trip_update.find_stop(stop_id, nav_order) new_st_update = _make_stop_time_update(base_arrival, base_departure, last_departure, new_st, navitia_stop['stop_point'], order=nav_order) has_changes |= (db_st is None) or db_st.is_ne(new_st_update) res_st = new_st_update if has_changes else db_st elif db_trip_update is None and new_st is not None: """ Second case: we have not yet recorded the delay Then : it's time to create one in the db """ has_changes = True res_st = _make_stop_time_update(base_arrival, base_departure, last_departure, new_st, navitia_stop['stop_point'], order=nav_order) res_st.message = new_st.message elif db_trip_update is not None and new_st is None: """ Third case: we have already recorded a delay but nothing is mentioned in the new trip update Then : For IRE, we do nothing but only update stop time's order For gtfs-rt, according to the specification, we should use the delay from the previous stop time, which will be handled sooner by the connector-specified model maker *** Here, we MUST NOT do anything, only update stop time's order *** """ db_st = db_trip_update.find_stop(stop_id, nav_order) res_st = db_st if db_st is not None else StopTimeUpdate(navitia_stop['stop_point'], departure=base_departure, arrival=base_arrival, order=nav_order) has_changes |= (db_st is None) else: """ Last case: nothing is recorded yet and there is no update info in the new trip update Then : take the base schedule's arrival/departure time and let's create a whole new world! """ has_changes = True res_st = StopTimeUpdate(navitia_stop['stop_point'], departure=base_departure, arrival=base_arrival, order=nav_order) last_departure = res_st.departure res_stoptime_updates.append(res_st) last_nav_dep = nav_departure_time if has_changes: res.stop_time_updates = res_stoptime_updates return res return None
def _make_db_vj(self, vj_source_code, since, until): navitia_vjs = self.navitia.vehicle_journeys( q={ 'filter': 'vehicle_journey.has_code({}, {})'.format( self.stop_code_key, vj_source_code), 'since': to_str(since), 'until': to_str(until), 'depth': '2', # we need this depth to get the stoptime's stop_area }) if not navitia_vjs: self.log.info('impossible to find vj {t} on [{s}, {u}]'.format( t=vj_source_code, s=since, u=until)) record_internal_failure('missing vj', contributor=self.contributor) return [] if len(navitia_vjs) > 1: vj_ids = [vj.get('id') for vj in navitia_vjs] self.log.info( 'too many vjs found for {t} on [{s}, {u}]: {ids}'.format( t=vj_source_code, s=since, u=until, ids=vj_ids)) record_internal_failure('duplicate vjs', contributor=self.contributor) return [] nav_vj = navitia_vjs[0] # Now we compute the real circulate_date of VJ from since, until and vj's first stop_time # We do this to prevent cases like pass midnight when [since, until] is too large # we need local timezone circulate_date (and it's sometimes different from UTC date) first_stop_time = nav_vj.get('stop_times', [{}])[0] tzinfo = get_timezone(first_stop_time) # 'since' and 'until' must have a timezone before being converted to local timezone local_since = pytz.utc.localize(since).astimezone(tzinfo) local_until = pytz.utc.localize(until).astimezone(tzinfo) circulate_date = None if local_since.date() == local_until.date(): circulate_date = local_since.date() else: arrival_time = first_stop_time['arrival_time'] # At first, we suppose that the circulate_date is local_since's date if local_since <= tzinfo.localize( datetime.datetime.combine(local_since.date(), arrival_time)) <= local_until: circulate_date = local_since.date() elif local_since <= tzinfo.localize( datetime.datetime.combine(local_until.date(), arrival_time)) <= local_until: circulate_date = local_until.date() if circulate_date is None: self.log.error( 'impossible to calculate the circulate date (local) of vj: {}'. format(nav_vj.get('id'))) record_internal_failure( 'impossible to calculate the circulate date of vj', contributor=self.contributor) return [] try: vj = model.VehicleJourney(nav_vj, circulate_date) return [vj] except Exception as e: self.log.exception( 'Error while creating kirin VJ of {}: {}'.format( nav_vj.get('id'), e)) record_internal_failure('Error while creating kirin VJ', contributor=self.contributor) return []