lib/bright/sis_apis/power_school.rb in bright-0.2.0 vs lib/bright/sis_apis/power_school.rb in bright-1.0

- old
+ new

@@ -1,38 +1,38 @@ module Bright module SisApi class PowerSchool < Base DATE_FORMAT = '%Y-%m-%d' INVALID_SEARCH_CHAR_RE = /[\,\;]/ - + @@description = "Connects to the PowerSchool API for accessing student information" @@doc_url = "http://psimages.sunnysideschools.org/api-developer-guide-1.6.0/" @@api_version = "1.6.0" - + attr_accessor :connection_options, :expansion_options - + def initialize(options = {}) self.connection_options = options[:connection] || {} self.expansion_options = options[:expansion] || {} # { - # :client_id => "", + # :client_id => "", # :client_secret => "", # :uri => "" # :access_token => "", #optional # } end - + def get_student_by_api_id(api_id, params = {}) params = self.apply_expansions(params) st_hsh = self.request(:get, "ws/v1/student/#{api_id}", params) Student.new(convert_to_student_data(st_hsh["student"])) if st_hsh and st_hsh["student"] end - + def get_student(params = {}, options = {}) self.get_students(params, options.merge(:per_page => 1, :wrap_in_collection => false)).first end - + def get_students(params = {}, options = {}) params = self.apply_expansions(params) params = self.apply_options(params, options) if options[:wrap_in_collection] != false @@ -42,36 +42,37 @@ end students_response_hash = self.request(:get, 'ws/v1/district/student', self.map_student_search_params(params)) if students_response_hash and students_response_hash["students"] && students_response_hash["students"]["student"] students_hash = [students_response_hash["students"]["student"]].flatten - + students = students_hash.compact.collect {|st_hsh| Student.new(convert_to_student_data(st_hsh)) } - + if options[:wrap_in_collection] != false api = self load_more_call = proc { |page| # pages start at one, so add a page here - api.get_students(params, {:wrap_in_collection => false, :page => (page + 1)}) + params[:page] = (page + 1) + api.get_students(params, {:wrap_in_collection => false}) } ResponseCollection.new({ - :seed_page => students, + :seed_page => students, :total => total_results, - :per_page => params[:pagesize], + :per_page => params[:pagesize], :load_more_call => load_more_call }) else students end else [] end end - + def create_student(student, additional_params = {}) response = self.request(:post, 'ws/v1/student', self.convert_from_student_data(student, "INSERT", additional_params)) if response["results"] and response["results"]["insert_count"] == 1 student.api_id = response["results"]["result"]["success_message"]["id"] @@ -85,103 +86,105 @@ else puts response.inspect end student end - + def update_student(student, additional_params = {}) response = self.request(:post, 'ws/v1/student', self.convert_from_student_data(student, "UPDATE", additional_params)) if response["results"] and response["results"]["update_count"] == 1 student.api_id = response["results"]["result"]["success_message"]["id"] self.get_student_by_api_id(student.api_id) else puts response.inspect student end end - + def subscribe_student(student) raise NotImplementedError end - + def get_schools(params = {}, options = {}) params = self.apply_options(params, options) if options[:wrap_in_collection] != false schools_count_response_hash = self.request(:get, 'ws/v1/district/school/count', params) # {"resource"=>{"count"=>293}} total_results = schools_count_response_hash["resource"]["count"].to_i if schools_count_response_hash["resource"] end schools_response_hash = self.request(:get, 'ws/v1/district/school', params) - puts schools_response_hash.inspect schools_hsh = [schools_response_hash["schools"]["school"]].flatten - + schools = schools_hsh.compact.collect {|st_hsh| School.new(convert_to_school_data(st_hsh)) } - + if options[:wrap_in_collection] != false api = self load_more_call = proc { |page| # pages start at one, so add a page here - api.get_schools(params, {:wrap_in_collection => false, :page => (page + 1)}) + params[:page] = (page + 1) + api.get_schools(params, {:wrap_in_collection => false}) } ResponseCollection.new({ - :seed_page => schools, + :seed_page => schools, :total => total_results, - :per_page => params[:pagesize], + :per_page => params[:pagesize], :load_more_call => load_more_call }) else schools end end - - def retrive_access_token + + def retrieve_access_token connection = Bright::Connection.new("#{self.connection_options[:uri]}/oauth/access_token/") response = connection.request(:post, "grant_type=client_credentials", self.headers_for_access_token) if !response.error? response_hash = JSON.parse(response.body) end if response_hash["access_token"] self.connection_options[:access_token] = response_hash["access_token"] end response_hash end - + def request(method, path, params = {}) uri = "#{self.connection_options[:uri]}/#{path}" body = nil if method == :get query = URI.encode_www_form(params) uri += "?#{query}" else body = JSON.dump(params) end - - headers = self.headers_for_auth - connection = Bright::Connection.new(uri) - response = connection.request(method, body, headers) - + + response = connection_retry_wrapper { + connection = Bright::Connection.new(uri) + headers = self.headers_for_auth + connection.request(method, body, headers) + } + if !response.error? response_hash = JSON.parse(response.body) else puts "#{response.inspect}" puts "#{response.body}" end response_hash end - + protected - + def map_student_search_params(params) params = params.dup default_params = {} - + q = "" %w(first_name middle_name last_name).each do |f| if fn = params.delete(f.to_sym) fn = fn.gsub(INVALID_SEARCH_CHAR_RE, " ").strip q += %(name.#{f}==#{fn};) @@ -197,27 +200,27 @@ end params[:q] = q default_params.merge(params).reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?} end - + def convert_to_student_data(attrs) cattrs = {} - + if attrs["name"] cattrs[:first_name] = attrs["name"]["first_name"] cattrs[:middle_name] = attrs["name"]["middle_name"] cattrs[:last_name] = attrs["name"]["last_name"] end - + cattrs[:api_id] = attrs["id"].to_s cattrs[:sis_student_id] = attrs["local_id"].to_s - cattrs[:state_student_id] = attrs["state_province_id"].to_s - + cattrs[:state_student_id] = attrs["state_province_id"].to_s + if attrs["demographics"] if attrs["demographics"]["birth_date"] - begin + begin cattrs[:birth_date] = Date.strptime(attrs["demographics"]["birth_date"], DATE_FORMAT) rescue => e puts "#{e.inspect} #{bd}" end end @@ -225,44 +228,75 @@ cattrs[:gender] = attrs["demographics"]["gender"] pg = attrs["demographics"]["projected_graduation_year"].to_i cattrs[:projected_graduation_year] = pg if pg > 0 end - + + #Student Address begin - cattrs[:addresses] = attrs["addresses"].to_a.collect{|a| self.convert_to_address_data(a)} if attrs["addresses"] - rescue - end + cattrs[:addresses] = attrs["addresses"].to_a.collect{|a| self.convert_to_address_data(a)}.select{|a| !a[:street].blank?}.uniq{|a| a[:street]} if attrs["addresses"] + rescue + end + + #Ethnicity / Race Info + if attrs["ethnicity_race"].is_a?(Hash) + if !(race_hshs = attrs.dig("ethnicity_race", "races")).nil? + #this should be an array, but it doesn't appear PS always sends it as one + cattrs[:race] = [race_hshs].flatten.map{|race_hsh| race_hsh["district_race_code"]}.compact.uniq + end + + if !attrs.dig("ethnicity_race", "federal_ethnicity").nil? + cattrs[:hispanic_ethnicity] = attrs.dig("ethnicity_race", "federal_ethnicity").to_bool + end + end + + #Contacts Info + [1,2].each do |contact_id| + if !attrs.dig("contact", "emergency_contact_name#{contact_id}").blank? and !attrs.dig("contact", "emergency_phone#{contact_id}").blank? + cattrs[:contacts] ||= [] + contact_attrs = { + :first_name => attrs.dig("contact", "emergency_contact_name#{contact_id}").to_s.split(",").last.strip, + :last_name => attrs.dig("contact", "emergency_contact_name#{contact_id}").to_s.split(",").first.strip, + :phone_numbers => [ + { + :phone_number => attrs.dig("contact", "emergency_phone#{contact_id}") + } + ] + } + cattrs[:contacts] << contact_attrs + end + end + cattrs.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?} end - + def convert_from_student_data(student, action = nil, additional_params = {}) return {} if student.nil? - + student_data = { :client_uid => student.client_id, :action => action, :id => student.api_id, :local_id => student.sis_student_id, :state_province_id => student.state_student_id, :name => { - :first_name => student.first_name, + :first_name => student.first_name, :middle_name => student.middle_name, :last_name => student.last_name }.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}, :demographics => { :gender => student.gender.to_s[0].to_s.upcase, :birth_date => (student.birth_date ? student.birth_date.strftime(DATE_FORMAT) : nil), :projected_graduation_year => student.projected_graduation_year }.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?} }.merge(additional_params).reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?} - + # apply enrollment info if student.enrollment - student_data.merge!(self.convert_from_enrollment_data(student.enrollment)) + student_data.merge!(self.convert_from_enrollment_data(student.enrollment)) end - + # apply addresses address_data = {} if ph = student.addresses.detect{|a| a.type == "physical"} address_data.merge!(self.convert_from_address_data(ph)) end @@ -273,16 +307,16 @@ cany = any.clone cany.type = "physical" address_data.merge!(self.convert_from_address_data(cany)) end if address_data.size > 0 - student_data.merge!({:addresses => address_data}) + student_data.merge!({:addresses => address_data}) end - + {:students => {:student => student_data}} end - + def convert_from_enrollment_data(enrollment) return {} if enrollment.nil? {:school_enrollment => { :enroll_status => "A", :entry_date => (enrollment.entry_date || Date.today).strftime(DATE_FORMAT), @@ -292,21 +326,28 @@ :grade_level => enrollment.grade, :school_number => enrollment.school ? enrollment.school.number : nil }.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?} } end - + def convert_to_school_data(attrs) cattrs = {} - cattrs[:api_id] = attrs["id"] cattrs[:name] = attrs["name"] cattrs[:number] = attrs["school_number"] - + cattrs[:low_grade] = attrs["low_grade"] + cattrs[:high_grade] = attrs["high_grade"] + if (address_attributes = attrs.dig("addresses")) + cattrs[:address] = convert_to_address_data(address_attributes) + end + if (phone_number_attributes = attrs.dig("phones", "main", "number")) + cattrs[:phone_number] = {:phone_number => phone_number_attributes} + end + cattrs.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?} end - + def convert_from_address_data(address) { (address.type || "physcial") => { :street => "#{address.street} #{address.apt}", # powerschool doesn't appear to support passing the apt in the api :city => address.city, @@ -314,14 +355,14 @@ :postal_code => address.postal_code, :grid_location => address.geographical_coordinates.to_s.gsub(",", ", ") # make sure there is a comma + space }.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?} } end - + def convert_to_address_data(attrs) cattrs = {} - + if attrs.is_a?(Array) if attrs.first.is_a?(String) cattrs[:type] = attrs.first attrs = attrs.last else @@ -335,16 +376,16 @@ cattrs[:street] = attrs["street"] cattrs[:city] = attrs["city"] cattrs[:state] = attrs["state_province"] cattrs[:postal_code] = attrs["postal_code"] if attrs["grid_location"] and lat_lng = attrs["grid_location"].split(/,\s?/) - cattrs[:lattitude], cattrs[:longitude] = lat_lng + cattrs[:latitude], cattrs[:longitude] = lat_lng end - + cattrs.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?} end - + def apply_expansions(params) if self.expansion_options.empty? hsh = self.request(:get, 'ws/v1/district/student', {:pagesize => 1, :q => "local_id==0"}) if hsh and hsh["students"] self.expansion_options = { @@ -357,30 +398,30 @@ params.merge({ :expansions => (%w(demographics addresses ethnicity_race phones contact contact_info) & (self.expansion_options[:expansions] || [])).join(","), :extensions => (%w(studentcorefields) & (self.expansion_options[:extensions] || [])).join(",") }.reject{|k,v| v.empty?}) end - + def apply_options(params, options) options[:per_page] = params[:pagesize] ||= params.delete(:per_page) || options[:per_page] || 100 params[:page] ||= options[:page] || 1 params end - + def headers_for_access_token { "Authorization" => "Basic #{Base64.strict_encode64("#{self.connection_options[:client_id]}:#{self.connection_options[:client_secret]}")}", "Content-Type" => "application/x-www-form-urlencoded;charset=UTF-8" } end - + def headers_for_auth - self.retrive_access_token if self.connection_options[:access_token].nil? + self.retrieve_access_token if self.connection_options[:access_token].nil? { "Authorization" => "Bearer #{self.connection_options[:access_token]}", "Accept" => "application/json;charset=UTF-8", "Content-Type" =>"application/json;charset=UTF-8" } end end end -end \ No newline at end of file +end