diff --git a/activitysim/abm/models/school_escorting.py b/activitysim/abm/models/school_escorting.py index 0ade57dd5..7605d492d 100644 --- a/activitysim/abm/models/school_escorting.py +++ b/activitysim/abm/models/school_escorting.py @@ -155,54 +155,6 @@ def add_prev_choices_to_choosers( return choosers -def create_bundle_attributes(row): - """ - Parse a bundle to determine escortee numbers and tour info. - """ - escortee_str = "" - escortee_num_str = "" - school_dests_str = "" - school_starts_str = "" - school_ends_str = "" - school_tour_ids_str = "" - num_escortees = 0 - - for child_num in row["child_order"]: - child_num = str(child_num) - child_id = int(row["bundle_child" + child_num]) - - if child_id > 0: - num_escortees += 1 - school_dest = str(int(row["school_destination_child" + child_num])) - school_start = str(int(row["school_start_child" + child_num])) - school_end = str(int(row["school_end_child" + child_num])) - school_tour_id = str(int(row["school_tour_id_child" + child_num])) - - if escortee_str == "": - escortee_str = str(child_id) - escortee_num_str = str(child_num) - school_dests_str = school_dest - school_starts_str = school_start - school_ends_str = school_end - school_tour_ids_str = school_tour_id - else: - escortee_str = escortee_str + "_" + str(child_id) - escortee_num_str = escortee_num_str + "_" + str(child_num) - school_dests_str = school_dests_str + "_" + school_dest - school_starts_str = school_starts_str + "_" + school_start - school_ends_str = school_ends_str + "_" + school_end - school_tour_ids_str = school_tour_ids_str + "_" + school_tour_id - - row["escortees"] = escortee_str - row["escortee_nums"] = escortee_num_str - row["num_escortees"] = num_escortees - row["school_destinations"] = school_dests_str - row["school_starts"] = school_starts_str - row["school_ends"] = school_ends_str - row["school_tour_ids"] = school_tour_ids_str - return row - - def create_school_escorting_bundles_table(choosers, tours, stage): """ Creates a table that has one row for every school escorting bundle. @@ -336,18 +288,13 @@ def create_school_escorting_bundles_table(choosers, tours, stage): bundles["inbound_order"], ) - bundles = bundles.apply(lambda row: create_bundle_attributes(row), axis=1) + # putting the bundle attributes in order of child pickup/dropoff + bundles = school_escort_tours_trips.create_bundle_attributes(bundles) # getting chauffer mandatory times mandatory_escort_tours = tours[ (tours.tour_category == "mandatory") & (tours.tour_num == 1) ] - # bundles["first_mand_tour_start_time"] = reindex( - # mandatory_escort_tours.set_index("person_id").start, bundles["chauf_id"] - # ) - # bundles["first_mand_tour_end_time"] = reindex( - # mandatory_escort_tours.set_index("person_id").end, bundles["chauf_id"] - # ) bundles["first_mand_tour_id"] = reindex( mandatory_escort_tours.reset_index().set_index("person_id").tour_id, bundles["chauf_id"], diff --git a/activitysim/abm/models/util/school_escort_tours_trips.py b/activitysim/abm/models/util/school_escort_tours_trips.py index e22a7198f..35a0e84b5 100644 --- a/activitysim/abm/models/util/school_escort_tours_trips.py +++ b/activitysim/abm/models/util/school_escort_tours_trips.py @@ -13,71 +13,324 @@ logger = logging.getLogger(__name__) -def determine_chauf_outbound_flag(row, i): - if row["school_escort_direction"] == "outbound": - outbound = True - elif ( - (row["school_escort_direction"] == "inbound") - & (i == 0) - & (row["escort_type"] == "pure_escort") - ): - # chauf is going to pick up the first child - outbound = True - else: - # chauf is inbound and has already picked up a child or taken their mandatory tour - outbound = False - return outbound - - -def create_chauf_trip_table(row): - dropoff = True if row["school_escort_direction"] == "outbound" else False - - row["person_id"] = row["chauf_id"] - row["destination"] = row["school_destinations"].split("_") +def create_bundle_attributes(bundles): + """ + Create attributes for school escorting bundles. + Majority of the code is to handle the different combinations of child order. + Structure is optimized for speed + (readability would be much better with pd.apply(), but this is too slow!) + + Parameters + ---------- + bundles : pandas.DataFrame + School escorting bundles + + Returns + ------- + pandas.DataFrame + + """ + + # Initialize columns + bundles["escortees"] = "" + bundles["escortee_nums"] = "" + bundles["num_escortees"] = "" + bundles["school_destinations"] = "" + bundles["school_starts"] = "" + bundles["school_ends"] = "" + bundles["school_tour_ids"] = "" + + if len(bundles) == 0: + return bundles + + bundles[["first_child", "second_child", "third_child"]] = pd.DataFrame( + bundles["child_order"].to_list(), index=bundles.index + ).astype(int) + + # index needs to be unique for filtering below + original_idx = bundles.index + bundles = bundles.reset_index(drop=True) + + def join_attributes(df, column_names): + """ + Concatenate the values of the columns in column_names into a single string. + + e.g. bundle_child[1,2,3] contains person_ids of children in the bundle with -1 filled in for no child escorted, + Passing these into the function would return a series with the person_ids concatenated with '_' and leading and trailing underscores removed. + So if the first child escorted has id 200 and the second child escorted has id 300, the output would be "200_300" + + Parameters + ---------- + df : pandas.DataFrame + DataFrame containing the columns to be concatenated + column_names : list + List of column names to be concatenated + + Returns + ------- + pandas.Series + """ + + # intialize series with empty strings + out_series = pd.Series("", index=df.index) + # loop through all columns and concatenate the values + for col in column_names: + series = ( + df[col] + .fillna(-1) + .astype(int) + .astype(str) + .replace("-1", "", regex=False) + ) + out_series = out_series.str.cat(series, sep="_") + + # return series with leading and trailing underscores removed + return out_series.str.replace(r"^_+", "", regex=True).str.replace( + r"_+$", "", regex=True + ) + + # Loop through all possible combinations of child order + # once the order is known, we can fill in the escorting information in the child order + for first_child in [1, 2, 3]: + for second_child in [1, 2, 3]: + for third_child in [1, 2, 3]: + if ( + (first_child == second_child) + | (first_child == third_child) + | (second_child == third_child) + ): + # children order is not unique + continue + + filtered_bundles = bundles[ + (bundles.first_child == first_child) + & (bundles.second_child == second_child) + & (bundles.third_child == third_child) + ] + + if len(filtered_bundles) == 0: + # no bundles for this combination of child order + continue + + bundles.loc[filtered_bundles.index, "escortees"] = join_attributes( + filtered_bundles, + [ + f"bundle_child{first_child}", + f"bundle_child{second_child}", + f"bundle_child{third_child}", + ], + ) + + # escortee_nums contain the child number of the escortees concatenated with '_' + escortee_num1 = pd.Series( + np.where( + filtered_bundles[f"bundle_child{first_child}"] > 0, + first_child, + "", + ), + index=filtered_bundles.index, + ).astype(str) + escortee_num2 = pd.Series( + np.where( + filtered_bundles[f"bundle_child{second_child}"] > 0, + second_child, + "", + ), + index=filtered_bundles.index, + ).astype(str) + escortee_num3 = pd.Series( + np.where( + filtered_bundles[f"bundle_child{third_child}"] > 0, + third_child, + "", + ), + index=filtered_bundles.index, + ).astype(str) + bundles.loc[filtered_bundles.index, "escortee_nums"] = ( + (escortee_num1 + "_" + escortee_num2 + "_" + escortee_num3) + .str.replace(r"^_+", "", regex=True) + .str.replace(r"_+$", "", regex=True) + ) + + # num_escortees contain the number of escortees + bundles.loc[filtered_bundles.index, "num_escortees"] = ( + filtered_bundles[ + [ + f"bundle_child{first_child}", + f"bundle_child{second_child}", + f"bundle_child{third_child}", + ] + ] + > 0 + ).sum(axis=1) + + # school_destinations, school_starts, school_ends, and school_tour_ids are concatenated + bundles.loc[ + filtered_bundles.index, "school_destinations" + ] = join_attributes( + filtered_bundles, + [ + f"school_destination_child{first_child}", + f"school_destination_child{second_child}", + f"school_destination_child{third_child}", + ], + ) + + bundles.loc[filtered_bundles.index, "school_starts"] = join_attributes( + filtered_bundles, + [ + f"school_start_child{first_child}", + f"school_start_child{second_child}", + f"school_start_child{third_child}", + ], + ) + + bundles.loc[filtered_bundles.index, "school_ends"] = join_attributes( + filtered_bundles, + [ + f"school_end_child{first_child}", + f"school_end_child{second_child}", + f"school_end_child{third_child}", + ], + ) + + bundles.loc[ + filtered_bundles.index, "school_tour_ids" + ] = join_attributes( + filtered_bundles, + [ + f"school_tour_id_child{first_child}", + f"school_tour_id_child{second_child}", + f"school_tour_id_child{third_child}", + ], + ) + + bundles.drop(columns=["first_child", "second_child", "third_child"], inplace=True) + + return bundles.set_index(original_idx) + + +def create_column_as_concatenated_list(bundles, col_dict): + for col, data in col_dict.items(): + df = pd.concat( + [df.dropna() for df in data], axis=1, ignore_index=False + ).reindex(bundles.index) + bundles[col] = [row.dropna().values.tolist() for _, row in df.iterrows()] + return bundles + + +def create_chauf_trip_table(bundles): + bundles["dropoff"] = bundles["school_escort_direction"] == "outbound" + bundles["person_id"] = bundles["chauf_id"] + + original_index = bundles.index + bundles.reset_index(drop=True, inplace=True) participants = [] school_escort_trip_num = [] outbound = [] purposes = [] + destinations = [] - for i, child_id in enumerate(row["escortees"].split("_")): - if dropoff: - # have the remaining children in car - participants.append("_".join(row["escortees"].split("_")[i:])) - else: - # remaining children not yet in car - participants.append("_".join(row["escortees"].split("_")[: i + 1])) - school_escort_trip_num.append(i + 1) - outbound.append(determine_chauf_outbound_flag(row, i)) - purposes.append("escort") - - if not dropoff: - # adding trip home - outbound.append(False) - school_escort_trip_num.append(i + 2) - purposes.append("home") - row["destination"].append(row["home_zone_id"]) - # kids aren't in car until after they are picked up, inserting empty car for first trip - participants = [""] + participants - - if dropoff & (row["escort_type"] == "ride_share"): - # adding trip to work - outbound.append(True) - school_escort_trip_num.append(i + 2) - purposes.append(row["first_mand_tour_purpose"]) - row["destination"].append(row["first_mand_tour_dest"]) - # kids have already been dropped off - participants = participants + [""] - - row["escort_participants"] = participants - row["school_escort_trip_num"] = school_escort_trip_num - row["outbound"] = outbound - row["purpose"] = purposes - return row + for i in range(bundles["num_escortees"].max()): + dropoff_mask = (bundles["dropoff"] == True) & ( + bundles["num_escortees"] >= (i + 1) + ) + pickup_mask = (bundles["dropoff"] == False) & ( + bundles["num_escortees"] >= (i + 1) + ) + participants.append( + bundles.loc[dropoff_mask, "escortees"].str.split("_").str[i:].str.join("_") + ) + participants.append( + bundles.loc[pickup_mask, "escortees"] + .str.split("_") + .str[: i + 1] + .str.join("_") + ) + school_escort_trip_num.append( + pd.Series(index=bundles.loc[dropoff_mask | pickup_mask].index, data=(i + 1)) + ) + + outbound_flag = np.where( + # is outbound trip + ( + bundles.loc[dropoff_mask | pickup_mask, "school_escort_direction"] + == "outbound" + ) + # or chauf is going back to pick up the first child + | ( + (i == 0) + & ( + bundles.loc[dropoff_mask | pickup_mask, "escort_type"] + == "pure_escort" + ) + & ( + bundles.loc[dropoff_mask | pickup_mask, "school_escort_direction"] + == "inbound" + ) + ), + True, + # chauf is inbound and has already picked up a child or taken their mandatory tour + False, + ) + outbound.append( + pd.Series( + index=bundles.loc[dropoff_mask | pickup_mask].index, data=outbound_flag + ) + ) + + purposes.append( + pd.Series( + index=bundles.loc[dropoff_mask | pickup_mask].index, data="escort" + ) + ) + destinations.append( + bundles.loc[dropoff_mask | pickup_mask, "school_destinations"] + .str.split("_") + .str[i] + ) + + # adding trip home for inbound + inbound_mask = bundles["dropoff"] == False + outbound.append(pd.Series(index=bundles.loc[inbound_mask].index, data=False)) + school_escort_trip_num.append(bundles.loc[inbound_mask, "num_escortees"] + 1) + purposes.append(pd.Series(index=bundles.loc[inbound_mask].index, data="home")) + destinations.append(bundles.loc[inbound_mask, "home_zone_id"]) + # kids aren't in the car until after they are picked up, inserting empty car for first trip + participants.insert(0, pd.Series(index=bundles.loc[inbound_mask].index, data="")) + + # adding trip to work + to_work_mask = (bundles["dropoff"] == True) & ( + bundles["escort_type"] == "ride_share" + ) + outbound.append(pd.Series(index=bundles.loc[to_work_mask].index, data=True)) + school_escort_trip_num.append(bundles.loc[to_work_mask, "num_escortees"] + 1) + purposes.append(bundles.loc[to_work_mask, "first_mand_tour_purpose"]) + destinations.append(bundles.loc[to_work_mask, "first_mand_tour_dest"]) + # kids have already been dropped off + participants.append(pd.Series(index=bundles.loc[to_work_mask].index, data="")) + + bundles = create_column_as_concatenated_list( + bundles, + { + "destination": destinations, + "escort_participants": participants, + "school_escort_trip_num": school_escort_trip_num, + "outbound": outbound, + "purpose": purposes, + }, + ) + + bundles.drop(columns=["dropoff"], inplace=True) + bundles["person_id"] = bundles["person_id"].fillna(-1).astype("int64") + + bundles.index = original_index + return bundles def create_chauf_escort_trips(bundles): - chauf_trip_bundles = bundles.apply(lambda row: create_chauf_trip_table(row), axis=1) + chauf_trip_bundles = create_chauf_trip_table(bundles.copy()) chauf_trip_bundles["tour_id"] = bundles["chauf_tour_id"].astype(int) # departure time is the first school start in the outbound school_escort_direction and the last school end in the inbound school_escort_direction @@ -158,71 +411,117 @@ def create_chauf_escort_trips(bundles): return chauf_trips -def create_child_escorting_stops(row, escortee_num): - escortees = row["escortees"].split("_") - if escortee_num > (len(escortees) - 1): - # this bundle does not have this many escortees - return row - dropoff = True if row["school_escort_direction"] == "outbound" else False +def create_child_escorting_stops(bundles, escortee_num): + bundles["num_escortees"] = bundles["escortees"].str.split("_").str.len() - row["person_id"] = int(escortees[escortee_num]) - row["tour_id"] = row["school_tour_ids"].split("_")[escortee_num] - school_dests = row["school_destinations"].split("_") + # only want the escortee bundles where the escortee_num is less than the number of escortees + if (escortee_num > (bundles["num_escortees"] - 1)).all(): + return bundles + bundles = bundles[escortee_num <= (bundles["num_escortees"] - 1)] + original_index = bundles.index + bundles.reset_index(drop=True, inplace=True) - destinations = [] - purposes = [] + # intializing variables + bundles["dropoff"] = bundles["school_escort_direction"] == "outbound" + bundles["person_id"] = bundles["escortees"].str.split("_").str[escortee_num] + bundles["tour_id"] = bundles["school_tour_ids"].str.split("_").str[escortee_num] participants = [] + purposes = [] + destinations = [] school_escort_trip_num = [] - escortee_order = ( - escortees[: escortee_num + 1] if dropoff else escortees[escortee_num:] - ) - - # for i, child_id in enumerate(escortees[:escortee_num+1]): - for i, child_id in enumerate(escortee_order): - is_last_stop = i == len(escortee_order) - 1 - - if dropoff: - # dropping childen off - # children in car are the child and the children after - participants.append("_".join(escortees[i:])) - dest = school_dests[i] - purpose = "school" if row["person_id"] == int(child_id) else "escort" - - else: - # picking children up - # children in car are the child and those already picked up - participants.append("_".join(escortees[: escortee_num + i + 1])) - # going home if last stop, otherwise to next school destination - dest = ( - row["home_zone_id"] - if is_last_stop - else school_dests[escortee_num + i + 1] + # looping up through the current escorting destination + for i in range(escortee_num + 1): + # dropping off children + dropoff_mask = (bundles["dropoff"] == True) & ( + bundles["num_escortees"] >= (i + 1) + ) + participants.append( + bundles.loc[dropoff_mask, "escortees"].str.split("_").str[i:].str.join("_") + ) + destinations.append( + bundles.loc[dropoff_mask, "school_destinations"].str.split("_").str[i] + ) + purposes.append( + pd.Series( + index=bundles.loc[dropoff_mask].index, + data=np.where( + bundles.loc[dropoff_mask, "person_id"] + == bundles.loc[dropoff_mask, "escortees"].str.split("_").str[i], + "school", + "escort", + ), ) - purpose = "home" if is_last_stop else "escort" + ) + school_escort_trip_num.append( + pd.Series(index=bundles.loc[dropoff_mask].index, data=(i + 1)) + ) - # filling arrays - destinations.append(dest) - school_escort_trip_num.append(i + 1) - purposes.append(purpose) + # picking up children after the current child, i.e. escortees[escortee_num:] + bundles["pickup_count"] = np.where( + bundles["num_escortees"] >= escortee_num, + bundles["escortees"].str.split("_").str[escortee_num:], + 0, + ) + for i in range(bundles["pickup_count"].str.len().max()): + pickup_mask = (bundles["dropoff"] == False) & ( + bundles["pickup_count"].str[i].isna() == False + ) + participants.append( + bundles.loc[pickup_mask, "escortees"] + .str.split("_") + .str[: escortee_num + i + 1] + .str.join("_") + ) + is_last_stop = i == (bundles.loc[pickup_mask, "pickup_count"].str.len() - 1) + destinations.append( + pd.Series( + index=bundles.loc[pickup_mask].index, + data=np.where( + is_last_stop, + bundles.loc[pickup_mask, "home_zone_id"], + bundles.loc[pickup_mask, "school_destinations"] + .str.split("_") + .str[escortee_num + i + 1], + ), + ) + ) + purposes.append( + pd.Series( + index=bundles.loc[pickup_mask].index, + data=np.where(is_last_stop, "home", "escort"), + ) + ) + + school_escort_trip_num.append( + pd.Series(index=bundles.loc[pickup_mask].index, data=(i + 1)) + ) + + bundles = create_column_as_concatenated_list( + bundles, + { + "escort_participants": participants, + "school_escort_trip_num": school_escort_trip_num, + "purpose": purposes, + "destination": destinations, + }, + ) + + bundles.drop(columns=["dropoff", "pickup_count"], inplace=True) + bundles["person_id"] = bundles["person_id"].fillna(-1).astype("int64") - row["escort_participants"] = participants - row["school_escort_trip_num"] = school_escort_trip_num - row["purpose"] = purposes - row["destination"] = destinations - return row + bundles.index = original_index + return bundles def create_escortee_trips(bundles): escortee_trips = [] for escortee_num in range(0, int(bundles.num_escortees.max()) + 1): - escortee_bundles = bundles.apply( - lambda row: create_child_escorting_stops(row, escortee_num), axis=1 - ) + escortee_bundles = create_child_escorting_stops(bundles.copy(), escortee_num) escortee_trips.append(escortee_bundles) escortee_trips = pd.concat(escortee_trips) - escortee_trips = escortee_trips[~escortee_trips.person_id.isna()] + escortee_trips = escortee_trips[escortee_trips.person_id > 0] # departure time is the first school start in the outbound direction and the last school end in the inbound direction starts = escortee_trips["school_starts"].str.split("_", expand=True).astype(float) @@ -349,9 +648,11 @@ def process_tours_after_escorting_model(state: workflow.State, escort_bundles, t tours = add_school_escorting_type_to_tours_table(escort_bundles, tours) # setting number of escortees on tour - num_escortees = escort_bundles.drop_duplicates("chauf_tour_id").set_index( - "chauf_tour_id" - )["num_escortees"] + num_escortees = ( + escort_bundles.drop_duplicates("chauf_tour_id") + .set_index("chauf_tour_id")["num_escortees"] + .astype(int) + ) tours.loc[num_escortees.index, "num_escortees"] = num_escortees # set same start / end time for tours if they are bundled together diff --git a/activitysim/abm/models/util/test/data/create_bundle_attributes_inbound__input.pkl b/activitysim/abm/models/util/test/data/create_bundle_attributes_inbound__input.pkl new file mode 100644 index 000000000..edf27860a Binary files /dev/null and b/activitysim/abm/models/util/test/data/create_bundle_attributes_inbound__input.pkl differ diff --git a/activitysim/abm/models/util/test/data/create_bundle_attributes_inbound__output.pkl b/activitysim/abm/models/util/test/data/create_bundle_attributes_inbound__output.pkl new file mode 100644 index 000000000..5481a671c Binary files /dev/null and b/activitysim/abm/models/util/test/data/create_bundle_attributes_inbound__output.pkl differ diff --git a/activitysim/abm/models/util/test/data/create_bundle_attributes_outbound_cond__input.pkl b/activitysim/abm/models/util/test/data/create_bundle_attributes_outbound_cond__input.pkl new file mode 100644 index 000000000..ea883e9a9 Binary files /dev/null and b/activitysim/abm/models/util/test/data/create_bundle_attributes_outbound_cond__input.pkl differ diff --git a/activitysim/abm/models/util/test/data/create_bundle_attributes_outbound_cond__output.pkl b/activitysim/abm/models/util/test/data/create_bundle_attributes_outbound_cond__output.pkl new file mode 100644 index 000000000..811d04eee Binary files /dev/null and b/activitysim/abm/models/util/test/data/create_bundle_attributes_outbound_cond__output.pkl differ diff --git a/activitysim/abm/models/util/test/data/create_chauf_trip_table__input.pkl b/activitysim/abm/models/util/test/data/create_chauf_trip_table__input.pkl new file mode 100644 index 000000000..034339e99 Binary files /dev/null and b/activitysim/abm/models/util/test/data/create_chauf_trip_table__input.pkl differ diff --git a/activitysim/abm/models/util/test/data/create_chauf_trip_table__output.pkl b/activitysim/abm/models/util/test/data/create_chauf_trip_table__output.pkl new file mode 100644 index 000000000..4d97e9a95 Binary files /dev/null and b/activitysim/abm/models/util/test/data/create_chauf_trip_table__output.pkl differ diff --git a/activitysim/abm/models/util/test/data/create_child_escorting_stops__input.pkl b/activitysim/abm/models/util/test/data/create_child_escorting_stops__input.pkl new file mode 100644 index 000000000..034339e99 Binary files /dev/null and b/activitysim/abm/models/util/test/data/create_child_escorting_stops__input.pkl differ diff --git a/activitysim/abm/models/util/test/data/create_child_escorting_stops__output.pkl b/activitysim/abm/models/util/test/data/create_child_escorting_stops__output.pkl new file mode 100644 index 000000000..4926c489d Binary files /dev/null and b/activitysim/abm/models/util/test/data/create_child_escorting_stops__output.pkl differ diff --git a/activitysim/abm/models/util/test/test_school_escorting_utils.py b/activitysim/abm/models/util/test/test_school_escorting_utils.py new file mode 100644 index 000000000..5a007952a --- /dev/null +++ b/activitysim/abm/models/util/test/test_school_escorting_utils.py @@ -0,0 +1,76 @@ +# ActivitySim +# See full license in LICENSE.txt. +import os +from ast import literal_eval +import pandas as pd +import numpy as np +import pandas.testing as pdt + +from activitysim.abm.models.util.school_escort_tours_trips import ( + create_bundle_attributes, + create_child_escorting_stops, + create_chauf_trip_table, +) + + +def test_create_bundle_attributes(): + data_dir = os.path.join(os.path.dirname(__file__), "data") + + inbound_input = pd.read_pickle( + os.path.join(data_dir, "create_bundle_attributes_inbound__input.pkl") + ) + inbound_expected = pd.read_pickle( + os.path.join(data_dir, "create_bundle_attributes_inbound__output.pkl") + ) + + outbound_input = pd.read_pickle( + os.path.join(data_dir, "create_bundle_attributes_outbound_cond__input.pkl") + ) + outbound_expected = pd.read_pickle( + os.path.join(data_dir, "create_bundle_attributes_outbound_cond__output.pkl") + ) + inbound_result = create_bundle_attributes(inbound_input) + pdt.assert_frame_equal(inbound_result, inbound_expected, check_dtype=False) + + outbound_result = create_bundle_attributes(outbound_input) + pdt.assert_frame_equal(outbound_result, outbound_expected, check_dtype=False) + + +def test_create_chauf_trip_table(): + data_dir = os.path.join(os.path.dirname(__file__), "data") + bundles = pd.read_pickle( + os.path.join(data_dir, "create_chauf_trip_table__input.pkl") + ) + chauf_trip_bundles = create_chauf_trip_table(bundles.copy()) + + chauf_trip_bundles_expected = pd.read_pickle( + os.path.join(data_dir, "create_chauf_trip_table__output.pkl") + ) + + pdt.assert_frame_equal(chauf_trip_bundles, chauf_trip_bundles_expected) + + +def test_create_child_escorting_stops(): + data_dir = os.path.join(os.path.dirname(__file__), "data") + bundles = pd.read_pickle( + os.path.join(data_dir, "create_child_escorting_stops__input.pkl") + ) + + escortee_trips = [] + for escortee_num in range(0, int(bundles.num_escortees.max()) + 1): + escortee_bundles = create_child_escorting_stops(bundles.copy(), escortee_num) + escortee_trips.append(escortee_bundles) + + escortee_trips = pd.concat(escortee_trips) + + escortee_trips_expected = pd.read_pickle( + os.path.join(data_dir, "create_child_escorting_stops__output.pkl") + ) + + pdt.assert_frame_equal(escortee_trips, escortee_trips_expected) + + +if __name__ == "__main__": + test_create_bundle_attributes() + test_create_chauf_trip_table() + test_create_child_escorting_stops() diff --git a/activitysim/examples/prototype_mtc_extended/test/prototype_mtc_extended_reference_pipeline.zip b/activitysim/examples/prototype_mtc_extended/test/prototype_mtc_extended_reference_pipeline.zip index 9dda15936..62fa47d65 100644 Binary files a/activitysim/examples/prototype_mtc_extended/test/prototype_mtc_extended_reference_pipeline.zip and b/activitysim/examples/prototype_mtc_extended/test/prototype_mtc_extended_reference_pipeline.zip differ