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.
- data/README.md +3 -1
- data/Rakefile +2 -0
- data/lib/health-data-standards.rb +10 -0
- data/lib/health-data-standards/export/cat_3.rb +24 -0
- data/lib/health-data-standards/export/html.rb +2 -1
- data/lib/health-data-standards/import/bundle/importer.rb +178 -146
- data/lib/health-data-standards/import/cat1/diagnosis_active_importer.rb +0 -5
- data/lib/health-data-standards/import/cat1/diagnosis_inactive_importer.rb +1 -6
- data/lib/health-data-standards/import/cat1/diagnostic_study_order_importer.rb +0 -4
- data/lib/health-data-standards/import/cat1/ecog_status_importer.rb +12 -0
- data/lib/health-data-standards/import/cat1/encounter_order_importer.rb +0 -5
- data/lib/health-data-standards/import/cat1/lab_order_importer.rb +1 -5
- data/lib/health-data-standards/import/cat1/lab_result_importer.rb +18 -0
- data/lib/health-data-standards/import/cat1/medication_active_importer.rb +27 -0
- data/lib/health-data-standards/import/cat1/patient_importer.rb +54 -46
- data/lib/health-data-standards/import/cat1/procedure_performed_importer.rb +28 -0
- data/lib/health-data-standards/import/cat1/symptom_active_importer.rb +12 -0
- data/lib/health-data-standards/import/cda/condition_importer.rb +2 -0
- data/lib/health-data-standards/import/cda/encounter_importer.rb +6 -0
- data/lib/health-data-standards/import/cda/procedure_importer.rb +1 -0
- data/lib/health-data-standards/import/cda/section_importer.rb +27 -5
- data/lib/health-data-standards/models/cda_identifier.rb +17 -0
- data/lib/health-data-standards/models/cqm/aggregate_objects.rb +92 -0
- data/lib/health-data-standards/models/cqm/measure.rb +51 -30
- data/lib/health-data-standards/models/cqm/query_cache.rb +64 -0
- data/lib/health-data-standards/models/entry.rb +1 -1
- data/lib/health-data-standards/models/record.rb +28 -3
- data/lib/health-data-standards/models/svs/value_set.rb +20 -0
- data/lib/health-data-standards/tasks/bundle.rake +18 -8
- data/lib/health-data-standards/util/hqmf_template_helper.rb +6 -2
- data/lib/health-data-standards/util/hqmf_template_oid_map.json +4 -0
- data/lib/health-data-standards/util/vs_api.rb +9 -6
- data/lib/hqmf-model/data_criteria.json +10 -10
- data/lib/hqmf-parser.rb +0 -2
- data/lib/hqmf-parser/1.0/range.rb +21 -9
- data/templates/c32/show.c32.erb +1 -1
- data/templates/cat1/_2.16.840.1.113883.10.20.24.3.4.cat1.erb +1 -0
- data/templates/cat1/_2.16.840.1.113883.10.20.24.3.40.cat1.erb +2 -1
- data/templates/cat1/_2.16.840.1.113883.10.20.24.3.44.cat1.erb +1 -0
- data/templates/cat1/_2.16.840.1.113883.10.20.24.3.46.cat1.erb +1 -1
- data/templates/cat1/_2.16.840.1.113883.10.20.24.3.54.cat1.erb +16 -1
- data/templates/cat1/_2.16.840.1.113883.10.20.24.3.62.cat1.erb +1 -1
- data/templates/cat1/_2.16.840.1.113883.10.20.24.3.69.cat1.erb +7 -2
- data/templates/cat1/_2.16.840.1.113883.10.20.24.3.76.cat1.erb +2 -1
- data/templates/cat1/_medication_details.cat1.erb +4 -4
- data/templates/cat1/_record_target.cat1.erb +2 -2
- data/templates/cat1/_result_value.cat1.erb +1 -1
- data/templates/cat1/show.cat1.erb +2 -2
- data/templates/cat3/_continuous_variable_value.cat3.erb +20 -0
- data/templates/cat3/_measure_data.cat3.erb +126 -0
- data/templates/cat3/_performance_rate.cat3.erb +16 -0
- data/templates/cat3/_supplemental_data.cat3.erb +36 -0
- data/templates/cat3/show.cat3.erb +157 -0
- data/templates/ccda/show.ccda.erb +1 -1
- metadata +25 -45
- 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
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
35
|
-
|
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
|
@@ -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 :
|
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
|
-
|
19
|
-
|
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: #{
|
25
|
-
puts "\t Test Patients Loaded: #{
|
26
|
-
puts "\t Extensions Loaded: #{
|
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
|
-
|
19
|
+
kv_pair = template_id_map.find {|k, v| v['definition'] == definition &&
|
20
20
|
v['status'] == status &&
|
21
21
|
v['negation'] == negation}
|
22
|
-
|
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,
|
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
|
-
|
16
|
-
|
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
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
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
|
-
|
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
|