health-data-standards 3.0.6 → 3.1.0

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 (56) hide show
  1. data/README.md +3 -1
  2. data/Rakefile +2 -0
  3. data/lib/health-data-standards.rb +10 -0
  4. data/lib/health-data-standards/export/cat_3.rb +24 -0
  5. data/lib/health-data-standards/export/html.rb +2 -1
  6. data/lib/health-data-standards/import/bundle/importer.rb +178 -146
  7. data/lib/health-data-standards/import/cat1/diagnosis_active_importer.rb +0 -5
  8. data/lib/health-data-standards/import/cat1/diagnosis_inactive_importer.rb +1 -6
  9. data/lib/health-data-standards/import/cat1/diagnostic_study_order_importer.rb +0 -4
  10. data/lib/health-data-standards/import/cat1/ecog_status_importer.rb +12 -0
  11. data/lib/health-data-standards/import/cat1/encounter_order_importer.rb +0 -5
  12. data/lib/health-data-standards/import/cat1/lab_order_importer.rb +1 -5
  13. data/lib/health-data-standards/import/cat1/lab_result_importer.rb +18 -0
  14. data/lib/health-data-standards/import/cat1/medication_active_importer.rb +27 -0
  15. data/lib/health-data-standards/import/cat1/patient_importer.rb +54 -46
  16. data/lib/health-data-standards/import/cat1/procedure_performed_importer.rb +28 -0
  17. data/lib/health-data-standards/import/cat1/symptom_active_importer.rb +12 -0
  18. data/lib/health-data-standards/import/cda/condition_importer.rb +2 -0
  19. data/lib/health-data-standards/import/cda/encounter_importer.rb +6 -0
  20. data/lib/health-data-standards/import/cda/procedure_importer.rb +1 -0
  21. data/lib/health-data-standards/import/cda/section_importer.rb +27 -5
  22. data/lib/health-data-standards/models/cda_identifier.rb +17 -0
  23. data/lib/health-data-standards/models/cqm/aggregate_objects.rb +92 -0
  24. data/lib/health-data-standards/models/cqm/measure.rb +51 -30
  25. data/lib/health-data-standards/models/cqm/query_cache.rb +64 -0
  26. data/lib/health-data-standards/models/entry.rb +1 -1
  27. data/lib/health-data-standards/models/record.rb +28 -3
  28. data/lib/health-data-standards/models/svs/value_set.rb +20 -0
  29. data/lib/health-data-standards/tasks/bundle.rake +18 -8
  30. data/lib/health-data-standards/util/hqmf_template_helper.rb +6 -2
  31. data/lib/health-data-standards/util/hqmf_template_oid_map.json +4 -0
  32. data/lib/health-data-standards/util/vs_api.rb +9 -6
  33. data/lib/hqmf-model/data_criteria.json +10 -10
  34. data/lib/hqmf-parser.rb +0 -2
  35. data/lib/hqmf-parser/1.0/range.rb +21 -9
  36. data/templates/c32/show.c32.erb +1 -1
  37. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.4.cat1.erb +1 -0
  38. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.40.cat1.erb +2 -1
  39. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.44.cat1.erb +1 -0
  40. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.46.cat1.erb +1 -1
  41. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.54.cat1.erb +16 -1
  42. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.62.cat1.erb +1 -1
  43. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.69.cat1.erb +7 -2
  44. data/templates/cat1/_2.16.840.1.113883.10.20.24.3.76.cat1.erb +2 -1
  45. data/templates/cat1/_medication_details.cat1.erb +4 -4
  46. data/templates/cat1/_record_target.cat1.erb +2 -2
  47. data/templates/cat1/_result_value.cat1.erb +1 -1
  48. data/templates/cat1/show.cat1.erb +2 -2
  49. data/templates/cat3/_continuous_variable_value.cat3.erb +20 -0
  50. data/templates/cat3/_measure_data.cat3.erb +126 -0
  51. data/templates/cat3/_performance_rate.cat3.erb +16 -0
  52. data/templates/cat3/_supplemental_data.cat3.erb +36 -0
  53. data/templates/cat3/show.cat3.erb +157 -0
  54. data/templates/ccda/show.ccda.erb +1 -1
  55. metadata +25 -45
  56. data/lib/hqmf-parser/value_sets/value_set_parser.rb +0 -241
@@ -1,36 +1,57 @@
1
1
  module HealthDataStandards
2
2
  module CQM
3
+ class Measure
4
+ include Mongoid::Document
5
+ store_in collection: 'measures'
6
+ field :id, type: String
7
+ field :sub_id, type: String
8
+ field :name, type: String
9
+ field :subtitle, type: String
10
+ field :short_subtitle, type: String
11
+ field :hqmf_id, type: String
12
+ field :hqmf_set_id, type: String
13
+ field :hqmf_version_number, type: String
14
+ field :nqf_id, type: String
15
+ field :type, type: String
16
+ field :category, type: String
17
+ field :population_ids , type: Hash
18
+ field :oids, type: Array
19
+ field :data_criteria, type: Array
3
20
 
4
- class Measure
5
- include Mongoid::Document
6
- store_in collection: 'measures'
7
- field :id, type: String
8
- field :sub_id, type: String
9
- field :name, type: String
10
- field :subtitle, type: String
11
- field :short_subtitle, type: String
12
- field :hqmf_id, type: String
13
- field :hqmf_set_id, type: String
14
- field :hqmf_version_number, type: String
15
- field :nqf_id, type: String
16
- field :type, type: String
17
- field :category, type: String
18
- field :population_ids , type: Hash
19
- field :oids, type: Array
20
- field :data_criteria, type: Array
21
- scope :top_level_by_type , ->(type){where({"type"=> type}).any_of({"sub_id" => nil}, {"sub_id" => "a"})}
22
- scope :top_level , any_of({"sub_id" => nil}, {"sub_id" => "a"})
23
- scope :order_by_id_sub_id, order_by([["id", :asc],["sub_id", :asc]])
24
- index({oids: 1})
25
- index({hqmf_id: 1})
26
- index({category: 1})
27
- index "bundle_id" => 1
28
-
29
- validates_presence_of :id
30
- validates_presence_of :name
31
-
21
+ scope :top_level_by_type , ->(type){where({"type"=> type}).any_of({"sub_id" => nil}, {"sub_id" => "a"})}
22
+ scope :top_level , any_of({"sub_id" => nil}, {"sub_id" => "a"})
23
+ scope :order_by_id_sub_id, order_by([["id", :asc],["sub_id", :asc]])
32
24
 
25
+ index({oids: 1})
26
+ index({hqmf_id: 1})
27
+ index({category: 1})
28
+ index "bundle_id" => 1
29
+
30
+ validates_presence_of :id
31
+ validates_presence_of :name
33
32
 
34
- end
35
- end
33
+ def self.categories
34
+ pipeline = []
35
+ pipeline << {'$group' => {_id: "$id",
36
+ name: {"$first" => "$name"},
37
+ description: {"$first" => "$description"},
38
+ sub_ids: {'$push' => "$sub_id"},
39
+ subs: {'$push' => {"sub_id" => "$sub_id", "short_subtitle" => "$short_subtitle"}},
40
+ category: {'$first' => "$category"}}}
41
+
42
+ pipeline << {'$group' => {_id: "$category",
43
+ measures: {'$push' => {"id" => "$_id",
44
+ 'name' => "$name",
45
+ 'description' => "$description",
46
+ 'subs' => "$subs",
47
+ 'sub_ids' => "$sub_ids"
48
+ }}}}
49
+
50
+ pipeline << {'$project' => {'category' => '$_id', 'measures' => 1, '_id' => 0}}
51
+
52
+ pipeline << {'$sort' => {"category" => 1}}
53
+ Mongoid.default_session.command(aggregate: 'measures', pipeline: pipeline)['result']
54
+ end
55
+ end
56
+ end
36
57
  end
@@ -0,0 +1,64 @@
1
+ module HealthDataStandards
2
+ module CQM
3
+ class QueryCache
4
+ include Mongoid::Document
5
+ store_in collection: 'query_cache'
6
+
7
+ field :measure_id, type: String
8
+ field :sub_id, type: String
9
+ field :population_ids, type: Hash
10
+ field :effective_date, type: Integer
11
+ field :IPP, type: Integer
12
+ field :DENOM, type: Integer
13
+ field :NUMER, type: Integer
14
+ field :antinumerator, type: Integer
15
+ field :DENEX, type: Integer
16
+ field :DENEXCEP, type: Integer
17
+ field :MSRPOPL, type: Integer
18
+ field :OBSERV, type: Float
19
+ field :supplemental_data, type: Hash
20
+
21
+ def self.aggregate_measure(measure_id, effective_date, test_id=nil)
22
+ cache_entries = self.where(effective_date: effective_date, measure_id: measure_id, test_id: test_id)
23
+ aggregate_count = AggregateCount.new
24
+ aggregate_count.measure_id = measure_id
25
+ cache_entries.each do |cache_entry|
26
+ if cache_entry.is_stratification?
27
+ stratification = Stratification.new
28
+ stratification.populations = cache_entry.build_populations
29
+ stratification.id = cache_entry.population_ids['stratification']
30
+ aggregate_count.stratifications << stratification
31
+ else
32
+ aggregate_count.top_level_populations = cache_entry.build_populations
33
+ if cache_entry.supplemental_data.present?
34
+ aggregate_count.supplemental_data = cache_entry.supplemental_data
35
+ end
36
+ end
37
+ end
38
+ aggregate_count
39
+ end
40
+
41
+ def is_stratification?
42
+ population_ids.has_key?('stratification')
43
+ end
44
+
45
+ def is_cv?
46
+ population_ids.has_key?('MSRPOPL')
47
+ end
48
+
49
+ def build_populations
50
+ populations = []
51
+ population_ids.each do |population_type, population_id|
52
+ unless population_type == 'stratification'
53
+ population = Population.new
54
+ population.type = population_type
55
+ population.id = population_id
56
+ population.value = self[population_type]
57
+ populations << population
58
+ end
59
+ end
60
+ populations
61
+ end
62
+ end
63
+ end
64
+ end
@@ -6,7 +6,7 @@ class Entry
6
6
  # embedded_in :entry_list, polymorphic: true
7
7
 
8
8
  embedded_in :record
9
-
9
+ embeds_one :cda_identifier, class_name: "CDAIdentifier"
10
10
  embeds_many :values, class_name: "ResultValue"
11
11
 
12
12
  field :description, type: String
@@ -1,5 +1,6 @@
1
1
  class Record
2
2
  include Mongoid::Document
3
+ extend Memoist
3
4
 
4
5
  field :title, type: String
5
6
  field :first, type: String
@@ -30,7 +31,11 @@ class Record
30
31
  embeds_many :medications
31
32
  embeds_many :procedures
32
33
  embeds_many :results, class_name: "LabResult"
33
- embeds_many :social_history, class_name: "Entry"
34
+ embeds_many :socialhistories, class_name: "Entry"
35
+
36
+ alias :social_history :socialhistories
37
+ alias :social_history= :socialhistories=
38
+
34
39
  embeds_many :vital_signs
35
40
  embeds_many :support
36
41
  embeds_many :advance_directives, class_name: "Entry"
@@ -47,7 +52,7 @@ class Record
47
52
 
48
53
  scope :by_provider, ->(prov, effective_date) { (effective_date) ? where(provider_queries(prov.id, effective_date)) : where('provider_performances.provider_id'=>prov.id) }
49
54
  scope :by_patient_id, ->(id) { where(:medical_record_number => id) }
50
-
55
+
51
56
  def providers
52
57
  provider_performances.map {|pp| pp.provider }
53
58
  end
@@ -67,10 +72,30 @@ class Record
67
72
  end
68
73
  matching_entries_by_section.flatten
69
74
  end
75
+
76
+ memoize :entries_for_oid
70
77
 
71
78
  alias :clinical_trial_participant :clinicalTrialParticipant
72
79
  alias :clinical_trial_participant= :clinicalTrialParticipant=
73
-
80
+
81
+ # Removed duplicate entries from a section based on id. This method may
82
+ # lose information because it does not compare entries based on clinical
83
+ # content
84
+ def dedup_section!(section)
85
+ unique_entries = self.send(section).uniq do |entry|
86
+ if entry.respond_to?(:cda_identifier) && entry.cda_identifier.present?
87
+ entry.cda_identifier
88
+ else
89
+ entry.id
90
+ end
91
+ end
92
+ self.send("#{section}=", unique_entries)
93
+ end
94
+
95
+ def dedup_record!
96
+ Record::Sections.each {|section| self.dedup_section!(section)}
97
+ end
98
+
74
99
  private
75
100
 
76
101
  def self.provider_queries(provider_id, effective_date)
@@ -69,6 +69,26 @@ module HealthDataStandards
69
69
  code_set_name
70
70
  end
71
71
  end
72
+ def self.denormalize_code_set_name(code_set_name)
73
+ case code_set_name
74
+ when'RxNorm'
75
+ 'RXNORM'
76
+ when'ICD-9-CM'
77
+ 'ICD9CM'
78
+ when'ICD-10-CM'
79
+ 'ICD10CM'
80
+ when'ICD-10-PCS'
81
+ 'ICD10PCS'
82
+ when'SNOMED-CT'
83
+ 'SNOMEDCT'
84
+ when'CDC Race'
85
+ 'CDCREC'
86
+ when'HL7 Healthcare Service Location'
87
+ 'HSLOC'
88
+ else
89
+ code_set_name
90
+ end
91
+ end
72
92
  end
73
93
  end
74
94
  end
@@ -4,26 +4,36 @@ db_name = ENV['DB_NAME'] || 'test'
4
4
 
5
5
  namespace :bundle do
6
6
  desc 'Import a quality bundle into the database.'
7
- task :import, [:bundle_path, :delete_existing, :update_measures, :type] => [:environment] do |task, args|
7
+ task :import, [:bundle_path, :delete_existing, :update_measures, :type, :create_indexes] => [:environment] do |task, args|
8
8
  raise "The path to the measures zip file must be specified" unless args.bundle_path
9
9
  options = {:delete_existing => (args.delete_existing == "true"),
10
- :type => args.type ,
10
+ :type => args.type,
11
11
  :update_measures => (args.update_measures == "true")
12
12
  }
13
13
 
14
14
  bundle = File.open(args.bundle_path)
15
15
  importer = HealthDataStandards::Import::Bundle::Importer
16
16
  bundle_contents = importer.import(bundle, options)
17
-
18
- ::Rails.application.eager_load!
19
- ::Rails::Mongoid.create_indexes
17
+
18
+ counts = {measures: bundle_contents.measures.count,
19
+ records: bundle_contents.records.count,
20
+ extensions: bundle_contents[:extensions].count,
21
+ value_sets: bundle_contents.value_sets.count}
22
+
23
+ if (args.create_indexes != 'false')
24
+ ::Rails.application.eager_load!
25
+ ::Rails::Mongoid.create_indexes
26
+ end
20
27
 
21
28
  puts "Successfully imported bundle at: #{args.bundle_path}"
22
29
  puts "\t Imported into environment: #{Rails.env.upcase}" if defined? Rails
23
30
  puts "\t Loaded #{args.type || 'all'} measures"
24
- puts "\t Measures Loaded: #{bundle_contents.measures.count}"
25
- puts "\t Test Patients Loaded: #{bundle_contents.records.count}"
26
- puts "\t Extensions Loaded: #{bundle_contents[:extensions].count}"
31
+ puts "\t Sub-Measures Loaded: #{counts[:measures]}"
32
+ puts "\t Test Patients Loaded: #{counts[:records]}"
33
+ puts "\t Extensions Loaded: #{counts[:extensions]}"
34
+ puts "\t Value Sets Loaded: #{counts[:value_sets]}"
35
+
36
+
27
37
  end
28
38
 
29
39
  # this task is most likely temporary. Once Bonnie can handle both EP and EH measures together, this would no longer be required.
@@ -16,10 +16,14 @@ module HealthDataStandards
16
16
  end
17
17
 
18
18
  def self.template_id_by_definition_and_status(definition, status, negation=false)
19
- pairs = template_id_map.select {|k, v| v['definition'] == definition &&
19
+ kv_pair = template_id_map.find {|k, v| v['definition'] == definition &&
20
20
  v['status'] == status &&
21
21
  v['negation'] == negation}
22
- pairs.keys.first if pairs.present?
22
+ if kv_pair
23
+ kv_pair.first
24
+ else
25
+ nil
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -223,6 +223,10 @@
223
223
  "definition":"medication",
224
224
  "status":"discharge",
225
225
  "negation":false},
226
+ "2.16.840.1.113883.3.560.1.200":{
227
+ "definition":"medication",
228
+ "status":"discharge",
229
+ "negation":true},
226
230
  "2.16.840.1.113883.3.560.1.13":{
227
231
  "definition":"medication",
228
232
  "status":"active",
@@ -3,24 +3,27 @@ require 'uri'
3
3
  module HealthDataStandards
4
4
  module Util
5
5
  class VSApi
6
- attr_accessor :api_url, :ticket_url, :username, :password
6
+ attr_accessor :api_url, :ticket_url, :username, :password
7
7
 
8
- def initialize(ticket_url, api_url,username,password)
8
+ def initialize(ticket_url, api_url, username, password)
9
9
  @api_url = api_url
10
10
  @ticket_url = ticket_url
11
11
  @username = username
12
12
  @password = password
13
13
  end
14
14
 
15
- def get_valueset(oid,&block)
16
- vs = RestClient.get api_url, {:params=>{id: oid, ticket: get_ticket}}
15
+
16
+ def get_valueset(oid, effective_date=nil, &block)
17
+ params = {id: oid, ticket: get_ticket}
18
+ params[:effectiveDate] = effective_date if effective_date
19
+ vs = RestClient.get api_url, {:params=>params}
17
20
  yield oid,vs if block_given?
18
21
  vs
19
22
  end
20
23
 
21
- def process_valuesets(oids, &block)
24
+ def process_valuesets(oids, effective_date=nil, &block)
22
25
  oids.each do |oid|
23
- vs = get_valueset(oid)
26
+ vs = get_valueset(oid,effective_date)
24
27
  yield oid,vs
25
28
  end
26
29
  end
@@ -483,16 +483,16 @@
483
483
  "qds_data_type":"medication_administered",
484
484
  "not_supported":false},
485
485
  "medication_discharge": {
486
- "title":"medication, discharge",
487
- "category":"medications",
488
- "definition":"medication_discharge",
489
- "status":"discharge",
490
- "sub_category":"discharge",
491
- "hard_status":true,
492
- "patient_api_function":"allMedications",
493
- "standard_category":"medication",
494
- "qds_data_type":"medication_discharge",
495
- "not_supported":false},
486
+ "title":"medication, discharge",
487
+ "category":"medications",
488
+ "definition":"medication_discharge",
489
+ "status":"discharge",
490
+ "sub_category":"discharge",
491
+ "hard_status":true,
492
+ "patient_api_function":"allMedications",
493
+ "standard_category":"medication",
494
+ "qds_data_type":"medication_discharge",
495
+ "not_supported":false},
496
496
  "medication_adverse_effects":{
497
497
  "title":"medication, adverse effects",
498
498
  "category":"medications",
data/lib/hqmf-parser.rb CHANGED
@@ -49,8 +49,6 @@ require_relative 'hqmf-parser/converter/pass1/simple_population_criteria'
49
49
  require_relative 'hqmf-parser/converter/pass2/comparison_converter'
50
50
  require_relative 'hqmf-parser/converter/pass2/operator_converter'
51
51
 
52
- require_relative 'hqmf-parser/value_sets/value_set_parser'
53
-
54
52
  require_relative 'hqmf-parser/parser'
55
53
 
56
54
  require_relative 'hqmf-generator/hqmf-generator'
@@ -4,8 +4,20 @@ module HQMF1
4
4
  class Value
5
5
  include HQMF1::Utilities
6
6
 
7
- def initialize(entry)
7
+ def initialize(entry, inclusive=nil)
8
8
  @entry = entry
9
+
10
+ if (inclusive.nil?)
11
+ case attr_val('./@inclusive')
12
+ when 'true'
13
+ @inclusive = true
14
+ else
15
+ @inclusive = false
16
+ end
17
+ else
18
+ @inclusive = inclusive
19
+ end
20
+
9
21
  end
10
22
 
11
23
  def value
@@ -17,12 +29,7 @@ module HQMF1
17
29
  end
18
30
 
19
31
  def inclusive?
20
- case attr_val('./@inclusive')
21
- when 'true'
22
- true
23
- else
24
- false
25
- end
32
+ @inclusive
26
33
  end
27
34
 
28
35
  def to_json
@@ -40,6 +47,11 @@ module HQMF1
40
47
  if @entry
41
48
  @low = optional_value('./cda:low')
42
49
  @high = optional_value('./cda:high')
50
+ if (@low == nil && @high == nil)
51
+ @low = optional_value('.',true)
52
+ @high = optional_value('.',true)
53
+ puts "\tfound = relationship parsing temporal reference (bugfix)"
54
+ end
43
55
  end
44
56
  end
45
57
 
@@ -52,10 +64,10 @@ module HQMF1
52
64
 
53
65
  private
54
66
 
55
- def optional_value(xpath)
67
+ def optional_value(xpath, inclusive=nil)
56
68
  value_def = @entry.at_xpath(xpath)
57
69
  if value_def
58
- Value.new(value_def)
70
+ Value.new(value_def, inclusive)
59
71
  else
60
72
  nil
61
73
  end