qa 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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