@@ -340,6 +340,36 @@ def filter_trips(
340340 self .shapes = self .feed .shapes [self .feed .shapes .shape_id .isin (shape_ids )]
341341
342342 logger .info (f"Filtered to { len (self .trips )} trips and { len (shape_ids )} shapes" )
343+
344+ # Add trip durations
345+ st_incl = self .feed .stop_times [
346+ self .feed .stop_times ["trip_id" ].isin (self .trips ["trip_id" ].unique ())
347+ ]
348+ trip_times = st_incl .groupby ("trip_id" ).agg (
349+ start_time = ("arrival_time" , "min" ), end_time = ("arrival_time" , "max" )
350+ )
351+ trip_times ["trip_duration_minutes" ] = (
352+ trip_times ["end_time" ] - trip_times ["start_time" ]
353+ ).dt .total_seconds () / 60
354+ self .trips = self .trips .merge (
355+ trip_times [["trip_duration_minutes" ]],
356+ left_on = "trip_id" ,
357+ right_index = True ,
358+ )
359+
360+ # Add number of times each trip is run
361+ sid_counts = (
362+ self .feed .get_service_ids_all_dates ()
363+ .groupby ("service_id" )["date" ]
364+ .count ()
365+ .rename ("trip_count" )
366+ )
367+ self .trips = self .trips .merge (
368+ sid_counts ,
369+ left_on = "service_id" ,
370+ right_index = True ,
371+ )
372+
343373 return self
344374
345375 def add_mid_block_deadhead (self ) -> Self :
@@ -681,7 +711,7 @@ def predict_energy(
681711 # Aggregate to trip level
682712 trip_results = self ._aggregate_predictions_by_trip (link_results , str (model ))
683713
684- # Optionally add HVAC
714+ # Optionally add HVAC to trip-level results
685715 if add_hvac :
686716 logger .info ("Adding HVAC energy impacts..." )
687717 if self .feed is None or self .trips .empty :
@@ -690,6 +720,8 @@ def predict_energy(
690720 )
691721 hvac_energy = add_HVAC_energy (self .feed , self .trips )
692722 trip_results = trip_results .merge (hvac_energy , on = "trip_id" , how = "left" )
723+ # Add HVAC energy to powertrain energy
724+ trip_results ["energy_used" ] += trip_results ["hvac_energy_kWh" ]
693725
694726 # Store results
695727 self .energy_predictions [f"{ model } _link" ] = link_results
@@ -790,17 +822,33 @@ def _aggregate_predictions_by_trip(
790822 Returns:
791823 DataFrame with trip-level aggregated results
792824 """
825+ vehicle_units = {
826+ "Transit_Bus_Diesel" : {
827+ "routee_unit" : "gallons" ,
828+ "unit_name" : "gallons_diesel" ,
829+ },
830+ "Transit_Bus_Battery_Electric" : {"routee_unit" : "kWhs" , "unit_name" : "kWh" },
831+ }
832+
793833 # Determine which energy columns to aggregate
794- agg_cols = [c for c in ["gallons" , "kWhs" ] if c in link_results .columns ]
834+ agg_col = vehicle_units [vehicle_name ]["routee_unit" ]
835+
836+ if agg_col not in link_results .columns :
837+ raise ValueError (
838+ f"No energy prediction found with unit { agg_col } for { vehicle_name } ."
839+ f"Results columns: { link_results .columns } "
840+ )
795841
796842 energy_by_trip = link_results .groupby ("trip_id" ).agg (
797- {"kilometers" : "sum" , ** { c : "sum" for c in agg_cols } }
843+ {"kilometers" : "sum" , agg_col : "sum" }
798844 )
799845
800846 energy_by_trip ["miles" ] = MI_PER_KM * energy_by_trip ["kilometers" ]
801847 energy_by_trip ["vehicle" ] = vehicle_name
848+ energy_by_trip ["energy_used" ] = energy_by_trip [agg_col ]
849+ energy_by_trip ["energy_unit" ] = vehicle_units [vehicle_name ]["unit_name" ]
802850
803- return energy_by_trip .drop (columns = "kilometers" )
851+ return energy_by_trip .drop (columns = [ "kilometers" , agg_col ] )
804852
805853 def get_link_predictions (self , vehicle_model : str | None = None ) -> pd .DataFrame :
806854 """
@@ -883,7 +931,7 @@ def save_results(
883931 # Save trip-level predictions
884932 if "trip" in self .energy_predictions :
885933 trip_path = output_path / "trip_energy_predictions.csv"
886- self .energy_predictions ["trip" ].to_csv (trip_path )
934+ self .energy_predictions ["trip" ].to_csv (trip_path , index = False )
887935 logger .info (f"Saved trip predictions to { trip_path } " )
888936
889937 # Save RouteE inputs
0 commit comments