class Standard
  def model_add_swh(model, building_type, climate_zone, prototype_input, epw_file)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started Adding Service Water Heating')

    # Add the main service water heating loop, if specified
    unless prototype_input['main_water_heater_volume'].nil?
      swh_fueltype = prototype_input['main_water_heater_fuel']
      # Add the main service water loop
      unless building_type == 'RetailStripmall' && template != 'NECB2011'
        main_swh_loop = model_add_swh_loop(model,
                                           'Main Service Water Loop',
                                           nil,
                                           OpenStudio.convert(prototype_input['main_service_water_temperature'], 'F', 'C').get,
                                           prototype_input['main_service_water_pump_head'],
                                           prototype_input['main_service_water_pump_motor_efficiency'],
                                           OpenStudio.convert(prototype_input['main_water_heater_capacity'], 'Btu/hr', 'W').get,
                                           OpenStudio.convert(prototype_input['main_water_heater_volume'], 'gal', 'm^3').get,
                                           swh_fueltype,
                                           OpenStudio.convert(prototype_input['main_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get,
                                           building_type)
      end

      # Attach the end uses if specified in prototype inputs
      # TODO remove special logic for large office SWH end uses
      # TODO remove special logic for stripmall SWH end uses and service water loops
      # TODO remove special logic for large hotel SWH end uses
      if building_type == 'LargeOffice' && template != 'NECB2011'

        # Only the core spaces have service water
        ['Core_bottom', 'Core_mid', 'Core_top'].sort.each do |space_name|
          # ['Mechanical_Bot_ZN_1','Mechanical_Mid_ZN_1','Mechanical_Top_ZN_1'].each do |space_name| # for new space type large office
          model_add_swh_end_uses(model,
                                 'Main',
                                 main_swh_loop,
                                 OpenStudio.convert(prototype_input['main_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
                                 prototype_input['main_service_water_flowrate_schedule'],
                                 OpenStudio.convert(prototype_input['main_water_use_temperature'], 'F', 'C').get,
                                 space_name,
                                 building_type)
        end
      elsif building_type == 'LargeOfficeDetail' && template != 'NECB2011'

        # Only mechanical rooms have service water
        ['Mechanical_Bot_ZN_1', 'Mechanical_Mid_ZN_1', 'Mechanical_Top_ZN_1'].sort.each do |space_name| # for new space type large office
          model_add_swh_end_uses(model,
                                 'Main',
                                 main_swh_loop,
                                 OpenStudio.convert(prototype_input['main_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
                                 prototype_input['main_service_water_flowrate_schedule'],
                                 OpenStudio.convert(prototype_input['main_water_use_temperature'], 'F', 'C').get,
                                 space_name,
                                 building_type)
        end
      elsif building_type == 'RetailStripmall' && template != 'NECB2011'

        return true if template == 'DOE Ref Pre-1980' || template == 'DOE Ref 1980-2004'

        # Create a separate hot water loop & water heater for each space in the list
        swh_space_names = ['LGstore1', 'SMstore1', 'SMstore2', 'SMstore3', 'LGstore2', 'SMstore5', 'SMstore6']
        swh_sch_names = ['RetailStripmall Type1_SWH_SCH', 'RetailStripmall Type1_SWH_SCH', 'RetailStripmall Type2_SWH_SCH',
                         'RetailStripmall Type2_SWH_SCH', 'RetailStripmall Type3_SWH_SCH', 'RetailStripmall Type3_SWH_SCH',
                         'RetailStripmall Type3_SWH_SCH']
        rated_use_rate_gal_per_min = 0.03 # in gal/min
        rated_flow_rate_m3_per_s = OpenStudio.convert(rated_use_rate_gal_per_min, 'gal/min', 'm^3/s').get

        # Loop through all spaces
        swh_space_names.zip(swh_sch_names).sort.each do |swh_space_name, swh_sch_name|
          swh_thermal_zone = model.getSpaceByName(swh_space_name).get.thermalZone.get
          main_swh_loop = model_add_swh_loop(model,
                                             "#{swh_thermal_zone.name} Service Water Loop",
                                             swh_thermal_zone,
                                             OpenStudio.convert(prototype_input['main_service_water_temperature'], 'F', 'C').get,
                                             prototype_input['main_service_water_pump_head'],
                                             prototype_input['main_service_water_pump_motor_efficiency'],
                                             OpenStudio.convert(prototype_input['main_water_heater_capacity'], 'Btu/hr', 'W').get,
                                             OpenStudio.convert(prototype_input['main_water_heater_volume'], 'gal', 'm^3').get,
                                             prototype_input['main_water_heater_fuel'],
                                             OpenStudio.convert(prototype_input['main_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get,
                                             building_type)

          model_add_swh_end_uses(model,
                                 'Main',
                                 main_swh_loop,
                                 rated_flow_rate_m3_per_s,
                                 swh_sch_name,
                                 OpenStudio.convert(prototype_input['main_water_use_temperature'], 'F', 'C').get,
                                 swh_space_name,
                                 building_type)
        end

      elsif prototype_input['main_service_water_peak_flowrate']

        # Attaches the end uses if specified as a lump value in the prototype_input
        model_add_swh_end_uses(model,
                               'Main',
                               main_swh_loop,
                               OpenStudio.convert(prototype_input['main_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
                               prototype_input['main_service_water_flowrate_schedule'],
                               OpenStudio.convert(prototype_input['main_water_use_temperature'], 'F', 'C').get,
                               nil,
                               building_type)

      else

        # Attaches the end uses if specified by space type
        space_type_map = @space_type_map

        if template == 'NECB2011'
          building_type = 'Space Function'
        end

        space_type_map.sort.each do |space_type_name, space_names|
          search_criteria = {
              'template' => template,
              'building_type' => model_get_lookup_name(building_type),
              'space_type' => space_type_name
          }
          data = model_find_object(standards_data['space_types'], search_criteria)

          # Skip space types with no data
          next if data.nil?

          # Skip space types with no water use, unless it is a NECB archetype (these do not have peak flow rates defined)
          next unless template == 'NECB2011' || !data['service_water_heating_peak_flow_rate'].nil?

          # Add a service water use for each space
          space_names.sort.each do |space_name|
            space = model.getSpaceByName(space_name).get
            space_multiplier = nil
            space_multiplier = case template
                                 when 'NECB2011'
                                   # Added this to prevent double counting of zone multipliers.. space multipliers are never used in NECB archtypes.
                                   1
                                 else
                                   space.multiplier
                               end

            model_add_swh_end_uses_by_space(model, model_get_lookup_name(building_type),
                                            climate_zone,
                                            main_swh_loop,
                                            space_type_name,
                                            space_name,
                                            space_multiplier)
          end
        end

      end

    end

    # Add the booster water heater, if specified
    unless prototype_input['booster_water_heater_volume'].nil?

      # Add the booster water loop
      swh_booster_loop = model_add_swh_booster(model,
                                               main_swh_loop,
                                               OpenStudio.convert(prototype_input['booster_water_heater_capacity'], 'Btu/hr', 'W').get,
                                               OpenStudio.convert(prototype_input['booster_water_heater_volume'], 'gal', 'm^3').get,
                                               prototype_input['booster_water_heater_fuel'],
                                               OpenStudio.convert(prototype_input['booster_water_temperature'], 'F', 'C').get,
                                               0,
                                               nil,
                                               building_type)

      # Attach the end uses
      model_add_booster_swh_end_uses(model,
                                     swh_booster_loop,
                                     OpenStudio.convert(prototype_input['booster_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
                                     prototype_input['booster_service_water_flowrate_schedule'],
                                     OpenStudio.convert(prototype_input['booster_water_use_temperature'], 'F', 'C').get,
                                     building_type)

    end

    # Add the laundry water heater, if specified
    unless prototype_input['laundry_water_heater_volume'].nil?

      # Add the laundry service water heating loop
      laundry_swh_loop = model_add_swh_loop(model,
                                            'Laundry Service Water Loop',
                                            nil,
                                            OpenStudio.convert(prototype_input['laundry_service_water_temperature'], 'F', 'C').get,
                                            prototype_input['laundry_service_water_pump_head'],
                                            prototype_input['laundry_service_water_pump_motor_efficiency'],
                                            OpenStudio.convert(prototype_input['laundry_water_heater_capacity'], 'Btu/hr', 'W').get,
                                            OpenStudio.convert(prototype_input['laundry_water_heater_volume'], 'gal', 'm^3').get,
                                            prototype_input['laundry_water_heater_fuel'],
                                            OpenStudio.convert(prototype_input['laundry_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get,
                                            building_type)

      # Attach the end uses if specified in prototype inputs
      model_add_swh_end_uses(model,
                             'Laundry',
                             laundry_swh_loop,
                             OpenStudio.convert(prototype_input['laundry_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
                             prototype_input['laundry_service_water_flowrate_schedule'],
                             OpenStudio.convert(prototype_input['laundry_water_use_temperature'], 'F', 'C').get,
                             nil,
                             building_type)

    end

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding Service Water Heating')

    return true
  end

  # add swh

  # add typical swh demand and supply to model
  #
  # @param trust_effective_num_spaces [Bool]
  # @param fuel [String] (gas, electric, nil) nil is smart
  # @param pipe_insul_in [Double]
  # @param circulating [String] (circulating, noncirculating, nil) nil is smart
  # @return [Array] hot water loops
  # @todo - add in losses from tank and pipe insulation, etc.
  def model_add_typical_swh(model, trust_effective_num_spaces = false, fuel = nil, pipe_insul_in = nil, circulating = nil)
    # array of hot water loops
    swh_systems = []

    # hash of general water use equipment awaiting loop
    water_use_equipment_hash = {} # key is standards building type value is array of water use equipment

    # create space type hash (need num_units for MidriseApartment and RetailStripmall)
    space_type_hash = model_create_space_type_hash(model, trust_effective_num_spaces = false)

    # add temperate schedules to hash so they can be shared across water use equipment
    water_use_def_schedules = {} # key is temp C value is schedule

    # loop through space types adding demand side of swh
    model.getSpaceTypes.sort.each do |space_type|
      next unless space_type.standardsBuildingType.is_initialized
      next unless space_type.standardsSpaceType.is_initialized
      next unless space_type_hash.key?(space_type) # this is used for space types without any floor area
      stds_bldg_type = space_type.standardsBuildingType.get
      stds_space_type = space_type.standardsSpaceType.get

      # lookup space_type_properties
      space_type_properties = space_type_get_standards_data(space_type)
      gal_hr_per_area = space_type_properties['service_water_heating_peak_flow_per_area']
      gal_hr_peak_flow_rate = space_type_properties['service_water_heating_peak_flow_rate']
      flow_rate_fraction_schedule = model_add_schedule(model, space_type_properties['service_water_heating_schedule'])
      service_water_temperature_si = space_type_properties['service_water_heating_target_temperature']
      service_water_fraction_sensible = space_type_properties['service_water_heating_fraction_sensible']
      service_water_fraction_latent = space_type_properties['service_water_heating_fraction_latent']
      floor_area_si = space_type_hash[space_type][:floor_area]
      floor_area_ip = OpenStudio.convert(floor_area_si, 'm^2', 'ft^2').get

      # next if no service water heating demand
      next unless gal_hr_per_area.to_f > 0.0 || gal_hr_peak_flow_rate.to_f > 0.0

      # If there is no SWH schedule specified, assume
      # that there should be no SWH consumption for this space type.
      unless flow_rate_fraction_schedule
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "No service water heating schedule was specified for #{space_type.name}, an always off schedule will be used and no water will be used.")
        flow_rate_fraction_schedule = model.alwaysOffDiscreteSchedule
      end

      if (stds_bldg_type == 'MidriseApartment' && stds_space_type.include?('Apartment')) || stds_bldg_type == 'StripMall'
        num_units = space_type_hash[space_type][:num_units].round
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding dedicated water heating fpr #{num_units} #{space_type.name} units, each with max flow rate of #{gal_hr_peak_flow_rate} gal/hr per.")

        # add water use equipment definition
        water_use_equip_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
        water_use_equip_def.setName("#{space_type.name} SWH def")
        peak_flow_rate_si = OpenStudio.convert(gal_hr_peak_flow_rate, 'gal/hr', 'm^3/s').get
        water_use_equip_def.setPeakFlowRate(peak_flow_rate_si)
        target_temp = service_water_temperature_si # in spreadsheet in si, no conversion needed unless that changes
        name = "#{target_temp} C"
        if water_use_def_schedules.key?(name)
          target_temperature_sch = water_use_def_schedules[name]
        else
          target_temperature_sch = model_add_constant_schedule_ruleset(model, target_temp, name)
          water_use_def_schedules[name] = target_temperature_sch
        end
        water_use_equip_def.setTargetTemperatureSchedule(target_temperature_sch)
        name = "#{service_water_fraction_sensible} Fraction"
        if water_use_def_schedules.key?(name)
          service_water_fraction_sensible_sch = water_use_def_schedules[name]
        else
          service_water_fraction_sensible_sch = model_add_constant_schedule_ruleset(model, service_water_fraction_sensible, name)
          water_use_def_schedules[name] = service_water_fraction_sensible_sch
        end
        water_use_equip_def.setSensibleFractionSchedule(service_water_fraction_sensible_sch)
        name = "#{service_water_fraction_latent} Fraction"
        if water_use_def_schedules.key?(name)
          service_water_fraction_latent_sch = water_use_def_schedules[name]
        else
          service_water_fraction_latent_sch = model_add_constant_schedule_ruleset(model, service_water_fraction_sensible, name)
          water_use_def_schedules[name] = service_water_fraction_latent_sch
        end
        water_use_equip_def.setLatentFractionSchedule(service_water_fraction_latent_sch)

        # add water use equipment, connection, and loop for each unit
        num_units.times do |i|
          # add water use equipment
          water_use_equip = OpenStudio::Model::WaterUseEquipment.new(water_use_equip_def)
          water_use_equip.setFlowRateFractionSchedule(flow_rate_fraction_schedule)
          water_use_equip.setName("#{space_type.name} SWH #{i + 1}")

          # add water use connection
          water_use_connection = OpenStudio::Model::WaterUseConnections.new(model)
          water_use_connection.addWaterUseEquipment(water_use_equip)
          water_use_connection.setName("#{space_type.name} WUC #{i + 1}")

          # gather inputs for add_swh_loop
          # default fuel, capacity, and volume from Table A.1. Water Heating Equipment Enhancements to ASHRAE Standard 90.1 Prototype Building Models
          # temperature, pump head, motor efficiency, and parasitic load from Prototype Inputs
          sys_name = "#{space_type.name} Service Water Loop #{i + 1}"
          water_heater_thermal_zone = nil
          service_water_temperature = service_water_temperature_si
          service_water_pump_head = 0.01
          service_water_pump_motor_efficiency = 1.0
          water_heater_fuel = if fuel.nil?
                                'Electric'
                              else
                                fuel
                              end
          if stds_bldg_type == 'MidriseApartment'
            water_heater_capacity = OpenStudio.convert(15.0, 'kBtu/hr', 'W').get
            water_heater_volume = OpenStudio.convert(50.0, 'gal', 'm^3').get
            parasitic_fuel_consumption_rate = 0.0 # Prototype inputs has 87.75W but prototype IDF's use 0
          else # StripMall
            water_heater_capacity = OpenStudio.convert(12.0, 'kBtu/hr', 'W').get
            water_heater_volume = OpenStudio.convert(40.0, 'gal', 'm^3').get
            parasitic_fuel_consumption_rate = 173.0
          end

          # make loop for each unit and add on water use equipment
          unit_hot_water_loop = model_add_swh_loop(model,
                                                   sys_name,
                                                   water_heater_thermal_zone,
                                                   service_water_temperature,
                                                   service_water_pump_head,
                                                   service_water_pump_motor_efficiency,
                                                   water_heater_capacity,
                                                   water_heater_volume,
                                                   water_heater_fuel,
                                                   parasitic_fuel_consumption_rate,
                                                   stds_bldg_type)

          # Connect the water use connection to the SWH loop
          unit_hot_water_loop.addDemandBranchForComponent(water_use_connection)

          # apply efficiency to hot water heater
          unit_hot_water_loop.supplyComponents.sort.each do |component|
            next if component.to_WaterHeaterMixed.empty?
            component = component.to_WaterHeaterMixed.get
            water_heater_mixed_apply_efficiency(component)
          end

          # add to list of systems
          swh_systems << unit_hot_water_loop
        end

      elsif stds_space_type.include?('Kitchen') || stds_space_type.include?('Laundry')
        gal_hr_peak_flow_rate = gal_hr_per_area * floor_area_ip
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding dedicated water heating for #{space_type.name} space type with max flow rate of #{gal_hr_peak_flow_rate.round} gal/hr.")

        # add water use equipment definition
        water_use_equip_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
        water_use_equip_def.setName("#{space_type.name} SWH def")
        peak_flow_rate_si = OpenStudio.convert(gal_hr_peak_flow_rate, 'gal/hr', 'm^3/s').get
        water_use_equip_def.setPeakFlowRate(peak_flow_rate_si)
        target_temp = service_water_temperature_si # in spreadsheet in si, no conversion needed unless that changes
        name = "#{target_temp} C"
        if water_use_def_schedules.key?(name)
          target_temperature_sch = water_use_def_schedules[name]
        else
          target_temperature_sch = model_add_constant_schedule_ruleset(model, target_temp, name)
          water_use_def_schedules[name] = target_temperature_sch
        end
        water_use_equip_def.setTargetTemperatureSchedule(target_temperature_sch)
        name = "#{service_water_fraction_sensible} Fraction"
        if water_use_def_schedules.key?(name)
          service_water_fraction_sensible_sch = water_use_def_schedules[name]
        else
          service_water_fraction_sensible_sch = model_add_constant_schedule_ruleset(model, service_water_fraction_sensible, name)
          water_use_def_schedules[name] = service_water_fraction_sensible_sch
        end
        water_use_equip_def.setSensibleFractionSchedule(service_water_fraction_sensible_sch)
        name = "#{service_water_fraction_latent} Fraction"
        if water_use_def_schedules.key?(name)
          service_water_fraction_latent_sch = water_use_def_schedules[name]
        else
          service_water_fraction_latent_sch = model_add_constant_schedule_ruleset(model, service_water_fraction_sensible, name)
          water_use_def_schedules[name] = service_water_fraction_latent_sch
        end
        water_use_equip_def.setLatentFractionSchedule(service_water_fraction_latent_sch)

        # add water use equipment
        water_use_equip = OpenStudio::Model::WaterUseEquipment.new(water_use_equip_def)
        water_use_equip.setFlowRateFractionSchedule(flow_rate_fraction_schedule)
        water_use_equip.setName("#{space_type.name} SWH")

        # add water use connection
        water_use_connection = OpenStudio::Model::WaterUseConnections.new(model)
        water_use_connection.addWaterUseEquipment(water_use_equip)
        water_use_connection.setName("#{space_type.name} WUC")

        # gather inputs for add_swh_loop
        sys_name = "#{space_type.name} Service Water Loop"
        water_heater_thermal_zone = nil
        water_heater_temp_si = 60.0 # C
        service_water_pump_head = 0.01
        service_water_pump_motor_efficiency = 1.0
        water_heater_fuel = if fuel.nil?
                              'Gas'
                            else
                              fuel
                            end

        # find_water_heater_capacity_volume_and_parasitic
        water_use_equipment_array = [water_use_equip]
        water_heater_sizing = model_find_water_heater_capacity_volume_and_parasitic(model, water_use_equipment_array)
        water_heater_capacity = water_heater_sizing[:water_heater_capacity]
        water_heater_volume = water_heater_sizing[:water_heater_volume]
        parasitic_fuel_consumption_rate = water_heater_sizing[:parasitic_fuel_consumption_rate]

        # make loop for each unit and add on water use equipment
        dedicated_hot_water_loop = model_add_swh_loop(model,
                                                      sys_name,
                                                      water_heater_thermal_zone,
                                                      water_heater_temp_si,
                                                      service_water_pump_head,
                                                      service_water_pump_motor_efficiency,
                                                      water_heater_capacity,
                                                      water_heater_volume,
                                                      water_heater_fuel,
                                                      parasitic_fuel_consumption_rate,
                                                      stds_bldg_type)

        # Connect the water use connection to the SWH loop
        dedicated_hot_water_loop.addDemandBranchForComponent(water_use_connection)

        # find water heater
        dedicated_hot_water_loop.supplyComponents.sort.each do |component|
          next if component.to_WaterHeaterMixed.empty?
          water_heater = component.to_WaterHeaterMixed.get

          # apply efficiency to hot water heater
          water_heater_mixed_apply_efficiency(water_heater)
        end

        # add to list of systems
        swh_systems << dedicated_hot_water_loop

        # add booster to all kitchens except for QuickServiceRestaurant (QuickServiceRestaurant assumed to use chemicals instead of hotter water)
        # boosters are all 6 gal elec but heating capacity varies from 3 to 19 (kBtu/hr) for prototype buildings
        if stds_space_type.include?('Kitchen') && stds_bldg_type != 'QuickServiceRestaurant'

          # find_water_heater_capacity_volume_and_parasitic
          water_use_equipment_array = [water_use_equip]
          inlet_temp_ip = OpenStudio.convert(service_water_temperature_si, 'C', 'F').get # pre-booster temp
          outlet_temp_ip = inlet_temp_ip + 40.0
          peak_flow_fraction = 0.6 # assume 60% of peak for dish washing
          water_heater_sizing = model_find_water_heater_capacity_volume_and_parasitic(model, water_use_equipment_array, pipe_hash = {}, 1.0, 1.0, inlet_temp_ip, outlet_temp_ip, peak_flow_fraction)
          water_heater_capacity = water_heater_sizing[:water_heater_capacity]

          # gather additional inputs for add_swh_booster
          water_heater_volume = OpenStudio.convert(6, 'gal', 'm^3').get
          water_heater_fuel = 'Electric'
          booster_water_temperature = 82.22 # C
          parasitic_fuel_consumption_rate = 0.0
          booster_water_heater_thermal_zone = nil

          # add_swh_booster
          booster_service_water_loop = model_add_swh_booster(model,
                                                             dedicated_hot_water_loop,
                                                             water_heater_capacity,
                                                             water_heater_volume,
                                                             water_heater_fuel,
                                                             booster_water_temperature,
                                                             parasitic_fuel_consumption_rate,
                                                             booster_water_heater_thermal_zone,
                                                             stds_bldg_type)

          # find water heater
          booster_service_water_loop.supplyComponents.sort.each do |component|
            next if component.to_WaterHeaterMixed.empty?
            water_heater = component.to_WaterHeaterMixed.get

            # apply efficiency to hot water heater
            water_heater_mixed_apply_efficiency(water_heater)
          end

          # rename booster loop
          booster_service_water_loop.setName("#{space_type.name} Booster Service Water Loop")
          OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding Electric Booster water heater for #{space_type.name} on a loop named #{booster_service_water_loop.name}.")

        end

      else # store water use equip by building type in hash so can add general building type hot water loop

        gal_hr_peak_flow_rate = gal_hr_per_area * floor_area_ip
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding water heating for #{space_type.name} space type with max flow rate of #{gal_hr_peak_flow_rate.round} gal/hr on a shared loop.")

        # add water use equipment definition
        water_use_equip_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
        water_use_equip_def.setName("#{space_type.name} SWH def")
        peak_flow_rate_si = OpenStudio.convert(gal_hr_peak_flow_rate, 'gal/hr', 'm^3/s').get
        water_use_equip_def.setPeakFlowRate(peak_flow_rate_si)
        target_temp = service_water_temperature_si # in spreadsheet in si, no conversion needed unless that changes
        name = "#{target_temp} C"
        if water_use_def_schedules.key?(name)
          target_temperature_sch = water_use_def_schedules[name]
        else
          target_temperature_sch = model_add_constant_schedule_ruleset(model, target_temp, name)
          water_use_def_schedules[name] = target_temperature_sch
        end
        water_use_equip_def.setTargetTemperatureSchedule(target_temperature_sch)
        name = "#{service_water_fraction_sensible} Fraction"
        if water_use_def_schedules.key?(name)
          service_water_fraction_sensible_sch = water_use_def_schedules[name]
        else
          service_water_fraction_sensible_sch = model_add_constant_schedule_ruleset(model, service_water_fraction_sensible, name)
          water_use_def_schedules[name] = service_water_fraction_sensible_sch
        end
        water_use_equip_def.setSensibleFractionSchedule(service_water_fraction_sensible_sch)
        name = "#{service_water_fraction_latent} Fraction"
        if water_use_def_schedules.key?(name)
          service_water_fraction_latent_sch = water_use_def_schedules[name]
        else
          service_water_fraction_latent_sch = model_add_constant_schedule_ruleset(model, service_water_fraction_sensible, name)
          water_use_def_schedules[name] = service_water_fraction_latent_sch
        end
        water_use_equip_def.setLatentFractionSchedule(service_water_fraction_latent_sch)

        # add water use equipment
        water_use_equip = OpenStudio::Model::WaterUseEquipment.new(water_use_equip_def)
        water_use_equip.setFlowRateFractionSchedule(flow_rate_fraction_schedule)
        water_use_equip.setName("#{space_type.name} SWH")

        if water_use_equipment_hash.key?(stds_bldg_type)
          water_use_equipment_hash[stds_bldg_type] << water_use_equip
        else
          water_use_equipment_hash[stds_bldg_type] = [water_use_equip]
        end

      end
    end

    # get building floor area and effective number of stories
    bldg_floor_area = model.getBuilding.floorArea
    bldg_effective_num_stories_hash = model_effective_num_stories(model)
    bldg_effective_num_stories = bldg_effective_num_stories_hash[:below_grade] + bldg_effective_num_stories_hash[:above_grade]

    # add non-dedicated system(s) here. Separate systems for water use equipment from different building types
    water_use_equipment_hash.sort.each do |stds_bldg_type, water_use_equipment_array|
      # gather inputs for add_swh_loop
      sys_name = "#{stds_bldg_type} Shared Service Water Loop"
      water_heater_thermal_zone = nil
      water_heater_temp_si = 60.0

      # find pump values
      # Table A.2 in PrototypeModelEnhancements_2014_0.pdf shows 10ft on everything except SecondarySchool which has 11.4ft
      # todo - if SmallOffice then shouldn't have circulating pump
      if ['Office', 'PrimarySchool', 'Outpatient', 'Hospital', 'SmallHotel', 'LargeHotel', 'FullServiceRestaurant', 'HighriseApartment'].include?(stds_bldg_type)
        service_water_pump_head = OpenStudio.convert(10.0, 'ftH_{2}O', 'Pa').get
        service_water_pump_motor_efficiency = 0.3
        if circulating.nil? then
          irculating = true
        end
        if pipe_insul_in.nil? then
          pipe_insul_in = 0.5
        end
      elsif ['SecondarySchool'].include?(stds_bldg_type)
        service_water_pump_head = OpenStudio.convert(11.4, 'ftH_{2}O', 'Pa').get
        service_water_pump_motor_efficiency = 0.3
        if circulating.nil? then
          irculating = true
        end
        if pipe_insul_in.nil? then
          pipe_insul_in = 0.5
        end
      else # values for non-circulating pump
        service_water_pump_head = 0.01
        service_water_pump_motor_efficiency = 1.0
        if circulating.nil? then
          irculating = false
        end
        if pipe_insul_in.nil? then
          pipe_insul_in = 0.0
        end
      end

      # TODO: - add building type or sice specific logic or just assume Gas? (SmallOffice and Warehouse are only non unit prototypes with Electric heating)
      water_heater_fuel = if fuel.nil?
                            'Gas'
                          else
                            fuel
                          end

      bldg_type_floor_area = 0.0
      space_type_hash.sort.each do |space_type, hash|
        next if hash[:stds_bldg_type] != stds_bldg_type
        bldg_type_floor_area += hash[:floor_area]
      end

      # inputs for find_water_heater_capacity_volume_and_parasitic
      pipe_hash = {}
      pipe_hash[:floor_area] = bldg_type_floor_area
      pipe_hash[:effective_num_stories] = bldg_effective_num_stories * (bldg_type_floor_area / bldg_floor_area)
      pipe_hash[:circulating] = circulating
      pipe_hash[:insulation_thickness] = pipe_insul_in

      # find_water_heater_capacity_volume_and_parasitic
      water_heater_sizing = model_find_water_heater_capacity_volume_and_parasitic(model, water_use_equipment_array, pipe_hash)
      water_heater_capacity = water_heater_sizing[:water_heater_capacity]
      water_heater_volume = water_heater_sizing[:water_heater_volume]
      parasitic_fuel_consumption_rate = water_heater_sizing[:parasitic_fuel_consumption_rate]
      if parasitic_fuel_consumption_rate > 0
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding parasitic loss for #{stds_bldg_type} loop of #{parasitic_fuel_consumption_rate.round} Btu/hr.")
      end

      # make loop for each unit and add on water use equipment
      shared_hot_water_loop = model_add_swh_loop(model,
                                                 sys_name,
                                                 water_heater_thermal_zone,
                                                 water_heater_temp_si,
                                                 service_water_pump_head,
                                                 service_water_pump_motor_efficiency,
                                                 water_heater_capacity,
                                                 water_heater_volume,
                                                 water_heater_fuel,
                                                 parasitic_fuel_consumption_rate,
                                                 stds_bldg_type)

      # find water heater
      shared_hot_water_loop.supplyComponents.sort.each do |component|
        next if component.to_WaterHeaterMixed.empty?
        water_heater = component.to_WaterHeaterMixed.get

        # apply efficiency to hot water heater
        water_heater_mixed_apply_efficiency(water_heater)
      end

      # loop through water use equipment
      water_use_equipment_array.sort.each do |water_use_equip|
        # add water use connection
        water_use_connection = OpenStudio::Model::WaterUseConnections.new(model)
        water_use_connection.addWaterUseEquipment(water_use_equip)
        water_use_connection.setName(water_use_equip.name.get.gsub('SWH', 'WUC'))

        # Connect the water use connection to the SWH loop
        shared_hot_water_loop.addDemandBranchForComponent(water_use_connection)
      end

      # add to list of systems
      swh_systems << shared_hot_water_loop

      OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding shared water heating loop for #{stds_bldg_type}.")
    end

    return swh_systems
  end

  # set capacity, volume, and parasitic
  #
  # @param water_use_equipment_array [Array] array of water use equipment objects that will be using this water heater
  # @param storage_to_cap_ratio [Double]  gal of storage to kBtu/hr of capacitiy
  # @param htg_eff [Double] fraction
  # @param inlet_temp_ip [Double] cold water temperature F
  # @param target_temp_ip [Double] F
  # @return [Hash] hash with values needed to size water heater made with downstream method
  def model_find_water_heater_capacity_volume_and_parasitic(model, water_use_equipment_array, pipe_hash = {}, storage_to_cap_ratio = 1.0, htg_eff = 0.8, inlet_temp_ip = 40.0, target_temp_ip = 140.0, peak_flow_fraction = 1.0)
    # A.1.4 Total Storage Volume and Water Heater Capacity of PrototypeModelEnhancements_2014_0.pdf shows 1 gallon of storage to 1 kBtu/h of capacity

    water_heater_sizing = {}

    # get water use equipment
    max_flow_rate_array = [] # gallons per hour
    water_use_equipment_array.sort.each do |water_use_equip|
      water_use_equip_sch = water_use_equip.flowRateFractionSchedule
      next if water_use_equip_sch.empty?
      water_use_equip_sch = water_use_equip_sch.get
      if water_use_equip_sch.to_ScheduleRuleset.is_initialized
        water_use_equip_sch = water_use_equip_sch.to_ScheduleRuleset.get
        max_sch_value = schedule_ruleset_annual_min_max_value(water_use_equip_sch)['max']
      elsif water_use_equip_sch.to_ScheduleConstant.is_initialized
        water_use_equip_sch = water_use_equip_sch.to_ScheduleConstant.get
        max_sch_value = schedule_constant_annual_min_max_value(water_use_equip_sch)['max']
      elsif water_use_equip_sch.to_ScheduleCompact.is_initialized
        water_use_equip_sch = water_use_equip_sch.to_ScheduleCompact.get
        max_sch_value = schedule_compact_annual_min_max_value(water_use_equip_sch)['max']
      end

      # get water_use_equip_def to get max flow rate
      water_use_equip_def = water_use_equip.waterUseEquipmentDefinition
      peak_flow_rate = water_use_equip_def.peakFlowRate

      # calculate adjusted flow rate
      adjusted_peak_flow_rate_si = max_sch_value * peak_flow_rate
      adjusted_peak_flow_rate_ip = OpenStudio.convert(adjusted_peak_flow_rate_si, 'm^3/s', 'gal/min').get
      max_flow_rate_array << adjusted_peak_flow_rate_ip * 60.0 # min per hour
    end

    # warn if max_flow_rate_array size doesn't match equipment size (one or more didn't have ruleset schedule)
    if max_flow_rate_array.size != water_use_equipment_array.size
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', 'One or more Water Use Equipment Fraction Flow Rate Scheules were not Schedule Rulestes and were excluding from Water Heating Sizing.')
    end

    # sum gpm values from water use equipment to use in formula
    adjusted_flow_rate_sum = max_flow_rate_array.inject(:+)

    # use formula to calculate volume and capacity based on analysis of combined water use equipment maximum flow rates and schedules
    # Max gal/hr * 8.4 lb/gal * 1 Btu/lb F * (120F - 40F)/0.8 = Btu/hr
    water_heater_capacity_ip = peak_flow_fraction * adjusted_flow_rate_sum * 8.4 * 1.0 * (target_temp_ip - inlet_temp_ip) / htg_eff
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Capacity of #{water_heater_capacity_ip} Btu/hr = #{peak_flow_fraction} peak fraction * #{adjusted_flow_rate_sum.round} gal/hr * 8.4 lb/gal * 1.0 Btu/lb F * (#{target_temp_ip.round} - #{inlet_temp_ip.round} deltaF / #{htg_eff} htg eff).")
    water_heater_capacity_si = OpenStudio.convert(water_heater_capacity_ip, 'Btu/hr', 'W').get
    # Assume 1 gal of volume per 1 kBtu/hr of heating capacity
    water_heater_volume_ip = OpenStudio.convert(water_heater_capacity_ip, 'Btu/hr', 'kBtu/hr').get
    # increase tank size to 40 galons if calculated value is smaller
    if water_heater_volume_ip < 40.0 # gal
      water_heater_volume_ip = 40.0
    end
    water_heater_volume_si = OpenStudio.convert(water_heater_volume_ip, 'gal', 'm^3').get

    # populate return hash
    water_heater_sizing[:water_heater_capacity] = water_heater_capacity_si
    water_heater_sizing[:water_heater_volume] = water_heater_volume_si

    # get pipe length (formula from A.3.1 PrototypeModelEnhancements_2014_0.pdf)
    if !pipe_hash.empty?

      pipe_length = 2.0 * (Math.sqrt(pipe_hash[:floor_area] / pipe_hash[:effective_num_stories]) + (10.0 * (pipe_hash[:effective_num_stories] - 1.0)))
      pipe_length_ip = OpenStudio.convert(pipe_length, 'm', 'ft').get

      # calculate pipe dump (from A.4.1)
      pipe_dump = pipe_length_ip * 0.689 # Btu/hr

      pipe_loss_per_foot = if pipe_hash[:circulating]
                             if pipe_hash[:insulation_thickness] >= 1.0
                               16.10
                             elsif pipe_hash[:insulation_thickness] >= 0.5
                               17.5
                             else
                               30.8
                             end
                           else
                             if pipe_hash[:insulation_thickness] >= 1.0
                               11.27
                             elsif pipe_hash[:insulation_thickness] >= 0.5
                               12.25
                             else
                               28.07
                             end
                           end

      # calculate pipe loss (from Table A.3 in section A.4.2)
      pipe_loss = pipe_length * pipe_loss_per_foot # Btu/hr

      # calculate parasitic loss
      water_heater_sizing[:parasitic_fuel_consumption_rate] = pipe_dump + pipe_loss
    else
      water_heater_sizing[:parasitic_fuel_consumption_rate] = 0.0
    end

    return water_heater_sizing
  end
end