Esempio n. 1
0
def availability_submit(request, roster):
    try:
        roster = Roster.objects.get(name = roster)
    except Roster.DoesNotExist:
        return notification(request, 'Er is geen rooster genaamd \'%s\' gevonden' % roster)
    if not roster.state == 1:
        return notification(request, 'Je kunt nog niet, of niet meer, je beschikbaarheid doorgeven')
    
    if not RosterWorker.objects.filter(roster = roster, user = request.user):
        return notification(request, 'Je bent niet uitgenodigd voor dit rooster')
    
    if request.POST['shifts']:
        shift_pks = map(int, request.POST['shifts'].split(';'))
    else:
        shift_pks = []
    
    year = int(request.POST['year'])
    week = int(request.POST['week'])
    monday = week_start_date(year, week)
    day = datetime.timedelta(days = 1)
    
    weekslots = TimeSlot.objects.filter(roster = roster, start__gt = monday, end__lt = monday + 7 * day)
    Availability.objects.filter(user = request.user, timeslot__pk__in = map(lambda sl: sl.pk, weekslots)).delete()
    
    for shift_pk in shift_pks:
        shift = TimeSlot.objects.get(pk = shift_pk)
        Availability(user = request.user, timeslot = shift).save()
    
    return redirect(to = reverse('availability', kwargs = {'roster': roster.name, 'year': year, 'week': week}))
Esempio n. 2
0
def availability_copy(request, roster, year, week):
    try:
        roster = Roster.objects.get(name = roster)
    except Roster.DoesNotExist:
        return notification(request, 'Er is geen rooster genaamd \'%s\' gevonden' % roster)
    if not roster.state == 1:
        return notification(request, 'Je kunt nog niet, of niet meer, je beschikbaarheid doorgeven')
    
    year = int(year)
    week = int(week)
    if year < roster.start.year or (week < roster.start.isocalendar()[1] and year == roster.start.year) \
    or year > roster.end.year or (week > roster.end.isocalendar()[1] and year == roster.end.year):
        return notification('Geen valide week')
    
    monday = week_start_date(year, week)
    day = datetime.timedelta(days = 1)
    
    weekslots = TimeSlot.objects.filter(roster = roster, start__gt = monday, end__lt = monday + 7 * day)
    weekavailabilities = Availability.objects.filter(user = request.user, timeslot__pk__in = map(lambda sl: sl.pk, weekslots))
    
    weeks = 0 * day
    while monday + weeks >= datetime.datetime.combine(roster.start, datetime.time()):
        weeks -= 7 * day
    while monday + weeks <= datetime.datetime.combine(roster.end, datetime.time()):
        if not ((monday + weeks).isocalendar()[0] == year and (monday + weeks).isocalendar()[1] == week):
            weekslots = TimeSlot.objects.filter(roster = roster, start__gt = monday + weeks, end__lt = monday + weeks + 7 * day)
            Availability.objects.filter(user = request.user, timeslot__pk__in = map(lambda sl: sl.pk, weekslots)).delete()
        weeks += 7 * day
    
    for weekav in weekavailabilities:
        weeks = 0 * day
        while weekav.timeslot.start + weeks >= datetime.datetime.combine(roster.start, datetime.time()):
            weeks -= 7 * day
        while weekav.timeslot.end + weeks <= datetime.datetime.combine(roster.end, datetime.time()):
            ts_match = TimeSlot.objects.filter(roster = roster, start = weekav.timeslot.start + weeks, end = weekav.timeslot.end + weeks)
            if ts_match:
                if not Availability.objects.filter(user = request.user, timeslot = ts_match):
                    Availability(user = request.user, timeslot = ts_match[0]).save()
            weeks += 7 * day
    
    return redirect(to = reverse('availability', kwargs = {'roster': roster.name, 'year': year, 'week': week}))
Esempio n. 3
0
def availability(request, roster, year = None, week = None):
    try:
        roster = Roster.objects.get(name = roster)
    except Roster.DoesNotExist:
        return notification(request, 'Er is geen rooster genaamd \'%s\' gevonden' % roster)
    if not roster.state == 1:
        return notification(request, 'Je kunt nog niet, of niet meer, je beschikbaarheid doorgeven') 
    
    if not RosterWorker.objects.filter(roster = roster, user = request.user):
        return notification(request, 'Je bent niet uitgenodigd voor dit rooster')
    
    if year == None or week == None:
        return redirect(to = reverse('availability', kwargs={'roster': roster.name, 'year': roster.start.year, 'week': roster.start.isocalendar()[1]}))
    else:
        year = int(year)
        week = int(week)
    if year < roster.start.year or (week < roster.start.isocalendar()[1] and year == roster.start.year):
        return redirect(to = reverse('availability', kwargs={'roster': roster.name, 'year': roster.start.year, 'week': roster.start.isocalendar()[1]}))
    if year > roster.end.year or (week > roster.end.isocalendar()[1] and year == roster.end.year):
        return redirect(to = reverse('availability', kwargs={'roster': roster.name, 'year': roster.end.year, 'week': roster.end.isocalendar()[1]}))
    
    monday = week_start_date(year, week)
    day = datetime.timedelta(days = 1)
    
    schedule = {
        'monday':  {'date': monday.strftime('%a %d %b'), 'name': 'monday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday, end__lt = monday + day)},
        'tuesday': {'date': (monday + day).strftime('%a %d %b'), 'name': 'tuesday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + day, end__lt = monday + 2 * day)},
        'wednesday': {'date': (monday + 2 * day).strftime('%a %d %b'), 'name': 'wednesday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 2 * day, end__lt = monday + 3 * day)},
        'thursday': {'date': (monday + 3 * day).strftime('%a %d %b'), 'name': 'thursday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 3 * day, end__lt = monday + 4 * day)},
        'friday': {'date': (monday + 4 * day).strftime('%a %d %b'), 'name': 'friday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 4 * day, end__lt = monday + 5 * day)},
        'saturday': {'date': (monday + 5 * day).strftime('%a %d %b'), 'name': 'saturday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 5 * day, end__lt = monday + 6 * day)},
        'sunday': {'date': (monday + 6 * day).strftime('%a %d %b'), 'name': 'sunday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 6 * day, end__lt = monday + 7 * day)},
    }
    
    for schedule_day in schedule.values():
        for timeslot in schedule_day['timeslots']:
            if Availability.objects.filter(user = request.user, timeslot = timeslot).count():
                timeslot.available = True
            else:
                timeslot.available = False
    
    (next_year, next_week) = (monday + 7 * day).isocalendar()[0:2]
    (prev_year, prev_week) = (monday - 7 * day).isocalendar()[0:2]
    
    if prev_year < roster.start.year or (prev_week < roster.start.isocalendar()[1] and prev_year == roster.start.year):
        (prev_year, prev_week) = (None, None)
    if next_year > roster.end.year or (next_week > roster.end.isocalendar()[1] and next_year == roster.end.year):
        (next_year, next_week) = (None, None)
    
    ''' Get jump links to all the weeks '''
    start_monday = week_start_date(roster.start.year, roster.start.isocalendar()[1])
    end_monday = week_start_date(roster.end.year, roster.end.isocalendar()[1])
    oneweek = datetime.timedelta(days = 7)
    mondays = []
    day_k = start_monday
    while day_k <= end_monday:
        mondays.append({'name': day_k.strftime('%d %b'), 'is_this_week': monday == day_k, 'year': day_k.isocalendar()[0], 'week': day_k.isocalendar()[1]})
        day_k += oneweek
    
    return render(request, 'availability.html', {
        'roster': roster, 
        'schedule': schedule,
        'year': year,
        'prev_year': prev_year,
        'next_year': next_year,
        'week': week,
        'prev_week': prev_week,
        'next_week': next_week,
        'mondays': mondays,
    })
Esempio n. 4
0
def final_roster(request, roster, year = None, week = None):
	try:
		roster = Roster.objects.get(name = roster)
	except Roster.DoesNotExist:
		return notification(request, 'Er is geen rooster genaamd \'%s\' gevonden' % roster)
	if not roster.state == 4 and not (roster.state == 3 and request.user.is_staff):
		return redirect(to = reverse('availability', kwargs = {'roster': roster.name}))
	if year == None or week == None:
		if datetime.date.today() < roster.start:
			year = roster.start.year
			week = roster.start.isocalendar()[1]
		elif datetime.date.today() > roster.end:
			year = roster.end.year
			week = roster.end.isocalendar()[1]
		else:
			year = datetime.date.today().year
			week = datetime.date.today().isocalendar()[1]
		return redirect(to = reverse('final_roster', kwargs={'roster': roster.name, 'year': year, 'week': week}))
	else:
		year = int(year)
		week = int(week)
	if year < roster.start.year or (week < roster.start.isocalendar()[1] and year == roster.start.year):
		return redirect(to = reverse('final_roster', kwargs={'roster': roster.name, 'year': roster.start.year, 'week': roster.start.isocalendar()[1]}))
	if year > roster.end.year or (week > roster.end.isocalendar()[1] and year == roster.end.year):
		return redirect(to = reverse('final_roster', kwargs={'roster': roster.name, 'year': roster.end.year, 'week': roster.end.isocalendar()[1]}))

	monday = week_start_date(year, week)
	day = datetime.timedelta(days = 1)

	schedule = {
		'monday':  {'date': monday.strftime('%a %d %b'), 'name': 'monday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday, end__lt = monday + day)},
		'tuesday': {'date': (monday + day).strftime('%a %d %b'), 'name': 'tuesday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + day, end__lt = monday + 2 * day)},
		'wednesday': {'date': (monday + 2 * day).strftime('%a %d %b'), 'name': 'wednesday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 2 * day, end__lt = monday + 3 * day)},
		'thursday': {'date': (monday + 3 * day).strftime('%a %d %b'), 'name': 'thursday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 3 * day, end__lt = monday + 4 * day)},
		'friday': {'date': (monday + 4 * day).strftime('%a %d %b'), 'name': 'friday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 4 * day, end__lt = monday + 5 * day)},
		'saturday': {'date': (monday + 5 * day).strftime('%a %d %b'), 'name': 'saturday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 5 * day, end__lt = monday + 6 * day)},
		'sunday': {'date': (monday + 6 * day).strftime('%a %d %b'), 'name': 'sunday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 6 * day, end__lt = monday + 7 *day)},
	}

	(next_year, next_week) = (monday + 7 * day).isocalendar()[0:2]
	(prev_year, prev_week) = (monday - 7 * day).isocalendar()[0:2]

	if prev_year < roster.start.year or (prev_week < roster.start.isocalendar()[1] and prev_year == roster.start.year):
		(prev_year, prev_week) = (None, None)
	if next_year > roster.end.year or (next_week > roster.end.isocalendar()[1] and next_year == roster.end.year):
		(next_year, next_week) = (None, None)

	urls = {}
	urls['all'] = 'http://' + request.get_host() + reverse('ical_all')
	urls['trade'] = 'http://' + request.get_host() + reverse('ical_trade')
	if request.user.is_authenticated():
		urls['own'] = 'http://' + request.get_host() + reverse('ical_own', kwargs = {'user': request.user.username})
		urls['available'] = 'http://' + request.get_host() + reverse('ical_available', kwargs = {'user': request.user.username})

	user = AnonymousUser()
	if request.user.is_authenticated():
		if RosterWorker.objects.filter(user = request.user, roster = roster) or request.user.is_staff:
			user = request.user

	''' Get jump links to all the weeks '''
	start_monday = week_start_date(roster.start.year, roster.start.isocalendar()[1])
	end_monday = week_start_date(roster.end.year, roster.end.isocalendar()[1])
	oneweek = datetime.timedelta(days = 7)
	mondays = []
	day_k = start_monday
	while day_k <= end_monday:
		mondays.append({'name': day_k.strftime('%d %b'), 'is_this_week': monday == day_k, 'year': day_k.isocalendar()[0], 'week': day_k.isocalendar()[1]})
		day_k += oneweek

	return render(request, 'final_roster.html', {
		'user': user,
		'roster': roster,
		'schedule': schedule,
		'year': year,
		'prev_year': prev_year,
		'next_year': next_year,
		'week': week,
		'prev_week': prev_week,
		'next_week': next_week,
		'urls': urls,
		'mondays': mondays,
	})
Esempio n. 5
0
def all_rosters_txt(request, year = None, week = None):
	rosters = Roster.objects.filter(state = 4).order_by('start')
	if not rosters:
		return notification(request, 'geen roosters gevonden')
	if year == None or week == None:
		year = datetime.date.today().year
		week = datetime.date.today().isocalendar()[1]
		return redirect(to = reverse('all_rosters_txt', kwargs={'year': year, 'week': week}))
	year = int(year)
	week = int(week)

	monday = week_start_date(year, week)
	day = datetime.timedelta(days = 1)

	schedule = [
		{'date': monday.strftime('%a %d %b'), 'name': 'monday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday, end__lt = monday + day)},
		{'date': (monday + day).strftime('%a %d %b'), 'name': 'tuesday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday + day, end__lt = monday + 2 * day)},
		{'date': (monday + 2 * day).strftime('%a %d %b'), 'name': 'wednesday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday + 2 * day, end__lt = monday + 3 * day)},
		{'date': (monday + 3 * day).strftime('%a %d %b'), 'name': 'thursday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday + 3 * day, end__lt = monday + 4 * day)},
		{'date': (monday + 4 * day).strftime('%a %d %b'), 'name': 'friday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday + 4 * day, end__lt = monday + 5 * day)},
		{'date': (monday + 5 * day).strftime('%a %d %b'), 'name': 'saturday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday + 5 * day, end__lt = monday + 6 * day)},
		{'date': (monday + 6 * day).strftime('%a %d %b'), 'name': 'sunday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__qt = monday + 6 * day, end__lt = monday + 7 * day)},
	]

	(next_year, next_week) = (monday + 7 * day).isocalendar()[0:2]
	(prev_year, prev_week) = (monday - 7 * day).isocalendar()[0:2]

	#if prev_year < rosters[0].start.year or (prev_week < rosters[0].start.isocalendar()[1] and prev_year == rosters[0].start.year):
	#    (prev_year, prev_week) = (None, None)
	#if next_year > rosters[0].end.year or (next_week > rosters[0].end.isocalendar()[1] and next_year == rosters[0].end.year):
	#    (next_year, next_week) = (None, None)

	urls = {}
	urls['all'] = 'http://' + request.get_host() + reverse('ical_all')
	urls['trade'] = 'http://' + request.get_host() + reverse('ical_trade')
	if request.user.is_authenticated():
		urls['own'] = 'http://' + request.get_host() + reverse('ical_own', kwargs = {'user': request.user.username})
		urls['available'] = 'http://' + request.get_host() + reverse('ical_available', kwargs = {'user': request.user.username})

	user = AnonymousUser()
	if request.user.is_authenticated():
		if RosterWorker.objects.filter(user = request.user, roster__in = rosters):
			user = request.user

	''' Get jump links to all the weeks '''
	#week_start_date(roster.start.year, roster.start.isocalendar()[1])
	#end_monday = week_start_date(roster.end.year, roster.end.isocalendar()[1])
	oneweek = datetime.timedelta(days = 7)
	start_monday = monday - 5 * oneweek
	end_monday = monday + 5 * oneweek
	mondays = []
	day_k = start_monday
	while day_k <= end_monday:
		mondays.append({'name': day_k.strftime('%d %b'), 'is_this_week': monday == day_k, 'year': day_k.isocalendar()[0], 'week': day_k.isocalendar()[1]})
		day_k += oneweek

	return render(request, 'all_rosters_txt.html', {
		'user': user,
		#'roster': roster,
		'schedule_vals': schedule,
		'year': year,
		'prev_year': prev_year,
		'next_year': next_year,
		'week': week,
		'prev_week': prev_week,
		'next_week': next_week,
		'urls': urls,
		'mondays': mondays,
	})
Esempio n. 6
0
    def handle(self, *args, **options):
        """ Input arguments """
        t_init = time.time()
        try:
            roster = Roster.objects.get(pk=int(args[0]))
        except IndexError:
            print 'Please provide a roster ID'
            show_rosters()
            return
        except ValueError:
            print 'Please provide an integer roster ID\n'
            show_rosters()
            return
        except Roster.DoesNotExist:
            print 'Roster with ID %s not found\n' % args[0]
            show_rosters()
            return

        N = options['steps']
        check_consistency = options['check']
        alpha = options['alpha']

        if roster.state < 1:
            print 'This roster is is not ready to distribute yet, complete other steps first'
            return
        elif roster.state == 3:
            if options['force']:
                roster.state = 2
                roster.save()
            else:
                print 'This roster has already been distributed; use -f to force redistribution'
                return
        elif roster.state > 3:
            print 'This roster has already been distributed'
            return
        elif roster.state == 1:
            roster.state = 2
            roster.save()

        print '* %s *' % roster.name.upper()
        ''' Create the availability data structure '''
        t_load = time.time()
        slotmap = {ts.pk: ts for ts in TimeSlot.objects.filter(roster=roster)}
        availabilities_qs = Availability.objects.filter(
            timeslot__roster=roster)
        t_assign = time.time()
        print 'TIME query data:   %.3fs' % (t_assign - t_load)

        A, D = {}, {}
        degeneracy, duration, similar = {}, {}, {}
        slots = []
        total_hours = 0
        for slot in slotmap.values():
            slots.append(slot)
            D[slot.pk] = slot.degeneracy * [None]
            A[slot.pk] = []
            degeneracy[slot.pk] = slot.degeneracy
            duration[slot.pk] = to_hours(slot.duration)
            similar[slot.pk] = []
            total_hours += slot.degeneracy * to_hours(slot.duration)
            ''' Find similar shifts (exactly a week difference) '''
            monday = week_start_date(slot.start.isocalendar()[0],
                                     slot.start.isocalendar()[1])
            day = datetime.timedelta(days=1)
            weeks = 0 * day
            while monday + weeks >= datetime.datetime.combine(
                    roster.start, datetime.time()):
                weeks -= 7 * day
            while monday + weeks <= datetime.datetime.combine(
                    roster.end, datetime.time()):
                if not ((monday + weeks).isocalendar()[0]
                        == slot.start.isocalendar()[0] and
                        (monday + weeks).isocalendar()[1]
                        == slot.start.isocalendar()[1]):
                    sim_slot = TimeSlot.objects.filter(roster=roster).filter(
                        roster=slot.roster,
                        start=slot.start + weeks,
                        end=slot.end + weeks)
                    if sim_slot:
                        similar[slot.pk].append(sim_slot[0])
                weeks += 7 * day

        for availability in availabilities_qs:
            ''' There should be no duplicates but it happens, somehow. Don't know where
				they come from, but I can't block them at database level because sqlite
				seems to throw random errors for unique_together... '''
            if availability.user.pk not in map_user_pk(
                    A[availability.timeslot.pk]):
                A[availability.timeslot.pk].append(availability)
            else:
                print 'There was a duplicate Availability; #%d removed' % availability.pk
                Availability.objects.get(pk=availability.pk).delete()

        if not N:
            N = 10 * len(slots)
        ''' Calculate the hours '''
        """
			extra hours for flexibility are calculated as:
			alpha * sqrt(user_available_hours / per_person_expected)
			[but never negative]
		"""
        t_hours = time.time()
        print 'TIME assign data:  %.3fs' % (t_hours - t_assign)
        workers = RosterWorker.objects.filter(roster=roster)
        current_hours = {}
        extra_hours = {}
        flexibility = {}
        expected_hours = float(total_hours) / len(workers)
        available_hours = {worker.user.pk: 0 for worker in workers}
        for availability_list in A.values():
            for availability in availability_list:
                available_hours[availability.user.pk] += to_hours(
                    availability.timeslot.duration)
        for worker in workers:
            extra_hours[worker.user.pk] = worker.extra
            flexibility[worker.user.pk] = sqrt(
                available_hours[worker.user.pk] / expected_hours)
            extra_hours[worker.user.pk] += alpha * flexibility[worker.user.pk]
            current_hours[worker.user.pk] = 0
        ''' Monte Carlo (with only favourable steps though) '''
        t_monte = time.time()
        no_change_steps = 0
        counter = 500 * len(slots)
        while no_change_steps < N and counter > 0:
            updated = False
            counter -= 1
            ''' Trivial slots are removed; if there are no slots left we quit  '''
            if not len(slots):
                print 'DETERMINISTIC / TRIVIAL'
                break

            slot = weighted_choice(slots, degeneracy.values())
            if len(A[slot.pk]) <= degeneracy[slot.pk]:
                ''' No competition for this position '''
                for deg in range(degeneracy[slot.pk]):
                    min_av = select_fewest_hours(
                        set(A[slot.pk]) - set(D[slot.pk]), current_hours,
                        extra_hours)
                    ''' Note that one of the positions might have been filled through the recursion mechanism '''
                    if min_av and not D[slot.pk][deg]:
                        (D, current_hours,
                         updated) = assign(min_av, A, D, slot, deg,
                                           current_hours)
                slots.remove(slot)
                degeneracy.pop(slot.pk)
            else:
                deg = random.randint(0, len(D[slot.pk]) - 1)
                if D[slot.pk][deg] == None:
                    ''' No current worker, so no comparison '''
                    worker = random.choice(A[slot.pk])
                    (D, current_hours,
                     updated) = assign(worker, A, D, slot, deg, current_hours)
                elif len(A[slot.pk]) > 1:
                    ''' There is a selected worker, and at least one alternative '''
                    worker = random.choice(A[slot.pk])
                    while worker.pk == D[slot.pk][deg].pk:
                        worker = random.choice(A[slot.pk])
                    (D, current_hours, updated) = switch(slot,
                                                         deg,
                                                         worker,
                                                         A,
                                                         D,
                                                         duration,
                                                         similar,
                                                         current_hours,
                                                         extra_hours,
                                                         recursive=True)
            ''' Count the steps without change (the while loop quits after N of them)
				The only way assign is called multiple times is when there is one succesful
				switch, in which case True is returned anyway '''
            no_change_steps += 1
            if updated:
                no_change_steps = 0
        ''' Prevent unnecessary blanks (generally none are found) '''
        t_post = time.time()
        print 'TIME monte carlo:  %.3fs' % (t_post - t_monte)
        for slot in slots:
            for deg in range(degeneracy[slot.pk]):
                if D[slot.pk][deg] == None:
                    ''' There is an empty slot; find the candidate with fewest hours '''
                    min_av = select_fewest_hours(
                        set(A[slot.pk]) - set(D[slot.pk]), current_hours,
                        extra_hours)
                    if min_av:
                        (D, current_hours,
                         updated) = assign(min_av, A, D, slot, deg,
                                           current_hours)
        ''' Consistency checks '''
        t_check = time.time()
        print 'TIME post check:   %.3fs' % (t_check - t_post)
        if check_consistency:
            all_slot_check = 0
            for slot in slots:
                all_slot_check += to_hours(slot.duration) * slot.degeneracy
                ''' Check the degeneracy values '''
                assert len(D[slot.pk]) == slot.degeneracy
                ''' Check that there aren't more shifts occupied than there are available people '''
                assert len(filter(lambda x: x, D[slot.pk])) <= len(A[slot.pk])
                ''' Check that a person doesn't have two shifts in one slot '''
                assert len(map_user_pk(D[slot.pk])) == len(
                    set(map_user_pk(D[slot.pk])))
            ''' Check that the total hours of all slots does not exceed the assigned hours '''
            all_assign_check = 0
            for slot_pk, avity_list in D.items():
                for deg, avity in enumerate(avity_list):
                    if avity:
                        all_assign_check += to_hours(avity.timeslot.duration)
                        ''' Check that all assigned shifts belong to someone that is available '''
                        assert avity.pk in map_pk(A[slot_pk])
                    else:
                        ''' If the slot is empty, then no one is available that isn't already working '''
                        if len(
                                set(map_pk(A[slot_pk])) -
                                set(map_pk(D[slot_pk]))):
                            print '>> %s | %s  (%d)' % (set(map_pk(
                                A[slot_pk])), set(map_pk(
                                    D[slot_pk])), len(D[slot_pk]))
                            print map_user_pk(A[slot_pk])
                        assert len(
                            set(map_pk(A[slot_pk])) -
                            set(map_pk(D[slot_pk]))) == 0
            ''' check sum of hours (bugged end 2013) and empty slots '''
            current_hours_check = {}
            for rw in workers:
                current_hours_check[rw.user.pk] = 0
            for slot_pk, avity_list in D.items():
                for deg, avity in enumerate(avity_list):
                    if avity is None:
                        ''' check that, for every empty slot, there are fewer availabilities than places '''
                        assert len(A[slot_pk]) <= [
                            slot.degeneracy
                            for slot in slotmap.values() if slot.pk == slot_pk
                        ][0]
                    else:
                        current_hours_check[avity.user.pk] += to_hours(
                            avity.timeslot.duration)
            ''' check that the total hours are the same '''
            assert sum(current_hours.values()) == sum(
                current_hours_check.values())
            assert len(current_hours) == len(current_hours_check)
            ''' check that the hours for individual users are the same '''
            for user_pk in current_hours.keys():
                assert current_hours[user_pk] == current_hours_check[user_pk]
            ''' Check that the hours of assigned shifts match the total of current_hours '''
            assert all_assign_check == sum(current_hours.values())
        ''' Store result '''
        t_store = time.time()
        if check_consistency:
            print 'TIME checks:       %.3fs' % (t_store - t_check)
        else:
            print 'TIME checks:       off'
        roster = Roster.objects.get(pk=roster.pk)
        if not roster.state == 2:
            print 'The state of the roster has changed! There may be a concurrent process. Mission aborted; no changes will be made.'
            return
        Assignment.objects.filter(timeslot__in=slotmap.values()).delete()
        batch = []
        empty_slots = []
        for key, availability_list in D.items():
            for deg, availability in enumerate(availability_list):
                if availability is None:
                    empty_slots.append(slotmap[key])
                else:
                    batch.append(
                        Assignment(user=availability.user,
                                   timeslot=availability.timeslot,
                                   note='shift %d' % deg))
        Assignment.objects.bulk_create(batch)
        roster.state = 3
        roster.save()
        ''' Process the result '''
        t_result = time.time()
        print 'TIME store result: %.3fs' % (t_result - t_store)
        print 'TIME other steps:  %.3fs' % (t_result - t_init -
                                            (t_check - t_monte) -
                                            (t_hours - t_assign) -
                                            (t_assign - t_load) -
                                            (t_result - t_store) -
                                            (t_store - t_check))
        print 'TIME total time:   %.3fs' % (t_result - t_init)
        print 'remaining hours:   %d' % (total_hours -
                                         sum(current_hours.values()))
        for empty_slot in empty_slots:
            print '\t %s (%.1fh)' % (empty_slot, to_hours(empty_slot.duration))
        print 'with alpha = %.3f max extra hours is %.1fh' % (
            alpha, alpha *
            (max(flexibility.values()) - min(flexibility.values())))
        print '       user\tfinal\textra (goal)'
        for user_pk in current_hours.keys():
            print ' %-20s\t%d\t%.1f' % (unicode(get_user_model().objects.get(
                pk=user_pk))[:20], int(
                    current_hours[user_pk]), extra_hours[user_pk])
        if not counter:
            print '(Monte Carlo reached iteration limit)'
Esempio n. 7
0
def all_rosters_txt(request, year = None, week = None):
	rosters = Roster.objects.filter(state = 4).order_by('start')
	if not rosters:
		return notification(request, 'geen roosters gevonden')
	if year == None or week == None:
		year = datetime.date.today().year
		week = datetime.date.today().isocalendar()[1]
		return redirect(to = reverse('all_rosters_txt', kwargs={'year': year, 'week': week}))
	year = int(year)
	week = int(week)

	monday = week_start_date(year, week)
	day = datetime.timedelta(days = 1)

	schedule = [
		{'date': monday.strftime('%a %d %b'), 'name': 'monday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday, end__lt = monday + day)},
		{'date': (monday + day).strftime('%a %d %b'), 'name': 'tuesday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday + day, end__lt = monday + 2 * day)},
		{'date': (monday + 2 * day).strftime('%a %d %b'), 'name': 'wednesday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday + 2 * day, end__lt = monday + 3 * day)},
		{'date': (monday + 3 * day).strftime('%a %d %b'), 'name': 'thursday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday + 3 * day, end__lt = monday + 4 * day)},
		{'date': (monday + 4 * day).strftime('%a %d %b'), 'name': 'friday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday + 4 * day, end__lt = monday + 5 * day)},
		{'date': (monday + 5 * day).strftime('%a %d %b'), 'name': 'saturday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__gt = monday + 5 * day, end__lt = monday + 6 * day)},
		{'date': (monday + 6 * day).strftime('%a %d %b'), 'name': 'sunday', 'timeslots': TimeSlot.objects.filter(roster__in = rosters, start__qt = monday + 6 * day, end__lt = monday + 7 * day)},
	]

	(next_year, next_week) = (monday + 7 * day).isocalendar()[0:2]
	(prev_year, prev_week) = (monday - 7 * day).isocalendar()[0:2]

	#if prev_year < rosters[0].start.year or (prev_week < rosters[0].start.isocalendar()[1] and prev_year == rosters[0].start.year):
	#    (prev_year, prev_week) = (None, None)
	#if next_year > rosters[0].end.year or (next_week > rosters[0].end.isocalendar()[1] and next_year == rosters[0].end.year):
	#    (next_year, next_week) = (None, None)

	urls = {}
	urls['all'] = 'http://' + request.get_host() + reverse('ical_all')
	urls['trade'] = 'http://' + request.get_host() + reverse('ical_trade')
	if request.user.is_authenticated():
		urls['own'] = 'http://' + request.get_host() + reverse('ical_own', kwargs = {'user': request.user.username})
		urls['available'] = 'http://' + request.get_host() + reverse('ical_available', kwargs = {'user': request.user.username})

	user = AnonymousUser()
	if request.user.is_authenticated():
		if RosterWorker.objects.filter(user = request.user, roster__in = rosters):
			user = request.user

	''' Get jump links to all the weeks '''
	#week_start_date(roster.start.year, roster.start.isocalendar()[1])
	#end_monday = week_start_date(roster.end.year, roster.end.isocalendar()[1])
	oneweek = datetime.timedelta(days = 7)
	start_monday = monday - 5 * oneweek
	end_monday = monday + 5 * oneweek
	mondays = []
	day_k = start_monday
	while day_k <= end_monday:
		mondays.append({'name': day_k.strftime('%d %b'), 'is_this_week': monday == day_k, 'year': day_k.isocalendar()[0], 'week': day_k.isocalendar()[1]})
		day_k += oneweek

	return render(request, 'all_rosters_txt.html', {
		'user': user,
		#'roster': roster,
		'schedule_vals': schedule,
		'year': year,
		'prev_year': prev_year,
		'next_year': next_year,
		'week': week,
		'prev_week': prev_week,
		'next_week': next_week,
		'urls': urls,
		'mondays': mondays,
	})
Esempio n. 8
0
def final_roster(request, roster, year = None, week = None):
	try:
		roster = Roster.objects.get(name = roster)
	except Roster.DoesNotExist:
		return notification(request, 'Er is geen rooster genaamd \'%s\' gevonden' % roster)
	if not roster.state == 4 and not (roster.state == 3 and request.user.is_staff):
		return redirect(to = reverse('availability', kwargs = {'roster': roster.name}))
	if year == None or week == None:
		if datetime.date.today() < roster.start:
			year = roster.start.year
			week = roster.start.isocalendar()[1]
		elif datetime.date.today() > roster.end:
			year = roster.end.year
			week = roster.end.isocalendar()[1]
		else:
			year = datetime.date.today().year
			week = datetime.date.today().isocalendar()[1]
		return redirect(to = reverse('final_roster', kwargs={'roster': roster.name, 'year': year, 'week': week}))
	else:
		year = int(year)
		week = int(week)
	if year < roster.start.year or (week < roster.start.isocalendar()[1] and year == roster.start.year):
		return redirect(to = reverse('final_roster', kwargs={'roster': roster.name, 'year': roster.start.year, 'week': roster.start.isocalendar()[1]}))
	if year > roster.end.year or (week > roster.end.isocalendar()[1] and year == roster.end.year):
		return redirect(to = reverse('final_roster', kwargs={'roster': roster.name, 'year': roster.end.year, 'week': roster.end.isocalendar()[1]}))

	monday = week_start_date(year, week)
	day = datetime.timedelta(days = 1)

	schedule = {
		'monday':  {'date': monday.strftime('%a %d %b'), 'name': 'monday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday, end__lt = monday + day)},
		'tuesday': {'date': (monday + day).strftime('%a %d %b'), 'name': 'tuesday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + day, end__lt = monday + 2 * day)},
		'wednesday': {'date': (monday + 2 * day).strftime('%a %d %b'), 'name': 'wednesday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 2 * day, end__lt = monday + 3 * day)},
		'thursday': {'date': (monday + 3 * day).strftime('%a %d %b'), 'name': 'thursday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 3 * day, end__lt = monday + 4 * day)},
		'friday': {'date': (monday + 4 * day).strftime('%a %d %b'), 'name': 'friday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 4 * day, end__lt = monday + 5 * day)},
		'saturday': {'date': (monday + 5 * day).strftime('%a %d %b'), 'name': 'saturday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 5 * day, end__lt = monday + 6 * day)},
		'sunday': {'date': (monday + 6 * day).strftime('%a %d %b'), 'name': 'sunday', 'timeslots': TimeSlot.objects.filter(roster = roster, start__gt = monday + 6 * day, end__lt = monday + 7 *day)},
	}

	(next_year, next_week) = (monday + 7 * day).isocalendar()[0:2]
	(prev_year, prev_week) = (monday - 7 * day).isocalendar()[0:2]

	if prev_year < roster.start.year or (prev_week < roster.start.isocalendar()[1] and prev_year == roster.start.year):
		(prev_year, prev_week) = (None, None)
	if next_year > roster.end.year or (next_week > roster.end.isocalendar()[1] and next_year == roster.end.year):
		(next_year, next_week) = (None, None)

	urls = {}
	urls['all'] = 'http://' + request.get_host() + reverse('ical_all')
	urls['trade'] = 'http://' + request.get_host() + reverse('ical_trade')
	if request.user.is_authenticated():
		urls['own'] = 'http://' + request.get_host() + reverse('ical_own', kwargs = {'user': request.user.username})
		urls['available'] = 'http://' + request.get_host() + reverse('ical_available', kwargs = {'user': request.user.username})

	user = AnonymousUser()
	if request.user.is_authenticated():
		if RosterWorker.objects.filter(user = request.user, roster = roster) or request.user.is_staff:
			user = request.user

	''' Get jump links to all the weeks '''
	start_monday = week_start_date(roster.start.year, roster.start.isocalendar()[1])
	end_monday = week_start_date(roster.end.year, roster.end.isocalendar()[1])
	oneweek = datetime.timedelta(days = 7)
	mondays = []
	day_k = start_monday
	while day_k <= end_monday:
		mondays.append({'name': day_k.strftime('%d %b'), 'is_this_week': monday == day_k, 'year': day_k.isocalendar()[0], 'week': day_k.isocalendar()[1]})
		day_k += oneweek

	return render(request, 'final_roster.html', {
		'user': user,
		'roster': roster,
		'schedule': schedule,
		'year': year,
		'prev_year': prev_year,
		'next_year': next_year,
		'week': week,
		'prev_week': prev_week,
		'next_week': next_week,
		'urls': urls,
		'mondays': mondays,
	})
Esempio n. 9
0
	def handle(self, *args, **options):

		""" Input arguments """
		t_init = time.time()
		try:
			roster = Roster.objects.get(pk = int(args[0]))
		except IndexError:
			print 'Please provide a roster ID'
			show_rosters()
			return
		except ValueError:
			print 'Please provide an integer roster ID\n'
			show_rosters()
			return
		except Roster.DoesNotExist:
			print 'Roster with ID %s not found\n' % args[0]
			show_rosters()
			return

		N = options['steps']
		check_consistency = options['check']
		alpha = options['alpha']

		if roster.state < 1:
			print 'This roster is is not ready to distribute yet, complete other steps first'
			return
		elif roster.state == 3:
			if options['force']:
				roster.state = 2
				roster.save()
			else:
				print 'This roster has already been distributed; use -f to force redistribution'
				return
		elif roster.state > 3:
			print 'This roster has already been distributed'
			return
		elif roster.state == 1:
			roster.state = 2
			roster.save()

		print '* %s *' % roster.name.upper()

		''' Create the availability data structure '''
		t_load = time.time()
		slotmap = {ts.pk: ts for ts in TimeSlot.objects.filter(roster = roster)}
		availabilities_qs = Availability.objects.filter(timeslot__roster = roster)
		t_assign = time.time()
		print 'TIME query data:   %.3fs' % (t_assign - t_load)

		A, D = {}, {}
		degeneracy, duration, similar = {}, {}, {}
		slots = []
		total_hours = 0
		for slot in slotmap.values():
			slots.append(slot)
			D[slot.pk] = slot.degeneracy * [None]
			A[slot.pk] = []
			degeneracy[slot.pk] = slot.degeneracy
			duration[slot.pk] = to_hours(slot.duration)
			similar[slot.pk] = []
			total_hours += slot.degeneracy * to_hours(slot.duration)
			''' Find similar shifts (exactly a week difference) '''
			monday = week_start_date(slot.start.isocalendar()[0], slot.start.isocalendar()[1])
			day = datetime.timedelta(days = 1)
			weeks = 0 * day
			while monday + weeks >= datetime.datetime.combine(roster.start, datetime.time()):
				weeks -= 7 * day
			while monday + weeks <= datetime.datetime.combine(roster.end, datetime.time()):
				if not ((monday + weeks).isocalendar()[0] == slot.start.isocalendar()[0] and (monday + weeks).isocalendar()[1] == slot.start.isocalendar()[1]):
					sim_slot = TimeSlot.objects.filter(roster = roster).filter(roster = slot.roster, start = slot.start + weeks, end = slot.end + weeks)
					if sim_slot:
						similar[slot.pk].append(sim_slot[0])
				weeks += 7 * day

		for availability in availabilities_qs:
			''' There should be no duplicates but it happens, somehow. Don't know where
				they come from, but I can't block them at database level because sqlite
				seems to throw random errors for unique_together... '''
			if availability.user.pk not in map_user_pk(A[availability.timeslot.pk]):
				A[availability.timeslot.pk].append(availability)
			else:
				print 'There was a duplicate Availability; #%d removed' % availability.pk
				Availability.objects.get(pk = availability.pk).delete()

		if not N:
			N = 10 * len(slots)

		''' Calculate the hours '''
		"""
			extra hours for flexibility are calculated as:
			alpha * sqrt(user_available_hours / per_person_expected)
			[but never negative]
		"""
		t_hours = time.time()
		print 'TIME assign data:  %.3fs' % (t_hours - t_assign)
		workers = RosterWorker.objects.filter(roster = roster)
		current_hours = {}
		extra_hours = {}
		flexibility = {}
		expected_hours = float(total_hours) / len(workers)
		available_hours = {worker.user.pk: 0 for worker in workers}
		for availability_list in A.values():
			for availability in availability_list:
				available_hours[availability.user.pk] += to_hours(availability.timeslot.duration)
		for worker in workers:
			extra_hours[worker.user.pk] = worker.extra
			flexibility[worker.user.pk] = sqrt(available_hours[worker.user.pk] / expected_hours)
			extra_hours[worker.user.pk] += alpha * flexibility[worker.user.pk]
			current_hours[worker.user.pk] = 0

		''' Monte Carlo (with only favourable steps though) '''
		t_monte = time.time()
		no_change_steps = 0
		counter = 500 * len(slots)
		while no_change_steps < N and counter> 0:
			updated = False
			counter -= 1
			''' Trivial slots are removed; if there are no slots left we quit  '''
			if not len(slots):
				print 'DETERMINISTIC / TRIVIAL'
				break

			slot = weighted_choice(slots, degeneracy.values())
			if len(A[slot.pk]) <= degeneracy[slot.pk]:
				''' No competition for this position '''
				for deg in range(degeneracy[slot.pk]):
					min_av = select_fewest_hours(set(A[slot.pk]) - set(D[slot.pk]), current_hours, extra_hours)
					''' Note that one of the positions might have been filled through the recursion mechanism '''
					if min_av and not D[slot.pk][deg]:
						(D, current_hours, updated) = assign(min_av, A, D, slot, deg, current_hours)
				slots.remove(slot)
				degeneracy.pop(slot.pk)
			else:
				deg = random.randint(0, len(D[slot.pk]) - 1)
				if D[slot.pk][deg] == None:
					''' No current worker, so no comparison '''
					worker = random.choice(A[slot.pk])
					(D, current_hours, updated) = assign(worker, A, D, slot, deg, current_hours)
				elif len(A[slot.pk]) > 1:
					''' There is a selected worker, and at least one alternative '''
					worker = random.choice(A[slot.pk])
					while worker.pk == D[slot.pk][deg].pk:
						worker = random.choice(A[slot.pk])
					(D, current_hours, updated) = switch(slot, deg, worker, A, D, duration, similar, current_hours, extra_hours, recursive = True)

			''' Count the steps without change (the while loop quits after N of them)
				The only way assign is called multiple times is when there is one succesful
				switch, in which case True is returned anyway '''
			no_change_steps += 1
			if updated:
				no_change_steps = 0

		''' Prevent unnecessary blanks (generally none are found) '''
		t_post = time.time()
		print 'TIME monte carlo:  %.3fs' % (t_post - t_monte)
		for slot in slots:
			for deg in range(degeneracy[slot.pk]):
				if D[slot.pk][deg] == None:
					''' There is an empty slot; find the candidate with fewest hours '''
					min_av = select_fewest_hours(set(A[slot.pk]) - set(D[slot.pk]), current_hours, extra_hours)
					if min_av:
						(D, current_hours, updated) = assign(min_av, A, D, slot, deg, current_hours)

		''' Consistency checks '''
		t_check = time.time()
		print 'TIME post check:   %.3fs' % (t_check - t_post)
		if check_consistency:
			all_slot_check = 0
			for slot in slots:
				all_slot_check += to_hours(slot.duration) * slot.degeneracy
				''' Check the degeneracy values '''
				assert len(D[slot.pk]) == slot.degeneracy
				''' Check that there aren't more shifts occupied than there are available people '''
				assert len(filter(lambda x: x, D[slot.pk])) <= len(A[slot.pk])
				''' Check that a person doesn't have two shifts in one slot '''
				assert len(map_user_pk(D[slot.pk])) == len(set(map_user_pk(D[slot.pk])))
			''' Check that the total hours of all slots does not exceed the assigned hours '''
			all_assign_check = 0
			for slot_pk, avity_list in D.items():
				for deg, avity in enumerate(avity_list):
					if avity:
						all_assign_check += to_hours(avity.timeslot.duration)
						''' Check that all assigned shifts belong to someone that is available '''
						assert avity.pk in map_pk(A[slot_pk])
					else:
						''' If the slot is empty, then no one is available that isn't already working '''
						if len(set(map_pk(A[slot_pk])) - set(map_pk(D[slot_pk]))):
							print '>> %s | %s  (%d)' % (set(map_pk(A[slot_pk])), set(map_pk(D[slot_pk])), len(D[slot_pk]))
							print map_user_pk(A[slot_pk])
						assert len(set(map_pk(A[slot_pk])) - set(map_pk(D[slot_pk]))) == 0
			''' check sum of hours (bugged end 2013) and empty slots '''
			current_hours_check = {}
			for rw in workers:
				current_hours_check[rw.user.pk] = 0
			for slot_pk, avity_list in D.items():
				for deg, avity in enumerate(avity_list):
					if avity is None:
						''' check that, for every empty slot, there are fewer availabilities than places '''
						assert len(A[slot_pk]) <= [slot.degeneracy for slot in slotmap.values() if slot.pk == slot_pk][0]
					else:
						current_hours_check[avity.user.pk] += to_hours(avity.timeslot.duration)
			''' check that the total hours are the same '''
			assert sum(current_hours.values()) == sum(current_hours_check.values())
			assert len(current_hours) == len(current_hours_check)
			''' check that the hours for individual users are the same '''
			for user_pk in current_hours.keys():
				assert current_hours[user_pk] == current_hours_check[user_pk]

			''' Check that the hours of assigned shifts match the total of current_hours '''
			assert all_assign_check == sum(current_hours.values())

		''' Store result '''
		t_store = time.time()
		if check_consistency:
			print 'TIME checks:       %.3fs' % (t_store - t_check)
		else:
			print 'TIME checks:       off'
		roster = Roster.objects.get(pk = roster.pk)
		if not roster.state == 2:
			print 'The state of the roster has changed! There may be a concurrent process. Mission aborted; no changes will be made.'
			return
		Assignment.objects.filter(timeslot__in = slotmap.values()).delete()
		batch = []
		empty_slots = []
		for key, availability_list in D.items():
			for deg, availability in enumerate(availability_list):
				if availability is None:
					empty_slots.append(slotmap[key])
				else:
					batch.append(Assignment(user = availability.user, timeslot = availability.timeslot, note = 'shift %d' % deg))
		Assignment.objects.bulk_create(batch)
		roster.state = 3
		roster.save()

		''' Process the result '''
		t_result = time.time()
		print 'TIME store result: %.3fs' % (t_result - t_store)
		print 'TIME other steps:  %.3fs' % (t_result - t_init - (t_check - t_monte) - (t_hours - t_assign) - (t_assign - t_load) - (t_result - t_store) - (t_store - t_check))
		print 'TIME total time:   %.3fs' % (t_result - t_init)
		print 'remaining hours:   %d' % (total_hours - sum(current_hours.values()))
		for empty_slot in empty_slots:
			print '\t %s (%.1fh)' % (empty_slot, to_hours(empty_slot.duration))
		print 'with alpha = %.3f max extra hours is %.1fh' % (alpha, alpha * (max(flexibility.values()) - min(flexibility.values())))
		print '       user\tfinal\textra (goal)'
		for user_pk in current_hours.keys():
			print ' %-20s\t%d\t%.1f' % (unicode(get_user_model().objects.get(pk = user_pk))[:20], int(current_hours[user_pk]), extra_hours[user_pk])
		if not counter:
			print '(Monte Carlo reached iteration limit)'