academic_benchmarks 0.0.10 → 1.1.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 +6 -4
- data/lib/academic_benchmarks/api/constants.rb +2 -20
- data/lib/academic_benchmarks/api/handle.rb +0 -20
- data/lib/academic_benchmarks/api/standards.rb +127 -83
- 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 +35 -9
- data/lib/academic_benchmarks/standards/disciplines.rb +21 -0
- data/lib/academic_benchmarks/standards/document.rb +14 -6
- data/lib/academic_benchmarks/standards/education_levels.rb +21 -0
- data/lib/academic_benchmarks/standards/grade.rb +4 -5
- data/lib/academic_benchmarks/standards/number.rb +21 -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 +37 -70
- 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 +3 -4
- data/lib/academic_benchmarks/standards/utilizations.rb +19 -0
- metadata +37 -18
- 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: e6ff603d8c11ac0b1800046ba7c13be13f1415482e2fa00b894a306eb44088c5
|
4
|
+
data.tar.gz: cc2fc48f7ed2ef3ee617abf3ce54d022bd706f49ca9b6ec92ee5da5492a51312
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0090cdf724490a82939a5f7e780c58f1de7f0118f0dc14bcbe32b901f0826312fb9dd62262a2df03c170131d80161e20ab7937e92516eb85beb7f557dfe77c7e'
|
7
|
+
data.tar.gz: bb84babbd1e4a10e5f94ec56958aa7da18515071bf66b56c7dab0012d41c4b254e382c18a4382a395f81d32aba492de5311062a02905cb724e05fe403c17f8ef
|
@@ -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:)
|
@@ -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
|
@@ -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.prefix_enhanced
|
19
|
+
status
|
20
|
+
disciplines.subjects.code
|
21
|
+
document.guid
|
22
|
+
document.descr
|
23
|
+
document.adopt_year
|
24
|
+
document.publication.descr
|
25
|
+
document.publication.guid
|
26
|
+
document.publication.authorities
|
27
|
+
statement.descr
|
28
|
+
utilizations.type
|
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
|
-
)
|
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"])
|
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)
|
45
40
|
end
|
46
41
|
end
|
47
42
|
|
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"])
|
43
|
+
def authorities
|
44
|
+
raw_facet("document.publication.authorities").map do |a|
|
45
|
+
AcademicBenchmarks::Standards::Authority.from_hash(a["data"])
|
55
46
|
end
|
56
47
|
end
|
57
48
|
|
58
|
-
def
|
59
|
-
|
60
|
-
AcademicBenchmarks::Standards::
|
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"])
|
61
52
|
end
|
62
53
|
end
|
63
54
|
|
64
|
-
def
|
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
|
-
def authority_tree(authority_or_auth_code_guid_or_desc, include_obsolete_standards: true)
|
60
|
+
def authority_tree(authority_or_auth_code_guid_or_desc, include_obsolete_standards: true, exclude_examples: false)
|
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, exclude_examples: exclude_examples)
|
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, exclude_examples: false)
|
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, exclude_examples: exclude_examples)
|
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,49 @@ 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
|
+
|
162
|
+
if query_params.delete :exclude_examples
|
163
|
+
if query_params.key? 'filter[standards]'
|
164
|
+
query_params['filter[standards]'] += " and utilizations.type not eq 'example'"
|
165
|
+
else
|
166
|
+
query_params['filter[standards]'] = "utilizations.type not eq 'example'"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def request_facet(query_params)
|
172
|
+
odata_filters query_params
|
173
|
+
page = request_page(
|
174
|
+
query_params: query_params,
|
175
|
+
limit: 0, # return no standards since facets are separate
|
176
|
+
offset: 0
|
177
|
+
).parsed_response
|
178
|
+
|
179
|
+
page.dig("meta", "facets", 0, "details")
|
180
|
+
end
|
181
|
+
|
149
182
|
def request_search_pages_and_concat_resources(query_params)
|
183
|
+
query_params['fields[standards]'] = STANDARDS_FIELDS.join(',')
|
184
|
+
odata_filters query_params
|
150
185
|
query_params.reverse_merge!({limit: DEFAULT_PER_PAGE})
|
151
186
|
|
152
187
|
if !query_params[:limit] || query_params[:limit] <= 0
|
@@ -161,10 +196,9 @@ module AcademicBenchmarks
|
|
161
196
|
offset: 0
|
162
197
|
).parsed_response
|
163
198
|
|
164
|
-
resources = first_page["
|
165
|
-
count = first_page["count"]
|
199
|
+
resources = first_page["data"]
|
200
|
+
count = first_page["meta"]["count"]
|
166
201
|
offset = query_params[:limit]
|
167
|
-
|
168
202
|
while offset < count
|
169
203
|
page = request_page(
|
170
204
|
query_params: query_params,
|
@@ -172,7 +206,7 @@ module AcademicBenchmarks
|
|
172
206
|
offset: offset
|
173
207
|
)
|
174
208
|
offset += query_params[:limit]
|
175
|
-
resources.push(page.parsed_response["
|
209
|
+
resources.push(page.parsed_response["data"])
|
176
210
|
end
|
177
211
|
|
178
212
|
resources.flatten
|
@@ -183,19 +217,29 @@ module AcademicBenchmarks
|
|
183
217
|
limit: limit,
|
184
218
|
offset: offset,
|
185
219
|
})
|
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:"
|
220
|
+
1.times do
|
221
|
+
resp = @handle.class.get(
|
222
|
+
'/standards',
|
223
|
+
query: query_params.merge({
|
224
|
+
limit: limit,
|
225
|
+
offset: offset,
|
226
|
+
})
|
196
227
|
)
|
228
|
+
if resp.code == 429
|
229
|
+
sleep retry_after(resp)
|
230
|
+
redo
|
231
|
+
end
|
232
|
+
if resp.code != 200
|
233
|
+
raise RuntimeError.new(
|
234
|
+
"Received response '#{resp.code}: #{resp.message}' requesting standards from Academic Benchmarks:"
|
235
|
+
)
|
236
|
+
end
|
237
|
+
return resp
|
197
238
|
end
|
198
|
-
|
239
|
+
end
|
240
|
+
|
241
|
+
def retry_after(response)
|
242
|
+
ENV['ACADEMIC_BENCHMARKS_TOO_MANY_REQUESTS_RETRY']&.to_f || 5
|
199
243
|
end
|
200
244
|
end
|
201
245
|
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 :
|
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
|