diff --git a/landbosse/excelio/XlsxReader.py b/landbosse/excelio/XlsxReader.py index 803c761d..d5cfc9e8 100644 --- a/landbosse/excelio/XlsxReader.py +++ b/landbosse/excelio/XlsxReader.py @@ -436,6 +436,11 @@ def create_master_input_dictionary(self, project_data_dataframes, project_parame incomplete_input_dict['gust_velocity_m_per_s'] = project_parameters['50-year Gust Velocity (m/s)'] incomplete_input_dict['project_size_megawatts'] = project_parameters['Number of turbines'] * project_parameters['Turbine rating MW'] + if project_parameters['Calculate road cost for distributed wind? (y/n)'] == 'y': + incomplete_input_dict['road_distributed_wind'] = True + else: + incomplete_input_dict['road_distributed_wind'] = False + incomplete_input_dict['site_prep_area_m2'] = project_parameters['Site prep area for Distributed wind (m2)'] incomplete_input_dict['road_length_adder_m'] = project_parameters['Road length adder (m)'] incomplete_input_dict['fraction_new_roads'] = project_parameters['Percent of roads that will be constructed'] incomplete_input_dict['road_quality'] = project_parameters['Road Quality (0-1)'] @@ -483,7 +488,7 @@ def create_master_input_dictionary(self, project_data_dataframes, project_parame incomplete_input_dict['overtime_multiplier'] = project_parameters['Overtime multiplier'] incomplete_input_dict['allow_same_flag'] = True if project_parameters['Allow same flag'] == 'y' else False - override_total_mgmt_cost_col_name = 'Override total management cost (0 does not override)' + override_total_mgmt_cost_col_name = 'Override total management cost for distributed (0 does not override)' if override_total_mgmt_cost_col_name in project_parameters and project_parameters[override_total_mgmt_cost_col_name] > 0: incomplete_input_dict['override_total_management_cost'] = \ project_parameters[override_total_mgmt_cost_col_name] diff --git a/landbosse/model/CollectionCost.py b/landbosse/model/CollectionCost.py index 98039e7b..90f237b6 100644 --- a/landbosse/model/CollectionCost.py +++ b/landbosse/model/CollectionCost.py @@ -192,7 +192,7 @@ def calc_num_turb_per_cable(self, addl_inputs): upstream_turb = addl_inputs['upstream_turb'] self.turb_sequence = addl_inputs['turb_sequence'] - self.num_turb_per_cable = self.max_turb_per_cable - upstream_turb # todo: add to ouptut csv + self.num_turb_per_cable = self.max_turb_per_cable - upstream_turb if upstream_turb == 0: self.downstream_connection = -1 @@ -216,7 +216,7 @@ def calc_array_cable_len(self, addl_inputs): self.calc_turb_section_len(turbine_spacing_rotor_diameters, rotor_diameter_m) - self.array_cable_len = ((self.num_turb_per_cable + self.downstream_connection) * self.turb_section_length) # todo: add to output csv + self.array_cable_len = ((self.num_turb_per_cable + self.downstream_connection) * self.turb_section_length) # @staticmethod def calc_turb_section_len(self, turbine_spacing_rotor_diameters, rotor_diameter_m): @@ -280,8 +280,10 @@ def __init__(self, input_dict, output_dict, project_name): self.output_dict['total_cable_len_km'] = 0 self._km_to_LF = 0.0003048 #Units: [km/LF] Conversion factor for converting from km to linear foot. self._total_cable_cost = 0 - + self._total_turbine_counter = 0 + self.turbines_on_cable = [] self._cable_length_km = dict() + self.check_terminal = 0 def calc_num_strings(self): @@ -326,7 +328,7 @@ def calc_num_strings(self): # Calculate number of full strings and any remainder required to # support the total number of turbines - self.output_dict['num_full_strings'] = np.floor(self.output_dict['total_turb'] / self.output_dict['total_turb_per_string']) # todo: add to output csv + self.output_dict['num_full_strings'] = np.floor(self.output_dict['total_turb'] / self.output_dict['total_turb_per_string']) self.output_dict['num_leftover_turb'] = self.output_dict['total_turb'] % self.output_dict['total_turb_per_string'] # Calculate number of turbines on a remaining partial string @@ -345,7 +347,7 @@ def calc_num_strings(self): self.output_dict['num_partial_strings'] = 0 self.output_dict['perc_partial_string'] = np.zeros(len(self.output_dict['num_turb_per_cable'])) - # todo: output number of partial strings + return (self.output_dict['total_turb_per_string'], self.output_dict['num_full_strings'], self.output_dict['num_partial_strings'], self.output_dict['perc_partial_string'], self.output_dict['num_turb_per_cable']) @@ -400,7 +402,7 @@ def calc_num_turb_partial_strings(self, num_leftover_turb, num_turb_per_cable): #TODO: change length_to_substation calculation as a user defined input? @staticmethod - def calc_cable_len_to_substation(turbine_spacing_rotor_diameters, row_spacing_rotor_diameters, + def calc_cable_len_to_substation(distance_to_grid, turbine_spacing_rotor_diameters, row_spacing_rotor_diameters, num_strings): """ Calculate the distance for the largest cable run to substation @@ -423,36 +425,45 @@ def calc_cable_len_to_substation(turbine_spacing_rotor_diameters, row_spacing_ro to substation, km """ - # Define spacing terms for even or odd number of strings - # Even number: substation centered between middle two strings - # Odd number : substation centered on middle string - if (num_strings % 2) == 0: - n_max = int(num_strings / 2) - turb_space_scaling = 0.5 - range_strings = range(1, n_max + 1) - else: - n_max = int((num_strings - 1) / 2) - turb_space_scaling = 1 - range_strings = range(n_max + 1) - - # Calculate hypotenuse length of each string to substation string_to_substation_length = [] - for idx in range_strings: - if idx == 0: - c = 1 + + if num_strings > 1: + # Define spacing terms for even or odd number of strings + # Even number: substation centered between middle two strings + # Odd number : substation centered on middle string + if (num_strings % 2) == 0: + n_max = int(num_strings / 2) + turb_space_scaling = 0.5 + range_strings = range(1, n_max + 1) + else: - c = 2 - string_to_substation_length.append(c * np.sqrt(row_spacing_rotor_diameters ** 2 + - (turb_space_scaling * idx * - turbine_spacing_rotor_diameters) ** 2)) + n_max = int((num_strings - 1) / 2) + turb_space_scaling = 1 + range_strings = range(n_max + 1) + + # Calculate hypotenuse length of each string to substation + for idx in range_strings: + if idx == 0: + c = 1 + else: + c = 2 + string_to_substation_length.append(c * np.sqrt(row_spacing_rotor_diameters ** 2 + + (turb_space_scaling * idx * + turbine_spacing_rotor_diameters) ** 2)) + + else: + string_to_substation_length.append(distance_to_grid) # Sum up total length to substation len_to_substation = np.sum(string_to_substation_length) - return len_to_substation # todo: add to output csv + return len_to_substation + + #TODO: Add parameter info in docstrings @staticmethod - def calc_total_cable_length(cable, cable_specs, num_full_strings, num_partial_strings, len_to_substation, perc_partial_string): + def calc_total_cable_length(total_turbines, count, check_terminal, turbines_per_cable, cable, cable_specs, + num_full_strings, num_partial_strings, len_to_substation, perc_partial_string): """ Calculate total length of each cable type, km @@ -479,18 +490,69 @@ def calc_total_cable_length(cable, cable_specs, num_full_strings, num_partial_st Total length of individual cable type """ - if cable.turb_sequence == len(cable_specs): - # Only add len_to_substation to the final cable in the string - total_cable_len = (num_full_strings * cable.array_cable_len + num_partial_strings * (cable.array_cable_len * perc_partial_string) + len_to_substation) - else: - total_cable_len = (num_full_strings * cable.array_cable_len + num_partial_strings * (cable.array_cable_len * perc_partial_string)) + # If terminal cable has already been accounted for, skip any + # calculations for other cables. + if (cable.turb_sequence - 1) > check_terminal: + cable.array_cable_len = 0 + cable.total_length = 0 + cable.num_turb_per_cable = 0 + return 0, 0 - return total_cable_len # todo: add to output csv + # If num full strings < = 1, find which cable the final turbine + # is on, and calculate total cable length (including the len to + # substation) using that cable. - def create_ArraySystem(self): + # This 'elif' is essentially a switch for distributed wind: + elif num_full_strings < 1 and num_partial_strings >= 0: + + # if number of turbines is less than total string capacity, + # find the terminal cable and find total cable len + # up till that cable. + + # If total turbines in project are less than cumulative turbines + # up till and including that cable. + + terminal_string = cable.turb_sequence - 1 # Flag this cable as it is + # also the actual terminal cable + + if (cable.turb_sequence - 1) == 0: # That is, if cable # 1 can hold + # more turbines than specified by user, it is the terminal cable + cable.num_turb_per_cable = total_turbines + cable.array_cable_len = ((cable.num_turb_per_cable + cable.downstream_connection) + * cable.turb_section_length) - #data used in parent classes: + total_cable_len = ((num_full_strings * cable.array_cable_len) + + (num_partial_strings * cable.array_cable_len)) + len_to_substation + + else: + + cable.num_turb_per_cable = total_turbines - turbines_per_cable[(count - 1)] + cable.array_cable_len = ((cable.num_turb_per_cable + cable.downstream_connection) * + cable.turb_section_length) + + total_cable_len = ((num_full_strings * cable.array_cable_len) + + (num_partial_strings * cable.array_cable_len)) + len_to_substation + + return total_cable_len, terminal_string + + else: # Switch for utility scale landbosse + if cable.turb_sequence == len(cable_specs): + + # Only add len_to_substation to the final cable in the string + total_cable_len = (num_full_strings * cable.array_cable_len + + num_partial_strings * (cable.array_cable_len * perc_partial_string) + + len_to_substation) + else: + total_cable_len = (num_full_strings * cable.array_cable_len + + num_partial_strings * (cable.array_cable_len * perc_partial_string)) + + # here 9999 == flag to announce that the terminal cable has NOT been reached + # and to continue calculations for each cable + return total_cable_len, 9999 + + def create_ArraySystem(self): + # data used in parent classes: self.addl_specs = dict() self.addl_specs['turbine_rating_MW'] = self.input_dict['turbine_rating_MW'] self.addl_specs['upstream_turb'] = 0 @@ -512,7 +574,7 @@ def create_ArraySystem(self): # Loops through all user defined array cable types, composing them # in ArraySystem - # TODO: Sort input cable types by ascending current capacity + self.cables = {} self.input_dict['cable_specs'] = self.input_dict['cable_specs_pd'].T.to_dict() @@ -532,39 +594,70 @@ def create_ArraySystem(self): self.addl_specs['upstream_turb'] += cable.num_turb_per_cable self.addl_specs['turb_sequence'] += 1 - - # Calculate number of required strings to support plant capacity - self.output_dict['turb_per_string'], self.output_dict['num_full_strings'], self.output_dict['num_partial_strings'], self.output_dict['perc_partial_string'], self.output_dict['num_turb_per_cable'] = self.calc_num_strings() + # Calculate number of required strings to support plant capacity + self.output_dict['turb_per_string'], \ + self.output_dict['num_full_strings'], \ + self.output_dict['num_partial_strings'], \ + self.output_dict['perc_partial_string'], \ + self.output_dict['num_turb_per_cable'] = self.calc_num_strings() # Calculate total length of cable run to substation - self.output_dict['num_strings'] = self.output_dict['num_full_strings'] + self.output_dict['num_partial_strings'] - - - if self.input_dict['user_defined_home_run_trench'] == 0: - self.output_dict['trench_len_to_substation_km'] = self.calc_cable_len_to_substation(self.input_dict['turbine_spacing_rotor_diameters'], self.input_dict['row_spacing_rotor_diameters'], self.output_dict['num_strings']) + self.output_dict['num_strings'] = self.output_dict[ + 'num_full_strings'] + self.output_dict['num_partial_strings'] + + if self.input_dict['user_defined_distance_to_grid_connection'] == 0: # where (0 = No) and (1 = Yes) + + # This only gets used if number of strings is <= 1 : + distributed_wind_distance_to_grid = (self.input_dict[ + 'turbine_spacing_rotor_diameters'] * self.input_dict['rotor_diameter_m']) / 1000 + self.output_dict['distance_to_grid_connection_km'] = self.\ + calc_cable_len_to_substation(distributed_wind_distance_to_grid, + self.input_dict['turbine_spacing_rotor_diameters'], + self.input_dict['row_spacing_rotor_diameters'], + self.output_dict['num_strings']) else: - self.output_dict['trench_len_to_substation_km'] = self.input_dict['trench_len_to_substation_km'] + self.output_dict['distance_to_grid_connection_km'] = self.input_dict['distance_to_grid_connection_km'] + + self.output_dict['cable_len_to_grid_connection_km'] = self.output_dict[ + 'distance_to_grid_connection_km'] # assumes 3 conductors and fiber and neutral - self.output_dict['cable_len_to_substation_km'] = self.output_dict['trench_len_to_substation_km'] # assumes 3 conductors and fiber and neutral + cable_sequence = 0 + # Make a list of how many turbines per cable + for _, (name, cable) in enumerate(self.cables.items()): + if cable_sequence == 0: + self.turbines_on_cable.append(cable.num_turb_per_cable) + else: + self.turbines_on_cable.append(cable.num_turb_per_cable + self.turbines_on_cable[(cable_sequence - 1)]) + # turbines_on_cable[cable_sequence] += cable.num_turb_per_cable + cable_sequence += 1 + self.__turbines_on_cable = self.turbines_on_cable # Calculate total length of each cable type, and total cost that calculated length of cable: + count = 0 for idx, (name, cable) in enumerate(self.cables.items()): - cable_specs = self.input_dict['cable_specs'] - num_full_strings = self.output_dict['num_full_strings'] - num_partial_strings = self.output_dict['num_partial_strings'] - trench_len_to_substation_km = self.output_dict['trench_len_to_substation_km'] - perc_partial_string = self.output_dict['perc_partial_string'][idx] - total_cable_len = self.calc_total_cable_length(cable, cable_specs, num_full_strings, num_partial_strings, - trench_len_to_substation_km, perc_partial_string) - - self._cable_length_km[name] = total_cable_len - #self.__cable_cost_usd[name] = cable.__dict__['cost'] - + total_cable_len, self.check_terminal = self.calc_total_cable_length(self.output_dict['total_turb'], count, + self.check_terminal, + self.__turbines_on_cable, + cable, self.input_dict['cable_specs'], + self.output_dict['num_full_strings'], + self.output_dict['num_partial_strings'], + self.output_dict[ + 'distance_to_grid_connection_km'], + self.output_dict['perc_partial_string'][ + idx], + ) + count += 1 + # self._total_turbine_counter = turbine_tally + self._cable_length_km[name] = total_cable_len cable.total_length = total_cable_len self.output_dict['total_cable_len_km'] += total_cable_len - # cable.total_mass = total_cable_len * cable.mass - cable.total_cost = (total_cable_len / self._km_to_LF)* cable.cost - self._total_cable_cost+=cable.total_cost #Keep running tally of total cable cost used in wind farm. + cable.total_cost = (total_cable_len / self._km_to_LF) * cable.cost + self._total_cable_cost += cable.total_cost # Keep running tally of total cable cost used in wind farm. + + # Repopulate the turbines per cable sequence to make sure it reflects any changes that happened since + # the first time this sequence was populated. + self.output_dict['num_turb_per_cable'] = [cable.num_turb_per_cable for cable in self.cables.values()] + self.output_dict['total_turb_per_string'] = sum(self.output_dict['num_turb_per_cable']) def calculate_trench_properties(self, trench_properties_input, trench_properties_output): """ @@ -572,7 +665,7 @@ def calculate_trench_properties(self, trench_properties_input, trench_properties """ # units of cubic meters - trench_properties_output['trench_length_km'] = trench_properties_output['total_cable_len_km'] # todo: add to output csv + trench_properties_output['trench_length_km'] = trench_properties_output['total_cable_len_km'] def calculate_weather_delay(self, weather_delay_input_data, weather_delay_output_data): """Calculates wind delays for roads""" @@ -590,7 +683,7 @@ def calculate_weather_delay(self, weather_delay_input_data, weather_delay_output def estimate_construction_time(self, construction_time_input_data, construction_time_output_data): """ - Function to estimate construction time on per turbine basis. TODO: What's a better definition of this function. It's task is to return a pd.DataFrame (operation_data). + Function to estimate construction time on per turbine basis. Parameters ------- @@ -615,7 +708,16 @@ def estimate_construction_time(self, construction_time_input_data, construction_ throughput_operations = construction_time_input_data['rsmeans'] trench_length_km = construction_time_output_data['trench_length_km'] - operation_data = throughput_operations.where(throughput_operations['Module'] == 'Collection').dropna(thresh=4) + if construction_time_input_data['turbine_rating_MW'] >= 0.1: + operation_data = throughput_operations.where(throughput_operations['Module'] == 'Collection').dropna( + thresh=4) + # from rsmeans data, only read in Collection related data and filter out the rest: + cable_trenching = throughput_operations[throughput_operations.Module == 'Collection'] + else: #switch for small DW + operation_data = throughput_operations.where( + throughput_operations['Module'] == 'Small DW Collection').dropna(thresh=4) + # from rsmeans data, only read in Collection related data and filter out the rest: + cable_trenching = throughput_operations[throughput_operations.Module == 'Small DW Collection'] # operation_data = pd.merge() # from rsmeans data, only read in Collection related data and filter out the rest: @@ -655,15 +757,20 @@ def estimate_construction_time(self, construction_time_input_data, construction_ operation_data['Time construct days'] = operation_data[['time_construct_bool', 'Number of days taken by single crew']].min(axis=1) num_days = operation_data['Time construct days'].max() - # pull out management data - crew_cost = self.input_dict['crew_cost'] - crew = self.input_dict['crew'][self.input_dict['crew']['Crew type ID'].str.contains('M0')] - management_crew = pd.merge(crew_cost, crew, on=['Labor type ID']) - management_crew = management_crew.assign(per_diem_total=management_crew['Per diem USD per day'] * management_crew['Number of workers'] * num_days) - management_crew = management_crew.assign(hourly_costs_total=management_crew['Hourly rate USD per hour'] * self.input_dict['hour_day'][self.input_dict['time_construct']] * num_days) - management_crew = management_crew.assign(total_crew_cost_before_wind_delay=management_crew['per_diem_total'] + management_crew['hourly_costs_total']) - self.output_dict['management_crew'] = management_crew - self.output_dict['managament_crew_cost_before_wind_delay']= management_crew['total_crew_cost_before_wind_delay'].sum() + + # No 'management crew' in small DW + if construction_time_input_data['turbine_rating_MW'] >= 0.1: + # pull out management data + crew_cost = self.input_dict['crew_cost'] + crew = self.input_dict['crew'][self.input_dict['crew']['Crew type ID'].str.contains('M0')] + management_crew = pd.merge(crew_cost, crew, on=['Labor type ID']) + management_crew = management_crew.assign(per_diem_total=management_crew['Per diem USD per day'] * management_crew['Number of workers'] * num_days) + management_crew = management_crew.assign(hourly_costs_total=management_crew['Hourly rate USD per hour'] * self.input_dict['hour_day'][self.input_dict['time_construct']] * num_days) + management_crew = management_crew.assign(total_crew_cost_before_wind_delay=management_crew['per_diem_total'] + management_crew['hourly_costs_total']) + self.output_dict['management_crew'] = management_crew + self.output_dict['managament_crew_cost_before_wind_delay'] = management_crew['total_crew_cost_before_wind_delay'].sum() + else: + self.output_dict['managament_crew_cost_before_wind_delay'] = 0.0 construction_time_output_data['operation_data_id_days_crews_workers'] = operation_data_id_days_crews_workers construction_time_output_data['operation_data_entire_farm'] = operation_data @@ -671,7 +778,6 @@ def estimate_construction_time(self, construction_time_input_data, construction_ return construction_time_output_data['operation_data_entire_farm'] - def calculate_costs(self, calculate_costs_input_dict, calculate_costs_output_dict): #read in rsmeans data: @@ -694,8 +800,25 @@ def calculate_costs(self, calculate_costs_input_dict, calculate_costs_output_dic calculate_costs_output_dict['Equipment Cost USD without weather delays'] = (calculate_costs_output_dict['Days taken for trenching (equipment)'] * calculate_costs_output_dict['Equipment cost of trenching per day {usd/day)']) calculate_costs_output_dict['Equipment Cost USD with weather delays'] = calculate_costs_output_dict['Equipment Cost USD without weather delays'] * calculate_costs_output_dict['wind_multiplier'] - trenching_equipment_rental_cost_df = pd.DataFrame([['Equipment rental',calculate_costs_output_dict['Equipment Cost USD with weather delays'], 'Collection']], - columns = ['Type of cost', 'Cost USD', 'Phase of construction']) + if calculate_costs_input_dict['turbine_rating_MW'] >= 0.1: + trenching_equipment_rental_cost_df = pd.DataFrame([['Equipment rental', calculate_costs_output_dict[ + 'Equipment Cost USD with weather delays'], 'Collection']], + columns=['Type of cost', 'Cost USD', + 'Phase of construction']) + + # switch for small DW + else: + if calculate_costs_output_dict['Equipment Cost USD with weather delays'] < 137: + calculate_costs_output_dict['Equipment Cost USD with weather delays'] = 137 #cost of renting for a day + trenching_equipment_rental_cost_df = pd.DataFrame([['Equipment rental', calculate_costs_output_dict[ + 'Equipment Cost USD with weather delays'], 'Collection']], + columns=['Type of cost', 'Cost USD', + 'Phase of construction']) + else: + trenching_equipment_rental_cost_df = pd.DataFrame([['Equipment rental', calculate_costs_output_dict[ + 'Equipment Cost USD with weather delays'], 'Small DW Collection']], + columns=['Type of cost', 'Cost USD', + 'Phase of construction']) #Calculating labor cost: calculate_costs_output_dict['Days taken for trenching (labor)'] = ((calculate_costs_output_dict['trench_length_km'] / self._km_to_LF) / calculate_costs_output_dict['trenching_labor_daily_output']) @@ -704,21 +827,42 @@ def calculate_costs(self, calculate_costs_input_dict, calculate_costs_output_dic calculate_costs_output_dict['Labor Cost USD without weather delays'] =((calculate_costs_output_dict['Days taken for trenching (labor)'] * calculate_costs_output_dict['Labor cost of trenching per day (usd/day)']) + (calculate_costs_output_dict['Total per diem costs (USD)'] + calculate_costs_output_dict['managament_crew_cost_before_wind_delay'])) calculate_costs_output_dict['Labor Cost USD with weather delays'] = calculate_costs_output_dict['Labor Cost USD without weather delays'] * calculate_costs_output_dict['wind_multiplier'] - trenching_labor_cost_df = pd.DataFrame([['Labor',calculate_costs_output_dict['Labor Cost USD with weather delays'], 'Collection']], - columns = ['Type of cost', 'Cost USD', 'Phase of construction']) + if calculate_costs_input_dict['turbine_rating_MW'] >= 0.1: + trenching_labor_cost_df = pd.DataFrame( + [['Labor', calculate_costs_output_dict['Labor Cost USD with weather delays'], 'Collection']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) + + # switch for small DW + else: + trenching_labor_cost_df = pd.DataFrame( + [['Labor', calculate_costs_output_dict['Labor Cost USD with weather delays'], 'Small DW Collection']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) #Calculate cable cost: cable_cost_usd_per_LF_df = pd.DataFrame([['Materials',self._total_cable_cost, 'Collection']], columns = ['Type of cost', 'Cost USD', 'Phase of construction']) # Combine all calculated cost items into the 'collection_cost' dataframe: - collection_cost = pd.DataFrame([],columns = ['Type of cost', 'Cost USD', 'Phase of construction']) # todo: I believe Phase of construction here is the same as Operation ID in other modules? we should change to be consistent + collection_cost = pd.DataFrame([],columns = ['Type of cost', 'Cost USD', 'Phase of construction']) collection_cost = collection_cost.append(trenching_equipment_rental_cost_df) collection_cost = collection_cost.append(trenching_labor_cost_df) collection_cost = collection_cost.append(cable_cost_usd_per_LF_df) - # Calculate Mobilization Cost and add to collection_cost dataframe: - mobilization_cost = pd.DataFrame([['Mobilization', collection_cost['Cost USD'].sum() * 0.05 , 'Collection']], + # Calculate Mobilization Cost and add to collection_cost dataframe. + # For utility scale plants, mobilization is assumed to be 5% of the sum of labor, equipment, and material costs. + # For distributed mode, mobilization is a calculated % that is a function of turbine size. + if calculate_costs_input_dict['num_turbines'] > 10: + calculate_costs_output_dict['mob_cost'] = collection_cost['Cost USD'].sum() * 0.05 + else: + if calculate_costs_input_dict['turbine_rating_MW'] >= 0.1: + calculate_costs_output_dict['mob_cost'] = collection_cost[ + 'Cost USD'].sum() * self.mobilization_cost_multiplier(calculate_costs_input_dict['turbine_rating_MW']) + + # switch for small DW + else: # mobilization cost included in equipment rental cost + calculate_costs_output_dict['mob_cost'] = 0.0 + + mobilization_cost = pd.DataFrame([['Mobilization', calculate_costs_output_dict['mob_cost'], 'Collection']], columns=['Type of cost', 'Cost USD', 'Phase of construction']) collection_cost = collection_cost.append(mobilization_cost) @@ -793,13 +937,13 @@ def outputs_for_detailed_tab(self, input_dict, output_dict): 'unit': '', 'type': 'variable', 'variable_df_key_col_name': 'Trench Length to Substation (km)', - 'value': float(self.output_dict['trench_len_to_substation_km']) + 'value': float(self.output_dict['distance_to_grid_connection_km']) }) result.append({ 'unit': '', 'type': 'variable', 'variable_df_key_col_name': 'Cable Length to Substation (km)', - 'value': float(self.output_dict['cable_len_to_substation_km']) + 'value': float(self.output_dict['cable_len_to_grid_connection_km']) }) cables = '' @@ -843,17 +987,15 @@ def outputs_for_detailed_tab(self, input_dict, output_dict): 'value': str(self.output_dict['num_turb_per_cable']) }) - # self.output_dict['turb_per_partial_string'] is only available if - # self.output_dict['num_leftover_turb'] > 0 which is not always the - # case. Commenting this output out - - # result.append({ - # 'unit': '', - # 'type': 'list', - # 'variable_df_key_col_name': 'Number of turbines per cable type in partial string [' + cables + ']', - # - # 'value': str(self.output_dict['turb_per_partial_string']) - # }) + if self.input_dict['turbine_rating_MW'] > 0.1: + for row in self.output_dict['management_crew'].itertuples(): + dashed_row = ' <--> '.join(str(x) for x in list(row)) + result.append({ + 'unit': '', + 'type': 'dataframe', + 'variable_df_key_col_name': 'Labor type ID <--> Hourly rate USD per hour <--> Per diem USD per day <--> Operation <--> Crew type <--> Crew name <--> Number of workers <--> Per Diem Total <--> Hourly costs total <--> Crew total cost ', + 'value': dashed_row + }) result.append({ 'unit': '', @@ -863,14 +1005,7 @@ def outputs_for_detailed_tab(self, input_dict, output_dict): 'value': str(self.output_dict['perc_partial_string']) }) - for row in self.output_dict['management_crew'].itertuples(): - dashed_row = ' <--> '.join(str(x) for x in list(row)) - result.append({ - 'unit': '', - 'type': 'dataframe', - 'variable_df_key_col_name': 'Labor type ID <--> Hourly rate USD per hour <--> Per diem USD per day <--> Operation <--> Crew type <--> Crew name <--> Number of workers <--> Per Diem Total <--> Hourly costs total <--> Crew total cost ', - 'value': dashed_row - }) + for row in self.output_dict['total_collection_cost'].itertuples(): dashed_row = '{} <--> {} <--> {}'.format(row[1], row[3], math.ceil(row[2])) diff --git a/landbosse/model/CostModule.py b/landbosse/model/CostModule.py index 5dc8d51b..c3867124 100644 --- a/landbosse/model/CostModule.py +++ b/landbosse/model/CostModule.py @@ -7,6 +7,25 @@ class CostModule: mobilization cost calculations. """ + def mobilization_cost_multiplier(self, turbine_rating): + """ + Calculates a mobilization cost term as a function of + turbine rating. + + Parameters + ---------- + turbine_rating : float + Turbine rating in megawatts + + Returns + ------- + float + The mobilization cost multiplier as a function of turbine rating. + """ + + mobilization_cost_multiplier = (36.892 * math.exp(-5e-04 * (turbine_rating * 1000))) / 100 + return mobilization_cost_multiplier + def outputs_for_costs_by_module_type_operation(self, *, input_df, diff --git a/landbosse/model/FoundationCost.py b/landbosse/model/FoundationCost.py index bbcb6e29..bf92aec6 100644 --- a/landbosse/model/FoundationCost.py +++ b/landbosse/model/FoundationCost.py @@ -410,9 +410,19 @@ def determine_foundation_size(self, foundation_size_input_data, foundation_size_ Foundation volume [in m^3] -> foundation_volume_concrete_m3_per_turbine """ + #TODO: still updating/fine-tuning foundation size equations for small DW (Parangat - Feb 27, 2020) r = float(foundation_size_output_data['Radius_m']) - foundation_size_output_data['excavated_volume_m3'] = np.pi * (r + 0.5) ** 2 * foundation_size_input_data['depth'] - foundation_size_output_data['foundation_volume_concrete_m3_per_turbine'] = np.pi * r ** 2 * foundation_size_input_data['depth'] * 0.45 # only compute the portion of the foundation that is composed of concrete (45% concrete; other portion is backfill); TODO: Add to sphinx -> (volume excavated = pi*(r_pick + .5m)^2 this assumes vertical sides which does not reflect reality as OSHA requires benched sides over 3’) + if foundation_size_input_data['turbine_rating_MW'] < 0.1: + foundation_size_output_data['excavated_volume_m3'] = r * r * foundation_size_input_data['depth'] + foundation_size_output_data['foundation_volume_concrete_m3_per_turbine'] = foundation_size_output_data['excavated_volume_m3'] * 0.45 + else: + foundation_size_output_data['excavated_volume_m3'] = np.pi * (r + 0.5) ** 2 * foundation_size_input_data['depth'] + + # only compute the portion of the foundation that is composed of concrete (45% concrete; other portion is + # backfill); TODO: Add to sphinx -> (volume excavated = pi*(r_pick + .5m)^2 this assumes vertical sides which + # does not reflect reality as OSHA requires benched sides over 3’) + foundation_size_output_data['foundation_volume_concrete_m3_per_turbine'] = np.pi * r ** 2 * \ + foundation_size_input_data['depth'] * 0.45 return foundation_size_output_data @@ -483,8 +493,13 @@ def estimate_construction_time(self, construction_time_input_data, construction_ construction_time_output_data['material_needs_entire_farm'] = material_needs_per_turbine.copy() material_needs_entire_farm = construction_time_output_data['material_needs_entire_farm'] material_needs_entire_farm['Quantity of material'] = quantity_materials_entire_farm - operation_data = throughput_operations.where(throughput_operations['Module'] == 'Foundations').dropna(thresh=4) - + if construction_time_input_data['turbine_rating_MW'] <= 0.1: + operation_data = throughput_operations.where( + throughput_operations['Module'] == 'Small DW Foundations').dropna( + thresh=4) + else: + operation_data = throughput_operations.where(throughput_operations['Module'] == 'Foundations').dropna( + thresh=4) #operation data for entire wind farm: operation_data = pd.merge(material_needs_entire_farm, operation_data, on=['Material type ID'], how='outer') @@ -506,14 +521,17 @@ def estimate_construction_time(self, construction_time_input_data, construction_ construction_time_output_data['operation_data_entire_farm'] = operation_data # pull out management data #TODO: Add this cost to Labor cost next - crew_cost = self.input_dict['crew_cost'] - crew = self.input_dict['crew'][self.input_dict['crew']['Crew type ID'].str.contains('M0')] - management_crew = pd.merge(crew_cost, crew, on=['Labor type ID']) - management_crew = management_crew.assign(per_diem_total=management_crew['Per diem USD per day'] * management_crew['Number of workers'] * num_days) - management_crew = management_crew.assign(hourly_costs_total=management_crew['Hourly rate USD per hour'] * self.input_dict['hour_day'][self.input_dict['time_construct']] * num_days) - management_crew = management_crew.assign(total_crew_cost_before_wind_delay=management_crew['per_diem_total'] + management_crew['hourly_costs_total']) - self.output_dict['management_crew'] = management_crew - self.output_dict['managament_crew_cost_before_wind_delay'] = management_crew['total_crew_cost_before_wind_delay'].sum() + if construction_time_input_data['turbine_rating_MW'] > 0.1: + crew_cost = self.input_dict['crew_cost'] + crew = self.input_dict['crew'][self.input_dict['crew']['Crew type ID'].str.contains('M0')] + management_crew = pd.merge(crew_cost, crew, on=['Labor type ID']) + management_crew = management_crew.assign(per_diem_total=management_crew['Per diem USD per day'] * management_crew['Number of workers'] * num_days) + management_crew = management_crew.assign(hourly_costs_total=management_crew['Hourly rate USD per hour'] * self.input_dict['hour_day'][self.input_dict['time_construct']] * num_days) + management_crew = management_crew.assign(total_crew_cost_before_wind_delay=management_crew['per_diem_total'] + management_crew['hourly_costs_total']) + self.output_dict['management_crew'] = management_crew + self.output_dict['managament_crew_cost_before_wind_delay'] = management_crew['total_crew_cost_before_wind_delay'].sum() + else: + self.output_dict['managament_crew_cost_before_wind_delay'] = 0 return construction_time_output_data['operation_data_entire_farm'] @@ -595,6 +613,7 @@ def calculate_costs(self, calculate_costs_input_dict, calculate_costs_output_dic operation_data = calculate_costs_output_dict['operation_data_entire_farm'] + wind_delay = calculate_costs_output_dict['wind_delay_time'] wind_delay_fraction = (wind_delay / calculate_costs_input_dict['operational_hrs_per_day']) / operation_data['Time construct days'].max(skipna=True) @@ -605,10 +624,17 @@ def calculate_costs(self, calculate_costs_input_dict, calculate_costs_output_dic calculate_costs_output_dict['wind_multiplier'] = wind_multiplier rsmeans = calculate_costs_input_dict['rsmeans'] + if calculate_costs_input_dict['turbine_rating_MW'] > 0.1: + rsmeans = rsmeans.where(rsmeans['Module'] == 'Foundations').dropna(thresh=4) + else: + rsmeans = rsmeans.where(rsmeans['Module'] == 'Small DW Foundations').dropna(thresh=4) labor_equip_data = pd.merge(material_vol_entire_farm, rsmeans, on=['Material type ID']) + # Create foundation cost dataframe + foundation_cost = pd.DataFrame(columns=['Type of cost', 'Cost USD', 'Phase of construction']) + # Calculate per diem per_diem = operation_data['Number of workers'] * operation_data['Number of crews'] * (operation_data['Time construct days'] + np.ceil(operation_data['Time construct days'] / 7)) * calculate_costs_input_dict['rsmeans_per_diem'] where_are_na_ns = np.isnan(per_diem) @@ -616,43 +642,58 @@ def calculate_costs(self, calculate_costs_input_dict, calculate_costs_output_dic labor_equip_data['Cost USD'] = (labor_equip_data['Quantity of material'] * labor_equip_data['Rate USD per unit'] * calculate_costs_input_dict['overtime_multiplier'] + per_diem + calculate_costs_output_dict['managament_crew_cost_before_wind_delay']) * wind_multiplier self.output_dict['labor_equip_data'] = labor_equip_data - #Create foundation cost dataframe - foundation_cost = pd.DataFrame(columns=['Type of cost', 'Cost USD', 'Phase of construction']) - - #Create equipment costs row to be appended to foundation_cost + # EQUIPMENT COST + # Create equipment costs row to be appended to foundation_cost equipment_dataframe = labor_equip_data[labor_equip_data['Type of cost'].str.match('Equipment rental')] - equipment_cost_usd_without_delay = (equipment_dataframe['Quantity of material'] * equipment_dataframe['Rate USD per unit'] * calculate_costs_input_dict['overtime_multiplier'] + per_diem) + + equipment_cost_usd_without_delay = ( + equipment_dataframe['Quantity of material'] * equipment_dataframe['Rate USD per unit'] * + calculate_costs_input_dict['overtime_multiplier'] + per_diem) equipment_cost_usd_with_weather_delays = equipment_cost_usd_without_delay.sum() * wind_multiplier equipment_costs = pd.DataFrame([['Equipment rental', equipment_cost_usd_with_weather_delays, 'Foundation']], columns=['Type of cost', 'Cost USD', 'Phase of construction']) + # LABOR COST # Create labor costs row to be appended to foundation_cost labor_dataframe = labor_equip_data[labor_equip_data['Type of cost'].str.match('Labor')] labor_cost_usd_without_management= (labor_dataframe['Quantity of material'] * labor_dataframe['Rate USD per unit'] * calculate_costs_input_dict['overtime_multiplier'] + per_diem ) labor_cost_usd_with_management = labor_cost_usd_without_management.sum() + calculate_costs_output_dict['managament_crew_cost_before_wind_delay'] labor_cost_usd_with_management_plus_weather_delays = labor_cost_usd_with_management * wind_multiplier labor_costs = pd.DataFrame([['Labor', labor_cost_usd_with_management_plus_weather_delays, 'Foundation']], - columns=['Type of cost', 'Cost USD', 'Phase of construction']) - - + columns=['Type of cost', 'Cost USD', 'Phase of construction']) + # MATERIAL COST material_cost_dataframe = pd.DataFrame(columns=['Operation ID', 'Type of cost', 'Cost USD']) material_cost_dataframe['Operation ID'] = material_data_entire_farm['Material type ID'] material_cost_dataframe['Type of cost'] = 'Materials' material_cost_dataframe['Cost USD'] = material_data_entire_farm['Cost USD'] material_costs_sum = material_cost_dataframe['Cost USD'].sum() material_costs = pd.DataFrame([['Materials', material_costs_sum, 'Foundation']], - columns=['Type of cost', 'Cost USD', 'Phase of construction']) - + columns=['Type of cost', 'Cost USD', 'Phase of construction']) # Append all cost items to foundation_cost foundation_cost = foundation_cost.append(equipment_costs) foundation_cost = foundation_cost.append(labor_costs) foundation_cost = foundation_cost.append(material_costs) - # calculate mobilization cost as percentage of total foundation cost and add to foundation_cost - mob_cost = pd.DataFrame([['Mobilization', foundation_cost['Cost USD'].sum() * 0.05, 'Foundation']], - columns=['Type of cost', 'Cost USD', 'Phase of construction']) + # Calculate mobilization cost as percentage of total foundation cost and add to foundation_cost + # Assumed 5% of total foundation cost and add to foundation_cost for utility scale plant + # A function of turbine size for distributed wind (< 10 turbines) + if calculate_costs_input_dict['num_turbines'] > 10: + mobilization_cost = foundation_cost['Cost USD'].sum() * 0.05 + else: + if calculate_costs_input_dict['turbine_rating_MW'] < 0.1: + # Zero since mobilization cost of equipment is included in the equipment rental cost + mobilization_cost = 0 + else: + # There is mobilization cost for 0-10 turbines 100+ kW in rating. + num_turbines = calculate_costs_input_dict['num_turbines'] + rating = calculate_costs_input_dict['turbine_rating_MW'] + mobilization_multipler = self.mobilization_cost_multiplier(rating) + mobilization_cost = foundation_cost['Cost USD'].sum() / num_turbines * mobilization_multipler + + mob_cost = pd.DataFrame([['Mobilization', mobilization_cost, 'Foundation']], columns=['Type of cost', 'Cost USD', 'Phase of construction']) + foundation_cost = foundation_cost.append(mob_cost) # todo: we add a separate tab in the output file for costs (all costs will be the same format but it's a different format than other data) diff --git a/landbosse/model/GridConnectionCost.py b/landbosse/model/GridConnectionCost.py index 39a6de14..817f1f76 100644 --- a/landbosse/model/GridConnectionCost.py +++ b/landbosse/model/GridConnectionCost.py @@ -63,15 +63,35 @@ def calculate_costs(self, calculate_costs_input_dict, calculate_costs_output_dic either returns a 0 if the module ran successfully, or it returns the error raised that caused the failure. """ - - if calculate_costs_input_dict['distance_to_interconnect_mi'] == 0: - calculate_costs_output_dict['trans_dist_usd'] = 0 - else: - if calculate_costs_input_dict['new_switchyard'] is True: - calculate_costs_output_dict['interconnect_adder_USD'] = 18115 * self.input_dict['interconnect_voltage_kV'] + 165944 + # Switch between utility scale model and distributed model + # Run utility version of GridConnectionCost for project size > 15 MW: + if (calculate_costs_input_dict['turbine_rating_MW'] * calculate_costs_input_dict['num_turbines']) > 15: + if calculate_costs_input_dict['distance_to_interconnect_mi'] == 0: + calculate_costs_output_dict['trans_dist_usd'] = 0 else: - calculate_costs_output_dict['interconnect_adder_USD'] = 0 - calculate_costs_output_dict['trans_dist_usd'] = ((1176 * self.input_dict['interconnect_voltage_kV'] + 218257) * (calculate_costs_input_dict['distance_to_interconnect_mi'] ** (-0.1063)) * calculate_costs_input_dict['distance_to_interconnect_mi']) + calculate_costs_output_dict['interconnect_adder_USD'] + if calculate_costs_input_dict['new_switchyard'] is True: + calculate_costs_output_dict['interconnect_adder_USD'] = 18115 * self.input_dict['interconnect_voltage_kV'] + 165944 + else: + calculate_costs_output_dict['interconnect_adder_USD'] = 0 + calculate_costs_output_dict['trans_dist_usd'] = ((1176 * self.input_dict[ + 'interconnect_voltage_kV'] + 218257) * (calculate_costs_input_dict['distance_to_interconnect_mi'] ** ( + -0.1063)) * calculate_costs_input_dict['distance_to_interconnect_mi']) + calculate_costs_output_dict[ + 'interconnect_adder_USD'] + + # Run distributed wind version of GridConnectionCost for project size < 15 MW: + else: + # Code below is for newer version of LandBOSSE which incorporates distributed wind into the model: + calculate_costs_output_dict['tower_to_point_of_interconnection_usd_per_kw'] = 1736.7 * (( + calculate_costs_input_dict[ + 'num_turbines'] * + calculate_costs_input_dict[ + 'turbine_rating_MW'] * 1000) ** ( + -0.272)) + calculate_costs_output_dict['trans_dist_usd'] = calculate_costs_input_dict['num_turbines'] * \ + calculate_costs_input_dict[ + 'turbine_rating_MW'] * 1000 * \ + calculate_costs_output_dict[ + 'tower_to_point_of_interconnection_usd_per_kw'] calculate_costs_output_dict['trans_dist_usd_df'] = pd.DataFrame([['Other', calculate_costs_output_dict['trans_dist_usd'], 'Transmission and Distribution']], diff --git a/landbosse/model/ManagementCost.py b/landbosse/model/ManagementCost.py index e8efe537..bbd28d55 100644 --- a/landbosse/model/ManagementCost.py +++ b/landbosse/model/ManagementCost.py @@ -94,6 +94,7 @@ def __init__(self, input_dict, output_dict, project_name): output_dict : dict Dictionary with output key / value pairs. """ + self.in_distributed_mode = 'override_total_management_cost' in input_dict self.validate_inputs(input_dict) self.input_dict = input_dict self.output_dict = output_dict @@ -116,26 +117,27 @@ def validate_inputs(self, input_dict): ValueError If one of the keys is missing, this method raises a ValueError """ - required_keys = { - 'project_value_usd', - 'foundation_cost_usd', - 'construct_duration', - 'num_hwy_permits', - 'num_turbines', - 'project_size_megawatts', - 'hub_height_meters', - 'num_access_roads', - 'markup_contingency', - 'markup_warranty_management', - 'markup_sales_and_use_tax', - 'markup_overhead', - 'markup_profit_margin', - 'site_facility_building_area_df' - } - found_keys = set(input_dict.keys()) - if len(required_keys - found_keys) > 0: - err_msg = '{}: did not find all required keys in inputs dictionary. Missing keys are {}' - raise ValueError(err_msg.format(type(self).__name__, required_keys - found_keys)) + if not self.in_distributed_mode: + required_keys = { + 'project_value_usd', + 'foundation_cost_usd', + 'construct_duration', + 'num_hwy_permits', + 'num_turbines', + 'project_size_megawatts', + 'hub_height_meters', + 'num_access_roads', + 'markup_contingency', + 'markup_warranty_management', + 'markup_sales_and_use_tax', + 'markup_overhead', + 'markup_profit_margin', + 'site_facility_building_area_df' + } + found_keys = set(input_dict.keys()) + if len(required_keys - found_keys) > 0: + err_msg = '{}: did not find all required keys in inputs dictionary. Missing keys are {}' + raise ValueError(err_msg.format(type(self).__name__, required_keys - found_keys)) def insurance(self): """ @@ -425,29 +427,40 @@ def outputs_for_detailed_tab(self): list(dict) A list of dicts, with each dict representing a row of the data. """ - management_cost_keys = [ - 'insurance_usd', - 'construction_permitting_usd', - 'bonding_usd', - 'project_management_usd', - 'markup_contingency_usd', - 'engineering_usd', - 'site_facility_usd' - ] - result = [] - - for key in management_cost_keys: - value = self.output_dict[key] + if self.in_distributed_mode: row = { 'project_id_with_serial': self.project_name, 'module': type(self).__name__, 'type': 'variable', - 'variable_df_key_col_name': key, + 'variable_df_key_col_name': 'total_management_cost', 'unit': 'usd', - 'value': value + 'value': self.output_dict['total_management_cost'] } result.append(row) + else: + management_cost_keys = [ + 'insurance_usd', + 'construction_permitting_usd', + 'bonding_usd', + 'project_management_usd', + 'markup_contingency_usd', + 'engineering_usd', + 'site_facility_usd' + ] + + for key in management_cost_keys: + value = self.output_dict[key] + row = { + 'project_id_with_serial': self.project_name, + 'module': type(self).__name__, + 'type': 'variable', + 'variable_df_key_col_name': key, + 'unit': 'usd', + 'value': value + } + result.append(row) + return result def outputs_for_module_type_operation(self): @@ -467,34 +480,41 @@ def outputs_for_module_type_operation(self): num_turbines = self.input_dict['num_turbines'] project_size_kw = num_turbines * turbine_rating_MW * 1000 - result.append({ - 'type_of_cost': 'insurance', - 'raw_cost': self.output_dict['insurance_usd'] - }) - result.append({ - 'type_of_cost': 'Construction Permitting', - 'raw_cost': self.output_dict['construction_permitting_usd'] - }) - result.append({ - 'type_of_cost': 'Project Management', - 'raw_cost': self.output_dict['project_management_usd'] - }) - result.append({ - 'type_of_cost': 'Bonding', - 'raw_cost': self.output_dict['bonding_usd'] - }) - result.append({ - 'type_of_cost': 'Markup Contingency', - 'raw_cost': self.output_dict['markup_contingency_usd'] - }) - result.append({ - 'type_of_cost': 'Engineering Foundation and Collections System (includes met mast)', - 'raw_cost': self.output_dict['engineering_usd'] - }) - result.append({ - 'type_of_cost': 'Site Facility', - 'raw_cost': self.output_dict['site_facility_usd'] - }) + if self.in_distributed_mode: + result.append({ + 'type_of_cost': 'total_management_cost', + 'raw_cost': self.output_dict['total_management_cost'] + }) + + else: + result.append({ + 'type_of_cost': 'insurance', + 'raw_cost': self.output_dict['insurance_usd'] + }) + result.append({ + 'type_of_cost': 'Construction Permitting', + 'raw_cost': self.output_dict['construction_permitting_usd'] + }) + result.append({ + 'type_of_cost': 'Project Management', + 'raw_cost': self.output_dict['project_management_usd'] + }) + result.append({ + 'type_of_cost': 'Bonding', + 'raw_cost': self.output_dict['bonding_usd'] + }) + result.append({ + 'type_of_cost': 'Markup Contingency', + 'raw_cost': self.output_dict['markup_contingency_usd'] + }) + result.append({ + 'type_of_cost': 'Engineering Foundation and Collections System (includes met mast)', + 'raw_cost': self.output_dict['engineering_usd'] + }) + result.append({ + 'type_of_cost': 'Site Facility', + 'raw_cost': self.output_dict['site_facility_usd'] + }) for _dict in result: _dict['turbine_rating_MW'] = self.input_dict['turbine_rating_MW'] @@ -529,14 +549,25 @@ def run_module(self): 0 then the second element is 0 as well. """ try: - self.output_dict['insurance_usd'] = self.insurance() - self.output_dict['construction_permitting_usd'] = self.construction_permitting() - self.output_dict['project_management_usd'] = self.project_management() - self.output_dict['bonding_usd'] = self.bonding() - self.output_dict['markup_contingency_usd'] = self.markup_contingency() - self.output_dict['engineering_usd'] = self.engineering_foundations_collection_sys() - self.output_dict['site_facility_usd'] = self.site_facility() - self.output_dict['total_management_cost'] = self.total_management_cost() + if self.in_distributed_mode: + self.output_dict['insurance_usd'] = 0 + self.output_dict['construction_permitting_usd'] = 0 + self.output_dict['project_management_usd'] = 0 + self.output_dict['bonding_usd'] = 0 + self.output_dict['markup_contingency_usd'] = 0 + self.output_dict['engineering_usd'] = 0 + self.output_dict['site_facility_usd'] = 0 + self.output_dict['total_management_cost'] = self.input_dict['override_total_management_cost'] + + else: + self.output_dict['insurance_usd'] = self.insurance() + self.output_dict['construction_permitting_usd'] = self.construction_permitting() + self.output_dict['project_management_usd'] = self.project_management() + self.output_dict['bonding_usd'] = self.bonding() + self.output_dict['markup_contingency_usd'] = self.markup_contingency() + self.output_dict['engineering_usd'] = self.engineering_foundations_collection_sys() + self.output_dict['site_facility_usd'] = self.site_facility() + self.output_dict['total_management_cost'] = self.total_management_cost() self.output_dict['management_cost_csv'] = self.outputs_for_detailed_tab() self.output_dict['mangement_module_type_operation'] = self.outputs_for_module_type_operation() return 0, 0 # module ran successfully diff --git a/landbosse/model/SitePreparationCost.py b/landbosse/model/SitePreparationCost.py index 4fa9976b..634953d5 100644 --- a/landbosse/model/SitePreparationCost.py +++ b/landbosse/model/SitePreparationCost.py @@ -211,12 +211,22 @@ def calculate_road_properties(self, road_properties_input, road_properties_outpu """ - road_properties_output['road_length_m'] = ((road_properties_input['num_turbines'] - 1) * road_properties_input['turbine_spacing_rotor_diameters'] * road_properties_input['rotor_diameter_m']) + road_properties_input['road_length_adder_m'] + if road_properties_input['road_distributed_wind'] == True or road_properties_input['turbine_rating_MW'] < 0.1: + road_properties_output['road_volume'] = road_properties_input['road_length_adder_m'] * \ + (road_properties_input['road_width_ft'] * self._meters_per_foot) * \ + (road_properties_input[ + 'road_thickness'] * self._meters_per_inch) # units of cubic meters + else: + road_properties_output['road_length_m'] = ((road_properties_input['num_turbines'] - 1) * + road_properties_input['turbine_spacing_rotor_diameters'] * + road_properties_input['rotor_diameter_m']) + \ + road_properties_input['road_length_adder_m'] + road_properties_output['road_width_m'] = road_properties_input['road_width_ft'] * self._meters_per_foot + road_properties_output['road_volume'] = road_properties_output['road_length_m'] * \ + (road_properties_input['road_width_ft'] * self._meters_per_foot) * \ + (road_properties_input[ + 'road_thickness'] * self._meters_per_inch) # units of cubic meters - # units of cubic meters - road_properties_output['road_volume'] = road_properties_output['road_length_m'] * \ - (road_properties_input['road_width_ft'] * self._meters_per_foot) * \ - (road_properties_input['road_thickness'] * self._meters_per_inch) road_properties_output['road_volume_m3'] = road_properties_output['road_volume'] + self._crane_pad_volume * \ road_properties_input['num_turbines'] # in cubic meters @@ -274,33 +284,77 @@ def estimate_construction_time(self, estimate_construction_time_input, estimate_ estimate_construction_time_output['road_construction_time'] = estimate_construction_time_input[ 'construct_duration'] * 1 / 5 # assumes road construction occurs for 1/5 of project time + # Main switch between small DW wind and (utility scale + distributed wind) # select operations for roads module that have data - operation_data = throughput_operations.where(throughput_operations['Module'] == 'Roads').dropna(thresh=4) + if estimate_construction_time_input['turbine_rating_MW'] >= 0.1: + operation_data = throughput_operations.where(throughput_operations['Module'] == 'Roads').dropna(thresh=4) + else: + operation_data = throughput_operations.where(throughput_operations['Module'] == 'Small DW Roads').dropna( + thresh=4) + operation_data = operation_data.dropna(subset=['Units']) # create list of unique material units for operations list_units = operation_data['Units'].unique() - # TODO: Make sure the correct (crane_width, road_length, depth_to_subgrade) variables are being used in calculations here: - estimate_construction_time_output['topsoil_volume'] = ( estimate_construction_time_output['crane_path_width_m']) * estimate_construction_time_output['road_length_m'] * (estimate_construction_time_output['depth_to_subgrade_m']) * self._cubic_yards_per_cubic_meter # units: cubic yards - - estimate_construction_time_output['embankment_volume_crane'] = (estimate_construction_time_output['crane_path_width_m']) * estimate_construction_time_output['road_length_m'] * (estimate_construction_time_output['depth_to_subgrade_m']) * self._cubic_yards_per_cubic_meter # units: cubic yards - - # TODO: Figure out if this 'road_volume' key is from the input or the output dictionary. - estimate_construction_time_output['embankment_volume_road'] = estimate_construction_time_output['road_volume'] * self._cubic_yards_per_cubic_meter * math.ceil(estimate_construction_time_output['road_thickness_m'] / self._lift_depth_m) # units: cubic yards road todo: output_dict - embankment volume for roads - - estimate_construction_time_output['rough_grading_area'] = estimate_construction_time_output['road_length_m'] * \ - estimate_construction_time_output['road_width_m'] * \ - self._square_feet_per_square_meter * \ - math.ceil(estimate_construction_time_output[ - 'road_thickness_m'] / self._lift_depth_m) / 100000 # Units: Each (100,000 square feet) - - material_quantity_dict = {'cubic yard': estimate_construction_time_output['topsoil_volume'], - 'embankment cubic yards crane': estimate_construction_time_output[ - 'embankment_volume_crane'], - 'embankment cubic yards road': estimate_construction_time_output[ - 'embankment_volume_road'], - 'loose cubic yard': estimate_construction_time_output['material_volume_cubic_yards'], - 'Each (100000 square feet)': estimate_construction_time_output['rough_grading_area']} + if estimate_construction_time_input['road_distributed_wind'] == True and estimate_construction_time_input[ + 'turbine_rating_MW'] >= 0.1: + estimate_construction_time_output['topsoil_volume'] = estimate_construction_time_input[ + 'site_prep_area_m2'] * ( + estimate_construction_time_output[ + 'depth_to_subgrade_m']) * self._cubic_yards_per_cubic_meter # units: cubic yards + estimate_construction_time_output['embankment_volume_crane'] = estimate_construction_time_output[ + 'topsoil_volume'] # units: cubic yards + estimate_construction_time_output['embankment_volume_road'] = estimate_construction_time_output[ + 'topsoil_volume'] # units: cubic yards + estimate_construction_time_output['rough_grading_area'] = (estimate_construction_time_input[ + 'site_prep_area_m2'] * 10.76391) / 100000 # where 10.76391 sq ft = 1 sq m + + elif estimate_construction_time_input['road_distributed_wind'] == True and estimate_construction_time_input[ + 'turbine_rating_MW'] < 0.1: + estimate_construction_time_output['topsoil_volume'] = estimate_construction_time_input[ + 'road_length_adder_m'] * ( + estimate_construction_time_input[ + 'road_width_ft'] * 0.3048) * ( + estimate_construction_time_input[ + 'road_thickness'] * 0.0254) * 1.308 # Units: CY (where 1 m3 = 1.308 CY) + + else: + estimate_construction_time_output['topsoil_volume'] = (estimate_construction_time_output[ + 'crane_path_width_m']) * estimate_construction_time_output['road_length_m'] * ( + estimate_construction_time_output[ + 'depth_to_subgrade_m']) * self._cubic_yards_per_cubic_meter # units: cubic yards + estimate_construction_time_output['embankment_volume_crane'] = (estimate_construction_time_output[ + 'crane_path_width_m']) * estimate_construction_time_output['road_length_m'] * ( + estimate_construction_time_output[ + 'depth_to_subgrade_m']) * self._cubic_yards_per_cubic_meter # units: cubic yards + estimate_construction_time_output['embankment_volume_road'] = estimate_construction_time_output[ + 'road_volume'] * self._cubic_yards_per_cubic_meter * math.ceil( + estimate_construction_time_output['road_thickness_m'] / self._lift_depth_m) # units: cubic yards road + estimate_construction_time_output['rough_grading_area'] = estimate_construction_time_output[ + 'road_length_m'] * \ + estimate_construction_time_output[ + 'road_width_m'] * \ + self._square_feet_per_square_meter * \ + math.ceil(estimate_construction_time_output[ + 'road_thickness_m'] / self._lift_depth_m) / 100000 # Units: Each (100,000 square feet) + + if estimate_construction_time_input['turbine_rating_MW'] >= 0.1: + material_quantity_dict = {'cubic yard': estimate_construction_time_output['topsoil_volume'], + 'embankment cubic yards crane': estimate_construction_time_output[ + 'embankment_volume_crane'], + 'embankment cubic yards road': estimate_construction_time_output[ + 'embankment_volume_road'], + 'loose cubic yard': estimate_construction_time_output[ + 'material_volume_cubic_yards'], + 'Each (100000 square feet)': estimate_construction_time_output[ + 'rough_grading_area']} + else: # small DW + material_quantity_dict = {'cubic yard': estimate_construction_time_output['topsoil_volume'], + 'embankment cubic yards crane': estimate_construction_time_output[ + 'topsoil_volume'], + 'loose cubic yard': estimate_construction_time_output['topsoil_volume'], + 'embankment cubic yards road': estimate_construction_time_output['topsoil_volume'] + } material_needs = pd.DataFrame(columns=['Units', 'Quantity of material']) for unit in list_units: @@ -327,16 +381,26 @@ def estimate_construction_time(self, estimate_construction_time_input, estimate_ operation_data['Time construct days'] = operation_data[['time_construct_bool', 'Number of days']].min(axis=1) num_days = operation_data['Time construct days'].max() - # pull out management data #TODO: Add this cost to Labor cost next - crew_cost = self.input_dict['crew_cost'] - crew = self.input_dict['crew'][self.input_dict['crew']['Crew type ID'].str.contains('M0')] - management_crew = pd.merge(crew_cost, crew, on=['Labor type ID']) - management_crew = management_crew.assign(per_diem_total=management_crew['Per diem USD per day'] * management_crew['Number of workers'] * num_days) - management_crew = management_crew.assign(hourly_costs_total=management_crew['Hourly rate USD per hour'] * self.input_dict['hour_day'][self.input_dict['time_construct']] * num_days) - management_crew = management_crew.assign(total_crew_cost_before_wind_delay=management_crew['per_diem_total'] + management_crew['hourly_costs_total']) - self.output_dict['management_crew'] = management_crew - self.output_dict['managament_crew_cost_before_wind_delay'] = management_crew['total_crew_cost_before_wind_delay'].sum() - + # pull out management data + if estimate_construction_time_input['turbine_rating_MW'] >= 0.1: + crew_cost = self.input_dict['crew_cost'] + crew = self.input_dict['crew'][self.input_dict['crew']['Crew type ID'].str.contains('M0')] + management_crew = pd.merge(crew_cost, crew, on=['Labor type ID']) + management_crew = management_crew.assign(per_diem_total= + management_crew['Per diem USD per day'] * + management_crew['Number of workers'] * + num_days) + management_crew = management_crew.assign(hourly_costs_total= + management_crew['Hourly rate USD per hour'] * + self.input_dict['hour_day'][self.input_dict['time_construct']] * + num_days) + management_crew = management_crew.assign(total_crew_cost_before_wind_delay= + management_crew['per_diem_total'] + + management_crew['hourly_costs_total']) + self.output_dict['management_crew'] = management_crew + + self.output_dict['managament_crew_cost_before_wind_delay'] = management_crew[ + 'total_crew_cost_before_wind_delay'].sum() estimate_construction_time_output['operation_data'] = operation_data @@ -427,10 +491,17 @@ def calculate_costs(self, calculate_cost_input_dict, calculate_cost_output_dict) material_cost_of_new_roads = material_data['Quantity of material'].iloc[0] * pd.to_numeric(material_data['Material price USD per unit'].iloc[0]) # New + old roads material cost: - material_cost_of_old_and_new_roads = self.new_and_existing_total_road_cost(material_cost_of_new_roads) - material_costs = pd.DataFrame( - [['Materials', float(material_cost_of_old_and_new_roads), 'Roads']], - columns=['Type of cost', 'Cost USD', 'Phase of construction']) + if calculate_cost_input_dict['turbine_rating_MW'] >= 0.1: + material_cost_of_old_and_new_roads = self.new_and_existing_total_road_cost(material_cost_of_new_roads) + material_costs = pd.DataFrame( + [['Materials', float(material_cost_of_old_and_new_roads), 'Roads']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) + else: + material_cost_of_old_and_new_roads = material_cost_of_new_roads + material_costs = pd.DataFrame( + [['Materials', float(material_cost_of_old_and_new_roads), 'Small DW Roads']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) + operation_data = self.estimate_construction_time(calculate_cost_input_dict, calculate_cost_output_dict) @@ -447,25 +518,109 @@ def calculate_costs(self, calculate_cost_input_dict, calculate_cost_output_dict) labor_per_diem = per_diem.dropna() # calculate_cost_output_dict['Total per diem (USD)'] = per_diem.sum() labor_equip_data = pd.merge(operation_data[['Operation ID', 'Units', 'Quantity of material']], rsmeans, on=['Units', 'Operation ID']) - labor_equip_data['Calculated per diem'] = per_diem - - - #Calculating labor costs: - labor_data = labor_equip_data[labor_equip_data['Type of cost'] == 'Labor'].copy() + # Calculating labor costs: + if calculate_cost_input_dict['turbine_rating_MW'] >= 0.1: + labor_equip_data['Calculated per diem'] = per_diem + labor_data = labor_equip_data[labor_equip_data['Type of cost'] == 'Labor'].copy() + else: + labor_equip_data['Calculated per diem'] = 0 + # labor_data = labor_equip_data[labor_equip_data['Type of cost'] == 'Labor'].copy() + labor_data = labor_equip_data[labor_equip_data['Module'] == 'Small DW Roads'].copy() + + quantity_material = material_data['Quantity of material'] + labor_usd_per_unit = labor_data['Rate USD per unit'] + overtime_multiplier = calculate_cost_input_dict['overtime_multiplier'] + wind_multiplier = calculate_cost_output_dict['wind_multiplier'] + + labor_data['Cost USD'] = ((labor_data['Quantity of material'] * + labor_data['Rate USD per unit'] * + calculate_cost_input_dict['overtime_multiplier']) + + labor_per_diem + ) * calculate_cost_output_dict['wind_multiplier'] + + if calculate_cost_input_dict['road_distributed_wind'] and \ + calculate_cost_input_dict['turbine_rating_MW'] >= 0.1: + + labor_for_new_roads_cost_usd = (labor_data['Cost USD'].sum()) + \ + calculate_cost_output_dict['managament_crew_cost_before_wind_delay'] + + labor_for_new_and_old_roads_cost_usd = self.new_and_existing_total_road_cost(labor_for_new_roads_cost_usd) + labor_costs = pd.DataFrame([['Labor', float(labor_for_new_and_old_roads_cost_usd), 'Roads']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) + + elif calculate_cost_input_dict['road_distributed_wind'] and \ + calculate_cost_input_dict['turbine_rating_MW'] < 0.1: # small DW + + labor_for_new_roads_cost_usd = (labor_data['Cost USD'].sum()) + labor_for_new_and_old_roads_cost_usd = self.new_and_existing_total_road_cost(labor_for_new_roads_cost_usd) + + labor_costs = pd.DataFrame([['Labor', float(labor_for_new_and_old_roads_cost_usd), 'Small DW Roads']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) + + else: + labor_for_new_roads_cost_usd = labor_data['Cost USD'].sum() + \ + calculate_cost_output_dict['managament_crew_cost_before_wind_delay'] + + labor_for_new_and_old_roads_cost_usd = self.new_and_existing_total_road_cost(labor_for_new_roads_cost_usd) + + labor_costs = pd.DataFrame([['Labor', + float(labor_for_new_and_old_roads_cost_usd), + 'Roads']], + + columns=['Type of cost', + 'Cost USD', + 'Phase of construction']) + + # Filter out equipment costs from rsmeans tab: + if calculate_cost_input_dict['turbine_rating_MW'] >= 0.1: + equipment_data = labor_equip_data[labor_equip_data['Type of cost'] == 'Equipment rental'].copy() + else: + equipment_data = labor_equip_data[labor_equip_data['Module'] == 'Small DW Roads'].copy() + equipment_data = equipment_data[equipment_data['Type of cost'] == 'Equipment rental'].copy() + + equipment_data['Cost USD'] = (equipment_data['Quantity of material'] * equipment_data['Rate USD per unit']) * \ + calculate_cost_output_dict['wind_multiplier'] + + # if rental cost is < half day minimum: + if equipment_data['Cost USD'].sum() < 460: + equip_for_new_roads_cost_usd = 460 + else: + equip_for_new_roads_cost_usd = equipment_data['Cost USD'].sum() + + if calculate_cost_input_dict['turbine_rating_MW'] >= 0.1: + equip_for_new_and_old_roads_cost_usd = self.new_and_existing_total_road_cost(equip_for_new_roads_cost_usd) + equipment_costs = pd.DataFrame([['Equipment rental', float(equip_for_new_and_old_roads_cost_usd), 'Roads']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) + else: + equip_for_new_and_old_roads_cost_usd = self.new_and_existing_total_road_cost(equip_for_new_roads_cost_usd) + equipment_costs = pd.DataFrame([['Equipment rental', float(equip_for_new_and_old_roads_cost_usd), 'Small DW Roads']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) + + # add costs for other operations not included in process data for utility mode (e.g., fencing, access roads) + if calculate_cost_input_dict['turbine_rating_MW'] > 0.1: + num_turbines = calculate_cost_input_dict['num_turbines'] + rotor_diameter_m = calculate_cost_input_dict['rotor_diameter_m'] + construct_duration = calculate_cost_input_dict['construct_duration'] + num_access_roads = calculate_cost_input_dict['num_access_roads'] + + cost_new_roads_adder = ( + (float(num_turbines) * 17639) + (float(num_turbines) * float(rotor_diameter_m) * 24.8) + ( + float(construct_duration) * 55500) + float(num_access_roads) * 3800) + cost_adder = self.new_and_existing_total_road_cost(cost_new_roads_adder) + additional_costs = pd.DataFrame([['Other', float(cost_adder), 'Roads']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) + + else: # No 'Other' cost in distributed wind mode: + cost_new_roads_adder = 0 + cost_adder = self.new_and_existing_total_road_cost(cost_new_roads_adder) + additional_costs = pd.DataFrame([['Other', float(cost_adder), 'Small DW Roads']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) - labor_data['Cost USD'] = ((labor_data['Quantity of material'] * labor_data['Rate USD per unit'] * calculate_cost_input_dict['overtime_multiplier']) + labor_per_diem) * calculate_cost_output_dict['wind_multiplier'] - labor_for_new_roads_cost_usd = labor_data['Cost USD'].sum() + calculate_cost_output_dict[ - 'managament_crew_cost_before_wind_delay'] - - labor_for_new_and_old_roads_cost_usd = self.new_and_existing_total_road_cost(labor_for_new_roads_cost_usd) - labor_costs = pd.DataFrame([['Labor', float(labor_for_new_and_old_roads_cost_usd), 'Roads']], - columns=['Type of cost', 'Cost USD', 'Phase of construction']) # Create empty road cost (showing cost breakdown by type) dataframe: road_cost = pd.DataFrame(columns=['Type of cost', 'Cost USD', 'Phase of construction']) - #Filter out equipment costs from rsmeans tab: equipment_data = labor_equip_data[labor_equip_data['Type of cost'] == 'Equipment rental'].copy() equipment_data['Cost USD'] = (equipment_data['Quantity of material'] * equipment_data['Rate USD per unit']) * calculate_cost_output_dict['wind_multiplier'] #TODO: Annika can you confirm if this is correct. @@ -505,21 +660,28 @@ def calculate_costs(self, calculate_cost_input_dict, calculate_cost_output_dict) road_cost = road_cost.append(labor_costs) road_cost = road_cost.append(additional_costs) - # set mobilization cost equal to 5% of total road cost - mobilization_costs_new_roads = road_cost["Cost USD"].sum() * 0.05 - mobilization_costs_new_plus_old_roads = self.new_and_existing_total_road_cost(mobilization_costs_new_roads) - mobilization_costs = pd.DataFrame([['Mobilization', mobilization_costs_new_plus_old_roads, 'Roads']], - columns=['Type of cost', 'Cost USD', 'Phase of construction']) + # set mobilization cost equal to 5% of total road cost for utility scale model and function of + # of turbine size for distributed wind: + if calculate_cost_input_dict['num_turbines'] > 10: + mobilization_costs_new_roads = road_cost["Cost USD"].sum() * 0.05 + mobilization_costs_new_plus_old_roads = self.new_and_existing_total_road_cost(mobilization_costs_new_roads) + mobilization_costs = pd.DataFrame([['Mobilization', mobilization_costs_new_plus_old_roads, 'Roads']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) + else: + mobilization_costs_new_roads = road_cost["Cost USD"].sum() * \ + self.mobilization_cost_multiplier(calculate_cost_input_dict['turbine_rating_MW']) + mobilization_costs_new_plus_old_roads = self.new_and_existing_total_road_cost(mobilization_costs_new_roads) + + if calculate_cost_input_dict['turbine_rating_MW'] >= 0.1: + mobilization_costs = pd.DataFrame([['Mobilization', mobilization_costs_new_plus_old_roads, 'Roads']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) + else: + mobilization_costs = pd.DataFrame([['Mobilization', mobilization_costs_new_plus_old_roads, 'Small DW Roads']], + columns=['Type of cost', 'Cost USD', 'Phase of construction']) road_cost = road_cost.append(mobilization_costs) - - # total_road_cost = road_cost.groupby(by=['Type of cost']).sum().reset_index() total_road_cost = road_cost - - # total_road_cost.loc[total_road_cost['Type of cost'] == 'Labor', 'Cost USD'] = float(total_road_cost.loc[total_road_cost['Type of cost'] == 'Labor', 'Cost USD']) + 48.8 * calculate_cost_output_dict['road_length_m'] #TODO:Why times 48.8 ? - # total_road_cost['Phase of construction'] = 'Roads' - calculate_cost_output_dict['total_road_cost'] = total_road_cost calculate_cost_output_dict['siteprep_construction_months'] = siteprep_construction_months return total_road_cost @@ -565,12 +727,13 @@ def outputs_for_detailed_tab(self, input_dict, output_dict): 'value': self.output_dict['crane_path_width_m'] #TODO: Rename variable to: crane_path_width_ft }) - result.append({ - 'unit': 'm', - 'type': 'variable', - 'variable_df_key_col_name': 'Road length', - 'value': float(self.output_dict['road_length_m']) - }) + if not input_dict['road_distributed_wind']: + result.append({ + 'unit': 'm', + 'type': 'variable', + 'variable_df_key_col_name': 'Road length', + 'value': float(self.output_dict['road_length_m']) + }) result.append({ 'unit': 'm', @@ -602,26 +765,27 @@ def outputs_for_detailed_tab(self, input_dict, output_dict): 'value': float(self.output_dict['topsoil_volume']) }) - result.append({ - 'unit': 'cubic yards', - 'type': 'variable', - 'variable_df_key_col_name': 'Embankment volume crane', - 'value': float(self.output_dict['embankment_volume_crane']) - }) + if input_dict['turbine_rating_MW'] >= 0.1: + result.append({ + 'unit': 'cubic yards', + 'type': 'variable', + 'variable_df_key_col_name': 'Embankment volume crane', + 'value': float(self.output_dict['embankment_volume_crane']) + }) - result.append({ - 'unit': 'cubic yards', - 'type': 'variable', - 'variable_df_key_col_name': 'Embankment volume road', - 'value': float(self.output_dict['embankment_volume_road']) - }) + result.append({ + 'unit': 'cubic yards', + 'type': 'variable', + 'variable_df_key_col_name': 'Embankment volume road', + 'value': float(self.output_dict['embankment_volume_road']) + }) - result.append({ - 'unit': 'ft^2', - 'type': 'variable', - 'variable_df_key_col_name': 'Rough grading area', - 'value': float(self.output_dict['rough_grading_area']) - }) + result.append({ + 'unit': 'ft^2', + 'type': 'variable', + 'variable_df_key_col_name': 'Rough grading area', + 'value': float(self.output_dict['rough_grading_area']) + }) for row in self.output_dict['total_road_cost'].itertuples(): dashed_row = '{} <--> {} <--> {}'.format(row[1], row[3], math.ceil(row[2])) @@ -678,7 +842,7 @@ def outputs_for_module_type_operation(self, input_dict, output_dict): def run_module(self): """ - Runs the FoundationCost module and populates the IO dictionaries with calculated values. + Runs the SitePreparation module and populates the IO dictionaries with calculated values. """ try: diff --git a/landbosse/model/SubstationCost.py b/landbosse/model/SubstationCost.py index 82c12ff0..4751aee6 100644 --- a/landbosse/model/SubstationCost.py +++ b/landbosse/model/SubstationCost.py @@ -73,7 +73,15 @@ def calculate_costs(self, calculate_costs_input_dict , calculate_costs_output_di """ - calculate_costs_output_dict['substation_cost_usd'] = 11652 * (calculate_costs_input_dict['interconnect_voltage_kV'] + calculate_costs_input_dict['project_size_megawatts']) + 11795 * (calculate_costs_input_dict['project_size_megawatts'] ** 0.3549) + 1526800 + # Run in utility mode if number of turbines is > 10: + if calculate_costs_input_dict['num_turbines'] > 10: + calculate_costs_output_dict['substation_cost_usd'] = 11652 * ( + calculate_costs_input_dict['interconnect_voltage_kV'] + calculate_costs_input_dict[ + 'project_size_megawatts']) + 11795 * (calculate_costs_input_dict[ + 'project_size_megawatts'] ** 0.3549) + 1526800 + # Run in distributed mode if number of turbines is <= 10: + else: + calculate_costs_output_dict['substation_cost_usd'] = 0 calculate_costs_output_dict['substation_cost_output_df'] = pd.DataFrame([['Other', calculate_costs_output_dict['substation_cost_usd'], 'Substation']], columns=['Type of cost', 'Cost USD', 'Phase of construction']) diff --git a/landbosse/tests/model/test_CollectionCost.py b/landbosse/tests/model/test_CollectionCost.py index 3059cf2b..3fa984f9 100644 --- a/landbosse/tests/model/test_CollectionCost.py +++ b/landbosse/tests/model/test_CollectionCost.py @@ -44,15 +44,16 @@ def setUp(self): self.input_dict['cable_specs_pd'] = pd.read_csv(cable_specs_path) #Read in cable specs into a dataframe. # self.input_dict['cable_specs'] = self.input_dict['cable_specs_pd'].T.to_dict() - self.input_dict['user_defined_home_run_trench'] = 0 # 0 = No ; 1 = Yes - self.input_dict['trench_len_to_substation_km'] = 50 + self.input_dict['user_defined_distance_to_grid_connection'] = 0 # 0 = No ; 1 = Yes + self.input_dict['distance_to_grid_connection_km'] = 2 self.project_name = 'project_1' #Inputs for ArraySystem: - self.input_dict['plant_capacity_MW'] = 100 - self.input_dict['row_spacing_rotor_diameters'] = 7 - self.input_dict['turbine_rating_MW'] = 1.5 + self.input_dict['num_turbines'] = 1 + self.input_dict['plant_capacity_MW'] = 15 + self.input_dict['row_spacing_rotor_diameters'] = 5 + self.input_dict['turbine_rating_MW'] = 0.02 self.input_dict['upstream_turb'] = 0 self.input_dict['turb_sequence'] = 1 self.input_dict['depth'] = 45 diff --git a/landbosse/tests/model/test_FoundationCost.py b/landbosse/tests/model/test_FoundationCost.py index 82a3a707..d508a0e0 100644 --- a/landbosse/tests/model/test_FoundationCost.py +++ b/landbosse/tests/model/test_FoundationCost.py @@ -35,7 +35,7 @@ def setUp(self): self.input_dict['critical_velocity_m_per_s'] = 52.5 self.input_dict['gust_velocity_m_per_s'] = 60 - + #Below are the inputs for calculate_foundation_load(): component_data = os.path.join(landbosse_test_input_dir(), 'components.csv') self.input_dict['component_data'] = pd.read_csv(component_data) diff --git a/landbosse/tests/model/test_GridConnectionCost.py b/landbosse/tests/model/test_GridConnectionCost.py index a06a533b..3cd75bac 100644 --- a/landbosse/tests/model/test_GridConnectionCost.py +++ b/landbosse/tests/model/test_GridConnectionCost.py @@ -11,6 +11,9 @@ def setUp(self): self.input_dict['distance_to_interconnect_mi'] = 5 self.input_dict['interconnect_voltage_kV'] = 130 self.input_dict['new_switchyard'] = True + self.input_dict['turbine_rating_MW'] = 1.5 + self.input_dict['num_turbines'] = 1 + self.input_dict['rotor_diameter_m'] = 75 self.project_name = 'Project_1' self.output_dict = dict() diff --git a/landbosse/tests/model/test_SitePreparationCost.py b/landbosse/tests/model/test_SitePreparationCost.py index 177c1c64..0649239c 100644 --- a/landbosse/tests/model/test_SitePreparationCost.py +++ b/landbosse/tests/model/test_SitePreparationCost.py @@ -13,28 +13,30 @@ class TestSitePreparationCost(TestCase): def setUp(self): self.input_dict = dict() self.project_name = 'Project_1' + self.input_dict['road_distributed_wind'] = True #TODO add this to input file. + #Inputs for calculate_road_properties(): # self.input_dict['road_length'] = 108000 #TODO: Also add option to make road_length a user specified input. - self.input_dict['road_width_ft'] = 20 # feet 16 - self.input_dict['road_thickness'] = 8 # inches - self.input_dict['crane_width'] = 12.2 # metres 10.7 + shoulder width 1.5 m + self.input_dict['road_width_ft'] = 10 # feet 10 + self.input_dict['road_thickness'] = 2 # inches + self.input_dict['crane_width'] = 4.548 # metres 10.7 + shoulder width 1.5 m self.input_dict['num_access_roads'] = 5 - self.input_dict['num_turbines'] = 100 + self.input_dict['num_turbines'] = 1 self.input_dict['rsmeans'] = pd.read_csv(os.path.join(landbosse_test_input_dir(), 'rsmeans_proprietary.csv')) crew_data = os.path.join(landbosse_test_input_dir(), 'crews.csv') self.input_dict['crew'] = pd.read_csv(crew_data) crew_cost_data = os.path.join(landbosse_test_input_dir(), 'crew_price_proprietary.csv') self.input_dict['crew_cost'] = pd.read_csv(crew_cost_data) - self.input_dict['duration_construction'] = 9 # months - self.input_dict['construct_duration'] = 9 # months - self.input_dict['fraction_new_roads'] = 0.33 + self.input_dict['duration_construction'] = 0.1 # months + self.input_dict['construct_duration'] = 0.1 # months + self.input_dict['fraction_new_roads'] = 1 self.input_dict['road_quality'] = 0.6 self.input_dict['rsmeans_per_diem'] = 144 self.input_dict['road_length_adder_m'] = 5000 # 5km self.input_dict['fraction_new_roads'] = 0.33 - self.input_dict['road_quality'] = 0.6 - self.input_dict['rsmeans_per_diem'] = 99 + self.input_dict['road_quality'] = 0.3 + self.input_dict['rsmeans_per_diem'] = 0 # Weather window: @@ -65,10 +67,12 @@ def setUp(self): #Inputs for calculate_costs(): material_price_csv = os.path.join(landbosse_test_input_dir(), 'material_price_proprietary.csv') self.input_dict['material_price'] = pd.read_csv(material_price_csv) + self.input_dict['turbine_rating_MW'] = 0.02 # 20 kW Turbine self.input_dict['overtime_multiplier'] = 1.4 # multiplier for labor overtime rates due to working 60 hr/wk rather than 40 hr/wk - self.input_dict['rotor_diameter_m'] = 130 + self.input_dict['rotor_diameter_m'] = 7 self.input_dict['turbine_spacing_rotor_diameters'] = 5 self.input_dict['critical_height_non_erection_wind_delays_m'] = 10 + self.input_dict['site_prep_area_m2'] = 0 self.output_dict = dict() diff --git a/landbosse/tests/model/test_SubstationCost.py b/landbosse/tests/model/test_SubstationCost.py index 303ca4db..44a55faf 100644 --- a/landbosse/tests/model/test_SubstationCost.py +++ b/landbosse/tests/model/test_SubstationCost.py @@ -10,10 +10,15 @@ class TestSubstationCost(TestCase): def setUp(self): self.input_dict = dict() self.input_dict['interconnect_voltage_kV'] = 1 - self.input_dict['project_size_megawatts'] = 1 #MW + self.input_dict['project_size_megawatts'] = 1 self.project_name = 'Project_1' self.output_dict = dict() + # self.input_dict['turbine_rating_MW'] = 1.5 + # self.input_dict['num_turbines'] = 15 + # self.input_dict['project_size_megawatts'] = self.input_dict[ + # 'num_turbines'] * self.input_dict['turbine_rating_MW'] # MW + # self.input_dict['rotor_diameter_m'] = 75 def test_SubstationCostModule(self): """ diff --git a/project_input_template/README.md b/project_input_template/README.md index beb83ac8..d98d3198 100644 --- a/project_input_template/README.md +++ b/project_input_template/README.md @@ -1,5 +1,5 @@ # Copy this folder -Copy this folder and move it out of the git repo before you use it. Make sure you set the `LANDBOSSE_INPUT_DIR` to point to the location of this copy of the folder. +Copy this folder and move it out of the git repo before you use it. Make sure you set the `LANDBOSSE_INPUT_DIR` to point to the location of this copy of the folder. Alternatively, you can use the `--input` or `-i` command line options to set the path to this input folder. All the project files should have a name the matches the "Project ID" in `projects_list.xlsx` and should be placed in a `project_data` folder underneath the `projects_list.xlsx` file. diff --git a/project_input_template/project_list.xlsx b/project_input_template/project_list.xlsx index 342a0f3e..a88d5c65 100644 Binary files a/project_input_template/project_list.xlsx and b/project_input_template/project_list.xlsx differ diff --git a/project_input_template/project_list_utility.xlsx b/project_input_template/project_list_utility.xlsx new file mode 100644 index 00000000..342a0f3e Binary files /dev/null and b/project_input_template/project_list_utility.xlsx differ