virtual agendas: balance fill rate (#40056)

This commit is contained in:
Emmanuel Cazenave 2020-02-24 15:02:42 +01:00 committed by Frédéric Péters
parent e2f041511f
commit 06f542f851
2 changed files with 130 additions and 8 deletions

View File

@ -561,18 +561,59 @@ class Fillslots(APIView):
# get all free slots and separate them by desk
all_slots = get_all_slots(agenda, agenda.get_meetingtype(id_=meeting_type_id))
all_slots = [slot for slot in all_slots if not slot.full]
all_free_slots = [slot for slot in all_slots if not slot.full]
datetimes_by_desk = defaultdict(set)
for slot in all_slots:
for slot in all_free_slots:
datetimes_by_desk[slot.desk.id].add(slot.start_datetime)
# TODO: fill policy for virtual agendas
# search first desk where all requested slots are free
for available_desk_id in sorted(datetimes_by_desk.keys()):
if datetimes.issubset(datetimes_by_desk[available_desk_id]):
available_desk = Desk.objects.get(id=available_desk_id)
break
available_desk = None
if agenda.kind == 'virtual':
# Compute fill_rate by agenda/date
fill_rates = defaultdict(dict)
for slot in all_slots:
ref_date = slot.start_datetime.date()
if ref_date not in fill_rates[slot.desk.agenda]:
date_dict = fill_rates[slot.desk.agenda][ref_date] = {'free': 0, 'full': 0}
else:
date_dict = fill_rates[slot.desk.agenda][ref_date]
if slot.full:
date_dict['full'] += 1
else:
date_dict['free'] += 1
for dd in fill_rates.values():
for date_dict in dd.values():
date_dict['fill_rate'] = date_dict['full'] / (date_dict['full'] + date_dict['free'])
# select a desk on the agenda with min fill_rate on the given date
for available_desk_id in sorted(datetimes_by_desk.keys()):
if datetimes.issubset(datetimes_by_desk[available_desk_id]):
desk = Desk.objects.get(id=available_desk_id)
if available_desk is None:
available_desk = desk
available_desk_rate = 0
for dt in datetimes:
available_desk_rate += fill_rates[available_desk.agenda][dt.date()][
'fill_rate'
]
else:
for dt in datetimes:
desk_rate = 0
for dt in datetimes:
desk_rate += fill_rates[desk.agenda][dt.date()]['fill_rate']
if desk_rate < available_desk_rate:
available_desk = desk
available_desk_rate = desk_rate
else:
# meeting agenda
# search first desk where all requested slots are free
for available_desk_id in sorted(datetimes_by_desk.keys()):
if datetimes.issubset(datetimes_by_desk[available_desk_id]):
available_desk = Desk.objects.get(id=available_desk_id)
break
if available_desk is None:
return Response(
{
'err': 1,

View File

@ -2874,3 +2874,84 @@ def test_virtual_agendas_meetings_booking(app, mock_now, user):
assert resp_booking.json['err'] == 1
assert resp_booking.json['err_class'] == 'no more desk available'
assert resp_booking.json['err_desc'] == 'no more desk available'
def test_virtual_agendas_meetings_booking_default_policy(app, mock_now, user):
foo_agenda = Agenda.objects.create(
label='Foo Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5
)
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
foo_desk_2 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 2')
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
)
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_2,
)
bar_agenda = Agenda.objects.create(
label='Bar Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5
)
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2')
bar_desk_3 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 3')
bar_desk_4 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 3')
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_1,
)
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_2,
)
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_3,
)
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_4,
)
virt_agenda = Agenda.objects.create(
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=5
)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
virt_meeting_type = virt_agenda.iter_meetingtypes()[0]
# We are saturday and we can book for next monday and tuesday, 4 slots available each day
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, virt_meeting_type.slug)
resp = app.get(api_url)
# We are saturday and we can book for next monday, 4 slots available each day
assert len(resp.json['data']) == 4
# there are 6 desks so we can make 6 bookings on the same slot
fillslot_url = resp.json['data'][0]['api']['fillslot_url']
app.authorization = ('Basic', ('john.doe', 'password'))
for i in range(1, 7):
foo_num_bookings = Booking.objects.filter(event__desk__agenda=foo_agenda).count()
bar_num_bookings = Booking.objects.filter(event__desk__agenda=bar_agenda).count()
foo_fill_rate = foo_num_bookings / 2
bar_fill_rate = bar_num_bookings / 4
next_agenda = None
if i != 1:
if foo_fill_rate < bar_fill_rate:
next_agenda = foo_agenda
elif foo_fill_rate > bar_fill_rate:
next_agenda = bar_agenda
elif foo_fill_rate == bar_fill_rate:
next_agenda = None
resp_booking = app.post(fillslot_url)
assert Booking.objects.count() == i
booking = Booking.objects.get(pk=resp_booking.json['booking_id'])
assert (
resp_booking.json['datetime']
== localtime(booking.event.start_datetime).strftime('%Y-%m-%d %H:%M:%S')
== resp.json['data'][0]['datetime']
)
if next_agenda:
assert booking.event.agenda == next_agenda
foo_num_bookings = Booking.objects.filter(event__desk__agenda=foo_agenda).count()
bar_num_bookings = Booking.objects.filter(event__desk__agenda=bar_agenda).count()
assert foo_num_bookings == 2
assert bar_num_bookings == 4