Add admin data for 4D. Finishes #182818489 and #182793402
parent
1e349519bc
commit
048aacd641
@ -0,0 +1,74 @@
|
||||
require 'csv'
|
||||
|
||||
module Dese
|
||||
class FourDLoader
|
||||
def self.load_data(filepath:)
|
||||
CSV.parse(File.read(filepath), headers: true) do |row|
|
||||
score = likert_score(row:)
|
||||
unless valid_likert_score(likert_score: score)
|
||||
puts "Invalid score: #{score}
|
||||
for school: #{School.find_by_dese_id(row['DESE ID']).name}
|
||||
admin data item #{admin_data_item(row:)} "
|
||||
next
|
||||
end
|
||||
create_admin_data_value(row:, score:)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.valid_likert_score(likert_score:)
|
||||
likert_score >= 1 && likert_score <= 5
|
||||
end
|
||||
|
||||
def self.likert_score(row:)
|
||||
likert_score = (row['Likert Score'] || row['LikertScore'] || row['Likert_Score']).to_f
|
||||
round_up_to_one(likert_score:)
|
||||
end
|
||||
|
||||
def self.round_up_to_one(likert_score:)
|
||||
likert_score = 1 if likert_score.positive? && likert_score < 1
|
||||
likert_score
|
||||
end
|
||||
|
||||
def self.ay(row:)
|
||||
row['Academic Year'] || row['AcademicYear']
|
||||
end
|
||||
|
||||
def self.dese_id(row:)
|
||||
row['DESE ID'] || row['Dese ID'] || row['Dese Id']
|
||||
end
|
||||
|
||||
def self.admin_data_item(row:)
|
||||
row['Admin Data Item'] || row['Item ID'] || row['Item Id']
|
||||
end
|
||||
|
||||
def self.create_admin_data_value(row:, score:)
|
||||
school = School.find_by_dese_id(dese_id(row:).to_i)
|
||||
return if school.nil?
|
||||
|
||||
admin_data_value = AdminDataValue.find_by(academic_year: AcademicYear.find_by_range(ay(row:)),
|
||||
school:,
|
||||
admin_data_item: AdminDataItem.find_by_admin_data_item_id(admin_data_item(row:)))
|
||||
if admin_data_value.present?
|
||||
admin_data_value.likert_score = score
|
||||
admin_data_value.save
|
||||
else
|
||||
AdminDataValue.create!(
|
||||
likert_score: score,
|
||||
academic_year: AcademicYear.find_by_range(ay(row:)),
|
||||
school:,
|
||||
admin_data_item: AdminDataItem.find_by_admin_data_item_id(admin_data_item(row:))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private_class_method :valid_likert_score
|
||||
private_class_method :likert_score
|
||||
private_class_method :round_up_to_one
|
||||
private_class_method :ay
|
||||
private_class_method :dese_id
|
||||
private_class_method :admin_data_item
|
||||
private_class_method :create_admin_data_value
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,65 @@
|
||||
require 'watir'
|
||||
require 'csv'
|
||||
|
||||
module Dese
|
||||
class FourDScraper
|
||||
def initialize(filepath: Rails.root.join('data', 'admin_data', 'dese', 'four_d.csv'))
|
||||
url = 'https://profiles.doe.mass.edu/statereport/plansofhsgrads.aspx'
|
||||
browser = Watir::Browser.new
|
||||
write_headers(filepath:)
|
||||
academic_years = AcademicYear.all
|
||||
academic_years.each do |academic_year|
|
||||
table = scrape(browser:, url:, range: academic_year.range)
|
||||
id = 'a-cgpr-i1'
|
||||
write_csv(table:, filepath:, range: academic_year.range, id:) unless table.nil?
|
||||
end
|
||||
browser.close
|
||||
end
|
||||
|
||||
def scrape(browser:, url:, range:)
|
||||
browser.goto(url)
|
||||
|
||||
return unless browser.option(text: range).present?
|
||||
|
||||
browser.select(id: 'ctl00_ContentPlaceHolder1_ddReportType').select(/School/)
|
||||
browser.select(id: 'ctl00_ContentPlaceHolder1_ddYear').select(text: range)
|
||||
browser.button(id: 'btnViewReport').click
|
||||
sleep 5 # Sleep to prevent hitting mass.edu with too many requests
|
||||
document = Nokogiri::HTML(browser.html)
|
||||
document.css('tr')
|
||||
end
|
||||
|
||||
def write_headers(filepath:)
|
||||
CSV.open(filepath, 'w') do |csv|
|
||||
headers = ['School Name', 'DESE ID', '4 Year Private College', '4 Year Public College', '2 Year Private College', '2 Year Public College',
|
||||
'Other Post Secondary', 'Apprenticeship', 'Work', 'Military', 'Other', 'Unknown', 'Total', 'Raw likert calculation', 'Likert Score', 'Admin Data Item', 'Academic Year']
|
||||
csv << headers
|
||||
end
|
||||
end
|
||||
|
||||
def write_csv(table:, filepath:, range:, id:)
|
||||
CSV.open(filepath, 'a') do |csv|
|
||||
table.each do |row|
|
||||
items = row.css('td').map(&:text)
|
||||
dese_id = items[1].to_i
|
||||
next if dese_id.nil? || dese_id.zero?
|
||||
|
||||
raw_likert_score = calculate(cells: items)
|
||||
items << raw_likert_score
|
||||
likert_score = raw_likert_score
|
||||
likert_score = 5 if raw_likert_score > 5
|
||||
likert_score = 1 if raw_likert_score < 1
|
||||
likert_score = likert_score.round(2)
|
||||
items << likert_score
|
||||
items << id
|
||||
items << range
|
||||
csv << items
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def calculate(cells:)
|
||||
(cells[2].to_f + cells[3].to_f + cells[4].to_f + cells[5].to_f + cells[6].to_f + cells[7].to_f + cells[8].to_f) * 4 / 75
|
||||
end
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,50 @@
|
||||
require 'rails_helper'
|
||||
RSpec.describe Dese::FourDLoader, type: :model do
|
||||
let(:path_to_admin_data) { Rails.root.join('spec', 'fixtures', 'sample_four_d_data.csv') }
|
||||
let(:ay_2020_21) { AcademicYear.find_by_range '2020-21' }
|
||||
let(:ay_2018_19) { AcademicYear.find_by_range '2018-19' }
|
||||
let(:ay_2017_18) { AcademicYear.find_by_range '2017-18' }
|
||||
let(:ay_2016_17) { AcademicYear.find_by_range '2016-17' }
|
||||
let(:four_d) { AdminDataItem.find_by_admin_data_item_id 'a-cgpr-i1' }
|
||||
let(:attleboro) { School.find_by_dese_id 160_505 }
|
||||
let(:winchester) { School.find_by_dese_id 3_440_505 }
|
||||
let(:milford) { School.find_by_dese_id 1_850_505 }
|
||||
let(:seacoast) { School.find_by_dese_id 2_480_520 }
|
||||
let(:next_wave) { School.find_by_dese_id 2_740_510 }
|
||||
|
||||
before :each do
|
||||
Rails.application.load_seed
|
||||
end
|
||||
|
||||
after :each do
|
||||
DatabaseCleaner.clean
|
||||
end
|
||||
context 'when running the loader' do
|
||||
before :each do
|
||||
Dese::FourDLoader.load_data filepath: path_to_admin_data
|
||||
end
|
||||
|
||||
it 'load the correct admin data values' do
|
||||
expect(AdminDataValue.find_by(school: winchester, admin_data_item: four_d,
|
||||
academic_year: ay_2016_17).likert_score).to eq 5
|
||||
expect(AdminDataValue.find_by(school: attleboro, admin_data_item: four_d,
|
||||
academic_year: ay_2018_19).likert_score).to eq 5
|
||||
expect(AdminDataValue.find_by(school: milford, admin_data_item: four_d,
|
||||
academic_year: ay_2017_18).likert_score).to eq 4.92
|
||||
expect(AdminDataValue.find_by(school: seacoast, admin_data_item: four_d,
|
||||
academic_year: ay_2020_21).likert_score).to eq 3.84
|
||||
expect(AdminDataValue.find_by(school: next_wave, admin_data_item: four_d,
|
||||
academic_year: ay_2020_21).likert_score).to eq 4.8
|
||||
end
|
||||
|
||||
it 'loads the correct number of items' do
|
||||
expect(AdminDataValue.count).to eq 230
|
||||
end
|
||||
|
||||
it 'is idempotent' do
|
||||
Dese::FourDLoader.load_data filepath: path_to_admin_data
|
||||
|
||||
expect(AdminDataValue.count).to eq 230
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,24 @@
|
||||
require 'rails_helper'
|
||||
require 'fileutils'
|
||||
RSpec.describe type: :model do
|
||||
let(:academic_years) do
|
||||
[
|
||||
create(:academic_year, range: '2020-21'),
|
||||
create(:academic_year, range: '2019-20'),
|
||||
create(:academic_year, range: '2018-19'),
|
||||
create(:academic_year, range: '2017-18'),
|
||||
create(:academic_year, range: '2016-17')
|
||||
]
|
||||
end
|
||||
before :each do
|
||||
academic_years
|
||||
end
|
||||
xcontext 'Creating a new FourDScraper' do
|
||||
it 'creates a csv file with the scraped data' do
|
||||
FileUtils.mkdir_p 'tmp/spec/dese'
|
||||
file = Rails.root.join('tmp', 'spec', 'dese', 'four_d.csv')
|
||||
Dese::FourDScraper.new(filepath: file)
|
||||
expect(file).to exist
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in new issue