academic_benchmarks 0.0.11 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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