diff --git a/Rakefile b/Rakefile new file mode 100644 index 000000000..f4f704fca --- /dev/null +++ b/Rakefile @@ -0,0 +1,9 @@ +require 'rake/testtask' + +Rake::TestTask.new do |t| + t.libs = ["lib"] + t.warning = false + t.test_files = FileList['specs/*_spec.rb'] +end + +task default: :test diff --git a/design-activity.md b/design-activity.md new file mode 100644 index 000000000..df81355ef --- /dev/null +++ b/design-activity.md @@ -0,0 +1,40 @@ +## What classes does each implementation include? Are the lists the same? +Implementations A and B include same three classes: CartEntry, ShoppingCart, Order. + +## Write down a sentence to describe each class. +Class CartEntry stores the information about product and its price. It is responsible for the price for each particular product. +Class ShoppingCart contains all products (several cart entries). +Class Order creates new instance of class ShoppingCart and is responsible for the final price of the whole order include taxes. + +## How do the classes relate to each other? It might be helpful to draw a diagram on a whiteboard or piece of paper. +Class Order is depends on the ShoppingCart class which is closely related to CartEntry class. Every time customer pick a new product(cart entry), it is getting stored to the instance of class ShoppingCar which is initialized in class Order. + +## What data does each class store? How (if at all) does this differ between the two implementations? +Class CartEntry stores price for product(unit_price) and quantity. +Class ShoppingCart stores all products that customer picked. +Class Order stores the total price for the order. +The difference between two implementations is that in implementation A all calculation logic is in Order class whereas in implementation B it is located in each class (CartEntry, ShoppingCart) where it belongs. + +##What methods does each class have? How (if at all) does this differ between the two implementations? +Class CartEntry has methods: initialize(A), and initialize and price(B). The difference between the two implementations is that in implementation B unit_price and quantity can't be accessed from the outside of class and instead it has a price method. +Class ShoppingCart has a similar difference between two implementations. In implementation A it has only one method initialize and can be accessed from outside of class. In implementation B private data is encapsulated. +Class Order in both implementations has two methods: initialize and total_price. The only difference is how it accesses data from two other classes. In implementation A, it uses data from CartEntry and ShoppingCart classes and calculates total_price, whereas in implementation B, it uses methods of these classes. + +##Consider the Order#total_price method. In each implementation: is logic to compute the price delegated to "lower level" classes like ShoppingCart and CartEntry, or is it retained in Order? +In implementation A #total_price method is completely retained in Order. In implementation B it delegated to "lower level" classes by having #price methods in them. + +##Does total_price directly manipulate the instance variables of other classes? +In implementation A #total_price method directly manipulates the instance variables of "lower level" classes such as entries, unit_price and quantity. + +##If we decide items are cheaper if bought in bulk, how would this change the code? Which implementation is easier to modify? +The implementation B is easier to modify because each class has a single responsibility. To add a discount for items in bulk I would add it to #price method of CartEntry class. + +##Which implementation better adheres to the single responsibility principle? +The implementation B is better adheres to the single responsibility principle. Each class has a certain logic calculations and can be describe in one sentence. + +##Bonus question once you've read Metz ch. 3: Which implementation is more loosely coupled? +The implementation B is more loosely coupled. The private data of classes is encapsulated and classes do not know much about each other. + +##My improvements: + +To improve design in Hotel project I added new method #reserve_room to class Block and changed method #make_reservation_from_block in class Hotel. It made my classes Hotel and Block more loosely coupled. In my new implementation the Hotel class knows less information about the Block class. This change did not break any of my tests but I still refactored them a little to check some new functionality. diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..3e4f45338 --- /dev/null +++ b/lib/block.rb @@ -0,0 +1,34 @@ +module BookingSystem + class Block + + @@id_count = 1 + + attr_reader :date_range, :rooms, :id + + def initialize(date_range, rooms) + @id = @@id_count + @@id_count += 1 + @date_range = date_range + @rooms = rooms #hash + + end #end of initialize + + def has_available_rooms? + return @rooms.length > 0 + end #end of method + + def reserve_room(room) + if !has_available_rooms? + raise NoRoomAvailableError.new("No available rooms in block") + end + if !rooms.keys.include?(room) + raise NoRoomAvailableError.new("Requested room is unavailable") + end + + @rooms.delete(room) + + end #end of method + + + end #end of class +end #end of module diff --git a/lib/date_range.rb b/lib/date_range.rb new file mode 100644 index 000000000..afe9d5fe7 --- /dev/null +++ b/lib/date_range.rb @@ -0,0 +1,26 @@ +require 'date' +require_relative 'invalid_date' + +module BookingSystem + + class DateRange + + attr_reader :check_in, :check_out + + def initialize(check_in, check_out) + @check_in = check_in + @check_out = check_out + + if @check_in.class != Date || @check_out.class != Date || @check_in > @check_out || @check_in < Date.today + raise InvalidDate.new("Invalid date or date range") + end + end #end of initialize + + def dates_within_range + dates = (@check_in .. @check_out - 1).map { |date| date } + return dates + end #end of method + + end #end of class + +end #end of module diff --git a/lib/hotel.rb b/lib/hotel.rb new file mode 100644 index 000000000..c2cfb44f7 --- /dev/null +++ b/lib/hotel.rb @@ -0,0 +1,123 @@ +require_relative 'reservation' +require_relative 'date_range' +require_relative 'no_room_available' +require_relative 'block' +require 'csv' + +module BookingSystem + class Hotel + + DEFAULT_ROOMS = {1 => 150, 2 => 200, 3 => 350, 4 => 120, 5 => 150, 6 => 170, 7 => 180, 8 => 150, 9 => 190, 10 => 200, 11 => 220, 12 => 135, 13 => 200, 14 => 190, 15 => 220, 16 => 200, 17 => 220, 18 => 250, 19 => 200, 20 => 250} + + DEFAULT_DISCOUNT = 10 + + DEFAULT_RESERVATIONS = "support/reservations.csv" + + DEFAULT_RATES = "support/rates.csv" + + attr_reader :rooms, :all_reservations, :block_discount, :all_blocks + + #didn't finish this part, method get_rates is unused + def get_rates(file=DEFAULT_RATES) + rates = {} + CSV.open(file, "r").each do |line| + rates[line[0]] = line[1] + end + return rates + end #end of method + + def initialize(rooms=DEFAULT_ROOMS, block_discount=DEFAULT_DISCOUNT) + + @all_reservations = [] #array of instances of class Reservation + @all_blocks = [] #array of instances of class Block + @rooms = rooms + @block_discount = block_discount + + end #end of initialize + + def room_unavailable(room) + dates = [] + @all_reservations.each do |reservation| + if reservation.room == room + reservation.date_range.dates_within_range.each do |date| + dates << date + end + end + end + @all_blocks.each do |block| + block.rooms.each do |block_room, price| + if block_room == room + block.date_range.dates_within_range.each do |date| + dates << date + end + end + end + end + return dates #array of dates on which this room is unavailable + end #end of method + + def list_of_available_rooms(date_range) + available_rooms = {} + @rooms.each do |room, price| + booked_dates = room_unavailable(room) #array of dates + count = 0 + date_range.dates_within_range.each do |date| + if !booked_dates.include?(date) + count += 1 + end + end + if count == date_range.dates_within_range.length + available_rooms[room] = price + end + end + if available_rooms.length == 0 + raise NoRoomAvailableError.new("No room is available on given dates") + end + return available_rooms #hash of rooms + end #end of method + + def make_reservation(date_range, room, file=DEFAULT_RESERVATIONS) + if !list_of_available_rooms(date_range).include?(room) + raise NoRoomAvailableError.new("Requested room is unavailable") + end + cost = @rooms[room] + new_reservation = Reservation.new(date_range, room, cost) + @all_reservations << new_reservation + + new_reservation.add_reservation(file) + + return new_reservation #new instance of class Reservation + end #end of method + + def list_of_reservations(date) + list = @all_reservations.map { |reservation| reservation if reservation.date_range.dates_within_range.include?(date) } + + return list #array of reservations + end #end of method + + def create_block(date_range, number_of_rooms) + if number_of_rooms > 5 + number_of_rooms = 5 + end + if list_of_available_rooms(date_range).length < number_of_rooms + raise NoRoomAvailableError.new("Not enough available rooms on given dates") + end + block_rooms = Hash[list_of_available_rooms(date_range).sort_by { |k, v| k }[0..number_of_rooms-1]] + new_block = Block.new(date_range, block_rooms) + @all_blocks << new_block + + return new_block #has hash of rooms and prices + end #end of method + + def make_reservation_from_block(block, room) + date_range = block.date_range + block.reserve_room(room) + cost = @rooms[room] * (1 - @block_discount / 100.0) + new_reservation_from_block = Reservation.new(date_range, room, cost) + return new_reservation_from_block #instance of class Block + + end #end of method + + end #end of class + +end #end of module diff --git a/lib/invalid_date.rb b/lib/invalid_date.rb new file mode 100644 index 000000000..0a5fd6299 --- /dev/null +++ b/lib/invalid_date.rb @@ -0,0 +1,4 @@ +module BookingSystem + class InvalidDate < ArgumentError + end #end of class +end #end of module diff --git a/lib/no_room_available.rb b/lib/no_room_available.rb new file mode 100644 index 000000000..cb3526712 --- /dev/null +++ b/lib/no_room_available.rb @@ -0,0 +1,4 @@ +module BookingSystem + class NoRoomAvailableError < ArgumentError + end +end #end of module diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..9b853c4a7 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,31 @@ +require_relative 'date_range' +require 'csv' + +module BookingSystem + class Reservation + + @@id_count = 1 + + attr_reader :id, :date_range, :room, :cost, :total_cost + + def initialize(date_range, room, cost) + + @id = @@id_count + @@id_count += 1 + @date_range = date_range + @room = room + @cost = cost + @total_cost = (cost * date_range.dates_within_range.length).to_f + + end #end of initialize + + #method seems to be working fine, but don't have any tests for loading data back + def add_reservation(file) + CSV.open(file, "a") do |line| + line << ["#{self.id}","#{self.room}","#{self.date_range.check_in.strftime("%m/%d/%Y")}","#{self.date_range.check_out.strftime("%m/%d/%Y")}","#{self.total_cost}"] + end + end #end of method + + end #end of class + +end #end of module diff --git a/specs/block_spec.rb b/specs/block_spec.rb new file mode 100644 index 000000000..ed0781d5a --- /dev/null +++ b/specs/block_spec.rb @@ -0,0 +1,55 @@ +require_relative 'spec_helper' + +describe "Block" do + before do + @five_rooms = {1 => 150, 2 => 200, 3 => 350, 4 => 120, 5 => 150} + @check_in = Date.new(2017,10,15) + @check_out = Date.new(2017,10,17) + @date_range = BookingSystem::DateRange.new(@check_in, @check_out) + @new_block = BookingSystem::Block.new(@date_range, @five_rooms) + end + describe "#initialize" do + it "ID should be an integer" do + @new_block.id.must_be_kind_of Integer + end + it "Date_range can be called by .date_range" do + @new_block.date_range.must_equal @date_range + end + end + + describe "#has_available_rooms?" do + before do + @five_room_hotel = BookingSystem::Hotel.new(@five_rooms) + @new_block = @five_room_hotel.create_block(@date_range, 3) + end + it "Returns true if there are available rooms in given block" do + @new_block.has_available_rooms?.must_equal true + end + it "Returns false if there is no available rooms in given block" do + 3.times do |i| + @five_room_hotel.make_reservation_from_block(@new_block, i+1) + end + @new_block.has_available_rooms?.must_equal false + end + + describe "#reserve_room" do + before do + @block = @five_room_hotel.create_block(@date_range, 2) + @block.reserve_room(4) + end + it "Booked room should be removed from rooms in block" do + @block.rooms.length.must_equal 1 + end + it "Raise an error if requested room is unavailable" do + proc { @block.reserve_room(4) }.must_raise BookingSystem::NoRoomAvailableError + end + it "Raise an error if no available rooms for reservation from block" do + @block.reserve_room(5) + proc { @block.reserve_room(4) }.must_raise BookingSystem::NoRoomAvailableError + end + + + end + end + +end #end of describe diff --git a/specs/date_range_spec.rb b/specs/date_range_spec.rb new file mode 100644 index 000000000..6c20350c2 --- /dev/null +++ b/specs/date_range_spec.rb @@ -0,0 +1,41 @@ +require_relative 'spec_helper' + +describe "DateRange" do + before do + @check_in = Date.new(2017,10,15) + @check_out = Date.new(2017,10,17) + @new_date_range = BookingSystem::DateRange.new(@check_in, @check_out) + @past_date = Date.new(2015,5,3) + end + + describe "#initialize" do + it "Check_in and check_out values are types of Date class" do + @new_date_range.check_in.must_be_kind_of Date + @new_date_range.check_out.must_be_kind_of Date + end + it "Raise an error if check_in or check_out values are not types of Date class" do + proc { BookingSystem::DateRange.new("2017-9-15", "2017-9-17") }.must_raise BookingSystem::InvalidDate + end + it "Raise an error if given check_out date is before check_in date" do + proc { BookingSystem::DateRange.new(@check_out, @check_in) }.must_raise BookingSystem::InvalidDate + end + it "Raise an error if given date is in the past" do + proc { BookingSystem::DateRange.new(@past_date, @check_out) }.must_raise BookingSystem::InvalidDate + end + end + + describe "dates_within_range" do + it "Returns an array" do + @new_date_range.dates_within_range.must_be_kind_of Array + end + it "Returns the array of right length for short ranges" do + @new_date_range.dates_within_range.length.must_equal 2 + end + it "Returns the array of right length for long ranges" do + check_in = Date.new(2017,10,15) + check_out = Date.new(2017,11,15) + BookingSystem::DateRange.new(check_in, check_out).dates_within_range.length.must_equal 31 + end + end + +end #end of discribe diff --git a/specs/hotel_spec.rb b/specs/hotel_spec.rb new file mode 100644 index 000000000..8dac8ff74 --- /dev/null +++ b/specs/hotel_spec.rb @@ -0,0 +1,147 @@ +require_relative 'spec_helper' + +describe "Hotel" do + before do + @two_rooms = {1 => 150, 2 => 200} + @five_rooms = {1 => 150, 2 => 200, 3 => 350, 4 => 120, 5 => 170} + @eight_rooms = {1 => 150, 2 => 200, 3 => 350, 4 => 120, 5 => 150, 6 => 170, 7 => 180, 8 => 150} + @two_room_hotel = BookingSystem::Hotel.new(@two_rooms) + @check_in = Date.new(2017,10,15) + @check_out = Date.new(2017,10,17) + @date_range = BookingSystem::DateRange.new(@check_in, @check_out) + @five_room_hotel = BookingSystem::Hotel.new(@five_rooms) + @eight_room_hotel = BookingSystem::Hotel.new(@eight_rooms) + end + + describe "#initialize" do + it "@rooms should be kind of hash" do + @two_room_hotel.rooms.must_be_kind_of Hash + end + it "Should return an array of the rigth length" do + @two_room_hotel.rooms.length.must_equal 2 + end + it "Each room should be kind of integer" do + @two_room_hotel.rooms.keys[0].must_be_kind_of Integer + end + it "All_reservations must be kind of array" do + @two_room_hotel.all_reservations.must_be_kind_of Array + end + it "All_reservations must be empty if no reservations were made" do + @two_room_hotel.all_reservations.length.must_equal 0 + end + it "All_blocks must be kind of array" do + @two_room_hotel.all_blocks.must_be_kind_of Array + end + it "All_blocks must be empty if no blocks were created" do + @two_room_hotel.all_blocks.length.must_equal 0 + end + end + + describe "#room_unavailable" do + it "Returns an array of dates for given room number" do + @two_room_hotel.room_unavailable(1).must_be_kind_of Array + end + it "Returns empty array if no reservations were made for given room number" do + @two_room_hotel.room_unavailable(1).length.must_equal 0 + end + it "Returns an array of one element if one reservation was made for given room number" do + new_reservation = @two_room_hotel.make_reservation(@date_range, 1) + @two_room_hotel.room_unavailable(1).length.must_equal @date_range.dates_within_range.length + end + it "Returns an array of dates if one block was created" do + new_block = @five_room_hotel.create_block(@date_range, 3) + @five_room_hotel.room_unavailable(1).must_equal @date_range.dates_within_range + end + end + + describe "#make_reservation" do + it "Returns an instance of class reservation" do + @two_room_hotel.make_reservation(@date_range, 1).must_be_kind_of BookingSystem::Reservation + end + it "Adds new reservation to all_reservations array" do + first_reservation = @two_room_hotel.make_reservation(@date_range, 1) + @two_room_hotel.all_reservations.length.must_equal 1 + end + + it "Raise an error if asked to reserve a room that is not available" do + reservation = @two_room_hotel.make_reservation(@date_range, 1) + proc { @two_room_hotel.make_reservation(@date_range, 1) }.must_raise BookingSystem::NoRoomAvailableError + end + end + + describe "#list_of_reservations" do + before do + @reservation = @two_room_hotel.make_reservation(@date_range, 1) + @second_reservation = @two_room_hotel.make_reservation(@date_range, 2) + @date = Date.new(2017,10,15) + end + it "Returns an instance of array class " do + @two_room_hotel.list_of_reservations(@date).must_be_kind_of Array + end + it "Returns an array of the right length" do + @two_room_hotel.list_of_reservations(@date).length.must_equal 2 + end + end + + describe "#list_of_available_rooms" do + it "Returns an instanse of hash" do + @two_room_hotel.list_of_available_rooms(@date_range).must_be_kind_of Hash + end + it "Returns the right list of available rooms if 1st room was booked" do + reservation = @two_room_hotel.make_reservation(@date_range, 1) + @two_room_hotel.list_of_available_rooms(@date_range).must_equal ({2 => 200}) + end + it "Raise an error if no room is available for given date range" do + reservation = @two_room_hotel.make_reservation(@date_range, 1) + second_reservation = @two_room_hotel.make_reservation(@date_range, 2) + proc { @two_room_hotel.list_of_available_rooms(@date_range) }.must_raise BookingSystem::NoRoomAvailableError + end + end + + describe "#create_block" do + before do + @new_block = @eight_room_hotel.create_block(@date_range, 2) + @another_block = @eight_room_hotel.create_block(@date_range, 7) + end + it "Should set number_of_rooms to 5 if given integer grater than 5" do + @another_block.rooms.length.must_equal 5 + end + it "New block should be an instance of Block class " do + @new_block.must_be_kind_of BookingSystem::Block + end + it "Created block should include given number of rooms" do + @new_block.rooms.length.must_equal 2 + end + it "Raise an error if no rooms available for given date range" do + proc { @eight_room_hotel.create_block(@date_range, 3) }.must_raise BookingSystem::NoRoomAvailableError + end + end + + describe "#make_reservation_from_block" do + before do + @new_block = @five_room_hotel.create_block(@date_range, 3) + @reservation_from_block = @five_room_hotel.make_reservation_from_block(@new_block, 1) + end + it "Returns an instance of class Reservation" do + @reservation_from_block.must_be_kind_of BookingSystem::Reservation + end + + end + + describe "Check total cost for two types of reservations" do + before do + @block = @five_room_hotel.create_block(@date_range, 3) + @general_reservation = @five_room_hotel.make_reservation(@date_range, 4) + @reservation_from_block = @five_room_hotel.make_reservation_from_block(@block, 1) + end + it "Returns the right total cost for general reservation" do + @general_reservation.total_cost.must_equal @five_rooms[4] * @date_range.dates_within_range.length + end + it "Returns the right total cost for reservation from block" do + @reservation_from_block.total_cost.must_equal @five_rooms[1] * @date_range.dates_within_range.length * 0.9 + end + end + + + +end #end of describe diff --git a/specs/reservation_spec.rb b/specs/reservation_spec.rb new file mode 100644 index 000000000..cc5c96a25 --- /dev/null +++ b/specs/reservation_spec.rb @@ -0,0 +1,28 @@ +require_relative 'spec_helper' + +describe "Reservation" do + before do + @check_in = Date.new(2017,10,15) + @check_out = Date.new(2017,10,17) + @room = 5 + @cost = 200 + @date_range = BookingSystem::DateRange.new(@check_in, @check_out) + @new_reservation = BookingSystem::Reservation.new(@date_range, @room, @cost) + end + + describe "#initialize" do + it "ID should be an integer" do + @new_reservation.id.must_be_kind_of Integer + end + it "Date_range can be called by .date_range" do + @new_reservation.date_range.must_equal @date_range + end + it "Total cost should be an integer" do + @new_reservation.total_cost.must_be_kind_of Float + end + it "Returns the rigth total cost" do + @new_reservation.total_cost.must_equal 400 + end + end + +end #end of discribe diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 000000000..793a2ac21 --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,16 @@ +require 'simplecov' +SimpleCov.start + +require 'minitest/autorun' +require 'minitest/reporters' +require 'minitest/skip_dsl' +require 'minitest/pride' + +# Require any classes +require_relative '../lib/date_range' +require_relative '../lib/reservation' +require_relative '../lib/hotel' +require_relative '../lib/no_room_available' +require_relative '../lib/block' + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new diff --git a/support/rates.csv b/support/rates.csv new file mode 100644 index 000000000..5a376c5a1 --- /dev/null +++ b/support/rates.csv @@ -0,0 +1,20 @@ +1,130 +2,345 +3,150 +4,180 +5,190 +6,200 +7,210 +8,135 +9,180 +10,90 +11,95 +12,100 +13,150 +14,160 +15,170 +16,210 +17,139 +18,130 +19,300 +20,238 diff --git a/support/reservations.csv b/support/reservations.csv new file mode 100644 index 000000000..0a84f8615 --- /dev/null +++ b/support/reservations.csv @@ -0,0 +1,208 @@ +1,1,09/15/2017,09/17/2017,300.0 +2,2,09/15/2017,09/17/2017,400.0 +3,1,09/15/2017,09/17/2017,300.0 +4,4,09/15/2017,09/17/2017,240.0 +6,4,09/15/2017,09/17/2017,240.0 +14,1,09/15/2017,09/17/2017,300.0 +15,2,09/15/2017,09/17/2017,400.0 +16,1,09/15/2017,09/17/2017,300.0 +17,2,09/15/2017,09/17/2017,400.0 +22,1,09/15/2017,09/17/2017,300.0 +23,1,09/15/2017,09/17/2017,300.0 +24,1,09/15/2017,09/17/2017,300.0 +28,1,09/15/2017,09/17/2017,300.0 +7,1,09/15/2017,09/17/2017,300.0 +8,1,09/15/2017,09/17/2017,300.0 +9,1,09/15/2017,09/17/2017,300.0 +14,1,09/15/2017,09/17/2017,300.0 +15,1,09/15/2017,09/17/2017,300.0 +16,2,09/15/2017,09/17/2017,400.0 +20,4,09/15/2017,09/17/2017,240.0 +22,4,09/15/2017,09/17/2017,240.0 +24,1,09/15/2017,09/17/2017,300.0 +25,1,09/15/2017,09/17/2017,300.0 +26,2,09/15/2017,09/17/2017,400.0 +27,1,09/15/2017,09/17/2017,300.0 +28,2,09/15/2017,09/17/2017,400.0 +1,4,09/15/2017,09/17/2017,240.0 +3,4,09/15/2017,09/17/2017,240.0 +5,1,09/15/2017,09/17/2017,300.0 +6,2,09/15/2017,09/17/2017,400.0 +7,1,09/15/2017,09/17/2017,300.0 +18,1,09/15/2017,09/17/2017,300.0 +19,2,09/15/2017,09/17/2017,400.0 +20,1,09/15/2017,09/17/2017,300.0 +21,2,09/15/2017,09/17/2017,400.0 +22,1,09/15/2017,09/17/2017,300.0 +23,1,09/15/2017,09/17/2017,300.0 +24,1,09/15/2017,09/17/2017,300.0 +28,1,09/15/2017,09/17/2017,300.0 +1,1,10/15/2017,10/17/2017,300.0 +2,1,10/15/2017,10/17/2017,300.0 +3,2,10/15/2017,10/17/2017,400.0 +4,1,10/15/2017,10/17/2017,300.0 +5,1,10/15/2017,10/17/2017,300.0 +6,1,10/15/2017,10/17/2017,300.0 +7,1,10/15/2017,10/17/2017,300.0 +8,2,10/15/2017,10/17/2017,400.0 +9,1,10/15/2017,10/17/2017,300.0 +10,2,10/15/2017,10/17/2017,400.0 +11,4,10/15/2017,10/17/2017,240.0 +13,4,10/15/2017,10/17/2017,240.0 +24,1,10/15/2017,10/17/2017,300.0 +1,1,10/15/2017,10/17/2017,300.0 +2,1,10/15/2017,10/17/2017,300.0 +3,1,10/15/2017,10/17/2017,300.0 +4,1,10/15/2017,10/17/2017,300.0 +8,1,10/15/2017,10/17/2017,300.0 +9,1,10/15/2017,10/17/2017,300.0 +10,2,10/15/2017,10/17/2017,400.0 +15,1,10/15/2017,10/17/2017,300.0 +16,2,10/15/2017,10/17/2017,400.0 +17,1,10/15/2017,10/17/2017,300.0 +18,2,10/15/2017,10/17/2017,400.0 +19,4,10/15/2017,10/17/2017,240.0 +21,4,10/15/2017,10/17/2017,240.0 +1,1,10/15/2017,10/17/2017,300.0 +2,1,10/15/2017,10/17/2017,300.0 +3,1,10/15/2017,10/17/2017,300.0 +4,1,10/15/2017,10/17/2017,300.0 +9,1,10/15/2017,10/17/2017,300.0 +10,2,10/15/2017,10/17/2017,400.0 +11,1,10/15/2017,10/17/2017,300.0 +12,2,10/15/2017,10/17/2017,400.0 +13,4,10/15/2017,10/17/2017,240.0 +15,4,10/15/2017,10/17/2017,240.0 +17,1,10/15/2017,10/17/2017,300.0 +18,1,10/15/2017,10/17/2017,300.0 +19,2,10/15/2017,10/17/2017,400.0 +1,1,10/15/2017,10/17/2017,300.0 +2,1,10/15/2017,10/17/2017,300.0 +3,2,10/15/2017,10/17/2017,400.0 +4,1,10/15/2017,10/17/2017,300.0 +5,2,10/15/2017,10/17/2017,400.0 +6,1,10/15/2017,10/17/2017,300.0 +7,2,10/15/2017,10/17/2017,400.0 +8,4,10/15/2017,10/17/2017,240.0 +10,4,10/15/2017,10/17/2017,240.0 +12,1,10/15/2017,10/17/2017,300.0 +13,1,10/15/2017,10/17/2017,300.0 +14,1,10/15/2017,10/17/2017,300.0 +28,1,10/15/2017,10/17/2017,300.0 +1,1,10/15/2017,10/17/2017,300.0 +2,1,10/15/2017,10/17/2017,300.0 +3,2,10/15/2017,10/17/2017,400.0 +17,1,10/15/2017,10/17/2017,300.0 +18,2,10/15/2017,10/17/2017,400.0 +19,1,10/15/2017,10/17/2017,300.0 +20,2,10/15/2017,10/17/2017,400.0 +21,4,10/15/2017,10/17/2017,240.0 +23,4,10/15/2017,10/17/2017,240.0 +25,1,10/15/2017,10/17/2017,300.0 +26,1,10/15/2017,10/17/2017,300.0 +27,1,10/15/2017,10/17/2017,300.0 +28,1,10/15/2017,10/17/2017,300.0 +1,1,10/15/2017,10/17/2017,300.0 +2,2,10/15/2017,10/17/2017,400.0 +3,1,10/15/2017,10/17/2017,300.0 +4,2,10/15/2017,10/17/2017,400.0 +5,4,10/15/2017,10/17/2017,240.0 +7,4,10/15/2017,10/17/2017,240.0 +10,1,10/15/2017,10/17/2017,300.0 +11,1,10/15/2017,10/17/2017,300.0 +12,1,10/15/2017,10/17/2017,300.0 +13,1,10/15/2017,10/17/2017,300.0 +18,1,10/15/2017,10/17/2017,300.0 +19,2,10/15/2017,10/17/2017,400.0 +20,1,10/15/2017,10/17/2017,300.0 +5,1,10/15/2017,10/17/2017,300.0 +6,1,10/15/2017,10/17/2017,300.0 +7,2,10/15/2017,10/17/2017,400.0 +11,4,10/15/2017,10/17/2017,240.0 +13,4,10/15/2017,10/17/2017,240.0 +16,1,10/15/2017,10/17/2017,300.0 +17,1,10/15/2017,10/17/2017,300.0 +18,1,10/15/2017,10/17/2017,300.0 +19,1,10/15/2017,10/17/2017,300.0 +20,2,10/15/2017,10/17/2017,400.0 +21,1,10/15/2017,10/17/2017,300.0 +22,2,10/15/2017,10/17/2017,400.0 +23,1,10/15/2017,10/17/2017,300.0 +1,1,10/15/2017,10/17/2017,300.0 +2,1,10/15/2017,10/17/2017,300.0 +3,2,10/15/2017,10/17/2017,400.0 +4,1,10/15/2017,10/17/2017,300.0 +5,4,10/15/2017,10/17/2017,240.0 +7,4,10/15/2017,10/17/2017,240.0 +9,1,10/15/2017,10/17/2017,300.0 +10,1,10/15/2017,10/17/2017,300.0 +11,1,10/15/2017,10/17/2017,300.0 +12,1,10/15/2017,10/17/2017,300.0 +13,2,10/15/2017,10/17/2017,400.0 +14,1,10/15/2017,10/17/2017,300.0 +15,2,10/15/2017,10/17/2017,400.0 +1,1,10/15/2017,10/17/2017,300.0 +2,1,10/15/2017,10/17/2017,300.0 +3,1,10/15/2017,10/17/2017,300.0 +4,1,10/15/2017,10/17/2017,300.0 +9,4,10/15/2017,10/17/2017,240.0 +11,4,10/15/2017,10/17/2017,240.0 +13,1,10/15/2017,10/17/2017,300.0 +14,2,10/15/2017,10/17/2017,400.0 +15,1,10/15/2017,10/17/2017,300.0 +19,1,10/15/2017,10/17/2017,300.0 +20,2,10/15/2017,10/17/2017,400.0 +21,1,10/15/2017,10/17/2017,300.0 +22,2,10/15/2017,10/17/2017,400.0 +1,1,10/15/2017,10/17/2017,300.0 +2,2,10/15/2017,10/17/2017,400.0 +3,1,10/15/2017,10/17/2017,300.0 +4,2,10/15/2017,10/17/2017,400.0 +5,4,10/15/2017,10/17/2017,240.0 +7,4,10/15/2017,10/17/2017,240.0 +12,1,10/15/2017,10/17/2017,300.0 +13,1,10/15/2017,10/17/2017,300.0 +14,2,10/15/2017,10/17/2017,400.0 +15,1,10/15/2017,10/17/2017,300.0 +21,1,10/15/2017,10/17/2017,300.0 +22,1,10/15/2017,10/17/2017,300.0 +23,1,10/15/2017,10/17/2017,300.0 +1,1,10/15/2017,10/17/2017,300.0 +2,1,10/15/2017,10/17/2017,300.0 +3,2,10/15/2017,10/17/2017,400.0 +12,1,10/15/2017,10/17/2017,300.0 +13,4,10/15/2017,10/17/2017,240.0 +15,4,10/15/2017,10/17/2017,240.0 +17,1,10/15/2017,10/17/2017,300.0 +18,1,10/15/2017,10/17/2017,300.0 +19,1,10/15/2017,10/17/2017,300.0 +20,1,10/15/2017,10/17/2017,300.0 +21,2,10/15/2017,10/17/2017,400.0 +22,1,10/15/2017,10/17/2017,300.0 +23,2,10/15/2017,10/17/2017,400.0 +2,1,10/15/2017,10/17/2017,300.0 +7,4,10/15/2017,10/17/2017,240.0 +9,4,10/15/2017,10/17/2017,240.0 +14,1,10/15/2017,10/17/2017,300.0 +15,2,10/15/2017,10/17/2017,400.0 +16,1,10/15/2017,10/17/2017,300.0 +17,1,10/15/2017,10/17/2017,300.0 +18,2,10/15/2017,10/17/2017,400.0 +19,1,10/15/2017,10/17/2017,300.0 +20,2,10/15/2017,10/17/2017,400.0 +21,1,10/15/2017,10/17/2017,300.0 +22,1,10/15/2017,10/17/2017,300.0 +23,1,10/15/2017,10/17/2017,300.0 +1,1,10/15/2017,10/17/2017,300.0 +2,1,10/15/2017,10/17/2017,300.0 +3,1,10/15/2017,10/17/2017,300.0 +5,1,10/15/2017,10/17/2017,300.0 +6,1,10/15/2017,10/17/2017,300.0 +7,2,10/15/2017,10/17/2017,400.0 +8,1,10/15/2017,10/17/2017,300.0 +16,1,10/15/2017,10/17/2017,300.0 +17,2,10/15/2017,10/17/2017,400.0 +18,1,10/15/2017,10/17/2017,300.0 +19,2,10/15/2017,10/17/2017,400.0 +20,4,10/15/2017,10/17/2017,240.0 +22,4,10/15/2017,10/17/2017,240.0