module CarrierInfo module Sources class Safer EQUIPMENT_CARGO_CARRIED = { equipment_vans: ["General Freight", "US Mail", "Paper Products"], equipment_household_vans: ["Household Goods"], equipment_flatbeds: ["Metal: sheets, coils, rolls", "Logs, Poles, Beams, Lumber", "Building Materials", "Construction", "Machinery, Large Objects"], equipment_car_carriers: ["Motor Vehicles"], equipment_tow_trucks: ["Drive/Tow away"], equipment_mobile_homes: ["Mobile Homes"], equipment_reefers: ["Fresh Produce", "Meat", "Refrigerated Food"], equipment_tanks: ["Liquids/Gases", "Chemicals"], equipment_intermodal_containers: ["Intermodal Cont."], equipment_buses: ["Passengers"], equipment_livestock_rack: ["Livestock"], equipment_hopper_bottom: ["Commodities Dry Bulk", "Grain, Feed, Hay", "Agricultural/Farm Supplies"], equipment_coal_trailers: ["Coal/Coke"], equipment_garbage_trucks: ["Garbage/Refuse"], equipment_beverage_trucks: ["Beverages"], equipment_utility_trucks: ["Utilities"], equipment_well_trucks: ["Water Well"] } CLASSIFICATION_LABEL = { classification_authorized_for_hire: "Auth. For Hire", classification_exempt_for_hire: "Exempt For Hire", classification_private_property: "Private(Property)", classification_private_passenger_business: "Priv. Pass. (Business)", classification_private_passenger_non_business: "Priv. Pass.(Non-business)", classification_migrant: "Migrant", classification_us_mail: "U.S. Mail", classification_federal_government: "Fed. Gov't", classification_state_government: "State Gov't", classification_local_government: "Local Gov't", classification_indian_tribe: "Indian Nation", classification_other: "Other" } attr_accessor :content, :carrier def initialize(carrier) @carrier = carrier end def query_param carrier.usdot_number ? "USDOT" : "MC_MX" end def query_string carrier.usdot_number || carrier.docket_number end def request_params params = { query_param: query_param, query_string: query_string, searchtype: 'ANY', query_type: 'queryCarrierSnapshot', button1: 'Search' } end def response @response ||= request_response end def request_response Nokogiri::HTML(Net::HTTP.post_form(URI.parse('http://safer.fmcsa.dot.gov/query.asp'), request_params).body) end def parse! carrier.safer_active = parse_safer_active if carrier.safer_active carrier.usdot_number = parse_usdot_number carrier.docket_prefix = parse_docket_prefix carrier.docket_number = parse_docket_number carrier.legal_name = parse_legal_name carrier.dba_name = parse_dba_name carrier.phone = parse_phone carrier.power_units = parse_power_units carrier.drivers = parse_drivers carrier.duns_number = parse_duns_number carrier.state_id_number = parse_state_id_number carrier.mcs_150_form_date = parse_mcs_150_form_date carrier.mcs_150_mileage = parse_mcs_150_mileage carrier.mcs_150_mileage_year = parse_mcs_150_mileage_year carrier.out_of_service_date = parse_out_of_service_date carrier.operating_status = parse_operating_status carrier.entity_type = parse_entity_type [:physical, :mailing].each do |type| [:street, :city, :state, :postal_code].each do |address| carrier.send("#{type}_#{address}=", parse_address(type, address)) end end carrier.equipment_names.each do |equipment_name| carrier.send("#{equipment_name}=", parse_equipment(equipment_name)) end carrier.interstate = parse_interstate carrier.instrastate_non_hazardous = parse_intrastate_non_hazardous carrier.intrastate_hazardous = parse_intrastate_hazardous carrier.classification_names.each do |classification_name| carrier.send("#{classification_name}=", parse_classification(classification_name)) end carrier.safer_last_updated = parse_safer_last_updated end end def parse_standard_info_field(name, args={}) value = response.xpath("//a[text()='#{name}:']").find do |field| field.attr("class") == "querylabel" end.parent.next_element.text.gsub(/[\u0080-\u00ff]/,"").strip if args[:integer] value.gsub!(/\D/, "") value = value.to_i end value end def parse_legal_name parse_standard_info_field("Legal Name") end def parse_dba_name parse_standard_info_field("DBA Name") end def parse_phone parse_standard_info_field("Phone") end def parse_power_units parse_standard_info_field("Power Units", integer: true) end def parse_drivers parse_standard_info_field("Drivers", integer: true) end def parse_usdot_number parse_standard_info_field("USDOT Number", integer: true) end def parse_docket_prefix docket = parse_standard_info_field("MC or MX Number") docket.split("-")[0] if docket =~ /-/ end def parse_docket_number docket = parse_standard_info_field("MC or MX Number") docket.split("-")[1].to_i if docket =~ /-/ end def parse_duns_number duns = parse_standard_info_field("DUNS Number", integer: true) duns == 0 ? nil : duns end def parse_state_id_number parse_standard_info_field("State Carrier ID Number") end def parse_mcs_150_form_date date_parts = parse_standard_info_field("MCS-150 Form Date").split("/").collect(&:to_i) Date.new(date_parts[2], date_parts[0], date_parts[1]) if date_parts.count == 3 end def parse_mcs_150_mileage parts = parse_standard_info_field("MCS-150 Mileage (Year)").split(" ") parts[0].gsub(/\D/,"").to_i if parts.count == 2 end def parse_mcs_150_mileage_year parts = parse_standard_info_field("MCS-150 Mileage (Year)").split(" ") parts[1].gsub(/\D/,"").to_i if parts.count == 2 end def parse_out_of_service_date parse_standard_info_field("Out of Service Date") # date_parts = parse_standard_info_field("Out of Service Date").split("/").collect(&:to_i) # Date.new(date_parts[2], date_parts[0], date_parts[1]) if date_parts.count == 3 end def parse_safer_active !(response.xpath("//text()='Record Inactive'") || response.xpath("//text()='Record Not Found'")) end def parse_operating_status parse_standard_info_field("Operating Status") end def parse_entity_type parse_standard_info_field("Entity Type") end def parse_address(type, part) address = parse_standard_info_field("#{type.to_s.titleize} Address") send("parse_#{part}", address) end def parse_street(address) address.split("\n")[0].strip end def parse_city(address) address.split("\n")[1].split(",")[0].strip end def parse_state(address) address.split("\n")[1].split(",")[1].strip =~ /\A(\w*)\s*(\S*)\s*\Z/ $1 end def parse_postal_code(address) address.split("\n")[1].split(",")[1].strip =~ /\A(\w*)\s*(\S*)\s*\Z/ $2 end def parse_equipment(equipment_name) !!EQUIPMENT_CARGO_CARRIED[equipment_name].find do |cargo_carried| check_box = response.xpath("//font[text()='#{cargo_carried}']").first.parent.previous_element check_box.text == "X" ? true : false end end def parse_checkbox_field(field_name) if label = response.xpath(%Q(//font[text()="#{field_name}"])).first check_box = label.parent.previous_element check_box.text == "X" ? true : false else false end end def parse_interstate parse_checkbox_field("Interstate") end def parse_intrastate_non_hazardous parse_checkbox_field("Intrastate Only (Non-HM)") end def parse_intrastate_hazardous parse_checkbox_field("Intrastate Only (HM)") end def parse_classification(classification_name) parse_checkbox_field(CLASSIFICATION_LABEL[classification_name]) end def parse_safer_last_updated if last_updated = response.at_xpath("//b[starts-with(.,'The information below reflects the content')]").xpath("./font").text.strip date_parts = last_updated.split("/").collect(&:to_i) Date.new(date_parts[2], date_parts[0], date_parts[1]) if date_parts.count == 3 end end end end end