academic_benchmarks 0.0.7 → 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 +5 -5
- data/lib/academic_benchmarks/api/auth.rb +7 -5
- data/lib/academic_benchmarks/api/constants.rb +2 -20
- data/lib/academic_benchmarks/api/handle.rb +2 -22
- data/lib/academic_benchmarks/api/standards.rb +120 -84
- data/lib/academic_benchmarks/lib/attr_to_vals.rb +7 -0
- data/lib/academic_benchmarks/lib/inst_vars_to_hash.rb +2 -0
- data/lib/academic_benchmarks/standards/authority.rb +36 -10
- data/lib/academic_benchmarks/standards/disciplines.rb +21 -0
- data/lib/academic_benchmarks/standards/document.rb +14 -7
- data/lib/academic_benchmarks/standards/education_levels.rb +21 -0
- data/lib/academic_benchmarks/standards/grade.rb +5 -6
- data/lib/academic_benchmarks/standards/number.rb +23 -0
- data/lib/academic_benchmarks/standards/publication.rb +59 -0
- data/lib/academic_benchmarks/standards/section.rb +23 -0
- data/lib/academic_benchmarks/standards/standard.rb +35 -72
- data/lib/academic_benchmarks/standards/standards_forest.rb +4 -38
- data/lib/academic_benchmarks/standards/standards_tree.rb +2 -63
- data/lib/academic_benchmarks/standards/statement.rb +21 -0
- data/lib/academic_benchmarks/standards/subject.rb +4 -5
- metadata +52 -21
- data/lib/academic_benchmarks/lib/remove_obsolete_children.rb +0 -10
- data/lib/academic_benchmarks/standards/course.rb +0 -22
- data/lib/academic_benchmarks/standards/has_relations.rb +0 -21
- data/lib/academic_benchmarks/standards/parent.rb +0 -41
- data/lib/academic_benchmarks/standards/subject_doc.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '05582989c51ab88e71317ed23866a5dd9c34a956b96791faa86f775c8c09e227'
|
4
|
+
data.tar.gz: c5a713f96b930b2b914674fd57390dd68d0cc8fc9269e3b3503514637d07d746
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
13
|
-
"
|
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:)
|
@@ -39,7 +41,7 @@ module AcademicBenchmarks
|
|
39
41
|
end
|
40
42
|
|
41
43
|
def self.expire_time_in(offset)
|
42
|
-
Time.now.to_i + offset
|
44
|
+
Time.now.to_i + offset.to_i
|
43
45
|
end
|
44
46
|
end
|
45
47
|
end
|
@@ -2,11 +2,11 @@ module AcademicBenchmarks
|
|
2
2
|
module Api
|
3
3
|
module Constants
|
4
4
|
def self.base_url
|
5
|
-
'https://api.
|
5
|
+
'https://api.abconnect.certicaconnect.com/rest/v4.1'
|
6
6
|
end
|
7
7
|
|
8
8
|
def self.api_version
|
9
|
-
'
|
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
|
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'academic_benchmarks/api/constants'
|
2
|
+
require 'academic_benchmarks/api/standards'
|
3
3
|
|
4
4
|
module AcademicBenchmarks
|
5
5
|
module Api
|
@@ -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)
|
@@ -1,99 +1,80 @@
|
|
1
1
|
require 'active_support/hash_with_indifferent_access'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require 'academic_benchmarks/api/auth'
|
4
|
+
require 'academic_benchmarks/api/constants'
|
5
5
|
|
6
6
|
module AcademicBenchmarks
|
7
7
|
module Api
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|
49
|
-
|
50
|
-
|
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
|
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
|
-
|
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 =
|
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
|
79
|
-
|
80
|
-
|
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
|
-
|
83
|
-
|
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
|
-
"
|
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.
|
111
|
+
auth.acronym == data ||
|
123
112
|
auth.guid == data ||
|
124
113
|
auth.descr == data
|
125
114
|
end
|
126
115
|
end
|
127
116
|
|
128
|
-
def
|
129
|
-
|
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
|
133
|
-
|
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
|
137
|
-
opts.
|
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["
|
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["
|
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
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
-
|
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
|
@@ -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
|
-
|
2
|
-
require_relative '../lib/remove_obsolete_children'
|
1
|
+
require 'academic_benchmarks/lib/inst_vars_to_hash'
|
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 :
|
8
|
+
attr_accessor :acronym, :descr, :guid, :children
|
11
9
|
|
12
|
-
alias_method :
|
10
|
+
alias_method :code, :acronym
|
11
|
+
alias_method :description, :descr
|
13
12
|
|
14
13
|
def self.from_hash(hash)
|
15
14
|
self.new(
|
16
|
-
|
15
|
+
acronym: hash["acronym"],
|
17
16
|
guid: hash["guid"],
|
18
|
-
|
17
|
+
descr: hash["descr"]
|
19
18
|
)
|
20
19
|
end
|
21
20
|
|
22
|
-
def initialize(
|
23
|
-
@
|
21
|
+
def initialize(acronym:, guid:, descr:, children: [])
|
22
|
+
@acronym = acronym
|
24
23
|
@guid = guid
|
25
|
-
@
|
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
|