academic_benchmarks 0.0.8 → 1.0.1
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 +130 -85
- 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 +15 -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 +38 -71
- 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
- data/lib/academic_benchmarks/standards/utilizations.rb +19 -0
- metadata +52 -20
- 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: 50df986c030289fb9f248460b600eebc30b059c7386fa6c5cdeba4d39bbe714a
|
4
|
+
data.tar.gz: 4421c98acc7fbe617d2a8b1b3a8c9a4eb736d7ea65016c4ba5933a52b6f28d35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f777d12e41775ac0cac11b9a76922b5b10c2957e480f16c0a5ff3c61328b92abfa409389cc6e116d4d41503c8821fce2cb99e0d401bdee09ce2daea2561ab8a
|
7
|
+
data.tar.gz: cf26bcd052ff8b71be7a360ba095580f3c6993dfd235d39e57a378a846bffbfc50790654c285a829e943c638a6f069492211c4c6fe1ec3f91a2e5cc93e040970
|
@@ -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,81 @@
|
|
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
|
+
utilizations.type
|
30
|
+
parent
|
31
|
+
]
|
32
|
+
|
11
33
|
def initialize(handle)
|
12
34
|
@handle = handle
|
13
35
|
end
|
14
36
|
|
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
|
-
)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
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"])
|
37
|
+
# TODO: in the future, support OData filtering for flexible querying
|
38
|
+
def search(authority_guid: nil, publication_guid: nil)
|
39
|
+
raw_search(authority: authority_guid, publication: publication_guid).map do |standard|
|
40
|
+
AcademicBenchmarks::Standards::Standard.new(standard)
|
45
41
|
end
|
46
42
|
end
|
47
43
|
|
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"])
|
44
|
+
def authorities
|
45
|
+
raw_facet("document.publication.authorities").map do |a|
|
46
|
+
AcademicBenchmarks::Standards::Authority.from_hash(a["data"])
|
55
47
|
end
|
56
48
|
end
|
57
49
|
|
58
|
-
def
|
59
|
-
|
60
|
-
AcademicBenchmarks::Standards::
|
50
|
+
def publications(authority_guid: nil)
|
51
|
+
raw_facet("document.publication", authority: authority_guid).map do |a|
|
52
|
+
AcademicBenchmarks::Standards::Publication.from_hash(a["data"])
|
61
53
|
end
|
62
54
|
end
|
63
55
|
|
64
|
-
def
|
56
|
+
def authority_publications(authority_or_auth_code_guid_or_desc)
|
65
57
|
authority = auth_from_code_guid_or_desc(authority_or_auth_code_guid_or_desc)
|
66
|
-
|
58
|
+
publications(authority_guid: authority.guid)
|
67
59
|
end
|
68
60
|
|
69
|
-
def authority_tree(authority_or_auth_code_guid_or_desc, include_obsolete_standards: true)
|
61
|
+
def authority_tree(authority_or_auth_code_guid_or_desc, include_obsolete_standards: true, exclude_examples: false)
|
70
62
|
authority = auth_from_code_guid_or_desc(authority_or_auth_code_guid_or_desc)
|
71
|
-
auth_children =
|
63
|
+
auth_children = raw_search(authority: authority.guid, include_obsoletes: include_obsolete_standards, exclude_examples: exclude_examples)
|
72
64
|
AcademicBenchmarks::Standards::StandardsForest.new(
|
73
|
-
auth_children
|
74
|
-
include_obsoletes: include_obsolete_standards
|
65
|
+
auth_children
|
75
66
|
).consolidate_under_root(authority)
|
76
67
|
end
|
77
68
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
69
|
+
def publication_tree(publication_or_pub_code_guid_or_desc, include_obsolete_standards: true, exclude_examples: false)
|
70
|
+
publication = pub_from_guid(publication_or_pub_code_guid_or_desc)
|
71
|
+
pub_children = raw_search(publication: publication.guid, include_obsoletes: include_obsolete_standards, exclude_examples: exclude_examples)
|
81
72
|
AcademicBenchmarks::Standards::StandardsForest.new(
|
82
|
-
|
83
|
-
|
84
|
-
).consolidate_under_root(document)
|
73
|
+
pub_children
|
74
|
+
).consolidate_under_root(publication)
|
85
75
|
end
|
86
76
|
|
87
77
|
private
|
88
78
|
|
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
79
|
def auth_from_code_guid_or_desc(authority_or_auth_code_guid_or_desc)
|
98
80
|
if authority_or_auth_code_guid_or_desc.is_a?(AcademicBenchmarks::Standards::Authority)
|
99
81
|
authority_or_auth_code_guid_or_desc
|
@@ -102,6 +84,14 @@ module AcademicBenchmarks
|
|
102
84
|
end
|
103
85
|
end
|
104
86
|
|
87
|
+
def pub_from_guid(publication_or_pub_code_guid_or_desc)
|
88
|
+
if publication_or_pub_code_guid_or_desc.is_a?(AcademicBenchmarks::Standards::Publication)
|
89
|
+
publication_or_pub_code_guid_or_desc
|
90
|
+
else
|
91
|
+
find_type(type: "publication", data: publication_or_pub_code_guid_or_desc)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
105
95
|
def find_type(type:, data:)
|
106
96
|
matches = send("match_#{type}", data)
|
107
97
|
if matches.empty?
|
@@ -110,7 +100,7 @@ module AcademicBenchmarks
|
|
110
100
|
)
|
111
101
|
elsif matches.count > 1
|
112
102
|
raise StandardError.new(
|
113
|
-
"
|
103
|
+
"#{type.upcase} code, guid, or description matched more than one #{type}. " \
|
114
104
|
"matched '#{matches.map(&:to_json).join('; ')}'"
|
115
105
|
)
|
116
106
|
end
|
@@ -119,22 +109,26 @@ module AcademicBenchmarks
|
|
119
109
|
|
120
110
|
def match_authority(data)
|
121
111
|
authorities.select do |auth|
|
122
|
-
auth.
|
112
|
+
auth.acronym == data ||
|
123
113
|
auth.guid == data ||
|
124
114
|
auth.descr == data
|
125
115
|
end
|
126
116
|
end
|
127
117
|
|
128
|
-
def
|
129
|
-
|
118
|
+
def match_publication(data)
|
119
|
+
publications.select do |pub|
|
120
|
+
pub.acronym == data ||
|
121
|
+
pub.guid == data ||
|
122
|
+
pub.descr == data
|
123
|
+
end
|
130
124
|
end
|
131
125
|
|
132
|
-
def
|
133
|
-
|
126
|
+
def raw_facet(facet, query_params = {})
|
127
|
+
request_facet({facet: facet}.merge(query_params).merge(auth_query_params))
|
134
128
|
end
|
135
129
|
|
136
|
-
def
|
137
|
-
opts.
|
130
|
+
def raw_search(opts = {})
|
131
|
+
request_search_pages_and_concat_resources(opts.merge(auth_query_params))
|
138
132
|
end
|
139
133
|
|
140
134
|
def auth_query_params
|
@@ -146,7 +140,49 @@ module AcademicBenchmarks
|
|
146
140
|
)
|
147
141
|
end
|
148
142
|
|
143
|
+
def odata_filters(query_params)
|
144
|
+
if query_params.key? :authority
|
145
|
+
value = query_params.delete :authority
|
146
|
+
query_params['filter[standards]'] = "document.publication.authorities.guid eq '#{value}'" if value
|
147
|
+
end
|
148
|
+
if query_params.key? :publication
|
149
|
+
value = query_params.delete :publication
|
150
|
+
query_params['filter[standards]'] = "document.publication.guid eq '#{value}'" if value
|
151
|
+
end
|
152
|
+
|
153
|
+
if query_params.key? :include_obsoletes
|
154
|
+
unless query_params.delete :include_obsoletes
|
155
|
+
if query_params.key? 'filter[standards]'
|
156
|
+
query_params['filter[standards]'] += " and status eq 'active'"
|
157
|
+
else
|
158
|
+
query_params['filter[standards]'] = "status eq 'active'"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
if query_params.delete :exclude_examples
|
164
|
+
if query_params.key? 'filter[standards]'
|
165
|
+
query_params['filter[standards]'] += " and utilizations.type not eq 'example'"
|
166
|
+
else
|
167
|
+
query_params['filter[standards]'] = "utilizations.type not eq 'example'"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def request_facet(query_params)
|
173
|
+
odata_filters query_params
|
174
|
+
page = request_page(
|
175
|
+
query_params: query_params,
|
176
|
+
limit: 0, # return no standards since facets are separate
|
177
|
+
offset: 0
|
178
|
+
).parsed_response
|
179
|
+
|
180
|
+
page.dig("meta", "facets", 0, "details")
|
181
|
+
end
|
182
|
+
|
149
183
|
def request_search_pages_and_concat_resources(query_params)
|
184
|
+
query_params['fields[standards]'] = STANDARDS_FIELDS.join(',')
|
185
|
+
odata_filters query_params
|
150
186
|
query_params.reverse_merge!({limit: DEFAULT_PER_PAGE})
|
151
187
|
|
152
188
|
if !query_params[:limit] || query_params[:limit] <= 0
|
@@ -161,10 +197,9 @@ module AcademicBenchmarks
|
|
161
197
|
offset: 0
|
162
198
|
).parsed_response
|
163
199
|
|
164
|
-
resources = first_page["
|
165
|
-
count = first_page["count"]
|
200
|
+
resources = first_page["data"]
|
201
|
+
count = first_page["meta"]["count"]
|
166
202
|
offset = query_params[:limit]
|
167
|
-
|
168
203
|
while offset < count
|
169
204
|
page = request_page(
|
170
205
|
query_params: query_params,
|
@@ -172,7 +207,7 @@ module AcademicBenchmarks
|
|
172
207
|
offset: offset
|
173
208
|
)
|
174
209
|
offset += query_params[:limit]
|
175
|
-
resources.push(page.parsed_response["
|
210
|
+
resources.push(page.parsed_response["data"])
|
176
211
|
end
|
177
212
|
|
178
213
|
resources.flatten
|
@@ -183,19 +218,29 @@ module AcademicBenchmarks
|
|
183
218
|
limit: limit,
|
184
219
|
offset: offset,
|
185
220
|
})
|
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:"
|
221
|
+
1.times do
|
222
|
+
resp = @handle.class.get(
|
223
|
+
'/standards',
|
224
|
+
query: query_params.merge({
|
225
|
+
limit: limit,
|
226
|
+
offset: offset,
|
227
|
+
})
|
196
228
|
)
|
229
|
+
if resp.code == 429
|
230
|
+
sleep retry_after(resp)
|
231
|
+
redo
|
232
|
+
end
|
233
|
+
if resp.code != 200
|
234
|
+
raise RuntimeError.new(
|
235
|
+
"Received response '#{resp.code}: #{resp.message}' requesting standards from Academic Benchmarks:"
|
236
|
+
)
|
237
|
+
end
|
238
|
+
return resp
|
197
239
|
end
|
198
|
-
|
240
|
+
end
|
241
|
+
|
242
|
+
def retry_after(response)
|
243
|
+
ENV['ACADEMIC_BENCHMARKS_TOO_MANY_REQUESTS_RETRY']&.to_f || 5
|
199
244
|
end
|
200
245
|
end
|
201
246
|
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
|