health-data-standards 3.5.3 → 3.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -3
  3. data/README.md +10 -2
  4. data/lib/health-data-standards.rb +5 -28
  5. data/lib/health-data-standards/export/cat_1.rb +3 -2
  6. data/lib/health-data-standards/export/cat_1_r2.rb +11 -0
  7. data/lib/health-data-standards/ext/node.rb +1 -2
  8. data/lib/health-data-standards/import/bulk_record_importer.rb +4 -4
  9. data/lib/health-data-standards/import/bundle/importer.rb +62 -0
  10. data/lib/health-data-standards/import/c32/insurance_provider_importer.rb +10 -8
  11. data/lib/health-data-standards/import/cat1/patient_importer.rb +3 -3
  12. data/lib/health-data-standards/import/cat1/procedure_importer.rb +42 -0
  13. data/lib/health-data-standards/import/cda/medication_importer.rb +11 -11
  14. data/lib/health-data-standards/import/cda/section_importer.rb +13 -7
  15. data/lib/health-data-standards/models/cqm/aggregate_objects.rb +5 -1
  16. data/lib/health-data-standards/models/cqm/bundle.rb +5 -3
  17. data/lib/health-data-standards/models/cqm/measure.rb +1 -1
  18. data/lib/health-data-standards/models/entry.rb +5 -1
  19. data/lib/health-data-standards/models/record.rb +10 -0
  20. data/lib/health-data-standards/models/reference.rb +23 -0
  21. data/lib/health-data-standards/models/svs/value_set.rb +4 -3
  22. data/lib/health-data-standards/railtie.rb +1 -1
  23. data/lib/health-data-standards/tasks.rb +1 -0
  24. data/lib/health-data-standards/tasks/bundle.rake +84 -2
  25. data/lib/health-data-standards/util/vs_api.rb +40 -7
  26. data/lib/health-data-standards/validate/base_validator.rb +23 -0
  27. data/lib/health-data-standards/validate/data_validator.rb +85 -0
  28. data/lib/health-data-standards/validate/measure_validator.rb +127 -0
  29. data/lib/health-data-standards/validate/performance_rate_validator.rb +94 -0
  30. data/lib/health-data-standards/validate/reported_result_extractor.rb +170 -0
  31. data/lib/health-data-standards/validate/schema_validator.rb +24 -0
  32. data/lib/health-data-standards/validate/schematron/c_processor.rb +28 -0
  33. data/lib/health-data-standards/validate/schematron/java_processor.rb +93 -0
  34. data/lib/health-data-standards/validate/schematron_validator.rb +34 -0
  35. data/lib/health-data-standards/validate/validation_error.rb +10 -0
  36. data/lib/health-data-standards/validate/validators.rb +80 -0
  37. data/lib/hqmf-generator/fulfills.xml.erb +7 -0
  38. data/lib/hqmf-generator/hqmf-generator.rb +8 -3
  39. data/lib/hqmf-model/data_criteria.rb +3 -0
  40. data/lib/hqmf-model/types.rb +29 -0
  41. data/lib/hqmf-parser/2.0/data_criteria.rb +7 -0
  42. data/lib/hqmf-parser/2.0/types.rb +24 -0
  43. data/resources/schema/infrastructure/cda/CDA_SDTC.xsd +44 -0
  44. data/resources/schema/infrastructure/cda/POCD_MT000040_SDTC.xsd +1500 -0
  45. data/resources/schema/infrastructure/cda/SDTC.xsd +210 -0
  46. data/resources/schema/processable/coreschemas/NarrativeBlock.xsd +557 -0
  47. data/resources/schema/processable/coreschemas/datatypes-base_SDTC.xsd +1850 -0
  48. data/resources/schema/processable/coreschemas/datatypes.xsd +1375 -0
  49. data/resources/schema/processable/coreschemas/infrastructureRoot.xsd +27 -0
  50. data/resources/schema/processable/coreschemas/voc.xsd +2124 -0
  51. data/resources/schematron/iso-schematron-xslt1/ExtractSchFromRNG.xsl +75 -0
  52. data/resources/schematron/iso-schematron-xslt1/ExtractSchFromXSD.xsl +77 -0
  53. data/resources/schematron/iso-schematron-xslt1/iso_abstract_expand.xsl +297 -0
  54. data/resources/schematron/iso-schematron-xslt1/iso_dsdl_include.xsl +1509 -0
  55. data/resources/schematron/iso-schematron-xslt1/iso_schematron_message.xsl +55 -0
  56. data/resources/schematron/iso-schematron-xslt1/iso_schematron_skeleton_for_xslt1.xsl +1844 -0
  57. data/resources/schematron/iso-schematron-xslt1/iso_svrl_for_xslt1.xsl +605 -0
  58. data/resources/schematron/iso-schematron-xslt1/readme.txt +101 -0
  59. data/resources/schematron/iso-schematron-xslt1/schematron-skeleton-api.htm +723 -0
  60. data/resources/schematron/iso-schematron-xslt2/ExtractSchFromRNG-2.xsl +75 -0
  61. data/resources/schematron/iso-schematron-xslt2/ExtractSchFromXSD-2.xsl +77 -0
  62. data/resources/schematron/iso-schematron-xslt2/iso_abstract_expand.xsl +297 -0
  63. data/resources/schematron/iso-schematron-xslt2/iso_dsdl_include.xsl +1508 -0
  64. data/resources/schematron/iso-schematron-xslt2/iso_schematron_message_xslt2.xsl +55 -0
  65. data/resources/schematron/iso-schematron-xslt2/iso_schematron_skeleton_for_saxon.xsl +2299 -0
  66. data/resources/schematron/iso-schematron-xslt2/iso_svrl_for_xslt2.xsl +684 -0
  67. data/resources/schematron/iso-schematron-xslt2/readme.txt +100 -0
  68. data/resources/schematron/iso-schematron-xslt2/sch-messages-cs.xhtml +56 -0
  69. data/resources/schematron/iso-schematron-xslt2/sch-messages-de.xhtml +57 -0
  70. data/resources/schematron/iso-schematron-xslt2/sch-messages-en.xhtml +57 -0
  71. data/resources/schematron/iso-schematron-xslt2/sch-messages-fr.xhtml +54 -0
  72. data/resources/schematron/iso-schematron-xslt2/sch-messages-nl.xhtml +58 -0
  73. data/resources/schematron/iso-schematron-xslt2/schematron-skeleton-api.htm +723 -0
  74. data/resources/schematron/qrda/cat_1/CDAR2_QRDA_I_R1_D3_2015MAY_Schematron.sch +4676 -0
  75. data/resources/schematron/qrda/cat_1/voc.xml +1177 -0
  76. data/resources/schematron/qrda/cat_1_r2/QRDA Category I Release 2.sch +4069 -0
  77. data/resources/schematron/qrda/cat_1_r2/voc.xml +1065 -0
  78. data/resources/schematron/qrda/cat_3/QRDA Category III.sch +675 -0
  79. data/resources/schematron/qrda/cat_3/voc.xml +21 -0
  80. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.26.cat1.erb +18 -0
  81. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.32.cat1.erb +4 -0
  82. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.38.cat1.erb +5 -1
  83. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.4.cat1.erb +1 -0
  84. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.64.cat1.erb +20 -0
  85. data/templates/cat1/_fulfills.cat1.erb +14 -0
  86. data/templates/cat1/_organization.cat1.erb +2 -1
  87. data/templates/cat1/_patient_data.cat1.erb +1 -1
  88. data/templates/cat1/show.cat1.erb +5 -4
  89. data/templates/cat3/_performance_rate.cat3.erb +5 -1
  90. data/templates/cat3/show.cat3.erb +1 -1
  91. metadata +128 -109
  92. data/lib/health-data-standards/export/ccr.rb +0 -417
  93. data/lib/health-data-standards/export/green_c32/entry.rb +0 -18
  94. data/lib/health-data-standards/export/green_c32/export_generator.rb +0 -23
  95. data/lib/health-data-standards/export/green_c32/record.rb +0 -18
  96. data/lib/health-data-standards/export/helper/gc32_view_helper.rb +0 -39
  97. data/lib/health-data-standards/import/ccr/patient_importer.rb +0 -238
  98. data/lib/health-data-standards/import/ccr/product_importer.rb +0 -60
  99. data/lib/health-data-standards/import/ccr/provider_importer.rb +0 -49
  100. data/lib/health-data-standards/import/ccr/result_importer.rb +0 -49
  101. data/lib/health-data-standards/import/ccr/section_importer.rb +0 -135
  102. data/lib/health-data-standards/import/ccr/simple_importer.rb +0 -30
  103. data/lib/health-data-standards/import/green_c32/advance_directive_importer.rb +0 -14
  104. data/lib/health-data-standards/import/green_c32/allergy_importer.rb +0 -20
  105. data/lib/health-data-standards/import/green_c32/care_goal_importer.rb +0 -26
  106. data/lib/health-data-standards/import/green_c32/condition_importer.rb +0 -38
  107. data/lib/health-data-standards/import/green_c32/encounter_importer.rb +0 -33
  108. data/lib/health-data-standards/import/green_c32/immunization_importer.rb +0 -23
  109. data/lib/health-data-standards/import/green_c32/medical_equipment_importer.rb +0 -24
  110. data/lib/health-data-standards/import/green_c32/medication_importer.rb +0 -68
  111. data/lib/health-data-standards/import/green_c32/patient_importer.rb +0 -14
  112. data/lib/health-data-standards/import/green_c32/procedure_importer.rb +0 -27
  113. data/lib/health-data-standards/import/green_c32/result_importer.rb +0 -43
  114. data/lib/health-data-standards/import/green_c32/section_importer.rb +0 -186
  115. data/lib/health-data-standards/import/green_c32/social_history_importer.rb +0 -13
  116. data/lib/health-data-standards/import/green_c32/support_importer.rb +0 -22
  117. data/lib/health-data-standards/import/green_c32/vital_sign_importer.rb +0 -21
  118. data/templates/gc32/_address.gc32.erb +0 -9
  119. data/templates/gc32/_advance_directive.gc32.erb +0 -5
  120. data/templates/gc32/_allergy.gc32.erb +0 -12
  121. data/templates/gc32/_care_goal.gc32.erb +0 -8
  122. data/templates/gc32/_condition.gc32.erb +0 -10
  123. data/templates/gc32/_encounter.gc32.erb +0 -28
  124. data/templates/gc32/_entry.gc32.erb +0 -3
  125. data/templates/gc32/_entry_attributes.gc32.erb +0 -10
  126. data/templates/gc32/_immunization.gc32.erb +0 -9
  127. data/templates/gc32/_insurance_provider.gc32.erb +0 -28
  128. data/templates/gc32/_medical_equipment.gc32.erb +0 -6
  129. data/templates/gc32/_medication.gc32.erb +0 -91
  130. data/templates/gc32/_name.gc32.erb +0 -11
  131. data/templates/gc32/_organization.gc32.erb +0 -10
  132. data/templates/gc32/_person_attributes.gc32.erb +0 -7
  133. data/templates/gc32/_procedure.gc32.erb +0 -9
  134. data/templates/gc32/_provider.gc32.erb +0 -9
  135. data/templates/gc32/_result.gc32.erb +0 -12
  136. data/templates/gc32/_social_history.gc32.erb +0 -6
  137. data/templates/gc32/_support.gc32.erb +0 -15
  138. data/templates/gc32/_telecom.gc32.erb +0 -1
  139. data/templates/gc32/_vital_sign.gc32.erb +0 -4
  140. data/templates/gc32/record.gc32.erb +0 -97
@@ -45,7 +45,7 @@ module HealthDataStandards
45
45
  extract_codes(entry_element, entry)
46
46
  extract_dates(entry_element, entry)
47
47
  if @value_xpath
48
- extract_value(entry_element, entry)
48
+ extract_values(entry_element, entry)
49
49
  end
50
50
  entry.description = entry_element.at_xpath("./cda:text").try("text")
51
51
  if @status_xpath
@@ -100,21 +100,26 @@ module HealthDataStandards
100
100
 
101
101
  def extract_dates(parent_element, entry, element_name="effectiveTime")
102
102
  if parent_element.at_xpath("cda:#{element_name}/@value")
103
- entry.time = HL7Helper.timestamp_to_integer(parent_element.at_xpath("cda:#{element_name}")['value'])
103
+ entry[:time] = HL7Helper.timestamp_to_integer(parent_element.at_xpath("cda:#{element_name}")['value'])
104
104
  end
105
105
  if parent_element.at_xpath("cda:#{element_name}/cda:low")
106
- entry.start_time = HL7Helper.timestamp_to_integer(parent_element.at_xpath("cda:#{element_name}/cda:low")['value'])
106
+ entry[:start_time] = HL7Helper.timestamp_to_integer(parent_element.at_xpath("cda:#{element_name}/cda:low")['value'])
107
107
  end
108
108
  if parent_element.at_xpath("cda:#{element_name}/cda:high")
109
- entry.end_time = HL7Helper.timestamp_to_integer(parent_element.at_xpath("cda:#{element_name}/cda:high")['value'])
109
+ entry[:end_time] = HL7Helper.timestamp_to_integer(parent_element.at_xpath("cda:#{element_name}/cda:high")['value'])
110
110
  end
111
111
  if parent_element.at_xpath("cda:#{element_name}/cda:center")
112
- entry.time = HL7Helper.timestamp_to_integer(parent_element.at_xpath("cda:#{element_name}/cda:center")['value'])
112
+ entry[:time] = HL7Helper.timestamp_to_integer(parent_element.at_xpath("cda:#{element_name}/cda:center")['value'])
113
113
  end
114
114
  end
115
115
 
116
- def extract_value(parent_element, entry)
117
- value_element = parent_element.at_xpath(@value_xpath)
116
+ def extract_values(parent_element, entry)
117
+ parent_element.xpath(@value_xpath).each do |elem|
118
+ extract_value(parent_element, elem, entry)
119
+ end
120
+ end
121
+
122
+ def extract_value(parent_element, value_element, entry)
118
123
  if value_element
119
124
  value = value_element['value']
120
125
  if value.present?
@@ -123,6 +128,7 @@ module HealthDataStandards
123
128
  elsif value_element['code'].present?
124
129
  crv = CodedResultValue.new
125
130
  add_code_if_present(value_element, crv)
131
+ extract_dates(parent_element, crv)
126
132
  entry.values << crv
127
133
  else
128
134
  value = value_element.text
@@ -79,7 +79,11 @@ module HealthDataStandards
79
79
  attr_accessor :populations
80
80
  def performance_rate
81
81
  numerator_count.to_f /
82
- (denominator_count - denominator_exclusions_count - denominator_exceptions_count)
82
+ (performance_rate_denominator)
83
+ end
84
+
85
+ def performance_rate_denominator
86
+ denominator_count - denominator_exclusions_count - denominator_exceptions_count
83
87
  end
84
88
 
85
89
  def is_cv?
@@ -19,6 +19,8 @@ module HealthDataStandards
19
19
 
20
20
  validates_presence_of :version
21
21
 
22
+ has_many :value_sets, class_name: "HealthDataStandards::SVS::ValueSet", inverse_of: :bundle
23
+
22
24
  scope :active, -> {where(active: true)}
23
25
 
24
26
  def self.latest_bundle_id
@@ -33,9 +35,9 @@ module HealthDataStandards
33
35
  Record.where(bundle_id: self._id, test_id: nil).order_by([["last", :asc]])
34
36
  end
35
37
 
36
- def value_sets
37
- HealthDataStandards::SVS::ValueSet.in(bundle_id: self.id)
38
- end
38
+ # def value_sets
39
+ # HealthDataStandards::SVS::ValueSet.in(bundle_id: self.id)
40
+ # end
39
41
 
40
42
  def delete
41
43
  self.measures.destroy
@@ -47,7 +47,7 @@ module HealthDataStandards
47
47
  index category: 1
48
48
  index sub_id: 1
49
49
  index _id: 1, sub_id: 1
50
- index bundle_id: 1
50
+ index({bundle_id: 1, hqmf_id: 1, sub_id: 1})
51
51
 
52
52
  validates_presence_of :id
53
53
  validates_presence_of :name
@@ -9,7 +9,7 @@ class Entry
9
9
  embedded_in :record
10
10
  embeds_one :cda_identifier, class_name: "CDAIdentifier", as: :cda_identifiable
11
11
  embeds_many :values, class_name: "ResultValue"
12
-
12
+ embeds_many :references
13
13
  field :description, type: String
14
14
  field :specifics, type: String
15
15
  field :time, type: Integer
@@ -27,6 +27,10 @@ class Entry
27
27
  attr_protected :created_at
28
28
  attr_protected :updated_at
29
29
 
30
+ def add_reference(entry, type)
31
+ references.build(type: type, referenced_type: entry.class, referenced_id: entry.id)
32
+ end
33
+
30
34
  def times_to_s(nil_string='UNK')
31
35
  if start_time.present? || end_time.present?
32
36
  start_string = start_time ? Entry.time_to_s(start_time) : nil_string
@@ -25,6 +25,9 @@ class Record
25
25
  # HITSP C32
26
26
 
27
27
  index "last" => 1
28
+ index medical_record_number: 1
29
+ index test_id: 1
30
+ index bundle_id: 1
28
31
  embeds_many :allergies
29
32
  embeds_many :care_goals, class_name: "Entry" # This can be any number of different entry types
30
33
  embeds_many :conditions
@@ -103,6 +106,9 @@ class Record
103
106
  # based on clinical content
104
107
  def dedup_section_ignoring_content!(section)
105
108
  unique_entries = self.send(section).uniq do |entry|
109
+ entry.references.each do |ref|
110
+ ref.resolve_referenced_id
111
+ end
106
112
  entry.identifier
107
113
  end
108
114
  self.send("#{section}=", unique_entries)
@@ -110,12 +116,16 @@ class Record
110
116
  def dedup_section_merging_codes_and_values!(section)
111
117
  unique_entries = {}
112
118
  self.send(section).each do |entry|
119
+ entry.references.each do |ref|
120
+ ref.resolve_referenced_id
121
+ end
113
122
  if unique_entries[entry.identifier]
114
123
  unique_entries[entry.identifier].codes = unique_entries[entry.identifier].codes.deep_merge(entry.codes){ |key, old, new| Array.wrap(old) + Array.wrap(new) }
115
124
  unique_entries[entry.identifier].values.concat(entry.values)
116
125
  else
117
126
  unique_entries[entry.identifier] = entry
118
127
  end
128
+
119
129
  end
120
130
  self.send("#{section}=", unique_entries.values)
121
131
  end
@@ -0,0 +1,23 @@
1
+ class Reference
2
+ include Mongoid::Document
3
+ include Mongoid::Attributes::Dynamic
4
+ embedded_in :entry
5
+ field :type, type: String
6
+ field :referenced_type, type: String
7
+ field :referenced_id
8
+
9
+ def resolve_reference
10
+ entry.record.entries.find do |e|
11
+ e.class.to_s == referenced_type &&
12
+ e.identifier.to_s == referenced_id.to_s
13
+ end
14
+ end
15
+
16
+ def resolve_referenced_id
17
+ resolved_reference = entry.record.entries.find do |e|
18
+ e.class.to_s == referenced_type &&
19
+ e.identifier == referenced_id
20
+ end
21
+ self.referenced_id = resolved_reference.id.to_s
22
+ end
23
+ end
@@ -1,12 +1,13 @@
1
1
  module HealthDataStandards
2
2
  module SVS
3
3
  class ValueSet
4
-
5
4
  include Mongoid::Document
6
- include Mongoid::Attributes::Dynamic
7
5
  field :oid, type: String
8
6
  field :display_name, type: String
9
7
  field :version, type: String
8
+ field :user_id, type: String # Eventually we need to delete this from bundles when exporting
9
+
10
+ belongs_to :bundle, class_name: "HealthDataStandards::CQM::Bundle", inverse_of: :value_sets
10
11
 
11
12
  index({oid: 1})
12
13
  index({display_name: 1})
@@ -43,7 +44,7 @@ module HealthDataStandards
43
44
  code_system_name = HealthDataStandards::Util::CodeSystemHelper::CODE_SYSTEMS[con["codeSystem"]] || con["codeSystemName"]
44
45
  Concept.new(code: con["code"],
45
46
  code_system_name: code_system_name,
46
- code_system_version: con["code_system_version"],
47
+ code_system_version: con["codeSystemVersion"],
47
48
  display_name: con["displayName"], code_system: con["codeSystem"])
48
49
  end
49
50
  vs.concepts = concepts
@@ -4,7 +4,7 @@ module HealthDataStandards
4
4
  HealthDataStandards.logger = Rails.logger
5
5
  end
6
6
  rake_tasks do
7
- Dir[File.join(File.dirname(__FILE__),'tasks/*.rake')].each { |f| load f }
7
+ require_relative "tasks"
8
8
  end
9
9
 
10
10
  end
@@ -0,0 +1 @@
1
+ Dir[File.join(File.dirname(__FILE__),'tasks/*.rake')].each { |f| load f }
@@ -1,4 +1,6 @@
1
1
  require 'health-data-standards'
2
+ require 'highline/import'
3
+ require 'open-uri'
2
4
 
3
5
  db_name = ENV['DB_NAME'] || 'test'
4
6
 
@@ -20,6 +22,86 @@ namespace :bundle do
20
22
 
21
23
  end
22
24
 
25
+ desc %{ Download measure/test deck bundle.
26
+ options
27
+ nlm_user - the nlm username to authenticate to the server - will prompt is not supplied
28
+ nlm_passwd - the nlm password for authenticating to the server - will prompt if not supplied
29
+ version - the version of the bundle to download. This will default to the version
30
+
31
+ example usage:
32
+ rake bundle:download nlm_name=username nlm_passwd=password version=2.1.0-latest
33
+ }
34
+ task :download => :environment do
35
+ nlm_user = ENV["nlm_user"]
36
+ nlm_passwd = ENV["nlm_pass"]
37
+ measures_dir = File.join(Dir.pwd, "bundles")
38
+
39
+ while nlm_user.nil? || nlm_user == ""
40
+ nlm_user = ask("NLM Username?: "){ |q| q.readline = true }
41
+ end
42
+
43
+ while nlm_passwd.nil? || nlm_passwd == ""
44
+ nlm_passwd = ask("NLM Password?: "){ |q| q.echo = false
45
+ q.readline = true }
46
+ end
47
+
48
+ bundle_version = ENV["version"] || "latest"
49
+ @bundle_name = "bundle-#{bundle_version}.zip"
50
+
51
+ puts "Downloading and saving #{@bundle_name} to #{measures_dir}"
52
+ # Pull down the list of bundles and download the version we're looking for
53
+ bundle_uri = "https://demo.projectcypress.org/bundles/#{@bundle_name}"
54
+ bundle = nil
55
+
56
+ tries = 0
57
+ max_tries = 10
58
+ last_error = nil
59
+ while bundle.nil? && tries < max_tries do
60
+ tries = tries + 1
61
+ begin
62
+ bundle = open(bundle_uri, :proxy => ENV["http_proxy"],:http_basic_authentication=>[nlm_user, nlm_passwd] )
63
+ rescue OpenURI::HTTPError => oe
64
+ last_error = oe
65
+ if oe.message == "401 Unauthorized"
66
+ puts "Please check your credentials and try again"
67
+ break
68
+ end
69
+ rescue => e
70
+ last_error = e
71
+ sleep 0.5
72
+ end
73
+ end
74
+
75
+ if bundle.nil?
76
+ puts "An error occured while downloading the bundle"
77
+ raise last_error if last_error
78
+ end
79
+ # Save the bundle to the measures directory
80
+ FileUtils.mkdir_p measures_dir
81
+ FileUtils.mv(bundle.path, File.join(measures_dir, @bundle_name))
82
+
83
+ end
84
+
85
+ desc %{ Download and install the measure/test deck bundle. This is essientally delegating to the bundle_download and bundle:import tasks
86
+ options
87
+ nlm_user - the nlm username to authenticate to the server - will prompt is not supplied
88
+ nlm_passwd - the nlm password for authenticating to the server - will prompt if not supplied
89
+ version - the version of the bundle to download. This will default to the version
90
+ delete_existing - delete any existing bundles with the same version and reinstall - default is false - will cause error if same version already exists
91
+ update_measures - update any existing measures with the same hqmf_id to those contained in this bundle.
92
+ Will only work for bundle versions greater than that of the installed version - default is false
93
+ type - type of measures to be installed from bundle. A bundle may have measures of different types such as ep or eh. This will constrain the types installed, defautl is all types
94
+ example usage:
95
+ rake budnle:download_and_install nlm_name=username nlm_passwd=password version=2.1.0-latest type=ep
96
+ }
97
+ task :download_and_install => [:download] do
98
+ de = ENV['delete_existing'] || false
99
+ um = ENV['update_measures'] || false
100
+ puts "Importing bundle #{@bundle_name} delete_existing: #{de} update_measures: #{um} type: #{ENV['type'] || 'ALL'}"
101
+ task("bundle:import").invoke("bundles/#{@bundle_name}",de, um , ENV['type'], 'true')
102
+ end
103
+
104
+
23
105
 
24
106
  desc 'List bundles'
25
107
  task :list => [:environment] do
@@ -30,7 +112,7 @@ namespace :bundle do
30
112
 
31
113
 
32
114
  desc 'Import a quality bundle into the database.'
33
- task :import, [:bundle_path, :delete_existing, :update_measures, :type, :create_indexes, :exclude_results] => [:environment] do |task, args|
115
+ task :import, [:bundle_path, :delete_existing, :update_measures, :type, :create_indexes, :exclude_results] => :environment do |task, args|
34
116
  raise "The path to the measures zip file must be specified" unless args.bundle_path
35
117
  options = {:delete_existing => (args.delete_existing == "true"),
36
118
  :type => args.type,
@@ -48,7 +130,7 @@ namespace :bundle do
48
130
  value_sets: bundle_contents.value_sets.count}
49
131
 
50
132
  if (args.create_indexes != 'false')
51
- ::Rails.application.eager_load!
133
+ ::Rails.application.eager_load! if defined? Rails
52
134
  ::Mongoid::Tasks::Database.create_indexes
53
135
  end
54
136
 
@@ -2,7 +2,10 @@ require 'rest_client'
2
2
  require 'uri'
3
3
  module HealthDataStandards
4
4
  module Util
5
- class VSApi
5
+ class VSNotFoundError < StandardError
6
+ end
7
+
8
+ class VSApi
6
9
  attr_accessor :api_url, :ticket_url, :username, :password
7
10
 
8
11
  def initialize(ticket_url, api_url, username, password)
@@ -12,7 +15,7 @@ module HealthDataStandards
12
15
  @password = password
13
16
  end
14
17
 
15
- def get_valueset(oid, effective_date=nil, include_draft=false, &block)
18
+ def get_valueset(oid, effective_date=nil, include_draft=false, &block)
16
19
  params = {id: oid, ticket: get_ticket}
17
20
  params[:effectiveDate] = effective_date if effective_date
18
21
  params[:includeDraft] = 'yes' if include_draft
@@ -23,24 +26,54 @@ module HealthDataStandards
23
26
 
24
27
  def process_valuesets(oids, effective_date=nil, &block)
25
28
  oids.each do |oid|
26
- vs = get_valueset(oid,effective_date)
27
- yield oid,vs
29
+ vs = get_valueset(oid,effective_date)
30
+ yield oid,vs
28
31
  end
29
32
  end
30
33
 
31
34
  def proxy_ticket
32
- @proxy_ticket ||= get_proxy_ticket
35
+ @proxy_ticket ||= get_proxy_ticket
33
36
  end
34
37
 
35
38
  def get_proxy_ticket
36
39
  # the content type is set and the body is a string becuase the NLM service does not support urlencoded content and
37
40
  # throws an error on that contnet type
38
- RestClient.post ticket_url, {username: username, password: password}
41
+ RestClient.post ticket_url, {username: username, password: password}
39
42
  end
40
-
43
+
41
44
  def get_ticket
42
45
  RestClient.post "#{ticket_url}/#{proxy_ticket}", {service: "http://umlsks.nlm.nih.gov"}
43
46
  end
44
47
  end
48
+
49
+ class VSApiV2 < VSApi
50
+ def initialize(ticket_url, api_url, username, password)
51
+ super(ticket_url, api_url, username, password)
52
+ end
53
+
54
+ def get_valueset(oid, options = {}, &block)
55
+ version = options.fetch(:version, nil)
56
+ include_draft = options.fetch(:include_draft, false)
57
+ params = {id: oid, ticket: get_ticket}
58
+ params[:version] = version if version
59
+ params[:includeDraft] = 'yes' if include_draft
60
+ begin
61
+ vs = RestClient.get api_url, {:params=>params}
62
+ rescue RestClient::ResourceNotFound
63
+ raise VSNotFoundError, "No ValueSet found for oid '#{oid}'"
64
+ end
65
+ yield oid,vs if block_given?
66
+ vs
67
+ end
68
+
69
+ def process_valuesets(oids, options = {}, &block)
70
+ version = options.fetch(:version, nil)
71
+ include_draft = options.fetch(:include_draft, false)
72
+ oids.each do |oid|
73
+ vs = get_valueset(oid, version: version, include_draft: include_draft)
74
+ yield oid,vs
75
+ end
76
+ end
77
+ end
45
78
  end
46
79
  end
@@ -0,0 +1,23 @@
1
+ module HealthDataStandards
2
+ module Validate
3
+ module BaseValidator
4
+
5
+ def build_error(msg, loc, file_name)
6
+ ValidationError.new(message: msg, location: loc, file_name: file_name, validator: @name)
7
+ end
8
+
9
+ def get_document(input)
10
+ doc = case input
11
+ when File
12
+ input.read
13
+ when Nokogiri::XML::Document
14
+ return input
15
+ else
16
+ input
17
+ end
18
+ Nokogiri::XML(doc.to_s) { |conf| conf.strict.nonet.noblanks } #grumble, grumble nokogiri java @SS
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,85 @@
1
+ module HealthDataStandards
2
+ module Validate
3
+ class DataValidator
4
+ include BaseValidator
5
+
6
+ HL7_QRDA_OIDS = ["2.16.840.1.113883.3.221.5",
7
+ "2.16.840.1.113883.3.88.12.3221.8.7",
8
+ "2.16.840.1.113883.3.88.12.3221.8.9",
9
+ "2.16.840.1.113883.1.11.12839",
10
+ "2.16.840.1.113883.3.88.12.3221.8.11",
11
+ "2.16.840.1.113883.3.88.12.3221.6.2",
12
+ "2.16.840.1.113883.11.20.9.40",
13
+ "2.16.840.1.113883.11.20.9.23",
14
+ "2.16.840.1.113883.3.88.12.3221.7.4",
15
+ "2.16.840.1.113883.11.20.9.18",
16
+ "2.16.840.1.113883.11.20.9.22",
17
+ "2.16.840.1.113883.1.11.16866",
18
+ "2.16.840.1.113883.1.11.20275",
19
+ "2.16.840.1.113883.11.20.9.34",
20
+ "2.16.840.1.113883.3.88.12.3221.7.2",
21
+ "2.16.840.1.113883.3.88.12.80.17",
22
+ "2.16.840.1.113883.3.88.12.80.22",
23
+ "2.16.840.1.113883.3.88.12.80.64",
24
+ "2.16.840.1.113883.3.88.12.3221.6.8",
25
+ "2.16.840.1.113883.1.11.78",
26
+ "2.16.840.1.113883.11.20.9.25",
27
+ "2.16.840.1.113883.11.20.9.39",
28
+ "2.16.840.1.113883.3.88.12.80.32",
29
+ "2.16.840.1.113883.11.20.9.21",
30
+ "2.16.840.1.113883.3.88.12.80.68",
31
+ "2.16.840.1.113883.1.11.20.12",
32
+ "2.16.840.1.113883.11.20.9.24",
33
+ "2.16.840.1.113883.11.20.9.41",
34
+ "2.16.840.1.113883.1.11.16926",
35
+ "2.16.840.1.113883.1.11.12212",
36
+ "2.16.840.1.113883.1.11.19185",
37
+ "2.16.840.1.113883.1.11.14914",
38
+ "2.16.840.1.114222.4.11.837",
39
+ "2.16.840.1.113883.1.11.19563",
40
+ "2.16.840.1.113883.1.11.11526",
41
+ "2.16.840.1.113883.11.20.9.20",
42
+ "2.16.840.1.113883.3.88.12.80.2",
43
+ "2.16.840.1.113883.3.88.12.80.63",
44
+ "2.16.840.1.113883.1.11.12249",
45
+ "2.16.840.1.113883.1.11.1",
46
+ "2.16.840.1.113883.1.11.12199",
47
+ "2.16.840.1.113883.11.20.9.33",
48
+ "2.16.840.1.114222.4.11.1066",
49
+ "2.16.840.1.113883.1.11.19579"]
50
+
51
+ def initialize(bundle, measure_ids)
52
+ @bundle = bundle
53
+ measures = @bundle.measures.in(hqmf_id: measure_ids)
54
+
55
+ @oids = measures.collect{|m| m.oids}.flatten.uniq + HL7_QRDA_OIDS
56
+ end
57
+
58
+ def validate(file, options={})
59
+ doc = get_document(file)
60
+
61
+ doc.xpath("//*[@sdtc:valueSet]").inject([]) do |errors, node|
62
+ oid = node.at_xpath("@sdtc:valueSet")
63
+ vs = @bundle.value_sets.where({"oid" => oid}).first
64
+ code = node.at_xpath("@code")
65
+ code_system = node.at_xpath("@codeSystem")
66
+ null_flavor = node.at_xpath("@nullFlavor")
67
+ if !vs
68
+ errors << build_error("The valueset #{oid} declared in the document cannot be found", node.path, options[:file_name])
69
+ elsif !@oids.include?(oid.value)
70
+ errors << build_error("File appears to contain data criteria outside that required by the measures. Valuesets in file not in measures tested #{oid}'",
71
+ node.path, options[:file_name])
72
+ elsif vs.concepts.where({"code" => code, "code_system"=>code_system}).count() == 0
73
+ if !null_flavor
74
+ errors << build_error("The code #{code} in codeSystem #{code_system} cannot be found in the declared valueset #{oid}",
75
+ node.path, options[:file_name])
76
+ end
77
+ end
78
+ errors
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+
85
+ end