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

Mark Kendall #602

Open
wants to merge 65 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
e2f393c
Initial local commit w/ gems installed
mkendall42 Dec 13, 2024
c345f73
Fix Facility class to pass tests
mkendall42 Dec 13, 2024
baf7b46
Fix Vehicle class to pass tests
mkendall42 Dec 13, 2024
4811339
Add Facility method include?()
mkendall42 Dec 13, 2024
64fb6dd
Fix Dmv class to pass tests
mkendall42 Dec 13, 2024
bc8c780
Fix DmvDataServiceClass to pass tests (only comments)
mkendall42 Dec 13, 2024
2509a24
Add comments to spec_helper.rb
mkendall42 Dec 13, 2024
e3157b1
Merge pull request #1 from mkendall42/fix_existing_codebase
mkendall42 Dec 13, 2024
5046684
Create registrant_spec.rb skeleton
mkendall42 Dec 13, 2024
6e2b5a1
Add initialize tests
mkendall42 Dec 13, 2024
e8814b1
Create Registrant class with initialize()
mkendall42 Dec 13, 2024
5e55a74
Add Registrant method permit?()
mkendall42 Dec 13, 2024
0e1afd1
Add Registrant method earn_permit()
mkendall42 Dec 13, 2024
934d3e1
Update registrant_spec.rb tests
mkendall42 Dec 13, 2024
b75c60a
Merge pull request #2 from mkendall42/registrant_class
mkendall42 Dec 13, 2024
5d032fe
Update Facility initialize() and associated test
mkendall42 Dec 13, 2024
9195b04
Add Facility register_vehicle() method and associated tests
mkendall42 Dec 13, 2024
b3f1eb9
Update Facility register_vehicle() method and associated tests / need…
mkendall42 Dec 14, 2024
4c5e6f0
Update Facility register_vehicle() method and associated testing
mkendall42 Dec 14, 2024
a75eaa6
Add Facility administer_written_test() method and baseline test
mkendall42 Dec 14, 2024
f295161
Update Facility administer_written_test() method and associated tests
mkendall42 Dec 14, 2024
281f8a4
Update Facility administer_written_test() method and associated tests
mkendall42 Dec 14, 2024
0d9a1eb
Add Facilities administer_road_test() method and associated tests
mkendall42 Dec 14, 2024
4ee6653
Add Facility renew_drivers_licsense() method and associated tests
mkendall42 Dec 14, 2024
8b5ab56
Merge pull request #3 from mkendall42/feature_facility_services
mkendall42 Dec 14, 2024
f54a8ce
Create VehicleFactory class and associated test file skeleton
mkendall42 Dec 14, 2024
102bbea
Add VehicleFactory add_vehicles() method and associated test
mkendall42 Dec 14, 2024
ae9ccd5
Update VehicleFactory create_vehicles() method and associated test
mkendall42 Dec 14, 2024
cdf6e0d
Update VehicleFactory initialize() method and associated test
mkendall42 Dec 14, 2024
f8ab7e3
Update VehicleFactory create_vehicles() method to utilize instance va…
mkendall42 Dec 14, 2024
d29adec
Merge pull request #4 from mkendall42/vehicle_factory_class
mkendall42 Dec 14, 2024
88db5d0
Add Dmv create_state_facilities() method and associated test
mkendall42 Dec 14, 2024
b4aa6c1
Update Dmv create_state_facilities() method and associated tests
mkendall42 Dec 15, 2024
b085fb6
Update Dmv create_state_facilities and associated test - works for Co…
mkendall42 Dec 15, 2024
01e7e07
Update Dmv create_state_facilities() method for NY and tests - interm…
mkendall42 Dec 15, 2024
4dcc4b2
Update Dmv create_state_facilities() method and associated tests for NY
mkendall42 Dec 15, 2024
4ba21e0
Update Dmv create_state_facilities() method Update Dmv create_state_f…
mkendall42 Dec 15, 2024
ce3ae04
Update Dmv create_state_facilities() method and associated tests for …
mkendall42 Dec 15, 2024
cb77fe8
Merge pull request #5 from mkendall42/feature_dmv_functionality
mkendall42 Dec 15, 2024
24513bb
Update Facility initialize() and tests to track state for other metho…
mkendall42 Dec 15, 2024
04d5d4b
Add Dmv get_ev_registration_analytics() method and test skeleton
mkendall42 Dec 15, 2024
5a65d4e
Update Dmv get_ev_registration_analytics() method and associated tests
mkendall42 Dec 16, 2024
d9881b8
Update Dmv get_ev_registration_analytics() method and tests - most po…
mkendall42 Dec 16, 2024
9bcac62
Update Dmv get_ev_registration_analytics() method and test - # regist…
mkendall42 Dec 16, 2024
8a60efc
Update Dmv get_ev_registration_analytics() method and tests
mkendall42 Dec 16, 2024
1fd2f51
Update Vehicle initialize(), var (county), and tests necessary for ne…
mkendall42 Dec 16, 2024
50d714d
Update VehicleFactory create_vehicle() method and tests necessary for…
mkendall42 Dec 16, 2024
5e7cdf5
Update Dmv get_ev_registration_analytics() method and tests - fully f…
mkendall42 Dec 16, 2024
517d94d
Update Facility initialize() method, instance vars (hours, holidays) …
mkendall42 Dec 16, 2024
7d10ec4
Update Dmv create_state_facilities() method and associated tests - ad…
mkendall42 Dec 16, 2024
aff3014
Add DmvDataService ny_vehicle_registrations() method and associated t…
mkendall42 Dec 16, 2024
4cdc621
Update VehicleFactory create_vehicles() method and tests to allow for…
mkendall42 Dec 17, 2024
bb4740d
Fix Vehicle initialize() method to correctly track variable as integer
mkendall42 Dec 17, 2024
730f52d
Update Dmv get_ev_registration_analytics() method and tests to analyz…
mkendall42 Dec 17, 2024
2f19172
Merge pull request #6 from mkendall42/feature_analytics_and_functiona…
mkendall42 Dec 17, 2024
8d28bfa
Update tests / code for Dmv class to be more tolerant to changing dat…
mkendall42 Dec 17, 2024
12b94cf
Update tests for Facility class to be thorough
mkendall42 Dec 17, 2024
ebb57fa
Organize rspec definitions consistently with spec_helper.rb
mkendall42 Dec 17, 2024
550bfbf
Update Facility tests
mkendall42 Dec 17, 2024
7b3229a
Tighten code and prune comments
mkendall42 Dec 17, 2024
9b8b363
Add helper methods to separate dataset formatting for each state CO, …
mkendall42 Dec 17, 2024
d310de2
Merge pull request #7 from mkendall42/feature_analytics_and_functiona…
mkendall42 Dec 17, 2024
b6dafac
Merge branch 'main' of github.com:mkendall42/the_dmv_markfork
mkendall42 Dec 17, 2024
8ae2a5c
Fix test error in WA vs NY vehicle analytics
mkendall42 Dec 17, 2024
f40eb13
Final code cleaning - project ready to submit
mkendall42 Dec 17, 2024
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
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ GEM
PLATFORMS
arm64-darwin-20
arm64-darwin-21
arm64-darwin-23

DEPENDENCIES
faraday
Expand Down
164 changes: 163 additions & 1 deletion lib/dmv.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#Class appears to track DMV facilities and services offered to customers
require 'pry'

class Dmv
attr_reader :facilities

def initialize
@facilities = []
Expand All @@ -9,8 +13,166 @@ def add_facility(facility)
end

def facilities_offering_service(service)
@facilities.find do |facility|
@facilities.find_all do |facility|
facility.services.include?(service)
end
end

#Larger methods below. For future: could consider classes for DMVs in each state, to better handle
#idiosyncracies to procedures and different dataset formats for different states.

def create_state_facilities(state, facilities_incoming_data)
facilities_incoming_data.each do |facility_data|
#Construct facility based on state, since dataset must be converted differently due to idiosyncracies in each case
if state == "Colorado"
new_facility = Facility.new(facility_parameters_co(facility_data))
elsif state == "New York"
new_facility = Facility.new(facility_parameters_ny(facility_data))
elsif state == "Missouri"
new_facility = Facility.new(facility_parameters_mo(facility_data))
end

add_facility(new_facility)

#Add services. CO specifies some of them, so those must be checked (in furture rewrite, could remake initialize() to deal with this more cleanly too)
new_facility.add_service("Written Test")
new_facility.add_service("Road Test")
if state == "Colorado"
new_facility.add_service("New Drivers License") if facility_data[:services_p].include?("registration")
new_facility.add_service("Renew Drivers License") if facility_data[:services_p].include?("renew")
else
new_facility.add_service("New Drivers License")
new_facility.add_service("Renew Drivers License")
end
end
end

def get_ev_registration_analytics(state, specified_year)
#Determine most popular make/model registered, the # registered for specified year, and the county with most registered vehicles
#NOTE: I kept method naming based on EV vehicles. However, now that I've extended functionality to allow NY vehicles (later in iteration 4), this method is more general,
#and can analyze general NY vehicles.

vehicle_tally = {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider breaking up some of this logic into well-named helper methods in order to shorten this method. We typically want methods to stay between 8-10 lines, so using a helper method like tally_vehicle could be a great way to extract some of this into a separate method.

@facilities.each do |facility|
if facility.state == state
facility.registered_vehicles.each do |vehicle|
if vehicle_tally.include?(vehicle.model) #Assume just 'model' is enough (i.e. its unique and not make + model is needed)
vehicle_tally[vehicle.model] += 1
else
vehicle_tally[vehicle.model] = 1
end
end
end
end
#Now find the largest count in the hash:
most_popular_model = vehicle_tally.key(vehicle_tally.values.max)

#Count number of occurences of given registration year in vehicle list
vehicle_year_total = 0
@facilities.each do |facility|
if facility.state == state
vehicle_year_total += facility.registered_vehicles.count do |vehicle|
vehicle.registration_date.year == specified_year
end
end
end

#Find county which has the most vehicle registrations. Note high machinery overlap with most popular model...a way to combine?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love seeing your note around combining! Extracting a helper method that just uses a different keyword value to look for, and updates the correct key in the hash, could do the trick!

county_tally = {}
@facilities.each do |facility|
if facility.state == state
facility.registered_vehicles.each do |vehicle|
if county_tally.include?(vehicle.registration_county)
county_tally[vehicle.registration_county] += 1
else
county_tally[vehicle.registration_county] = 1
end
end
end
end
most_popular_county = county_tally.key(county_tally.values.max)

{most_popular_model: most_popular_model, number_registered_for_year: vehicle_year_total, county_most_registered_vehicles: most_popular_county}
end

def facility_parameters_co(facility_co_data)
#Try to centralize idiosyncratic aspects of each state's dataset to specific spots in codebase
#Alternate: could hosue this in Facility class. Might try that another time - would require a different format since Facility wouldn't yet be initialized
{
name: facility_co_data[:dmv_office],
address: "#{facility_co_data[:address_li]} #{facility_co_data[:address__1]} #{facility_co_data[:location]} #{facility_co_data[:city]} #{facility_co_data[:state]} #{facility_co_data[:zip]}",
phone: facility_co_data[:phone],
hours: facility_co_data[:hours] #Iteration 4: add hours (direct copy for CO)
}
end

def facility_parameters_ny(facility_ny_data)
#To format name: have to concatenate and then 'fix' capitlization the hard way (since each word is capitalized for a title usually)
name_formatted = facility_ny_data[:office_name].split.map { |word| word.capitalize }
name_formatted << (facility_ny_data[:office_type].split.map { |word| word.capitalize }).join(" ")
name_formatted = name_formatted.join(" ")

#To format address: lots of overlap here with previous entry. If I knew how to make a method that accepted varying arguments, I might be able to; don't know this yet...
address_formatted = facility_ny_data[:street_address_line_1].split.map { |word| word.capitalize }
address_formatted << facility_ny_data[:city].split.map { |word| word.capitalize }
address_formatted << facility_ny_data[:state]
address_formatted << facility_ny_data[:zip_code]
address_formatted = address_formatted.join(" ")

#Hours are listed for each day by opening and closing. For now, I'm going to brute-force this,
#since no exact format is expected. This is a mess though:
#This is gross...some facilities don't have keys for all M-F. So I need to run ifs now (otherwise problems with nil)
hours_formatted = ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This data cleaning does get a bit icky. That's why extracting into a helper method that can be called such as format_opening_times could at least encapsulate the ickyness.

if facility_ny_data.include?(:monday_beginning_hours)
hours_formatted = "Monday: " + facility_ny_data[:monday_beginning_hours] + " - " + facility_ny_data[:monday_ending_hours] + "; "
end
if facility_ny_data.include?(:tuesday_beginning_hours)
hours_formatted << "Tuesday: " + facility_ny_data[:tuesday_beginning_hours] + " - " + facility_ny_data[:tuesday_ending_hours] + "; "
end
if facility_ny_data.include?(:wednesday_beginning_hours)
hours_formatted << "Wednesday: " + facility_ny_data[:wednesday_beginning_hours] + " - " + facility_ny_data[:wednesday_ending_hours] + "; "
end
if facility_ny_data.include?(:thursday_beginning_hours)
hours_formatted << "Thursday: " + facility_ny_data[:thursday_beginning_hours] + " - " + facility_ny_data[:thursday_ending_hours] + "; "
end
if facility_ny_data.include?(:friday_beginning_hours)
hours_formatted << "Friday: " + facility_ny_data[:friday_beginning_hours] + " - " + facility_ny_data[:friday_ending_hours]
end

#Some of the office don't seem to have phone numbers...make sure to process 'nil' correctly here
phone = facility_ny_data[:public_phone_number]
if phone != nil
phone_formatted = "(" + phone.slice(0, 3) + ") " + phone.slice(3, 3) + "-" + phone.slice(6, 4)
else
phone_formatted = "not applicable"
end

{
name: name_formatted,
address: address_formatted,
phone: phone_formatted,
hours: hours_formatted
}
end

def facility_parameters_mo(facility_mo_data)
address_formatted = facility_mo_data[:address1].delete_suffix(",") #Certain (random?) entries end with it for some reason
address_formatted = "#{address_formatted} #{facility_mo_data[:city]} #{facility_mo_data[:state]} #{facility_mo_data[:zipcode]}"
hours_formatted = facility_mo_data[:daysopen]
if facility_mo_data.include?(:daysclosed)
hours_formatted << " except for " + facility_mo_data[:daysclosed]
end

facility_info = {
name: facility_mo_data[:name] + " Office", #To be as consistent with other states as possible
address: address_formatted,
phone: facility_mo_data[:phone],
#Add hours (may need to format a little to be consistent across states)
#Add holidays here (since MO provides them)
#Need :daysopen, :daysclosed; :holidaysclosed
hours: hours_formatted,
holidays: facility_mo_data[:holidaysclosed]
}
end

end
7 changes: 7 additions & 0 deletions lib/dmv_data_service.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require 'faraday'
require 'json'

#NOTE: I see that for the vehicle data loading, it only pulls 1000 vehicles for both WA and NY. No way that is coincidence...where is the '1000' constraint present?

class DmvDataService
def load_data(source)
response = Faraday.get(source)
Expand All @@ -11,6 +13,11 @@ def wa_ev_registrations
@wa_ev_registrations ||= load_data('https://data.wa.gov/resource/rpr4-cgyd.json')
end

def ny_vehicle_registrations
#Iteration 4 - based on dataset URL provided. Should have same structure as other entries:
@ny_vehicle_registrations ||= load_data('https://data.ny.gov/resource/w4pv-hbkt.json')
end

def co_dmv_office_locations
@co_dmv_office_locations ||= load_data('https://data.colorado.gov/resource/dsw3-mrn4.json')
end
Expand Down
64 changes: 58 additions & 6 deletions lib/facility.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,66 @@
#This tracks a specific DMV facility's core information and serviced rendered (or perhaps I should say """"""services""""""", given that it's the DMV)

class Facility
attr_reader :name, :address, :phone, :services
attr_reader :name, :address, :phone, :services, :collected_fees, :registered_vehicles, :state, :hours, :holidays
@@fee_chart = {ev: 200, antique: 25, regular: 100} #Fun new thing. Can use again and again (class var, not instance var)

def initialize(name, address, phone)
@name = name
@address = address
@phone = phone
def initialize(facility_info)
@name = facility_info[:name]
@address = facility_info[:address]
@phone = facility_info[:phone]
@services = []
@collected_fees = 0
@registered_vehicles = []
#Added in iteration 4 (for by-state searching). Default is nil, if not specified (backwards compatible methinks)
@state = facility_info[:state]
@hours = facility_info[:hours]
@holidays = facility_info[:holidays]
end

def add_services(service)
def add_service(service)
@services << service
end

def include?(service_specified)
#NOTE: find must return 'false' if nothing found to play nice with methods accessing it
@services.find(proc {false}) { |service| service == service_specified }
end

#Facility services

def register_vehicle(vehicle)
#Only register the vehicle if this facility offers the service! Otherwise don't waste time with processing.
return nil if !include?("Vehicle Registration")

vehicle.registration_date = Date.today

#Determine plate type and collect fees (this of course assumes the registrant has/will pay)
if vehicle.electric_vehicle?()
vehicle.plate_type = :ev
@collected_fees += @@fee_chart[:ev]
elsif vehicle.antique?()
vehicle.plate_type = :antique
@collected_fees += @@fee_chart[:antique]
else
vehicle.plate_type = :regular
@collected_fees += @@fee_chart[:regular]
end

@registered_vehicles << vehicle
end

def administer_written_test(registrant)
#ALL conditions must be met. Pretty compact!
registrant.license_data[:written] = include?("New Drivers License") && registrant.age >= 16 && registrant.permit?()
end

def administer_road_test(registrant)
#Of course, this assumes the registrant actually passes the test (happy path)
registrant.license_data[:license] = include?("Road Test") && registrant.license_data[:written] == true
end

def renew_drivers_license(registrant)
registrant.license_data[:renewed] = include?("Renew License") && registrant.license_data[:license] == true
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice logic here!

end
18 changes: 18 additions & 0 deletions lib/registrant.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class Registrant
attr_reader :name, :age, :license_data

def initialize(name, age, permit = false)
@name = name
@age = age
@permit = permit
@license_data = {:written => false, :license => false, :renewed => false}
end

def permit?()
@permit
end

def earn_permit()
@permit = true
end
end
11 changes: 9 additions & 2 deletions lib/vehicle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,21 @@ class Vehicle
:year,
:make,
:model,
:engine
:engine,
:registration_county
attr_accessor :registration_date, :plate_type
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the purposes of this project, it is totally okay to use an attr_accessor. However, we often want to stay away from these and instead create methods for changing the value of our attributes. We want to be able to have control over these data changes, and maintain data integrity. Right now, someone could change the registration_date to banana and we have no control over that. But, if you added a "setter" method in this class that an outside class had to call, like set_registration_date(date) then you could add logic like checking that it's a valid date. This is a good rule of thumb for adhering to the principle of encapsulation in our our classes.


def initialize(vehicle_details)
@vin = vehicle_details[:vin]
@year = vehicle_details[:year]
@year = vehicle_details[:year].to_i #Need to_i to permit proper function of antique?() method
@make = vehicle_details[:make]
@model = vehicle_details[:model]
@engine = vehicle_details[:engine]
@registration_date = nil

#Could make a registration_data hash for compactness, but will keep vars separate to mimic interaction pattern
@plate_type = nil
@registration_county = vehicle_details[:county] #This could either be set based on where car is built, OR where it's registered
end

def antique?
Expand Down
55 changes: 55 additions & 0 deletions lib/vehicle_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#This 'manufactures' cars. Kinda a misnomer, since it just takes existing registration data from API
#(doesn't really build them), but I see why we could do this to 'help' the Vehicle class along.

class VehicleFactory
attr_reader :vehicles_manufactured

def initialize()
@vehicles_manufactured = []
end

def create_vehicles(vehicle_registration_list, state)
#Different states produce different datasets, so need to switch based on this.

vehicle_registration_list.each do |vehicle|
if state == "Washington"
vehicle_data = {
vin: vehicle[:vin_1_10],
make: vehicle[:make],
model: vehicle[:model],
year: vehicle[:model_year],
#Assume the engine is always ev here in WA (though not generally true - happy path for now)
engine: :ev,
county: vehicle[:county]
}
elsif state == "New York"
#Engine type isn't specified in dataset. Let's not get too fancy here, so just provide three options: :ev, :ice, or :other
case vehicle[:fuel_type]
when "ELECTRIC"
engine_type = :ev
when "GAS" || "DIESEL" || "PROPANE" || "FLEX" #There are several fossil-fuel types...these are the only ones I'm pretty confident about
engine_type = :ice
else
engine_type = :other
end

vehicle_data = {
vin: vehicle[:vin],
make: vehicle[:make],
model: "data not available", #Not provided in NY dataset, hence this value given
year: vehicle[:model_year],
engine: engine_type,
county: vehicle[:county].capitalize #Be consistent between states
}
else
puts "Error: state invalid / not recognized. Cannot build vehicles from registry."
return [] #To be consistent with typical return type
end

@vehicles_manufactured << Vehicle.new(vehicle_data)
end

return @vehicles_manufactured
end

end
Loading