academic_benchmarks 0.0.11 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3ecf3026a1b108596fe8a1d59915d6c3dff504e0cc1556bd9072e0cbfbf5882
4
- data.tar.gz: 3177ee2135a7b5a005de63c40ef5854c40b8e4a7fea73d57ce40e21647491171
3
+ metadata.gz: '05582989c51ab88e71317ed23866a5dd9c34a956b96791faa86f775c8c09e227'
4
+ data.tar.gz: c5a713f96b930b2b914674fd57390dd68d0cc8fc9269e3b3503514637d07d746
5
5
  SHA512:
6
- metadata.gz: 02b49c5ce790e0a0f4555800fd3b59e812c8baa56cac1ca7476273be0c70ae6e82ca02a73df8d908bb132a39f2b758f3e03bd1ac9279fa9a522fdf8ae7e2771d
7
- data.tar.gz: a9e5de63ed5d1dc3e885212d38a97bc4a8874c8fbb6834c98708baae639ceddf08ed3ec419428e6c9b67eaf805a093952e71f51517e88c8fb589e40c7e5e00c9
6
+ metadata.gz: a1cf494c1bde537ad7c8f6f6755ad71196b8946b2087479cefe4c9d5c0fee7c7f4657c14853fd8d26b01c2cd864ed8bb99e931404bd1b7c12ee1d8786b30f087
7
+ data.tar.gz: bb5bfe960f0182139852c1bc27ccda0cce0ae150e81aa8682c1090390d0412b6326f0000a587d948acee8d05363a9233b5466d7c9686cf27ce6fb7e8ecf66ad0
@@ -8,10 +8,12 @@ module AcademicBenchmarks
8
8
  "partner.id" => partner_id,
9
9
  "auth.signature" => signature_for(
10
10
  partner_key: partner_key,
11
- message: self.message(expires: expires, user_id: user_id)),
12
- "auth.expires" => expires,
13
- "user.id" => user_id
14
- }
11
+ message: self.message(expires: expires, user_id: user_id)
12
+ ),
13
+ "auth.expires" => expires
14
+ }.tap do |params|
15
+ params["user.id"] = user_id unless user_id.empty?
16
+ end
15
17
  end
16
18
 
17
19
  def self.signature_for(partner_key:, message:)
@@ -2,11 +2,11 @@ module AcademicBenchmarks
2
2
  module Api
3
3
  module Constants
4
4
  def self.base_url
5
- 'https://api.academicbenchmarks.com/rest/v3'
5
+ 'https://api.abconnect.certicaconnect.com/rest/v4.1'
6
6
  end
7
7
 
8
8
  def self.api_version
9
- '3'
9
+ '4.1'
10
10
  end
11
11
 
12
12
  def self.partner_id_env_var
@@ -20,24 +20,6 @@ module AcademicBenchmarks
20
20
  def self.user_id_env_var
21
21
  'ACADEMIC_BENCHMARKS_USER_ID'
22
22
  end
23
-
24
- def self.standards_search_params
25
- %w[
26
- query
27
- authority
28
- subject
29
- grade
30
- subject_doc
31
- course
32
- document
33
- parent
34
- deepest
35
- limit
36
- offset
37
- list
38
- fields
39
- ]
40
- end
41
23
  end
42
24
  end
43
25
  end
@@ -44,30 +44,10 @@ module AcademicBenchmarks
44
44
  @user_id = user_id.to_s
45
45
  end
46
46
 
47
- def related(guid:, fields: [])
48
- raise StandardError.new("Sorry, not implemented yet!")
49
- end
50
-
51
47
  def standards
52
48
  Standards.new(self)
53
49
  end
54
50
 
55
- def assets
56
- raise StandardError.new("Sorry, not implemented yet!")
57
- end
58
-
59
- def alignments
60
- raise StandardError.new("Sorry, not implemented yet!")
61
- end
62
-
63
- def topics
64
- raise StandardError.new("Sorry, not implemented yet!")
65
- end
66
-
67
- def special
68
- raise StandardError.new("Sorry, not implemented yet!")
69
- end
70
-
71
51
  private
72
52
 
73
53
  def api_resp_to_array_of_standards(api_resp)
@@ -8,92 +8,73 @@ module AcademicBenchmarks
8
8
  class Standards
9
9
  DEFAULT_PER_PAGE = 100
10
10
 
11
+ STANDARDS_FIELDS = %w[
12
+ guid
13
+ education_levels.grades.code
14
+ label
15
+ level
16
+ section.guid
17
+ section.descr
18
+ number.raw
19
+ number.enhanced
20
+ status
21
+ disciplines.subjects.code
22
+ document.guid
23
+ document.descr
24
+ document.adopt_year
25
+ document.publication.descr
26
+ document.publication.guid
27
+ document.publication.authorities
28
+ statement.descr
29
+ parent
30
+ ]
31
+
11
32
  def initialize(handle)
12
33
  @handle = handle
13
34
  end
14
35
 
15
- def search(opts = {})
16
- # query: "", authority: "", subject: "", grade: "", subject_doc: "", course: "",
17
- # document: "", parent: "", deepest: "", limit: -1, offset: -1, list: "", fields: []
18
- invalid_params = invalid_search_params(opts)
19
- if invalid_params.empty?
20
- raw_search(opts).map do |standard|
21
- AcademicBenchmarks::Standards::Standard.new(standard)
22
- end
23
- else
24
- raise ArgumentError.new(
25
- "Invalid search params: #{invalid_params.join(', ')}"
26
- )
36
+ # TODO: in the future, support OData filtering for flexible querying
37
+ def search(authority_guid: nil, publication_guid: nil)
38
+ raw_search(authority: authority_guid, publication: publication_guid).map do |standard|
39
+ AcademicBenchmarks::Standards::Standard.new(standard)
27
40
  end
28
41
  end
29
42
 
30
- alias_method :where, :search
31
-
32
- def guid(guid, fields: [])
33
- query_params = if fields.empty?
34
- auth_query_params
35
- else
36
- auth_query_params.merge({
37
- fields: fields.join(",")
38
- })
39
- end
40
- @handle.class.get(
41
- "/standards/#{guid}",
42
- query: query_params
43
- ).parsed_response["resources"].map do |r|
44
- AcademicBenchmarks::Standards::Standard.new(r["data"])
43
+ def authorities
44
+ raw_facet("document.publication.authorities").map do |a|
45
+ AcademicBenchmarks::Standards::Authority.from_hash(a["data"])
45
46
  end
46
47
  end
47
48
 
48
- def all
49
- request_search_pages_and_concat_resources(auth_query_params)
50
- end
51
-
52
- def authorities(query_params = {})
53
- raw_search({list: "authority"}.merge(query_params)).map do |a|
54
- AcademicBenchmarks::Standards::Authority.from_hash(a["data"]["authority"])
49
+ def publications(authority_guid: nil)
50
+ raw_facet("document.publication", authority: authority_guid).map do |a|
51
+ AcademicBenchmarks::Standards::Publication.from_hash(a["data"])
55
52
  end
56
53
  end
57
54
 
58
- def documents(query_params = {})
59
- raw_search({list: "document"}.merge(query_params)).map do |a|
60
- AcademicBenchmarks::Standards::Document.from_hash(a["data"]["document"])
61
- end
62
- end
63
-
64
- def authority_documents(authority_or_auth_code_guid_or_desc)
55
+ def authority_publications(authority_or_auth_code_guid_or_desc)
65
56
  authority = auth_from_code_guid_or_desc(authority_or_auth_code_guid_or_desc)
66
- documents(authority: authority.code)
57
+ publications(authority_guid: authority.guid)
67
58
  end
68
59
 
69
60
  def authority_tree(authority_or_auth_code_guid_or_desc, include_obsolete_standards: true)
70
61
  authority = auth_from_code_guid_or_desc(authority_or_auth_code_guid_or_desc)
71
- auth_children = search(authority: authority.code)
62
+ auth_children = raw_search(authority: authority.guid, include_obsoletes: include_obsolete_standards)
72
63
  AcademicBenchmarks::Standards::StandardsForest.new(
73
- auth_children,
74
- include_obsoletes: include_obsolete_standards
64
+ auth_children
75
65
  ).consolidate_under_root(authority)
76
66
  end
77
67
 
78
- def document_tree(document_or_guid, include_obsolete_standards: true)
79
- document = doc_from_guid(document_or_guid)
80
- doc_children = search(document: document.guid)
68
+ def publication_tree(publication_or_pub_code_guid_or_desc, include_obsolete_standards: true)
69
+ publication = pub_from_guid(publication_or_pub_code_guid_or_desc)
70
+ pub_children = raw_search(publication: publication.guid, include_obsoletes: include_obsolete_standards)
81
71
  AcademicBenchmarks::Standards::StandardsForest.new(
82
- doc_children,
83
- include_obsoletes: include_obsolete_standards
84
- ).consolidate_under_root(document)
72
+ pub_children
73
+ ).consolidate_under_root(publication)
85
74
  end
86
75
 
87
76
  private
88
77
 
89
- def doc_from_guid(document_or_guid)
90
- if document_or_guid.is_a?(AcademicBenchmarks::Standards::Document)
91
- document_or_guid
92
- else
93
- find_type(type: "document", data: document_or_guid)
94
- end
95
- end
96
-
97
78
  def auth_from_code_guid_or_desc(authority_or_auth_code_guid_or_desc)
98
79
  if authority_or_auth_code_guid_or_desc.is_a?(AcademicBenchmarks::Standards::Authority)
99
80
  authority_or_auth_code_guid_or_desc
@@ -102,6 +83,14 @@ module AcademicBenchmarks
102
83
  end
103
84
  end
104
85
 
86
+ def pub_from_guid(publication_or_pub_code_guid_or_desc)
87
+ if publication_or_pub_code_guid_or_desc.is_a?(AcademicBenchmarks::Standards::Publication)
88
+ publication_or_pub_code_guid_or_desc
89
+ else
90
+ find_type(type: "publication", data: publication_or_pub_code_guid_or_desc)
91
+ end
92
+ end
93
+
105
94
  def find_type(type:, data:)
106
95
  matches = send("match_#{type}", data)
107
96
  if matches.empty?
@@ -110,7 +99,7 @@ module AcademicBenchmarks
110
99
  )
111
100
  elsif matches.count > 1
112
101
  raise StandardError.new(
113
- "Authority code, guid, or description matched more than one authority. " \
102
+ "#{type.upcase} code, guid, or description matched more than one #{type}. " \
114
103
  "matched '#{matches.map(&:to_json).join('; ')}'"
115
104
  )
116
105
  end
@@ -119,22 +108,26 @@ module AcademicBenchmarks
119
108
 
120
109
  def match_authority(data)
121
110
  authorities.select do |auth|
122
- auth.code == data ||
111
+ auth.acronym == data ||
123
112
  auth.guid == data ||
124
113
  auth.descr == data
125
114
  end
126
115
  end
127
116
 
128
- def match_document(data)
129
- documents.select { |doc| doc.guid == data }
117
+ def match_publication(data)
118
+ publications.select do |pub|
119
+ pub.acronym == data ||
120
+ pub.guid == data ||
121
+ pub.descr == data
122
+ end
130
123
  end
131
124
 
132
- def raw_search(opts = {})
133
- request_search_pages_and_concat_resources(opts.merge(auth_query_params))
125
+ def raw_facet(facet, query_params = {})
126
+ request_facet({facet: facet}.merge(query_params).merge(auth_query_params))
134
127
  end
135
128
 
136
- def invalid_search_params(opts)
137
- opts.keys.map(&:to_s) - AcademicBenchmarks::Api::Constants.standards_search_params
129
+ def raw_search(opts = {})
130
+ request_search_pages_and_concat_resources(opts.merge(auth_query_params))
138
131
  end
139
132
 
140
133
  def auth_query_params
@@ -146,7 +139,41 @@ module AcademicBenchmarks
146
139
  )
147
140
  end
148
141
 
142
+ def odata_filters(query_params)
143
+ if query_params.key? :authority
144
+ value = query_params.delete :authority
145
+ query_params['filter[standards]'] = "document.publication.authorities.guid eq '#{value}'" if value
146
+ end
147
+ if query_params.key? :publication
148
+ value = query_params.delete :publication
149
+ query_params['filter[standards]'] = "document.publication.guid eq '#{value}'" if value
150
+ end
151
+
152
+ if query_params.key? :include_obsoletes
153
+ unless query_params.delete :include_obsoletes
154
+ if query_params.key? 'filter[standards]'
155
+ query_params['filter[standards]'] += " and status eq 'active'"
156
+ else
157
+ query_params['filter[standards]'] = "status eq 'active'"
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ def request_facet(query_params)
164
+ odata_filters query_params
165
+ page = request_page(
166
+ query_params: query_params,
167
+ limit: 0, # return no standards since facets are separate
168
+ offset: 0
169
+ ).parsed_response
170
+
171
+ page.dig("meta", "facets", 0, "details")
172
+ end
173
+
149
174
  def request_search_pages_and_concat_resources(query_params)
175
+ query_params['fields[standards]'] = STANDARDS_FIELDS.join(',')
176
+ odata_filters query_params
150
177
  query_params.reverse_merge!({limit: DEFAULT_PER_PAGE})
151
178
 
152
179
  if !query_params[:limit] || query_params[:limit] <= 0
@@ -161,10 +188,9 @@ module AcademicBenchmarks
161
188
  offset: 0
162
189
  ).parsed_response
163
190
 
164
- resources = first_page["resources"]
165
- count = first_page["count"]
191
+ resources = first_page["data"]
192
+ count = first_page["meta"]["count"]
166
193
  offset = query_params[:limit]
167
-
168
194
  while offset < count
169
195
  page = request_page(
170
196
  query_params: query_params,
@@ -172,7 +198,7 @@ module AcademicBenchmarks
172
198
  offset: offset
173
199
  )
174
200
  offset += query_params[:limit]
175
- resources.push(page.parsed_response["resources"])
201
+ resources.push(page.parsed_response["data"])
176
202
  end
177
203
 
178
204
  resources.flatten
@@ -183,19 +209,29 @@ module AcademicBenchmarks
183
209
  limit: limit,
184
210
  offset: offset,
185
211
  })
186
- resp = @handle.class.get(
187
- '/standards',
188
- query: query_params.merge({
189
- limit: limit,
190
- offset: offset,
191
- })
192
- )
193
- if resp.code != 200
194
- raise RuntimeError.new(
195
- "Received response '#{resp.code}: #{resp.message}' requesting standards from Academic Benchmarks:"
212
+ 1.times do
213
+ resp = @handle.class.get(
214
+ '/standards',
215
+ query: query_params.merge({
216
+ limit: limit,
217
+ offset: offset,
218
+ })
196
219
  )
220
+ if resp.code == 429
221
+ sleep retry_after(resp)
222
+ redo
223
+ end
224
+ if resp.code != 200
225
+ raise RuntimeError.new(
226
+ "Received response '#{resp.code}: #{resp.message}' requesting standards from Academic Benchmarks:"
227
+ )
228
+ end
229
+ return resp
197
230
  end
198
- resp
231
+ end
232
+
233
+ def retry_after(response)
234
+ ENV['ACADEMIC_BENCHMARKS_TOO_MANY_REQUESTS_RETRY']&.to_f || 5
199
235
  end
200
236
  end
201
237
  end
@@ -0,0 +1,7 @@
1
+ module AttrToVals
2
+ def attr_to_vals(klass, arr)
3
+ return [] if arr.nil?
4
+
5
+ arr.map {|v| klass.from_hash(v)}
6
+ end
7
+ end
@@ -19,6 +19,8 @@ module InstVarsToHash
19
19
  def to_h(omit_parent: true, omit_empty_children: true)
20
20
  retval = {}
21
21
  instance_variables.each do |iv|
22
+ # Don't hashify these attributes, otherwise it can lead to infinite recursion
23
+ next if %w[@authority @document @publication @section].include? iv.to_s
22
24
  if !(skip_parent?(omit_parent, iv) || skip_children?(omit_empty_children, iv))
23
25
  retval[iv.to_s.delete('@').to_sym] = elem_to_h(instance_variable_get(iv))
24
26
  end
@@ -1,30 +1,56 @@
1
1
  require 'academic_benchmarks/lib/inst_vars_to_hash'
2
- require 'academic_benchmarks/lib/remove_obsolete_children'
3
2
 
4
3
  module AcademicBenchmarks
5
4
  module Standards
6
5
  class Authority
7
6
  include InstVarsToHash
8
- include RemoveObsoleteChildren
9
7
 
10
- attr_accessor :code, :guid, :description, :children
8
+ attr_accessor :acronym, :descr, :guid, :children
11
9
 
12
- alias_method :descr, :description
10
+ alias_method :code, :acronym
11
+ alias_method :description, :descr
13
12
 
14
13
  def self.from_hash(hash)
15
14
  self.new(
16
- code: hash["code"],
15
+ acronym: hash["acronym"],
17
16
  guid: hash["guid"],
18
- description: (hash["descr"] || hash["description"])
17
+ descr: hash["descr"]
19
18
  )
20
19
  end
21
20
 
22
- def initialize(code:, guid:, description:, children: [])
23
- @code = code
21
+ def initialize(acronym:, guid:, descr:, children: [])
22
+ @acronym = acronym
24
23
  @guid = guid
25
- @description = description
24
+ @descr = descr
26
25
  @children = children
27
26
  end
27
+
28
+ # Children are standards, so rebranch them so we have
29
+ # the following structure:
30
+ #
31
+ # Authority -> Publication -> Document -> Section -> Standard
32
+ def rebranch_children
33
+ @seen = Set.new()
34
+ @guid_to_obj = {}
35
+ new_children = []
36
+ @children.each do |child|
37
+ pub = reparent(child.document.publication, new_children)
38
+ doc = reparent(child.document, pub.children)
39
+ sec = reparent(child.section, doc.children)
40
+ sec.children.push(child)
41
+ end
42
+ @children.replace(new_children)
43
+ remove_instance_variable('@seen')
44
+ remove_instance_variable('@guid_to_obj')
45
+ end
46
+
47
+ private
48
+
49
+ def reparent(object, children)
50
+ cached_object = (@guid_to_obj[object.guid] ||= object)
51
+ children.push(cached_object) if @seen.add? cached_object.guid
52
+ cached_object
53
+ end
28
54
  end
29
55
  end
30
56
  end
@@ -0,0 +1,21 @@
1
+ require 'academic_benchmarks/lib/attr_to_vals'
2
+ require 'academic_benchmarks/lib/inst_vars_to_hash'
3
+
4
+ module AcademicBenchmarks
5
+ module Standards
6
+ class Disciplines
7
+ include AttrToVals
8
+ include InstVarsToHash
9
+
10
+ attr_accessor :subjects
11
+
12
+ def self.from_hash(hash)
13
+ self.new(subjects: hash["subjects"])
14
+ end
15
+
16
+ def initialize(subjects:)
17
+ @subjects = attr_to_vals(Subject, subjects)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,23 +1,30 @@
1
1
  require 'academic_benchmarks/lib/inst_vars_to_hash'
2
- require 'academic_benchmarks/lib/remove_obsolete_children'
3
2
 
4
3
  module AcademicBenchmarks
5
4
  module Standards
6
5
  class Document
7
6
  include InstVarsToHash
8
- include RemoveObsoleteChildren
9
7
 
10
- attr_accessor :title, :guid, :children
8
+ attr_accessor :guid, :descr, :publication, :adopt_year, :children
11
9
 
12
10
  def self.from_hash(hash)
13
- self.new(title: hash["title"], guid: hash["guid"])
11
+ self.new(guid: hash["guid"], descr: hash["descr"], publication: hash["publication"], adopt_year: hash["adopt_year"])
14
12
  end
15
13
 
16
- def initialize(title:, guid:, children: [])
17
- @title = title
14
+ def initialize(guid:, descr:, publication:, adopt_year:, children: [])
18
15
  @guid = guid
16
+ @descr = descr
17
+ @publication = attr_to_val_or_nil(Publication, publication)
18
+ @adopt_year = adopt_year
19
19
  @children = children
20
20
  end
21
+
22
+ private
23
+
24
+ def attr_to_val_or_nil(klass, hash)
25
+ return nil if hash.nil?
26
+ klass.from_hash(hash)
27
+ end
21
28
  end
22
29
  end
23
30
  end
@@ -0,0 +1,21 @@
1
+ require 'academic_benchmarks/lib/attr_to_vals'
2
+ require 'academic_benchmarks/lib/inst_vars_to_hash'
3
+
4
+ module AcademicBenchmarks
5
+ module Standards
6
+ class EducationLevels
7
+ include AttrToVals
8
+ include InstVarsToHash
9
+
10
+ attr_accessor :grades
11
+
12
+ def self.from_hash(hash)
13
+ self.new(grades: hash["grades"])
14
+ end
15
+
16
+ def initialize(grades:)
17
+ @grades = attr_to_vals(Grade, grades)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -5,15 +5,14 @@ module AcademicBenchmarks
5
5
  class Grade
6
6
  include InstVarsToHash
7
7
 
8
- attr_accessor :high, :low
8
+ attr_accessor :code
9
9
 
10
10
  def self.from_hash(hash)
11
- self.new(high: hash["high"], low: hash["low"])
11
+ self.new(code: hash["code"])
12
12
  end
13
13
 
14
- def initialize(high:, low:)
15
- @high = high
16
- @low = low
14
+ def initialize(code:)
15
+ @code = code
17
16
  end
18
17
  end
19
18
  end
@@ -0,0 +1,23 @@
1
+ require 'academic_benchmarks/lib/inst_vars_to_hash'
2
+
3
+ module AcademicBenchmarks
4
+ module Standards
5
+ class Number
6
+ include InstVarsToHash
7
+
8
+ attr_accessor :enhanced, :raw
9
+
10
+ def self.from_hash(hash)
11
+ self.new(
12
+ enhanced: hash["enhanced"],
13
+ raw: hash["raw"]
14
+ )
15
+ end
16
+
17
+ def initialize(enhanced:, raw:)
18
+ @enhanced = enhanced
19
+ @raw = raw
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,59 @@
1
+ require 'academic_benchmarks/lib/attr_to_vals'
2
+ require 'academic_benchmarks/lib/inst_vars_to_hash'
3
+
4
+ module AcademicBenchmarks
5
+ module Standards
6
+ class Publication
7
+ include AttrToVals
8
+ include InstVarsToHash
9
+
10
+ attr_accessor :acronym, :descr, :guid, :authorities, :children
11
+
12
+ alias_method :code, :acronym
13
+ alias_method :description, :descr
14
+
15
+ def self.from_hash(hash)
16
+ self.new(
17
+ acronym: hash["acronym"],
18
+ descr: hash["descr"],
19
+ guid: hash["guid"],
20
+ authorities: hash["authorities"]
21
+ )
22
+ end
23
+
24
+ def initialize(acronym:, descr:, guid:, authorities:, children: [])
25
+ @acronym = acronym
26
+ @descr = descr
27
+ @guid = guid
28
+ @authorities = attr_to_vals(Authority, authorities)
29
+ @children = children
30
+ end
31
+
32
+ # Children are standards, so rebranch them so we have
33
+ # the following structure:
34
+ #
35
+ # Publication -> Document -> Section -> Standard
36
+ def rebranch_children
37
+ @seen = Set.new()
38
+ @guid_to_obj = {}
39
+ new_children = []
40
+ @children.each do |child|
41
+ doc = reparent(child.document, new_children)
42
+ sec = reparent(child.section, doc.children)
43
+ sec.children.push(child)
44
+ end
45
+ @children.replace(new_children)
46
+ remove_instance_variable('@seen')
47
+ remove_instance_variable('@guid_to_obj')
48
+ end
49
+
50
+ private
51
+
52
+ def reparent(object, children)
53
+ cached_object = (@guid_to_obj[object.guid] ||= object)
54
+ children.push(cached_object) if @seen.add? cached_object.guid
55
+ cached_object
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,23 @@
1
+ require 'academic_benchmarks/lib/inst_vars_to_hash'
2
+
3
+ module AcademicBenchmarks
4
+ module Standards
5
+ class Section
6
+ include InstVarsToHash
7
+
8
+ attr_accessor :guid, :descr, :children
9
+
10
+ alias_method :description, :descr
11
+
12
+ def self.from_hash(hash)
13
+ self.new(descr: hash["descr"], guid: hash["guid"])
14
+ end
15
+
16
+ def initialize(guid:, descr:, children: [])
17
+ @guid = guid
18
+ @descr = descr
19
+ @children = children
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,80 +1,47 @@
1
1
  require 'academic_benchmarks/lib/inst_vars_to_hash'
2
- require 'academic_benchmarks/lib/remove_obsolete_children'
3
2
 
4
3
  module AcademicBenchmarks
5
4
  module Standards
6
5
  class Standard
7
6
  include InstVarsToHash
8
- include RemoveObsoleteChildren
9
7
 
10
- attr_reader :status, :deepest, :children
11
- attr_writer :grade
12
- attr_accessor :guid, :description, :number, :stem, :label, :level,
13
- :version, :seq, :adopt_year, :authority, :course,
14
- :document, :has_relations, :subject, :subject_doc,
8
+ attr_reader :status, :children
9
+ attr_writer :education_levels
10
+ attr_accessor :guid,
11
+ :statement,
12
+ :number, :stem, :label, :level,
13
+ :seq,
14
+ :section,
15
+ :document, :disciplines,
15
16
  :parent, :parent_guid
16
17
 
17
- alias_method :descr, :description
18
-
18
+ # Before standards are rebranched in Authority#rebranch_children
19
+ # or Document#rebranch_children, they have the following structure.
20
+ #
21
+ # Standard
22
+ # |-> Document
23
+ # | |-> Publication
24
+ # | |-> Authority
25
+ # |-> Section
26
+ #
19
27
  def initialize(data)
20
- data = data["data"] if data["data"]
21
- @guid = data["guid"]
22
- @grade = attr_to_val_or_nil(Grade, data, "grade")
23
- @label = data["label"]
24
- @level = data["level"]
25
- @course = attr_to_val_or_nil(Course, data, "course")
26
- @number = data["number"]
27
- @status = data["status"]
28
- @parent = nil
29
- @subject = attr_to_val_or_nil(Subject, data, "subject")
30
- @deepest = data["deepest"]
31
- @version = data["version"]
28
+ attributes = data["attributes"]
29
+ @guid = attributes["guid"]
30
+ @education_levels = attr_to_val_or_nil(EducationLevels, attributes, "education_levels")
31
+ @label = attributes["label"]
32
+ @level = attributes["level"]
33
+ @section = attr_to_val_or_nil(Section, attributes, "section")
34
+ @number = attr_to_val_or_nil(Number, attributes, "number")
35
+ @status = attributes["status"]
36
+ @disciplines = attr_to_val_or_nil(Disciplines, attributes, "disciplines")
32
37
  @children = []
33
- @document = attr_to_val_or_nil(Document, data, "document")
34
- @authority = attr_to_val_or_nil(Authority, data, "authority")
35
- @adopt_year = data["adopt_year"]
36
- @description = data["descr"]
37
- @subject_doc = attr_to_val_or_nil(SubjectDoc, data, "subject_doc")
38
- @has_relations = attr_to_val_or_nil(HasRelations, data, "has_relations")
39
-
40
- # Parent guid extraction can be a little more complicated
41
- if data["parent"] && data["parent"].is_a?(String)
42
- @parent_guid = data["parent"]
43
- elsif data["parent"] && data["parent"].is_a?(Hash)
44
- @parent_guid = data["parent"]["guid"]
45
- end
38
+ @document = attr_to_val_or_nil(Document, attributes, "document")
39
+ @statement = attr_to_val_or_nil(Statement, attributes, "statement")
40
+ @parent_guid = data.dig("relationships", "parent", "data", "id")
46
41
  end
47
42
 
48
43
  alias_method :from_hash, :initialize
49
44
 
50
- def active?
51
- status == "Active"
52
- end
53
-
54
- def obsolete?
55
- status == "Obsolete"
56
- end
57
-
58
- def deepest?
59
- deepest == 'Y'
60
- end
61
-
62
- def status=(status)
63
- unless %w[Active Obsolete].include?(status)
64
- raise ArgumentError.new(
65
- "Standard status must be either 'Active' or 'Obsolete'"
66
- )
67
- end
68
- @status = status
69
- end
70
-
71
- def deepest=(deepest)
72
- unless %w[Y N].include?(deepest)
73
- raise ArgumentError.new("Standard deepest must be either 'Y' or 'N'")
74
- end
75
- @deepest = deepest
76
- end
77
-
78
45
  def add_child(child)
79
46
  raise StandardError.new("Tried to add self as a child") if self == child
80
47
 
@@ -94,17 +61,13 @@ module AcademicBenchmarks
94
61
  @children.count > 0
95
62
  end
96
63
 
97
- def leaf?
98
- !has_children?
99
- end
100
-
101
- def grade
102
- return @grade if @grade
64
+ def education_levels
65
+ return @education_levels if @education_levels
103
66
 
104
- # check to see if one of our parents has a grade. Use that if so
67
+ # check to see if one of our parents has education levels. Use that if so
105
68
  p = parent
106
69
  while p
107
- return p.grade if p.grade
70
+ return p.education_levels if p.education_levels
108
71
  p = p.parent
109
72
  end
110
73
  nil
@@ -1,36 +1,17 @@
1
1
  module AcademicBenchmarks
2
2
  module Standards
3
3
  class StandardsForest
4
- attr_reader :trees, :data_hash, :orphans
5
-
6
- # The guid to standard hash can optionally be saved to permit speedily
7
- # adding standards to the tree (since the tree is unordered,
8
- # this would otherwise be an expensive operation).
9
- #
10
- # The initial data hash can also be optionally saved to
11
- # permit testing and internal consistency checks
12
-
13
- def initialize(
14
- data_hash,
15
- save_guid_to_standard_hash: true,
16
- save_initial_data_hash: false,
17
- include_obsoletes: true
18
- )
19
- @data_hash = data_hash.dup.freeze if save_initial_data_hash
4
+ attr_reader :trees, :orphans
5
+
6
+ def initialize(data_hash)
20
7
  @guid_to_standard = {} # a hash of guids to standards
21
8
  @trees = []
22
9
  @orphans = []
23
10
  process_items(data_hash)
24
11
 
25
- @trees.delete_if(&:obsolete?) unless include_obsoletes
26
-
27
12
  # upgrade the hash data to a StandardsTree object
28
13
  @trees.map! do |item|
29
- StandardsTree.new(
30
- item,
31
- build_item_hash: save_guid_to_standard_hash,
32
- include_obsoletes: include_obsoletes
33
- )
14
+ StandardsTree.new(item)
34
15
  end
35
16
 
36
17
  # We will still have the guid-to-standards saved at the Tree level,
@@ -47,21 +28,6 @@ module AcademicBenchmarks
47
28
  StandardsTree.new(root).tap{ |st| st.add_orphans(@orphans) }
48
29
  end
49
30
 
50
- def add_standard(standard)
51
- if standard.is_a?(Standard)
52
- raise StandardError.new(
53
- "adding standards is not currently implemented"
54
- )
55
- elsif standard.is_a?(Hash)
56
- add_standard(Standard.new(standard))
57
- else
58
- raise ArgumentError.new(
59
- "standard must be an 'AcademicBenchmarks::Standards::Standard' " \
60
- "or a 'Hash' but was a #{standard.class}"
61
- )
62
- end
63
- end
64
-
65
31
  def single_tree?
66
32
  @trees.count == 1
67
33
  end
@@ -6,40 +6,10 @@ module AcademicBenchmarks
6
6
  attr_reader :root, :orphans
7
7
  delegate :children, :to_s, :to_h, :to_json, to: :root
8
8
 
9
- # The item hash can optionally be built to permit the speedy
10
- # addition of standards to the tree. since the tree is unordered,
11
- # adding to it can be expensive without this
12
-
13
- def initialize(root, build_item_hash: true, include_obsoletes: true)
9
+ def initialize(root)
14
10
  @orphans = []
15
11
  @root = root
16
- prune_obsolete_branches unless include_obsoletes
17
-
18
- if build_item_hash
19
- @item_hash = {}
20
- go_ahead_and_build_item_hash
21
- end
22
- end
23
-
24
- def add_standard(standard)
25
- if standard.is_a?(Standard)
26
- parent = @item_hash ? @item_hash[standard.parent_guid] : find_parent(standard)
27
- unless parent
28
- raise StandardError.new(
29
- "Parent of standard not found in tree. Parent guid is " \
30
- "'#{standard.parent_guid}' and child guid is '#{standard.guid}'"
31
- )
32
- end
33
- parent.add_child(standard)
34
- standard.parent = parent
35
- elsif standard.is_a?(Hash)
36
- add_standard(Standard.new(standard))
37
- else
38
- raise ArgumentError.new(
39
- "standard must be an 'AcademicBenchmarks::Standards::Standard' " \
40
- "or a 'Hash' but was a #{standard.class}"
41
- )
42
- end
12
+ root.rebranch_children if root.is_a?(Authority) || root.is_a?(Publication)
43
13
  end
44
14
 
45
15
  def add_orphan(orphan)
@@ -53,37 +23,6 @@ module AcademicBenchmarks
53
23
  def has_orphans?
54
24
  @orphans.present?
55
25
  end
56
-
57
- private
58
-
59
- def go_ahead_and_build_item_hash
60
- @item_hash[@root.guid] = @root
61
- add_children_to_item_hash(@root)
62
- end
63
-
64
- def add_children_to_item_hash(parent)
65
- parent.children.each do |child|
66
- @item_hash[child.guid] = child
67
- add_children_to_item_hash(child) if child.has_children?
68
- end
69
- end
70
-
71
- def find_parent(standard)
72
- return @root if @root.guid == standard.parent_guid
73
- check_children_for_parent(standard.parent_guid, @root)
74
- end
75
-
76
- # does a depth-first search
77
- def check_children_for_parent(parent_guid, standard)
78
- standard.children.each do |child|
79
- return child if child.guid == parent_guid
80
- check_children_for_parent(parent_guid, child) if child.has_children?
81
- end
82
- end
83
-
84
- def prune_obsolete_branches
85
- root.remove_obsolete_children
86
- end
87
26
  end
88
27
  end
89
28
  end
@@ -0,0 +1,21 @@
1
+ require 'academic_benchmarks/lib/inst_vars_to_hash'
2
+
3
+ module AcademicBenchmarks
4
+ module Standards
5
+ class Statement
6
+ include InstVarsToHash
7
+
8
+ attr_accessor :descr
9
+
10
+ alias_method :description, :descr
11
+
12
+ def self.from_hash(hash)
13
+ self.new(descr: hash["descr"])
14
+ end
15
+
16
+ def initialize(descr:)
17
+ @descr = descr
18
+ end
19
+ end
20
+ end
21
+ end
@@ -5,15 +5,14 @@ module AcademicBenchmarks
5
5
  class Subject
6
6
  include InstVarsToHash
7
7
 
8
- attr_accessor :code, :broad
8
+ attr_accessor :code
9
9
 
10
10
  def self.from_hash(hash)
11
- self.new(code: hash["code"], broad: hash["broad"], )
11
+ self.new(code: hash["code"])
12
12
  end
13
13
 
14
- def initialize(code:, broad:)
14
+ def initialize(code:)
15
15
  @code = code
16
- @broad = broad
17
16
  end
18
17
  end
19
18
  end
metadata CHANGED
@@ -1,10 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: academic_benchmarks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Porter
8
+ - Augusto Callejas
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
@@ -44,6 +45,20 @@ dependencies:
44
45
  - - "<"
45
46
  - !ruby/object:Gem::Version
46
47
  version: '6.1'
48
+ - !ruby/object:Gem::Dependency
49
+ name: hash_dig_and_collect
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.0.1
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.0.1
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: rake
49
64
  requirement: !ruby/object:Gem::Requirement
@@ -106,14 +121,14 @@ dependencies:
106
121
  requirements:
107
122
  - - "~>"
108
123
  - !ruby/object:Gem::Version
109
- version: '1.22'
124
+ version: '3.5'
110
125
  type: :development
111
126
  prerelease: false
112
127
  version_requirements: !ruby/object:Gem::Requirement
113
128
  requirements:
114
129
  - - "~>"
115
130
  - !ruby/object:Gem::Version
116
- version: '1.22'
131
+ version: '3.5'
117
132
  - !ruby/object:Gem::Dependency
118
133
  name: rubocop
119
134
  requirement: !ruby/object:Gem::Requirement
@@ -158,7 +173,9 @@ dependencies:
158
173
  version: '1.6'
159
174
  description: A ruby api for accessing the Academic Benchmarks API. A valid subscription
160
175
  with accompanying credentials will be required to access the API
161
- email: bporter@instructure.com
176
+ email:
177
+ - bporter@instructure.com
178
+ - acallejas@instructure.com
162
179
  executables: []
163
180
  extensions: []
164
181
  extra_rdoc_files: []
@@ -168,19 +185,21 @@ files:
168
185
  - lib/academic_benchmarks/api/constants.rb
169
186
  - lib/academic_benchmarks/api/handle.rb
170
187
  - lib/academic_benchmarks/api/standards.rb
188
+ - lib/academic_benchmarks/lib/attr_to_vals.rb
171
189
  - lib/academic_benchmarks/lib/inst_vars_to_hash.rb
172
- - lib/academic_benchmarks/lib/remove_obsolete_children.rb
173
190
  - lib/academic_benchmarks/standards/authority.rb
174
- - lib/academic_benchmarks/standards/course.rb
191
+ - lib/academic_benchmarks/standards/disciplines.rb
175
192
  - lib/academic_benchmarks/standards/document.rb
193
+ - lib/academic_benchmarks/standards/education_levels.rb
176
194
  - lib/academic_benchmarks/standards/grade.rb
177
- - lib/academic_benchmarks/standards/has_relations.rb
178
- - lib/academic_benchmarks/standards/parent.rb
195
+ - lib/academic_benchmarks/standards/number.rb
196
+ - lib/academic_benchmarks/standards/publication.rb
197
+ - lib/academic_benchmarks/standards/section.rb
179
198
  - lib/academic_benchmarks/standards/standard.rb
180
199
  - lib/academic_benchmarks/standards/standards_forest.rb
181
200
  - lib/academic_benchmarks/standards/standards_tree.rb
201
+ - lib/academic_benchmarks/standards/statement.rb
182
202
  - lib/academic_benchmarks/standards/subject.rb
183
- - lib/academic_benchmarks/standards/subject_doc.rb
184
203
  homepage: https://github.com/instructure/academic_benchmarks
185
204
  licenses:
186
205
  - AGPL-3.0
@@ -200,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
200
219
  - !ruby/object:Gem::Version
201
220
  version: '0'
202
221
  requirements: []
203
- rubygems_version: 3.0.3
222
+ rubygems_version: 3.1.2
204
223
  signing_key:
205
224
  specification_version: 4
206
225
  summary: A ruby api for accessing the Academic Benchmarks API
@@ -1,10 +0,0 @@
1
- module RemoveObsoleteChildren
2
- def remove_obsolete_children
3
- @children.delete_if do |child|
4
- unless child.obsolete?
5
- child.remove_obsolete_children
6
- end
7
- child.obsolete?
8
- end
9
- end
10
- end
@@ -1,22 +0,0 @@
1
- require 'academic_benchmarks/lib/inst_vars_to_hash'
2
-
3
- module AcademicBenchmarks
4
- module Standards
5
- class Course
6
- include InstVarsToHash
7
-
8
- attr_accessor :guid, :description
9
-
10
- alias_method :descr, :description
11
-
12
- def self.from_hash(hash)
13
- self.new(description: hash["descr"], guid: hash["guid"])
14
- end
15
-
16
- def initialize(guid:, description:)
17
- @guid = guid
18
- @description = description
19
- end
20
- end
21
- end
22
- end
@@ -1,21 +0,0 @@
1
- require 'academic_benchmarks/lib/inst_vars_to_hash'
2
-
3
- module AcademicBenchmarks
4
- module Standards
5
- class HasRelations
6
- include InstVarsToHash
7
-
8
- attr_accessor :origin, :derivative, :related_derivative
9
-
10
- def self.from_hash(hash)
11
- self.new(derivative: hash["derivative"], origin: hash["origin"], related_derivative: hash["related_derivative"])
12
- end
13
-
14
- def initialize(origin: 0, derivative: 0, related_derivative: 0)
15
- @origin = origin
16
- @derivative = derivative
17
- @related_derivative = related_derivative
18
- end
19
- end
20
- end
21
- end
@@ -1,41 +0,0 @@
1
- require 'academic_benchmarks/lib/inst_vars_to_hash'
2
-
3
- module AcademicBenchmarks
4
- module Standards
5
- class Parent
6
- include InstVarsToHash
7
-
8
- attr_accessor :guid, :description, :number, :stem, :label, :deepest,
9
- :seq, :level, :status, :version
10
-
11
- def self.from_hash(hash)
12
- self.new(
13
- guid: hash["guid"],
14
- description: hash["description"],
15
- number: hash["number"],
16
- stem: hash["stem"],
17
- label: hash["label"],
18
- deepest: hash["deepest"],
19
- seq: hash["seq"],
20
- level: hash["level"],
21
- status: hash["status"],
22
- version: hash["version"]
23
- )
24
- end
25
-
26
- def initialize(guid:, description:, number:, stem:, label:, deepest:,
27
- seq:, level:, status:, version:)
28
- @guid = guid
29
- @description = description
30
- @number = number
31
- @stem = stem
32
- @label = label
33
- @deepest = deepest
34
- @seq = seq
35
- @level = level
36
- @status = status
37
- @version = version
38
- end
39
- end
40
- end
41
- end
@@ -1,22 +0,0 @@
1
- require 'academic_benchmarks/lib/inst_vars_to_hash'
2
-
3
- module AcademicBenchmarks
4
- module Standards
5
- class SubjectDoc
6
- include InstVarsToHash
7
-
8
- attr_accessor :guid, :description
9
-
10
- alias_method :descr, :description
11
-
12
- def self.from_hash(hash)
13
- self.new(guid: hash["guid"], description: hash["descr"])
14
- end
15
-
16
- def initialize(guid:, description:)
17
- @guid = guid
18
- @description = description
19
- end
20
- end
21
- end
22
- end