docs: def get_sitecol_assetcol(oqparam, haz_sitecol=None, cost_types=()):
      """
      :param oqparam: calculation parameters
      :param haz_sitecol: the hazard site collection
      :param cost_types: the expected cost types
      :returns: (site collection, asset collection, discarded)
      """
      global exposure
      asset_hazard_distance = oqparam.asset_hazard_distance['default']
      if exposure is None:
          # haz_sitecol not extracted from the exposure
          exposure = get_exposure(oqparam)
      if haz_sitecol is None:
          haz_sitecol = get_site_collection(oqparam)
      if oqparam.region_grid_spacing:
          haz_distance = oqparam.region_grid_spacing * 1.414
          if haz_distance != asset_hazard_distance:
              logging.info('Using asset_hazard_distance=%d km instead of %d km',
                           haz_distance, asset_hazard_distance)
      else:
          haz_distance = asset_hazard_distance
  
      if haz_sitecol.mesh != exposure.mesh:
          # associate the assets to the hazard sites
          sitecol, assets_by, discarded = geo.utils.assoc(
              exposure.assets_by_site, haz_sitecol,
              haz_distance, 'filter', exposure.asset_refs)
          assets_by_site = [[] for _ in sitecol.complete.sids]
          num_assets = 0
          for sid, assets in zip(sitecol.sids, assets_by):
              assets_by_site[sid] = assets
              num_assets += len(assets)
          logging.info(
              'Associated %d assets to %d sites', num_assets, len(sitecol))
      else:
          # asset sites and hazard sites are the same
          sitecol = haz_sitecol
          assets_by_site = exposure.assets_by_site
          discarded = []
          logging.info('Read %d sites and %d assets from the exposure',
                       len(sitecol), sum(len(a) for a in assets_by_site))
      assetcol = asset.AssetCollection(
          exposure, assets_by_site, oqparam.time_event)
      if assetcol.occupancy_periods:
          missing = set(cost_types) - set(exposure.cost_types['name']) - set(
              ['occupants'])
      else:
          missing = set(cost_types) - set(exposure.cost_types['name'])
      if missing and not oqparam.calculation_mode.endswith('damage'):
          raise InvalidFile('The exposure %s is missing %s' %
                            (oqparam.inputs['exposure'], missing))
      if (not oqparam.hazard_calculation_id and 'gmfs' not in oqparam.inputs
              and 'hazard_curves' not in oqparam.inputs
              and sitecol is not sitecol.complete):
          assetcol = assetcol.reduce_also(sitecol)
      return sitecol, assetcol, discarded