diff --git a/.floo b/.floo new file mode 100644 index 000000000..9b907d2c2 --- /dev/null +++ b/.floo @@ -0,0 +1,3 @@ +{ + "url": "https://floobits.com/SophieMess/oo-ride-share" +} \ No newline at end of file diff --git a/.flooignore b/.flooignore new file mode 100644 index 000000000..ed824d39a --- /dev/null +++ b/.flooignore @@ -0,0 +1,6 @@ +extern +node_modules +tmp +vendor +.idea/workspace.xml +.idea/misc.xml diff --git a/lib/driver.rb b/lib/driver.rb new file mode 100644 index 000000000..879a3f166 --- /dev/null +++ b/lib/driver.rb @@ -0,0 +1,73 @@ +require_relative 'csv_record' + +module RideShare + class Driver < CsvRecord + attr_reader :name, :vin, :status, :trips + + def initialize(id:, name:, vin:, status: :AVAILABLE, trips: []) + super(id) + + @name = name + + raise ArgumentError.new("Invalid VIN: #{ vin }") if vin.length != 17 + @vin = vin + + raise ArgumentError.new("Invalid status: #{ status }") unless [:AVAILABLE, :UNAVAILABLE].include? (status.to_sym) + @status = status.to_sym + + @trips = trips + end + + def add_trip(trip) + @trips << trip + end + + def average_rating + length = @trips.length + return 0 if length == 0 + + total = 0 + + @trips.each do |trip| + total += trip.rating unless trip.rating.nil? + if trip.rating.nil? + length -= 1 + end + end + average = total.to_f / length + return average.round(1) + end + + def total_revenue + return 0 if @trips.length == 0 + + total = 0 + + @trips.each do |trip| + if trip.cost.nil? + next + elsif trip.cost <= 1.65 + total += (trip.cost * 0.8) + elsif + total += ((trip.cost - 1.65) * 0.8) + end + end + return total.round(2) + end + + def trip_in_progress + @status = :UNAVAILABLE + end + + private + + def self.from_csv(record) + return new( + id: record[:id], + name: record[:name], + vin: record[:vin], + status: record[:status] + ) + end + end +end \ No newline at end of file diff --git a/lib/passenger.rb b/lib/passenger.rb index 12ec25982..40d9e2d61 100644 --- a/lib/passenger.rb +++ b/lib/passenger.rb @@ -16,6 +16,19 @@ def add_trip(trip) @trips << trip end + def net_expenditures + total = 0 + @trips.each do |trip| + total += trip.cost unless trip.cost.nil? + end + return total + end + + def total_time_spent + total = @trips.sum { |trip| trip.duration } + return total + end + private def self.from_csv(record) diff --git a/lib/trip.rb b/lib/trip.rb index f59464dae..dbcc30268 100644 --- a/lib/trip.rb +++ b/lib/trip.rb @@ -1,15 +1,18 @@ require 'csv' +require 'time' require_relative 'csv_record' module RideShare class Trip < CsvRecord - attr_reader :id, :passenger, :passenger_id, :start_time, :end_time, :cost, :rating + attr_reader :id, :passenger, :passenger_id,:driver, :driver_id, :start_time, :end_time, :cost, :rating def initialize( id:, passenger: nil, passenger_id: nil, + driver: nil, + driver_id: nil, start_time:, end_time:, cost: nil, @@ -28,14 +31,28 @@ def initialize( raise ArgumentError, 'Passenger or passenger_id is required' end + raise ArgumentError, 'Driver or driver_id is required' unless driver || driver_id + @driver = driver + @driver.nil? ? @driver_id = driver_id : @driver_id = driver.id + @start_time = start_time @end_time = end_time + raise ArgumentError.new("Start time should occur before the end time") if @start_time > @end_time unless @end_time.nil? + @cost = cost + @rating = rating + raise ArgumentError.new("Invalid rating #{@rating}") if @rating > 5 || @rating < 1 unless @rating.nil? - if @rating > 5 || @rating < 1 - raise ArgumentError.new("Invalid rating #{@rating}") + end + + def duration + if @end_time.nil? + duration = 0 + else + duration = @end_time - @start_time end + return duration.to_i end def inspect @@ -44,15 +61,18 @@ def inspect "#<#{self.class.name}:0x#{self.object_id.to_s(16)} " + "id=#{id.inspect} " + "passenger_id=#{passenger&.id.inspect} " + + "driver_id=#{driver&.id.inspect} " + "start_time=#{start_time} " + "end_time=#{end_time} " + - "cost=#{cost} " + + "cost=#{cost} " + "rating=#{rating}>" end - def connect(passenger) + def connect(passenger, driver) @passenger = passenger + @driver = driver passenger.add_trip(self) + driver.add_trip(self) end private @@ -61,8 +81,9 @@ def self.from_csv(record) return self.new( id: record[:id], passenger_id: record[:passenger_id], - start_time: record[:start_time], - end_time: record[:end_time], + driver_id: record[:driver_id], + start_time: Time.parse(record[:start_time]), + end_time: Time.parse(record[:end_time]), cost: record[:cost], rating: record[:rating] ) diff --git a/lib/trip_dispatcher.rb b/lib/trip_dispatcher.rb index 5130849f8..d6aa94421 100644 --- a/lib/trip_dispatcher.rb +++ b/lib/trip_dispatcher.rb @@ -11,6 +11,7 @@ class TripDispatcher def initialize(directory: './support') @passengers = Passenger.load_all(directory: directory) @trips = Trip.load_all(directory: directory) + @drivers = Driver.load_all(directory: directory) connect_trips end @@ -19,6 +20,36 @@ def find_passenger(id) return @passengers.find { |passenger| passenger.id == id } end + def find_driver(id) + Driver.validate_id(id) + return @drivers.find { |driver| driver.id == id } + end + + def intelligent_dispatch + drivers_available = @drivers.select { |driver| driver.status == :AVAILABLE } + raise ArgumentError.new("No available drivers") if drivers_available.empty? + + if drivers_available.any? { |candidate| candidate.trips.count == 0 } + driver = drivers_available.find { |candidate| candidate.trips.count == 0 } + else + driver = drivers_available.min_by { |candidate| candidate.trips.last.end_time } + end + return driver + end + + def request_trip(passenger_id) + passenger = find_passenger(passenger_id) + driver = intelligent_dispatch + + trip = Trip.new(id: @trips.last.id + 1, passenger: passenger, passenger_id: passenger_id, driver: driver, driver_id: driver.id, start_time: Time.now, end_time: nil, cost: nil, rating: nil) + + @trips << trip + passenger.add_trip(trip) + driver.add_trip(trip) + driver.trip_in_progress + return trip + end + def inspect # Make puts output more useful return "#<#{self.class.name}:0x#{object_id.to_s(16)} \ @@ -32,9 +63,9 @@ def inspect def connect_trips @trips.each do |trip| passenger = find_passenger(trip.passenger_id) - trip.connect(passenger) + driver = find_driver(trip.driver_id) + trip.connect(passenger, driver) end - return trips end end diff --git a/test/driver_test.rb b/test/driver_test.rb index 4e6076ec2..badbf0770 100644 --- a/test/driver_test.rb +++ b/test/driver_test.rb @@ -1,6 +1,6 @@ require_relative 'test_helper' -xdescribe "Driver class" do +describe "Driver class" do describe "Driver instantiation" do before do @driver = RideShare::Driver.new( @@ -84,7 +84,7 @@ id: 54, name: "Rogers Bartell IV", vin: "1C9EVBRM0YBC564DZ" - ) + ) trip = RideShare::Trip.new( id: 8, driver: @driver, @@ -92,7 +92,7 @@ start_time: Time.new(2016, 8, 8), end_time: Time.new(2016, 8, 8), rating: 5 - ) + ) @driver.add_trip(trip) end @@ -111,11 +111,11 @@ id: 54, name: "Rogers Bartell IV", vin: "1C9EVBRM0YBC564DZ" - ) + ) expect(driver.average_rating).must_equal 0 end - it "correctly calculates the average rating" do + it "correctly calculates the average rating for a driver" do trip2 = RideShare::Trip.new( id: 8, driver: @driver, @@ -123,14 +123,83 @@ start_time: Time.new(2016, 8, 8), end_time: Time.new(2016, 8, 9), rating: 1 - ) + ) + @driver.add_trip(trip2) + + expect(@driver.average_rating).must_be_close_to (5.0 + 1.0) / 2.0, 0.01 + end + + it "correctly calculates the average rating for a driver with an in-progress trip" do + trip2 = RideShare::Trip.new( + id: 8, + driver: @driver, + passenger_id: 3, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 9), + rating: 1 + ) + trip3 = RideShare::Trip.new( + id: 9, + driver: @driver, + passenger_id: 6, + start_time: Time.now, + end_time: nil, + rating: nil, + cost: nil + ) + @driver.add_trip(trip2) + @driver.add_trip(trip3) expect(@driver.average_rating).must_be_close_to (5.0 + 1.0) / 2.0, 0.01 end end describe "total_revenue" do - # You add tests for the total_revenue method + before do + @driver = RideShare::Driver.new( + id: 54, + name: "Rogers Bartell IV", + vin: "1C9EVBRM0YBC564DZ" + ) + trip = RideShare::Trip.new( + id: 8, + driver: @driver, + passenger_id: 3, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 8), + cost: 1, + rating: 5 + ) + trip_2 = RideShare::Trip.new( + id: 9, + driver: @driver, + passenger_id: 3, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 8), + cost: 10, + rating: 5 + ) + @driver.add_trip(trip) + @driver.add_trip(trip_2) + end + it "can calculate the total revenue for each driver" do + expect(@driver.total_revenue).must_equal 7.48 + end + + it "can calculate the total revenue for a driver with a trip in-progress" do + trip_3 = RideShare::Trip.new( + id: 10, + driver: @driver, + passenger_id: 6, + start_time: Time.now, + end_time: nil, + rating: nil, + cost: nil + ) + @driver.add_trip(trip_3) + + expect(@driver.total_revenue).must_equal 7.48 + end end end diff --git a/test/passenger_test.rb b/test/passenger_test.rb index eb3a631df..03e0a5ec2 100644 --- a/test/passenger_test.rb +++ b/test/passenger_test.rb @@ -37,18 +37,26 @@ describe "trips property" do before do - # TODO: you'll need to add a driver at some point here. @passenger = RideShare::Passenger.new( id: 9, name: "Merl Glover III", phone_number: "1-602-620-2330 x3723", trips: [] ) + @driver = RideShare::Driver.new( + id: 2, + name: "Chris", + vin: "1B6CF40K1J3Y74UY2", + status: :AVAILABLE, + trips: [] + ) trip = RideShare::Trip.new( id: 8, passenger: @passenger, + driver: @driver, start_time: Time.new(2016, 8, 8), end_time: Time.new(2016, 8, 9), + cost: 10.5, rating: 5 ) @@ -69,6 +77,119 @@ end describe "net_expenditures" do - # You add tests for the net_expenditures method + before do + @passenger = RideShare::Passenger.new( + id: 9, + name: "Merl Glover III", + phone_number: "1-602-620-2330 x3723", + trips: [] + ) + @driver = RideShare::Driver.new( + id: 2, + name: "Chris", + vin: "1B6CF40K1J3Y74UY2", + status: :AVAILABLE, + trips: [] + ) + trip = RideShare::Trip.new( + id: 8, + passenger: @passenger, + driver: @driver, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 9), + cost: 10.5, + rating: 5 + ) + trip_2 = RideShare::Trip.new( + id: 9, + passenger: @passenger, + driver: @driver, + start_time: Time.new(2016, 8, 8), + end_time: Time.new(2016, 8, 9), + cost: 9, + rating: 5 + ) + + @passenger.add_trip(trip) + @passenger.add_trip(trip_2) + end + + it "can calculate the total spending for each passenger" do + expect(@passenger.net_expenditures).must_equal 19.5 + end + + it "can calculate total spending for a passenger with a trip in-progress" do + trip_3 = RideShare::Trip.new( + id: 10, + passenger: @passenger, + driver: @driver, + start_time: Time.now, + end_time: nil, + cost: nil, + rating: nil + ) + @passenger.add_trip(trip_3) + expect(@passenger.net_expenditures).must_equal 19.5 + end + + end + + describe "total_time_spent" do + before do + @passenger = RideShare::Passenger.new( + id: 9, + name: "Merl Glover III", + phone_number: "1-602-620-2330 x3723", + trips: [] + ) + @driver = RideShare::Driver.new( + id: 2, + name: "Chris", + vin: "1B6CF40K1J3Y74UY2", + status: :AVAILABLE, + trips: [] + ) + trip = RideShare::Trip.new( + id: 8, + passenger: @passenger, + driver: @driver, + start_time: Time.parse("2018-12-27 01:39:05 -0800"), + end_time: Time.parse("2018-12-27 02:39:05 -0800"), + cost: 10.5, + rating: 5 + ) + trip_2 = RideShare::Trip.new( + id: 9, + passenger: @passenger, + driver: @driver, + start_time: Time.parse("2018-12-27 01:39:05 -0800"), + end_time: Time.parse("2018-12-27 01:59:05 -0800"), + cost: 9, + rating: 5 + ) + + @passenger.add_trip(trip) + @passenger.add_trip(trip_2) + end + + it "can calculate the total time spent for each passenger" do + expect(@passenger.total_time_spent).must_equal 4800 + end + + it "can calculate total time spent for a passenger with a trip in-progress" do + trip_3 = RideShare::Trip.new( + id: 10, + passenger: @passenger, + driver: @driver, + start_time: Time.now, + end_time: nil, + cost: nil, + rating: nil + ) + @passenger.add_trip(trip_3) + expect(@passenger.total_time_spent).must_equal 4800 + end + end + end diff --git a/test/test_data/trips.csv b/test/test_data/trips.csv index 636dac122..4e5891e7e 100644 --- a/test/test_data/trips.csv +++ b/test/test_data/trips.csv @@ -3,4 +3,4 @@ id,driver_id,passenger_id,start_time,end_time,cost,rating 2,1,3,2018-05-25 04:39:00 -0700,2018-05-25 04:55:00 -0700,7,3 3,2,4,2018-06-11 22:22:00 -0700,2018-06-11 22:57:00 -0700,15,4 4,2,7,2018-08-12 15:04:00 -0700,2018-08-12 15:14:00 -0700,8,1 -5,2,6,2018-08-05 08:58:00 -0700,2018-08-05 09:30:00 -0700,32,1 +5,2,6,2018-08-05 08:58:00 -0700,2018-08-05 09:30:00 -0700,32,1 \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index bdb9bf352..082369790 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -12,4 +12,4 @@ require_relative '../lib/passenger' require_relative '../lib/trip' require_relative '../lib/trip_dispatcher' -# require_relative '../lib/driver' +require_relative '../lib/driver' diff --git a/test/trip_dispatch_test.rb b/test/trip_dispatch_test.rb index 21f4457b7..7a928f49b 100644 --- a/test/trip_dispatch_test.rb +++ b/test/trip_dispatch_test.rb @@ -23,7 +23,7 @@ def build_test_dispatcher expect(dispatcher.trips).must_be_kind_of Array expect(dispatcher.passengers).must_be_kind_of Array - # expect(dispatcher.drivers).must_be_kind_of Array + expect(dispatcher.drivers).must_be_kind_of Array end it "loads the development data by default" do @@ -78,8 +78,7 @@ def build_test_dispatcher end end - # TODO: un-skip for Wave 2 - xdescribe "drivers" do + describe "drivers" do describe "find_driver method" do before do @dispatcher = build_test_dispatcher @@ -122,4 +121,90 @@ def build_test_dispatcher end end end + + describe "request a new trip" do + before do + @dispatcher = build_test_dispatcher + end + passenger_id = 6 + + it "works for request_trip" do + expect(@dispatcher.request_trip(passenger_id)).must_be_kind_of RideShare::Trip + end + + xit "finds the first available driver" do + expect(@dispatcher.request_trip(passenger_id).driver_id).must_equal 2 + end + + it "(Wave 4) verify selected driver is :AVAILABLE, and change driver status to :UNAVAILABLE when trip is in-progress" do + driver_id = 3 + # before request a trip + expect(@dispatcher.find_driver(driver_id).status).must_equal :AVAILABLE + # after request the trip + @dispatcher.request_trip(passenger_id) + expect(@dispatcher.find_driver(driver_id).status).must_equal :UNAVAILABLE + end + + it "(Wave 4) Intelligent Dispatching: selects the available driver with 0 trips first" do + expect(@dispatcher.request_trip(passenger_id).driver_id).must_equal 3 + expect(@dispatcher.request_trip(passenger_id).driver.status).must_equal :UNAVAILABLE + end + + it "checks that requested trip has the right parameters" do + passenger_id = 6 + trip = @dispatcher.request_trip(passenger_id) + + expect(trip.passenger_id).must_equal 6 + expect(trip.id).must_equal 6 + expect(trip.end_time).must_be_nil + expect(trip.cost).must_be_nil + expect(trip.rating).must_be_nil + end + + it "checks that trip id increments by 1" do + array = [6, 7] + trip_id = 5 + + array.each do |pass| + trip = @dispatcher.request_trip(pass) + expect(trip.id).must_equal trip_id += 1 + end + end + + it "raises an error for no driver available" do + array = [6, 7, 3] + + expect{ + array.each do |pass| + @dispatcher.request_trip(pass) + end}.must_raise ArgumentError + end + + it "checks a trip is added to passenger trips" do + passenger_id = 6 + trip = @dispatcher + expect(trip.find_passenger(passenger_id).trips.count).must_equal 1 + + # after request a trip + trip.request_trip(passenger_id) + expect(trip.find_passenger(passenger_id).trips.count).must_equal 2 + + end + + it "checks a trip is added to driver trips" do + + passenger_id = 6 + driver_id = 3 + + trip = @dispatcher + + # before request a trip + expect(trip.find_driver(driver_id).trips.count).must_equal 0 + # after request a trip + trip.request_trip(passenger_id) + expect(trip.find_driver(driver_id).trips.count).must_equal 1 + + end + end end + diff --git a/test/trip_test.rb b/test/trip_test.rb index 2063e28a7..69dea44cd 100644 --- a/test/trip_test.rb +++ b/test/trip_test.rb @@ -12,6 +12,12 @@ name: "Ada", phone_number: "412-432-7640" ), + driver: RideShare::Driver.new( + id: 2, + name: "Chris", + vin: "1B6CF40K1J3Y74UY2", + status: :AVAILABLE + ), start_time: start_time, end_time: end_time, cost: 23.45, @@ -29,7 +35,6 @@ end it "stores an instance of driver" do - skip # Unskip after wave 2 expect(@trip.driver).must_be_kind_of RideShare::Driver end @@ -41,5 +46,36 @@ end.must_raise ArgumentError end end + + it 'raises an error for end time before the start time' do + start_time = Time.parse("2018-12-27 02:39:05 -0800") + end_time = Time.parse("2018-12-27 01:39:05 -0800") + @trip_data = { + id: 8, + passenger: RideShare::Passenger.new( + id: 1, + name: "Ada", + phone_number: "412-432-7640" + ), + driver: RideShare::Driver.new( + id: 2, + name: "Chris", + vin: "1B6CF40K1J3Y74UY2", + status: :AVAILABLE + ), + start_time: start_time, + end_time: end_time, + cost: 23.45, + rating: 3 + } + + expect do + RideShare::Trip.new(@trip_data) + end.must_raise ArgumentError + end + + it "returns a trip duration" do + expect(@trip.duration).must_equal 1500 + end end end