health-data-standards 3.0.6 → 3.1.0

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