Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interview/ok #1

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 88 additions & 12 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from datetime import datetime
from datetime import date, datetime, timedelta
from decimal import Decimal
from collections import Counter, defaultdict
import itertools
import operator

from flask import Flask
from flask import Flask, request
from flask_restplus import Api, Resource
from sqlalchemy import and_, func

from models import Bookings, HotelRooms, Hotels
from models import BlockedRooms, Bookings, HotelRooms, Hotels
from utils import get_session

app = Flask(__name__)
Expand Down Expand Up @@ -33,27 +37,39 @@ def get(self, hotelroom_id, start_date, end_date):
hotelroom = session.query(HotelRooms).get(hotelroom_id)

# get number of bookings and cancellations
num_of_bookings = session.query(func.count(Bookings)).filter(
num_of_bookings = session.query(func.count(Bookings.id)).filter(
and_(
Bookings.hotelroom_id == hotelroom_id,
Bookings.reserved_night_date.between(
start_date, end_date
),
Bookings.row_type == 'booking'
)
).all()
num_of_cancellations = session.query(func.Count(Bookings)).filter(
).scalar()
num_of_cancellations = session.query(func.Count(Bookings.id)).filter(
and_(
Bookings.hotelroom_id == hotelroom_id,
Bookings.reserved_night_date.between(
start_date, end_date
),
Bookings.row_type == 'cancellations'
)
).all()
).scalar()
num_of_blocked_rooms = session.query(func.Sum(BlockedRooms.rooms)).filter(
and_(
BlockedRooms.hotelroom_id == hotelroom_id,
BlockedRooms.reserved_night_date.between(
start_date, end_date
),
)
).scalar()

num_of_bookings = num_of_bookings or 0
num_of_cancellations = num_of_cancellations or 0
num_of_blocked_rooms = num_of_blocked_rooms or 0

# calculate numerator and denominator for occupancy
net_bookings = num_of_bookings - num_of_cancellations
net_bookings = num_of_blocked_rooms + num_of_bookings - num_of_cancellations
total_available_rooms = hotelroom.capacity * ((end_date - start_date).days + 1)

# check to make sure total_available_rooms is not 0 (division by zero error)
Expand Down Expand Up @@ -86,14 +102,74 @@ def get(self, hotelroom_id, reserved_night_date):
# get a database session
session = get_session()

occupancy = []
revenue_booking_curve = []
# get the hotelroom object to calculate capacity
hotelroom = session.query(HotelRooms).get(hotelroom_id)

days = request.args.get("days", 90)
today = date.today()
start_date = today - timedelta(days=days-1)

# bookings for the given room made prior the curve start date
prior_occupancy, prior_revenue = session.query(
func.count(Bookings.id),
func.sum(Bookings.price),
).filter(
and_(
Bookings.hotelroom_id == hotelroom_id,
Bookings.reserved_night_date == reserved_night_date,
Bookings.booking_datetime < start_date,
)
).one()

# bookings for the given room made within the curve date range
bookings = session.query(
Bookings.booking_datetime,
Bookings.price,
).filter(
and_(
Bookings.hotelroom_id == hotelroom_id,
Bookings.reserved_night_date == reserved_night_date,
Bookings.booking_datetime >= start_date
)
).all()

# write code here for Question 2
# get occupancy and revenue per day out of existing bookings
occupancy_per_day = Counter(
[booking.booking_datetime.date() for booking in bookings]
)
revenue_per_day = defaultdict(Decimal)
for booking in bookings:
revenue_per_day[booking.booking_datetime.date()] += booking.price

# occupancy and revenue per each day of the range
# (including days with no bookings)
occupancy_per_day = [
occupancy_per_day.get(today - timedelta(days=day), 0)
for day in reversed(range(days))
]
revenue_per_day = [
revenue_per_day.get(today - timedelta(days=day), 0)
for day in reversed(range(days))
]

# account for prior occupancy and revenue
occupancy_per_day[0] += prior_occupancy
revenue_per_day[0] += prior_revenue

# accumulate occupancy and calculate percentage
occupancy_percentage = [
str(round(occupancy * 100 / hotelroom.capacity, 2))
for occupancy
in itertools.accumulate(occupancy_per_day, func=operator.add)
]
# accumulate revenue
revenue_booking_curve = list(
itertools.accumulate(revenue_per_day, func=operator.add)
)

return {
'booking_curve': {
"occupancy": occupancy,
"occupancy": occupancy_percentage,
"revenue": revenue_booking_curve
}
}
Expand Down
12 changes: 12 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,15 @@ class Bookings(Base):
row_type = Column(Text, nullable=False)

price = Column(Numeric(10, 2), nullable=False)


class BlockedRooms(Base):
__tablename__ = 'blockedrooms'
id = Column(Integer, primary_key=True)

hotelroom_id = Column(Integer, ForeignKey('hotelrooms.id'), nullable=False, index=True)
hotelroom = relationship("HotelRooms", primaryjoin=hotelroom_id == HotelRooms.id)

reserved_night_date = Column(Date, nullable=False)

rooms = Column(Integer, nullable=False)
230 changes: 230 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
from datetime import date
from decimal import Decimal
import itertools
from unittest.mock import patch

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker

import app, models


@pytest.fixture
def db_connection():

engine = create_engine(
# TODO make this configurable (for both the fixture and the app)
"postgresql://prix:prix@localhost:5432/interview"
)

models.Base.metadata.create_all(bind=engine)

yield engine.connect()

# TODO properly close the scoped session usedd by the app
from utils import _SESSION
if _SESSION:
_SESSION.close()

models.Base.metadata.reflect(bind=engine)
models.Base.metadata.drop_all(bind=engine)


@pytest.fixture
def db_session(db_connection):
Session = sessionmaker(bind=db_connection)
session = Session()
yield session
session.close()


@pytest.fixture
def hotel(db_session):
hotel = models.Hotels(id=1, name="Intercontinental")
db_session.add(hotel)
db_session.commit()
return hotel


@pytest.fixture
def hotelroom(db_session, hotel):
hotelroom = models.HotelRooms(
id=1,
hotel_id=hotel.id,
name="Queen Suite",
capacity=10,
)
db_session.add(hotelroom)
db_session.commit()
return hotelroom


@pytest.fixture
def make_booking(db_session, hotelroom):
def make(**overrides):
booking_data = dict(
id=1,
hotelroom_id=hotelroom.id,
reserved_night_date=date(2018, 12, 26),
booking_datetime=date(2018, 12, 26),
row_type="booking",
price=Decimal("100.00"),
)
booking_data.update(overrides)
return models.Bookings(**booking_data)
return make


@pytest.fixture
def bookings(db_session, make_booking):
bookings = [make_booking(id=i) for i in range(1, 7)]
db_session.add_all(bookings)
db_session.commit()
return bookings


def test_occupancy(bookings, hotelroom):

start_date = "2018-12-26"
end_date = "2018-12-26"

response = app.OccupancyEndpoint().get(
hotelroom.id, start_date, end_date
)

assert response["occupancy"] == "60.0"


def test_occupancy_with_blocked_rooms(
db_session, bookings, hotelroom, make_booking
):

start_date = "2018-12-26"
end_date = "2018-12-26"

blocked_rooms = models.BlockedRooms(
id=1,
hotelroom_id=hotelroom.id,
reserved_night_date=date(2018, 12, 26),
rooms=4
)
db_session.add(blocked_rooms)
db_session.commit()

response = app.OccupancyEndpoint().get(
hotelroom.id, start_date, end_date
)

assert response["occupancy"] == "100.0"


def test_occupancy_with_date_range():
pass


@pytest.fixture
def request():
with patch("app.request") as request:
yield request


def test_booking_curve_occupancy(
db_session, request, hotelroom, make_booking
):
# TODO add also bookings for different room - should be filtered out

# for testing shorten the 90 days default
request.args = {"days": 5}

reserved_night_date = date(2018, 12, 26)

ids = itertools.count()
bookings = []

def make_bookings(n, **overrides):
bookings = [
make_booking(
id=next(ids),
reserved_night_date=reserved_night_date,
**overrides
) for i in range(n)
]
db_session.add_all(bookings)
db_session.commit()

# bookings prior the curve date range
make_bookings(2, booking_datetime=date(2018, 12, 21))

# bookings within the curve date range
make_bookings(4, booking_datetime=date(2018, 12, 23))
make_bookings(1, booking_datetime=date(2018, 12, 24))
make_bookings(3, booking_datetime=date(2018, 12, 26))

response = app.BookingCurveEndpoint().get(
hotelroom.id, reserved_night_date
)

expected_occupancy_curve = ["20.0", "60.0", "70.0", "70.0", "100.0"]

assert response["booking_curve"]["occupancy"] == expected_occupancy_curve


def test_booking_curve_revenue(
db_session, request, hotelroom, make_booking
):
# TODO add also bookings for different room - should be filtered out

# for testing shorten the 90 days default
request.args = {"days": 5}

reserved_night_date = date(2018, 12, 26)

ids = itertools.count()
bookings = []

def make_bookings(prices, **overrides):
bookings = [
make_booking(
id=next(ids),
reserved_night_date=reserved_night_date,
price=price,
**overrides
) for price in prices
]
db_session.add_all(bookings)
db_session.commit()

# bookings prior the curve date range
make_bookings(
("100.0", "200.0"),
booking_datetime=date(2018, 12, 21)
)

# bookings within the curve date range
make_bookings(
("100.0", "100.0", "100.0", "100.0"),
booking_datetime=date(2018, 12, 23)
)
make_bookings(
("100.0",),
booking_datetime=date(2018, 12, 24)
)
make_bookings(
("100.0", "100.0"),
booking_datetime=date(2018, 12, 26)
)

response = app.BookingCurveEndpoint().get(
hotelroom.id, reserved_night_date
)

expected_revenue_curve = [
Decimal("300.00"),
Decimal("700.00"),
Decimal("800.00"),
Decimal("800.00"),
Decimal("1000.00")
]

assert response["booking_curve"]["revenue"] == expected_revenue_curve