qa 0.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.
Files changed (101) hide show
  1. data/LICENSE +15 -0
  2. data/README.md +174 -0
  3. data/Rakefile +10 -0
  4. data/app/assets/javascripts/qa/application.js +13 -0
  5. data/app/assets/stylesheets/qa/application.css +13 -0
  6. data/app/controllers/qa/application_controller.rb +4 -0
  7. data/app/controllers/qa/terms_controller.rb +76 -0
  8. data/app/helpers/qa/application_helper.rb +2 -0
  9. data/app/models/qa/mesh_tree.rb +37 -0
  10. data/app/models/qa/subject_mesh_term.rb +45 -0
  11. data/app/views/layouts/qa/application.html.erb +14 -0
  12. data/config/authorities.yml +1 -0
  13. data/config/authorities/states.yml +101 -0
  14. data/config/initializers/authorities.rb +1 -0
  15. data/config/oclcts-authorities.yml +24 -0
  16. data/config/routes.rb +7 -0
  17. data/db/migrate/20130917200611_create_qa_subject_mesh_terms.rb +11 -0
  18. data/db/migrate/20130917201026_create_qa_mesh_tree.rb +10 -0
  19. data/db/migrate/20130918141523_add_term_lower_to_qa_subject_mesh_terms.rb +7 -0
  20. data/lib/qa.rb +9 -0
  21. data/lib/qa/authorities.rb +12 -0
  22. data/lib/qa/authorities/base.rb +50 -0
  23. data/lib/qa/authorities/lcsh.rb +46 -0
  24. data/lib/qa/authorities/loc.rb +200 -0
  25. data/lib/qa/authorities/local.rb +69 -0
  26. data/lib/qa/authorities/mesh.rb +20 -0
  27. data/lib/qa/authorities/mesh_tools.rb +6 -0
  28. data/lib/qa/authorities/mesh_tools/mesh_data_parser.rb +65 -0
  29. data/lib/qa/authorities/mesh_tools/mesh_importer.rb +41 -0
  30. data/lib/qa/authorities/oclcts.rb +63 -0
  31. data/lib/qa/authorities/tgnlang.rb +40 -0
  32. data/lib/qa/data/TGN_LANGUAGES.xml +7435 -0
  33. data/lib/qa/engine.rb +5 -0
  34. data/lib/qa/version.rb +3 -0
  35. data/lib/tasks/qa_tasks.rake +4 -0
  36. data/spec/controllers/terms_controller_spec.rb +70 -0
  37. data/spec/fixtures/authorities/authority_A.yml +10 -0
  38. data/spec/fixtures/authorities/authority_B.yml +7 -0
  39. data/spec/fixtures/authorities/authority_C.yml +4 -0
  40. data/spec/fixtures/lcsh-response.txt +1 -0
  41. data/spec/fixtures/loc-response.txt +147 -0
  42. data/spec/fixtures/mesh.txt +334 -0
  43. data/spec/fixtures/oclcts-response-mesh-1.txt +28 -0
  44. data/spec/fixtures/oclcts-response-mesh-2.txt +253 -0
  45. data/spec/fixtures/oclcts-response-mesh-3.txt +28 -0
  46. data/spec/internal/Gemfile +48 -0
  47. data/spec/internal/Gemfile.lock +156 -0
  48. data/spec/internal/README.rdoc +28 -0
  49. data/spec/internal/Rakefile +6 -0
  50. data/spec/internal/app/assets/javascripts/application.js +16 -0
  51. data/spec/internal/app/assets/stylesheets/application.css +13 -0
  52. data/spec/internal/app/controllers/application_controller.rb +5 -0
  53. data/spec/internal/app/helpers/application_helper.rb +2 -0
  54. data/spec/internal/app/views/layouts/application.html.erb +14 -0
  55. data/spec/internal/bin/bundle +3 -0
  56. data/spec/internal/bin/rails +4 -0
  57. data/spec/internal/bin/rake +4 -0
  58. data/spec/internal/config.ru +4 -0
  59. data/spec/internal/config/application.rb +23 -0
  60. data/spec/internal/config/boot.rb +4 -0
  61. data/spec/internal/config/database.yml +25 -0
  62. data/spec/internal/config/environment.rb +5 -0
  63. data/spec/internal/config/environments/development.rb +29 -0
  64. data/spec/internal/config/environments/production.rb +80 -0
  65. data/spec/internal/config/environments/test.rb +36 -0
  66. data/spec/internal/config/initializers/backtrace_silencers.rb +7 -0
  67. data/spec/internal/config/initializers/filter_parameter_logging.rb +4 -0
  68. data/spec/internal/config/initializers/inflections.rb +16 -0
  69. data/spec/internal/config/initializers/mime_types.rb +5 -0
  70. data/spec/internal/config/initializers/secret_token.rb +12 -0
  71. data/spec/internal/config/initializers/session_store.rb +3 -0
  72. data/spec/internal/config/initializers/wrap_parameters.rb +14 -0
  73. data/spec/internal/config/locales/en.yml +23 -0
  74. data/spec/internal/config/oclcts-authorities.yml +24 -0
  75. data/spec/internal/config/routes.rb +60 -0
  76. data/spec/internal/db/development.sqlite3 +0 -0
  77. data/spec/internal/db/migrate/20130930151844_create_qa_subject_mesh_terms.qa.rb +12 -0
  78. data/spec/internal/db/migrate/20130930151845_create_qa_mesh_tree.qa.rb +11 -0
  79. data/spec/internal/db/migrate/20130930151846_add_term_lower_to_qa_subject_mesh_terms.qa.rb +8 -0
  80. data/spec/internal/db/schema.rb +34 -0
  81. data/spec/internal/db/seeds.rb +7 -0
  82. data/spec/internal/db/test.sqlite3 +0 -0
  83. data/spec/internal/lib/generators/test_app_generator.rb +20 -0
  84. data/spec/internal/log/development.log +193 -0
  85. data/spec/internal/public/404.html +58 -0
  86. data/spec/internal/public/422.html +58 -0
  87. data/spec/internal/public/500.html +57 -0
  88. data/spec/internal/public/favicon.ico +0 -0
  89. data/spec/internal/public/robots.txt +5 -0
  90. data/spec/internal/test/test_helper.rb +15 -0
  91. data/spec/lib/authorities_lcsh_spec.rb +61 -0
  92. data/spec/lib/authorities_loc_spec.rb +35 -0
  93. data/spec/lib/authorities_local_spec.rb +89 -0
  94. data/spec/lib/authorities_mesh_spec.rb +38 -0
  95. data/spec/lib/authorities_oclcts_spec.rb +49 -0
  96. data/spec/lib/authorities_tgnlang_spec.rb +22 -0
  97. data/spec/lib/mesh_data_parser_spec.rb +125 -0
  98. data/spec/models/subject_mesh_term_spec.rb +49 -0
  99. data/spec/spec_helper.rb +60 -0
  100. data/spec/support/lib/generators/test_app_generator.rb +20 -0
  101. metadata +396 -0
@@ -0,0 +1 @@
1
+ AUTHORITIES_CONFIG = YAML.load_file(File.expand_path("../../authorities.yml", __FILE__))
@@ -0,0 +1,24 @@
1
+ url-pattern:
2
+ prefix-query: http://tspilot.oclc.org/{authority-id}/?query=oclcts.rootHeading+exact+%22{query}*%22&version=1.1&operation=searchRetrieve&recordSchema=http%3A%2F%2Fzthes.z3950.org%2Fxml%2F1.0%2F&maximumRecords=10&startRecord=1&resultSetTTL=300&recordPacking=xml&recordXPath=&sortKeys=
3
+ id-lookup: http://tspilot.oclc.org/{authority-id}/?query=dc.identifier+exact+%22{id}%22&version=1.1&operation=searchRetrieve&recordSchema=http%3A%2F%2Fzthes.z3950.org%2Fxml%2F1.0%2F&maximumRecords=10&startRecord=1&resultSetTTL=300&recordPacking=xml&recordXPath=&sortKeys=
4
+ authorities:
5
+ lcgft:
6
+ name: Library of Congress Genre/Form Terms for Library and Archival Materials (LCGFT)
7
+ bisacsh:
8
+ name: Book Industry Study Group Subject Headings (BISAC®). Used with permission.
9
+ fast:
10
+ name: Faceted Application of Subject Terminology (FAST subject headings)
11
+ gsafd:
12
+ name: Form and genre headings for fiction and drama
13
+ lcshac:
14
+ name: Library of Congress AC Subject Headings
15
+ lcsh:
16
+ name: Library of Congress Subject Headings
17
+ mesh:
18
+ name: Medical Subject Headings (MeSH®)
19
+ lctgm:
20
+ name: "Thesaurus for graphic materials: TGM I, Subject terms"
21
+ gmgpc:
22
+ name: "Thesaurus for graphic materials: TGM II, Genre terms"
23
+ meta:
24
+ name: Controlled Vocabulary Metadata
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ Qa::Engine.routes.draw do
2
+ match "/search/:vocab" => "terms#search", :via=>:get
3
+ match "/search/:vocab/:sub_authority" => "terms#search", :via=>:get
4
+ match "/terms/:vocab" => "terms#index", :via=>:get
5
+ match "/terms/:vocab/:sub_authority" => "terms#index", :via=>:get
6
+ match "/terms" => "terms#index", :via=>:get
7
+ end
@@ -0,0 +1,11 @@
1
+ class CreateQaSubjectMeshTerms < ActiveRecord::Migration
2
+ def change
3
+ create_table :qa_subject_mesh_terms do |t|
4
+ t.string :term_id
5
+ t.string :term
6
+ t.text :synonyms
7
+ end
8
+ add_index :qa_subject_mesh_terms, :term_id
9
+ add_index :qa_subject_mesh_terms, :term
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ class CreateQaMeshTree < ActiveRecord::Migration
2
+ def change
3
+ create_table :qa_mesh_trees do |t|
4
+ t.string :term_id
5
+ t.string :tree_number
6
+ end
7
+ add_index :qa_mesh_trees, :term_id
8
+ add_index :qa_mesh_trees, :tree_number
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ class AddTermLowerToQaSubjectMeshTerms < ActiveRecord::Migration
2
+ def change
3
+ add_column :qa_subject_mesh_terms, :term_lower, :string
4
+ add_index :qa_subject_mesh_terms, :term_lower
5
+ remove_index :qa_subject_mesh_terms, :term
6
+ end
7
+ end
data/lib/qa.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "qa/engine"
2
+ require "active_record"
3
+ require "activerecord-import"
4
+
5
+ module Qa
6
+ extend ActiveSupport::Autoload
7
+
8
+ autoload :Authorities
9
+ end
@@ -0,0 +1,12 @@
1
+ module Qa::Authorities
2
+ extend ActiveSupport::Autoload
3
+
4
+ autoload :Base
5
+ autoload :Lcsh
6
+ autoload :Loc
7
+ autoload :Local
8
+ autoload :Mesh
9
+ autoload :MeshTools
10
+ autoload :Oclcts
11
+ autoload :Tgnlang
12
+ end
@@ -0,0 +1,50 @@
1
+ require 'curl'
2
+
3
+ module Qa::Authorities
4
+ class Base
5
+ attr_accessor :response, :query_url, :raw_response
6
+
7
+ def initialize(q, sub_authority='')
8
+ # Implement Me and set self.query_url
9
+
10
+ if self.query_url == nil
11
+ raise Exception 'query url in your authorities lib is not set (implement in initialize)'
12
+ end
13
+
14
+ #Default implementation assumed query_url is set
15
+ http = Curl.get(self.query_url) do |http|
16
+ http.headers['Accept'] = 'application/json'
17
+ end
18
+
19
+ self.raw_response = JSON.parse(http.body_str)
20
+ end
21
+
22
+ def self.authority_valid?(sub_authority)
23
+ sub_authority == nil || sub_authorities.include?(sub_authority)
24
+ end
25
+
26
+ def self.sub_authorities
27
+ [] #Overwrite if you have sub_authorities
28
+ end
29
+
30
+ def parse_authority_response
31
+ # Overwrite me unless your raw response needs no parsing
32
+ self.response = self.raw_response
33
+
34
+ end
35
+
36
+ def get_full_record(id)
37
+ # implement me
38
+ {"id"=>id}.to_json
39
+ end
40
+
41
+ # Parse the result from LOC, and return an JSON array of terms that match the query.
42
+ def results
43
+ self.response.to_json
44
+ end
45
+
46
+ # TODO: there's other info in the self.response that might be worth making access to, such as
47
+ # RDF links, etc.
48
+
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ require 'uri'
2
+
3
+ module Qa::Authorities
4
+ class Lcsh < Qa::Authorities::Base
5
+
6
+ # Initialze the Lcsh class with a query and get the http response from LOC's server.
7
+ # This is set to a JSON object
8
+ def initialize(q, sub_authority='')
9
+ self.query_url= "http://id.loc.gov/authorities/suggest/?q=" + q
10
+
11
+ super
12
+ end
13
+
14
+ # Format response to the correct JSON structure
15
+ def parse_authority_response
16
+ self.response = build_response
17
+ end
18
+
19
+ def query
20
+ self.raw_response[0]
21
+ end
22
+
23
+ def suggestions
24
+ self.raw_response[1]
25
+ end
26
+
27
+ def urls_for_suggestions
28
+ self.raw_response[3]
29
+ end
30
+
31
+ private
32
+
33
+ def build_response a = Array.new
34
+ self.suggestions.each_index do |i|
35
+ a << {"id"=>get_id_from_url(urls_for_suggestions[i]), "label"=>suggestions[i]}
36
+ end
37
+ return a
38
+ end
39
+
40
+ def get_id_from_url url
41
+ uri = URI(url)
42
+ return uri.path.split(/\//).last
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,200 @@
1
+ require 'uri'
2
+
3
+ module Qa::Authorities
4
+ class Loc < Qa::Authorities::Base
5
+
6
+ # Initialze the Loc class with a query and get the http response from LOC's server.
7
+ # This is set to a JSON object
8
+ def initialize(q, sub_authority='')
9
+ q += '*'
10
+ authority_url = sub_authorityURL(sub_authority)
11
+ self.query_url = "http://id.loc.gov/search/?q=#{q}&q=#{authority_url}&format=json"
12
+
13
+ super
14
+ end
15
+
16
+ def sub_authorityURL(sub_authority)
17
+ vocab_base_url = 'cs%3Ahttp%3A%2F%2Fid.loc.gov%2Fvocabulary%2F'
18
+ authority_base_url = 'cs%3Ahttp%3A%2F%2Fid.loc.gov%2Fauthorities%2F'
19
+ datatype_base_url = 'cs%3Ahttp%3A%2F%2Fid.loc.gov%2Fdatatypes%2F'
20
+ vocab_preservation_base_url = 'cs%3Ahttp%3A%2F%2Fid.loc.gov%2Fvocabulary%2Fpreservation%2F'
21
+
22
+ case sub_authority
23
+ when 'subjects'
24
+ return authority_base_url + URI.escape(sub_authority)
25
+ when 'names'
26
+ return authority_base_url + URI.escape(sub_authority)
27
+ when 'classification'
28
+ return authority_base_url + URI.escape(sub_authority)
29
+ when 'childrensSubjects'
30
+ return authority_base_url + URI.escape(sub_authority)
31
+ when 'genreForms'
32
+ return authority_base_url + URI.escape(sub_authority)
33
+ when 'graphicMaterials'
34
+ return vocab_base_url + URI.escape(sub_authority)
35
+ when 'organizations'
36
+ return vocab_base_url + URI.escape(sub_authority)
37
+ when 'relators'
38
+ return vocab_base_url + URI.escape(sub_authority)
39
+ when 'countries'
40
+ return vocab_base_url + URI.escape(sub_authority)
41
+ when 'geographicAreas'
42
+ return vocab_base_url + URI.escape(sub_authority)
43
+ when 'languages'
44
+ return vocab_base_url + URI.escape(sub_authority)
45
+ when 'iso639-1'
46
+ return vocab_base_url + URI.escape(sub_authority)
47
+ when 'iso639-2'
48
+ return vocab_base_url + URI.escape(sub_authority)
49
+ when 'iso639-5'
50
+ return vocab_base_url + URI.escape(sub_authority)
51
+ when 'edtf'
52
+ return datatype_base_url + URI.escape(sub_authority)
53
+ when 'preservation'
54
+ return vocab_base_url + URI.escape(sub_authority)
55
+ when 'actionsGranted'
56
+ return vocab_base_url + URI.escape(sub_authority)
57
+ when 'agentType'
58
+ return vocab_base_url + URI.escape(sub_authority)
59
+ when 'contentLocationType'
60
+ return vocab_preservation_base_url + URI.escape(sub_authority)
61
+ when 'copyrightStatus'
62
+ return vocab_preservation_base_url + URI.escape(sub_authority)
63
+ when 'cryptographicHashFunctions'
64
+ return vocab_preservation_base_url + URI.escape(sub_authority)
65
+ when 'environmentCharacteristic'
66
+ return vocab_preservation_base_url + URI.escape(sub_authority)
67
+ when 'environmentPurpose'
68
+ return vocab_preservation_base_url + URI.escape(sub_authority)
69
+ when 'eventRelatedAgentRole'
70
+ return vocab_preservation_base_url + URI.escape(sub_authority)
71
+ when 'eventRelatedObjectRole'
72
+ return vocab_preservation_base_url + URI.escape(sub_authority)
73
+ when 'eventType'
74
+ return vocab_preservation_base_url + URI.escape(sub_authority)
75
+ when 'formatRegistryRole'
76
+ return vocab_preservation_base_url + URI.escape(sub_authority)
77
+ when 'hardwareType'
78
+ return vocab_preservation_base_url + URI.escape(sub_authority)
79
+ when 'inhibitorTarget'
80
+ return vocab_preservation_base_url + URI.escape(sub_authority)
81
+ when 'inhibitorType'
82
+ return vocab_preservation_base_url + URI.escape(sub_authority)
83
+ when 'objectCategory'
84
+ return vocab_preservation_base_url + URI.escape(sub_authority)
85
+ when 'preservationLevelRole'
86
+ return vocab_preservation_base_url + URI.escape(sub_authority)
87
+ when 'relationshipSubType'
88
+ return vocab_preservation_base_url + URI.escape(sub_authority)
89
+ when 'relationshipType'
90
+ return vocab_preservation_base_url + URI.escape(sub_authority)
91
+ when 'rightsBasis'
92
+ return vocab_preservation_base_url + URI.escape(sub_authority)
93
+ when 'rightsRelatedAgentRole'
94
+ return vocab_preservation_base_url + URI.escape(sub_authority)
95
+ when 'signatureEncoding'
96
+ return vocab_preservation_base_url + URI.escape(sub_authority)
97
+ when 'signatureMethod'
98
+ return vocab_preservation_base_url + URI.escape(sub_authority)
99
+ when 'softwareType'
100
+ return vocab_preservation_base_url + URI.escape(sub_authority)
101
+ when 'storageMedium'
102
+ return vocab_preservation_base_url + URI.escape(sub_authority)
103
+ else
104
+ return ''
105
+ end
106
+ end
107
+
108
+ def self.sub_authorities
109
+ ['iso639-2', 'subjects', 'names', 'classification', 'childrensSubjects', 'genreForms', 'graphicMaterials', 'organizations', 'relators', 'countries', 'geographicAreas', 'languages', 'iso639-5', 'edtf', 'preservation', 'actionsGranted', 'agentType', 'contentLocationType', 'copyrightStatus', 'cryptographicHashFunctions', 'environmentCharacteristic', 'environmentPurpose', 'eventRelatedAgentRole', 'eventRelatedObjectRole', 'eventType', 'formatRegistryRole', 'hardwareType', 'inhibitorTarget', 'inhibitorType', 'objectCategory', 'preservationLevelRole', 'relationshipSubType', 'relationshipType', 'rightsBasis', 'rightsRelatedAgentRole', 'signatureEncoding', 'signatureMethod', 'softwareType', 'storageMedium']
110
+ end
111
+
112
+
113
+ def parse_authority_response
114
+ result = []
115
+ self.raw_response.each do |single_response|
116
+ if single_response[0] == "atom:entry"
117
+ id = nil
118
+ label = ''
119
+ single_response.each do |result_part|
120
+ if(result_part[0] == 'atom:title')
121
+ label = result_part[2]
122
+ end
123
+
124
+ if(result_part[0] == 'atom:id')
125
+ id = result_part[2]
126
+ end
127
+
128
+ end
129
+
130
+ id ||= label
131
+ result << {"id"=>id, "label"=>label}
132
+
133
+ end
134
+ end
135
+ self.response = result
136
+ end
137
+
138
+ def get_full_record(id)
139
+ full_record = nil
140
+ parsed_result = {}
141
+ self.raw_response.each do |single_response|
142
+ if single_response[0] == "atom:entry"
143
+
144
+ single_response.each do |result_part|
145
+ if(result_part[0] == 'atom:title')
146
+ if id == result_part[2]
147
+ full_record = single_response
148
+ end
149
+ end
150
+
151
+ if(result_part[0] == 'atom:id')
152
+ if id == result_part[2]
153
+ full_record = single_response
154
+ end
155
+ end
156
+
157
+ end
158
+
159
+ end
160
+ end
161
+
162
+
163
+ if full_record != nil
164
+ full_record.each do |section|
165
+ if section.class == Array
166
+ label = section[0].split(':').last.to_s
167
+ case label
168
+ when 'title'
169
+ parsed_result[label] = section[2]
170
+ when 'link'
171
+ if section[1]['type'] != nil
172
+ parsed_result[label + "||#{section[1]['type']}"] = section[1]["href"]
173
+ else
174
+ parsed_result[label] = section[1]["href"]
175
+ end
176
+ when 'id'
177
+ parsed_result[label] = section[2]
178
+ when 'author'
179
+ author_list = []
180
+ #FIXME: Find example with two authors to better understand this data.
181
+ author_list << section[2][2]
182
+ parsed_result[label] = author_list
183
+ when 'updated'
184
+ parsed_result[label] = section[2]
185
+ when 'created'
186
+ parsed_result[label] = section[2]
187
+ end
188
+
189
+ end
190
+ end
191
+ else
192
+ raise Exception 'Lookup without using a result search first not implemented yet'
193
+ end
194
+
195
+ parsed_result
196
+
197
+ end
198
+
199
+ end
200
+ end
@@ -0,0 +1,69 @@
1
+ module Qa::Authorities
2
+ class Local < Qa::Authorities::Base
3
+
4
+ attr_accessor :response
5
+
6
+ def initialize(q, sub_authority)
7
+ begin
8
+ sub_authority_hash = YAML.load(File.read(File.join(Qa::Authorities::Local.sub_authorities_path, "#{sub_authority}.yml")))
9
+ rescue
10
+ sub_authority_hash = {}
11
+ end
12
+ @terms = normalize_terms(sub_authority_hash.fetch(:terms, []))
13
+ if q.blank?
14
+ self.response = @terms
15
+ else
16
+ sub_terms = []
17
+ @terms.each { |term| sub_terms << term if term[:term].downcase.start_with?(q.downcase) }
18
+ self.response = sub_terms
19
+ end
20
+ end
21
+
22
+ def normalize_terms(terms)
23
+ normalized_terms = []
24
+ terms.each do |term|
25
+ if term.is_a? String
26
+ normalized_terms << { :id => term, :term => term }
27
+ else
28
+ term[:id] = term[:id] || term[:term]
29
+ normalized_terms << term
30
+ end
31
+ end
32
+ normalized_terms
33
+ end
34
+
35
+ def parse_authority_response
36
+ parsed_response = []
37
+ self.response.each do |res|
38
+ parsed_response << { :id => res[:id], :label => res[:term] }
39
+ end
40
+ self.response = parsed_response
41
+ end
42
+
43
+ def get_full_record(id)
44
+ target_term = {}
45
+ @terms.each do |term|
46
+ if term[:id] == id
47
+ target_term = term
48
+ end
49
+ end
50
+ target_term.to_json
51
+ end
52
+
53
+ def self.sub_authorities_path
54
+ config_path = AUTHORITIES_CONFIG[:local_path]
55
+ if config_path.starts_with?(File::Separator)
56
+ config_path
57
+ else
58
+ File.join(Rails.root, config_path)
59
+ end
60
+ end
61
+
62
+ def self.sub_authorities
63
+ sub_auths = []
64
+ Dir.foreach(Qa::Authorities::Local.sub_authorities_path) { |file| sub_auths << File.basename(file, File.extname(file)) }
65
+ sub_auths
66
+ end
67
+
68
+ end
69
+ end