docs: def auto_assign_shifts(semester=None, pool=None, profiles=None, shifts=None):
      """
      Auto-assigns profiles to regular workshifts.
  
      Parameters
      ----------
      semester : workshift.models.Semester, optional
      pool : workshift.models.WorkshiftPool, optional
      profiles : list of workshift.models.WorkshiftProfile, optional
      shifts : list of workshift.models.RegularWorkshift, optional
      """
      if semester is None:
          try:
              semester = Semester.objects.get(current=True)
          except (Semester.DoesNotExist, Semester.MultipleObjectsReturned):
              return
      if pool is None:
          pool = WorkshiftPool.objects.get(
              semester=semester,
              is_primary=True,
          )
      if profiles is None:
          profiles = WorkshiftProfile.objects.filter(
              semester=semester,
          ).order_by('preference_save_time')
      if shifts is None:
          shifts = RegularWorkshift.objects.filter(
              pool=pool,
              workshift_type__assignment=WorkshiftType.AUTO_ASSIGN,
          )
  
      shifts = set([
          shift
          for shift in shifts
          if shift.current_assignees.count() < shift.count
      ])
      profiles = list(profiles)
  
      # List of hours assigned to each profile
      hours_mapping = defaultdict(float)
  
      # Pre-process, rank shifts by their times / preferences
      rankings = defaultdict(set)
  
      # Initialize with already-assigned shifts
      for profile in profiles:
          pool_hours = profile.pool_hours.get(pool=pool)
          hours_mapping[profile] = float(pool_hours.assigned_hours)
  
          for shift in shifts:
              # Skip shifts that put a member over their hour requirement
              added_hours = float(shift.hours) + hours_mapping[profile]
              if added_hours > float(pool_hours.hours):
                  continue
  
              # Check how well this shift fits the member's schedule
              status = is_available(profile, shift)
              if not status or status == TimeBlock.BUSY:
                  continue
  
              try:
                  rating = profile.ratings.get(
                      workshift_type=shift.workshift_type,
                  ).rating
              except WorkshiftRating.DoesNotExist:
                  rating = WorkshiftRating.INDIFFERENT
  
              if rating == WorkshiftRating.DISLIKE:
                  rank = 5
              elif rating == WorkshiftRating.INDIFFERENT:
                  rank = 3
              else:
                  rank = 1
  
              if status != TimeBlock.PREFERRED:
                  rank += 1
  
              rankings[profile, rank].add(shift)
  
      # Assign shifts in a round-robin manner, run until we can't assign anyone
      # any more shifts
      while any(rankings.values()):
          for profile in profiles[:]:
              pool_hours = profile.pool_hours.get(pool=pool)
  
              # Assign a shift, picking from the most preferable groups first
              for rank in range(1, 7):
                  # Update the rankings with the list of available shifts
                  rankings[profile, rank].intersection_update(shifts)
  
                  if rankings[profile, rank]:
                      # Select the shift, starting with those that take the most
                      # hours and fit the best into the workshifter's allotted
                      # hours
                      shift = max(rankings[profile, rank], key=lambda x: x.hours)
  
                      # Assign the person to their shift
                      shift.current_assignees.add(profile)
  
                      hours_mapping[profile] += float(shift.hours)
  
                      # Remove shift from shifts if it has been completely filled
                      if shift.current_assignees.count() >= shift.count:
                          shifts.remove(shift)
  
                      break
  
              # Remove profiles when their hours have all been assigned
              if float(pool_hours.hours) <= hours_mapping[profile]:
                  profiles.remove(profile)
  
                  for rank in range(1, 7):
                      if (profile, rank) in rankings:
                          del rankings[profile, rank]
  
                  continue
  
              # Otherwise, Remove shifts that put people above their weekly
              # allocation
              for rank in range(1, 7):
                  rankings[profile, rank] = set(
                      shift
                      for shift in rankings[profile, rank]
                      if float(shift.hours) + hours_mapping[profile] <=
                      float(pool_hours.hours)
                  )
  
      # Return profiles that were incompletely assigned shifts
      return profiles