require 'restclient' require_relative 'resource_config' module Medivo class Appointment LABCORP_FORMAT = "%m/%d/%Y|%H:%M %p" unless defined? LABCORP_FORMAT MDY_FORMAT = "%m/%d/%Y" unless defined? MDY_FORMAT class << self ## # Find a labcorp appointment # # @param lab_code lab code # @param date date to seek # @param am_pm optional time of day value ( 'AM' or 'PM' ) # # @return Array of times ['time1', 'time2'] # # @throws exception mainly Restclient::Exception types def find(lab_code, date, am_pm='') if real_data? data = resource.get params: {labcorp_id: lab_code, appointment_date: date.strftime("%m/%d/%Y")} # data is json encoded hash: { :times => ['time1', 'time2'] } data = JSON.parse(data)['times'] else data = build_fake_data(date) end # filter the hash before returning filter_data(data, am_pm, date) end ## # Make the labcorp appointment # # @param lab_code lab code # @param time time # @param user user info must include date_of_birth, first_name, last_name, # email_address, phone_number # # @return String confirmation number or nil if none made # # @throws exception mainly Restclient::Exception types def make(lab_code, time, user) time_id = time.strftime(LABCORP_FORMAT) if real_data? data = resource.post labcorp_id: lab_code, time_id: time_id, user: user # data is json { :confimation => ( 'real number' or nil if no appointment made ) } JSON.parse(data)['confirmation'] else nil end end ## # Cancel the labcorp appointment # # @param confirmation confirmation number # @param user user info must include first_name, last_name, # # @return boolean true if success # # @throws exception mainly Restclient::Exception types def cancel(confirmation, first_name, last_name) data = resource.delete params: { confirmation: confirmation, first_name: first_name, last_name: last_name } JSON.parse(data)['success'] end def resource @resource ||= begin @config = ResourceConfig.find 'appointment_resource.yml' RestClient::Resource.new @config.href, :timeout => (@config.timeout || 12) rescue => e Rails.logger.error e.inspect # blow up later, so the server can start end end ## using real data? # the flag is set in the config/appointment_resource.yml def real_data? resource # to init the resource and config file true unless !Rails.env.test? and @config.real_data == false end ## # ordering the times, firstly by proximity to search date, and # then by the time in the day def filter_data(times, am_pm, search_date) dates = times .collect { |time| AppointmentTime.new(time) } # wrap times in model .select { |at| at.match(am_pm, Time.now) } # filter out times .group_by(&:date) # get the times into groups by date # order the dates by nearness to search date keys = dates.keys.sort_by {|date| (date - search_date).to_i.abs } keys .collect{|date| dates[date].sort_by(&:time) } # order the times in each day .flatten .collect { |at| at.time_str } # return the times as a strings end def build_fake_data(date) date = date.is_a?(String) ? Date.strptime(date, MDY_FORMAT) : date [ build_date(date, "08:30 AM"), build_date(date, "10:30 AM"), build_date((date + 1), "03:30 PM"), build_date((date+1), "01:30 PM"), build_date((date + 2), "10:30 AM"), build_date((date+1), "03:00 PM"), ] end def build_date(date, time) "#{date.strftime(MDY_FORMAT)}|#{time}" end end end end class AppointmentTime attr_reader :time, :time_str def initialize(time_str) @time_str = time_str @time = Time.strptime(time_str, Medivo::Appointment::LABCORP_FORMAT) rescue nil end def date @time.to_date end # filter out nil times or by am_pm or past times def match(am_pm, current_time) time and time_str.match(/#{am_pm}/) and (time >= current_time) end def nearness_to_time(other_time) (time - other_time).to_i.abs end end