Skip to content

Commit

Permalink
Fix room stats calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
OmeGak committed Jul 18, 2014
1 parent 77895f0 commit 84a76c5
Show file tree
Hide file tree
Showing 8 changed files with 37 additions and 25 deletions.
2 changes: 2 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ For the moment, we are focusing on _PostgreSQL_. These are some of the specific

* ReservationEditLog uses a PostgreSQL ARRAY to store the changes for a single log entry

* calculate_rooms_booked_time uses `extract('dow')` PostgreSQL specific for day of the week.

In some cases you have properties in your models which trigger additional queries or are expensive for some other reason.
Sometimes you can simply write tricky queries to retrieve all data at once, but in other cases that's not feasible,
either because of what the property does or because you need it for serializing and thus only have the object itself
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ indicoRequest(
<td style="text-align: right;">${ _('Average occupancy') }:</td>
<td>
<span style="background-color: #C9FFC9; font-weight: bold;">
${ kpi['occupancy'] }
${ '{0:.02f}'.format(kpi['occupancy'] * 100) }<small>%</small>
</span>
${inlineContextHelp('Average room occupancy in last 30 days during working hours (8H30-17H30, Monday-Friday including holidays). Only active, publically reservable rooms are taken into account.' )}
</td>
Expand Down
2 changes: 1 addition & 1 deletion indico/MaKaC/webinterface/tpls/RoomBookingRoomStats.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
% endif
</td>
<td class="occupancy-value">
${ '{0:.02f}'.format(occupancy) }<small>%</small>
${ '{0:.02f}'.format(occupancy * 100) }<small>%</small>
</td>
</tr>
</table>
Expand Down
2 changes: 1 addition & 1 deletion indico/modules/rb/controllers/admin/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def _process(self):
rooms = sorted(self._location.rooms, key=lambda r: natural_sort_key(r.full_name))
kpi = {}
if self._with_kpi:
kpi['occupancy'] = calculate_rooms_occupancy(self._location.rooms)
kpi['occupancy'] = calculate_rooms_occupancy(self._location.rooms.all())
kpi['total_rooms'] = self._location.rooms.count()
kpi['active_rooms'] = self._location.rooms.filter_by(is_active=True).count()
kpi['reservable_rooms'] = self._location.rooms.filter_by(is_reservable=True).count()
Expand Down
3 changes: 2 additions & 1 deletion indico/modules/rb/controllers/user/rooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from dateutil.relativedelta import relativedelta
from flask import request, session
from sqlalchemy import func
from werkzeug.datastructures import MultiDict

from MaKaC.common.cache import GenericCache
Expand Down Expand Up @@ -174,7 +175,7 @@ def _checkParams(self):
elif self._occupancy_period == 'thisyear':
self._start = date(self._end.year, 1, 1)
elif self._occupancy_period == 'sinceever':
self._start = Reservation.find().first().start_date.date()
self._start = Reservation.query.with_entities(func.min(Reservation.start_date)).one()[0].date()
else:
raise IndicoError('Invalid period specified')

Expand Down
5 changes: 5 additions & 0 deletions indico/modules/rb/models/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
Holder of rooms in a place and its map view related data
"""

from datetime import time

from sqlalchemy import func, or_, and_
from sqlalchemy.orm import aliased
from sqlalchemy.sql.expression import select
Expand All @@ -38,6 +40,9 @@
class Location(db.Model):
__tablename__ = 'locations'

working_time_start = time(8)
working_time_end = time(17, 30)

# columns

id = db.Column(
Expand Down
5 changes: 2 additions & 3 deletions indico/modules/rb/models/rooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@

import ast
import json
from datetime import date, datetime, timedelta
from datetime import date, timedelta

from dateutil.relativedelta import relativedelta
from sqlalchemy import and_, func, exists, extract, or_, type_coerce
from sqlalchemy import and_, func, exists, or_, type_coerce
from sqlalchemy.dialects.postgresql.base import ARRAY as sa_array
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import joinedload
Expand Down
41 changes: 23 additions & 18 deletions indico/modules/rb/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
from datetime import date, datetime, timedelta

from dateutil.relativedelta import relativedelta
from sqlalchemy import func
from sqlalchemy import func, extract, cast, TIME

from indico.core.db.sqlalchemy.custom import greatest, least
from indico.util.date_time import days_between
from indico.modules.rb.models.locations import Location
from indico.modules.rb.models.reservations import Reservation
from indico.modules.rb.models.reservation_occurrences import ReservationOccurrence

Expand All @@ -13,29 +16,31 @@ def calculate_rooms_bookable_time(rooms, start_date=None, end_date=None):
if end_date is None:
end_date = datetime.utcnow()
if start_date is None:
start_date = end_date + relativedelta(months=-1)

total_days = (end_date - start_date).days + 1
bookable_time = 0
for room in rooms:
bookable_time += (total_days - room.get_nonbookable_days(start_date, end_date)) * room.bookable_time_per_day

return bookable_time
start_date = end_date - relativedelta(months=1)
working_time_start = datetime.combine(date.today(), Location.working_time_start)
working_time_end = datetime.combine(date.today(), Location.working_time_end)
working_time_per_day = (working_time_end - working_time_start).seconds
working_days = days_between(start_date, end_date, include_weekends=False, inclusive=True)
return working_days * working_time_per_day * len(rooms)


def calculate_rooms_booked_time(rooms, start_date=None, end_date=None):
if end_date is None:
end_date = date.today()
if start_date is None:
start_date = end_date + relativedelta(months=-1)

reservations = Reservation.find(Reservation.room_id.in_(r.id for r in rooms))
query = (reservations.join(ReservationOccurrence)
.with_entities(func.sum(ReservationOccurrence.end - ReservationOccurrence.start))
.filter(ReservationOccurrence.start >= start_date,
ReservationOccurrence.end <= end_date,
ReservationOccurrence.is_valid))
return (query.scalar() or timedelta()).total_seconds()
start_date = end_date - relativedelta(months=1)
# Reservations on working days
reservations = Reservation.find(Reservation.room_id.in_(r.id for r in rooms),
extract('dow', ReservationOccurrence.start) < 5,
ReservationOccurrence.start >= start_date,
ReservationOccurrence.end <= end_date,
ReservationOccurrence.is_valid,
_join=ReservationOccurrence)
# Take into account only working hours
earliest_time = greatest(cast(ReservationOccurrence.start, TIME), Location.working_time_start)
latest_time = least(cast(ReservationOccurrence.end, TIME), Location.working_time_end)
booked_time = reservations.with_entities(func.sum(latest_time - earliest_time)).scalar()
return (booked_time or timedelta()).total_seconds()


def calculate_rooms_occupancy(rooms, start=None, end=None):
Expand Down

0 comments on commit 84a76c5

Please sign in to comment.