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
data/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ##########################################################################
2
+ # Copyright 2013 Rock and Roll Hall of Fame and Museum
3
+ # Additional copyright may be held by others, as reflected in the commit log
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
data/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # Questioning Authority
2
+
3
+ [![Build Status](https://travis-ci.org/projecthydra/questioning_authority.png?branch=master)](https://travis-ci.org/projecthydra/questioning_authority)
4
+
5
+ You should question your authorities.
6
+
7
+ ## What does this do?
8
+
9
+ Provides a set of uniform RESTful routes to query any controlled vocabulary of set of authority terms.
10
+ Results are returned in JSON, to be used in the context of a Rails application. Primary examples would
11
+ include providing auto-complete functionality via Javascript or populating a dropdown menu with a set
12
+ of terms.
13
+
14
+ ## How does it work?
15
+
16
+ Authorities are defined as classes, each implementing a set of methods allowing a controller to return
17
+ results from a given vocabulary in the JSON format. The controller does three things:
18
+
19
+ * provide a list of all terms (if allowed by the class)
20
+ * return a set of terms matching a given query
21
+ * return the complete information for a specific term given its identifier
22
+
23
+ ### Sub-Authorities
24
+
25
+ Some authorities, such as Library of Congress, allow sub-authorities which is an additional parameter that
26
+ further defines the kind of authority to use with the context of a larger one.
27
+
28
+ ## How do use this?
29
+
30
+ Add the gem to your Gemfile
31
+
32
+ gem 'qa'
33
+
34
+ Add the engine to your config/routes.rb file
35
+
36
+ mount Qa::Engine => '/qa'
37
+
38
+ Start questioning your authorities!
39
+
40
+ ### Examples
41
+
42
+ Return a complete list of terms:
43
+
44
+ /qa/terms/:vocab
45
+ /qa/terms/:vocab/:sub_authority
46
+
47
+ Return a set of terms matching a given query
48
+
49
+ /qa/search/:vocab?q=search_term
50
+ /qa/search/:vocab/:sub_authority?q=search_term
51
+
52
+ Return the complete information for a specific term given its identifier
53
+
54
+ /qa/show/:vocab/:id
55
+ /qa/show/:vocab/:sub_authority/:id
56
+
57
+ ### JSON Results
58
+
59
+ Results are returned in JSON in this format:
60
+
61
+ [
62
+ {"id" : "subject_id_1", "label" : "First labels"},
63
+ {"id" : "subject_id_2", "label" : "Printing labels"},
64
+ {"id" : "", "label" : "This term has no id number"},
65
+ {"id" : "", "label" : "Neither does this"}
66
+ ]
67
+
68
+ Results for specific terms may vary according to the term. For example:
69
+
70
+ /qa/show/mesh/D000001
71
+
72
+ Might return:
73
+
74
+ { "id" : "D000001",
75
+ "label" : "Calcimycin",
76
+ "tree_numbers" : ["D03.438.221.173"],
77
+ "synonyms" : ["A-23187", "A23187", "Antibiotic A23187", "A 23187", "A23187, Antibiotic"]
78
+ }
79
+
80
+ This is due to the varying nature of each authority source. However, results for multiple terms, such as a search, we
81
+ should always use the above id and label structure to ensure interoperability at the GUI level.
82
+
83
+ # Authority Sources information
84
+
85
+ ### Library of Congress (example uses language):
86
+
87
+ Base url: http://id.loc.gov/search/
88
+
89
+ Example search (html): http://id.loc.gov/search/?q=eng&q=cs%3Ahttp%3A%2F%2Fid.loc.gov%2Fvocabulary%2Fiso639-2
90
+
91
+ Example search (json): http://id.loc.gov/search/?q=eng&q=cs%3Ahttp%3A%2F%2Fid.loc.gov%2Fvocabulary%2Fiso639-2&format=json
92
+
93
+ Example search (json, second page): http://id.loc.gov/search/?q=a*%20cs:http://id.loc.gov/vocabulary/countries&start=21&format=json
94
+
95
+ # Local Authority Files
96
+
97
+ ### YAML file of terms
98
+
99
+ #### Location and Naming Convention
100
+
101
+ Local authorities are specified in YAML files, one for each sub-authority. By default, local
102
+ authority YAML files are located in config/authorities/ . This location can be changed by editing
103
+ the :local_path entry in config/authorities.yml. Relative paths are assumed to be relative to
104
+ Rails.root.
105
+
106
+ Local authority YAML files are named for the sub-authority they represent. For example, a YAML file
107
+ for the "states" sub-authority would be named states.yml.
108
+
109
+ #### Supported formats
110
+
111
+ ##### List of terms
112
+
113
+ :terms:
114
+ - Term 1
115
+ - Term 2
116
+
117
+ ##### List of id and term keys and, optionally, active key
118
+
119
+ :terms:
120
+ - :id: id1
121
+ :term: Term 1
122
+ :active: true
123
+ - :id: id2
124
+ :term: Term 2
125
+ :active: false
126
+
127
+ # Medical Subject Headings (MeSH)
128
+
129
+ Provides autocompletion of [MeSH terms](http://www.nlm.nih.gov/mesh/introduction.html). This
130
+ implementation is simple, and only provides *descriptors* and does not implement *qualifiers* (in
131
+ the technical MeSH sense of these terms). The terms are stored in a local database, which is then
132
+ queried to provide the suggestions.
133
+
134
+ ## Loading Terms
135
+
136
+ To import the mesh terms into the local database, first download the MeSH descriptor dump in ASCII
137
+ format (see [http://www.nlm.nih.gov/mesh/filelist.html][]). Once you have this file, the rake task
138
+ `mesh:import` will load the entire file of terms into the database. It does not do an update (yet!).
139
+
140
+ MESH_FILE=path/to/mesh.txt rake mesh:import
141
+
142
+ This may take a few minutes to finish.
143
+
144
+ # TODOs
145
+
146
+ * Provide show method to TermsController to return individual terms
147
+
148
+ check the issue list for more...
149
+
150
+ # Developer Notes
151
+
152
+ To develop this gem, clone the repository, then run:
153
+
154
+ bundle install
155
+ rake
156
+
157
+ This will install the gems, create a dummy application under spec/internal and run the tests. After you've made changes, remove the entire spec/internal
158
+ directory so that further tests and run against a new dummy application.
159
+
160
+ # Authors
161
+
162
+ * [Stephen Anderson](https://github.com/scande3)
163
+ * [Don Brower](https://github.com/dbrower)
164
+ * [Jim Coble](https://github.com/coblej)
165
+ * [Mike Durbin](https://github.com/mikedurbin)
166
+ * [Randall Floyd](https://github.com/stormfin)
167
+ * [Eric James](https://github.com/yulgit1)
168
+ * [Mike Stroming](https://github.com/mstroming)
169
+ * [Adam Wead](https://github.com/awead)
170
+
171
+ ### Special thanks to...
172
+
173
+ [Jeremy Friesen](https://github.com/jeremyf) who gave us the name for our gem.
174
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ Dir.glob('tasks/*.rake').each { |r| import r }
5
+
6
+ ENV["RAILS_ROOT"] ||= 'spec/internal'
7
+
8
+ require 'rspec/core/rake_task'
9
+
10
+ task :default => [:spec]
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,4 @@
1
+ module Qa
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,76 @@
1
+ # This controller is used for all requests to all authorities. It will verify params and figure out
2
+ # which class to instantiate based on the "vocab" param. All the authotirty classes inherit from a
3
+ # super class so they implement the same methods.
4
+
5
+ class Qa::TermsController < ApplicationController
6
+
7
+ before_action :check_search_params, only:[:search]
8
+ before_action :check_vocab_param, :check_authority, :check_sub_authority
9
+
10
+ #search that returns all results
11
+ def index
12
+ #initialize the authority and run the search. if there's a sub-authority and it's valid, include that param
13
+ @authority = authority_class.constantize.new(params[:q], params[:sub_authority])
14
+
15
+ #parse the results
16
+ @authority.parse_authority_response
17
+
18
+ respond_to do |format|
19
+ format.html { render :layout => false, :text => @authority.results }
20
+ format.json { render :layout => false, :text => @authority.results }
21
+ format.js { render :layout => false, :text => @authority.results }
22
+ end
23
+ end
24
+
25
+ def search
26
+
27
+ #convert wildcard to be URI encoded
28
+ params[:q].gsub!("*", "%2A")
29
+
30
+ #initialize the authority and run the search. if there's a sub-authority and it's valid, include that param
31
+ @authority = authority_class.constantize.new(params[:q], params[:sub_authority])
32
+
33
+ #parse the results
34
+ @authority.parse_authority_response
35
+
36
+ respond_to do |format|
37
+ format.html { render :layout => false, :text => @authority.results }
38
+ format.json { render :layout => false, :text => @authority.results }
39
+ format.js { render :layout => false, :text => @authority.results }
40
+ end
41
+
42
+ end
43
+
44
+ def check_vocab_param
45
+ unless params[:vocab].present?
46
+ redirect_to :status => 400
47
+ end
48
+ end
49
+
50
+ def check_search_params
51
+ unless params[:q].present? && params[:vocab].present?
52
+ redirect_to :status => 400
53
+ end
54
+ end
55
+
56
+ def check_authority
57
+ begin
58
+ authority_class.constantize
59
+ rescue
60
+ redirect_to :status => 400
61
+ end
62
+ end
63
+
64
+ def check_sub_authority
65
+ unless params[:sub_authority].nil?
66
+ redirect_to :status => 400 unless authority_class.constantize.authority_valid?(params[:sub_authority])
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def authority_class
73
+ "Qa::Authorities::"+params[:vocab].capitalize
74
+ end
75
+
76
+ end
@@ -0,0 +1,2 @@
1
+ module Qa::ApplicationHelper
2
+ end
@@ -0,0 +1,37 @@
1
+ class Qa::MeshTree < ActiveRecord::Base
2
+ belongs_to :subject_mesh_term , :foreign_key => "term_id"
3
+
4
+ def self.classify_all_trees
5
+ MeshTreeStructure.find_each do |mts|
6
+ mts.classify_tree!
7
+ end
8
+ end
9
+
10
+ def eval_tree_path
11
+ trees = read_attribute(:eval_tree_path) || write_attribute(:eval_tree_path, "")
12
+ if trees
13
+ trees.split("|")
14
+ else
15
+ []
16
+ end
17
+ end
18
+
19
+ def classify_tree
20
+ tree_levels = initial_segements_of(tree_structure)
21
+ tree_levels.map &method(:lookup_tree_term)
22
+ end
23
+
24
+ def classify_tree!
25
+ unless classify_tree.empty?
26
+ tree_path = classify_tree.join('|')
27
+ puts "After Join #{tree_path.inspect}"
28
+ update_attribute(:eval_tree_path, tree_path)
29
+ end
30
+ end
31
+
32
+ # given a tree id, return the main subject term
33
+ # e.g. 'C03.752.530' returns 'Malaria'
34
+ def lookup_tree_term(tree_id)
35
+ self.class.get_term(tree_id).first.term
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ class Qa::SubjectMeshTerm < ActiveRecord::Base
2
+ has_many :mesh_trees, :foreign_key => "term_id"
3
+
4
+ def self.from_tree_number(tree_id)
5
+ Qa::SubjectMeshTerm.joins('INNER JOIN qa_mesh_trees ON qa_subject_mesh_terms.term_id = qa_mesh_trees.term_id').where('qa_mesh_trees.tree_number = ?', tree_id)
6
+ end
7
+
8
+ def trees
9
+ Qa::MeshTree.where(term_id: self.term_id).map { |t| t.tree_number }
10
+ end
11
+
12
+ def synonyms
13
+ s = read_attribute(:synonyms)
14
+ s.nil? ? [] : s.split("|")
15
+ end
16
+
17
+ def synonyms=(syn_list)
18
+ write_attribute(:synonyms,
19
+ if syn_list.respond_to?(:join)
20
+ syn_list.join('|')
21
+ else
22
+ syn_list
23
+ end)
24
+ end
25
+
26
+ def parents
27
+ t = self.trees
28
+ t.map { |tn| initial_segements_of(tn) }.flatten.uniq.map { |tn| Qa::SubjectMeshTerm.from_tree_number(tn) }
29
+ end
30
+
31
+ private
32
+ # Return all of the intial segements of our tree number,
33
+ # from most general to most specific
34
+ # e.g. 'D03.456.23.789' returns ['D03', 'D03.456', 'D03.456.23', 'D03.456.23.789']
35
+ def initial_segements_of(s)
36
+ result = []
37
+ loop do
38
+ result << s
39
+ s = s.rpartition('.').first
40
+ break if s.empty?
41
+ end
42
+ result.reverse
43
+ end
44
+
45
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Qa</title>
5
+ <%= stylesheet_link_tag "qa/application", media: "all" %>
6
+ <%= javascript_include_tag "qa/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1 @@
1
+ :local_path: "config/authorities"
@@ -0,0 +1,101 @@
1
+ :terms:
2
+ - :id: AL
3
+ :term: Alabama
4
+ - :id: AK
5
+ :term: Alaska
6
+ - :id: AZ
7
+ :term: Arizona
8
+ - :id: AR
9
+ :term: Arkansas
10
+ - :id: CA
11
+ :term: California
12
+ - :id: CO
13
+ :term: Colorado
14
+ - :id: CT
15
+ :term: Connecticut
16
+ - :id: DE
17
+ :term: Delaware
18
+ - :id: FL
19
+ :term: Florida
20
+ - :id: GA
21
+ :term: Georgia
22
+ - :id: HI
23
+ :term: Hawaii
24
+ - :id: ID
25
+ :term: Idaho
26
+ - :id: IL
27
+ :term: Illinois
28
+ - :id: IN
29
+ :term: Indiana
30
+ - :id: IA
31
+ :term: Iowa
32
+ - :id: KS
33
+ :term: Kansas
34
+ - :id: KY
35
+ :term: Kentucky
36
+ - :id: LA
37
+ :term: Louisana
38
+ - :id: ME
39
+ :term: Maine
40
+ - :id: MD
41
+ :term: Maryland
42
+ - :id: MA
43
+ :term: Massachusetts
44
+ - :id: MI
45
+ :term: Michigan
46
+ - :id: MN
47
+ :term: Minnesota
48
+ - :id: MS
49
+ :term: Mississippi
50
+ - :id: MO
51
+ :term: Missouri
52
+ - :id: MT
53
+ :term: Montana
54
+ - :id: NE
55
+ :term: Nebraska
56
+ - :id: NV
57
+ :term: Nevada
58
+ - :id: NH
59
+ :term: New Hampshire
60
+ - :id: NJ
61
+ :term: New Jersey
62
+ - :id: NM
63
+ :term: New Mexico
64
+ - :id: NY
65
+ :term: New York
66
+ - :id: NC
67
+ :term: North Carolina
68
+ - :id: ND
69
+ :term: North Dakota
70
+ - :id: OH
71
+ :term: Ohio
72
+ - :id: OK
73
+ :term: Oklahoma
74
+ - :id: OR
75
+ :term: Oregon
76
+ - :id: PA
77
+ :term: Pennsylvania
78
+ - :id: RI
79
+ :term: Rhode Island
80
+ - :id: SC
81
+ :term: Sourth Carolina
82
+ - :id: SD
83
+ :term: South Dakota
84
+ - :id: TN
85
+ :term: Tennessee
86
+ - :id: TX
87
+ :term: Texas
88
+ - :id: UT
89
+ :term: Utah
90
+ - :id: VT
91
+ :term: Vermont
92
+ - :id: VA
93
+ :term: Virginia
94
+ - :id: WA
95
+ :term: Washington
96
+ - :id: WV
97
+ :term: West Virginia
98
+ - :id: WI
99
+ :term: Wisconsin
100
+ - :id: WY
101
+ :term: Wyoming